Skip to main content

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​

FieldWhat to set
abi_version1
struct_sizesizeof(CtGameModuleV1)
sdk_idCT_NATIVE_SDK_ID
host_identityct_native_runtime_identity
prepareOptional. Runs once before the game's assets load. Register things the assets need, such as custom shaders. Undo all of it in unprepare.
initializeRequired. Runs once per game start, after assets load and before your scripts start. Register managers, ECS systems and other per-game setup.
unprepareRuns 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_countThe 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"] }
}
FieldTypeDefaultDescription
sourcesarray of stringsrequiredYour .c files, relative to the project.
include_directoriesarray of strings[]Extra include folders. The project folder and cTurtle's headers are always included.
compile_definitionsarray of strings[]Preprocessor definitions. CT_GAME_MODULE=1 is always defined.
link_librariesobject{}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 unprepare and undo everything prepare did.
  • Don't keep pointers to a Game in 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​

MessageWhat to do
Native SDK requires ...Compiler or configuration mismatch. See toolchain errors.
SDK/build mismatch; rebuild the module against this host's SDKThe library was built with a different copy of cTurtle. Rebuild it.
a second engine runtime was linked; link the module to cturtle_runtimeYour CMake project links cTurtle libraries other than cturtle_runtime. Remove them.
missing per-game initialize callbackSet initialize in your module.
unsupported module ABISet abi_version = 1 and struct_size = sizeof(CtGameModuleV1).
manifest ... has invalid settings when running the project in Studionative 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 buildUse the Source code form for that web target.