Compare commits
243 commits
main
...
beat-em-up
Author | SHA1 | Date | |
---|---|---|---|
![]() |
208743c2f1 | ||
![]() |
f67f95fd6a | ||
![]() |
a63cfdd2b0 | ||
![]() |
3d3d95030b | ||
![]() |
5edea42188 | ||
![]() |
b641589188 | ||
![]() |
630f60af94 | ||
![]() |
6b8dbaee7b | ||
![]() |
2895ce6079 | ||
![]() |
c50a3f3563 | ||
![]() |
c939895390 | ||
![]() |
3fbaca9dac | ||
![]() |
6194f6fc9f | ||
![]() |
cac345fe43 | ||
![]() |
58b41bd1d2 | ||
![]() |
a978d140c7 | ||
![]() |
02e346d887 | ||
![]() |
1d919fef38 | ||
![]() |
aec965f3f6 | ||
![]() |
23741714f2 | ||
![]() |
05451c6ca3 | ||
![]() |
6035de1ad4 | ||
![]() |
ae25ebdaa0 | ||
![]() |
c4dc2a8ccc | ||
![]() |
1d92c6b827 | ||
![]() |
16d2d8ebf6 | ||
![]() |
4188047bbc | ||
![]() |
368332b2b3 | ||
![]() |
8c5aa78195 | ||
![]() |
6f6cd9032f | ||
![]() |
fc5deef324 | ||
![]() |
2d2d0bb7ca | ||
![]() |
953aacde64 | ||
![]() |
afec949efd | ||
![]() |
f4e0d89d0f | ||
![]() |
c2423f146a | ||
![]() |
6f2947bcab | ||
![]() |
f1cf1110fc | ||
![]() |
0616d1d3a1 | ||
![]() |
a27acee155 | ||
![]() |
73104a02c4 | ||
![]() |
526ff0b0c6 | ||
![]() |
27fbda2c6d | ||
![]() |
25badcb847 | ||
![]() |
92bf07d050 | ||
![]() |
3756280796 | ||
![]() |
058c8e18fa | ||
![]() |
5e99651a0e | ||
![]() |
c947316369 | ||
![]() |
40194b9e6c | ||
![]() |
3c15d808b5 | ||
![]() |
2f26672fc4 | ||
![]() |
e9ec0e9237 | ||
![]() |
1304b572d7 | ||
![]() |
9597d612af | ||
![]() |
986ab06478 | ||
![]() |
fc76049fc6 | ||
![]() |
85c1952cc2 | ||
![]() |
28ab75bf8d | ||
![]() |
f30902e8c9 | ||
![]() |
888fb3e383 | ||
![]() |
857783d42b | ||
![]() |
d98b01c9d6 | ||
![]() |
e6055ed4e6 | ||
![]() |
c66a8d8705 | ||
![]() |
70d9c93f63 | ||
![]() |
0ba96010eb | ||
![]() |
f8047369ca | ||
![]() |
6175e52297 | ||
![]() |
4265148156 | ||
![]() |
9d220a1cb8 | ||
![]() |
6ffdf9f5a4 | ||
![]() |
2dec4da52c | ||
![]() |
96acaa0a24 | ||
![]() |
559c86ddaf | ||
![]() |
5c61152007 | ||
![]() |
46ef92ef92 | ||
![]() |
8a05984afa | ||
![]() |
f50d6a0b93 | ||
![]() |
3f5ff9da55 | ||
![]() |
2f9cd32f6c | ||
![]() |
1fb0aedac2 | ||
![]() |
255c4ce6db | ||
![]() |
7e9dc4003b | ||
![]() |
b2f6f9c176 | ||
![]() |
95f5bcfee7 | ||
![]() |
aca01507ed | ||
![]() |
1dc6f8352a | ||
![]() |
e13f9fc63a | ||
![]() |
0c6f1dd8cf | ||
![]() |
760d9f2879 | ||
![]() |
1a3c4c9676 | ||
![]() |
d14e030545 | ||
![]() |
a17915dbfb | ||
![]() |
bf23ff877a | ||
![]() |
be64a55588 | ||
![]() |
39f89d5127 | ||
![]() |
9af31f6695 | ||
![]() |
5fac1f1629 | ||
![]() |
f201fe50f5 | ||
![]() |
cca9b97378 | ||
![]() |
9fbbd833a5 | ||
![]() |
c2229a5bba | ||
![]() |
264141ceb5 | ||
![]() |
97cea9c8c7 | ||
![]() |
0d7eb82ea7 | ||
![]() |
086e8fc449 | ||
![]() |
04ade25772 | ||
![]() |
26bea065b9 | ||
![]() |
29a40ce082 | ||
![]() |
af6c8d33ea | ||
![]() |
fbdfef79a2 | ||
![]() |
2bf6bea14f | ||
![]() |
f76cb2d57b | ||
![]() |
879d72e10f | ||
![]() |
1fbb3b1530 | ||
![]() |
b3622ab279 | ||
![]() |
58e93e3336 | ||
![]() |
c0203325b6 | ||
![]() |
3e8c7d4bb7 | ||
![]() |
f3f48780c0 | ||
![]() |
87c976824e | ||
![]() |
087b74244e | ||
![]() |
1dae0d7f12 | ||
![]() |
804d8784a3 | ||
![]() |
dd2f0799cb | ||
![]() |
39999eaabd | ||
![]() |
71d8ebd45c | ||
![]() |
02f48c5fb3 | ||
![]() |
53af5fb66d | ||
![]() |
b1cecfc0ae | ||
![]() |
5e371a3754 | ||
![]() |
965ae37c7e | ||
![]() |
a4253abfa1 | ||
![]() |
163fac8f02 | ||
![]() |
ece2a24a78 | ||
![]() |
b1a4fa2f0a | ||
![]() |
f2159a31a9 | ||
![]() |
20c5d9dfe1 | ||
![]() |
b82c0a5765 | ||
![]() |
94cc7e8c6c | ||
![]() |
e0a05e546e | ||
![]() |
92907b1b82 | ||
![]() |
c9b6a0da60 | ||
![]() |
0b0f4bcdfb | ||
![]() |
9c68fca766 | ||
![]() |
240186c8de | ||
![]() |
84a940d046 | ||
![]() |
6a2a612594 | ||
![]() |
29b2852cea | ||
![]() |
1bba6c86f1 | ||
![]() |
966f7b9efe | ||
![]() |
ac8cacd2fc | ||
![]() |
7851fc5a24 | ||
![]() |
c406a0a8b9 | ||
![]() |
36d9fee5f4 | ||
![]() |
67b138741b | ||
![]() |
73cd7c5f96 | ||
![]() |
c1e100211e | ||
![]() |
14e006344e | ||
![]() |
b2dda356a1 | ||
![]() |
e2c4f5d4c9 | ||
![]() |
9adfac023f | ||
![]() |
333ada2752 | ||
![]() |
a70658bab6 | ||
![]() |
5043f1a53e | ||
![]() |
90dc3e1792 | ||
![]() |
14877b0a28 | ||
![]() |
eb871a9d5f | ||
![]() |
70a64dfc77 | ||
![]() |
74e633f070 | ||
![]() |
e7e952e57d | ||
![]() |
27d6c7e0d7 | ||
![]() |
3f9bfa25fd | ||
![]() |
c1a043c4a7 | ||
![]() |
05334eb8ee | ||
![]() |
01d9a9417d | ||
![]() |
d1870e7a19 | ||
![]() |
7d1bce119b | ||
![]() |
e679bc4a0a | ||
![]() |
0d28097818 | ||
![]() |
fa38d1a7ca | ||
![]() |
54efa7c83e | ||
![]() |
1ee59154fc | ||
![]() |
adaa01ae4b | ||
![]() |
37ec5a7558 | ||
![]() |
2b7af06d00 | ||
![]() |
5d512b6c7a | ||
![]() |
2f65ef014f | ||
![]() |
1d18873f4b | ||
![]() |
dd0af050c9 | ||
![]() |
08a5befc82 | ||
![]() |
8c3bcbce85 | ||
![]() |
f7607f71ab | ||
![]() |
3e0490d5bf | ||
![]() |
1ffbd74a08 | ||
![]() |
b0a4de6037 | ||
![]() |
efdd5f29db | ||
![]() |
3c647ccfc2 | ||
![]() |
2b1aa6236f | ||
![]() |
36d378ec37 | ||
![]() |
913bb32a39 | ||
![]() |
51b889c6ef | ||
![]() |
5d8d996c91 | ||
![]() |
663d6e36c2 | ||
![]() |
686c3c7578 | ||
![]() |
109cd51d14 | ||
![]() |
918e73c306 | ||
![]() |
ad983812f6 | ||
![]() |
f300c8b4a5 | ||
![]() |
cd2de223fc | ||
![]() |
9e643e7cec | ||
![]() |
0ccdc8c5d0 | ||
![]() |
d57bc1e4e2 | ||
![]() |
7a0a60846a | ||
![]() |
fd0183d34a | ||
![]() |
d4d650c2c7 | ||
![]() |
5aae7dd1a5 | ||
![]() |
2032488149 | ||
![]() |
3ebc370ca4 | ||
![]() |
5cb53f39f2 | ||
![]() |
9f3bfcb879 | ||
![]() |
76c9ecd2e9 | ||
![]() |
960783ac42 | ||
![]() |
4e5ed0af94 | ||
![]() |
8961e38113 | ||
![]() |
810761aedf | ||
![]() |
db2938353b | ||
![]() |
c78dbba674 | ||
![]() |
6e5d16e4cc | ||
![]() |
5755efb529 | ||
![]() |
f435a96052 | ||
![]() |
e479ecf0ad | ||
![]() |
6bd6b348f0 | ||
![]() |
9d34f2cbeb | ||
![]() |
c440e7d435 | ||
![]() |
23a134aedc | ||
![]() |
c10c4ad629 | ||
![]() |
c6d4351c48 | ||
![]() |
9bc74046d6 | ||
![]() |
5ab93d62ad | ||
![]() |
96867d6e1b | ||
![]() |
956b2ab02f |
22
.gitignore
vendored
|
@ -1,8 +1,16 @@
|
||||||
.cache/clangd/index
|
.vscode
|
||||||
bin
|
.cache
|
||||||
maps/fencer.tiled-session
|
compile_commands/
|
||||||
fencer
|
bin/
|
||||||
build/obj/Debug
|
build/
|
||||||
build/Makefile
|
intermediate/
|
||||||
Makefile
|
packages/
|
||||||
|
*.vcxproj
|
||||||
|
*.vcxproj.user
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
|
Makefile
|
||||||
|
.vs
|
||||||
|
.idea
|
||||||
|
*.sln
|
||||||
|
.kdev4
|
||||||
|
fencer.kdev4
|
||||||
|
|
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "core/src/utils"]
|
||||||
|
path = core/src/utils
|
||||||
|
url = git@git.saragerretsen.nl:Sara/cutes.git
|
25
Build.lua
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
workspace "Fencer-Template"
|
||||||
|
architecture "x64"
|
||||||
|
configurations { "Debug", "Release", "Dist" }
|
||||||
|
language "C"
|
||||||
|
startproject "Game"
|
||||||
|
|
||||||
|
filter "system:windows"
|
||||||
|
defines { "SDL_MAIN_HANDLED", "_CRT_SECURE_NO_WARNINGS" }
|
||||||
|
buildoptions { "/EHsc", "/Zc:preprocessor" }
|
||||||
|
|
||||||
|
OutputDir = "%{cfg.system}-%{cfg.architecture}/%{cfg.buildcfg}"
|
||||||
|
|
||||||
|
group "Core"
|
||||||
|
include "core/Build-Core.lua"
|
||||||
|
group ""
|
||||||
|
|
||||||
|
libdirs {
|
||||||
|
os.findlib("SDL2"),
|
||||||
|
os.findlib("SDL2_image"),
|
||||||
|
os.findlib("SDL2_ttf"),
|
||||||
|
os.findlib("m"),
|
||||||
|
os.findlib("cJSON")
|
||||||
|
}
|
||||||
|
|
||||||
|
include "game/Build-Game.lua"
|
BIN
SDL2_image.dll
Normal file
BIN
SDL2_image.lib
Normal file
BIN
SDL2main.lib
Normal file
BIN
SDL2test.lib
Normal file
35
core/Build-Core.lua
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
project "Engine-Core"
|
||||||
|
kind "StaticLib"
|
||||||
|
language "C"
|
||||||
|
targetdir "bin/%{cfg.buildcfg}"
|
||||||
|
staticruntime "off"
|
||||||
|
|
||||||
|
defines { "VMATH_SDL" }
|
||||||
|
|
||||||
|
files { "src/**.c" }
|
||||||
|
includedirs { "src/" }
|
||||||
|
includedirs { "src/utils" }
|
||||||
|
|
||||||
|
targetdir ( "../bin/" .. OutputDir .. "/%{prj.name}" )
|
||||||
|
objdir ( "../intermediate/" .. OutputDir .. "/%{prj.name}" )
|
||||||
|
|
||||||
|
filter "system:windows"
|
||||||
|
systemversion "latest"
|
||||||
|
defines {}
|
||||||
|
|
||||||
|
filter "configurations:Debug"
|
||||||
|
defines { "DEBUG" }
|
||||||
|
runtime "Debug"
|
||||||
|
symbols "On"
|
||||||
|
|
||||||
|
filter "configurations:Release"
|
||||||
|
defines { "RELEASE" }
|
||||||
|
runtime "Release"
|
||||||
|
optimize "On"
|
||||||
|
symbols "On"
|
||||||
|
|
||||||
|
filter "configurations:Dist"
|
||||||
|
defines { "DIST" }
|
||||||
|
runtime "Release"
|
||||||
|
optimize "On"
|
||||||
|
symbols "Off"
|
114
core/src/animation_sprite.c
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
#include "animation_sprite.h"
|
||||||
|
#include "debug.h"
|
||||||
|
#include "program.h"
|
||||||
|
#include "list.h"
|
||||||
|
|
||||||
|
struct AnimationSprite {
|
||||||
|
Spritesheet *sheet;
|
||||||
|
Sprite *sprite_target;
|
||||||
|
|
||||||
|
AnimationSpriteLoopMode loop_mode;
|
||||||
|
|
||||||
|
float frame_interval;
|
||||||
|
float start_time;
|
||||||
|
|
||||||
|
size_t event_index;
|
||||||
|
List events;
|
||||||
|
};
|
||||||
|
|
||||||
|
AnimationSprite *animation_sprite_new(Sprite *target_sprite, Spritesheet *sheet,
|
||||||
|
float framerate, AnimationSpriteLoopMode loop_mode,
|
||||||
|
AnimationEvent *events, size_t event_count) {
|
||||||
|
AnimationSprite *self = malloc(sizeof(AnimationSprite));
|
||||||
|
ASSERT_RETURN(self != NULL, NULL, "Failed to allocate memory for AnimationSprite");
|
||||||
|
*self = (AnimationSprite){
|
||||||
|
.sheet = sheet,
|
||||||
|
.frame_interval = 1.0f / framerate,
|
||||||
|
.loop_mode = loop_mode,
|
||||||
|
.start_time = game_time(),
|
||||||
|
.sprite_target = target_sprite,
|
||||||
|
.event_index = 0,
|
||||||
|
.events = list_with_len(sizeof(AnimationEvent), event_count),
|
||||||
|
};
|
||||||
|
if(event_count > 0) {
|
||||||
|
memcpy(self->events.data, events, sizeof(AnimationEvent) * event_count);
|
||||||
|
self->events.len = event_count;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
void animation_sprite_destroy(AnimationSprite *self) {
|
||||||
|
spritesheet_destroy(self->sheet);
|
||||||
|
free(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
void animation_sprite_reset(AnimationSprite *self) {
|
||||||
|
self->start_time = game_time();
|
||||||
|
}
|
||||||
|
|
||||||
|
void animation_sprite_play_from(AnimationSprite *self, float normalized_time) {
|
||||||
|
self->start_time = game_time() - normalized_time * animation_sprite_get_length(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
void animation_sprite_draw(AnimationSprite *self, Transform *transform) {
|
||||||
|
const size_t frame_count = spritesheet_get_tile_count(self->sheet);
|
||||||
|
const float time = game_time() - self->start_time;
|
||||||
|
size_t frame = (size_t)(time / self->frame_interval);
|
||||||
|
switch(self->loop_mode) {
|
||||||
|
case LoopMode_Stop:
|
||||||
|
if(frame >= frame_count)
|
||||||
|
frame = frame_count - 1;
|
||||||
|
case LoopMode_Hide:
|
||||||
|
if(frame >= frame_count)
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
case LoopMode_Loop:
|
||||||
|
frame %= frame_count;
|
||||||
|
break;
|
||||||
|
case LoopMode_PingPong:
|
||||||
|
frame %= frame_count * 2;
|
||||||
|
if(frame >= frame_count)
|
||||||
|
frame = frame_count - (frame - frame_count);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprite_set_spritesheet(self->sprite_target, self->sheet);
|
||||||
|
sprite_set_tile(self->sprite_target, frame);
|
||||||
|
sprite_draw(self->sprite_target, *transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
void animation_sprite_update_events(AnimationSprite *self) {
|
||||||
|
const float current_time = game_time() - self->start_time;
|
||||||
|
for(;self->event_index <= self->events.len; ++self->event_index) {
|
||||||
|
AnimationEvent *event = list_at_as(AnimationEvent, &self->events, self->event_index);
|
||||||
|
if(event->time < current_time)
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
event->fn(event->target, event->arg.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float animation_sprite_get_length(AnimationSprite *self) {
|
||||||
|
return (float)spritesheet_get_tile_count(self->sheet) * self->frame_interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
void animation_sprite_set_framerate(AnimationSprite *self, float framerate) {
|
||||||
|
self->frame_interval = 1.0f / framerate;
|
||||||
|
}
|
||||||
|
|
||||||
|
float animation_sprite_get_framerate(const AnimationSprite *self) {
|
||||||
|
return 1.0f / self->frame_interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sprite *animation_sprite_get_sprite(AnimationSprite *self) {
|
||||||
|
return self->sprite_target;
|
||||||
|
}
|
||||||
|
|
||||||
|
float animation_sprite_get_time(AnimationSprite *self) {
|
||||||
|
return game_time() - self->start_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
float animation_sprite_get_time_normalized(AnimationSprite *self) {
|
||||||
|
return animation_sprite_get_time(self) / animation_sprite_get_length(self);
|
||||||
|
}
|
42
core/src/animation_sprite.h
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
#ifndef _fencer_animation_sprite_h
|
||||||
|
#define _fencer_animation_sprite_h
|
||||||
|
|
||||||
|
#include "drop.h"
|
||||||
|
#include "sprite.h"
|
||||||
|
#include "spritesheet.h"
|
||||||
|
|
||||||
|
typedef enum AnimationSpriteLoopMode {
|
||||||
|
LoopMode_Stop,
|
||||||
|
LoopMode_Hide,
|
||||||
|
LoopMode_Loop,
|
||||||
|
LoopMode_PingPong,
|
||||||
|
} AnimationSpriteLoopMode;
|
||||||
|
|
||||||
|
typedef void (*AnimationEventFn)(void *object, void *arg);
|
||||||
|
|
||||||
|
typedef struct AnimationEvent {
|
||||||
|
float time;
|
||||||
|
void *target;
|
||||||
|
Drop arg;
|
||||||
|
AnimationEventFn fn;
|
||||||
|
} AnimationEvent;
|
||||||
|
|
||||||
|
typedef struct AnimationSprite AnimationSprite;
|
||||||
|
|
||||||
|
extern AnimationSprite *animation_sprite_new(Sprite *target_sprite, Spritesheet *sheet, float framerate, AnimationSpriteLoopMode loop_mode, AnimationEvent *events, size_t event_count);
|
||||||
|
extern void animation_sprite_destroy(AnimationSprite *self);
|
||||||
|
|
||||||
|
extern void animation_sprite_reset(AnimationSprite *self);
|
||||||
|
extern void animation_sprite_play_from(AnimationSprite *self, float normalized_time);
|
||||||
|
extern void animation_sprite_draw(AnimationSprite *self, Transform *transform);
|
||||||
|
|
||||||
|
extern void animation_sprite_update_events(AnimationSprite *self);
|
||||||
|
|
||||||
|
extern float animation_sprite_get_length(AnimationSprite *self);
|
||||||
|
extern void animation_sprite_set_framerate(AnimationSprite *self, float framerate);
|
||||||
|
extern float animation_sprite_get_framerate(const AnimationSprite *self);
|
||||||
|
extern Sprite *animation_sprite_get_sprite(AnimationSprite *self);
|
||||||
|
extern float animation_sprite_get_time(AnimationSprite *self);
|
||||||
|
extern float animation_sprite_get_time_normalized(AnimationSprite *self);
|
||||||
|
|
||||||
|
#endif // !_fencer_animation_sprite_h
|
|
@ -19,11 +19,11 @@ typedef struct Asset {
|
||||||
} Asset;
|
} Asset;
|
||||||
|
|
||||||
#define impl_Asset_for(T, get_id_f, set_id_f)\
|
#define impl_Asset_for(T, get_id_f, set_id_f)\
|
||||||
static inline Asset T##_as_Asset(T* x) {\
|
Asset T##_as_Asset(T* x) {\
|
||||||
TC_FN_TYPECHECK(asset_id, get_id_f, T*);\
|
TC_FN_TYPECHECK(asset_id, get_id_f, T*);\
|
||||||
TC_FN_TYPECHECK(void, set_id_f, T*, asset_id);\
|
TC_FN_TYPECHECK(void, set_id_f, T*, asset_id);\
|
||||||
TC_FN_TYPECHECK(Drop, T##_as_Drop, T*);\
|
TC_FN_TYPECHECK(Drop, T##_as_Drop, T*);\
|
||||||
static IAsset tc = (IAsset){\
|
static IAsset const tc = {\
|
||||||
.get_id = (asset_id(*const)(void*)) get_id_f,\
|
.get_id = (asset_id(*const)(void*)) get_id_f,\
|
||||||
.set_id = (void(*const)(void*,asset_id)) set_id_f,\
|
.set_id = (void(*const)(void*,asset_id)) set_id_f,\
|
||||||
};\
|
};\
|
|
@ -10,16 +10,16 @@ static asset_id _next_id = 0;
|
||||||
|
|
||||||
static
|
static
|
||||||
size_t file_length(FILE* fp) {
|
size_t file_length(FILE* fp) {
|
||||||
size_t start = ftell(fp);
|
long start = ftell(fp);
|
||||||
fseek(fp, 0, SEEK_END);
|
fseek(fp, 0, SEEK_END);
|
||||||
size_t r = ftell(fp);
|
long r = ftell(fp);
|
||||||
fseek(fp, start, SEEK_SET);
|
fseek(fp, start, SEEK_SET);
|
||||||
return r;
|
return (size_t)r;
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static
|
||||||
void read_file(FILE* fp, char* out_buffer, size_t out_size) {
|
void read_file(FILE* fp, char* out_buffer, size_t out_size) {
|
||||||
size_t start = ftell(fp);
|
long start = ftell(fp);
|
||||||
fread(out_buffer, 1, out_size, fp);
|
fread(out_buffer, 1, out_size, fp);
|
||||||
fseek(fp, start, SEEK_SET);
|
fseek(fp, start, SEEK_SET);
|
||||||
}
|
}
|
||||||
|
@ -88,15 +88,13 @@ asset_id get_asset_id(void* asset) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void free_asset(asset_id id) {
|
void free_asset(asset_id id) {
|
||||||
Asset* found;
|
Asset* found = NULL;
|
||||||
size_t found_index;
|
size_t found_index = _assets.len;
|
||||||
for(size_t i = 0; i < _assets.len; ++i) {
|
for(size_t i = 0; i < _assets.len; ++i) {
|
||||||
found = list_at_as(Asset, &_assets, i);
|
found = list_at_as(Asset, &_assets, i);
|
||||||
if(found->tc->get_id(found->data) == id) {
|
if(found->tc->get_id(found->data) == id) {
|
||||||
found_index = i;
|
found_index = i;
|
||||||
break;
|
break;
|
||||||
} else {
|
|
||||||
found = NULL;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ASSERT_RETURN(found != NULL,, "Attempt to free nonexistent asset.");
|
ASSERT_RETURN(found != NULL,, "Attempt to free nonexistent asset.");
|
|
@ -31,8 +31,8 @@ size_t json_array_len(cJSON* array);
|
||||||
static inline
|
static inline
|
||||||
Vector json_array_to_vector(cJSON* array) {
|
Vector json_array_to_vector(cJSON* array) {
|
||||||
return (Vector) {
|
return (Vector) {
|
||||||
cJSON_GetArrayItem(array, 0)->valuedouble,
|
(float)cJSON_GetArrayItem(array, 0)->valuedouble,
|
||||||
cJSON_GetArrayItem(array, 1)->valuedouble,
|
(float)cJSON_GetArrayItem(array, 1)->valuedouble,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
static inline
|
static inline
|
|
@ -2,36 +2,40 @@
|
||||||
#define _update_entity_h
|
#define _update_entity_h
|
||||||
|
|
||||||
#include "drop.h"
|
#include "drop.h"
|
||||||
|
#include "mirror.h"
|
||||||
#include "typeclass_helpers.h"
|
#include "typeclass_helpers.h"
|
||||||
#include "vmath.h"
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
void (*const spawn)(void* self, Vector at);
|
|
||||||
void (*const update)(void* self, float dt);
|
void (*const update)(void* self, float dt);
|
||||||
void (*const start)(void* self);
|
void (*const start)(void* self);
|
||||||
void (*const draw)(void* self);
|
void (*const draw)(void* self);
|
||||||
|
long (*const get_depth)(void* self);
|
||||||
} IEntityBehaviour;
|
} IEntityBehaviour;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
void* data;
|
void* data;
|
||||||
IEntityBehaviour const* tc;
|
IEntityBehaviour const* tc;
|
||||||
IDrop const* drop;
|
IDrop const* drop;
|
||||||
|
IMirror const* mirror;
|
||||||
} BehaviourEntity;
|
} BehaviourEntity;
|
||||||
|
|
||||||
#define impl_BehaviourEntity_for(T, start_f, update_f, spawn_f, draw_f)\
|
#define impl_BehaviourEntity_for(T, start_f, update_f, draw_f, get_depth_f)\
|
||||||
static inline BehaviourEntity T##_as_BehaviourEntity(T* x) {\
|
BehaviourEntity T##_as_BehaviourEntity(T* x) {\
|
||||||
TC_FN_TYPECHECK(void, start_f, T*);\
|
TC_FN_TYPECHECK(void, start_f, T*);\
|
||||||
TC_FN_TYPECHECK(void, update_f, T*, float);\
|
TC_FN_TYPECHECK(void, update_f, T*, float);\
|
||||||
TC_FN_TYPECHECK(void, draw_f, T*);\
|
TC_FN_TYPECHECK(void, draw_f, T*);\
|
||||||
|
TC_FN_TYPECHECK(long, get_depth_f, T*);\
|
||||||
static IEntityBehaviour const tc = {\
|
static IEntityBehaviour const tc = {\
|
||||||
.spawn = (void(*const)(void*, Vector)) spawn_f,\
|
|
||||||
.update = (void(*const)(void*, float)) update_f,\
|
.update = (void(*const)(void*, float)) update_f,\
|
||||||
.start = (void(*const)(void*)) start_f,\
|
.start = (void(*const)(void*)) start_f,\
|
||||||
.draw = (void(*const)(void*)) draw_f,\
|
.draw = (void(*const)(void*)) draw_f,\
|
||||||
|
.get_depth=(long(*const)(void*)) get_depth_f,\
|
||||||
};\
|
};\
|
||||||
TC_FN_TYPECHECK(Drop, T##_as_Drop, T*);\
|
TC_FN_TYPECHECK(Drop, T##_as_Drop, T*);\
|
||||||
|
TC_FN_TYPECHECK(Mirror, T##_as_Mirror, T*);\
|
||||||
IDrop const* drop = T##_as_Drop(x).tc;\
|
IDrop const* drop = T##_as_Drop(x).tc;\
|
||||||
return (BehaviourEntity){.tc = &tc, .drop = drop, .data = x};\
|
IMirror const* mirror = T##_as_Mirror(x).tc;\
|
||||||
|
return (BehaviourEntity){.data = x, .tc = &tc, .drop = drop, .mirror = mirror};\
|
||||||
}\
|
}\
|
||||||
|
|
||||||
#endif // !_update_entity_h
|
#endif // !_update_entity_h
|
|
@ -21,7 +21,7 @@ SDL_FRect camera_world_to_pixel_rect(Camera* self, SDL_FRect* world_space) {
|
||||||
t.scale = OneVector;
|
t.scale = OneVector;
|
||||||
t = transform_invert(t);
|
t = transform_invert(t);
|
||||||
|
|
||||||
Vector tl = {world_space->x + (self->fov / 2.0), world_space->y + (_camera_height(self) / 2.0)};
|
Vector tl = {world_space->x + (self->fov / 2.0f), world_space->y + (_camera_height(self) / 2.0f)};
|
||||||
Vector size = {world_space->w, world_space->h};
|
Vector size = {world_space->w, world_space->h};
|
||||||
|
|
||||||
tl = vmulff(transform_point(&t, tl), g_render_resolution.x / self->fov);
|
tl = vmulff(transform_point(&t, tl), g_render_resolution.x / self->fov);
|
||||||
|
@ -40,7 +40,7 @@ Vector camera_world_to_pixel_point(Camera* self, Vector point) {
|
||||||
t.scale = OneVector;
|
t.scale = OneVector;
|
||||||
t = transform_invert(t);
|
t = transform_invert(t);
|
||||||
|
|
||||||
point = (Vector){point.x + (self->fov / 2.0), point.y + (_camera_height(self) / 2.0)};
|
point = (Vector){point.x + (self->fov / 2.0f), point.y + (_camera_height(self) / 2.0f)};
|
||||||
|
|
||||||
return vmulff(transform_point(&t, point), g_render_resolution.x / self->fov);
|
return vmulff(transform_point(&t, point), g_render_resolution.x / self->fov);
|
||||||
}
|
}
|
77
core/src/collider.c
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
#include "collider.h"
|
||||||
|
#include "collision.h"
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
struct Collider {
|
||||||
|
Shape* shape;
|
||||||
|
PhysicsEntity owner;
|
||||||
|
RigidBody* body;
|
||||||
|
PhysicsMask layers;
|
||||||
|
PhysicsMask mask;
|
||||||
|
int overlap;
|
||||||
|
};
|
||||||
|
|
||||||
|
Collider* collider_new(PhysicsEntity owner, Shape* shape, int overlap, PhysicsMask layers, PhysicsMask mask) {
|
||||||
|
Collider* self = malloc(sizeof(Collider));
|
||||||
|
ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for Collider");
|
||||||
|
*self = (Collider) {
|
||||||
|
.shape = shape,
|
||||||
|
.owner = owner,
|
||||||
|
.body = owner.tc->get_rigidbody(owner.data),
|
||||||
|
.layers = layers,
|
||||||
|
.mask = mask,
|
||||||
|
.overlap = overlap,
|
||||||
|
};
|
||||||
|
rigidbody_add_collider(self->body, self);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
void collider_destroy(Collider* self) {
|
||||||
|
shape_destroy(self->shape);
|
||||||
|
rigidbody_remove_collider(self->body, self);
|
||||||
|
free(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
PhysicsQuery collider_to_query(Collider* self) {
|
||||||
|
return (PhysicsQuery) {
|
||||||
|
.shape = self->shape,
|
||||||
|
.transform = rigidbody_get_transform(self->body),
|
||||||
|
.mask = self->mask
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Shape* collider_get_shape(Collider* self) {
|
||||||
|
return self->shape;
|
||||||
|
}
|
||||||
|
|
||||||
|
RigidBody* collider_get_rigidbody(Collider* self) {
|
||||||
|
return self->body;
|
||||||
|
}
|
||||||
|
|
||||||
|
int collider_is_overlap(Collider* self) {
|
||||||
|
return self->overlap;
|
||||||
|
}
|
||||||
|
|
||||||
|
void collider_set_overlap(Collider* self, int value) {
|
||||||
|
self->overlap = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
PhysicsMask collider_get_mask(const Collider* self) {
|
||||||
|
return self->mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
void collider_set_mask(Collider* self, PhysicsMask mask) {
|
||||||
|
self->mask = mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
PhysicsMask collider_get_layers(const Collider* self) {
|
||||||
|
return self->layers;
|
||||||
|
}
|
||||||
|
|
||||||
|
void collider_set_layers(Collider* self, PhysicsMask layers) {
|
||||||
|
self->layers = layers;
|
||||||
|
}
|
||||||
|
|
||||||
|
PhysicsEntity collider_get_owner(Collider* self) {
|
||||||
|
return self->owner;
|
||||||
|
}
|
27
core/src/collider.h
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
#ifndef _fencer_collider_h
|
||||||
|
#define _fencer_collider_h
|
||||||
|
|
||||||
|
#include "shape.h"
|
||||||
|
#include "rigidbody.h"
|
||||||
|
|
||||||
|
typedef struct Collider Collider;
|
||||||
|
|
||||||
|
extern Collider* collider_new(PhysicsEntity owner, Shape* shape, int overlap, PhysicsMask layers, PhysicsMask mask);
|
||||||
|
extern void collider_destroy(Collider* self);
|
||||||
|
extern PhysicsQuery collider_to_query(Collider* self);
|
||||||
|
|
||||||
|
extern Shape* collider_get_shape(Collider* self);
|
||||||
|
extern RigidBody* collider_get_rigidbody(Collider* self);
|
||||||
|
|
||||||
|
extern int collider_is_overlap(Collider* self);
|
||||||
|
extern void collider_set_overlap(Collider* self, int value);
|
||||||
|
|
||||||
|
extern PhysicsMask collider_get_mask(const Collider* self);
|
||||||
|
extern void collider_set_mask(Collider* self, PhysicsMask mask);
|
||||||
|
|
||||||
|
extern PhysicsMask collider_get_layers(const Collider* self);
|
||||||
|
extern void collider_set_layers(Collider* self, PhysicsMask layers);
|
||||||
|
|
||||||
|
extern PhysicsEntity collider_get_owner(Collider* self);
|
||||||
|
|
||||||
|
#endif // !_fencer_collider_h
|
|
@ -1,6 +1,7 @@
|
||||||
#include "collision.h"
|
#include "collision.h"
|
||||||
#include "vmath.h"
|
#include "vmath.h"
|
||||||
#include "rigidbody.h"
|
#include "rigidbody.h"
|
||||||
|
#include "collider.h"
|
||||||
|
|
||||||
// =====================================================
|
// =====================================================
|
||||||
// Shape overlap test using the separating axis theorem
|
// Shape overlap test using the separating axis theorem
|
||||||
|
@ -9,9 +10,9 @@
|
||||||
typedef struct Range {float min; Vector minpoint; float max; Vector maxpoint; } Range;
|
typedef struct Range {float min; Vector minpoint; float max; Vector maxpoint; } Range;
|
||||||
|
|
||||||
static
|
static
|
||||||
Range _internal_collision_get_range_on_axis(PhysicsEntity self, Vector axis) {
|
Range _internal_collision_get_range_on_axis(PhysicsQuery self, Vector axis) {
|
||||||
Transform* transform = self.transformable->get_transform(self.data);
|
Transform* transform = self.transform;
|
||||||
Shape* shape = self.tc->get_shape(self.data);
|
Shape* shape = self.shape;
|
||||||
Vector point = shape_get_point_transformed(shape, 0, *transform);
|
Vector point = shape_get_point_transformed(shape, 0, *transform);
|
||||||
float dot = vdotf(axis, point);
|
float dot = vdotf(axis, point);
|
||||||
Range range = {dot, point, dot, point};
|
Range range = {dot, point, dot, point};
|
||||||
|
@ -33,7 +34,7 @@ Range _internal_collision_get_range_on_axis(PhysicsEntity self, Vector axis) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static
|
||||||
Vector _internal_collision_overlap_on_axis(PhysicsEntity self, PhysicsEntity other, Vector axis, Vector* out_point) {
|
Vector _internal_collision_overlap_on_axis(PhysicsQuery self, PhysicsQuery other, Vector axis, Vector* out_point) {
|
||||||
Range a_range = _internal_collision_get_range_on_axis(self, axis);
|
Range a_range = _internal_collision_get_range_on_axis(self, axis);
|
||||||
Range b_range = _internal_collision_get_range_on_axis(other, axis);
|
Range b_range = _internal_collision_get_range_on_axis(other, axis);
|
||||||
|
|
||||||
|
@ -49,10 +50,20 @@ Vector _internal_collision_overlap_on_axis(PhysicsEntity self, PhysicsEntity oth
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static
|
||||||
int _internal_collision_get_overlap(PhysicsEntity self, PhysicsEntity other, Collision* out) {
|
int _internal_collision_get_collisions(Collider* self, Collider* other, Collision* out) {
|
||||||
// get components used
|
// get components used
|
||||||
Shape* self_shape = self.tc->get_shape(self.data);
|
Shape* self_shape = collider_get_shape(self);
|
||||||
Transform* self_transform = self.transformable->get_transform(self.data);
|
Transform* self_transform = rigidbody_get_transform(collider_get_rigidbody(self));
|
||||||
|
PhysicsQuery self_query = {
|
||||||
|
.shape = self_shape,
|
||||||
|
.transform = self_transform,
|
||||||
|
.mask = 0x0 // not used
|
||||||
|
};
|
||||||
|
PhysicsQuery other_query = {
|
||||||
|
.shape = collider_get_shape(other),
|
||||||
|
.transform = rigidbody_get_transform(collider_get_rigidbody(other)),
|
||||||
|
.mask = 0x0 // not used
|
||||||
|
};
|
||||||
|
|
||||||
// the shortest distance to solve collision found so far
|
// the shortest distance to solve collision found so far
|
||||||
Vector shortest_escape = InfinityVector;
|
Vector shortest_escape = InfinityVector;
|
||||||
|
@ -67,14 +78,14 @@ int _internal_collision_get_overlap(PhysicsEntity self, PhysicsEntity other, Col
|
||||||
// the next point on the line
|
// the next point on the line
|
||||||
size_t next_index = (point_index + 1) % self_point_count;
|
size_t next_index = (point_index + 1) % self_point_count;
|
||||||
// get the two points defining the collision edge
|
// get the two points defining the collision edge
|
||||||
Vector edge_lhs = shape_get_point_transformed(self.tc->get_shape(self.data), point_index, *self_transform);
|
Vector edge_lhs = shape_get_point_transformed(self_shape, point_index, *self_transform);
|
||||||
Vector edge_rhs = shape_get_point_transformed(self.tc->get_shape(self.data), next_index, *self_transform);
|
Vector edge_rhs = shape_get_point_transformed(self_shape, next_index, *self_transform);
|
||||||
// the direction of the line
|
// the direction of the line
|
||||||
Vector normal = vnormalizedf(vperpendicularf(vsubf(edge_rhs, edge_lhs)));
|
Vector normal = vnormalizedf(vperpendicularf(vsubf(edge_rhs, edge_lhs)));
|
||||||
|
|
||||||
Vector overlap_point;
|
Vector overlap_point;
|
||||||
// the smallest escape vector on this axis
|
// the smallest escape vector on this axis
|
||||||
Vector escape = _internal_collision_overlap_on_axis(self, other, normal, &overlap_point);
|
Vector escape = _internal_collision_overlap_on_axis(self_query, other_query, normal, &overlap_point);
|
||||||
float dot = vdotf(vinvf(normal), escape);
|
float dot = vdotf(vinvf(normal), escape);
|
||||||
if(dot <= 0.0) {
|
if(dot <= 0.0) {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -86,11 +97,11 @@ int _internal_collision_get_overlap(PhysicsEntity self, PhysicsEntity other, Col
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RigidBody* rba = self.tc->get_rigidbody(self.data);
|
RigidBody* rba = collider_get_rigidbody(self);
|
||||||
RigidBody* rbb = other.tc->get_rigidbody(other.data);
|
RigidBody* rbb = collider_get_rigidbody(other);
|
||||||
const Vector velocity = vsubf(rigidbody_get_velocity(rba), rigidbody_get_velocity(rbb));
|
const Vector velocity = vsubf(rigidbody_get_velocity(rba), rigidbody_get_velocity(rbb));
|
||||||
const Vector normal = vnormalizedf(shortest_escape);
|
const Vector normal = vnormalizedf(shortest_escape);
|
||||||
Vector world_point = _internal_collision_get_range_on_axis(self, normal).minpoint;
|
Vector world_point = _internal_collision_get_range_on_axis(self_query, normal).minpoint;
|
||||||
|
|
||||||
*out = (Collision) {
|
*out = (Collision) {
|
||||||
.other = other,
|
.other = other,
|
||||||
|
@ -108,9 +119,13 @@ int _internal_collision_get_overlap(PhysicsEntity self, PhysicsEntity other, Col
|
||||||
return !veqf(shortest_escape, ZeroVector);
|
return !veqf(shortest_escape, ZeroVector);
|
||||||
}
|
}
|
||||||
|
|
||||||
Collision collision_invert(Collision collision_a, PhysicsEntity a) {
|
Collision collision_invert(Collision collision_a, Collider* a) {
|
||||||
Vector world_point = _internal_collision_get_range_on_axis(collision_a.other, collision_a.normal).maxpoint;
|
RigidBody* body = collider_get_rigidbody(a);
|
||||||
RigidBody* body = collision_a.other.tc->get_rigidbody(collision_a.other.data);
|
Shape* shape = collider_get_shape(a);
|
||||||
|
Transform* transform = rigidbody_get_transform(body);
|
||||||
|
|
||||||
|
Vector world_point = _internal_collision_get_range_on_axis((PhysicsQuery){.shape = shape, .transform = transform }, collision_a.normal).maxpoint;
|
||||||
|
|
||||||
return (Collision){
|
return (Collision){
|
||||||
.other = a,
|
.other = a,
|
||||||
.point = inverse_transform_point(rigidbody_get_transform(body), world_point),
|
.point = inverse_transform_point(rigidbody_get_transform(body), world_point),
|
||||||
|
@ -122,10 +137,12 @@ Collision collision_invert(Collision collision_a, PhysicsEntity a) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
int collision_check(PhysicsEntity a, PhysicsEntity b, Collision* out_a, Collision* out_b) {
|
int collision_check(Collider* a, Collider* b, Collision* out_a, Collision* out_b) {
|
||||||
|
if(!(collider_get_layers(a) & collider_get_mask(b)) || !(collider_get_mask(a) & collider_get_layers(b)))
|
||||||
|
return 0;
|
||||||
Collision collision_a, collision_b;
|
Collision collision_a, collision_b;
|
||||||
int collision_a_overlaps = _internal_collision_get_overlap(a, b, &collision_a);
|
int collision_a_overlaps = _internal_collision_get_collisions(a, b, &collision_a);
|
||||||
int collision_b_overlaps = _internal_collision_get_overlap(b, a, &collision_b);
|
int collision_b_overlaps = _internal_collision_get_collisions(b, a, &collision_b);
|
||||||
|
|
||||||
if(!collision_a_overlaps || !collision_b_overlaps)
|
if(!collision_a_overlaps || !collision_b_overlaps)
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -139,3 +156,34 @@ int collision_check(PhysicsEntity a, PhysicsEntity b, Collision* out_a, Collisio
|
||||||
*out_b = collision_b;
|
*out_b = collision_b;
|
||||||
return (collision_b_overlaps << 1) | collision_a_overlaps;
|
return (collision_b_overlaps << 1) | collision_a_overlaps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
int _internal_overlap_check(PhysicsQuery a, PhysicsQuery b) {
|
||||||
|
Shape* shape = a.shape;
|
||||||
|
Transform* transform = a.transform;
|
||||||
|
const size_t shape_point_count = shape_get_points_count(shape);
|
||||||
|
|
||||||
|
for(size_t point_index = 0; point_index < shape_point_count; ++point_index) {
|
||||||
|
// the next point on the line
|
||||||
|
size_t next_index = (point_index + 1) % shape_point_count;
|
||||||
|
// get the two points defining the collision edge
|
||||||
|
Vector edge_lhs = shape_get_point_transformed(shape, point_index, *transform);
|
||||||
|
Vector edge_rhs = shape_get_point_transformed(shape, next_index, *transform);
|
||||||
|
// the direction of the line
|
||||||
|
Vector normal = vnormalizedf(vperpendicularf(vsubf(edge_rhs, edge_lhs)));
|
||||||
|
|
||||||
|
Vector overlap_point;
|
||||||
|
// the smallest escape vector on this axis
|
||||||
|
Vector escape = _internal_collision_overlap_on_axis(a, b, normal, &overlap_point);
|
||||||
|
float dot = vdotf(vinvf(normal), escape);
|
||||||
|
if(dot <= 0.0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int overlap_check(PhysicsQuery query, Collider* collider) {
|
||||||
|
PhysicsQuery collider_query = collider_to_query(collider);
|
||||||
|
return (query.mask & collider_get_layers(collider)) != 0 && (_internal_overlap_check(query, collider_query) || _internal_overlap_check(collider_query, query));
|
||||||
|
}
|
13
core/src/collision.h
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#ifndef _fencer_collision_h
|
||||||
|
#define _fencer_collision_h
|
||||||
|
|
||||||
|
#include "physics_entity.h"
|
||||||
|
#include "physics.h"
|
||||||
|
|
||||||
|
typedef struct Collider Collider;
|
||||||
|
|
||||||
|
extern Collision collision_invert(Collision src, Collider* new_other);
|
||||||
|
extern int collision_check(Collider* a, Collider* b, Collision* out_a, Collision* out_b);
|
||||||
|
extern int overlap_check(PhysicsQuery query, Collider* collider);
|
||||||
|
|
||||||
|
#endif // !_fencer_collision_h
|
|
@ -12,7 +12,7 @@ static inline
|
||||||
size_t _internal_find_index_for_entity(void* data, const List* list) {
|
size_t _internal_find_index_for_entity(void* data, const List* list) {
|
||||||
for(size_t i = 0; i < _game_entities.len; ++i) {
|
for(size_t i = 0; i < _game_entities.len; ++i) {
|
||||||
BehaviourEntity* entity = list_at_as(BehaviourEntity, &_game_entities, i);
|
BehaviourEntity* entity = list_at_as(BehaviourEntity, &_game_entities, i);
|
||||||
if(entity->data == entity) {
|
if(entity->data == data) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,10 +22,10 @@ size_t _internal_find_index_for_entity(void* data, const List* list) {
|
||||||
|
|
||||||
static inline
|
static inline
|
||||||
void _internal_clear_removed() {
|
void _internal_clear_removed() {
|
||||||
list_foreach(size_t*, index, &_remove_queue) {
|
list_foreach(BehaviourEntity*, entity, &_remove_queue) {
|
||||||
BehaviourEntity* entity = list_at_as(BehaviourEntity, &_game_entities, *index);
|
size_t index = _internal_find_index_for_entity(entity->data, &_game_entities);
|
||||||
entity->drop->drop(entity->data);
|
entity->drop->drop(entity->data);
|
||||||
list_erase(&_game_entities, *index);
|
list_erase(&_game_entities, index);
|
||||||
}
|
}
|
||||||
list_empty(&_remove_queue);
|
list_empty(&_remove_queue);
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ void _internal_process_new() {
|
||||||
void game_world_init() {
|
void game_world_init() {
|
||||||
_game_entities = list_from_type(BehaviourEntity);
|
_game_entities = list_from_type(BehaviourEntity);
|
||||||
_add_queue = list_from_type(BehaviourEntity);
|
_add_queue = list_from_type(BehaviourEntity);
|
||||||
_remove_queue = list_from_type(size_t);
|
_remove_queue = list_from_type(BehaviourEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void game_world_close() {
|
void game_world_close() {
|
||||||
|
@ -58,11 +58,9 @@ void game_world_add_entity(BehaviourEntity entity) {
|
||||||
list_add(&_add_queue, &entity);
|
list_add(&_add_queue, &entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void game_world_remove_entity(void* entity) {
|
void game_world_destroy_entity(BehaviourEntity entity) {
|
||||||
size_t index = _internal_find_index_for_entity(entity, &_game_entities);
|
if(list_contains(&_remove_queue, &entity) == _remove_queue.len)
|
||||||
if(index != _game_entities.len) {
|
list_add(&_remove_queue, &entity);
|
||||||
list_add(&_remove_queue, &index);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void game_world_update() {
|
void game_world_update() {
|
||||||
|
@ -73,8 +71,21 @@ void game_world_update() {
|
||||||
_internal_clear_removed();
|
_internal_clear_removed();
|
||||||
}
|
}
|
||||||
|
|
||||||
void game_word_draw() {
|
static
|
||||||
list_foreach(BehaviourEntity*, entity, &_game_entities) {
|
int _internal_compare_depth(const BehaviourEntity* a, const BehaviourEntity* b) {
|
||||||
|
return b->tc->get_depth(b->data) - a->tc->get_depth(a->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define AS_COMPARISON(__FN) ((int(*)(const void*, const void*))__FN)
|
||||||
|
|
||||||
|
void game_world_draw() {
|
||||||
|
List draw_order = list_copy(&_game_entities);
|
||||||
|
|
||||||
|
qsort(_game_entities.data, _game_entities.len, _game_entities.element_size,
|
||||||
|
AS_COMPARISON(_internal_compare_depth));
|
||||||
|
list_foreach(BehaviourEntity*, entity, &draw_order) {
|
||||||
entity->tc->draw(entity->data);
|
entity->tc->draw(entity->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
list_empty(&draw_order);
|
||||||
}
|
}
|
|
@ -7,7 +7,7 @@ extern void game_world_init();
|
||||||
extern void game_world_close();
|
extern void game_world_close();
|
||||||
|
|
||||||
extern void game_world_add_entity(BehaviourEntity entity);
|
extern void game_world_add_entity(BehaviourEntity entity);
|
||||||
extern void game_world_remove_entity(void* entity);
|
extern void game_world_destroy_entity(BehaviourEntity entity);
|
||||||
|
|
||||||
extern void game_world_update();
|
extern void game_world_update();
|
||||||
extern void game_world_draw();
|
extern void game_world_draw();
|
|
@ -7,6 +7,7 @@ static List _devices;
|
||||||
static inline
|
static inline
|
||||||
void _internal_open_keyboard() {
|
void _internal_open_keyboard() {
|
||||||
InputDevice* keyboard = malloc(sizeof(InputDevice));
|
InputDevice* keyboard = malloc(sizeof(InputDevice));
|
||||||
|
ASSERT_RETURN(keyboard != NULL, , "Failed to allocate space for keyboard input device");
|
||||||
*keyboard = (InputDevice){
|
*keyboard = (InputDevice){
|
||||||
.listeners = NULL,
|
.listeners = NULL,
|
||||||
.type = InputDevice_KBM,
|
.type = InputDevice_KBM,
|
||||||
|
@ -20,8 +21,9 @@ void _internal_open_keyboard() {
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline
|
static inline
|
||||||
void _internal_open_controller(size_t id) {
|
void _internal_open_controller(int id) {
|
||||||
InputDevice* device = malloc(sizeof(InputDevice));
|
InputDevice* device = malloc(sizeof(InputDevice));
|
||||||
|
ASSERT_RETURN(device != NULL, , "Failed to allocate space for gamecontroller input device");
|
||||||
*device = (InputDevice) {
|
*device = (InputDevice) {
|
||||||
.listeners = NULL,
|
.listeners = NULL,
|
||||||
.type = InputDevice_Gamepad,
|
.type = InputDevice_Gamepad,
|
||||||
|
@ -42,8 +44,8 @@ void input_init() {
|
||||||
_internal_open_keyboard();
|
_internal_open_keyboard();
|
||||||
|
|
||||||
// open any controllers already available
|
// open any controllers already available
|
||||||
const size_t joystick_count = SDL_NumJoysticks();
|
const int joystick_count = SDL_NumJoysticks();
|
||||||
for(size_t i = 0; i < joystick_count; ++i) {
|
for(int i = 0; i < joystick_count; ++i) {
|
||||||
_internal_open_controller(i);
|
_internal_open_controller(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -10,8 +10,6 @@
|
||||||
|
|
||||||
struct PlayerInput;
|
struct PlayerInput;
|
||||||
|
|
||||||
typedef void (*InputDelegateFn)(void* self, InputEvent event);
|
|
||||||
|
|
||||||
typedef struct InputListener {
|
typedef struct InputListener {
|
||||||
void* self;
|
void* self;
|
||||||
InputDelegateFn fn;
|
InputDelegateFn fn;
|
|
@ -2,8 +2,45 @@
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
#include "input.h"
|
#include "input.h"
|
||||||
|
|
||||||
|
|
||||||
|
impl_default_Drop_for(
|
||||||
|
KeyBind
|
||||||
|
)
|
||||||
|
impl_InputAxis_for(KeyBind,
|
||||||
|
keybind_is_changed_by,
|
||||||
|
keybind_evaluate,
|
||||||
|
keybind_set_device
|
||||||
|
)
|
||||||
|
impl_default_Drop_for(
|
||||||
|
ControllerAxis
|
||||||
|
)
|
||||||
|
impl_InputAxis_for(ControllerAxis,
|
||||||
|
controlleraxis_is_changed_by,
|
||||||
|
controlleraxis_evaluate,
|
||||||
|
controlleraxis_set_device
|
||||||
|
)
|
||||||
|
|
||||||
|
impl_default_Drop_for(
|
||||||
|
ControllerButton
|
||||||
|
)
|
||||||
|
impl_InputAxis_for(ControllerButton,
|
||||||
|
controllerbutton_is_changed_by,
|
||||||
|
controllerbutton_evaluate,
|
||||||
|
controllerbutton_set_device
|
||||||
|
)
|
||||||
|
|
||||||
|
impl_Drop_for(CompositeAxis1D,
|
||||||
|
compositeaxis1d_drop
|
||||||
|
)
|
||||||
|
impl_InputAxis_for(CompositeAxis1D,
|
||||||
|
compositeaxis1d_is_changed_by,
|
||||||
|
compositeaxis1d_evaluate,
|
||||||
|
compositeaxis1d_set_device
|
||||||
|
)
|
||||||
|
|
||||||
KeyBind* keybind_new(SDL_Scancode key) {
|
KeyBind* keybind_new(SDL_Scancode key) {
|
||||||
KeyBind* self = malloc(sizeof(KeyBind));
|
KeyBind* self = malloc(sizeof(KeyBind));
|
||||||
|
ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for KeyBind instance");
|
||||||
*self = (KeyBind) {
|
*self = (KeyBind) {
|
||||||
.device = NULL,
|
.device = NULL,
|
||||||
.scancode = key,
|
.scancode = key,
|
||||||
|
@ -15,7 +52,8 @@ KeyBind* keybind_new(SDL_Scancode key) {
|
||||||
int keybind_is_changed_by(KeyBind* self, SDL_Event event) {
|
int keybind_is_changed_by(KeyBind* self, SDL_Event event) {
|
||||||
return self->device->type == InputDevice_KBM
|
return self->device->type == InputDevice_KBM
|
||||||
&& (event.type == SDL_KEYUP || event.type == SDL_KEYDOWN)
|
&& (event.type == SDL_KEYUP || event.type == SDL_KEYDOWN)
|
||||||
&& event.key.keysym.scancode == self->scancode;
|
&& event.key.keysym.scancode == self->scancode
|
||||||
|
&& event.key.repeat == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
InputEvent keybind_evaluate(KeyBind* self, SDL_Event event) {
|
InputEvent keybind_evaluate(KeyBind* self, SDL_Event event) {
|
||||||
|
@ -31,6 +69,7 @@ void keybind_set_device(KeyBind* self, InputDevice* device) {
|
||||||
|
|
||||||
ControllerAxis* controlleraxis_new(int axis) {
|
ControllerAxis* controlleraxis_new(int axis) {
|
||||||
ControllerAxis* self = malloc(sizeof(ControllerAxis));
|
ControllerAxis* self = malloc(sizeof(ControllerAxis));
|
||||||
|
ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for ControllerAxis instance");
|
||||||
*self = (ControllerAxis){
|
*self = (ControllerAxis){
|
||||||
.axis = axis,
|
.axis = axis,
|
||||||
.device = NULL
|
.device = NULL
|
||||||
|
@ -47,8 +86,7 @@ int controlleraxis_is_changed_by(ControllerAxis* self, SDL_Event event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
InputEvent controlleraxis_evaluate(ControllerAxis* self, SDL_Event event) {
|
InputEvent controlleraxis_evaluate(ControllerAxis* self, SDL_Event event) {
|
||||||
float result = (float)event.caxis.value / 32767.0;
|
float result = (float)event.caxis.value / 32767.0f;
|
||||||
LOG_INFO("axis %f", result);
|
|
||||||
return (InputEvent) {
|
return (InputEvent) {
|
||||||
.type = InputEvent_Float,
|
.type = InputEvent_Float,
|
||||||
.as_float = result
|
.as_float = result
|
||||||
|
@ -61,6 +99,7 @@ void controlleraxis_set_device(ControllerAxis* self, InputDevice* device) {
|
||||||
|
|
||||||
ControllerButton* controllerbutton_new(int button) {
|
ControllerButton* controllerbutton_new(int button) {
|
||||||
ControllerButton* self = malloc(sizeof(ControllerButton));
|
ControllerButton* self = malloc(sizeof(ControllerButton));
|
||||||
|
ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for ControllerButton instance");
|
||||||
*self = (ControllerButton) {
|
*self = (ControllerButton) {
|
||||||
.button = button,
|
.button = button,
|
||||||
.device = NULL
|
.device = NULL
|
||||||
|
@ -88,6 +127,7 @@ void controllerbutton_set_device(ControllerButton* self, InputDevice* device) {
|
||||||
|
|
||||||
CompositeAxis1D* compositeaxis1d_new(InputAxis left, InputAxis right, InputEventType type) {
|
CompositeAxis1D* compositeaxis1d_new(InputAxis left, InputAxis right, InputEventType type) {
|
||||||
CompositeAxis1D* self = malloc(sizeof(CompositeAxis1D));
|
CompositeAxis1D* self = malloc(sizeof(CompositeAxis1D));
|
||||||
|
ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for CompositeAxis1D instance");
|
||||||
*self = (CompositeAxis1D) {
|
*self = (CompositeAxis1D) {
|
||||||
.left = left,
|
.left = left,
|
||||||
.right = right,
|
.right = right,
|
||||||
|
@ -112,19 +152,19 @@ InputEvent _internal_event_to_type(InputEventType type, InputEvent event) {
|
||||||
LOG_ERROR("No (1)");
|
LOG_ERROR("No (1)");
|
||||||
break;
|
break;
|
||||||
case InputEvent_Bool:
|
case InputEvent_Bool:
|
||||||
as_float = event.as_bool;
|
as_float = (float)event.as_bool;
|
||||||
break;
|
break;
|
||||||
case InputEvent_Float:
|
case InputEvent_Float:
|
||||||
as_float = event.as_float;
|
as_float = event.as_float;
|
||||||
break;
|
break;
|
||||||
case InputEvent_Int:
|
case InputEvent_Int:
|
||||||
as_float = event.as_int;
|
as_float = (float)event.as_int;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
event.type = type;
|
event.type = type;
|
||||||
switch(type) {
|
switch(type) {
|
||||||
case InputEvent_Int:
|
case InputEvent_Int:
|
||||||
event.as_int = round(as_float);
|
event.as_int = (int)round(as_float);
|
||||||
return event;
|
return event;
|
||||||
case InputEvent_Float:
|
case InputEvent_Float:
|
||||||
event.as_float = as_float;
|
event.as_float = as_float;
|
||||||
|
@ -185,3 +225,17 @@ void compositeaxis1d_drop(CompositeAxis1D* self) {
|
||||||
self->right.drop->drop(self->right.data);
|
self->right.drop->drop(self->right.data);
|
||||||
free(self);
|
free(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CompositeAxis1D* compositeaxis1d_from_keys(SDL_Scancode negative, SDL_Scancode positive) {
|
||||||
|
return compositeaxis1d_new(
|
||||||
|
KeyBind_as_InputAxis(keybind_new(negative)),
|
||||||
|
KeyBind_as_InputAxis(keybind_new(positive)),
|
||||||
|
InputEvent_Float);
|
||||||
|
}
|
||||||
|
|
||||||
|
CompositeAxis1D* compositeaxis1d_from_buttons(int negative, int positive) {
|
||||||
|
return compositeaxis1d_new(
|
||||||
|
ControllerButton_as_InputAxis(controllerbutton_new(negative)),
|
||||||
|
ControllerButton_as_InputAxis(controllerbutton_new(positive)),
|
||||||
|
InputEvent_Float);
|
||||||
|
}
|
|
@ -39,8 +39,10 @@ typedef struct {
|
||||||
IDrop const* drop;
|
IDrop const* drop;
|
||||||
} InputAxis;
|
} InputAxis;
|
||||||
|
|
||||||
|
typedef void (*InputDelegateFn)(void* self, InputEvent event);
|
||||||
|
|
||||||
#define impl_InputAxis_for(T, is_changed_by_f, evaluate_f, set_device_f)\
|
#define impl_InputAxis_for(T, is_changed_by_f, evaluate_f, set_device_f)\
|
||||||
static inline InputAxis T##_as_InputAxis(T* x) {\
|
InputAxis T##_as_InputAxis(T* x) {\
|
||||||
TC_FN_TYPECHECK(int, is_changed_by_f, T*, SDL_Event);\
|
TC_FN_TYPECHECK(int, is_changed_by_f, T*, SDL_Event);\
|
||||||
TC_FN_TYPECHECK(struct InputEvent, evaluate_f, T*, SDL_Event);\
|
TC_FN_TYPECHECK(struct InputEvent, evaluate_f, T*, SDL_Event);\
|
||||||
TC_FN_TYPECHECK(void, set_device_f, T*, struct InputDevice*);\
|
TC_FN_TYPECHECK(void, set_device_f, T*, struct InputDevice*);\
|
||||||
|
@ -64,12 +66,8 @@ extern int keybind_is_changed_by(KeyBind* self, SDL_Event event);
|
||||||
extern struct InputEvent keybind_evaluate(KeyBind* self, SDL_Event);
|
extern struct InputEvent keybind_evaluate(KeyBind* self, SDL_Event);
|
||||||
extern void keybind_set_device(KeyBind* self, struct InputDevice* device);
|
extern void keybind_set_device(KeyBind* self, struct InputDevice* device);
|
||||||
|
|
||||||
impl_default_Drop_for(KeyBind)
|
decl_typeclass_impl(InputAxis, KeyBind)
|
||||||
impl_InputAxis_for(KeyBind,
|
decl_typeclass_impl(Drop, KeyBind)
|
||||||
keybind_is_changed_by,
|
|
||||||
keybind_evaluate,
|
|
||||||
keybind_set_device
|
|
||||||
)
|
|
||||||
|
|
||||||
typedef struct ControllerAxis {
|
typedef struct ControllerAxis {
|
||||||
struct InputDevice* device;
|
struct InputDevice* device;
|
||||||
|
@ -81,12 +79,8 @@ extern int controlleraxis_is_changed_by(ControllerAxis* self, SDL_Event event);
|
||||||
extern struct InputEvent controlleraxis_evaluate(ControllerAxis* self, SDL_Event event);
|
extern struct InputEvent controlleraxis_evaluate(ControllerAxis* self, SDL_Event event);
|
||||||
extern void controlleraxis_set_device(ControllerAxis* self, struct InputDevice* device);
|
extern void controlleraxis_set_device(ControllerAxis* self, struct InputDevice* device);
|
||||||
|
|
||||||
impl_default_Drop_for(ControllerAxis)
|
decl_typeclass_impl(InputAxis, ControllerAxis)
|
||||||
impl_InputAxis_for(ControllerAxis,
|
decl_typeclass_impl(Drop, ControllerAxis)
|
||||||
controlleraxis_is_changed_by,
|
|
||||||
controlleraxis_evaluate,
|
|
||||||
controlleraxis_set_device
|
|
||||||
)
|
|
||||||
|
|
||||||
typedef struct ControllerButton {
|
typedef struct ControllerButton {
|
||||||
struct InputDevice* device;
|
struct InputDevice* device;
|
||||||
|
@ -98,12 +92,8 @@ extern int controllerbutton_is_changed_by(ControllerButton* self, SDL_Event even
|
||||||
extern struct InputEvent controllerbutton_evaluate(ControllerButton* self, SDL_Event event);
|
extern struct InputEvent controllerbutton_evaluate(ControllerButton* self, SDL_Event event);
|
||||||
extern void controllerbutton_set_device(ControllerButton* self, struct InputDevice* device);
|
extern void controllerbutton_set_device(ControllerButton* self, struct InputDevice* device);
|
||||||
|
|
||||||
impl_default_Drop_for(ControllerButton)
|
decl_typeclass_impl(InputAxis, ControllerButton)
|
||||||
impl_InputAxis_for(ControllerButton,
|
decl_typeclass_impl(Drop, ControllerButton)
|
||||||
controllerbutton_is_changed_by,
|
|
||||||
controllerbutton_evaluate,
|
|
||||||
controllerbutton_set_device
|
|
||||||
)
|
|
||||||
|
|
||||||
typedef struct CompositeAxis1D {
|
typedef struct CompositeAxis1D {
|
||||||
InputAxis left;
|
InputAxis left;
|
||||||
|
@ -117,13 +107,10 @@ extern struct InputEvent compositeaxis1d_evaluate(CompositeAxis1D* self, SDL_Eve
|
||||||
extern void compositeaxis1d_set_device(CompositeAxis1D* self, struct InputDevice* device);
|
extern void compositeaxis1d_set_device(CompositeAxis1D* self, struct InputDevice* device);
|
||||||
extern void compositeaxis1d_drop(CompositeAxis1D* self);
|
extern void compositeaxis1d_drop(CompositeAxis1D* self);
|
||||||
|
|
||||||
impl_Drop_for(CompositeAxis1D,
|
extern CompositeAxis1D* compositeaxis1d_from_keys(SDL_Scancode negative, SDL_Scancode positive);
|
||||||
compositeaxis1d_drop
|
extern CompositeAxis1D* compositeaxis1d_from_buttons(int negative, int positive);
|
||||||
)
|
|
||||||
impl_InputAxis_for(CompositeAxis1D,
|
decl_typeclass_impl(InputAxis, CompositeAxis1D)
|
||||||
compositeaxis1d_is_changed_by,
|
decl_typeclass_impl(Drop, CompositeAxis1D)
|
||||||
compositeaxis1d_evaluate,
|
|
||||||
compositeaxis1d_set_device
|
|
||||||
)
|
|
||||||
|
|
||||||
#endif // !_fencer_input_axis_h
|
#endif // !_fencer_input_axis_h
|
177
core/src/level.c
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
#include "level.h"
|
||||||
|
#include "game_world.h"
|
||||||
|
#include "physics_entity.h"
|
||||||
|
#include "physics_world.h"
|
||||||
|
#include "strutil.h"
|
||||||
|
#include "variant.h"
|
||||||
|
#include "ctype.h"
|
||||||
|
|
||||||
|
static Dictionary internal_deserializers = {
|
||||||
|
.list = { .data = NULL, .len = 0, .cap = 0, .element_size = 0 },
|
||||||
|
.element_size = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
static int is_open_bracket(int c) { return c == '{'; }
|
||||||
|
static int is_close_bracket(int c) { return c == '}'; }
|
||||||
|
static int is_colon(int c) { return c == ':'; }
|
||||||
|
static int ends_parameter(int c) { return c == '}' || c == ','; }
|
||||||
|
|
||||||
|
int level_init() {
|
||||||
|
internal_deserializers = dictionary_from_type(DeserializeFn);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int level_clean() {
|
||||||
|
dictionary_empty(&internal_deserializers);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int level_register_spawner(const char* object_tag, DeserializeFn spawn_function) {
|
||||||
|
dictionary_set_value(DeserializeFn, &internal_deserializers, object_tag, spawn_function);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
void spawn_object(DeserializeFn spawner, Dictionary* args) {
|
||||||
|
BehaviourEntity entity = spawner(args);
|
||||||
|
game_world_add_entity(entity);
|
||||||
|
if(TC_MIRRORS(entity, PhysicsEntity))
|
||||||
|
physics_world_add_entity(TC_CAST(entity, PhysicsEntity));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
long get_until(FILE* fp, char* out_buf, size_t buf_len, CharPredFn predfn) {
|
||||||
|
if(feof(fp)) return 0;
|
||||||
|
int c;
|
||||||
|
char* write = out_buf;
|
||||||
|
long count = 0;
|
||||||
|
while(c != EOF) {
|
||||||
|
// don't overflow
|
||||||
|
if(out_buf != NULL && count + 1 >= buf_len)
|
||||||
|
return count;
|
||||||
|
// fetch next character
|
||||||
|
c = fgetc(fp);
|
||||||
|
// check if this is the desired character
|
||||||
|
if(predfn(c)) {
|
||||||
|
if(write != NULL) {
|
||||||
|
*write = c;
|
||||||
|
if(count + 2 < buf_len) *(write + 1) = '\0';
|
||||||
|
}
|
||||||
|
return count + 1;
|
||||||
|
}
|
||||||
|
// skip space characters (unless search is a space character, which is handled above)
|
||||||
|
if(isspace(c)) continue;
|
||||||
|
// write and increment
|
||||||
|
if(write != NULL) {
|
||||||
|
*write = c;
|
||||||
|
++write;
|
||||||
|
}
|
||||||
|
++count;
|
||||||
|
} // EOF reached
|
||||||
|
// write null terminator in place of EOF if possible
|
||||||
|
if(write != NULL)
|
||||||
|
*(write-1) = '\0';
|
||||||
|
return count + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
long get_key(FILE* fp, char* out, size_t out_size) {
|
||||||
|
long length = get_until(fp, out, out_size, is_colon);
|
||||||
|
if(strfirst(out, out + length, '}') == 0)
|
||||||
|
return 0;
|
||||||
|
if(feof(fp))
|
||||||
|
return -1;
|
||||||
|
length--;
|
||||||
|
out[length] = '\0';
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
Variant get_value(FILE* fp, char* buffer, size_t buffer_size, int* out_end_of_object) {
|
||||||
|
long length = 0;
|
||||||
|
*out_end_of_object = 0;
|
||||||
|
do {
|
||||||
|
length += get_until(fp, buffer+length, buffer_size-length, ends_parameter);
|
||||||
|
if (length <= 0)
|
||||||
|
return UndefinedVariant();
|
||||||
|
*out_end_of_object = strfirst(buffer, buffer + length, '}') != -1;
|
||||||
|
} while(strcount(buffer, buffer+length, '(') != strcount(buffer, buffer+length, ')') && !(*out_end_of_object));
|
||||||
|
length--;
|
||||||
|
buffer[length] = '\0';
|
||||||
|
return variant_from_str(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
int peek_next_non_blank(FILE* fp) {
|
||||||
|
long position = ftell(fp);
|
||||||
|
int c;
|
||||||
|
do {
|
||||||
|
c = fgetc(fp);
|
||||||
|
} while(isspace(c));
|
||||||
|
fseek(fp, position, SEEK_SET);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
int load_args(FILE* fp, Dictionary* out, char* buffer, size_t buffer_size) {
|
||||||
|
char* key;
|
||||||
|
long length;
|
||||||
|
int is_end;
|
||||||
|
do {
|
||||||
|
length = get_key(fp, buffer, buffer_size);
|
||||||
|
if(length == -1) // detect EOF before end of key
|
||||||
|
return -1;
|
||||||
|
key = malloc(length+1);
|
||||||
|
strncpy(key, buffer, length);
|
||||||
|
Variant var = get_value(fp, buffer, buffer_size, &is_end);
|
||||||
|
dictionary_set_value(Variant, out, key, var);
|
||||||
|
free(key);
|
||||||
|
if(peek_next_non_blank(fp) == '}') {
|
||||||
|
get_until(fp, NULL, 0, ends_parameter);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while(!is_end);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int level_parse_file(FILE* fp) {
|
||||||
|
// initialize a buffer string
|
||||||
|
size_t length = 81;
|
||||||
|
char buffer[length];
|
||||||
|
char* obj_tag = NULL;
|
||||||
|
Dictionary args = dictionary_new(sizeof(Variant));
|
||||||
|
DeserializeFn spawner = NULL;
|
||||||
|
do {
|
||||||
|
// read the next line of the level file
|
||||||
|
length = get_until(fp, buffer, sizeof(buffer)-1, is_open_bracket);
|
||||||
|
--length;
|
||||||
|
buffer[length] = '\0';
|
||||||
|
long start = strfirst(buffer, buffer+length, '}') + 1;
|
||||||
|
// initialize object tag buffer
|
||||||
|
obj_tag = malloc((length-start)+1);
|
||||||
|
obj_tag[length - start] = '\0';
|
||||||
|
strncpy(obj_tag, buffer + start, length - start);
|
||||||
|
// find the spawn function
|
||||||
|
if(dictionary_try_get(&internal_deserializers, obj_tag, &spawner)) {
|
||||||
|
// load arguments from file and spawn object
|
||||||
|
load_args(fp, &args, buffer, sizeof(buffer));
|
||||||
|
spawn_object(spawner, &args);
|
||||||
|
// clean up for next loop
|
||||||
|
free(obj_tag);
|
||||||
|
dictionary_empty(&args);
|
||||||
|
} else {
|
||||||
|
// clean up for next loop
|
||||||
|
free(obj_tag);
|
||||||
|
dictionary_empty(&args);
|
||||||
|
obj_tag = NULL;
|
||||||
|
// return -1;
|
||||||
|
}
|
||||||
|
} while(!feof(fp));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int level_load_file(const char* path) {
|
||||||
|
FILE* fp = fopen("assets/test.sc", "r");
|
||||||
|
level_parse_file(fp);
|
||||||
|
return 0;
|
||||||
|
}
|
18
core/src/level.h
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#ifndef _fencer_level_h
|
||||||
|
#define _fencer_level_h
|
||||||
|
|
||||||
|
#include "stdio.h"
|
||||||
|
#include "behaviour_entity.h"
|
||||||
|
#include "dictionary.h"
|
||||||
|
|
||||||
|
typedef BehaviourEntity(*DeserializeFn)(Dictionary* args);
|
||||||
|
|
||||||
|
extern int level_init();
|
||||||
|
extern int level_clean();
|
||||||
|
|
||||||
|
extern int level_register_spawner(const char* object_tag, DeserializeFn spawn_function);
|
||||||
|
|
||||||
|
extern int level_parse_file(FILE* level);
|
||||||
|
extern int level_load_file(const char* path);
|
||||||
|
|
||||||
|
#endif // !_fencer_level_h
|
34
core/src/physics.h
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
#ifndef _fencer_physics_h
|
||||||
|
#define _fencer_physics_h
|
||||||
|
|
||||||
|
#include "vmath.h"
|
||||||
|
#include "transform.h"
|
||||||
|
#include "stdint.h"
|
||||||
|
#include "physics_entity.h"
|
||||||
|
#include "shape.h"
|
||||||
|
|
||||||
|
typedef uint32_t PhysicsMask;
|
||||||
|
|
||||||
|
typedef struct Collision {
|
||||||
|
struct Collider* other;
|
||||||
|
|
||||||
|
Vector point;
|
||||||
|
Vector normal;
|
||||||
|
|
||||||
|
Vector velocity;
|
||||||
|
Vector penetration_vector;
|
||||||
|
|
||||||
|
Vector edge_left;
|
||||||
|
Vector edge_right;
|
||||||
|
} Collision;
|
||||||
|
|
||||||
|
typedef struct PhysicsQuery {
|
||||||
|
Shape* shape;
|
||||||
|
Transform* transform;
|
||||||
|
PhysicsMask mask;
|
||||||
|
} PhysicsQuery;
|
||||||
|
|
||||||
|
|
||||||
|
#define PHYSICS_LAYER_DEFAULT 0x1
|
||||||
|
|
||||||
|
#endif // !_fencer_physics_h
|
|
@ -4,13 +4,15 @@
|
||||||
#include "shape.h"
|
#include "shape.h"
|
||||||
#include "render.h"
|
#include "render.h"
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
|
#include "collider.h"
|
||||||
|
|
||||||
void physics_entity_debug_draw(PhysicsEntity self) {
|
void physics_entity_debug_draw(PhysicsEntity self) {
|
||||||
RigidBody* body = self.tc->get_rigidbody(self.data);
|
RigidBody* body = self.tc->get_rigidbody(self.data);
|
||||||
Shape* shape = self.tc->get_shape(self.data);
|
Transform* transform = rigidbody_get_transform(body);
|
||||||
Transform* transform = self.transformable->get_transform(self.data);
|
|
||||||
|
|
||||||
shape_draw(shape, *transform);
|
list_foreach(Collider**, collider, rigidbody_get_colliders(body)) {
|
||||||
|
shape_draw(collider_get_shape(*collider), *transform);
|
||||||
|
}
|
||||||
rigidbody_debug_draw_contacts(body);
|
rigidbody_debug_draw_contacts(body);
|
||||||
|
|
||||||
Vector lhs = transform->position;
|
Vector lhs = transform->position;
|
||||||
|
@ -18,12 +20,13 @@ void physics_entity_debug_draw(PhysicsEntity self) {
|
||||||
lhs = camera_world_to_pixel_point(&g_camera, lhs);
|
lhs = camera_world_to_pixel_point(&g_camera, lhs);
|
||||||
rhs = camera_world_to_pixel_point(&g_camera, rhs);
|
rhs = camera_world_to_pixel_point(&g_camera, rhs);
|
||||||
SDL_SetRenderDrawColor(g_renderer, 0, 255, 0, 255);
|
SDL_SetRenderDrawColor(g_renderer, 0, 255, 0, 255);
|
||||||
SDL_RenderDrawLine(g_renderer, lhs.x, lhs.y, rhs.x, rhs.y);
|
SDL_RenderDrawLineF(g_renderer, lhs.x, lhs.y, rhs.x, rhs.y);
|
||||||
|
|
||||||
rhs = camera_world_to_pixel_point(&g_camera, vaddf(transform->position, rigidbody_get_force(body)));
|
rhs = camera_world_to_pixel_point(&g_camera, vaddf(transform->position, rigidbody_get_force(body)));
|
||||||
SDL_SetRenderDrawColor(g_renderer, 0, 255, 255, 255);
|
SDL_SetRenderDrawColor(g_renderer, 0, 255, 255, 255);
|
||||||
SDL_RenderDrawLine(g_renderer, lhs.x, lhs.y, rhs.x, rhs.y);
|
SDL_RenderDrawLineF(g_renderer, lhs.x, lhs.y, rhs.x, rhs.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline
|
static inline
|
||||||
Vector _internal_calculate_contact_force(RigidBody* self, Contact* contact) {
|
Vector _internal_calculate_contact_force(RigidBody* self, Contact* contact) {
|
||||||
const Vector velocity = contact->hit.velocity;
|
const Vector velocity = contact->hit.velocity;
|
||||||
|
@ -69,7 +72,7 @@ void physics_entity_solve_contacts(PhysicsEntity self, List* contacts) {
|
||||||
float dot = vdotf(dir, vel);
|
float dot = vdotf(dir, vel);
|
||||||
|
|
||||||
if(dot < 0)
|
if(dot < 0)
|
||||||
vel = vsubf(vel, vmulff(dir, dot * (1.0 + rigidbody_get_bounce(body))));
|
vel = vsubf(vel, vmulff(dir, dot * (1.0f + rigidbody_get_bounce(body))));
|
||||||
|
|
||||||
rigidbody_set_velocity(body, vel);
|
rigidbody_set_velocity(body, vel);
|
||||||
}
|
}
|
||||||
|
@ -81,9 +84,13 @@ void physics_entity_update(PhysicsEntity self) {
|
||||||
|
|
||||||
List* contacts = rigidbody_get_contacts(body);
|
List* contacts = rigidbody_get_contacts(body);
|
||||||
if(contacts->len > 0) {
|
if(contacts->len > 0) {
|
||||||
self.tc->collision_solver(self.data, contacts);
|
if (rigidbody_is_static(body) == 0) {
|
||||||
list_foreach(Contact*, contact, contacts)
|
physics_entity_solve_contacts(self, contacts);
|
||||||
|
}
|
||||||
|
|
||||||
|
list_foreach(Contact *, contact, contacts) {
|
||||||
self.tc->on_collision(self.data, contact->hit);
|
self.tc->on_collision(self.data, contact->hit);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
rigidbody_collect_contacts(body);
|
rigidbody_collect_contacts(body);
|
||||||
|
|
|
@ -1,47 +1,46 @@
|
||||||
#ifndef _fencer_collidable_h
|
#ifndef _fencer_collidable_h
|
||||||
#define _fencer_collidable_h
|
#define _fencer_collidable_h
|
||||||
|
|
||||||
#include "vmath.h"
|
|
||||||
#include "typeclass_helpers.h"
|
#include "typeclass_helpers.h"
|
||||||
#include "list.h"
|
#include "list.h"
|
||||||
#include "shape.h"
|
#include "transformable.h"
|
||||||
|
#include "mirror.h"
|
||||||
|
|
||||||
|
typedef struct Collider Collider;
|
||||||
typedef struct Collision Collision;
|
typedef struct Collision Collision;
|
||||||
typedef struct RigidBody RigidBody;
|
typedef struct RigidBody RigidBody;
|
||||||
|
typedef struct PhysicsEntity PhysicsEntity;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct IPhysicsEntity {
|
||||||
RigidBody* (*const get_rigidbody)(void* self);
|
RigidBody* (*const get_rigidbody)(void* self);
|
||||||
Shape* (*const get_shape)(void* self);
|
|
||||||
void(*const on_collision)(void* self, Collision collision);
|
void(*const on_collision)(void* self, Collision collision);
|
||||||
void(*const collision_solver)(void* self, List* collisions);
|
void(*const on_overlap)(void* self, Collider* other);
|
||||||
} IPhysicsEntity;
|
} IPhysicsEntity;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct PhysicsEntity {
|
||||||
void* data;
|
void* data;
|
||||||
IPhysicsEntity const* tc;
|
IPhysicsEntity const* tc;
|
||||||
ITransformable const* transformable;
|
ITransformable const* transformable;
|
||||||
|
IMirror const* mirror;
|
||||||
} PhysicsEntity;
|
} PhysicsEntity;
|
||||||
|
|
||||||
extern void physics_entity_debug_draw(PhysicsEntity self);
|
extern void physics_entity_debug_draw(PhysicsEntity self);
|
||||||
extern void physics_entity_solve_contacts(PhysicsEntity self, List* contacts);
|
extern void physics_entity_solve_contacts(PhysicsEntity self, List* contacts);
|
||||||
extern void physics_entity_update(PhysicsEntity self);
|
extern void physics_entity_update(PhysicsEntity self);
|
||||||
|
|
||||||
|
#define impl_PhysicsEntity_for(T, get_rigidbody_f, on_collision_f, on_overlap_f)\
|
||||||
#define impl_PhysicsEntity_for(T, get_rigidbody_f, get_shape_f, on_collision_f, collision_solver_f)\
|
PhysicsEntity T##_as_PhysicsEntity(T* x) {\
|
||||||
static inline PhysicsEntity T##_as_PhysicsEntity(T* x) {\
|
|
||||||
TC_FN_TYPECHECK(Transformable, T##_as_Transformable, T*);\
|
|
||||||
TC_FN_TYPECHECK(RigidBody*, get_rigidbody_f, T*);\
|
TC_FN_TYPECHECK(RigidBody*, get_rigidbody_f, T*);\
|
||||||
TC_FN_TYPECHECK(Shape*, get_shape_f, T*);\
|
|
||||||
TC_FN_TYPECHECK(void, on_collision_f, T*, Collision);\
|
TC_FN_TYPECHECK(void, on_collision_f, T*, Collision);\
|
||||||
TC_FN_TYPECHECK(void, collision_solver_f, T*, List*);\
|
TC_FN_TYPECHECK(void, on_overlap_f, T*, Collider*);\
|
||||||
static IPhysicsEntity const tc = {\
|
static IPhysicsEntity const tc = {\
|
||||||
.get_rigidbody = (RigidBody*(*const)(void*)) get_rigidbody_f,\
|
.get_rigidbody = (RigidBody*(*const)(void*)) get_rigidbody_f,\
|
||||||
.get_shape = (Shape*(*const)(void*)) get_shape_f,\
|
.on_collision = (void(*const)(void*,Collision)) on_collision_f,\
|
||||||
.on_collision = (void(*const)(void*,Collision)) on_collision_f,\
|
.on_overlap = (void(*const)(void*,Collider*)) on_overlap_f,\
|
||||||
.collision_solver = (void(*const)(void*,List*)) collision_solver_f,\
|
|
||||||
};\
|
};\
|
||||||
Transformable transformable = T##_as_Transformable(x);\
|
Transformable transformable = T##_as_Transformable(x);\
|
||||||
return (PhysicsEntity){.data = x, .tc = &tc, .transformable = transformable.tc};\
|
Mirror mirror = T##_as_Mirror(x);\
|
||||||
|
return (PhysicsEntity){.data = x, .tc = &tc, .transformable = transformable.tc, .mirror = mirror.tc};\
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // !_fencer_collidable_h
|
#endif // !_fencer_collidable_h
|
179
core/src/physics_world.c
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
#include "physics_world.h"
|
||||||
|
#include "debug.h"
|
||||||
|
#include "collision.h"
|
||||||
|
#include "collider.h"
|
||||||
|
#include "rigidbody.h"
|
||||||
|
|
||||||
|
static List _world_bodies;
|
||||||
|
static List _world_bodies_add_queue;
|
||||||
|
static List _world_bodies_remove_queue;
|
||||||
|
|
||||||
|
void physics_world_init() {
|
||||||
|
_world_bodies = list_from_type(PhysicsEntity);
|
||||||
|
_world_bodies_add_queue = list_from_type(PhysicsEntity);
|
||||||
|
_world_bodies_remove_queue = list_from_type(PhysicsEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void physics_world_clean() {
|
||||||
|
list_empty(&_world_bodies);
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
size_t _internal_physics_world_find_index(PhysicsEntity entity) {
|
||||||
|
for(size_t i = 0; i < _world_bodies.len; ++i) {
|
||||||
|
PhysicsEntity* found = list_at_as(PhysicsEntity, &_world_bodies, i);
|
||||||
|
if(found->data == entity.data) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ASSERT_RETURN(0, _world_bodies.len, "Failed to find entity %p in world bodies", entity.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void _internal_physics_world_add_remove() {
|
||||||
|
size_t index = 0;
|
||||||
|
list_foreach(PhysicsEntity*, entity, &_world_bodies_remove_queue) {
|
||||||
|
index = _internal_physics_world_find_index(*entity);
|
||||||
|
list_erase(&_world_bodies, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
list_empty(&_world_bodies_remove_queue);
|
||||||
|
|
||||||
|
list_foreach(PhysicsEntity*, entity, &_world_bodies_add_queue) {
|
||||||
|
list_add(&_world_bodies, entity);
|
||||||
|
}
|
||||||
|
list_empty(&_world_bodies_add_queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void physics_world_add_entity(PhysicsEntity entity) {
|
||||||
|
list_add(&_world_bodies_add_queue, &entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void physics_world_remove_entity(PhysicsEntity entity) {
|
||||||
|
list_add(&_world_bodies_remove_queue, &entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
void _internal_physics_check_entities(PhysicsEntity left, PhysicsEntity right) {
|
||||||
|
RigidBody* rbleft = left.tc->get_rigidbody(left.data);
|
||||||
|
RigidBody* rbright = right.tc->get_rigidbody(right.data);
|
||||||
|
|
||||||
|
Collision collision_left, collision_right;
|
||||||
|
int is_overlap = 0;
|
||||||
|
|
||||||
|
list_foreach(Collider**, left_col, rigidbody_get_colliders(rbleft)) {
|
||||||
|
list_foreach(Collider**, right_col, rigidbody_get_colliders(rbright)) {
|
||||||
|
is_overlap = collider_is_overlap(*left_col) || collider_is_overlap(*right_col);
|
||||||
|
|
||||||
|
if(collision_check(*left_col, *right_col, &collision_left, &collision_right)) {
|
||||||
|
if(is_overlap) {
|
||||||
|
left.tc->on_overlap(left.data, *right_col);
|
||||||
|
right.tc->on_overlap(right.data, *left_col);
|
||||||
|
} else {
|
||||||
|
rigidbody_add_contact(rbleft, collision_left);
|
||||||
|
rigidbody_add_contact(rbright, collision_right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
void _internal_physics_narrow_collision() {
|
||||||
|
size_t half_end = _world_bodies.len/2;
|
||||||
|
|
||||||
|
PhysicsEntity* right = NULL;
|
||||||
|
list_foreach(PhysicsEntity*, left, &_world_bodies) {
|
||||||
|
for(size_t right_index = 0; right_index <= half_end; ++right_index) {
|
||||||
|
right = list_at_as(PhysicsEntity, &_world_bodies, right_index);
|
||||||
|
|
||||||
|
if(left->data == right->data) continue;
|
||||||
|
_internal_physics_check_entities(*left, *right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
void _internal_physics_apply() {
|
||||||
|
list_foreach(PhysicsEntity*, entity, &_world_bodies) {
|
||||||
|
physics_entity_update(*entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
void _internal_physics_integrate_forces() {
|
||||||
|
list_foreach(PhysicsEntity*, entity, &_world_bodies)
|
||||||
|
rigidbody_integrate_forces(entity->tc->get_rigidbody(entity->data));
|
||||||
|
}
|
||||||
|
|
||||||
|
void physics_world_tick() {
|
||||||
|
_internal_physics_world_add_remove();
|
||||||
|
_internal_physics_integrate_forces();
|
||||||
|
_internal_physics_narrow_collision();
|
||||||
|
_internal_physics_apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
List physics_world_query_all(PhysicsQuery query, RigidBody* ignore) {
|
||||||
|
List result = list_from_type(Collider*);
|
||||||
|
list_foreach(PhysicsEntity*, entity, &_world_bodies) {
|
||||||
|
RigidBody* body = entity->tc->get_rigidbody(entity->data);
|
||||||
|
if(body == ignore) continue;
|
||||||
|
|
||||||
|
list_foreach(Collider**, collider, rigidbody_get_colliders(body))
|
||||||
|
if(overlap_check(query, *collider))
|
||||||
|
list_add(&result, collider);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
List physics_world_box_query_all(Vector centre, Vector extents, PhysicsMask mask, RigidBody* ignore) {
|
||||||
|
Transform transform = {
|
||||||
|
.position = centre,
|
||||||
|
.scale = OneVector,
|
||||||
|
.rotation = 0.f
|
||||||
|
};
|
||||||
|
Shape* shape = shape_new((Vector[]){
|
||||||
|
MakeVector(-extents.x, -extents.y),
|
||||||
|
MakeVector(extents.x, -extents.y),
|
||||||
|
MakeVector(extents.x, extents.y),
|
||||||
|
MakeVector(-extents.x, extents.y)
|
||||||
|
}, 4);
|
||||||
|
PhysicsQuery query = {
|
||||||
|
.shape = shape,
|
||||||
|
.transform = &transform,
|
||||||
|
.mask = mask
|
||||||
|
};
|
||||||
|
return physics_world_query_all(query, ignore);
|
||||||
|
}
|
||||||
|
|
||||||
|
Collider* physics_world_query(PhysicsQuery query, RigidBody* ignore) {
|
||||||
|
list_foreach(PhysicsEntity*, entity, &_world_bodies) {
|
||||||
|
RigidBody* body = entity->tc->get_rigidbody(entity->data);
|
||||||
|
if(body == ignore) continue;
|
||||||
|
|
||||||
|
list_foreach(Collider**, collider, rigidbody_get_colliders(body))
|
||||||
|
if(overlap_check(query, *collider))
|
||||||
|
return *collider;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Collider* physics_world_box_query(Vector centre, Vector extents, PhysicsMask mask, RigidBody* ignore) {
|
||||||
|
Transform transform = {
|
||||||
|
.position = centre,
|
||||||
|
.scale = OneVector,
|
||||||
|
.rotation = 0.f
|
||||||
|
};
|
||||||
|
Shape* shape = shape_new((Vector[]){
|
||||||
|
MakeVector(-extents.x, -extents.y),
|
||||||
|
MakeVector(extents.x, -extents.y),
|
||||||
|
MakeVector(extents.x, extents.y),
|
||||||
|
MakeVector(-extents.x, extents.y)
|
||||||
|
}, 4);
|
||||||
|
PhysicsQuery query = {
|
||||||
|
.shape = shape,
|
||||||
|
.transform = &transform,
|
||||||
|
.mask = mask
|
||||||
|
};
|
||||||
|
return physics_world_query(query, ignore);
|
||||||
|
}
|
20
core/src/physics_world.h
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#ifndef _fencer_physics_world_h
|
||||||
|
#define _fencer_physics_world_h
|
||||||
|
|
||||||
|
#include "physics_entity.h"
|
||||||
|
#include "collision.h"
|
||||||
|
|
||||||
|
extern void physics_world_init();
|
||||||
|
extern void physics_world_clean();
|
||||||
|
|
||||||
|
extern void physics_world_add_entity(PhysicsEntity entity);
|
||||||
|
extern void physics_world_remove_entity(PhysicsEntity entity);
|
||||||
|
|
||||||
|
extern void physics_world_tick();
|
||||||
|
|
||||||
|
extern List physics_world_query_all(PhysicsQuery query, RigidBody* ignore);
|
||||||
|
extern List physics_world_box_query_all(Vector centre, Vector extents, PhysicsMask mask, RigidBody* ignore);
|
||||||
|
extern Collider* physics_world_query(PhysicsQuery query, RigidBody* ignore);
|
||||||
|
extern Collider* physics_world_box_query(Vector centre, Vector extents, PhysicsMask mask, RigidBody* ignore);
|
||||||
|
|
||||||
|
#endif // !_fencer_physics_world_h
|
|
@ -1,7 +1,13 @@
|
||||||
#include "player_input.h"
|
#include "player_input.h"
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
impl_Drop_for(PlayerInput,
|
||||||
|
playerinput_drop
|
||||||
|
)
|
||||||
|
|
||||||
PlayerInput* playerinput_new(void* target, int device) {
|
PlayerInput* playerinput_new(void* target, int device) {
|
||||||
PlayerInput* self = malloc(sizeof(PlayerInput));
|
PlayerInput* self = malloc(sizeof(PlayerInput));
|
||||||
|
ASSERT_RETURN(self != NULL, NULL, "Could not allocate memory for PlayerInput instance");
|
||||||
self->listeners = list_from_type(InputListener);
|
self->listeners = list_from_type(InputListener);
|
||||||
self->device = input_get_device_by_id(device);
|
self->device = input_get_device_by_id(device);
|
||||||
self->target = target;
|
self->target = target;
|
|
@ -4,6 +4,7 @@
|
||||||
#include "list.h"
|
#include "list.h"
|
||||||
#include "input.h"
|
#include "input.h"
|
||||||
#include "input_axis.h"
|
#include "input_axis.h"
|
||||||
|
#include "typeclass_helpers.h"
|
||||||
|
|
||||||
typedef struct PlayerInput {
|
typedef struct PlayerInput {
|
||||||
InputDevice* device;
|
InputDevice* device;
|
||||||
|
@ -16,8 +17,6 @@ extern void playerinput_add(PlayerInput* self, InputAxis axis, InputDelegateFn d
|
||||||
extern void playerinput_set_device(PlayerInput* self, int device);
|
extern void playerinput_set_device(PlayerInput* self, int device);
|
||||||
extern void playerinput_drop(PlayerInput* self);
|
extern void playerinput_drop(PlayerInput* self);
|
||||||
|
|
||||||
impl_Drop_for(PlayerInput,
|
decl_typeclass_impl(Drop, PlayerInput)
|
||||||
playerinput_drop
|
|
||||||
)
|
|
||||||
|
|
||||||
#endif // !_fencer_player_input_h
|
#endif // !_fencer_player_input_h
|
|
@ -2,6 +2,7 @@
|
||||||
#include "camera.h"
|
#include "camera.h"
|
||||||
#include "game_world.h"
|
#include "game_world.h"
|
||||||
#include "physics_world.h"
|
#include "physics_world.h"
|
||||||
|
#include "level.h"
|
||||||
#include "time.h"
|
#include "time.h"
|
||||||
#include "assets.h"
|
#include "assets.h"
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
|
@ -25,7 +26,7 @@ double tstos(struct timespec ts) {
|
||||||
|
|
||||||
struct timespec get_time() {
|
struct timespec get_time() {
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
timespec_get(&ts, TIME_UTC);
|
(void)timespec_get(&ts, TIME_UTC);
|
||||||
return ts;
|
return ts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +34,13 @@ double get_time_s() {
|
||||||
return tstos(get_time());
|
return tstos(get_time());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void program_tick(const struct ProgramSettings* settings, float delta_time) {
|
||||||
|
settings->on_tick();
|
||||||
|
game_world_update();
|
||||||
|
physics_world_tick();
|
||||||
|
}
|
||||||
|
|
||||||
void program_run(const struct ProgramSettings* settings) {
|
void program_run(const struct ProgramSettings* settings) {
|
||||||
LOG_INFO("Starting program...");
|
LOG_INFO("Starting program...");
|
||||||
if(settings->target_fps <= 0) {
|
if(settings->target_fps <= 0) {
|
||||||
|
@ -60,6 +68,7 @@ void program_run(const struct ProgramSettings* settings) {
|
||||||
assets_init();
|
assets_init();
|
||||||
input_init();
|
input_init();
|
||||||
game_world_init();
|
game_world_init();
|
||||||
|
level_init();
|
||||||
|
|
||||||
LOG_INFO("settings->on_play");
|
LOG_INFO("settings->on_play");
|
||||||
settings->on_play();
|
settings->on_play();
|
||||||
|
@ -73,13 +82,17 @@ void program_run(const struct ProgramSettings* settings) {
|
||||||
_frame_start = current_time;
|
_frame_start = current_time;
|
||||||
|
|
||||||
program_handle_events();
|
program_handle_events();
|
||||||
while(_delta_time > _target_delta_time) {
|
if(settings->target_fps == 0) {
|
||||||
_delta_time -= _target_delta_time;
|
program_tick(settings, _delta_time);
|
||||||
settings->on_tick();
|
_delta_time = 0.f;
|
||||||
game_world_update();
|
} else {
|
||||||
physics_world_tick();
|
while(_delta_time > _target_delta_time) {
|
||||||
|
_delta_time -= _target_delta_time;
|
||||||
|
program_tick(settings, _target_delta_time);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
settings->on_draw();
|
settings->on_draw();
|
||||||
|
game_world_draw();
|
||||||
SDL_Delay(1);
|
SDL_Delay(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,6 +101,7 @@ void program_run(const struct ProgramSettings* settings) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void program_quit() {
|
void program_quit() {
|
||||||
|
level_clean();
|
||||||
game_world_close();
|
game_world_close();
|
||||||
input_clean();
|
input_clean();
|
||||||
assets_clean();
|
assets_clean();
|
||||||
|
@ -132,10 +146,10 @@ void program_handle_windowevent(SDL_WindowEvent* event) {
|
||||||
|
|
||||||
inline
|
inline
|
||||||
float delta_time() {
|
float delta_time() {
|
||||||
return _target_delta_time == 0 ? _delta_time : _target_delta_time;
|
return (float)(_target_delta_time == 0 ? _delta_time : _target_delta_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline
|
inline
|
||||||
float game_time() {
|
float game_time() {
|
||||||
return get_time_s() - _game_start_time;
|
return (float)(get_time_s() - _game_start_time);
|
||||||
}
|
}
|
|
@ -62,10 +62,10 @@ void render_calculate_render_area() {
|
||||||
// calculate the largest area that will fit the entire rendertexture into the window space
|
// calculate the largest area that will fit the entire rendertexture into the window space
|
||||||
g_render_area = (SDL_Rect) {0, 0, window_resolution.x, window_resolution.y};
|
g_render_area = (SDL_Rect) {0, 0, window_resolution.x, window_resolution.y};
|
||||||
if(window_aspect <= target_aspect) {
|
if(window_aspect <= target_aspect) {
|
||||||
g_render_area.h = window_resolution.x / target_aspect;
|
g_render_area.h = (int)((float)window_resolution.x / target_aspect);
|
||||||
g_render_area.y = (window_resolution.y - g_render_area.h) / 2;
|
g_render_area.y = (window_resolution.y - g_render_area.h) / 2;
|
||||||
} else {
|
} else {
|
||||||
g_render_area.w = window_resolution.y * target_aspect;
|
g_render_area.w = (int)((float)window_resolution.y * target_aspect);
|
||||||
g_render_area.x += (window_resolution.x - g_render_area.w) / 2;
|
g_render_area.x += (window_resolution.x - g_render_area.w) / 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,10 +6,9 @@
|
||||||
#include "transformable.h"
|
#include "transformable.h"
|
||||||
|
|
||||||
struct RigidBody {
|
struct RigidBody {
|
||||||
Transformable transformable;
|
PhysicsEntity owner;
|
||||||
|
|
||||||
float mass;
|
float mass;
|
||||||
|
|
||||||
float bounce;
|
float bounce;
|
||||||
|
|
||||||
Vector last_linear_force;
|
Vector last_linear_force;
|
||||||
|
@ -17,16 +16,25 @@ struct RigidBody {
|
||||||
Vector linear_velocity;
|
Vector linear_velocity;
|
||||||
Transform internal_transform;
|
Transform internal_transform;
|
||||||
|
|
||||||
|
PhysicsMask layers;
|
||||||
|
PhysicsMask collision_mask;
|
||||||
|
|
||||||
|
List colliders;
|
||||||
|
|
||||||
int is_static;
|
int is_static;
|
||||||
|
|
||||||
List contacts;
|
List contacts;
|
||||||
};
|
};
|
||||||
|
|
||||||
RigidBody* rigidbody_make(Transformable transform) {
|
impl_Transformable_for(RigidBody,
|
||||||
|
rigidbody_get_transform
|
||||||
|
)
|
||||||
|
|
||||||
|
RigidBody* rigidbody_make(PhysicsEntity owner) {
|
||||||
RigidBody* self = malloc(sizeof(RigidBody));
|
RigidBody* self = malloc(sizeof(RigidBody));
|
||||||
ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for rigidbody");
|
ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for rigidbody");
|
||||||
*self = (RigidBody){
|
*self = (RigidBody){
|
||||||
.transformable = transform,
|
.owner = owner,
|
||||||
.mass = 1.0f,
|
.mass = 1.0f,
|
||||||
.bounce = 0.0f,
|
.bounce = 0.0f,
|
||||||
|
|
||||||
|
@ -34,11 +42,18 @@ RigidBody* rigidbody_make(Transformable transform) {
|
||||||
.next_linear_force = ZeroVector,
|
.next_linear_force = ZeroVector,
|
||||||
.last_linear_force = ZeroVector,
|
.last_linear_force = ZeroVector,
|
||||||
|
|
||||||
.internal_transform = *transform.tc->get_transform(transform.data),
|
.internal_transform = *owner.transformable->get_transform(owner.data),
|
||||||
|
|
||||||
|
.layers = 0x1,
|
||||||
|
.collision_mask = 0x1,
|
||||||
|
|
||||||
|
.colliders = list_from_type(Collider*),
|
||||||
|
|
||||||
.is_static = 0,
|
.is_static = 0,
|
||||||
|
|
||||||
.contacts = list_from_type(Contact),
|
.contacts = list_from_type(Contact),
|
||||||
};
|
};
|
||||||
|
self->internal_transform.scale = OneVector;
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,15 +88,18 @@ void _internal_debug_draw_collision_edge(RigidBody* self, Contact* contact) {
|
||||||
Vector b = camera_world_to_pixel_point(&g_camera, right);
|
Vector b = camera_world_to_pixel_point(&g_camera, right);
|
||||||
Vector n = transform_direction(&g_camera.transform, contact->hit.normal);
|
Vector n = transform_direction(&g_camera.transform, contact->hit.normal);
|
||||||
SDL_SetRenderDrawColor(g_renderer, 255, 2, 255, 255);
|
SDL_SetRenderDrawColor(g_renderer, 255, 2, 255, 255);
|
||||||
SDL_RenderDrawLine(g_renderer, a.x, a.y, b.x, b.y);
|
SDL_RenderDrawLineF(g_renderer, a.x, a.y, b.x, b.y);
|
||||||
a = camera_world_to_pixel_point(&g_camera, point);
|
a = camera_world_to_pixel_point(&g_camera, point);
|
||||||
b = vaddf(a, vmulff(n, 100.f));
|
b = vaddf(a, vmulff(n, 100.f));
|
||||||
SDL_SetRenderDrawColor(g_renderer, 255, 0, 0, 255);
|
SDL_SetRenderDrawColor(g_renderer, 255, 0, 0, 255);
|
||||||
SDL_RenderDrawLine(g_renderer, a.x, a.y, b.x, b.y);
|
SDL_RenderDrawLineF(g_renderer, a.x, a.y, b.x, b.y);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void rigidbody_integrate_forces(RigidBody* self) {
|
void rigidbody_integrate_forces(RigidBody* self) {
|
||||||
|
if(self->is_static)
|
||||||
|
return;
|
||||||
|
|
||||||
const float dt = delta_time();
|
const float dt = delta_time();
|
||||||
|
|
||||||
Vector position = self->internal_transform.position;
|
Vector position = self->internal_transform.position;
|
||||||
|
@ -96,7 +114,8 @@ void rigidbody_integrate_forces(RigidBody* self) {
|
||||||
|
|
||||||
self->linear_velocity = velocity;
|
self->linear_velocity = velocity;
|
||||||
self->internal_transform.position = position;
|
self->internal_transform.position = position;
|
||||||
transformable_set_position(self->transformable, position);
|
Transform* owner_trans = self->owner.transformable->get_transform(self->owner.data);
|
||||||
|
owner_trans->position = position;
|
||||||
|
|
||||||
self->last_linear_force = self->next_linear_force;
|
self->last_linear_force = self->next_linear_force;
|
||||||
self->next_linear_force = ZeroVector;
|
self->next_linear_force = ZeroVector;
|
||||||
|
@ -129,12 +148,28 @@ void rigidbody_accelerate(RigidBody* self, Vector force, int use_mass) {
|
||||||
self->next_linear_force = vaddf(self->next_linear_force, force);
|
self->next_linear_force = vaddf(self->next_linear_force, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
int rigidbody_is_static(const RigidBody* self) {
|
PhysicsMask rigidbody_get_layers(RigidBody* self) {
|
||||||
|
return self->layers;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rigidbody_set_layers(RigidBody* self, PhysicsMask layers) {
|
||||||
|
self->layers = layers;
|
||||||
|
}
|
||||||
|
|
||||||
|
PhysicsMask rigidbody_get_collision_mask(RigidBody* self) {
|
||||||
|
return self->collision_mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rigidbody_set_collision_mask(RigidBody* self, PhysicsMask mask) {
|
||||||
|
self->collision_mask = mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rigidbody_is_static(RigidBody* self) {
|
||||||
return self->is_static;
|
return self->is_static;
|
||||||
}
|
}
|
||||||
|
|
||||||
void rigidbody_set_static(RigidBody* self, int is_static) {
|
void rigidbody_set_static(RigidBody* self, int value) {
|
||||||
self->is_static = is_static;
|
self->is_static = value != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector rigidbody_get_velocity(const RigidBody* self) {
|
Vector rigidbody_get_velocity(const RigidBody* self) {
|
||||||
|
@ -150,6 +185,23 @@ Vector rigidbody_get_force(RigidBody* self) {
|
||||||
return self->next_linear_force;
|
return self->next_linear_force;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void rigidbody_add_collider(RigidBody *self, Collider* collider) {
|
||||||
|
list_add(&self->colliders, &collider);
|
||||||
|
}
|
||||||
|
|
||||||
|
void rigidbody_remove_collider(RigidBody *self, Collider* collider) {
|
||||||
|
for(size_t i = 0; i < self->colliders.len; ++i) {
|
||||||
|
if(collider == *list_at_as(Collider*, &self->colliders, i)) {
|
||||||
|
list_erase(&self->colliders, i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List* rigidbody_get_colliders(RigidBody* self) {
|
||||||
|
return &self->colliders;
|
||||||
|
}
|
||||||
|
|
||||||
void rigidbody_debug_draw_contacts(RigidBody* self) {
|
void rigidbody_debug_draw_contacts(RigidBody* self) {
|
||||||
list_foreach(Contact*, contact, &self->contacts) {
|
list_foreach(Contact*, contact, &self->contacts) {
|
||||||
_internal_debug_draw_collision_edge(self, contact);
|
_internal_debug_draw_collision_edge(self, contact);
|
|
@ -1,21 +1,22 @@
|
||||||
#ifndef _fencer_rigidbody_h
|
#ifndef _fencer_rigidbody_h
|
||||||
#define _fencer_rigidbody_h
|
#define _fencer_rigidbody_h
|
||||||
|
|
||||||
#include "shape.h"
|
|
||||||
#include "transformable.h"
|
#include "transformable.h"
|
||||||
#include "list.h"
|
#include "list.h"
|
||||||
#include "collision.h"
|
#include "physics.h"
|
||||||
|
|
||||||
struct Collision;
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
struct Collision hit;
|
Collision hit;
|
||||||
float duration;
|
float duration;
|
||||||
} Contact;
|
} Contact;
|
||||||
|
|
||||||
typedef struct RigidBody RigidBody;
|
typedef struct RigidBody RigidBody;
|
||||||
|
typedef struct PhysicsEntity PhysicsEntity;
|
||||||
|
|
||||||
typedef void (*CollisionHandlerFn)(void* obj, List* collisions);
|
typedef void (*CollisionHandlerFn)(void* obj, List* collisions);
|
||||||
|
|
||||||
// Referenced transform is stored but not owned by the rigidbody.
|
// Referenced transform is stored but not owned by the rigidbody.
|
||||||
extern RigidBody* rigidbody_make(Transformable transform);
|
extern RigidBody* rigidbody_make(PhysicsEntity owner);
|
||||||
extern void rigidbody_destroy(RigidBody* self);
|
extern void rigidbody_destroy(RigidBody* self);
|
||||||
|
|
||||||
extern void rigidbody_add_contact(RigidBody* self, struct Collision hit);
|
extern void rigidbody_add_contact(RigidBody* self, struct Collision hit);
|
||||||
|
@ -33,19 +34,31 @@ extern void rigidbody_set_bounce(RigidBody* self, float bounce);
|
||||||
extern void rigidbody_add_impulse(RigidBody* self, Vector force, int use_mass);
|
extern void rigidbody_add_impulse(RigidBody* self, Vector force, int use_mass);
|
||||||
extern void rigidbody_accelerate(RigidBody* self, Vector force, int use_mass);
|
extern void rigidbody_accelerate(RigidBody* self, Vector force, int use_mass);
|
||||||
|
|
||||||
extern int rigidbody_is_static(const RigidBody* self);
|
extern PhysicsMask rigidbody_get_layers(RigidBody* self);
|
||||||
extern void rigidbody_set_static(RigidBody* self, int is_static);
|
extern void rigidbody_set_layers(RigidBody* self, PhysicsMask layers);
|
||||||
|
|
||||||
|
extern PhysicsMask rigidbody_get_collision_mask(RigidBody* self);
|
||||||
|
extern void rigidbody_set_collision_mask(RigidBody* self, PhysicsMask mask);
|
||||||
|
|
||||||
|
extern int rigidbody_get_overlap(RigidBody* self);
|
||||||
|
extern void rigidbody_set_overlap(RigidBody* self, int value);
|
||||||
|
|
||||||
|
extern int rigidbody_is_static(RigidBody* self);
|
||||||
|
extern void rigidbody_set_static(RigidBody* self, int value);
|
||||||
|
|
||||||
extern Vector rigidbody_get_velocity(const RigidBody* self);
|
extern Vector rigidbody_get_velocity(const RigidBody* self);
|
||||||
extern void rigidbody_set_velocity(RigidBody* self, Vector velocity);
|
extern void rigidbody_set_velocity(RigidBody* self, Vector velocity);
|
||||||
|
|
||||||
extern Vector rigidbody_get_force(RigidBody* self);
|
extern Vector rigidbody_get_force(RigidBody* self);
|
||||||
|
|
||||||
|
extern void rigidbody_add_collider(RigidBody* self, Collider* collider);
|
||||||
|
extern void rigidbody_remove_collider(RigidBody* self, Collider* collider);
|
||||||
|
extern List* rigidbody_get_colliders(RigidBody* self);
|
||||||
|
|
||||||
extern void rigidbody_debug_draw_contacts(RigidBody* self);
|
extern void rigidbody_debug_draw_contacts(RigidBody* self);
|
||||||
|
|
||||||
extern Transform* rigidbody_get_transform(RigidBody* self);
|
extern Transform* rigidbody_get_transform(RigidBody* self);
|
||||||
|
|
||||||
impl_Transformable_for(RigidBody,
|
decl_typeclass_impl(Transformable, RigidBody)
|
||||||
rigidbody_get_transform
|
|
||||||
)
|
|
||||||
|
|
||||||
#endif // !_fencer_rigidbody_h
|
#endif // !_fencer_rigidbody_h
|
|
@ -11,6 +11,7 @@ struct Shape {
|
||||||
|
|
||||||
Vector mean;
|
Vector mean;
|
||||||
int is_convex;
|
int is_convex;
|
||||||
|
Vector min, max;
|
||||||
};
|
};
|
||||||
|
|
||||||
static
|
static
|
||||||
|
@ -40,7 +41,7 @@ int _shape_calculate_is_convex(Shape* self) {
|
||||||
// point relative to mean
|
// point relative to mean
|
||||||
Vector relative;
|
Vector relative;
|
||||||
list_foreach(Vector*, point, &self->points) {
|
list_foreach(Vector*, point, &self->points) {
|
||||||
relative = vsubf(*point, self->mean);
|
relative = vnormalizedf(vsubf(*point, self->mean));
|
||||||
if(point != _shape_get_furthest_in_direction(self, relative)) {
|
if(point != _shape_get_furthest_in_direction(self, relative)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -55,7 +56,7 @@ Vector _shape_calculate_mean(Shape* self) {
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
list_foreach(Vector*, point, &self->points) {
|
list_foreach(Vector*, point, &self->points) {
|
||||||
++count;
|
++count;
|
||||||
avg = vaddf(avg, vmulff(*point, 1.0/count));
|
avg = vaddf(avg, vmulff(*point, 1.f/count));
|
||||||
}
|
}
|
||||||
|
|
||||||
return avg;
|
return avg;
|
||||||
|
@ -86,10 +87,10 @@ Shape* shape_new(const Vector* points, size_t points_len) {
|
||||||
|
|
||||||
Shape* shape_new_square(Vector size) {
|
Shape* shape_new_square(Vector size) {
|
||||||
return shape_new((Vector[4]){
|
return shape_new((Vector[4]){
|
||||||
ZeroVector,
|
MakeVector(-size.x, -size.y),
|
||||||
(Vector){size.x, 0.f},
|
MakeVector(size.x, -size.y),
|
||||||
size,
|
MakeVector(size.x, size.y),
|
||||||
(Vector){0.f, size.y},
|
MakeVector(-size.x, size.y)
|
||||||
}, 4);
|
}, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,3 +184,29 @@ void shape_draw(Shape* self, Transform transform) {
|
||||||
SDL_RenderDrawLineF(g_renderer, lhs.x, lhs.y, rhs.x, rhs.y);
|
SDL_RenderDrawLineF(g_renderer, lhs.x, lhs.y, rhs.x, rhs.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vector shape_get_min_extent(Shape* self, Transform* transform) {
|
||||||
|
if(self->points.len == 0)
|
||||||
|
return ZeroVector;
|
||||||
|
Vector min = *list_at_as(Vector, &self->points, 0);
|
||||||
|
Vector point;
|
||||||
|
for(size_t i = 0; i < shape_get_points_count(self); ++i) {
|
||||||
|
point = shape_get_point_transformed(self, i, *transform);
|
||||||
|
min.x = fminf(min.x, point.x);
|
||||||
|
min.y = fminf(min.y, point.y);
|
||||||
|
}
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector shape_get_max_extent(Shape* self, Transform* transform) {
|
||||||
|
if(self->points.len == 0)
|
||||||
|
return ZeroVector;
|
||||||
|
Vector max = *list_at_as(Vector, &self->points, 0);
|
||||||
|
Vector point;
|
||||||
|
for(size_t i = 0; i < shape_get_points_count(self); ++i) {
|
||||||
|
point = shape_get_point_transformed(self, i, *transform);
|
||||||
|
max.x = fmaxf(max.x, point.x);
|
||||||
|
max.y = fmaxf(max.y, point.y);
|
||||||
|
}
|
||||||
|
return max;
|
||||||
|
}
|
|
@ -29,4 +29,7 @@ extern Vector shape_get_median_point(Shape* self);
|
||||||
extern int shape_is_convex(Shape* self);
|
extern int shape_is_convex(Shape* self);
|
||||||
extern void shape_draw(Shape* self, Transform transform);
|
extern void shape_draw(Shape* self, Transform transform);
|
||||||
|
|
||||||
|
extern Vector shape_get_min_extent(Shape* self, Transform* transform);
|
||||||
|
extern Vector shape_get_max_extent(Shape* self, Transform* transform);
|
||||||
|
|
||||||
#endif // !_fencer_shape_h
|
#endif // !_fencer_shape_h
|
|
@ -19,16 +19,23 @@ struct Sprite {
|
||||||
SDL_RendererFlip flip_state;
|
SDL_RendererFlip flip_state;
|
||||||
};
|
};
|
||||||
|
|
||||||
Sprite* sprite_from_spritesheet(Spritesheet* sheet, size_t initial_frame) {
|
Sprite* sprite_new_empty() {
|
||||||
Sprite* self = malloc(sizeof(Sprite));
|
Sprite* self = malloc(sizeof(Sprite));
|
||||||
|
|
||||||
ASSERT_RETURN(self != NULL, NULL, "Failed to allocate memory for new sprite.");
|
ASSERT_RETURN(self != NULL, NULL, "Failed to allocate memory for new sprite.");
|
||||||
|
*self = (Sprite){
|
||||||
|
.spritesheet = NULL,
|
||||||
|
.tile_index = 0,
|
||||||
|
.origin = ZeroVector,
|
||||||
|
.flip_state = SDL_FLIP_NONE,
|
||||||
|
};
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sprite* sprite_from_spritesheet(Spritesheet* sheet, size_t initial_frame) {
|
||||||
|
Sprite* self = sprite_new_empty();
|
||||||
self->spritesheet = sheet;
|
self->spritesheet = sheet;
|
||||||
self->origin = (Vector){0.5f, 0.5f};
|
self->origin = VectorFrom(0.5f);
|
||||||
self->tile_index = initial_frame;
|
self->tile_index = initial_frame;
|
||||||
self->flip_state = SDL_FLIP_NONE;
|
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,10 +49,10 @@ void sprite_draw(Sprite* self, Transform transform) {
|
||||||
|
|
||||||
Vector origin = self->origin;
|
Vector origin = self->origin;
|
||||||
if(self->flip_state && SDL_FLIP_HORIZONTAL) {
|
if(self->flip_state && SDL_FLIP_HORIZONTAL) {
|
||||||
origin.x = 1.0-origin.x;
|
origin.x = 1.0f-origin.x;
|
||||||
}
|
}
|
||||||
if((self->flip_state & SDL_FLIP_VERTICAL) != 0) {
|
if((self->flip_state & SDL_FLIP_VERTICAL) != 0) {
|
||||||
origin.y = 1.0-origin.y;
|
origin.y = 1.0f-origin.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector left_top = transform_point(&transform, vinvf(origin));
|
Vector left_top = transform_point(&transform, vinvf(origin));
|
||||||
|
@ -75,8 +82,7 @@ size_t sprite_get_tile(const Sprite* self) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void sprite_set_tile(Sprite* self, size_t frame) {
|
void sprite_set_tile(Sprite* self, size_t frame) {
|
||||||
frame = frame % spritesheet_get_tile_count(self->spritesheet);
|
self->tile_index = frame % spritesheet_get_tile_count(self->spritesheet);
|
||||||
self->tile_index = frame;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Spritesheet* sprite_get_spritesheet(const Sprite* self) {
|
Spritesheet* sprite_get_spritesheet(const Sprite* self) {
|
|
@ -10,6 +10,7 @@
|
||||||
// Forward declaration of the private sprite struct
|
// Forward declaration of the private sprite struct
|
||||||
typedef struct Sprite Sprite;
|
typedef struct Sprite Sprite;
|
||||||
|
|
||||||
|
extern Sprite* sprite_new_empty();
|
||||||
extern Sprite* sprite_from_spritesheet(Spritesheet* sheet, size_t initial_frame);
|
extern Sprite* sprite_from_spritesheet(Spritesheet* sheet, size_t initial_frame);
|
||||||
extern void sprite_destroy(Sprite* sprite);
|
extern void sprite_destroy(Sprite* sprite);
|
||||||
|
|
|
@ -20,6 +20,15 @@ struct Spritesheet {
|
||||||
IVector tile_size;
|
IVector tile_size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
impl_Drop_for(Spritesheet,
|
||||||
|
_internal_spritesheet_destroy
|
||||||
|
)
|
||||||
|
|
||||||
|
impl_Asset_for(Spritesheet,
|
||||||
|
spritesheet_get_asset_id,
|
||||||
|
spritesheet_set_asset_id
|
||||||
|
)
|
||||||
|
|
||||||
void _internal_spritesheet_destroy(Spritesheet* self) {
|
void _internal_spritesheet_destroy(Spritesheet* self) {
|
||||||
SDL_DestroyTexture(self->texture);
|
SDL_DestroyTexture(self->texture);
|
||||||
free(self);
|
free(self);
|
||||||
|
@ -43,8 +52,8 @@ Spritesheet* spritesheet_from_texture(SDL_Texture* texture, IVector tile_size) {
|
||||||
SDL_QueryTexture(self->texture, NULL, NULL, &self->resolution.x, &self->resolution.y);
|
SDL_QueryTexture(self->texture, NULL, NULL, &self->resolution.x, &self->resolution.y);
|
||||||
|
|
||||||
self->tile_size = tile_size;
|
self->tile_size = tile_size;
|
||||||
self->tile_shear = self->resolution.x / self->tile_size.x;
|
self->tile_shear = (size_t)self->resolution.x / self->tile_size.x;
|
||||||
self->tile_count = self->resolution.x / self->tile_size.x * self->resolution.y / self->tile_size.y;
|
self->tile_count = (size_t)self->resolution.x / self->tile_size.x * self->resolution.y / self->tile_size.y;
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +66,7 @@ SDL_Texture* spritesheet_get_texture(const Spritesheet* self) {
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_Rect spritesheet_get_tile_rect(const Spritesheet* self, size_t index) {
|
SDL_Rect spritesheet_get_tile_rect(const Spritesheet* self, size_t index) {
|
||||||
IVector tile_coord = {index % self->tile_shear, index / self->tile_shear};
|
IVector tile_coord = {(int)(index % self->tile_shear), (int)(index / self->tile_shear)};
|
||||||
tile_coord = vmuli(tile_coord, self->tile_size);
|
tile_coord = vmuli(tile_coord, self->tile_size);
|
||||||
|
|
||||||
return (SDL_Rect) {
|
return (SDL_Rect) {
|
|
@ -2,6 +2,7 @@
|
||||||
#define _fencer_spritesheet_h
|
#define _fencer_spritesheet_h
|
||||||
|
|
||||||
#include "asset.h"
|
#include "asset.h"
|
||||||
|
#include "typeclass_helpers.h"
|
||||||
#include "vmath.h"
|
#include "vmath.h"
|
||||||
#include <SDL2/SDL_render.h>
|
#include <SDL2/SDL_render.h>
|
||||||
|
|
||||||
|
@ -21,13 +22,7 @@ extern void spritesheet_set_asset_id(Spritesheet* self, asset_id id);
|
||||||
|
|
||||||
extern void _internal_spritesheet_destroy(Spritesheet* self_void);
|
extern void _internal_spritesheet_destroy(Spritesheet* self_void);
|
||||||
|
|
||||||
impl_Drop_for(Spritesheet,
|
decl_typeclass_impl(Drop, Spritesheet)
|
||||||
_internal_spritesheet_destroy
|
decl_typeclass_impl(Asset, Spritesheet)
|
||||||
)
|
|
||||||
|
|
||||||
impl_Asset_for(Spritesheet,
|
|
||||||
spritesheet_get_asset_id,
|
|
||||||
spritesheet_set_asset_id
|
|
||||||
)
|
|
||||||
|
|
||||||
#endif // !_fencer_spritesheet_h
|
#endif // !_fencer_spritesheet_h
|
27
core/src/state.h
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
#ifndef _fencer_state_h
|
||||||
|
#define _fencer_state_h
|
||||||
|
|
||||||
|
#include "typeclass_helpers.h"
|
||||||
|
|
||||||
|
typedef struct State State;
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
void (*const enter)(void* data);
|
||||||
|
void (*const exit)(void* data);
|
||||||
|
const State* (*const update)(void* data, float dt);
|
||||||
|
};
|
||||||
|
|
||||||
|
#define DefineState(_StateName, _DataType, enter_fn, update_fn, exit_fn)\
|
||||||
|
static inline const State* _StateName() {\
|
||||||
|
TC_FN_TYPECHECK(void, enter_fn, _DataType*);\
|
||||||
|
TC_FN_TYPECHECK(const State*, update_fn, _DataType*, float);\
|
||||||
|
TC_FN_TYPECHECK(void, exit_fn, _DataType*);\
|
||||||
|
static const State instance = {\
|
||||||
|
.enter = (void(*const)(void*)) enter_fn,\
|
||||||
|
.update = (const State*(*const)(void*, float)) update_fn,\
|
||||||
|
.exit = (void(*const)(void*)) exit_fn,\
|
||||||
|
};\
|
||||||
|
return &instance;\
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // !_fencer_state_h
|
41
core/src/state_machine.c
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
#include "state_machine.h"
|
||||||
|
#include "stdlib.h"
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
struct StateMachine {
|
||||||
|
const State* current_state;
|
||||||
|
void* data;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline
|
||||||
|
void internal_state_machine_set_state(StateMachine* self, const State* state) {
|
||||||
|
self->current_state->exit(self->data);
|
||||||
|
self->current_state = state;
|
||||||
|
self->current_state->enter(self->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
StateMachine* state_machine_init(void* data, const State* start_state) {
|
||||||
|
StateMachine* self = malloc(sizeof(StateMachine));
|
||||||
|
ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for StateMachine instance");
|
||||||
|
*self = (StateMachine){
|
||||||
|
.current_state = start_state,
|
||||||
|
.data = data
|
||||||
|
};
|
||||||
|
|
||||||
|
self->current_state->enter(self->data);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
void state_machine_destroy(StateMachine* self) {
|
||||||
|
free(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
void state_machine_update(StateMachine* self, float dt) {
|
||||||
|
const State* next = self->current_state->update(self->data, dt);
|
||||||
|
if(next != self->current_state)
|
||||||
|
internal_state_machine_set_state(self, next);
|
||||||
|
}
|
||||||
|
|
||||||
|
const State* state_machine_get_current_state(StateMachine* self) {
|
||||||
|
return self->current_state;
|
||||||
|
}
|
15
core/src/state_machine.h
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#ifndef _fencer_state_machine_h
|
||||||
|
#define _fencer_state_machine_h
|
||||||
|
|
||||||
|
#include "state.h"
|
||||||
|
|
||||||
|
typedef struct StateMachine StateMachine;
|
||||||
|
|
||||||
|
extern StateMachine* state_machine_init(void* data, const State* start_state);
|
||||||
|
extern void state_machine_destroy(StateMachine* self);
|
||||||
|
|
||||||
|
extern void state_machine_update(StateMachine* self, float dt);
|
||||||
|
|
||||||
|
extern const State* state_machine_get_current_state(StateMachine* self);
|
||||||
|
|
||||||
|
#endif // !_fencer_state_machine_h
|
5
core/src/transform.c
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#include "transform.h"
|
||||||
|
|
||||||
|
impl_Transformable_for(Transform,
|
||||||
|
transform_get_transform
|
||||||
|
)
|
|
@ -1,6 +1,7 @@
|
||||||
#ifndef _fencer_transform_h
|
#ifndef _fencer_transform_h
|
||||||
#define _fencer_transform_h
|
#define _fencer_transform_h
|
||||||
|
|
||||||
|
#include "typeclass_helpers.h"
|
||||||
#include "vmath.h"
|
#include "vmath.h"
|
||||||
#include "transformable.h"
|
#include "transformable.h"
|
||||||
|
|
||||||
|
@ -56,8 +57,6 @@ Transform* transform_get_transform(Transform* self) {
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_Transformable_for(Transform,
|
decl_typeclass_impl(Transformable, Transform)
|
||||||
transform_get_transform
|
|
||||||
);
|
|
||||||
|
|
||||||
#endif // !_fencer_transform_h
|
#endif // !_fencer_transform_h
|
|
@ -26,7 +26,7 @@ extern void transformable_set_rotation(Transformable self, float rotation);
|
||||||
extern void transformable_rotate(Transformable self, float delta);
|
extern void transformable_rotate(Transformable self, float delta);
|
||||||
|
|
||||||
#define impl_Transformable_for(T, get_transform_f)\
|
#define impl_Transformable_for(T, get_transform_f)\
|
||||||
static inline Transformable T##_as_Transformable(T* x) {\
|
Transformable T##_as_Transformable(T* x) {\
|
||||||
TC_FN_TYPECHECK(Transform*, get_transform_f, T*);\
|
TC_FN_TYPECHECK(Transform*, get_transform_f, T*);\
|
||||||
static ITransformable const tc = {\
|
static ITransformable const tc = {\
|
||||||
.get_transform = (Transform*(*const)(void*)) get_transform_f\
|
.get_transform = (Transform*(*const)(void*)) get_transform_f\
|
1
core/src/utils
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit f84e3a3a27d626405173bd84055c299969cf7033
|
|
@ -1,26 +0,0 @@
|
||||||
# Mechanics
|
|
||||||
|
|
||||||
## Movement
|
|
||||||
|
|
||||||
Slow and considered.
|
|
||||||
The walking speed is slow, the running speed slightly less so.
|
|
||||||
Walkable parts of environments are relatively small.
|
|
||||||
|
|
||||||
## Combat
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Exploration
|
|
||||||
|
|
||||||
Resources are scattered throughout the environment.
|
|
||||||
It should be rewarding to stop and gather them.
|
|
||||||
|
|
||||||
## Upgrades
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Dynamics
|
|
||||||
|
|
||||||
## Careful Progress
|
|
||||||
|
|
||||||
The player should be careful and considered during second to second gameplay.
|
|
52
game/Build-Game.lua
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
project "Game"
|
||||||
|
kind "WindowedApp"
|
||||||
|
language "C"
|
||||||
|
staticruntime "Off"
|
||||||
|
targetdir "bin/%{cfg.buildcfg}"
|
||||||
|
debugdir "."
|
||||||
|
|
||||||
|
defines { "VMATH_SDL" }
|
||||||
|
|
||||||
|
files { "src/**.c", "src/**.h" }
|
||||||
|
includedirs {
|
||||||
|
"src/",
|
||||||
|
"../core/src/",
|
||||||
|
"../core/src/utils"
|
||||||
|
}
|
||||||
|
links {
|
||||||
|
"Engine-Core"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildinputs { "assets/**" }
|
||||||
|
|
||||||
|
postbuildcommands {
|
||||||
|
"{RMDIR} %{cfg.targetdir}/assets/",
|
||||||
|
"{COPYDIR} assets/ %{cfg.targetdir}/assets/"
|
||||||
|
}
|
||||||
|
|
||||||
|
targetdir ("../bin/" .. OutputDir .. "/%{prj.name}" )
|
||||||
|
objdir ("../intermediate/" .. OutputDir .. "/%{prj.name}" )
|
||||||
|
|
||||||
|
filter "system:linux"
|
||||||
|
links { "SDL2", "SDL2_image", "SDL2_ttf", "m", "cjson"}
|
||||||
|
|
||||||
|
filter "system:windows"
|
||||||
|
linkoptions { "/ENTRY:mainCRTStartup" }
|
||||||
|
links { "../SDL2.dll", "../SDL2_image.dll" }
|
||||||
|
|
||||||
|
filter "configurations:Debug"
|
||||||
|
defines { "DEBUG" }
|
||||||
|
runtime "Debug"
|
||||||
|
symbols "On"
|
||||||
|
|
||||||
|
filter "configurations:Release"
|
||||||
|
defines { "RELEASE" }
|
||||||
|
runtime "Release"
|
||||||
|
optimize "On"
|
||||||
|
symbols "On"
|
||||||
|
|
||||||
|
filter "configurations:Dist"
|
||||||
|
defines { "DIST" }
|
||||||
|
runtime "Release"
|
||||||
|
optimize "On"
|
||||||
|
symbols "Off"
|
111
game/asset-src/bag.svg
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="300mm"
|
||||||
|
height="300mm"
|
||||||
|
viewBox="0 0 300 300"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
|
||||||
|
sodipodi:docname="bag.svg"
|
||||||
|
inkscape:export-filename="../assets/bag.png"
|
||||||
|
inkscape:export-xdpi="43.349998"
|
||||||
|
inkscape:export-ydpi="43.349998"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#999999"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:zoom="0.50116091"
|
||||||
|
inkscape:cx="426.01088"
|
||||||
|
inkscape:cy="558.70279"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false" />
|
||||||
|
<defs
|
||||||
|
id="defs1">
|
||||||
|
<inkscape:path-effect
|
||||||
|
effect="bspline"
|
||||||
|
id="path-effect1"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1.3"
|
||||||
|
weight="33.333333"
|
||||||
|
steps="2"
|
||||||
|
helper_size="0"
|
||||||
|
apply_no_weight="true"
|
||||||
|
apply_with_weight="true"
|
||||||
|
only_selected="false"
|
||||||
|
uniform="false" />
|
||||||
|
</defs>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<g
|
||||||
|
id="g3"
|
||||||
|
transform="matrix(1.1358157,0,0,1.2842045,-18.957162,-87.076589)"
|
||||||
|
style="fill:#4d4d4d;stroke-width:0.827997">
|
||||||
|
<ellipse
|
||||||
|
style="font-variation-settings:'wght' 700;fill:#4d4d4d;stroke-width:0;stroke-linejoin:round"
|
||||||
|
id="path2"
|
||||||
|
cx="148.69629"
|
||||||
|
cy="278.12759"
|
||||||
|
rx="50.073914"
|
||||||
|
ry="8.5465384" />
|
||||||
|
<ellipse
|
||||||
|
style="font-variation-settings:'wght' 700;fill:#4d4d4d;stroke-width:0;stroke-linejoin:round"
|
||||||
|
id="path2-0"
|
||||||
|
cx="148.69629"
|
||||||
|
cy="290.61612"
|
||||||
|
rx="50.073914"
|
||||||
|
ry="8.5465384" />
|
||||||
|
<rect
|
||||||
|
style="font-variation-settings:'wght' 700;fill:#4d4d4d;stroke-width:0;stroke-linejoin:round"
|
||||||
|
id="rect2"
|
||||||
|
width="101.05038"
|
||||||
|
height="11.855784"
|
||||||
|
x="98.228836"
|
||||||
|
y="277.74551"
|
||||||
|
ry="0" />
|
||||||
|
</g>
|
||||||
|
<rect
|
||||||
|
style="font-variation-settings:'wght' 700;fill:#666666;stroke-width:0;stroke-linejoin:round"
|
||||||
|
id="rect3"
|
||||||
|
width="21.228964"
|
||||||
|
height="45.109776"
|
||||||
|
x="139.38551"
|
||||||
|
y="224.49448" />
|
||||||
|
<path
|
||||||
|
style="font-variation-settings:'wght' 700;fill:#e6e6e6;stroke-width:0;stroke-linejoin:round"
|
||||||
|
d="m 146.00741,42.642737 c 11.32407,-0.09936 22.44917,-0.09936 30.09777,1.688619 7.64861,1.787978 11.82057,5.363791 13.60877,25.926294 1.7882,20.562504 1.19259,58.11013 1.49068,90.4934 0.29809,32.38327 1.48985,59.60036 0.39712,74.79869 -1.09272,15.19834 -4.46982,18.37725 -17.58207,19.76801 -13.11224,1.39077 -35.95865,0.99334 -49.46814,-0.59618 -13.5095,-1.58951 -17.68146,-4.37099 -19.46966,-16.58952 -1.7882,-12.21853 -1.19259,-33.87318 -1.39132,-66.45533 -0.19873,-32.58216 -1.19177,-76.089647 3.1e-4,-99.333718 1.19208,-23.244071 4.56918,-26.223739 12.01941,-27.812965 7.45023,-1.589226 18.97305,-1.78794 30.29713,-1.8873 z"
|
||||||
|
id="path1"
|
||||||
|
inkscape:path-effect="#path-effect1"
|
||||||
|
inkscape:original-d="m 146.20639,42.543378 c 11.12509,0 22.25019,0 33.37527,0 4.17213,3.575954 8.34409,7.151766 12.51613,10.727652 -0.59564,37.549124 -1.19125,75.09675 -1.78688,112.64512 1.19181,27.21817 2.38357,54.43526 3.57535,81.65289 -3.37723,3.17904 -6.75433,6.35795 -10.13149,9.53692 -22.84733,-0.39744 -45.69374,-0.79487 -68.54061,-1.19231 -4.17212,-2.78159 -8.34408,-5.56307 -12.51612,-8.34461 0.59564,-21.65552 1.19125,-43.31017 1.78688,-64.96525 -0.99309,-43.50923 -1.98613,-87.016717 -2.9792,-130.525075 3.37723,-2.979785 6.75433,-5.959454 10.1315,-8.939181 11.52329,-0.198723 23.04611,-0.397438 34.56917,-0.596156 z"
|
||||||
|
transform="matrix(1.1358157,0,0,1.1358157,-20.37236,-42.684204)" />
|
||||||
|
<rect
|
||||||
|
style="font-variation-settings:'wght' 700;fill:#666666;stroke-width:0;stroke-linejoin:round"
|
||||||
|
id="rect1"
|
||||||
|
width="99.513878"
|
||||||
|
height="14.216011"
|
||||||
|
x="96.948997"
|
||||||
|
y="39.785141"
|
||||||
|
ry="4.181891" />
|
||||||
|
<rect
|
||||||
|
style="font-variation-settings:'wght' 700;fill:#666666;stroke-width:0;stroke-linejoin:round"
|
||||||
|
id="rect1-8"
|
||||||
|
width="99.513878"
|
||||||
|
height="14.216011"
|
||||||
|
x="96.948997"
|
||||||
|
y="210.85724"
|
||||||
|
ry="4.181891" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.5 KiB |
1408
game/asset-src/player.svg
Normal file
After Width: | Height: | Size: 107 KiB |
1508
game/asset-src/player_v2_sketches.svg
Normal file
After Width: | Height: | Size: 87 KiB |
BIN
game/assets/Player_Air_Heavy.png
Normal file
After Width: | Height: | Size: 2 MiB |
BIN
game/assets/Player_Hurt.png
Normal file
After Width: | Height: | Size: 1 MiB |
BIN
game/assets/Player_Idle.png
Normal file
After Width: | Height: | Size: 1 MiB |
BIN
game/assets/Player_Jab_A.png
Normal file
After Width: | Height: | Size: 6 MiB |
BIN
game/assets/Player_Jab_B.png
Normal file
After Width: | Height: | Size: 3 MiB |
BIN
game/assets/Player_Jumping.png
Normal file
After Width: | Height: | Size: 1 MiB |
BIN
game/assets/Player_Kick_A.png
Normal file
After Width: | Height: | Size: 5 MiB |
BIN
game/assets/Player_Slash.png
Normal file
After Width: | Height: | Size: 5 MiB |
BIN
game/assets/Player_Slide.png
Normal file
After Width: | Height: | Size: 1 MiB |
BIN
game/assets/Player_Walk.png
Normal file
After Width: | Height: | Size: 4 MiB |
BIN
game/assets/bag.png
Normal file
After Width: | Height: | Size: 1 MiB |
7
game/assets/test.sc
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Player {
|
||||||
|
position: Vector(0, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
Enemy {
|
||||||
|
position: Vector(2, 0)
|
||||||
|
}
|
|
@ -1,9 +1,13 @@
|
||||||
#include "camera.h"
|
#include "Player.h"
|
||||||
|
#include "Enemy.h"
|
||||||
|
#include "level.h"
|
||||||
#include "program.h"
|
#include "program.h"
|
||||||
|
|
||||||
static
|
static
|
||||||
void play() {
|
void play() {
|
||||||
g_camera.fov = 40;
|
level_register_spawner("Player", SpawnPlayer);
|
||||||
|
level_register_spawner("Enemy", SpawnEnemy);
|
||||||
|
level_load_file("assets/test.sc");
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static
|
||||||
|
@ -14,7 +18,7 @@ void draw() {}
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
struct ProgramSettings config = {
|
struct ProgramSettings config = {
|
||||||
.target_fps = 240,
|
.target_fps = 0,
|
||||||
.title = "fencer",
|
.title = "fencer",
|
||||||
.view_resolution = {1920, 1080},
|
.view_resolution = {1920, 1080},
|
||||||
.on_play = &play,
|
.on_play = &play,
|
34
game/src/Damagable.h
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
#ifndef FIGHT_DAMAGABLE_H
|
||||||
|
#define FIGHT_DAMAGABLE_H
|
||||||
|
|
||||||
|
#include "typeclass_helpers.h"
|
||||||
|
|
||||||
|
#include "vmath.h"
|
||||||
|
|
||||||
|
typedef struct DamageEventData {
|
||||||
|
int damageAmount;
|
||||||
|
int knockdown;
|
||||||
|
float stun;
|
||||||
|
float knockback;
|
||||||
|
Vector origin;
|
||||||
|
} DamageEventData;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int (*const damage)(void*, DamageEventData*);
|
||||||
|
} IDamagable;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void* data;
|
||||||
|
IDamagable const* tc;
|
||||||
|
} Damagable;
|
||||||
|
|
||||||
|
#define impl_Damagable_for(T, damage_f)\
|
||||||
|
Damagable T##_as_Damagable(T* x) {\
|
||||||
|
TC_FN_TYPECHECK(int, damage_f, T*, DamageEventData*);\
|
||||||
|
static const IDamagable tc = {\
|
||||||
|
.damage = (int(*const)(void*, DamageEventData*)) damage_f,\
|
||||||
|
};\
|
||||||
|
return (Damagable){.data = x, .tc = &tc};\
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // !FIGHT_DAMAGABLE_H
|
156
game/src/Enemy.c
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
#include "Enemy.h"
|
||||||
|
#include "Layers.h"
|
||||||
|
#include "debug.h"
|
||||||
|
#include "game_world.h"
|
||||||
|
#include "physics.h"
|
||||||
|
#include "physics_world.h"
|
||||||
|
#include "program.h"
|
||||||
|
#include "sprite.h"
|
||||||
|
#include "variant.h"
|
||||||
|
|
||||||
|
START_REFLECT(Enemy);
|
||||||
|
REFLECT_TYPECLASS(Enemy, Transformable);
|
||||||
|
REFLECT_TYPECLASS(Enemy, Drop);
|
||||||
|
REFLECT_TYPECLASS(Enemy, PhysicsEntity);
|
||||||
|
REFLECT_TYPECLASS(Enemy, BehaviourEntity);
|
||||||
|
REFLECT_TYPECLASS(Enemy, Damagable);
|
||||||
|
END_REFLECT(Enemy);
|
||||||
|
|
||||||
|
impl_Transformable_for(Enemy,
|
||||||
|
EnemyGetTransform
|
||||||
|
)
|
||||||
|
|
||||||
|
impl_Drop_for(Enemy,
|
||||||
|
EnemyDestroy
|
||||||
|
)
|
||||||
|
|
||||||
|
impl_BehaviourEntity_for(Enemy,
|
||||||
|
EnemyStart,
|
||||||
|
EnemyUpdate,
|
||||||
|
EnemyDraw,
|
||||||
|
EnemyGetDepth
|
||||||
|
)
|
||||||
|
|
||||||
|
impl_PhysicsEntity_for(Enemy,
|
||||||
|
EnemyGetRigidBody,
|
||||||
|
EnemyOnCollision,
|
||||||
|
EnemyOnOverlap
|
||||||
|
)
|
||||||
|
|
||||||
|
impl_Damagable_for(Enemy,
|
||||||
|
EnemyDamage
|
||||||
|
)
|
||||||
|
|
||||||
|
Enemy* MakeEnemy() {
|
||||||
|
Enemy* self = malloc(sizeof(Enemy));
|
||||||
|
ASSERT_RETURN(self != NULL, NULL, "Failed to allocate Enemy");
|
||||||
|
*self = (Enemy){
|
||||||
|
.transform = IdentityTransform,
|
||||||
|
.behaviour = NULL,
|
||||||
|
.rigidbody = NULL,
|
||||||
|
.collider = NULL,
|
||||||
|
.hitbox = NULL,
|
||||||
|
.sprite = sprite_new_empty(),
|
||||||
|
.facing = 1,
|
||||||
|
.stun_time = 0.f,
|
||||||
|
.idleAnim = NULL,
|
||||||
|
.walkAnim = NULL,
|
||||||
|
.hurtAnim = NULL,
|
||||||
|
.currentAnimation = NULL,
|
||||||
|
.health = 15,
|
||||||
|
};
|
||||||
|
|
||||||
|
self->rigidbody = rigidbody_make(Enemy_as_PhysicsEntity(self));
|
||||||
|
// collider used for physics and movement
|
||||||
|
self->collider = collider_new(Enemy_as_PhysicsEntity(self), shape_new_square(MakeVector(0.2f, 0.05f)), 0,
|
||||||
|
PHYSICS_LAYER_DEFAULT, PHYSICS_LAYER_DEFAULT);
|
||||||
|
// hitbox used to detect damage
|
||||||
|
self->hitbox = collider_new(Enemy_as_PhysicsEntity(self), shape_new((Vector[]){
|
||||||
|
MakeVector(-0.1f, -0.9f),
|
||||||
|
MakeVector( 0.1f, -0.9f),
|
||||||
|
MakeVector( 0.1f, 0.0f),
|
||||||
|
MakeVector(-0.1f, 0.0f),
|
||||||
|
}, 4), 1, PHYSICS_LAYER_COMBAT, 0x0); // does not query
|
||||||
|
|
||||||
|
sprite_set_origin(self->sprite, MakeVector(0.45f, 0.925f));
|
||||||
|
|
||||||
|
// load and configure animations
|
||||||
|
self->idleAnim = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Idle.png", IVectorFrom(512)), 1.5f, LoopMode_Loop, NULL, 0);
|
||||||
|
self->walkAnim = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Walk.png", IVectorFrom(512)), 1.5f, LoopMode_Loop, NULL, 0);
|
||||||
|
self->hurtAnim = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Hurt.png", IVectorFrom(512)), 5.f, LoopMode_Stop, NULL, 0);
|
||||||
|
self->behaviour = state_machine_init(self, EnemyIdle());
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
BehaviourEntity SpawnEnemy(Dictionary* args) {
|
||||||
|
Variant value;
|
||||||
|
Enemy* self = MakeEnemy();
|
||||||
|
// spawn at location
|
||||||
|
if(dictionary_try_get(args, "position", &value) && value.type == Variant_Vector)
|
||||||
|
rigidbody_get_transform(self->rigidbody)->position = value.as_vector;
|
||||||
|
return Enemy_as_BehaviourEntity(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnemyStart(Enemy* self) {}
|
||||||
|
|
||||||
|
void EnemyUpdate(Enemy* self, float deltaTime) {
|
||||||
|
// apply drag
|
||||||
|
Vector velocity = vmovetowardsf(rigidbody_get_velocity(self->rigidbody), ZeroVector, 80.0f * deltaTime);
|
||||||
|
rigidbody_set_velocity(self->rigidbody, velocity);
|
||||||
|
// update state machine
|
||||||
|
state_machine_update(self->behaviour, deltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnemyDraw(Enemy* self) {
|
||||||
|
sprite_flip_horizontal(self->sprite, self->facing == -1);
|
||||||
|
animation_sprite_draw(self->currentAnimation, &self->transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnemyDestroy(Enemy* self) {
|
||||||
|
state_machine_destroy(self->behaviour);
|
||||||
|
// unregister *before* deallocating physics components
|
||||||
|
physics_world_remove_entity(Enemy_as_PhysicsEntity(self));
|
||||||
|
collider_destroy(self->collider);
|
||||||
|
rigidbody_destroy(self->rigidbody);
|
||||||
|
sprite_destroy(self->sprite);
|
||||||
|
|
||||||
|
free(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnemyOnCollision(Enemy* self, Collision collision) {}
|
||||||
|
void EnemyOnOverlap(Enemy* self, Collider* other) {}
|
||||||
|
|
||||||
|
Transform* EnemyGetTransform(Enemy* self) {
|
||||||
|
return &self->transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
RigidBody* EnemyGetRigidBody(Enemy* self) {
|
||||||
|
return self->rigidbody;
|
||||||
|
}
|
||||||
|
|
||||||
|
int EnemyDamage(Enemy* self, DamageEventData* data) {
|
||||||
|
// being stunned gives iframes
|
||||||
|
if(self->stun_time > game_time())
|
||||||
|
return 0;
|
||||||
|
// subtract damage
|
||||||
|
self->health -= data->damageAmount;
|
||||||
|
// die if health drops below zero
|
||||||
|
if(self->health <= 0) {
|
||||||
|
// TODO: swap to death state instead
|
||||||
|
game_world_destroy_entity(Enemy_as_BehaviourEntity(self));
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
// store the most recent damage event
|
||||||
|
self->last_damage = *data;
|
||||||
|
// get stunned
|
||||||
|
self->stun_time = game_time() + data->stun;
|
||||||
|
// calculate which direction the damage is coming from
|
||||||
|
const float direction = (data->origin.x - self->transform.position.x) >= 0.0f ? 1.0f : -1.0f;
|
||||||
|
// face the direction damage is coming from
|
||||||
|
self->facing = direction;
|
||||||
|
// add knockback according to damage data in the calculated direction
|
||||||
|
rigidbody_add_impulse(self->rigidbody, MakeVector(-direction * data->knockback, 0), 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
70
game/src/Enemy.h
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
#ifndef FIGHT_ENEMY_H
|
||||||
|
#define FIGHT_ENEMY_H
|
||||||
|
|
||||||
|
#include "dictionary.h"
|
||||||
|
#include "mirror.h"
|
||||||
|
#include "transform.h"
|
||||||
|
#include "state_machine.h"
|
||||||
|
#include "rigidbody.h"
|
||||||
|
#include "physics_entity.h"
|
||||||
|
#include "behaviour_entity.h"
|
||||||
|
#include "collider.h"
|
||||||
|
#include "sprite.h"
|
||||||
|
#include "animation_sprite.h"
|
||||||
|
|
||||||
|
#include "Damagable.h"
|
||||||
|
#include "EnemyStates.h"
|
||||||
|
#include "typeclass_helpers.h"
|
||||||
|
|
||||||
|
typedef struct Enemy {
|
||||||
|
Transform transform;
|
||||||
|
|
||||||
|
StateMachine* behaviour;
|
||||||
|
|
||||||
|
RigidBody* rigidbody;
|
||||||
|
Collider* collider;
|
||||||
|
Collider* hitbox;
|
||||||
|
|
||||||
|
Sprite* sprite;
|
||||||
|
|
||||||
|
int facing;
|
||||||
|
|
||||||
|
DamageEventData last_damage;
|
||||||
|
float stun_time;
|
||||||
|
|
||||||
|
AnimationSprite* idleAnim;
|
||||||
|
AnimationSprite* walkAnim;
|
||||||
|
AnimationSprite* hurtAnim;
|
||||||
|
|
||||||
|
AnimationSprite* currentAnimation;
|
||||||
|
|
||||||
|
int health;
|
||||||
|
} Enemy;
|
||||||
|
|
||||||
|
extern Enemy* MakeEnemy();
|
||||||
|
extern BehaviourEntity SpawnEnemy(Dictionary* args);
|
||||||
|
|
||||||
|
extern void EnemyStart(Enemy* self);
|
||||||
|
extern void EnemyUpdate(Enemy* self, float deltaTime);
|
||||||
|
extern void EnemyDestroy(Enemy* self);
|
||||||
|
|
||||||
|
extern void EnemyDraw(Enemy* self);
|
||||||
|
|
||||||
|
extern void EnemyOnCollision(Enemy* self, Collision collision);
|
||||||
|
extern void EnemyOnOverlap(Enemy* self, Collider* other);
|
||||||
|
|
||||||
|
extern Transform* EnemyGetTransform(Enemy* self);
|
||||||
|
extern RigidBody* EnemyGetRigidBody(Enemy* self);
|
||||||
|
static long EnemyGetDepth(Enemy* self) { return (long)(-self->transform.position.y * 1000); }
|
||||||
|
|
||||||
|
extern int EnemyDamage(Enemy* self, DamageEventData* data);
|
||||||
|
|
||||||
|
DECL_REFLECT(Enemy)
|
||||||
|
|
||||||
|
decl_typeclass_impl(Transformable, Enemy)
|
||||||
|
decl_typeclass_impl(Drop, Enemy)
|
||||||
|
decl_typeclass_impl(BehaviourEntity, Enemy)
|
||||||
|
decl_typeclass_impl(PhysicsEntity, Enemy)
|
||||||
|
decl_typeclass_impl(Damagable, Enemy)
|
||||||
|
|
||||||
|
#endif // !FIGHT_ENEMY_H
|
40
game/src/EnemyStates.c
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
#include "EnemyStates.h"
|
||||||
|
#include "Enemy.h"
|
||||||
|
#include "animation_sprite.h"
|
||||||
|
#include "program.h"
|
||||||
|
|
||||||
|
void EnemyState_Exit(Enemy* self) {}
|
||||||
|
|
||||||
|
void EnemyIdle_Enter(Enemy* self) {
|
||||||
|
self->currentAnimation = self->idleAnim;
|
||||||
|
animation_sprite_play_from(self->currentAnimation, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
const State* EnemyIdle_Update(Enemy* self, float deltaTime) {
|
||||||
|
// state transitions
|
||||||
|
if(self->stun_time >= game_time())
|
||||||
|
return EnemyHurt();
|
||||||
|
return EnemyIdle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnemyWalk_Enter(Enemy* self) {
|
||||||
|
self->currentAnimation = self->walkAnim;
|
||||||
|
animation_sprite_play_from(self->currentAnimation, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
const State* EnemyWalk_Update(Enemy* self, float deltaTime) {
|
||||||
|
return EnemyWalk();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnemyHurt_Enter(Enemy* self) {
|
||||||
|
self->currentAnimation = self->hurtAnim;
|
||||||
|
animation_sprite_play_from(self->currentAnimation, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
const State* EnemyHurt_Update(Enemy* self, float deltaTime) {
|
||||||
|
const float time = animation_sprite_get_time(self->currentAnimation);
|
||||||
|
// state transitions
|
||||||
|
if(self->stun_time < game_time())
|
||||||
|
return EnemyIdle();
|
||||||
|
return EnemyHurt();
|
||||||
|
}
|
37
game/src/EnemyStates.h
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#ifndef FIGHT_ENEMY_STATES_H
|
||||||
|
#define FIGHT_ENEMY_STATES_H
|
||||||
|
|
||||||
|
#include "state.h"
|
||||||
|
|
||||||
|
typedef struct Enemy Enemy;
|
||||||
|
|
||||||
|
extern void EnemyState_Exit(Enemy* self);
|
||||||
|
|
||||||
|
extern void EnemyIdle_Enter(Enemy* self);
|
||||||
|
extern const State* EnemyIdle_Update(Enemy* self, float deltaTime);
|
||||||
|
|
||||||
|
DefineState(EnemyIdle, Enemy,
|
||||||
|
EnemyIdle_Enter,
|
||||||
|
EnemyIdle_Update,
|
||||||
|
EnemyState_Exit
|
||||||
|
)
|
||||||
|
|
||||||
|
extern void EnemyWalk_Enter(Enemy* self);
|
||||||
|
extern const State* EnemyWalk_Update(Enemy* self, float deltaTime);
|
||||||
|
|
||||||
|
DefineState(EnemyWalk, Enemy,
|
||||||
|
EnemyWalk_Enter,
|
||||||
|
EnemyWalk_Update,
|
||||||
|
EnemyState_Exit
|
||||||
|
)
|
||||||
|
|
||||||
|
extern void EnemyHurt_Enter(Enemy* self);
|
||||||
|
extern const State* EnemyHurt_Update(Enemy* self, float deltaTime);
|
||||||
|
|
||||||
|
DefineState(EnemyHurt, Enemy,
|
||||||
|
EnemyHurt_Enter,
|
||||||
|
EnemyHurt_Update,
|
||||||
|
EnemyState_Exit
|
||||||
|
)
|
||||||
|
|
||||||
|
#endif // !FIGHT_ENEMY_STATES_H
|
40
game/src/Hurtbox.c
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
#include "Hurtbox.h"
|
||||||
|
#include "Damagable.h"
|
||||||
|
#include "transformable.h"
|
||||||
|
#include "transform.h"
|
||||||
|
#include "physics_entity.h"
|
||||||
|
#include "physics_world.h"
|
||||||
|
#include "collider.h"
|
||||||
|
#include "Layers.h"
|
||||||
|
|
||||||
|
static inline
|
||||||
|
void Internal_HurtboxDealDamage(const Hurtbox* self, List* colliders, float min_y, float max_y) {
|
||||||
|
DamageEventData data = self->damage;
|
||||||
|
list_foreach(Collider**, collider, colliders) {
|
||||||
|
PhysicsEntity entity = collider_get_owner(*collider);
|
||||||
|
const IDamagable* damagable = mirror_get_typeclass(entity.data, entity.mirror, "Damagable");
|
||||||
|
const Transform* transform = entity.transformable->get_transform(entity.data);
|
||||||
|
if(damagable && transform->position.y >= min_y && transform->position.y <= max_y) damagable->damage(entity.data, &data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int HurtboxCast(Hurtbox* self, float facing_dir, List *out) {
|
||||||
|
const IPhysicsEntity* physics_entity = mirror_get_typeclass(self->owner.data, self->owner.mirror, "PhysicsEntity");
|
||||||
|
const ITransformable* transformable = mirror_get_typeclass(self->owner.data, self->owner.mirror, "Transformable");
|
||||||
|
|
||||||
|
if(physics_entity == NULL || transformable == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
RigidBody* body = physics_entity->get_rigidbody(self->owner.data);
|
||||||
|
const Transform* transform = transformable->get_transform(self->owner.data);
|
||||||
|
self->damage.origin = transform->position;
|
||||||
|
|
||||||
|
const Vector offset = vaddf(transform->position, MakeVector(facing_dir * self->offset.x, self->offset.y));
|
||||||
|
List found = physics_world_box_query_all(offset, self->size, PHYSICS_LAYER_COMBAT, body);
|
||||||
|
Internal_HurtboxDealDamage(self, &found, transform->position.y - self->depth_extent, transform->position.y + self->depth_extent);
|
||||||
|
if(out == NULL)
|
||||||
|
list_empty(&found);
|
||||||
|
else
|
||||||
|
*out = found;
|
||||||
|
return found.len;
|
||||||
|
}
|
18
game/src/Hurtbox.h
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#ifndef FIGHT_HURTBOX_H
|
||||||
|
#define FIGHT_HURTBOX_H
|
||||||
|
|
||||||
|
#include "Damagable.h"
|
||||||
|
#include "behaviour_entity.h"
|
||||||
|
#include "vmath.h"
|
||||||
|
|
||||||
|
typedef struct Hurtbox {
|
||||||
|
BehaviourEntity owner;
|
||||||
|
DamageEventData damage;
|
||||||
|
Vector size;
|
||||||
|
Vector offset;
|
||||||
|
float depth_extent;
|
||||||
|
} Hurtbox;
|
||||||
|
|
||||||
|
extern int HurtboxCast(Hurtbox* self, float facing_dir, List *out);
|
||||||
|
|
||||||
|
#endif // !FIGHT_HURTBOX_H
|
7
game/src/Layers.h
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#ifndef FIGHT_LAYERS_H
|
||||||
|
#define FIGHT_LAYERS_H
|
||||||
|
|
||||||
|
#define PHYSICS_LAYER_CHARACTERS 0x2
|
||||||
|
#define PHYSICS_LAYER_COMBAT 0x3
|
||||||
|
|
||||||
|
#endif // !FIGHT_LAYERS_H
|
272
game/src/Player.c
Normal file
|
@ -0,0 +1,272 @@
|
||||||
|
#include "Player.h"
|
||||||
|
#include "debug.h"
|
||||||
|
#include "dictionary.h"
|
||||||
|
#include "input_axis.h"
|
||||||
|
#include "physics_world.h"
|
||||||
|
|
||||||
|
#include "PlayerStates.h"
|
||||||
|
#include "Layers.h"
|
||||||
|
#include "program.h"
|
||||||
|
#include "variant.h"
|
||||||
|
|
||||||
|
const Vector PLAYER_SPEED = { 1.0f, 0.50f };
|
||||||
|
static const float PLAYER_INPUT_RATE = 1.f/15.f;
|
||||||
|
|
||||||
|
START_REFLECT(Player);
|
||||||
|
REFLECT_TYPECLASS(Player, Drop);
|
||||||
|
REFLECT_TYPECLASS(Player, PhysicsEntity);
|
||||||
|
REFLECT_TYPECLASS(Player, BehaviourEntity);
|
||||||
|
REFLECT_TYPECLASS(Player, Transformable);
|
||||||
|
END_REFLECT(Player);
|
||||||
|
|
||||||
|
impl_Drop_for(Player,
|
||||||
|
DestroyPlayer
|
||||||
|
)
|
||||||
|
|
||||||
|
impl_BehaviourEntity_for(Player,
|
||||||
|
PlayerStart,
|
||||||
|
PlayerUpdate,
|
||||||
|
PlayerDraw,
|
||||||
|
PlayerGetDepth
|
||||||
|
)
|
||||||
|
|
||||||
|
impl_Transformable_for(Player,
|
||||||
|
PlayerGetTransform
|
||||||
|
)
|
||||||
|
|
||||||
|
impl_PhysicsEntity_for(Player,
|
||||||
|
PlayerGetRigidBody,
|
||||||
|
PlayerOnCollision,
|
||||||
|
PlayerOnOverlap
|
||||||
|
)
|
||||||
|
|
||||||
|
static inline
|
||||||
|
void Internal_PlayerInitInput(Player* self) {
|
||||||
|
playerinput_add(self->playerInput,
|
||||||
|
CompositeAxis1D_as_InputAxis(compositeaxis1d_from_keys(SDL_SCANCODE_A, SDL_SCANCODE_D)),
|
||||||
|
(InputDelegateFn)PlayerHorizontalInput);
|
||||||
|
playerinput_add(self->playerInput,
|
||||||
|
CompositeAxis1D_as_InputAxis(compositeaxis1d_from_keys(SDL_SCANCODE_S, SDL_SCANCODE_W)),
|
||||||
|
(InputDelegateFn)PlayerVerticalInput);
|
||||||
|
playerinput_add(self->playerInput,
|
||||||
|
KeyBind_as_InputAxis(keybind_new(SDL_SCANCODE_SPACE)),
|
||||||
|
(InputDelegateFn)PlayerJumpInput);
|
||||||
|
playerinput_add(self->playerInput,
|
||||||
|
KeyBind_as_InputAxis(keybind_new(SDL_SCANCODE_J)),
|
||||||
|
(InputDelegateFn)PlayerLightAttackInput);
|
||||||
|
playerinput_add(self->playerInput,
|
||||||
|
KeyBind_as_InputAxis(keybind_new(SDL_SCANCODE_K)),
|
||||||
|
(InputDelegateFn)PlayerHeavyAttackInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
Player* MakePlayer() {
|
||||||
|
Player* self = malloc(sizeof(Player));
|
||||||
|
ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for new Player instance");
|
||||||
|
|
||||||
|
*self = (Player) {
|
||||||
|
.transform = IdentityTransform,
|
||||||
|
.height = 0.f,
|
||||||
|
|
||||||
|
.rigidbody = NULL,
|
||||||
|
.physicsCollider = NULL,
|
||||||
|
.hitbox = NULL,
|
||||||
|
|
||||||
|
.verticalVelocity = 0.f,
|
||||||
|
|
||||||
|
.playerInput = playerinput_new(self, -1),
|
||||||
|
.moveInput = ZeroVector,
|
||||||
|
.attackInput = 0,
|
||||||
|
.animationTriggers = 0,
|
||||||
|
|
||||||
|
.facing = 1,
|
||||||
|
|
||||||
|
.sprite = sprite_new_empty(),
|
||||||
|
|
||||||
|
.idle = NULL,
|
||||||
|
.walk = NULL,
|
||||||
|
.jump = NULL,
|
||||||
|
.jab_a = NULL,
|
||||||
|
.jab_b = NULL,
|
||||||
|
.kick_a = NULL,
|
||||||
|
.slash = NULL,
|
||||||
|
.air_heavy = NULL,
|
||||||
|
.slide = NULL,
|
||||||
|
|
||||||
|
.animationStateMachine = NULL,
|
||||||
|
.boxes = malloc(sizeof(Hurtbox) * 3),
|
||||||
|
|
||||||
|
.pushInputTimer = 0.f,
|
||||||
|
.inputLog = list_from_type(PlayerInputFrame),
|
||||||
|
};
|
||||||
|
|
||||||
|
self->rigidbody = rigidbody_make(Player_as_PhysicsEntity(self));
|
||||||
|
// physics collider used for movement
|
||||||
|
self->physicsCollider = collider_new(Player_as_PhysicsEntity(self), shape_new((Vector[]){
|
||||||
|
MakeVector(-0.2f, -0.065f),
|
||||||
|
MakeVector( 0.2f, -0.065f),
|
||||||
|
MakeVector( 0.2f, 0.065f),
|
||||||
|
MakeVector(-0.2f, 0.065f)
|
||||||
|
}, 4), 0, PHYSICS_LAYER_CHARACTERS, PHYSICS_LAYER_DEFAULT);
|
||||||
|
// hitbox is used for combat only
|
||||||
|
self->hitbox = collider_new(Player_as_PhysicsEntity(self), shape_new((Vector[]){
|
||||||
|
MakeVector(-0.1f, -0.9f),
|
||||||
|
MakeVector( 0.1f, -0.9f),
|
||||||
|
MakeVector( 0.1f, 0.00f),
|
||||||
|
MakeVector(-0.1f, 0.00f)
|
||||||
|
}, 4), 1, PHYSICS_LAYER_COMBAT, 0x0); // empty mask and overlap means this does not detect collisions itself
|
||||||
|
|
||||||
|
sprite_set_origin(self->sprite, MakeVector(0.45f, 0.925f));
|
||||||
|
|
||||||
|
// load and configure animations
|
||||||
|
self->idle = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Idle.png", IVectorFrom(512)), 1.5f, LoopMode_Loop, NULL, 0);
|
||||||
|
self->walk = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Walk.png", IVectorFrom(512)), 5.f, LoopMode_Loop, NULL, 0);
|
||||||
|
self->jump = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Jumping.png", IVectorFrom(512)), 1.f, LoopMode_Stop, NULL, 0);
|
||||||
|
self->jab_a = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Jab_A.png", IVectorFrom(512)), 10.f, LoopMode_Stop, NULL, 0);
|
||||||
|
self->jab_b = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Jab_B.png", IVectorFrom(512)), 10.f, LoopMode_Stop, NULL, 0);
|
||||||
|
self->kick_a = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Kick_A.png", IVectorFrom(512)), 12.f, LoopMode_Stop, NULL, 0);
|
||||||
|
self->slash = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Slash.png", IVectorFrom(512)), 12.f, LoopMode_Stop, NULL, 0);
|
||||||
|
self->air_heavy = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Air_Heavy.png", IVectorFrom(512)), 10.f, LoopMode_Stop, NULL, 0);
|
||||||
|
self->slide = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Slide.png", IVectorFrom(512)), 1.f, LoopMode_Loop, NULL, 0);
|
||||||
|
|
||||||
|
self->animationStateMachine = state_machine_init(self, PlayerIdle());
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
BehaviourEntity SpawnPlayer(Dictionary* args) {
|
||||||
|
Variant arg;
|
||||||
|
Player* self = MakePlayer();
|
||||||
|
Internal_PlayerInitInput(self);
|
||||||
|
if(dictionary_try_get(args, "position", &arg) && arg.type == Variant_Vector)
|
||||||
|
self->transform.position = arg.as_vector;
|
||||||
|
return Player_as_BehaviourEntity(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DestroyPlayer(Player* self) {
|
||||||
|
// deregister and free physics components
|
||||||
|
physics_world_remove_entity(Player_as_PhysicsEntity(self));
|
||||||
|
collider_destroy(self->physicsCollider);
|
||||||
|
rigidbody_destroy(self->rigidbody);
|
||||||
|
|
||||||
|
playerinput_drop(self->playerInput);
|
||||||
|
|
||||||
|
// erase animations
|
||||||
|
animation_sprite_destroy(self->idle);
|
||||||
|
animation_sprite_destroy(self->walk);
|
||||||
|
animation_sprite_destroy(self->jump);
|
||||||
|
animation_sprite_destroy(self->jab_a);
|
||||||
|
animation_sprite_destroy(self->jab_b);
|
||||||
|
animation_sprite_destroy(self->kick_a);
|
||||||
|
animation_sprite_destroy(self->air_heavy);
|
||||||
|
animation_sprite_destroy(self->slide);
|
||||||
|
sprite_destroy(self->sprite);
|
||||||
|
|
||||||
|
state_machine_destroy(self->animationStateMachine);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerStart(Player* self) {
|
||||||
|
animation_sprite_play_from(self->currentAnimation, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void PlayerPushInput(Player* self) {
|
||||||
|
// the current input state
|
||||||
|
PlayerInputFrame state = {
|
||||||
|
.time = game_time(),
|
||||||
|
.direction = self->moveInput,
|
||||||
|
.light = self->attackInput == 1,
|
||||||
|
.heavy = self->attackInput == 2,
|
||||||
|
.jump = self->jumpInput,
|
||||||
|
.facing = self->facing,
|
||||||
|
};
|
||||||
|
// push onto the end of the log
|
||||||
|
list_add(&self->inputLog, &state);
|
||||||
|
// erase oldest input from the log
|
||||||
|
if(self->inputLog.len >= 5)
|
||||||
|
list_erase(&self->inputLog, 0);
|
||||||
|
// log current input state
|
||||||
|
LOG_INFO("%f %f, L:%x, H:%x, J:%x",
|
||||||
|
state.direction.x, state.direction.y,
|
||||||
|
state.light, state.heavy, state.jump);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerUpdate(Player* self, float deltaTime) {
|
||||||
|
// update state machine
|
||||||
|
state_machine_update(self->animationStateMachine, deltaTime);
|
||||||
|
// update gravity
|
||||||
|
self->height += self->verticalVelocity * deltaTime;
|
||||||
|
if(self->height <= 0.f)
|
||||||
|
self->height = 0.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerDraw(Player* self) {
|
||||||
|
// create a new transform that adjusts the "ground" transform to reflect distance from the ground as well
|
||||||
|
Transform trans = self->transform;
|
||||||
|
trans.position.y -= self->height;
|
||||||
|
animation_sprite_draw(self->currentAnimation, &trans);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerHorizontalInput(Player* self, InputEvent value) {
|
||||||
|
self->moveInput.x = value.as_float;
|
||||||
|
PlayerPushInput(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerVerticalInput(Player* self, InputEvent value) {
|
||||||
|
self->moveInput.y = -value.as_float;
|
||||||
|
PlayerPushInput(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerJumpInput(Player* self, InputEvent value) {
|
||||||
|
if(value.as_bool) {
|
||||||
|
self->jumpInput = value.as_bool;
|
||||||
|
PlayerPushInput(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerLightAttackInput(Player* self, InputEvent value) {
|
||||||
|
if(value.as_bool) {
|
||||||
|
self->attackInput = 1;
|
||||||
|
PlayerPushInput(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerHeavyAttackInput(Player* self, InputEvent value) {
|
||||||
|
if(value.as_bool) {
|
||||||
|
self->attackInput = 2;
|
||||||
|
PlayerPushInput(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerOnCollision(Player* self, Collision collision) {}
|
||||||
|
void PlayerOnOverlap(Player* self, Collider* other) {}
|
||||||
|
|
||||||
|
Transform* PlayerGetTransform(Player* self) {
|
||||||
|
return &self->transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
RigidBody* PlayerGetRigidBody(Player* self) {
|
||||||
|
return self->rigidbody;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerInputFrame* PlayerInputHistory(Player* self) {
|
||||||
|
return (PlayerInputFrame*)self->inputLog.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
int PlayerInputIsQuarterCircleForward(Player* self) {
|
||||||
|
// at least four inputs have to be logged for a valid quartercircle
|
||||||
|
// (three directional, one button, in that order)
|
||||||
|
if(self->inputLog.len < 4)
|
||||||
|
return 0;
|
||||||
|
PlayerInputFrame* history = PlayerInputHistory(self);
|
||||||
|
// the most recent input (assumed to be a button input)
|
||||||
|
const size_t last = self->inputLog.len-1;
|
||||||
|
// the forward direction at the assumed start of the action
|
||||||
|
const float forward = history[last-3].facing;
|
||||||
|
if(game_time() - history[last-2].time > 0.225f)
|
||||||
|
return 0;
|
||||||
|
// check if the three inputs before the most recent input are a quartercircle towards the facing direction at last-3 (three before current)
|
||||||
|
// current (history[last]) is assumed to be a button input
|
||||||
|
return veqf(history[last-3].direction, MakeVector(0.f, 1.f))
|
||||||
|
&& veqf(history[last-2].direction, MakeVector(forward, 1.f))
|
||||||
|
&& veqf(history[last-1].direction, MakeVector(forward, 0.f));
|
||||||
|
}
|
100
game/src/Player.h
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
#ifndef FIGHT_PLAYER_H
|
||||||
|
#define FIGHT_PLAYER_H
|
||||||
|
|
||||||
|
#include "Hurtbox.h"
|
||||||
|
#include "dictionary.h"
|
||||||
|
#include "state_machine.h"
|
||||||
|
#include "mirror.h"
|
||||||
|
#include "behaviour_entity.h"
|
||||||
|
#include "animation_sprite.h"
|
||||||
|
#include "vmath.h"
|
||||||
|
#include "transform.h"
|
||||||
|
#include "player_input.h"
|
||||||
|
#include "rigidbody.h"
|
||||||
|
#include "collider.h"
|
||||||
|
#include "typeclass_helpers.h"
|
||||||
|
|
||||||
|
extern const Vector PLAYER_SPEED;
|
||||||
|
|
||||||
|
typedef struct PlayerInputFrame {
|
||||||
|
float time;
|
||||||
|
Vector direction;
|
||||||
|
int light;
|
||||||
|
int heavy;
|
||||||
|
int jump;
|
||||||
|
float facing;
|
||||||
|
} PlayerInputFrame;
|
||||||
|
|
||||||
|
typedef struct Player {
|
||||||
|
Transform transform;
|
||||||
|
float height;
|
||||||
|
|
||||||
|
RigidBody* rigidbody;
|
||||||
|
Collider* physicsCollider;
|
||||||
|
Collider* hitbox;
|
||||||
|
|
||||||
|
float verticalVelocity;
|
||||||
|
|
||||||
|
PlayerInput* playerInput;
|
||||||
|
|
||||||
|
Vector moveInput;
|
||||||
|
int attackInput;
|
||||||
|
int jumpInput;
|
||||||
|
size_t animationTriggers;
|
||||||
|
|
||||||
|
int facing;
|
||||||
|
|
||||||
|
Sprite* sprite;
|
||||||
|
|
||||||
|
AnimationSprite* idle;
|
||||||
|
AnimationSprite* walk;
|
||||||
|
AnimationSprite* jump;
|
||||||
|
AnimationSprite* jab_a;
|
||||||
|
AnimationSprite* jab_b;
|
||||||
|
AnimationSprite* kick_a;
|
||||||
|
AnimationSprite* slash;
|
||||||
|
AnimationSprite* air_heavy;
|
||||||
|
AnimationSprite* slide;
|
||||||
|
|
||||||
|
StateMachine* animationStateMachine;
|
||||||
|
Hurtbox *boxes;
|
||||||
|
|
||||||
|
AnimationSprite* currentAnimation;
|
||||||
|
float pushInputTimer;
|
||||||
|
List inputLog;
|
||||||
|
PlayerInputFrame nextInputFrame;
|
||||||
|
} Player;
|
||||||
|
|
||||||
|
Player* MakePlayer();
|
||||||
|
BehaviourEntity SpawnPlayer(Dictionary* args);
|
||||||
|
void DestroyPlayer(Player* self);
|
||||||
|
|
||||||
|
void PlayerStart(Player* self);
|
||||||
|
void PlayerUpdate(Player* self, float deltaTime);
|
||||||
|
void PlayerDraw(Player* self);
|
||||||
|
|
||||||
|
void PlayerHorizontalInput(Player* self, InputEvent value);
|
||||||
|
void PlayerVerticalInput(Player* self, InputEvent value);
|
||||||
|
void PlayerJumpInput(Player* self, InputEvent value);
|
||||||
|
void PlayerLightAttackInput(Player* self, InputEvent value);
|
||||||
|
void PlayerHeavyAttackInput(Player* self, InputEvent value);
|
||||||
|
|
||||||
|
void PlayerOnCollision(Player* self, Collision collision);
|
||||||
|
void PlayerOnOverlap(Player* self, Collider* other);
|
||||||
|
|
||||||
|
Transform* PlayerGetTransform(Player* self);
|
||||||
|
RigidBody* PlayerGetRigidBody(Player* self);
|
||||||
|
|
||||||
|
PlayerInputFrame* PlayerInputHistory(Player* self);
|
||||||
|
|
||||||
|
int PlayerInputIsQuarterCircleForward(Player* self);
|
||||||
|
|
||||||
|
static long PlayerGetDepth(Player* self) { return (int)(-10-self->transform.position.y * 1000); }
|
||||||
|
|
||||||
|
DECL_REFLECT(Player);
|
||||||
|
decl_typeclass_impl(BehaviourEntity, Player)
|
||||||
|
decl_typeclass_impl(Drop, Player)
|
||||||
|
decl_typeclass_impl(Transformable, Player)
|
||||||
|
decl_typeclass_impl(PhysicsEntity, Player)
|
||||||
|
|
||||||
|
#endif // !FIGHT_PLAYER_H
|
262
game/src/PlayerStates.c
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
#include "PlayerStates.h"
|
||||||
|
#include "Hurtbox.h"
|
||||||
|
|
||||||
|
#include "Damagable.h"
|
||||||
|
#include "Player.h"
|
||||||
|
#include "animation_sprite.h"
|
||||||
|
|
||||||
|
// flip the facing direction of the player based on input
|
||||||
|
// does not do movement
|
||||||
|
static inline
|
||||||
|
void InternalSpriteFlipWithMovement(Player* self) {
|
||||||
|
if(self->moveInput.x > 0.f)
|
||||||
|
self->facing = 1;
|
||||||
|
if(self->moveInput.x < -0.1f)
|
||||||
|
self->facing = -1;
|
||||||
|
sprite_flip_horizontal(self->sprite, self->facing != 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerAnimationExit(Player* self) {
|
||||||
|
self->animationTriggers = 0;
|
||||||
|
self->attackInput = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
const State* PlayerTryStartNewChain(Player* self, const State* fallback) {
|
||||||
|
if(self->attackInput == 1) {
|
||||||
|
return PlayerJabA();
|
||||||
|
}
|
||||||
|
if(self->attackInput == 2) {
|
||||||
|
if(self->height > 0.f)
|
||||||
|
return PlayerAirHeavy();
|
||||||
|
else if(PlayerInputIsQuarterCircleForward(self))
|
||||||
|
return PlayerSlide();
|
||||||
|
else
|
||||||
|
return PlayerSlash();
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerIdleEnter(Player* self) {
|
||||||
|
self->currentAnimation = self->idle;
|
||||||
|
rigidbody_set_velocity(self->rigidbody, ZeroVector);
|
||||||
|
animation_sprite_play_from(self->currentAnimation, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
const State* PlayerIdleUpdate(Player* self, float deltaTime) {
|
||||||
|
if(!veqf(self->moveInput, ZeroVector))
|
||||||
|
return PlayerWalk();
|
||||||
|
if(self->jumpInput)
|
||||||
|
return PlayerJump();
|
||||||
|
return PlayerTryStartNewChain(self, PlayerIdle());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerWalk_Enter(Player* self) {
|
||||||
|
self->currentAnimation = self->walk;
|
||||||
|
animation_sprite_play_from(self->currentAnimation, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
const State* PlayerWalk_Update(Player* self, float deltaTime) {
|
||||||
|
rigidbody_set_velocity(self->rigidbody, vmulf(vnormalizedf(self->moveInput), PLAYER_SPEED));
|
||||||
|
|
||||||
|
if(veqf(self->moveInput, ZeroVector))
|
||||||
|
return PlayerIdle();
|
||||||
|
if(self->jumpInput)
|
||||||
|
return PlayerJump();
|
||||||
|
InternalSpriteFlipWithMovement(self);
|
||||||
|
return PlayerTryStartNewChain(self, PlayerWalk());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerAttackEnter(Player* self) {
|
||||||
|
self->attackInput = 0;
|
||||||
|
rigidbody_set_velocity(self->rigidbody, ZeroVector);
|
||||||
|
InternalSpriteFlipWithMovement(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void PlayerHurtbox(Player* self, DamageEventData damage, Vector hitbox_size, Vector offset) {
|
||||||
|
Hurtbox box = {
|
||||||
|
.owner = Player_as_BehaviourEntity(self),
|
||||||
|
.damage = damage,
|
||||||
|
.size = hitbox_size,
|
||||||
|
.offset = vaddf(offset, MakeVector(0.f, self->height)),
|
||||||
|
.depth_extent = 0.15f,
|
||||||
|
};
|
||||||
|
HurtboxCast(&box, self->facing, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerJabA_Enter(Player* self) {
|
||||||
|
PlayerAttackEnter(self);
|
||||||
|
self->currentAnimation = self->jab_a;
|
||||||
|
animation_sprite_play_from(self->currentAnimation, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
const State* PlayerJabA_Update(Player* self, float deltaTime) {
|
||||||
|
const static DamageEventData damage = {
|
||||||
|
.damageAmount = 1,
|
||||||
|
.knockdown = 0,
|
||||||
|
.stun = 0.15f,
|
||||||
|
.knockback = 7.0f,
|
||||||
|
};
|
||||||
|
const float ntime = animation_sprite_get_time_normalized(self->currentAnimation);
|
||||||
|
if(self->animationTriggers == 0 && ntime > 0.33f) {
|
||||||
|
PlayerHurtbox(self, damage, MakeVector(0.1f, 0.1f), MakeVector(0.265f, -0.6f));
|
||||||
|
++self->animationTriggers;
|
||||||
|
}
|
||||||
|
if(ntime > 1.0f) {
|
||||||
|
if(self->attackInput == 1)
|
||||||
|
return PlayerJabB();
|
||||||
|
else if(self->attackInput == 2)
|
||||||
|
return PlayerKickA();
|
||||||
|
}
|
||||||
|
if(!veqf(self->moveInput, ZeroVector) && ntime > 1.05f)
|
||||||
|
return PlayerWalk();
|
||||||
|
if(ntime >= 1.5f)
|
||||||
|
return PlayerIdle();
|
||||||
|
return PlayerJabA();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerJabB_Enter(Player* self) {
|
||||||
|
PlayerAttackEnter(self);
|
||||||
|
self->currentAnimation = self->jab_b;
|
||||||
|
animation_sprite_play_from(self->currentAnimation, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
const State* PlayerJabB_Update(Player* self, float deltaTime) {
|
||||||
|
static const DamageEventData damage = {
|
||||||
|
.damageAmount = 1,
|
||||||
|
.knockdown = 0,
|
||||||
|
.stun = 0.15f,
|
||||||
|
.knockback = 7.0f,
|
||||||
|
};
|
||||||
|
const float ntime = animation_sprite_get_time_normalized(self->currentAnimation);
|
||||||
|
const size_t frame = sprite_get_tile(self->sprite);
|
||||||
|
if(self->animationTriggers == 0 && ntime > 0.33f) {
|
||||||
|
PlayerHurtbox(self, damage, MakeVector(0.13f, 0.05f), MakeVector(0.32f, -0.7f));
|
||||||
|
++self->animationTriggers;
|
||||||
|
}
|
||||||
|
if(ntime > 1.0f && self->attackInput == 1)
|
||||||
|
return PlayerKickA();
|
||||||
|
if(!veqf(self->moveInput, ZeroVector) && ntime > 1.05f)
|
||||||
|
return PlayerWalk();
|
||||||
|
if(ntime >= 2.0f)
|
||||||
|
return PlayerIdle();
|
||||||
|
return PlayerJabB();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerKickA_Enter(Player* self) {
|
||||||
|
PlayerAttackEnter(self);
|
||||||
|
self->currentAnimation = self->kick_a;
|
||||||
|
animation_sprite_play_from(self->currentAnimation, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
const State* PlayerKickA_Update(Player* self, float deltaTime) {
|
||||||
|
static const DamageEventData damage = {
|
||||||
|
.damageAmount = 2,
|
||||||
|
.knockdown = 1,
|
||||||
|
.stun = 0.5f,
|
||||||
|
.knockback = 15.0f,
|
||||||
|
};
|
||||||
|
const float ntime = animation_sprite_get_time_normalized(self->currentAnimation);
|
||||||
|
const size_t frame = sprite_get_tile(self->sprite);
|
||||||
|
if(frame >= 3 && frame <= 4) {
|
||||||
|
PlayerHurtbox(self, damage, MakeVector(0.16f, 0.06f), MakeVector(0.33f, -0.4f));
|
||||||
|
}
|
||||||
|
if(ntime >= 1.f)
|
||||||
|
return PlayerIdle();
|
||||||
|
return PlayerKickA();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerSlash_Enter(Player* self) {
|
||||||
|
PlayerAttackEnter(self);
|
||||||
|
self->currentAnimation = self->slash;
|
||||||
|
animation_sprite_play_from(self->currentAnimation, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
const State* PlayerSlash_Update(Player* self, float deltaTime) {
|
||||||
|
static const DamageEventData damage = {
|
||||||
|
.damageAmount = 2,
|
||||||
|
.knockdown = 0,
|
||||||
|
.stun = 0.5f,
|
||||||
|
.knockback = 4.f
|
||||||
|
};
|
||||||
|
const float ntime = animation_sprite_get_time_normalized(self->currentAnimation);
|
||||||
|
const size_t frame = sprite_get_tile(self->sprite);
|
||||||
|
if(frame >= 2 && self->animationTriggers == 0) {
|
||||||
|
++self->animationTriggers;
|
||||||
|
PlayerHurtbox(self, damage, MakeVector(0.4f, 0.1f), MakeVector(0.2f, -0.7f));
|
||||||
|
}
|
||||||
|
if(!veqf(self->moveInput, ZeroVector) && ntime > 1.05f)
|
||||||
|
return PlayerWalk();
|
||||||
|
if(ntime >= 1.f)
|
||||||
|
return PlayerIdle();
|
||||||
|
return PlayerSlash();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerSlide_Enter(Player* self) {
|
||||||
|
PlayerAttackEnter(self);
|
||||||
|
self->currentAnimation = self->slide;
|
||||||
|
// adjust for the downward motion of the quartercircle
|
||||||
|
Transform* trans = rigidbody_get_transform(self->rigidbody);
|
||||||
|
trans->position.y -= 0.03f;
|
||||||
|
animation_sprite_play_from(self->currentAnimation, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
const State* PlayerSlide_Update(Player* self, float deltaTime) {
|
||||||
|
static const DamageEventData damage = {
|
||||||
|
.damageAmount = 3,
|
||||||
|
.knockdown = 1,
|
||||||
|
.stun = 0.1f,
|
||||||
|
.knockback = 17.0f,
|
||||||
|
};
|
||||||
|
static const float duration = 0.2f;
|
||||||
|
static const float speed = 4.f;
|
||||||
|
const float time = animation_sprite_get_time(self->currentAnimation);
|
||||||
|
rigidbody_set_velocity(self->rigidbody, MakeVector(self->facing * speed, -0.05f));
|
||||||
|
if(time > duration)
|
||||||
|
return PlayerIdle();
|
||||||
|
if(time > 0.1f) {
|
||||||
|
PlayerHurtbox(self, damage, MakeVector(0.2f, 0.15f), MakeVector(0.21f, -0.6f));
|
||||||
|
}
|
||||||
|
return PlayerSlide();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerJump_Enter(Player* self) {
|
||||||
|
if(self->jumpInput) {
|
||||||
|
self->jumpInput = 0;
|
||||||
|
self->currentAnimation = self->jump;
|
||||||
|
self->verticalVelocity = 3.f;
|
||||||
|
}
|
||||||
|
animation_sprite_play_from(self->currentAnimation, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
const State* PlayerJump_Update(Player* self, float deltaTime) {
|
||||||
|
self->verticalVelocity -= 5.f * deltaTime;
|
||||||
|
if(self->height == 0.f) {
|
||||||
|
self->verticalVelocity = 0.f;
|
||||||
|
return PlayerIdle();
|
||||||
|
}
|
||||||
|
if(self->attackInput == 2)
|
||||||
|
return PlayerAirHeavy();
|
||||||
|
return PlayerJump();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerAirHeavy_Enter(Player* self) {
|
||||||
|
self->currentAnimation = self->air_heavy;
|
||||||
|
animation_sprite_play_from(self->currentAnimation, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
const State* PlayerAirHeavy_Update(Player* self, float deltaTime) {
|
||||||
|
const static DamageEventData damage = {
|
||||||
|
.damageAmount = 4,
|
||||||
|
.knockdown = 1,
|
||||||
|
.stun = 0.75f,
|
||||||
|
.knockback = 0.15f
|
||||||
|
};
|
||||||
|
const float ntime = animation_sprite_get_time_normalized(self->currentAnimation);
|
||||||
|
size_t frame = sprite_get_tile(self->sprite);
|
||||||
|
if(frame == 1) PlayerHurtbox(self, damage, MakeVector(0.12f, 0.12f), MakeVector(0.12f, -0.48f));
|
||||||
|
const State* result = PlayerJump_Update(self, deltaTime);
|
||||||
|
return (result == PlayerJump()) ? PlayerAirHeavy() : result;
|
||||||
|
}
|
93
game/src/PlayerStates.h
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
#ifndef FIGHT_PLAYER_STATES_H
|
||||||
|
#define FIGHT_PLAYER_STATES_H
|
||||||
|
|
||||||
|
#include "state.h"
|
||||||
|
|
||||||
|
typedef struct Player Player;
|
||||||
|
|
||||||
|
extern void PlayerAnimationExit(Player* self);
|
||||||
|
|
||||||
|
extern void PlayerIdleEnter(Player* self);
|
||||||
|
extern const State* PlayerIdleUpdate(Player* self, float deltaTime);
|
||||||
|
|
||||||
|
DefineState(PlayerIdle, Player,
|
||||||
|
PlayerIdleEnter,
|
||||||
|
PlayerIdleUpdate,
|
||||||
|
PlayerAnimationExit
|
||||||
|
)
|
||||||
|
|
||||||
|
extern void PlayerWalk_Enter(Player* self);
|
||||||
|
extern const State* PlayerWalk_Update(Player* self, float deltaTime);
|
||||||
|
|
||||||
|
DefineState(PlayerWalk, Player,
|
||||||
|
PlayerWalk_Enter,
|
||||||
|
PlayerWalk_Update,
|
||||||
|
PlayerAnimationExit
|
||||||
|
)
|
||||||
|
|
||||||
|
extern void PlayerAttackEnter(Player* self);
|
||||||
|
|
||||||
|
extern void PlayerJabA_Enter(Player* self);
|
||||||
|
extern const State* PlayerJabA_Update(Player* self, float deltaTime);
|
||||||
|
|
||||||
|
DefineState(PlayerJabA, Player,
|
||||||
|
PlayerJabA_Enter,
|
||||||
|
PlayerJabA_Update,
|
||||||
|
PlayerAnimationExit
|
||||||
|
)
|
||||||
|
|
||||||
|
extern void PlayerJabB_Enter(Player* self);
|
||||||
|
extern const State* PlayerJabB_Update(Player* self, float deltaTime);
|
||||||
|
|
||||||
|
DefineState(PlayerJabB, Player,
|
||||||
|
PlayerJabB_Enter,
|
||||||
|
PlayerJabB_Update,
|
||||||
|
PlayerAnimationExit
|
||||||
|
)
|
||||||
|
|
||||||
|
extern void PlayerKickA_Enter(Player* self);
|
||||||
|
extern const State* PlayerKickA_Update(Player* self, float deltaTime);
|
||||||
|
|
||||||
|
DefineState(PlayerKickA, Player,
|
||||||
|
PlayerKickA_Enter,
|
||||||
|
PlayerKickA_Update,
|
||||||
|
PlayerAnimationExit
|
||||||
|
)
|
||||||
|
|
||||||
|
extern void PlayerSlash_Enter(Player* self);
|
||||||
|
extern const State* PlayerSlash_Update(Player* self, float deltaTime);
|
||||||
|
|
||||||
|
DefineState(PlayerSlash, Player,
|
||||||
|
PlayerSlash_Enter,
|
||||||
|
PlayerSlash_Update,
|
||||||
|
PlayerAnimationExit
|
||||||
|
)
|
||||||
|
|
||||||
|
extern void PlayerSlide_Enter(Player* self);
|
||||||
|
extern const State* PlayerSlide_Update(Player* self, float deltaTime);
|
||||||
|
|
||||||
|
DefineState(PlayerSlide, Player,
|
||||||
|
PlayerSlide_Enter,
|
||||||
|
PlayerSlide_Update,
|
||||||
|
PlayerAnimationExit
|
||||||
|
)
|
||||||
|
|
||||||
|
extern void PlayerJump_Enter(Player *self);
|
||||||
|
extern const State* PlayerJump_Update(Player* self, float deltaTime);
|
||||||
|
|
||||||
|
DefineState(PlayerJump, Player,
|
||||||
|
PlayerJump_Enter,
|
||||||
|
PlayerJump_Update,
|
||||||
|
PlayerAnimationExit
|
||||||
|
)
|
||||||
|
|
||||||
|
extern void PlayerAirHeavy_Enter(Player* self);
|
||||||
|
extern const State* PlayerAirHeavy_Update(Player* self, float deltaTime);
|
||||||
|
|
||||||
|
DefineState(PlayerAirHeavy, Player,
|
||||||
|
PlayerAirHeavy_Enter,
|
||||||
|
PlayerAirHeavy_Update,
|
||||||
|
PlayerAnimationExit
|
||||||
|
)
|
||||||
|
|
||||||
|
#endif // !FIGHT_PLAYER_STATES_H
|
89
game/src/Prop.c
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
#include "Prop.h"
|
||||||
|
#include "debug.h"
|
||||||
|
#include "game_world.h"
|
||||||
|
#include "mirror.h"
|
||||||
|
#include "physics_world.h"
|
||||||
|
|
||||||
|
START_REFLECT(Prop);
|
||||||
|
REFLECT_TYPECLASS(Prop, Transformable);
|
||||||
|
REFLECT_TYPECLASS(Prop, Drop);
|
||||||
|
REFLECT_TYPECLASS(Prop, BehaviourEntity);
|
||||||
|
END_REFLECT(Prop);
|
||||||
|
|
||||||
|
impl_Transformable_for(Prop,
|
||||||
|
PropGetTransform
|
||||||
|
)
|
||||||
|
|
||||||
|
impl_PhysicsEntity_for(Prop,
|
||||||
|
PropGetRigidBody,
|
||||||
|
PropOnCollision,
|
||||||
|
PropOnOverlap
|
||||||
|
)
|
||||||
|
|
||||||
|
impl_Drop_for(Prop,
|
||||||
|
DestroyProp
|
||||||
|
)
|
||||||
|
|
||||||
|
impl_BehaviourEntity_for(Prop,
|
||||||
|
PropStart,
|
||||||
|
PropUpdate,
|
||||||
|
PropDraw,
|
||||||
|
PropGetDepth
|
||||||
|
)
|
||||||
|
|
||||||
|
Prop* MakeProp(Sprite* sprite, Shape* shape) {
|
||||||
|
Prop* self = malloc(sizeof(Prop));
|
||||||
|
ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for Prop instance");
|
||||||
|
*self = (Prop) {
|
||||||
|
.transform = IdentityTransform,
|
||||||
|
.sprite = sprite,
|
||||||
|
.rigidbody = NULL,
|
||||||
|
.collisionShape = NULL
|
||||||
|
};
|
||||||
|
self->rigidbody = rigidbody_make(Prop_as_PhysicsEntity(self));
|
||||||
|
self->collisionShape = collider_new(Prop_as_PhysicsEntity(self), shape, 0, PHYSICS_LAYER_DEFAULT, PHYSICS_LAYER_DEFAULT);
|
||||||
|
rigidbody_set_static(self->rigidbody, 1);
|
||||||
|
sprite_set_origin(self->sprite, MakeVector(0.5f, 1.0f));
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
Prop* SpawnProp(Vector location, Sprite* sprite, Shape* shape, Vector origin) {
|
||||||
|
Prop* self = MakeProp(sprite, shape);
|
||||||
|
|
||||||
|
self->transform.position
|
||||||
|
= rigidbody_get_transform(self->rigidbody)->position
|
||||||
|
= location;
|
||||||
|
|
||||||
|
game_world_add_entity(Prop_as_BehaviourEntity(self));
|
||||||
|
physics_world_add_entity(Prop_as_PhysicsEntity(self));
|
||||||
|
|
||||||
|
sprite_set_origin(self->sprite, origin);
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DestroyProp(Prop* self) {
|
||||||
|
sprite_destroy(self->sprite);
|
||||||
|
physics_world_remove_entity(Prop_as_PhysicsEntity(self));
|
||||||
|
collider_destroy(self->collisionShape);
|
||||||
|
rigidbody_destroy(self->rigidbody);
|
||||||
|
free(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PropStart(Prop* self) {}
|
||||||
|
void PropUpdate(Prop* self, float deltaTime) {}
|
||||||
|
|
||||||
|
void PropDraw(Prop* self) {
|
||||||
|
sprite_draw(self->sprite, self->transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PropOnCollision(Prop* self, Collision collision) {}
|
||||||
|
void PropOnOverlap(Prop* self, Collider* other) {}
|
||||||
|
|
||||||
|
Transform* PropGetTransform(Prop* self) {
|
||||||
|
return &self->transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
RigidBody* PropGetRigidBody(Prop* self) {
|
||||||
|
return self->rigidbody;
|
||||||
|
}
|
44
game/src/Prop.h
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
#ifndef FIGHT_PROP_H
|
||||||
|
#define FIGHT_PROP_H
|
||||||
|
|
||||||
|
#include "physics_entity.h"
|
||||||
|
#include "behaviour_entity.h"
|
||||||
|
#include "transformable.h"
|
||||||
|
#include "drop.h"
|
||||||
|
#include "transform.h"
|
||||||
|
#include "sprite.h"
|
||||||
|
#include "rigidbody.h"
|
||||||
|
#include "collider.h"
|
||||||
|
#include "typeclass_helpers.h"
|
||||||
|
|
||||||
|
typedef struct Prop {
|
||||||
|
Transform transform;
|
||||||
|
Sprite* sprite;
|
||||||
|
RigidBody* rigidbody;
|
||||||
|
Collider* collisionShape;
|
||||||
|
} Prop;
|
||||||
|
|
||||||
|
Prop* MakeProp(Sprite* sprite, Shape* shape);
|
||||||
|
Prop* SpawnProp(Vector location, Sprite* sprite, Shape* shape, Vector origin);
|
||||||
|
void DestroyProp(Prop* self);
|
||||||
|
|
||||||
|
void PropStart(Prop* self);
|
||||||
|
void PropUpdate(Prop* self, float deltaTime);
|
||||||
|
void PropDraw(Prop* self);
|
||||||
|
|
||||||
|
void PropOnCollision(Prop* self, Collision collision);
|
||||||
|
void PropOnOverlap(Prop* self, Collider* other);
|
||||||
|
|
||||||
|
Transform* PropGetTransform(Prop* self);
|
||||||
|
RigidBody* PropGetRigidBody(Prop* self);
|
||||||
|
|
||||||
|
static long PropGetDepth(Prop* self) { return -(int)(self->transform.position.y * 1000); }
|
||||||
|
|
||||||
|
DECL_REFLECT(Prop)
|
||||||
|
|
||||||
|
decl_typeclass_impl(Transformable, Prop)
|
||||||
|
decl_typeclass_impl(PhysicsEntity, Prop)
|
||||||
|
decl_typeclass_impl(Drop, Prop)
|
||||||
|
decl_typeclass_impl(BehaviourEntity, Prop)
|
||||||
|
|
||||||
|
#endif // !FIGHT_PROP_H
|
20
premake5.lua
|
@ -1,20 +0,0 @@
|
||||||
workspace "fencer"
|
|
||||||
configurations { "Debug", "Release" }
|
|
||||||
location "."
|
|
||||||
|
|
||||||
project "fencer"
|
|
||||||
kind "WindowedApp"
|
|
||||||
language "C"
|
|
||||||
location "build/"
|
|
||||||
files { "src/**.c" }
|
|
||||||
links { "SDL2", "SDL2_image", "cjson", "m" }
|
|
||||||
buildoptions { "-Wall", "-DVMATH_SDL=1" }
|
|
||||||
targetdir "bin/"
|
|
||||||
filter "configurations:Debug"
|
|
||||||
defines { "DEBUG" }
|
|
||||||
buildoptions { "-g3" }
|
|
||||||
symbols "On"
|
|
||||||
filter "configurations:Release"
|
|
||||||
buildoptions { "-g0" }
|
|
||||||
defines { "NDEBUG" }
|
|
||||||
optimize "On"
|
|
|
@ -1,24 +0,0 @@
|
||||||
#ifndef _fencer_collision_h
|
|
||||||
#define _fencer_collision_h
|
|
||||||
|
|
||||||
#include "shape.h"
|
|
||||||
#include "physics_entity.h"
|
|
||||||
#include <stddef.h>
|
|
||||||
|
|
||||||
typedef struct Collision {
|
|
||||||
PhysicsEntity other;
|
|
||||||
|
|
||||||
Vector point;
|
|
||||||
Vector normal;
|
|
||||||
|
|
||||||
Vector velocity;
|
|
||||||
Vector penetration_vector;
|
|
||||||
|
|
||||||
Vector edge_left;
|
|
||||||
Vector edge_right;
|
|
||||||
} Collision;
|
|
||||||
|
|
||||||
extern Collision collision_invert(Collision src, PhysicsEntity new_other);
|
|
||||||
extern int collision_check(PhysicsEntity a, PhysicsEntity b, Collision* out_a, Collision* out_b);
|
|
||||||
|
|
||||||
#endif // !_fencer_collision_h
|
|
|
@ -1,9 +0,0 @@
|
||||||
#include "debug.h"
|
|
||||||
|
|
||||||
#if NDEBUG
|
|
||||||
int g_debug_error_abort = 0;
|
|
||||||
int g_debug_log_lvl = 0;
|
|
||||||
#else
|
|
||||||
int g_debug_error_abort = 1;
|
|
||||||
int g_debug_log_lvl = 3;
|
|
||||||
#endif
|
|
69
src/debug.h
|
@ -1,69 +0,0 @@
|
||||||
#ifndef _fencer_debug_h
|
|
||||||
#define _fencer_debug_h
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#ifndef NDEBUG
|
|
||||||
#include <assert.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extern int g_debug_error_abort;
|
|
||||||
extern int g_debug_log_lvl;
|
|
||||||
|
|
||||||
#define LOG_INFO(...) do {\
|
|
||||||
if(g_debug_log_lvl < 3) break;\
|
|
||||||
printf("[%s:%d] INFO | ", __FILE__, __LINE__);\
|
|
||||||
printf(__VA_ARGS__);\
|
|
||||||
printf("\n");\
|
|
||||||
} while(0)
|
|
||||||
|
|
||||||
#define LOG_ERROR(...) do {\
|
|
||||||
if(g_debug_log_lvl >= 1) {\
|
|
||||||
printf("[%s:%d] ERROR | ", __FILE__, __LINE__);\
|
|
||||||
printf(__VA_ARGS__);\
|
|
||||||
printf("\n");\
|
|
||||||
fflush(stdout);\
|
|
||||||
}\
|
|
||||||
if(g_debug_error_abort != 0) abort();\
|
|
||||||
} while(0)
|
|
||||||
|
|
||||||
#define LOG_WARNING(...) do {\
|
|
||||||
if(g_debug_log_lvl < 2) break;\
|
|
||||||
printf("[%s:%d] WARNING | ", __FILE__, __LINE__);\
|
|
||||||
printf(__VA_ARGS__);\
|
|
||||||
printf("\n");\
|
|
||||||
} while(0)
|
|
||||||
|
|
||||||
|
|
||||||
#define RETURN_ERROR(__VALUE, ...) do {\
|
|
||||||
LOG_ERROR(__VA_ARGS__);\
|
|
||||||
return __VALUE;\
|
|
||||||
} while(0)
|
|
||||||
|
|
||||||
#define RETURN_WARNING(__VALUE, ...) do {\
|
|
||||||
LOG_WARNING(__VA_ARGS__);\
|
|
||||||
return __VALUE;\
|
|
||||||
} while(0)
|
|
||||||
|
|
||||||
#define ASSERT_RETURN(__ASSERT, __RETURN, ...) do {\
|
|
||||||
if(!(__ASSERT)) {\
|
|
||||||
LOG_ERROR(__VA_ARGS__);\
|
|
||||||
return __RETURN;\
|
|
||||||
}\
|
|
||||||
} while(0)
|
|
||||||
|
|
||||||
#define CHECK(__ASSERT, ...) do {\
|
|
||||||
if(!(__ASSERT)) {\
|
|
||||||
LOG_ERROR(__VA_ARGS__);\
|
|
||||||
}\
|
|
||||||
} while(0)
|
|
||||||
|
|
||||||
#define ASSERT_RETURN_WARN(__ASSERT, __RETURN, ...) do {\
|
|
||||||
if(!(__ASSERT)) {\
|
|
||||||
LOG_WARNING(__VA_ARGS__);\
|
|
||||||
return __RETURN;\
|
|
||||||
}\
|
|
||||||
} while(0)
|
|
||||||
|
|
||||||
#endif // !_fencer_debug_h
|
|
26
src/drop.h
|
@ -1,26 +0,0 @@
|
||||||
#ifndef _fencer_drop_h
|
|
||||||
#define _fencer_drop_h
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
void (*const drop)(void* self);
|
|
||||||
} IDrop;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
void const* data;
|
|
||||||
IDrop const* tc;
|
|
||||||
} Drop;
|
|
||||||
|
|
||||||
#define impl_Drop_for(T, drop_f)\
|
|
||||||
static inline Drop T##_as_Drop(T* x) {\
|
|
||||||
TC_FN_TYPECHECK(void, drop_f, T*);\
|
|
||||||
static IDrop const tc = {\
|
|
||||||
.drop = (void(*const)(void*)) drop_f,\
|
|
||||||
};\
|
|
||||||
return (Drop){.tc = &tc, .data = x};\
|
|
||||||
}
|
|
||||||
|
|
||||||
#define impl_default_Drop_for(T)\
|
|
||||||
static void default_drop_##T(T* v) { free(v); }\
|
|
||||||
impl_Drop_for(T, default_drop_##T)
|
|
||||||
|
|
||||||
#endif // !_fencer_drop_h
|
|
161
src/list.c
|
@ -1,161 +0,0 @@
|
||||||
#include "list.h"
|
|
||||||
#include "stdint.h"
|
|
||||||
#include "stdlib.h"
|
|
||||||
#include "string.h"
|
|
||||||
#include "debug.h"
|
|
||||||
|
|
||||||
#ifndef LIST_DEFAULT_RESERVE
|
|
||||||
#define LIST_DEFAULT_RESERVE 4
|
|
||||||
#endif
|
|
||||||
|
|
||||||
List list_init(size_t element_size) {
|
|
||||||
List self = {
|
|
||||||
.element_size = element_size,
|
|
||||||
.cap = LIST_DEFAULT_RESERVE,
|
|
||||||
.len = 0,
|
|
||||||
.data = malloc(element_size * LIST_DEFAULT_RESERVE),
|
|
||||||
};
|
|
||||||
|
|
||||||
if(self.data == NULL) {
|
|
||||||
LOG_ERROR("Failed to allocate list with starting capacity of %d", LIST_DEFAULT_RESERVE);
|
|
||||||
self.cap = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
List list_copy(const List* source) {
|
|
||||||
List self = list_init(source->element_size);
|
|
||||||
list_reserve(&self, source->cap);
|
|
||||||
if(self.cap > 0) {
|
|
||||||
memcpy(self.data, source->data, source->element_size * source->len);
|
|
||||||
self.len = source->len;
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
void list_empty(List* self) {
|
|
||||||
if(self->data == NULL || self->cap == 0)
|
|
||||||
return;
|
|
||||||
self->data = NULL;
|
|
||||||
self->cap = 0;
|
|
||||||
self->len = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void list_reserve(List* self, size_t at_least) {
|
|
||||||
if(at_least < self->cap)
|
|
||||||
return;
|
|
||||||
|
|
||||||
size_t new_cap = self->cap > 0 ? self->cap : LIST_DEFAULT_RESERVE;
|
|
||||||
while(at_least >= new_cap) {
|
|
||||||
new_cap *= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* new;
|
|
||||||
if(self->data == NULL)
|
|
||||||
new = malloc(new_cap * self->element_size);
|
|
||||||
else
|
|
||||||
new = realloc(self->data, new_cap * self->element_size);
|
|
||||||
ASSERT_RETURN(new != NULL,, "Failed to reserve space for %zu extra elements in list", new_cap);
|
|
||||||
|
|
||||||
self->data = new;
|
|
||||||
self->cap = new_cap;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline
|
|
||||||
void* list_at_unchecked(List* self, size_t at) {
|
|
||||||
union {
|
|
||||||
uint8_t* as_byte;
|
|
||||||
void* as_void;
|
|
||||||
} data = {
|
|
||||||
.as_void = self->data
|
|
||||||
};
|
|
||||||
|
|
||||||
return data.as_byte + self->element_size * at;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* list_at(List* self, size_t at) {
|
|
||||||
ASSERT_RETURN(at < self->len, NULL, "Index %zu out of bounds", at);
|
|
||||||
return list_at_unchecked(self, at);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t list_add(List* self, void* item) {
|
|
||||||
list_reserve(self, self->len + 1);
|
|
||||||
union {
|
|
||||||
uint8_t* as_byte;
|
|
||||||
void* as_void;
|
|
||||||
} data = {
|
|
||||||
.as_void = self->data
|
|
||||||
};
|
|
||||||
|
|
||||||
uint8_t* into = data.as_byte + self->element_size * self->len;
|
|
||||||
|
|
||||||
memcpy(into, item, self->element_size);
|
|
||||||
++self->len;
|
|
||||||
|
|
||||||
return self->len - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void list_insert(List* self, void* item, size_t at) {
|
|
||||||
list_reserve(self, self->len + 1);
|
|
||||||
|
|
||||||
if(at == self->len - 1) {
|
|
||||||
list_add(self, item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
union {
|
|
||||||
uint8_t* as_byte;
|
|
||||||
void* as_void;
|
|
||||||
} data = {
|
|
||||||
.as_void = self->data
|
|
||||||
};
|
|
||||||
uint8_t* from = data.as_byte + self->element_size * at;
|
|
||||||
uint8_t* into = data.as_byte + self->element_size * (at + 1);
|
|
||||||
uint8_t* end = data.as_byte + self->element_size * self->len;
|
|
||||||
memmove(into, from, end - from);
|
|
||||||
memcpy(from, item, self->element_size);
|
|
||||||
++self->len;
|
|
||||||
}
|
|
||||||
|
|
||||||
void list_erase(List* self, size_t at) {
|
|
||||||
ASSERT_RETURN(at < self->len,, "Index %zu out of bounds", at);
|
|
||||||
|
|
||||||
union {
|
|
||||||
uint8_t* as_byte;
|
|
||||||
void* as_void;
|
|
||||||
} data = {
|
|
||||||
.as_void = self->data
|
|
||||||
};
|
|
||||||
|
|
||||||
uint8_t* into = data.as_byte + at * self->element_size;
|
|
||||||
uint8_t* from = data.as_byte + (at + 1) * self->element_size;
|
|
||||||
|
|
||||||
if(at < self->len - 1)
|
|
||||||
memmove(into, from, (self->len - at) * self->element_size);
|
|
||||||
--self->len;
|
|
||||||
|
|
||||||
size_t new_cap = self->cap;
|
|
||||||
while(new_cap > self->len) {
|
|
||||||
new_cap /= 2;
|
|
||||||
}
|
|
||||||
new_cap *= 2;
|
|
||||||
|
|
||||||
|
|
||||||
if(new_cap == self->cap)
|
|
||||||
return;
|
|
||||||
|
|
||||||
void* shrunk = realloc(self->data, new_cap * self->element_size);
|
|
||||||
ASSERT_RETURN(shrunk != NULL || new_cap == 0,, "Failed to shrink List to %zu", new_cap);
|
|
||||||
|
|
||||||
self->data = shrunk;
|
|
||||||
self->cap = new_cap;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* list_iterator_begin(List* self) {
|
|
||||||
return list_at_unchecked(self, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void* list_iterator_end(List* self) {
|
|
||||||
return list_at_unchecked(self, self->len);
|
|
||||||
}
|
|
35
src/list.h
|
@ -1,35 +0,0 @@
|
||||||
#ifndef _fencer_list_h
|
|
||||||
#define _fencer_list_h
|
|
||||||
|
|
||||||
#include "stddef.h"
|
|
||||||
|
|
||||||
typedef struct List List;
|
|
||||||
struct List {
|
|
||||||
void* data;
|
|
||||||
size_t cap;
|
|
||||||
size_t len;
|
|
||||||
|
|
||||||
size_t element_size;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern List list_init(size_t element_size);
|
|
||||||
extern List list_copy(const List* source);
|
|
||||||
extern void list_empty(List* list);
|
|
||||||
|
|
||||||
extern void list_reserve(List* self, size_t at_least);
|
|
||||||
extern void* list_at(List* list, size_t at);
|
|
||||||
|
|
||||||
extern size_t list_add(List* self, void* item);
|
|
||||||
extern void list_insert(List* self, void* item, size_t at);
|
|
||||||
extern void list_erase(List* self, size_t at);
|
|
||||||
|
|
||||||
extern void* list_iterator_begin(List* self);
|
|
||||||
extern void* list_iterator_end(List* self);
|
|
||||||
|
|
||||||
#define list_from_type(T) list_init(sizeof(T))
|
|
||||||
#define list_foreach(T, iter, list) for(T iter = list_iterator_begin(list); iter != (T)list_iterator_end(list); ++iter)
|
|
||||||
#define list_at_as(T, __list, __i) ((T*)(list_at(__list, __i)))
|
|
||||||
|
|
||||||
#define list_iterator_begin_as(T, __list) ((T*)(list_iterator_begin(__list)))
|
|
||||||
|
|
||||||
#endif // !_fencer_list_h
|
|
|
@ -1,68 +0,0 @@
|
||||||
#include "physics_world.h"
|
|
||||||
#include "debug.h"
|
|
||||||
#include "collision.h"
|
|
||||||
#include "rigidbody.h"
|
|
||||||
|
|
||||||
static List _world_bodies;
|
|
||||||
|
|
||||||
void physics_world_init() {
|
|
||||||
_world_bodies = list_from_type(PhysicsEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
void physics_world_clean() {
|
|
||||||
list_empty(&_world_bodies);
|
|
||||||
}
|
|
||||||
|
|
||||||
void physics_world_add_entity(PhysicsEntity entity) {
|
|
||||||
list_add(&_world_bodies, &entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
void physics_world_remove_entity(PhysicsEntity entity) {
|
|
||||||
for(size_t i = 0; i < _world_bodies.len; ++i) {
|
|
||||||
PhysicsEntity* found = list_at_as(PhysicsEntity, &_world_bodies, i);
|
|
||||||
if(found->data == entity.data) {
|
|
||||||
list_erase(&_world_bodies, i);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ASSERT_RETURN(0,, "Physics entity with data at %p is not registered in physics world", entity.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline
|
|
||||||
void _internal_physics_narrow_collision() {
|
|
||||||
size_t half_end = _world_bodies.len/2;
|
|
||||||
Collision collision_left, collision_right;
|
|
||||||
|
|
||||||
PhysicsEntity* right = NULL;
|
|
||||||
list_foreach(PhysicsEntity*, left, &_world_bodies) {
|
|
||||||
for(size_t right_index = 0; right_index < half_end; ++right_index) {
|
|
||||||
right = list_at_as(PhysicsEntity, &_world_bodies, right_index);
|
|
||||||
if(left->data == right->data) continue;
|
|
||||||
|
|
||||||
if(collision_check(*left, *right, &collision_left, &collision_right)) {
|
|
||||||
left->tc->on_collision(left->data, collision_left);
|
|
||||||
right->tc->on_collision(right->data, collision_right);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline
|
|
||||||
void _internal_physics_apply() {
|
|
||||||
list_foreach(PhysicsEntity*, entity, &_world_bodies) {
|
|
||||||
physics_entity_update(*entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline
|
|
||||||
void _internal_physics_integrate_forces() {
|
|
||||||
list_foreach(PhysicsEntity*, entity, &_world_bodies)
|
|
||||||
rigidbody_integrate_forces(entity->tc->get_rigidbody(entity->data));
|
|
||||||
}
|
|
||||||
|
|
||||||
void physics_world_tick() {
|
|
||||||
_internal_physics_integrate_forces();
|
|
||||||
_internal_physics_narrow_collision();
|
|
||||||
_internal_physics_apply();
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
#ifndef _fencer_physics_world_h
|
|
||||||
#define _fencer_physics_world_h
|
|
||||||
|
|
||||||
#include "physics_entity.h"
|
|
||||||
|
|
||||||
extern void physics_world_init();
|
|
||||||
extern void physics_world_clean();
|
|
||||||
|
|
||||||
extern void physics_world_add_entity(PhysicsEntity entity);
|
|
||||||
extern void physics_world_remove_entity(PhysicsEntity entity);
|
|
||||||
|
|
||||||
extern void physics_world_tick();
|
|
||||||
|
|
||||||
#endif // !_fencer_physics_world_h
|
|