NOGDUS $1670.00 has been donated to NOGDUS!
June 29, 2017, 05:53:20 AM *
Welcome, Guest. Please login or register.

Login with username, password and session length
 
   Home   Help Search Login Register  
Pages: [1]   Go Down
  Print  
Author Topic: ASK Tutorial 04 - Bullet Collision  (Read 3405 times)
0 Members and 1 Guest are viewing this topic.
Richard Marks
Administrator
Offline Offline

Respect: 3425
« on: March 28, 2010, 07:07:33 PM »

ASK Tutorial 04 - Bullet Collision

Guess what? Time for yet another Allegro Starter Kit tutorial! Cool

The concept: Let the player's bullets kill the ball, and let the ball re-spawn after some time has passed.

It is assumed that you are using Visual C++ 2008 Express (MSVC9)
If you're not using MSVC9, then you will need to adjust some steps to suit your IDE.

This tutorial builds off the previous tutorial's code, so don't forget to add the code from the previous tutorial.


Step #1 - Create a new Empty Win32 Project (not a Console Application) in your IDE.

File -> New Project
Choose Win32 Project
Enter "ASKBulletCollisionDemo" for the Name
Click OK
Click Application Settings
Check the Empty Project check-box
Click Finish

Step #2 - Save the Empty Project

File -> Save All

Step #3 - Copy the code files from Tutorial 03 into your project's folder.

Copy all the *.cpp and *.h files from the Tutorial 03 project into the folder that holds the ASKBulletCollisionDemo.vcproj file.

Step #4 - Add the files to the Project

Project -> Add Existing Item
Choose all the .h and .cpp files you just copied and click Add.

Step #5 - Reorganize your code.

The program is starting to get a little larger now, and the LoadContent() function is getting ugly.
The simple solution is to add more functions and call them from LoadContent() and that is what we will do now.

In the PrimaryWindow.h file, add these three function declarations:
Code:
bool LoadContentForBall();
bool LoadContentForPlayer();
bool LoadContentForPlayerShot();

In the PrimaryWindow.cpp file, add these function "stubs" to the end of the file:
Code:
bool PrimaryWindow::LoadContentForBall()
{
return true;
}

bool PrimaryWindow::LoadContentForPlayer()
{
return true;
}

bool PrimaryWindow::LoadContentForPlayerShot()
{
return true;
}

Now, move each of the initialization code lines for each object into their respective functions.

For example, the LoadContentForBall() function should contain:
Code:
// a blue ball
ballColor_ = makecol(0, 0, 255);

// the ball has a radius of 8 pixels
ballRadius_ = 8;

// the ball starts in the center of the screen
ballX_ = SCREEN_W / 2;
ballY_ = SCREEN_H / 2;

// the ball starts on a random trajectory
ballDX_ = (float)4+(rand()%8);
ballDY_ = (float)4+(rand()%8);

return true;

Once you have moved the code into the three new functions, we need to call them from the LoadContent() function.
Here I show you how the function should look:
Code:
bool PrimaryWindow::LoadContent()
{
// load your content here

if (!this->LoadContentForBall())
{
return false;
}

if (!this->LoadContentForPlayer())
{
return false;
}

if (!this->LoadContentForPlayerShot())
{
return false;
}

return ASKWindow::LoadContent();
}

Okay, at this time, compile and make sure it all still functions correctly.
If so, continue, if not, review the previous steps and double check what you do.
Once you have the program working as it should, we can continue with this tutorial.

Step #6 - Add the code to the PrimaryWindow files to implement the demo concept.

We are going to add few variables to the ball object, a boolean for tracking if the ball is alive, and 2 integers for handling the re-spawn of the ball when it dies.


In the PrimaryWindow.h file, where you have the ball object variables, add this code:
Code:
bool ballAlive_;
int ballRespawnTimeCounter_;
int ballRespawnTimeDelay_;

In PrimaryWindow.cpp in the LoadContentForBall() function, add these lines of code:
Code:
// the ball starts off alive by default
ballAlive_ = true;

// a dead ball will respawn in roughly 1 second (30 FPS)
ballRespawnTimeDelay_ = 30;
ballRespawnTimeCounter_ = 0;

Now, we need to modify the UpdateBall() function to implement the new ability to re-spawn the ball when it dies.

To start with we will check to see if the ball is dead.
When the ball is dead, we need to wait a short period of time before re-spawning the ball.
To do this, we increment the ball's re-spawn counter and check if we've reached the time to re-spawn the ball.
If we have, then we choose a new trajectory and position, and then make the ball alive once more.
We then exit the function so that the old ball update logic doesn't get processed unless it is alive.

That may have been a lot to take in, and it might be confusing, but it is simple if you really think about it.
I'm going to show you the full UpdateBall() function so that there is less confusion. Grin
Code:
void PrimaryWindow::UpdateBall()
{
// NEW - ball only updates when its alive

// if the ball is not alive
if (!ballAlive_)
{
// increment the respawn counter
ballRespawnTimeCounter_++;

// if the respawn counter has reached the respawn delay
if (ballRespawnTimeCounter_ == ballRespawnTimeDelay_)
{
// reset the respawn counter
ballRespawnTimeCounter_ = 0;

// respawn the ball:
// * choose a random trajectory
ballDX_ = (float)4+(rand()%8);
ballDY_ = (float)4+(rand()%8);

// * choose a new random position in the top half of the screen
ballX_ = (float)(ballRadius_ + rand() % (SCREEN_W - ballRadius_));
ballY_ = (float)(ballRadius_ + rand() % ((SCREEN_H / 2) - ballRadius_));

// * set the ball to be alive
ballAlive_ = true;
}

// exit the function
return;
}

ballX_ += ballDX_;
ballY_ += ballDY_;

if (ballX_ < ballRadius_ || ballX_ > (SCREEN_W - ballRadius_))
{
ballDX_ = ballDX_ * -1.0f;
ballX_ += ballDX_;
}

if (ballY_ < ballRadius_ || ballY_ > (SCREEN_H - ballRadius_))
{
ballDY_ = ballDY_ * -1.0f;
ballY_ += ballDY_;
}
}

We want the ball to not be rendered when it is dead, so in the RenderBall() function, before you draw the ball, add this code:
Code:
if (!ballAlive_)
{
return;
}

In the UpdatePlayerShot() function, we will add the code necessary to check for collisions between the shot and the ball.
This is where things get interesting.

When the shot object collides with the ball object, we want to kill them both.
We are going to use a simple bounding-box collision detection method using a rectangle / rectangle intersection test.

I'm going to just show the entire function code since I commented what it does heavily.
Code:
void PrimaryWindow::UpdatePlayerShot()
{
// if the shot is not alive, then we move it to match
// the player's X center position
if (!playerShotAlive_)
{
playerShotX_ = playerX_ + (playerWidth_ / 2);
}
else
// if the shot is alive, we move it along the Y axis
{
playerShotY_ -= playerShotDY_;

// if the shot moves off the top of the screen
if (playerShotY_ <  -playerShotRadius_)
{
// kill the shot
playerShotAlive_ = false;

// reset its position to the top of the player
playerShotX_ = playerX_ + (playerWidth_ / 2);
playerShotY_ = playerY_ - playerShotRadius_;

// exit the function because we don't test for
// collisions if its dead.
return;
}

// if the shot collides with the ball:
/*
To test the collision of the ball and the shot,
a simple rectangle / rectangle intersection is
tested. If there is an intersection, there is a
collision.

I chose to use rectangle / rectangle intersection
instead of circle/circle intersection because
it is much faster, and the perfect accuracy is not
needed for a simple demonstration.

This is a collision:

     E        F
     o--------o
A     |  B     |
o-----|--o     |
|     |  |     |
|     o--|-----o
|     G  |     H
o--------o
C        D

Shot Object Rectangle:

A: sx, sy
B: sx + (sr * 2), sy
C: sx, sy + (sr * 2)
D: sx + (sr * 2), sy + (sr * 2)

Ball Object Rectangle:

E: bx, by
F: bx + (br * 2), by
G: bx, by + (br * 2)
H: bx + (br * 2), by + (br * 2)
*/

// calculate the diameter of the ball and shot
// just once since we use this dimension multiple times
int ballDiameter = ballRadius_ * 2;
int shotDiameter = playerShotRadius_ * 2;

// use integers for testing to be simpler and faster
// the x/y coordinates for these objects are the centers.
// we need to calculate the upper-left corned of the bounding
// rectangle, so we just subtract the radius from the center.
int sx = (int)playerShotX_ - playerShotRadius_;
int sy = (int)playerShotY_ - playerShotRadius_;
int bx = (int)ballX_ - ballRadius_;
int by = (int)ballY_ - ballRadius_;

// if there is a collision of the shot and the ball
if (
sx + shotDiameter > bx &&
sx < bx + ballDiameter &&
sy + shotDiameter > by &&
sy < by + ballDiameter
)
{
// kill the shot
playerShotAlive_ = false;

// reset its position to the top of the player
playerShotX_ = playerX_ + (playerWidth_ / 2);
playerShotY_ = playerY_ - playerShotRadius_;

// kill the ball
ballAlive_ = false;
}
}
}

And that is it.

When you compile and run the project, you should get:
1 - A window with a black background
2 - The bouncing blue ball from the first tutorial
3 - The green triangle from the second tutorial that you can move left and right using the arrow keys.
4 - A single reloading cyan bullet that you can fire using the space bar.
5 - The ball should disappear if the bullet collides with it.
6 - The ball should re-appear (re-spawn) after roughly 1 second has passed after it dies.

This is starting to get interesting isn't it?
Well there is more to come! Cheesy
Stay tuned!
Let me know if you were able to follow this tutorial without any trouble.



(Updated April 2, 2010 - Changed usage of underscores in regards to Redslash's notice.)
Logged

Tags: ask code  Allegro tutorial allegro starter kit tutorial c++ 
Pages: [1]   Go Up
  Print  
 
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.21 | SMF © 2015, Simple Machines
.: Theme by Richard Marks :.
Valid XHTML 1.0! Valid CSS!