Experimental code structure critism and help!

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
User avatar
scissors61
Citizen
Posts: 76
Joined: Fri Jan 08, 2016 10:16 am

Experimental code structure critism and help!

Post by scissors61 »

It has been more than a year since I started studying löve and Lua. After learning some basics, I created two prototypes of game ideas I had. Only the basic mechanics, nothing releasable. But soon after that I got frustrated and stopped working on my projects. I think that the reason for that frustration was bad coding practices and bad code structure, so after not coding for a long time I recently began studying different abstract concepts and programming principles to create a better code structure. So what better thing to do than to take everything and put it in a blender? That's what I'm doing, and I would like to hear how am I implementing this recently obtained knowledge. BTW it was hard for me at my level, and it took me a lot of experimenting. So it might be weird and maybe just plain wrong.

I'm using an ECS but mixed with OOP to create classes. I'm violating the ECS principle of "keep data separated from systems" because I like the concept of polymorphism and to have a wide variety of behaviors with just an ECS, I'd need to create too many systems and keep track of how they relate to each entity. Being able just to overwrite methods when creating instances seems easier to me. Besides, I feel more comfortable organizing the behavior in an object-like approach, like saying: this thing will do this!

My end goal is to be able to design the whole game by just creating instances of classes and turning them into entities that will already have set where they are going to act (gamestate, draw, update, etc.) and everything.

Anyway, would like to hear your opinion and criticism before getting into a large project with this experiment, and if you could help me a little implementing it. I'm missing creating classes for gamestates. I want to turn the gamestate into an entity (trying this makes me feel like if I were in the movie inception).

In the first attachment (template), everything was perfect!, the only thing missing was to add the gamestates, and then things began to fall apart (templateGamestate), I might have to give up on this experiment due to my inability of adding gamestates.
Attachments
templateGamestate.love
(1.54 MiB) Downloaded 78 times
template.love
(1.54 MiB) Downloaded 68 times
User avatar
ivan
Party member
Posts: 1911
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Experimental code structure critism and help!

Post by ivan »

OOP and ECS are design paradigms - both have their advantages, disadvantages and subtleties.
Years ago, when I discovered these through tutorials I was super enthusiastic and naively though: "I really want to use X paradigm in my game!"
My end goal is to be able to design the whole game by just creating instances of classes and turning them into entities that will already have set where they are going to act (gamestate, draw, update, etc.) and everything
This is where you are losing sight of the important things - you want to make a game, right?
OK so if you've never made a game of the same genre, it's better to start from the bottom-up:
start pragmatically by writing the shortest and most simple Lua code required to get your game up and running.
Designing from the top-down is only for people with a lot of experience (in games of the same genre).
Once you have a few RPGs, platformers or whatever under your belt you can decide ahead of time: 'OK, this paradigm is clearly best suited for an RPG'. Unfortunately, for most of us - 99% of the time, we need to start from the bottom-up.
What I'm saying is, you don't want to make your game fit around a paradigm - just because it looks good on paper.
Regardless of your approach: pragmatism and simplicity are important - people don't realize how hard it is to keep the code simple.
Briefly going over the "template.love" file, I see a lot of code that looks repetitive (for example mainMenu.lua, systems.lua and sharedSystem.lua).
I'm sure there's a reason for this but to be honest, it just looks like the thing is on crutches.
And I don't write this just to be mean - you want to be pragmatic with the code and keep it as succinct as possible, regardless if you use OOP, ECS or whatever. Good luck!
User avatar
scissors61
Citizen
Posts: 76
Joined: Fri Jan 08, 2016 10:16 am

Re: Experimental code structure critism and help!

Post by scissors61 »

Hey Ivan, Thanks for your criticism.
You have a point. I started programming from the bottom-up at first (of course, didn't have any option than to do so), created two prototypes that I couldn't move from prototypes to games due to how horrible the code had become. After not coding for a long time I set myself the condition of coding only with a good structure. But here I am with the problem you pointed out. It has taken me too long to decide on a structure. Maybe as you say, designing from top-down is a privilege for people with experience.

BTW, I think it looks great on paper what I'm trying to do. If I implemented, on paper again, it would make the development much simpler. But maybe it's not something I should attempt to do right now. I'll try to decide soon on how I'll code, and to be more pragmatical, to start making games again.
User avatar
airstruck
Party member
Posts: 650
Joined: Thu Jun 04, 2015 7:11 pm
Location: Not being time thief.

Re: Experimental code structure critism and help!

Post by airstruck »

In my opinion those systems are too general, they should be doing very specific things based on whether components exist. Your systems seem to be doing the job that gamestates should be doing.

Instead of one update system, I'd have many:

Code: Select all

local function updatePositionFromVelocity (e, dt)
    if e.vx and e.vy and e.x and e.y then
        e.x = e.x + e.vx * dt
        e.y = e.y + e.vy * dt
    end
end

local function updateVelocityFromAcceleration (e, dt)
    if e.ax and e.ay and e.vx and e.vy then
        e.vx = e.vx + e.ax * dt
        e.vy = e.vy + e.ay * dt
    end
end
Forgetting about gamestates for now, you'd apply all of your update systems in love.update:

Code: Select all

local entities = {}

function love.update (dt)
    for _, entity in ipairs(entities) do
        updatePositionFromVelocity(entity, dt)
        updateVelocityFromAcceleration(entity, dt)
    end
end
Draw systems could work the same way, for example:

Code: Select all

local function drawPosition (e)
    if e.x and e.y then
        love.graphics.translate(e.x, e.y)
    end
end

local function drawAngle (e)
    if e.angle then
        love.graphics.rotate(e.angle)
    end
end

local function drawSize (e)
    if e.size then
        love.graphics.scale(e.size)
    end
end

local function drawPolygon (e)
    if e.polygon then
        love.graphics.polygon('fill', e.polygon)
    end
end

function love.draw ()
    for _, entity in ipairs(entities) do
        love.graphics.push('all')
        drawPosition(entity)
        drawAngle(entity)
        drawSize(entity)
        drawPolygon(entity)
        love.graphics.pop()
    end
end
As far as input stuff goes, it probably only directly affects a single "player" entity, in which case there's really no reason to put it in a system (where it will need to look at and reject a lot of irrelevant entities). It can live in gamestates instead.

Of course if you have an unusual case where input does directly affect many entities, you could do the same sort of thing as above for love.keypressed and company, or have update systems look at things like love.keyboard.isDown.

As for gamestates, I think you're correct that polymorphism is almost a necessity here. The point of gamestates is to forward all those love.whatever callbacks to a different handler depending on what "screen" the game is on. You want to be able to tell the current gamestate to "draw" or "update" and you don't care how it happens, it's completely up to the gamestate.

So, you might have something like this:

Code: Select all

local currentState

function love.update (...) return currentState.update and currentState:update(...) end
function love.draw (...) return currentState.draw and currentState:draw(...) end
function love.keypressed (...) return currentState.keypressed and currentState:keypressed(...) end
I agree that it makes sense to implement those states as classes. The constructor should initialize everything the state object needs.

Code: Select all

local PlayState = Class() -- hypothetical class library

function PlayState:init (options) -- the constructor
    self.options = options
    self.entities = {}
end
Now you can move the code that fires off your systems into your gamestate.

Code: Select all

function PlayState:update (dt)
    for _, entity in ipairs(self.entities) do
        updatePositionFromVelocity(entity, dt)
        updateVelocityFromAcceleration(entity, dt)
    end
end
I think you'll get the polymorphism benefits you're looking for here. For example a TimeAttackState:update and a ScoreAttackState:update would fire off slightly different sets of systems, and probably a TitleScreenState:update or PauseMenuState:update wouldn't fire off any systems at all, unless you're playing a demo or doing something unconventional like using ECS for UI (I know you've been experimenting with that, it's an interesting idea).

Note that I'm not suggesting this because of a purist view about mixing OOP with ECS, I just don't think "gamestate stuff" is a job systems or entities need to be doing. Using special gamestate-managing systems might actually be a workable idea (treating gamestates as entities and callbacks as components), but I wouldn't pass all those update / draw / etc. callbacks through to the entities; by doing that instead of using specialized systems as described above you're missing out on the benefits of ECS.

There are other legitimate cases where you might want methods on your entities; for example you might want a bullet-firing system that looks for a method named "fire" and calls it if it exists. This is more or less in line with ECS as long as you consider that "fire" method to be a component, and of course you get polymorphism because each entity can implement that function however it wants (shotgun guy can do one thing, chaingun guy can do another). Or, you could have a "weapon" component that is an object, with weapon:fire. Just keep in mind you can't serialize functions (easily), so it might cause trouble for networked games or savegames. That's where being strict about "pure data components" can be useful.
User avatar
scissors61
Citizen
Posts: 76
Joined: Fri Jan 08, 2016 10:16 am

Re: Experimental code structure critism and help!

Post by scissors61 »

Hey airstruck! Thanks for your input suggestions. And I'm glad to see you participating in this topic, after all, the ECS in this code is thanks to your library.

About my use of systems being too general.
The intention of this is to avoid having to tell instances where they are going to work. If I create an instance I won't have to tell it: instance:draw(), instance:update(), instance:mousepressed(). I just create it, and the systems will check if it has a _load, _update, or _draw component, to make it work. That's why they are so general. The motive behind this is laziness. Once the structure is set, the rest will be just about creating classes and instances. Actually, this is just OOP and using an ECS as some sort of executioner or wrapper of instances.

I thought about this when I was trying to wrap love.physices with your ECS library. I had to create too many components to not use a class for it. Also, love.physics has many ways that can be modified, so one would have to create too many systems for manipulating a physical body (change gravity, linear velocity, angle, etc). That for me is more stressing than creating the data with its function at the same time and in the same place. I had functions for manipulating a physical body in draw systems, load systems, keypressed systems, mousepressed systems, etc.

This is a subjective concern, and what I want to achieve can somehow be made with a pure ECS, but I find this experiment much more natural and simpler to me. Of course, I might not be able to achieve what I'm trying to do, and might have to return to a more pure ECS (as you are suggesting me) or OOP after this.

About using systems for gamestates.
I'm using hump for gamestates, and never thought of replacing it with your library until I read your post. And even though you are suggesting me not to do it, it doesn't sound so bad. I could just add the generic systems to main.lua, have a variable pointing to which gamestate the game is currently in, and only the entities with the matching gamestate component will be executed by the system. But I like that idea. The problem with this is that I won't have access to the love.load function.

I also like your idea of creating classes for gamestates that are not part of the ECS. I didn't know how to pass the love functions to handlers so your example is going to come in handy. I'll try to implement it. Anything that I'll come up I'm going to post it here.
User avatar
scissors61
Citizen
Posts: 76
Joined: Fri Jan 08, 2016 10:16 am

Re: Experimental code structure critism and help!

Post by scissors61 »

I somehow managed to do what I intended. It might not work in the long run, but at least it works now, it's just an experiment.

I reduced some of the repetitive code as Ivan pointed.

airstuck: I couldn't implement your suggested gamestate class due to not having enough knowledge on how love handlers work. I ended up using systems and a variable to manage gamestates, plus resetting the game by executing love.load(), it seems to be working fine by now.

I like it. It looks like I won't have to deal with gamestates files or telling each instance where it's going to work, let's see how it works.
Attachments
template.love
(1.53 MiB) Downloaded 69 times
Post Reply

Who is online

Users browsing this forum: No registered users and 38 guests