Getting Started

Your First Script

Let's create a behavior that rotates an entity every frame.

Step 1: Create the File

Create SafiEngine/Core/src/behaviors/SpinBehavior.cpp:

#include "Core/Behavior.h"
#include "Core/Components.h"
#include "Core/Engine.h"

namespace SafiEngine {

class SpinBehavior : public Behavior {
public:
  void OnUpdate(flecs::entity e, Engine &engine, float dt) override {
    auto *t = e.get_mut<Transform>();
    if (!t)
      return;

    t->rotation.y += 90.0f * dt; // 90 degrees per second
  }
};

void RegisterSpinBehavior() {
  BehaviorRegistry::Register(
      "Spin", [] { return std::make_shared<SpinBehavior>(); });
}

} // namespace SafiEngine

Step 2: Register the Behavior

In SafiEngine/Core/src/Behavior.cpp, add the forward declaration and call:

void RegisterSpinBehavior(); // add this line

void BehaviorRegistry::RegisterAll() {
  RegisterPlayerBehavior();
  RegisterSpinBehavior(); // add this line
}

Step 3: Add to CMake

In SafiEngine/Core/CMakeLists.txt, add the source file:

src/behaviors/SpinBehavior.cpp

Step 4: Rebuild

cmake --build SafiEngine/build

Step 5: Attach in the Editor

  1. Select an entity in the Scene Hierarchy
  2. Click Add Component > NativeScript
  3. Select Spin from the Behavior dropdown
  4. Press Play and watch the entity rotate

Using the "New Behavior" Button

The Inspector's NativeScript component has a New Behavior button that generates a template .cpp file in your project's assets/scripts/ directory. This gives you the boilerplate, but you still need to add the file to CMake and rebuild.

Anatomy of a Behavior

#include "Core/Behavior.h"    // Behavior base class, NativeScript, BehaviorRegistry
#include "Core/Components.h"  // Transform, Camera, Animator, lights, etc.
#include "Core/Engine.h"      // Engine class with all subsystem access

#include <GLFW/glfw3.h>       // Key constants (GLFW_KEY_W, etc.)

namespace SafiEngine {

class MyBehavior : public Behavior {
public:
  void OnStart(flecs::entity e, Engine &engine) override {
    // Runs once when Play is pressed
    // Good for: reading properties, caching references, initial setup
  }

  void OnUpdate(flecs::entity e, Engine &engine, float dt) override {
    // Runs every frame
    // dt = time since last frame in seconds
    // Good for: input, movement, game logic
  }

private:
  // Per-instance state (each entity gets its own instance)
  float m_Timer = 0.0f;
  int m_Score = 0;
};

// Registration function (called from BehaviorRegistry::RegisterAll)
void RegisterMyBehavior() {
  BehaviorRegistry::Register(
      "My", [] { return std::make_shared<MyBehavior>(); });
}

} // namespace SafiEngine
Tip

Each entity gets its own behavior instance. Private member variables are per-entity state that persists across frames during a play session. State is reset when you stop and start play mode.

Common Includes

IncludeProvides
"Core/Behavior.h"Behavior, NativeScript, BehaviorRegistry
"Core/Components.h"Transform, Camera, ModelRenderer, Animator, lights
"Core/Engine.h"Engine class
"Core/PhysicsComponents.h"RigidBody, Collider
"Core/AudioComponents.h"AudioSource, AudioListener
"Core/ParticleSystem.h"ParticleEmitter
<GLFW/glfw3.h>Key and mouse button constants
<glm/glm.hpp>glm::vec3, glm::mix, math functions