I found an excellent article in Spanish that shows how to build a Scene Manager for your games, which makes things easier once you start adding different screens to your little creation. It’s a great way to avoid headaches.
Just listen to Doge: “Many chaos, much scenes, such headaches, wow!”
Before we start, the article can be found here, if you are interested and can read it (more info at the end).
My intention is to translate the original into English as well as I can since I’m not a native speaker, and second, use it and apply it to games made on Invent Your Own Computer Games with Python. I won’t obviously re-post every game found on that tutorial… or maybe yes, if I can find a way to refine them and improve them, and learn something along the way. So, lets get into it!
Managing Scenes with Pygame
A video game is not formed by only one screen, but many, like an introductory menu, a map screen, an inventory screen, scores screen. This screens are called scenes, and each one of them represent something specific in our game.
Changing a scene can be complicated, remember you have to keep the game loop active while doing so. The problem is that the logic, events and draw processes on each scene might be, and normally are, different. For example, on a menu the program will have to manage events as pushing buttons and showing score rankings, while another will have to draw different things.
The Director object and the master Scene
Being different between them, all the scenes are going to share the features of the game loop, meaning that they must be initialized, updated, managed and drawn on the screen. Therefore, we could create a superclass class that would take care of doing all that with the active scene, regardless of which one is it.
The Director object
Instead of managing the game loop through the main function of our program, we are going to create a Director class that will be in charge of managing the game loop, which will receive any scene and will execute the scene’s methods for updating, events and drawing processes.
# -*- encoding: utf-8 -*- # Modules import pygame, sys class Director: """Represents the main object of the game. The Director object keeps the game on, and takes care of updating it, drawing it and propagate events. This object must be used with Scene objects that are defined later.""" def __init__(self): self.screen = pygame.display.set_mode((640, 480)) pygame.display.set_caption("Game Name") self.scene = None self.quit_flag = False self.clock = pygame.time.Clock() def loop(self): "Main game loop." while not self.quit_flag: time = self.clock.tick(60) # Exit events for event in pygame.event.get(): if event.type == pygame.QUIT: self.quit() if event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: self.quit() # Detect events self.scene.on_event() # Update scene self.scene.on_update() # Draw the screen self.scene.on_draw(self.screen) pygame.display.flip() def change_scene(self, scene): "Changes the current scene." self.scene = scene def quit(self): self.quit_flag = True
This object must be created within our main function instead of a game loop. Let’s explain this:
The class constructor creates the screen with pygame, we give it a title and we set up the clock. We also define some variables, like self.scene = None
which will be the scene we would like it to represent, none for the moment. Also self.quit_flag = False, which will help us to know when to quit the loop.
Then we have the loop method, which contains the game loop under the condition self.quit_flag, when this condition is True, the loop will break, to do so, we only have to call the quit method.
Once in the loop, the first thing is to set the frame rate, in this case 60 frames per second. After this we check for if any exit event has occurred, if yes, we call self.quit().
Afterwards we call self.scene.in_event(), but we have to assign a scene first, which leads us to our next section.
The Scene class
The attributes of this class must be inherited by all the scenes that we use in our game.
class Scene: """Represents a scene of the game. Scenes must be created inheriting this class attributes in order to be used afterwards as menus, introduction screens, etc.""" def __init__(self, director): self.director = director def on_update(self): "Called from the director and defined on the subclass." raise NotImplementedError("on_update abstract method must be defined in subclass.") def on_event(self, event): "Called when a specific event is detected in the loop." raise NotImplementedError("on_event abstract method must be defined in subclass.") def on_draw(self, screen): "Se llama cuando se quiere dibujar la pantalla." raise NotImplementedError("on_draw abstract method must be defined in subclass.")
A Scene object receives a Director instance during instantiation, and it is this class that implements the methods (abstract ones in this case) on_update, on_event and on_draw, expecting the subclass to define them, otherwise it will raise an exception We are going to define a super simple Scene subclass and fill those methods (with nothing, for now):
class SceneHome(scene.Scene): """ Welcome screen of the game, the first one to be loaded.""" def __init__(self, director): scene.Scene.__init__(self, director) def on_update(self): pass def on_event(self): pass def on_draw(self): pass
Our scene has inherited from Scene superclass (scene.Scene syntax depends on where you coded the Scene class). We implement the three methods, but it will do nothing so far.
Let’s initialize a Scene, we’ll do it on the main file:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Modules import pygame import director import scene_home def main(): dir = director.Director() scene = scene_home.SceneHome(dir) dir.change_scene(scene) dir.loop() if __name__ == '__main__': pygame.init() main()
This is the main file of our project, as we can see we import three modules, pygame, the director module containing the Director class, and the scene_home module with the Scene subclass that we defined before and that will be the first one used.
On the main function we instantiate a Director with dir = director.Director(). Afterwards we create our Scene: scene = scene_home.SceneHome(dir) and we call the method change_scene of the Director instance using the new Scene as a parameter.
Afterwards, dir.loop() will initiate the game loop.
About the Director object
On a final note, the Director object is the one who makes the calls of the Scene object on_event, on_update, on_draw and later the pygame.display.flip() to draw the screen.
Now we have a way to load any scene we want from our Director object with its own methods for updating, managing events and drawing. To change the scene we only need to call the dir.change_scene(scene) method passing the new scene as a parameter.
So this is it. I’ve tried to translate the article as well as I can, but maybe you can spot some mistakes. Feel free to inform me through the comments, and feel free to use this examples on your own code, since I guess that was the author’s intention from the beginning.
This post is a translation (with slight touches here and there) of this.
The author is Adrián Guerra Marrero.
Also, the code on the original article was intended for Python 2.X, I think it should work on Python 3.X, but again, if you spot a mistake just let me know so I can fix it.
My next step is to put this code to use and create a simple game with it. Let’s see how that goes.