Skip to content
Snippets Groups Projects
Commit d2a40a8b authored by Filip Strömbäck's avatar Filip Strömbäck
Browse files

Initial commit

parents
No related branches found
No related tags found
No related merge requests found
Showing with 666 additions and 0 deletions
#pragma once
#include "state.h"
#include "world.h"
class Game_State : public State {
public:
/**
* Create it.
*/
Game_State();
/**
* Tick all game elements.
*/
shared_ptr<State> tick(sf::Time delta) override;
/**
* Draw all game elements.
*/
void render(sf::RenderWindow &to) override;
private:
/**
* The game world.
*/
World world;
};
inheritance/green.png

1.24 KiB

#include "common.h"
#include "menu_state.h"
const size_t width = 1024;
const size_t height = 768;
int main() {
sf::RenderWindow window{sf::VideoMode{width, height}, "Demo"};
window.setKeyRepeatEnabled(false);
window.setVerticalSyncEnabled(true);
State::run(window, std::make_shared<Menu_State>(nullptr));
return 0;
}
#include "menu_state.h"
#include "game_state.h"
Menu_State::Menu_State(shared_ptr<State> resume)
: selected(0), enter_pressed(false), delay(sf::milliseconds(300)) {
font.loadFromFile("font.ttf");
if (resume) {
add("Resume", [resume]() { return resume; });
background = resume;
}
add("New game", []() { return std::make_shared<Game_State>(); });
add("Exit", []() { return std::make_shared<Exit_State>(); });
}
void Menu_State::add(const string &text, Action action) {
entries.push_back({ sf::Text{text, font, 60}, 0.0f, action });
}
void Menu_State::on_key_press(sf::Keyboard::Key key) {
switch (key) {
case sf::Keyboard::Down:
if (selected + 1 < entries.size())
selected++;
break;
case sf::Keyboard::Up:
if (selected > 0)
selected--;
break;
case sf::Keyboard::Return:
enter_pressed = true;
break;
default:
break;
}
}
shared_ptr<State> Menu_State::tick(sf::Time time) {
float diff = float(time.asMicroseconds()) / float(delay.asMicroseconds());
for (size_t i = 0; i < entries.size(); i++) {
Entry &e = entries[i];
if (i == selected) {
e.state += diff;
if (e.state > 1.0f)
e.state = 1.0f;
} else {
e.state -= diff;
if (e.state < 0.0f)
e.state = 0.0f;
}
}
if (enter_pressed)
return entries[selected].action();
else
return nullptr;
}
void Menu_State::render(sf::RenderWindow &to) {
if (background)
background->render(to);
float y{100};
auto windowSize = to.getSize();
for (auto &e : entries) {
auto bounds = e.text.getLocalBounds();
e.text.setPosition((windowSize.x - bounds.width) / 2, y);
y += bounds.height * 2.0f;
int state = static_cast<int>(255 * e.state);
e.text.setFillColor(sf::Color(state, 255, state));
to.draw(e.text);
}
}
#pragma once
#include "state.h"
#include <functional>
/**
* State responsible for showing either the start menu or the pause menu.
*/
class Menu_State : public State {
public:
/**
* Create the state. If 'resume' is set, the menu will show a "resume"
* option to return to that state.
*/
Menu_State(shared_ptr<State> resume = nullptr);
/**
* Handle key presses.
*/
void on_key_press(sf::Keyboard::Key key) override;
/**
* Tick.
*/
shared_ptr<State> tick(sf::Time time) override;
/**
* Render.
*/
void render(sf::RenderWindow &drawTo) override;
private:
/**
* What to do when an item is selected.
*/
using Action = std::function<shared_ptr<State>()>;
/**
* Menu item.
*/
struct Entry {
/**
* Text to draw.
*/
sf::Text text;
/**
* Determine the color (i.e. how selected it is). 0-1.
*/
float state;
/**
* Action to perform when selected.
*/
Action action;
};
/**
* Font to use.
*/
sf::Font font;
/**
* All menu entries.
*/
vector<Entry> entries;
/**
* Which one is currently selected?
*/
size_t selected;
/**
* Was the enter key pressed?
*/
bool enter_pressed;
/**
* Animation delay.
*/
sf::Time delay;
/**
* State to use as a background (if any).
*/
shared_ptr<State> background;
/**
* Helper to add an element.
*/
void add(const string &text, Action action);
};
#include "player.h"
#include "world.h"
#include "enemy.h"
Player::Player(sf::Vector2f position)
: Textured_Object(position, "player.png"), speed{300.0f} {
shield.setRadius(radius * 2);
shield.setOrigin(radius * 2, radius * 2);
shield.setFillColor(sf::Color(255, 0, 0, 128));
}
static sf::Vector2f find_direction() {
sf::Vector2f direction;
if (sf::Keyboard::isKeyPressed(sf::Keyboard::W) || sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
direction.y -= 1;
if (sf::Keyboard::isKeyPressed(sf::Keyboard::S) || sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
direction.y += 1;
if (sf::Keyboard::isKeyPressed(sf::Keyboard::A) || sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
direction.x -= 1;
if (sf::Keyboard::isKeyPressed(sf::Keyboard::D) || sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
direction.x += 1;
float len = sqrt(pow(direction.x, 2) + pow(direction.y, 2));
if (len > 0.0f)
return direction * (1.0f / len);
else
return direction;
}
bool Player::tick(sf::Time delta, World &world) {
auto dir = find_direction();
center += dir * (speed * delta.asMicroseconds() / 1000000.0f);
shield_visible -= delta;
if (shield_visible <= sf::Time{}) {
shield_visible = sf::Time{};
// Don't check for collisions if the shield is up.
for (auto &collision : world.collides_with(*this)) {
// Was it an enemy?
if (dynamic_cast<Enemy *>(collision.get())) {
shield_visible = sf::milliseconds(1000);
}
}
}
return true;
}
void Player::render(sf::RenderWindow &to) {
Textured_Object::render(to);
if (shield_visible > sf::Time{}) {
shield.setPosition(center);
to.draw(shield);
}
}
#pragma once
#include "game_object.h"
/**
* Object representing the player.
*/
class Player : public Textured_Object {
public:
/**
* Create the player.
*/
Player(sf::Vector2f position);
/**
* Update the player position.
*/
bool tick(sf::Time delta, World &world) override;
/**
* Draw the player.
*/
void render(sf::RenderWindow &to) override;
private:
/**
* Speed. Pixels/second.
*/
float speed;
/**
* Shield shape.
*/
sf::CircleShape shield;
/**
* How long should the shield be visible for?
*/
sf::Time shield_visible;
};
inheritance/player.png

2.69 KiB

#include "random.h"
#include <random>
/**
* Random number generator.
*/
class Generator {
public:
Generator() : g(time(nullptr)) {}
std::mt19937 g;
};
static Generator generator;
int random_int(int min, int max) {
std::uniform_int_distribution<int> dist(min, max);
return dist(generator.g);
}
#pragma once
/**
* Get a random integer.
*/
int random_int(int min, int max);
#include "state.h"
State::State() {}
State::~State() {}
void State::on_key_press(sf::Keyboard::Key) {}
void State::on_key_release(sf::Keyboard::Key) {}
void State::run(sf::RenderWindow &window, shared_ptr<State> state) {
sf::Clock clock;
while (state) {
sf::Event event;
while (window.pollEvent(event)) {
switch (event.type) {
case sf::Event::Closed:
return;
case sf::Event::KeyPressed:
state->on_key_press(event.key.code);
break;
case sf::Event::KeyReleased:
state->on_key_release(event.key.code);
break;
default:
break;
}
}
window.clear();
if (auto new_state = state->tick(clock.restart())) {
if (std::dynamic_pointer_cast<Exit_State>(new_state)) {
return;
} else {
state = new_state;
}
continue;
}
state->render(window);
window.display();
}
}
#pragma once
#include "common.h"
/**
* A state that represents a large-scale state of the application. For example,
* if we're in a menu, if we're in the game, etc.
*/
class State : public std::enable_shared_from_this<State> {
public:
/**
* Default creation.
*/
State();
/**
* Make sure the destructor is virtual.
*/
virtual ~State();
/**
* Called when a key is pressed.
*/
virtual void on_key_press(sf::Keyboard::Key key);
/**
* Called when a key is released.
*/
virtual void on_key_release(sf::Keyboard::Key key);
/**
* Called before each screen refresh. May switch states.
*/
virtual shared_ptr<State> tick(sf::Time time) = 0;
/**
* Called to draw the screen.
*/
virtual void render(sf::RenderWindow &to) = 0;
/**
* Run a state, handling state-switches, until completion.
*/
static void run(sf::RenderWindow &window, shared_ptr<State> state);
};
/**
* Special state used to indicate that we should exit.
*/
class Exit_State : public State {
public:
shared_ptr<State> tick(sf::Time) { return nullptr; }
void render(sf::RenderWindow &) {}
};
#include "texture_manager.h"
#include <stdexcept>
sf::Texture *Texture_Manager::get(const string &name) {
auto found = instance.textures.find(name);
if (found != instance.textures.end())
return found->second.get();
sf::Texture *t = new sf::Texture();
if (!t->loadFromFile(name))
throw std::logic_error("Failed to load texture!");
instance.textures.insert(std::make_pair(name, std::unique_ptr<sf::Texture>(t)));
return t;
}
Texture_Manager Texture_Manager::instance;
#pragma once
#include "common.h"
#include <map>
/**
* This is a class that keeps track of loaded textures, so that we don't load
* more than one instance of each texture.
*
* This is what is known as a singleton class.
*/
class Texture_Manager {
public:
/**
* Get a texture. This might cause the texture to be loaded, or it might
* return a texture that was loaded previously.
*/
static sf::Texture *get(const string &name);
private:
/**
* Private constructor. Only we can create instances.
*/
Texture_Manager() = default;
/**
* The one and only instance.
*/
static Texture_Manager instance;
/**
* Keep track of loaded textures.
*/
std::map<string, std::unique_ptr<sf::Texture>> textures;
};
This diff is collapsed.
\documentclass[a4paper]{article}
\usepackage{fontspec}
\usepackage{tikz}
\usepackage{tikz-uml}
\usetikzlibrary{shapes,arrows}
\usepackage{geometry}
\geometry{a4paper,left=20mm,right=20mm,top=20mm,bottom=30mm}
\begin{document}
\center
\begin{tikzpicture}
\umlinterface[x=0,y=0]{State}{
}{
+ on\_key\_press(Key) : void \\
+ on\_key\_release(Key) : void \\
+ tick(Time) : void \\
+ render(RenderWindow) : void \\
+ \umlstatic{run(State) : void}
}
\umlclass[x=-6,y=-3.5]{ExitState}{
}{
+ tick(Time) : void \\
+ render(RenderWindow) : void
}
\umlreal{ExitState}{State}
\umlclass[x=6,y=-3.5]{MenuState}{
}{
+ on\_key\_press(Key) : void \\
+ on\_key\_release(Key) : void \\
+ tick(Time) : void \\
+ render(RenderWindow) : void
}
\umlreal{MenuState}{State}
\umlaggreg[geometry=|-,anchor1=90,anchor2=0,arg1=--background,mult1=1,mult2=0..*]{MenuState}{State}
\umlclass[x=0,y=-3.5]{GameState}{
}{
+ tick(Time) : void \\
+ render(RenderWindow) : void \\
}
\umlreal{GameState}{State}
\umlclass[x=0,y=-7]{World}{
}{
+ tick(Time) : void \\
+ render(RenderWindow) : void
}
\umlcompo[arg1=--world,mult1=1,mult2=1]{GameState}{World}
\umlinterface[x=0,y=-11]{GameObject}{
+ center : Vector2f \\
+ radius : float
}{
+ tick(Time, World) : bool \\
+ render(RenderWindow) : void
}
\umlcompo[arg1=--objects,mult1=1,mult2=0..*]{World}{GameObject}
\umlclass[x=0,y=-15]{TextureObject}{
-- shape : RectangleShape
}{
+ render(RenderWindow) : void
}
\umlreal{TextureObject}{GameObject}
\umlclass[x=-3,y=-17]{Enemy}{
}{
}
\umlinherit{Enemy}{TextureObject}
\umlclass[x=-6,y=-20]{Spawner}{
}{
+ tick(Time, World) : bool
}
\umlinherit{Spawner}{Enemy}
\umlclass[x=0,y=-20]{Bullet}{
}{
+ tick(Time, World) : bool
}
\umlinherit{Bullet}{Enemy}
\umlclass[x=6,y=-20]{Player}{
}{
+ tick(Time, World) : bool \\
+ render(RenderWindow) : void
}
\umlinherit{Player}{TextureObject}
\end{tikzpicture}
\end{document}
#include "world.h"
void World::tick(sf::Time delta) {
// Note: We cannot use iterators here as some of the objects might want to
// add new entities while we are iterating in this loop. Therefore, we use
// indices instead.
for (size_t i = 0; i < objects.size(); i++) {
if (!objects[i]->tick(delta, *this)) {
// Remove this element.
objects.erase(objects.begin() + i);
i--;
}
}
}
void World::render(sf::RenderWindow &to) {
for (auto &x : objects)
x->render(to);
}
void World::add(shared_ptr<Game_Object> object) {
objects.push_back(object);
}
static bool collides(Game_Object &a, Game_Object &b) {
auto distance = a.center - b.center;
// Calculate the lenght of "distance", squared (calling sqrt is expensive, and we don't actually need that).
auto lengthSq = pow(distance.x, 2) + pow(distance.y, 2);
return lengthSq <= pow(a.radius + b.radius, 2);
// Note: If we're working with bounding boxes instead of circles. We can consider the 'x' and
// 'y' axes separately, and arrive at something like this:
// return (a.left < b.right && a.right > b.left) // x-axis
// && (a.top < b.bottom && a.bottom > b.top) // y-axis
}
vector<shared_ptr<Game_Object>> World::collides_with(Game_Object &me) const {
vector<shared_ptr<Game_Object>> result;
for (auto &x : objects) {
// Don't collide with yourself.
if (x.get() == &me)
continue;
if (collides(*x, me))
result.push_back(x);
}
return result;
}
#pragma once
#include "common.h"
#include "game_object.h"
/**
* The game world. Contains a set of game objects.
*/
class World {
public:
/**
* Update all objects in the world.
*/
void tick(sf::Time delta);
/**
* Render the world.
*/
void render(sf::RenderWindow &to);
/**
* Add an object. This is safe to do while the world is updating itself.
*/
void add(shared_ptr<Game_Object> object);
/**
* See if something collides with 'me'.
*/
vector<shared_ptr<Game_Object>> collides_with(Game_Object &me) const;
private:
/**
* All game objects.
*/
vector<shared_ptr<Game_Object>> objects;
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment