Day 12 - Quick Demos ← Previous

Day 13 - Platform Game

This is going to be a pretty dense tutorial, and it will most likely need for you to have read most, if not all, of the other tutorials/demos... You must have knowledge on sprites, backgrounds, collisions, animation, and ALL the Dev-related Math stuff before starting it out...

It will show the basics of starting your platform game, using mario ! Hehe, he’s just everywhere. As always, this project will be built in several steps...

  1. Animating mario, moving him on the screen, having him jump around...
  2. Adding a simple background, without scrolling, and having mario move on it.
  3. Adding background scrolling, with parallax scrolling

Step 1 - Gravity...

This is going to be easy, as it just re-uses stuff we have already seen... I’ll post the complete code in here. For once, I decided to cut the code into several functions. So I’ll start out with the declarations and the main function, then move on to the second function :

First, we’ll look at mario’s simple graphics :

As you can see, there are only 3 frames. The first 2 are for the walking animation, and the third one is for jumping... Pretty simple...

Now, here comes the first chunk of code, taken from Demos/Platfrom :

// Includes, only one sprite
#include <PA9.h>
 
 
// PAGfxConverter Include
#include "gfx/all_gfx.c"
#include "gfx/all_gfx.h"
 
 
typedef struct{
	s32 x, y;
	s32 vy; // used for jumping...
} mariotype;
 
mariotype mario;
 
#define GRAVITY 48
 
 
void MoveMario(void);
 
 
// Main function
int main(void)	{
	// PAlib init
	PA_Init();
	PA_InitVBL();
	
	PA_InitText(1, 0);
 
	PA_LoadSpritePal(0, 0, (void*)sprite0_Pal);	// Palette....	
 
	mario.x = 0<<8; mario.y = (192-32)<<8; // bottom of the screen... fixed point
	mario.vy = 0; // not jumping
	PA_CreateSprite(0, 0,(void*)mario_Sprite, OBJ_SIZE_32X32,1, 0, mario.x>>8, mario.y>>8); // Sprite
	
	while(1)
	{
		MoveMario();
	
		PA_SetSpriteXY(0, 0, mario.x>>8, mario.y>>8);
	
		PA_WaitForVBL();
	}
	
	return 0;
}

I won’t detail all the beginning, as it’s just all the normal includes, followed by mario’s movement structure (will use fixed point), and then the macro for the gravity...

Next, we have a little void MoveMario(void);, which is just the declaration of the function we will use to move mario around... If the code’s not there, why do we need it ?? Because the function is written AFTER the main function, so in order for main to know about it, we just have to copy/paste its declaration at the beginning...

Then come the normal inits, with text on the top screen...

After that, we just load the sprite (on the bottom screen) and initialize the position... Note that we are using fixed point math, that’s what the «8 is there for... And the »8 to convert back to normal position, when placing the sprite...

while(1)
{
	MoveMario();
	
	PA_SetSpriteXY(0, 0, mario.x>>8, mario.y>>8);
	
	PA_WaitForVBL();
}

For once, the main loop doesn’t have much in it... It has a function call (to move mario), then places the sprite on the screen, and waits for the screen sync... Nothing much.

Now we’ll check the MoveMario function, as that’s where most of the code resides :

void MoveMario(void){
	if(Pad.Newpress.Right) {
		PA_StartSpriteAnim(0, 0, 0, 1, 6);	
		PA_SetSpriteHflip(0, 0, 0);
	}
	else if(Pad.Newpress.Left) {
		PA_StartSpriteAnim(0, 0, 0, 1, 6);	
		PA_SetSpriteHflip(0, 0, 1);
	}
	
	if ((Pad.Newpress.A) && (mario.vy == 0)){  // If pressed A and not in the air
		mario.vy = -1000; // Start jumping
	}
 
	// Moving Code
	mario.x += (Pad.Held.Right - Pad.Held.Left)<<8;	 // in fixed point...
	
	// Add gravity
	mario.vy += GRAVITY;
	mario.y += mario.vy;
	if (mario.y >= (192-32)<<8) {
		mario.y = (192-32)<<8;
		mario.vy = 0;
	}
	
	if (mario.vy != 0) PA_SetSpriteAnim(0, 0, 2); // If going up or down, means the sprite is jumping !
	else if(!((Pad.Held.Left)||(Pad.Held.Right))) PA_SetSpriteAnim(0, 0, 0);// Image if not in the air and not walking
}

This should look famliliar, as it has some code copied from the Animation examples in PAlib...

if(Pad.Newpress.Right) {
	PA_StartSpriteAnim(0, 0, 0, 1, 6);	
	PA_SetSpriteHflip(0, 0, 0);
}

This simply means that if you press Right for the first time, the sprite should not be flipped, and the animation should start, from frame 0 to 1 (because the walking animation only has 2 frames...), at a speed of 6 frames per second... 6 fps is really slow, but we only have 2 frames... so that’s good.

If you haven’t pressed right, it checks if you pressed left, in which case it’s pretty much the same thing, but this time the sprite must be flipped, in order to look in the correct direction...

Then comes the first part of the jumping/gravity code, with

	if ((Pad.Newpress.A) && (mario.vy == 0)){ 
	mario.vy = -1000; // Start jumping
}

When you press A, the vertical speeds is set to it’s maximum (well, you can choose to put more if you want), so mario will go up... Why did we have to add the mario.vy == 0 condition ? If not, you could press A several times in a row, and just move up and up !! With this, you cannot start a jump unless you are already on the floor level...

In order to move mario left and right, there’s a little mario.x += (Pad.Held.Right - Pad.Held.Left)«8; in there... What’s the «8 here for ? Remember we are working with fixed point numbers, so you’ll want to have more than just 1/256 pixel movement per turn ! Note that with this code, you can change direction and move while in the air...

Then comes the second part of the gravity code :

mario.vy += GRAVITY;
mario.y += mario.vy;
if (mario.y >= (192-32)<<8) {
	mario.y = (192-32)<<8;
	mario.vy = 0;
}

This is just like we have already seen... Each frame, you change the speed according to the gravity, then move the sprite according to the speed... If the sprite moves down the floor level, you have to correct its position and set its speed to 0...

The last part of the code (wow, was pretty short !) is what changes the animation if you are jumping, and stops it if you aren’t walking :

if (mario.vy != 0) PA_SetSpriteAnim(0, 0, 2); 
else if(!((Pad.Held.Left)||(Pad.Held.Right))) PA_SetSpriteAnim(0, 0, 0);

First, if your speed is not null (either going up or down... so when you are in the air...), it sets the animation to the last frame (frame 2), which is the frame with mario and yoshi in the air... If you aren’t jumping, and neither going left or right, then why just set the animation to 0 (the normal standing frame).

And that’s already it for step 1 !! Come back soon for the next, more interesting but harder part, with simple tile collisions...

Step 2 - Basic Tile Collisions

Now that our little mario can run around, we’ll add a background and make him collide with it... Here are the 2 backgrounds I added (one for collisions, and one for the back...

As you can see, they are just the size of the DS screen, because we won’t have them scrolling yet...

I’ll post the whole code again, but in several parts, explaining it part by part... :

// Includes, only one sprite
#include <PA9.h>
 
 
// PAGfxConverter Include
#include "gfx/all_gfx.c"
#include "gfx/all_gfx.h"
 
 
typedef struct{
	s32 x, y;
	s32 vy; // used for jumping...
	s32 flip;
} mariotype;
 
mariotype mario;
 
#define GRAVITY 48
#define MARIO_SPEED 512
 
 
void MoveMario(void);
void CheckCollisions(void);
u8 GetTile(s16 x, s16 y);
u8 LeftCollision(void);
u8 RightCollision(void);
u8 DownCollision(void);
u8 TouchingGround(void);
 

This is the code preceding the main function. As you can see, I added a flip variable in the structure, which will be used later on. There are also quite a few new functions declared, which have pretty easy names, I bet you could be able to guess what each of them do... Some of these functions aren’t declared as void, but as u8, which means it’ll return a value...

On we go to the main’s code :

// Main function
int main(void)	{
	// PAlib init
	PA_Init();
	PA_InitVBL();
	
	PA_InitText(1, 0);
 
	PA_LoadSpritePal(0, 0, (void*)sprite0_Pal);	// Palette....	
	
	PA_LoadPAGfxLargeBg(0, 1, mario_world); // platfroms...
	PA_LoadPAGfxLargeBg(0, 3, back); // back
	
	mario.x = 0<<8; mario.y = (128-32)<<8; // bottom of the screen... fixed point
	mario.vy = 0; // not jumping
	mario.flip = 0;
	PA_CreateSprite(0, 0,(void*)mario_Sprite, OBJ_SIZE_32X32,1, 0, mario.x>>8, mario.y>>8); // Sprite
	
	while(1)
	{
		MoveMario();
	
		PA_SetSpriteXY(0, 0, mario.x>>8, mario.y>>8);
		
		PA_OutputText(1, 2, 9, "X : %d   ", mario.x >> 8);
		PA_OutputText(1, 2, 10, "Y : %d   ", mario.y >> 8);	
	
		PA_WaitForVBL();
	}
	
	return 0;
}

Haven’t changed much in this code. It only has 2 extra background loadings :

  • PA_LoadPAGfxLargeBg(0, 1, mario_world); loads the background we will collide with... I used LargeBg because we’ll need to have more than 512 pixels large when we’ll want to scroll it...
  • PA_LoadPAGfxLargeBg(0, 3, back); is the back, with some clouds on it. It’s at position 3, because nothing can be behind it.

Why haven’t I used background number 0 for mario_world ? Because we can keep it to display the score, the coins, etc...

Last thing changed : I added 2 small texts displaying mario’s position...

And now, the MoveMario function :

void MoveMario(void){
	if(Pad.Newpress.Right) {
		PA_StartSpriteAnim(0, 0, 0, 1, 6);	
		PA_SetSpriteHflip(0, 0, 0);
		mario.flip = 0;
	}
	else if(Pad.Newpress.Left) {
		PA_StartSpriteAnim(0, 0, 0, 1, 6);	
		PA_SetSpriteHflip(0, 0, 1);
		mario.flip = 1;
	}
	
	if ((Pad.Newpress.A) && (TouchingGround())){  // If pressed A and not in the air
		mario.vy = -1200; // Start jumping
	}
 
	// Moving Code
	mario.x += (Pad.Held.Right - Pad.Held.Left)*MARIO_SPEED;	 // in fixed point...
	
	// Add gravity
	mario.vy += GRAVITY;
	mario.y += mario.vy;
	
	CheckCollisions();
	
	if (!TouchingGround()) PA_SetSpriteAnim(0, 0, 2); // Not on the ground
	else if(!((Pad.Held.Left)||(Pad.Held.Right))) PA_SetSpriteAnim(0, 0, 0);// Image if not in the air and not walking
}

Nothing much has changed here, except :

  • if (Pad.Newpress.A) && (TouchingGround){ Now, mario jumps when we have checked that he is touching the ground... This uses one of the new functions we’ll see later on.
  • mario.x += (Pad.Held.Right - Pad.Held.Left)*MARIO_SPEED; I decided he didn’t move fast enough, so I added a MARIO_SPEED macro, and made him go like 2 pixels per frame, which looks better.
  • CheckCollisions(); is the new collision checking function... we’ll see that right after
  • if (!TouchingGround()) PA_SetSpriteAnim(0, 0, 2); is the command to show mario as jumping... Instead of using the speed like before, it now checks if mario is touching the ground or not...

That’s about it for this function, nothing much, as I had said. But now, all the remaining functions are new, I’ll detail them almost 1 by 1...

u8 GetTile(s16 x, s16 y){
	if (x < 0 || x > 256) return 1; //Say it was a collision if the sprite tries 
                                        //to move out of the map horizontal boundaries
	return mario_world_Map[((y>>3)*32) + (x>>3)];
}

This is a very simple function which returns the tile number when given the position in pixels... Why did I put that in a function all by itself ? Because when we’ll add scrolling, we’ll just have to change this function, and everything will be updated... Isn’t that easier ? How does it find the correct tile ?

  • First, it checks that x is a correct value (>0 or <256), and if not will return 1 (out of bounds).
  • Then, it reads directly in the backgrounds map the tile...
    • (x»3) is the tile position. X is divided by 8 (equivalent to »3) because a tile is 8 pixels wide...
    • (y»3)*32 is composed of :
    • (y»3) to get the tile vertical number, just like x»3, because tiles are 8 pixels high
    • *32, because the background is 32 tiles wide, so to go down by 1 tile, you have to add 32 tiles... We’ll have to change that when we add scrolling, to make it work on bigger maps...

So this function returns the number of the tile... If there is no tile (transparent tile), the number returned will be 0... In this example, we’ll do a collision for any number but 0. This means that you will collide with everything. If you need different collisions, like passing through certain walls, you have to adapt the code to your need (this is just a basic code for starters...)

u8 LeftCollision(void){
	return GetTile((mario.x>>8)+2, (mario.y>>8)+8+(mario.flip*13));
}
 
u8 RightCollision(void){
	return (GetTile((mario.x>>8)+29, (mario.y>>8)+8 + ((!mario.flip)*13)));
}

These 2 functions will be used to check if there is a left or right collision. First thing, it uses the GetTile function we have just seen, and gives it mario’s position in normal, non fixed point, coordinates.

  • For the left collision, it checks either position (on mario) (2, 8) or (2, 21), depending on if the sprite is flipped or not... Why ? Because if flipped, the collision will be done either with the nose or the back (there’s a free space under the nose). So if flipped, the vertical position will change to test the collision at a different spot...
  • For the right collision, it’s the same, but with x = 29 instead of 2... same thing with the nose...

Why does it test x = 2 or 29, and not x = 0 and x = 31 ? Because if you look at mario’s sprite, on the top of the page, you’ll see that it has 2 free pixels on each side... So we want to test the collision only where the actual drawing starts...

u8 DownCollision(void){
	return (mario.vy >= 0 && GetTile((mario.x>>8)+10 + (mario.flip*11), (mario.y>>8)+31));
}

Theis one tests if mario is touching a floor or not. If yes, we’ll need to move him back up a little... Here, there’s the mario.flip again !! Why this time ? Because, look at the sprite’s feet, which will determine the actual collision... They aren’t centered ! This means that depending on the side he’s looking at, his feet will be a little more to the left or to the right. This is taken into account by changing the x position checked from 10 to 21 if flipped...

u8 TouchingGround(void){
	return GetTile((mario.x>>8)+10 + (mario.flip*11), (mario.y>>8)+32);
}

This is the last collision check. It is the exact same code as the previous one, but 1 pixel lower (+32 instead of +31). It check if there’s floor underneath yoshi’s feet...

Now comes the actual CheckCollisions function, which will use all the functions we have just talked about and adjust the position/speed...

void CheckCollisions(void){
 
	while(LeftCollision()){ // Collision on the left of the sprite...
		mario.x+= 256; // Move by 1 pixel...
	}
	while(RightCollision()){ // Collision on the right of the sprite...
		mario.x-= 256; // Move by 1 pixel...
	}
	
	while(DownCollision()){ // Collision on the bottom of the sprite...
		mario.y -= 128; // Move by 1/2 pixel...
		mario.vy = 0; // TOuched the floor...
	}
	if(TouchingGround()) mario.vy = 0;
 
}

Nothing much to say, in fact ! While the sprite touches left, move right, and while it touches right, move left... Feels logical...

For the floor collisions, I made it move by half a pixel, just for fun, lol... If you touch the floor level, then the vertical speed must be set to 0...

There are 2 different parts to the ground level check :

  • One that actually checks if you are IN the ground, and moves mario and yoshi accordingly (while...)
  • One that just looks if there’s ground right underneath yoshi, and just puts the speed to 0 if that’s the case...

And that’s it !!

Now that this is working, we just need to add scrolling (and parallax scrolling, for a nice effect) and see the result :-P Once again, this is a BASIC tile collision system, as it treats all the tiles the same way. You could have a different check, and have the player react differently to each type of tile...

Here’s what I get in dualis r12 (r11 doesn’t work with LargeMaps) :

Step 3 - Background Scrolling

Now that this works, we’ll add background scrolling (parallax scrolling) to give it a nice depth when scrolling. The 3 backgrounds we’ll use are :

As you probably noticed, they don’t all have the same length !! Why ? Because they won’t scroll at the same speed. The first background will scroll at full speed, so I made it 1024 pixels wide to test it out. The second will be a bit slower, and the clouds will scroll at like 1/4th of the speed, so don’t need to be very long...

I’ll post the text in PAGfx.ini for once :

#TranspColor Magenta
 
#Sprites : 
mario.png 256colors sprite0
 
#Backgrounds : 
mario_world.png LargeMap
hills.png LargeMap
back.png LargeMap

As you can see, the transparent color is magenta... In the platform and hills backgrounds, you have tons of magenta, which will all become transparent... The last one (clouds), however, has no magenta... As it’s behind, you don’t want it to be transparent... All the backgrounds are declared as LargeMap, because they’ll need to be more than 512 pixels wide...

For this third part, I’ll only post blocks of code, as there have been very little changes...

Concerning mario’s structure, I’ve just added a scroll parameter :

typedef struct{
	s32 x, y;
	s32 vy; // used for jumping...
	s32 flip;
	s32 scrollx; // Scroll value...
} mariotype;

Nothing much to say about it I guess. It starts at 0...

Then we need to load the background, each at a different level :

PA_EasyBgLoad(0, 1, mario_world); // platfroms...
PA_EasyBgLoad(0, 2, hills); // hills
PA_EasyBgLoad(0, 3, back); // back

Now that the backgrounds are loaded, we have to initialize the parallax scrolling... If you are unsure of what that is, go back to the background tutorial and reread the parallax scrolling part... The code is simple :

PA_InitParallaxX(0, 0, 256, 128, 64);
  • This means we’ll start parallax scrolling on screen 0, and not activate it (0) on the first background (which could/should be used for the score and all...).
  • For background 1, it has a speed of 256, which means normal speed...
  • For background 2, it has a speed of 128, so that would be only half the speed...
  • For background 3, the speed is 64, so 1/4 of the speed...

Here comes the only code added, for scrolling when mario gets to the edge :

MoveMario();
 
if ((((mario.x-mario.scrollx)>>8) > 160) && ((mario.x>>8) < 1024-128)){ // Scroll more...
	mario.scrollx = mario.x - (160<<8);		
}
else if ((((mario.x-mario.scrollx)>>8) < 64) && ((mario.x>>8) > 64)){
	mario.scrollx = mario.x - (64<<8);		
}
		
PA_ParallaxScrollX(0, mario.scrollx>>8);
	
PA_SetSpriteXY(0, 0, (mario.x-mario.scrollx)>>8, mario.y>>8);

I added it right after MoveMario, so that it has the latest sprite position...

As you can see, when will it scroll to the right ? When mario’s position on the screen is further than pixel 160, but only if it’s not the end of the level (1024-128, because it stops scrolling before the end...)

And how does it get the correct scrolling value (in fixed point) ? mario.scrollx = mario.x - (160«8); This means that we scroll enough to position the sprite at a maximum x = 160 value... (except, again, if you’re at the end of the level)

The same thing applies the other way around, scrolling only if the sprite is at more than 64 pixels from the start, and never putting the sprite closer than 64 pixels if possible...

PA_ParallaxScrollX(0, mario.scrollx>>8);

This scrolls all the background by the given scroll value, with »8 because we were using fixed point values, and the parallax scrolling takes normal values...

Another change is the sprite’s position :

PA_SetSpriteXY(0, 0, (mario.x-mario.scrollx)>>8, mario.y>>8);

Here, we remove the scroll value before positionning the sprite... This means that if the mario.x is at 512, it will be placed back on the screen according to the scroll value... isn’t that neat ?

The last change is in the GetTile function :

u8 GetTile(s16 x, s16 y){
	if (x < 0) return 1; // Say it was a collision...
	return mario_world_Map[((y>>3)*128) + (x>>3)];
}

I changed the number of tiles horizontally from 32 (256 pixels) to 128 (1024 pixels wide), to make it work again...

And that’s all !!

Note that to have more effective collisions, you’ll want to use a collision map, which is a map you do like any normal map in an editor, but you don’t actually show it on the screen...

 
day13.txt · Last modified: 08/12/2009 18:02 by 86.17.59.207
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki