# IDE-files
.vscode
+
+# SFML files
+SFML
CC := g++
-CCFLAGS := -std=c++17 -Wall -Wextra -pedantic -Weffc++ -Wold-style-cast
+CCFLAGS := -std=c++17 -Wall -Wextra -pedantic -Weffc++ -Wold-style-cast -I src
LDFLAGS :=
OBJDIR := build
--- /dev/null
+#include "Ball.h"
+
+#include <random>
+#include <iostream>
+
+using namespace std;
+
+/*
+ * One way to create variables, functions and classes
+ * which are only visible in one file is by using an
+ * anonymous namespace (that is, a namespace which
+ * has no name).
+ *
+ * This will ensure that no other file will get
+ * access to these variables.
+ */
+namespace
+{
+ /*
+ * an engine which generates random numbers
+ */
+ random_device rd { };
+
+ /*
+ * wrappers which defines various ranges
+ * we want to generate numbers in.
+ */
+
+ // used to randomly choose what sprite to use
+ uniform_int_distribution<unsigned> index { 0, 1 };
+
+ // randomly choose a radius
+ uniform_real_distribution<float> radius { 10, 50 };
+
+ // randomly choose a velocity direction
+ uniform_real_distribution<float> angle { 0, 2*3.14 };
+
+ // randomly choose a speed for our ball
+ uniform_real_distribution<float> length { 0.5, 8.0 };
+}
+
+/*
+ * This constructor uses some basic trigonometry
+ * to create a velocity vector.
+ */
+//Ball :: Ball (Spritesheet & sheet,
+// float x,
+// float y)
+// : sprite { sheet.get_sprite (0, index (rd)) },
+// velocity { }
+//{
+// float direction { angle (rd) };
+// float speed { length (rd) };
+//
+// velocity = sf::Vector2f { speed * cos (direction),
+// speed * sin (direction) };
+//
+// /* position the ball, and make sure that we place
+// * the center of the ball where the mouse clicked */
+// auto sprite_size { sheet.sprite_size () };
+// sprite.setPosition (x, y);
+// sprite.setOrigin (sprite_size.x / 2, sprite_size.y / 2);
+//}
+
+Ball :: Ball (float x,
+ float y)
+ :
+ sprite{radius(rd)}, velocity { }
+{
+ float direction { angle (rd) };
+ float speed { length (rd) };
+
+ velocity = sf::Vector2f { speed * cos (direction),
+ speed * sin (direction) };
+
+ /* position the ball, and make sure that we place
+ * the center of the ball where the mouse clicked */
+
+ // Create a sprite
+ float sprite_size { sprite.getRadius () };
+ sprite.setPosition (x, y);
+ sprite.setOrigin (sprite_size / 2, sprite_size / 2);
+}
+
+void Ball :: update ()
+{
+ // move the sprite according to the velocity
+ sprite.move (velocity);
+}
+
+void Ball :: render (sf::RenderTarget & target)
+{
+ // draw the sprite
+ target.draw (sprite);
+}
+
+sf::FloatRect Ball :: bounds () const
+{
+ // get the hitbox of the sprite
+ return sprite.getGlobalBounds ();
+}
--- /dev/null
+#pragma once
+
+#include <SFML/Graphics.hpp>
+
+/*
+ * Class representing a moving ball on the screen.
+ *
+ * This class serves as a demonstration of a moving
+ * object.
+ */
+class Ball
+{
+
+public:
+
+ /*
+ * Create a ball and place it at the supplied coordinates.
+ */
+ Ball( float x, float y);
+ /*
+ * This function contains all logic that needs
+ * to be run for the ball each iteration in the
+ * game loop.
+ */
+ void update ();
+
+ /*
+ * draw the ball to 'target'
+ */
+ void render (sf::RenderTarget & target);
+
+ /*
+ * Get a rectangle which covers the ball
+ * completly (a hitbox)
+ */
+ sf::FloatRect bounds () const;
+
+private:
+
+ // graphical representation of the ball
+ sf::CircleShape sprite;
+
+
+ // the velocity (speed + direction) in
+ // which this ball is moving
+ sf::Vector2f velocity;
+
+};
--- /dev/null
+#include "constants.h"
+#include "Game.h"
+#include "Menu_State.h"
+#include "Game_State.h"
+
+using namespace sf;
+
+/*
+ * Initialize the window and the first state is Menu_State.
+ *
+ * The constructor of RenderWindow takes a 'VideoMode'
+ * which defines the width, height and bits per pixel
+ * (default 32), a title which should be visible in
+ * the titlebar of the windows, as well as a style flag.
+ * The style flag is a bit mask (i.e. a value where
+ * each bit represents a property) of properties we
+ * want our window to have. If we do not care,
+ * we can omitt this argument or use sf::Style::Default.
+ */
+Game :: Game (std::string const & title,
+ unsigned width,
+ unsigned height)
+ : window { VideoMode { width, height },
+ title, Style::Titlebar | Style::Close },
+ current_state{ MENU_STATE },
+ running { true }
+{
+ // Insert all sates you want in your game in the states map
+ states.insert(std::pair<int,
+ std::unique_ptr<State>>({MENU_STATE,
+ std::make_unique<Menu_State>()}));
+
+ states.insert(std::pair<int,
+ std::unique_ptr<State>>({GAME_STATE,
+ std::make_unique<Game_State>()}));
+}
+
+
+void Game :: start ()
+{
+ // The clock is used to measure of long each iteration took
+ // this is used to keep the framerate as steady as possible.
+ Clock clock { };
+ while ( running )
+ {
+ // Handle user events e.g. mouse click or key pressed
+ handle_events();
+
+ // Let the current state do its update
+ states.at(current_state) -> update();
+
+ /*
+ * clear fills the entire window with one color
+ * (by default black) thus overwriting everything
+ * that was drawn before.
+ *
+ * If we do not perform this step we allow for weird
+ * graphical artifacts to show up.
+ */
+ window.clear ();
+
+ // let the current state render itself onto the window
+ states.at(current_state) -> render(window);
+
+ /*
+ * No drawn pixels will be shown in the window
+ * until this function is called.
+ *
+ * All drawing operations are performed on a
+ * hidden buffer in memory, so if we want them
+ * to actually show up on the screen we have
+ * make sure that the window switches to drawing
+ * that buffer instead of its current one.
+ * (This technique is called 'double buffering')
+ */
+ window.display ();
+
+ /*
+ * When all logic and rendering has been performed
+ * we are now ready to update the current_state
+ */
+ current_state = states.at(current_state) -> get_next_state();
+
+ /*
+ * Wait if we still haven't reached the target
+ * time for a frame.
+ */
+ delay (clock);
+ }
+}
+
+
+void Game :: handle_events ()
+{
+ /*
+ * event is an object which contains all
+ * relevant information for an event that
+ * occured in the window (i.e. key-pressed,
+ * mouse clicks etc.).
+ *
+ * The function 'pollEvent' takes the next
+ * event in the event queue and places it
+ * in the 'event' variable so that we can
+ * read what that event was.
+ *
+ * While there are events in event queue
+ * 'pollEvent' will return true.
+ */
+ Event event;
+ while ( window.pollEvent (event) )
+ {
+ // Check if the window has been closed.
+ // This event fires whenever the user
+ // presses the X icon, or if the operating
+ // system kills it.
+ if ( event.type == Event::Closed )
+ running = false;
+
+ // send the event to 'state'
+ states.at(current_state) -> handle_event (event);
+ }
+}
+
+void Game :: delay (sf::Clock & clock) const
+{
+ sleep (milliseconds (1000.0 / fps) - clock.getElapsedTime ());
+ clock.restart ();
+}
--- /dev/null
+#ifndef GAME_H
+#define GAME_H
+
+#include <SFML/Graphics.hpp>
+
+#include <string>
+#include <memory>
+
+#include "State.h"
+
+class Game {
+
+public:
+
+ /*
+ * Constructor of Game.
+ *
+ * Will perform these tasks:
+ * - initialize the current_state with an initial state (welcome
+ * screen),
+ * - Fill the states map with the States needed during the game
+ * - spawn a window with the specified titlebar, width and height
+ * (in pixels)
+ */
+ Game (std::string const & title, unsigned width, unsigned height);
+
+ /*
+ * Run the main loop of the state machine, and therefore by extension
+ * the entire game.
+ *
+ * Each iteration of the main loop go through these steps:
+ * - Handle all window events from the event queue (these include
+ * key presses/releases, mouse movement, resizing of the window
+ * and more).
+ *
+ * What this means is that we check if the user has requested
+ * to close the window and therefore the game, and for any other
+ * events we pass them on to the state.
+ * - Update the current state.
+ *
+ * This means running any logic that the state might have, for
+ * example; move objects, check for collision, manage resources
+ * etc. This step will probably perform the vast majority of the
+ * logic in the project (depending on the project of course).
+ *
+ * - Draw the currently active state to the window.
+ *
+ * This step will simply pass the window to the state and will
+ * thus allow the state to draw itself onto the windows. This
+ * is beneficial since we now have completely decoupled the
+ * state from the Game class, thus allowing all behaviour of a
+ * state to be implemented in the corresponding class without
+ * having to touch any other code.
+ */
+ void start ();
+
+private:
+ /*
+ * SFML representation of window which we can draw on.
+ */
+ sf::RenderWindow window;
+
+
+ /*
+ * Container for all states in the game.
+ * Current_state keeps track on the current state
+ */
+ std::map<int, std::unique_ptr<State>> states;
+ int current_state;
+
+
+ /*
+ * If true, then the game should keep on run, otherwise the next
+ * iteration of the game loop will not run.
+ */
+ bool running;
+
+
+ void handle_events();
+
+ /*
+ * This function puts the program to sleep for a certain
+ * period of time in order to keep a steady framerate.
+ * The inargument is how many milliseconds the current iteration
+ * took to complete.
+ *
+ * Say that the game is supposed to run at 60 frames per second (fps).
+ * This means that each frame should take exactly 1000/60 = 16.666...
+ * milliseconds to run. If it took longer, then there is not much
+ * we can do about it, but if it took a shorter amount of time,
+ * then request that our program sleep for the remaining time.
+ */
+ void delay (sf::Clock & clock) const;
+};
+
+#endif // GAME_H
--- /dev/null
+#include "Game_State.h"
+#include "constants.h"
+
+#include <iostream>
+
+using namespace sf;
+
+/*
+ * Check for mouse button presses.
+ *
+ * If a mouse button press is detected, when that
+ * mouse button is release we add a new ball to
+ * the screen at the mouse's location.
+ *
+ * This demonstrates how we can integrate the mouse
+ * into our project.
+ */
+void Game_State :: handle_event (Event event)
+{
+ if ( event.type == Event::MouseButtonReleased )
+ {
+ if ( event.mouseButton.button == Mouse::Button::Left )
+ {
+ balls.emplace_back (
+ // get the coordinates of the mouse right now
+ event.mouseButton.x,
+ event.mouseButton.y);
+ }
+ }
+ else if ( event.type == Event::KeyPressed )
+ {
+ if ( event.key.code == Keyboard::Key::Return )
+ end_game = true;
+ }
+}
+
+void Game_State :: update ()
+{
+ // Iterate over all balls and let
+ // them update themselves.
+ for ( auto & ball : balls )
+ ball.update ();
+}
+
+void Game_State :: render (RenderTarget & target)
+{
+ // Let each ball render itself onto the
+ // supplied RenderTarget
+ for ( auto & ball : balls )
+ ball.render (target);
+}
+
+/*
+ * If return was pressed, we jump back to MENU_STATE,
+ * otherwise we stay in this state GAME_STATE
+ */
+int Game_State :: get_next_state()
+{
+ if (end_game)
+ {
+ end_game = false;
+ return MENU_STATE;
+ }
+ return GAME_STATE;
+}
+
+/*
+ * If we want to remove all balls which are outside
+ * of the screen we need to iterate all balls and
+ * do two things for each ball:
+ * 1. Check if it is outside of the screen
+ * 2. If it is, make sure that we remove it from
+ * the vector.
+ *
+ * The first part can be solved in several ways.
+ * We can for instance implement our own logic
+ * for checking if a sprite is on the screen
+ * or not, or we can use some built-in functions
+ * in SFML.
+ *
+ * In this example we use the latter.
+ *
+ * We begin by getting the global bounds of each ball.
+ *
+ * A bounds is the smallest possible rectangle which
+ * covers the entire ball (these are basically the
+ * "hitboxes" of our balls).
+ *
+ * There are two kinds of bounds for each ball, a
+ * global bound and a local bound, the difference
+ * is where the (0, 0) coordinate is placed.
+ *
+ * In a global bound, (0, 0) referes to top-left
+ * corner of the window, while in a local bounds
+ * it referes to the origin of the ball (in this
+ * example, the center of the ball (see Ball.cpp
+ * for details)).
+ *
+ * Using the global bounds we can use the built-in
+ * 'intersects' function to check if the bounds
+ * intersects the screen. If it does, it is still
+ * visible, if not it is safe to remove.
+ *
+ * The second part is a bit trickier.
+ *
+ * A problem arises when we iterate over the
+ * balls and then remove one of them; the current
+ * index has been changed so we have now potentially
+ * skipped over an element.
+ *
+ * Example of how this might happen:
+ *
+ * Say that we are iterating over this list, with
+ * a standard index based for-loop (here ^ denotes
+ * the element we are currently looking at):
+ *
+ * 1 5 7 2 4 3
+ * ^
+ * Now say that we remove the 7, then we have:
+ *
+ * 1 5 2 4 3
+ * ^
+ * Note that ^ now points to the next element, but the
+ * same location as before, which might at first glance
+ * seem like what we exactly want. But we must not
+ * forget that the final step in a for loop is to step
+ * the index, so the next iteration will look like this:
+ *
+ * 1 5 2 4 3
+ * ^
+ * Which shows that we have skipped an element.
+ *
+ * This means that we have to be a bit more cleaver
+ * when removing elements from the list we are
+ * iterating.
+ *
+ * But we are in luck, we can use an iterator based
+ * for-loop, since erase (the function which removes
+ * elements from the vector) removes elements pointed
+ * to by iterators, and returns an iterator to the next
+ * element.
+ */
+void Game_State :: cleanup ()
+{
+ for ( auto it { std::begin (balls) }; it != std::end (balls); )
+ {
+ // get the global bounds of our current ball
+ auto bounds { it -> bounds () };
+ // get a rectangle representing the screen
+ FloatRect screen { 0, 0, screen_width, screen_height };
+ if ( !screen.intersects (bounds) )
+ it = balls.erase (it);
+ else
+ ++it;
+ }
+}
--- /dev/null
+#include "State.h"
+#include "Ball.h"
+
+#include <vector>
+
+/*
+ * This class represents the "game".
+ *
+ * It is a simple program where a ball appears wherever
+ * the user has clicked with the mouse. This ball
+ * is assigned a random velocity and will begin moving
+ * according to that velocity.
+ *
+ * This class serves as an example of a state, but also
+ * of how the various functions of the State class can
+ * be used to get moving objects in the game.
+ *
+ * It also demonstrates how to remove objects from
+ * the game in a safe way, as to avoid memory leaks.
+ */
+class Game_State : public State
+{
+public:
+
+ Game_State () = default;
+
+ void handle_event (sf::Event event) override;
+ void update () override;
+ void render (sf::RenderTarget & target) override;
+ virtual int get_next_state() override;
+
+
+private:
+ bool end_game{false};
+
+ /*
+ * Remove all balls which are no longer visible on the
+ * screen.
+ */
+ void cleanup ();
+
+
+ /*
+ * A collection which contains all balls that are currently
+ * visible on the screen (see Ball.h).
+ */
+ std::vector<Ball> balls { };
+
+};
\ No newline at end of file
--- /dev/null
+#include "Menu_State.h"
+
+#include <string>
+#include <stdexcept>
+
+using namespace sf;
+
+/*
+ * Create the welcome message using the font given in
+ * 'resources/fonts/font.ttf'.
+ *
+ * The first argument of the sf::Text constructor is what string
+ * it should draw, the second argument is what font should be used
+ * to draw the text and the final argument specifies the font-size
+ * (in pixels) of the text.
+ */
+Menu_State :: Menu_State ()
+ :play { false }
+{
+ std::string file = "resources/fonts/font.ttf";
+ if ( !font.loadFromFile (file) )
+ throw std::invalid_argument ("Unable to load font");
+ text = sf::Text{ "Press <Enter> to start", font, 16 };
+}
+
+/*
+ * check for the 'enter' key.
+ *
+ * If it has been pressed we want to change to a new state.
+ */
+void Menu_State :: handle_event (Event event)
+{
+ if ( event.type == Event::KeyPressed )
+ {
+ if ( event.key.code == Keyboard::Key::Return )
+ play = true;
+ }
+}
+
+
+void Menu_State :: update ()
+{
+
+}
+
+void Menu_State :: render (RenderTarget & target)
+{
+ auto bounds { text.getGlobalBounds () };
+ auto size { target.getSize () };
+
+ text.setPosition ((size.x - bounds.width) / 2,
+ (size.y - bounds.height) / 2);
+
+ target.draw (text);
+}
+
+/*
+ * If return has been pressed we change to the GAME_STATE, otherwise
+ * we stay in MENU_STATE
+ */
+int Menu_State :: get_next_state()
+{
+ if (play)
+ {
+ play = false;
+ return GAME_STATE;
+ }
+ else
+ {
+ return MENU_STATE;
+ }
+}
+
--- /dev/null
+#pragma once
+
+#include <SFML/Graphics.hpp>
+#include "State.h"
+
+class Menu_State: public State
+{
+public:
+ Menu_State ();
+
+ virtual void handle_event (sf::Event event) override;
+ virtual void update () override;
+ virtual void render (sf::RenderTarget & taget) override;
+ virtual int get_next_state() override;
+
+
+private:
+
+ /*
+ * sf::Text is a drawable object representing a string.
+ *
+ * sf::Text can be drawn to any RenderTarget as long as
+ * a font and a string is supplied.
+ *
+ * It works like a sf::Sprite but represents text instead
+ * of an image.
+ */
+ sf::Text text;
+ sf::Font font{};
+
+
+ /*
+ * Flag to determine wheter or not the 'enter' key has been
+ * pressed (see Menu_State.cpp for details).
+ */
+ bool play;
+
+
+
+};
--- /dev/null
+#ifndef STATE_H
+#define STATE_H
+#include <SFML/Graphics.hpp>
+#include <string>
+
+int const MENU_STATE{0};
+int const GAME_STATE{1};
+
+
+class State
+{
+public:
+
+ // this is a base class so a virtual destructor is needed
+ virtual ~State () = default;
+
+ /*
+ * This function is called for each event which occurs
+ * in the window. It is the responsibility of the state
+ * to filter out any events it is interested in, since
+ * no filtering is performed before calling this function.
+ *
+ * What this means is that if a state only wants to know
+ * if a certain event has happened, the state needs to
+ * check for that event inside this function.
+ */
+ virtual void handle_event (sf::Event event) = 0;
+
+ /*
+ * The 'update' function is called every iteration of the
+ * game loop, no more and no less.
+ */
+ virtual void update () = 0;
+
+ /*
+ * Render the state onto the screen through a 'RenderTarget'.
+ *
+ * A RenderTarget is an abstract class which defines an
+ * interface for rendering to a canvas. Two types
+ * of RenderTargets exist in SFML:
+ * - RenderWindow; draw directly to a window.
+ * - RenderTexture; draw onto a texture which can be extracted
+ * later on using the 'getTexture' member function.
+ */
+ virtual void render(sf::RenderTarget & target) = 0;
+
+ /*
+ * Return the new state if it should change,
+ * otherwise return the current state
+ */
+ virtual int get_next_state() = 0;
+
+};
+
+#endif //STATE_H
* All spelkod bör köras från denna fil och denna fil enbart.
*/
-#include <iostream>
+#include "Game.h"
+#include "constants.h"
+/*
+ * If you want to study the code, it is recommended that
+ * you start looking in Game.h.
+ */
-int main() {
- std::cout << "_main.cc" << std::endl;
- return 0;
-}
\ No newline at end of file
+int main ()
+{
+ // We only want the main function to start the Game and then run it
+ Game g { "Example Program", screen_width, screen_height };
+ g.start ();
+}
--- /dev/null
+#pragma once
+
+/*
+ * In this file we define some global constants.
+ *
+ * Please note that the problem with global variables
+ * is that anyone can change them whenever which makes
+ * it extremely hard to reason about your code. But for
+ * constants, this is not a problem since we cannot
+ * change them, and therefore they stay the same during
+ * the entire execution of the program.
+ */
+
+/*
+ * define the size of the window.
+ */
+int const screen_width { 640 };
+int const screen_height { 480 };
+
+/*
+ * define how many fps we want our program to run in.
+ */
+double const fps { 60.0 };