Skip to content

Instantly share code, notes, and snippets.

/Camera.cpp Secret

Created March 16, 2017 00:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/2af3c35d568324fb0a6ce8039ef42a64 to your computer and use it in GitHub Desktop.
Save anonymous/2af3c35d568324fb0a6ce8039ef42a64 to your computer and use it in GitHub Desktop.
#include "Camera.h"
Camera::Camera()
{
// Set size of camera
camera.reset(sf::FloatRect(0.0f, 0.0f, (float)_screenDimensions.x, (float)_screenDimensions.y));
// Set aspect ratio
camera.setViewport(sf::FloatRect(0.0f, 0.0f, 1.0f, 1.0f));
// Set position
cameraPosition = sf::Vector2f(_screenDimensions.x / 2.0f, _screenDimensions.y / 2.0f);
}
sf::View &Camera::getView()
{
return camera;
}
void Camera::update(Player &player)
{
// Check if player position is half way across the screen
if (player.getPosition().x + 10.0f > _screenDimensions.x / 2.0f)
cameraPosition.x = player.getPosition().x + 10.0f;
else
cameraPosition.x = _screenDimensions.x / 2.0f;
// Check if player position is half way down the screen
if (player.getPosition().y - 10.0f > _screenDimensions.y / 2.0f)
cameraPosition.y = player.getPosition().y - 10.0f;
else
cameraPosition.y = _screenDimensions.y / 2.0f;
// Move the center
camera.setCenter(cameraPosition);
}
#pragma once
#include <SFML/Graphics.hpp>
#include "Globals.h"
#include "Player.h"
class Camera
{
private:
sf::View camera;
sf::Vector2f cameraPosition;
public:
Camera();
// Getters & Setters
sf::View &getView();
// Game loop methods
void update(Player &player);
};
#include "Game.h"
// Split constructor (let it call initialize and load)
Game::Game(unsigned int width, unsigned int height, std::string windowTitle)
{
// Set the width, height, & title
_screenDimensions.x = width;
_screenDimensions.y = height;
this->windowTitle = windowTitle;
// Create the window
window.create(sf::VideoMode(_screenDimensions.x, _screenDimensions.y), windowTitle);
window.setMouseCursorVisible(true);
// Frame stuff
window.setFramerateLimit(60);
window.setVerticalSyncEnabled(false);
// Fixed time-step
timePerTick = sf::seconds(1.0f / 60.0f); // Ticks per second
timeSinceLastUpdate = sf::Time::Zero; // Time since last tick
frameTime = sf::Time::Zero; // Change in time
alpha = 0.0f; // Time between frames
this->loadContent(); // Load content
}
void Game::loadContent()
{
// Get fonts
robotoFont.loadFromFile("Resources/Fonts/Roboto.ttf");
// Set FPS text
fps.setFont(robotoFont);
fps.setCharacterSize(16);
fps.setPosition(10.0f, 10.0f);
// Set position text
posX.setFont(robotoFont);
posX.setCharacterSize(16);
posX.setPosition(10.0f, 30.0f);
posY.setFont(robotoFont);
posY.setCharacterSize(16);
posY.setPosition(10.0f, 50.0f);
// Camera settings
camera = Camera();
// Load the player spritesheet (use initialization list in Game : if want to use constructor)
player.initialize("Resources/Sprites/Skeleton.png", 4, 9, sf::Vector2f(100.0f, 100.0f), 300.0f);
// Tilemaps
tileMap.load("Resources/Maps/LevelTest.xml");
}
/*\ //*******************************************
|*|
|*| GAME LOOP
|*| - run() Handles events
|*| - update() Handles game updates
|*| - draw() Renders everything
|*|
\*/ //*******************************************
void Game::run()
{
while (window.isOpen())
{
// Create event
sf::Event event;
// Handle events
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
if (event.type == sf::Event::KeyPressed)
{
// Close game on Escape
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))
window.close();
// Reload tile
if (sf::Keyboard::isKeyPressed(sf::Keyboard::L))
tileMap.load("Resources/Maps/LevelTest.xml");
}
}
// Updates
update();
// Render
draw();
}
}
void Game::update()
{
// Get the time since this method was last called
frameTime = clock.restart();
timeSinceLastUpdate += frameTime;
// Update per tick (around 120 ticks per second)
while (timeSinceLastUpdate > timePerTick)
{
timeSinceLastUpdate -= timePerTick;
// Update FPS
fps.setString("FPS: " + std::to_string((int)(1.0f / frameTime.asSeconds())));
// Update player
player.update(timePerTick.asSeconds());
// Update the camera
camera.update(player);
posX.setString("X: " + std::to_string((int)player.getPosition().x));
posY.setString("Y: " + std::to_string((int)player.getPosition().y));
}
// Anything that needs to be updated every frame goes here. Otherwise put updates inside above loop!
}
void Game::draw()
{
// Extra time between each tick
alpha = timeSinceLastUpdate / timePerTick;
// Clear everything
window.clear();
// Follow camera
window.setView(camera.getView());
window.draw(tileMap);
player.draw(window, alpha);
// Don't follow camera
window.setView(window.getDefaultView());
window.draw(fps);
window.draw(posX);
window.draw(posY);
// Show everything
window.display();
}
#pragma once
#include <SFML/Graphics.hpp>
#include <pugixml.hpp>
#include "Globals.h"
#include "Camera.h"
#include "TileMap.h"
#include "Player.h"
#include "WorldObject.h"
class Game
{
private:
// Game window
sf::RenderWindow window;
std::string windowTitle;
// Fixed time-step
sf::Clock clock;
sf::Time timePerTick;
sf::Time timeSinceLastUpdate;
sf::Time frameTime;
float alpha;
// Camera fields
Camera camera;
// TileMaps
TileMap tileMap;
// FPS display
sf::Font robotoFont;
sf::Text fps, posX, posY;
// TODO: Make an Entity class
Player player;
// Private methods
void loadContent();
void update();
void draw();
public:
Game(unsigned int width, unsigned int height, std::string windowTitle);
// Game loop
void run();
};
#include "Player.h"
Player::Player() { }
Player::Player(std::string filename, int rows, int columns, sf::Vector2f position, float moveSpeed)
{
this->initialize(filename, rows, columns, position, moveSpeed);
}
void Player::initialize(std::string filename, int rows, int columns, sf::Vector2f position, float moveSpeed)
{
// Load the texture
if (!texture.loadFromFile(filename))
std::cout << "Could not load " << filename << std::endl;
// Attach to player sprite
sprite.setTexture(texture);
// Sheet dimensions
sheetWidth = texture.getSize().x;
sheetHeight = texture.getSize().y;
// Set the position
crntPosition = sf::Vector2f(position.x, position.y);
// Get the sprite width and height
spriteWidth = sheetWidth / columns;
spriteHeight = sheetHeight / rows;
// Set the source frame
source.x = 0;
source.y = Right;
// Sprite frame
frameCounter = 0.0f;
frameSwitch = 100.0f;
frameSpeed = 1000.0f;
this->moveSpeed = moveSpeed;
}
sf::Vector2f &Player::getPosition()
{
return crntPosition;
}
/* ===========================================================
* HELPER METHODS
* ======================================================== */
void Player::moveFrame(float &timePerTick)
{
if (source.y != previousRow)
source.x = 0;
frameCounter += frameSpeed * timePerTick;
if (frameCounter >= frameSwitch)
{
source.x++;
if (source.x * spriteWidth >= sheetWidth)
source.x = 0;
frameCounter = 0;
}
}
void Player::resetFrame()
{
//bool upKey = sf::Keyboard::isKeyPressed(sf::Keyboard::Up);
//bool downKey = sf::Keyboard::isKeyPressed(sf::Keyboard::Down);
//bool leftKey = sf::Keyboard::isKeyPressed(sf::Keyboard::Left);
//bool rightKey = sf::Keyboard::isKeyPressed(sf::Keyboard::Right);
// If no movement keys are pressed
if (!sf::Keyboard::isKeyPressed(sf::Keyboard::Up) && !sf::Keyboard::isKeyPressed(sf::Keyboard::Down) &&
!sf::Keyboard::isKeyPressed(sf::Keyboard::Left) && !sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
{
source.x = 0;
}
}
/* ===========================================================
* GAME LOOP
* ======================================================== */
void Player::update(float timePerTick)
{
// Get the previous position/direction
prevPosition = crntPosition;
previousRow = source.y;
// Try to reset frame if possible
resetFrame();
// Check which key is pressed
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
{
source.y = Up;
moveFrame(timePerTick);
crntPosition.y -= moveSpeed * timePerTick;
}
else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
{
source.y = Down;
moveFrame(timePerTick);
crntPosition.y += moveSpeed * timePerTick;
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
{
source.y = Left;
moveFrame(timePerTick);
crntPosition.x -= moveSpeed * timePerTick;
}
else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
{
source.y = Right;
moveFrame(timePerTick);
crntPosition.x += moveSpeed * timePerTick;
}
// TODO: Normalize speed when going diagonal
}
void Player::draw(sf::RenderWindow &window, float &alpha)
{
// Interpolate movement
sprite.setPosition(crntPosition * alpha + prevPosition * (1.0f - alpha));
// Create rectangle in sheet around a sprite
sprite.setTextureRect(sf::IntRect(source.x * spriteWidth, source.y * spriteHeight, spriteWidth, spriteHeight));
// Draw the sprite
window.draw(sprite);
}
#pragma once
#include <iostream>
#include <SFML/Graphics.hpp>
class Player
{
private:
sf::Texture texture;
sf::Sprite sprite;
sf::Vector2i source;
sf::Vector2f prevPosition, crntPosition;
unsigned int rows, columns;
unsigned int spriteWidth, spriteHeight;
unsigned int sheetWidth, sheetHeight;
float moveSpeed, frameCounter, frameSwitch, frameSpeed;
unsigned int previousRow;
// Corresponds to the row in the sprite sheet
enum Direction { Up, Left, Down, Right };
// Private methods
void moveFrame(float &timePerTick);
void resetFrame();
public:
Player();
Player(std::string filename, int rows, int columns, sf::Vector2f position, float moveSpeed = 200.0f);
void initialize(std::string filename, int rows, int columns, sf::Vector2f position, float moveSpeed = 200.0f);
// Getters & Setters
sf::Vector2f &getPosition();
// Game loop methods
void update(float timePerTick);
void draw(sf::RenderWindow &window, float &alpha);
};
#include "TileMap.h"
TileMap::TileMap() {}
bool TileMap::load(const char* xmlDocument)
{
// Read Level from XML
if (!readXML(xmlDocument))
return false;
// Load the tileset texture
if (!tileSheet.loadFromFile(filepath))
return false;
// Get size of a tile
tileWidth = tileSheet.getSize().x / sheetColumns;
tileHeight = tileSheet.getSize().y / sheetRows;
// Intitalize the vertex arrays
for (unsigned int i = 0; i < layers; i++)
{
sf::VertexArray vertexLayer;
// Resize the vertex array to fit the level size
vertexLayer.setPrimitiveType(sf::Quads);
vertexLayer.resize(worldWidth * worldHeight * 4);
// Add to whole vertex array vector
vertexArray.push_back(vertexLayer);
// Set the points and textures
setCoords(map[i], vertexArray[i]);
}
// Tilemap loaded successfully
return true;
}
// Set the VertexArray corners and texture coordinates
void TileMap::setCoords(std::vector<int> &mapLayer, sf::VertexArray &vertexLayer)
{
// Populate the vertex array, with one quad per tile
for (unsigned int i = 0; i < worldWidth; i++)
{
for (unsigned int j = 0; j < worldHeight; j++)
{
// Get the current tile number (Does 1 column at a time)
int tileNumber = mapLayer[i + j * worldWidth];
// Find its position in the tileset texture
int tileRow = tileNumber / (tileSheet.getSize().x / tileWidth);
int tileCol = tileNumber % (tileSheet.getSize().x / tileWidth);
// Get a pointer to the current tile's quad
sf::Vertex* quad = &vertexLayer[(i + j * worldWidth) * 4];
/*\
|*| Define its 4 corners
|*|
|*| 0 ----- 1 This takes care of where and how the quad will
|*| | | appear in the world. Adding or subtracting in the
|*| | | X or Y will change the size/shape.
|*| 3 ----- 2
\*/
quad[0].position = sf::Vector2f((float)(i * tileWidth), (float)(j * tileHeight));
quad[1].position = sf::Vector2f((float)((i + 1) * tileWidth), (float)(j * tileHeight));
quad[2].position = sf::Vector2f((float)((i + 1) * tileWidth), (float)((j + 1) * tileHeight));
quad[3].position = sf::Vector2f((float)(i * tileWidth), (float)((j + 1) * tileHeight));
/*\
|*| Define its 4 texture coordinates
|*|
|*| 0 ------- 1 Offsets => Top(T), Right(R), Bottom(B), Left(L)
|*| | T |
|*| | L R | Offsets will be subtracted from the texture.
|*| | B | They are not required if you want the whole texture.
|*| 3 ------- 2
\*/
quad[0].texCoords = sf::Vector2f((float)((tileCol * tileWidth) - leftOffset), (float)((tileRow * tileHeight) - topOffset));
quad[1].texCoords = sf::Vector2f((float)(((tileCol + 1) * tileWidth) - rightOffset), (float)((tileRow * tileHeight) - topOffset));
quad[2].texCoords = sf::Vector2f((float)(((tileCol + 1) * tileWidth) - rightOffset), (float)(((tileRow + 1) * tileHeight) - bottomOffset));
quad[3].texCoords = sf::Vector2f((float)((tileCol * tileWidth) - leftOffset), (float)(((tileRow + 1) * tileHeight) - bottomOffset));
}
}
}
// Parse the level xml document
bool TileMap::readXML(const char* xmlDocument)
{
// Used for tokenizing the level array
char *token, *nextToken;
// Clear the vector for reloading
map.clear();
// Load the xml file
pugi::xml_parse_result result = document.load_file(xmlDocument);
// Check if loaded correctly
if (!result)
return false;
// Get the fields from the document
filepath = document.first_element_by_path("Level/Tileset", '/').text().as_string();
sheetRows = document.first_element_by_path("Level/SheetRows", '/').text().as_uint();
sheetColumns = document.first_element_by_path("Level/SheetColumns", '/').text().as_uint();
worldWidth = document.first_element_by_path("Level/WorldWidth", '/').text().as_uint();
worldHeight = document.first_element_by_path("Level/WorldHeight", '/').text().as_uint();
topOffset = document.first_element_by_path("Level/TopOffset", '/').text().as_float();
bottomOffset = document.first_element_by_path("Level/BottomOffset", '/').text().as_float();
leftOffset = document.first_element_by_path("Level/LeftOffset", '/').text().as_float();
rightOffset = document.first_element_by_path("Level/RightOffset", '/').text().as_float();
layers = document.first_element_by_path("Level/Layers").text().as_uint();
// Read all the map layers
for (unsigned int i = 0; i < layers; i++)
{
std::vector<int> tiles; // Vector of tiles for a single layer
std::string layer = std::to_string(i + 1); // Get the correct layer number
// Get all the children of <Map layer="#">
pugi::xml_node rows = document.root().first_child().find_child_by_attribute("layer", layer.c_str());
// Loop through the rows and get the tile numbers
for each (pugi::xml_node row in rows.children())
{
// Tokenize the row
token = strtok_s((char*)row.text().as_string(), " ", &nextToken);
while (token)
{
// Add tile number ('x' means no tile)
if (*token == 'x')
tiles.push_back(-1);
else
tiles.push_back(std::stoi(token));
// Get next token
token = strtok_s(NULL, " ", &nextToken);
}
}
// Add layer to the map
map.push_back(tiles);
}
// XML file read successfully
return true;
}
// Draw each vertex array
void TileMap::draw(sf::RenderTarget& target, sf::RenderStates states) const
{
// Apply the transforms
states.transform *= getTransform();
// Apply the tileset texture
states.texture = &tileSheet;
// Draw the vertex arrays
for (unsigned int i = 0; i < vertexArray.size(); i++)
target.draw(vertexArray[i], states);
}
#pragma once
#include <string>
#include <SFML/Graphics.hpp>
#include <pugixml.hpp>
class TileMap : public sf::Drawable, public sf::Transformable
{
private:
sf::Texture tileSheet;
std::string filepath;
// Holds the tiles
std::vector<std::vector<int>> map;
std::vector<sf::VertexArray> vertexArray;
// Level fields
unsigned int layers;
unsigned int sheetRows, sheetColumns;
unsigned int worldWidth, worldHeight;
unsigned int tileWidth, tileHeight;
// Offsets in sheet (transparent spaces usually)
float topOffset, bottomOffset, leftOffset, rightOffset;
// XML Document
pugi::xml_document document;
public:
TileMap();
bool load(const char* xmlDocument);
private:
void setCoords(std::vector<int> &mapLayer, sf::VertexArray &vertexLayer);
bool readXML(const char* xmlDocument);
virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment