/* ✨ +------------------------------------------------------+ ____/ \____ ✨/| 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 #include #include #include #include #include #include #define SDL_MAIN_HANDLED #include "SDL.h" #include "SDL_mixer.h" #include "SDL_ttf.h" #if defined(__EMSCRIPTEN__) #include #include #endif #if defined(__ANDROID__) || defined(ANDROID) #include #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 _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 frame_length_history; sb::Recorder recorder {_configuration, display}; Game(std::initializer_list 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 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 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 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