[Solved] Initialising parent portion of an inherited class using hump

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
SunnyCloudySkies
Prole
Posts: 5
Joined: Sun Aug 16, 2020 9:48 am

[Solved] Initialising parent portion of an inherited class using hump

Post by SunnyCloudySkies »

Hi all! I seem to have a problem inheriting the 'parent' portion of a class using class.lua (hump). Here is the relevant code:

For the parent class:

Code: Select all

Npc = Class{}

function Npc:init(map, spritesheet, width, height, xPos, yPos)
    self.walking_speed = 90

    -- supports npc placement
    self.x = xPos or math.floor(VIRTUAL_WIDTH / 2)
    self.y = yPos or math.floor(VIRTUAL_HEIGHT / 2)
    self.width = width or 25
    self.height = height or 32

    -- supporting flipping to change direction
    self.xOffset = math.floor(self.width / 2)
    self.yOffset = math.floor(self.width / 2)

    -- link player to map
    self.map = map

    -- states
    self.state = 'idle'

    -- determines sprite flipping
    self.direction = 'left'

    -- x and y velocity
    self.dx = 0
    self.dy = 0

    -- grab player spritesheet
    self.texture = love.graphics.newImage(spritesheet)

    -- animation handling
    self.frames = {}
    self.quadFrames = generateQuads(self.texture, self.width, self.height)
    self.currentFrame = nil

    self.animations = {
        ['idle'] = Animation({
            texture = self.texture,
            frames = {
                self.quadFrames[1]
            }
        }),
        ['walking_down'] = Animation({
            texture = self.texture,
            frames = {
                self.quadFrames[4],
                self.quadFrames[1],
                self.quadFrames[2],
                self.quadFrames[3],
                self.quadFrames[3]
            },
            interval = 0.2
        }),
        ['walking_up'] = Animation({
            texture = self.texture,
            frames = {
                self.quadFrames[5],
                self.quadFrames[6],
                self.quadFrames[7],
                self.quadFrames[8],
                self.quadFrames[8]
            },
            interval = 0.2
        }),
        ['walking_sideways'] = Animation({
            texture = self.texture,
            frames = {
                self.quadFrames[10],
                self.quadFrames[9],
                self.quadFrames[12],
                self.quadFrames[11]
            },
            interval = 0.16
        })
    }

    -- starting animation
    self.animation = self.animations['idle']
    self.currentFrame = self.animation:getCurrentFrame()

    -- behaviour map that we can call based on movement and player state
    self.behaviours = {
        ['idle'] = function(dt)

            if self.dy > 0 then
                self.dy = self.walking_speed
                self.state = 'walking_down'
                self.animations['walking_down']:restart()
                self.animation = self.animations['walking_down']
            elseif self.dy < 0 then
                self.dy = -self.walking_speed
                self.state = 'walking_up'
                self.animations['walking_up']:restart()
                self.animation = self.animations['walking_up']
            elseif self.dx < 0 then
                self.dx = -self.walking_speed
                self.state = 'walking_sideways'
                self.animations['walking_sideways']:restart()
                self.animation = self.animations['walking_sideways']
                self.direction = 'left'
            elseif self.dx > 0 then
                self.dx = self.walking_speed
                self.state = 'walking_sideways'
                self.animations['walking_sideways']:restart()
                self.animation = self.animations['walking_sideways']
                self.direction = 'right'
            else
                self.dy = 0
            end

        end,
        ['walking_down'] = function(dt)

            if self.dy > 0 then
                self.dy = self.walking_speed
            else
                self.dy = 0
                self.state = 'idle'
                self.animation = self.animations['idle']
            end
        end,
        ['walking_up'] = function(dt)

            if self.dy < 0 then
                self.dy = -self.walking_speed
            else
                self.dy = 0
                self.state = 'idle'
                self.animation = self.animations['idle']
            end
        end,
        ['walking_sideways'] = function(dt)
            if self.dx < 0 then
                self.dx = -self.walking_speed
                self.direction = 'left'
            elseif self.dx > 0 then
                self.dx = self.walking_speed
                self.direction = 'right'
            else
                self.dx = 0
                self.state = 'idle'
                self.animation = self.animations['idle']
            end

        end
    }

end

---add methods here...
For the subclass:

Code: Select all

require 'classes/Npc'
Player = Class{__includes = Npc}

function Player:init(map, spritesheet, width, height, xPos, yPos)
    Npc:init(map, spritesheet, width, height, xPos, yPos)
    self.hp = 100
    self.mana = 100

end

function Player:directionChange()
    if love.keyboard.isDown('down') then
        self.dy =  self.walking_speed
        self.dx = 0
    elseif love.keyboard.isDown('up') then
        self.dy = -self.walking_speed
        self.dx = 0
    elseif love.keyboard.isDown('left') then
        self.dx = -self.walking_speed
        self.dy = 0
    elseif love.keyboard.isDown('right') then
        self.dx = self.walking_speed
        self.dy = 0
    else
        self.dx = 0
        self.dy = 0
    end

end

-- updates the player over time
function Player:update(dt)
    self:directionChange()
    self.behaviours[self.state](dt)
    self.animation:update(dt)
    self.currentFrame = self.animation:getCurrentFrame()
    self.x = self.x + self.dx * dt
    self.y = self.y + self.dy * dt
end

Here's the issue: I'm trying to add more instance variables onto the inherited class, but I want to keep the instance variables from the parent class as well. class.lua's documentation says to use class.init(object, ...) as "derived classes should use this function their constructors to initialize the parent class(es) portions of the object", but I can't seem to get this to work. I would be very grateful for any advice!

Some background: I'm very new to programming/lua/love2d and am trying to learn OOP via game development, so I apologise if the terminology I used here was incorrect/didn't make any sense! I hope I still got my message across nonetheless, if you need clarification please ask. If I did use incorrect terminology, feel free to correct me: it will be a great learning experience.
Last edited by SunnyCloudySkies on Sun Aug 23, 2020 12:15 pm, edited 1 time in total.
User avatar
pgimeno
Party member
Posts: 3656
Joined: Sun Oct 18, 2015 2:58 pm

Re: Initialising parent portion of an inherited class using hump

Post by pgimeno »

Can you post a simple self-contained example demonstrating the issue? The simpler the better.
SunnyCloudySkies
Prole
Posts: 5
Joined: Sun Aug 16, 2020 9:48 am

Re: Initialising parent portion of an inherited class using hump

Post by SunnyCloudySkies »

pgimeno wrote: Sat Aug 22, 2020 11:30 pm Can you post a simple self-contained example demonstrating the issue? The simpler the better.
Yes, sure!
Here is a simple example using objects that print text to the screen:
Parent class:

Code: Select all

NewText = Class()

function NewText:init(text)
    self.text = text
end

function NewText:render()
    love.graphics.printf(self.text, 200, 20, 200, 'center')
end
Inherited Class:

Code: Select all

require 'NewText'
NewerText = Class{__includes = NewText}

function NewerText:init(text1, text2)
    NewText:init(text1)
    self.text2 = text2
end

function NewerText:render()

    love.graphics.printf(self.text, 200, 20, 200, 'center')
    love.graphics.printf(self.text2, 200, 50, 200, 'center')

end
main.lua:

Code: Select all

--[[
    Personal project: Yo-caves
    Author: Amelia-N

    Refer to readme for more.
]]

push = require 'push'
Class = require 'class'
require 'NewerText'

newestText = NewerText("Hello!")

-- close resolution to NES but 16:9
VIRTUAL_WIDTH = 432
VIRTUAL_HEIGHT = 243

-- actual window resolution
WINDOW_WIDTH = 1280
WINDOW_HEIGHT = 720

function love.load()
    love.graphics.setDefaultFilter("nearest", "nearest")

    -- sets up virtual screen resolution for an authentic retro feel
    push:setupScreen(VIRTUAL_WIDTH, VIRTUAL_HEIGHT, WINDOW_WIDTH, WINDOW_HEIGHT, {
        fullscreen = false,
        resizable = true
    })
    

    love.window.setTitle('Hello World')

    love.keyboard.keysPressed = {}
    love.keyboard.keysReleased = {}
end

function love.resize(w, h)
    push:resize(w, h)
    
end

function love.keyboard.wasPressed(key)
    if (love.keyboard.keysPressed[key]) then
        return true
    else
        return false
    end
end

function love.keyboard.wasReleased(key)
    if (love.keyboard.keysReleased[key]) then
        return true
    else
        return false
    end
end

function love.keypressed(key)
    if key == 'escape' then
        love.event.quit()
    end

    love.keyboard.keysPressed[key] = true
end

function love.keyreleased(key)
    love.keyboard.keysReleased[key] = true
end

function love.update(dt)

    -- reset all keys pressed and released this frame
    love.keyboard.keysPressed = {}
    love.keyboard.keysReleased = {}
end

function love.draw()
    -- begin virtual resolution drawing
    push:apply('start')

    love.graphics.setColor(1, 1, 1, 1)
    
    newestText:render()

    -- end virtual resolution
    push:apply('end')
end
The error I get: Error

NewerText.lua:11: bad argument #1 to 'printf' (string expected, got nil)


Traceback

[C]: in function 'printf'
NewerText.lua:11: in function 'render'
main.lua:84: in function 'draw'
[C]: in function 'xpcall'

Here, I used a simple object with its only method being to print text. My inherited class overrides the one and only method from the parent class, but in the first code I sent I wanted to keep some of the parent's methods while also changing some of the old ones, and that worked out fine.

The issue was when I tried to also use the parent's 'self' variables, it doesn't work out quite how I expected. In this example, I wanted to inherit self.text from the parent as well as adding self.text2 to the inherited object, but I'm quite confused as to how you would implement this :? Is there any way of doing that using hump? (I'm not sure about the exact terminology, if any, of this)

Thank you for your reply, and any advice would be nice! :ultrahappy:
User avatar
pgimeno
Party member
Posts: 3656
Joined: Sun Oct 18, 2015 2:58 pm

Re: Initialising parent portion of an inherited class using hump

Post by pgimeno »

Thanks. So I've distilled it to this (which is what I understand by "simple test case"):

Code: Select all

local Class = require 'class'

local NewText = Class()

function NewText:init(text)
  self.text = text
end

local NewerText = Class{__includes = NewText}

function NewerText:init(text1, text2)
  NewText:init(text1)
  self.text2 = text2
end

function NewerText:render()
  print("NewerText:render() says " .. self.text .. self.text2)
end

local instNewerText = NewerText('Hello, ', 'World!');

instNewerText:render()
The problem is with this line:

Code: Select all

  NewText:init(text1)
That's not how you call the init constructor. If you review the hump docs about init, note it says:
class.init(object, ...)

Arguments:
  • object (Object) – The object. Usually self.
  • ... (mixed) – Arguments to pass to the constructor.
Returns:
  • Whatever the parent class constructor returns.
Calls class constructor of a class on an object.

Derived classes should use this function [in] their constructors to initialize the parent class(es) portions of the object.
Notice it doesn't say class:init(...) which is how you're using it. It says: class.init(object, ...) where the object is usually 'self'. Pay attention to dot vs colon.

So, you should have used this instead:

Code: Select all

  NewText.init(self, text1)
SunnyCloudySkies
Prole
Posts: 5
Joined: Sun Aug 16, 2020 9:48 am

Re: Initialising parent portion of an inherited class using hump

Post by SunnyCloudySkies »

@pgimeno Ah! That makes so much sense! Thank you so much for your help :awesome:
Post Reply

Who is online

Users browsing this forum: No registered users and 3 guests