Skip to main content

Spaceship Control Example

This example demonstrates the complete GameLoop architecture with phase-based execution, the GameObjectFactory builder pattern, component-based game systems, and ImGui debug overlay integration.

Features

  • GameLoop Architecture - Phase-based game loop (Pre/Main/Post) with commit points
  • GameObjectFactory - Fluent builder pattern for creating GameObjects with components
  • Component System - Composition-based entity design with Move2DComponent, SteeringComponent
  • Gamepad Input - Control a spaceship using analog sticks via TwinStickInputSystem
  • ImGui Debug Overlay - Real-time logging, camera control, physics tuning via dockable widgets
  • Frame Pacing - Configurable target FPS with performance metrics
  • Scene Graph Camera - Camera follows spaceship using transform inheritance
  • Units System - Object sizes defined in meters (1 hu = 1 m)

Building

cmake -S . -B build
cmake --build build --target spaceship_control

Running

./build/bin/spaceship_control

Controls

InputAction
Left StickMove spaceship / Rotate
ESCExit application

Code Structure

FilePurpose
main.cppApplication entry point and game loop
SpaceshipWidget.ixxImGui widget for physics parameter tuning

Components Used

ComponentPurpose
SceneNodeComponentLinks GameObject to scene graph
Move2DComponent2D physics with velocity, acceleration, dampening
SteeringComponentRotation control and direction management
DirectionComponentMovement direction vector
ComposeTransformComponentLocal/world transform composition
TranslationStateComponentPosition state tracking
RotationStateComponentRotation state tracking
ScaleStateComponentUnit-based sizing (meters)

Systems Used

SystemPurpose
TwinStickInputSystemTranslates gamepad input to movement commands
ScaleSystemApplies ScaleStateComponent sizing
SteeringSystemRotation and direction updates
Move2DSystemPhysics simulation (velocity integration)
ComposeTransformSystemComputes final transforms
SceneSyncSystemSyncs transforms to scene graph
TransformClearSystemClears dirty flags post-frame

GameObjectFactory Builder

The example uses the fluent builder pattern for GameObject creation:

auto shipGameObject = GameObjectFactory::instance()
.gameObject(gameWorld)
.withRendering([&](auto& rnb) {
rnb.meshRenderable()
.shader(shader)
.color(Colors::Yellow)
.primitiveType(PrimitiveType::LineLoop)
.shape(std::make_shared<Triangle>())
.attachTo(&root);
})
.withTransform([](auto& tb) {
tb.transform()
.scale(vec3f(SPACESHIP_SIZE, SPACESHIP_SIZE, 0.0f));
})
.withMotion([](auto& mcb) {
mcb.move2D()
.speed(30.0f)
.instantAcceleration(false);
mcb.steering()
.steeringSetsDirection(true)
.instantSteering(false);
})
.make();

GameLoop Phase Configuration

// Pre-Phase: Input and Physics
gameLoop.phase(PhaseType::Pre)
.addPass()
.addSystem<TwinStickInputSystem>(shipGameObject)
.addCommitPoint(CommitPoint::Structural)
.addPass()
.addSystem<ScaleSystem>()
.addSystem<SteeringSystem>()
.addSystem<Move2DSystem>();

// Post-Phase: Transform and Rendering
gameLoop.phase(PhaseType::Post)
.addPass()
.addSystem<ComposeTransformSystem>()
.addSystem<SceneSyncSystem>(scene.get())
.addSystem<TransformClearSystem>();

Camera System

The camera is attached to the spaceship in the scene graph and uses selective transform inheritance:

// Create camera as child of spaceship
auto cameraNode = std::make_unique<CameraSceneNode>(std::move(camera));
shipSceneNode->addNode(std::move(cameraNode));

// Inherit only position, not rotation
cameraNode->setInheritance(TransformType::Translation);
cameraNode->lookAtLocal(vec3f(0.0f, 0.0f, 0.0f), vec3f(0.0f, 1.0f, 0.0f));

ImGui Integration

The example includes a complete ImGui setup with multiple widgets:

auto imguiBackend = ImGuiGlfwOpenGLBackend(window->nativeHandle());
auto imguiOverlay = ImGuiOverlay::forBackend(&imguiBackend);

imguiOverlay.addWidget(new MainMenuWidget());
imguiOverlay.addWidget(new FpsWidget(&metrics, &framePacer));
imguiOverlay.addWidget(new GamepadWidget(&inputManager));
imguiOverlay.addWidget(new LogWidget());
imguiOverlay.addWidget(new CameraWidget());
imguiOverlay.addWidget(new SpaceshipWidget());

Main Game Loop

while (!win->shouldClose()) {
framePacer.beginFrame();

// Process events and input
app->eventManager().dispatchAll();
inputManager.poll(0.0f);

// Update game logic via GameLoop
const auto inputSnapshot = InputSnapshot(gamepadState);
gameLoop.update(gameWorld, deltaTime, inputSnapshot);

// Render scene
const auto& snapshot = scene->createSnapshot(*mainViewport);
if (snapshot.has_value()) {
auto renderPass = RenderPassFactory::getInstance().buildRenderPass(*snapshot);
app->renderingDevice().render(renderPass);
}

// Render ImGui
imguiOverlay.render();

// Swap and sync
win->swapBuffers();
frameStats = framePacer.sync();
}