CODE CREATIVE
VERTICAL SCROLLING
Creating the illusion of movement
The Camera
The introduction of a camera was a major evolutionary step in game development. The camera was first was seen in 1977, with the release of ‘Super Bug’ on the Atari game system.

Although the developers of ‘Super Bug’ can claim credit for creating the first side-scroller, it was the second installment of the ‘Mario Bros’ series that placed side-scrolling on the map and hearts of 30-somethings all across the world. If you ever find yourself talking to a 30-something, ask them about the first time they played ‘Super Mario Bros’ and you’ll see their eyes glaze over as their mind races with nostalgic thoughts of gyroscopes and staircases glitches.

We are now going to extend our game to incorporate a camera to follow the ship around. One difference to note between a side-scroller like ‘Super Mario Bros’ and a vertical scrolling shooter like ‘Raiden 2’ is that the camera cannot be controlled as it continues to move upwards. The camera moves independently of the ship while the camera on most side scrollers follows the controllable character.

Although it’s counterintuitive, creating a game whose camera follows the controllable character around is easier to construct that one that moves independently of the main character. This is because we need to create a camera that continuously moves up the level independently from the ship and also follows the ship as it moves side to side.

As we construct our camera, we will first build one that follows the ship around, then extend that algorithm so that the camera moves upward independently of the ship.

Extending the Map
The first step to adding a camera would be to extend our map so that the ship has an area bigger than the screen in which it can move around. Let’s add to our map so that it contains 3 more units on each side for a total of 22 units instead of 16. That would add three 32 pixel blocks to each side of the screen if our viewable screen size stays the same. Let’s also extend our map on the y-axis so that it contains 10 more units making a total of 33 units tall.

Understand that our absolute screen size can, and will, change, so don’t fret if these screen dimensions don’t sit well with you. In the end, we will be creating a game that scrolls up for 1 minute, which adds up to far more than 30 units in length.

Now Try
  • Add to the screen size by modifying the contents of the ‘level’ array. Make the map 22 units wide and 33 units tall.

  • Try running your program. You notice that now you only see the top left corner of the screen and your ship is unseen at the bottom of the map? This is because we placed the ship in the middle of the ‘container’ rect, which we defined to be WIN_W pixels wide. We also placed the ship 3 ship lengths above the bottom of the screen. Effectively, this places the ship at the bottom of the map at a currently unseen portion of the world. Notice that you can move the ship up and it will appear after a few seconds.

    We now have the stage set and we are ready to incorporate a camera.

    Incorporating a Camera
    Before we begin coding a camera algorithm, you must understand that the way the camera works is by drawing the ship in the middle of the screen and all the other sprites in the game, namely all the sprites in ‘platform_group’, relative to the main character. If the main character moves to the right 5 pixels, the camera algorithm draws everything to the left by 5 pixels. If the main character moves to the left 5 pixels, the camera algorithm draws everything to the right by 5 pixels.

    Repeating the algorithm above, we can see that as our user moves the ship around the screen by holding down the arrow keys, the ship will appear to move around the map. In actuality, the ship stays in the middle of the screen and all the background objects move around, creating the illusion that the main character is exploring the world. To extend this idea further, if we introduces enemy sprites, we would simply have the enemy sprites run through the camera algorithm and also be moved relative to the main character’s keystrokes.

    Add the following code to the top of your program where your classes are defined:
    class Camera(object):
        def __init__(self, total_width, total_height):
            self.state = pygame.Rect(0, 0, total_width, total_height)
    
        def apply(self, target):
            return target.rect.move(self.state.topleft)
    
        def update(self, target_rect):
            self.state = pygame.Rect(-target_rect.x + WIN_W/2, -target_rect.y + WIN_H/2, self.state.width, self.state.height)
    
    Here things may get very confusing, so you will need to pay attention closely and perhaps re-read this portion of the tutorial over again

    The 'Camera' class is used to run an algorithm that calculates how much to move all the other sprites on the x and y axis in relation to the ship. Let’s call this amount of movement an offset. The offset information will be stored as a rect in the property 'state'. When an object of type 'Camera' is created, the constructor is run and is given the total height and width of the map as parameters. Let’s do this now by adding the following line of code to your program:
    # Build Level
    …
     
    # Set Up Camera
    total_width = len(level[0])*32
    total_height = len(level)*32
    camera = Camera(total_width, total_height)
     
    # Create Game Objects
    …
    
    Now our program has a camera object and is passing the constructor the total height and width of the map. The constructor uses this information to define the 'state' property to be a rect describing the entire map.

    Now that we have a camera object, we can use it in our game loop to calculate the offset in relation to the ship and move all the platform objects by that offset. We need to give the ship's rect to the 'update' method, which calculates how much each platform object needs to be moved in relation to the ship. The offset information is then stored in the 'state' property. Let’s invoke 'update' now by adding the following code to our game loop:
    # Update
    camera.update(ship.rect)
    …
    
    Now Try
  • 1. We are going to use a piece of paper for the next couple of assignments. Get one and use the sides to represent your game's total height and width. Let's use a total_height of 1000 pixels and a total_width of 500 pixels. 2. Label the (x, y) coordinates of the corners of your paper. 3. Draw your ship at (250, 500). 4. Draw the screen window around the ship using a WIN_W of 300 pixels and a WIN_H of 600 pixels. 5. Label the (x, y) values of the 4 corners of the screen window. Remember that the top left corner of the screen window corresponds to (0, 0). 6. Run your ship through the 'update' method and calculate the x and y values of the 'state' property. Write down your answer on your sheet.

  • We are storing the offset information calculated from the ship's position in the camera’s 'state' property as a rect because then we can simply use Rect’s ‘move’ method when implementing the Camera's 'apply' method to calculate the offset for each platform. Understand that we could have also just kept this information in a x and y variable. The change we need to make to our main() function is that we won’t simply blit out the platforms to their corresponding x and y values. Instead, we will run 'apply' on each platform object to add the x and y values of the 'state' property to each platform's actual position. Then we will place the platform at the x and y value returned by the 'apply' method. This will effectively place the platform objects in the game window in the correct position rather than in their actual position.

    You must understand that 'move' adds the x value of the argument to the x value of the target's rect and likewise for the y values. This means that if the ship is in the bottom left corner, the Camera will run the 'apply' method for every platform background object and calculates how much the platform should be shifted to the right and upward so that the ship appears to be in the bottom left corner of the map. Likewise, if the ship is in the top right corner, the 'apply' method returns a rect whose (x, y) values are such that when they are added to the platforms, the platforms move to the left and downward creating the same illusion of movement.

    Now Try
  • Draw the platforms on your piece of paper. Now, draw a T chart. Label the left side 'Actual (x, y)'. Label the right side 'Camera (x, y)'. Pick a platform object that lands inside the viewable window and write down its rect's actual x and y values. Run the platform object through the 'apply' method and write down the returned (x, y) values of the platform under the 'Camera (x, y)' column. Do this for 2 more platform objects within the viewable window.

  • Now that we can see what the ‘apply’ method calculates, let’s modify our code to blit the platforms on the screen after the offset is applied. Add the following code to your game loop:
    # Update
    …
    
    # Print Everything
    for p in platform_group:
        screen.blit(p.image, camera.apply(p))
    
    # Limits frames per iteration of while loop
    …
    
    Run your program. You will notice that the game kind of scrolls, however, your ship begins off the bottom of the viewing window. The reason for this is that we haven’t applied the offset to the ship! Let’s do that now by adding the following code:
    				
    # Update
    …
    
    # Print Everything
    …
    for s in ship_group:
        screen.blit(s.image, camera.apply(s))
    
    # Limits frames per iteration of while loop
    …			
    
    Notice here that we are using a for loop to run through the ‘ship_group’. This seems pointless since we only have one ship in the group, but what if we create a true clone that allows two players just like the arcade version? Another reason for the loop is that it’s the only way to access objects in a group.

    Run your program and you will see that your ship starts 3 ship lengths from the bottom of the screen and when you move we get a nice scrolling action! Notice that you can extend the width and length of your map and your scrolling action will remain.

    Now Try
  • Add 8 more units for each row so that we can get the ship scrolling sideways as well as vertically. Compare your game’s behavior with your worksheet and answer the following question on Interactive Python under the Class Assignments page: Explain in detail how the Camera class creates a side-scrolling camera that follows the ship. Be sure to use appropriate terminology and specific names of methods and properties. (8-15 sentences)

  • Smart Camera
    Congratulations on adding vertical-scrolling to your program! Now, let’s create an even better vertical-scroller that doesn’t show the space outside of the map. Let’s rearrange our ‘update’ method as follows:
    					
    def update(self, target_rect):
        x = -target_rect.x + WIN_W/2
        y = -target_rect.y + WIN_H/2
    
        # Stop scrolling at left edge
        if x > 0:
            x = 0
        # Stop scrolling at the right edge
        elif x < -(self.state.width-WIN_W):
            x = -(self.state.width-WIN_W)
        # Stop scrolling at top
        if y > 0:
            y = 0
        # Stop scrolling at the bottom
        elif y < -(self.state.height-WIN_H):
            y = -(self.state.height-WIN_H)
    
        self.state = pygame.Rect(x, y, self.state.width, self.state.height)
    
    If you take a close look, you will realize that there isn’t much of a difference between the code we have replaced and what we have now. One difference is that we are now calculating x and y before defining ‘state’ to be a rect with those x and y values. Also notice, that we are using a set of conditional statements to set a min and max value for x and y. Why you may ask? Let’s find out.

    Now Try

    Flip your worksheet over and do the following:
    1. Draw a large box with 2 inch margins on all sides. This box will represent the entire world with the following dimensions (500, 1000).
    2. Draw the ship at (100, 500).
    3. Run the ship through the update algorithm and write down the x and y values of the ‘state’ property.
    4. Do the same for the ship at (0, 500) and (-100, 500).
    5. Take the ‘state’ rect values you have calculated and run the ‘apply’ method on the ship’s rect in each of the three cases above. Write down the corresponding x and y values that are returned next to the ships.
    6. Understanding that the x and y values that are returned by the ‘apply’ method are used to blit the ship to the screen in the game loop, describe in detail how the changes to the ‘update’ method forces wanted behavior on our ship. Use names of methods, properties and variable names in your description. Also, since we are also running ‘apply’ on our platforms, how is the ‘apply’ method creating wanted behavior to our game? (10-15 sentences)
    Vertical Scrolling
    We have created a world in which our ship can fly around in, however, the camera in “Raiden 2” automatically moves up until it reaches the boss at the top of the screen. We need to get the same behavior worked into our game. We will do this by creating another object similar to the ship called ‘camera_entity’ which the camera will follow on the x-y axis.

    The ‘camera_entity’ will start at the middle of the screen and will slowly move upwards at a constant speed. Once the ‘camera_entity’ reaches the top of the screen, it will stop until the boss is defeated, at which time it will continue its ascent through levels 1, 2 and 3 taking our ship on a trip through enemy infested territory.

    Let’s start by defining the ‘CameraEntity’ class.

    Now Try

    • Define a class called ‘CameraEntity’ that will have the following properties: image, rect. Be sure to place the entity at the bottom of the page in the middle of the viewing window.
    Now we need to create an object of type ‘CameraEntity’. Add the following code to your main() function:
    					
    # Create Game Objects
    
    ship = Ship(pygame.rect.Rect(0, 0, total_width, total_height))
    
    camera_entity = CameraEntity(pygame.rect.Rect(0, 0, total_width, total_height))
    
    Notice here that we are giving both the Ship’s constructor and the CameraEntity’s constructor a rect that is the width and height of the world. Let’s make our code cleaner by defining a rect with these dimensions and passing the rect to both constructors. Make the following modifications to your code:
    					
    # Set Up Camera
    …
    total_rect = pygame.rect.Rect(0, 0, total_width, total_height)
    
    # Create Game Objects
    ship = Ship(total_rect)
    camera_entity = CameraEntity(total_rect)
    
    Great, that looks much better. Now, let’s add the ‘camera_entity’ object to the ‘ship_group’ so we can get the object to run its update function and be blitted to the screen. Add the following code to your program:
    					
    # Create Game Objects
    …
    
    # Add Entities to Groups
    …
    platform_group.add(camera_entity)
    
    # Gameplay
    …
    
    Run your program and you will see that there is a ‘camera_entity’. The ‘camera_entity’ should be a few ship lengths above the ship. If this is not so, make it so.

    Notice that the 'camera_entity' is not moving. Let’s change that by adding an ‘update’ method to the ‘CameraEntity’ class.

    Now Try

    • Define an ‘update’ method that will move the ‘camera_entity’ upwards at a speed of 1 pixel per iteration of the game loop. To accomplish this, define a property called ‘speed’ by which the object’s y value will be decremented.
    • Program the ‘camera_entity’ to stop its upward movement once it has reached the top of the screen at y = WIN_H/2. To accomplish this, define a Boolean property called ‘moving’. Only decrement the ‘camera_entity’s y position when ‘moving’ is set to ‘True’.
    Run your program and you will see that the ‘camera_entity’ isn’t moving upward. This is because we still need to call update on the group in which the ‘camera_entity’ is a part of, namely the platform_group. Add the following code to your program:
    		
    # Update
    …
    platform_group.update()
     
    Now when running your program, you should see the ‘camera_entity’ moving at a pace of 60 pixels per second in an upwards direction. However, the camera isn’t following the ‘camera_entity’! Let’s change that by making the following adjustment to our program:
    		
    # Gameplay Loop
    …
        # Update
        camera.update(camera_entity.rect)
        … 
    
        # Print Everything
        …
    
    Run your program and you will see that the camera is now following the ‘camera_entity’! However, it is leaving our ship behind.

    Now Try

    • Add to the ‘update’ method so that the ship moves forward at the same speed as the camera, effectively following the camera. Hint: decrement the ship’s y position.
    • Program the ship to stop moving forward automatically when the ‘camera_entity’ stops moving forward at the top of the level. Hint: pass ‘camera_entity.moving’ to the ship’s ‘update’ method.
    Our ship now follows the ‘camera_entity’ as it makes its way up the level, however if you move to the left and right, the camera doesn’t behave as expected. The camera doesn’t sway to the left or the right when the ship does. Of course it doesn’t because the camera is only following the ‘camera_entity’ which is moving along the y-axis.

    To program the correct behavior of the camera, we need for the camera to follow the ‘camera_entity’ when calculating the upward movement of the camera and we need for the camera to follow the ship when calculating the lateral movement of the camera. In order to get this done, we need to send both the ‘camera_entity’ rect and the ‘ship’ rect to the camera object’s ‘update’ method. Make the following adjustment to your code:
    			
    # Gameplay Loop
    …
        # Update
        camera.update(camera_entity.rect, ship.rect)
        … 
    
        # Print Everything
             …
    
    Also make the following adjustments to your code:
    	
    def update(self, camera_entity_rect, ship_rect):
        x = self.ship_camera(ship_rect)
        y = self.level_camera(camera_entity_rect)
        
        self.state = pygame.Rect(x, y, self.state.width, self.state.height)
    
    def level_camera(self, camera_entity_rect):
        # Y calculation is done here 
    
        return y
    
    def ship_camera(self, ship_rect):
        # X calculation is done here. 
    
        return x
    
    Taking a look at the adjusted ‘update’ method, we can see that instead of having ‘update’ calculate the x and y position of the offset based only on a single object, we now are using the ship to calculate the x value of the offset and the ‘camera_entity’ to calculate the y value of the offset. Understand that this is simply a rearrangement of code. You should not need to write a single line of code.

    Now Try

    • Complete the code for both methods ‘ship_camera’ and ‘level_camera’.
    Now you should have a game that scrolls upwards following the ‘camera_entity’ and scrolls sideways based on the ship. However, notice that we can control the ship to fly off the screen at the top and bottom? Let’s fix that now.

    Now Try

    • Write the necessary conditional statements that will stop the ship from fly off the screen as the game scrolls vertically. Hint: pass ‘camera.y’ to the ship’s ‘update’ method.


    Congratulations, you have successfully incorporated a vertical scrolling camera!