No matter how simple your game might be, chances are that there will be multiple screens or modes the player will be able to interact with. Think of it for a moment—in most games there is a title screen, which is followed by a main menu, which lets you branch into various other sub-screens that let you set the game's difficulty or level of detail. Typically, you must navigate your way through many if not most of these screens and menus before you are finally ready to play the game itself.
All these varying screens and modes fall into the game development concept of game states. Each of these screens is seen as a state and might set the game to a different mode of operation. As the game transitions from one state to another, it is setting variables, loading content and finally, drawing different graphics to the screen or presenting new ways of interaction to the player.
This recipe will show you how to define and use a state machine that lets you easily define and cleanly switch back and forth through the differing states of a game.
As a prerequisite, please follow the instructions found in the recipe Setting up the game structure back in Chapter 1 before proceeding with the following tasks.
Let's get started with the practical part of this recipe:
AppState.py
to the project. AppState.py:
from direct.fsm.FSM import FSM from direct.gui.DirectGui import * from panda3d.core import * class AppState(FSM): def enterMenu(self): self.pandaBtn = DirectButton(text = "Panda", scale = 0.12, pos = Vec3(0, 0, 0.1), command = self.request, extraArgs = ["Panda"]) self.smileyBtn = DirectButton(text = "Smiley", scale = 0.1, pos = Vec3(0, 0, -0.1), command = self.request, extraArgs = ["Smiley"]) def exitMenu(self): Panda3Dgame state, controllingself.pandaBtn.destroy() self.smileyBtn.destroy()
def enterPanda(self): self.menuBtn = DirectButton(text = "Menu", scale = 0.1, pos = Vec3(0, 0, -0.8), command = self.request, extraArgs = ["Menu"]) self.panda = loader.loadModel("panda") self.panda.reparentTo(render) base.cam.setPos(0, -40, 5) def exitPanda(self): self.menuBtn.destroy() self.panda.removeNode()
AppState
class, add this code to it:def enterSmiley(self): self.menuBtn = DirectButton(text = "Menu", scale = 0.1, pos = Vec3(0, 0, -0.8), command = self.request, extraArgs = ["Menu"]) self.smiley = loader.loadModel("smiley") self.smiley.reparentTo(render) base.cam.setPos(0, -20, 0) def exitSmiley(self): self.menuBtn.destroy() self.smiley.removeNode()
Application.py
and paste the following piece of code:from direct.showbase.ShowBase import ShowBase from panda3d.core import * from AppState import AppState class Application(ShowBase): def __init__(self): ShowBase.__init__(self) state = AppState("Application") state.request("Menu")
For handling state in our code, Panda3D comes with the FSM
(finite state machine) class, which we use as the base class for our AppState
class. This class handles state switches for our sample application. This class is kept quite simple for the purpose of this recipe. A full game would feature a lot more states, of course.
The AppState
class defines 3 states: Menu, Panda
, and Smiley
, all of which are implicitly defined by their respective enter()
and exit()
methods. These methods implement the actions that are performed if the state is entered, or when the state transitions to a different state.
Each state adds either some user interface controls or loads a model and then cleans up after itself, once another state is requested. Changing the state of our AppState
object (and thus of our application) is done using the request()
method, passing the name of the state to switch to as the method parameter. This can be seen in the last line of Application.py
, for example, where we set the initial Menu
state.