#Complete Examples
#Player Controller
WASD movement with physics, jumping, and animation switching:
#include "Core/Behavior.h"
#include "Core/Components.h"
#include "Core/Engine.h"
#include <GLFW/glfw3.h>
namespace SafiEngine {
class PlayerBehavior : public Behavior {
public:
void OnStart(flecs::entity e, Engine &engine) override {
if (auto *anim = e.get_mut<Animator>()) {
anim->clipIndex = m_IdleClip;
}
}
void OnUpdate(flecs::entity e, Engine &engine, float dt) override {
auto &input = engine.GetInput();
float speed = 6.0f;
float vx = 0.0f, vz = 0.0f;
if (input.IsKeyDown(GLFW_KEY_W)) vz -= speed;
if (input.IsKeyDown(GLFW_KEY_S)) vz += speed;
if (input.IsKeyDown(GLFW_KEY_A)) vx -= speed;
if (input.IsKeyDown(GLFW_KEY_D)) vx += speed;
bool moving = (vx != 0.0f || vz != 0.0f);
// Animation
if (auto *anim = e.get_mut<Animator>()) {
int desired = moving ? m_WalkClip : m_IdleClip;
if (anim->clipIndex != desired) {
anim->clipIndex = desired;
anim->time = 0.0f;
anim->playing = true;
}
}
// Physics-based movement (preserves gravity)
engine.GetPhysicsWorld().SetHorizontalVelocity(e.id(), vx, vz);
// Jump
if (input.IsKeyPressed(GLFW_KEY_SPACE)) {
engine.GetPhysicsWorld().SetLinearVelocity(e.id(), vx, 8.0f, vz);
}
}
private:
int m_IdleClip = 0;
int m_WalkClip = 1;
};
void RegisterPlayerBehavior() {
BehaviorRegistry::Register(
"Player", [] { return std::make_shared<PlayerBehavior>(); });
}
} // namespace SafiEngine#Follow Camera
Configurable camera that follows any named entity using CameraUtils:
#include "Core/Behavior.h"
#include "Core/Components.h"
#include "Core/Engine.h"
#include <cstdlib>
#include <glm/glm.hpp>
namespace SafiEngine {
class FollowCameraBehavior : public Behavior {
public:
void OnStart(flecs::entity e, Engine &engine) override {
const auto *ns = e.get<NativeScript>();
if (!ns) return;
auto it = ns->properties.find("target");
if (it != ns->properties.end() && !it->second.empty())
m_TargetName = it->second;
it = ns->properties.find("offsetX");
if (it != ns->properties.end()) m_Offset.x = std::atof(it->second.c_str());
it = ns->properties.find("offsetY");
if (it != ns->properties.end()) m_Offset.y = std::atof(it->second.c_str());
it = ns->properties.find("offsetZ");
if (it != ns->properties.end()) m_Offset.z = std::atof(it->second.c_str());
it = ns->properties.find("smoothing");
if (it != ns->properties.end()) m_Smoothing = std::atof(it->second.c_str());
}
void OnUpdate(flecs::entity e, Engine &engine, float dt) override {
auto target = engine.GetWorld().lookup(m_TargetName.c_str());
if (!target.is_valid() || !target.has<Transform>()) return;
auto *cam = e.get_mut<Transform>();
if (!cam) return;
const auto *tgt = target.get<Transform>();
auto &utils = engine.GetCameraUtils();
glm::vec3 desired = tgt->position + m_Offset;
utils.SmoothFollow(*cam, desired, m_Smoothing, dt);
utils.SmoothLookAt(*cam, tgt->position, m_Smoothing * 2.0f, dt);
}
private:
std::string m_TargetName = "Player";
glm::vec3 m_Offset{0.0f, 5.0f, 8.0f};
float m_Smoothing = 5.0f;
};
void RegisterFollowCameraBehavior() {
BehaviorRegistry::Register(
"FollowCamera", [] { return std::make_shared<FollowCameraBehavior>(); });
}
} // namespace SafiEngineProperties (set in Inspector):
target- entity name to follow (default: "Player")offsetX,offsetY,offsetZ- camera offsetsmoothing- follow speed
#Rotating Object
Spin an entity continuously:
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
}
};#Light Flicker
Make a point light flicker like a candle:
class FlickerBehavior : public Behavior {
public:
void OnUpdate(flecs::entity e, Engine &engine, float dt) override {
auto *pl = e.get_mut<PointLight>();
if (!pl) return;
m_Timer += dt;
float noise = std::sin(m_Timer * 15.0f) * 0.3f
+ std::sin(m_Timer * 7.3f) * 0.2f;
pl->intensity = m_BaseIntensity + noise;
}
private:
float m_Timer = 0.0f;
float m_BaseIntensity = 1.5f;
};#Cinematic Camera Switcher
Switch between cameras on a timer:
class CinematicDirector : public Behavior {
public:
void OnStart(flecs::entity e, Engine &engine) override {
const auto *ns = e.get<NativeScript>();
if (ns) {
auto it = ns->properties.find("cam1");
if (it != ns->properties.end()) m_CamNames[0] = it->second;
it = ns->properties.find("cam2");
if (it != ns->properties.end()) m_CamNames[1] = it->second;
it = ns->properties.find("cam3");
if (it != ns->properties.end()) m_CamNames[2] = it->second;
it = ns->properties.find("switchTime");
if (it != ns->properties.end()) m_SwitchTime = std::atof(it->second.c_str());
}
SwitchToCamera(engine, 0);
}
void OnUpdate(flecs::entity e, Engine &engine, float dt) override {
m_Timer += dt;
if (m_Timer > m_SwitchTime) {
m_Timer = 0.0f;
m_CurrentCam = (m_CurrentCam + 1) % 3;
SwitchToCamera(engine, m_CurrentCam);
}
}
private:
void SwitchToCamera(Engine &engine, int index) {
if (m_CamNames[index].empty()) return;
auto cam = engine.GetWorld().lookup(m_CamNames[index].c_str());
if (cam.is_valid()) engine.SetMainCamera(cam);
}
std::string m_CamNames[3] = {"CineCam1", "CineCam2", "CineCam3"};
float m_SwitchTime = 5.0f;
float m_Timer = 0.0f;
int m_CurrentCam = 0;
};Properties: cam1, cam2, cam3 (camera entity names), switchTime (seconds between switches).