Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Subsystems

The goal: a system with one instance per game, per level or per player, created and destroyed by the engine, that any code can find: a wave director, a quest log, a score keeper, a music manager.

The engine creates one subsystem object for every class deriving from a subsystem base. A Rust subsystem derives from one of Rusteal’s bases, which turn the engine’s C++ virtuals into events:

ParentOne perEvents
RustealGameInstanceSubsystemgame session (survives level changes)receive_initialize, receive_deinitialize, receive_should_create_subsystem
RustealWorldSubsystemworld: each level, in the game and in Play In Editorthe same, plus receive_post_initialize, receive_world_begin_play, and receive_tick when it wants to tick
RustealLocalPlayerSubsystemlocal playerthe same as the game’s, plus receive_player_controller_changed
RustealEngineSubsystemprocessthe same as the game’s

They come from bindings::rusteal.

Use case: a wave director

The goal: a system that starts enemy waves when the level begins and counts down between them.

#![allow(unused)]
fn main() {
use bindings::rusteal::{RustealWorldSubsystem, RustealWorldSubsystemExt};

#[uclass(parent = RustealWorldSubsystem)]
pub struct WaveDirector {
    #[uproperty]
    wave: i32,

    countdown: f32,
}

#[uclass_impl]
impl WaveDirector {
    #[class_defaults]
    fn class_defaults(&mut self) -> RustealResult<()> {
        self.as_ref().checked()?.set_wants_tick(true);
        Ok(())
    }

    #[ufunction(Override)]
    fn receive_world_begin_play(&mut self) {
        self.set_wave(1);
        self.set_countdown(10.0);
    }

    #[ufunction(Override)]
    fn receive_tick(&mut self, delta_seconds: f32) {
        let countdown = self.countdown() - delta_seconds;

        if countdown <= 0.0 {
            self.set_wave(self.wave() + 1);
            self.set_countdown(30.0);
        } else {
            self.set_countdown(countdown);
        }
    }

    #[ufunction(BlueprintPure)]
    pub fn current_wave(&self) -> i32 {
        self.wave()
    }
}
}

Finding a subsystem

From Rust, through the engine’s SubsystemBlueprintLibrary, with any object in the world as context:

#![allow(unused)]
fn main() {
use bindings::engine::{SubsystemBlueprintLibrary, WorldSubsystem};

impl WaveDirector {
    pub fn get(context: UObjectRef<Object>) -> RustealResult<Self> {
        let class = SubclassOf::<WaveDirector>::base().upcast_to::<WorldSubsystem>();
        Self::from_obj(SubsystemBlueprintLibrary::get_world_subsystem(context, class))
    }
}

let mut director = WaveDirector::get(self.as_ref().upcast_to())?;
}

get_game_instance_subsystem, get_local_player_subsystem and get_engine_subsystem find the other kinds.

Choosing the kind

  • State of the current level, gone when the level changes: world.
  • State that lives across levels (the player’s progress, settings loaded once, a connection): game instance.
  • State per player in split screen: local player.

A world subsystem exists in the editor’s own world too only when it asks for it (bCreateInEditorWorlds); by default it exists in the game and in Play In Editor.