(The Unofficial) PopCap Framework Developer Board
September 24, 2017, 09:20:33 PM CEST *
Welcome, Guest. Please login or register.

Login with username, password and session length
News:
 
  Home   Forum   Help Calendar Contact Login Register  
Pages: [1] 2   Go Down
  Print  
Author Topic: PopCap Breakout Tutorial: Part 4  (Read 26083 times)
0 Members and 1 Guest are viewing this topic.
vortex
Guest
« on: June 02, 2007, 11:11:07 PM CEST »

Introduction

Welcome to part 4 in my PopCap game development tutorial series.

If you haven’t read the first 3 tutorials then they can be found here:

Part 1
Part 2
Part 3

In the last tutorial we created a sprite class and started to learn some things about movement. In this tutorial we are going to create a Ball class and learn how to do sprite collisions. We are also going to create a paddle that is controllable by the mouse.

The ball class

Sometimes in game development you will encounter situations where you need to allow for multiple instances of a particular object. In this case we may eventually want to have more than one ball bouncing around the screen if we get a special power-up or something.

If we didn’t make a ball class then in this case we would have to add the same code over and over again for each ball instance and then this becomes a pain to manage, especially if the balls have to be created at runtime.

Now we want our ball to have the same methods as the sprite class, so we can have the ball object inherit from it.

The first problem

Open up your solution and go to Sprite.h.

Notice how for the variables that we didn’t want public we used the keyword private. Well we want our ball class to be able to use fields and methods that are private to this class, however we still want our class to be protected from the user calling these methods.

The answer is to use the protected keyword instead. So I the Sprite.h header replace private with protected

When you are done your code should look like this:

	
protected:
	
	
float mX,mY,mVX,mVY,mSpeed;
	
	
Sexy::DDImage*mImg;
Now in this same header locate the line of code that reads:

	
	
Sexy::DDImageGetImage();

After this line add:

	
	
bool CheckPixel(int pxint py);

This is a method that will be used with the CheckOverlap method. The reason that we don’t make this method private is that we need to call it on a passed sprite as well.

Ball header

Later on in this tutorial we are going to implement Sprite collisions, but for right now we are going to add a new header called Ball.h

We start with the typical header stuff:

#ifndef __BALL_H__
#define __BALL_H__

#endif

Now we want to make a class that inherits from our sprite class, so we need to include our sprite header in the Ball header. We will also be using PopCap’s rectangle class in our Ball class, so we will need to include that header as well.

#include "Sprite.h"
#include "SexyAppFramework/Rect.h"

Now we put our Breakout namespace:

namespace Breakout
{

}

Now let’s prototype our entire Ball class:

	
class 
Ball : public Sprite
	
{
	
private:
	
	
Sexy::Rect mBoundry;
	
public:
	
	
Ball(float pX,float pY,float pVX,float pVY,float pSpeed);
	
	
~
Ball();

	
	
void SetBoundry(Sexy::Rect &pBoundry);
	
	
Sexy::Rect GetBoundry();
	
	

	
	
void Bounce(float pVX,float pVY);

	
	
virtual void Update();
	

	
private:
	
	
void CheckBoundry();
	
};

We use class Ball : public Sprite because our ball class inherits from our sprite class.

Our mBoundry field will store the window boundries. While we could have just provided the width and the height of the window, this will make our code more flexible.

Right now the Ball constructor has the same parameters as our Sprite constructor but it doesn’t have to have the same parmeters. We could add new parameters or have less parameters if we wanted.

The SetBoundry and GetBoundry method allow us to set the boundries for our ball.

The Bounce method is a special method that will allow us to react to collisions between other moving objects.

The Update method is from the Sprite class. In our sprite class we also allowed the Draw method to be changed by a child class, however we don’t need to change the draw method for our ball class.

Finally the CheckBoundry method is a private method because we only need to call it in our Update method and the user should never need to call this method directly.

Ball source

Now it is time to add the Ball.cpp source file.

We start by adding the ball header include, graphic and image includes, and tell it to use the breakout namespace like so:

#include "Ball.h"

#include "SexyAppFramework/Graphics.h"
#include "SexyAppFramework/DDImage.h"

using namespace Breakout;

Now for the Ball constructor and deconstructor:

Ball::Ball(float pX,float pY,float pVX,float pVY,float pSpeed):Sprite(pX,pY,pVX,pVY,pSpeed)
{
	
mBoundry=Sexy::Rect();
}

Ball::~Ball()
{
}

The Ball constructor calls the Sprite constructor. If you are unfamiliar with this concept them it would be a good idea to read up on class inheritance.

We define our mBoundry field to a blank rectangle because it is a good idea to initialize all variables before use to prevent them from containing junk data.

The next two methods are the SetBoundry and GetBoundry methods and they are fairly simple:

void Ball::SetBoundry(Sexy::Rect &pBoundry)
{
	
mBoundry=pBoundry;
}

Sexy::Rect Ball::GetBoundry()
{
	
return 
mBoundry;
}

The SetBoundry method takes the passed rectangle and stores it in our mBoundry field, and the GetBoundry method returns our mBoundry field.
« Last Edit: June 04, 2009, 05:43:31 PM CEST by Heiko » Logged
vortex
Guest
« Reply #1 on: June 02, 2007, 11:11:41 PM CEST »

Now here is the CheckBoundry method:


void Ball
::CheckBoundry()
{
 if(
mImg!=NULL//make sure image set
 
{
	
 if(
mX+mVX<mBoundry.mX//Check left
	
 {
	
	
 
mVX=abs(mVX); //Move right
	
	
 
mX=mBoundry.mX-mVX//Reposition X inside boundry
	
 } 
	
if(
mX+mVX+mImg->GetWidth()>mBoundry.mX+mBoundry.mWidth//Check right
	
{
	
	
 
mVX=abs(mVX)*-1//Move left
	
	
 
mX=mBoundry.mX+mBoundry.mWidth-mImg->GetWidth()-mVX//Reposition X inside boundry
	

	
if(
mY+mVY<mBoundry.mY//Check top
	
{
	
	
 
mVY=abs(mVY); //Move down
	
	
 
mY=mBoundry.mY-mVY//reposition Y inside boundry
	

	
if(
mY+mVY+mImg->GetHeight()>mBoundry.mY+mBoundry.mHeight//Check bottom
	
{
	
	
 
mVY=abs(mVY)*-1//Move up
	
	
 
mY=mBoundry.mY+mBoundry.mHeight-mImg->GetHeight()-mVY//reposition y inside boundry
	
}
 }
}


It is a lot of code but hopefully the comments will clear up what we are doing. We basically check every bound and if the object has left it then we reposition the ball and reverse the appropriate directional fields.

Now we can add the Bounce method:


void Ball
::Bounce(float pVX,float pVY)
{
	
mVX*=-1;
	
mVY*=-1;
}


The Bounce method for now simply reverses the X and Y directional fields. Later we will change this to take into account the passed directional parameters.

Finally we add the Update method:


void Ball
::Update()
{
	
CheckBoundry();
	
Sprite::Update();
}


Here is the complete code:


#include "Ball.h"

#include "SexyAppFramework/Graphics.h"
#include "SexyAppFramework/DDImage.h"

using namespace Breakout;

Ball::Ball(float pX,float pY,float pVX,float pVY,float pSpeed):Sprite(pX,pY,pVX,pVY,pSpeed)
{
	
mBoundry=Sexy::Rect();
}
Ball::~Ball()
{
}

void Ball::SetBoundry(Sexy::Rect &pBoundry)
{
	
mBoundry=pBoundry;
}

Sexy::Rect Ball::GetBoundry()
{
	
return 
mBoundry;
}

void Ball::CheckBoundry()
{
 if(
mImg!=NULL//make sure image set
 
{
	
 if(
mX+mVX<mBoundry.mX//Check left
	
 {
	
	
 
mVX=abs(mVX); //Move right
	
	
 
mX=mBoundry.mX-mVX//Reposition X inside boundry
	
 } 
	
if(
mX+mVX+mImg->GetWidth()>mBoundry.mX+mBoundry.mWidth//Check right
	
{
	
	
 
mVX=abs(mVX)*-1//Move left
	
	
 
mX=mBoundry.mX+mBoundry.mWidth-mImg->GetWidth()-mVX//Reposition X inside boundry
	

	
if(
mY+mVY<mBoundry.mY//Check top
	
{
	
	
 
mVY=abs(mVY); //Move down
	
	
 
mY=mBoundry.mY-mVY//reposition Y inside boundry
	

	
if(
mY+mVY+mImg->GetHeight()>mBoundry.mY+mBoundry.mHeight//Check bottom
	
{
	
	
 
mVY=abs(mVY)*-1//Move up
	
	
 
mY=mBoundry.mY+mBoundry.mHeight-mImg->GetHeight()-mVY//reposition y inside boundry
	
}
 }
}

void Ball::Bounce(float pVX,float pVY)
{
	
mVX*=-1;
	
mVY*=-1;
}

void Ball::Update()
{
	
CheckBoundry();
	
Sprite::Update();
}


A small fix

When we added the ball graphic we messed up and forgot to delete the ball graphic in the GameApp deconstructor. Open GameApp.cpp and in the GameApp deconstructor add:


	
delete mBallImg;


You should always be on the lookout for places where you don’t clean up resources as these can cause memory leaks.

Implementing the Ball

Ok now in Board.h locate:


namespace Breakout
{
	
class 
Sprite;
}


And after class Sprite add:


	
class 
Ball;


So that you have:


namespace Breakout
{
	
class 
Sprite;
	
class 
Ball;
}


Now locate:


	
	
Breakout::SpritemBall;


and change it to:


	
	
Breakout::BallmBall;


Now go to Board.cpp. Locate this line of code:


#include "SexyAppFramework/DDImage.h"


and add the following:


#include "SexyAppFramework/Rect.h"


We have to add the Rect header because we want to use rectangles in our Board class.

We also need to include the Ball header so after:


#include "Sprite.h"


Add:


#include "Ball.h"


Locate the Board constructor and change the line that initalizes mBall to:


	
mBall=new Breakout::Ball(400,300,10,10,0);


Locate the code the code:


	
mBall->SetImage(theApp->mBallImg);


And after it add:


	
Rect mWinBoundry(0,0,mApp->mWidth,mApp->mHeight);
	
mBall->SetBoundry(mWinBoundry);


Now we can remove this code in our Board widgets update method:


if(mBall->GetX()+mBall->GetVX()<0//Leaves left 
        
mBall->SetVX(abs(mBall->GetVX())); 

    if(
mBall->GetX()+mBall->GetVX()+mBall->GetImage()->GetWidth()>mApp->mWidth//leave right 
        
mBall->SetVX(abs(mBall->GetVX())*-1); 

    if(
mBall->GetY()+mBall->GetVY()<0//leave up 
        
mBall->SetVY(abs(mBall->GetVY())); 

    if(
mBall->GetY()+mBall->GetVY()+mBall->GetImage()->GetHeight()>mApp->mHeight//leave down 
        
mBall->SetVY(abs(mBall->GetVY())*-1); 


This code is no longer needed because we have included it in our Ball class.

At this point we should be able to run the game and see the ball bouncing around on the screen once again.

Press F5 to run your game!

« Last Edit: June 02, 2007, 11:21:57 PM CEST by vortex » Logged
vortex
Guest
« Reply #2 on: June 02, 2007, 11:12:42 PM CEST »

Let’s add another ball to the mix.

Go to Board.h and locate:


	
	
Breakout::BallmBall;


Below it add:


	
	
Breakout::BallmBall2;


Now in Board.cpp

Locate:


	
mBall=new Breakout::Ball(400,300,10,10,0);


And below it add:


	
mBall2=new Breakout::Ball(100,100,1,-1,0);


Locate:

	
mBall->SetImage(theApp->mBallImg);


And below it add:


	
mBall2->SetImage(theApp->mBallImg);


Then locate:


mBall
->SetBoundry(mWinBoundry);


And add:


	
mBall2->SetBoundry(mWinBoundry);


In the Board deconstructor after:


	
delete mBall;


Add:


	
delete mBall2;


Your Board constructor and deconstructor should now look like this:


Board
::Board(GameApptheApp)
{
	
mApp theApp;
	
mBall=new Breakout::Ball(400,300,10,10,0);
	
mBall2=new Breakout::Ball(100,100,1,-1,0);
	
mBall->SetImage(theApp->mBallImg);
	
mBall2->SetImage(theApp->mBallImg);
	
Rect mWinBoundry(0,0,mApp->mWidth,mApp->mHeight);
	
mBall->SetBoundry(mWinBoundry);
	
mBall2->SetBoundry(mWinBoundry);
}

Board::~Board()
{
	
delete mBall;
	
delete mBall2;
}


In the Board update method locate:


	
mBall->Update();


Add the following code:


	
mBall2->Update();


Finally in the Board draw method locate:


	
mBall->Draw(g);


And add:


	
mBall2->Draw(g);


For now remove the commenting before:


	
//g->FillRect(0, 0, mWidth, mHeight); //Fill screen with current color


Our Draw method should look like this:


void Board
::Draw(Graphicsg)
{
	
g->SetColor(Color(0,0,0)); //Set dark red color
	
g->FillRect(00mWidthmHeight); //Fill screen with current color
	
//draw here
	
mBall->Draw(g);
	
mBall2->Draw(g);
}


Test out your application and you will see two balls moving around on the screen!



The first ball is moving too fast so change this line in the constructor:


	
mBall=new Breakout::Ball(400,300,10,10,0);


To the following:

	
mBall=new Breakout::Ball(400,300,-1,-1,0);



Now run your application and eventually you will something like this:



The problem is that we have no code to tell if two ball objects have collided with each other and as a result the two balls simply pass right through one another.

Naturally this isn’t very realistic and so we want to do two things:

A) Detect when a collision has occurred between two sprites
B) Respond to the collision by making the two sprites move away from one another

The first step is to detect the collision and to do this we are going to implement the CheckOverlap method of our Sprite class.
« Last Edit: June 02, 2007, 11:23:30 PM CEST by vortex » Logged
vortex
Guest
« Reply #3 on: June 02, 2007, 11:13:47 PM CEST »

Collision detection

Collision detection is a very broad subject and there are a number of ways to detect collisions between two objects.

The first way we can check for collisions is amoung the easiest.

Imagine that around our sprites we had a box that bounded the sprite perfectly.

Like this:



Now we can state that if these two rectangles intersect, we have a collision!



However as this picture shows, this method has a bit of a problem. This method tends to overdetect a collision. That means that it detects a collision between two objects even if they are not actually touching.

We want to implement something called Pixel perfect collision detection which means that we will only register a collision if two nontransparent areas of our sprite overlap.

We are in luck however because we can expand our bounding box collision to add pixel perfect collision detection!

The Bounding box check is the first check we do. If the two bounding boxes do not overlap then obviously there can not be a collision. If they do then we need to check the area of overlap to see if two non transparent pixels are overlapping!

This has the advantage of reducing the amount of pixels we have to search to the size of the overlapping area.

Here is the difference that such a check can make:



It results in much more accurate detection!

Now let’s look at how to implement this.

Implementing pixel perfect collision detection

Open Sprite.cpp and after this line of code:


#include "SexyAppFramework/DDImage.h"


Add this line of code:


#include "SexyAppFramework/Rect.h"


This will allow us to use the Rectangle class inside of our sprite class.

Now locate the CheckOverlap method.

The first thing we will do is add some error checking to make sure both our sprite and the passed sprite have defined images. This will prevent this method from crashing our game:


	
if(
mImg!=NULL&&pOtherSprite->GetImage()!=NULL)
	
{
	
}


The step step that we need to do is to construct bounding rectangles for both sprites.


	
Sexy::Rect Bound1(mX,mY,mImg->GetWidth(),mImg->GetHeight());
	
Sexy::Rect Bound2(pOtherSprite->GetX(),pOtherSprite->GetY(),pOtherSprite->GetImage()->GetWidth(),pOtherSprite->GetImage()->GetHeight());


The X and the Y of each sprite becomes the X and Y starting position of the bounding rectangle, and the width and height of each image becomes the width and height of the bounding rectangles.

Now we need to check to see if the two bounding rectangles overlap. The rectangle class has an Intersects methd that does just that:


	
	
if(
Bound1.Intersects(Bound2))
	
	
{

	
	
}


Now we need to get a rectangle that is the intersection area:


	
	
	
Sexy::Rect Result=Bound1.Intersection(Bound2);


We need to loop through this area so we can test each pixel that it contains:


	
	
	
for(
int iy=Result.mY;iy<Result.mHeight+Result.mY;iy++)
	
	
	
{
	
	
	
	
for(
int ix=Result.mX;ix<Result.mWidth+Result.mX;ix++)
	
	
	
	
{

	
	
	
	
}
	
	
	
}


Now we can check each looped pixel and if both sprites have an opaque pixel at that point, then there is a collision!


	
	
	
	
	
if(
CheckPixel(ix-Bound1.mX,iy-Bound1.mY)&&pOtherSprite->CheckPixel(ix-Bound2.mX,iy-Bound2.mY))
	
	
	
	
	
	
return 
true;


Wait a minute! What is CheckPixel? Well we will implement that in a second.


Now right before the bracket that ends our method we want to add:


return false;


because if the method hasn’t returned by now, then there was no collision.

Here is the complete code to the CheckOverlap method:


bool Sprite
::CheckOverlap(SpritepOtherSprite)
{
	
if(
mImg!=NULL&&pOtherSprite->GetImage()!=NULL)
	
{
	
	
Sexy::Rect Bound1(mX,mY,mImg->GetWidth(),mImg->GetHeight());
	
	
Sexy::Rect Bound2(pOtherSprite->GetX(),pOtherSprite->GetY(),pOtherSprite->GetImage()->GetWidth(),pOtherSprite->GetImage()->GetHeight());

	
	
if(
Bound1.Intersects(Bound2))
	
	
{
	
	
	
Sexy::Rect Result=Bound1.Intersection(Bound2);
	
	
	
for(
int iy=Result.mY;iy<Result.mHeight+Result.mY;iy++)
	
	
	
{
	
	
	
	
for(
int ix=Result.mX;ix<Result.mWidth+Result.mX;ix++)
	
	
	
	
{
	
	
	
	
	
if(
CheckPixel(ix-Bound1.mX,iy-Bound1.mY)&&pOtherSprite->CheckPixel(ix-Bound2.mX,iy-Bound2.mY))
	
	
	
	
	
	
return 
true;
	
	
	
	
}
	
	
	
}
	
	
}
	
}
	
return 
false;
}


Now copy and paste this code before the CheckOverlap method:


bool Sprite
::CheckPixel(int px,int py)
{
	
ulongPixelData=mImg->GetBits();
	
if(
PixelData)
	
{
	
	
int mAlphaVal=(int)(PixelData[mImg->GetWidth()*py+px]>>24);
	
	
return 
mAlphaVal>20;
	
}
	
return 
false;
}


This gets the bits from an image as a 1D array. We then get a specific pixel by converting the X and Y coordinats into a 1D index using the formula:

Pos=Y*Width+X

Say I had a 5x5 grid and I was at 2,3 in the grid. Well because the index is 0 based, there are 3 complete rows before the 3 position and so I have 3*5 cells. Then I add 2 because there are two more cells at this coordinate.

3*5=15+2=17

Code:

      0   1   [color=red]2[/color]   3   4
0   [00][01][02][03][04]
1   [05][06][07][08][09]
2   [10][11][12][13][14]
[color=red]3[/color]   [15][16][color=red][17][/color][18][19]
4   [20][21][22][23][24]


Hopefully that should clear it up a bit.

Once I get the pixel color I shift it to get the alpha component.

When I have retrieved the alpha component of the image I test to see if it is greater than 20 (Just a threshold value so that really transparent pixels don’t cause a collision). If so then this pixel was opaque.

The CheckOverlap function checked each image to see if we could find a common opaque pixel in the overlap area. If so then we found an overlap and thus a collision!

Ok so we implemented our CheckOverlap method and now we are ready to check to see if our two balls overlap.
« Last Edit: June 03, 2007, 05:03:57 AM CEST by vortex » Logged
vortex
Guest
« Reply #4 on: June 02, 2007, 11:14:26 PM CEST »

Go to Board.cpp and before the update methods for our ball objects in our Board widgets update method, add this:


	
if(
mBall->CheckOverlap(mBall2))
	
{
	
	
mBall->Bounce(0,0);
	
	
mBall2->Bounce(0,0);
	
}


So that the final method looks like this:


void Board
::Update()
{
	
Widget::Update();
	
//Your update code here!
	
if(
mBall->CheckOverlap(mBall2))
	
{
	
	
mBall->Bounce(0,0);
	
	
mBall2->Bounce(0,0);
	
}
	
mBall->Update();
	
mBall2->Update();
	
MarkDirty();
}



Here we check to see if the Ball sprite is overlapping the Ball2 sprite, and if they are then we bounce both sprites.

Run the game by pressing F5 and you will see the two sprites bounce off of each other when they collide!

A better bounce

The bounce that we have looks ok when both balls are moving at the same speed, but if you chance the speed of either ball then you will notice that it looks a bit unrealistic.

We want the speeds to transfer.

Open Ball.cpp and implement this new bounce method:


void Ball
::Bounce(float pVX,float pVY)
{
	
mVX=(mVX*-0.5+pVX/2);
	
mVY=(mVY*-0.5+pVY/2);


The factors in this method are completely fudged, so feel free to play around with them until you find values that look the best to you.

What we have done here is sumed half of the current reversed energy plus half of the passed energy. This is like each object imparts half of it’s energy onto the other.

Now in Board.cpp in our Update method change our CheckOverlap code to:


	
if(
mBall->CheckOverlap(mBall2))
	
{
	
	
float tVX=mBall->GetVX();
	
	
float tVY=mBall->GetVY();
	
	
mBall->Bounce(mBall2->GetVX(),mBall2->GetVY());
	
	
mBall2->Bounce(tVX,tVY);
	
}


We store the old VX and VY values for our first ball so that when we change them with the first bounce we can pass the old values to the second ball. This will allow both balls to be affected by the energy of the ball they collided with!

You can decide if you want to have perfectly elastic collisions or if you want to have the balls get slower as they impact each other. I am programming them to lose energy as I view it as more realistic.

Try out your game by pressing F5. Now when the two balls collide with each other, they become slower!

But wait! There is a problem. It appears that our border checking code doesn’t do well with floating point values. The balls seem to slow down when hitting the boundries. If our speed ever falls close to 0 then this will cause our balls to eventually stop.



To correct this problem we are going to use a floating point version of our absolute value function.

Go to Ball.cpp and after


#include "SexyAppFramework/DDImage.h"

Add the following include:


#include <cmath>


We now have access to a different absolute value function called fabs which gets the absolute value of a float instead of an integer.

Change your CheckBoundry method to this:


void Ball
::CheckBoundry()
{
 if(
mImg!=NULL//make sure image set
 
{
	
 if(
mX+mVX<mBoundry.mX//Check left
	
 {
	
	
 
mVX=fabs(mVX); //Move right
	
	
 
mX=mBoundry.mX-mVX//Reposition X inside boundry
	
 } 
	
if(
mX+mVX+mImg->GetWidth()>mBoundry.mX+mBoundry.mWidth//Check right
	
{
	
	
 
mVX=fabs(mVX)*-1//Move left
	
	
 
mX=mBoundry.mX+mBoundry.mWidth-mImg->GetWidth()-mVX//Reposition X inside boundry
	

	
if(
mY+mVY<mBoundry.mY//Check top
	
{
	
	
 
mVY=fabs(mVY); //Move down
	
	
 
mY=mBoundry.mY-mVY//reposition Y inside boundry
	

	
if(
mY+mVY+mImg->GetHeight()>mBoundry.mY+mBoundry.mHeight//Check bottom
	
{
	
	
 
mVY=fabs(mVY)*-1//Move up
	
	
 
mY=mBoundry.mY+mBoundry.mHeight-mImg->GetHeight()-mVY//reposition y inside boundry
	
}
 }
}



Now the ball doesn’t slow down when it touches a wall.



Cleaning up

Your first challenge in this course is to remove everything pertaining to mBall2. I won’t explain how to do this but you need to remove the mBall2 and the collision code in our Board widgets Update method.

We need to do this because we no longer want to have two balls on the screen, we want to have one ball like we started with.

Incase you are too lazy to do this, then I have provided the project up to this point so that you can download it.

Get the project
« Last Edit: June 02, 2007, 11:24:55 PM CEST by vortex » Logged
vortex
Guest
« Reply #5 on: June 02, 2007, 11:16:11 PM CEST »

Adding a paddle

This tutorial series is all about creating the game Breakout. In the game Breakout you knock a ball around with a paddle and destroy waves of bricks.

The paddle is the element that is controlled by the player and so that is the element that we will add next to our game.

When it comes to Casual game development the primary rule is that all player input should come from the mouse. The only time the player should be required to use the keyboard is to quit the game, or enter their name.

The first thing that we need is a graphic for our paddle.

A good size for our paddle will be 64x20 pixels and at this point you can either create your own graphic like wih the ball graphic, or I will provide a graphic for you.

Here is the paddle graphic:



And here is the Alpha channel:



Now in GameApp.h we need to locate:


	
	
DDImagemBallImg;


Below it add:


	
	
DDImagemPaddleImg;


Now go to GameApp.cpp and locate the LoadingThreadProc method.

Add the code:


	
mPaddleImg=(DDImage*)GetImage("GreenBat"); 


So this method should now look like:


void GameApp
::LoadingThreadProc()
{
	
mBallImg=(DDImage*)GetImage("BlueBall");
	
mPaddleImg=(DDImage*)GetImage("GreenBat");
}


In the GameApp deconstructor we need to add the following line of code:


	
delete mPaddleImg;


So your GameApp deconstructor should now look like this:


GameApp
::~GameApp()
{
	
mWidgetManager->RemoveWidget(mBoard);
	
delete mBoard;
	
delete mBallImg;
	
delete mPaddleImg;
}


Now go to Board.h

Right below the line of code:


	
	
Breakout::BallmBall;


Add this code:


	
	
Breakout::Sprite *mPaddle;


So your code should look like:


	
private:

	
	
GameApp*
	
mApp;

	
	
Breakout::BallmBall;
	
	
Breakout::Sprite *mPaddle;
	
public:


We created our paddle as type Sprite instead of as a Ball because we don’t need the extra functionality of the Ball class.

While we could make a paddle class, right now we don’t need to do this. We only have one paddle and we can’t think of any circumstances in our game design where we would need another paddle. In this situation it is a safe bet to just code the logic for the paddle directly in the Board widget.

When using OOP it is very easy to go class crazy and use a class for EVERYTHING, however this can make the code longer then it needs to be and more complicated as well. I like to use the general rule of thumb that if it is reusuable or if we need multiple instances which would otherwise have the exact same code over and over again, then we should make a class. If we only have one instance of something and it isn’t reusuable then there is no harm in coding some logic in the Board widget.

After the line of code you just added add this line of code:


	
	
int mOldMouseX,mOldMouseY,mMouseX,mMouseY


This will store the old mouse positions and the current mouse positions so that we can calculate the direction the mouse has moved.

Now after the line of code:


	
	
virtual void Update();


Add this line of code:


	
	
virtual void MouseMove(int x,int y);



This will allow us to add our own code to the widget method that is called whenever the mouse is moved.
« Last Edit: June 02, 2007, 11:26:12 PM CEST by vortex » Logged
vortex
Guest
« Reply #6 on: June 02, 2007, 11:16:47 PM CEST »

[FONT='Courier New']Now go to Board.cpp[/FONT]

[FONT='Courier New']Locate the code:[/FONT]

[FONT='Courier New']
#include "SexyAppFramework/Rect.h"
[/FONT]

[FONT='Courier New']And the following code below it:[/FONT]

[FONT='Courier New']
#include "SexyAppFramework/WidgetManager.h"
[/FONT]

[FONT='Courier New']We need to include this class because we need to use the WidgetManager in our widget.[/FONT]

[FONT='Courier New']I have made a few changes to the Board widget constructor so just replace the method with this code:[/FONT]

[FONT='Courier New']
Board::Board(GameApptheApp)
{
      
mApp theApp;
      
mBall=new Breakout::Ball(mApp->mWidth/2,mApp->mHeight/2,-2,-2,0);
      
mBall->SetImage(mApp->mBallImg);
      
Rect mWinBoundry(0,0,mApp->mWidth,mApp->mHeight);
      
mBall->SetBoundry(mWinBoundry);
      
mPaddle=new Breakout::Sprite(0,mApp->mHeight-mApp->mPaddleImg->GetHeight()-10,0,0,0);
      
mPaddle->SetImage(mApp->mPaddleImg);
      
mOldMouseX=mApp->mWidgetManager->mLastMouseX//Gets current mouse X pos
      
mOldMouseY=mApp->mWidgetManager->mLastMouseY//Gets current mouse Y pos
      
mMouseX=mOldMouseX;
      
mMouseY=mOldMouseY;
      
mApp->SetCursor(CURSOR_NONE);

[/FONT]

[FONT='Courier New']I changed the Ball constructor so that it places the ball at the center of the window width and height and I initalized our Paddle sprite. [/FONT]

[FONT='Courier New']I set the mOldMouseX and the mOldMouseY variables to the current X and Y mouse position. We can use these variables to determine in what direction the mouse moves! The mMouseX and mMouseY variables always hold the latest mouse coordinates and right now the old mouse position and the new mouse position are equal. That means that we haven’t moved our paddle any direction yet.[/FONT]

[FONT='Courier New']The SetCursor method of the GameApp class allows us to hide the mouse cursor. Since we will be moving a paddle with the mouse we don’t want the mouse cursor to distract us![/FONT]

[FONT='Courier New']Now on the Board deconstructor add:[/FONT]

[FONT='Courier New']
      delete mPaddle;
[/FONT]

[FONT='Courier New']So that the Board deconstructor looks like this:[/FONT]

[FONT='Courier New']
Board::~Board()
{
      
delete mBall;
      
delete mPaddle;
}
[/FONT]

[FONT='Courier New']Now you really don’t need to call the pdate method for the paddle sprite as our VX and VY directional variables are 0. [/FONT]

[FONT='Courier New']However if you want to call the update method anyway in the Board update method add:[/FONT]

[FONT='Courier New']
      mPaddle->Update();
[/FONT]

[FONT='Courier New']Before mBall calls it’s update method we need to add the following code:[/FONT]

[FONT='Courier New']
      float PaddleSpeed=min(max(abs(mMouseX-mOldMouseX)/2.0f,4),10);
      
int PaddleDir=0;
      if(
abs(mMouseX-mOldMouseX)>0)
            
PaddleDir=(mMouseX-mOldMouseX)/abs(mMouseX-mOldMouseX);
 
      if(
mBall->CheckOverlap(mPaddle))
      {
            
mBall->SetY(mPaddle->GetY()-mBall->GetImage()->GetWidth());
            
mBall->Bounce(PaddleDir*PaddleSpeed,-6);
      }
      
mBall->SetVY(mBall->GetVY()+0.02);
[/FONT]

[FONT='Courier New']This is quite a bit of code! [/FONT]

[FONT='Courier New']First we calculate the speed of the paddle which is calculated by taking the magnitude that we have moved the mouse and then limiting it to a certain range.[/FONT]

[FONT='Courier New']Then we see if our mouse has changed X position any. If it has then we can set our mouse direction to the sign of that change.[/FONT]

[FONT='Courier New']Then we check to see if the ball has collided with the paddle. If it has then we set the ball to above the paddle and we make the ball bounce off the paddle. We pass our X speed by recombining our speed and our direction to give the VX component and we pass -6 as our VY component (which will make the ball travel faster each time it hits the paddle. [/FONT]

[FONT='Courier New']Then by adding a small value to the VY component of our ball each frame we can make the ball seem like it has gravity![/FONT]



[FONT='Courier New']Now in the Draw method for the Board widget add:[/FONT]

[FONT='Courier New']
      mPaddle->Draw(g);
[/FONT]


[FONT='Courier New']Now after the Draw method, copy and paste this new method:[/FONT]
[FONT='Courier New']
void Board::MouseMove(int xint y)
{
      
mPaddle->SetX(min(max(x-mPaddle->GetImage()->GetWidth()/2,0),mWidth-mPaddle->GetImage()->GetWidth()));
      
mMouseX=x;
      
mMouseY=y;
}
[/FONT]
[FONT='Courier New'] [/FONT]
[FONT='Courier New']This method is called when the mouse is moved. We change the X position of our paddle to the x mouse position but we adjust the position of the image so that our X is located at the center of our image. [/FONT]
[FONT='Courier New'] [/FONT]
[FONT='Courier New']We then limit the values that this X position can have so that our paddle does not move off screen.[/FONT]
[FONT='Courier New'] [/FONT]
[FONT='Courier New']Test the game[/FONT]
[FONT='Courier New'] [/FONT]
[FONT='Courier New']Test the game by pressing F5 and move the paddle around with the mouse to knock the ball around![/FONT]
[FONT='Courier New'] [/FONT]
[FONT='Courier New'][/FONT]
[FONT='Courier New'] [/FONT]
[FONT='Courier New'] [/FONT]
[FONT='Courier New']What we have done in this tutorial[/FONT]
[FONT='Courier New'] [/FONT]
[FONT='Courier New']In this tutorial we implemented a ball class, added sprite collisions, and added a mouse controlled paddle to the game.[/FONT]
[FONT='Courier New'] [/FONT]
[FONT='Courier New']Until next time…[/FONT]
[FONT='Courier New'] [/FONT]
[FONT='Courier New']Well I hope you enjoyed this part of my tutorial! Next time we are going to look at making the ball move smoother and we are going to start our block manager![/FONT]
[FONT='Courier New'] [/FONT]
Attached is the latest code for our project.[FONT='Courier New'][/FONT]
[FONT='Courier New'] [/FONT]
[FONT='Courier New']Legal Stuff[/FONT]
[FONT='Courier New'] [/FONT]
[FONT='Courier New'][/FONT] I take no responsibility what so ever for any damages or liabilities from the code and/or information presented in this tutorial. This tutorial is provided AS-IS with no warranty what so ever.[/FONT][/COLOR][FONT='Courier New'][/FONT]
Logged
vortex
Guest
« Reply #7 on: June 02, 2007, 11:19:27 PM CEST »

Now go to Board.cpp

Locate the code:


#include "SexyAppFramework/Rect.h"


And the following code below it:


#include "SexyAppFramework/WidgetManager.h"


We need to include this class because we need to use the WidgetManager in our widget.

I have made a few changes to the Board widget constructor so just replace the method with this code:


Board
::Board(GameApptheApp)
{
	
mApp theApp;
	
mBall=new Breakout::Ball(mApp->mWidth/2,mApp->mHeight/2,-2,-2,0);
	
mBall->SetImage(mApp->mBallImg);
	
Rect mWinBoundry(0,0,mApp->mWidth,mApp->mHeight);
	
mBall->SetBoundry(mWinBoundry);
	
mPaddle=new Breakout::Sprite(0,mApp->mHeight-mApp->mPaddleImg->GetHeight()-10,0,0,0);
	
mPaddle->SetImage(mApp->mPaddleImg);
	
mOldMouseX=mApp->mWidgetManager->mLastMouseX//Gets current mouse X pos
	
mOldMouseY=mApp->mWidgetManager->mLastMouseY//Gets current mouse Y pos
	
mMouseX=mOldMouseX;
	
mMouseY=mOldMouseY;
	
mApp->SetCursor(CURSOR_NONE);



I changed the Ball constructor so that it places the ball at the center of the window width and height and I initalized our Paddle sprite.

I set the mOldMouseX and the mOldMouseY variables to the current X and Y mouse position. We can use these variables to determine in what direction the mouse moves! The mMouseX and mMouseY variables always hold the latest mouse coordinates and right now the old mouse position and the new mouse position are equal. That means that we haven’t moved our paddle any direction yet.

The SetCursor method of the GameApp class allows us to hide the mouse cursor. Since we will be moving a paddle with the mouse we don’t want the mouse cursor to distract us!

Now on the Board deconstructor add:


	
delete mPaddle;


So that the Board deconstructor looks like this:


Board
::~Board()
{
	
delete mBall;
	
delete mPaddle;
}


Now you really don’t need to call the pdate method for the paddle sprite as our VX and VY directional variables are 0.

However if you want to call the update method anyway in the Board update method add:


	
mPaddle->Update();


Before mBall calls it’s update method we need to add the following code:


	
float PaddleSpeed=min(max(abs(mMouseX-mOldMouseX)/2.0f,4),10);
	
int PaddleDir=0;
	
if(
abs(mMouseX-mOldMouseX)>0)
	
	
PaddleDir=(mMouseX-mOldMouseX)/abs(mMouseX-mOldMouseX);

	
if(
mBall->CheckOverlap(mPaddle))
	
{
	
	
mBall->SetY(mPaddle->GetY()-mBall->GetImage()->GetWidth());
	
	
mBall->Bounce(PaddleDir*PaddleSpeed,-6);
	
}
	
mBall->SetVY(mBall->GetVY()+0.02);


This is quite a bit of code!

First we calculate the speed of the paddle which is calculated by taking the magnitude that we have moved the mouse and then limiting it to a certain range.

Then we see if our mouse has changed X position any. If it has then we can set our mouse direction to the sign of that change.

Then we check to see if the ball has collided with the paddle. If it has then we set the ball to above the paddle and we make the ball bounce off the paddle. We pass our X speed by recombining our speed and our direction to give the VX component and we pass -6 as our VY component (which will make the ball travel faster each time it hits the paddle.

Then by adding a small value to the VY component of our ball each frame we can make the ball seem like it has gravity!

We also need to set the mOldMouseX and mOldMouseY variables so here is the final code:


void Board
::Update()
{
	
Widget::Update();
	
//Your update code here!
	
float PaddleSpeed=min(max(abs(mMouseX-mOldMouseX)/2.0f,4),10);
	
int PaddleDir=0;
	
if(
abs(mMouseX-mOldMouseX)>0)
	
	
PaddleDir=(mMouseX-mOldMouseX)/abs(mMouseX-mOldMouseX);

	
if(
mBall->CheckOverlap(mPaddle))
	
{
	
	
mBall->SetY(mPaddle->GetY()-mBall->GetImage()->GetWidth());
	
	
mBall->Bounce(PaddleDir*PaddleSpeed,-6);
	
}
	
mBall->SetVY(mBall->GetVY()+0.02);
	
mBall->Update();
	
mPaddle->Update();
	
mOldMouseX=mMouseX;
	
mOldMouseY=mMouseY;
	
MarkDirty();
}


Now in the Draw method for the Board widget add:


	
mPaddle->Draw(g);



Now after the Draw method, copy and paste this new method:

void Board
::MouseMove(int xint y)
{
	
mPaddle->SetX(min(max(x-mPaddle->GetImage()->GetWidth()/2,0),mWidth-mPaddle->GetImage()->GetWidth()));
	
mMouseX=x;
	
mMouseY=y;
}


This method is called when the mouse is moved. We change the X position of our paddle to the x mouse position but we adjust the position of the image so that our X is located at the center of our image.

We then limit the values that this X position can have so that our paddle does not move off screen.

Test the game

Test the game by pressing F5 and move the paddle around with the mouse to knock the ball around!




What we have done in this tutorial

In this tutorial we implemented a ball class, added sprite collisions, and added a mouse controlled paddle to the game.

Until next time…

Well I hope you enjoyed this part of my tutorial! Next time we are going to look at making the ball move smoother and we are going to start our block manager!

Attached is the latest code for our project.

Legal Stuff

I take no responsibility what so ever for any damages or liabilities from the code and/or information presented in this tutorial. This tutorial is provided AS-IS with no warranty what so ever.

Continue to part 5
« Last Edit: December 23, 2007, 08:42:40 AM CET by vortex » Logged
Neuman
Guest
« Reply #8 on: June 03, 2007, 09:52:47 PM CEST »

When talking about balls, radial collision detection should also be mentioned. It's much faster than per-pixel collision and it's most preferable for detecting collision between circles or spheres.
All that is required is to check the squared distance between the centers of the two balls. Few fields are to be added to Ball's implementation.

class Ball {
	
float mRadius;
	
float mCenterX;
	
float mCenterY;
	
// ...
public:
	
void SetRadius(float radius);

	
float GetRadius();
	
float GetCenterX();
	
float GetCenterY();
	
// ...
};

void Ball::SetRadius(float radius)
{
	
mRadius radius;
}

float Ball::GetRadius() 
{
	
return 
mRadius;
}

float Ball::GetCenterX() 
{
	
return 
mCenterX;
}

float Ball::GetCenterY() 
{
	
return 
mCenterY;
}

// ...

mBall->SetRadius(mBallImg->GetWidth()/2);


The actual collision detection should look like this:

float square
(float val)
{
	
return 
val val;
}

float distance(Ballball1Ballball2)
{
	
return 
square(ball2->GetCenterX() - ball1->GetCenterX) + square(ball2->GetCenterY() - ball1->GetCenterY);
}

if (
distance(mBallmBall2) < square(mBall->GetRadius() + mBall2->GetRadius())) {
	
// bounce code here
}
Logged
CGV
Guest
« Reply #9 on: June 04, 2007, 10:37:55 PM CEST »

Almost missed it down here, someone make this sticky.

Great tutorial again vortex. I'm surprised how much I learned from this one.

The alternative collision detection method posted by Neuman in the ideas thread is also good to know, Thanks Neuman.
Logged
sudhakar_reddym
Guest
« Reply #10 on: June 06, 2007, 08:42:04 AM CEST »

I have just started using the PopCap framework for creating games, learned a lot from your tutorial. Would like to know if we can create multiplayer games using this framework?
Logged
fezztah
Guest
« Reply #11 on: June 13, 2007, 04:56:22 PM CEST »

A nice simple introduction to the library - thanks Smiley
Logged
vortex
Guest
« Reply #12 on: June 14, 2007, 11:17:34 PM CEST »

Thanks Smiley. I just started part 5 and it will be up by monday.
Logged
lacop
Guest
« Reply #13 on: July 16, 2007, 09:55:07 PM CEST »

Great tutorials Smiley What's with part5? When it will be done?
Logged
vortex
Guest
« Reply #14 on: July 17, 2007, 01:03:04 AM CEST »

Soon, on vacation now Wink .
Logged
Pages: [1] 2   Go Up
  Print  
 
Jump to:  


Powered by MySQL Powered by PHP Powered by SMF 1.1.19 | SMF © 2013, Simple Machines Valid XHTML 1.0! Valid CSS!
SimplePortal 2.3.3 © 2008-2010, SimplePortal