feat: add textures
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
After Width: | Height: | Size: 509 B |
BIN
src/assets/textures/ladder.png
Normal file
|
After Width: | Height: | Size: 304 B |
BIN
src/assets/textures/platform.png
Executable file
|
After Width: | Height: | Size: 233 B |
BIN
src/assets/textures/player_falling.png
Normal file
|
After Width: | Height: | Size: 184 B |
BIN
src/assets/textures/player_idle.png
Normal file
|
After Width: | Height: | Size: 178 B |
BIN
src/assets/textures/sand.png
Normal file
|
After Width: | Height: | Size: 635 B |
BIN
src/assets/textures/spike.png
Normal file
|
After Width: | Height: | Size: 482 B |
BIN
src/assets/textures/wall.png
Normal file
|
After Width: | Height: | Size: 285 B |
BIN
src/assets/textures/water.png
Normal file
|
After Width: | Height: | Size: 227 B |
BIN
src/assets/textures_base/box.pxo
Normal file
BIN
src/assets/textures_base/ladder.pxo
Normal file
BIN
src/assets/textures_base/platform.pxo
Normal file
BIN
src/assets/textures_base/player_falling.pxo
Normal file
BIN
src/assets/textures_base/player_idle.pxo
Normal file
BIN
src/assets/textures_base/sand.pxo
Normal file
BIN
src/assets/textures_base/spike.pxo
Normal file
BIN
src/assets/textures_base/wall.pxo
Normal file
BIN
src/assets/textures_base/water.pxo
Normal file
3
src/camera.hpp
Normal file
@@ -0,0 +1,3 @@
|
||||
struct Pos {
|
||||
float x, y;
|
||||
};
|
||||
68
src/main.cpp
@@ -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);
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ Welt 4: In der Klemme
|
||||
|
||||
S
|
||||
|
||||
---
|
||||
-0-
|
||||
0
|
||||
0 x x
|
||||
0 -------------
|
||||
|
||||
@@ -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
|
||||
|
||||