GameStates

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 :slight_smile:
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