Access super class field from a method

Hello, I hope I'm not double posting. I searched the forum for an answer to my question bu couldn't find one so I'm making my own post.

I'm used to programming, but this is my first project with Lua and the Playdate SDK, so I'm a bit lost with the OOP and extends mechanisms. (In case you'd like to know, i'm working on a simple space invaders clone to learn to make games on playdate :grin:)

I have an entity class, which is basically a class that draws rectangle at given positions for now:

class('Entity').extends()

function Entity:init(rect, speed, alive)
	self.rect = rect
	self.speed = speed
	self.alive = alive
end

It may not be very elegant, but it works.

What I would like to do, is to create a subclass called Placeholder of this Entity class, that will be able to perform operations to the rect field of Entity.

So I created my subclass:

class('Placeholder').extends(Entity)

function Placeholder:init(rect, speed, alive, padding)
	Placeholder.super.init(self, rect, speed, alive)
	self.base_width = base_width
	self.base_height = base_height
	self.entities = {}
	self.entities_number = 0
	self.horizontal_movement = 1
end

function Placeholder:add_entity()
	x = self.rect.x + self.entities_number * (self.base_width + self.padding)
	y = self.rect.y
	rect = playdate.geometry.rect.new(x, y, self.base_width, self.base_height)
	e = Entity(rect, self.speed, self.alive)
	self.entities[self.entities_number] = e
	self.entities_number += 1
	self.width += self.base_width + self.padding
end

Halas, doing this make my game crash on startup, with the following error:

placeholder.lua:13: attempt to index a nil value (field 'rect')
stack traceback:
    placeholder.lua:13: in method 'add_entity'
    main.lua:52: in main chunk

I think that either accessing a field of the superclass is not possible, either I'm not doing it correctly.

I didn't saw anything that helped in the Object Oriented Programming section of the documentation, so I thought that would be trivial.

Does someone know if what I want to do is doable ?

Thanks you in advance :innocent:

OOP in Lua can definitely take some getting used to! Accessing a member of a superclass is possible. Your problem isn’t that you can’t access self.rect, but that self.rect is nil and you’re attempting to access the x field within that nil value. What does the code you’re using to instantiate your Placeholder look like?

The pattern you’ve set up here is perplexing, so I suspect you might be using a subclass where you don’t actually need to. Making Placeholder a subclass of Entity means that it is a special type of Entity. However, add_entity is instantiating another Entity object separate from self, in what appears more like an attempt at a factory pattern. In other words, as configured here a Placeholder is an Entity itself, but also stores a list of other Entity objects inside it. Is that your intention?

A few other notes:

  • Placeholder:init accepts a padding value which is never set on self, but accessed later in add_entity
  • You should declare your variables as local inside add_entity, as they are global by default.
  • Are base_width and base_height globally defined somewhere? You’re setting them on self in init but they aren’t passed in as parameters.

Thank you for your quick answer !

OOP in Lua can definitely take some getting used to! Accessing a member of a superclass is possible. Your problem isn’t that you can’t access self.rect, but that self.rect is nil and you’re attempting to access the x field within that nil value. What does the code you’re using to instantiate your Placeholder look like?

Okay. I was afraid that rect was considered nil because the Placeholder class have no visibility upon Entity's members. Here is the code that initialize my Placeholder Object:

ennemies_number = 8
ennemy_placeholder_rect = playdate.geometry.rect.new(ennemy_xpos, ennemy_ypos, ennemy_width, ennemy_height)
local ennemy_placeholder = Placeholder(ennemy_placeholder_rect, ennemy_speed, padding)
for i = 1, ennemies_number do
	ennemy_placeholder:add_entity()
end

The pattern you’ve set up here is perplexing, so I suspect you might be using a subclass where you don’t actually need to. Making Placeholder a subclass of Entity means that it is a special type of Entity. However, add_entity is instantiating another Entity object separate from self, in what appears more like an attempt at a factory pattern. In other words, as configured here a Placeholder is an Entity itself, but also stores a list of other Entity objects inside it. Is that your intention?

Yup, that's totally what I want to do (although it might not be the best solution for doing what I'm looking to do). Actually, the Placeholder class is a kind of meta-Entity that will handle several Entities at the same time: When the Placeholder moves, every entity inside moves with the same movement. There are several other methods in the Placeholder class that I have not included in this post, because they are not relevant to my problem. They perform those kind of tasks: move the entities, check if one is colliding...

  • Placeholder:init accepts a padding value which is never set on self, but accessed later in add_entity

Oops, it's a mistake, it should be set in the init function. :person_facepalming:

You should declare your variables as local inside add_entity, as they are global by default.

Yes indeed, I'll do that.

Are base_width and base_height globally defined somewhere? You’re setting them on self in init but they aren’t passed in as parameters.

They were once passed as parameters in the init function, along with x and y coordinates, but I refactored them into the rect parameter. I forgot to change the code that set the base_height and base_width of the object. (I wrote this code too late in the evening, I think I should have gone to bed instead :sweat_smile:)

Anyway, I'm glad to read that I should be able to access the super class' members. I think the morality of this discussion, regarding the numerous typos I left in my code is that I wrote too much code in one go. I should have focused on little bits of code that would have been easier to test.

I'll try to do some debugging to see what causes the rect to have a nil value.

Thank you very much for your help !

2 Likes

Ah! Space invaders…if I’d thought longer about it in the context of that application your nested entity approach would have made more sense. :sweat_smile:

I suspect that the rect you are attempting to construct inside add_entity may be nil because the values you’re passing for its width and height are nil (given that they reference the unset base_height and base_width values). Let us know how it goes!

Also I (and I suspect we all) have been guilty of some late night coding haze and corresponding forum posts. Don’t sweat it!

Not sure if that can be the issue here, but I always call the super init method and return the object itself when using this pattern in Lua. Like so:

function Entity:init(rect, speed, alive)
	Entity.super.init(self)
	self.rect = rect
	self.speed = speed
	self.alive = alive
	return self
end

Hello !

I've managed to correct the mistakes you pointed out in my code, and it has resolved my problem !

Indeed, I think there was something fishy going on with the unset variables that were referenced by the rect object I was try to create.

Here are the the fixed methods of the placeholder class:

function Placeholder:init(rect, speed, alive, padding)
	Placeholder.super.init(self, rect, speed, alive)
	self.entities = {}
	self.entities_number = 0
	self.horizontal_movement = 1
	self.base_width = self.rect.width
	self.base_height = self.rect.height
	self.padding = padding
end

function Placeholder:add_entity()
	local x = self.rect.x + self.entities_number * (self.base_width + self.padding)
	local y = self.rect.y
	local rect = playdate.geometry.rect.new(x, y, self.base_width, self.base_height)
	local e = Entity(rect, self.speed, self.alive)
	-- In lua, for loops won't iterate starting from 0, so we must start to 1.
	self.entities_number += 1
	self.entities[self.entities_number] = e
	self.rect.width += self.base_width + self.padding
end

There was also a problem with the initialisation of the padding field. In my main.lua file, I forgot to pass the 'alive' parameter, so I think my padding variable was used to set the self.alive variable of the super class, and nothing was used to set my self.padding variable... Anyway, it is fixed too.

local ennemy_placeholder_rect = playdate.geometry.rect.new(ennemy_xpos, ennemy_ypos, ennemy_width, ennemy_height)
local ennemy_placeholder = Placeholder(ennemy_placeholder_rect, ennemy_speed, ennemy_alive, padding)
for i = 1, ennemies_number do
	ennemy_placeholder:add_entity()
end

Thank you very much !

Hello, thank you for your answer ! :smile:

Well, I think this would have worked indeed.

However, the issue with your code is that it would have broken the encapsulation principle regarding of the super class member's initialisation.

Because theses fields belong to the super class, I would like them to be initialised by the super class.

I wonder what could happen with a call to the super.init like the one you posted, in the case where self.rect is used to perform something else in the init function. I believe that calling Entity.super.init(self) will set to nil the members of the superclass, and that could trigger an error.