feat: implemented game
parent
ed3e91aead
commit
5e4f6e70b6
Binary file not shown.
|
@ -8,12 +8,12 @@ AssetDB::AssetDB() {
|
||||||
this->index_assets();
|
this->index_assets();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetDB::clean() {
|
void AssetDB::clean(bool force) {
|
||||||
for(size_t i{0}; i < this->loaded.size();) {
|
for(size_t i{0}; i < this->loaded.size();) {
|
||||||
std::string asset_name{this->loaded.at(i)};
|
std::string asset_name{this->loaded.at(i)};
|
||||||
std::shared_ptr<Asset> asset{this->assets.at(asset_name)};
|
std::shared_ptr<Asset> asset{this->assets.at(asset_name)};
|
||||||
// usecount=1 means the asset is unused (other than the local use and the use in this->assets)
|
// usecount of 2 means the asset is unused (other than the local use and the use in this->assets)
|
||||||
if(asset.use_count() == 2) {
|
if(force || asset.use_count() <= 2) {
|
||||||
asset->unload();
|
asset->unload();
|
||||||
this->loaded.erase(this->loaded.begin() + i);
|
this->loaded.erase(this->loaded.begin() + i);
|
||||||
} else ++i; // don't iterate when the asset is unloaded
|
} else ++i; // don't iterate when the asset is unloaded
|
||||||
|
|
|
@ -15,7 +15,7 @@ public:
|
||||||
AssetDB();
|
AssetDB();
|
||||||
~AssetDB() = default;
|
~AssetDB() = default;
|
||||||
template <class AssetType> std::optional<std::shared_ptr<AssetType>> get_asset(std::string const &name);
|
template <class AssetType> std::optional<std::shared_ptr<AssetType>> get_asset(std::string const &name);
|
||||||
void clean();
|
void clean(bool force);
|
||||||
private:
|
private:
|
||||||
void index_assets();
|
void index_assets();
|
||||||
void load(std::string asset_name);
|
void load(std::string asset_name);
|
||||||
|
@ -25,8 +25,10 @@ template <class AssetType> std::optional<std::shared_ptr<AssetType>> AssetDB::ge
|
||||||
if(!this->assets.contains(name)) return std::nullopt;
|
if(!this->assets.contains(name)) return std::nullopt;
|
||||||
std::shared_ptr<AssetType> found{std::dynamic_pointer_cast<AssetType>(this->assets.at(name))};
|
std::shared_ptr<AssetType> found{std::dynamic_pointer_cast<AssetType>(this->assets.at(name))};
|
||||||
if(found == nullptr) return std::nullopt;
|
if(found == nullptr) return std::nullopt;
|
||||||
if(!found->is_loaded())
|
if(!found->is_loaded()) {
|
||||||
found->load();
|
found->load();
|
||||||
|
this->loaded.push_back(name);
|
||||||
|
}
|
||||||
return std::make_optional(found);
|
return std::make_optional(found);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "asset_wrapper.h"
|
#include "asset_wrapper.h"
|
||||||
#include "core/canvas_engine.hpp"
|
#include "core/canvas_engine.hpp"
|
||||||
#include <SDL2/SDL_image.h>
|
#include <SDL2/SDL_image.h>
|
||||||
|
#include <SDL2/SDL_log.h>
|
||||||
#include <SDL2/SDL_render.h>
|
#include <SDL2/SDL_render.h>
|
||||||
#include <SDL2/SDL_ttf.h>
|
#include <SDL2/SDL_ttf.h>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
@ -15,13 +16,16 @@ Texture::~Texture() {
|
||||||
this->unload();
|
this->unload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Texture::load() {
|
||||||
|
this->texture = IMG_LoadTexture(CanvasEngine::get_singleton()->get_render(), this->path.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
void Texture::unload() {
|
void Texture::unload() {
|
||||||
|
if(this->is_loaded()) {
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "unloading font %s", this->path.c_str());
|
||||||
SDL_DestroyTexture(this->texture);
|
SDL_DestroyTexture(this->texture);
|
||||||
this->texture = nullptr;
|
this->texture = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Texture::load() {
|
|
||||||
this->texture = IMG_LoadTexture(CanvasEngine::get_singleton()->get_render(), this->path.c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Texture::is_loaded() const {
|
bool Texture::is_loaded() const {
|
||||||
|
@ -39,15 +43,21 @@ Font::~Font() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Font::load() {
|
void Font::load() {
|
||||||
this->font = TTF_OpenFont(this->path.c_str(), 32);
|
this->font = TTF_OpenFont(this->path.c_str(), this->PT_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Font::unload() {
|
void Font::unload() {
|
||||||
|
if(this->is_loaded()) {
|
||||||
TTF_CloseFont(this->font);
|
TTF_CloseFont(this->font);
|
||||||
this->font = nullptr;
|
this->font = nullptr;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool Font::is_loaded() const {
|
bool Font::is_loaded() const {
|
||||||
return this->font != nullptr;
|
return this->font != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TTF_Font *Font::get() {
|
||||||
|
return this->font;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,8 @@ public:
|
||||||
|
|
||||||
class Font : public Asset {
|
class Font : public Asset {
|
||||||
TTF_Font *font{nullptr};
|
TTF_Font *font{nullptr};
|
||||||
|
public:
|
||||||
|
static const int PT_SIZE{64};
|
||||||
public:
|
public:
|
||||||
Font(std::filesystem::path const &path);
|
Font(std::filesystem::path const &path);
|
||||||
~Font();
|
~Font();
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#include "canvas_engine.hpp"
|
#include "canvas_engine.hpp"
|
||||||
|
#include "core/level.hpp"
|
||||||
#include <SDL2/SDL_pixels.h>
|
#include <SDL2/SDL_pixels.h>
|
||||||
#include <SDL2/SDL_timer.h>
|
#include <SDL2/SDL_timer.h>
|
||||||
|
#include <SDL2/SDL_ttf.h>
|
||||||
#include <SDL2/SDL_video.h>
|
#include <SDL2/SDL_video.h>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <SDL2/SDL_error.h>
|
#include <SDL2/SDL_error.h>
|
||||||
|
@ -22,18 +24,29 @@ CanvasEngine *CanvasEngine::singleton_instance{nullptr};
|
||||||
CanvasEngine::CanvasEngine() {
|
CanvasEngine::CanvasEngine() {
|
||||||
if(SDL_Init(SDL_INIT_EVERYTHING) != 0) {
|
if(SDL_Init(SDL_INIT_EVERYTHING) != 0) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to initialize SDL, SDL error: %s", SDL_GetError());
|
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to initialize SDL, SDL error: %s", SDL_GetError());
|
||||||
|
this->deinit_handled = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(IMG_Init(IMG_INIT_PNG | IMG_INIT_JXL) == 0) {
|
if(IMG_Init(IMG_INIT_PNG | IMG_INIT_JXL) == 0) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to initialize SDL_image, error: %s", IMG_GetError());
|
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to initialize SDL_image, error: %s", IMG_GetError());
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
|
this->deinit_handled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(TTF_Init() == -1) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to initialize SDL_ttf, error: %s", TTF_GetError());
|
||||||
|
IMG_Quit();
|
||||||
|
SDL_Quit();
|
||||||
|
this->deinit_handled = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->window = SDL_CreateWindow(PROJECTNAME, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1000, 800, SDL_WINDOW_RESIZABLE | SDL_WINDOW_FULLSCREEN_DESKTOP);
|
this->window = SDL_CreateWindow(PROJECTNAME, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1000, 800, SDL_WINDOW_RESIZABLE | SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||||
if(this->window == nullptr) {
|
if(this->window == nullptr) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to create window, SDL error: %s", SDL_GetError());
|
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to create window, SDL error: %s", SDL_GetError());
|
||||||
IMG_Quit();
|
IMG_Quit();
|
||||||
|
TTF_Quit();
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
|
this->deinit_handled = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->render = SDL_CreateRenderer(this->window, -1, SDL_RENDERER_ACCELERATED);
|
this->render = SDL_CreateRenderer(this->window, -1, SDL_RENDERER_ACCELERATED);
|
||||||
|
@ -41,7 +54,9 @@ CanvasEngine::CanvasEngine() {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to initialize renderer, SDL error: %s", SDL_GetError());
|
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to initialize renderer, SDL error: %s", SDL_GetError());
|
||||||
SDL_DestroyWindow(this->window);
|
SDL_DestroyWindow(this->window);
|
||||||
IMG_Quit();
|
IMG_Quit();
|
||||||
|
TTF_Quit();
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
|
this->deinit_handled = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->render_target = SDL_CreateTexture(this->render, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_TARGET, 1920, 1080);
|
this->render_target = SDL_CreateTexture(this->render, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_TARGET, 1920, 1080);
|
||||||
|
@ -50,12 +65,22 @@ CanvasEngine::CanvasEngine() {
|
||||||
}
|
}
|
||||||
|
|
||||||
CanvasEngine::~CanvasEngine() {
|
CanvasEngine::~CanvasEngine() {
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "CANVAS: Shutting down");
|
||||||
|
this->level.reset();
|
||||||
|
this->assets.clean(true);
|
||||||
|
if(!this->deinit_handled) {
|
||||||
SDL_DestroyRenderer(this->render);
|
SDL_DestroyRenderer(this->render);
|
||||||
SDL_DestroyWindow(this->window);
|
SDL_DestroyWindow(this->window);
|
||||||
|
IMG_Quit();
|
||||||
|
TTF_Quit();
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
}
|
}
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "CANVAS: Done shutting down");
|
||||||
|
}
|
||||||
|
|
||||||
void CanvasEngine::run(std::unique_ptr<Level> &level) {
|
void CanvasEngine::run(std::unique_ptr<Level> &level) {
|
||||||
|
if(!stay_open)
|
||||||
|
return;
|
||||||
assert(CanvasEngine::singleton_instance == nullptr && "Engine singleton instance already assigned, starting another instance is invalid");
|
assert(CanvasEngine::singleton_instance == nullptr && "Engine singleton instance already assigned, starting another instance is invalid");
|
||||||
// register as singleton
|
// register as singleton
|
||||||
CanvasEngine::singleton_instance = this;
|
CanvasEngine::singleton_instance = this;
|
||||||
|
@ -82,6 +107,10 @@ void CanvasEngine::run(std::unique_ptr<Level> &level) {
|
||||||
} else {
|
} else {
|
||||||
SDL_Delay(2);
|
SDL_Delay(2);
|
||||||
}
|
}
|
||||||
|
if(this->next_level) {
|
||||||
|
this->level = std::move(this->next_level);
|
||||||
|
this->level->instantiate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
assert(CanvasEngine::singleton_instance == this && "Engine singleton instance changed while game was running");
|
assert(CanvasEngine::singleton_instance == this && "Engine singleton instance changed while game was running");
|
||||||
CanvasEngine::singleton_instance = nullptr;
|
CanvasEngine::singleton_instance = nullptr;
|
||||||
|
@ -95,6 +124,10 @@ void CanvasEngine::set_target_delta_time(double target) {
|
||||||
this->target_delta_time = target;
|
this->target_delta_time = target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CanvasEngine::change_level(std::unique_ptr<Level> &level) {
|
||||||
|
this->next_level = std::move(level);
|
||||||
|
}
|
||||||
|
|
||||||
AssetDB &CanvasEngine::get_assets() {
|
AssetDB &CanvasEngine::get_assets() {
|
||||||
return this->assets;
|
return this->assets;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,11 +25,13 @@ private:
|
||||||
CollisionWorld collision_world{};
|
CollisionWorld collision_world{};
|
||||||
InputMap input_map{}; //!< map of inputs to input callback objects
|
InputMap input_map{}; //!< map of inputs to input callback objects
|
||||||
std::unique_ptr<Level> level;
|
std::unique_ptr<Level> level;
|
||||||
|
std::unique_ptr<Level> next_level;
|
||||||
Uint64 last_frame_start_time{}; //!< time at start of last frame
|
Uint64 last_frame_start_time{}; //!< time at start of last frame
|
||||||
Uint64 frame_start_time{}; //!< time at start of this frame
|
Uint64 frame_start_time{}; //!< time at start of this frame
|
||||||
double delta_time{0.f}; //!< measured delta time
|
double delta_time{0.f}; //!< measured delta time
|
||||||
double target_delta_time{}; //!< delta time target
|
double target_delta_time{}; //!< delta time target
|
||||||
bool stay_open{false}; //!< application loop will continue so long as this is true
|
bool stay_open{false}; //!< application loop will continue so long as this is true
|
||||||
|
bool deinit_handled{false};
|
||||||
public:
|
public:
|
||||||
CanvasEngine();
|
CanvasEngine();
|
||||||
~CanvasEngine();
|
~CanvasEngine();
|
||||||
|
@ -41,6 +43,7 @@ public:
|
||||||
void run(std::unique_ptr<Level> &level);
|
void run(std::unique_ptr<Level> &level);
|
||||||
void request_close();
|
void request_close();
|
||||||
void set_target_delta_time(double target);
|
void set_target_delta_time(double target);
|
||||||
|
void change_level(std::unique_ptr<Level> &level);
|
||||||
AssetDB &get_assets();
|
AssetDB &get_assets();
|
||||||
CollisionWorld &get_collision_world();
|
CollisionWorld &get_collision_world();
|
||||||
InputMap &get_input_map();
|
InputMap &get_input_map();
|
||||||
|
|
|
@ -5,15 +5,23 @@
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
namespace ce {
|
namespace ce {
|
||||||
|
Level::~Level() {
|
||||||
|
this->deinstantiate();
|
||||||
|
}
|
||||||
void Level::instantiate() {
|
void Level::instantiate() {
|
||||||
std::unique_ptr<Node> constructed{this->construct()};
|
std::unique_ptr<Node> constructed{this->construct()};
|
||||||
constructed->set_is_inside_tree(true);
|
|
||||||
this->root = std::move(constructed);
|
this->root = std::move(constructed);
|
||||||
|
this->root->set_is_inside_tree(true);
|
||||||
|
this->root->set_level(this);
|
||||||
this->root->propagate_added();
|
this->root->propagate_added();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Level::deinstantiate() {
|
void Level::deinstantiate() {
|
||||||
|
if(this->root) {
|
||||||
|
this->root->propagate_removed();
|
||||||
this->root.reset();
|
this->root.reset();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Level::propagate_tick(double delta_time) {
|
void Level::propagate_tick(double delta_time) {
|
||||||
this->root->propagate_tick(delta_time);
|
this->root->propagate_tick(delta_time);
|
||||||
|
@ -24,20 +32,17 @@ void Level::propagate_draw(SDL_Renderer *render) {
|
||||||
int w, h;
|
int w, h;
|
||||||
SDL_Window *window{SDL_RenderGetWindow(render)};
|
SDL_Window *window{SDL_RenderGetWindow(render)};
|
||||||
SDL_GetWindowSize(window, &w, &h);
|
SDL_GetWindowSize(window, &w, &h);
|
||||||
ce::Transform const screen_transform{
|
Transform const screen_transform{
|
||||||
.position = ce::Vecf::ZERO,
|
.position = Vecf::ZERO,
|
||||||
.rotation = 0.f,
|
.rotation = 0.f,
|
||||||
.scale = ce::Vecf::ONE * float(w),
|
.scale = Vecf::ONE * float(w),
|
||||||
};
|
};
|
||||||
float const ratio{float(h)/float(w)};
|
float const ratio{float(h)/float(w)};
|
||||||
this->root->propagate_draw(render, Transform().translated({
|
this->root->propagate_draw(render, Transform().translated({
|
||||||
0.5f / this->view_transform.scale.x,
|
0.5f / this->view_transform.scale.x,
|
||||||
0.5f / this->view_transform.scale.y * ratio
|
0.5f / this->view_transform.scale.y * ratio
|
||||||
}) * this->view_transform * screen_transform);
|
}) * view_transform * screen_transform);
|
||||||
this->root->propagate_draw_ui(render, Transform().translated({
|
this->root->propagate_draw_ui(render, screen_transform);
|
||||||
0.5f / this->view_transform.scale.x,
|
|
||||||
0.5f / this->view_transform.scale.y * ratio
|
|
||||||
}) * screen_transform);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Node *Level::get_root() {
|
Node *Level::get_root() {
|
||||||
|
|
|
@ -14,7 +14,7 @@ protected:
|
||||||
.scale = {1.f/10.f, 1.f/10.f},
|
.scale = {1.f/10.f, 1.f/10.f},
|
||||||
};
|
};
|
||||||
public:
|
public:
|
||||||
virtual ~Level() = default;
|
virtual ~Level();
|
||||||
void instantiate();
|
void instantiate();
|
||||||
virtual Node::OwnedPtr construct() = 0;
|
virtual Node::OwnedPtr construct() = 0;
|
||||||
void deinstantiate();
|
void deinstantiate();
|
||||||
|
|
|
@ -19,6 +19,7 @@ void Node::add_child(Node::OwnedPtr &child) {
|
||||||
this->children.push_back({child->get_name(), std::move(child)});
|
this->children.push_back({child->get_name(), std::move(child)});
|
||||||
added->parent = this;
|
added->parent = this;
|
||||||
added->set_is_inside_tree(this->inside_tree);
|
added->set_is_inside_tree(this->inside_tree);
|
||||||
|
added->set_level(this->level);
|
||||||
this->child_added.invoke(added);
|
this->child_added.invoke(added);
|
||||||
added->propagate_added();
|
added->propagate_added();
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,14 +22,12 @@ void Sprite::_draw(SDL_Renderer *render, ce::Transform const &view_transform) {
|
||||||
}
|
}
|
||||||
int w, h;
|
int w, h;
|
||||||
SDL_QueryTexture(this->texture->get(), NULL, NULL, &w, &h);
|
SDL_QueryTexture(this->texture->get(), NULL, NULL, &w, &h);
|
||||||
Transform transform{this->get_global_transform() * view_transform};
|
Transform const transform{this->get_global_transform() * view_transform};
|
||||||
assert(transform.scale.x != 0 && transform.scale.y != 0); // !!!
|
ce::Vecf const size{this->get_size().scaled(view_transform.scale)};
|
||||||
ce::Vecf size{this->get_size()};
|
|
||||||
size.scale(view_transform.scale);
|
|
||||||
assert(size.x != 0.f && size.y != 0.f);
|
assert(size.x != 0.f && size.y != 0.f);
|
||||||
//float fw(w), fh(h);
|
//float fw(w), fh(h);
|
||||||
SDL_Rect src{.x=0, .y=0, .w=w, .h=h};
|
SDL_Rect const src{.x=0, .y=0, .w=w, .h=h};
|
||||||
SDL_FRect dst{.x=transform.position.x - size.x/2.f, .y=transform.position.y - size.y/2.f, .w=size.x, .h=size.y};
|
SDL_FRect const dst{.x=transform.position.x - size.x/2.f, .y=transform.position.y - size.y/2.f, .w=size.x, .h=size.y};
|
||||||
SDL_RenderCopyExF(render, this->texture->get(),
|
SDL_RenderCopyExF(render, this->texture->get(),
|
||||||
&src, &dst,
|
&src, &dst,
|
||||||
transform.rotation * 57.2958f,NULL,
|
transform.rotation * 57.2958f,NULL,
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
#include "ui_text.hpp"
|
||||||
|
#include "core/assets/asset_db.hpp"
|
||||||
|
#include "core/canvas_engine.hpp"
|
||||||
|
#include <SDL2/SDL_render.h>
|
||||||
|
#include <SDL2/SDL_ttf.h>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace ce {
|
||||||
|
UiText::UiText(std::string const &name, std::string text, std::string font, SDL_Color color)
|
||||||
|
: Node2D(name)
|
||||||
|
, font{}
|
||||||
|
, text{text}
|
||||||
|
, color{color} {
|
||||||
|
std::optional<std::shared_ptr<Font>> font_asset{CanvasEngine::get_singleton()->get_assets().get_asset<Font>(font)};
|
||||||
|
if(font_asset.has_value())
|
||||||
|
this->font = font_asset.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
UiText::~UiText() {
|
||||||
|
if(this->cached)
|
||||||
|
SDL_DestroyTexture(this->cached);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UiText::_draw_ui(SDL_Renderer *render, Transform const &ui_transform) {
|
||||||
|
if(dirty) {
|
||||||
|
this->render();
|
||||||
|
}
|
||||||
|
if(this->cached == nullptr) {
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "No texture assigned");
|
||||||
|
this->set_visible(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int w, h; SDL_QueryTexture(this->cached, NULL, NULL, &w, &h);
|
||||||
|
Transform const transform{this->get_global_transform() * ui_transform};
|
||||||
|
Vecf const size{Vecf{float(w) / float(h), 1.f}.scaled(transform.scale)};
|
||||||
|
assert(size.x != 0.f && size.y != 0.f);
|
||||||
|
SDL_Rect const src{.x=0, .y=0, .w=w, .h=h};
|
||||||
|
SDL_FRect const dst{.x=transform.position.x, .y=transform.position.y, .w=size.x, .h=size.y};
|
||||||
|
SDL_RenderCopyExF(render, this->cached,
|
||||||
|
&src, &dst,
|
||||||
|
transform.rotation * 57.2958f,NULL,
|
||||||
|
SDL_FLIP_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UiText::set_text(std::string text) {
|
||||||
|
this->text = text;
|
||||||
|
this->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string const &UiText::get_text() const {
|
||||||
|
return this->text;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UiText::render() {
|
||||||
|
if(this->cached != nullptr)
|
||||||
|
SDL_DestroyTexture(this->cached);
|
||||||
|
SDL_Renderer *render{CanvasEngine::get_singleton()->get_render()};
|
||||||
|
if(render == nullptr) {
|
||||||
|
this->dirty = true; // can't render right now, defer for later
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SDL_Surface *surf{TTF_RenderText_Blended(this->font->get(), this->text.c_str(), this->color)};
|
||||||
|
assert(surf != nullptr && "Failed to render text");
|
||||||
|
this->cached = SDL_CreateTextureFromSurface(render, surf);
|
||||||
|
assert(this->cached != nullptr && "Failed to pass rendered text to the GPU");
|
||||||
|
SDL_FreeSurface(surf);
|
||||||
|
this->dirty = false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
#ifndef CORE_UI_TEXT_HPP
|
||||||
|
#define CORE_UI_TEXT_HPP
|
||||||
|
|
||||||
|
#include "core/assets/asset_wrapper.h"
|
||||||
|
#include "core/math/transform.hpp"
|
||||||
|
#include "core/node2d.hpp"
|
||||||
|
|
||||||
|
namespace ce {
|
||||||
|
class UiText : public Node2D {
|
||||||
|
std::shared_ptr<Font> font{nullptr};
|
||||||
|
std::string text{};
|
||||||
|
SDL_Color color{255, 255, 255, 255};
|
||||||
|
SDL_Texture *cached{nullptr};
|
||||||
|
bool dirty{true};
|
||||||
|
public:
|
||||||
|
UiText(std::string const &name, std::string text, std::string font, SDL_Color color);
|
||||||
|
~UiText();
|
||||||
|
virtual void _draw_ui(SDL_Renderer *render, Transform const &view_transform) override;
|
||||||
|
|
||||||
|
void set_text(std::string text);
|
||||||
|
std::string const &get_text() const;
|
||||||
|
protected:
|
||||||
|
void render();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // !CORE_UI_TEXT_HPP
|
|
@ -0,0 +1,15 @@
|
||||||
|
#include "end_screen.hpp"
|
||||||
|
#include "core/math/transform.hpp"
|
||||||
|
#include "core/node2d.hpp"
|
||||||
|
#include "core/ui_text.hpp"
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
EndScreen::EndScreen(unsigned score)
|
||||||
|
: score{score} {}
|
||||||
|
|
||||||
|
ce::Node::OwnedPtr EndScreen::construct() {
|
||||||
|
ce::Node::OwnedPtr root{new ce::Node2D("root")};
|
||||||
|
root->create_child<ce::UiText>("score", std::format("Score: {}", this->score), "inter", SDL_Color{255, 255, 255, 255})
|
||||||
|
->set_global_transform(ce::Transform().translated({0.3f, 0.3f}).scaled({0.1f, 0.1f}));
|
||||||
|
return std::move(root);
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
#ifndef END_SCREEN_HPP
|
||||||
|
#define END_SCREEN_HPP
|
||||||
|
|
||||||
|
#include "core/level.hpp"
|
||||||
|
|
||||||
|
class EndScreen : public ce::Level {
|
||||||
|
unsigned score;
|
||||||
|
public:
|
||||||
|
EndScreen(unsigned score);
|
||||||
|
virtual ce::Node::OwnedPtr construct() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // !END_SCREEN_HPP
|
|
@ -1,9 +1,16 @@
|
||||||
#include "level_1.hpp"
|
#include "level_1.hpp"
|
||||||
|
#include "core/canvas_engine.hpp"
|
||||||
|
#include "core/level.hpp"
|
||||||
#include "core/node.hpp"
|
#include "core/node.hpp"
|
||||||
#include "core/node2d.hpp"
|
#include "core/node2d.hpp"
|
||||||
|
#include "core/ui_text.hpp"
|
||||||
|
#include "end_screen.hpp"
|
||||||
|
#include "life_display.hpp"
|
||||||
#include "player.hpp"
|
#include "player.hpp"
|
||||||
|
#include "score_display.hpp"
|
||||||
#include "scrolling_ground.hpp"
|
#include "scrolling_ground.hpp"
|
||||||
#include "truck.hpp"
|
#include "spawner.hpp"
|
||||||
|
#include <SDL2/SDL_log.h>
|
||||||
|
|
||||||
ce::Node::OwnedPtr Level1::construct() {
|
ce::Node::OwnedPtr Level1::construct() {
|
||||||
ce::Node::OwnedPtr root{new ce::Node2D("root")};
|
ce::Node::OwnedPtr root{new ce::Node2D("root")};
|
||||||
|
@ -13,6 +20,23 @@ ce::Node::OwnedPtr Level1::construct() {
|
||||||
.scale = ce::Vecf::ONE
|
.scale = ce::Vecf::ONE
|
||||||
});
|
});
|
||||||
root->create_child<Player>();
|
root->create_child<Player>();
|
||||||
root->create_child<Truck>(true);
|
root->create_child<Spawner>();
|
||||||
|
root->create_child<ScoreDisplay>()
|
||||||
|
->set_global_transform(ce::Transform().scaled({.05f, .05f}).translated({0.01f, 0.f}));
|
||||||
|
root->create_child<LifeDisplay>()
|
||||||
|
->set_global_transform(ce::Transform().scaled({.05f, .05f}).translated({.9f, 0.f}));
|
||||||
return std::move(root);
|
return std::move(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Level1::add_score() {
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "add_score");
|
||||||
|
this->score_added.invoke(++this->score);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Level1::lose_life() {
|
||||||
|
this->life_lost.invoke(--this->lives);
|
||||||
|
if(this->lives == 0) {
|
||||||
|
std::unique_ptr<ce::Level> next{ce::Level::make<EndScreen>(this->score)};
|
||||||
|
ce::CanvasEngine::get_singleton()->change_level(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,8 +2,17 @@
|
||||||
#define LEVEL_1_HPP
|
#define LEVEL_1_HPP
|
||||||
|
|
||||||
#include "core/level.hpp"
|
#include "core/level.hpp"
|
||||||
|
#include "core/signal.hpp"
|
||||||
|
|
||||||
class Level1 : public ce::Level {
|
class Level1 : public ce::Level {
|
||||||
|
public:
|
||||||
|
unsigned score{0};
|
||||||
|
unsigned lives{3};
|
||||||
|
ce::Signal<unsigned> score_added{};
|
||||||
|
ce::Signal<unsigned> life_lost{};
|
||||||
|
public:
|
||||||
|
void add_score();
|
||||||
|
void lose_life();
|
||||||
virtual ce::Node::OwnedPtr construct() override;
|
virtual ce::Node::OwnedPtr construct() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
#include "life_display.hpp"
|
||||||
|
#include "core/ui_text.hpp"
|
||||||
|
#include "level_1.hpp"
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
LifeDisplay::LifeDisplay()
|
||||||
|
: ce::UiText("score_display", "0", "inter", {255, 255, 255, 255}) {}
|
||||||
|
|
||||||
|
void LifeDisplay::_added() {
|
||||||
|
if(Level1 *level{dynamic_cast<Level1*>(this->get_level())}) {
|
||||||
|
level->life_lost.connect(ce::Callable<void, unsigned>::make(this, &LifeDisplay::_lives_changed));
|
||||||
|
this->_lives_changed(level->lives);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LifeDisplay::_lives_changed(unsigned value) {
|
||||||
|
this->set_text(std::format("{}", value));
|
||||||
|
this->render();
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
#ifndef LIFE_DISPLAY_HPP
|
||||||
|
#define LIFE_DISPLAY_HPP
|
||||||
|
|
||||||
|
#include "core/ui_text.hpp"
|
||||||
|
|
||||||
|
class LifeDisplay : public ce::UiText {
|
||||||
|
public:
|
||||||
|
LifeDisplay();
|
||||||
|
virtual void _added() override;
|
||||||
|
void _lives_changed(unsigned new_value);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // !LIFE_DISPLAY_HPP
|
|
@ -1,4 +1,5 @@
|
||||||
#include "core/canvas_engine.hpp"
|
#include "core/canvas_engine.hpp"
|
||||||
|
#include "core/level.hpp"
|
||||||
#include "level_1.hpp"
|
#include "level_1.hpp"
|
||||||
#include <SDL2/SDL_log.h>
|
#include <SDL2/SDL_log.h>
|
||||||
|
|
||||||
|
@ -6,6 +7,6 @@ ce::CanvasEngine engine{};
|
||||||
|
|
||||||
int main(int argc [[maybe_unused]], char* argv [[maybe_unused]][]) {
|
int main(int argc [[maybe_unused]], char* argv [[maybe_unused]][]) {
|
||||||
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE);
|
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE);
|
||||||
std::unique_ptr<ce::Level> level{std::make_unique<Level1>()};
|
std::unique_ptr<ce::Level> level{ce::Level::make<Level1>()};
|
||||||
engine.run(level);
|
engine.run(level);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "player.hpp"
|
#include "player.hpp"
|
||||||
|
#include "level_1.hpp"
|
||||||
#include "truck.hpp"
|
#include "truck.hpp"
|
||||||
#include "core/callable.hpp"
|
#include "core/callable.hpp"
|
||||||
#include "core/canvas_engine.hpp"
|
#include "core/canvas_engine.hpp"
|
||||||
|
@ -72,6 +73,10 @@ void Player::_input_vertical_movement(ce::InputValue value) {
|
||||||
void Player::_on_overlap_enter(ce::CollisionShape *, ce::CollidableNode *other, ce::CollisionShape *shape) {
|
void Player::_on_overlap_enter(ce::CollisionShape *, ce::CollidableNode *other, ce::CollisionShape *shape) {
|
||||||
if(this->invincibility > 0.f)
|
if(this->invincibility > 0.f)
|
||||||
return;
|
return;
|
||||||
if(Truck *truck{dynamic_cast<Truck*>(other)})
|
if(Truck *truck{dynamic_cast<Truck*>(other)}) {
|
||||||
this->invincibility = 2.f;
|
this->invincibility = 2.f;
|
||||||
|
if(Level1 *level{dynamic_cast<Level1*>(this->get_level())}) {
|
||||||
|
level->lose_life();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
#include "score_display.hpp"
|
||||||
|
#include "core/ui_text.hpp"
|
||||||
|
#include "level_1.hpp"
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
ScoreDisplay::ScoreDisplay()
|
||||||
|
: ce::UiText("score_display", "0", "inter", {255, 255, 255, 255}) {}
|
||||||
|
|
||||||
|
void ScoreDisplay::_added() {
|
||||||
|
if(Level1 *level{dynamic_cast<Level1*>(this->get_level())}) {
|
||||||
|
level->score_added.connect(ce::Callable<void, unsigned>::make(this, &ScoreDisplay::_score_changed));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScoreDisplay::_score_changed(unsigned value) {
|
||||||
|
this->set_text(std::format("{}", value));
|
||||||
|
this->render();
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
#ifndef SCORE_DISPLAY_HPP
|
||||||
|
#define SCORE_DISPLAY_HPP
|
||||||
|
|
||||||
|
#include "core/ui_text.hpp"
|
||||||
|
|
||||||
|
class ScoreDisplay : public ce::UiText {
|
||||||
|
public:
|
||||||
|
ScoreDisplay();
|
||||||
|
virtual void _added() override;
|
||||||
|
void _score_changed(unsigned new_value);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // !SCORE_DISPLAY_HPP
|
|
@ -2,6 +2,7 @@
|
||||||
#include "core/math/transform.hpp"
|
#include "core/math/transform.hpp"
|
||||||
#include "core/sprite.hpp"
|
#include "core/sprite.hpp"
|
||||||
#include "core/collision_shape.hpp"
|
#include "core/collision_shape.hpp"
|
||||||
|
#include "level_1.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <SDL2/SDL_log.h>
|
#include <SDL2/SDL_log.h>
|
||||||
|
@ -29,6 +30,11 @@ void Truck::_tick(double delta) {
|
||||||
trans.position.x = std::clamp(trans.position.x, -LIMITS.x, LIMITS.x);
|
trans.position.x = std::clamp(trans.position.x, -LIMITS.x, LIMITS.x);
|
||||||
trans.position.y = std::max(trans.position.y, -LIMITS.y);
|
trans.position.y = std::max(trans.position.y, -LIMITS.y);
|
||||||
this->set_global_transform(trans);
|
this->set_global_transform(trans);
|
||||||
if(transform.position.y > 4.5f)
|
if(transform.position.y > 4.5f) {
|
||||||
this->flag_for_deletion();
|
this->flag_for_deletion();
|
||||||
|
if(Level1 *level{dynamic_cast<Level1*>(this->get_level())}) {
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "adding score");
|
||||||
|
level->add_score();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue