application separation
This commit is contained in:
23
apps/template/CMakeLists.txt
Normal file
23
apps/template/CMakeLists.txt
Normal 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
3
apps/template/build.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/env sh
|
||||
|
||||
cmake -B .build "$@" && cmake --build .build
|
26
apps/template/game.c
Normal file
26
apps/template/game.c
Normal 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
13
apps/template/game.h
Normal 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
12
apps/template/state.h
Normal 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
|
31
apps/testgame/CMakeLists.txt
Normal file
31
apps/testgame/CMakeLists.txt
Normal 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
3
apps/testgame/build.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/env sh
|
||||
|
||||
cmake -B .build "$@" && cmake --build .build
|
64
apps/testgame/game.c
Normal file
64
apps/testgame/game.c
Normal 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
14
apps/testgame/game.h
Normal 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
303
apps/testgame/player.c
Normal 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
66
apps/testgame/player.h
Normal 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
|
99
apps/testgame/scenes/ingame.c
Normal file
99
apps/testgame/scenes/ingame.c
Normal 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;
|
||||
}
|
23
apps/testgame/scenes/ingame.h
Normal file
23
apps/testgame/scenes/ingame.h
Normal 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
|
8
apps/testgame/scenes/scene.c
Normal file
8
apps/testgame/scenes/scene.c
Normal 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;
|
||||
}
|
16
apps/testgame/scenes/scene.h
Normal file
16
apps/testgame/scenes/scene.h
Normal 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
|
42
apps/testgame/scenes/title.c
Normal file
42
apps/testgame/scenes/title.c
Normal 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;
|
||||
}
|
23
apps/testgame/scenes/title.h
Normal file
23
apps/testgame/scenes/title.h
Normal 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
16
apps/testgame/state.h
Normal 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
187
apps/testgame/world.c
Normal 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
44
apps/testgame/world.h
Normal 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
|
Reference in New Issue
Block a user