Day 11 - Video Functions ← Previous
This part of the tutorial will contain a few small codes, either new or based on some of the other examples, which can help you a little more in DS development...
This is an example with a ship following the stylus. You could use this for a GTA 2D like movement system. You can look at the final version of this code in the PAlib Examples, under Demos/FollowStylus/. It simply shows a sprite following the stylus, turning towards it.
I’ll only post and comment the difference with the other code Trajectory Example :
s32 x = (128) << 8; // ship x position in 8bit fixed point s32 y = (96) << 8; // Y u16 angle = 0; // direction in which to move ! while(1) { angle = PA_GetAngle(x>>8, y>>8, Stylus.X, Stylus.Y); PA_SetRotsetNoZoom(0, 0, angle); // Turn the ship in the correct direction if (Stylus.Held){ // Move forward x += PA_Cos(angle); y -= PA_Sin(angle); } PA_OutputText(1, 5, 10, "Angle : %d ", angle); PA_SetSpriteXY(0, 0, (x>>8)-16, (y>>8)-16); // Sprite position converted to normal... PA_WaitForVBL(); }
angle = PA_GetAngle(x»8, y»8, Stylus.X, Stylus.Y); This is completely different from the other angle code... This time, we get the angle using the sprite’s center and the stylus (hence the »8, and using x and y as the center of the sprite...). if (Stylus.Held){ was changed in order to move when the stylus is pressed...
And that’s it ! It was a pretty simple demo, I must admit, but that’s because it’s the first one in here
This will be a tutorial in several steps... The end goal will be to have a nice demo with 10 frisbees flying around, thrown by the stylus, and colliding... We’ll do this in 3 easy steps :
Here comes a nice little example : it shows how you can use the stylus codes to catch a frisbee, throw it, and have it bounce on the walls (on both screens...). And have it turn on itself, just because I wanted it too...
You’ll find this example in Demos/Frisbee in the next PAlib version (there was an old outdated one without stylus support)
I’ll post a large chunk of code :
#define FRISBEE 10 // Sprite number... #define SCREENHOLE 48 // Size of the space between the screens... This is what looked the best typedef struct{ s16 x, y; // This is for the frisbee's position s16 vx, vy; // Frisbee speed s16 angle; // To make the frisbee turn on itself }frisinfos; frisinfos frisbee; // Frisbee structure variable int main(void) { // Initialise the lib... PA_Init(); PA_InitVBL(); PA_InitText(1, 0); // Load the palettes for the sprites on both screens PA_DualLoadSpritePal(0, (void*)sprite0_Pal); // Create the sprite on both screens... PA_DualCreateSprite(FRISBEE, (void*)frisbee_Sprite, OBJ_SIZE_32X32, 1, 0, 96, 300); // Bottom screen PA_DualSetSpriteRotEnable(FRISBEE, 0); // Enable rotation/zoom, rotset 0 // Sprite initial position... frisbee.x = 96+16; frisbee.y = 300+16; // on the bottom screen // Speed of frisbee in both ways frisbee.vx = 0; frisbee.vy = 0; while(1) { // Move with the stylus, or move on... if (PA_MoveSprite(FRISBEE)){ frisbee.x = PA_MovedSprite.X; frisbee.y = PA_MovedSprite.Y + 192 + SCREENHOLE; frisbee.vx = PA_MovedSprite.Vx; frisbee.vy = PA_MovedSprite.Vy; } else{ // Now, the frisbee's fixed point position will be updated according to the speed... frisbee.x += frisbee.vx; frisbee.y += frisbee.vy; // If the sprite touches the left or right border, flip the horizontal speed if ((frisbee.x -16 <= 0) && (frisbee.vx < 0)) frisbee.vx = -frisbee.vx; else if ((frisbee.x + 16 >= 256)&&(frisbee.vx > 0)) frisbee.vx = - frisbee.vx; // Same thing, for top and bottom limits... if ((frisbee.y -16 <= 0) && (frisbee.vy < 0)) frisbee.vy = -frisbee.vy; else if ((frisbee.y + 16 >= 192 + 192 + SCREENHOLE)&& (frisbee.vy > 0)) frisbee.vy = - frisbee.vy; // The bottom limit is at the bottom of the bottom screen, so that would be 2 screen heights, plus the space in between... PA_DualSetSpriteXY(FRISBEE, frisbee.x-16, frisbee.y-16); } PA_OutputText(1, 2, 10, "SpeedX : %d ", frisbee.vx); PA_OutputText(1, 2, 11, "SpeedY : %d ", frisbee.vy); frisbee.angle+=4; // Make the frisbee turn... PA_DualSetRotsetNoZoom(0, frisbee.angle); PA_WaitForVBL(); // Synch to the framerate... }
And now, the comments...
#define FRISBEE 10 // Sprite number... #define SCREENHOLE 48 // Size of the space between the screens... This is what looked the best
Are just 2 definitions that will make our life easier... FRISBEE is the frisbee’s sprite number, and SCREENHOLE the space in between the screens, in pixels.
// Load the palettes for the sprites on both screens PA_DualLoadSpritePal(0, (void*)sprite0_Pal); // Create the sprite on both screens... PA_DualCreateSprite(FRISBEE, (void*)frisbee_Sprite, OBJ_SIZE_32X32, 1, 0, 96, 300); // Bottom screen PA_DualSetSpriteRotEnable(FRISBEE, 0); // Enable rotation/zoom, rotset 0
Here we load the palette, create the sprite on both screens (hence the Dual prefix), and enable rotations... Have you noticed the sprite’s Y coordinate ? 300... That’s on the bottom screen. In Dual Mode, PAlib considers the DS like having a single screen of 384+SCREENHOLE pixels, in our case 48 pixels... (which looks the best on DS, but is horrible on emulators...)
Then we initialise the frisbee structure values :
frisbee.x = 96+16; frisbee.y = 300+16; // on the bottom screen frisbee.vx = 0; frisbee.vy = 0;
The position is set to 96, 300... +16 because we’ll use the sprite’s center... and it’s a 32×32 sprite. And the speed to 0... This does not use fixed point, but we could have...
Next comes one of the important parts of code...
if (PA_MoveSprite(FRISBEE)){ frisbee.x = PA_MovedSprite.X; frisbee.y = PA_MovedSprite.Y + 192 + SCREENHOLE; frisbee.vx = PA_MovedSprite.Vx; frisbee.vy = PA_MovedSprite.Vy; }
This part of the code has multiple functions :
frisbee.x = PA_MovedSprite.X;.
In the end, what you’ll want to remember, is the PA_MovedSprite structure, which stores information on the sprite’s position, as well as the current moving speed... So as you see, there actually is more the PA_MoveSprite than just moving the sprite around
Now, if the sprite wasn’t touched, we go into a different loop, which starts with
else{ // Now, the frisbee's fixed point position will be updated according to the speed... frisbee.x += frisbee.vx; frisbee.y += frisbee.vy;
This bit of code moves the sprite according to the current speed... Nothing special about that
// If the sprite touches the left or right border, flip the horizontal speed if ((frisbee.x -16 <= 0) && (frisbee.vx < 0)) frisbee.vx = -frisbee.vx; else if ((frisbee.x + 16 >= 256)&&(frisbee.vx > 0)) frisbee.vx = - frisbee.vx; // Same thing, for top and bottom limits... if ((frisbee.y -16 <= 0) && (frisbee.vy < 0)) frisbee.vy = -frisbee.vy; else if ((frisbee.y + 16 >= 192 + 192 + SCREENHOLE)&& (frisbee.vy > 0)) frisbee.vy = - frisbee.vy
This is more important... It checks if the frisbee is moving out of the screen ! If that’s the case, it’ll change the speed to make the frisbee move in the correct direction... Concerning the Y position, you’ll notice that we have to check with 192+192+SCREENHOLE, because that’s the total height of both screens, taking into account the space...
PA_DualSetSpriteXY(FRISBEE, frisbee.x-16, frisbee.y-16); }
This ends the else loop... It just positions the sprite at the correct place. Notice the Dual prefix, to use both screens as 1... -16 is there because x and y are the central position, and we need to give the top left corner position...
The end of the code is pretty trivial... First we show the current speed on the top screen, just to check it out. And then we make the frisbee turn, with
frisbee.angle+=4; // Make the frisbee turn... PA_DualSetRotsetNoZoom(0, frisbee.angle);
It turns at a speed of 4 PAlib degrees per frame, which looks pretty good... I used DualSetRotset to make it turn on both screens...
And that it ! I recommend compiling and testing the code (even works on Dualis r11, except that the colors are wrong), and you’ll see it was a pretty nice example...
Guess what ? For the next example, I’ll do the exact same thing, but with 10 frisbees
You’ll see there are like 2 lines of code to add, using an array of structures...
As said before, this should be fairly easy... I’ll post most of the code, but won’t comment the things we have already seen right before...
typedef struct{ s16 x, y; // This is for the frisbee's position s16 vx, vy; // Frisbee speed s16 angle; // To make the frisbee turn on itself }frisinfos; frisinfos frisbee[10]; // 10 Frisbees !! int main(void) { // Initialise the lib... PA_Init(); PA_InitVBL(); PA_InitText(1, 0); // Load the palettes for the sprites on both screens PA_DualLoadSpritePal(0, (void*)sprite0_Pal); s32 i; // will be used in for loops to cycle through the frisbees... PA_InitRand(); // Init the random stuff... for (i = 0; i < 10; i++){ // Sprite initial position... frisbee[i].x = (PA_Rand()%256)-16; // random position on the screen frisbee[i].y = 192+SCREENHOLE + (PA_Rand()%192)-16; // random position on the bottom screen; // Speed of frisbee in both ways frisbee[i].vx = 0; frisbee[i].vy = 0; frisbee[i].angle = 0; // Create the sprite on both screens... PA_DualCreateSprite(FRISBEE+i, (void*)frisbee_Sprite, OBJ_SIZE_32X32, 1, 0, frisbee[i].x-16, frisbee[i].y-16); PA_DualSetSpriteRotEnable(FRISBEE+i, i); // Enable rotation/zoom, rotset 0 } while(1) { for (i = 0; i < 10; i++){ // Move with the stylus, or move on... if (PA_MoveSprite(FRISBEE+i)){ frisbee[i].x = PA_MovedSprite.X; frisbee[i].y = PA_MovedSprite.Y + 192 + SCREENHOLE; frisbee[i].vx = PA_MovedSprite.Vx; frisbee[i].vy = PA_MovedSprite.Vy; } else{ // Now, the frisbee's fixed point position will be updated according to the speed... frisbee[i].x += frisbee[i].vx; frisbee[i].y += frisbee[i].vy; // If the sprite touches the left or right border, flip the horizontal speed if ((frisbee[i].x - 16 <= 0) && (frisbee[i].vx < 0)) frisbee[i].vx = -frisbee[i].vx; else if ((frisbee[i].x + 16 >= 256)&&(frisbee[i].vx > 0)) frisbee[i].vx = - frisbee[i].vx; // Same thing, for top and bottom limits... if ((frisbee[i].y - 16 <= 0) && (frisbee[i].vy < 0)) frisbee[i].vy = -frisbee[i].vy; else if ((frisbee[i].y + 16 >= 192 + 192 + SCREENHOLE)&& (frisbee[i].vy > 0)) frisbee[i].vy = - frisbee[i].vy; // The bottom limit is at the bottom of the bottom screen, so that would be 2 screen heights, plus the space in between... PA_DualSetSpriteXY(FRISBEE+i, frisbee[i].x-16, frisbee[i].y-16); } frisbee[i].angle+=4; // Make the frisbee turn... PA_DualSetRotsetNoZoom(i, frisbee[i].angle); }
Ok, as you may see, there are a few differences...
frisinfos frisbee[10]; /, this is now an array !! Yahoo ! Of a total size of 10, for 10 frisbees...The frisbee init code has changed a bit, too :
for (i = 0; i < 10; i++){ // Sprite initial position... frisbee[i].x = (PA_Rand()%256)-16; // random position on the screen frisbee[i].y = 192+SCREENHOLE + (PA_Rand()%192)-16; // random position on the bottom screen; // Speed of frisbee in both ways frisbee[i].vx = 0; frisbee[i].vy = 0; frisbee[i].angle = 0; // Create the sprite on both screens... PA_DualCreateSprite(FRISBEE+i, (void*)frisbee_Sprite, OBJ_SIZE_32X32, 1, 0, frisbee[i].x-16, frisbee[i].y-16); PA_DualSetSpriteRotEnable(FRISBEE+i, i); // Enable rotation/zoom, rotset 0 }
The last bit of code that changes is in the infinite loop...
And that’s all there is to know for this time ! Next tutorial to come up ? 10 frisbees with collisions !
This example is just another quick modification of the Firsbee2 example, but adding a basic collision system so that the frisbees don’t run into each other... As it is the exact same code with only a few lines added at 1 part of it, I decided to only post the new code... I left in the DualSetSpriteXY(...) so that you see where to add it...
PA_DualSetSpriteXY(FRISBEE+i, frisbee[i].x-16, frisbee[i].y-16); } u8 j; for (j = 0; j < i; j++){ // Test collisions for all frisbees with a smaller number... if (PA_Distance(frisbee[i].x, frisbee[i].y, frisbee[j].x, frisbee[j].y) < 32*32) { frisbee[i].vx = (frisbee[i].x - frisbee[j].x)/6; frisbee[i].vy = (frisbee[i].y - frisbee[j].y)/6; frisbee[j].vx = -frisbee[i].vx; frisbee[j].vy = -frisbee[i].vy; } }
Yes, that’s all you need to have ALL the frisbees hitting each other pretty well !
The first thing that was needed was to check for collisions between all the frisbees... This is done with a for loop : for (j = 0; j < i; j++). Have you noticed anything strange about this loop ? If it had been like the first for loop, it would be from 0 to 10... but here, it’s from 0 to... i ??? Why ?
Next comes the collision check, taken from the Circular Collision tutorial (in the Math Dev stuff) : if (PA_Distance(frisbee[i].x, frisbee[i].y, frisbee[j].x, frisbee[j].y) < 32*32), which checks if the distance between the 2 frisbees is compatible with a collision... if that’s the case, it’ll just modify the speed of both of the frisbees in collision For the first one :
frisbee[i].vx = (frisbee[i].x - frisbee[j].x)/6; frisbee[i].vy = (frisbee[i].y - frisbee[j].y)/6;
The new speed is defined by the substraction of the frisbee’s position. This guarantees that they will move appart from one another. I divided by 6 to have a nice speed, if not it would be way too fast... You can change the 6 to get a different speed when it collides...
frisbee[j].vx = -frisbee[i].vx; frisbee[j].vy = -frisbee[i].vy;
This code gives the opposite direction to the second frisbee, nothing special about it...
And that’s it ! It was really simple, wasn’t it ? There is one flaw to this system, though... It doesn’t come from the collision detection code, but from the new speed code... It does not take into account the speed at which the frisbees were moving. So even if they hit really hard or really slowly, they’ll move apart at the same speed...
If you need more precise speed correction, I recommend that you check the next example, as it takes speed into account in a pretty effective way...
Here comes another demo ! This one is derived from the circular collision code, as well as the frisbee example... What does it do ? You have a Puck in the middle of the screen (just a blue circle, nothing special, but if anyone wants to change the graphics...), and you have your ‘raquette’ (another blue circle). When you hit the puck with the raquette, it sends the puck bouncing all over the place. The harder you hit, the faster it’ll go.
Here’s the code, you’ll see it’s almost nothing new... Most of it is just recycled code...
typedef struct{ s16 x, y; // position s16 vx, vy; // speed }puckinfos; puckinfos puck; #define SCREENHOLE 48 int main(void){ PA_Init(); PA_InitVBL(); PA_InitText(1,0); // On the top screen PA_DualLoadSpritePal(0, (void*)sprite0_Pal); // This'll be the movable sprite... PA_CreateSprite(0, 0,(void*)circle_Sprite, OBJ_SIZE_32X32,1, 0, 16, 16); s32 x = 16; s32 y = 16; // Sprite's center position // This will be the hit circle PA_DualCreateSprite(1,(void*)circle_Sprite, OBJ_SIZE_32X32,1, 0, 128-16, 96-16); puck.x = 128; puck.y = 96+192+SCREENHOLE; // central position on bottom screen puck.vx = 0; puck.vy = 0; // No speed while(1) { if (PA_MoveSprite(0)){ x = PA_MovedSprite.X; y = PA_MovedSprite.Y; } // Collision ? if (PA_Distance(x, y, puck.x, puck.y-192-SCREENHOLE) < 32*32) { // Collision, so we'l change the pucks speed to move it out of our 'raquette' u16 angle = PA_GetAngle(x, y, puck.x, puck.y-192-SCREENHOLE); // New direction angle u16 speed = (32*32-PA_Distance(x, y, puck.x, puck.y-192-SCREENHOLE))/32; // The closer they are, the harder the hit was... puck.vx = (PA_Cos(angle)*speed)>>8; puck.vy = -(PA_Sin(angle)*speed)>>8; } puck.x += puck.vx; puck.y += puck.vy; // If the sprite touches the left or right border, flip the horizontal speed if ((puck.x -16 <= 0) && (puck.vx < 0)) puck.vx = -puck.vx; else if ((puck.x + 16 >= 256)&&(puck.vx > 0)) puck.vx = - puck.vx; // Same thing, for top and bottom limits... if ((puck.y -16 <= 0) && (puck.vy < 0)) puck.vy = -puck.vy; else if ((puck.y + 16 >= 192 + 192 + SCREENHOLE)&& (puck.vy > 0)) puck.vy = - puck.vy; // The bottom limit is at the bottom of the bottom screen, so that would be 2 screen heights, plus the space in between... PA_DualSetSpriteXY(1, puck.x-16, puck.y-16); PA_WaitForVBL(); } return 0; }
And the comments...
typedef struct{ s16 x, y; // position s16 vx, vy; // speed }puckinfos; puckinfos puck;
Classic definition we used over and over again, to store the puck’s position and speed... In this example, we haven’t used fixed point, though we could have.
Then the palette is loaded, the first sprite too, and then
PA_DualCreateSprite(1,(void*)circle_Sprite, OBJ_SIZE_32X32,1, 0, 128-16, 96-16); puck.x = 128; puck.y = 96+192+SCREENHOLE; // central position on bottom screen puck.vx = 0; puck.vy = 0; // No speed
This loads the puck for both screens, and sets its current position and speed...
Then comes the classic
if (PA_MoveSprite(0)){ x = PA_MovedSprite.X; y = PA_MovedSprite.Y; }
Just to store the raquette’s position (central point)...
The next part is the only new code... It’s what is used to hit the puck with a different speed depending on the way you hit it, and send it in the correct direction...
if (PA_Distance(x, y, puck.x, puck.y-192-SCREENHOLE) < 32*32) { // Collision, so we'l change the pucks speed to move it out of our 'raquette' u16 angle = PA_GetAngle(x, y, puck.x, puck.y-192-SCREENHOLE); // New direction angle u16 speed = (32*32-PA_Distance(x, y, puck.x, puck.y-192-SCREENHOLE))/32; // The closer they are, the harder the hit was... puck.vx = (PA_Cos(angle)*speed)>>8; puck.vy = -(PA_Sin(angle)*speed)>>8; }
if (PA_Distance(x, y, puck.x, puck.y-192-SCREENHOLE) < 32*32) Check if there’s a collision, like said before... 32 is the distance between the 2 circles (must be change if you change the size of one of the circles), and 32×32 because we use the squared distance, for speed issues...u16 angle = PA_GetAngle(x, y, puck.x, puck.y-192-SCREENHOLE); is to get the angle formed with the puck’s and the raquette’s centers... What for ? You can draw this on a paper, and you’ll see that this angle is the direction in which the puck should go !! Yup ! I don’t have the time to do another drawing for this, but if someone ever does one, I’ll add it here...u16 speed = (32*32-PA_Distance(x, y, puck.x, puck.y-192-SCREENHOLE))/32; is the second very important line, as it will determine the puck’s speed ! How does it work ? The closer you are from the puck when it hits, the faster you were moving... why ? Because if you touch the puck by only 1 pixel, it means you were moving very slowly... But if you touch the puck by like 16 pixels ? It means that before the frame was updated, you had the time to move by at least 16 pixels, which is 16 times faster than the 1 pixel hit... 32×32 is the square of the distance needed to hit. If you had a collision by just 0 pixels, you didn’t hit the puck, since your speed wasn’t high enough...PA_Distance(x, y, puck.x, puck.y-192-SCREENHOLE)) is recalculated, and substracted to the squared distance... This will give a higher speed when you move fast...puck.vx = (PA_Cos(angle)*speed)»8; determines the horizontal (vx) speed of the puck, with the new hit. This speed is found by using the Cos of the angle and the global speed it will have... It’s just like the Trajectory tutorial, but with the speed added in... »8 ??? Because, as said at the beginning, I haven’t used fixed point math in this example. And PA_Cos returns a number in fixed point math, so I had to convert it to a normal number again... »8 after the speed, and not right after the Cos ?? Because if done after the PA_Cos, as it’s an integer, the result would always be 0 ! When done after the multiplication with the speed, the total value before division is superior to 256, so dividing by 256 (which is the same as »8) will not result in given you 0, but rather the correct speed in pixels, rounded down (a true speed of 2.5 will give you 2, etc...). And that’s about it ! I won’t comment the rest of the code, as it’s just a copy/paste of the frisbee code (I just replaced ‘frisbee’ by ‘puck’ ).
Hope this was clear enough ! Enjoy