Collision Detection

Today we’re going to look into collision detection. Collision detection works very much like our edge detection but we need to keep in mind that the objects location is a point, but it’s representation on the screen is a square.

We’re essentially going to go through the list of objects and, for each other object, see if they are colliding and if so, do something. Because we are using circles the algorithm is a simple check to see if the distance between to objects is less than the sum of their radii. Because we are using square objects we will need to offset our test location by half of the height and width the get the center point of the images.

[pastacode lang=”cpp” manual=”void%20mover%3A%3AobjectCollision(mover*%20m)%0A%7B%0A%20%20%20%20%2F%2Fcircle-based%20collision%20detection%0A%20%20%20%20vec2%20aCenter(%20(position.getX())%20%2B%20(width%20%2F%202)%2C%20(position.getY())%20%2B%20(width%20%2F%202)%20)%3B%0A%20%20%20%20vec2%20bCenter(%20(m-%3Eposition.getX())%20%2B%20(m-%3Ewidth%20%2F%202)%2C%20(m-%3Eposition.getY())%20%2B%20(m-%3Eheight%20%2F%202)%20)%3B%0A%20%20%20%20vec2%20distance%20%3D%20vec2%3A%3Asub(aCenter%2CbCenter)%3B%0A%0A%20%20%20%20if%20(%20distance.getMag()%20%3C%20(%20(width%20%2F%202)%20%2B%20(m-%3Ewidth%20%2F%202)%20)%20)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20std%3A%3Acout%20%3C%3C%20%22Collision%20detected%20between%20%22%20%3C%3C%20id%20%3C%3C%20%22%20%26%20%22%20%3C%3C%20m-%3Eid%20%3C%3C%20std%3A%3Aendl%3B%0A%20%20%20%20%7D%0A%7D” message=”” highlight=”” provider=”manual”/]

This will now print to the console whenever it detects a collision between two of the movers, if we add applyForce(distance) to the mix, we can now make the balls bounce off each other using our force system and, because we are using the distance between centers as the force, smaller objects will be bounced off with more force than larger heavier objects, simulating real world expectations!

We can get better results by tweaking the force applied like this:

[pastacode lang=”cpp” manual=”if%20(%20distance.getMag()%20%3C%20(%20(width%20%2F%202)%20%2B%20(m-%3Ewidth%20%2F%202)%20)%20)%0A%7B%0A%20%20%20%20std%3A%3Acout%20%3C%3C%20%22Collision%20detected%20between%20%22%20%3C%3C%20id%20%3C%3C%20%22%20%26%20%22%20%3C%3C%20m-%3Eid%20%3C%3C%20std%3A%3Aendl%3B%0A%20%20%20%20vec2%20momentum(0%2C0)%3B%0A%20%20%20%20momentum.add(m-%3Evelocity)%3B%0A%20%20%20%20momentum.mult(m-%3Emass)%3B%0A%20%20%20%20distance.setMag(momentum.getMag()%2F2)%3B%0A%20%20%20%20applyForce(distance)%3B%0A%20%20%20%20distance.mult(-1)%3B%0A%20%20%20%20m-%3EapplyForce(distance)%3B%0A%7D” message=”” highlight=”” provider=”manual”/]

This approximates the transfer of momentum by the impacting objects current mass and acceleration, the formula is almost certainly not real world accurate as I just came up with it on the spot, but it does produce reasonable visuals!

We can also make the wind function more interesting by making it sure it applies as long as the mouse button is being pressed. What we can do here is add a bool named mousePressed and set it to false. In our event handler we change the MOUSEBUTTONDOWN event to set mousePressed to true and add a MOUSEBUTTONUP event, that sets mousePressed to false. with this mousePressed will only be true if the mouse button is being pressed. In our update loop for our objects we add in:

[pastacode lang=”cpp” manual=”if(mousePressed)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mover%3A%3AmoverList%5Bi%5D-%3EapplyForce(wind)%3B” message=”” highlight=”” provider=”manual”/]

Now we have a little physics toy that is somewhat fun to play with!

Static Member Variables

There is a bug in the collision code that is easy to address, a mover can get stuck on the border of the window if it’s gone far enough past the edge before getting tested, to address this, we can move the object away from the edge to prevent it getting stuck.

[pastacode lang=”markup” manual=”void%20mover%3A%3AedgeCollision()%0A%7B%0A%20%20%20%20if%20(%20(position.getX()%20%2B%20width%20%3E%3D%20SCREEN_WIDTH)%20)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20position.setX(SCREEN_WIDTH%20-%20width)%3B%0A%20%20%20%20%20%20%20%20velocity.setX(velocity.getX()%20*%20-1)%3B%0A%20%20%20%20%7D%0A%20%20%20%20else%20if(position.getX()%20%3C%3D%200)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20position.setX(0)%3B%0A%20%20%20%20%20%20%20%20velocity.setX(velocity.getX()%20*%20-1)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20if%20(%20(position.getY()%20%2B%20height%20%3E%3D%20SCREEN_HEIGHT)%20)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20position.setY(SCREEN_HEIGHT%20-%20height)%3B%0A%20%20%20%20%20%20%20%20velocity.setY(velocity.getY()%20*%20-1)%3B%0A%20%20%20%20%7D%0A%20%20%20%20else%20if(position.getY()%20%3C%3D%200)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20position.setY(0)%3B%0A%20%20%20%20%20%20%20%20velocity.setY(velocity.getY()%20*%20-1)%3B%0A%20%20%20%20%7D%0A%0A%7D” message=”” highlight=”” provider=”manual”/]

Because the image is being drawn from it’s top left corner, we need to offset the positioning by the width and height of the image respectively so that it stays within the window.

 

Just for a bit of fun, I was wondering what would happen if I added a ball that follows the mouse and, when it strikes another ball, it imparts a force equal to it’s previous position minus it’s last position on the ball that it struck.

To do that we would need to add a function that checks for collisions for all the objects in the world, this would be easier if we had all the objects in a nice list that where could them from the constructor of the mover class and remove them from the list via the destructor. A std::vector would probably be a good container object for this and I think making it a static member of the class would allow us to update it on each creation.

[pastacode lang=”cpp” manual=”%7BAdded%20to%20mover.h%7D%0A%0Astatic%20std%3A%3Avector%3Cmover*%3E%20moverList%3B%0Aint%20id%3B%0A%0A%0A%0A%7BAdded%20to%20mover.cpp%7D%0A%0Amover%3A%3Amover(float%20posX%2C%20float%20posY%2C%20float%20velX%2C%20float%20velY%2C%20float%20accX%2C%20float%20accY%2C%20float%20maxS%2C%20float%20_mass)%0A%7B%0A%20%20%20%20mover%3A%3AmoverList.push_back(this)%3B%0A%20%20%20%20id%20%3D%20mover%3A%3AmoverList.size()%3B%0A%7B…%7D%0A%7D%0A%0A%0Amover%3A%3A~mover()%0A%7B%0A%20%20%20%20SDL_DestroyTexture(image)%3B%0A%20%20%20%20mover%3A%3AmoverList.begin()%2Bid%3B%0A%7D%0A%0A%0A%0Astd%3A%3Avector%3Cmover*%3E%20mover%3A%3AmoverList%3B%0A%0A” message=”” highlight=”” provider=”manual”/]

This code simply declares a static vector of mover pointers called moverList in the header, adds a variable called id which takes the current size of moverList as it’s value, adds a function call that adds the current address of the object to the vector, adds a function call in the destructor that removes the current id from the vector and has a section that defines the vector below the class definition, upon testing this works exactly as intended. Now we can iterate through the objects in the list via this moverList instead of the array we created earlier. Not using an array has many benefits, the most notable being that we don’t declare the size of the vector at compile time, meaning we can add an arbitrary number of movers to our scene and out code will handle them all just fine!

To iterate through our new list we can use a for loop as before with new parameters:

[pastacode lang=”cpp” manual=”for(int%20i%3D0%3B%20i%3Cmover%3A%3AmoverList.size()%3Bi%2B%2B)%0A%7B%0A%20%20%20%20mover%3A%3AmoverList%5Bi%5D-%3Emass%20%3D%20rand()%20%25%20100%2B1%3B%0A%20%20%20%20mover%3A%3AmoverList%5Bi%5D-%3EloadImage(%22Images%2Fball.png%22%2C%20renderer)%3B%0A%20%20%20%20mover%3A%3AmoverList%5Bi%5D-%3EsetHeight(mover%3A%3AmoverList%5Bi%5D-%3Emass%2B25)%3B%0A%20%20%20%20mover%3A%3AmoverList%5Bi%5D-%3EsetWidth(mover%3A%3AmoverList%5Bi%5D-%3Eheight)%3B%0A%7D” message=”” highlight=”” provider=”manual”/]

And to instantiate our objects on the heap we can use:

[pastacode lang=”cpp” manual=”for(int%20i%3D0%3B%20i%3C50%3B%20i%2B%2B)%0A%7B%0A%20%20%20%20new%20mover()%3B%0A%7D” message=”” highlight=”” provider=”manual”/]

This works for our case here, but is not really proper as it doesn’t give use a way to run the corresponding delete that is required to prevent memory leaks.

 

That’s all for today, we didn’t get to much of what we set out to do today, but we did learn a fair bit about std::vectors and static member variables!

Smart Mass

Smart Mass

Now we need to add mass to our objects to make the physics system a little more robust: we’ll simply add float mass to our object, update the constructor to allow us to set this float, and in the applyForce() function we’ll divide the force by the mass of the object to get our acceleration that we want to apply: force.div(force).

With that applied we can see that nothing has really changed, if we change the mass of the object we find it accelerates more slowly, but the affect is more obvious when we add a second mover object to the scene. We’ll add another object called ball2 and make sure to set the mouse button to apply wind to it as well:

[pastacode lang=”cpp” manual=”%7BIn%20Main%20before%20render%20loop%7D%0A%0Amover%20ball2(0%2C0%2C0%2C0%2C0%2C0%2C20%2C5)%3B%0Aball2.loadImage(%22Images%2Fball.png%22%2C%20renderer)%3B%0A%0A%0A%0A%7BIn%20event%20handler%7D%0A%0Acase%20SDL_MOUSEBUTTONDOWN%3A%0A%20%20%20%20%20%20%20%20%20%20ball.applyForce(wind)%3B%0A%20%20%20%20%20%20%20%20%20%20ball2.applyForce(wind)%3B%0A%20%20%20%20%20%20%20%20%20%20break%3B%0A%0A%0A%0A%7BIn%20Render%20Loop%7D%0A%0Aball2.applyForce(gravity)%3B%0Aball2.update()%3B%0Aball2.display(renderer)%3B%0Aball2.edgeCollision()%3B” message=”” highlight=”” provider=”manual”/]

 

Notice that we set ball2 to have a mass of 5, this is half of the original ball, so we should see a difference  between them now:

 

The wrapping effect is getting a little tired, let’s change it back to the bounce effect and see what we get:

[pastacode lang=”cpp” manual=”void%20mover%3A%3AedgeCollision()%0A%7B%0A%20%20%20%20if%20(%20(position.getX()%20%3E%3D%20SCREEN_WIDTH)%20%7C%7C%20(position.getX()%20%3C%3D%200)%20)%0A%20%20%20%20%20%20%20%20velocity.setX(velocity.getX()%20*%20-1)%3B%0A%0A%20%20%20%20if%20(%20(position.getY()%20%2B%20%3E%3D%20SCREEN_HEIGHT)%20%7C%7C%20(position.getY()%20%3C%3D%200)%20)%0A%20%20%20%20%20%20%20%20velocity.setY(velocity.getY()%20*%20-1)%3B%0A%7D” message=”” highlight=”” provider=”manual”/]

 

That doesn’t look very good, we should add a width and height variable to our mover object so we can offset our screen edge checking by their values so that ball doesn’t leave the screen, we can have the loadImage() function update those variables as well as add setter and getter functions to allow changing it later on, in addition we should also change the display() function to take the width and height into account so it will scale the image accordingly.

[pastacode lang=”cpp” manual=”void%20mover%3A%3Adisplay(SDL_Renderer*%20ren)%0A%7B%0A%20%20%20%20renderTexture(image%2C%20ren%2C%20position.getX()%2C%20position.getY()%2C%20width%2C%20height)%3B%0A%7D%0A%0Avoid%20mover%3A%3AloadImage(std%3A%3Astring%20imagePath%2C%20SDL_Renderer*%26%20ren)%0A%7B%0A%20%20%20%20%20%20%20%20image%20%3D%20loadTexture(imagePath%2Cren)%3B%0A%20%20%20%20%20%20%20%20SDL_QueryTexture(image%2C%20NULL%2C%20NULL%2C%20%26width%2C%20%26height)%3B%0A%7D” message=”Mover Update and Display” highlight=”” provider=”manual”/]

 

Now we should be able to get these movers bouncing a bit more naturally:

 

What’s more, we can now use the get and set functions we added for the width and the height to set the scale of the balls so that the lighter of the two appears to be smaller:

or even make one an ellipse:

We can also create an array of movers and give them random values:

As an aside, the array of movers has a few special things that need to be done to make it work:

[pastacode lang=”cpp” manual=”%7Bin%20main%20before%20render%20loop%7D%0A%0Amover%20balls%5B25%5D%3B%0A%0Afor(int%20i%3D0%3B%20i%3C25%3B%20i%2B%2B)%0A%7B%0A%20%20%20%20balls%5Bi%5D.mass%20%3D%20rand()%20%25%20100%20%2B%201%3B%0A%20%20%20%20balls%5Bi%5D.loadImage(%22Images%2Fball.png%22%2C%20renderer)%3B%0A%20%20%20%20balls%5Bi%5D.setHeight(balls%5Bi%5D.mass%20%2B%2025)%3B%0A%20%20%20%20balls%5Bi%5D.setWidth(balls%5Bi%5D.height)%3B%0A%7D%0A%0A%0A%0A%7BIn%20event%20handler%7D%0A%0Acase%20SDL_MOUSEBUTTONDOWN%3A%0Afor(int%20i%3D0%3B%20i%3C25%3B%20i%2B%2B)%0A%7B%0A%20%20%20%20balls%5Bi%5D.applyForce(wind)%3B%0A%7D%0A%0A%0A%0A%7Bin%20render%20loop%7D%0A%0Afor(int%20i%3D0%3B%20i%3C25%3B%20i%2B%2B)%0A%7B%0A%20%20%20%20std%3A%3Acout%20%3C%3C%20%22Iteration%20%3A%20%22%20%3C%3C%20i%20%3C%3C%20std%3A%3Aendl%3B%0A%0A%20%20%20%20vec2%20%0A%20%20%20%20balls%5Bi%5D.applyForce(gravity)%3B%0A%20%20%20%20balls%5Bi%5D.update()%3B%0A%20%20%20%20balls%5Bi%5D.display(renderer)%3B%0A%20%20%20%20balls%5Bi%5D.edgeCollision()%3B%0A%20%20%20%20std%3A%3Acout%20%3C%3C%20%22ball%20%22%20%3C%3C%20i%20%3C%3C%20%22acceleration%3A%20%22%20%3C%3C%20balls%5Bi%5D.acceleration.getMag()%20%3C%3C%20std%3A%3Aendl%3B%0A%7D” message=”” highlight=”” provider=”manual”/]

 

The astute observer might notice now that the lighter balls actually fall faster than the heavy balls in this case, showing that gravity is not pulling uniformly as it would in the real world. One way that we can address this is by moving where we are defining the gravitational constant to the for loop that is applying it and multiplying by the objects weight, that way the gravity is constant per-object. There is probably a more elegant way to tackle the gravity problem, but this suits our needs for now.

[pastacode lang=”cpp” manual=”%7Bin%20render%20loop%7D%0A%0Afor(int%20i%3D0%3B%20i%3C25%3B%20i%2B%2B)%0A%7B%0A%20%20%20%20std%3A%3Acout%20%3C%3C%20%22Iteration%20%3A%20%22%20%3C%3C%20i%20%3C%3C%20std%3A%3Aendl%3B%0A%0A%20%20%20%20vec2%20gravity(0%2CGRAV_CONSTANT*balls%5Bi%5D.mass)%3B%0A%20%20%20%20balls%5Bi%5D.applyForce(gravity)%3B%0A%20%20%20%20balls%5Bi%5D.update()%3B%0A%20%20%20%20balls%5Bi%5D.display(renderer)%3B%0A%20%20%20%20balls%5Bi%5D.edgeCollision()%3B%0A%20%20%20%20std%3A%3Acout%20%3C%3C%20%22ball%20%22%20%3C%3C%20i%20%3C%3C%20%22acceleration%3A%20%22%20%3C%3C%20balls%5Bi%5D.acceleration.getMag()%20%3C%3C%20std%3A%3Aendl%3B%0A%7D” message=”” highlight=”” provider=”manual”/]

 

And that’s all for today, next time I think we might look into moving the objects we’re creating from the stack to the heap using pointers and the new keyword and maybe a few more forces like friction.

Force Bug

Last time we ran into a bug where forces were not having the expected effect, today we’ll look into solving that.

To start off with, we have two vec2 objects: wind and gravity. Wind has a value of 5,0 and gravity has a value of 0,1. We want a sum of the force vectors and we create this sum with the applyForce() function, applying each force should be aggregating it into the target objects acceleration so let’s break that down step by step to see where the bug is.

applyForce(wind) is being called from the event handler and gravity is being called from the bottom of the main loop, right before the update() function, if we comment out gravity and leave just wind, we see that clicking creates the wind effect we were expecting, if we add gravity back in, we see that the wind force is nullified and the gravity force takes over.

If we move gravity to be applied when the mouse is pressed as well as the wind, it works as intended, the forces are aggregated and the motion is the sum of the two vectors, if we separate the forces and apply gravity when space is pressed, it works as expected, but if we hold the button down to constantly apply the force we see that the ball stops moving sideways and starts moving downward only, so the issue seems to be the frequency with which the gravity is being applied. Since the wind force is being applied only in the instance of the click, it doesn’t seem to have any lasting effect, let’s move it to be bound to space and allow gravity to be constant again.

This effect is closer to what we want, but it’s still not quite right, so we’ll add a few lines to output the X and Y velocity of the ball to the console to see what we get as we change the vectors. Everything seems good until we get close to 10, when the limit function comes into play it starts to mess with the other velocity component, if we comment out the limit function we see that the code performs the way that we wanted it to. Bug found!

Let’s look at the steps involved in Limit():

[pastacode lang=”cpp” manual=”void%20vec2%3A%3Alimit(float%20limit)%0A%7B%0A%20%20%20%20if%20(this-%3EgetMag()%20%3E%20limit)%0A%20%20%20%20%20%20%20%20this-%3EsetMag(limit)%3B%0A%7D%0A%0A%0A%0AgetMag()%20%7Breturn%20sqrt(%20(x*x)%20%2B%20(y*y)%20)%3B%7D%3B%0A%0A%0A%0Avoid%20vec2%3A%3AsetMag(float%20mag)%0A%7B%0A%20%20%20%20if(this-%3EgetMag()%20!%3D%200)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20this-%3Enormalize()%3B%0A%20%20%20%20%20%20%20%20this-%3Emult(mag)%3B%0A%20%20%20%20%7D%0A%20%20%20%20else%20if(%20mag%20%3E%200%20)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20this-%3Ex%20%3D%201%20*%20mag%3B%0A%20%20%20%20%20%20%20%20this-%3Ey%20%3D%201%20*%20mag%3B%0A%20%20%20%20%7D%0A%7D%0A%0A%0A%0Avoid%20vec2%3A%3Anormalize()%0A%7B%0A%20%20%20%20float%20mag%20%3D%20this-%3EgetMag()%3B%0A%20%20%20%20if%20(mag)%0A%20%20%20%20%20%20%20%20div(mag)%3B%0A%7D%0A%0A%0A%0Avoid%20vec2%3A%3Amult(float%20scaler)%0A%7B%0A%20%20%20%20this-%3Ex%20*%3D%20scaler%3B%0A%20%20%20%20this-%3Ey%20*%3D%20scaler%3B%0A%7D%0A%0A%0A%0Avoid%20vec2%3A%3Adiv(float%20div)%0A%7B%0A%20%20%20%20if%20(div%20!%3D%200)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20this-%3Ex%20%2F%3D%20div%3B%0A%20%20%20%20%20%20%20%20this-%3Ey%20%2F%3D%20div%3B%0A%20%20%20%20%7D%0A%7D%0A” message=”” highlight=”” provider=”manual”/]

Looking at these functions we can see an error right off the bat:

  • setMag() has zero handling that it doesn’t need, we can cut that out

Other than that though, the code looks good! The problem appears to be with our perception, if both wind and gravity are equal and both are applied constantly the ball travels in a diagonal direction, but because the wind was intermittent before, gravity quickly accelerated the ball the max velocity, and in order to limit the magnitude of the vector and still increase velocity to the max allowable in one direction, we have to take away from one component, so as Y gets faster, X must get slower so the magnitude can increase as they are both linked!

We can more easily see what is happening by using smaller values for our gravity and higher values for our wind. Setting Wind to 5,0 and gravity to 0,.1 shows this well, the ball slowly accelerates to the limit and then if we add a burst of wind, it subtracts from the vertical speed and contributes to the lateral speed, once the wind is stopped, it begins accelerating downward again, slowly eroding the lateral speed.

Problem solved! The error was one of incorrect expectations!

 

Next up we can look at adding mass to our objects will will further complicate the mover object.

A Force to be Reckoned With

Newton’s second law states that Force = Mass * Acceleration and with this simple statement we can add forces to our objects. To simplify things, we’ll say for now that all of our objects have a mass of one, which simplifies the expression to Force = Acceleration, therefore we can simply apply the net of any forces that we want to interact with the object and we should get a reasonable result. We will also need to zero out the acceleration on the object after each update to prevent the previous frames acceleration from accumulating with the next frame and so on.

We will be modifying our mover class to add an applyForce() function and also to nullify the acceleration at the end of the update() function.

[pastacode lang=”cpp” manual=”void%20mover%3A%3Aupdate()%0A%7B%0A%20%20%20%20velocity.add(acceleration)%3B%0A%20%20%20%20velocity.limit(maxSpeed)%3B%0A%20%20%20%20position.add(velocity)%3B%0A%20%20%20%20acceleration.mult(0)%3B%0A%7D%0A%0Avoid%20mover%3A%3AapplyForce(vec2%20force)%0A%7B%0A%20%20%20%20acceleration.add(force)%3B%0A%7D” message=”” highlight=”” provider=”manual”/]

 

Now if we add a force vector, say wind which could be a vector of 1,0 and we apply that force to the ball object, we should see the ball begin to accelerate, this affect is more obvious if we make it so that the mouse button triggers the force:

[pastacode lang=”cpp” manual=”case%20SDL_MOUSEBUTTONDOWN%3A%0A%20%20%20%20%20%20%20%20ball.applyForce(wind)%3B%0A%20%20%20%20%20%20%20%20break%3B” message=”” highlight=”” provider=”manual”/]

Adding this to our existing event handler allows us to add the force when the button is pressed, this won’t work for holding, we’ll need a different solution for click and hold, but this works for the purposes of exploration.

Next we can add gravity to be added at each frame by simply adding a gravity force, which is essentially a downwards facing wind force: vec2 gravity(0,1).

Interestingly this does not have the anticipated result of the ball continuing to move at a diagonal,  we’ll have to dig a little deeper and see what is going on!

 

Thinking Objectively

Our code is getting quite messy now that we’ve added all these variables to track velocity, position, acceleration, etc in the main function, so I think it’s time to roll another class that will contain all the things we need for an object with the following traits:

  • position
  • velocity
  • acceleration
  • max speed
  • SDL_Texture

and have the following functions:

  • edge collision handling
  • display
  • load image
  • update

We’ll also need to add a destructor that will handle clearing the SDL_Texture.

This should allow the object to abstract away all of this complexity within itself instead of spilling out into the main function. Borrowing from Daniel Shiffman’s Nature of Code, which I am referencing, we’ll call this the mover class.

 

Firstly, we’ll create a new branch to work on this code and then merge it later when we’re done. We’ll create a new branch in git by running git checkout -b mover, where mover is the name of the branch.

Now that we’re in our new branch, our class should look something like this:

[pastacode lang=”cpp” manual=”class%20mover%0A%7B%0Apublic%3A%0A%20%20%20%20vec2%20position%3B%0A%20%20%20%20vec2%20velocity%3B%0A%20%20%20%20vec2%20acceleration%3B%0A%20%20%20%20float%20maxSpeed%3B%0Aprivate%3A%0A%20%20%20%20SDL_Texture*%20image%3B%0A%0Apublic%3A%0A%20%20%20%20mover(float%20posX%3D1%2C%20float%20posY%3D1%2C%20float%20velX%3D0%2C%20float%20velY%3D0%2C%20float%20AccX%3D0%2C%20float%20AccY%3D0%2C%20float%20maxS%3D0)%3B%0A%20%20%20%20~mover()%3B%0A%20%20%20%20void%20edgeCollision()%3B%0A%20%20%20%20void%20display(SDL_Renderer*%20ren)%3B%0A%20%20%20%20void%20loadImage(std%3A%3Astring%20imagePath%2C%20SDL_Renderer**%20ren)%3B%0A%20%20%20%20void%20update()%3B%0A%0A%7D%3B%0A%0Amover%3A%3Amover(float%20posX%2C%20float%20posY%2C%20float%20velX%2C%20float%20velY%2C%20float%20accX%2C%20float%20accY%2C%20float%20maxS)%0A%7B%0A%20%20%20%20position.setX(posX)%3B%0A%20%20%20%20position.setY(posY)%3B%0A%20%20%20%20velocity.setX(velX)%3B%0A%20%20%20%20velocity.setY(velY)%3B%0A%20%20%20%20acceleration.setX(accX)%3B%0A%20%20%20%20acceleration.setY(accY)%3B%0A%20%20%20%20maxSpeed%20%3D%20maxS%3B%0A%7D%0A%0Amover%3A%3A~mover()%0A%7B%0A%20%20%20%20SDL_DestroyTexture(image)%3B%0A%7D%0A%0Avoid%20mover%3A%3AedgeCollision()%0A%7B%0A%20%20%20%20if%20(position.getX()%20%3E%20SCREEN_WIDTH)%0A%20%20%20%20%20%20%20%20position.setX(0)%3B%0A%20%20%20%20else%20if(position.getX()%20%3C%200)%0A%20%20%20%20%20%20%20%20position.setX(SCREEN_WIDTH)%3B%0A%0A%20%20%20%20if%20(position.getY()%20%3E%20SCREEN_HEIGHT)%0A%20%20%20%20%20%20%20%20position.setY(0)%3B%0A%20%20%20%20else%20if%20(position.getY()%20%3C%200)%0A%20%20%20%20%20%20%20%20position.setY(SCREEN_HEIGHT)%3B%0A%7D%0A%0Avoid%20mover%3A%3Adisplay(SDL_Renderer*%20ren)%0A%7B%0A%20%20%20%20renderTexture(image%2C%20ren%2C%20position.getX()%2C%20position.getY())%3B%0A%7D%0A%0Avoid%20mover%3A%3AloadImage(std%3A%3Astring%20imagePath%2C%20SDL_Renderer**%20ren)%0A%7B%0A%20%20%20%20%20%20%20%20image%20%3D%20loadTexture(imagePath%2C*ren)%3B%0A%7D%0A%0Avoid%20mover%3A%3Aupdate()%0A%7B%0A%20%20%20%20velocity.add(acceleration)%3B%0A%20%20%20%20velocity.limit(maxSpeed)%3B%0A%20%20%20%20position.add(velocity)%3B%0A%7D%0A” message=”mover” highlight=”” provider=”manual”/]

 

There’s quite a lot going on here, but nothing we haven’t seen so far, it’s just moved into it’s own class. the member variables are pretty self explanatorys o let’s go through the functions:

edgeCollision()

This is the same collision code we had before that detects the window edge and wraps the object if it hits the border.

display(SDL_Renderer* ren)

This function runs the renderTexture() function from the sdl_helpers.h

loadImage(std::string imagePath, SDL_Renderer*& ren)

This function loads an image with the provided path and uses the renderer that we pass in via pointer reference.

update()

This function simply performs the same vectors add that we were doing before to create motion.

~mover();

Finally, the destructor destroys any texture we might create for this object.

 

Also worth noting is that the two consts for screen width and height were moved into their own globals.h.

 

After all that we can now add the following to our main function and render loop respectively:

[pastacode lang=”cpp” manual=”%3C~%20in%20main%20~%3E%0A%0Amover%20ball(1%2C1%2C0%2C0%2C0%2C0%2C10)%3B%0Aball.loadImage(%22Images%2Fball.png%22%2C%20renderer)%3B%0Aball.acceleration.setMag(.1)%3B%0A%0A%0A%3C~%20in%20loop%20~%3E%0A%0Aball.update()%3B%0Aball.edgeCollision()%3B%0Aball.display(renderer)%3B” message=”” highlight=”” provider=”manual”/]

These six lines replace all the code we previously had for an autonomously moving object.

 

Now that we have all of this implemented into our mover branch, we need to merge our changes, we can do this by running a commit of the code, checking out the master branch and running  git merge mover to merge the changes into the master branch. This is a simple merge as no changes were made to master since the branch was created, if there had been changes we would have had to deal with conflicts.

 

Next time we’ll add a way for our mover object to experience external forces such as wind, gravity and friction.

Picking up the pace

Today I’m looking at adding acceleration (another vector!) to my code, essentially it works like this:

Our location vector is our current point in space.
Our Velocity vector is our change in location over time.
Our Acceleration vector is our change in velocity over time.

The reason we were having issues with strangeness when we changed the velocity earlier was because we are reversing the velocity when we meet a wall. When you add a positive value (our new velocity) to a negative value (our current velocity), you get a smaller negative value, resulting in a deceleration, instead of an acceleration. What we actually need to do to get a directionless change in velocity is to change the magnitude of the current velocity vector via out setMag() function.

I  playing around with this I discovered a bug in the vec2 class that needed to be corrected:

What happens if we normalize() a zeroed vector?
What happens if we setMag() a zeroed vector?

These are actually the same question, as normalize() requires that we divide the vector components by the magnitude and setMag() requires that we normalize() first!

To correct this, I added a div() function that ignores divide-by-zero cases and also modified the normalize() and setMag() functions to handle zero cases:

[pastacode lang=”cpp” manual=”void%20vec2%3A%3Adiv(int%20div)%0A%7B%0A%20%20%20%20if%20(div%20!%3D%200)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20this-%3Ex%20%2F%3D%20div%3B%0A%20%20%20%20%20%20%20%20this-%3Ey%20%2F%3D%20div%3B%0A%20%20%20%20%7D%0A%7D%0A%0Avoid%20vec2%3A%3Anormalize()%0A%7B%0A%20%20%20%20float%20mag%20%3D%20this-%3EgetMag()%3B%0A%20%20%20%20if%20(mag)%0A%20%20%20%20%20%20%20%20div(mag)%3B%0A%7D%0A%0Avoid%20vec2%3A%3AsetMag(float%20mag)%0A%7B%0A%20%20%20%20if(this-%3EgetMag()%20!%3D%200)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20this-%3Enormalize()%3B%0A%20%20%20%20%20%20%20%20this-%3Emult(mag)%3B%0A%20%20%20%20%7D%0A%20%20%20%20else%20if(%20mag%20%3E%200%20)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20this-%3Ex%20%3D%201%20*%20mag%3B%0A%20%20%20%20%20%20%20%20this-%3Ey%20%3D%201%20*%20mag%3B%0A%20%20%20%20%7D%0A%7D” message=”” highlight=”” provider=”manual”/]

 

Now with this modified code we can increase and decrease the velocity of the bouncing image with the following calls in our event handler:

[pastacode lang=”cpp” manual=”case%20SDLK_a%3A%0A%20%20%20%20velocity.setMag(velocity.getMag()%20%2B%201)%3B%0A%20%20%20%20break%3B%0A%0Acase%20SDLK_z%3A%0A%20%20%20%20velocity.setMag(velocity.getMag()%20-%201)%3B%0A%20%20%20%20break%3B” message=”” highlight=”” provider=”manual”/]

There is no special error handling for zero cases as it’s all handled within the class!

 


 

In order to clean main a little bit, I decided I wanted to break the SDL initialization functions into there own separate fucntion, this turned out to be quite a confusing rabbit hole where I ended up learning about pointers to pointers and references to pointers.

The thing to know about pointers is that when you pass a pointer to a function, the pointer will be dereferenced and ultimately you are effectively passing a copy of the value that the pointer points to, not the pointer itself, this doesn’t work if you want to actually change the contents of the pointer within a function.

 

Here’s the code in question:

[pastacode lang=”cpp” manual=”bool%20initializeSDL(SDL_Window*%26%20win%2C%20SDL_Renderer*%26%20ren)%0A%7B%0A%20%20%20%20if%20(SDL_Init(SDL_INIT_VIDEO)%20!%3D0)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20logSDLError(std%3A%3Acout%2C%20%22SDL%20Init%22)%3B%0A%20%20%20%20%20%20%20%20return%20false%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20win%20%3D%20SDL_CreateWindow(%22Hello%20World%22%2C%20100%2C%20100%2C%20640%2C%20480%2C%20SDL_WINDOW_SHOWN)%3B%0A%20%20%20%20if%20(win%20%3D%3D%20nullptr)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20logSDLError(std%3A%3Acout%2C%20%22SDL_CreateWindow%22)%3B%0A%20%20%20%20%20%20%20%20SDL_Quit()%3B%0A%20%20%20%20%20%20%20%20return%20false%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20ren%20%3D%20SDL_CreateRenderer(win%2C%20-1%2C%20SDL_RENDERER_ACCELERATED%20%7C%20SDL_RENDERER_PRESENTVSYNC)%3B%0A%20%20%20%20if%20(ren%20%3D%3D%20nullptr)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20SDL_DestroyWindow(win)%3B%0A%20%20%20%20%20%20%20%20logSDLError(std%3A%3Acout%2C%20%22SDL_CreateRenderer%22)%3B%0A%20%20%20%20%20%20%20%20SDL_Quit()%3B%0A%20%20%20%20%20%20%20%20return%20false%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20if(%20(IMG_Init(IMG_INIT_PNG)%20%26%20IMG_INIT_PNG)%20!%3D%20IMG_INIT_PNG)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20logSDLError(std%3A%3Acout%2C%20%22IMG_init%22)%3B%0A%20%20%20%20%20%20%20%20SDL_Quit()%3B%0A%20%20%20%20%20%20%20%20return%201%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20return%20true%3B%0A%7D%0A” message=”” highlight=”” provider=”manual”/]

We would first initialize our window and renderer pointers to nullptr, and call this function with: initializeSDL(window, renderer).

This would normally pass the copy of the data to the function, but because we specified in the function that we want a reference to a pointer(*&), it will instead pass the pointer itself without dereferencing it, this allows us to manipulate the pointer itself, in this case, filling our previously created pointers with an SDL_Window and an SDL_Renderer respectively.

 


Acceleration

Finally, we’re getting to acceleration, what was intended to be the focal point of this post!

We’ll start by just adding a constant acceleration. As before, this has the strange effect of having the bouncing image work it’s way down to the bottom right corner due to the inverting velocity, we can more easily observe what is happening by wrapping the object instead of bouncing it:

[pastacode lang=”cpp” manual=”%2F%2Fif%20(%20(position.getX()%20%3E%3D%20(SCREEN_WIDTH%20-%20iW)%20)%20%7C%20(position.getX()%20%3C%3D%200))%0A%2F%2F%20%20%20%20velocity.setX(velocity.getX()%20*-1)%3B%0A%2F%2Fif%20(%20(position.getY()%20%3E%3D%20(SCREEN_HEIGHT%20-%20iH)%20)%7C%20(position.getY()%20%3C%3D%200))%0A%2F%2F%20%20%20%20velocity.setY(velocity.getY()%20*-1)%3B%0A%0Aif%20(position.getX()%20%3E%20SCREEN_WIDTH)%0A%20%20%20%20position.setX(0)%3B%0Aelse%20if(position.getX()%20%3C%200)%0A%20%20%20%20position.setX(SCREEN_WIDTH)%3B%0A%0Aif%20(position.getY()%20%3E%20SCREEN_HEIGHT)%0A%20%20%20%20position.setY(0)%3B%0Aelse%20if%20(position.getY()%20%3C%200)%0A%20%20%20%20position.setY(SCREEN_HEIGHT)%3B” message=”” highlight=”” provider=”manual”/]

 

The object quickly accelerates to an insane pace, it should be limited, so we’ll add a limit() function to the vec2 class:

[pastacode lang=”cpp” manual=”void%20vec2%3A%3Alimit(float%20limit)%0A%7B%0A%20%20%20%20if%20(this-%3EgetMag()%20%3E%20limit)%0A%20%20%20%20%20%20%20%20this-%3EsetMag(limit)%3B%0A%7D” message=”” highlight=”” provider=”manual”/]

This just checks the current magnitude, compares it to limit and if it’s higher, sets it equal to limit.

 

This isn’t terribly useful in and of itself, but if we accelerate based on a more dynamic vector, like the mouse location, we can get some more interesting behaviour:

[pastacode lang=”cpp” manual=”vec2%20acceleration%20%3D%20vec2%3A%3Asub(mouse%2C%20position)%3B%0Aacceleration.normalize()%3B%0Aacceleration.mult(.5)%3B%0A%0Avelocity.add(acceleration)%3B%0Aposition.add(velocity)%3B%0Avelocity.limit(10)%3B” message=”” highlight=”” provider=”manual”/]

So here we had to make some changes to our vec2 class to make it more useful:

We added two static functions: one for subtraction and one for addition and we changed all instances of int to float for greater precision.

The interesting thing here are the static functions; declaring a function as static allows you to call that function from anywhere without needing to create an object in order to use it. For example, in the code snippet above we have vec2 acceleration = vec2::sub(mouse, position), this creates a vec2 object called acceleration and makes it’s values equal to mouseposition which is a vector pointing from the object position to the mouse location and with a magnitude equal to the distance between them.

After normalizing and scaling, we have an acceleration vector that will cause the image object to accelerate toward the mouse cursor at all times!

 

Whew! That was a lot to get through today, hopefully tomorrow we can start looking into applying forces on demand instead of constant accelerations as well as making this a bit more object oriented so we can do a few more interesting things! I should also be making these changes in separate branches and then merging to master for branching practice in git.

Getting Physical

Here’s where things get interesting, I will be referring to Daniel Shiffman’s work over at shiffman.net, he’s done excellent work on simple physics for programming, most of his work is done in P5.JS but it should be easily adaptable to any language.

 

First we will create a vector that contains the objects position and set it to the center of the screen, in this case we’ll be using the foreground image from the previous code as our object, but it could really be anything.

[pastacode lang=”cpp” manual=”vec2%20position%3B%0A%0Aposition.setX(SCREEN_WIDTH%20%2F%202)%3B%0Aposition.setY(SCREEN_HEIGHT%20%2F%202)%3B” message=”” highlight=”” provider=”manual”/]

 

Now that we have the position covered we’ll modify the rendering code to use the X and Y member variables of the vector position instead of the mouse cursor locations. rerunning the program we see no changes, this is good! Now to make it move we need to update it’s location at every frame by a certain amount, that amount will be it’s velocity, velocity is just a change in position over time.

We’ll Add another vector called velocity and assign it an arbitrary value of 5 to get started by using the member function setMag():

[pastacode lang=”cpp” manual=”vec2%20velocity%3B%0A%0Avelocity.setMag(5)” message=”” highlight=”” provider=”manual”/]

 

Now in the render loop, we use the add() function we created for vec2 to add the velocity to the position each frame: position.add(velocity);

Running the program we see that our image has disappeared, I’m thinking it’s just moving too fast so I’m going to reduce the velocity by an order of magnitude to .5. Still no good, let’s go a few more orders of magnitude, nope, something is definitely wrong, let’s see what happened here. I’m going to add std::cout << position.getX() << std::endl before and after the velocity is added in my code so I can see what is happening.

It looks like the program is running so fast we don’t get to see the results, I’ll add a delay to the code with SDL_Delay(100) to artificially slow it down, later on we’ll look into frame rate limiting to make this more automatic and less wasteful of CPU cycles, but this will do for now.

With the delay in place I can see that the starting position.x is 320 and then it goes to -nan, looks like a bug!

Further investigation and a few more lines printed to console shows that there is an error in the vec2 class, it has allowed us to create an empty vector!  We’ll need to modify the class to include default values if none are specified in the function call.

Adding the following constructors to the class should resolve the issue:

[pastacode lang=”cpp” manual=”vec2%3A%3Avec2()%0A%7B%0A%20%20%20%20this-%3Ex%20%3D%201%3B%0A%20%20%20%20this-%3Ey%20%3D%201%3B%0A%7D%0A%0Avec2%3A%3Avec2(int%20x%2C%20int%20y)%0A%7B%0A%20%20%20%20this-%3Ex%20%3D%20x%3B%0A%20%20%20%20this-%3Ey%20%3D%20y%3B%0A%7D” message=”” highlight=”” provider=”manual”/]

 

This should allow us to create a vector object and populate it with any values we want and, if we don’t specify any values for it, it should default to one for both Y and Y. Commenting out the setMag() function call we can see that the object is now slowly creeping down to the bottom left of the screen just as we would expect for an object moving at 1 pixel per frame! If I remove the delay we see that the object moves at a steady but fairly leisurely pace, slower than I would have expected for something with no frame rate limiting. Looking back in the code I recall that we set the renderer to sync to monitor vsync, which is 60hz, let’s disable it and see what happens.

As expected the object shot off the screen faster than could be seen, this is good, it means that we don’t really need to worry about frame rate limiting at this stage in the game, though timers will be useful for determining just how long a frame took to render, that way we can tell how much time we have to calculate everything else.

Let’s make it bounce when it hits a wall now, instead of just disappearing into the ether:

[pastacode lang=”cpp” manual=”if%20(position.getX()%20%3E%3D%20SCREEN_WIDTH%20-%20iW%20%7C%20position.getX()%20%3C%3D%200)%0A%7B%0A%20%20%20%20velocity.setX(velocity.getX()%20*-1)%3B%0A%7D%0Aif%20(position.getY()%20%3E%3D%20SCREEN_HEIGHT%20-%20iH%7C%20position.getY()%20%3C%3D%200)%0A%7B%0A%20%20%20%20velocity.setY(velocity.getY()%20*-1)%3B%0A%7D%0A” message=”” highlight=”” provider=”manual”/]

 

This simple collision code will test to see if the image is at or past the window boundaries and, if so, invert the velocity, effectively reversing it’s direction. Note also that we are subtracting the images width and height from the greater than comparison, because the object is location at a point, we need to look beyond that point to the width or height of the image to be sure it doesn’t appear to pass out of the window.

Modifying the render code will make things render correctly: renderTexture(image, renderer, position.getX(), position.getY())

All we’ve done here is omit the centering of the image on the render location, this simplifies the collision code.

 

One last simple change we can make for today is to increment and decrement the velocity with keypresses using the event handler from earlier:

[pastacode lang=”cpp” manual=”case%20SDLK_a%3A%0A%20%20%20%20velocity.setX(velocity.getX()%20%2B%201)%3B%0A%20%20%20%20velocity.setY(velocity.getY()%20%2B%201)%3B%0A%20%20%20%20break%3B%0A%0Acase%20SDLK_z%3A%0A%20%20%20%20velocity.setX(velocity.getX()%20-%201)%3B%0A%20%20%20%20velocity.setY(velocity.getY()%20-%201)%3B%0A%20%20%20%20break%3B%0A” message=”” highlight=”” provider=”manual”/]

There is an interesting bug here where the direction of the velocity is not taken into account when incrementing and decrementing which causes the object to appear to have acceleration in some instances, fun!

That’s it for today, hopefully more tomorrow!

Getting a handle on things

The event handler is a way for us to interface with the program, it will monitor for events, store them in a queue and let us iterate through them and have actions taken based on the input. This will let us use the x to close the window and end the program, among other things.

Conceptually, we can think of the the event queue as a line up of actions, we check to can see if anything is in the line up and, if so, take the oldest one from the line and do something based on the kind of action that it was. For example, if the windows was closed, we can exit the program.

 

We’ll be moving our rendering logic into a while loop that will run constantly until we tell it we want to exit by way of the event handler. SDL_PollEvent will return a 1 if there are events in the queue to be handled, so this makes checking for and processing events fairly straightforward, before we render each frame, we will check for and handle, any events that may have occurred.

First, we need to setup an SDL_event object that will store the event found when we poll the queue, we can do that by instantiating one with: SDL_Event event;

Now when we run SDL_PollEvent(&event) it will take the oldest event off the queue and put it into event so we can process it.

A simple loop would look like this:

[pastacode lang=”cpp” manual=”bool%20quit%20%3D%20false%3B%0A%0Awhile(!quit)%0A%7B%0A%0A%20%20%20%20while(SDL_PollEvent(%26event))%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20switch(event.type)%0A%20%20%20%20%20%20%20%20%7B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20case%20SDL_QUIT%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20quit%20%3D%20true%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20case%20SDL_KEYDOWN%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20quit%20%3D%20true%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%0A%2F%2FDo%20rendering%20stuff%20and%20game%20logic%20here%0A%0A%7D” message=”” highlight=”” provider=”manual”/]

This is a simplified example as it only checks if the window has been closed or any key has been pressed and then exits the program, but this the essence of what is required to handle any event and be able to exit out of an otherwise infinite game loop.

To make this a bit more selective, we could modify the code to read  like this:

[pastacode lang=”cpp” manual=”while(SDL_PollEvent(%26event))%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20switch(event.type)%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20case%20SDL_QUIT%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20quit%20%3D%20true%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20case%20SDL_KEYDOWN%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20switch%20(event.key.keysym.sym)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20case%20SDLK_q%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20quit%20%3D%20true%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20case%20SDLK_x%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20quit%20%3D%20true%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%7D%0A%7D” message=”” highlight=”” provider=”manual”/]

This code section has a switch that looks for an SDL_QUIT event or an SDL_KEYDOWN event, if it finds an SDL_KEYDOWN event it goes into another switch that checks to see if either SDLK_q (the SDL key code for ‘q’) or SDLK_x (the SDL key code for ‘x’) have been pressed and then exits the program, otherwise it ignores it. I’m sure you can see how this could be used to control any number of functions in a program or game.


Finally getting things moving!

Now with what we have learned here we can finally get something moving on the screen! With a simple modification to the code we’ve been building here thus far, we can make the foreground image that we rendered in the center of the screen now get rendered wherever the mouse pointer is in the window and have the application exit if we close the window or press ‘q’ or ‘x’ on the keyboard:

[pastacode lang=”cpp” manual=”%3CSDL%20and%20module%20init%20stuff%3E%0A%0A%20%20%20%20bool%20quit%20%3D%20false%3B%0A%0A%20%20%20%20%2F%2Fload%20textures%0A%0A%20%20%20%20SDL_Texture%20*background%20%3D%20loadTexture(%22Images%2Fbackground.png%22%2C%20renderer)%3B%0A%20%20%20%20%20%20%20%20SDL_Texture%20*image%20%3D%20loadTexture(%22Images%2Fimage.png%22%2Crenderer)%3B%0A%20%20%20%20%20%20%20%20if(background%20%3D%3D%20nullptr%20%7C%7C%20image%20%3D%3D%20nullptr)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20logSDLError(std%3A%3Acout%2C%20%22loadTexture%22)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20SDL_DestroyTexture(background)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20SDL_DestroyTexture(image)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20SDL_DestroyRenderer(renderer)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20SDL_DestroyWindow(window)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20SDL_Quit()%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%201%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%2F%2FVariables%20to%20store%20mouse%20x%20and%20y%0A%20%20%20%20%20%20%20%20int%20mouseX%20%3D%20SCREEN_WIDTH%20%2F%202%3B%0A%20%20%20%20%20%20%20%20int%20mouseY%20%3D%20SCREEN_HEIGHT%20%2F%202%3B%0A%0A%20%20%20%20%20%20%20%20int%20iW%2C%20iH%3B%0A%20%20%20%20%20%20%20%20SDL_QueryTexture(image%2C%20NULL%2C%20NULL%2C%20%26iW%2C%20%26iH)%3B%0A%20%20%20%20%20%20%20%20SDL_SetRenderDrawColor(renderer%2C%20255%2C255%2C255%2C255)%3B%20%20%2F%2FSet%20the%20clear%20colour%20to%20white.%0A%0A%20%20%20%20while(!quit)%0A%20%20%20%20%7B%0A%0A%20%20%20%20%20%20%20%20while(SDL_PollEvent(%26event))%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20switch(event.type)%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20case%20SDL_QUIT%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20quit%20%3D%20true%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20case%20SDL_KEYDOWN%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20switch%20(event.key.keysym.sym)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20case%20SDLK_q%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20quit%20%3D%20true%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20case%20SDLK_x%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20quit%20%3D%20true%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20case%20SDL_MOUSEMOTION%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mouseX%20%3D%20event.motion.x%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mouseY%20%3D%20event.motion.y%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20SDL_RenderClear(renderer)%3B%0A%20%20%20%20%20%20%20%20renderTexture(image%2C%20renderer%2C%20mouseX%20-%20iW%2F2%2C%20mouseY%20-%20iH%2F2)%3B%0A%0A%20%20%20%20%20%20%20%20SDL_RenderPresent(renderer)%3B%0A%0A%20%20%20%20%7D%0A%0A%3CSDL%20destroy%20and%20quit%20functions%3E” message=”” highlight=”” provider=”manual”/]

Next up we’ll make use of the vector class created ealier to implement a basic physics system and get an object bouncing around the screen!

More cleanup!

Now that Christmas is over with I can get back to some work (play?) on my SDL experience, todays order of business is splitting the code from the main.cpp file into several header and source files to help make everything more tidy and to also remind myself how this all works.

 

First a few introductions:

Header files declare classes and functions without defining them and are suffixed with .h, for example: header.h.

Source files define classes and functions and include header files with the #include directive, they are suffixed with .cpp, for example: source.cpp

Include Guards are used in header files to prevent the accidental inclusion of a header file twice, which will cause errors. Include guards look like this:

[pastacode lang=”cpp” manual=”%23ifndef%20HEADER_H%0A%23define%20HEADER_H%0A%0A%3Cdeclarations%20goes%20here%3E%0A%0A%23endif” message=”” highlight=”” provider=”manual”/]

If we break that down, all it’s saying is “If HEADER_H has been defined, ignore this file, otherwise define it and include all this code”. It effectively prevents us from including something multiple times.

This brings us to the #include directive. When performing an include with the #include directive we are literally asking the compiler to insert the contents of the file specified at that point in the code during compilation, with that in mind cutting out our vector class is pretty straight forward.


Getting Ahead(er)

First we take the declaration and put it into a header file:

[pastacode lang=”cpp” manual=”%23ifndef%20VEC2_H%0A%23define%20VEC2_H%0A%0A%23include%20%3Cmath.h%3E%0A%0Aclass%20vec2%0A%7B%0A%20%20%20%20float%20x%3B%0A%20%20%20%20float%20y%3B%0A%0Apublic%3A%0A%20%20%20%20float%20getX()%20%7Breturn%20x%3B%7D%3B%0A%20%20%20%20float%20getY()%20%7Breturn%20y%3B%7D%3B%0A%20%20%20%20void%20setX(float%20i)%20%7Bx%20%3D%20i%20%3B%7D%3B%0A%20%20%20%20void%20setY(float%20i)%20%7By%20%3D%20i%20%3B%7D%3B%0A%0A%20%20%20%20void%20add(vec2%20vec)%3B%0A%20%20%20%20void%20mult(int%20scaler)%3B%0A%20%20%20%20void%20sub(vec2%20vec)%3B%0A%20%20%20%20float%20getMag()%20%7Breturn%20sqrt(%20(x*x)%20%2B%20(y*y)%20)%3B%7D%3B%0A%20%20%20%20void%20setMag(float%20mag)%3B%0A%20%20%20%20void%20normalize()%3B%0A%7D%3B%0A%0A%23endif” message=”vec2.h” highlight=”” provider=”manual”/]

Note that we included the math.h library into this header file, this is because, while we don’t normally define things in header files, GetMag() returns a simple square root that is coded directly into it’s declaration.

 

Now we take the definition and put that into a .cpp file and include the vec2.h file:

[pastacode lang=”cpp” manual=”%23include%20%22vec2.h%22%0A%0Avoid%20vec2%3A%3Aadd(vec2%20vec)%0A%7B%0A%20%20%20%20this-%3Ex%20%2B%3D%20vec.x%3B%0A%20%20%20%20this-%3Ey%20%2B%3D%20vec.y%3B%0A%0A%7D%0A%0Avoid%20vec2%3A%3Amult(int%20scaler)%0A%7B%0A%20%20%20%20this-%3Ex%20*%3D%20scaler%3B%0A%20%20%20%20this-%3Ey%20*%3D%20scaler%3B%0A%7D%0A%0Avoid%20vec2%3A%3Asub(vec2%20vec)%0A%7B%0A%20%20%20%20this-%3Ex%20-%3D%20vec.x%3B%0A%20%20%20%20this-%3Ey%20-%3D%20vec.y%3B%0A%7D%0A%0Avoid%20vec2%3A%3Anormalize()%0A%7B%0A%20%20%20%20float%20mag%20%3D%20this-%3EgetMag()%3B%0A%20%20%20%20this-%3Ex%20%3D%20this-%3Ex%20%2F%20mag%3B%0A%20%20%20%20this-%3Ey%20%3D%20this-%3Ey%20%2F%20mag%3B%0A%7D%0A%0Avoid%20vec2%3A%3AsetMag(float%20mag)%0A%7B%0A%20%20%20%20this-%3Enormalize()%3B%0A%20%20%20%20this-%3Emult(mag)%3B%0A%7D%0A” message=”vec2.cpp” highlight=”” provider=”manual”/]

 

Finally we modify our main.cpp file to include the new header and remove the old implementation:

[pastacode lang=”cpp” manual=”%23include%20%3CSDL2%2FSDL.h%3E%0A%23include%20%3CSDL2%2FSDL_image.h%3E%0A%23include%20%3Ciostream%3E%0A%23include%20%3Cstring%3E%0A%23include%20%22vec2.h%22″ message=”main.cpp” highlight=”” provider=”manual”/]

Note that here we have omitted the math.h library, as it is included in the vec2.h header and is only required there so far.

A quick recompile and we can see that out code is still working as it was before, except now the main.cpp file is a little more straightforward to read and understand. You should always strive for readable, comprehensible code, your code should not need extensive commenting to be readable, if it does, it may be time for a refactor! To make this process easier, it’s generally good practice to create multiple files immediately and work that way, rather than writing everything into main and then trying to sort it out later on, a time will come when separating the code is virtually impossible!


Even More Cleanup!

Now we can remove some of the helper functions that we created to make working with SDL easier and put them into their own files.

[pastacode lang=”cpp” manual=”%23ifndef%20SDL_HELPERS_H%0A%23define%20SDL_HELPERS_H%0A%0A%23include%20%3CSDL2%2FSDL.h%3E%0A%23include%20%3CSDL2%2FSDL_image.h%3E%0A%23include%20%3Ciostream%3E%0A%0Avoid%20logSDLError(std%3A%3Aostream%20%26os%2C%20const%20std%3A%3Astring%20%26msg)%3B%0A%0Avoid%20renderTexture(SDL_Texture%20*texture%2C%20SDL_Renderer%20*renderer%2C%20int%20x%2C%20int%20y)%3B%0Avoid%20renderTexture(SDL_Texture%20*texture%2C%20SDL_Renderer%20*renderer%2C%20int%20x%2C%20int%20y%2C%20int%20w%2C%20int%20h)%3B%0A%0A%23endif%20%2F%2F%20SDL_HELPERS_H” message=”sdl_helpers.h” highlight=”” provider=”manual”/]

We had to include iostream here as it is used in the declaration of logSDLError and because these helper functions are for SDL, it makes sense that we need to include the headers here.

 

And the declarations:

[pastacode lang=”cpp” manual=”%23include%20%22sdl_helpers.h%22%0A%0Avoid%20logSDLError(std%3A%3Aostream%20%26os%2C%20const%20std%3A%3Astring%20%26msg)%0A%7B%0A%20%20%20%20os%20%3C%3C%20msg%20%3C%3C%20%22%20Error%3A%20%22%20%3C%3C%20SDL_GetError()%20%3C%3C%20std%3A%3Aendl%3B%0A%7D%0A%0ASDL_Texture*%20loadTexture(const%20std%3A%3Astring%20%26path%2C%20SDL_Renderer%20*renderer)%0A%7B%0A%20%20%20%20SDL_Texture%20*texture%20%3D%20IMG_LoadTexture(renderer%2C%20path.c_str())%3B%0A%20%20%20%20if%20(texture%20%3D%3D%20nullptr)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20logSDLError(std%3A%3Acout%2C%20%22IMG_LoadTexture%22)%3B%0A%20%20%20%20%7D%0A%20%20%20%20return%20texture%3B%0A%7D%0A%0Avoid%20renderTexture(SDL_Texture%20*texture%2C%20SDL_Renderer%20*renderer%2C%20int%20x%2C%20int%20y)%0A%7B%0A%20%20%20%20int%20w%2C%20h%3B%0A%20%20%20%20SDL_QueryTexture(texture%2C%20NULL%2C%20NULL%2C%20%26w%2C%20%26h)%3B%0A%20%20%20%20renderTexture(texture%2C%20renderer%2C%20x%2C%20y%2C%20w%2C%20h)%3B%0A%7D%0A%0Avoid%20renderTexture(SDL_Texture%20*texture%2C%20SDL_Renderer%20*renderer%2C%20int%20x%2C%20int%20y%2C%20int%20w%2C%20int%20h)%0A%7B%0A%20%20%20%20SDL_Rect%20dst%3B%0A%20%20%20%20dst.x%20%3D%20x%3B%0A%20%20%20%20dst.y%20%3D%20y%3B%0A%20%20%20%20dst.w%20%3D%20w%3B%0A%20%20%20%20dst.h%20%3D%20h%3B%0A%0A%20%20%20%20SDL_RenderCopy(renderer%2C%20texture%2C%20NULL%2C%20%26dst)%3B%0A%7D%0A” message=”sdl_helpers.cpp” highlight=”” provider=”manual”/]

 

We include the new header in main and clear out the old functions:

[pastacode lang=”cpp” manual=”%23include%20%3CSDL2%2FSDL.h%3E%0A%23include%20%3CSDL2%2FSDL_image.h%3E%0A%23include%20%3Ciostream%3E%0A%23include%20%3Cstring%3E%0A%23include%20%22sdl_helpers.h%22%0A%23include%20%22vec2.h%22%0A%0Aconst%20int%20SCREEN_WIDTH%20%3D%20640%3B%0Aconst%20int%20SCREEN_HEIGHT%20%3D%20480%3B%0A%0A%0ASDL_Texture*%20loadTexture(const%20std%3A%3Astring%20%26path%2C%20SDL_Renderer%20*renderer)%3B%0A%0A%0Aint%20main(int%20argc%2C%20char%20**argv)%0A%7B%0A%20%20%20%20if%20(SDL_Init(SDL_INIT_VIDEO)%20!%3D0)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20logSDLError(std%3A%3Acout%2C%20%22SDL%20Init%22)%3B%0A%20%20%20%20%20%20%20%20return%201%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20SDL_Window%20*window%20%3D%20SDL_CreateWindow(%22Hello%20World%22%2C%20100%2C%20100%2C%20640%2C%20480%2C%20SDL_WINDOW_SHOWN)%3B%0A%20%20%20%20if%20(window%20%3D%3D%20nullptr)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20logSDLError(std%3A%3Acout%2C%20%22SDL_CreateWindow%22)%3B%0A%20%20%20%20%20%20%20%20SDL_Quit()%3B%0A%20%20%20%20%20%20%20%20return%201%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20SDL_Renderer%20*renderer%20%3D%20SDL_CreateRenderer(window%2C%20-1%2C%20SDL_RENDERER_ACCELERATED%20%7C%20SDL_RENDERER_PRESENTVSYNC)%3B%0A%20%20%20%20if%20(renderer%20%3D%3D%20nullptr)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20SDL_DestroyWindow(window)%3B%0A%20%20%20%20%20%20%20%20logSDLError(std%3A%3Acout%2C%20%22SDL_CreateRenderer%22)%3B%0A%20%20%20%20%20%20%20%20SDL_Quit()%3B%0A%20%20%20%20%20%20%20%20return%201%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20if(%20(IMG_Init(IMG_INIT_PNG)%20%26%20IMG_INIT_PNG)%20!%3D%20IMG_INIT_PNG)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20logSDLError(std%3A%3Acout%2C%20%22IMG_init%22)%3B%0A%20%20%20%20%20%20%20%20SDL_Quit()%3B%0A%20%20%20%20%20%20%20%20return%201%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20SDL_Texture%20*background%20%3D%20loadTexture(%22Images%2Fbackground.png%22%2C%20renderer)%3B%0A%20%20%20%20SDL_Texture%20*image%20%3D%20loadTexture(%22Images%2Fimage.png%22%2Crenderer)%3B%0A%20%20%20%20if(background%20%3D%3D%20nullptr%20%7C%7C%20image%20%3D%3D%20nullptr)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20logSDLError(std%3A%3Acout%2C%20%22loadTexture%22)%3B%0A%20%20%20%20%20%20%20%20SDL_DestroyTexture(background)%3B%0A%20%20%20%20%20%20%20%20SDL_DestroyTexture(image)%3B%0A%20%20%20%20%20%20%20%20SDL_DestroyRenderer(renderer)%3B%0A%20%20%20%20%20%20%20%20SDL_DestroyWindow(window)%3B%0A%20%20%20%20%20%20%20%20SDL_Quit()%3B%0A%20%20%20%20%20%20%20%20return%201%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20SDL_RenderClear(renderer)%3B%0A%0A%20%20%20%20int%20scaler%20%3D%203%3B%0A%0A%20%20%20%20int%20bW%2C%20bH%3B%0A%20%20%20%20SDL_QueryTexture(background%2CNULL%2CNULL%2C%26bW%2C%20%26bH)%3B%0A%20%20%20%20for%20(int%20i%20%3D%200%3B%20i%20%3C%20SCREEN_HEIGHT%3B%20i%20%3D%20i%20%2B%20bH%20%2F%20scaler)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20for%20(int%20j%20%3D%200%3B%20j%20%3C%20SCREEN_WIDTH%3B%20j%20%3D%20j%20%2B%20bW%20%2F%20scaler)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20renderTexture(background%2C%20renderer%2C%20j%2C%20i%2C%20bW%20%2F%20scaler%2C%20bH%20%2F%20scaler)%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%0A%20%20%20%20int%20iW%2C%20iH%3B%0A%20%20%20%20SDL_QueryTexture(image%2C%20NULL%2C%20NULL%2C%20%26iW%2C%20%26iH)%3B%0A%20%20%20%20renderTexture(image%2C%20renderer%2C%20SCREEN_WIDTH%2F2%20-%20iW%2F2%2C%20SCREEN_HEIGHT%2F2%20-%20iH%2F2)%3B%0A%0A%20%20%20%20SDL_RenderPresent(renderer)%3B%0A%20%20%20%20SDL_Delay(2000)%3B%0A%0A%20%20%20%20SDL_DestroyTexture(background)%3B%0A%20%20%20%20SDL_DestroyTexture(image)%3B%0A%20%20%20%20SDL_DestroyRenderer(renderer)%3B%0A%20%20%20%20SDL_DestroyWindow(window)%3B%0A%20%20%20%20SDL_Quit()%3B%0A%0A%20%20%20%20return%200%3B%0A%7D%0A” message=”main.cpp” highlight=”” provider=”manual”/]

And now our main.cpp is down to 85 lines, much more manageable than what we had before, and if we need to make changes to a function, we can easily tell where to find it!

 

In the next installment we will be setting up an event handler and a game loop which will pave the way to getting some items moving around on the screen!