Move shader loading function out of Game class and into sb:: namespace. Allow multiple file paths to be passed to sb::load_shader. The files are concatenated to build a shader string to pass to OpenGL. Also prepend dynamic version string to the shader based on whether the build is using OpenGL or OpenGL ES.
329 lines
13 KiB
C++
329 lines
13 KiB
C++
/* +=======================================================+
|
|
____/ \____ /: Open source game framework licensed to freely use, :
|
|
\ / / : copy, and modify - created for dank.game :
|
|
+==\ ^__^ /==+ : :
|
|
: ~/ \~ : : Download at https://open.shampoo.ooo/shampoo/spacebox :
|
|
: ~~~~~~~~~~~~ : +=======================================================+
|
|
: SPACE ~~~~~ : /
|
|
: ~~~~~~~ BOX :/
|
|
+=============*/
|
|
|
|
#pragma once
|
|
|
|
#include <stdlib.h>
|
|
#include <vector>
|
|
#include <string>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <ctime>
|
|
#include <fstream>
|
|
#include <functional>
|
|
#include <optional>
|
|
|
|
/* Needed to make system calls and check exit status of Linux processes */
|
|
#if defined(__LINUX__)
|
|
#include <sys/wait.h>
|
|
#include <string.h>
|
|
#endif
|
|
|
|
#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__)
|
|
#include <android/log.h>
|
|
#endif
|
|
|
|
#include "sb.hpp"
|
|
#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"
|
|
#include "version.hpp"
|
|
#include "progress.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:
|
|
|
|
std::shared_ptr<SDL_Window> _window;
|
|
std::shared_ptr<TTF_Font> _font;
|
|
|
|
/*!
|
|
* 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);
|
|
|
|
/*!
|
|
* Get a list of all JSON files in a given search path. A search path is a vector of file paths and directory paths
|
|
* to search for JSON.
|
|
*
|
|
* If an entry in the search path is an existing file ending in .json (case insensitive), it is included in the
|
|
* results.
|
|
*
|
|
* If an entry is a directory, all directory entries matching the above rule are included in the results. Note that
|
|
* the search will not recurse into sub-directories (this can be advantageous - per-game configs can be stored in
|
|
* sub-directories and loaded manually into the Game object).
|
|
*
|
|
* Note that files are not checked for valid JSON. JSON files not saved with .json as their extension will not be
|
|
* detected (for example, the file may not be named MyGame.config even if it has JSON syntax).
|
|
*
|
|
* @param search_path A vector of file paths and directory paths to search for JSON files
|
|
*
|
|
* @return A vector of JSON file paths found
|
|
*/
|
|
std::vector<fs::path> jsons(const std::vector<fs::path> search_path) const;
|
|
|
|
/* Indicate whether configuration is in the process of loading */
|
|
bool loading_configuration = false;
|
|
|
|
/*!
|
|
* Merge configuration passed into the constructor, followed by configuration passed on the command line.
|
|
*
|
|
* @param configs Configuration forwarded from the constructor
|
|
*/
|
|
void merge_additional_config(const std::vector<nlohmann::json>& configs);
|
|
|
|
protected:
|
|
|
|
sb::Configuration _configuration;
|
|
sb::progress::Progress preferences;
|
|
|
|
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;
|
|
|
|
/* The void type is used instead of SDL_GLContext because the SDL_GLContext type is "an alias for void*", meaning
|
|
* SDL_GLContext cannot be passed as the type to std::shared_ptr because the resulting shared_ptr would actually be
|
|
* a pointer to a pointer. See SDL's wiki documentation for SDL_GL_CreateContext. */
|
|
std::shared_ptr<void> glcontext;
|
|
|
|
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(const std::vector<nlohmann::json>& additional_configs = {});
|
|
Game(nlohmann::json additional_config) : Game(std::vector<nlohmann::json>({additional_config})) {};
|
|
void print_frame_length_history();
|
|
|
|
/*!
|
|
* 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 sb::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.
|
|
*/
|
|
sb::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();
|
|
|
|
/*!
|
|
* Get a constant reference to SDL's window object for the window the game is running in.
|
|
*
|
|
* @warning This function will probably be removed or deprecated soon since it basically limits the game to having
|
|
* a single window.
|
|
*
|
|
* @return A shared pointer to the window created during the construction of the game object
|
|
*/
|
|
const std::shared_ptr<SDL_Window> window() const;
|
|
|
|
/*!
|
|
* Get SDL's window object for the window the game is running in.
|
|
*
|
|
* @warning This function will probably be removed or deprecated soon since it basically limits the game to having
|
|
* a single window.
|
|
*
|
|
* @return A shared pointer to the window created during the construction of the game object
|
|
*/
|
|
std::shared_ptr<SDL_Window> window();
|
|
|
|
/*!
|
|
* @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`.
|
|
*
|
|
* Optionally, a hinting parameter can be passed. It should be one of TTF_HINTING_NORMAL, TTF_HINTING_LIGHT,
|
|
* TTF_HINTING_MONO, TTF_HINTING_LIGHT_SUBPIXEL, or TTF_HINTING_NONE.
|
|
*
|
|
* Optionally, an alignment parameter can be passed. It should be one of TTF_WRAPPED_ALIGN_LEFT,
|
|
* TTF_WRAPPED_ALIGN_CENTER, or TTF_WRAPPED_ALIGN_RIGHT.
|
|
*
|
|
* @param path Path to a TTF font to load
|
|
* @param size Point size of the font
|
|
* @param hinting Hinting level of the font
|
|
* @param alignment Alignment of wrapped text
|
|
*
|
|
* @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, std::optional<int> hinting = std::nullopt,
|
|
std::optional<int> alignment = std::nullopt) const;
|
|
|
|
/*!
|
|
* 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, subscribe this function to
|
|
* a specific type of SDL event using Delegate::add_subscriber.
|
|
*/
|
|
virtual void respond(SDL_Event& event);
|
|
|
|
/*!
|
|
* Launch the main update/draw loop of the game. The update function will run, followed by the draw function,
|
|
* once every frame at the speed specified by "display" -> "max framerate".
|
|
*
|
|
* Both functions must accept a float, which is the timestamp recorded at the beginning of the frame.
|
|
*
|
|
* Note that the given functions can be regular functions, lambdas, or member functions (the latter will need to be
|
|
* passed using std::bind).
|
|
*
|
|
* Note that a planned future revision will allow the update function to run on a separate timer. To implement
|
|
* separate timing now, use the timestamp parameter passed into the function.
|
|
*
|
|
* @param draw Function to draw a frame
|
|
* @param update Optional function to separately update game logic
|
|
*/
|
|
void run(std::function<void(float)> draw, std::optional<std::function<void(float)>> update = std::nullopt);
|
|
|
|
/*!
|
|
* @warning This function most likely should not be called directly. It's made public so that Emscripten can access
|
|
* it through a void pointer. Use Game::run to start running this function automatically.
|
|
*
|
|
* The draw and update function will run if enough time has passed that a new frame should be drawn (or max
|
|
* framerate is set to -1, which is the default). The update function runs first, followed by the draw function.
|
|
*
|
|
* Both functions run at the same rate, but a planned future revision will allow the update function to run on a
|
|
* separate timer.
|
|
*
|
|
* @param draw Function to draw a frame
|
|
* @param update Optional function to separately update game logic
|
|
*/
|
|
void frame(float timestamp, std::function<void(float)> draw, std::optional<std::function<void(float)>> update);
|
|
|
|
/*!
|
|
* Run this from within the draw or update function to flag the main loop to exit at the next opportunity. The main
|
|
* loop could be launched again with Game::run after it exits, or Game::quit can be called to exit the program.
|
|
*/
|
|
void flag_to_end();
|
|
|
|
void suppress_input_temporarily(float length = 0.0f);
|
|
void set_framerate(int);
|
|
void handle_quit_event(SDL_Event&);
|
|
void quit();
|
|
|
|
~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* user_data);
|
|
|
|
#endif
|