Native plugins (C, C++)
For native code, game systems and component types that ship with the game, write the plugin in C or C++ against the Plugin SDK: a plain C ABI and a header-only C++ layer, with any compiler and C runtime.
When to go native
C# plugins are editor extensions. A plugin needs a native module when it adds runtime parts, which load in the player and the editor's Play mode and ship with the game: component types (with fields in the Inspector and the scene files) and systems in the game's system graph. Native code is also the way to wrap an existing C or C++ library.
The C++ layer
Derive from kroys::Plugin, register in OnLoad through kroys::Host, and export the module with KROYS_PLUGIN. Panels and commands work like their C# counterparts; the UI has the same controls.
HelloPlugin.cpp
class Hello final : public kroys::Plugin { public: bool OnLoad(kroys::Host& host) override { host_ = &host; kroys::CommandOptions greet{ "Greet", "Say Hello" }; greet.toolbar = true; kroys::PanelOptions panel{ "Panel", "Hello" }; return host.AddCommand(greet, [this] { host_->Notify(KROYS_SEVERITY_INFO, "Hello", text_); }) != 0u && host.AddPanel(panel, [this](const kroys::Ui& ui, const KroysDrawArgs&) { if (kroys::Ui::Section section{ ui, "Greeting" }) { ui.InputText("Text", text_); if (ui.Button("Say Hello", KROYS_UI_BUTTON_PRIMARY)) host_->Execute("Hello.Greet"); } }) != 0u; } private: kroys::Host* host_ = nullptr; std::string text_ = "Hello!"; }; KROYS_PLUGIN(Hello, "Hello", "1.0.0", KROYS_TARGET_EDITOR | KROYS_TARGET_RUNTIME)
Runtime components and systems
A runtime part registers component types with ComponentTypeBuilder (typed fields with defaults, ranges and labels) and systems with AddSystem (a phase, what it reads and writes, and a tick callback). Systems iterate entities with World::Query.
Spinner.cpp
// A runtime component and a system that turns every entity carrying it. Runtime parts load in // the editor (inspector, Play mode) and ship with the packaged game. kroys::ComponentType RegisterSpinner(kroys::Host& host) { const kroys::ComponentType spinner = host.AddComponentType(kroys::ComponentTypeBuilder("Spinner", "Spinner", "Gameplay") .Description("Turns the entity around its Y axis while the game runs.") .Float("degreesPerSecond", 90.0f, "Degrees Per Second", "Turn speed.").Range(-720.0f, 720.0f)); if (spinner == 0u) return 0u; kroys::SystemOptions spin; spin.name = "Spin"; spin.phase = KROYS_PHASE_UPDATE; spin.reads = { "Hello.Spinner" }; spin.writes = { "TransformComponent" }; host.AddSystem(spin, [spinner](const kroys::World& world, const KroysTickArgs& args) { for (const kroys::Entity entity : world.Query(spinner)) { std::optional<KroysTransform> transform = world.Transform(entity); if (!transform) continue; transform->rotationDegrees[1] += world.GetFloat(entity, spinner, "degreesPerSecond") * args.deltaSeconds; world.SetTransform(entity, *transform); } }); return spinner; }
The C ABI
Under the C++ layer is a plain C ABI: two exported functions, versioned function tables and structs that carry their own size. Nothing is freed across the boundary and no exception may leave a callback. A plugin built against an older 2.x SDK keeps loading on a newer engine.
HelloPlugin.c
static const KroysHost* gHost = 0; static void KROYS_CALL Greet(void* userData) { (void)userData; gHost->core->Log(gHost->context, KROYS_SEVERITY_INFO, "Hello from C."); } KROYS_PLUGIN_EXPORT int32_t KROYS_CALL KroysPlugin_GetModuleInfo(KroysModuleInfo* info) { if (info == 0) return 0; info->structSize = (uint32_t)sizeof(KroysModuleInfo); info->targets = KROYS_TARGET_EDITOR; info->sdkVersion.major = KROYS_SDK_VERSION_MAJOR; info->sdkVersion.minor = KROYS_SDK_VERSION_MINOR; info->sdkVersion.patch = KROYS_SDK_VERSION_PATCH; info->pluginId = "HelloC"; info->pluginVersion = "1.0.0"; return 1; } KROYS_PLUGIN_EXPORT KroysResult KROYS_CALL KroysPlugin_Start(const KroysHost* host, KroysPluginCallbacks* callbacks) { KroysCommandDesc command; KroysCommandCallbacks commandCallbacks; KroysHandle handle = 0u; (void)callbacks; if (host == 0 || host->editor == 0) return KROYS_E_UNSUPPORTED; gHost = host; memset(&command, 0, sizeof(command)); command.structSize = (uint32_t)sizeof(command); /* lets newer engines read older plugins */ command.id = "Greet"; command.label = "Say Hello (C)"; memset(&commandCallbacks, 0, sizeof(commandCallbacks)); commandCallbacks.structSize = (uint32_t)sizeof(commandCallbacks); commandCallbacks.Execute = &Greet; return host->editor->RegisterCommand(host->context, &command, &commandCallbacks, &handle); }
Build it
Export the SDK once, then build the plugin outside the engine with any compiler, /MT or /MD, Debug or Release:
Plugins/Samples/ExternalSample in the engine repository is such a project. See the C/C++ plugin SDK reference for every table and class.
PowerShell
Tools/ExportPluginSdk.ps1 -Destination C:\KroysSDK cmake -S MyPlugin -B MyPlugin/Build -DCMAKE_PREFIX_PATH=C:\KroysSDK cmake --build MyPlugin/Build --config Release
CMakeLists.txt
find_package(KroysPluginSDK 2.1 REQUIRED)
kroys_add_plugin_module(MyPlugin MANIFEST MyPlugin.kplugin SOURCES Source/MyPlugin.cpp
OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/MyPlugin")Isolation
Every call into a native plugin runs under a structured-exception guard. A plugin that crashes (access violation, stack overflow, an exception escaping it) is disabled for the session: its registrations turn inert, its panels show the reason, and the editor keeps running.
KroysEngine 0.1.0 documentation, built from the engine repository by Tools/BuildDocsSite.py.