## Managing multiple class objects at same time

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Krumpet
Prole
Posts: 12
Joined: Thu Dec 03, 2020 4:59 am

### Managing multiple class objects at same time

What is the best way to structure a situation where a game has multiple instances of an object at the same time? I'm building a Space Invaders clone and have used two classes to manage the enemies: EnemyFormation and Enemy. The EnemyFormation class sets self.enemy property to a table of Enemy instances.

I would prefer to have these two classes be combined, but I'm stuck on the fact that such a class would need to hold an array of itself...which I'm unsure of how to accomplish. What is the preferred way to handle such a situation when working with classes?

I'm using Matthias Richter's Hump class library.

Thanks!

Here is my EnemyFormation class...

Code: Select all

EnemyFormation = Class{}

function EnemyFormation:init()
self.x = 10
self.y = 10
self.rows = 4
self.cols = 8
self.xMin = 0
self.xMax = self.cols * 40 - 20
self.yMin = 0
self.yMax = self.rows * 30 - 10
self.dx = 30
self.spacing = 2
self.enemy = self:generateEnemyFormation(self.cols, self.rows, self.spacing)
self.timer = 0
self.stepTime = 1
self.accelerator = 0.9
self.xStep = (VIRTUAL_WIDTH - (ENEMY_WIDTH * (self.spacing * (self.cols - 1) + 1))) / 20
self.yStep = 10
self.edgeFlag = false
self.stepFlag = true
self.width = self.cols * 40 - 20
self.enemyLasers = {}
end

function EnemyFormation:update(dt)
self.timer = self.timer + dt

self.xMin = self:xMinCheck()
self.xMax = self:xMaxCheck()

-- Simple timer to "step" the enemyFormation across screen and toggle edgeFlag
if self.timer > self.stepTime then
if not self.edgeFlag then
self.x = self.x + self.xStep
elseif self.edgeFlag then
self.y = self.y + self.yStep
self.stepTime = self.stepTime * self.accelerator
self.edgeFlag = false
end

self:fireEnemyLaser()
self.timer = 0
self.stepFlag = not self.stepFlag
end

for key, laser in pairs(self.enemyLasers) do
laser:update(dt)
end

-- Track position of bounding box to move Enemy instances as a group vs. individually
if self.x + self.xMax >= VIRTUAL_WIDTH then
self.edgeFlag = true
self.xStep = -self.xStep
self.x = VIRTUAL_WIDTH - self.xMax - 1
elseif self.x + self.xMin < 0 then
self.edgeFlag = true
self.xStep = -self.xStep
self.x = 1 - self.xMin
end
end

-- Pass x, y coordinates of the enemyFormation box for Enemy instances to use as a reference point.
function EnemyFormation:render()
for key, enemy in ipairs(self.enemy) do
if enemy.isActive then
enemy:render(self.x, self.y, self.stepFlag)
end
end

for key, laser in pairs(self.enemyLasers) do
if laser.y > VIRTUAL_HEIGHT then
table.remove(self.enemyLasers, key)
end
laser:render()
end
end

-- Function to generate all enemy instances within the formation
function EnemyFormation:generateEnemyFormation()
local enemies = {}
for row=1, self.rows do
for col=1, self.cols do
table.insert(enemies, Enemy(12, 12, row, col, self.spacing))
end
end
return enemies
end

-- Function to determine the leftmost x position of enemy formation
function EnemyFormation:xMinCheck()
for col=1, self.cols do
for row=1, self.rows do
if self.enemy[(col - 1) + (row - 1) * self.cols + 1].isActive then
return ((col - 1) * 40)
end
end
end
end

-- Function to determine the rightmost x position of enemy formation
function EnemyFormation:xMaxCheck()
for col=self.cols, 1, -1 do
for row=self.rows, 1, -1 do
if self.enemy[(col - 1) + (row - 1) * self.cols + 1].isActive then
return (col * 40) - 20
end
end
end
end

function EnemyFormation:fireEnemyLaser()
local shooter = self:selectShooter()
local laser = Laser(shooter.xOffset + shooter.width * 0.5 + self.x, shooter.yOffset + shooter.height + self.y, 1)
laser.isVisible = true
table.insert(self.enemyLasers, laser)
end

function EnemyFormation:selectShooter()
while true do
local col = math.random(self.cols)
for row = self.rows, 1, -1 do
if self.enemy[(row-1) * self.cols + col].isActive then
return self.enemy[(row-1) * self.cols + col]
end
end
end
end

Here is my Enemy class...

Code: Select all

Enemy = Class{}

function Enemy:init(width, height, row, col, spacing)
self.xOffset = (col - 1) * ENEMY_WIDTH * spacing
self.yOffset = (row - 1) * ENEMY_HEIGHT * spacing
self.width = width
self.height = height
self.isActive = true
self.isShooter = false
end

function Enemy:render(formationX, formationY, stepFlag)
if stepFlag then
love.graphics.setColor( gColorPalette['yellow'], 1)
else
love.graphics.setColor( gColorPalette['lgreen'], 1)
end
love.graphics.rectangle("fill", formationX + self.xOffset, formationY + self.yOffset, self.width, self.height)
end

Edited for typos
ReFreezed
Party member
Posts: 217
Joined: Sun Oct 25, 2015 11:32 pm
Location: Sweden
Contact:

### Re: Managing multiple class objects at same time

Is there an issue with how you're currently handling the situation? As I understand it, EnemyFormation represents a group of enemies and thus is a different "thing" from what Enemy represents, i.e. an enemy. That separation into two classes seem logical. I'm not sure what you mean by combining these two things?
Tools: Hot Particles, LuaPreprocess, (more)
Games: Momento Temporis: Light from the Deep, Energize!
"If each mistake being made is a new one, then progress is being made."
Krumpet
Prole
Posts: 12
Joined: Thu Dec 03, 2020 4:59 am

### Re: Managing multiple class objects at same time

The issue is I would prefer to manage the enemies with a single class. I worry that using two classes is amateur hour and that I'm not seeing an obvious way to accomplish the same results with one class.

It isn't that what I have doesn't work. It works. I'm trying to understand if my approach could be more "professional" in its design.
ReFreezed
Party member
Posts: 217
Joined: Sun Oct 25, 2015 11:32 pm
Location: Sweden
Contact:

### Re: Managing multiple class objects at same time

Well, you can always get rid of the Enemy class and just have the enemies be represented by "plain old tables" (i.e. basically move the Enemy methods/functionality to the EnemyFormation class, and think of the enemies as just data instead of individual "objects"). It's a good idea in general to keep things simple, and thinking about data is often easier than trying to combine both data and functionality (in classes).

In other words, remove the concept of an individual enemy (except inside the EnemyFormation class where needed) but keep the concept of multiple enemies.

There's no objectively "best" way of doing this though. The real answer is to just do what feels more intuitive to you (which is probably going to change as you get more experience).
Tools: Hot Particles, LuaPreprocess, (more)
Games: Momento Temporis: Light from the Deep, Energize!
"If each mistake being made is a new one, then progress is being made."
Krumpet
Prole
Posts: 12
Joined: Thu Dec 03, 2020 4:59 am

### Re: Managing multiple class objects at same time

What you are suggesting sounds like what I did for my Shelter class. I wanted to have four shelters the player can hide behind. This put me in the exact same situation of having to manage multiple instances. As I mentioned, I wanted to avoid using two classes to do this. I ended up putting the x-, y-coordinates directly in a table in the Shelter class as follows:

Code: Select all

Shelter = Class{}

function Shelter:init()
self.width = 35
self.height = 15
self.x = VIRTUAL_WIDTH / 5
self.y = VIRTUAL_HEIGHT - 50
self.shelters = {{x=self.x - self.width / 2, y=self.y},
{x=self.x * 2 - self.width / 2, y=self.y},
{x=self.x * 3 - self.width / 2, y=self.y},
{x=self.x * 4 - self.width / 2, y=self.y}}
end

function Shelter:render()
love.graphics.setColor(gColorPalette['white'], 1)
for key, shelter in ipairs(self.shelters) do
love.graphics.rectangle("fill", shelter.x, shelter.y, self.width, self.height)
end
end

For me to take this approach with the Enemy class, I would need to generate the table of Enemy instances in the EnemyFormation init function. Is placing logic in an init file ok? Frowned upon? I'm not really sure and it makes the init function sort of messy and that makes me nervous...

Thanks!
ReFreezed
Party member
Posts: 217
Joined: Sun Oct 25, 2015 11:32 pm
Location: Sweden
Contact:

### Re: Managing multiple class objects at same time

Yeah, that Shelter situation does look similar, though I have to ask why a Shelter consists of multiple actual shelters? If there's no relation between the individual shelters then just have each Shelter represent one thing (i.e. have four separate Shelter objects instead of that self.shelters array). Right now, both EnemyFormation and Shelter are like "manager" classes, but the former may have something like an AI that logically connects the "managed objects" while the latter does not need anything like that (unless your game actually does).

Anyway, all logic has to happen at some point, whether it's in the "init" function or later. Putting things in init() doesn't necessarily mean it's messy - it depends on how your game works, or rather, what rules you've put in place for your own code. For example, if your objects are trying to access other objects at init time then maybe it makes sense to put that code in a secondary init function that you call after all objects have been created and are ready to be accessed by others. You can think of it as there being multiple phases when you load a level, for example an "init" phase (where "simple" initialization happen) and an "objects are ready" phase (where more involved things happen).

Similarly, there can also be multiple phases that happen during an update, like a "normal update" phase (where maybe you update velocities/positions and AI etc.), a "physics update" (where physics simulation, including collisions, happens) and an "animation update" (before things are rendered to the screen). If you identify what "phases" your game needs to work then you'll know better where to put code and thus things will be less messy.
Tools: Hot Particles, LuaPreprocess, (more)
Games: Momento Temporis: Light from the Deep, Energize!
"If each mistake being made is a new one, then progress is being made."
Krumpet
Prole
Posts: 12
Joined: Thu Dec 03, 2020 4:59 am

### Re: Managing multiple class objects at same time

I think what you're saying is either approach (a single class or a class with a manager class) is acceptable.

The issue I noticed with this Shelter approach is updating the Shelters (for example when they take damage from the Enemy objects) will be much harder when they aren't individual instances.

I did consider creating four separate shelters in this class, but the idea of hard coding each shelter (Shelter1, Shelter2, etc) didn't sit well with me. Also, I don't know of any way to create a Shelter instance inside the Shelter class. Pretty sure that isn't possible...hence the need for a manager class. All that being said, the manner in which I ended up doing it isn't any better. Where I want to land is if I decide in the future to have five shelters, I want to be able to edit one line of code and have the rest of the game adjust accordingly. Perhaps I'll weave that into the player control options. They can decide whether they want 2, 4, or 6 shelters, for example.

Edited for clarity
ReFreezed
Party member
Posts: 217
Joined: Sun Oct 25, 2015 11:32 pm
Location: Sweden
Contact:

### Re: Managing multiple class objects at same time

I would advice minimizing the usage of manager classes as I think they're usually not very useful - they often just spread out the code. But the "state of things" has to be controlled somewhere, whether it's in a manager class or e.g. directly in PlayState (in reference to your other thread).

I'm not sure I explained very well regarding the shelters. Instead of having each Shelter object contain an array of "actual" shelters, have an array outside the class containing a list of Shelter instances, like this:

Code: Select all

function PlayState:enter(params)
self.shelters = {}

for i = 1, 4 do -- 4 here can be a variable!
local shelter = Shelter()
shelter.x     = something
shelter.y     = something
table.insert(self.shelters, shelter)
end
end

Aiming for a "flatter" game structure usually makes things simpler than having a deeper one (where one thing contains other things that contains other things... etc).

You could do the same with the enemies - have an array of all Enemy instances (from all formations) stored in the PlayState and have each EnemyFormation "reach out" to the PlayState, look for the related Enemy objects and do stuff to them. That does sound a bit messy though and maybe isn't good in this case.

In my games I always have an object representing the world or the level, and then I store all "world objects" (like the player, enemies, projectiles, sound emitters, purely graphical objects etc.) in that object. In your code, PlayState seem to be the equivalent object.
Tools: Hot Particles, LuaPreprocess, (more)
Games: Momento Temporis: Light from the Deep, Energize!
"If each mistake being made is a new one, then progress is being made."
Krumpet
Prole
Posts: 12
Joined: Thu Dec 03, 2020 4:59 am

### Re: Managing multiple class objects at same time

Funny! I was aiming to trim my PlayState file down to the absolute bare minimum in favor of everything living in deeper object relationships. In my head, I'm drawn to such relationships. They seem simpler and more logical to me. However, as I've gone further down this path, I've begun to run into the issues I've mentioned. Tops on the list is this approach leading to decoupled classes that need a way to communicate.

Just like you suggested, I've seen others propose the idea of a "World" object to use as the unifying location for everything. I'm thinking there are (at least) two approaches here. I'll give that some deeper thought.

Thanks for taking the time to weigh in here. Greatly appreciate your time!
Last edited by Krumpet on Sun Dec 13, 2020 2:43 am, edited 1 time in total.
ReFreezed
Party member
Posts: 217
Joined: Sun Oct 25, 2015 11:32 pm
Location: Sweden
Contact:

### Re: Managing multiple class objects at same time

Sure thing!
Tools: Hot Particles, LuaPreprocess, (more)
Games: Momento Temporis: Light from the Deep, Energize!
"If each mistake being made is a new one, then progress is being made."

### Who is online

Users browsing this forum: Bing [Bot] and 5 guests