• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
  • entries
    6
  • comments
    0
  • views
    7897

Arkanong part 2: basic collision detection

Sign in to follow this  
Followers 0
molehill mountaineer

964 views

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

Here's how it works:

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

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

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

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

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

=> 70 Tick() calls per second

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

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

=> 30 Tick() calls per second

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

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


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

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



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


The final step in the updateAll function is to change the position of the object we're updating.
We're consuming the frameTime in steps of 0.00001. For an in depth explanation of how this works
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)

overshoot.jpgvoid 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.


border angles.png
quadrants.png


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


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

0
Sign in to follow this  
Followers 0


0 Comments


There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now