• entries
6
0
• views
8092

Capricious crafting of coding calamities

## Arkanong part 6: batching draw calls

Once again I must apologize for how long it took to post this - There are many things competing for my time so this journal doesn't always get the tender love and care it should. I've managed to find a few spare hours so I'm going to show you how I batched the draw calls for my game objects.

Up to this point every game object has been in charge of drawing itself - that is, each object has its own texture and draw() function which loads and then renders the sprite. The object manager loops over all the objects and calls Object->draw() for every one of them.
This approach is fine if your game doesn't have that many objects but ideally you should try to batch your draw calls because they are computationally expensive. Batching draw calls means that you only call draw() once for every texture rather than once for every object. So if you have 50 objects which use 2 textures (e.g. 25 blue balls and 25 red balls) you will only have to call draw() twice instead of 50.

Since we're only calling draw() once for every texture, we need a way to tell the renderer where the texture is to be applied (that is, what objects will be using this texture). Luckily SFML has a class which does exactly this: the vertex array

A vertex is a (graphical) point which has the following datamembers:
- a position (x and y coordinates)
- a color (I won't be using this since I'm drawing textures)
- a pair of texture coordinates (which determine the part of the texture you want to use)

Every game object consist of a number of these vertices - a triangle would have 3, a rectangle 4 (which is why it is commonly referred to as a 'quad'). So really they are just the "corners" of your object with a bit of extra information attached.
The texture coordinates in particular are handy because you can use them to make draw calls more efficient. Let's say that I put the "red ball" and "blue ball" textures in a single texture called "balls". By specifying the texture coordinates so that only the left or right half of the texture is drawn I can "cut out" a piece of the image and apply it to my game object. If I did this I would be able to render all 50 blue/red balls with only one draw call since they are drawing off the same texture. You can see an example of this technique in the link I provided above.

Even when you specify texture coordinates the renderer will not automatically know how to display the vertices. 4 points could be a 'filled in' rectangle or 4 lines or really just 4 single points - you might even draw the objects with a texture one minute and press a button later to display everything as lines to produce a 'wireframe' effect.

So in order to draw a texture using vertex arrays we will need to provide the following to the renderer:

- The texture (duh)
- The collection of points that use this texture
(i.e. all the corners that make up the objects that use this texture)
- The way that they should be drawn
(Quad, line, point respectively meaning 'filled in', 'wireframe' and 'single points'.
There are other so-called primitive types but for now just knowing about quads will suffice)

The textures are kept in a map inside a new managing class called TextureManager. If you're not familiar with the map datatype, it's a way to store key/value pairs similar to the way primary keys work for a database. The texture is associated with a unique string value (in this case, the name of the texture). If you'd like to know more check out the wikipedia articles on associative arrays and associative containers.

The game objects store their own vertices and a string that corresponds to one of the key values from the texture map. For example: the texturemap might contain a texture with key-string "blue ball". Every game object that uses this texture would have the datamember textureName set to "blue ball". When the renderer want to draw the blue ball texture it loops over all the objects and collects the vertices that belong to objects with textureName set to "blue ball".

In pseudocode, the steps taken to render all the textures look like this:
- Loop over the different textures - Create a vertex array to store the points of all the objects that use this texture - Loop over all the game objects - If object::textureName equals texture key-string: collect the vertices for this object and add them to the vertex array - end of game object loop - call draw() with the current texture and the vertex array- end of texture loop

Here's what that looks like in code. Though the syntax might be a little confusing it's really just following the steps I outlined above.
void TextureManager::drawAllTextures(std::vector& objectList){ std::map::const_iterator textureIterator = m_textures.begin(); //loop over all textures while (textureIterator != m_textures.end()) { //vertex array for the current texture sf::VertexArray* vertexArray = new sf::VertexArray(sf::Quads); //collect all vertices which use this texture std::vector::const_iterator objectIterator = objectList.begin(); while (objectIterator != objectList.end()) { //if the object uses this texture //retrieve object's tranformed vertices into vertexArray if ((*objectIterator)->getTextureName() == textureIterator->first) { (*objectIterator)->getTransformedVertices(vertexArray); } ++objectIterator; } //draw the current texture and move on drawTexture(textureIterator->first, vertexArray); ++textureIterator; }}
The method 'getTransformedVertices()' retrieves the (transformed) points that make up the object in question. If you don't understand what the word 'tranformed' means it's a way of 'positioning' the object in a certain place.
The object knows its own points in a local frame of reference (my toes are 1.7 meters below my head and 0 meters in front of my nose) - transforming these points places them in the 'global' space of the game (my toes are 10 meters away from the nearest crosswalk and 100 meters below the top of that building).

It's outside of the scope of this article to explain this properly so if you don't understand what I'm talking about I strongly recommend that you learn more about the use of matrixes in game development. You could start here. The benefit of working this way is that I don't have to recalculate all the points every time an object moves. I simply have to adjust the transformation matrix which 'places' the vertices in the game world. The same goes for scaling and rotation so it's a pretty neat trick to have in your toolbox.

Once all the points for the current texture have been collected, the texture is rendered in the following piece of code:void TextureManager::drawTexture(std::string name, sf::VertexArray* vertices){ sf::Texture* texture = m_textures.at(name); if (texture != 0) { sf::RenderStates renderstate; renderstate.texture = texture; m_pRenderWindow->draw(*vertices, renderstate); }}
First I attempt to retrieve the texture to verify that it exists (drawing textures that don't exist is not a good idea). Then I create a renderstate which will hold the rendering information. A renderstate is just a way to pass along information to the renderer - you might want to manipulate blending modes for example. Since I'm only interested in drawing rudimentary textures for now I simply set its texture datamember. Finally I pass the vertex array and renderstate to SFML which will take care of the drawing for me.

Ok guys, that's it for now. Keep in mind that I am a relatively inexperienced game programmer which means that all the code in this journal should be taken with a grain of salt. If you think you know a better way to batch draw calls - you probably do and I'd love to hear about it in the comments section!

## Arkanong part 5: Hexagons! (collision detection, again)

To make the game a bit more interesting we're going to add a few hexagonal barriers in the playing field.
This object is different from the paddle and balls in that it doesn't have a 'regular' shape. This means that using a rectangular
or circular hitregion will not cover the object properly.
Now, I could fudge the dimensions of the hexagon a little and consider it a circle for the purposes of collision detection, but where's the fun in that?

Luckily I found a neat little algorithm online which detects collisions between a circle and a line segment. Here's how it works:

- make a vector which represents the line segment.
We will call this vector d and form it by subtracting the start of the line segment from the end of the line segment.
linevector = endPoint - startPoint
=> d = l - e

- make a vector from the center of the circle to the start of the line segment.
We'll call it vector f and form it by subtracting the circle's position from the start of the ray. We'll use this later on to simplify the equation.

centerToStartPoint = start - center
=> f = e - c

- combine the two following equations:
a) p = e + t*d
This equation locates the collision point on the line segment by starting from the first point (e) and 'moving' along the vector (d)
by a certain percentage (t).
In other words, if you have a line that starts at (4,0) and ends at (10,0), and a collision occurs in the middle of the line (= 50%)
t will be 0.5 and p will be equal to (9,0).

b) (x - h)[sup]2[/sup] + (y - k)[sup]2[/sup] = r[sup]2[/sup]
This equation uses the pythagorean theorem to represent circle 'touching' the collision point. (h,k) is the center of the circle
it basically means that if you take the distance from the collision point to the centre of the circle it will be equal to the radius (r) of the circle.

Now we're going to combine these two equations to end up with our actual collision detection formula:
1 ) first expand equation number two. You'll end up with the following:
x[sup]2[/sup] - 2xh + h[sup]2[/sup] + y[sup]2[/sup] - 2yk + k[sup]2[/sup] - r[sup]2[/sup] = 0

2) now, in the first equation the points p, e and vector d consists of two components (x and y).
So we 'split' it into:
x = e[sub]x[/sub] + td[sub]x[/sub]
y = e[sub]y[/sub] + td[sub]y[/sub]

Then you substitute the x and y variables in step 1 with these ones. You'll end up with this:
( e[sub]x[/sub] + td[sub]x[/sub] )[sup]2[/sup] - 2( e[sub]x[/sub] + td[sub]x[/sub] )h + h[sup]2[/sup] + ( e[sub]y[/sub] + td[sub]y[/sub] )[sup]2[/sup] - 2( e[sub]y[/sub] + td[sub]y[/sub] )k + k[sup]2[/sup] - r[sup]2[/sup] = 0

expand => e[sub]x[/sub][sup]2[/sup] + 2e[sub]x[/sub]td[sub]x[/sub] + t[sup]2[/sup]d[sub]x[/sub][sup]2[/sup] - 2e[sub]x[/sub]h - 2td[sub]x[/sub]h + h[sup]2[/sup] + e[sub]y[/sub][sup]2[/sup] + 2e[sub]y[/sub]td[sub]y[/sub] + t[sup]2[/sup]d[sub]y[/sub][sup]2[/sup] - 2e[sub]y[/sub]k - 2td[sub]y[/sub]k + k[sup]2[/sup] - r[sup]2[/sup] = 0

group => t[sup]2[/sup]( d[sub]x[/sub][sup]2[/sup] + d[sub]y[/sub][sup]2[/sup] ) + 2t( e[sub]x[/sub]d[sub]x[/sub] + e[sub]y[/sub]d[sub]y[/sub] - d[sub]x[/sub]h - d[sub]y[/sub]k ) + e[sub]x[/sub][sup]2[/sup] + e[sub]y[/sub][sup]2[/sup] - 2e[sub]x[/sub]h - 2e[sub]y[/sub]k + h[sup]2[/sup] + k[sup]2[/sup] - r[sup]2[/sup] = 0

write as dotproducts, where d = line segment, c = centre of circle
=> t[sup]2[/sup]( d DOT d ) + 2t( e DOT d - d DOT c ) + _e DOT _e - 2( _e DOT_c ) + _c DOT _c - r[sup]2[/sup] = 0
=> t[sup]2[/sup]( d DOT d ) + 2t( d DOT ( e - c ) ) + ( e - c ) DOT ( e - c ) - r[sup]2[/sup] = 0

remember that e is the starting point of the line segment, and that we have a vector f which is the vector from the
start of the line to the center of the circle (f = e - c) -> we can use this to further simplify the equation
=> t[sup]2[/sup]( d DOT d ) + 2t( d DOT f ) + f DOT f - r[sup]2[/sup] = 0

After we've simplified the formula as much as possible we ended up with the following:

t[sup]2[/sup] * (d DOT d) + 2t*( f DOT d ) + ( f DOT f - r[sup]2[/sup] ) = 0

where:
f is the vector from the centre of the circle to the starting point of the ray
d is the vector representing the line itself
r is the radius of the circle
t is the 'percentage' of the line at which is the collision occurs

If you look closely, you will see that this equation is of the form

where:
a = d DOT D
b = 2 * f DOT d
c = f DOT f - r^2

This is a quadratic equation, which means we can solve it by using the quadratic formula!sf::Vector2f lineVector = p_rayEnd - p_rayStart; sf::Vector2f centreToStartOfRay = p_rayStart - ballPosition; float a = (lineVector.x * lineVector.x) + (lineVector.y * lineVector.y); float b = (2 * ((centreToStartOfRay.x * lineVector.x) + (centreToStartOfRay.y * lineVector.y))); float c = ((centreToStartOfRay.x * centreToStartOfRay.x) + (centreToStartOfRay.y * centreToStartOfRay.y)) - (ballRadius * ballRadius); //calculate discriminant to determine if collision has taken place float discriminant = (b*b) - 4 * a*c;
If the discriminant if greater than or equal to zero, then the circle has collided with the ray at some point. Calculate the solution
to the equation by using the quadratic formula and you will end up with the 'percentage' values which in turn can be used to find the coordinates of the collision point.

Now, these calculations consider the line as being infinitely long so we need to check if these percentages are between 0 and 1 (= 0% to 100% of the line). If they are greater or lesser than these values then the collision was either before or after the two points which make up the line segment.
//one or more collision points with entire ray //(meaning also past the actual corners of the hexagon) if (discriminant >= 0) { //determine t1 & t2 //(this is the 'percentage' along the ray at which the collision point can be found) discriminant = sqrt(discriminant); float t1 = (-b - discriminant) / (2 * a); float t2 = (-b + discriminant) / (2 * a); //if the 'percentages' are between 0 and 1 there are //one or more collisions points within the actual line segment //(meaning contained between the two corners) if ((t1 >= 0 && t1 <= 1) || (t2 >= 0 && t2 <= 1)) {
Once we know the coordinates of the collision point we simply perform the same response as in the previous posts - project the velocity of the ball onto a collision normal unit vector and a unit tangent vector perpendicular to it, then 'flip' the normal part and recombine them into the new velocity of the ball.

Now that we know how to bounce a ball of a line all that needs doing is doing the check for every side of the hexagon - that's what this piece of code does. It seems to work quite well, except when the ball hits the exact corner of two line segments, I'll take care of that bug a bit later.//loop over the six corners, perform circle to ray collision checks using the corners //as start and end point for the rays for (int i = 0; i < 6; ++i) { //form ray between this corner and next if (i != 5) performCircleToRayCollisionChecks(p_ball, hexagonCorners, hexagonCorners[i + 1]); else //last corner connects to first corner performCircleToRayCollisionChecks(p_ball, hexagonCorners, hexagonCorners[0]); }
If you feel you need to brush up on your algebra (I certainly did while making this) you can do that for free at khanacademy.org - I highly recommend it!

[sharedmedia=gallery:images:4786]

You can take a look at the code at https://github.com/bytechomper/arkanong
All credit goes to the following stack overflow post: http://stackoverflow.com/questions/1073336/circle-line-collision-detection

## Arkanong part 4: bouncing balls off each other

Well, it certainly has been a while since I updated this journal!
Now that my school projects have been handed in and all my exams are behind me I will hopefully be able to spend more time developing arkanong.
Let's take a look at how we can make the gameballs bounce off each other.

There are three important things to keep in mind about our game physics:

a) all balls have the same mass, meaning they all "weigh" the same.

b) all collisions are perfectly elastic, so no energy will be converted to another form.
This means that after two balls bounce off each other they will retain their kinetic energy (in real life part of it would be converted to sound waves, heat, etcetera).

c) all movements/collisions are frictionless
In a real pool game the friction of the felt would cause the ball to come to a halt after a while. We want our gameballs to keep moving indefinitely so
our physics will be completely frictionless

These three properties enable us to greatly simplify our physics formulas, as you will see further on.

The sphere to sphere collision detection/response consists of the following steps:

1) detect a collision between two balls.

This step is pretty easy; we simply check if the distance between two balls is less than (or equal to) the sum of their radiuses.
The meaning of collision normal will be explained in step two, for now just think of it as the line that connects the two centres of the ball.
The distance between the two balls is calculated using the pythagorean theorem.float radius1 = ball1->getSprite().getLocalBounds().width / 2;float radius2 = ball2->getSprite().getLocalBounds().width / 2;//minimumDistance for collision = radius1 + radius2float minimumDistance = radius1 + radius2;//amount of pixels between the centresfloat actualDistance = sqrt((collisionNormal.x * collisionNormal.x) + (collisionNormal.y * collisionNormal.y));//if the distance <= radius1+radius2 a collision has occuredif (actualDistance <= minimumDistance){ [collision response code goes here]}
2) determine the collision normal.
The collision normal is the unit vector that points in the direction of the collision (i.e. the vector that points toward the place where the two balls are touching). To calculate this vector we simply connect the centres of the two balls. We already did this in the first step to determine if the gameballs are touching. All that remains to be done is to turn it into a unit vector (that is: make the vector have a magnitude of 1).//normalize collision vectorcollisionNormal.x /= actualDistance;collisionNormal.y /= actualDistance;
3) determine the unit tangent.
Besides the collision normal we will also need the vector which is perpendicular to it, a.k.a. the tangent vector.
Because we already have the collision normal it is very easy to calculate the unit tangent: we simply "flip" the collision normal and make y negative.sf::Vector2f unitTangent(-collisionNormal.y, collisionNormal.x);
try this out on a piece of paper, it really works!

4) project the velocity of the ball onto the unit tangent & unit normal vectors.
Projecting the velocity vector on the tangent & normal will decompose it into two components. You could think of them as x and y - in reality it's the part that is affected by the collision (= normal component), and the part that isn't (= tangent component).
Think of a ball that is moving horizontally from left to right. When another ball grazes it on the bottom it will no longer be moving horizontally, but it will still be moving from left to right. Because the tangent and normal are unit vectors, we can project the velocity vectors onto them by calculating the dot product. (this is vector math)//----------project velocities onto unit normal & unit tangent vector-----//this will decompose the velocities into a normal and a tangent componentsf::Vector2f ball1Velocity = ball1->getVelocities();double ball1NormalComponent = (collisionNormal.x * ball1Velocity.x) + (collisionNormal.y * ball1Velocity.y);double ball1TangentComponent = (unitTangent.x * ball1Velocity.x) + (unitTangent.y * ball1Velocity.y);sf::Vector2f ball2Velocity = ball2->getVelocities();double ball2NormalComponent = (collisionNormal.x * ball2Velocity.x) + (collisionNormal.y * ball2Velocity.y);double ball2TangentComponent = (unitTangent.x * ball2Velocity.x) + (unitTangent.y * ball2Velocity.y);
5) Adjust the normal components of the ball velocities.

As stated in the previous step, the velocity has a component that isn't affected (= tangent) and a component that is (= collision normal). In this step, we adjust the part of the velocity vector that will react to the collision, in other words: this is where the collision response is actually occuring.

In Newtonian physics the adjusted normal velocities of two colliding spheres is calculated with the following formulas:

where V is the new normal velocity after the collision, U is the old normal velocity before the collision, and M is the mass of the ball.
Remember when I said that all balls "weigh" the same? This is where that knowledge comes in handy for reducing the complexity of the physics. Substitute m1 and m2 with the number 1 (or any number for that matter) and see what happens:

V1 = (U1 * 0) + (2/2) * U2
V1 = 0 + 1 * U2
V1 = U2

you can just switch the two normal components!//-----------------switch normal components---------------//mass is the same for all balls, so the collision response function can be greatly simplified.//we simply switch the components.double newBall1Component = ball2NormalComponent;double newBall2Component = ball1NormalComponent;
6) multiply the components with the two unit vectors.
By calculating the two components (normal & tangent) we now have the amount of pixels by which the ball should shift along these directions
(you could think of this as "5 pixels

and 10 pixels [up]", but again it's really the part of the velocity that is and isn't affected by the collision).

These two numbers correspond to the magnitude of the new velocity vectors ("5 pixels and 10 pixels"), but we still need convert them into actual vectors via multiplication. (that is, give them a direction or make it "5 pixels to the left and 10 pixels up"). Actually we only need the normal part, because the tangent component is unaffected by the collision. I included the calculation anyway.//---------------convert components back into vectors----------sf::Vector2f newBall1NormalVector(0.0f, 0.0f);newBall1NormalVector.x = newBall1Component * collisionNormal.x;newBall1NormalVector.y = newBall1Component * collisionNormal.y;//these actually remain the same as before but we leave the calculation in for claritysf::Vector2f newBall1TangentVector(0.0f, 0.0f);newBall1TangentVector.x = ball1TangentComponent * unitTangent.x;newBall1TangentVector.y = ball1TangentComponent * unitTangent.y;sf::Vector2f newBall2NormalVector(0.0f, 0.0f);newBall2NormalVector.x = newBall2Component * collisionNormal.x;newBall2NormalVector.y = newBall2Component * collisionNormal.y;//these actually remain the same as before but we leave the calculation in for claritysf::Vector2f newBall2TangentVector(0.0f, 0.0f);newBall2TangentVector.x = ball2TangentComponent * unitTangent.x;newBall2TangentVector.y = ball2TangentComponent * unitTangent.y;
7) add the components back together.
Earlier we decomposed the velocity of the balls into two components (normal & tangent), now that we've recalculated these two components we simply add them back together to get the new velocity for the ball. ('5 to the left and 10 up' becomes '14 diagonally')sf::Vector2f newBall1Velocity = newBall1TangentVector + newBall1NormalVector;sf::Vector2f newBall2Velocity = newBall2TangentVector + newBall2NormalVector;
Now, because I store angles rather than velocity vectors I do some additional trigonometry on these but if you work with vectors in your code then you're as good as done, all that needs doing is setting the new directions for the balls.

I realize this is probably confusing as hell if you're not familiar with physics or vector mathematics. Check out these links if you want to get a better grasp of what's going on here - sooner or later every game programmer is going to come into contact with these things so it's better to start early.

http://en.wikipedia.org/wiki/Elastic_collision
http://www.vobarian.com/collisions/2dcollisions2.pdf
http://nicoschertler.wordpress.com/2013/10/07/elastic-collision-of-circles-and-spheres/

As always you can check out the code for the game on github.
The code shown in this article can be found in source -> ObjectManager.cpp -> performCircleToCirlceCollisionChecks()

## Arkanong part 3: Vector reflector

Yesterday I posted a few snippets of code to show how game objects can be kept within the confines of a client window. Because my game ball stores the direction it is heading in as an angle, the quick and dirty way to bounce it back was to add or subtract 90?.
This only works if you can guarantee that the ball will be coming in at one of four angles (45?, 135?, 225?, 315?) - otherwise the new angle doesn't look natural. This method of adding or subtracting 90? is essentially a way of doing vector reflection without doing any actual calculations (not counting the adding or subtracting of course).
That's nice if you're going for that authentic retro pong feeling, but I'd like the game to feel a bit more like pool. Before you read this code I recommend you check out understanding vector reflections visually to get a grasp of what's going on and stack exchange to see the formula I'll be using.

A 'unit normal vector' is a vector perpendicular to a surface (= normal) which has been normalized (= having a magnitude of one).
Thus, finding the normals of the four sides of the window is easy:
left side: (1, 0)
right side: (-1, 0)
top side: (1, 0) //remember that y grows downwards
bottom side: (-1, 0)

If all of this seems like gibberish to you I strongly recommend you brush up on your maths. It may seem like a chore, but a little mathematics can go a very long way. Vectors, matrices, trigonometry - all of this will pop up frequently in the course of developing a game. I used to hate doing math until I started programming, now I get to use it to bounce balls around on the screen which is much more rewarding than writing a number down on a piece of paper. check out khan academy or udacity if you're looking to learn.

Ok, so this is what the new collision response looks like: //bounce ball off sides if(collisionDetected) { object->getSprite().move(overshootCorrection); //correct overshoot sf::Vector2f ballVector = ((GameBall*)object)->getVelocities(); float dotProduct = (ballVector.x * collisionNormal.x) + (ballVector.y * collisionNormal.y); sf::Vector2f reflectionVector = ballVector - 2*dotProduct*collisionNormal; //call arctan to get angle back from vector and convert to degrees float newAngle = std::atan2f(-1 * reflectionVector.y,reflectionVector.x) * (180.0f/3.1415926); //we will assume all spherical objects are gameballs (for now) ((GameBall*)object)->setAngle(newAngle); }
Since I am working with an angle and speed (and the formula reflects a vector) the first thing I do is call getVelocities() to get the movement vector. GetVelocities() is the function I posted yesterday which 'divides' the speed into and offset on the x and y axis (i.e. a vector).
The next step is to feed the data into the reflection formula - this should make sense if you clicked the links in the beginning of this post.
Now that we have the vector representing the new direction we use the function atan (= arctan = trigonometry!) to get the angle of the vector in radians. I find degrees much easier to work with so I convert it by multiplying with [color=#b4b4b4]([/color][color=#b5cea8]180.0f[/color][color=#b4b4b4]/[/color][color=#b5cea8]3.1415926[/color][color=#b4b4b4]). [/color]

Cool, now I can bounce the ball at any angle I damn well please - let's put in some code to play around with the reflection.void ObjectManager::spawnBall(sf::Vector2f& destination, bool playerBall){ sf::Vector2f source; if(playerBall) { //ball spawns at player location source = m_objects.find("PlayerPaddle")->second->getPosition(); } //TODO: implement spawning ball from enemy paddle when !playerBall //get the vector from player to destination sf::Vector2f vector = destination - source; //determine angle of vector and convert to degrees float angle = std::atan2f(-1*vector.y,vector.x) * (180.0f/3.1415926); //convert to degrees GameBall* newBall = new GameBall(); newBall->setPosition(source.x, source.y - 30); newBall->setAngle(angle); //Find a unique name for the new ball bool uniqueNameFound = false; std::stringstream ss; if(playerBall) ss << "PlayerBall"; else ss << "EnemyBall"; int ballNumber = m_objects.size(); while(!uniqueNameFound) { ss << ballNumber; std::string stringballNumber = ss.str(); if(this->getObject(stringballNumber) == 0) //check if name is free { this->addObject(stringballNumber, newBall); uniqueNameFound = true; } else //name was already taken -> make the number higher and try again { ++ballNumber; } }}
This function spawns a ball at the position of the paddle and make it head towards the destination. It is called in Game::Tick() when the mousebutton is released (note that I didn't use mousePressed as this would keep making balls while you're holding down the button. You'd have to have some killer timing to spawn a single ball).

in Game::Tick()switch(m_currentState) { case IsRunning: [edited for brevity] if(currentEvent.type == sf::Event::MouseButtonReleased) { sf::Vector2f mouseclick; mouseclick.x = sf::Mouse::getPosition(m_mainWindow).x; //x relative to window mouseclick.y = sf::Mouse::getPosition(m_mainWindow).y; //y relative to window m_pObjectManager->spawnBall(mouseclick, true); //spawn player ball }

Now we can bounce balls to our heart's content! As always you can check out the project here
The code pertaining to the game is in the directory "pong_rework". You will find the collision detection/response code in the ObjectManager class.

## Arkanong part 2: basic collision detection

When I last posted about my SFML/C++ pong clone (now dubbed 'arkanong' because I suck at namegiving) I ended up with a non-moving paddle and ball. While that's all well and good the game won't be very exciting if we can't bounce the ball around, so that's exactly what I put in next.

Here's how it works:

in function Game::initialize()sf::Clock timer; while(!IsExiting()) { Tick(timer.restart()); }
When the game is loaded a clock object is made which will keep track of the amount of time that has passed since we last called Tick().

The Clock::restart function resets the clock and returns the elapsed time since the last time we called Clock::restart().

Tick() is the fuction that is responsable for updating the position of objects and drawing them. We will call tick() for every new frame that we draw to the screen - in other words: the clock is storing the amount of time that has passed since we drew the last frame.

You may be wondering why we pass the frame time to Tick() . Suppose we didn't supply this parameter and just moved the paddle by 3 pixels every time we draw a frame. Now we give the game to our friends Peter and Paul for playtesting.

Peter has a monster PC and runs the game at 70 frames per second

=> 70 Tick() calls per second

=> paddle moves at 70 * 3 pixels = 210 pixels per second

Paul's setup is more modest and runs the game at 30 frames per second

=> 30 Tick() calls per second

= paddle moves at 30 * 3 pixels = 90 pixels per second

If you've ever played an old DOS game on a modern PC and the game looked like it was being fast-forwarded this is what's going. The solution is to fix your timestep

in function Game::Tick(sf::Time& frameTime)case IsRunning: m_mainWindow.clear(sf::Color(0,0,0)); m_pObjectManager->updateAll(frameTime); m_pObjectManager->drawAll(m_mainWindow); m_mainWindow.display();
Tick doesn't really do anything special except check the gamestate and pass the frameTime to the objectManager.

The objectManager does exactly what is says on the tin: it manages game objects - updating, drawing & performing collision detection happens in this class.

Inside ObjectManager::updateAll(sf::Time& frameTime)void ObjectManager::updateAll(sf::Time& frameTime){ std::map::const_iterator it = m_objects.begin(); while(it != m_objects.end()) { //perform collision checks if(it->second->isInitialized()) { if(it->second->getBoundingType() == VisibleObject::boundingType::Rectangle ||it->second->getBoundingType() == VisibleObject::boundingType::Undeclared) performRectToBorderCollisionChecks(it->second); if(it->second->getBoundingType() == VisibleObject::boundingType::Circle) performCircleToBorderCollisionChecks(it->second); //update positions float remainingTime = frameTime.asSeconds(); float timeStep = 0.00001; while(remainingTime >= timeStep) { it->second->update(timeStep); remainingTime -= timeStep; } } ++it; }}
The ObjectManager is where most of the magic (or horror, depending on your point of view) happens.
First we retrieve the bounding type of the object we're updating. The bounding type is an enumeration for the type of collision detection we want to perform.
If we're talking about a rectangle (= paddle) we do rectToBorder detection, in case of circles it's circleToBorder. Right now we're only checking the borders of the window for collisions, later we'll implement circleToRect collision detection (for bouncing the ball off the paddle) and CircleToCircle detection
(for bouncing balls off each other).

The final step in the updateAll function is to change the position of the object we're updating.
We're consuming the frameTime in steps of 0.00001. For an in depth explanation of how this works

function ObjectManager::performRectToBorderCollisionChecks(VisibleObject* object)void ObjectManager::performRectToBorderCollisionChecks(VisibleObject* object){ sf::Vector2f overshootCorrection; float halfWidth = object->getSprite().getLocalBounds().width / 2; float halfHeight = object->getSprite().getLocalBounds().height / 2; unsigned int windowRight = Game::getInstance()->getWindowDimensions().x; unsigned int windowTop = Game::getInstance()->getWindowDimensions().y; sf::Vector2f position = object->getSprite().getPosition(); float leftSideX = position.x - halfWidth; float rightSideX = position.x + halfWidth; float topSideY = position.y + halfHeight; float bottomSideY = position.y - halfHeight; if(leftSideX <= 0) //we hit the left side overshootCorrection.x = -1 * leftSideX; //reverse overshoot if(rightSideX >= windowRight) //we hit the right side overshootCorrection.x = windowRight - rightSideX; if(topSideY >= windowTop) //we hit the top of the window overshootCorrection.y = windowTop - topSideY; if(bottomSideY <= 0) //we hit the bottom of the window overshootCorrection.y = -1 * bottomSideY; object->getSprite().move(overshootCorrection);}
This is the simplest of the two collision detection algorithms.
"Overshoot" is the term I've chosen to label the amount by which the paddle has gone past the window.
We move the paddle back by that amount to ensure that it never goes past the edges.
If you're having trouble understanding this perhaps this illustration will help. (click to expand)

void ObjectManager::performCircleToBorderCollisionChecks(VisibleObject* object){ //we're going to assume the radius of the circle is the width of the sprite float radius = object->getSprite().getLocalBounds().width / 2; float angle = ((GameBall*)object)->getAngle(); sf::Vector2f position = object->getSprite().getPosition(); //get centre of circular object float margin = 0.5f; //pixel margin on collision detection radius += margin; sf::Vector2f overshootCorrection; unsigned int windowRight = Game::getInstance()->getWindowDimensions().x; unsigned int windowTop = Game::getInstance()->getWindowDimensions().y; float distanceToRight = windowRight - position.x; float distanceToBottom = windowTop - position.y; bool flipXDirection = false; bool flipYDirection = false; if(position.x < radius) //distanceToLeft == position.x { //we bounced on the left side flipXDirection = true; overshootCorrection.x += (radius - position.x); } if(distanceToRight < radius) { //we bounced on the right side flipXDirection = true; overshootCorrection.x -= (radius - distanceToRight); } if(position.y < radius) // distanceToBottom == position.y { //we bounced on the top flipYDirection = true; overshootCorrection.y += (radius - position.y); } if(distanceToBottom < radius) { //we bounced on the bottom flipYDirection = true; overshootCorrection.y -= (radius - distanceToBottom); } //bounce ball off sides if(flipXDirection || flipYDirection) { object->getSprite().move(overshootCorrection); //correct overshoot //determine quadrant of the angle at which the ball is hitting the sides bool q1 = angle <= 90; bool q2 = (angle > 90) && (angle <= 180); bool q3 = (angle > 180) && (angle <= 270); bool q4 = (angle > 270) && (angle <= 360); float newAngle = angle; if(flipXDirection) { if(q2 || q4) newAngle -= 90.0f; else newAngle += 90.0f; } else //flip Y { if(q1 || q3) newAngle -= 90.0f; else newAngle += 90.0f; } //limit degrees to range [0:360] if(newAngle < 0.0f) newAngle += 360.0f; if(newAngle > 360.0f) newAngle -= 360.0f; //we will assume all spherical objects are gameballs (for now) ((GameBall*)object)->setAngle(newAngle); }}
Bouncing the ball off the sides is a bit more complex. Not only do we correct the overshoot, but we have to reverse the X or Y direction of the ball too.
Since we're storing the movement of the ball as an angle and speed this means adding or subtracting 90?. We can know which operation to perform based on the quadrant that the angle falls in. You can see the four quadrant illustrated below, it comes down to dividing the 360? into four slices.
Working with angles may seem needlessly complex (and it is for the current version of the game) but it will
come in handy once I start doing stuff like bouncing balls off each other.
Check out these illustrations if you want to get a better picture of what's going on.

The final thing I want to show you is the way the angles and speed are used to move the ball around.sf::Vector2f GameBall::getVelocities() const{ sf::Vector2f result; double angleInRadians = m_angle * (3.1415926 / 180.0f); //converting degrees to radians //prevent rounding errors angleInRadians *= pow(10, 5); angleInRadians = ceil(angleInRadians); angleInRadians /= pow(10, 5); //get x and y components of velocity result.x = m_speed * std::cos(angleInRadians); result.y = -1 * (m_speed * std::sin(angleInRadians)); //y grows downwards return result;}
This code does the following things:
1) convert degrees to radians for use with std::cos and std::sin
2) raise the radian by power 5, round it and revert back to prevent floating point rounding errors
3) m_speed is the distance that the ball travels in pixels. This is easy to figure out if we're
moving down or right, since the movement vector would be (0, m_speed) and (m_speed,0) respectively.
But we're moving diagonally, therefore we need to figure out how many pixels x and y need to be in order for the distance of the vector to be equal to m_speed.
This is what cos and sin does - it calculates the ratio of m_speed we attribute to x and y. This comes down to trigonometry so brush up if this code seems weird to you (check out khan academy and udacity physics section - the links are in my previous post).
Note that y grows downwards, so if you want to go up you decrease the y coordinate. That's why I multiply with -1 to make sure the ball heads in the right direction.

You can find the project in its current state here. This will be subject to change as I add things so you might want to store a local copy. I'm always looking for cleaner code so if you have any suggestions I'd love to hear them.

## Arkanong part 1: Porting for the sake of sanity

Last year I decided that I had enough of my procrastrination (and several other bad habits) and began a process of self-reformation.
Part of this undertaking was the development of a small framework with which I could make a pong clone. The plan was to build the engine as an educational exercise, produce several small games and perhaps form a development partnership with somebody on this site down the road.

This is what I managed to put together:

It's pong meets breakout with several balls - touching the other player's balls (heh heh) will convert them to yours and vice versa. The paddle can move in eight directions up to a certain vertical limit and the bricks contain powerups (not yet implemented except a single "cheat key" which will cause the blue balls to move much faster).

This took a long time to make, as I have school and 'real world' projects competing for my time.
The real problem though, was that every single step of the way was new to me.
Drawing quads on screen, batching sprites, implementing physics for bouncing balls off each other, working with sound, writing code that generates textured quads because for some reason directX10 doesn't support drawing text on screen, etcetera.
Although the code works, the 'engine' and game quickly became a patchwork of messy amateur implementations. Check this out: (don't read the whole thing unless you're a masochist)void PongPrototype::performCollisionChecks(){ HitRegion* playerRegion = m_pHumanPlayer->getPaddle()->getHitRegion(); //HitRegion* enemyRegion = m_pEnemyPaddle->getHitRegion(); HitRegion* enemyRegion = m_pEnemyPlayer->getPaddle()->getHitRegion(); HitRegion* ballRegion = NULL; HitRegion* brickRegion = NULL; Ball* currentBall = NULL; // //check player collisions against level border and correct if necessary if(checkBorderCollision(playerRegion)) { correctPaddleOverschoot(m_pHumanPlayer->getPaddle()); } if(checkBorderCollision(enemyRegion)) { //correctPaddleOverschoot(m_pEnemyPaddle); correctPaddleOverschoot(m_pEnemyPlayer->getPaddle()); } //iterate over balls and perform collision checks std::vector::iterator it = m_balls.begin(); for(it; it != m_balls.end(); ++it) { currentBall = (*it); if(currentBall != NULL) //is there a ball to check? { ballRegion = (*it)->getHitRegion(); //check ball collision against border if(checkBorderCollision(ballRegion)) { correctBallovershoot(currentBall); bounceBallOffWall(currentBall); } // // //it's important that we check the ball against paddle (in checkRegionCollision) and not the other way around // //-> because of the way that the collision is calculated a larger hitregion checked against a // //smaller one will not return true. // //check ball collision against players if(m_pParentProject->checkRegionCollision(ballRegion, playerRegion)) //did ball hit player paddle? { bounceBallOffPaddle(currentBall, m_pHumanPlayer->getPaddle()); } if(m_pParentProject->checkRegionCollision(ballRegion, enemyRegion)) //did ball hit player paddle? { //bounceBallOffPaddle((*it), m_pEnemyPaddle); bounceBallOffPaddle(currentBall, m_pEnemyPlayer->getPaddle()); } // //iterate over bricks and perform checks //iterate over bricks and perform collision checks std::vector::iterator brickIt = m_bricks.begin(); for(brickIt; brickIt != m_bricks.end(); ++brickIt) { if((*brickIt) != NULL) //is there a brick to check? { brickRegion = (*brickIt)->getHitRegion(); if(m_pParentProject->checkRegionCollision(ballRegion, brickRegion)) { bounceBallOffBrick((*brickIt), currentBall); } } } //finally iterate over entire ball vector to perform collision checks with other balls std::vector::iterator it2 = m_balls.begin(); for(it2; it2 != m_balls.end(); ++it2) { //we've got to make sure that there is a ball to collide and that //we don't "collide" with ourself if((*it2 != NULL) && (it2 != it)) { HitRegion* otherBallRegion = (*it2)->getHitRegion(); //did the balls hit each other? if(m_pParentProject->checkRegionCollision(ballRegion, otherBallRegion)) bounceBallsOffEachOther(currentBall, *it2); } } } }}//this function moves the ball back a little if it's gone past the window edgesvoid PongPrototype::correctBallovershoot(Ball* p_ball){ floatRect region = p_ball->getHitRegion()->getRegion(); XMFLOAT2 offset; offset.x = 0.0f; offset.y = 0.0f; if(region.left <= 0) offset.x -= region.left; //negative number so we subtract if(region.right > SOLIPSIST.getWindowWidth()) offset.x -= (region.right - SOLIPSIST.getWindowWidth()); //hit top or bottom? adjust overshoot and flip y direction if(region.bottom <= 0) offset.y -= region.bottom; if(region.top > SOLIPSIST.getWindowHeight()) offset.y -= (region.top - SOLIPSIST.getWindowHeight()); //adjust overshoot p_ball->move(offset);}void PongPrototype::bounceBallsOffEachOther(Ball* p_ballOne, Ball* p_ballTwo){ //hitregion is a bit smaller than the actual ball so we add a margin int ballOneRadius = (p_ballOne->getHitRegion()->getRegionWidth() / 2) + 2; int ballTwoRadius = (p_ballTwo->getHitRegion()->getRegionWidth() / 2) + 2; //find out the normal to the collision plane (by normalizing the distance between the centerpoints //position = centerpoint XMFLOAT2 ballOnePosition = p_ballOne->getBitmap()->getPosition(); XMFLOAT2 ballTwoPosition = p_ballTwo->getBitmap()->getPosition(); XMFLOAT2 normalVector; normalVector.x = ballOnePosition.x - ballTwoPosition.x; normalVector.y = ballOnePosition.y - ballTwoPosition.y; //find out magnitude (=length) of directional vector by taking square root of squared components //(brush up on vector math if you don't understand this bit) float normalMagnitude = sqrt((normalVector.x * normalVector.x) + (normalVector.y * normalVector.y)); if(normalMagnitude <= (ballOneRadius + ballTwoRadius)) //collision? { //move balls back a little to correct any overshoot so the hitregions don't get stuck inside eachother float overshoot = (ballOneRadius + ballTwoRadius) - normalMagnitude; XMFLOAT2 overshootCorrection; overshootCorrection.x = (-p_ballOne->getDirection().x * overshoot); overshootCorrection.y = (-p_ballOne->getDirection().y * overshoot); p_ballOne->move(overshootCorrection); overshootCorrection.x = (-p_ballTwo->getDirection().x * overshoot); overshootCorrection.y = (-p_ballTwo->getDirection().y * overshoot); p_ballTwo->move(overshootCorrection); //make normal to collision plane a unit vector XMFLOAT2 unitNormal; unitNormal.x = normalVector.x / normalMagnitude; unitNormal.y = normalVector.y / normalMagnitude; //find unit tangent to collision plane //since tangent is perpendicular to collision plane we can //get the unit tangent from the unit normal XMFLOAT2 unitTangent; unitTangent.x = -unitNormal.y; unitTangent.y = unitNormal.x; //project velocity onto normal and tangent (= decomposing the velocity vector into tangent & normal) XMFLOAT2 ball1Velocity; ball1Velocity.x = p_ballOne->getDirection().x * p_ballOne->getSpeed(); ball1Velocity.y = p_ballOne->getDirection().y * p_ballOne->getSpeed(); float normalSpeed1, tangentSpeed1; float xDot, yDot; //project velocity onto normal xDot = ball1Velocity.x * unitNormal.x; yDot = ball1Velocity.y * unitNormal.y; normalSpeed1 = xDot + yDot; //project velocity onto tangent xDot = ball1Velocity.x * unitTangent.x; yDot = ball1Velocity.y * unitTangent.y; tangentSpeed1 = xDot + yDot; //preserve tangent and normal velocities (unlike pool, where normal velocity would be affected from collision) XMFLOAT2 newDirection1; newDirection1.x = -(unitNormal.x * normalSpeed1) + (unitTangent.x * tangentSpeed1); newDirection1.y = -(unitNormal.y * normalSpeed1) + (unitTangent.y * tangentSpeed1); float directionalMagnitude = sqrt((newDirection1.x * newDirection1.x) + (newDirection1.y * newDirection1.y)); newDirection1.x /= directionalMagnitude; newDirection1.y /= directionalMagnitude; p_ballOne->setDirection(newDirection1); //project velocity onto normal and tangent (= decomposing the velocity vector into tangent & normal) XMFLOAT2 ball2Velocity; ball2Velocity.x = p_ballTwo->getDirection().x * p_ballTwo->getSpeed(); ball2Velocity.y = p_ballTwo->getDirection().y * p_ballTwo->getSpeed(); float normalSpeed2, tangentSpeed2; //project velocity onto normal xDot = ball2Velocity.x * unitNormal.x; yDot = ball2Velocity.y * unitNormal.y; normalSpeed2 = xDot + yDot; //project velocity onto tangent xDot = ball2Velocity.x * unitTangent.x; yDot = ball2Velocity.y * unitTangent.y; tangentSpeed2 = xDot + yDot; //preserve tangent and normal velocities (unlike pool, where normal velocity would be affected from collision) XMFLOAT2 newDirection2; newDirection2.x = (unitNormal.x * normalSpeed1) + (unitTangent.x * tangentSpeed2); newDirection2.y = (unitNormal.y * normalSpeed1) + (unitTangent.y * tangentSpeed2); directionalMagnitude = sqrt((newDirection2.x * newDirection2.x) + (newDirection2.y * newDirection2.y)); newDirection2.x /= directionalMagnitude; newDirection2.y /= directionalMagnitude; p_ballTwo->setDirection(newDirection2); }}void PongPrototype::bounceBallOffWall(Ball* &p_ball){ //bool markedForDeletion = false; XMFLOAT2 direction = p_ball->getDirection(); floatRect region = p_ball->getHitRegion()->getRegion(); //hit left or right? adjust overshoot and flip x direction if(region.left <= 0) { direction.x = -direction.x; } if(region.right >= SOLIPSIST.getWindowWidth()) { direction.x = -direction.x; } //hit top or bottom? adjust overshoot and flip y direction + check scoring conditions if(region.bottom <= 0) { //player ball bounces of bottom, otherwise score if(p_ball->isFriendly()) direction.y = -direction.y; //else //ball wasn't friendly, score ball //{ // m_enemyScore++; // markedForDeletion = true; //} } if(region.top >= SOLIPSIST.getWindowHeight()) { /*if(p_ball->isFriendly()) { m_playerScore++; markedForDeletion = true; }*/ if(!p_ball->isFriendly()) { direction.y = -direction.y; } } //change direction p_ball->setDirection(direction); //if(markedForDeletion) //{ // //TODO: let pass pass through hitregion before deleting // delete p_ball; // p_ball = 0; //}}//check which balls are in the score zone, remove them and mark up the scorevoid PongPrototype::removeOvershotBalls(){ m_playerScore += m_pHumanPlayer->removeOvershotBalls(m_pParentProject->getWindowHeight() - 20); m_enemyScore += m_pEnemyPlayer->removeOvershotBalls(20);}//this function is only called when a collision is already detected, so //we just have to check which side of the paddle was hitvoid PongPrototype::bounceBallOffPaddle(Ball* p_ball, Paddle* p_paddle){ //these boolean are used to determine which part of the brick the ball hit bool top = false; bool bottom = false; bool left = false; bool right = false; //these floats are used to determine have far the "overshoot" is (that is, how deep one hitregion penetrated into another // - we need this value to make sure things don't get stuck to each other when a collision has occured) float ballRadius = p_ball->getHitRegion()->getRegionWidth() / 2; float paddleWidth = p_paddle->getHitRegion()->getRegionWidth(); float paddleHeight = p_paddle->getHitRegion()->getRegionHeight(); XMFLOAT2 overshootNudge; overshootNudge.x = 0.0f; overshootNudge.y = 0.0f; //centerpoints of colliding objects XMFLOAT2 paddlePosition = p_paddle->getPosition(); XMFLOAT2 ballPosition = p_ball->getPosition(); //direction of ball after hitting paddle (we "flip" these values) XMFLOAT2 ballDirectionPrime; ballDirectionPrime.x = p_ball->getDirection().x; ballDirectionPrime.y = p_ball->getDirection().y; //distance of the impact from the centrepoint of the brick // x > 0 = ball is on right side of brick centre // x < 0 = ball is on left side of brick centre // y > 0 = ball is above brick centre // y < 0 = ball is below brick centre XMFLOAT2 impactDistance; impactDistance.x = ballPosition.x - paddlePosition.x; impactDistance.y = ballPosition.y - paddlePosition.y; if(impactDistance.x > 0) right = true; if(impactDistance.x < 0) left = true; if(impactDistance.y > 0) top = true; if(impactDistance.y < 0) bottom = true; float paddleWidthHalved = paddleWidth/2; float paddleHeightHalved = paddleHeight/2; if(left) //impact distance is a negative number (x < 0 -> left) { //if the centre of the ball isn't positioned past the left wall of the brick we can assume it's a top or bottom hit if((impactDistance.x + paddleWidthHalved) > 0) left = false; } if(right) //impact distance is a positive number (x > 0 -> right) { if((impactDistance.x - paddleWidthHalved) < 0) right = false; } if(bottom) //distance is negative (y < 0 -> bottom) { if((impactDistance.y + paddleHeightHalved) > 0) bottom = false; } if(top) { if((impactDistance.y - paddleHeightHalved) < 0) top = false; } if(top || bottom) { ballDirectionPrime.y *= -1; float yNudge = 0.0f; if(top) yNudge = (paddleHeightHalved + ballRadius) - impactDistance.y; if(bottom) yNudge = (paddleHeightHalved + ballRadius + impactDistance.y) * -1; if(yNudge < 0) overshootNudge.y = floor(yNudge); if(yNudge > 0) overshootNudge.y = ceil(yNudge); } if(left || right) { ballDirectionPrime.x *= -1; float xNudge = 0.0f; //x-nudge only for hit on the sides if(right && !(top || bottom)) xNudge = (paddleWidthHalved + ballRadius) - impactDistance.x; if(left && !(top ||bottom)) xNudge = (impactDistance.x + paddleWidthHalved + ballRadius) * -1; if(xNudge < 0) overshootNudge.x = floor(xNudge); if(xNudge > 0) overshootNudge.x = ceil(xNudge); } Paddle* AIPaddle = m_pEnemyPlayer->getPaddle(); Paddle* humanPaddle = m_pHumanPlayer->getPaddle(); if(p_ball->isFriendly() && p_paddle == AIPaddle) { //remove reference to ball from player vector m_pHumanPlayer->popBall(p_ball); p_ball->setFriendly(false); XMFLOAT2 position; position.x = p_ball->getPosition().x; position.y = p_ball->getPosition().y; p_ball->cleanup(); p_ball->initialize(SOLIPSIST.getGraphics(), _T("Graphics/Sprites/enemyBall.dds")); p_ball->move(position); m_pEnemyPlayer->pushBall(p_ball); } if(!(p_ball->isFriendly()) && p_paddle == humanPaddle) { //remove reference to ball from enemy vector m_pEnemyPlayer->popBall(p_ball); p_ball->setFriendly(true); XMFLOAT2 position; position.x = p_ball->getPosition().x; position.y = p_ball->getPosition().y; p_ball->cleanup(); p_ball->initialize(SOLIPSIST.getGraphics(), _T("Graphics/Sprites/playerBall.dds")); p_ball->move(position); m_pHumanPlayer->pushBall(p_ball); } p_ball->move(overshootNudge); p_ball->setDirection(ballDirectionPrime);}//TODO: dit heeft eigenlijk geen aparte functie nodigvoid PongPrototype::bounceBallOffBrick(PowerupBrick* p_brick, Ball* p_ball){ //these boolean are used to determine which part of the brick the ball hit bool top = false; bool bottom = false; bool left = false; bool right = false; //these floats are used to determine have far the "overshoot" is (that is, how deep one hitregion penetrated into another // - we need this value to make sure things don't get stuck to each other when a collision has occured) float ballRadius = p_ball->getHitRegion()->getRegionWidth() / 2; float brickWidth = p_brick->getHitRegion()->getRegionWidth(); float brickHeight = p_brick->getHitRegion()->getRegionHeight(); XMFLOAT2 overshootNudge; overshootNudge.x = 0.0f; overshootNudge.y = 0.0f; //centerpoints of colliding objects XMFLOAT2 brickPosition = p_brick->getPosition(); XMFLOAT2 ballPosition = p_ball->getPosition(); //direction of ball after hitting paddle (we "flip" these values) XMFLOAT2 ballDirectionPrime; ballDirectionPrime.x = p_ball->getDirection().x; ballDirectionPrime.y = p_ball->getDirection().y; //distance of the impact from the centrepoint of the brick // x > 0 = ball is on right side of brick centre // x < 0 = ball is on left side of brick centre // y > 0 = ball is above brick centre // y < 0 = ball is below brick centre XMFLOAT2 impactDistance; impactDistance.x = (ballPosition.x - brickPosition.x); impactDistance.y = ballPosition.y - brickPosition.y; if(impactDistance.x > 0) right = true; if(impactDistance.x < 0) left = true; if(impactDistance.y > 0) top = true; if(impactDistance.y < 0) bottom = true; float brickWidthHalved = brickWidth/2; float brickHeightHalved = brickHeight/2; if(left) //impact distance is a negative number (x < 0 -> left) { //if the centre of the ball isn't positioned past the left wall of the brick we can assume it's a top or bottom hit if((impactDistance.x + brickWidthHalved) > 0) left = false; } if(right) //impact distance is a positive number (x > 0 -> right) { if((impactDistance.x - brickWidthHalved) < 0) right = false; } if(bottom) //distance is negative (y < 0 -> bottom) { if((impactDistance.y + brickHeightHalved) > 0) bottom = false; } if(top) { if((impactDistance.y - brickHeightHalved) < 0) top = false; } if(top || bottom) { ballDirectionPrime.y *= -1; float yNudge = 0.0f; if(top) yNudge = (brickHeightHalved + ballRadius) - impactDistance.y; if(bottom) yNudge = (brickHeightHalved + ballRadius + impactDistance.y) * -1; if(yNudge < 0) overshootNudge.y = floor(yNudge); if(yNudge > 0) overshootNudge.y = ceil(yNudge); } if(left || right) { ballDirectionPrime.x *= -1; float xNudge = 0.0f; //x-nudge only for hit on the sides if(right && !(top || bottom)) xNudge = (brickWidthHalved + ballRadius) - impactDistance.x; if(left && !(top ||bottom)) xNudge = (impactDistance.x + brickWidthHalved + ballRadius) * -1; if(xNudge < 0) overshootNudge.x = floor(xNudge); if(xNudge > 0) overshootNudge.x = ceil(xNudge); } p_ball->move(overshootNudge); p_ball->setDirection(ballDirectionPrime); p_brick->decreaseHitCounter();}//The purpose of this function is to keep the paddle within the confines of the WIN32 window //and prevent the player from moving his paddle out of sightvoid PongPrototype::correctPaddleOverschoot(Paddle* p_paddle){ XMFLOAT2 offset; offset.x = 0.0f; offset.y = 0.0f; int windowWidth = SOLIPSIST.getWindow()->getWidth(); int windowHeight = SOLIPSIST.getWindow()->getHeight(); floatRect region = p_paddle->getHitRegion()->getRegion(); float margin = 0.4f; //store overshoot corrections if(region.left < 0) offset.x -= (region.left - margin); //subtract because region.left is a negative value if(region.bottom < 0) offset.y -= (region.bottom - margin); //same here, subtract because it's a negative value if(region.right > windowWidth) offset.x -= (region.right - windowWidth) - margin; if(region.top > windowHeight) offset.y -= (region.top - windowHeight) - margin; p_paddle->move(offset); XMFLOAT2 nullDirection; nullDirection.x = 0.0f; nullDirection.y = 0.0f; p_paddle->setDirection(nullDirection);}//check collision against edges of window bool PongPrototype::checkBorderCollision(HitRegion* p_region){ bool collision = false; floatRect rect = p_region->getRegion(); //retrieve the location of the hitregion (HitRegion is a wrapper for floatRect) //check the border of the map and adjust any overshoots if( rect.bottom <= 0 || rect.top >= SOLIPSIST.getWindow()->getHeight() || rect.left <= 0 || rect.right >= SOLIPSIST.getWindow()->getWidth()) collision = true; return collision;}
Ugh - that is alot of ugly code just for bouncing a bunch of balls and sticks around.
So, to make a long story short I've decided to kill project solipsist (yes my engine had a name that snooty) and decided to remake the game in SFML.

Don't get me wrong - I'm happy I did it because I learned a ton of stuff BUT:
1) I would like the game to be finished sometime before 2020
2) I would like a modicum of portability
3) I would like to see the inner workings of a properly coded framework
4) the prospect of clean code appeals to me
5) I'm a sucker for reputation points and am going to blog about making this game (and maybe refactoring solipsist).

So yesterday I spent the whole day following this SFML tutorial and have gotten up to this point:

...and now I'm about to work on the collision detection. This is where my code will being to diverge since my paddles move up and down and the player will have multiple balls bouncing around (heh heh again) .

Obviously I'm hoping to avoid the messy code you saw earlier - that's why I'm currently teaching myself trigonometry - if you want to do the same thing I suggest you check out udacity or khan academy.

I'll try to post code and explanations as new things are added. Hopefully newbies can follow along and learn a thing or two.
I didn't do that for this post since it would pretty much be a rip off from the tutorial I posted a link to.

Also, since I am quite busy some patience will be required - Thanks for reading!