8

Top Python Game Engines

 1 year ago
source link: https://realpython.com/top-python-game-engines/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Top Python Game Engines

Top Python Game Engines

Like many people, maybe you wanted to write video games when you first learned to code. But were those games like the games you played? Maybe there was no Python when you started, no Python games available for you to study, and no game engines to speak of. With no real guidance or framework to assist you, the advanced graphics and sound that you experienced in other games may have remained out of reach.

Now, there’s Python, and a host of great Python game engines available. This powerful combination makes crafting great computer games much easier than in the past. In this tutorial, you’ll explore several of these game engines, learning what you need to start crafting your own Python video games!

By the end of this article, you’ll:

  • Understand the pros and cons of several popular Python game engines
  • See these game engines in action
  • Understand how they compare to stand-alone game engines
  • Learn about other Python game engines available

To get the most out of this tutorial, you should be well-versed in Python programming, including object-oriented programming. An understanding of basic game concepts is helpful, but not necessary.

Ready to dive in? Click the link below to download the source code for all the games that you’ll be creating:

Get Source Code: Click here to get the source code you’ll use to try out Python game engines.

Python Game Engines Overview

Game engines for Python most often take the form of Python libraries, which can be installed in a variety of ways. Most are available on PyPI and can be installed with pip. However, a few are available only on GitHub, GitLab, or other code sharing locations, and they may require other installation steps. This article will cover installation methods for all the engines discussed.

Python is a general purpose programming language, and it’s used for a variety of tasks other than writing computer games. In contrast, there are many different stand-alone game engines that are tailored specifically to writing games. Some of these include:

These stand-alone game engines differ from Python game engines in several key aspects:

  • Language support: Languages like C++, C#, and JavaScript are popular for games written in stand-alone game engines, as the engines themselves are often written in these languages. Very few stand-alone engines support Python.
  • Proprietary scripting support: In addition, many stand-alone game engines maintain and support their own scripting languages, which may not resemble Python. For example, Unity uses C# natively, while Unreal works best with C++.
  • Platform support: Many modern stand-alone game engines can produce games for a variety of platforms, including mobile and dedicated game systems, with very little effort. In contrast, porting a Python game across various platforms, especially mobile platforms, can be a major undertaking.
  • Licensing options: Games written using a stand-alone game engine may have different licensing options and restrictions, based on the engine used.

So why use Python to write games at all? In a word, Python. Using a stand-alone game engine often requires you to learn a new programming or scripting language. Python game engines leverage your existing knowledge of Python, reducing the learning curve and getting you moving forward quickly.

There are many game engines available for the Python environment. The engines that you’ll learn about here all share the following criteria:

  • They’re relatively popular engines, or they cover aspects of gaming that aren’t usually covered.
  • They’re currently maintained.
  • They have good documentation available.

For each engine, you’ll learn about:

  • Installation methods
  • Basic concepts, as well as assumptions that the engine makes
  • Major features and capabilities
  • Two game implementations, to allow for comparison

Where appropriate, you should install these game engines in a virtual environment. Full source code for the games in this tutorial is available for download at the link below and will be referenced throughout the article:

Get Source Code: Click here to get the source code you’ll use to try out Python game engines.

With the source code downloaded, you’re ready to begin.

Pygame

When people think of Python game engines, the first thought many have is Pygame. In fact, there’s already a great primer on Pygame available at Real Python.

Written as a replacement for the stalled PySDL library, Pygame wraps and extends the SDL library, which stands for Simple DirectMedia Layer. SDL provides cross-platform access to your system’s underlying multimedia hardware components, such as sound, video, mouse, keyboard, and joystick. The cross-platform nature of both SDL and Pygame means that you can write games and rich multimedia Python programs for every platform that supports them!

Pygame Installation

Pygame is available on PyPI, so after creating and activating a virtual environment, you can install it using the appropriate pip command:

(venv) $ python -m pip install pygame

Once that’s done, you can verify the installation by running an example that comes with the library:

(venv) $ python -m pygame.examples.aliens

Now that you’ve installed Pygame, you can begin using it right away. If you run into problems during installation, then the Getting Started guide outlines some known issues and possible solutions for all platforms.

Basic Concepts

Pygame is organized into several different modules, which provide abstracted access to your computer graphics, sound, and input hardware. Pygame also defines numerous classes, which encapsulate concepts that aren’t hardware dependent. For example, drawing is done on Surface objects, whose rectangular limits are defined by their Rect object.

Every game utilizes a game loop to control game play. This loop iterates constantly as the game progresses. Pygame provides methods and functions to implement a game loop, but it doesn’t provide one automatically. The game author is expected to implement the functionality of a game loop.

Each iteration of the game loop is called a frame. Every frame, the game performs four vital actions:

  1. Processing user input. User input in Pygame is handled using an event model. Mouse and keyboard input generate events, which can be read and handled, or ignored as you see fit. Pygame itself doesn’t provide any event handlers.

  2. Updating the state of game objects. Game objects can be represented using any Pygame data structure or special Pygame class. Objects such as sprites, images, fonts, and colors can be created and extended in Python to provide as much state information as necessary.

  3. Updating the display and audio output. Pygame provides abstract access to display and sound hardware. The display, mixer, and music modules allow game authors flexibility in game design and implementation.

  4. Maintaining the speed of the game. Pygame’s time module allows game authors to control the game speed. By ensuring each frame completes within a specified time limit, game authors can ensure the game runs similarly on different hardware.

You can see these concepts come together in a basic example.

Basic Application

This basic Pygame program draws a few shapes and some text on the screen:

Basic code in Pygame

The code for this sample can be found below and in the downloadable materials:

Despite its humble aspirations, even this basic Pygame program requires a game loop and event handlers. The game loop begins on line 27 and is controlled by the running variable. Setting this variable to False will end the program.

Event handling begins on line 30 with an event loop. Events are retrieved from a queue using pygame.event.get() and are processed one at a time during every loop iteration. In this case, the only event being handled is the pygame.QUIT event, which is generated when the user closes the game window. When this event is processed, you set running = False, which will eventually end the game loop and the program.

Pygame provides various methods for drawing basic shapes, such as circles and rectangles. In this sample, a blue circle is drawn on line 38, and a red square is drawn on lines 41 and 42. Note that drawing a rectangle requires you to create a Rect object first.

Drawing text on the screen is a little more involved. First, on line 45, you select a font and create a font object. Using that font on lines 46 to 48, you call the .render() method. This creates a Surface object containing the text rendered in the specified font and color. Finally, you copy Surface to the screen using the .blit() method on line 49.

The end of the game loop occurs on line 52, when everything that was previously drawn is shown on the display. Without this line, nothing would be displayed.

To run this code, use the following command:

(venv) $ python pygame/pygame_basic.py

You should see a window appear with the image shown above. Congratulations! You just ran your first Pygame program!

Advanced Application

Of course, Pygame was designed to write games in Python. To explore the capabilities and requirements of an actual Pygame game, you’ll examine a game written in Pygame with the following details:

  • The player is a single sprite on the screen, controlled by moving the mouse.
  • At regular intervals, coins appear on the screen one by one.
  • As the player moves over each coin, it disappears and the player is awarded ten points.
  • As the game progresses, coins are added more quickly.
  • The game ends when there are more than ten coins visible on the screen.

When done, the game will look something like this:

A coin-collecting game in Pygame

The complete code for this game can be found in the downloaded materials and below:

Sprites in Pygame provide some basic functionality, but they’re designed to be subclassed rather than used on their own. Pygame sprites don’t have images associated with them by default, and they can’t be positioned on their own.

To properly draw and manage the player and the coins on-screen, a Player class is created on lines 35 to 55, and a Coin class on lines 58 to 75. When each sprite object is created, it first locates and loads the image it’ll display, saving it in self.surf. The self.rect property positions and moves the sprite on the screen.

Adding coins to the screen at regular intervals is done with a timer. In Pygame, events are fired whenever a timer expires, and game creators can define their own events as integer constants. The ADDCOIN event is defined on line 90, and the timer fires the event after coin_countdown milliseconds on line 91.

Since ADDCOIN is an event, it needs to be handled in an event loop, which happens on lines 118 to 134. The event creates a new Coin object and adds it to the existing coin_list. The number of coins on-screen is checked. If there are fewer than three, then coin_countdown is reduced. Finally, the previous timer is stopped, and a new one starts.

As the player moves, they collide with coins, collecting them as they do. This removes each collected coin from the coin_list automatically. This also updates the score and plays a sound.

Player movement occurs on line 137. Collisions with coins on the screen are checked on lines 140 to 142. The dokill=True parameter removes the coin from the coin_list automatically. Finally, lines 143 to 147 update the score and play the sound for each coin collected.

The game ends when the user either closes the window, or when there are more than ten coins on the screen. Checking for more than ten coins is done on lines 150 to 152.

Because Pygame sprites have no built-in knowledge of an image, they also don’t know how to draw themselves on the screen. The game author needs to clear the screen, draw all the sprites in the correct order, draw the on-screen score, then .flip() the display to make everything appear. That all happens on lines 155 to 170.

Pygame is a very powerful and well-established library, but it has its drawbacks. Pygame makes game authors work to get their results. It’s up to the game author to implement basic sprite behavior and implement key game requirements such as game loops and basic event handlers. Next up, you’ll see how other game engines deliver similar results while reducing the amount of work you have to do.

Pygame Zero

There are many things Pygame does well, but others where its age is evident. For game-writing beginners, a better option can be found in Pygame Zero. Designed for education, Pygame Zero is guided by a simple set of principles aimed at being perfect for young and beginning programmers:

  • Make it accessible: Everything is designed for beginning programmers.
  • Be conservative: Support the common platform and avoid experimental features.
  • Just work: Make sure everything works without a lot of fuss.
  • Minimize runtime costs: If something might fail, fail early.
  • Error clearly: Nothing’s worse than not knowing why something went wrong.
  • Document well: A framework is only as good as its docs.
  • Minimize breaking changes: Upgrading shouldn’t require rewriting your game.

The documentation for Pygame Zero is very accessible for beginning programmers, and it includes a complete step-by-step tutorial. Further, the Pygame Zero team recognizes that many beginning programmers start coding with Scratch, so they provide a tutorial demonstrating how to migrate a Scratch program to Pygame Zero.

Pygame Zero Installation

Pygame Zero is available on PyPI, and you can install it like any other Python library on Windows, macOS, or Linux:

(venv) $ python -m pip install pgzero

Pygame Zero, as its name suggests, is built on Pygame, so this step also installs Pygame as a dependent library. Pygame Zero is installed by default on the Raspberry Pi platform, on Raspbian Jessie or a later release.

Basic Concepts

Pygame Zero automates many things that programmers have to do manually in Pygame. By default, Pygame Zero provides the game creator:

  • A game loop, so there’s no need to write one
  • An event model to handle drawing, update, and input handling
  • Uniform image, text, and sound handling
  • A usable sprite class and useful animation methods for user sprites

Because of these provisions, a basic Pygame Zero program can be very short:

Pygame Zero recognizes that the constants WIDTH and HEIGHT on lines 16 and 17 refer to the size of the window and automatically uses those dimensions to create it. Plus, Pygame Zero provides a built-in game loop and calls the draw() function defined on lines 19 to 43 once per frame to render the screen.

Because Pygame Zero is based on Pygame, some shape drawing code is inherited. You can see the similarities in drawing the circle on line 29 and the square on lines 34 to 35:

Basic code for Pygame Zero

However, text drawing is now a single function call on lines 38 to 43, rather than three separate functions.

Pygame Zero also provides basic window-handling code, so you can close the window by clicking the appropriate close button, without requiring an event handler.

You can find code demonstrating some of Pygame Zero’s basic capabilities in the downloadable materials:

Get Source Code: Click here to get the source code you’ll use to try out Python game engines.

Running Pygame Zero programs is done from the command line using the command:

(venv) $ python pygame_zero/pygame_zero_basic.py

Running this command will start your Pygame Zero game. You should see a window appear with basic shapes and your Pygame Zero greeting.

Sprites and Images

Sprites are called Actors in Pygame Zero, and they have a few characteristics which require some explanation:

  1. Pygame Zero provides the Actor class. Each Actor has, at minimum, an image and a position.
  2. All images used in a Pygame Zero program must be located in a subfolder called ./images/, and be named using lowercase letters, numbers, and underscores only.
  3. Images are referenced using only the base name of the image. For example, if your image is called alien.png, you reference it in your program as "alien".

Because of these built-in features of Pygame Zero, drawing sprites on the screen requires very little code:

 1alien = Actor("alien")
 2alien.pos = 100, 56
 3
 4WIDTH = 500
 5HEIGHT = alien.height + 20
 6
 7def draw():
 8    screen.clear()
 9    alien.draw()

Now you’ll break this small sample down line by line:

  • Line 1 creates the new Actor object, giving it the name of the image to draw.
  • Line 2 sets the initial x and y position of the Actor.
  • Lines 4 and 5 set the size of the Pygame Zero window. Notice that HEIGHT is based on the .height attribute of the sprite. This value comes from the height of the image used to create the sprite.
  • Line 9 draws the sprite by calling .draw() on the Actor object. This draws the sprite image on the screen at the location provided by .pos.

You’ll use these techniques in a more advanced game next.

Advanced Application

To demonstrate the difference between the game engines, you’ll revisit the same advanced game that you saw in the Pygame section, now written using Pygame Zero. As a reminder, the key details of that game are:

  • The player is a single sprite on the screen, controlled by moving the mouse.
  • At regular intervals, coins appear on the screen one by one.
  • As the player moves over each coin, it disappears and the player is awarded ten points.
  • As the game progresses, coins are added more quickly.
  • The game ends when there are more than ten coins visible on the screen.

This game should look and behave identically to the Pygame version demonstrated earlier, with only the window title bar betraying the Pygame Zero origin:

A coin-collecting game in Pygame Zero

You can find the complete code for this sample in the downloaded materials and below:

Creating the player Actor is done on lines 26 to 28. The initial position is the center of the screen.

The clock.schedule() method handles creating coins at regular intervals. This method takes a function to call and the number of seconds to delay before calling that function.

Lines 41 to 65 define the add_coin() function that will be scheduled. It creates a new coin Actor at a random location on lines 48 to 50 and adds it to a global list of visible coins.

As the game progresses, coins should appear more and more quickly, but not too quickly. Managing the interval is done on lines 57 to 62. Because clock.schedule() will only fire a single time, you schedule another call on line 65.

Mouse movement is processed in the on_mouse_move() event handler on lines 67 to 87. The mouse position is captured and stored in a global variable on line 76. Lines 79 to 87 ensure this position is never off the screen.

Storing the player position in a global variable is a convenience that simplifies the code and focuses allows you to focus on the capabilities of Pygame Zero. Your design choices may differ in more complete games.

The update() function defined on lines 89 to 122 is called once per frame by Pygame Zero. You use this to move Actor objects and update the state of all your game objects. The position of the player Actor is updated to track the mouse on line 98.

Collisions with coins are handled on lines 102 to 113. If the player has collided with a coin, then the coin is added to coin_remove_list, the score is incremented, and a sound is played. When all the collisions have been processed, you remove the coins which were added to coin_remove_list on lines 112 to 113.

After coin collisions are handled, you check to see if there are still too many coins on the screen on line 116. If so, the game is over, so you stop creating new coins, print the final score, and end the game on lines 118 to 122.

Of course, all this updating needs to be reflected on the screen. The draw() function on lines 124 to 146 is called after update() once per frame. After clearing the screen and filling it with a background color on lines 128 and 131, the player Actor and all the coins are drawn on lines 134 to 138. The current score is the last thing drawn on lines 141 to 146.

The Pygame Zero implementation used 152 lines of code to deliver the same game as 182 lines of Pygame code. While these line counts are comparable, the Pygame Zero version is arguably cleaner, more modular, and possibly easier to understand and code than the Pygame version.

Of course, there’s always one more way to write a game.

Arcade

Arcade is a modern Python framework for crafting games with compelling graphics and sound. Object-oriented by design, Arcade provides game authors with a modern set of tools for crafting great Python game experiences.

Designed by Professor Paul Craven from Simpson College in Iowa, USA, Arcade is built on top of the pyglet windowing and multimedia library. It provides a set of improvements, modernizations, and enhancements that compare favorably with both Pygame and Pygame Zero:

  • Supports modern OpenGL graphics
  • Supports Python 3 type hinting
  • Has support for frame-based animated sprites
  • Incorporates consistent command, function, and parameter names
  • Encourages separation of game logic from display code
  • Requires less boilerplate code
  • Provides well-maintained and up-to-date documentation, including several tutorials and complete Python game examples
  • Has built-in physics engines for top-down and platform games

Arcade is under constant development, is well supported in the community, and has an author who’s very responsive to issues, bug reports, and potential fixes.

Arcade Installation

To install Arcade and its dependencies, use the appropriate pip command:

(venv) $ python -m pip install arcade

Complete installation instructions based on your platform are available for Windows, macOS, and Linux. You can even install arcade directly from source if you’d prefer.

Basic Concepts

Everything in Arcade occurs in a window that’s created with a user-defined size. The coordinate system assumes that the origin (0, 0) is located in the lower-left corner of the screen, with the y-coordinates increasing as you move up. This is different from many other game engines, which place (0, 0) in the upper left and increase the y-coordinates moving down.

At its heart, Arcade is an object-oriented library. While it’s possible to write Arcade applications procedurally, its real power is revealed when you create fully object-oriented code.

Arcade, like Pygame Zero, provides a built-in game loop and a well-defined event model, so you end up with very clean and readable game code. Also like Pygame Zero, Arcade provides a powerful sprite class which aids rendering, positioning, and collision detection. In addition, Arcade sprites can be animated by providing multiple images.

The code for a basic Arcade application listed below is provided in the tutorial’s source code as arcade_basic.py:

To run this code, use the following command:

(venv) $ python arcade/arcade_basic.py

This program draws a few shapes and some text on the screen, as in the basic examples previously shown:

Basic code for Arcade

As mentioned above, Arcade programs can be written as fully object-oriented code. The arcade.Window class is designed to be subclassed by your games, as shown on line 20. Calling super().__init() on line 33 ensures the game window is set up properly.

Arcade calls the .on_draw() event handler defined on lines 38 to 70 once per frame to render everything to the screen. This method starts with a call to .start_render(), which tells Arcade to prepare the window for drawing. This is comparable to the pygame.flip() call required at the end of the Pygame drawing step.

Each of the basic shape-drawing methods in Arcade starts with draw_* and requires a single line to complete. Arcade has built-in drawing support for numerous shapes.

Arcade comes loaded with hundreds of named colors in the arcade.color package, but you’re also free to pick your own colors using RGB or RGBA tuples.

Advanced Application

To show how Arcade is different from other game engines, you’ll see the same game from before, now implemented in Arcade. As a reminder, here are the key details of the game:

  • The player is a single sprite on the screen, controlled by moving the mouse.
  • At regular intervals, coins appear on the screen one by one.
  • As the player moves over each coin, it disappears and the player is awarded ten points.
  • As the game progresses, coins are added more quickly.
  • The game ends when there are more than ten coins visible on the screen.

Again, the game should act the same as the previous examples:

A coin-collecting game in Arcade

The code for the full Arcade game listed below is provided in the downloadable materials as arcade_game.py:

The object-oriented nature of Arcade allows you to quickly implement different levels by separating the initialization of the game from the initialization of each different level. The game is initialized in the .__init__() method on lines 40 to 63, while levels are set up and can be restarted using the separate .setup() method on lines 65 to 85. This is a great pattern to use even for games that have a single level, like this one.

Sprites are defined by creating an object of the class arcade.Sprite, and providing the path to an image. Arcade supports pathlib paths, which eases the creation of the player sprite on lines 72 to 75.

Creating new coins is handled on lines 78 to 80, which call arcade.schedule() to call the self.add_coin() method at regular intervals.

The .add_coin() method defined on lines 87 to 120 creates a new coin sprite at a random location and adds it to a list to simplify drawing as well as collision handling later.

To move the player using the mouse, you implement the .on_mouse_motion() method on lines 122 to 134. The arcade.clamp() method ensures the player’s center coordinates are never off the screen.

Checking for collisions between the player and the coin is handled in the .on_update() method on lines 144 to 156. The arcade.check_for_collision_with_list() method returns a list of all the sprites in the list that have collided with the specified sprite. The code walks through that list, incrementing the score and playing a sound effect before removing each coin from play.

The .on_update() method also checks if there are too many coins on the screen on lines 159 to 168. If so, it ends the game.

This Arcade implementation is just as readable and well structured as the Pygame Zero code, although it took over 27% more code, with 194 lines written. The longer code may be worth it, as Arcade offers many more features not demonstrated here, such as:

  • Animated sprites
  • Several built-in physics engines
  • Support for third-party game maps
  • Updated particle and shader systems

New game authors coming from Python Zero will find Arcade similar in structure while offering more powerful and extensive features.

adventurelib

Of course, not every game requires a colorful player moving on the screen, avoiding obstacles, and killing bad guys. Classic computer games like Zork showed off the power of good storytelling while still providing a great gaming experience. Crafting these text-based games, also called interactive fiction, can be difficult in any language. Luckily for the Python programmer, there’s adventurelib:

adventurelib provides basic functionality for writing text-based adventure games, with the aim of making it easy enough for young teenagers to do. (Source)

It’s not just for teenagers, though! adventurelib is great for anyone who wants to write a text-based game without having to also write a natural language parser to do so.

adventurelib was created by the folks behind Pygame Zero, and it tackles more advanced computer science topics such as state management, business logic, naming and references, and set manipulation, to name a few. This makes it a great next step for educators, parents, and mentors helping young people learn computer science through the creation of games. It’s also great for broadening your own game-coding skills.

adventurelib Installation

adventurelib is available on PyPI and can be installed using the appropriate pip command:

(venv) $ python -m pip install adventurelib

adventurelib is a single file, so it can also be downloaded from the GitHub repo, saved in the same folder as your game, and used directly.

Basic Concepts

To learn the basics of adventurelib, you’ll see a small game with three rooms and a key to unlock a door to the final room below. The code for this sample game is provided in the downloadable materials in adventurelib_basic.py:

To run this code, use the following command:

(venv) $ python adventurelib/adventurelib_basic.py

Text-based games rely heavily on parsing user input to drive the game forward. adventurelib defines the text that a player types as a command and provides the @when() decorator to define commands.

A good example of a command is the look command defined on lines 113 to 125. The @when("look") decorator adds the text look to a list of valid commands and connects it to the look() function. Whenever the player types look, adventurelib will call the look() function.

Commands are case-insensitive when typed by the player. The player can type look, LOOK, Look, or even lOOk, and adventurelib will find the correct command.

Multiple commands can all use the same function, as seen with the go() function on lines 78 to 110. This function is decorated with nine separate commands, allowing the player several different ways to move around the game world. In the game play example below, the commands south, east, and north are all used, but each results in the same function being called:

Basic Example of adeventurelib

Sometimes the commands that a player types are directed at a specific item. For example, the player may want to look at a particular thing or go in a specific direction. The game designer can capture additional command context by specifying capitalized words in the @when() decorator. These are treated as variable names, and the text that the player types in their place are the values.

This can be seen in the look_at() function on lines 128 to 137. This function defines a single string parameter called item. In the @when() decorators defining the look at and inspect commands, the word ITEM acts as a placeholder for any text following the command. This text is then passed to the look_at() function as the item parameter. For example, if the player types look at book, then the parameter item will get the value "book".

The strength of a text-based game relies on the descriptiveness of its text. While you can and should certainly use print() functions, printing numerous lines of text in response to user commands can introduce difficulties spanning text over multiple lines and determining line breaks. adventurelib eases this burden with the say() function, which works well with triple-quoted multiline strings.

You can see the say() function in action on line 118 in the look() function. Whenever the player types look, the say() function outputs the description of the current room to the console.

Of course, your commands need places to occur. adventurelib provides the Room class to define different areas of your game world. Rooms are created by providing a description of the room, and they can be connected to other rooms by using the .north, .south, .east, and .west properties. You can also define custom properties that apply to either the entire Room class or individual objects.

The three rooms in this game are created on lines 15 to 35. The Room() constructor accepts a description as a string, or in this case, as a multiline string. Once you’ve created the rooms, then you connect them on lines 38 to 39. Setting bedroom.south to living_room implies that living_room.north will be bedroom. adventurelib is smart enough to make this connection automatically.

You also create a constraint on line 44 to indicate a locked door between the living room and the front porch. Unlocking this door will require the player to locate an item.

Text-based games often feature items which must be collected to open new areas of the game or to solve certain puzzles. Items can also represent non-player characters with whom the player can interact. adventurelib provides the Item class to define both collectable items and non-player characters by their names and aliases. For example, the alias key refers to the front door key:

Example for adventurlib: Getting an Item

On line 63, you define the key used to unlock the door between the living room and the front porch. The Item() constructor takes one or more strings. The first is the default or full name of the item, and it’s used when printing the name of the item. All other names are used as aliases so the player doesn’t have to type the full name of the item.

The key doesn’t just have a name and aliases. It also has an intended use, which is defined on line 64. key.use_item refers to a function that will be called when a player tries to use the item by typing "use key". This function is called in the use() command handler defined on lines 159 to 175.

Collections of items, such as the player’s inventory or items on the ground in a room, can be stored in a Bag object. You can add items to the bag, remove items from the bag, and inspect the bag’s contents. Bag objects are iterable in Python, so you can also use in to test if something is in the bag and loop over the bag’s contents in a for loop.

Four different Bag objects are defined on lines 67 to 75. Each of the three rooms has a Bag to hold items in the room, and the player also has a Bag to hold their inventory of items they pick up. The key item is placed in its starting location in the bedroom.

Items are added to the player’s inventory by the get() function defined on lines 140 to 156. When the player types get key, you attempt to take() the item from the room’s contents bag on line 151. If the key is returned, it’s also removed from the room’s contents. You then add the key to the player’s inventory on line 156.

Advanced Application

Of course, there’s much more to adventurelib. To show off its other capabilities, you’ll craft a more involved text adventure with the following backstory:

  • You live in a small, quiet hamlet.
  • Recently, your neighbors have begun complaining of missing livestock.
  • As a member of a night patrol, you notice a broken fence and a trail leading away from it.
  • You decide to investigate, armed only with a wooden practice sword.

The game has several areas to describe and define:

  • Your small, quiet hamlet
  • The trail leading away from the field
  • A nearby village where you can buy a better weapon
  • A side path leading to a wizard who can enchant your weapon
  • A cave containing the giant who has been taking your livestock

There are several items to collect, such as weapons and food, and characters with which to interact. You also need a basic battle system to allow you to fight the giant and win the game.

All of the code for this game is listed below, and can be found in the downloaded materials:

Get Source Code: Click here to get the source code you’ll use to try out Python game engines.

To keep things organized, you break your game into different files:

  • adventurelib_game_rooms.py defines the rooms and areas.
  • adventurelib_game_items.py defines the items and their attributes.
  • adventurelib_game_characters.py defines the characters with which you can interact.
  • adventurelib_game.py pulls everything together, adds commands, and starts the game.

You can start this game with the following command:

(venv) $ python adventurelib/adventurelib_game.py

After defining the backstory, you mapped out the different game areas and the paths which the player uses move between them:

A map showing the areas of an AdventureLib game.

Each area has various properties associated with it, including:

  • Items and characters that are in that area
  • Some exits that are locked
  • A title, a short description, and a longer description
  • An indication of whether the player has been in this area or not

To ensure that each area has its own instance of each of these properties, you create a subclass of Room called GameArea in adventurelib_game_rooms.py on lines 15 to 41. Items in each room are held in a Bag object called items, while characters are stored in characters, defined on lines 28 and 31. Now you can create GameArea objects, describe them, and populate them with unique items and characters, which are all imported on lines 9 and 12.

Some game items are required to finish the game, while others are just there for flavor and to add interest. Flavor items are identified and placed on lines 192 to 194, followed by characters on lines 197 to 200.

All of your game items are defined in adventurelib_game_items.py as objects of type Item(). Game items have properties that define them, but because you’ve used the Item base class, some basic universal properties are added to the class on lines 9 to 12. These properties are used when the item is created. For example, the apple object is created on lines 15 to 19 and defines each of the universal properties when it is created.

Some items, however, have specific properties unique to the item. For example, the wooden_sword and steel_sword items need properties to track the damage they do and any magical bonuses they carry. Those are added on lines 43 to 44 and 53 to 54.

Interacting with characters helps drive the game story forward and often gives the player a reason to explore. Characters in adventurelib are created as Item objects, and for this game are defined in adventurelib_game_characters.py.

Each character, like each item, has universal properties associated with it, such as a long description and the greeting used when the player encounters it for the first time. These properties are declared on lines 9 and 10, and they’re defined for each character when the character is created.

Of course, if you have characters, then it makes sense for the player to talk to and interact with them. It’s often a good idea to know when you’re interacting with a character, and when you’re just in the same game area with one.

This is done by using an adventurelib concept called a context. Contexts allow you to turn on different commands for different situations. They also allow certain commands to behave differently, and they track additional information about actions that the player may have taken.

When the game starts, there’s no context set. As the player progresses, they first encounter Elder Barron. When the player types "talk to elder", the context is set to elder.context, which in this case is elder.

Elder Barron’s greeting ends with a yes or no question to the player. If the player types "yes", then the command handler on line 173 in adventurelib_game.py is triggered, which is defined as @when("yes", context="elder"), as shown below:

Later, when the player is talking to the blacksmith, a second level of context is added to reflect that they’re engaged in a possible weapon trade. Lines 203 to 233 define the discussion with the blacksmith, which includes the offer of a weapons trade. A new context is defined on line 222, which allows the same "yes" command to be used elegantly in multiple ways.

You can also check the context in a command handler. For example, the player cannot simply leave the fight with the giant by ending the conversation. The "goodbye" command handler defined on lines 389 to 403 checks if the player is in the "giant" context, which is entered when they start fighting the giant. If so, they’re not allowed to stop the conversation — it’s a fight to the death!

You can also ask questions of the player requiring a specific answer. When the player talks to the wizard Trent, they’re asked to solve a riddle. An incorrect answer will end the interaction. While the correct answer is handled with the command handler on lines 252 to 262, one of the nearly infinite wrong answers won’t match any handler.

Commands with no matches are handled by the no_command_matches() function on lines 497 to 503. You leverage this to handle incorrect answers to the wizard’s riddle by checking for the wizard.riddle context on line 498. Any incorrect answer to the riddle will result in the wizard ending the conversation. You connect this to adventurelib on line 523 by setting adventurelib.no_command_matches to your new function.

You can customize the prompt shown to the player by writing a function that returns the new prompt. Your new prompt is defined on lines 483 to 495 and connected to adventurelib on line 520.

Of course, there’s always more that you could add. Creating a complete text adventure game is challenging, and adventurelib makes sure the the main challenge lies in painting a picture with words.

Ren’Py

The modern descendant of the pure text adventure is the visual novel, which highlights the storytelling aspect of the game, limiting player interactions while adding visuals and sound to heighten the experience. Visual novels are the graphic novels of the game world — modern, innovative, and extremely compelling to create and consume.

Ren’Py is a tool based on Pygame and designed specifically to create visual novels. Ren’Py takes its name from the Japanese word for romantic love and provides tools and a framework for crafting compelling visual novels.

To be fair, Ren’Py is not strictly a Python library that you can pip install and use. Ren’Py games are created using the Ren’Py Launcher, which comes with the full Ren’Py SDK. This launcher also features a game editor, although you can edit your game in your editor of choice. Ren’Py also features its own scripting language for game creation. However, Ren’Py is based on Pygame, and it’s extendable using Python, which warrants its appearance here.

Ren’Py Installation

As mentioned, Ren’Py requires not only the SDK, but also the Ren’Py Launcher. These are packaged together in a single unit, which you need to download.

Knowing which package to download and how to install it depends on your platform. Ren’Py provides installers and instructions for Windows, macOS, and Linux users:

Windows users should download the provided executable, then run it to install the SDK and the Ren’Py Launcher.

After the package is installed, you can navigate to the folder containing the SDK then run the Ren’Py Launcher. Windows users should use renpy.exe, while macOS and Linux users should run renpy.sh. This will start the Ren’Py Launcher for the first time:

This is where you’ll start new Ren’Py projects, work on existing projects, and set overall preferences for Ren’Py.

Basic Concepts

Ren’Py games start as new projects in the Ren’Py Launcher. Creating one will set up the proper file and folder structure for a Ren’Py game. After the project is set up, you can use your own editor to write your game, although the Ren’Py Launcher is required to run the game:

Ren’Py games are contained in files called scripts. Don’t think of Ren’Py scripts as you would shell scripts. They’re more analogous to scripts for plays or television shows. Ren’Py scripts have the extension .rpy and are written in the Ren’Py language. Your game can consist of as many scripts as you like, which are all stored in the game/ subfolder of your project folder.

When you create a new Ren’Py project, the following scripts are created for you to use and update:

  • gui.rpy, which defines the look of all UI elements used in your game
  • options.rpy, which defines changeable options to customize your game
  • screens.rpy, which defines the styles used for dialogue, menus, and other game output
  • script.rpy, which is where you start writing your game

To run the sample games from the downloaded materials for this tutorial, you’ll use the following process:

  1. Start the Ren’Py Launcher.
  2. Click Preferences, then Projects Directory.
  3. Change the Projects Directory to the renpy folder in the repository that you downloaded.
  4. Click Return to return to the main Ren’Py Launcher page.

You’ll see basic_game and giant_quest_game in the Projects list on the left. Select the one that you wish to run, then click Launch Project.

For this example, you’ll only modify the script.rpy file for basic_game. The complete code for this game can be found in the downloaded materials, as well as below:

Labels define entry points into your story, and are often used to start new scenes and provide alternate paths through the story. All Ren’Py games start running at the line label start:, which can appear in any script you choose. You can see this on line 12 of script.rpy.

You can also use labels to define background images, set up transitions between scenes, and control the appearance of characters. In the sample, a second scene starts on line 54 with the line label check_room:.

Text enclosed in double-quotes on a line is called a say statement. A single string on a line is treated as narration. Two strings get treated as dialogue, identifying a character first and then providing the line that they’re speaking.

At the beginning of the game, narration is seen on line 16, which sets the scene. Dialogue is provided on line 18, when your mom calls to you.

You can define characters by simply naming them in the story. However, you can also define characters at the top of your script. You can see this on lines 6 to 8, where you, your brother Kevin, and your mom are defined. The define statement initializes the three variables as Characters, giving them a display name, followed by a text color used to display the name.

Of course, this is a visual novel, so it makes sense that Ren’Py would have a way to handle images. Like Pygame Zero, Ren’Py requires that all images and sounds used in the game reside in specific folders. Images are found in the game/images/ folder, and sounds are in the game/audio/ folder. In the game script, you refer to them by filename without any file extension.

Line 25 shows this in action, when you open your eyes and see your bedroom for the first time. The scene keyword clears the screen, then displays the bedroom day.png image. Ren’Py supports the JPG, WEBP, and PNG image formats.

You can also display characters on the screen using the show keyword and the same naming convention for the image. Line 41 shows a picture of your brother Kevin, stored as kevin normal.png.

Of course, it’s not much of a game if you can’t make decisions to affect the outcome. In Ren’Py, players make choices from a menu presented to them in the course of the game. The game reacts by jumping to predefined labels, changing character images, playing sounds, or taking other actions as necessary.

A basic choice in this sample is shown on lines 47 to 52, where you realize you’ve forgotten your phone. In a more complete story, this choice may have consequences later.

Of course, you can do much more with Ren’Py. You can control transitions between scenes, have characters enter and leave scenes in specific ways, and include sound and music for your game. Ren’Py also supports writing more complex Python code, including using Python data types and making direct Python function calls. Now take a closer look at these capabilities in a more advanced application.

Advanced Application

To show the depth of Ren’Py, you’ll implement the same game as you did for adventurelib. As a reminder, here’s the basic design of that game:

  • You live in a small, quiet hamlet.
  • Recently, your neighbors have begun complaining of missing livestock.
  • As a member of a night patrol, you notice a broken fence and a trail leading away from it.
  • You decide to investigate, armed only with a wooden practice sword.

The game has several areas to define and provide images for. For example, you’ll need images and definitions for your small, quiet hamlet, the trail leading away from the field, a nearby village where you can buy a better weapon, a side path leading to a wizard who can enchant your weapon, and the cave containing the giant who has been taking your livestock.

There are also a few characters to define and provide images for. You need a blacksmith who can give you a better weapon, a wizard who can enchant your weapon, and the giant whom you need to defeat.

For this example, you’ll create four separate scripts:

  • script.rpy, which is where the game starts
  • town.rpy, which contains the story of the nearby village
  • path.rpy, which contains the path between villages
  • giant.rpy, which contains the logic for the giant battle

You can create the wizard encounter as an independent exercise.

The complete code for this game can be found in the downloaded materials at renpy_sample/giant_quest/ and is also available below:

As in the previous example, you define Character() objects before the script begins on lines 14 to 17 of script.rpy.

You can also define background or character image objects for later use. Lines 21 to 27 define several images which you refer to later, both to use as backgrounds and to display as items. Using this syntax allows you to assign shorter and more descriptive internal names for images. Later, you’ll see how they’re displayed.

You also need to track the capabilities of the equipped weapon. This is done on lines 31 to 37, using default variable values, which you’ll use during the giant battle later.

To indicate which weapon is enabled, you show the image as an expression. Ren’Py expressions are small images displayed in the corners of the game window and are used to show a wide variety of information. For this game, you use an expression to show the weapon using show expression first on lines 67 and 68.

The show command has a number of modifiers documented. The with moveinleft modifier causes the current_weapon image to slide onto the screen from the left. Also, it’s important to remember that every time scene changes, the entire screen is cleared, requiring you to show the current weapon again. You can see that on lines 75 to 78.

When you enter the town in town.rpy, you meet the blacksmith, who greets you:

The initial blacksmith encounter in Ren'Py

The blacksmith offers you the opportunity to upgrade your weapon. If you choose to do so, then you update the values for current_weapon and the weapon stats. This is done on lines 93 to 98.

Lines that begin with the $ character are interpreted by Ren’Py as Python statements, allowing you to write arbitrary Python code as necessary. Updating the current_weapon and weapon stats is done using three Python statements on lines 96 to 98, which change the values of the the default variables that you defined at the top of script.rpy.

You can also define a large block of Python code using a python: section, as shown in giant.rpy starting on line 41.

Lines 43 to 61 contain a helper function to show the condition of the giant, based on the giant’s remaining hit points. It uses the renpy.say() method to output narration back to the main Ren’Py window. A similar helper function to show the player’s condition is seen on lines 63 to 85.

The battle is controlled by fight_giant() on lines 87 to 159. The game loop is implemented on line 98 and is controlled by the battle_over variable. Player choices of fight or flee are displayed using the renpy.display_menu() method.

If the player fights, then a random amount of damage is done on lines 116 to 118, and the giant’s hit points are adjusted. If the giant is still alive, then they get to attack in a similar fashion on lines 136 to 149. Note that the giant has a chance to miss, while the player always hits. The fight continues either until the player or the giant has zero hit points or until the player flees:

Fighting the giant in the Ren'Py game

It’s important note that this code is very similar to the code that you used in the adventurelib battle. This shows how you can drop full Python code into your Ren’Py games without needing to translate it into Ren’Py script.

There’s much more to Ren’Py than what you’ve tried out here. Consult the Ren’Py documentation for more complete details.

Other Notable Python Game Engines

These five engines are only a small sampling of the many different Python game engines available. There are dozens of others available, and a few are worth noting here:

  • Wasabi 2D is developed by the team behind Pygame Zero. It’s a modern framework built on moderngl that automates rendering, provides coroutines for animation effects, has built-in particle effects, and uses an event-driven model for game play.

  • cocos2d is a framework designed for coding cross-platform games. Sadly, cocos2d-python hasn’t been updated since 2017.

  • Panda 3D is an open-source framework for creating 3D games and 3D renderings. Panda 3D is portable across platforms, supports multiple asset types, connects out of the box with numerous third-party libraries, and provides built-in pipeline profiling.

  • Ursina is built on top of Panda 3D and provides a dedicated game development engine that simplifies many aspects of Panda 3D. Well supported and well documented, Ursina is under active development at the time of this writing.

  • PursuedPyBear is billed as an educational library. It boasts a scene management system, frame-based animated sprites which can be paused, and a low barrier to entry. Documentation is sparse, but help is only a GitHub discussion away.

New Python game engines are created every day. If you find one that suits your needs and wasn’t mentioned here, please sing its praises in the comments!

Sources for Game Assets

Often, creating game assets is the biggest issue facing game authors. Large video game companies employ teams of artists, animators, and musicians to design the look and sound of their games. Solo game developers with a background in coding may find this aspect of game development daunting. Luckily, there are many different sources for game assets available. Here are some that were vital in locating assets for the games in this tutorial:

  • OpenGameArt.org hosts a wide variety of game art, music, backgrounds, icons, and other assets for both 2D and 3D games. Artists and musicians list their assets for download, which you can download and use in your games. Most assets are freely available, and licensing terms may apply to many of them.

  • Kenney.nl hosts a set of free and paid assets, many of which can be found nowhere else. Donations are always welcome to support the free assets, which are all licensed for use in commercial games.

  • Itch.io is a marketplace for digital creators focused on independent game development. Here you can find digital assets for just about any purpose, both free and paid, along with complete games. Individual creators control their own content here, so you’re always working directly with talented individuals.

Most assets available from third-parties carry licensing terms dictating the proper and allowable use of the assets. As the user of these assets, it’s your responsibility to read, understand, and comply with the licensing terms as defined by the asset owner. If you have questions or concerns about those terms, please consult a legal professional for assistance.

All of the assets used in the games referenced in this article conform to their respective licensing requirements.

Conclusion

Congratulations, great game design is now within your reach! Thanks to Python and a buffet of highly capable Python game engines, you can create quality computer games much more easily than before. In this tutorial, you’ve explored several such game engines, learning the information that you need to start crafting your own Python video games!

By now, you’ve seen some of the top Python game engines in action, and you’ve:

  • Explored the pros and cons of several popular Python game engines
  • Experienced how they compare to stand-alone game engines
  • Learned about other Python game engines available

If you’d like to review the code for the games in this tutorial, you can do so by clicking the link below:

Get Source Code: Click here to get the source code you’ll use to try out Python game engines.

Now you can choose the best Python game engine for your purpose. So what are you waiting for? Get out there and write some games!

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

Python Tricks Dictionary Merge

About Jon Fincher

Jon taught Python and Java in two high schools in Washington State. Previously, he was a Program Manager at Microsoft.

» More about Jon

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Master Real-World Python Skills
With Unlimited Access to Real Python

lesson-locked.f5105cfd26db.svg

Join us and get access to hundreds of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

What Do You Think?

Real Python Comment Policy: The most useful comments are those written with the goal of learning from or helping out other readers—after reading the whole article and all the earlier comments. Complaints and insults generally won’t make the cut here.

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Keep Learning

Related Tutorial Categories: basics gamedev


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK