Scenes
Scene System
Scenes are the primary organizational unit in VkEngine. They contain game objects and resources for a logical level or area of your game. Each scene is responsible for its own initialization, updates, and cleanup. Developers create custom scene classes to implement game-specific logic.
Core Scene Class
Base class for creating custom scenes:
class Scene {
public:
virtual ~Scene() {}
virtual void EarlyInitScene(Engine* engine); // Before resource loading
virtual void InitScene(Engine* engine); // After resources loaded
virtual void UpdateScene(Engine* engine); // Per-frame updates
virtual void DestroyScene(Engine* engine); // Cleanup
virtual GameObject* CreateGameObject(
Engine* engine, const char* objectType, const char* tag,
const char* name, Transform transform,
Mesh* mesh, Texture* texture, bool dynamic);
virtual void ResourceLoaded(std::string name, const char* path, ResourceType type);
};
Initialization Phases
EarlyInitScene
Called first, before resources are loaded. Use this phase to:
- Request resources to be loaded
- Configure scene parameters
- Initialize systems
class Level1Scene : public Scene {
void EarlyInitScene(Engine* engine) override {
// Request resources (they may not be ready yet)
// This is where you'd start background loading tasks
// Initialize RNG for procedural generation
rng.seed(time(nullptr));
}
};
InitScene
Called after all requested resources are available. Use this phase to:
- Create game objects using loaded resources
- Set up physics triggers
- Configure scene-specific audio
- Initialize game state
class Level1Scene : public Scene {
void InitScene(Engine* engine) override {
// All resources are now available
Mesh* playerMesh = engine->getMesh("player");
Texture* playerTex = engine->getTexture("player");
PhysicsMaterial* mat = engine->createPhysicsMaterial(0.5f, 0.3f, 0.2f);
// Create player
auto player = engine->createGameObject<Player>(
{0, 1, 0, {0,0,0,1}, {1,1,1}}, // Transform
playerMesh,
playerTex,
mat,
true // Dynamic
);
// Create level environment
Mesh* levelMesh = engine->getMesh("level");
auto level = engine->createGameObject<GameObject>(
{0, 0, 0, {0,0,0,1}, {1,1,1}},
levelMesh,
nullptr, // No texture
mat,
false // Static
);
// Set up exit trigger
exitTrigger = engine->createBoxTrigger({0, 1, 10}, {2, 2, 2});
exitTrigger->onTriggerEnter = [this](GameObject* other) {
if (other->tag == "player") levelComplete = true;
};
}
};
Runtime Behavior
UpdateScene
Called every frame for scene-specific logic:
class Level1Scene : public Scene {
void UpdateScene(Engine* engine) override {
// Check for level completion
if (levelComplete) {
loadNewScene = true;
sceneToLoad = nextLevel;
}
// Update UI
updateHUD(engine);
// Play ambient sounds
if (ambienceSound && !isPlaying) {
player->playSound(ambienceSound, 0.3f);
isPlaying = true;
}
}
};
DestroyScene
Called during cleanup. Usually most cleanup is automatic, but use this for:
- Stopping background tasks
- Saving game state
- Explicit cleanup for complex objects
class Level1Scene : public Scene {
void DestroyScene(Engine* engine) override {
// Stop background music
if (musicPlaying) {
engine->requestDestroy(musicSound);
}
// Save any persistent game state
savePlayerProgress();
}
};
Object Creation Customization
Override CreateGameObject() to customize object instantiation:
GameObject* CreateGameObject(
Engine* engine,
const char* objectType, // Custom type name from scene file
const char* tag,
const char* name,
Transform transform,
Mesh* mesh,
Texture* texture,
bool dynamic
) override {
// Create custom game object types based on objectType string
if (strcmp(objectType, "enemy") == 0) {
return engine->createGameObject<Enemy>(transform, mesh, texture, material, dynamic);
}
else if (strcmp(objectType, "pickup") == 0) {
return engine->createGameObject<Pickup>(transform, mesh, texture, material, dynamic);
}
else if (strcmp(objectType, "trigger_zone") == 0) {
return engine->createGameObject<TriggerZone>(transform, mesh, texture, material, dynamic);
}
// Default: standard GameObject
return engine->createGameObject<GameObject>(transform, mesh, texture, material, dynamic);
}
Scene Resources
Scenes manage collections of:
- Meshes: 3D geometry available in the scene
- Textures: Image assets available in the scene
- Game Objects: Instantiated entities in the scene
These are automatically organized for efficient rendering and physics.
Scene File Format
Scene files define the initial layout. Format is text-based:
MESH: meshName filePath
TEXTURE: textureName filePath
OBJECT: objectType tag name meshName textureName dynamic
TRANSFORM: posX posY posZ rotX rotY rotZ rotW scaleX scaleY scaleZ
Example scene file:
MESH: player models/player.obj
MESH: level models/level.obj
TEXTURE: player_tex textures/player.png
TEXTURE: level_tex textures/level.png
OBJECT: Player player Player player_mesh player_tex true
TRANSFORM: 0 1 0 0 0 0 1 1 1 1
OBJECT: LevelGeometry level Level level_mesh level_tex false
TRANSFORM: 0 0 0 0 0 0 1 1 1 1
Loading from file:
bool valid;
MyScene* scene = engine->createScene<MyScene>("assets/level1.scene", &valid);
if (!valid) {
std::cerr << "Failed to load scene file\n";
}
engine->loadScene(scene);
Creating empty scene:
MyScene* scene = engine->createScene<MyScene>();
engine->loadScene(scene);
Scene Transitions
In main game loop:
while (engine->running()) {
engine->updateScene();
engine->update();
engine->render();
// Check if scene wants to transition
if (engine->isLastFrame()) {
auto scene = static_cast<MyScene*>(engine->getActiveScene());
if (scene && scene->loadNewScene) {
scene->loadNewScene = false;
engine->loadScene(scene->sceneToLoad);
}
}
}
In scene class:
class Level1Scene : public Scene {
public:
bool loadNewScene = false;
Scene* sceneToLoad = nullptr;
void UpdateScene(Engine* engine) override {
if (playerReachedExit) {
loadNewScene = true;
sceneToLoad = nextLevel;
}
}
};
Example: Complete Game Scene
class GameLevel : public Scene {
private:
ICharacterController* player;
Sound* ambience;
Sound* footsteps;
bool levelComplete = false;
public:
void EarlyInitScene(Engine* engine) override {
// Create character controller
PhysicsMaterial* mat = engine->createPhysicsMaterial(0.5f, 0.3f, 0.2f);
player = engine->createCharacterController(1.8f, 0.4f, {0, 1, 0}, mat, true);
}
void InitScene(Engine* engine) override {
// Load assets
ambience = engine->createSound("ambient", "assets/wind.ogg", true, false);
footsteps = engine->createSound("step", "assets/footstep.ogg", false, true);
Mesh* levelMesh = engine->getMesh("level");
auto levelGeo = engine->createGameObject<GameObject>(
{{0,0,0}, {0,0,0,1}, {1,1,1}},
levelMesh, nullptr, mat, false
);
// Create exit trigger
Trigger* exit = engine->createBoxTrigger({0, 1, 10}, {2, 2, 2});
exit->onTriggerEnter = [this](GameObject* other) {
levelComplete = true;
};
}
void UpdateScene(Engine* engine) override {
// Handle player movement
Vector3 moveDir = {};
if (engine->getKey(KeyCode::W) == PRESS) moveDir.z += 1;
if (engine->getKey(KeyCode::S) == PRESS) moveDir.z -= 1;
if (engine->getKey(KeyCode::A) == PRESS) moveDir.x -= 1;
if (engine->getKey(KeyCode::D) == PRESS) moveDir.x += 1;
player->Move(moveDir, 10.0f, engine->getDeltaTime());
// Check level completion
if (levelComplete) {
loadNewScene = true;
sceneToLoad = nextLevel;
}
}
void DestroyScene(Engine* engine) override {
// Cleanup happens automatically
}
};
Tips and Best Practices
- Separate concerns: Keep scene initialization clean, move complex logic to GameObjects
- Use tags for filtering: Filter objects by tag for quick lookups and logic
- Scene files for layout: Use scene files for static level design, code for dynamic behavior
- Resource sharing: Load shared resources once, reuse across objects
- Deferred cleanup: Always use
requestDestroy(), never delete directly - Camera control: Engine camera follows the physics, override for custom behavior