Object oriented round system/combining subclasses help

I am trying to create Round/“Stage” objects by combining a map with a game mode, but I am having trouble daisy-chaining it together or imagining anything else.

I think the Stage should be a superclass that gives methods/signals like :RoundStart() :RoundEnd(), Game mode modules should set up objectives, win conditions, and map modules should set up parts and connect to signals from the game mode.

To really simplify my approach, something like

local map = require(crossroads)
local gamemode = require(ctf)

local stage = map.new(gamemode)
--map modules
local map = require(StageSuperClass).new() --stage class as the base
map.__index = map
map.Name= "Crossroads"

function map.new(gamemode)
     local newmap = gamemode.new()
     setmetatable(newmap, map)
     return newmap
end
--mode modules
local mode = {}
mode.__index = mode
mode.GameMode = "Capture the Flag"

function mode.new()
     local gamemode= {}
     setmetatable(gamemode, mode)
     return gamemode
end

I know this currently doesn’t retain the game mode. Not sure where to go. previously the map just directly got the game mode without .new() before I realized it was overwriting the entire module everytime.
Its easy to imagine map/mode as subclasses of stage, but not subclasses of eachother. Not sure how to combine. What do you think of this attempt, would you have done it differently?

1 Like

Personally, I wouldn’t use OOP for a round system unless it’s very complicated, but that’s just my preference. If it works, and is easy to debug, then your fine.

1 Like

I don’t think you should have the module script handle and change the game, keep the module script as something to compose from thru other normal scripts and not have it change when you use it:

local map = require(StageSuperClass).new() --stage class as the base
map.__index = map
map.Name= "Crossroads"

function map.new(gamemode)
     return setmetatable(gamemode.new(), map)
end
1 Like

With OOP avoid using subclasses/inheritance

Because of this:

That is the diamond problem, I talked about it here like @JamminRedPandaMan said and why I moved on to use ECS instead (Though it has other problems like too many components and my bad organization because I’m new to ECS and didn’t plan my system).

If you are still going with an OOP approach consider componentizing it instead. Discussion here.

Instead of a IsA relationship you use HasA relationship maybe something like this?.

map.__index = map

function map.new(gamemode, mapSettings)
     local newmap= map.new()
     setmetatable(newmap, map)
     local mode = mode.new(mapSettings)
if mode == "CaptureTheFlag" then
self:AddFlagToMap()
end
     return newmap
end

Each map has a mode which can modify the map object.

Honestly either way, just functionalize everything make it easy to copy and paste when you need to rework the system eventually.

1 Like

Okay, lets break this down.

So, you have three modules:

  1. Stage - It is basically an interface for the round itself. It also contains the map and the gamemode.
  2. Gamemode - Contains the rules of the game as well as checks, rewards, etc.
  3. Map - Sets up the map, clones the parts from a backup, fixes up all the RBXScriptSignals.

Now, daisy-chaining them would be kind of difficult, which is why I suggest you don’t do that. As dthecoolest (forgive me if I’m misinterpreting this) is suggesting, instead of having the gamemode → map → stage, you could have the stage contain an instance of both.

E.g.

Stage:
- map : Map
- gamemode : Gamemode

Stage.new(gamemode):
 - self.gamemode = gamemode
 - self.map = gamemode.createMap()

CaptureTheFlagGamemode extends Gamemode
 - createMap:
   - return CaptureTheFlagMap.new()

Here, CaptureTheFlagGamemode extends the Gamemode class, and it has a createMap function, which returns an instance of CaptureTheFlagMap. Once the round starts, the stage would probably call an init function on the map, and the gamemode which can the do the setting up of the timer, rules, and the map itself.

But this is just an example, I am very bad at explaining, so sorry if I didn’t exactly get what you mean, or if I didn’t explain well, but this is just one way it can be done.

An alternative is you can handle all of it in one Gamemode object, but you can carry the same principle of storing an instance of it rather than extending it.

2 Likes