The Level that Never Ends

http://www.youtube.com/watch?v=qkboq5Lc5A0

In this video, the world is one continuous loop.  Each set of colored blocks represent one “room” in the game world, which are pieced together to form a continuous scrolling world.

Tile Size Change

Scaled down the tiles from 64×64 to 48×48.  This allows 15 rows (previously 11) and 26 columns (previously 20) on the 1280×720 pixel screen at once.

Scrolling

Scrolling isn’t too difficult, but it really makes you think.  First, set two constants which will be your left and right bounds.  If the player walks right and their position is greater than the right bounds, then don’t move the player and scroll the level left.  This can be accomplished by storing an X offset variable.  Then, draw all the tiles using the X offset to shift the screen.  I ended up setting these at 200 for the left bound and 1080 for the right bound, which gives 200 pixels of buffer on both sides of the screen.

Once the offset equals the size of the room minus the right bounds, then it means the player should be moved to the next room.  I created a Level class which keeps track of all of the level’s rooms.  Additionally, each room has a reference to the left and right room.  When moving right to the next room, set the current room to the room to the right, and set the X offset to room size minus the right bounds.  Be careful as well to only check for a right room flip whenever the player is at the right bounds, or a left room flip when the player is at the left bounds.  Otherwise, the player will continually flip rooms, because the offset will be set to a value outside of the bounds at the other end.  I’ll admit, this gave me a bit of a headache getting all of this straight.  However, in the end this will work out much better than having one huge array.  I had to draw out the transitions on paper first to get it straight in my head.

What made this more of a pain was the fact that my screen width (1280) isn’t a multiple of the tile size (48).  Thefore, I only used 26 columns for a room, which left a small difference (32 pixels) between the room size and the screen size.  Remembering when to use the screen size and when to use the room size was quite cumbersome, but in the long run I think this will allow rooms to be created of any size.

Setting the last room’s right room to the first room, and setting the first room’s left room to the last room turns it into a continuous scroller, similar to games like Magic Sword or Odin Sphere.  I’m a big fan of those games, so I’m going to leave it that way for now.

Blocks

Cleaning Up

Before adding block tiles to the game engine, I pulled out all of the player variables from the GameScreen class and put those in its own Player class.  I also created a Projectiles class to hold the set of projectiles.

Blocks and Collision

For now, I created a simple 12×20 array to represent the blocks on the screen.  This just represents one screen, so later on multiple screens will need to be pieced together to form a seamless scrolling game world.  On each GameScreen update, I pass an array containing the current blocks on the screen to the player.  The player then uses that array in its update method to determine if it has collided with any of the blocks during a fall or a move.  My block collision method does a simple loop through all of the blocks on the current screen, and returns true if the player has collided with any of the blocks.  However, I also add the player’s X velocity and Y velocity to the collision rectangle’s position.  If I wait until after the player has moved into the block to do the check, then the player will become stuck in the block.  That’s why I have to check the collision based on the player’s next position.

If the player’s falling boolean is set to true and they collide with a block, then the falling boolean is set to false.  If the player’s X velocity is non-zero (they’re walking) and collide with a block, then I prevent the player from moving in the X direction.  I could set the X velocity to zero, but that would stop the run animation and the player would have to press the directional button again in the case the blocks move (such as an elevator).  If the player holds down in a direction, they should start moving again as soon as the obstacle is moved.

Walking on Air

One last problem is that when the player walks off of a block, they will continue to float until they jump.  Therefore, I had to add another check if the player is not falling and not jumping, then check to see if they collide with a block if falling downward.  If the player doesn’t collide with a block, then set the falling boolean to true so that the player starts falling.  This condition only arises if the player is not falling and not jumping (they are walking or standing).  Checking this way will be beneficial if disappearing blocks are added, then the player will start falling.  Handling elevators may be a little more tricky, since those are not aligned directly on the tile grid.

Run and Jump

http://www.youtube.com/watch?v=Dc3PoVTmc5U

Jump

It’s been over a decade since I’ve written a side scrolling platformer.  However, it’s sort of like riding a bicycle.  One you’ve did it once, you’ll never forget.

On the surface, it seems really simple.  When the player presses”A” the character starts jumping until the player releases the “A” button.  That’s a starting point, but that allows the player to hold down  the button forever, which results in the character blasting off into space.

Therefore, you’ve got to add a counter to keep track of how long the player has jumped.  If this value reaches a certain point, then the player should start to fall.  This is easily handled by adding a boolean jump variable.  Press “A”, and then the jump boolean gets set to true.  Release “A”, then the jump boolean gets set to false.  If the player reaches the jump limit, then set the jump boolean to false.  In the update function, subtract from the player’s Y position the jump velocity if the jump boolean is set to true.  If the player is not standing on anything, then add the falling rate the player’s Y position.  Starting out, I just used Y == 400 as the ground.  More complex collision detection will be handled later.

This works okay, but it still allows the player to press “A” again while the character is falling, which results in a double jump.  Further, the player can keep jumping over and over again to go as high as they want.  Therefore, another boolean is introduced to keep track of falling.  When the jump period is over, the falling boolean gets set to true, and the player is not allowed to jump again until they have collided with the ground.  At that point, the falling boolean gets set to false.

 

Run Animation

For the run animation, I used Blender to render 3 frames of a new stick man model that I created.  For this model I used additional smoothing before creating the bones.  Then I used Gimp to crop all three images to the same size (128×128).  Then I flipped all three images to create the player facing the opposite direction.  (There may be a function in XNA to do that).

A new integer variable is created to keep track of the current walk animation frame.  In the update method, this variable is incremented.  However, if it changes one image per frame, then the walk animation will go too fast.  This would probably work if you have a graphic for each frame.  However, I only have three for now, so I just divide the animation counter (which maxes out at 30 and gets reset back to 0) by 10.  This provides 3 frames for every half second, assuming that the update function is getting called 60 times a second.

Facing

Shooting seems fairly simple as well, but there is actually a little more involved than one would think.  Just pressing either shoot button will set the pellet to active, but it doesn’t know which way to travel.  Therefore, a new variable has to be added to the player to keep track of which way they are facing.  I used -1 for facing left and +1 for facing right.  That way, I set the velocity of the pellet to the pellet speed times the facing value.  Additionally, the alive value has to be set to false once the pellet reaches the left or right window bounds.

Also, the player can repeatedly press the shoot button, which will keep reinitializing the pellet back to the player’s location.  To avoid this, a check must be preformed to ensure that a new pellet is not taking the place of an already existing pellet.