Page 1 of 9

stateful.lua

Posted: Wed Oct 12, 2011 5:42 pm
by kikito
Hi everyone,

I'm officially releasing my new stateful lib. It's hosted on github:

https://github.com/kikito/stateful.lua

I'll be using the next post for writing a short tutorial - apologies for the double post.

Stateful used to be part of middleclass-extras, but I've decided to separate that lib into smaller ones. It was getting too complicated.

There are several differences between this version of Stateful and the one on middleclass-extras.
  • The states on this implementation are regular tables, not classes. It's still possible to make one state "inherit" from another.
  • The enterState and exitState callbacks have been renamed to enteredState and exitedState.
  • I've replaced the "getCurrentStateName" method by a "getStateStackDebugInfo" - the later returns an array with the names of all the states on the instance. I hope this will discourage people from doing things like if self:getCurrentStateName() == "Foo" then ... which basically go against the spirit of the library
EDIT: Added a demo using stateful + other libs to make a game with game states

Re: stateful.lua

Posted: Wed Oct 12, 2011 5:46 pm
by kikito
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.

Re: stateful.lua

Posted: Wed Oct 12, 2011 6:02 pm
by TechnoCat
kikito wrote:[*]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.
What would an example of this be? Like stacking Ghost on top of Dead for enemies or something? and would that be Enemy:pushState?

EDIT: Yes, https://github.com/kikito/stateful.lua/ ... l.lua#L152
kikito wrote:A typical use is on Game state management.
Indeedly. Stateful has become the core of my GSM.

Re: stateful.lua

Posted: Wed Oct 12, 2011 6:29 pm
by kikito
TechnoCat wrote:What would an example of this be? Like stacking Ghost on top of Dead for enemies or something? and would that be Enemy:pushState?

EDIT: Yes, https://github.com/kikito/stateful.lua/ ... l.lua#L152
Actually, the specs have better examples - here:

https://github.com/kikito/stateful.lua/ ... ce.lua#L94
kikito wrote:A typical use is on Game state management.
Indeedly. Stateful has become the core of my GSM.[/quote]

^^ I've expanded that post and added more stuff about Game state.

Re: stateful.lua

Posted: Wed Oct 12, 2011 7:16 pm
by TechnoCat
I wonder if SteveWonder is some up and coming hit new artist!

Re: stateful.lua

Posted: Wed Oct 12, 2011 8:38 pm
by kikito
Yeah I might have missed a vowel there ... Oh well...

Re: stateful.lua

Posted: Sun Oct 16, 2011 6:51 pm
by mike
This library is really awesome and saves me the trouble of implementing a states system myself. Also, the ability to stack the states??? Brilliant! :awesome:

Re: stateful.lua

Posted: Sun Oct 16, 2011 9:42 pm
by kikito
mike wrote:This library is really awesome and saves me the trouble of implementing a states system myself. Also, the ability to stack the states??? Brilliant! :awesome:
You are too kind, I'm very glad you find it useful ^^ .

Don't forget about plain old inheritance; you can make one state inherit from another (even from one belonging to a different class).

I must point out though that most of the ideas on this lib weren't mine. I borrowed heavily from UnrealScript. My implementation is based on this page - including a State stacking. Those guys were awesome and AFAIK were the ones that came out with the original idea of mixing states directly into classes.

There is lots of interesting & inspiring stuff in UnrealScript, but to to me, its highlights are the state management and networking code (best explanation I could find about networking code, a.k.a. Replication, is here)

Regards!

Re: stateful.lua

Posted: Fri Dec 23, 2011 12:43 pm
by Indigo gem
Hi, how I can implement state of the game in another file? Because state created only when added, so all state should be implemented in one file?

Re: stateful.lua

Posted: Fri Dec 23, 2011 1:42 pm
by kikito
Indigo gem wrote:Hi, how I can implement state of the game in another file? Because state created only when added, so all state should be implemented in one file?
Hi Indigo gem,

The simplest way of doing that is making the Game class global when creating them, and then require the files so that they modify it. In other words:

In your main.lua file, you require the files that creates Game, and the files that modify it. You may also want to create the game instance itself, and set it up to the initial menu:

Code: Select all

-- main.lua
require 'middleclass'
Stateful = require 'stateful'
require 'game'
require 'game_main_menu'
require 'game_options_menu'
require 'game_play'
...

function love.load()
  game = Game:new()
  game:gotoState('MainMenu')
end
So you have a game.lua file, a game_main_menu.lua file, etc.

Now inside game.lua you just create the Game class (making it global, not local). You add methods to the Game class that you want to have in all States.

Code: Select all

-- game.lua
Game = class('Game'):include(Stateful)

function Game:initialize()
  -- whatever you want to do when creating the initial game
end

function Game:foo() -- foo will be available in all states
end
Then, inside each of the other game_*.lua files, you just add the relevant state to Game. This is how game_main_menu.lua might look:

Code: Select all

-- game_main_menu.lua
local MainMenu = Game:addState('MainMenu')

function MainMenu:enteredState()
  -- create buttons, menus, etc
end

function MainMenu:exitedState()
  -- destroy buttons, menus, etc
end

function MainMenu:draw()
  -- draw the menus
end
And the same would go for the other states. Let me know if this was not clear enough.