While I have done C++ before, it has been a while. So I was motivated to make a game in C++. I decided to use the library SFML because it includes everything I need.
There are still parts I wanted to change, but for now this is the final version.
To become Super Sonic, collect at least 7 chaos emeralds and 50 rings. While jumping (NOT falling, you gotta be quick) type “supersonic” or “super sonic”.
Note: You require Visual C++ Redistributable Packages for Visual Studio 2013 x86 to run this.
Threading
This is my first SFML game so I followed the tutorials first to get the basics.
I hadn’t to read far untill I came accross threads. I’ve heard about threads before but I never looked into it, after reading the short explanation by SFML I decided to give it a try! While SFML has it’s own threading class. SFML tells it’s still better to use STD::Thread if possible. So after a quick look at an example I started using it.
std::thread drawThread(Draw, &window);
By using threads, I was suspicous whether it wont be a problem when I use the same classes in my 2 threads. On my early code this didn’t seem to a problem, but soon ennough I received “access violation” at random moments. Then I learned to use a mutex.
obstaclesMutex.lock(); // now we can savely iterate through obstacles and draw them for (std::vector<Obstacle*>::iterator it = obstacles.begin(); it != obstacles.end(); ++it) (*it)->Draw(window); obstaclesMutex.unlock();
Animation
The first step was to get a sprite on the screen. By following the SFML tutorials that’s done quite quickly. But unlike unity SFML doesn’t have something for animations, so I had to make my own function for this:
void Player::UpdateAnimation(Time elaspedTime) { spriteUpdateTimer += elaspedTime.asSeconds(); if (spriteUpdateTimer >= spriteUpdateTime) { spriteUpdateTimer = 0.f; // if spritePos is higher than the frameamount go back to the first frame spritePos >= spriteStartingPoint.x + frameAmount - 1 ? spritePos = spriteStartingPoint.x : spritePos++; sprite.setTextureRect(IntRect( /* X */ spritePos * spriteSize.x, /* Y */ spriteStartingPoint.y * spriteSize.y, /* W */ spriteSize.x, /* H */ spriteSize.y)); } }
Ground
Next thing was to generate ground for Sonic to roll on. My first attempt was to use a convex shape which makes a mountain randomly going up & down
/* For every point in the world X Position stays the same, but every point (except the ones that are number maxGroundPoints & higher) will take the Y position of the next one. The last one (maxGroundPoint-1) will get a random new position. */ updateTimer -= elaspedTime.asSeconds(); if (updateTimer <= 0) { for (int i = 0; i < maxGroundPoints; i++) { if (i != maxGroundPoints - 1) ground.setPoint(i, Vector2f(ground.getPoint(i).x, ground.getPoint(i + 1).y - steepness)); else { float y = getNextYChange(ground.getPoint(i).y); ground.setPoint(i, Vector2f(ground.getPoint(i).x, ground.getPoint(i).y + y)); } } updateTimer += updateTime; }
The problem was that the convexshape sometimes seemed to deform, and because of the solid x position it seemed like it was updating in a very low FPS. So after thinking about various ideas to do this I decided to use sprites in a multi-dimensional vector instead.
for (int x = 0; x < ground.size(); x++) { for (int y = 0; y < ground[x].size(); y++) { // get the current position and calculate the x/y change that needs to be added Vector2f curPos = ground[x][y].getPosition(); Vector2f change = Vector2f(spriteSize.x * speed * -1, spriteSize.y * speed * -1); // Make sure the ground tile is in-screen, else move it to the right if (curPos.x > 0 - spriteSize.x * 2) // move to left { // if this tile is the first in line(most left) just add the change to it if (x == firstX) { ground[x][y].setPosition(curPos + change * elaspedTime.asSeconds()); } else { // if it's not the first in line get the one before it. int getX = x - 1; if (getX < 0) getX = ground.size() - 1; ground[x][y].setPosition(ground[getX][y].getPosition() + spriteSize); } } else // teleport to the right side { int lastX = x - 1; if (lastX < 0) lastX = ground.size() - 1; ground[x][y].setPosition(ground[lastX][y].getPosition() + spriteSize); firstX = x + 1; if (firstX >= ground.size()) firstX = 0; break; } } }
Collisions
I had to check collisions for the obstacles and the ground. The obstacles return a string that’s combined with them and linked to player actions. (At the time of writing this is either an obstacle that makes the player jump or an obstacle that requires no action) the string it returns is what the player types to make it happen.
I also used an iterator instead of a classic for loop, iterators were new to me. Click here for the example I used.
std::string ObstacleSpawner::CheckObstacleCollision(Vector2f position, Vector2f size) { for (std::vector<Obstacle*>::iterator it = obstacles.begin(); it != obstacles.end(); ++it) { Vector2f oPos = (*it)->GetPosition(); if (position.x > oPos.x - size.x && position.y > oPos.y - size.y) // position x to the right of left side & position y below the top check { if (position.x < oPos.x + size.x && position.y < oPos.y + size.y) // position x to the left of right side & position y above the bottom check { // collision! (*it)->hit = true; if ((*it)->audio != "" && playSfx) { sb.loadFromFile((*it)->audio); sound.setBuffer(sb); sound.play(); } return (*it)->GetString(); } } } return ""; }
For the ground collision (used to know when player is back on the ground after jumping) it’s different. The ground sprites form a triangle, so to check for collision I draw a triangle based on the player x point.
Because I only know 1 length (player x) and all degrees of the triangle I can’t use pythagoras…
Vector2f Ground::UpdateTriangle(float x) { // http://stackoverflow.com/questions/1727881/how-to-use-the-pi-constant-in-c float pi = std::atanf(1.0) * 4; // lijn schuine zijde / line hypotenuse | rotation: 45 debuglines[0].setSize(Vector2f(x / sinf(45 * pi / 180), 5)); // overstaande zijde / opposite side | rotation: 90 debuglines[1].setSize(Vector2f(x * tanf(45 * pi / 180), 5)); // aanliggende zijde / adjacent side | rotation: 0 debuglines[2].setSize(Vector2f(x, 5)); debuglines[2].setPosition(Vector2f(5, debuglines[1].getSize().x + groundYstartingPoint)); return Vector2f(debuglines[1].getPosition().x + debuglines[1].getSize().x, debuglines[2].getPosition().x + debuglines[2].getSize().x); }
Input
While in unity you can ask for input at any time. At SFML this needs to be done through the event system. So in case when there’s text entered I’ll send this to the input class
case Event::TextEntered: // If unicode is in the ASCII range and input isn't paused we'll send it to our input class. if (event.text.unicode < 128 && !input.paused) input.AddInput(static_cast<char>(event.text.unicode)); break;
Then, in the input class I compare it with the word the player has to type and I handle any ‘action word’ (where the player should jump for example)
To the above code the part “static_cast<char>” is something I’ve never seen before. I learned that this was a feature introduced in C++ to have a saver form of casting compared to how you do it at the C language: “(char)”.
Settings.ini
For a school assignment I had to add a .ini file to a game of mine. I’d liked to do that for this game, so people can change stuff like the resolution.
std::string ExePath() // http://stackoverflow.com/questions/875249/how-to-get-current-directory { char * buffer = new char[MAX_PATH]; GetModuleFileNameA(NULL, buffer, MAX_PATH); std::string::size_type pos = std::string(buffer).find_last_of("\\/"); return std::string(buffer).substr(0, pos); } void LoadConfig() { // I'm using microsoft docs here http://msdn.microsoft.com/en-us/library/windows/desktop/ms724345(v=vs.85).aspx // According to microsoft docs it'll use windows directory as default so I need to get the path of the executable. std::string file = ExePath() + "\\settings.ini"; windowSize.x = GetPrivateProfileIntA("SCREEN", "width", windowSize.x, file.c_str()); windowSize.y = GetPrivateProfileIntA("SCREEN", "height", windowSize.y, file.c_str()); char* result = new char[255]; GetPrivateProfileStringA("SCREEN", "fullscreen", "false", result, 255, file.c_str()); if (strcmp(result, "true") == 0) windowStyle = Style::Fullscreen; GetPrivateProfileStringA("SOUND", "bgm", "on", result, 255, file.c_str()); if (strcmp(result, "off") == 0) playbgm = false; GetPrivateProfileStringA("SOUND", "sfx", "on", result, 255, file.c_str()); if (strcmp(result, "off") == 0) playsfx = false; GetPrivateProfileStringA("DEBUG", "groundcollision", "on", result, 255, file.c_str()); if (strcmp(result, "on") == 0) ground.showDebug = true; }
Overriding Player Actions
When I wanted the player to be able to jump I started using a enumeration of player actions and a long function that updated every single one of those in the player class.
I thought this was simply starting to be too big, so I’ve made a simple class to make player actions from
#pragma once #include <SFML/Graphics.hpp> class Player; using namespace sf; class PlayerAction { public: PlayerAction(); virtual void Update(Time elaspedTime) { } Player * player; };
In the player class there’s now simply a playerAction variable which calls the update function. When setting the player action I set this variable to a class that overrides PlayerAction.
Example:
#pragma once #include "PlayerAction.h" using namespace sf; class RollingAction : public PlayerAction { public: RollingAction(Player * p); void Update(Time elaspedTime); };