feat: add textures

This commit is contained in:
Martin Prokoph
2025-03-01 20:42:13 +01:00
parent 324c8f979e
commit c3c21a2236
27 changed files with 221 additions and 58 deletions

View File

@@ -151,12 +151,28 @@ else()
DEPENDS "${filename}"
)
endmacro()
macro(copy_dir filename)
if (ANDROID)
# MOBILE_ASSETS_DIR is set in the gradle file via the cmake command line and points to the Android Studio Assets folder.
# when we copy assets there, the Android build pipeline knows to add them to the apk.
set(outname "${MOBILE_ASSETS_DIR}/${filename}")
else()
# for platforms that do not have a good packaging format, all we can do is copy the assets to the process working directory.
set(outname "${CMAKE_BINARY_DIR}/$<CONFIGURATION>/${filename}")
endif()
add_custom_command(POST_BUILD
TARGET "${EXECUTABLE_NAME}"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_LIST_DIR}/src/${filename}" "${outname}"
DEPENDS "${filename}"
)
endmacro()
copy_helper("assets/Inter-VariableFont.ttf")
copy_helper("assets/MartianMono-VariableFont.ttf")
copy_helper("assets/sunset_on_the_beach.ogg")
copy_helper("assets/success.ogg")
copy_helper("assets/failure.ogg")
copy_helper("assets/gs_tiger.svg")
copy_dir("assets/textures/")
endif()
# set some extra configs for each platform

View File

@@ -59,7 +59,7 @@ bool startWorld(string worldFile) {
world.loadFromFile(worldFile);
Player player = Player(world.getStartPos(), world);
render(world, player.mapToWorldspace());
renderWorld(world);
inputLoop(player, world, testMode, worldIndex);

27
src/appcontext.hpp Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3/SDL_init.h>
#include <SDL3_ttf/SDL_ttf.h>
#include <SDL3_mixer/SDL_mixer.h>
#include <SDL3_image/SDL_image.h>
#include "player.hpp"
#include "world.hpp"
#include "camera.hpp"
struct AppContext {
SDL_Window* window;
SDL_Renderer* renderer;
SDL_Texture* messageTex, *imageTex;
SDL_FRect messageDest;
SDL_AudioDeviceID audioDevice;
Mix_Music* music;
SDL_AppResult app_quit = SDL_APP_CONTINUE;
Player* player;
World* world;
TTF_Font* font;
std::filesystem::path basePath;
Pos camera;
};

BIN
src/assets/textures/box.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

BIN
src/assets/textures/platform.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

3
src/camera.hpp Normal file
View File

@@ -0,0 +1,3 @@
struct Pos {
float x, y;
};

View File

@@ -7,11 +7,15 @@
#include <cmath>
#include <string_view>
#include <filesystem>
#include <map>
#include "adventura.cpp"
#include "events.hpp"
#include "renderer.cpp"
#include "appcontext.hpp"
using std::pair;
using std::map;
constexpr uint32_t windowStartWidth = 1200;
constexpr uint32_t windowStartHeight = 800;
@@ -20,20 +24,7 @@ void loadWorld(void* appstate, string worldFile);
void resetWorld(void* appstate);
void loadNextWorld(void* appstate);
void playSound(void* appstate, string soundFile);
struct AppContext {
SDL_Window* window;
SDL_Renderer* renderer;
SDL_Texture* messageTex, *imageTex;
SDL_FRect messageDest;
SDL_AudioDeviceID audioDevice;
Mix_Music* music;
SDL_AppResult app_quit = SDL_APP_CONTINUE;
Player* player;
World* world;
TTF_Font* font;
std::filesystem::path basePath;
};
void setScale(void* appstate);
SDL_AppResult SDL_Fail(){
SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Error %s", SDL_GetError());
@@ -160,11 +151,11 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
loadNextWorld(*appstate);
SDL_SetRenderVSync(renderer, -1); // enable vysnc
setScale(*appstate);
SDL_Log("Application started successfully!");
//start(argc, argv);
return SDL_APP_CONTINUE;
}
@@ -191,6 +182,10 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event* event) {
playSound(appstate, "assets/failure.ogg");
resetWorld(appstate);
}
if (event->type == SDL_EVENT_WINDOW_RESIZED) {
setScale(appstate);
}
if (event->type == SDL_EVENT_QUIT) {
app->app_quit = SDL_APP_SUCCESS;
@@ -198,7 +193,7 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event* event) {
return SDL_APP_CONTINUE;
}
vector<pair<vector<char>, SDL_Texture*>> textureCache;
SDL_AppResult SDL_AppIterate(void *appstate) {
auto* app = (AppContext*)appstate;
@@ -210,44 +205,16 @@ SDL_AppResult SDL_AppIterate(void *appstate) {
auto blue = (std::sin(time) * 2 + 1) / 2.0 * 255;
SDL_SetRenderDrawBlendMode(app->renderer, SDL_BLENDMODE_ADD);
SDL_SetRenderDrawColor(app->renderer, red, green, blue, 1);
SDL_SetRenderDrawColor(app->renderer, 0, 0, 0, 20);
SDL_SetRenderDrawColor(app->renderer, 20, 20, 20, 20);
SDL_RenderClear(app->renderer);
setCameraPos(appstate);
// Renderer uses the painter's algorithm to make the text appear above the image, we must render the image first.
//SDL_RenderTexture(app->renderer, app->imageTex, NULL, NULL);
vector<vector<char>> worldState = render(*(app->world), (*app->player).mapToWorldspace());
for (int i = 0; i < worldState.size(); i++) {
vector<char> line = worldState.at(i);
SDL_Texture* messageTex;
if (i < textureCache.size() && textureCache.at(i).first == line) {
messageTex = textureCache.at(i).second;
}
else {
if (i < textureCache.size()) {
SDL_DestroyTexture(textureCache.at(i).second);
}
SDL_Surface* surfaceMessage = TTF_RenderText_Solid(app->font, line.data(), line.size(), { 255,255,255 });
// make a texture from the surface
messageTex = SDL_CreateTextureFromSurface(app->renderer, surfaceMessage);
SDL_DestroySurface(surfaceMessage);
while (textureCache.size() <= i) {
textureCache.push_back({{}, nullptr});
}
textureCache.at(i) = {line, messageTex};
}
auto messageTexProps = SDL_GetTextureProperties(messageTex);
SDL_FRect text_rect{
.x = 0,
.y = float(i * SDL_GetNumberProperty(messageTexProps, SDL_PROP_TEXTURE_HEIGHT_NUMBER, 0)*2),
.w = float(SDL_GetNumberProperty(messageTexProps, SDL_PROP_TEXTURE_WIDTH_NUMBER, 0))*2,
.h = float(SDL_GetNumberProperty(messageTexProps, SDL_PROP_TEXTURE_HEIGHT_NUMBER, 0))*2
};
SDL_RenderTexture(app->renderer, messageTex, NULL, &text_rect);
}
renderWorld(appstate);
renderPlayer(appstate);
//SDL_RenderTexture(app->renderer, app->messageTex, NULL, &app->messageDest);
SDL_RenderPresent(app->renderer);
@@ -302,6 +269,7 @@ void loadNextWorld(void* appstate) {
int index = std::find(worldFiles.begin(), worldFiles.end(), currentWorld) - worldFiles.begin();
if (index + 1 < worldFiles.size()) {
loadWorld(appstate, worldFiles.at(index + 1));
enteredNextWorld = true;
}
else {
resetWorld(appstate);

View File

@@ -9,6 +9,28 @@
static void jumpBackOneLine() {
}
/**
* Renders the current state of the game world and player onto the console.
* It prints the world's blocks with their respective colors and encodings (characters).
*
* @param world Reference to the World object representing the current world.
*/
static vector<vector<char>> renderWorld(World &world) {
vector<vector<Block>> canvas = world.getFieldState();
vector<vector<char>> out;
for (unsigned int y = 0; y <= world.getMaxY(); y++) {
vector<char> line;
for (unsigned int x = 0; x <= world.getMaxX(); x++) {
if (canvas.size() > y && canvas.at(y).size() > x) {
line.push_back(canvas.at(y).at(x).getEncoding());
}
else line.push_back(' ');
}
out.push_back(line);
}
return out;
}
/**
* Renders the current state of the game world and player onto the console.
* It prints the world's blocks with their respective colors and encodings (characters).
@@ -17,7 +39,7 @@ static void jumpBackOneLine() {
* @param world Reference to the World object representing the current world.
* @param playerTexture Reference to the current Player texture.
*/
static vector<vector<char>> render(World &world, vector<vector<char>> playerTexture) {
static vector<vector<char>> renderPlayer(World &world, vector<vector<char>> playerTexture) {
vector<vector<Block>> canvas = world.getFieldState();
vector<vector<char>> out;
@@ -29,10 +51,6 @@ static vector<vector<char>> render(World &world, vector<vector<char>> playerText
line.push_back(playerTexture.at(y).at(x));
//cout << Color::BRIGHT_YELLOW << playerTexture.at(y).at(x);
}
else if (canvas.size() > y && canvas.at(y).size() > x) {
line.push_back(canvas.at(y).at(x).getEncoding());
//cout << canvas.at(y).at(x).getColor() << canvas.at(y).at(x).getEncoding();
}
else line.push_back(' ');
}
out.push_back(line);

131
src/renderer.cpp Normal file
View File

@@ -0,0 +1,131 @@
#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
#include <SDL3_image/SDL_image.h>
#include <cmath>
#include <string_view>
#include <filesystem>
#include <map>
#include "appcontext.hpp"
using std::pair;
using std::map;
float scale = 0;
void setScale(void* appstate) {
auto* app = (AppContext*)appstate;
int width, height, bbwidth, bbheight;
SDL_GetWindowSize(app->window, &width, &height);
SDL_GetWindowSizeInPixels(app->window, &bbwidth, &bbheight);
scale = float(width) / 500;
SDL_Log("Scale: %f", scale);
}
map<char, pair<SDL_Texture*, bool>> textureCache;
void renderWorld(void* appstate) {
auto* app = (AppContext*)appstate;
vector<vector<char>> worldState = renderWorld(*(app->world));
for (int y = 0; y < worldState.size(); y++) {
vector<char> line = worldState.at(y);
for (int x = 0; x < line.size(); x++) {
char blockChar = line.at(x);
SDL_Texture* messageTex;
if (textureCache.contains(blockChar)) {
messageTex = textureCache.at(blockChar).first;
}
else {
SDL_Surface* surface;
Block block = (*app->world).getBlockRegistry().getByEncoding(blockChar);
string texturePath = (app->basePath / "assets/textures/").string() + block.getId().path_ + ".png";
bool isText = true;
if (std::filesystem::exists(texturePath)) {
isText = false;
// load the SVG
surface = IMG_Load(texturePath.c_str());
}
else {
std::string_view block_str = string(1, blockChar);
surface = TTF_RenderText_Solid(app->font, block_str.data(), block_str.size(), { 255,255,255 });
}
// make a texture from the surface
messageTex = SDL_CreateTextureFromSurface(app->renderer, surface);
SDL_SetTextureScaleMode(messageTex, SDL_ScaleMode::SDL_SCALEMODE_NEAREST);
SDL_DestroySurface(surface);
textureCache.insert({blockChar, {messageTex, isText}});
}
auto messageTexProps = SDL_GetTextureProperties(messageTex);
SDL_FRect text_rect{
.x = float(x * 16 * scale)+3 + app->camera.x,
.y = float(y * 16 * scale) + app->camera.y,
.w = float(10 * scale),
.h = float(16 * scale)
};
SDL_FRect texture_rect{
.x = float(x * 16 * scale) + app->camera.x,
.y = float(y * 16 * scale) + app->camera.y,
.w = float(16 * scale),
.h = float(16 * scale)
};
bool isText = textureCache.at(blockChar).second;
SDL_RenderTexture(app->renderer, messageTex, NULL, isText ? &text_rect : &texture_rect);
}
}
}
map<string, SDL_Texture*> playerTextureCache;
void renderPlayer(void* appstate) {
auto* app = (AppContext*)appstate;
BlockPos playerPos = (*app->player).getPos();
string state = "idle";
if ((*app->player).isFreeFalling()) state = "falling";
if (!(*app->player).isAlive()) state = "dead";
SDL_Texture* messageTex;
if (playerTextureCache.contains(state)) {
messageTex = playerTextureCache.at(state);
}
else {
SDL_Surface* surface;
string texturePath = (app->basePath / "assets/textures/").string() + "player_"+ state + ".png";
if (std::filesystem::exists(texturePath)) {
// load the SVG
surface = IMG_Load(texturePath.c_str());
}
// make a texture from the surface
messageTex = SDL_CreateTextureFromSurface(app->renderer, surface);
SDL_SetTextureScaleMode(messageTex, SDL_ScaleMode::SDL_SCALEMODE_NEAREST);
SDL_DestroySurface(surface);
playerTextureCache.insert({state, messageTex});
}
auto messageTexProps = SDL_GetTextureProperties(messageTex);
SDL_FRect text_rect{
.x = float((playerPos.getX()-0.5) * 16 * scale) + app->camera.x,
.y = float(playerPos.getY() * 16 * scale) + app->camera.y,
.w = float(32 * scale),
.h = float(32 * scale)
};
SDL_RenderTexture(app->renderer, messageTex, NULL, &text_rect);
}
bool enteredNextWorld = false;
auto smoothStartTime = 0;
int targetCameraX = 0;
void setCameraPos(void* appstate) {
auto* app = (AppContext*)appstate;
auto time = SDL_GetTicks() / 100000.f;
int lastCameraX = app->camera.x;
targetCameraX = -(std::max(0, app->player->getPos().getX() - 5)) * 16 * scale;
if (!enteredNextWorld) {
if (lastCameraX != app->camera.x) smoothStartTime = time;
app->camera.x = lastCameraX + (targetCameraX - lastCameraX) * std::min(1.0f, (time - smoothStartTime) * 0.25f);
}
else {
app->camera.x = targetCameraX;
enteredNextWorld = false;
}
}

View File

@@ -3,7 +3,7 @@ Welt 4: In der Klemme
S
---
-0-
0
0 x x
0 -------------

View File

@@ -4,7 +4,7 @@ Welt 5: Die Beachparty
0***************0
0 0 x
-------^^^^^^^^^^^^^^^^^^^------------
H ----------------
H -0--------------
H H
H 0 H
S H 0 H