This page shows a small player controller that reads input, shoots a physics raycast, and spawns an actor from a key press.
The engine does not currently expose a desktop input manager. Use GLFW for desktop keys and Input::XrInputHandler for OpenXR controller values.
APIs Used
| Task | API |
| Desktop key state | glfwGetKey(GLFWwindow*, GLFW_KEY_*) |
| VR trigger value | Input::XrInputHandler::getTriggerValue(Input::Hand) |
| Physics engine | Engine::Subsystems::get().physicsEngine() |
| Raycast | Engine::Core::PhysicsEngine::raycastClosest(origin, direction, maxDistance, hit) |
| Mesh asset reference | Engine::Assets::MeshAssetRef |
| Spawn actor | Engine::Entities::Scene::spawnActor<T>(transform, args...) |
Controller Actor
Pass the mirror window into the controller when the scene spawns it. The engine does not expose a desktop input manager, so the controller needs the raw GLFWwindow* to poll key state with glfwGetKey(). The actor polls keys in tick() and edge-detects key presses so actions run once per press.
#pragma once
#include "Engine/Entity/Actor.h"
#include "Engine/Entity/SpawnContext.h"
#include "Engine/Files/AssetPath.h"
#include "Engine/Mesh/MeshAssetRef.h"
#include <GLFW/glfw3.h>
{
public:
SmallPlayerController(
GLFWwindow* window
);
void tick(
double deltaTimeSeconds)
override;
private:
void handleInput();
void shootRaycastAndSpawn();
void spawnCubeAt(const glm::vec3& position);
GLFWwindow* window_ = nullptr;
bool fireWasDown_ = false;
};
An Actor is similar to an Engine::Entities::Entity. An actor is an Entity with a transform.
virtual void beginPlay()
Gets executed when the actor is spawned into the world.
void tick(double deltaTime) override
Executed every frame.
Asset::Ref< Asset::MeshPath, Mesh > MeshAssetRef
Asset reference to mesh geometry loaded from a mesh asset path.
Identifies a named mesh-like asset inside an asset file.
Scene ownership data passed to actor and component constructors during spawning.
#include "SmallPlayerController.h"
#include "Engine/Core/Engine.h"
#include "Engine/Entity/Scene.h"
#include "Engine/Entity/SpawnContext.h"
#include "Engine/Entity/StaticMeshActor.h"
#include "Engine/Files/AssetPath.h"
#include "Engine/Files/Path.h"
#include "Engine/Mesh/AssetManager.h"
#include "Engine/Physics/PhysicsEngine.h"
#include "Engine/Subsystems/Subsystems.h"
#include "Engine/World/Transform.h"
#include <glm/gtc/matrix_transform.hpp>
SmallPlayerController::SmallPlayerController(
GLFWwindow* window
) : Actor(context), window_(window)
{
constexpr static auto cubeMeshAssetPath = CREATE_ASSET_PATH(
);
cubeMeshAssetPath.fsPath(),
"Cube"
);
allowTicking_ = true;
enableTick(true);
}
void SmallPlayerController::beginPlay()
{
Actor::beginPlay();
auto* scene = getOwningScene();
auto* assets = scene ? scene->getAssetManager() : nullptr;
if (!assets) {
return;
}
}
void SmallPlayerController::tick(double deltaTimeSeconds)
{
Actor::tick(deltaTimeSeconds);
handleInput();
}
void SmallPlayerController::handleInput()
{
if (!window_) {
return;
}
const bool fireIsDown = glfwGetKey(window_, GLFW_KEY_F) == GLFW_PRESS;
if (fireIsDown && !fireWasDown_) {
shootRaycastAndSpawn();
}
fireWasDown_ = fireIsDown;
}
void SmallPlayerController::shootRaycastAndSpawn()
{
if (!physics) {
return;
}
const glm::vec3 origin = getActorLocation();
const glm::vec3 direction = glm::normalize(
glm::vec3(getWorldTransform() * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f))
);
if (physics->raycastClosest(origin, direction, 100.0f, hit)) {
return;
}
spawnCubeAt(origin + direction * 3.0f);
}
void SmallPlayerController::spawnCubeAt(const glm::vec3& position)
{
auto* scene = getOwningScene();
auto* assets = scene ? scene->getAssetManager() : nullptr;
if (!assets) {
return;
}
if (!cubeMeshRef_.isLoaded()) {
return;
}
}
The mesh asset stores geometry data and.
static constexpr auto engineGeometry()
Path to engine geometry assets.
Wrapper for entt component creation which ensures types safety and attaches the correct component wit...
static Subsystems & get()
Core::PhysicsEngine * physicsEngine() const
Result data for physics raycasts and sweep tests.
glm::vec3 normal
World-space surface normal at the impact point.
glm::vec3 point
World-space impact point.
Engine::Assets::MeshAssetRef keeps a stable handle to the mesh record while the model loads asynchronously. getAsset<Engine::Assets::Mesh>() returns that ref and requests the backing model when needed. Use isLoaded() on the returned ref before spawning when the actor needs the mesh immediately. StaticMeshActor takes Asset::MeshPath, so the ref is used for readiness and the mesh path is passed to the actor constructor.
Scene Setup
The engine creates the GLFW window before the game module is initialized. GameModule::init(GLFWwindow*) receives a pointer to it for convenience, and GameModule::getWindow() returns that pointer. Scenes do not own the window; pass it through the scene into the controller when spawning the actor so SmallPlayerController can read desktop keys.
{
public:
explicit DemoScene(GLFWwindow* window) : window_(window) {}
{
}
private:
GLFWwindow* window_ = nullptr;
};
{
return new DemoScene(getWindow());
}
Owns scene entities, actor lifetime, and the scene graph root.
virtual void loadContent()
Loads scene content. Override in derived scenes.
T * spawnActor(const Ecs::Transform &spawnTransform, Args &&... args)
Creates an actor at a world transform and calls beginPlay().
VR Trigger Variant
For VR input, read the trigger value from Input::XrInputHandler. Use the same edge-detection pattern.
const bool triggerIsDown =
if (triggerIsDown && !triggerWasDown_) {
shootRaycastAndSpawn();
}
triggerWasDown_ = triggerIsDown;
Input::XrInputHandler & getXrInputHandler() const
Returns the XR input handler.
static EngineManager & getInstance()
Returns the singleton engine manager instance.
EngineKern * getEngineModule()
Returns the active engine.
Use Input::XrInputHandler::getHandPoseInfo(Input::Hand::RIGHT) when the ray should come from the controller aim pose instead of the player actor transform.
Notes
- spawnActor<T>() calls beginPlay() before returning the actor pointer.
- Set allowTicking_ = true before enableTick(true) for actors that need tick().
- Request reusable assets in beginPlay() and store an Asset::Ref for readiness checks.
- Use Asset::MeshPath for named objects inside glTF files.
- Use Asset::Path for single-file assets such as audio clips and standalone textures.
- StaticMeshActor creates a CollidableMesh component for the mesh path.
- The ray only hits objects registered in Bullet's dynamics world.
- Use key edge detection for spawning. Polling GLFW_PRESS directly spawns one actor every frame while the key is held.