application separation

This commit is contained in:
2024-07-30 01:20:30 +03:00
parent 922e521867
commit a99cb340d8
23 changed files with 147 additions and 34 deletions

View File

@ -0,0 +1,23 @@
cmake_minimum_required(VERSION 3.21)
project(template LANGUAGES C)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif()
# add root townengine cmake file
if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR})
add_subdirectory(../../ ../../../.build)
endif()
set(SOURCE_FILES
game.c game.h
)
add_executable(${PROJECT_NAME} ${SOURCE_FILES})
use_townengine(${PROJECT_NAME})
set_target_properties(${PROJECT_NAME} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)

3
apps/template/build.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/env sh
cmake -B .build "$@" && cmake --build .build

26
apps/template/game.c Normal file
View File

@ -0,0 +1,26 @@
#include "townengine/game_api.h"
#include "game.h"
#include "state.h"
#include <malloc.h>
void game_tick(void) {
/* do your initialization on first tick */
if (ctx.tick_count == 0) {
/* application data could be stored in ctx.udata and retrieved anywhere */
ctx.udata = ccalloc(1, sizeof (struct state));
}
/* a lot of data is accessible from `ctx`, look into `townengine/context.h` for more */
struct state *state = ctx.udata;
++state->counter;
}
void game_end(void) {
/* do your deinitialization here */
struct state *state = ctx.udata;
free(state);
}

13
apps/template/game.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef GAME_H
#define GAME_H
#include "townengine/game_api.h"
#include <stdint.h>
void game_tick(void);
void game_end(void);
#endif

12
apps/template/state.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef STATE_H
#define STATE_H
#include "townengine/game_api.h"
/* populate it with state information */
struct state {
uint64_t counter;
};
#endif

View File

@ -0,0 +1,31 @@
cmake_minimum_required(VERSION 3.21)
project(testgame LANGUAGES C)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif()
# add root townengine cmake file
if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR})
add_subdirectory(../../ ../../../.build)
endif()
set(SOURCE_FILES
game.c game.h
state.h
player.c player.h
world.c world.h
scenes/scene.c scenes/scene.h
scenes/title.c scenes/title.h
scenes/ingame.c scenes/ingame.h
)
add_executable(${PROJECT_NAME} ${SOURCE_FILES})
use_townengine(${PROJECT_NAME})
set_target_properties(${PROJECT_NAME} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)

3
apps/testgame/build.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/env sh
cmake -B .build "$@" && cmake --build .build

64
apps/testgame/game.c Normal file
View File

@ -0,0 +1,64 @@
#include "game.h"
#include "townengine/game_api.h"
#include "state.h"
#include "scenes/scene.h"
#include "scenes/title.h"
#include <stdio.h>
#include <malloc.h>
#include <stdint.h>
void game_tick(void) {
if (ctx.tick_count == 0) {
ctx.udata = ccalloc(1, sizeof (struct state));
struct state *state = ctx.udata;
state->ctx = &ctx;
state->scene = title_scene(state);
input_add_action(&ctx.input, "debug_dump_atlases");
input_bind_action_scancode(&ctx.input, "debug_dump_atlases", SDL_SCANCODE_HOME);
input_add_action(&ctx.input, "debug_toggle");
input_bind_action_scancode(&ctx.input, "debug_toggle", SDL_SCANCODE_BACKSPACE);
input_add_action(&ctx.input, "player_left");
input_bind_action_scancode(&ctx.input, "player_left", SDL_SCANCODE_LEFT);
input_add_action(&ctx.input, "player_right");
input_bind_action_scancode(&ctx.input, "player_right", SDL_SCANCODE_RIGHT);
input_add_action(&ctx.input, "player_jump");
input_bind_action_scancode(&ctx.input, "player_jump", SDL_SCANCODE_X);
input_add_action(&ctx.input, "ui_accept");
input_bind_action_scancode(&ctx.input, "ui_accept", SDL_SCANCODE_RETURN);
}
struct state *state = ctx.udata;
if (input_is_action_just_pressed(&ctx.input, "debug_dump_atlases")) {
textures_dump_atlases(&ctx.texture_cache);
}
if (input_is_action_just_pressed(&ctx.input, "debug_toggle")) {
ctx.debug = !ctx.debug;
}
state->scene->tick(state);
/* there's a scene switch pending, we can do it now that the tick is done */
if (state->next_scene != NULL) {
state->scene->end(state);
state->scene = state->next_scene;
state->is_scene_switching = false;
state->next_scene = NULL;
}
}
void game_end(void) {
struct state *state = ctx.udata;
state->scene->end(state);
free(state);
}

14
apps/testgame/game.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef GAME_H
#define GAME_H
#include "townengine/game_api.h"
#include <stdint.h>
void game_tick(void);
void game_end(void);
#endif

303
apps/testgame/player.c Normal file
View File

@ -0,0 +1,303 @@
#include "player.h"
#include "world.h"
#include "townengine/game_api.h"
#include <SDL2/SDL.h>
#include <stdbool.h>
#include <stdlib.h>
#include <tgmath.h>
static void update_timers(struct player *player) {
tick_timer(&player->jump_air_timer);
tick_timer(&player->jump_coyote_timer);
tick_timer(&player->jump_buffer_timer);
}
static void input_move(struct input_state *input, struct player *player) {
/* apply horizontal damping when the player stops moving */
/* in other words, make it decelerate to a standstill */
if (!input_is_action_pressed(input, "player_left") &&
!input_is_action_pressed(input, "player_right"))
{
player->dx *= player->horizontal_damping;
}
int input_dir = 0;
if (input_is_action_pressed(input, "player_left"))
input_dir = -1;
if (input_is_action_pressed(input, "player_right"))
input_dir = 1;
if (input_is_action_pressed(input, "player_left") &&
input_is_action_pressed(input, "player_right"))
input_dir = 0;
player->dx += (float)input_dir * player->run_horizontal_speed;
player->dx = SDL_clamp(player->dx, -player->max_dx, player->max_dx);
if (fabs(player->dx) < player->run_horizontal_speed) {
player->dx = 0;
}
}
static void jump(struct player *player) {
player->jump_coyote_timer = 0;
player->jump_buffer_timer = 0;
player->dy = player->jump_force_initial;
player->action = PLAYER_ACTION_JUMP;
player->jump_air_timer = player->jump_air_ticks;
}
static void input_jump(struct input_state *input, struct player *player) {
player->current_gravity_multiplier = player->jump_default_multiplier;
if (input_is_action_just_pressed(input, "player_jump")) {
player->jump_air_timer = 0;
player->jump_buffer_timer = player->jump_buffer_ticks;
if (player->action == PLAYER_ACTION_GROUND || player->jump_coyote_timer > 0) {
jump(player);
}
}
if (input_is_action_pressed(input, "player_jump")) {
if (player->action != PLAYER_ACTION_GROUND && player->jump_air_timer > 0) {
player->current_gravity_multiplier = player->jump_boosted_multiplier;
player->dy += player->jump_force_increase;
}
}
}
static void update_collider_x(struct player *player) {
player->collider_x.w = player->rect.w;
player->collider_x.h = player->rect.h - 8;
player->collider_x.x = player->rect.x;
player->collider_x.y = player->rect.y + ((player->rect.h - player->collider_x.h) / 2);
}
static void update_collider_y(struct player *player) {
player->collider_y.w = player->rect.w;
player->collider_y.h = player->rect.h;
player->collider_y.x = player->rect.x + ((player->rect.w - player->collider_y.w) / 2);
player->collider_y.y = player->rect.y;
}
static void apply_gravity(struct player *player, float gravity) {
player->dy -= gravity * player->current_gravity_multiplier;
player->dy = fmax(player->dy, -player->terminal_velocity);
if (player->dy < 0) {
/* just started falling */
if (player->action == PLAYER_ACTION_GROUND) {
player->jump_coyote_timer = player->jump_coyote_ticks;
}
player->action = PLAYER_ACTION_FALL;
}
}
/* returns whether or not a correction was applied */
static bool corner_correct(struct player *player, t_frect collision) {
/*
* somewhat of a hack here. we only want to do corner correction
* if the corner in question really is the corner of a "platform,"
* and not simply the edge of a single tile in a row of many
* tiles, as that would briefly clip the player into the ceiling,
* halting movement. the dumbest way i could think of to fix this
* is to simply ensure that there's no tile to possibly clip into
* before correcting, by checking if there's a tile right above
* the center of the player
*/
if (world_is_tile_at(player->world,
player->rect.x + (player->rect.w / 2),
player->rect.y - 1))
{
return false;
}
float player_center_x = player->collider_x.x + (player->collider_x.w / 2);
float collision_center_x = collision.x + (collision.w / 2);
float abs_difference = fabs(player_center_x - collision_center_x);
/* we're good, no correction needed */
if (abs_difference < player->jump_corner_correction_offset)
return false;
/* collision was on player's right side */
if (player_center_x < collision_center_x) {
player->rect.x -= collision.w / 2;
}
/* collision was on player's left side */
else if (player_center_x > collision_center_x) {
player->rect.x += collision.w / 2;
}
return true;
}
static void calc_collisions_x(struct player *player) {
t_frect collision;
bool is_colliding = world_find_intersect_frect(player->world, player->collider_x, &collision);
if (!is_colliding) return;
float player_center_x = player->collider_x.x + (player->collider_x.w / 2);
float collision_center_x = collision.x + (collision.w / 2);
enum collision_direction { COLLISION_LEFT = -1, COLLISION_RIGHT = 1 };
enum collision_direction dir_x =
player_center_x > collision_center_x ? COLLISION_LEFT : COLLISION_RIGHT;
player->rect.x -= collision.w * (float)dir_x;
player->dx = 0;
}
static void calc_collisions_y(struct player *player) {
t_frect collision;
bool is_colliding = world_find_intersect_frect(player->world, player->collider_y, &collision);
if (!is_colliding) return;
float player_center_y = player->collider_y.y + (player->collider_y.h / 2);
float collision_center_y = collision.y + (collision.h / 2);
enum collision_direction { COLLISION_ABOVE = -1, COLLISION_BELOW = 1 };
enum collision_direction dir_y =
player_center_y > collision_center_y ? COLLISION_ABOVE : COLLISION_BELOW;
/* before the resolution */
if (dir_y == COLLISION_ABOVE) {
if (corner_correct(player, collision)) {
return;
}
}
/* resolution */
player->rect.y -= collision.h * (float)dir_y;
player->dy = 0;
/* after the resolution */
if (dir_y == COLLISION_BELOW) {
/* bandaid fix for float precision-related jittering */
player->rect.y = ceilf(player->rect.y);
player->action = PLAYER_ACTION_GROUND;
if (player->jump_buffer_timer > 0) {
jump(player);
apply_gravity(player, player->world->gravity);
}
} else if (dir_y == COLLISION_ABOVE) {
player->jump_air_timer = 0;
}
}
struct player *player_create(struct world *world) {
struct player *player = cmalloc(sizeof *player);
*player = (struct player) {
.world = world,
.sprite_w = 48,
.sprite_h = 48,
.rect = (t_frect) {
.x = 92,
.y = 200,
.w = 16,
.h = 32,
},
.action = PLAYER_ACTION_GROUND,
.collider_thickness = 42,
.max_dx = 8,
.run_horizontal_speed = 0.5f,
.horizontal_damping = 0.9f,
.current_gravity_multiplier = 1,
.terminal_velocity = 30,
.jump_force_initial = 10,
.jump_force_increase = 1,
.jump_air_ticks = 18,
.jump_coyote_ticks = 8,
.jump_buffer_ticks = 8,
.jump_default_multiplier = 1.4f,
.jump_boosted_multiplier = 1,
.jump_corner_correction_offset = 16.0f,
};
return player;
}
static void drawdef(struct player *player) {
push_sprite("/assets/player/baron-walk.png",
(t_frect) {
.x = player->rect.x + ((player->rect.w - player->sprite_w) / 2),
.y = player->rect.y - 8,
.w = player->sprite_w,
.h = player->sprite_h,
});
push_circle((t_fvec2) { 256, 128 },
24,
(t_color) { 255, 0, 0, 255 });
}
static void drawdef_debug(struct player *player) {
if (!ctx.debug)
return;
/* const int info_separation = 24; */
/* const struct RectPrimitive info_theme = { */
/* .x = 8, */
/* .r = 0, .g = 0, .b = 0, .a = 255, */
/* }; */
push_rectangle(player->collider_x,
(t_color){ 0, 0, 255, 128 });
push_rectangle(player->collider_y,
(t_color){ 0, 0, 255, 128 });
}
void player_destroy(struct player *player) {
free(player);
}
void player_calc(struct player *player) {
update_timers(player);
input_move(&ctx.input, player);
input_jump(&ctx.input, player);
player->rect.x += player->dx;
update_collider_x(player);
calc_collisions_x(player);
apply_gravity(player, player->world->gravity);
player->rect.y -= player->dy;
update_collider_y(player);
calc_collisions_y(player);
update_collider_x(player);
update_collider_y(player);
drawdef(player);
drawdef_debug(player);
}

66
apps/testgame/player.h Normal file
View File

@ -0,0 +1,66 @@
#ifndef PLAYER_H
#define PLAYER_H
#include "townengine/game_api.h"
struct world;
enum player_action {
PLAYER_ACTION_GROUND,
PLAYER_ACTION_FALL,
PLAYER_ACTION_JUMP,
};
struct player {
struct world *world;
/* visual */
float sprite_w;
float sprite_h;
/* body */
t_frect rect;
/* state */
enum player_action action;
/* physics */
t_frect collider_x;
t_frect collider_y;
int collider_thickness;
float dx;
float dy;
float max_dx;
float run_horizontal_speed;
float horizontal_damping;
float current_gravity_multiplier;
float terminal_velocity;
/* jumping */
float jump_force_initial;
float jump_force_increase; /* aka "jump power", increment of dy per tick */
int jump_air_ticks;
int jump_air_timer;
int jump_coyote_ticks;
int jump_coyote_timer;
int jump_buffer_ticks;
int jump_buffer_timer;
/* gravity multipliers */
float jump_default_multiplier;
float jump_boosted_multiplier;
float jump_corner_correction_offset; /* from center */
};
struct player *player_create(struct world *world);
void player_destroy(struct player *player);
void player_calc(struct player *player);
#endif

View File

@ -0,0 +1,99 @@
#include "ingame.h"
#include "title.h"
#include "scene.h"
#include "townengine/game_api.h"
static void ingame_tick(struct state *state) {
struct scene_ingame *scn = (struct scene_ingame *)state->scene;
world_drawdef(scn->world);
player_calc(scn->player);
static t_camera cam = { .pos = { 0 }, .target = { 0, 0, -1 }, .up = { 0, -1, 0 }, .fov = (float)M_PI_2 };
if (input_is_action_pressed(&ctx.input, "player_left"))
cam.pos.x -= 0.01f;
if (input_is_action_pressed(&ctx.input, "player_right"))
cam.pos.x += 0.01f;
if (input_is_action_pressed(&ctx.input, "player_jump"))
cam.pos.z -= 0.01f;
push_camera(&cam);
push_sprite_ex((t_frect){ .x = 32, .y = 64, .w = 64, .h = 64 }, (t_push_sprite_args){
.path = "/assets/light.png",
.color = (t_color){255, 0, 0, 255}, });
push_sprite_ex((t_frect){ .x = 48, .y = 64, .w = 64, .h = 64 }, (t_push_sprite_args){
.path = "/assets/light.png",
.color = (t_color){0, 255, 0, 255}, });
push_sprite_ex((t_frect){ .x = 64, .y = 64, .w = 64, .h = 64 }, (t_push_sprite_args){
.path = "/assets/light.png",
.color = (t_color){0, 0, 255, 255}, });
push_sprite_ex((t_frect){ .x = 32, .y = 32, .w = 64, .h = 64 }, (t_push_sprite_args){
.path = "/assets/player/baron-walk.png",
.color = (t_color){255, 255, 255, 255},
.rotation = (float)M_PI * 2 * (float)(ctx.tick_count % 64) / 64, });
push_sprite_ex((t_frect){ .x = 64, .y = 32, .w = 64, .h = 64 }, (t_push_sprite_args){
.path = "/assets/player/baron-walk.png",
.color = (t_color){255, 255, 255, 255},
.rotation = (float)M_PI / 64, });
push_sprite_ex((t_frect){ .x = 96, .y = 32, .w = 64, .h = 64 }, (t_push_sprite_args){
.path = "/assets/player/baron-walk.png",
.color = (t_color){255, 255, 255, 255}, });
push_sprite_ex((t_frect){ .x = 128, .y = 32, .w = 128, .h = 64 }, (t_push_sprite_args){
.path = "/assets/player/baron-walk.png",
.color = (t_color){255, 255, 255, 255},
.rotation = (float)M_PI * 2 * (float)(ctx.tick_count % 64) / 64, });
unfurl_triangle("/assets/big-violet.png",
(t_fvec3){ -1, -1, 0 },
(t_fvec3){ 1, -1, 0 },
(t_fvec3){ 1, 1, 0 },
(t_shvec2){ 0, 2048 },
(t_shvec2){ 2048, 2048 },
(t_shvec2){ 2048, 0 });
unfurl_triangle("/assets/big-violet.png",
(t_fvec3){ 1, 1, 0 },
(t_fvec3){ -1, 1, 0 },
(t_fvec3){ -1, -1, 0 },
(t_shvec2){ 2048, 0 },
(t_shvec2){ 0, 0 },
(t_shvec2){ 0, 2048 });
}
static void ingame_end(struct state *state) {
struct scene_ingame *scn = (struct scene_ingame *)state->scene;
player_destroy(scn->player);
world_destroy(scn->world);
free(state->scene);
}
struct scene *ingame_scene(struct state *state) {
(void)state;
struct scene_ingame *new_scene = ccalloc(1, sizeof *new_scene);
new_scene->base.tick = ingame_tick;
new_scene->base.end = ingame_end;
new_scene->world = world_create();
new_scene->player = player_create(new_scene->world);
play_audio_ex("music/repeat-test.xm", "soundtrack", (t_play_audio_args){
.volume = 0.8f,
.panning = -0.5f,
.repeat = true,
});
return (struct scene *)new_scene;
}

View File

@ -0,0 +1,23 @@
#ifndef INGAME_H
#define INGAME_H
#include "townengine/game_api.h"
#include "../state.h"
#include "scene.h"
#include "../player.h"
#include "../world.h"
struct scene_ingame {
struct scene base;
struct world *world;
struct player *player;
};
struct scene *ingame_scene(struct state *state);
#endif

View File

@ -0,0 +1,8 @@
#include "scene.h"
#include "../state.h"
void switch_to(struct state *state, struct scene *(*scene_func)(struct state *)) {
state->next_scene = scene_func(state);
state->is_scene_switching = true;
}

View File

@ -0,0 +1,16 @@
#ifndef SCENE_H
#define SCENE_H
struct state;
struct scene {
char *id;
void (*tick)(struct state *);
void (*end)(struct state *);
};
void switch_to(struct state *state, struct scene *(*scene_func)(struct state *));
#endif

View File

@ -0,0 +1,42 @@
#include "title.h"
#include "ingame.h"
#include "../world.h"
#include "../player.h"
#include "townengine/game_api.h"
static void title_tick(struct state *state) {
struct scene_title *scn = (struct scene_title *)state->scene;
(void)scn;
if (input_is_action_just_pressed(&state->ctx->input, "ui_accept")) {
switch_to(state, ingame_scene);
}
push_sprite("/assets/title.png", (t_frect) {
(RENDER_BASE_WIDTH / 2) - (320 / 2), 64, 320, 128
});
}
static void title_end(struct state *state) {
struct scene_title *scn = (struct scene_title *)state->scene;
player_destroy(scn->player);
world_destroy(scn->world);
free(state->scene);
}
struct scene *title_scene(struct state *state) {
(void)state;
struct scene_title *new_scene = ccalloc(1, sizeof *new_scene);
new_scene->base.tick = title_tick;
new_scene->base.end = title_end;
new_scene->world = world_create();
new_scene->player = player_create(new_scene->world);
return (struct scene *)new_scene;
}

View File

@ -0,0 +1,23 @@
#ifndef TITLE_H
#define TITLE_H
#include "townengine/game_api.h"
#include "../state.h"
#include "scene.h"
#include "../player.h"
#include "../world.h"
struct scene_title {
struct scene base;
struct world *world;
struct player *player;
};
struct scene *title_scene(struct state *state);
#endif

16
apps/testgame/state.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef STATE_H
#define STATE_H
#include "townengine/game_api.h"
struct state {
t_ctx *ctx;
struct scene *scene;
struct scene *next_scene;
bool is_scene_switching;
};
#endif

187
apps/testgame/world.c Normal file
View File

@ -0,0 +1,187 @@
#include "world.h"
#include "townengine/game_api.h"
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include <tgmath.h>
static void update_tiles(struct world *world) {
for (size_t row = 0; row < world->tilemap_height; ++row) {
for (size_t col = 0; col < world->tilemap_width; ++col) {
world->tiles[(row * world->tilemap_width) + col] = (struct tile) {
.rect = (t_rect) {
.x = (int)col * world->tile_size,
.y = (int)row * world->tile_size,
.w = world->tile_size,
.h = world->tile_size,
},
.type = world->tilemap[row][col],
};
}
}
}
static t_vec2 to_grid_location(struct world *world, float x, float y) {
return (t_vec2) {
.x = (int)floor(x / (float)world->tile_size),
.y = (int)floor(y / (float)world->tile_size),
};
}
static void drawdef_debug(struct world *world) {
if (!ctx.debug) return;
for (size_t i = 0; i < world->tilemap_height * world->tilemap_width; ++i) {
if (world->tiles[i].type == TILE_TYPE_VOID) continue;
push_rectangle(to_frect(world->tiles[i].rect),
(t_color) { 255, 0, 255, 128 });
}
}
struct world *world_create(void) {
struct world *world = cmalloc(sizeof *world);
*world = (struct world) {
.tiles = NULL,
.tile_size = 42,
.tilemap_width = 20,
.tilemap_height = 12,
.gravity = 1,
};
/* create the tilemap */
/* it simply stores what's in each tile as a 2d array */
/* on its own, it's entirely unrelated to drawing or logic */
world->tilemap = cmalloc(sizeof *world->tilemap * world->tilemap_height);
for (size_t i = 0; i < world->tilemap_height; ++i) {
world->tilemap[i] = cmalloc(sizeof **world->tilemap * world->tilemap_width);
for (size_t j = 0; j < world->tilemap_width; ++j) {
world->tilemap[i][j] = TILE_TYPE_VOID;
}
}
for (size_t i = 0; i < 12; ++i) {
world->tilemap[world->tilemap_height-2][2+i] = TILE_TYPE_SOLID;
}
world->tilemap[world->tilemap_height-3][8] = TILE_TYPE_SOLID;
world->tilemap[world->tilemap_height-4][8] = TILE_TYPE_SOLID;
world->tilemap[world->tilemap_height-5][10] = TILE_TYPE_SOLID;
world->tilemap[world->tilemap_height-6][10] = TILE_TYPE_SOLID;
for (size_t i = 0; i < 7; ++i) {
world->tilemap[world->tilemap_height-6][12+i] = TILE_TYPE_SOLID;
}
/* the tiles array contains data meant to be used by other logic */
/* most importantly, it is used to draw the tiles */
const size_t tile_count = world->tilemap_height * world->tilemap_width;
world->tiles = cmalloc(sizeof *world->tiles * tile_count);
update_tiles(world);
return world;
}
void world_destroy(struct world *world) {
free(world->tiles);
for (size_t i = 0; i < world->tilemap_height; ++i) {
free(world->tilemap[i]);
}
free(world->tilemap);
free(world);
}
void world_drawdef(struct world *world) {
for (size_t i = 0; i < world->tilemap_height * world->tilemap_width; ++i) {
if (world->tiles[i].type == TILE_TYPE_VOID)
continue;
push_sprite("/assets/white.png", to_frect(world->tiles[i].rect));
}
drawdef_debug(world);
}
bool world_find_intersect_frect(struct world *world, t_frect rect, t_frect *intersection) {
bool is_intersecting = false;
const size_t tile_count = world->tilemap_height * world->tilemap_width;
for (size_t i = 0; i < tile_count; ++i) {
if (world->tiles[i].type == TILE_TYPE_VOID)
continue;
t_frect tile_frect = {
.x = (float)(world->tiles[i].rect.x),
.y = (float)(world->tiles[i].rect.y),
.w = (float)(world->tiles[i].rect.w),
.h = (float)(world->tiles[i].rect.h),
};
if (intersection == NULL) {
t_frect temp;
is_intersecting = intersect_frect(&rect, &tile_frect, &temp);
} else {
is_intersecting = intersect_frect(&rect, &tile_frect, intersection);
}
if (is_intersecting)
break;
}
return is_intersecting;
}
bool world_find_intersect_rect(struct world *world, t_rect rect, t_rect *intersection) {
bool is_intersecting = false;
const size_t tile_count = world->tilemap_height * world->tilemap_width;
for (size_t i = 0; i < tile_count; ++i) {
if (world->tiles[i].type == TILE_TYPE_VOID)
continue;
t_rect *tile_rect = &world->tiles[i].rect;
if (intersection == NULL) {
t_rect temp;
is_intersecting = intersect_rect(&rect, tile_rect, &temp);
} else {
is_intersecting = intersect_rect(&rect, tile_rect, intersection);
}
if (is_intersecting)
break;
}
return is_intersecting;
}
bool world_is_tile_at(struct world *world, float x, float y) {
t_vec2 position_in_grid = to_grid_location(world, x, y);
return world->tilemap[position_in_grid.y][position_in_grid.x] != TILE_TYPE_VOID;
}
void world_place_tile(struct world *world, float x, float y) {
t_vec2 position_in_grid = to_grid_location(world, x, y);
world->tilemap[position_in_grid.y][position_in_grid.x] = TILE_TYPE_SOLID;
update_tiles(world);
}
void world_remove_tile(struct world *world, float x, float y) {
t_vec2 position_in_grid = to_grid_location(world, x, y);
world->tilemap[position_in_grid.y][position_in_grid.x] = TILE_TYPE_VOID;
update_tiles(world);
}

44
apps/testgame/world.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef WORLD_H
#define WORLD_H
#include "townengine/game_api.h"
#include <stdint.h>
#include <stdbool.h>
enum tile_type {
TILE_TYPE_VOID,
TILE_TYPE_SOLID,
};
struct tile {
t_rect rect;
enum tile_type type;
};
struct world {
enum tile_type **tilemap;
struct tile *tiles;
int tile_size;
unsigned int tilemap_width;
unsigned int tilemap_height;
size_t tile_nonvoid_count;
float gravity;
};
struct world *world_create(void);
void world_destroy(struct world *world);
void world_drawdef(struct world *world);
bool world_find_intersect_frect(struct world *world, t_frect rect, t_frect *intersection);
bool world_find_intersect_rect(struct world *world, t_rect rect, t_rect *intersection);
bool world_is_tile_at(struct world *world, float x, float y);
void world_place_tile(struct world *world, float x, float y);
void world_remove_tile(struct world *world, float x, float y);
#endif