Getting Started

To get started with this library first create a new project and create a virtual environment & activate it (optional but recommended). Once you’ve done that you may continue.

Installation

Install game_state through pip in your terminal-

(.venv) $ pip install game_state

game-state is designed to be independent of any specific framework or engine. For this example, we will walk through integrating game-state with a pygame(-ce) based project.

(.venv) $ pip install pygame-ce

Using the Library

Note

This library has been updated to version 2.0, introducing breaking changes that are not backward compatible with version 1.x.

If you are using a version older than 2.0.0, refer to the v1 documentation for guidance: game-state v1.1.3 Documentation

We highly recommend upgrading to version 2.0, as it offers significant optimizations and improvements over v1.

Note

Since version 2.3.0 onwards, the library deprecates all pygame-specific attributes / parameters to aim being more independant. This allows game-state to be integrated by any other framework and not just pygame.

Let’s create a simple pygame script having two screens. One screen will display green colour and the other will display blue with a moveable red square.

Setting up the basics

import pygame

from game_state import State, StateManager
from game_state.utils import MISSING


pygame.init()
pygame.display.init()
pygame.display.set_caption("Game State Example")

speed = 200  # Player speed
BLUE = (0, 255, 0)
GREEN = (0, 0, 255)

Before creating our states, we need to first make a base state that defines the core structure which all our actual states inherits from.

Defining our base state

class MyBaseState(State["MyBaseState"]):
    screen: pygame.Surface = MISSING
    # Mention the attributes we want all our states to share.

In this example we’ll keep it simple and have only one attribute to share across all our states (screen). It’s opitional to assign MISSING to the attribute but it’s recommended to do so.

Now that we have created our base state, let’s create a main menu screen.

Creating a simple screen

class MainMenuState(MyBaseState, state_name="MainMenu"):
    def process_event(self, event: pygame.event.Event) -> None:
      # This is executed in our our game loop for every event.

      if event.type == pygame.QUIT:
            # We set the state manager's is_running variable to false
            # which stops the game loop from continuing.
            self.manager.is_running = False

    def process_update(self, dt: float) -> None:
        # This is executed in our game loop.

        self.screen.fill(GREEN)
        pygame.display.update()

Note

In this library screens are referred to as States and screen manager as StateManager

Now that we have created a screen, let’s add it to our screen manager and run it!

Adding our screen to the state manager.

def main() -> None:
    screen = pygame.display.set_mode((500, 600))
    # Create a basic 500x600 pixel window

    state_manager = StateManager(bound_state_type=MyBaseState, screen=screen)
    # `bound_state_type` takes in the base state we have made.
    # Don't forget to pass in kwargs to assign to the attributes we've defined
    # in our ``MyBaseState`` class.

    state_manager.load_states(MainMenuState)
    # We pass in all the screens that we want to use in our game / app.

    state_manager.change_state("MainMenu")
    # We need to use the name we supplied in the __init_sublcass__'s `state_name`.
    # If no state_name was passed, we use the class name itself.

    clock = pygame.time.Clock()

    while state_manager.is_running:
        # The state manager has a `is_running` attribute which is `True` by default

        dt = clock.tick(60) / 1000  # The delta time from the clock for frame rate independance.

        for event in pygame.event.get():
            state_manager.current_state.process_event(event)
            # Calling the event function of the running state.

        state_manager.current_state.process_update(dt)
        # Calling the update function of the running state.

if __name__ == "__main__":
    main()

There you have it! We have set up a simple screen using the Game State library. Adding more screens is just as simple as the subclassing State & adding it to the StateManager.

Adding the main game screen to our state manager.

class MainMenuState(MyBaseState, state_name="MainMenu"):
    def process_event(self, event: pygame.event.Event) -> None:
        # This is executed in our our game loop for every event.

        if event.type == pygame.QUIT:
            # We set the state manager's is_running variable to false
            # which stops the game loop from continuing.
            self.manager.is_running = False

        if event.type == pygame.KEYDOWN and event.key == pygame.K_w:
            # Check if we're clicking the " w " button.
            # If the condition is met, we change our screen to the
            # "Game" screen from the manager.

            self.manager.change_state("Game")

    def process_update(self, *args: float) -> None:
        # This is executed in our game loop.

        self.screen.fill(GREEN)
        pygame.display.update()


class GameState(MyBaseState, state_name="Game"):
    def __init__(self) -> None:
        self.player_x: float = 250.0

    def process_event(self, event: pygame.event.Event) -> None:
        if event.type == pygame.QUIT:
            self.manager.is_running = False

        if event.type == pygame.KEYDOWN and event.key == pygame.K_w:
            # Check if we're clicking the " w " button.
            # If the condition is met, we change our screen to the
            # "MainMenu" screen from the manager.

            self.manager.change_state("MainMenu")

    def process_update(self, *args: float) -> None:
        dt = args[0]

        self.screen.fill(BLUE)

        # Player movement-
        pressed = pygame.key.get_pressed()
        if pressed[pygame.K_a]:
            self.player_x -= speed * dt

        if pressed[pygame.K_d]:
            self.player_x += speed * dt

        pygame.draw.rect(
            self.screen,
            "red",
            (
                self.player_x,
                100,
                50,
                50,
            ),
        )

        pygame.display.update()

Finally, we need to add our GameState to our StateManager just like how we did for our MainMenuState.

state_manager.load_states(MainMenuState, GameState)

There you go! We have made a simple pygame to handle multiple screens via Game State! The final code will look something like this-

import pygame
from game_state import State, StateManager
from game_state.utils import MISSING

GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
speed = 200
pygame.init()
pygame.display.init()
pygame.display.set_caption("Game State Example")


class MyBaseState(State["MyBaseState"]):
    screen: pygame.Surface = MISSING
    # Mention the attributes we want all our states to share.


class MainMenuState(MyBaseState, state_name="MainMenu"):
    def process_event(self, event: pygame.event.Event) -> None:
        # This is executed in our our game loop for every event.

        if event.type == pygame.QUIT:
            # We set the state manager's is_running variable to false
            # which stops the game loop from continuing.
            self.manager.is_running = False

        if event.type == pygame.KEYDOWN and event.key == pygame.K_w:
            # Check if we're clicking the " w " button.
            # If the condition is met, we change our screen to the
            # "Game" screen from the manager.

            self.manager.change_state("Game")

    def process_update(self, *args: float) -> None:
        # This is executed in our game loop.

        self.screen.fill(GREEN)
        pygame.display.update()


class GameState(MyBaseState, state_name="Game"):
    def __init__(self) -> None:
        self.player_x: float = 250.0

    def process_event(self, event: pygame.event.Event) -> None:
        if event.type == pygame.QUIT:
            self.manager.is_running = False

        if event.type == pygame.KEYDOWN and event.key == pygame.K_w:
            # Check if we're clicking the " w " button.
            # If the condition is met, we change our screen to the
            # "MainMenu" screen from the manager.

            self.manager.change_state("MainMenu")

    def process_update(self, *args: float) -> None:
        dt = args[0]

        self.screen.fill(BLUE)

        # Player movement-
        pressed = pygame.key.get_pressed()
        if pressed[pygame.K_a]:
            self.player_x -= speed * dt

        if pressed[pygame.K_d]:
            self.player_x += speed * dt

        pygame.draw.rect(
            self.screen,
            "red",
            (
                self.player_x,
                100,
                50,
                50,
            ),
        )

        pygame.display.update()


def main() -> None:
    screen = pygame.display.set_mode((500, 600))
    # Create a basic 500x600 pixel window

    state_manager = StateManager(bonud_state_type=MyBaseState, screen=screen)
    state_manager.load_states(MainMenuState, GameState)
    # We pass in all the screens that we want to use in our game / app.

    state_manager.change_state("MainMenu")
    # We need to use the name we supplied in the __init_sublcass__'s `state_name`.
    # If no state_name was passed, we use the class name itself.

    clock = pygame.time.Clock()

    while state_manager.is_running:
        # The state manager has a `is_running` attribute which is `True` by default

        dt = clock.tick(60) / 1000
        # The delta time from the clock for frame rate independance.

        for event in pygame.event.get():
            state_manager.current_state.process_event(event)
            # Calling the event function of the running state.

        state_manager.current_state.process_update(dt)
        # Calling the update function of the running state.


if __name__ == "__main__":
    main()