Jump to content

  • Log In with Google      Sign In   
  • Create Account

Arkanong development blog



Arkanong part 6: batching draw calls

Posted by , 21 March 2014 - - - - - - · 1,262 views
sprite batching, draw batching and 5 more...
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<string, texture> 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<VisibleObject*>& objectList)
{
    std::map<std::string, sf::Texture*>::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<VisibleObject*>::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!


Thanks for reading and as always you can download the code in its current state here.


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

Posted by , 09 February 2014 - - - - - - · 1,009 views
collision detection, sphere and 8 more...
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)2 + (y - k)2 = r2
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 ® 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:
x2 - 2xh + h2 + y2 - 2yk + k2 - r2 = 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 = ex + tdx
y = ey + tdy

Then you substitute the x and y variables in step 1 with these ones. You'll end up with this:
( ex + tdx )2 - 2( ex + tdx )h + h2 + ( ey + tdy )2 - 2( ey + tdy )k + k2 - r2 = 0

expand => ex2 + 2extdx + t2dx2 - 2exh - 2tdxh + h2 + ey2 + 2eytdy + t2dy2 - 2eyk - 2tdyk + k2 - r2 = 0

group => t2( dx2 + dy2 ) + 2t( exdx + eydy - dxh - dyk ) + ex2 + ey2 - 2exh - 2eyk + h2 + k2 - r2 = 0

write as dotproducts, where d = line segment, c = centre of circle
=> t2( d DOT d ) + 2t( e DOT d - d DOT c ) + _e DOT _e - 2( _e DOT_c ) + _c DOT _c - r2 = 0
=> t2( d DOT d ) + 2t( d DOT ( e - c ) ) + ( e - c ) DOT ( e - c ) - r2 = 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
=> t2( d DOT d ) + 2t( d DOT f ) + f DOT f - r2 = 0



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

t2 * (d DOT d) + 2t*( f DOT d ) + ( f DOT f - r2 ) = 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
Posted Image

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[i], hexagonCorners[i + 1]);
        else //last corner connects to first corner
            performCircleToRayCollisionChecks(p_ball, hexagonCorners[i], 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!


arkanong 16 02 2014


You can take a look at the code at https://github.com/bytechomper/arkanong
(code for this article is in the class 'CollisionManager' in the folder 'source')
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

Posted by , 19 November 2013 - - - - - - · 800 views
c++, sfml, collision detection and 1 more...
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 + radius2
float minimumDistance = radius1 + radius2;

//amount of pixels between the centres
float actualDistance = sqrt((collisionNormal.x * collisionNormal.x) +
    (collisionNormal.y * collisionNormal.y));


//if the distance <= radius1+radius2 a collision has occured
if (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 vector
collisionNormal.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 component
sf::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:

Posted Image
Posted Image

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 [left] 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 clarity
sf::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 clarity
sf::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

Posted by , 18 November 2013 - - - - - - · 796 views
c++, sfml, pong, breakout and 2 more...
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 (180.0f/3.1415926).


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
		}

Attached Image

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

Posted by , 17 November 2013 - - - - - - · 827 views
c++, sfml, 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<std::string, VisibleObject*>::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
check out the link 'fix your timestep' I posted above.


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)

Attached Image
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.


Attached Image
Attached Image


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 coördinate. 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.
Thanks for reading!


Arkanong part 1: Porting for the sake of sanity

Posted by , 15 November 2013 - - - - - - · 716 views
arkanong
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:


Attached Image


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<Ball*>::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<PowerupBrick*>::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<Ball*>::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 edges

void 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 score

void 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 hit

void 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 nodig

void 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 sight

void 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:

Attached Image


...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!





September 2016 »

S M T W T F S
    123
45678910
11121314151617
18192021222324
252627 28 2930 


PARTNERS