Adding C code
You can add your own C code to a game, for example to call a C library or to write a hot loop in C, and call it from your BT scripts. The C code is built into the game: players still get one executable (or a web build), with no extra DLL or SO to ship.
C code has full access to the player's computer. It is compiled against your exact copy of cTurtle, so rebuild it whenever you update cTurtle.
What you need
- The C toolchain for desktop builds and for running the game in Studio.
- Nothing extra for web builds; the web SDK compiles your C.
Only C (.c files) is supported.
1. Write the C file
Your C code provides one function, ct_game_module_v1, that tells cTurtle
what to call. This example adds a BT package my-game/math with one function,
lerp:
// native/game.c
#include "engine/game_module.h"
// Called from BT as lerp(a, b, t).
static BtVmOpResult native_lerp(BtRuntime* runtime, void* context,
const BtValue* args, size_t count) {
(void)runtime; (void)context; (void)count;
double a = args[0].real, b = args[1].real, t = args[2].real;
return (BtVmOpResult){.status = BT_VM_OP_SUCCESS,
.value.real = a + (b - a) * t};
}
static BtResult register_math(BtHostRegistry* registry, void* context) {
(void)context;
static const BtLangHostParameterDecl params[] = {
{.name = "a", .type = "float"},
{.name = "b", .type = "float"},
{.name = "t", .type = "float"},
};
return bt_host_register_native(registry, &(BtNativeDef){
.name = "lerp", .return_type = "float",
.parameters = params, .parameter_count = 3,
.callback = native_lerp});
}
static const BtNativeModule math_package = {
.name = "my-game/math",
.register_natives = register_math,
};
static const BtNativeModule* const packages[] = {&math_package};
static CtResult initialize(Game* game) {
(void)game; // register managers and ECS systems here
return (CtResult){.status = CT_STATUS_OK};
}
static CtResult prepare(void) { return (CtResult){.status = CT_STATUS_OK}; }
static void unprepare(void) {}
CT_GAME_MODULE_EXPORT const CtGameModuleV1* ct_game_module_v1(void) {
static const CtGameModuleV1 module = {
.abi_version = 1,
.struct_size = sizeof(CtGameModuleV1),
.sdk_id = CT_NATIVE_SDK_ID,
.host_identity = ct_native_runtime_identity,
.prepare = prepare,
.initialize = initialize,
.unprepare = unprepare,
.bt_modules = packages,
.bt_module_count = 1,
};
return &module;
}
Scripts then use it like any other package:
include "my-game/math";
fn halfway(float a, float b) -> float {
return lerp(a, b, 0.5);
}
The module fields
| Field | What to set |
|---|---|
abi_version | 1 |
struct_size | sizeof(CtGameModuleV1) |
sdk_id | CT_NATIVE_SDK_ID |
host_identity | ct_native_runtime_identity |
prepare | Optional. Runs once before the game's assets load. Register things the assets need, such as custom shaders. Undo all of it in unprepare. |
initialize | Required. Runs once per game start, after assets load and before your scripts start. Register managers, ECS systems and other per-game setup. |
unprepare | Runs when the code is unloaded. Undo everything prepare did and stop any threads you started. Needed for reloading in Studio; if it is missing (nullptr), Studio can't reload your code without restarting. |
bt_modules, bt_module_count | The BT packages your C code adds, which scripts include. |
A package's register_natives function must only register functions, types
and constants. It also runs while your game is being built, to check your
scripts, so it must not touch game state. Put setup in initialize, or in the
package's optional attach function.
2. List the C files in game.json
"native": {
"sources": ["native/game.c"],
"include_directories": ["native/include"],
"compile_definitions": ["USE_FAST_PATH=1"],
"link_libraries": { "linux": ["m"], "windows": ["ws2_32"] }
}
| Field | Type | Default | Description |
|---|---|---|---|
sources | array of strings | required | Your .c files, relative to the project. |
include_directories | array of strings | [] | Extra include folders. The project folder and cTurtle's headers are always included. |
compile_definitions | array of strings | [] | Preprocessor definitions. CT_GAME_MODULE=1 is always defined. |
link_libraries | object | {} | System libraries to link, per platform (windows, linux). Ignored for web builds. |
Full field list: game.json native.
Every build of the game now compiles these files into the executable, in any code form. Desktop builds need the C toolchain; Studio uses its Native Toolchain settings.
3. Run it in Studio
Studio's Play runs your C code as a separate library (DLL on Windows, SO on Linux) that it can rebuild and reload without restarting Studio. You set this up with a small CMake project in your project folder:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
project(MyGame C)
set(CMAKE_C_STANDARD 23)
find_package(cTurtleNative CONFIG REQUIRED
PATHS "${CTURTLE_NATIVE_SDK}" NO_DEFAULT_PATH)
add_library(game MODULE native/game.c)
target_include_directories(game PRIVATE native/include)
target_link_libraries(game PRIVATE cturtle_runtime)
set_target_properties(game PROPERTIES
PREFIX "" OUTPUT_NAME my_game
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/native")
and tell the game where the built library is:
"native": {
"sources": ["native/game.c"],
"windows": "native/my_game.dll",
"linux": "native/my_game.so",
"build_target": "game"
}
Link the library to cturtle_runtime only, never to anything else from
cTurtle. Add the built native/my_game.dll / .so to .gitignore; it is
never shipped.
In Studio, Build (with Project selected in the toolbar) or Play builds the library and loads it. Compiler errors appear in Output and Problems.
Reloading
After a successful build, Studio reloads your C code and restarts the running game. Game state resets; nothing in C memory is carried over. If the new code fails to start, Studio goes back to the previous version.
For reloading to work:
- Implement
unprepareand undo everythingpreparedid. - Don't keep pointers to a
Gamein global variables between runs.
Changes to cTurtle itself need a Studio restart and a rebuild of your C code.
Web builds
Your C code is compiled into the web build automatically. On the web, use the Source code form if scripts call your C functions; see Web.
Errors
| Message | What to do |
|---|---|
Native SDK requires ... | Compiler or configuration mismatch. See toolchain errors. |
SDK/build mismatch; rebuild the module against this host's SDK | The library was built with a different copy of cTurtle. Rebuild it. |
a second engine runtime was linked; link the module to cturtle_runtime | Your CMake project links cTurtle libraries other than cturtle_runtime. Remove them. |
missing per-game initialize callback | Set initialize in your module. |
unsupported module ABI | Set abi_version = 1 and struct_size = sizeof(CtGameModuleV1). |
manifest ... has invalid settings when running the project in Studio | native needs the library path for your platform (native.windows or native.linux) to run outside an export. |
A script fails with an unknown function after include "my-game/..." in a web Bytecode build | Use the Source code form for that web target. |