spacebox/src/Game.hpp

240 lines
8.1 KiB
C++

/* ✨ +------------------------------------------------------+
____/ \____ ✨/| Open source game framework licensed to freely use, |
✨\ / / | copy, and modify. Created for 🌠dank.game🌠 |
+--\ . . /--+ | |
| ~/ ︶ \👍| | 🌐 https://open.shampoo.ooo/shampoo/spacebox |
| ~~~🌊~~~~🌊~ | +------------------------------------------------------+
| SPACE 🪐🅱 OX | /
| 🌊 ~ ~~~~ ~~ |/
+-------------*/
#pragma once
#include <stdlib.h>
#include <vector>
#include <string>
#include <iostream>
#include <sstream>
#include <ctime>
#include <fstream>
#define SDL_MAIN_HANDLED
#include "SDL.h"
#include "SDL_mixer.h"
#include "SDL_ttf.h"
#if defined(__EMSCRIPTEN__)
#include <emscripten.h>
#include <emscripten/html5.h>
#endif
#if defined(__ANDROID__) || defined(ANDROID)
#include <android/log.h>
#endif
#include "gl.h"
#include "Node.hpp"
#include "Input.hpp"
#include "Display.hpp"
#include "Configuration.hpp"
#include "Delegate.hpp"
#include "Recorder.hpp"
#include "Audio.hpp"
#include "Log.hpp"
#include "filesystem.hpp"
#include "extension.hpp"
class Game : public Node
{
protected:
/* Construct sb::Delegate first, so it is the last object to be destroyed, allowing the other objects to successfully call its
* unsubscribe method in their destructors. */
sb::Delegate _delegate {this};
private:
SDL_Window* _window;
std::shared_ptr<TTF_Font> _font;
inline static const std::string user_config_path = "config.json";
/*!
* Overrides SDL's default log function to log a message to stdout/stderr and, if log is enabled in the
* global configuration, to a file. Debug level statements may be suppressed, printed to stdout, or printed to
* both stdout and file, depending on the global configuration. This shouldn't be called directly. Use
* `sb::Log::log` instead.
*
* @see sb::Log::log(const std::string&)
*
* @param userdata must be a pointer to Game
* @param category SDL log category. It is not used by SPACEBOX, so it should be sb::Log::DEFAULT_CATEGORY
* @param priority SDL log priority, which is equivalent to the values in sb::Log::Level
* @param message message as a C-style string
*/
static void sdl_log_override(void* userdata, int category, SDL_LogPriority priority, const char* message);
protected:
Configuration _configuration;
public:
/* two-state enum equivalent to a boolean that can improve readability depending on the context */
enum class Flip {
OFF,
ON
};
/* Prevent an instance of this class from being copied or moved */
Game(const Game&) = delete;
Game& operator=(const Game&) = delete;
Game(Game&&) = delete;
Game& operator=(Game&&) = delete;
SDL_GLContext glcontext = nullptr;
int frame_count_this_second = 0, last_frame_length, current_frames_per_second = 0;
float frame_time_overflow = 0, last_frame_timestamp, last_frame_count_timestamp;
bool done = false, show_framerate = true;
sb::Display display {this};
Input input {this};
std::vector<float> frame_length_history;
sb::Recorder recorder {_configuration, display};
Game(std::initializer_list<std::string> configuration_merge);
void print_frame_length_history();
GLuint load_shader(const fs::path&, GLenum) const;
bool link_shader(GLuint program) const;
/*!
* Write resolution, monitor refresh rate, and pixel format to the log. Taken from SDL_GetCurrentDisplayMode.html
* on the SDL wiki.
*/
void log_display_mode() const;
/*!
* Log properties of the GL context. Taken from `sdl_source/tests/testgles2.c`
*/
void log_gl_properties() const;
void log_surface_format(SDL_Surface*, std::string = "surface");
/*!
* @deprecated Global configuration will be removed in favor of a mix of default configuration and user configuration(s)
* which can be swapped in and out arbitrarily. For now, it's better to pass the global configuration directly to objects
* which need it instead of relying on this public accessor.
*/
const Configuration& configuration() const;
/*!
* @deprecated Global configuration will be removed in favor of a mix of default configuration and user configuration(s)
* which can be swapped in and out arbitrarily. For now, it's better to pass the global configuration directly to objects
* which need it instead of relying on this public accessor.
*/
Configuration& configuration();
/*!
* @deprecated The delegate class will be kept private to the Game object. Instead of subscribing individual objects to specific
* input, subscribe and respond to all events by extending Game::response and subscribing new events if necessary.
*/
const sb::Delegate& delegate() const;
/*!
* @deprecated The delegate class will be kept private to the Game object. Instead of subscribing individual objects to specific
* input, subscribe and respond to all events by extending Game::response and subscribing new events if necessary.
*/
sb::Delegate& delegate();
/*!
* @deprecated The window will be kept private and access will have to come directly from the class.
*/
const SDL_Window* window() const;
/*!
* @deprecated The window will be kept private and access will have to come directly from the class.
*/
SDL_Window* window();
/*!
* @deprecated The input class's functionality will be kept private to Game objects, so access will need to go through the Game
* class.
*/
const Input& get_input() const;
/*!
* @deprecated The input class's functionality will be kept private to Game objects, so access will need to go through the Game
* class.
*/
Input& get_input();
/*!
* @return shared pointer to the default font pre-loaded at construction
*/
std::shared_ptr<TTF_Font> font() const;
/*!
* Get a new font object with the given font size. If the font cannot be loaded, the default font will be returned. If there was an
* error, the shared pointer will point to `nullptr`.
*
* @return shared pointer to the font object created from the TTF font file at the given path
*/
std::shared_ptr<TTF_Font> font(const fs::path& path, int size) const;
void run();
/*!
* Dispatch framework events to relevant manager classes like Input, Display, and Recorder.
*
* Extend this function to respond to custom framework events. Use Delegate::compare to check which event has been fired.
*
* Framework events like "reset", "quit", and "up", have default input bindings that can be reconfigured or extended to respond
* to more bindings.
*
* @see Input::add_to_key_map
* @see Input::load_key_map
*
* To respond directly to lower-level SDL events like keyboard, mouse, and gamepad input directly, subscribe this function
* to a specific type of SDL event using Delegate::add_subscriber.
*/
virtual void respond(SDL_Event& event);
void frame(float);
void flag_to_end();
virtual void update(float timestamp) = 0;
void set_framerate(int);
void handle_quit_event(SDL_Event&);
void quit();
virtual std::string class_name() const { return "Game"; }
~Game();
/*!
* Applies delta timing to a value: returns the value as weighted by the amount of time passed since the
* last frame update, allowing for values to change the same amount over time independent of the frame rate.
* The amount is how much the value should change per second.
*
* @param amount any scalar value to be weighted
* @return weighted value
*/
template<typename T>
T weight(T amount) const
{
return (last_frame_length / 1000.0f) * amount;
}
};
/* Add Game class to the sb namespace. This should be the default location, but Game is left in the global namespace
* for backward compatibility.
*/
namespace sb
{
using ::Game;
}
#if defined(__EMSCRIPTEN__)
void loop(void*);
#endif