Compare commits
25 Commits
d4ce6ab9ec
...
0b89c90ad7
Author | SHA1 | Date | |
---|---|---|---|
|
0b89c90ad7 | ||
|
3d51c8c48f | ||
|
21d8e2c5a5 | ||
|
f805bf3f92 | ||
|
5228fa7e41 | ||
|
f044a75ffe | ||
|
723ccf1126 | ||
|
6bd3afe9b2 | ||
|
d90bf4cbe2 | ||
|
48f34f4623 | ||
|
9c007f34df | ||
|
a1f4599efd | ||
|
4c1a8e087a | ||
|
a2b1f1820a | ||
|
85ec8d3366 | ||
|
7eebc7a2d7 | ||
|
2b26fad983 | ||
|
47799deb8b | ||
|
1cd4bfa638 | ||
|
9beef7686e | ||
|
cee344c7c1 | ||
18a76649b9 | |||
88a4876d91 | |||
|
835edd737c | ||
9a486fa912 |
@ -84,7 +84,6 @@ set(TWN_THIRD_PARTY_SOURCE_FILES
|
||||
|
||||
set(TWN_NONOPT_SOURCE_FILES
|
||||
src/twn_stb.c
|
||||
src/twn_main.c
|
||||
|
||||
src/twn_context.c include/twn_context.h
|
||||
src/twn_audio.c include/twn_audio.h
|
||||
@ -275,6 +274,7 @@ endfunction()
|
||||
function(link_deps target)
|
||||
target_link_libraries(${target} PUBLIC
|
||||
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:SDL2::SDL2>
|
||||
$<$<NOT:$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>>:SDL2::SDL2main>
|
||||
physfs-static
|
||||
xms)
|
||||
target_include_directories(${target} PUBLIC ${SDL2_INCLUDE_DIRS})
|
||||
@ -289,7 +289,7 @@ function(use_townengine sources output_directory)
|
||||
add_library(${target}_game SHARED ${sources})
|
||||
give_options(${target}_game)
|
||||
include_deps(${target}_game)
|
||||
target_link_libraries(${target}_game PUBLIC $<$<NOT:$<BOOL:${EMSCRIPTEN}>>:SDL2::SDL2> ${TWN_TARGET})
|
||||
target_link_libraries(${target}_game PUBLIC ${TWN_TARGET})
|
||||
set_target_properties(${target}_game PROPERTIES
|
||||
OUTPUT_NAME game
|
||||
LIBRARY_OUTPUT_DIRECTORY ${output_directory}
|
||||
|
@ -11,9 +11,9 @@
|
||||
|
||||
|
||||
static void update_timers(Player *player) {
|
||||
player->jump_air_timer = timer_tick_frames(player->jump_air_timer);
|
||||
player->jump_coyote_timer = timer_tick_frames(player->jump_coyote_timer);
|
||||
player->jump_buffer_timer = timer_tick_frames(player->jump_buffer_timer);
|
||||
player->jump_air_timer = player->jump_air_timer - 1 <= 0 ? 0 : player->jump_air_timer - 1;
|
||||
player->jump_coyote_timer = player->jump_coyote_timer - 1 <= 0 ? 0 : player->jump_coyote_timer - 1;
|
||||
player->jump_buffer_timer = player->jump_buffer_timer - 1 <= 0 ? 0 : player->jump_buffer_timer - 1;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
cmake_policy(SET CMP0171 NEW)
|
||||
cmake_minimum_required(VERSION 3.30)
|
||||
project(twnlua LANGUAGES C)
|
||||
|
||||
find_package(Python3 COMPONENTS Interpreter)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
endif()
|
||||
@ -14,14 +15,13 @@ set(FLAGS
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/game.c
|
||||
COMMAND ${PYTHON3} ${CMAKE_CURRENT_SOURCE_DIR}/bindgen.py $ENV{TWNROOT}/share/twn_api.json ${FLAGS} > ${CMAKE_CURRENT_SOURCE_DIR}/luabind.c
|
||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/bindgen.py $ENV{TWNROOT}/share/twn_api.json ${FLAGS} > ${CMAKE_CURRENT_SOURCE_DIR}/luabind.c
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/bindgen.py $ENV{TWNROOT}/share/twn_api.json
|
||||
CODEGEN
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${TWN_OUT_DIR}/data/scripts/twnapi.lua
|
||||
COMMAND ${PYTHON3} ${CMAKE_CURRENT_SOURCE_DIR}/docgen.py $ENV{TWNROOT}/share/twn_api.json > ${TWN_OUT_DIR}/data/scripts/twnapi.lua
|
||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/docgen.py $ENV{TWNROOT}/share/twn_api.json > ${TWN_OUT_DIR}/data/scripts/twnapi.lua
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/docgen.py $ENV{TWNROOT}/share/twn_api.json
|
||||
)
|
||||
|
||||
|
@ -147,12 +147,12 @@ static void exchange_lua_states(lua_State *from, lua_State *to, int level, int i
|
||||
void game_tick(void) {
|
||||
if (ctx.initialization_needed) {
|
||||
if (!ctx.udata)
|
||||
ctx.udata = calloc(1, sizeof (State));
|
||||
ctx.udata = SDL_calloc(1, sizeof (State));
|
||||
|
||||
State *state = ctx.udata;
|
||||
|
||||
/* let's init lua */
|
||||
lua_State *new_state = luaL_newstate();
|
||||
lua_State *new_state = lua_newstate(custom_alloc, NULL);
|
||||
lua_setallocf(new_state, custom_alloc, NULL);
|
||||
|
||||
/* state existed already, copy its udata over */
|
||||
@ -161,7 +161,6 @@ void game_tick(void) {
|
||||
lua_getfield(state->L, -1, "udata");
|
||||
SDL_assert(!lua_isnoneornil(state->L, -1));
|
||||
SDL_assert(!lua_isnoneornil(state->L, -2));
|
||||
// SDL_TriggerBreakpoint();
|
||||
if (!lua_isnoneornil(state->L, -1)) {
|
||||
log_info("Exchanging lua states...");
|
||||
lua_newtable(new_state);
|
||||
@ -228,7 +227,7 @@ void game_tick(void) {
|
||||
lua_pop(state->L, 1);
|
||||
}
|
||||
|
||||
free(game_buf);
|
||||
SDL_free(game_buf);
|
||||
|
||||
/* from this point we have access to everything defined in lua */
|
||||
}
|
||||
@ -261,5 +260,5 @@ void game_end(void) {
|
||||
State *state = ctx.udata;
|
||||
bindgen_unload_twn(state->L);
|
||||
lua_close(state->L);
|
||||
free(state);
|
||||
SDL_free(state);
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
#ifndef STATE_H
|
||||
#define STATE_H
|
||||
|
||||
#include <lua.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct State {
|
||||
|
11
bin/twn
11
bin/twn
@ -4,7 +4,7 @@
|
||||
set +e
|
||||
|
||||
exe="$(basename $PWD)"
|
||||
toolpath="$(dirname -- "${BASH_SOURCE[0]}")"
|
||||
toolpath="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
|
||||
export TWNROOT=$(realpath "$toolpath"/../)
|
||||
|
||||
case "$1" in
|
||||
@ -15,10 +15,11 @@ case "$1" in
|
||||
;;
|
||||
|
||||
gdb ) unset DEBUGINFOD_URLS
|
||||
$0 build && gdb --se=libgame.so -ex run --args "$(basename $PWD)" --no-sanity-timer "${@:2}"
|
||||
$0 build --debug && gdb --se=libgame.so -ex run --args "$(basename $PWD)" --no-sanity-timer "${@:2}"
|
||||
;;
|
||||
|
||||
init ) cp -r "$TWNROOT/apps/templates/$2" "$3"
|
||||
ln -s "$TWNROOT/bin/twn" "$3/twn"
|
||||
;;
|
||||
|
||||
apitrace ) case "$2" in
|
||||
@ -40,7 +41,11 @@ case "$1" in
|
||||
api-gen ) "$toolpath"/gen_api_header.sh
|
||||
;;
|
||||
|
||||
wiki ) xdg-open "file://$TWNROOT/docs/wiki/index.html"
|
||||
wiki ) if [ "$OS" = "Windows_NT" ]; then
|
||||
explorer "file://""$(cygpath -w "$(command realpath $TWNROOT/docs/wiki/index.html)")"
|
||||
else
|
||||
xdg-open "file://$TWNROOT/docs/wiki/index.html"
|
||||
fi
|
||||
;;
|
||||
|
||||
* ) echo "Unknown command."
|
||||
|
16
bin/twnbuild
16
bin/twnbuild
@ -20,9 +20,23 @@ if has_clang:
|
||||
if has_ninja:
|
||||
cmake += ["-G", "Ninja"]
|
||||
cmake += ["-B", "build"]
|
||||
# TODO: have it --fast instead, where separate --no-debug would mean stripping the debug info
|
||||
|
||||
# TODO: have it --fast=1 instead, where separate --debug=0 would mean stripping the debug info
|
||||
if "--release" in argv:
|
||||
cmake += ["-DCMAKE_BUILD_TYPE=Release"]
|
||||
elif "--debug" in argv:
|
||||
cmake += ["-DCMAKE_BUILD_TYPE=Debug"]
|
||||
|
||||
if "--unified=1" in argv:
|
||||
cmake += ["-DTWN_FEATURE_DYNLIB_GAME=ON"]
|
||||
elif "--unified=0" in argv:
|
||||
cmake += ["-DTWN_FEATURE_DYNLIB_GAME=OFF"]
|
||||
|
||||
if "--sanitize=1" in argv:
|
||||
cmake += ["-DTWN_SANITIZE=ON"]
|
||||
elif "--sanitize=0" in argv:
|
||||
cmake += ["-DTWN_SANITIZE=OFF"]
|
||||
|
||||
cmake += [f"-DTWN_OUT_DIR={getcwd()}"]
|
||||
# pass arbitrary arguments over
|
||||
if "--" in argv:
|
||||
|
@ -1,19 +0,0 @@
|
||||
# interoperability
|
||||
api needs to facilitate easy interoperability with other languages and tools,
|
||||
for that certain considerations are taken:
|
||||
|
||||
* number of public api calls is kept at the minimum
|
||||
* procedure parameters can only use basic types, no aggregates, with exception of Vec/Matrix types and alike,
|
||||
with no new additions, ever (see [/include/twn_types.h](../include/twn_types.h))
|
||||
* enum types are allowed, as they decay to numeric type (but language-specific api can decide to provide simple ways to use them)
|
||||
* optionals can be expressed via pointer passage of value primitives, assumed immutable, with the NULL expressing default value
|
||||
* no opaque types, only keys if needed
|
||||
* pointers to pointers aren't allowed
|
||||
* when mutation on input is done, - it shouldn't be achieved by a mutable pointer, but the return value
|
||||
* return value could be a simple aggregate that is translatable to a dictionary of primitives
|
||||
* module prefix is used for namespacing, actual symbols come after the prefix (`<module>_<symbol>`)
|
||||
* symbols should not contain numerics at the start nor after the namespace prefix
|
||||
* 32 bit floating point is the only numeric type
|
||||
* [/include/twn_api.json](../share/twn_api.json) file is hand-kept with a schema to aid automatic binding generation and tooling
|
||||
* parameter names should not collide with keywords of any language that is targetted; if so happens, parameter alias could be added
|
||||
* any procedure can't have more than 8 parameters
|
@ -1,15 +0,0 @@
|
||||
assets are distributed by packs, which can come in archived or folder form (for development purposes)
|
||||
|
||||
one pack by the name of 'data' is always assumed to be present alongside game executable root,
|
||||
whether in folder /data/ or /data.btw file, where precedence is taking over /data/
|
||||
|
||||
root 'data' should be used to point to other dependency packs in /packs/data.toml file
|
||||
|
||||
---
|
||||
[[deps]]
|
||||
source = "../../common-data" # where does it come from, might be an url
|
||||
name = "common-data" # should be globally unique
|
||||
---
|
||||
|
||||
they're mounted to / in the virtual file system by default, if same files are present in multiple packs,
|
||||
only the one loaded first is visible
|
@ -9,8 +9,7 @@
|
||||
<a href="index.html">Go back
|
||||
<p><a name="introduction"></a><strong>T1.1 </strong><strong>Introduction</strong>
|
||||
<blockquote>
|
||||
<p>Townengine, {twn}, is an opinionated game development framework designed around ideas of simplicity, managed state,
|
||||
care for old devices, portability, language agnosticism, use-case orientation, iterability and low latency.
|
||||
<p>Townengine, {twn}, is an opinionated game development framework designed around specific set of ideas:
|
||||
<p><b>Simplicity.</b> It makes assumptions that trickle down to your game code. There's no delta between frames, nor resolution change.
|
||||
Textures have constant known size, not requiring scaling.
|
||||
<p><b>Managed state.</b> Designed around this we can provide hot reloading at any point,
|
||||
@ -18,27 +17,60 @@
|
||||
Every frame apparent engine state is cleared, which removes another variable for you to handle, - when to initialize.
|
||||
There's no need for you to call `LoadImage()` of sorts before using it in render, you just pass filepaths around.
|
||||
Input is initialized anew each frame, making rebinds trivial.
|
||||
<p><b>Care for old devices.</b> It's to both to provide it for more people, who otherwise might not be able to make games they want,
|
||||
as well as to have restrictions that constitute in desired aesthetic. Graphics capabilities are limited, but
|
||||
<p><b>Device care.</b> Older hardware is still used in many parts of the world,
|
||||
which users might not otherwise be able to make games they dream of.
|
||||
Restrictions also constitute in desired aesthetics. Graphics capabilities are limited, but
|
||||
what is present, - is heavily optimized. It is rather different from performance-driven approach that tries to take
|
||||
advantage of the latest hardware and features, sacrificing both the reach and portability.
|
||||
advantage of the latest hardware and features, sacrificing both the reach and portability in process.
|
||||
<p><b>Portability.</b> Written in C11 with all dependencies being in C, it's possible to compile it to most platforms.
|
||||
SDL2 is at the center and you cannot get better than this. Default graphics API is OpenGL 1.5 with extension detection.
|
||||
As an example, it builds and runs on Haiku OS with LLVMpipe.
|
||||
<p><b>Language agnosticism.</b> API is restricted to not require much glue.
|
||||
<p><b>Language agnosticism.</b> API is restricted to minimize the amount of glue code.
|
||||
Language interpreters don't need to be part of the engine itself. Anything with C ABI support can link to it.
|
||||
/share/twn_api.json schema is provided for automatic binding and annotation generation.
|
||||
<p><b>Use-case orientation.</b> It doesn't try to be a yet another general purpose engine conquering all and nothing.
|
||||
Instead we seek particular use cases and implement the most essential parts.
|
||||
User code copying should be promoted instead of delegating it all to the engine, this way we don't restrict.
|
||||
<p><b>Iterability.</b> Defaults provided for most of the API, making initial code faster to spring.
|
||||
Hot reloading for both assets and code.
|
||||
<p><b>Low latency.</b> Care is given to various incarnations of feared latency. Engine is fast to build, allowing for low commitment patches.
|
||||
Startup time is profiled and optimized. Streaming is used as much as possible for asset load.
|
||||
Hot reloading works for both assets and code.
|
||||
<p><b>Low latency.</b> Care is given to various incarnations of feared latency. Engine is fast to build, allowing for low commitment local patches.
|
||||
Startup time is profiled and frequently looked into. Streaming is used as much as possible.
|
||||
<p><b>No-Versioning.</b> We don't stick to releases and "contract" obligations for things to remain stagnant.
|
||||
It's fair to ask end user to put little effort if they need newer set of features, instead of putting all the burden on us.
|
||||
You can always just stick to particular version you started the development of the project with,
|
||||
{twn} doesn't need system wide installation.
|
||||
<p><b>Bounded.</b> Most places assume runtime limits, to help with portability and growing out-of-hand complexity.
|
||||
Frames cannot take more than sane allocated time, breaking off infinite loops.
|
||||
<p><b>Toolable.</b> External editors are the way, with their own separate modes of being. Simple web socket interface will be defined for that.
|
||||
</blockquote>
|
||||
<p><a name="wiki"></a><strong>T1.2 </strong><strong>Wiki</strong>
|
||||
<blockquote>
|
||||
<p>Purpose of this wiki is to collect information on various usages of {twn} across the genres, FAQ style.
|
||||
You're welcomed to contribute to it. It's written in HTML so basic you can edit it by hand.
|
||||
Just check <a href="template.html">template.html</a>.
|
||||
<p>Content is divided into chapters, where prefix specifies its category. Currently following prefixes are used:
|
||||
<ul>
|
||||
<li><b>T</b> for townengine; development and apis.
|
||||
<li><b>G</b> for gamedev; guides and FAQs on game making.
|
||||
</ul>
|
||||
</blockquote>
|
||||
<p><a name="abi"></a><strong>T1.3 </strong><strong>ABI</strong>
|
||||
<blockquote>
|
||||
<p>For native code ABI defines convention to ease tooling integration.
|
||||
<ul>
|
||||
<li>32 bit floating point is the only numeric type.
|
||||
<li>Procedure parameters can only use basic types, with no aggregates. Exceptions are Vec, Rect and Color types.
|
||||
(see /include/twn_types.h)
|
||||
<li>Enum types are not allowed, as they decay to integer type, identity strings are used instead.
|
||||
<li>No opaque nor pointer types allowed, use string keys if needed. Think of it as data base relations.
|
||||
<li>Only null terminated string is allowed as a sequential type in both parameters and returns.
|
||||
<li>Return value could be a simple aggregate that is translatable to a dictionary of primitives, without nesting.
|
||||
<li>Symbols should not contain numerics. For example, sqrt2 must become square_root.
|
||||
<li>/share/twn_api.json file is hand-kept with a schema to aid automatic binding generation and tooling.
|
||||
<li>Parameter names should not collide with keywords of any language that is targeted; if so happens, parameter alias could be added.
|
||||
Currently forbidden: <b>repeat</b>.
|
||||
<li>Procedure can't have more than 8 parameters.
|
||||
</ul>
|
||||
</blockquote>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -13,6 +13,8 @@
|
||||
</tr>
|
||||
<tr><td>T2.</strong> <a href="#input-system">Input System</a></td>
|
||||
</tr>
|
||||
<tr><td>T3.</strong> <a href="#packaging">Packaging</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
<p style="margin-bottom:0"><a name="about-townengine"></a>T1. </strong><a href="about-townengine.html">About Townengine</strong></a></p>
|
||||
<blockquote style="margin-top:0">
|
||||
@ -24,6 +26,10 @@
|
||||
<p style="margin:0">T2.1 <a href="input-system.html#design">Design</a></p>
|
||||
<p style="margin:0">T2.2 <a href="input-system.html#api">API</a></p>
|
||||
</blockquote>
|
||||
<p style="margin-bottom:0"><a name="packaging"></a>T3. </strong><a href="packaging.html">Packaging</strong></a></p>
|
||||
<blockquote style="margin-top:0">
|
||||
<p style="margin:0">T3.1 <a href="packaging.html#overview">Overview</a></p>
|
||||
</blockquote>
|
||||
<p style="margin-bottom:0"><a name="making-2dot5d-shooters"></a>G1. </strong><a href="making-2dot5d-shooters.html">Making 2.5D Shooters</strong></a></p>
|
||||
<blockquote style="margin-top:0">
|
||||
</blockquote>
|
||||
|
39
docs/wiki/packaging.html
Normal file
39
docs/wiki/packaging.html
Normal file
@ -0,0 +1,39 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Townengine Wiki : Packaging</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1 style="margin-bottom:0in">T3. Packaging<span style="float:right">{twn}</span></h1>
|
||||
<a href="index.html">Go back
|
||||
<p><a name="overview"></a><strong>T3.1 </strong><strong>Overview</strong>
|
||||
<blockquote>
|
||||
<p>Assets are distributed by packs, which can come in either archived or, for development purposes, folder form.
|
||||
{twn} games operate in a sandboxed filesystem and operate strictly on supplied content.
|
||||
|
||||
<p>Archives are limited to zip format, with .btw file extension.
|
||||
|
||||
<p>One pack by the name of 'data' is always assumed to be present alongside executable,
|
||||
whether in folder /data/ or /data.btw archive, where precedence is taking over for /data/ folder variant.
|
||||
|
||||
<p>Root /data/ could be used to point to other packs in its /packs/data.toml file.
|
||||
It results in a union filesystem, where directory structures are overlaid on top of each other.
|
||||
|
||||
<p>Packs which are loaded first have precedence over later ones, preserving their contents.
|
||||
For example, if /assets/foo exists in root /data/, and another /assets/foo is found elsewhere,
|
||||
only the root /data/ version will be available.
|
||||
|
||||
<pre>
|
||||
# Example of data.toml file
|
||||
# `deps` array of tables is loaded sequentially, where dependencies of dependencies are processed first before continuing.
|
||||
|
||||
[[deps]]
|
||||
source = "../../common-data" # where does it come from
|
||||
name = "common-data" # should be a globally unique identifier</pre>
|
||||
|
||||
<p>Compilation utility might later be available to combine packs for easier distribution.
|
||||
|
||||
</blockquote>
|
||||
</body>
|
||||
</html>
|
@ -2,7 +2,6 @@
|
||||
#define TWN_AUDIO_H
|
||||
|
||||
#include "twn_engine_api.h"
|
||||
#include "twn_option.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
@ -23,6 +22,8 @@ TWN_API void audio_parameter(const char *channel, const char *parameter, float v
|
||||
|
||||
#ifndef TWN_NOT_C
|
||||
|
||||
#include "src/twn_option_c.h"
|
||||
|
||||
typedef struct PlayAudioArgs {
|
||||
char *path;
|
||||
|
||||
|
@ -1,8 +0,0 @@
|
||||
#ifndef TWN_CONCATENATE_H
|
||||
#define TWN_CONCATENATE_H
|
||||
|
||||
#define m_concatenate(p_a, p_b) m_concatenate_(p_a, p_b)
|
||||
#define m_concatenate_(p_a, p_b) m_concatenate__(p_a, p_b)
|
||||
#define m_concatenate__(p_a, p_b) p_a##p_b
|
||||
|
||||
#endif
|
@ -2,7 +2,6 @@
|
||||
#define TWN_DRAW_H
|
||||
|
||||
#include "twn_types.h"
|
||||
#include "twn_option.h"
|
||||
#include "twn_engine_api.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
@ -118,6 +117,8 @@ TWN_API void draw_model(const char *model,
|
||||
|
||||
#ifndef TWN_NOT_C
|
||||
|
||||
#include "src/twn_option_c.h"
|
||||
|
||||
typedef struct DrawSpriteArgs {
|
||||
char const *texture;
|
||||
Rect rect;
|
||||
|
@ -1,16 +0,0 @@
|
||||
#ifndef TWN_TEXTURES_MODES_H
|
||||
#define TWN_TEXTURES_MODES_H
|
||||
|
||||
/* TODO: rename, as it doesn't have to be about textures only, but blending */
|
||||
/* TODO: move from public /include/ tree */
|
||||
|
||||
/* alpha channel information */
|
||||
typedef enum TextureMode {
|
||||
TEXTURE_MODE_OPAQUE, /* all pixels are solid */
|
||||
TEXTURE_MODE_SEETHROUGH, /* some pixels are alpha zero */
|
||||
TEXTURE_MODE_GHOSTLY, /* arbitrary alpha values */
|
||||
TEXTURE_MODE_COUNT,
|
||||
TEXTURE_MODE_UNKNOWN = -1, /* a sentinel */
|
||||
} TextureMode;
|
||||
|
||||
#endif
|
@ -7,19 +7,18 @@
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <math.h>
|
||||
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846264338327950288 /**< pi */
|
||||
#endif
|
||||
|
||||
/* multiply by these to convert degrees <---> radians */
|
||||
#define DEG2RAD (M_PI / 180)
|
||||
#define RAD2DEG (180 / M_PI)
|
||||
|
||||
/* TODO: shouldn't be a thing */
|
||||
#ifndef TWN_NOT_C
|
||||
#include <math.h>
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846264338327950288 /**< pi */
|
||||
#endif
|
||||
|
||||
/* multiply by these to convert degrees <---> radians */
|
||||
#define DEG2RAD (M_PI / 180)
|
||||
#define RAD2DEG (180 / M_PI)
|
||||
|
||||
TWN_API void log_info(const char *restrict format, ...);
|
||||
TWN_API void log_critical(const char *restrict format, ...);
|
||||
@ -28,13 +27,8 @@
|
||||
/* saves all texture atlases as BMP files in the write directory */
|
||||
TWN_API void textures_dump_atlases(void);
|
||||
|
||||
/* returns true if str ends with suffix */
|
||||
TWN_API bool strends(const char *str, const char *suffix);
|
||||
|
||||
/* TODO: this is why generics were invented. sorry, i'm tired today */
|
||||
TWN_API double clamp(double d, double min, double max);
|
||||
TWN_API float clampf(float f, float min, float max);
|
||||
TWN_API int clampi(int i, int min, int max);
|
||||
|
||||
/* sets buf_out to a pointer to a byte buffer which must be freed. */
|
||||
/* returns the size of this buffer. */
|
||||
@ -52,28 +46,14 @@
|
||||
TWN_API Rect rect_overlap(Rect a, Rect b);
|
||||
/* returns true if two rectangles are intersecting */
|
||||
TWN_API bool rect_intersects(Rect a, Rect b);
|
||||
TWN_API Vec2 rect_center(Rect rect);
|
||||
|
||||
/* decrements an integer value, stopping at 0 */
|
||||
/* meant for tick-based timers in game logic */
|
||||
/*
|
||||
* example:
|
||||
* tick_timer(&player->jump_air_timer);
|
||||
*/
|
||||
TWN_API int32_t timer_tick_frames(int32_t frames_left);
|
||||
|
||||
/* decrements a floating point second-based timer, stopping at 0.0f */
|
||||
/* meant for poll based real time logic in game logic */
|
||||
/* note that it should be decremented only on the next tick after its creation */
|
||||
TWN_API float timer_tick_seconds(float seconds_left);
|
||||
|
||||
typedef struct TimerElapseFramesResult {
|
||||
bool elapsed; int32_t frames_left;
|
||||
} TimerElapseFramesResult;
|
||||
TWN_API TimerElapseFramesResult timer_elapse_frames(int32_t frames_left, int32_t interval);
|
||||
|
||||
typedef struct TimerElapseSecondsResult {
|
||||
bool elapsed; float seconds_left;
|
||||
float seconds_left; float interval; bool elapsed;
|
||||
} TimerElapseSecondsResult;
|
||||
TWN_API TimerElapseSecondsResult timer_elapse_seconds(float seconds_left, float interval);
|
||||
|
||||
@ -83,6 +63,5 @@ TWN_API void log_rect(Rect value, char const *identity);
|
||||
|
||||
TWN_API void profile_start(char const *profile);
|
||||
TWN_API void profile_end(char const *profile);
|
||||
TWN_API void profile_list_stats(void);
|
||||
|
||||
#endif
|
||||
|
@ -1,9 +0,0 @@
|
||||
#ifndef TWN_VARARGCOUNT_H
|
||||
#define TWN_VARARGCOUNT_H
|
||||
|
||||
#define m_narg(...) m_narg_(__VA_ARGS__, m_rseq_n_())
|
||||
#define m_narg_(...) m_arg_n_(__VA_ARGS__)
|
||||
#define m_arg_n_(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, N, ...) N
|
||||
#define m_rseq_n_() 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
|
||||
|
||||
#endif
|
@ -264,6 +264,34 @@
|
||||
]
|
||||
},
|
||||
|
||||
"timer_tick_seconds": {
|
||||
"module": "util",
|
||||
"symbol": "tick_seconds",
|
||||
"header": "twn_util.h",
|
||||
"params": [
|
||||
{ "name": "seconds_left", "type": "float" }
|
||||
],
|
||||
"return": "float"
|
||||
},
|
||||
|
||||
"timer_elapse_seconds": {
|
||||
"module": "util",
|
||||
"symbol": "elapse_seconds",
|
||||
"header": "twn_util.h",
|
||||
"params": [
|
||||
{ "name": "seconds_left", "type": "float" },
|
||||
{ "name": "interval", "type": "float" }
|
||||
],
|
||||
"return": {
|
||||
"fields": [
|
||||
{ "name": "seconds_left", "type": "float" },
|
||||
{ "name": "interval", "type": "float" },
|
||||
{ "name": "elapsed", "type": "bool" }
|
||||
],
|
||||
"c_type": "TimerElapseSecondsResult"
|
||||
}
|
||||
},
|
||||
|
||||
"log_vec2": {
|
||||
"module": "util",
|
||||
"symbol": "log_vec2",
|
||||
|
@ -6,7 +6,7 @@
|
||||
#include "twn_textures_c.h"
|
||||
#include "twn_types_c.h"
|
||||
#include "twn_text_c.h"
|
||||
#include "twn_option.h"
|
||||
#include "twn_option_c.h"
|
||||
#include "twn_deferred_commands.h"
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include "twn_util.h"
|
||||
#include "twn_util_c.h"
|
||||
#include "twn_textures_c.h"
|
||||
#include "twn_option.h"
|
||||
#include "twn_option_c.h"
|
||||
|
||||
#include <stb_ds.h>
|
||||
|
||||
@ -216,7 +216,7 @@ void render_sprite_batch(const Primitive2D primitives[],
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
const Vec2 c = rect_center(sprite.rect);
|
||||
const Vec2 c = { sprite.rect.x + sprite.rect.w / 2, sprite.rect.y + sprite.rect.h / 2 };
|
||||
const Vec2 t = fast_cossine(sprite.rotation + (float)M_PI_4);
|
||||
const Vec2 d = {
|
||||
.x = t.x * sprite.rect.w * (float)M_SQRT1_2,
|
||||
@ -231,7 +231,7 @@ void render_sprite_batch(const Primitive2D primitives[],
|
||||
} else {
|
||||
/* rotated non-square case*/
|
||||
|
||||
const Vec2 c = rect_center(sprite.rect);
|
||||
const Vec2 c = { sprite.rect.x + sprite.rect.w / 2, sprite.rect.y + sprite.rect.h / 2 };
|
||||
const Vec2 t = fast_cossine(sprite.rotation);
|
||||
|
||||
const Vec2 h = { sprite.rect.w / 2, sprite.rect.h / 2 };
|
||||
|
@ -108,11 +108,8 @@ static char *string_arena_alloc(StringArena *arena, size_t size) {
|
||||
|
||||
|
||||
static FontData *text_load_font_data(const char *path, int height_px) {
|
||||
FontData *font_data = ccalloc(1, sizeof *font_data);
|
||||
font_data->file_path = path;
|
||||
font_data->height_px = height_px;
|
||||
|
||||
unsigned char* bitmap = ccalloc(ctx.font_texture_size * ctx.font_texture_size, 1);
|
||||
FontData *font_data;
|
||||
unsigned char* bitmap;
|
||||
|
||||
{
|
||||
unsigned char *buf = NULL;
|
||||
@ -125,10 +122,21 @@ static FontData *text_load_font_data(const char *path, int height_px) {
|
||||
buf_len = font_file_ptr->value.len;
|
||||
} else {
|
||||
buf_len = file_to_bytes(path, &buf);
|
||||
if (buf_len == -1) {
|
||||
/* TODO: have a fallback default font */
|
||||
log_warn("Font %s not found", path);
|
||||
return NULL;
|
||||
}
|
||||
FontFileBuffer buffer = { buf_len, buf };
|
||||
shput(font_file_cache_hash, path, buffer);
|
||||
}
|
||||
|
||||
font_data = ccalloc(1, sizeof *font_data);
|
||||
font_data->file_path = path;
|
||||
font_data->height_px = height_px;
|
||||
|
||||
bitmap = ccalloc(ctx.font_texture_size * ctx.font_texture_size, 1);
|
||||
|
||||
stbtt_InitFont(&font_data->info, buf, stbtt_GetFontOffsetForIndex(buf, 0));
|
||||
|
||||
/* might as well get these now, for later */
|
||||
@ -224,7 +232,7 @@ static void text_draw_with(FontData* font_data, char* text, Vec2 position, Color
|
||||
}
|
||||
|
||||
|
||||
static void ensure_font_cache(const char *font_path, int height_px) {
|
||||
static bool ensure_font_cache(const char *font_path, int height_px) {
|
||||
/* HACK: don't */
|
||||
bool is_cached = false;
|
||||
for (size_t i = 0; i < arrlenu(ctx.text_cache.data); ++i) {
|
||||
@ -236,8 +244,11 @@ static void ensure_font_cache(const char *font_path, int height_px) {
|
||||
}
|
||||
if (!is_cached) {
|
||||
FontData *new_font_data = text_load_font_data(font_path, height_px);
|
||||
if (new_font_data == NULL)
|
||||
return false;
|
||||
arrput(ctx.text_cache.data, new_font_data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -294,7 +305,8 @@ void draw_text(const char *string, Vec2 position, float height, Color color, con
|
||||
return;
|
||||
}
|
||||
|
||||
ensure_font_cache(font, (int)height);
|
||||
if (!ensure_font_cache(font, (int)height))
|
||||
return;
|
||||
|
||||
/* the original string might not be around by the time it's used, so copy it */
|
||||
size_t str_length = SDL_strlen(string) + 1;
|
||||
@ -322,7 +334,9 @@ void draw_text(const char *string, Vec2 position, float height, Color color, con
|
||||
|
||||
|
||||
float draw_text_width(const char *string, float height, const char *font) {
|
||||
ensure_font_cache(font, (int)height);
|
||||
if (!ensure_font_cache(font, (int)height))
|
||||
return 0;
|
||||
|
||||
FontData *font_data = get_font_data(font, (int)height);
|
||||
|
||||
int length = 0;
|
||||
|
@ -546,7 +546,7 @@ void audio_callback(void *userdata, uint8_t *stream, int len) {
|
||||
i == audio_channels_len - 1);
|
||||
}
|
||||
|
||||
size_t const unnamed_audio_channels_len = shlen(ctx.unnamed_audio_channels);
|
||||
size_t const unnamed_audio_channels_len = arrlen(ctx.unnamed_audio_channels);
|
||||
for (size_t i = 0; i < unnamed_audio_channels_len; ++i) {
|
||||
sanity_check_channel((AudioChannelItem){NULL, ctx.unnamed_audio_channels[i]});
|
||||
audio_sample_and_mixin_channel(&ctx.unnamed_audio_channels[i],
|
||||
@ -556,7 +556,7 @@ void audio_callback(void *userdata, uint8_t *stream, int len) {
|
||||
|
||||
/* ditch finished unnamed */
|
||||
size_t i = 0;
|
||||
while (i < unnamed_audio_channels_len) {
|
||||
while (i < arrlenu(ctx.unnamed_audio_channels)) {
|
||||
if (ctx.unnamed_audio_channels[i].finished) {
|
||||
free_audio_channel(ctx.unnamed_audio_channels[i]);
|
||||
arrdelswap(ctx.unnamed_audio_channels, i);
|
||||
|
@ -1,11 +1,17 @@
|
||||
#ifndef TWN_OPTION_H
|
||||
#define TWN_OPTION_H
|
||||
|
||||
#include "twn_concatenate.h"
|
||||
#include "twn_varargcount.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#define m_narg(...) m_narg_(__VA_ARGS__, m_rseq_n_())
|
||||
#define m_narg_(...) m_arg_n_(__VA_ARGS__)
|
||||
#define m_arg_n_(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, N, ...) N
|
||||
#define m_rseq_n_() 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
|
||||
|
||||
#define m_concatenate(p_a, p_b) m_concatenate_(p_a, p_b)
|
||||
#define m_concatenate_(p_a, p_b) m_concatenate__(p_a, p_b)
|
||||
#define m_concatenate__(p_a, p_b) p_a##p_b
|
||||
|
||||
/* usage example:
|
||||
*
|
||||
* struct result {
|
@ -361,7 +361,7 @@ void textures_cache_deinit(TextureCache *cache) {
|
||||
|
||||
/* free cache hashes */
|
||||
for (size_t i = 0; i < shlenu(cache->hash); ++i) {
|
||||
if (missing_texture_surface && cache->hash[i].value.data->pixels != missing_texture_surface->pixels)
|
||||
if (missing_texture_surface == NULL || cache->hash[i].value.data->pixels != missing_texture_surface->pixels)
|
||||
stbi_image_free(cache->hash[i].value.data->pixels);
|
||||
else
|
||||
SDL_free(cache->hash[i].value.data->pixels);
|
||||
|
@ -2,7 +2,6 @@
|
||||
#define TWN_TEXTURES_C_H
|
||||
|
||||
#include "twn_types.h"
|
||||
#include "twn_texture_modes.h"
|
||||
#include "rendering/twn_gpu_texture_c.h"
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
@ -16,6 +15,16 @@
|
||||
#define TEXTURE_ATLAS_FORMAT SDL_PIXELFORMAT_RGBA32
|
||||
|
||||
|
||||
/* alpha channel information */
|
||||
typedef enum TextureMode {
|
||||
TEXTURE_MODE_OPAQUE, /* all pixels are solid */
|
||||
TEXTURE_MODE_SEETHROUGH, /* some pixels are alpha zero */
|
||||
TEXTURE_MODE_GHOSTLY, /* arbitrary alpha values */
|
||||
TEXTURE_MODE_COUNT,
|
||||
TEXTURE_MODE_UNKNOWN = -1, /* a sentinel */
|
||||
} TextureMode;
|
||||
|
||||
|
||||
typedef struct Texture {
|
||||
Rect srcrect; /* position in atlas */
|
||||
SDL_Surface *data; /* original image data */
|
||||
|
@ -109,24 +109,12 @@ void *ccalloc(size_t num, size_t size) {
|
||||
}
|
||||
|
||||
|
||||
double clamp(double d, double min, double max) {
|
||||
const double t = d < min ? min : d;
|
||||
return t > max ? max : t;
|
||||
}
|
||||
|
||||
|
||||
float clampf(float f, float min, float max) {
|
||||
const float t = f < min ? min : f;
|
||||
return t > max ? max : t;
|
||||
}
|
||||
|
||||
|
||||
int clampi(int i, int min, int max) {
|
||||
const int t = i < min ? min : i;
|
||||
return t > max ? max : t;
|
||||
}
|
||||
|
||||
|
||||
int64_t file_to_bytes(const char *path, unsigned char **buf_out) {
|
||||
SDL_RWops *handle = PHYSFSRWOPS_openRead(path);
|
||||
|
||||
@ -198,17 +186,6 @@ void textures_dump_atlases(void) {
|
||||
}
|
||||
|
||||
|
||||
bool strends(const char *str, const char *suffix) {
|
||||
size_t str_length = SDL_strlen(str);
|
||||
size_t suffix_length = SDL_strlen(suffix);
|
||||
|
||||
if (suffix_length > str_length)
|
||||
return false;
|
||||
|
||||
return SDL_memcmp((str + str_length) - suffix_length, suffix, suffix_length) == 0;
|
||||
}
|
||||
|
||||
|
||||
/* TODO: have our own */
|
||||
Rect rect_overlap(const Rect a, const Rect b) {
|
||||
SDL_FRect a_sdl = { a.x, a.y, a.w, a.h };
|
||||
@ -229,43 +206,12 @@ bool rect_intersects(const Rect a, const Rect b) {
|
||||
}
|
||||
|
||||
|
||||
Vec2 rect_center(Rect rect) {
|
||||
return (Vec2){
|
||||
.x = rect.x + rect.w / 2,
|
||||
.y = rect.y + rect.h / 2,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
int32_t timer_tick_frames(int32_t frames_left) {
|
||||
SDL_assert(frames_left >= 0);
|
||||
return MAX(frames_left - 1, 0);
|
||||
}
|
||||
|
||||
|
||||
float timer_tick_seconds(float seconds_left) {
|
||||
SDL_assert(seconds_left >= 0);
|
||||
return MAX(seconds_left - ((float)ctx.delta_time / (float)ctx.clocks_per_second), 0.0f);
|
||||
}
|
||||
|
||||
|
||||
TimerElapseFramesResult timer_elapse_frames(int32_t frames_left, int32_t interval) {
|
||||
SDL_assert(frames_left >= 0);
|
||||
SDL_assert(interval > 0);
|
||||
|
||||
frames_left -= 1;
|
||||
bool elapsed = false;
|
||||
if (frames_left <= 0) {
|
||||
elapsed = true;
|
||||
frames_left += interval;
|
||||
}
|
||||
return (TimerElapseFramesResult) {
|
||||
.elapsed = elapsed,
|
||||
.frames_left = frames_left
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
TimerElapseSecondsResult timer_elapse_seconds(float seconds_left, float interval) {
|
||||
SDL_assert(seconds_left >= 0);
|
||||
SDL_assert(interval > 0);
|
||||
@ -278,7 +224,8 @@ TimerElapseSecondsResult timer_elapse_seconds(float seconds_left, float interval
|
||||
}
|
||||
return (TimerElapseSecondsResult) {
|
||||
.elapsed = elapsed,
|
||||
.seconds_left = seconds_left
|
||||
.seconds_left = seconds_left,
|
||||
.interval = interval,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,8 @@ _Noreturn void die_abruptly(void);
|
||||
/* note: you must free the returned string */
|
||||
char *expand_asterisk(const char *mask, const char *to);
|
||||
|
||||
void profile_list_stats(void);
|
||||
|
||||
/* http://www.azillionmonkeys.com/qed/sqroot.html */
|
||||
static inline float fast_sqrt(float x)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user