What does it do?
It attempts to automatize the pattern in which you have some code that depends on a variable representing a "state". For example, you might have this to control your enemies:
Code: Select all
function enemyTalk(enemy)
if enemy.state == "sleeping" return "zzz"
elseif enemy.state == "jumping" return "hop!"
else -- default action
return "hello"
end
end
In addition to "enemyTalk", you will probably have other actions such as "enemyMove" or "enemyDie" - each of them with similar "ifs" inside them.
This implementation has some problems. For instance, adding a new state is kind of difficult. Also, the code can get very complicated if you, for example, want to control several kinds of enemies with similar behaviours in some states but totally different ones in others.
Those are the problems stateful attempts to solve.
First of all, stateful requires the use of middleclass (2.0+). So your enemy must be transformed into a class - I'll call it Enemy:
Code: Select all
local class = require 'middleclass'
Enemy = class('Enemy')
...
function Enemy:talk()
if self.state == "sleeping" return "zzz"
elseif self.state == "jumping" return "hop!"
else -- default action
return "hello"
end
end
Now, stateful can be used included inside of Enemy like this:
Code: Select all
local class = require 'middleclass'
local Stateful = require 'stateful'
Enemy = class('Enemy'):include(Stateful)
And now the Enemy class just does the "default action" on talk:
Code: Select all
function Enemy:talk()
return "hello"
end
Then the rest of the actions are "separated" into states. Let's add Sleeping and Jumping:
Code: Select all
local Sleeping = Enemy:addState('Sleeping')
function Sleeping:talk()
return "zzz"
end
local Jumping = Enemy:addState('Jumping')
function Jumping:talk()
return "hop!"
end
An enemy can be created like this:
Code: Select all
local e = Enemy:new()
e:talk() -- hello
Now e can be moved to the jumping or sleeping states:
Code: Select all
e:gotoState('Jumping')
e:talk() -- hop!
I know it might not seem like much, but it is.
- Now adding a new state can be done without having to fiddle with the "ifs" of previous states
- If you create a subclass of Enemy (for example, Dragon) it will inherit the states of its parent class. You can then personalize them, so they are only sligthly different.
- States are stackable. This means that an Enemy can be both Sleeping and Jumping. Although depending on your intentions, in that case you might want to add a specific state called "Sonambulistic" or something.
- In addition to that, it's possible to "hook" methods that will be called when a state is entered, exited, pushed, popped
Game Management
A typical use is on Game state management. Games usually have very common states - the loading phase, the game menu, and the game itself. Stateful can be used to simplify how those are managed. In LÖVE, the two stateful methods would be "update" and "draw", while the entered/exited callback of each state can be used to build/cleanup the different objects used by the game. Here's how you do it:
You begin by loading middleclass and stateful somewhere (probably the main.lua file) as well as a "game.lua" file, where you will be defining the game and its states.
In your main.lua file, you basically create a game instance, and
Code: Select all
-- main.lua
local class = require 'middleclass'
local Stateful = require 'stateful'
local Game = require 'game'
local game
function love.load()
game = Game:new()
end
function love.update(dt)
game:update(dt)
end
function love.draw()
game:draw()
end
Then Game can be defined as a class inside Game.lua, and have a Menu state, a Play state, and so on.
Here's how you would create a Game with a Menu state:
Code: Select all
-- game.lua
local class = require 'middleclass'
local Stateful = require 'stateful'
local Game = class("Game"):include(Stateful)
function Game:initialize()
self.image = love.graphics.newImage("image.jpg")
self:gotoState("Menu") -- start on the Menu state
end
local Menu = Game:addState("Menu")
function Menu:enteredState() -- create buttons, options, etc and store them into self
print("entering the state menu")
end
function Menu:draw() -- draw the menu
end
function Menu:update(dt) -- update anything that needs updates
end
function Menu:exitedState() -- destroy buttons, options etc here
print("exiting the menu state")
end
return Game
That's the basic structure. You could add another state called "Play" similar to "Menu" following this schema.
That's pretty much it. Let me know if you have any questions.