Chapter 10. Timing and Sound: The Oil Spill Game

Timing is not necessarily a subject closely associated with sound effects or music in a game, but we’re going to be using both extensively in this chapter while making a very interesting game. The chapter project is called The Oil Spill Game. While studying timing and new gameplay concepts, you will learn how to load and play sound files.

You will learn how to:

  • Load and play a sound using the Pygame mixer

  • Use a back buffer to better control drawing

  • Create a fast-paced arcade-style game called The Oil Spill Game

Examining the Oil Spill Game

The Oil Spill Game is shown in Figure 10.1. In this game, the player has to clean up an oil spill using a water cannon that fires a high-pressure stream of water at the contaminated areas to clean them up. At least, that’s the theory! In reality, we just use the mouse cursor to click on an oil splotch to clean it up! This game uses color alpha channel manipulation to erase the oil splotches and is a good exercise in user input as well as program logic.

The Oil Spill Game uses timing and sound with fun gameplay.

Figure 10.1. The Oil Spill Game uses timing and sound with fun gameplay.

Sound

The audio system we’re going to use is included with Pygame in the pygame.mixer module. We will learn how to create an audio clip by loading an audio file and then playing the clip in game. Pygame offers some advanced features to control the channels for audio playback, control the mixing of sounds, and even the ability to generate sounds. Pygame does not always initialize the audio mixer, so it’s best to initialize it on our own just to be sure our program doesn’t crash upon trying to load and play a file. This only needs to be called once:

pygame.mixer.init()

Loading an Audio File

We use the pygame.mixer.Sound() class to load and manage audio objects. Pygame supports two audio file formats: uncompressed WAV and OGG audio files. OGG is a good alternative to MP3 swith no licensing issues. If you wanted to use MP3 in a commercial game, you would have to license the MP3 technology in order to use it. OGG offers similar quality and compression without the licensing! As a result, OGG is a good choice for longer-running music tracks in your game. Of course, you can use OGG for regular short-duration sound effects too. It just tends to be more common to use WAV for shorter audio files and OGG for the longer ones. That is entirely up to you, though. Since WAV files must be uncompressed, you cannot load any WAV file created with a special codec. Since they are uncompressed, the file sizes will tend to be on the large size if they are more than a few seconds in length. Because of the size issue, OGG is recommended for audio clips longer than a few seconds.

audio_clip = pygame.mixer.Sound("audio_file.wav")

Note

Loading an Audio File

If you have audio files in some format (like WAV or MP3) that you want to convert to OGG in order to use the format, you will need to convert the files with an audio converter. One good example is free software called Audacity which has advanced audio editing features as well. You can download it from http://audacity.sourceforge.net.

Playing an Audio Clip

The pygame.mixer.Sound() constructor returns a Sound object. Among the several methods in this class are play() and stop(). These are easy enough to use, but we’re not going to use Sound for playback, just for loading and storing the audio data. For playing sounds, we’re going to use pygame.mixer.Channel. This is a class that offers more versatility than Sound for playback. The Pygame audio mixer handles channels internally, so what we do is request an available channel.

channel = pygame.mixer.find_channel()

This will request an unused channel and return it so we can use it. If there are no available channels, then the mixer returns None. Because this may be a problem, if you want to override that default behavior, then pass True to find_channel() in order to force it to return the lowest-priority channel available.

channel = pygame.mixer.find_channel(True)

Once we have a channel, we can play a Sound object by using the Channel.play() method:

channel.play(sound_clip)

Note

Playing an Audio Clip

There are additional features in both the Sound and Channel classes that you can find in the Pygame documentation online at www.pygame.org/docs/ref/mixer.html.

Building The Oil Spill Game

The Oil Spill Game is an interesting experiment in color manipulation with the use of alpha to cause the oil splotches to disappear as the user “wipes” them with the mouse cursor. This game could be called “stain remover” just as well, and the colors could be changed to any theme for variations on the theme of “oil cleanup.”

Gameplay

The oil splotches are indeed sprites, each created as a root pygame.sprite.Sprite, and stored in a sprite group with pygame.sprite.Group. A custom class inherits from MySprite to add some new features required for the game, but they are otherwise pygame.sprite.Sprite-based MySprite objects. But there is no source artwork—the circles are drawn onto the sprite image at load time.

Timing

Timing is important in The Oil Spill Game. Once every second, a new oil sprite is added to a group that is drawn at a random location on the screen. Each oil sprite has a random radius so that after a while the screen does indeed begin to look like oil is dripping all over it. The trick is to add a new oil sprite only once per second, using timing. There are a few ways to do this, but perhaps the easiest way is with a millisecond timer. First, we create a root time:

     last_time = 0

Then, this root time value is updated whenever the one-second interval is reached using pygame.time.get_ticks():

    ticks = pygame.time.get_ticks()
    if ticks > last_time + 1000:
        add_oil()
        last_time = ticks

The key is saving the current ticks value in last_time when a timing even occurs. If the ticks value is not saved in last_time, then it will only happen once and then never again.

Oil Mess

When the mouse cursor moves around on the screen, circle-based collision detection is used via pygame.sprite.collide_circle_ratio() to determine when the mouse cursor is over an oil splotch sprite. Figure 10.2 shows the gameplay when the cursor is over such a sprite.

Identifying oil splotches on the screen with the mouse cursor.

Figure 10.2. Identifying oil splotches on the screen with the mouse cursor.

The oil sprites have a custom image that is created in memory (not loaded from a bitmap file). That image has a dark circle drawn on it with a random radius to represent a single oil splotch.

    image = pygame.Surface((oil.radius,oil.radius)).convert_alpha()
    image.fill((255,255,255,0))
    oil.fadelevel = random.randint(50,150)
    oil_color = 10,10,20,oil.fadelevel
    r2 = oil.radius//2
    pygame.draw.circle(image, oil_color, (r2,r2), r2, 0)
    oil.set_image(image)

Cleaning the Oil

By clicking the mouse on an oil splotch, that has the effect of “cleaning” it because the dark circle fades out as it is being cleaned and disappears. This is done by changing the alpha channel color component of the sprite until it is completely invisible. When alpha reaches zero, then the sprite is removed from the group.

Cleaning the oil splotches by “washing” them with the mouse.

Figure 10.3. Cleaning the oil splotches by “washing” them with the mouse.

Identifying an oil sprite in the group could be done with a group collision method in pygame.sprite, but I wanted to get a little more control over how collision response works, so this game iterates the group of oil sprites manually and calls pygame.sprite.collide_circle_ratio(0.5). This makes the collision radius half of the normal value to increase the game’s challenge a little bit.

    for oil in oil_group:
        if pygame.sprite.collide_circle_ratio(0.5)(cursor, oil):
            oil_hit = oil

Washing the Background

Although it is not necessary, to make the game a little more fun, clicking the mouse cursor on the screen causes it to appear to be cleaning the background as well. This helps the player get a feel for the gameplay before any oil splotches even show up. Two colors are defined: darktan for the normal background, and tan for the cleaned spots:

darktan = 190,190,110,255
tan = 210,210,130,255

Note that a fourth color component (alpha) is added to these colors. This is required when working with 32-bit color. If you don’t specify an alpha channel when creating the color, then it is not available later.

When the user clicks the mouse button anywhere on the screen, a tan circle is drawn to help the player get a feel for what they need to do in the game. It also makes the game more fun.

    b1,b2,b3 = pygame.mouse.get_pressed()
    mx,my = pygame.mouse.get_pos()
    pos = (mx+30,my+30)
    if b1 > 0:
        pygame.draw.circle(backbuffer, tan, pos, 30, 0)

Source Code

The source code for The Oil Spill Game is not long at all with 147 lines (including blanks and comments), but the source code is rather packed and efficient, so we’ll list the entire source code here for easy reference. It is an interesting game. I think there’s potential here by designing some gameplay challenges, adding scoring, etc.

# Oil Spill Game
# Chapter 10
import sys, time, random, math, pygame
from pygame.locals import *
from MyLibrary import *
darktan = 190,190,110,255
tan = 210,210,130,255

class OilSprite(MySprite):
    def __init__(self):
        MySprite.__init__(self)
        self.radius = random.randint(0,60) + 30 #radius 30 to 90
        play_sound(new_oil)

    def update(self, timing, rate=30):
        MySprite.update(self, timing, rate)

    def fade(self):
        r2 = self.radius//2
        color = self.image.get_at((r2,r2))
        if color.a > 5:
            color.a -= 5
            pygame.draw.circle(self.image, color, (r2,r2), r2, 0)
        else:
            oil_group.remove(self)
            play_sound(clean_oil)

#this function initializes the game
def game_init():
    global screen, backbuffer, font, timer, oil_group, cursor, cursor_group

    pygame.init()
    screen = pygame.display.set_mode((800,600))
    pygame.display.set_caption("Oil Spill Game")
    font = pygame.font.Font(None, 36)
    pygame.mouse.set_visible(False)
    timer = pygame.time.Clock()

    #create a drawing surface
    backbuffer = pygame.Surface((800,600))
    backbuffer.fill(darktan)

    #create oil list
    oil_group = pygame.sprite.Group()

    #create cursor sprite
    cursor = MySprite()
    cursor.radius = 60
    image = pygame.Surface((60,60)).convert_alpha()
    image.fill((255,255,255,0))
    pygame.draw.circle(image, (80,80,220,70), (30,30), 30, 0)
    pygame.draw.circle(image, (80,80,250,255), (30,30), 30, 4)
    cursor.set_image(image)
    cursor_group = pygame.sprite.GroupSingle()
    cursor_group.add(cursor)

#this function initializes the audio system
def audio_init():
    global new_oil, clean_oil

    #initialize the audio mixer
    pygame.mixer.init() #not always called by pygame.init()

    #load sound files
    new_oil = pygame.mixer.Sound("new_oil.wav")
    clean_oil = pygame.mixer.Sound("clean_oil.wav")


def play_sound(sound):
    channel = pygame.mixer.find_channel(True)
    channel.set_volume(0.5)
    channel.play(sound)

def add_oil():
    global oil_group, new_oil

    oil = OilSprite()
    image = pygame.Surface((oil.radius,oil.radius)).convert_alpha()
    image.fill((255,255,255,0))
    oil.fadelevel = random.randint(50,150)
    oil_color = 10,10,20,oil.fadelevel
    r2 = oil.radius//2
    pygame.draw.circle(image, oil_color, (r2,r2), r2, 0)
    oil.set_image(image)
    oil.X = random.randint(0,760)
    oil.Y = random.randint(0,560)
    oil_group.add(oil)

#main program begins
game_init()
audio_init()
game_over = False
last_time = 0

#repeating loop
while True:
    timer.tick(30)
    ticks = pygame.time.get_ticks()

    for event in pygame.event.get():
        if event.type == QUIT: sys.exit()
    keys = pygame.key.get_pressed()
    if keys[K_ESCAPE]: sys.exit()

    #get mouse input
    b1,b2,b3 = pygame.mouse.get_pressed()
    mx,my = pygame.mouse.get_pos()
    pos = (mx+30,my+30)
    if b1 > 0: pygame.draw.circle(backbuffer, tan, pos, 30, 0)

    #collision test
    oil_hit = None
    for oil in oil_group:
        if pygame.sprite.collide_circle_ratio(0.5)(cursor, oil):
            oil_hit = oil
            if b1 > 0: oil_hit.fade()
            break

    #add new oil sprite once per second
    if ticks > last_time + 1000:
        add_oil()
        last_time = ticks

    #draw backbuffer
    screen.blit(backbuffer, (0,0))

    #draw oil
    oil_group.update(ticks)
    oil_group.draw(screen)

    #draw cursor
    cursor.position = (mx,my)
    cursor_group.update(ticks)
    cursor_group.draw(screen)

    if oil_hit: print_text(font, 0, 0, "OIL SPLOTCH - CLEAN IT!")
    else: print_text(font, 0, 0, "CLEAN")
    pygame.display.update()

Summary

This chapter covered the audio system in Pygame via pygame.mixer and showed how to load and play a sound file in the context of gameplay events. An interesting game called The Oil Spill Game was used as a backdrop for an audio demonstration program, but it ended up having more interesting color and sprite manipulation code with audio something of an afterthought. Overall, though, there were some valuable concepts and useful source code presented.

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

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