Best practices to structure the code of a love game?

General discussion about LÖVE, Lua, game development, puns, and unicorns.
Post Reply
Titousensei
Prole
Posts: 13
Joined: Mon Aug 20, 2012 11:35 pm

Best practices to structure the code of a love game?

Post by Titousensei »

Hi all,

I'm working on my first "serious" (non-demo) project with love/lua, and I'm wondering if you guys have a preferred way of structuring your code. I'm new to lua and love, but not to programming, and I know from experience that after any project becomes sufficiently large, the code will be a mess unless you follow basic best practices. I couldn't find any wiki page of forum topics on that, so I decided to start one and give you my findings so far. Can you guys comment on my ideas below, and add your own to help me improve my skills?

After a few experiments, I decided to structure my code in different states, with the code for one give state being written each in one lua file. (I also try to hide most variables and functions as local as much as possible.) Each state implements load(), update(dt) and draw(), and the current state variable just calls the function.

My main.lua looks like this:

Code: Select all

require "menu"

local state = nil

function change_state(s)
  state = s
  state.load()
end

function love.load()
  math.randomseed(os.time())
  change_state(menu)
end

function love.draw() state.draw() end
function love.update(dt) state.update(dt) end

function love.keypressed(key)
  if (key == "escape") then
    if (love.event.quit) then
      love.event.quit() -- 0.8
    else
      love.event.push("q") -- 0.7
    end
  else
    state.keypressed(key)
  end
end
Then my menu.lua looks like this:

Code: Select all

require "game"
require "credits"
require "instructions"

menu = {}

function menu.load()
  -- init menu graphics here
end

function menu.draw()
  -- menu draws here
end

function menu.update(dt)
  -- menu animations here
end

function menu.keypressed(key)
  if key =="g" then
    change_state(game)
  elseif key =="c" then
    change_state(credits)
  elseif key =="i" then
    change_state(instructions)
  end
end
This allows me to have a menu screen, a credits screen, an instruction screen, possibly a splash screen and different game modes coded in distinct units. My state machine at this level is thus defined by the different lua source files. I'm quite happy with this method, but I have to maintain global variables to remember stuff between states, so I might use one global table to store data.

Also, I noticed that I often need to switch between different "action modes" within a state. For instance, there's the initial animation of the menu screen, then the wait for a key to be pressed, then an animation to transition to a different state. Since the differences only need to be handled by the update(dt) function, I decided to have one update function for each mode, and switch between them as they finish.

Code: Select all

local update_fn

local function exit_menu_update(dt)
  -- selected item jumps out and screen fades to black
  -- at black, change_state(game)
end

local function wait_menu_update(dt)
  -- here my menu items wiggle around their position
end

local function init_menu_update(dt)
  -- here my menu items move into the screen to their position
  -- when the init animation is finished: update_fn = wait_menu_update
end

function menu.update(dt) update_fn(dt) end

function menu.draw()
  -- draw function is common for all update modes
end

function menu.keypressed(key)
  if update_fn == wait_menu_update then
    update_fn = exit_menu_update
  end
end
I know that all this could be done with a big if/elseif, but I like the cleaner approach that lua's functional nature gives me. This state machine only uses the variable update_fn, so that's what I use to test which action mode I'm in.

Finally, I'm playing with the idea of using a class library. I like the ideas and the implementation of MiddleClass, and I'll probably try to use it soon.

What do you guys think? Did you come across any other useful recipes during your coding?

Thanks.
Santos
Party member
Posts: 384
Joined: Sat Oct 22, 2011 7:37 am

Re: Best practices to structure the code of a love game?

Post by Santos »

Event/observer pattern libraries like beholder.lua, Lua-Event and hump.signal look interesting.

And math.random is already seeded in love.run, just so you know. :ultraglee:

EDIT: Sorry for not answering this question, I'm also interested in the answers people will give and know nothing more about this than what you know.
Last edited by Santos on Tue Aug 21, 2012 9:35 am, edited 2 times in total.
User avatar
Roland_Yonaba
Inner party member
Posts: 1563
Joined: Tue Jun 21, 2011 6:08 pm
Location: Ouagadougou (Burkina Faso)
Contact:

Re: Best practices to structure the code of a love game?

Post by Roland_Yonaba »

You had a good start. I also tend to think that splitting your code in different files, each one for a specific part of the game is a good habit. It makes each part short and easy to maintain.

Working with different states is a good habit, too. But there's already a very good library for that, gamestate. That is part of a larger set, named hump. Have a taste of it, you'll get addicted.
User avatar
bartbes
Sex machine
Posts: 4946
Joined: Fri Aug 29, 2008 10:35 am
Location: The Netherlands
Contact:

Re: Best practices to structure the code of a love game?

Post by bartbes »

Instead of handing you libraries, I decided to actually answer your question:
It depends on how you code.

Of course, now that's over with, I generally use OOP in my games, so from that I usually get a folder structure like this:

Code: Select all

classes/
graphics/
libraries/
sounds/
states/
conf.lua
main.lua
Then, I have an init.lua in classes and states that just loads every other file in there, and I add in a nice caching loader for graphics and sounds, and I start working. Note that even this isn't absolute, if I ever feel a single file is getting too big, I might look for an artificial split.
User avatar
ivan
Party member
Posts: 1918
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Best practices to structure the code of a love game?

Post by ivan »

Hi Titousensei, looks like you're generally on the right path.
I'm quite happy with this method, but I have to maintain global variables to remember stuff between states, so I might use one global table to store data.
One thing to point out about the 'state pattern' is that formally, states aren't supposed to exist entirely on their own. States usually have an 'owner' object on which they can act upon. One way to describe this is to say "object A is in the state of B" where A is the 'owner' of state B. This might give you some ideas about encapsulation instead of allowing each state to access global data/functions. In your case, it looks like the object which owns these states is the 'game screen'.
User avatar
Inny
Party member
Posts: 652
Joined: Fri Jan 30, 2009 3:41 am
Location: New York

Re: Best practices to structure the code of a love game?

Post by Inny »

Oh boy a discussion about gamestates where I can tangent off into lua's coroutines :P
I've kinda adopted this pattern with my code:

Code: Select all

SomeState = class()
function SomeState:init()
  self.co = coroutine.wrap(function() self:play() end)
end

function SomeState:update(dt)
  self.co(dt)
end

function SomeState:wait(sec)
  while sec > 0 do
    local dt = coroutine.wait(true)
    sec = sec - dt
  end
end

function SomeState:play()
  moveSomethingAround()
  self:wait(0.5)
  moveSomethingElseAround()
  self:wait(1)
  showSomeTextBox()
  self:wait(2)
  addAnExplosion()
  self:wait(5)
  andSoOn()
end

function SomeState:draw()
  -- actually do all of the animation stuff
end
In this particular model, It make sure that the term "States" applies to the modal nature of games, meaning you have the titlescreen, the menu, actually playing, the gameover screen, etc. Internal to each state, however, there's no second layer of statemachine to deal with, as the coroutines alleviate the need for it.

It's something that works for me. I wouldn't necessarily advocate people do this, just be aware that it's a thing that can be done.
User avatar
Lafolie
Inner party member
Posts: 809
Joined: Tue Apr 05, 2011 2:59 pm
Location: SR388
Contact:

Re: Best practices to structure the code of a love game?

Post by Lafolie »

If you use simple class-based structure, your main.lua file will probably look like this:

Code: Select all

require("requireFiles")

function love.load()
	game = gameClass()
end

function love.update(dt)
	game:update(dt)
end

function love.draw()
	game:draw()
end
Making your own system of callbacks can help cut down on globals and keep things organised.
Do you recognise when the world won't stop for you? Or when the days don't care what you've got to do? When the weight's too tough to lift up, what do you? Don't let them choose for you, that's on you.
User avatar
Codex
Party member
Posts: 106
Joined: Tue Mar 06, 2012 6:49 am

Re: Best practices to structure the code of a love game?

Post by Codex »

So I'm in the middle of refactoring a lot of old code since I've just become familiar with OOP, modules, metamethods, classes, and would like to use them. I've been looking at other peoples game structures for guidance. One thing I seek to clarify in this thread, (related to) is how should a module be structured? I see a lot of this:

Code: Select all

-- some_file.lua
Some_file = {}
Some_file.mt = {}

Some_file.function_01 = function() 
-- do something
end

Some_file.function_02 = function()
-- do something
end

etc. etc.
Containing mostly functions and methods. Sometimes the data is bundled all together in different file like - resources.lua or data.lua, that has the values and strings that are needed by the module.

Questions:

#1. Would it be better to put the data into the same module for quick reference? This would make the file bigger and more cluttered as a downside.

#2. Should the functions in the modules ever reference globals outside the module? Is this good practice?

#3. When using metamethods in a module, is it possible to do - mt.__add = a:some_fun(b) instead of - tab.some_fun(a,b)

#4. Should you use OOP if you only plan on having one instance of the object? It seems a bit tedious to do so.

And as an example this is what I'm working on: http://codepad.org/37bM8Tdg It contains the methods and data together. (and a "setting" table that can be configured, but I'm considering having a separate setting.lua file to put all modules configurable settings there)
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: Best practices to structure the code of a love game?

Post by kikito »

My usual folder structure:

Code: Select all

.
├── media
│   ├── img/
│   ├── sfx/
│   └── other/ (levels, animations)
└── src
    ├── game/ (mainmenu.lua, play.lua, gameover.lua)
    ├── lib/ (external libraries)
    ├── game-specific folders & files
    └── main.lua
When I write def I mean function.
User avatar
bartbes
Sex machine
Posts: 4946
Joined: Fri Aug 29, 2008 10:35 am
Location: The Netherlands
Contact:

Re: Best practices to structure the code of a love game?

Post by bartbes »

Codex wrote: #1. Would it be better to put the data into the same module for quick reference? This would make the file bigger and more cluttered as a downside.
I'm not sure what data you're talking about, if it's just internal stuff, yeah, stuff it in there, if it's a level, consider having it externally.
Codex wrote: #2. Should the functions in the modules ever reference globals outside the module? Is this good practice?
Well yes, how else would you ever use the standard libraries. That said, try to minimize the existence of globals.
Codex wrote: #3. When using metamethods in a module, is it possible to do - mt.__add = a:some_fun(b) instead of - tab.some_fun(a,b)
Apart from that broken syntax, those two are equivalent.
Codex wrote: #4. Should you use OOP if you only plan on having one instance of the object? It seems a bit tedious to do so.
That's personal preference.

One thing I'd like to notice, the idiomatic (and since lua 5.2, standard) way of having modules is by starting them not unlike you do, but with that table local, and returning it at the end.
(Keep in mind require caches, so if you need it in multiple places you can just require it again, without it doing actual work.)
Post Reply

Who is online

Users browsing this forum: No registered users and 7 guests