© Stewart Watkiss 2020
S. WatkissBeginning Game Programming with Pygame Zerohttps://doi.org/10.1007/978-1-4842-5650-3_4

4. Game Design

Stewart Watkiss1 
(1)
Redditch, UK
 

Hopefully you’ve had chance to play the game from Chapter 3 before moving on to this chapter. What did you think of it?

If your experience is like mine, then the first few goes were quite fun, but then the enjoyment dropped off somewhat. Two reasons for this: one is that once you’ve memorized the moves, it’s quite straightforward to play and is in fact a bit too simple, and the other thing is that because of the way the timer reduces with every level, it quickly gets to the point where there is too little time to make the move, which means you get end up with a similar score on each game.

In this chapter we will look at what makes a game interesting to play and how we can make a few changes to improve the game. This forms the basis of game design.

What Makes a Game Enjoyable?

Before we look at adding any code, think about the games you have played and what makes them enjoyable. Here are a few of the things I came up with, perhaps you can think of other factors:
  • Challenging but achievable

  • Choices and consequences

  • Rewards and progress

  • Likeable characters

  • Storyline/historical relevance

  • Educational (sometimes)

  • Takes an appropriate level of time to play

  • Inclusivity

  • Age appropriate

These are not required for all games. Think of them as being guidelines that make you think about the game design, but without being too restrictive. Being aware of when you can include these features can make a game more enjoyable. These may also relate to each other, such as how rewards can help overcome a challenge or where progress is used to reveal the next part in a storyline.

When designing a new game, it’s a good idea to work through these and think about how they can be implemented in the game. If you don’t think it’s important to your game, then that’s fine.

There is no single answer to all these features, and it really depends upon what type of game you want to create and who your target audience is.

Challenging but Achievable

When you are playing a game, you want to be able to feel you have achieved something. This is often achieved by having a challenge in the game that you need to overcome. The challenge may be a skill; it may be about quick reactions; or it may involve having to use brain power to solve a puzzle.

There are some games that are popular that don’t provide a challenge, but they normally provide something else. If you think about the paint by number apps, they are not what you would normally consider challenging, but instead are relaxing or therapeutic; some games may be creative rather than competitive such as Minecraft in creative mode. Arguably you could say that the lack of challenge means that they wouldn’t be classed as games, which is something to think about.

In most games there is a balance between it being easy to play and challenging enough to feel like you have achieved something. Make a game too easy and the player may get bored and look for a new challenge elsewhere, make it too hard and they may give up thinking they can’t progress any further.

In general, you will want the game to start easy, so that the player understands how to play without facing too much challenge. Then as they progress through the game, it should get harder to make it challenging and give the player a sense of achievement.

When thinking about how to make a game challenging, you should think about whether the game will be predictable or whether there will be random elements. A predictable game would react in exactly the same way each time it is played. This means that every game has the same level of difficulty, but with lots of practice a player may learn the level. With a random element, the game is less predictable, and the player will need to adapt their play to fit the game.

Choices and Consequences

Some games create choices that the player needs to make. Some choices just change the look or feel of the game (perhaps a different color costume), but I am really talking about choices that determine the play of the game. These could be a choice of direction, a decision of whether to battle or choose diplomacy, or what technology to pursue. This is a particularly good way of making a game challenging and having the player feel in control of the game. If providing a choice, then there should normally be a consequence to the choice that the player made which determines how they progress through the game.

Rewards and Progress

When a game includes a challenge, then it’s useful to reward the player which gives them a feeling of satisfaction that it was worth the effort. The reward can be just progressing through the levels (level up), or it could involve unlocking a new character or a power-up. These power-ups often can work in conjunction with the challenge where they help in completing the next level.

Likeable Characters

Many computer games put you in the role of a particular character or control of a team of characters. A character in your game may be specially created for your game, or it may be related to an existing franchise such as film or TV.

You may want to try and create a game that relates to your favorite film, perhaps a Harry Potter Wizard game, but you are likely to come across copyright issues. If it is for an existing franchise, then you need to be aware of the copyright and licensing restrictions. In general, if you use anything based around a place from a film, TV, or well-known character, then you will need to get permission from the owner of the franchise.

If you create your own characters, then you can give them their own personality and traits so that players can associate with them. In some cases, the characters can become personalities in their own right, just think about the Lara Croft character who started as a video character and was made into a film.

Also remember that characters don’t have to be people. They could be creatures or vehicles, or you could even make inanimate objects come to life.

Storyline/Historical Relevance

One thing that is often optional is whether the game follows a storyline or is set in a historical story. A story can help the player to relate more to the game and make them feel a part of the story. This can be a powerful motivation to keep playing the game.

A historical relevance is where you base your game around a real moment in history. A popular one is to have a game that is associated with a historic battle or an important time in history such as birth of the railways.

There are however many games that don’t have any kind of storyline and you just play for fun. It all depends upon the type of game you want to create.

Educational

Another optional aspect is whether the game is educational or not. This can include traditional children’s educational games such as addition and multiplication games, adult “brain games”, games to help teach you to play a musical instrument, or perhaps games that include references to historical events.

These can be an obvious goal or just a subtle feature to the game play. This can then tie into the reward, but instead of just a badge on the screen, the player can have the feeling that they have learned something that they can use away from the computer. They could also be very subtle, perhaps learning history through the storyline or by learning how to overcome an obstacle.

Takes an Appropriate Level of Time to Play

When thinking about how long it takes to play, you need to think of how the player is going to be playing. Is this a game you expect them to sit down at for a long time or something they may use to pass away a few minutes that they have spare during the day?

You should also think about whether the game can be saved and how long it can go between saves. It can be very frustrating if you have spent a long time trying to complete a level, but then don’t have the time to finish it. If you can save and resume that level, that may avoid the frustration of needing to be elsewhere.

Inclusivity

There are several ways that a game can be made more inclusive of other people. This may include additional/simplified controls for those with a disability that may find traditional keyboard controls difficult to use. Or it may include the ability to have different characters to represent the gender or skin color of those that may play the game.

It can be just as important to make sure you don’t use any negative stereotypes. In the past female characters have been used as damsels in distress, waiting for a male knight to come to her rescue. Thankfully, these are now becoming less common with more female characters taking a lead role in films and computer games.

Keeping these thoughts in mind when developing the game, there may be some simple things that can be implemented to make the game more appealing to a more diverse group of people.

Age Appropriate

Finally, I will mention that a game should be age appropriate. The games in this book are all designed to be family-friendly. If you are aiming for older players, then you may use less family friend language, but that would may make it less suitable for others. There is a similar thing with the amount of violence or the realism of harm inflicted. The target age of the game should also be reflected in the type of graphics used which will be considered more in the next chapter on graphics.

Improving Compass Game

Taking some of these suggestions, there are some things that can be done to make compass game a bit better. It won’t be possible to implement all these ideas in this chapter, but you will be able to add three new features to improve the gameplay:
  1. 1.

    Improve the timer so that there is more chance of completing even when the score is quite high.

     
  2. 2.

    Add some random obstacles to make the game more challenging.

     
  3. 3.

    Add a high score, which saves the highest achieved score.

     

These are about making the game more challenging, but also include aspects of a reward in terms of saving the high score.

Note

The code used in this chapter needs the same resources as Chapter 3. You will need to copy the source code from Chapter 4 to the same directory as Chapter 3’s source code.

Updated Timer

The problem with the game timer is that it decrements linearly, counting down the same length of time each go. This works well initially, but then after around 38 points scored, it gets so difficult; it is practically impossible to complete the task. What is needed is a timer function which reduces the time quite quickly at first (to create an element of challenge), but that over time it decreases less quickly giving a reasonable chance of still being able to complete the task.

This will involve some math. We will keep this simple at this stage. The formula to be used is x / (x + h). Here x is the score and h is an offset amount. We will use an offset of 10. This formula increases quickly at first, but then as x gets larger, it tends toward the value 1. To get the time for the timer, we then subtract this from the start time.

To determine the appropriate values, this was tested using the Python plot module. I won’t go into details on how the code works, but the source code is provided in a file called timedecaygraph.py. If you look in the source code, you should be able to see how it works. If you would like to try running the code, you will first need to install the plotly module. Future versions of the Mu editor will include a way of installing modules, but that is not available at the time of writing. To add a module, perform one of the following:
  • On a Raspberry Pi, you can install the module using
    sudo pip3 install plotly
  • On other Linux distributions

    Install either the same as previously or
    sudo pip install plotly
  • On Windows

    You will need to tell pip the location of the pkgs that Mu is using.

    On my computer, that is achieved using
    pip install plotly --target="c:usersstewartAppDataLocalMupkgs"

    You will need to replace stewart with the username that Mu was installed under.

  • On Mac OS X

    First create a separate directory to run the program and copy in the timedecaygraph.py file.

Create a file called setup.cfg with the following:
[install]
Prefix=
Then install the package using
pip3 install plotly --upgrade --target /Applications/mu-editor.app/Contents/Resources/app_packages

Once you have installed plotly, you can then run timedecaygraph.py from within Mu (change the mode from Pygame Zero to Python 3 first).

Depending upon your system, it may open the results in a web browser, but on others you may need to save the output as an html file and then opening it with your web browser manually.

Through adjusting the formula values, I found that the following formula worked well:
start_value + 1.5 - (start_value ∗ (i/ (i + 10)))
See the screenshot in Figure 4-1 showing how this new formula compares with the linear decay.
../images/488486_1_En_4_Chapter/488486_1_En_4_Fig1_HTML.jpg
Figure 4-1

Screenshot of graph showing different decay formulas

As you can see from the graph, the improved formula initially decreases much quicker than the linear decay, but the decay is much smaller as the score increases.

To implement this in the code, load the current version of code from the end of the previous chapter (compassgame-v0.1.py).

Remove the timer_decrement variable as that is no longer required.

Then in the update function, replace the following entry
timer = timer_start - (score ∗ timer_decrement)
with
timer = timer_start + 1.5 - (timer_start ∗ (score/ (score + 10)))

The value of 10 sets the decay speed and 1.5 is used to increase the offset. These could be changed to variables if you want to be able to fine tune the values.

This is included in the source code as compassgame-timer2.py.

Adding Obstacles

The next thing we can do is to add a bit more of a challenge through adding obstacles that the player must avoid. This can be done by adding new levels. The first level does not have any obstacles, level 2 adds some obstacles, level 3 adds some different obstacles, and so on. The screenshot in Figure 4-2 shows how the game will look with some obstacles to avoid.
../images/488486_1_En_4_Chapter/488486_1_En_4_Fig2_HTML.jpg
Figure 4-2

Compass game with obstacles to avoid

There are several changes needed for adding the obstacles. Start with the code from the end of Chapter 3 (compassgame-v0.1.py). The first is to add some more variables and definitions near the top of the file:
OBSTACLE_IMG = "compassgame_obstacle_01"
# Current score for this game
score = 0
# Score for each level
score_per_level = 20
# What level are we on
level = 1
#Obstacles - these are actors, but stationary ones - default positions
obstacles = []
# Positions to place obstacles Tuples: (x,y)
obstacle_positions = [(200,200), (400, 400), (500,500), (80,120), (700, 150), (750,540), (200,550), (60,320), (730, 290), (390,170), (420,500) ]
To display the obstacles, add this to the draw function making sure it’s not within any of the if-else clauses.
    for i in range (0,len(obstacles)):
        obstacles[i].draw()
Add a new set_level function which creates obstacle Actors. This can be toward the end of the tile.
def set_level(level_number):
    global level, obstacles, obstacle_positions
    level = level_number
    # Reset / remove all obstacles
    obstacles = []
    if (level < 1):
        return
    # Add appropriate number of obstacles - up to maximum available positions
    for i in range (0,len(obstacle_positions)):
        # If we have already added more than the obstacle level number then stop adding more
        if (i >= level_number - 1):
            break
        obstacles.append(Actor(OBSTACLE_IMG, obstacle_positions[i]))

This function is to be called whenever the level increases. As well as updating the global variable for the level number, it also creates the obstacles to be avoided.

The obstacles list starts out as empty, so no obstacles are drawn. When the level is changed above level 1, then new obstacles are created. These are added as Actors, but unlike our player, they won’t be able to move around the screen.

You will need to make sure that the obstacle image exists; otherwise, the program may hang with no error message making it difficult to know what has gone wrong.

Update the if(reach_target(target_direction)): block of code which is located near the bottom of the update function.
    if (reach_target(target_direction)):
        target_direction = get_new_direction()
        score += 1
        # check if we need to move up a level
        if (score >= level * score_per_level):
            set_level(level + 1)
        # Level score is the number of points scored in this level
        level_score = score - ((level - 1) * score_per_level)
        # Update timer - subtracting timer decrement for each point scored
        timer = timer_start + 1.5 - (timer_start * (level_score/ (level_score + 10)))

In this code the level increases every 20 levels. There will be no obstacles until 20 points are scored, then one obstacle will be added, and the second obstacle at 40 points and so on. This gives a reasonable level of difficulty for each level but is a lot of time to be playing when testing the game during development. You may want to reduce the value of score_per_level to 10 so that you can test that the obstacles are created correctly without needing to play for a long time. This is a common thing to do when developing games. In some games these are coded into the game as special “cheat codes” which would be used to jump direct to a certain level or add certain power-ups to help with testing.

The updated code is provided as compassgame-obstacle1.py in the source code. You can test the code and the obstacles will appear after the scoring 20 points, but the player is able to walk straight through them. Clearly some extra code is needed to do something when the player bumps into them. This is done by adding the following block of code at the end of the update function:
    # detect if collision with obstacle (game over)
    for current_obstacle in obstacles:
        if player.colliderect(current_obstacle):
            game_state = "end"
            return

This is the same as the code that is used to detect when the player reaches one of the sides of the game area but using a loop to compare against each of the obstacles in the list. If the player collides with an obstacle, then the game is set to the “end” state which triggers the end of the game. The code so far is included as compassgame-obstacle2.py in the source code.

Adding a High Score

The next feature is to add a high score. This tells the player what the previously attained highest score is and gives the player something to aim for. Typically, a high score will store multiple values along with their name or initials, but for now you should start with a single highest score value. One thing about a high score is that it needs to be saved somewhere so that it’s not lost when the computer is switched off. This will therefore cover how to save data to a file on disk and how to read it back. In the case of the Raspberry Pi, instead of a physical hard disk, it will be stored on an SD card, but using Python it is accessed in the same way as if it was on a disk.

In recent versions of Pygame Zero, there is a storage function which provides a simple way of storing information. At the time of writing, the function is not fully documented in the Pygame Zero documents. While the traditional Python file operations are more difficult to use, they are a useful tool for any Python programming. I recommend learning the method used here which will be useful for future Python programming.

Add the following new global variable near the top of the file:
HIGH_SCORE_FILENAME = "compassgame_score.dat"
Add two new functions, one to retrieve the high score from the disk (get_high_score) and the other to save the latest high score (set_high_score). These can be added at the bottom of the file.
# Reads high score from file and returns as a number
def get_high_score():
    file = open(HIGH_SCORE_FILENAME, 'r')
    entry = file.readline()
    file.close()
    high_score = int(entry)
    return high_score
# Writes a high score to the file
def set_high_score(new_score):
    file = open(HIGH_SCORE_FILENAME, 'w')
    file.write(str(high_score))
    file.close()

The get_high_score function reads a value from a file. First it opens the file using the open function. The first argument is the filename, the second is one or more characters to denote what mode the file should be opened in. In this case ‘r’ is for read, other common modes are write ‘w’ and append ‘a’. By default, the file is opened in the default text mode, but you could access the file in binary mode by using the ‘b’ option. For example, to open a file as read-only binary mode, you would use ‘rb’.

The file is returned as a file object which can then be used for reading the file. The function uses the file object with the readline method which will read a line from the file. Subsequent calls to readline will read in further lines. In this case we only have a single entry, so it only needs to be called once.

As the high score has been stored into a text file, it will be a string rather than as a number. As we need to be able to compare it to a number, it needs to be converted from a character to an integer using the int function. The resulting value is then returned.

You will also notice that there is a line file.close() which closes the file when the function has finished reading it. This is needed to free the file up, so that it can be opened by this or another program later.

The set_high_score function works in a similar way to get_high_score, but it is writing to the file instead of reading from it. First the global variable high_score is updated and then it opens the file in write mode and writes the high score value converted to a string. Then the file is closed.

Inside the update function, add the following code just before the line score = 0:
            high_score = get_high_score()
            if (score > high_score) :
                set_high_score(score)

Where this is placed in the code means that the new high score is not saved until after the next game is started. This is done to keep the code simple and make it easier to read. You may like to look at checking this once the game ends instead.

Finally, the code is needed to display the high score when the game is over. Replace the current print statement for “Game Over” with the following two lines:
        high_score = get_high_score()
        screen.draw.text("Game Over Score "+str(score)+" High score "+str(high_score)+" Press map or duck button to start", fontsize=60, center=(WIDTH/2,HEIGHT/2), shadow=(1,1), color=(255,255,255), scolor="#202020")

Try and Except

If you try and run the code now, then it will not work. Unfortunately, it fails without giving an error message, which can be frustrating. The reason for this is that there is no error checking on the file access. When the code tries to read in the high score file for the first time, then it doesn’t exist. You could add code to check to see if the file exists or not, but then there are other things that can go wrong during file operations. For example, the file may exist, but the value is corrupt. To avoid having to put in lots of different checks, we can use Python exception handling with the try except blocks of code.

The try except has three steps. First the “try” block will run the code; if there are any errors (exceptions), then they can be handled using “except” blocks, and then the “finally” block will run whether an exception has occurred or not.

Listing 4-1 shows a generic example of code used for handling an exception.
try:
    operation_that_may_fail()
except:
    print ("An exception occurred")
finally:
    print ("I run regardless")
Listing 4-1

Example of a try except exception handling

Here the code tries to run operation_that_may_fail. If it triggers an exception, then the except code will run. The finally block runs regardless.

You can also catch only certain exceptions. The following code shows how you would only catch IO errors:
except IOError:
You can also use multiple except blocks for different kinds of errors. When an exception occurs, you can access the exception attributes as follows:
except Exception as e:

This will provide an Exception value in the variable e. You can display this to the console screen using print (e). Exception handling is explained further in Chapter 11.

To use the try except exception handling on the access of the high score file, you can replace the two high score functions with the following new code:
# Reads high score from file and returns as a number
def get_high_score():
    try:
        file = open(HIGH_SCORE_FILENAME, 'r')
        entry = file.readline()
        file.close()
        high_score = int(entry)
    except Exception as e:
        print ("An error occurred reading the high score file :" + str(e))
        high_score = 0
    return high_score
# Writes a high score to the file
def set_high_score(new_score):
    global high_score
    high_score = new_score
    try:
        file = open(HIGH_SCORE_FILENAME, 'w')
        file.write(str(high_score))
        file.close()
    except Exception as e:
        print ("An error occurred writing to the high score file :" + str(e))

The updated code is named compassgame-highscore.py.

The way that the exception is handled in the code means that if there is an exception, the program continues to run. In the case of a read error, the high_score is just given a value of zero. This is acceptable here because the game can still be played without saving the high score. On some programs, a failure to save the data may be a critical issue and would therefore result in other actions, possibly including terminating the program.

A simple high score like this can add additional game play for a while, but eventually you will reach a point where it is difficult or even impossible to beat the score. Many games overcome this by adding different elements or by earning credits when you play which can be used to buy objects to make it easier to gain a higher score. In a military game, this may be armor or a more powerful weapon. That is beyond the scope of this book as it would need a lot of additional code to include a reward-based system but is something you may want to consider when designing your own games.

This game has just implemented a few of the ideas. This is enough to make the game a bit more enjoyable. The compass game is never going to be a particularly good game in its current form as it is a little too simplistic. It is however a good game for demonstrating how to include graphics into a game and the basics of computer animation. The new features should give you an idea of how to implement some of these features to make your own game more interesting.

Summary

You have now seen how some additional elements can change the game play and make a game more interesting. This has been achieved by adding a new feature at a time which is a feature of agile programming.

This chapter has also shown how you can include timing elements to add a challenge element. It has then shown how files can be read and written to and how to handle errors that can occur when accessing files.

The next chapter will look at how graphics can be created and used in games.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.181.81