Repository navigation
Making Your First Game
This guide walks through representative source-development patterns for a game module -- from an empty module to gameplay, asset, and editor experiments. It is not an end-to-end packaging or production-game recipe.
Stable-v1 boundary: Stable-v1 is blocked and uncertified. Its only intended product shape is Windows 11 x64 with MSVC v143, D3D11 (or Windows NullRHI), C++ game modules, and one first-party single-player
SparkGameFPSvertical slice. Everything in this tutorial remains development guidance until the applicable installed-package, editor, and gameplay gates pass; other platforms, backends, scripting, collaboration, and multiplayer are experimental or unsupported.
Prerequisites: Complete the Getting Started guide first. You need a working build before proceeding.
SparkEngine loads one selected Game-kind module as a DLL/shared library. There are two distinct setup routes:
-
Installed template: materialize or copy
Templates/EmptyProjectoutside the engine checkout, install the engine SDK, and configure that package withSparkEngine_DIR. A stock template is a standalone installed-SDK project. -
In-tree module: create
GameModules/MyGamewith a CMake target that the repository root auto-discovers. Do not copy a stockTemplates/EmptyProjectpackage directly underGameModules/: itsfind_package(SparkEngine CONFIG REQUIRED)setup expects an installed SDK rather than the source-tree build.
For the in-tree route, start with this layout:
MyGame/
CMakeLists.txt # Build configuration (auto-discovered by root CMake)
Source/
GameModule.h # IModule implementation
GameModule.cpp # DLL entry point + factory exports
GameModules/MyGame/CMakeLists.txt can use the helper already included by the root build:
file(GLOB_RECURSE GAME_SOURCES CONFIGURE_DEPENDS "Source/*.cpp" "Source/*.h")
spark_add_game_module(MyGame ${GAME_SOURCES})
target_include_directories(MyGame PRIVATE "Source")Your module implements Spark::IModule from <Spark/SparkSDK.h>:
#include <Spark/SparkSDK.h>
class MyGameModule : public Spark::IModule
{
public:
Spark::ModuleInfo GetModuleInfo() const override
{
return {"My Game", "1.0.0", SPARK_SDK_VERSION, 1000};
}
bool OnLoad(Spark::IEngineContext* context) override
{
// Called once when the DLL is loaded.
// Store the context -- it's your gateway to all engine services.
m_context = context;
return true;
}
void OnUnload() override { /* cleanup */ }
void OnUpdate(float deltaTime) override
{
// Called every frame. Put your game logic here.
}
void OnRender() override
{
// Called after OnUpdate. Custom rendering (HUD, debug) goes here.
}
private:
Spark::IEngineContext* m_context{nullptr};
};Use the canonical module macro in exactly one .cpp file rather than hand-writing the exports:
#include "GameModule.h"
#include <Spark/ModuleDllMain.h>
SPARK_IMPLEMENT_MODULE(MyGameModule)# From the repository root, for the in-tree route above.
cmake -S . -B build -G "Visual Studio 17 2022" -A x64
cmake --build build --config Release --target SparkEngine MyGame
.\build\bin\Release\SparkEngine.exe -game .\build\bin\Release\MyGame.dllFor Debug or another multi-config configuration, replace both Release path segments with the same configuration name. For the standalone-template route, use the installed-SDK commands in Project Templates instead.
The IEngineContext exposes service accessors provided by the active build; availability varies by configuration:
bool MyGameModule::OnLoad(Spark::IEngineContext* context)
{
m_context = context;
m_graphics = context->GetGraphics(); // Rendering
m_input = context->GetInput(); // Keyboard, mouse, gamepad
m_audio = context->GetAudio(); // Sound playback
m_physics = context->GetPhysics(); // Jolt physics
m_eventBus = context->GetEventBus(); // Event system
m_save = context->GetSaveSystem(); // Save/load
return true;
}Create a first-person player with camera and movement:
#include "Camera/SparkEngineCamera.h"
#include "Input/InputManager.h"
// In your game class:
void MyGame::Initialize()
{
m_camera = std::make_unique<SparkEngineCamera>();
m_camera->SetPosition({0.0f, 2.0f, -5.0f});
m_camera->SetNearFarPlanes(0.1f, 1000.0f);
}
void MyGame::Update(float dt)
{
auto* input = m_context->GetInput();
// WASD movement
float moveX = 0.0f, moveZ = 0.0f;
if (input->IsKeyDown('W')) moveZ += 1.0f;
if (input->IsKeyDown('S')) moveZ -= 1.0f;
if (input->IsKeyDown('A')) moveX -= 1.0f;
if (input->IsKeyDown('D')) moveX += 1.0f;
float speed = 10.0f * dt;
m_camera->MoveForward(moveZ * speed);
m_camera->MoveRight(moveX * speed);
// Mouse look
auto [dx, dy] = input->GetMouseDelta();
m_camera->RotateYaw(dx * 0.002f);
m_camera->RotatePitch(dy * 0.002f);
}For a source reference containing an FPS controller with jumping, sprinting, crouching, health, and weapons, inspect GameModules/SparkGameFPS/Source/Game/Player.h.
#include "Graphics/Mesh.h"
auto cube = std::make_unique<Mesh>();
cube->CreateCube(device, context);
cube->SetPosition({5.0f, 0.5f, 0.0f});
auto sphere = std::make_unique<Mesh>();
sphere->CreateSphere(device, context);
sphere->SetPosition({-5.0f, 1.0f, 0.0f});auto model = std::make_unique<Mesh>();
model->LoadOBJ(device, context, "Assets/Models/crate.obj"); // Wavefront OBJ
model->LoadGLTF(device, context, "Assets/Models/robot.glb"); // glTF binarySparkEngine has a full AI system with behavior trees, perception, and pathfinding. The easiest way is using the Enemy class:
#include "Game/Enemy.h"
// Spawn an enemy
auto enemy = std::make_unique<Enemy>();
enemy->Initialize(device, context, EnemyType::Grunt, m_player.get());
enemy->SetPosition({15.0f, 1.0f, 15.0f});
// Give it patrol waypoints
enemy->SetPatrolPoints({{15, 1, 15}, {15, 1, -5}, {-5, 1, -5}});| Type | HP | Speed | Behavior |
|---|---|---|---|
Grunt |
100 | 5.0 | Patrols and attacks at medium range |
Guard |
150 | 3.0 | Stationary sentry with long detection range |
Scout |
60 | 8.0 | Fast flanker, strafes during combat |
Heavy |
300 | 2.5 | Slow tank, high damage, short range |
Sniper |
70 | 3.5 | Long-range, retreats if player gets close |
Medic |
120 | 5.5 | Heals nearby allies, moderate combat |
For advanced AI, build your own behavior tree:
#include "Engine/AI/BehaviorTree.h"
using namespace Spark::AI;
auto root = std::make_unique<SelectorNode>("Root");
// Combat branch: if enemy visible, engage
auto combat = std::make_unique<SequenceNode>("Combat");
combat->AddChild(std::make_unique<ConditionNode>("SeePlayer",
[](const Blackboard& bb) { return bb.Get<bool>("enemyVisible", false); }));
combat->AddChild(std::make_unique<ActionNode>("Attack",
[this](float dt, Blackboard& bb) -> NodeStatus {
// Your attack logic here
return NodeStatus::Running;
}));
root->AddChild(std::move(combat));
// Fallback: idle
root->AddChild(std::make_unique<ActionNode>("Idle",
[](float, Blackboard&) { return NodeStatus::Running; }));
auto tree = std::make_unique<BehaviorTree>("MyEnemyAI");
tree->SetRoot(std::move(root));For escalating waves of enemies:
#include "Game/WaveSpawner.h"
m_waveSpawner = std::make_unique<Spark::WaveSpawner>();
m_waveSpawner->Initialize(spawnPoints);
m_waveSpawner->SetTotalWaves(20);
m_waveSpawner->SetRestDuration(8.0f);
m_waveSpawner->GetCallbacks().onWaveStart = [](int wave, const std::string& msg) {
// Show wave announcement
};
m_waveSpawner->Start();
// In Update():
m_waveSpawner->Update(dt, GetAliveEnemyCount(), this);#include "Projectiles/ProjectilePool.h"
m_projectilePool = std::make_unique<ProjectilePool>(100);
m_projectilePool->Initialize(device, context);
// Fire a bullet
m_projectilePool->FireProjectile(ProjectileType::BULLET, position, direction, speed);
m_projectilePool->FireRocket(position, direction, 40.0f);
m_projectilePool->FireGrenade(position, direction, 25.0f);
// In Update():
m_projectilePool->Update(dt);
// In Render():
m_projectilePool->Render(viewMatrix, projMatrix);// Load sounds (once, during initialization)
auto* audio = m_context->GetAudio();
audio->LoadSound("gunshot", L"Assets/Sounds/gunshot.wav");
audio->LoadSound("explosion", L"Assets/Sounds/explosion.wav");
audio->LoadSound("ambient", L"Assets/Sounds/forest.wav");
// Play 2D sound (UI, music)
audio->PlaySound("gunshot", 0.8f);
// Play 3D positional sound
audio->PlaySound3D("explosion", explosionPos, 1.0f);
// Set listener position (usually from camera, every frame)
audio->Console_SetListenerPosition(camPos.x, camPos.y, camPos.z);
// Volume controls
audio->SetMasterVolume(0.8f);
audio->SetSFXVolume(1.0f);
audio->SetMusicVolume(0.5f);Physics uses the Jolt integration. Add physics bodies via ECS components:
#include "Engine/ECS/Components/PhysicsComponents.h"
// In your ECS world:
auto entity = world.CreateEntity("Box");
auto& transform = world.AddComponent<TransformComponent>(entity);
transform.position = {0, 10, 0};
auto& body = world.AddComponent<RigidBodyComponent>(entity);
body.type = BodyType::Dynamic;
body.mass = 10.0f;
auto& collider = world.AddComponent<ColliderComponent>(entity);
collider.shape = ColliderShape::Box;
collider.halfExtents = {0.5f, 0.5f, 0.5f};Or use the physics system directly:
auto* physics = m_context->GetPhysics();
physics->RayCast(origin, direction, maxDistance, hitResult);auto& save = Spark::SaveSystem::GetInstance();
save.Initialize("Saves");
// Save current state to slot 1
save.Save(1, world);
// Load from slot 1
save.Load(1, world);Register custom component serializers for your game data:
save.GetSerializerRegistry().Register<MyComponent>(
[](const MyComponent& c) { /* serialize to JSON */ },
[](MyComponent& c, const json& data) { /* deserialize */ });SparkEngine includes a full vehicle system with 9 vehicle types:
#include "Game/VehicleSystem.h"
auto* vehicle = m_vehicleSystem->SpawnVehicle(
SparkEditor::VehicleType::BUGGY, {25.0f, 0.5f, 0.0f}, device, context);
// Vehicles support multiple seats
vehicle->EnterSeat(player, -1); // -1 = first available
vehicle->ExitSeat(player);
// Vehicle weapons fire through the projectile pool automatically
vehicle->SetProjectilePool(m_projectilePool.get());| Vehicle | Type | Seats | Weapon |
|---|---|---|---|
| Buggy | Ground | Driver + Gunner | Machine Gun |
| Tank | Ground | Driver (cannon) + Gunner | Railgun + MG |
| APC | Ground | Driver + 2 Gunners | Machine Guns |
| Motorcycle | Ground | Driver | Pistol |
| Helicopter | Aerial | Pilot + Gunner | Minigun |
| Jet | Aerial | Pilot | Rockets |
| Dropship | Aerial | Pilot + 2 Gunners + 4 Passengers | Machine Guns |
You don't need to write C++ to create content for SparkEngine.
SparkEngine loads these formats directly:
-
3D Models:
.obj(Wavefront),.glb/.gltf(glTF 2.0) -
Textures:
.png,.jpg,.bmp,.tga,.dds -
Audio:
.wav
Place assets in the Assets/ directory:
Assets/
Models/ -- 3D models
Textures/ -- Texture files
Sounds/ -- Audio files
Scenes/ -- Scene files (.scene)
Scripts/ -- AngelScript files (.as)
Start SparkEditor.exe as a separate executable from the matching multi-config output (for example, build\bin\Release\SparkEditor.exe); it is not toggled with F1 from SparkEngine. The editor product is in the intended stable-v1 set but remains blocked and uncertified. Current development panels include:
| Panel | What it does |
|---|---|
| Scene Hierarchy | View and select all objects in the scene |
| Properties | Inspect and edit available entity and component properties |
| Asset Browser | Browse and drag-drop assets into the scene |
| Material Editor | Create and edit PBR materials (albedo, metallic, roughness, normal) |
| Terrain Editor | Sculpt and paint terrain |
| Lighting | Place and configure lights (directional, point, spot) |
| Particle Editor | Create and tune particle effects |
| Gizmo System | Implemented translation path; rotation, scale, and complete undo remain incomplete |
-
Model: Create your 3D model in Blender/Maya/3ds Max, export as
.glb - Place in Assets/Models/ and they appear in the Asset Browser
- Start SparkEditor separately from the matching build output, then drag models into the scene
- Position using the implemented translation gizmo or the Properties panel
-
Save the scene -- it's stored as a
.scenefile inAssets/Scenes/ -
Preview with a separately launched engine and an explicit
-game <module-path>selection
Collaborative editing is experimental and outside stable-v1. Do not treat the current development surfaces as a supported multi-user editing workflow.
AngelScript work is experimental and outside stable-v1. The following is an API-oriented development example, not a stable scripting-runtime or hot-reload promise:
// Assets/Scripts/my_script.as
void OnStart()
{
Print("Hello from AngelScript!");
Entity@ player = CreateEntity("ScriptedNPC");
}
void OnUpdate(float dt)
{
if (IsKeyDown("E"))
{
Print("Interaction triggered!");
}
}Do not rely on saving a script as a stable hot-reload contract; validate the active scripting path for the current development build.
Press ` (backtick) to open the console. Useful commands:
help -- List all commands
spawn_enemy grunt 10 1 10 -- Spawn an enemy at position
spawn_vehicle buggy 0 1 0 -- Spawn a vehicle
wave_start -- Start enemy waves
wave_skip 5 -- Skip to wave 5
teleport 0 10 0 -- Teleport player
timescale 0.5 -- Slow motion
god -- Toggle invincibility
noclip -- Toggle fly mode
fps -- Show framerate
physics_info -- Physics system stats
audio_info -- Audio system stats
The wave spawner can be configured via console or code:
wave_set_total 30 -- Total waves
wave_set_rest 5.0 -- Rest period between waves (seconds)
wave_set_difficulty 1.5 -- Difficulty multiplier
GameModules/SparkGameFPS/ is the declared first-party single-player vertical-slice reference for stable-v1. It is in scope but blocked and uncertified; it is not a complete game, a production claim, or multiplayer evidence. Study its source as a reference while independently validating the features your own module uses.
- Architecture Overview -- Understand the engine design
- Entity Component System -- ECS in depth
- AI and Navigation -- Behavior trees, NavMesh, perception
- Rendering and Graphics -- Rendering pipeline details
- Networking -- Multiplayer implementation
- Scripting with AngelScript -- Script API reference
- SparkEditor -- Editor panel reference
Published from 2b03dc797148. Edit the canonical source in wiki/.
- Documentation
- Docs route
- Wiki index
- Guides
- Tutorials
- Samples
- Examples
- API Reference
- API route
- Reference
- Build Guide
- Dependencies
- FAQ
- Changelog
- Roadmap
- Contributing
- Code of Conduct
- Home
- FAQ
- Getting Started
- Quick-Start Tutorial
- Making Your First Game
- Making Your First Multiplayer Game
- Artist Workflow Guide
- Editor Walkthrough
- Migration Guide
- How SparkEngine Works
- Architecture Overview
- Engine Architecture Flowchart
- Creating a Game Module
- Game Modules (catalog)
- Entity Component System
- Rendering and Graphics
- Physics
- Cloth Simulation
- Audio
- Input System
- Camera System
- Scripting with AngelScript
- Visual Scripting
- AI and Navigation
- Animation
- 2D Systems
- Networking
- Dedicated Server
- Multiplayer Quick Start
- Area Server Architecture
- Scene Management
- Large World Support
- Collaborative Editing
- Coroutine System
- Event System
- Event Response System
- Job System
- UI System
- UI Layout Extensions
- Localization
- Dialogue System
- Destruction System
- Replay System
- Achievement System
- Loading System
- Mod System
- Content Delivery
- Tween System
- Memory Integrity
- Gameplay Systems
- Terrain and Procedural Generation
- Save System
- Persistence System
- Day Night Cycle and Weather
- Cinematic Sequencer
- Runtime Prefabs
- SparkEditor
- Editor Tutorials
- SparkConsole
- SparkDaemon
- Shader Pipeline
- Asset Pipeline
- Asset Validation
- Asset Migration
- Game Packaging
- Online Services
- DataTable System
- Loot and Crafting System
- CSG System
- Font System
- Timer Manager
- Movie Render Pipeline
- HLOD and World Partition
- Remote Debug System
- Selection Manager
- Asset Dependency Graph
- Editor Automation
- File Watcher
- Project Templates
- System Requirements
- VR Support
- Mobile Platform
- Accessibility
- Platform Input
- Platform Certification
- Cross-Compilation: Wine Testing
- RHI Abstraction Layer
- D3D11 Backend
- D3D12 Backend
- Vulkan Backend
- OpenGL Backend
- Metal Backend
- DXR Raytracing
- Hybrid Ray Tracing
- Upscaling (DLSS/FSR)
- Render Graph
- Shader Graph
- GPU Particles
- GPU-Driven Rendering
- Volumetric Fog
- Volumetric Clouds
- Global Illumination
- Virtual Texturing
- Water Rendering
- Clustered Lighting
- Material System
- Post-Processing
- Shadow System
- Particle System
- Decal System
- Sky and Atmosphere
- Foliage System
- Mesh Shaders
- Neural Rendering
- Configuration Reference
- Performance Tips
- Benchmark Framework
- Threading Model
- Fuzz Policy and Parser Security
- Memory Safety
- Memory Management Patterns
- Build System and CMake Modules
- Profiler and Debugging
- Performance Profiling Guide
- Telemetry System
- Crash Reporting
- Golden Image Testing
- Utilities
- Testing
- Fuzz Policy and Parser Security
- Codebase Statistics
- Codebase Health
- Error Handling Patterns
- Hot Reload Overview
- Troubleshooting
- Contributing
- Workflow Patterns
- Build Optimizations
- CI Reproducible Builds
- GitHub API and PR Checks
- Git Rebase Conflicts
- Clang-Format
- Code Quality Violations
- AI Bloat Pattern
- MinGW + Wine Cross-Compilation
- Live Editor Testing
- Engine & Renderer Landscape
- DuetOS Portability Catalog
- Five-Engine Analysis
- Eleven-Engine Analysis
- ThorVG / Unity Graphics Analysis
- Advanced Techniques Catalog
- Third-Party Library Evaluation
- Engine Viability Evaluation
- Engine Feature Recommendations
- Project Recommendations
- Mac Compatibility Analysis
- Codebase Observations
- Codebase Bloat Audit
- Test Suite Audit
- Documentation Coverage Audit
- ThirdParty Dependencies Audit
- Load Test Baseline
- Gameplay Systems Status
- SparkGame Module Status
- Stub and Abandoned Features
- Memory Integrity System
- Memory Safety Evaluation
- Hardware Acceleration Systems
- Jolt Physics Integration
- GPU/CPU Separation Plan
- Daemon Services Architecture
- Reflection & Polymorphism Refactoring Plan
- SparkBuild In-Tree
- Wine No-JobSystem Breakthrough
- Wine Role and Fallback Tiers