In programming, "duck typing" is when you consider an object of a certain type if it has certain functions and properties, rather than the actual class or type that you declared the object as.If it walks like a duck, swims like a duck, and quacks like a duck, then it's a duck.
In other words, you don't care where the object came from, as long as it has some specific functions and/or properties available that you can use.
In Lua, you can store functions inside a table. You can put them in some keys for example:
Code: Select all
local tableA = {
function1 = function()
print("Function 1 of table A")
end,
function2 = function()
print("Function 2 of table A")
end,
function3 = function()
print("Function 3 of table A")
end
}
-- And adding more functions after the table was created:
function tableA:function4()
print("Function 4 of table A")
end
Code: Select all
local tableB = {
function1 = function()
print("Function 1 of table B")
end,
function2 = function()
print("Function 2 of table B")
end,
function3 = function()
print("Function 3 of table B")
end
}
Code: Select all
local unknownTable = tableA
unknownTable.function1() -- Prints "Function 1 of table A"
unknownTable = tableB
unknownTable.function1() -- Prints "Function 1 of table B"
That's exactly what you'll use to implement game states in your game.
If all states have the same set of functions (or rather, the same keys that point to functions), then you don't care what state is stored in the "current state" variable, you can execute the functions from it all the same.
I think it's a good idea to think of a game state as three distinct steps:
- A beginning moment, where you can prepare, load or initialize some things that the state will need.
- A middle moment (which lasts a long time), where you keep updating the game under that state, until the state wants the game to change to another state;
- An ending moment, where you can finish, destroy or cleanup some things which were only necessary in that state and won't be needed anymore in the next state(s).
Since Löve works with callbacks (love.update, love.keypressed, love.draw etc.), that "middle moment" of each state table can be represented by a unique set of functions, with each function corresponding to one of those callbacks.
This means that whatever game state is active, its callbacks will be called and that state will have control over the program.
So you could make a Main Menu state that looks like this:
Code: Select all
local mainMenuState = {}
function mainMenuState:prepare()
print("The start of the Main Menu state")
end
-- Callbacks:
function mainMenuState:update(dt)
print("Updating the game with the Main Menu state")
end
function mainMenuState:keypressed(key)
print("Listening to keys with the Main Menu state")
end
function mainMenuState:finish()
print("Cleaning up at the end of the Main Menu state")
end
Code: Select all
local currentState
function love.load()
-- Set the initial state to Main Menu, and prepare it.
currentState = mainMenuState
currentState:prepare()
end
function love.update(dt)
currentState:update(dt)
end
function love.draw()
currentState:draw()
end
function love.keypressed(key)
currentState:keypressed(key)
end
It's better to write a function for changing states in your program, this way you're sure that the prepare() and finish() functions of the states that you're changing between are always called.
Code: Select all
function changeGameState(newState)
-- Right now the 'currentState' variable is storing the active state.
-- Call its finish() function, if it exists.
if currentState.finish then
currentState:finish()
end
-- Change to the new state and call its prepare() function
-- if it exists.
currentState = newState
if currentState.prepare then
currentState:prepare()
end
end
HOWEVER, since that function changes the state immediately (instead of "queueing" it to be done later), you need to use it with a return statement so that it exits out of whatever function you're running at that moment, to avoid the function continuing from that point and expecting some resources that might've already been deleted by the finish() function of the state.
For example:
Code: Select all
function mainMenuState:keypressed(key)
if key == 'return' and currentOption == OPTION_START_GAME then
return changeGameState(loadLevel1State)
end
(...)
print("We're still in the Main Menu state")
end
What if a state doesn't need to do any preparation or finishing?
There's a couple of options. You can set the "prepare" and "finish" keys of a state table to nil, or set them to a dummy function that is empty and doesn't do anything. If you go the nil route, make sure that the code for changing between states will test for those keys being nil before using them, as seen in the changeGameState above.
I want to show some animated graphics when I go from the main menu to a certain game level. Do I use the prepare() or finish() functions from my states to do that?
You don't use either.
The prepare() and finish() functions are meant for instantaneous changes, like the loading of small resources, changing some global variables, freeing some objects etc.
If you need for the game to "hold" on a certain screen and show some animated graphics or be interactive while there's a countdown or heavier resource loading etc, then you can create a state whose sole purpose is to serve as a transition or intermediary step between the two other states.
That is, instead of having the states change like this:
Code: Select all
mainMenuState -> level1State
Code: Select all
mainMenuState -> loadLevel1State -> level1State