Push Button, Receive Code

Programming tutorials, games, music...free beer.

SFML Platformer in Less Than 1 Million lines Part 1

Posted by Mark on Monday February 16th, 2015 at 3:00pm

 

First of all, I'd like to give a special thanks to Alex Diener for the tutorial on collision detection. Because he originally took the pains to write a tutorial explaining collision detection and response, and gave away working code at his own expense, I was able to write this platformer tutorial. Here is some footage of the final result:

If you'd just like to take it for a spin and see what it's all about, just download the binary for Windows. Otherwise, the source has been uploaded to github.

That said, let's get started shall we? First thing is first, I adapted the collision detection code to be object oriented, and added a couple things to procure information about collisions as they happen. Whether or not it was a good idea, I'll let you make that call - but it's helped me wrap my mind around how it all works (and does NOT work). Because the bulk of the code is already explained in great detail by Alex (link above) I will just show the header files for the relevant objects. The following is based on the original collision detection and response functions written by Alex (most of the code is practically untouched for the methods):

/**
*	\brief Copyright (c) 2011 Alex Diener
*
*	This software is provided 'as-is', without any express or implied
*	warranty. In no event will the authors be held liable for any damages
*	arising from the use of this software.
*
*	Permission is granted to anyone to use this software for any purpose,
*	including commercial applications, and to alter it and redistribute it
*	freely, subject to the following restrictions:
*
*	1. The origin of this software must not be misrepresented; you must not
*	claim that you wrote the original software. If you use this software
*	in a product, an acknowledgment in the product documentation would be
*	appreciated but is not required.
*	2. Altered source versions must be plainly marked as such, and must not be
*	misrepresented as being the original software.
*	3. This notice may not be removed or altered from any source distribution.
*
*	Alex Diener adiener@sacredsoftware.net
*/

#ifndef COLLISION_SYSTEM_HPP
#define COLLISION_SYSTEM_HPP

////////////////////////////////////////////////////////////
/// \brief This class is based on the code
/// initially created by [Alex Diener] in order to
/// increase functionality and provide an object-oriented interface.
/// This includes the addition of different types of bodies
/// in order to create a more robust platformer collision system.
/// It also allows for the implementation of different types of platforms
/// such as conveyor belts, breakable, falling, and moving.
/// This is accomplished by sharing more information about
/// collisions and platforms with the main program.
/// [Alex Diener] : (http://ludobloom.com/tutorials/collision.html/ "Collision Detection")
////////////////////////////////////////////////////////////

#include <stdlib.h>
#include <algorithm>
#include <stdbool.h>
#include <stdio.h>
#include <dynamicBody.hpp>
#include <platformBody.hpp>
#include <iostream>

namespace phys
{
	struct collision{
		float time = 0.f;
		float surfaceArea = 0.f;
		int axis = 0;
	};

    struct collisionInfo{
        bool m_collisionTop = false;
	    bool m_collisionBottom = false;
	    bool m_collisionLeft = false;
	    bool m_collisionRight = false;
    };

    enum bodyType{
        none = 0,
        platform = 1,
        conveyorBelt = 2,
        moving = 3,
        jumpthrough = 4,
        falling = 5,
        vanishing = 6
    };

    struct bodyInfo{
        unsigned int m_type = phys::bodyType::none;
	    unsigned int m_id = 0;
	    float m_width = 0.f;
	    float m_height = 0.f;
	    bool m_collisionTop = false;
	    bool m_collisionBottom = false;
	    bool m_collisionLeft = false;
	    bool m_collisionRight = false;
	    bool m_falling = false;
	    sf::Vector2f m_position = sf::Vector2f(0,0);
        float m_surfaceVelocity = 0.f;
    };

	class collisionSystem
	{
	public:
		collisionSystem();
		~collisionSystem();

		void resolveCollisions(class dynamicBody * dynamicBody, class platformBody * platformBodies, int numberOfPlatformObjects);
        void setBodyInfo(platformBody& platform);
        bodyInfo getBodyInfo(){return m_bodyInfo;};

        void setCollisionInfo(bool t, bool b, bool l, bool r);
        collisionInfo getCollisionInfo(){return m_collisionInfo;};

	private:
		static bool movingToStaticIntersectionTest(class dynamicBody * dynamicBody, class platformBody platformBody, struct collision * outCollision);
		static void resolveStaticCollision(class dynamicBody * dynamicBody, struct collision collision, float timesliceRemaining);

	public:
        static bodyInfo m_bodyInfo;
        static collisionInfo m_collisionInfo;
        static bool m_collisionTop;
        static bool m_collisionBottom;
        static bool m_collisionLeft;
        static bool m_collisionRight;
	};
}

#endif //COLLISION_SYSTEM_HPP

At the heart of most of this are a handful of methods. The most important of which are the movingToStaticIntersectionTest(), resolveStaticCollision(), and resolveCollisions() methods. These are the original functions written to detect and resolve collisions between moving and static objects. You will notice at the top there are includes for "dynamicBody.hpp" and "platformBody.hpp". These are the types of "physics" bodies collisions will be detected for. Even in its current state, is almost pure C++ - the only exceptions are sf::Vector2f variables because it is convenient. If you wanted to decouple the system completely from SFML, it's as simple as writing your own vector classes. Have a look at both headers for dynamic bodies and platform bodies:

#ifndef DYNAMICBODY_HPP
#define DYNAMICBODY_HPP

////////////////////////////////////////////////////////////
/// \brief This class was created to complement the code
/// initially created by [Alex Diener] in order to
/// increase functionality.
/// [Alex Diener] : (http://ludobloom.com/tutorials/collision.html/ "Collision Detection")
////////////////////////////////////////////////////////////

#include <SFML/System/Vector2.hpp>

namespace phys
{
	class dynamicBody
	{
	public:
		dynamicBody();
		~dynamicBody();

	public:
		int m_moveX;
		int m_moveY;
		sf::Vector2f m_lastPosition;
		sf::Vector2f m_position;
		sf::Vector2f m_velocity;
		float m_width;
		float m_height;
	};
}

#endif //DYNAMICBODY_HPP

#ifndef PLATFORMBODY_HPP
#define PLATFORMBODY_HPP

////////////////////////////////////////////////////////////
/// \brief This class was created to complement the code
/// initially created by [Alex Diener] in order to
/// increase functionality.This includes the
/// implementation of different types of platforms
/// such as conveyor belts, breakable, falling, and rotating.
/// [Alex Diener] : (http://ludobloom.com/tutorials/collision.html/ "Collision Detection")
////////////////////////////////////////////////////////////

#include <SFML/System/Vector2.hpp>
#include <SFML/System/Clock.hpp>

/**
* phys namespace to encapsulate all physics-related code.
*/
namespace phys
{
	class platformBody
	{
	public:

		////////////////////////////////////////////////////////////
		/// \brief Constructor
		///
		/// Creates a platform body, defaults to a static body (m_type = 0).
		/// Width and height are set to 32 by default, positional
		/// attributes are initialized at sf::Vector2f(0,0).
		///
		////////////////////////////////////////////////////////////
		platformBody();

		////////////////////////////////////////////////////////////
		/// \brief Default destructor
		///
		////////////////////////////////////////////////////////////

		~platformBody();

	public:

		////////////////////////////////////////////////////////////
		/// \brief Member data
		////////////////////////////////////////////////////////////

        unsigned int m_id;///< unique identifier for the platform
		sf::Vector2f m_position;///< Coordinates describing the position of the platform body
		float m_width;///< Width of the platform body
		float m_height;///< Height of the platform body
		int m_type;///< The type of platform body
		bool m_collisionTop;///< Collision information - top
		bool m_collisionBottom;///< Collision information - bottom
		bool m_collisionLeft;///< Collision information - left
		bool m_collisionRight;///< Collision information - right
		bool m_falling;///< Flag for a falling platform
		float m_surfaceVelocity;///< The velocity interacting with objects in contact with the surface
	};
}

#endif //PLATFORMBODY_HPP

Ultimitely, physics (collision detection and response in this case) is decoupled from the graphical representation of your game objects. This is usually a good thing for many reasons - the most obvious reason is it makes the interfaces for your game objects much cleaner and it is clear that they are intended to do very specific things. You could inherit from platformBody and dynamicBody and make different objects and even tie them to SFML sprites and geometry, but that is probably not a great design decision. Thinking ahead, when the number of objects in the game increase by a lot, you will want to slice the data up into processable chunks so that your game doesn't grind to a halt. In its current state, you could do just that. I'm sure you've heard of things like "spatial partitioning", which require you to manage which objects the collision system "sees" and manages. If you inherit from platformBody or dynamicBody, you make this type of performance improvement more complicated at the very least. You are better off, IMHO, storing your platform bodies in contiguious chunks, and your platform tiles or geometry in separate contiguous chunks. That said, these three class definitions form the blueprint for how the simulation will behave, and SFML objects will form, as put by Erin Cato for Box2D, the moving billboards attached to the simulated bodies. Hopefully this is all making sense. In the next installment of this series, I will step through a fairly short example using these classes, and some other code I had lying around to lay the groundwork for building a platformer. As always, thanks for reading!