Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon


Game From Scratch C++ Edition Part 7

 

 

 

Ok, I admit outright, this part was a long time in releasing and I apologize for that.  A big part of the reason for the delay is in my first few drafts I attempted to teach the mathematics behind this chapter, which I initially believe would be a minor task., I was wrong.  Teaching mathematics requires so much prior knowledge that I cannot safely assume you have, but once you start getting into one subject you realize you have to explain a few dozen others before it makes sense.  After a few dozen attempts I did the pragmatic thing… I gave up.  So in this chapter there is some relatively basic math that will be lacking a detailed explanation of “why” it works.  Frankly it is a (massive) topic all it’s own.  As a result I will scour the net and various books and put together a reference post on math for game programmers at some point in the future.

 

 

Alright, that covers what this chapter isn’t, lets get into what it is.  In this chapter we will set the ball in motion and add some basic collision detection.  We are also going to cover C++ casting but other than that, there isn’t much new C++ coverage this chapter. The physics are a little bit more involved than a typical pong clone, but nothing too complex.  For the most part, all of this logic is going to take place in the GameBall class, but first we need to make some basic changes to it’s base class  VisibleGameObject.  First lets open up VisibleGameObject.h and we want to add the following:

 

virtual float GetWidth() const; virtual float GetHeight() const; virtual sf::Rect<float> GetBoundingRect() const;

Click here to download VisibleGameObject.h

 

 

Now we need to implement these changes in VisibleGameObject.cpp:

 

float VisibleGameObject::GetHeight() const { return _sprite.GetSize().y; } float VisibleGameObject::GetWidth() const { return _sprite.GetSize().x; } sf::Rect<float> VisibleGameObject::GetBoundingRect() const { sf::Vector2f size = _sprite.GetSize(); sf::Vector2f position = _sprite.GetPosition(); return sf::Rect<float>( position.x - size.x/2, position.y - size.y/2, position.x + size.x/2, position.y + size.y/2 ); }

Click here to download VisibleGameObject.cpp

 

 

The first two methods are super straight forward accessor methods which we will be calling a lot.  Another reason for these methods existence is I personally find SFML’s use of x and y for width and height very unintuitive.

 

 

The GetBoundingRect() method simply returns a rectangle that defines the boundaries of our sprite.  We will use this quite often for checking for collisions.  Keep in mind that the results are in screen space coordinates and since we used SetCenter to define our sprites position to be relative to it’s middle as opposed to it’s top left corner, we obtain the four corners of the rectangle by add and subtracting half the width or height of the sprite relative to it’s current position.

 

 

As I said earlier, the majority of the changes occur in the GameBall class.  Previously it was mostly just a stub, now we are going to add a great deal more functionality.  The GameBall is going to be in charge of determining if a collision occurred and eventually much more.  Open up GameBall.h and add the edit it as follows:

 

#pragma once #include "visiblegameobject.h" class GameBall : public VisibleGameObject { public: GameBall(); virtual ~GameBall(); void Update(float); private: float _velocity; float _angle; float _elapsedTimeSinceStart; float LinearVelocityX(float angle); float LinearVelocityY(float angle); };

Click here to download GameBall.h

 

 

 

Notice that now instead of letting our base class VisibleGameObject handle Update(), we are overriding it and implementing it ourselves.  As you will see shortly, Update is where the majority of changes took place.  Beyond that, we added a couple private member variables, _velocity, _angle and _elapsedTimeSinceStart, as well as the two functions LinearVelocityX and LinearVelocityY.  Let’s take a look at their implementation below in GameBall.cpp:

 

 

 

#include "StdAfx.h" #include "GameBall.h" #include "Game.h" GameBall::GameBall() : _velocity(230.0f), _elapsedTimeSinceStart(0.0f) { Load("images/ball.png"); assert(IsLoaded()); GetSprite().SetCenter(15,15); sf::Randomizer::SetSeed(std::clock()); _angle = (float)sf::Randomizer::Random(0,360); } GameBall::~GameBall() { } void GameBall::Update(float elapsedTime) { _elapsedTimeSinceStart += elapsedTime; // Delay game from starting until 3 seconds have passed if(_elapsedTimeSinceStart < 3.0f) return; float moveAmount = _velocity * elapsedTime; float moveByX = LinearVelocityX(_angle) * moveAmount; float moveByY = LinearVelocityY(_angle) * moveAmount; //collide with the left side of the screen if(GetPosition().x + moveByX <= 0 + GetWidth()/2 || GetPosition().x + GetHeight()/2 + moveByX >= Game::SCREEN_WIDTH) { //Ricochet! _angle = 360.0f - _angle; if(_angle > 260.0f && _angle < 280.0f) _angle += 20.0f; if(_angle > 80.0f && _angle < 100.0f) _angle += 20.0f; moveByX = -moveByX; } PlayerPaddle* player1 = dynamic_cast<PlayerPaddle*>(Game::GetGameObjectManager().Get("Paddle1")); if(player1 != NULL) { sf::Rect<float> p1BB = player1->GetBoundingRect(); if(p1BB.Intersects(GetBoundingRect())) { _angle = 360.0f - (_angle - 180.0f); if(_angle > 360.0f) _angle -= 360.0f; moveByY = -moveByY; // Make sure ball isn't inside paddle if(GetBoundingRect().Bottom > player1->GetBoundingRect().Top) { SetPosition(GetPosition().x,player1->GetBoundingRect().Top - GetWidth()/2 -1 ); } // Now add "English" based on the players velocity. float playerVelocity = player1->GetVelocity(); if(playerVelocity < 0) { // moving left _angle -= 20.0f; if(_angle < 0 ) _angle = 360.0f - _angle; } else if(playerVelocity > 0) { _angle += 20.0f; if(_angle > 360.0f) _angle = _angle - 360.0f; } _velocity += 5.0f; } if(GetPosition().y - GetHeight()/2 <= 0) { _angle = 180 - _angle; moveByY = -moveByY; } if(GetPosition().y + GetHeight()/2 + moveByY >= Game::SCREEN_HEIGHT) { // move to middle of the screen for now and randomize angle GetSprite().SetPosition(Game::SCREEN_WIDTH/2, Game::SCREEN_HEIGHT/2); _angle = (float)sf::Randomizer::Random(0,360); _velocity = 220.0f; _elapsedTimeSinceStart = 0.0f; } GetSprite().Move(moveByX,moveByY); } } float GameBall::LinearVelocityX(float angle) { angle -= 90; if (angle < 0) angle = 360 + angle; return (float)std::cos( angle * ( 3.1415926 / 180.0f )); } float GameBall::LinearVelocityY(float angle) { angle -= 90; if (angle < 0) angle = 360 + angle; return (float)std::sin( angle * ( 3.1415926 / 180.0f )); }

Click here to download GameBall.cpp

 

 

Yeah, it’s a bit of a monster at first, but don’t worry, it’s not really all that complex.  Lets start at the top with GameBall’s constructor.  First of all we initialize _velocity to 230 and _elapsedTimeSinceStart to 0.  _velocity represents the speed that ball is going to move.  A value of 230 means the ball will move at 230 pixels/second.  You can change the game greatly by tweaking this setting.  _elapsedTimeSinceStart represents that mount of time ( in seconds ) since the game gameball was created.  It’s use will make sense shortly.

 

 

 

Next up is our Update() method, which we will return to later.  First lets take a look at LinearVelocityX and LinearVelocityY methods.  Each of these methods returns a float denoting how far to move in a given direction given an angle.  Remember earlier when I said that explaining the math was well beyond the scope of this tutorial, well, this is one such example!  Essentially it all comes down to Pythagoreans theorem.  Do you remember back in school when you were taught SohCahToa? Well, now is the time to apply that knowledge! 

 

 

 

Essentially the angle –= 90 is make it so in our coordinate system 0 is up instead of to the right.  The next if statement makes sure that subtracting 90 from our angle didn’t result in a value like –20 but instead gives us 340 ( 360 degrees – 20 degrees ) for our angle.  Now for the math I simply cannot explain.  To get the direction of movement in the X direction, you simply take the cosine of the angle.  The catch is, you take the cosine of the angle in radians.  Radians can basically be thought of as segments of a circle, where an entire circle represents 2 times pi in radians ( or 6.28 rounded), therefore a single radian is 57.3 degrees ( rounded again ). To convert from degrees to radians, we multiple our angle by pi over 180 degrees.  Obviously in my example, I rounded off pi rather significantly.  To calculate movement by a certain angle in the y direction, you do exactly the same thing, but using Sine instead of the Cosine.

 

 

Optional information

 

Sohcah... what?

SOHCAHTOA ( pronounced so cah toe ah ) is one of those word devices that help you remember things. Given that I was introduced to it almost 20 years ago, I would say it works.

It represents how to figure out sine, cosine and tangent ( sin, cos, tan ) using a triangle.

SOH = sine = opposite over the hypotenuse
CAH = cosine = adjacent over the hypotenuse
TOA = tangent = opposite over the adjacent

If you have never encountered it before, it is a very handy mnemonic.

 

 

 

Alright, now that we’ve covered that, lets jump into Update().  The very first thing we do is update _elapsedTimeSinceStart with the elapsedTime that is passed into Update().  Remember that elapsed time is the amount of time since SFML last updated, so it will be a very small number representing a fraction of a second.  Next we check that _elapsedTimeSinceStart has reached 3 seconds and if it hasn’t we stop updating until it has.  This is so the game doesn’t instantly start the ball moving right when the game starts.  If you wanted you could add a count down like “Starting in 3… 2… 1…”, I leave that as an exercise for the reader. Smile

 

 

So once _elapsedTimeSinceStart is greater than 3 seconds, we continue through our update and figure out the amount we want the ball to move.  This is where our velocity comes in, along with the elapsedTime. Say for example elapsed time is 1/5th a second ( 0.2 ) and given that our _velocity is 230 pixels per second, multiplying _velocity by 0.2 will give up 46 pixels, meaning that to move 230 pixels in a second, in this update we want to move 46 pixels.  Like with the paddle, this keeps our speed the same regardless to the speed of the computer we are running on.  Generally though elapsed time will be much smaller than 0.2 and Update() will be called many more times a second than 5!

 

 

So now that we have the amount to move to maintain our _velocity, when need to determine where we want to move.  This is where the results of LinearVelocityX/LinearVelocityY come in.  By multiplying the result by our current velocity, we now know how much we want to move in the X and Y directory this frame.

 

 

Before we actually move the ball, we check to see if our movement is going to cause a collision of some kind.  Remember, since we set our coordinates to be relative to the sprites center as opposed to the top left, we need to add half the sprites width and height to any checks.  First we check to see if we hit the left or right side of the screen.  For reasons I can’t really explain, instead of making my Pong clone left and right like everyone else, I made Pang vertical.  Other than altering the math slightly, it really has no effect. 

 

 

If our sprite position was going to go off the side of the screen on either the left or right, we ricochet off the side by inverting the angle.  For example, if the sprite was going to hit the right side of the screen at 90 degrees, it will ricochet out at 270 degrees.  That said, if the sprite did in fact come in to the side at near to a right angle, that is going to result in the ball bouncing back out at nearly a straight line thus boring the hell out of the player as their ball slowly ricochet’s left and right over and over.  Therefore if the ball hits either side at +- 10 degrees to straight on, we add 20 degrees to the resulting angle.  This has an interesting side effect in that the ball can actually bounce back at the player who hit it, which is actually kind of fun in my opinion, but then I am a big table hockey fan! Finally we flip the sign on the x move amount, so if we were going left ( – ) we are now going right ( + ) and vice versa.

 

 

 

Next we introduce a new C++ concept, the dynamic_cast.  Truth told, in this case it is overkill but I wanted to demonstrate the usage.  We call Get() on our GameObjectManager looking for a VisibleGameObject named “Paddle1”.  If dynamic_cast<> is called on an object that isn’t capable of being casted to the type it’s being asked to cast to, it will return NULL, otherwise it will return a pointer to our game PlayerPaddle object.  Since Get() returns a VisibleGameObject pointer, the cast is required if we want to use any of the methods that are specific to the derived PlayerPaddle class.  Again, this step really wasn’t required in Pang as we never actually used any methods that are specific to PlayerPaddle, but it is a very valuable thing to learn.

 

 

 

Optional information

 

Casting in C++

In C++, you type cast an object if you want it to be treated as an object a different type.
 

When it comes to classes, a C++ object can be cast to either it's own type, or any other type it inherits. As the example in our code shows, since PlayerPaddle is derived from VisibleGameObject, it can be pointed to by a VisibleGameObject pointer. However, when it is pointed to by a VisibleGameObject, that pointer has no concept of an abilities beyond those of it's pointer type. Therefore to let it know that the VisibleGameObject pointer is actually pointing to a PlayerPaddle pointer, we type cast it.


Now you might be asking yourself, what happens if you cast an object to something it isn't? That’s a very good question with a very simple answer, with a standard cast, you just created a nasty bug in your program! This is why a number of different casts types exist.


First is the standard C style cast. int i = (int)someValue; is an example of C style cast. These are the most common types of casts, and potentially the most dangerous. Simply put, the compiler just assumes that what you are doing is perfectly logical. This means you can cast to types that aren't compatible. Even worse, you can actually cast a const object to a non-const object, generally another no no.


To rectify this potential hazard, C++ implemented a number of other cast styles. The one you saw in the example was the dynamic_cast<>. This handy cast actually checks to see if the cast is legit, and if it is not, it returns NULL. You may think that sounds wonderful, but there are a few catches. First off, dynamic_cast depends on RTTI being enabled to even work, which generally won't be a problem under most scenarios. Next, it is slower, which often can be a big problem! Finally, dynamic_cast can only be used on object pointers or references.


The next most common cast is the static_cast<>. It performs no runtime checks like the dynamic_cast so it is faster, but also it will allow an invalid cast, so it is not as safe. It however can be used to do casts that dynamic_cast<> cannot, like casting an enum to an int.


Next is const_cast<> when can be used to change the "constness" of a variable. Unless you have a very good reason, you generally shouldn't de-const anything! It is the only cast capable of doing this, except of course traditional C style casts.

Finally there is the reinterpert_cast<> when essentially allows you to cast things to things they simply aren't. For example, if you wanted to turn a pointer into an int, then later on back to a pointer you could use a reinterpert_cast<>. Unless you have a really really good excuse, and "look at this, isn't this cool!" is not a good excuse!, you really shouldn't use reinterpret_cast<>.

You can basically think of regular casts as capable of all of these different casts abilities, with all of their downsides too!



Now that we have a pointer to the player's paddle, we make sure the cast didn't fail and return NULL. If it did return NULL something really bad happened and the Paddle wasn't found. If this actually occurred in a more complex program we would do something much more severe than just skip updating!

 

 

Now we want to check if the GameBall is about to collide with the player’s paddle.  We do this by getting the bounding box ( a sfml rect ) of the PlayerPaddle and testing if it intersects the GameBall’s bounding rectangle.  There is an obvious flaw here, but I can live with it in this example.  Our ball is a circle, not a rectangle, so it is possible if the ball was on a certain angle it could report a collision when in fact the corner’s didn’t actually hit.  To correct this we would have to check the rect against a bounding circle.  This wiki entry has more details on implementing a circle collision test if you are interested.

 

 

Once we determine that a collision occurred, we need to decide how the ball will ricochet.  This process is very similar to when the ball hit the sides of the screen.  Once we calculate the angle the ball will bounce out at we invert the Y direction.   If by some fluke the ball actually managed to be inside the bounds of the paddle, we now make sure it is moved above the paddle instead.  This also handles the situation where the ball might collide with the side instead of the top of the paddle.  It would be more elegant ( and complex ) to check for collision against the top and both sides instead and perform a different result depending on direction.

 

 

Next we add “English” to the ball.  For those unfamiliar with the term, you can apply “English” to the cue ball in pool based on how you hit it, resulting in certain amount of “spin” on collision.  We are doing something similar based on how the player is moving at the time of the collision.  If the player is moving to the left ( negative velocity ) we subtract 20 degrees from the ricochet angle, while if the player is moving right ( positive velocity ) we add 20 degrees to the ricochet angle.  This step is completely optional, but adds a little variety to the game and gives the player a bit more control over how the ball rebounds.  Arkanoids uses a similar system I believe.  Finally we increase the velocity by 5 ( pixels / second ) each time the player hits the ball, again just to make things a bit more interesting.  In testing I had this set to 50 instead, which made for extremely interesting games!

 

 

Now that we have handled a collision with the players paddle, we need to handle the ball hitting the top and bottom of the screen.  For now if the ball gets to the top of the screen ( 0 Y ), we simply bounce it back by inverting the angle and direction of the ball.  In the case however the ball get’s past the Players paddle, we move the ball back to the middle of the screen, restart the 3 second count down then send the ball back out at a random angle slowed back down to 220 pixels per second.  In the future, this is where we would have updated the games score.

 

 

Finally, after all the direction calculations, collision detections and angle corrections, we finally move the ball!  Now are game has collisions and movement, we are one step closer to a full game.  Here now, is Pang in action:

 

 

pang7

 

 

To download the entire project solution click here.

 

EDIT(5/14/2012): Download SFML 2.0RC project here. This is the above tutorial ported to SFML 2.0. Note, this was ported by a reader. The code will vary slightly from the tutorial above.



Back to Part Six Forward to Part Eight




blog comments powered by Disqus


Month List

Popular Comments