KROYSDOCS
HomeWhat's newv0.1.0
MANUAL / Extend the editor / Native plugins (C, C++)

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.

Headerskroys/kroys_plugin.h, kroys/plugin.hpp
SDK2.1
Partseditor, runtime, both
01

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.

02

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)
03

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;
}
04

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);
}
05

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")
06

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.