Learn Simulant
Everything you need to know to build games with Simulant
Your First Simulant Game
Welcome! This tutorial will guide you through creating your first Simulant game from scratch. We'll create a simple application that displays a 3D model and lets you look around.
Prerequisites: You should have Simulant installed. If not, follow the Installation Guide.
Step 1: Create a New Project
Open a terminal and run:
# Create a projects directory
mkdir -p ~/Projects
cd ~/Projects
# Create a new Simulant project called "my-first-game"
simulant start my-first-game
The start command will:
- Download the Simulant engine for your platform
- Set up the project structure
- Download core assets
- Configure the build system
This may take a minute. When it's done, you'll see a new directory:
my-first-game/
├── assets/ # Game assets (models, textures, sounds)
├── libraries/ # Simulant library
├── packages/ # Built packages for distribution
├── sources/ # Your C++ source code
├── tests/ # Unit tests
├── tools/ # Binary tools
├── CMakeLists.txt # Build configuration
└── simulant.json # Project configuration
Step 2: Build and Run
Let's verify everything works:
cd my-first-game
# Build the project
simulant build
# Run the game
simulant run
You should see the Simulant splash screen, followed by a blank window. Press Escape to quit.
Tip: Use
simulant run --rebuildto rebuild and run in one command.
Step 3: Understand the Project Structure
Open the project in your favorite editor. The main source file is:
sources/main.cpp
Let's look at the default code:
#include <simulant/simulant.h>
using namespace smlt;
class MyGame : public Application {
public:
MyGame(const AppConfig& config):
Application(config) {}
bool init() {
// Scene registration happens here
scenes->register_scene<InGameScene>("ingame");
scenes->register_scene<InGameScene>("main"); // "main" is the starting scene
return true;
}
};
int main(int argc, char* argv[]) {
AppConfig config;
config.title = "My First Game";
config.width = 1280;
config.height = 720;
MyGame app(config);
return app.run(argc, argv);
}
Key points:
- Your game is an
Applicationsubclass - The
init()method is where you set up scenes - Scenes are registered by name -
"main"is the first scene loaded AppConfigcontrols window settings
Step 4: Create Your First Scene
Scenes represent different parts of your game (menu, gameplay, game over, etc.). Let's create a simple gameplay scene.
Create a new file sources/ingame_scene.h:
#pragma once
#include <simulant/simulant.h>
using namespace smlt;
class InGameScene : public Scene<InGameScene> {
public:
InGameScene(Window* window):
Scene<InGameScene>(window) {}
void on_load() override;
void on_update(float dt) override;
};
Now create sources/ingame_scene.cpp:
#include "ingame_scene.h"
void InGameScene::on_load() {
// This is where we build our scene
S_INFO("InGameScene loaded!");
}
void InGameScene::on_update(float dt) {
// Called every frame
// dt is the time since the last frame in seconds
}
Now update your main.cpp to include the new scene:
#include <simulant/simulant.h>
#include "ingame_scene.h" // Add this
using namespace smlt;
class MyGame : public Application {
public:
MyGame(const AppConfig& config):
Application(config) {}
bool init() {
scenes->register_scene<InGameScene>("main");
return true;
}
};
int main(int argc, char* argv[]) {
AppConfig config;
config.title = "My First Game";
config.width = 1280;
config.height = 720;
MyGame app(config);
return app.run(argc, argv);
}
Build and run:
simulant run --rebuild
You should see a blank window with "InGameScene loaded!" in the console output.
Step 5: Add a Camera
Every visible scene needs a camera. Let's add one to our scene.
Update ingame_scene.h:
#pragma once
#include <simulant/simulant.h>
using namespace smlt;
class InGameScene : public Scene<InGameScene> {
public:
InGameScene(Window* window):
Scene<InGameScene>(window) {}
void on_load() override;
void on_update(float dt) override;
private:
CameraID camera_;
};
Update ingame_scene.cpp:
#include "ingame_scene.h"
void InGameScene::on_load() {
S_INFO("InGameScene loaded!");
// Create a 3D camera
camera_ = create_child<Camera3D>()->id();
// Position the camera
auto camera_ptr = camera(camera_);
camera_ptr->move_to(0, 5, -10);
camera_ptr->rotate_to(Degrees(20), Degrees(0), Degrees(0));
}
void InGameScene::on_update(float dt) {
// Called every frame
}
Step 6: Add a Render Pipeline
A camera alone won't show anything - we need a render pipeline (called a Layer in the compositor).
Update ingame_scene.cpp:
#include "ingame_scene.h"
void InGameScene::on_load() {
S_INFO("InGameScene loaded!");
// Create a 3D camera
camera_ = create_child<Camera3D>()->id();
// Position the camera
auto camera_ptr = camera(camera_);
camera_ptr->move_to(0, 5, -10);
camera_ptr->rotate_to(Degrees(20), Degrees(0), Degrees(0));
// Create a render pipeline
// This tells the compositor: "Render this stage from this camera"
auto layer = compositor->create_layer(this, camera(camera_));
layer->activate();
// Link the pipeline to this scene so it auto-activates/deactivates
link_pipeline(layer);
}
Build and run. You should see a blank scene with a dark background. We have a camera, but nothing to look at!
Step 7: Add a 3D Object
Let's add a simple cube to the scene. Simulant includes procedural mesh generation, so we don't need external assets yet.
Add a member variable to ingame_scene.h:
private:
CameraID camera_;
ActorID cube_actor_;
Update ingame_scene.cpp:
#include "ingame_scene.h"
void InGameScene::on_load() {
S_INFO("InGameScene loaded!");
// Create a 3D camera
camera_ = create_child<Camera3D>()->id();
auto camera_ptr = camera(camera_);
camera_ptr->move_to(0, 5, -10);
camera_ptr->rotate_to(Degrees(20), Degrees(0), Degrees(0));
// Create a cube mesh procedurally
auto cube_mesh = assets->new_mesh_from_procedural_cube();
// Create an actor to render the mesh
cube_actor_ = create_child<Actor>()->id();
auto actor = actor(cube_actor_);
actor->set_mesh(cube_mesh->id());
actor->move_to(0, 0, 0);
// Create a render pipeline
auto layer = compositor->create_layer(this, camera(camera_));
layer->activate();
link_pipeline(layer);
}
Build and run. You should now see a white cube in the center of the screen!
Step 8: Make It Move
Static cubes are boring. Let's make it rotate!
Add a member variable:
private:
CameraID camera_;
ActorID cube_actor_;
float rotation_angle_ = 0.0f;
Update the on_update method:
void InGameScene::on_update(float dt) {
// Rotate the cube over time
rotation_angle_ += dt * 90.0f; // 90 degrees per second
if (auto actor = maybe_actor(cube_actor_)) {
actor->rotate_to(Degrees(0), Degrees(rotation_angle_), Degrees(0));
}
}
The maybe_actor() method safely returns an optional pointer - if the actor was destroyed, it returns empty rather than crashing.
Build and run. Your cube should now be spinning!
Step 9: Handle Input
Let's make the camera move with the arrow keys.
Update ingame_scene.cpp:
#include "ingame_scene.h"
void InGameScene::on_load() {
S_INFO("InGameScene loaded!");
// Create a 3D camera
camera_ = create_child<Camera3D>()->id();
auto camera_ptr = camera(camera_);
camera_ptr->move_to(0, 5, -10);
camera_ptr->rotate_to(Degrees(20), Degrees(0), Degrees(0));
// Define input axes
auto move_left_axis = input->new_axis("move_left");
move_left_axis->set_positive_keyboard_key(KEYBOARD_CODE_LEFT);
auto move_right_axis = input->new_axis("move_right");
move_right_axis->set_positive_keyboard_key(KEYBOARD_CODE_RIGHT);
auto move_forward_axis = input->new_axis("move_forward");
move_forward_axis->set_positive_keyboard_key(KEYBOARD_CODE_UP);
auto move_back_axis = input->new_axis("move_back");
move_back_axis->set_positive_keyboard_key(KEYBOARD_CODE_DOWN);
// Create a cube mesh procedurally
auto cube_mesh = assets->new_mesh_from_procedural_cube();
// Create an actor to render the mesh
cube_actor_ = create_child<Actor>()->id();
auto actor = actor(cube_actor_);
actor->set_mesh(cube_mesh->id());
actor->move_to(0, 0, 0);
// Create a render pipeline
auto layer = compositor->create_layer(this, camera(camera_));
layer->activate();
link_pipeline(layer);
}
void InGameScene::on_update(float dt) {
// Rotate the cube
rotation_angle_ += dt * 90.0f;
if (auto actor = maybe_actor(cube_actor_)) {
actor->rotate_to(Degrees(0), Degrees(rotation_angle_), Degrees(0));
}
// Move the camera
if (auto cam = maybe_camera(camera_)) {
float speed = 5.0f * dt;
if (input->axis_value("move_left") > 0) {
cam->move_by(-speed, 0, 0);
}
if (input->axis_value("move_right") > 0) {
cam->move_by(speed, 0, 0);
}
if (input->axis_value("move_forward") > 0) {
cam->move_by(0, 0, speed);
}
if (input->axis_value("move_back") > 0) {
cam->move_by(0, 0, -speed);
}
}
}
Build and run. Use the arrow keys to move the camera around the spinning cube!
Step 10: Add a Material
White cubes are dull. Let's add color with a material.
Add to on_load() before creating the actor:
// Create a red material
auto material = assets->new_material();
material->set_diffuse(Colour(1.0f, 0.0f, 0.0f, 1.0f)); // Red
// Create an actor to render the mesh
cube_actor_ = create_child<Actor>()->id();
auto actor = actor(cube_actor_);
actor->set_mesh(cube_mesh->id());
actor->set_material(material->id());
actor->move_to(0, 0, 0);
Build and run. Your cube should now be red!
What We've Learned
Congratulations! You've created your first Simulant game. Here's what you learned:
- Applications - Entry point to your game
- Scenes - Manage game state with
on_load()andon_update() - Cameras - Viewpoints into the 3D world
- Layers - Render pipelines for the compositor
- Actors - Objects that render meshes
- Materials - Control appearance with colors
- Input - Read keyboard input with axes
- Safe Access - Use
maybe_actor()andmaybe_camera()for safety
What's Next?
- Project Structure - Understand Simulant projects deeply
- Scenes - Learn more about scene management
- Actors & Meshes - Load 3D models from files
- Physics - Add physics simulation
- UI - Build user interfaces
Complete Code
Here's the final code for reference:
sources/ingame_scene.h
#pragma once
#include <simulant/simulant.h>
using namespace smlt;
class InGameScene : public Scene<InGameScene> {
public:
InGameScene(Window* window):
Scene<InGameScene>(window) {}
void on_load() override;
void on_update(float dt) override;
private:
CameraID camera_;
ActorID cube_actor_;
float rotation_angle_ = 0.0f;
};
sources/ingame_scene.cpp
#include "ingame_scene.h"
void InGameScene::on_load() {
S_INFO("InGameScene loaded!");
// Create a 3D camera
camera_ = create_child<Camera3D>()->id();
auto camera_ptr = camera(camera_);
camera_ptr->move_to(0, 5, -10);
camera_ptr->rotate_to(Degrees(20), Degrees(0), Degrees(0));
// Define input axes
auto move_left_axis = input->new_axis("move_left");
move_left_axis->set_positive_keyboard_key(KEYBOARD_CODE_LEFT);
auto move_right_axis = input->new_axis("move_right");
move_right_axis->set_positive_keyboard_key(KEYBOARD_CODE_RIGHT);
auto move_forward_axis = input->new_axis("move_forward");
move_forward_axis->set_positive_keyboard_key(KEYBOARD_CODE_UP);
auto move_back_axis = input->new_axis("move_back");
move_back_axis->set_positive_keyboard_key(KEYBOARD_CODE_DOWN);
// Create a cube mesh procedurally
auto cube_mesh = assets->new_mesh_from_procedural_cube();
// Create a red material
auto material = assets->new_material();
material->set_diffuse(Colour(1.0f, 0.0f, 0.0f, 1.0f));
// Create an actor to render the mesh
cube_actor_ = create_child<Actor>()->id();
auto actor = actor(cube_actor_);
actor->set_mesh(cube_mesh->id());
actor->set_material(material->id());
actor->move_to(0, 0, 0);
// Create a render pipeline
auto layer = compositor->create_layer(this, camera(camera_));
layer->activate();
link_pipeline(layer);
}
void InGameScene::on_update(float dt) {
// Rotate the cube
rotation_angle_ += dt * 90.0f;
if (auto actor = maybe_actor(cube_actor_)) {
actor->rotate_to(Degrees(0), Degrees(rotation_angle_), Degrees(0));
}
// Move the camera
if (auto cam = maybe_camera(camera_)) {
float speed = 5.0f * dt;
if (input->axis_value("move_left") > 0) {
cam->move_by(-speed, 0, 0);
}
if (input->axis_value("move_right") > 0) {
cam->move_by(speed, 0, 0);
}
if (input->axis_value("move_forward") > 0) {
cam->move_by(0, 0, speed);
}
if (input->axis_value("move_back") > 0) {
cam->move_by(0, 0, -speed);
}
}
}