CODE CREATIVE
CREATING THE MAP
Using arrays to create worlds
Raiden 2
Raiden 2 is a vertical scrolling shooter classic arcade that has close to perfect game dynamics. A ship starts at the bottom of the screen and shoots bullets to destroy enemy planes and tanks. Along the way, the ship can collect power ups that will increase the amount of bullets that are shot or the amount of bombs that are available. At the end of every level is a boss.

Now Try
  1. Watch an expert beat the first round here.
  2. Play a round or two yourself in the window to the right.
Now that we have familiarized ourselves with the vertical scrolling perfection that is “Raiden 2”, let’s start programming the game. Of course every game needs a start screen, so let’s start there!

Now Try
  1. Create a start screen in which the user needs to mouse click the screen or press enter to begin. Be sure to use a background and sound effects.
  2. Create a game loop that runs once instead of forever. We are doing this so we can test the beginning and ending screens.
  3. Create an ending screen in which the user needs to mouse click the screen or press enter to play again. Be sure to use a background and sound effects.

Loading and Building the Level
Great, you now have a beginning screen that introduces the user to your game. Something you need to understand is that when creating a vertical shooter game, we must follow a modified gaming framework which is shown in the code below:
import pygame, os

os.environ['SDL_VIDEO_CENTERED'] = '1'  # Force static position of screen

# Constants
WHITE = (255, 255, 255)

WIN_W = 16*32
WIN_H = 700


def main():
 pygame.init()

 # Create Game Variables
 fps = 60
 clock = pygame.time.Clock()
 play = True
 pygame.display.set_caption('Raiden 2 Clone')
 screen = pygame.display.set_mode((WIN_W, WIN_H), pygame.SRCALPHA)

 # Create Game Objects

 # Create Groups

 # Load Level

 # Build Level

 # Gameplay

 while play:
      # Checks if window exit button pressed
      for event in pygame.event.get():
           if event.type == pygame.QUIT: 
                                                    sys.exit()
           elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                     pygame.quit()
                     sys.exit()

     # Update
     screen.fill(WHITE)

     # Draw Everything

     # Limits frames per iteration of while loop
     clock.tick(fps)
     # Writes to main surface
     pygame.display.flip()


if __name__ == "__main__":
 main()
Notice that the only aspect that is different is the addition of two new sections: ‘# Load Level’ and ‘# Build Level’. Loading a level refers to the process of creating an array and filling it with characters that will be used to determine which background to set on each 32 pixel by 32 pixel piece of the screen. Building the level refers to the process of creating a set of 32 by 32 pixel surfaces to blit to the screen. The end result of executing these two processes one after the other is the creation of a screen with a background that our ship can play in.

When creating a map, what we need to do is create a two-dimensional array that is filled with characters that we will later use to fill the screen with the appropriate background. This background can be an image from a tile set, or simply a color. Since we won’t be exploring adding images and animation in this lesson series, we will simply be using the information in the 2-D array to tell us what color to set the background tile as. However, be aware that if we were creating an actual game, we would be using this information to determine which art tile to use for that 32 by 32 pixel space of the background.

As a side note, don’t be worried if you are feeling nervous and confused, we will be going through the process of level creation slowly and it is my hope that you will understand the process once you see the finished product running. I am confident once you complete this portion of the tutorial and then come back and read this text, my words will be very meaningful.

Ok, let’s create our array and fill it with the information we need to draw our background! Add the following code to your program:
# Create Groups … # Load Level level = [ "PPPPPPPPPPPPPPPP", "P P", "P P", "P PPPPP", "P P", "PPPPPPPP P", "P P", "P P" "P P", "P PPPPP", "P P", "P P", "PPPP P", "P P", "P PPPP", "PPPPP P", "P P", "P P", "P PPPPPPP", "P P", "PPPPPP P", "P P", "PPPPPPPPPPPPPPPP", ] # Build Level … # Gameplay …
Looking at our code above, we’ve done it! We have created an array called ‘level’ and we filled it with either a blank space or the letter ‘P’. Each letter in our ‘level’ array represents a portion of the screen that is 32x32 pixels. The blank spaces will tell the program that to ignore the corresponding area on the screen and not blit anything there. This will result in a blank space being present at that location. The ‘P’ letters will tell us that we need to fill that area with a 32x32 pixel surface that has a light grey color. To create the simplest background possible, we will simply be filling our screen with either a blank 32x32 pixel space represented in the ‘level’ array by a blank space, or a 32x32 pixel space that is a shade of light grey represented by the letter ‘P’.

Understand that we are simply filling the screen with two shades of 32x32 pixel surfaces for the sake of simplicity. We very easily could have filled the portions of the screen represented by the empty spaces using a tile image that shows water and filled the portions of the screen represented by the letter ‘P’ using a tile image that shows ground. Moreover, we could have filled our ‘level’ array with a host of different letters, each representing a different tile from a tile set such as the one seen to the right to create a more interesting world for our ship to fly around in.

Platforms
Let’s move forward with our utterly simple ‘level’ array that will either show a blank space or a shade of grey for our background. In order to create our level, we will need to create an empty list that will contain 32x32 pixel surfaces which we will call ‘Platforms’. We also need an empty list and a set of counters to contain the positional information for each ‘Platform’. Add the following code to your program:
# Load Level
…

# Build Level
x = y = 0
for row in level:
 for col in row:
      if col == "P":
           p = Platform(x, y)
      x += 32
 y += 32
 x = 0
 
# Gameplay
…
Taking a look at the code above, we have a nested for loop that will run through the 2D ‘level’ array we created earlier. Understand that ‘row’ will contain the entire row of characters in the 2D array and ‘col’ represents the columns. The first time through the for loop, ‘row’ will be equal to an array that is made up of a bunch of ‘P’s, since that is what is contained in the first row of our ‘level’ array. Then when we get to the inner for loop, ‘col’ is equal to each ‘P’ in the ‘row’.

Before we use our nested for loop, we create an empty list called ‘platforms’ and a couple of counter variables ‘x and y’. The ‘x’ variable is a counter that increments by 32 since our surfaces are 32 pixels wide. For each ‘col’ in ‘row’, the ‘x’ variable is incremented by 32. If the condition is met that ”col == P”, we know that we need to place a grey block down and the ‘x’ variable tells us exactly where to blit it to the screen on the x-axis. Notice, that the ‘y’ variable is incremented by 32 every time the for loop moves down a row in the ‘level’ array. This is because our surfaces are also 32 pixels tall and once we move down a row, our ‘y’ variable needs to be incremented in order to tell our ‘Platform’ where it should be blitted to the screen on the y-axis.

Try running your code. You should get an error stating that ‘Platform’ is not defined. That is absolutely correct, we need to define our ‘Platform’ class. Luckily, ‘Platform’ is as simple as it gets. Add the following code to your program above your ‘main()’ function and below your constants:
# Constants
…

class Platform(pygame.sprite.Sprite):
 def __init__(self, x, y):
      pygame.sprite.Sprite.__init__(self)
      self.image = pygame.Surface((32, 32))
      self.image.convert()
      self.image.fill(LIGHT_GREY)
      self.rect = pygame.Rect(x, y, 32, 32)


def main():
                    …	
By looking at the code above, you can see that you need to add to our constants a global variable called ‘LIGHT_GREY’. Be sure to define it appropriately. We want it to act as a background, so it shouldn’t be a dark color.

We can see that every time we are creating an instance of the ‘Platform’ class, we are simply creating an object that has a surface that is 32x32 pixels, set it’s position on the screen to a specified x and y coordinate, and set its background color to a light grey color.

Before moving forward, notice that the ‘Platform’ class inherits from ‘Sprite’. This is because we want to be able to draw all of the background blocks by adding the objects to a group and use the ‘draw’ functionality of the ‘Sprite’ class on the group.

Now that we have added the ‘Platform’ class to our program, try running it. You will notice that even though we added the class, there is no change! Remember that all we have done so far is create 32x32 pixel surfaces and give each one a x and y coordinate to be placed at. We haven’t done anything that would initiate a change to our screen, namely adding the objects to a group and running the draw functionality on the group. Let’s do that now!

Now Try
  • Create a group called ‘platform_group’.
  • Add all of the ‘Platform’ objects to the group just after they are created.
  • Call the ‘draw’ method on the ‘platform_group’.

  • If you have completed the steps above correctly, you will see a game window similar to that found on the right when you run your program.

    Congratulations! Hopefully all the mumbo jumbo I was talking about above now makes more sense seeing the final product. If not, please go back and re-read the text above and ask any me to clear up any questions you might have. It is fairly critical that you understand the map creation algorithm as it is used in the creation of every 2D game.

    Adding the Ship
    To add the ship, we need to define a ‘Ship’ class. Add the following class definition to your code, just below the definition for ‘Platform’:
    class Ship(pygame.sprite.Sprite):
       def __init__(self, container):
           pygame.sprite.Sprite.__init__(self)
           self.side_speed = 5
           self.top_speed = 4
           self.image = pygame.Surface((SHIP_WIDTH, SHIP_HEIGHT)).convert()
           self.image.fill(GREEN)
           self.rect = self.image.get_rect()
           self.rect.centerx = container.centerx
           self.rect.y = container.bottom - self.rect.height * 3
           self.container = container
    
       def update(self):
       # Movement
       ...
    
       self.rect.clamp_ip(self.container)
    

    The first part of the class definition I want you to notice are the constants ‘SHIP_WIDTH’ and ‘SHIP_HEIGHT’. Be sure that when you define these constants, you do so in terms of ‘WIN_W’ and ‘WIN_H’. This is a good idea so that if the screen size changes, the size of the elements of the game stay the same relative to one another. I used ‘WIN_W/15’ and ‘WIN_H/15’ respectively.

    The next part of the class definition I want you to notice is the parameter ‘container’. ‘container’ will contain a rect describing the entire screen. We are using it so that we can more easily place the ship at its starting position in the middle of the screen using the line ‘self.rect.centerx = container.centerx‘. What is happening here is the center of the ship’s rect on the x-axis is being set to the the center of the ‘container’ on the x-axis. In effect, we are placing the ship in the middle of the screen on the x-axis.

    Lastly, let’s move our attention to the ‘update’ method. Notice the line of code ‘self.rect.clamp_ip(self.container)’. What ‘self.rect.clamp_ip()’ does is limit the x value of the ship’s rect to the x value of the ‘container’s rect and the y value of the ship’s rect to the y value of the ‘container’s rect. In effect, this one line of code is keeping our ship within the bounds of the screen. Are you now understanding why it is helpful to pass ‘container’ to the ship’s constructor?

    Try running your code. You will notice that there is no change to the screen and the ship is nowhere to be seen. You probably can guess that’s because we haven’t created an instance of ‘Ship’, nor have we created a ‘ship_group’, nor have we added the instance of ‘Ship’ to ‘ship_group’, nor have we called ‘update’ on our ‘ship_group’, nor have we called ‘draw’ on our ‘ship_group’. You know the drill. Let’s get our ship on the screen.

    Now Try
    Create an instance of ‘Ship’ and place it on the screen.

    Try running your program now and you should see the ship placed in the middle of the screen. This is our container making our life easier. You will also notice that the ship is placed a couple of ship lengths from the bottom of the screen. What you should also notice is that the ship doesn’t respond to your keyboard commands. Let’s fix that!

    Now Try
    Add to the ‘update’ method so that the ship moves using the UP, DOWN, LEFT and RIGHT arrow keys

    It should now be proven to you that ‘clamp_ip’ works as previously described. Don’t you love how well developed Pygame is? If it wasn’t for developers adding to Pygame’s library, programmers all over the world would need to juggle an array of variables whenever they wanted to get the ship to stay within the screen.

    Congratulations!
    You have created a map for your game!