How I would make some Game States on my app? For example, if you press the Start button in the Intro state, you could pass to Game Menu state, and if you click a button in the Game Menu you pass to Game state, and so. Is that possible?
You mean something like Finite State Machines(FSM)?
Sure, FSM is a built in class in Panda3D!
Have a look at the Manual.
I haven’t used them, yet. But when the time comes I’ll use them for menues, too.
Yeah, I think I’ll try it. Thanks.
Hello,
In my game, I wanted to do the same thing as you describe in your
message. First, I tried to use the Panda FSM but I realized that it was not
suitable for my game.
As you probably know, a game loop looks like:
Loop:
Calculate time between frames
Get inputs from player (s)
Update avatar
Update objects, the environment, apply physics, calculate AI, etc.
Redraw everything
It is clear that this loop is not fix, and there can be slightly variations.
What I have used it is the State pattern tailored to fulfil previous loop.
I have defined each possible state of the game as a subclass of:
class GameState:
# Class attributes
name = ""
game = None
def __init__(self, stateName):
self.name = stateName
self.game = None
def setEngine(self, theGame):
# It should be an instance of GameEngine
self.game = theGame
def getName(self):
return self.name
# Methods to be created
def enter(self):
pass
def exit(self):
pass
def pause(self):
pass
def resume(self):
pass
def handleEvents(self):
pass
def update(self, deltaTime = 0):
pass
They are; intro state, running state, menu state
Then, the game loop looks like:
def gameLoop(self, task):
delta = task.time - task.last
task.last = task.time
if not self.running:
return Task.done
# Main loop
self.handleEvents()
self.update(delta)
return Task.cont
The ‘redraw everything’ is omitted because Panda will do it for you.
This loop runs in a separate task. In fact, it is a method a GameEngine class that runs in a separate task.
In addition, this class implements a stack of game states. It is used for
going forth and back of game states.
For example, the IntroState looks like:
# Panda 3D
from direct.gui.DirectGui import *
# Game imports
from state import GameState
from run_state import RunState
from singleton import Singleton
from logger import defaultLogger
class IntroState(GameState):
# Attribute known to function Singleton
_instance = None
def __init__(self):
GameState.__init__(self, "IntroState")
#self.counter = 0
# Methods to be created
def enter(self):
defaultLogger.log("Entering state: %s" % self.name)
# Draw splash screen
# "Press space to continue"
self.titleText = OnscreenText(text=self.game.name,
style=2,
scale = 0.2)
self.spaceText = OnscreenText(text="Press space to continue",
style=2,
fg=(1,1,1,1),
pos=(0.0, -0.16),
#align=TextNode.ALeft,
scale = .05)
# Play a sound
#self.introMusic = base.loadMusic("")
#self.introMusic.setVolume(0.5)
#self.introMusic.play()
self.exitState = False
def exit(self):
defaultLogger.log("Exiting state: %s" % self.name)
if self.titleText:
self.titleText.destroy()
if self.spaceText:
self.spaceText.destroy()
#self.introMusic.stop()
def pause(self):
pass
def resume(self):
pass
def handleEvents(self):
assert(self.game)
if self.game.inputHandler.getKey("space") == 1:
self.exitState = True
def update(self, deltaTime = 0):
assert(self.game)
if self.exitState:
self.game.changeState(Singleton(RunState))
return
#if soundEnd == True:
# replay sound
And in the initialization of the game:
self.gameTask = taskMgr.add(self.gameLoop, "gameLoop")
self.gameTask.last = 0 #Task time of the last frame
# Start the main loop
self.running = True
# load the intro
self.changeState(Singleton(IntroState))
I hope this help you.
Alberto
Great
I always wonder what’s the best way to do such things
Thanks. But I need to know something…
Where should I put the Initialization of the game loop? Inside some class, some loop or out of everything?
I dont excactly know what you mean, but you can do the main loop by creating an instance of your class and then calling the run() function:
import allstuff
class MyClass(DirectObject):
blah
blah
myWorld=MyClass()
run()
I meant this:
self.gameTask = taskMgr.add(self.gameLoop, "gameLoop")
self.gameTask.last = 0 #Task time of the last frame
# Start the main loop
self.running = True
# load the intro
self.changeState(Singleton(IntroState))
Hello,
The initialization of the game is an abstract issue. You can put it wherever
you want. What I have done is to create a main.py where it is the
creation of the Game class and it starts the execution of the Panda loop:
Main.py
# Panda 3D engine
import direct.directbase.DirectStart
from pandac.PandaModules import *
# Game imports
from game import GameEngine
# --------------------------------------------------------------------
# Create the game object
game = GameEngine("The blackbell")
# Init the game
game.init()
# Set the camera in a fixed position
base.setBackgroundColor(0, 0, 0)
base.disableMouse()
# Set camera len parameters.
# 35 deg in blender default value is equal to 50 deg in Panda
# I do not know why
base.camLens.setFov(50.0, 40.0)
# Test show the frustrum
#base.camera.showFrustrum()
#base.oobe()
# Run the main loop
run()
The Game class looks like:
# Python imports
import sys
import time
# Panda 3D
import direct.directbase.DirectStart
from pandac.PandaModules import *
from direct.task import Task
# Game imports
from singleton import Singleton
from state import GameState
from intro_state import IntroState
#from actor import MyActor
from stack import Stack
from input_handler import InputHandler
from logger import defaultLogger
class GameEngine:
def __init__(self, gameName = ""):
self.name = gameName
defaultLogger.log("Game Engine created")
# It handles the input events
self.inputHandler = InputHandler(self)
defaultLogger.log("Input handler created")
self.running = False
# Game states
self.states = Stack()
# Game methods
# Private area
# ----------------------------------------------------
# State management methods
def changeState(self, newState):
if (newState == None):
defaultLogger.log("NewState pointer is null. State: " % newState.getName())
return
if (not isinstance(newState,GameState)):
defaultLogger.log("NewState is not a GameState class. State: " % newState.getName())
return
# Cleanup the current state
if (not self.states.isEmpty()):
lastState = self.states.pop()
lastState.exit()
# Set the pointer to the engine
newState.setEngine(self)
# Store and init the new state
self.states.push(newState)
newState.enter()
def pushState(self, newState):
if (newState == None):
defaultLogger.log("NewState pointer is null. State: " % newState.getName())
return
if (not isinstance(newState,GameState)):
defaultLogger.log("NewState is not a GameState class. State: " % newState.getName())
return
# Pause current state
if (not self.states.isEmpty()):
self.states.getLast().pause()
# Set the pointer to the engine
newState.setEngine(self)
# Store and init the new state
self.states.push(newState)
newState.enter()
def popState(self):
# Cleanup the current state
if (not self.states.isEmpty()):
lastState = self.states.pop()
lastState.exit()
# Resume previous state
if (not self.states.isEmpty()):
self.states.getLast().resume()
# Public Area
# ----------------------------------------------------
def init(self):
"""
Initializes the game.
- Create actors
- Create rooms
- Create objects
"""
defaultLogger.log("GameEngine.init() started")
# Load game data
# self.createActors()
# From Asteriod example. Tasks example.
# Now we create the task. taskMgr is the task manager that actually calls
#The function each frame. The add method creates a new task. The first
#argument is the function to be called, and the second argument is the name
#for the task. It returns a task object, that is passed to the function
#each frame
self.gameTask = taskMgr.add(self.gameLoop, "gameLoop")
#The task object is a good place to put variables that should stay
#persistant for the task function from frame to frame
self.gameTask.last = 0 #Task time of the last frame
# Start the main loop
self.running = True
# load the intro
self.changeState(Singleton(IntroState))
def close(self):
"""
# Free used resources
#
# Close hardware
# Mouse
# Keyboard
# Screen
"""
defaultLogger.log("GameEngine.close() started")
# Remove main loop
taskMgr.remove("gameLoop")
# End of the game
sys.exit(0)
# This is our main task function, which does all of the per-frame processing
# It takes in self like all functions in a class, and task, the task object
# returned by taskMgr
def gameLoop(self, task):
#task contains a variable time, which is the time in seconds the task has
#been running. By default, it does not have a delta time (or dt), which is
#the amount of time elapsed from the last frame. A common way to do this is
#to store the current time in task.last. This can be used to find dt
delta = task.time - task.last
task.last = task.time
#If the ship is not alive, do nothing. Tasks return Task.cont to signify
#that the task should continue running. If Task.done were returned instead,
#the task would be removed and would no longer be called every frame
if not self.running:
return Task.done
# Main loop
self.handleEvents()
self.update(delta)
return Task.cont #Since every return is Task.cont, the task will
#continue indefinitely
def isRunning(self):
return self.running
def quit(self):
self.running = False
def handleEvents(self):
# Delegate to current state
self.states.getLast().handleEvents()
def update(self, deltaTime):
# Delegate to current state
self.states.getLast().update(deltaTime)
# Input handler callback
# ----------------------------------------------------
def doExit(self):
defaultLogger.log("GameEngine.doExit() started")
# Unset the flag
self.quit()
# Close the game
self.close()
def doSave(self):
defaultLogger.log("GameEngine.doSave() started")
def doLoad(self):
defaultLogger.log("GameEngine.doLoad() started")
First, you define the initial state (IntroState - presentation and open
screen of the game) and the execution will remain in this state until the
exit condition is meet (see Update method).
These are the barebone classes of my game. After that, you only have
to define your own states and do whatever you want inside them.
I hope this clarify your question.
Regards.
Alberto
Thanks. I’ll try it.
About Singleton, is there a way to import it without making the code referent to it?
Hello,
The singleton is defined in a separated python file. It is very
straightforward to implement the Singleton pattern.
# Game imports
from singleton import Singleton
The code reference is the use of the singleton pattern (function call).
Regards.
Alberto