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

Properties

The goal: values that designers tune in the editor, that Blueprints read and write, and that the engine saves with the object.

#![allow(unused)]
fn main() {
use bindings::engine::{AnimMontage, SoundBase};
use bindings::prelude::*;
use rusteal_runtime::runtime::{FName, SubclassOf, UObjectRef, UeArray};
use rusteal_runtime::{uclass, uclass_impl};

#[uclass(parent = Actor)]
pub struct Turret {
    #[uproperty(EditAnywhere, category = "Combat", default = 25.0)]
    damage: f32,

    #[uproperty(EditAnywhere, category = "Combat", default = 3)]
    burst_size: i32,

    #[uproperty(EditAnywhere, BlueprintReadWrite, default = true)]
    b_active: bool,

    #[uproperty(EditAnywhere, category = "Effects")]
    fire_sound: UObjectRef<SoundBase>,

    #[uproperty(EditAnywhere, category = "Effects")]
    fire_montage: UObjectRef<AnimMontage>,

    #[uproperty(EditAnywhere, category = "Combat")]
    projectile_class: SubclassOf<Actor>,

    #[uproperty(EditAnywhere)]
    target_tags: UeArray<FName>,

    #[uproperty]
    current_target: UObjectRef<Actor>,

    shots_fired: u32,
}

#[uclass_impl]
impl Turret {}
}

A field marked #[uproperty] is a real Unreal property. It lives in the object’s memory, the editor shows it according to its specifiers, Blueprints reach it, it is saved with the level or the Blueprint, and object references in it keep the object alive for the garbage collector.

Getters and setters

Each property gets a getter named after the field and a setter with set_:

#![allow(unused)]
fn main() {
let damage = self.damage();
self.set_burst_size(5);
self.set_current_target(target);
}

The getter of a scalar, an object, a class or a name returns a copy. The getter of a UeArray returns a view of the array inside the object, changed in place:

#![allow(unused)]
fn main() {
let tags = self.target_tags();
tags.push(&FName::new("Player"))?;
let all: Vec<_> = tags.to_vec()?;
}

The getter of a struct property (OwnedStruct<FVector>) returns a copy; change it and set it back.

Specifiers

SpecifierWhat it does
EditAnywhereEditable in the Details panel, on the class defaults and on each instance in a level.
EditDefaultsOnlyEditable on the class defaults only (a Blueprint’s Class Defaults), not per instance.
VisibleAnywhereShown, read-only, in the Details panel.
BlueprintReadWriteBlueprints get Get and Set nodes for it.
BlueprintReadOnlyBlueprints get a Get node only. Rust code still has the setter.
SaveGameWritten by a save game archive. See Saving the game.
ConfigRead from and saved to the ini files of the class’s config category. See Project settings.

Several specifiers can be combined. A property with none (#[uproperty]) is hidden from the editor and from Blueprints, but is still a real property: use it for object references the class holds, so the garbage collector sees them.

Any other argument is a compile error.

Named arguments

ArgumentWhat it does
default = <value>The property’s default value, for scalars and engine enums: default = 25.0, default = true, default = ECollisionChannel::ECC_Pawn, or a constant (default = model::MAX_HP).
category = "..."The category it is listed under in the Details panel. `“Combat
name = "..."The Unreal name, when the field’s name in PascalCase is not the one you want.

default is for scalars and enums. Objects, classes, arrays and structs get their defaults in Class defaults or in a Blueprint child.

Names

The Unreal name is the field’s name in PascalCase: burst_size is BurstSize. A bool whose field starts with b_ follows Unreal’s b convention: b_active is bActive. When PascalCase gets it wrong, name fixes it:

#![allow(unused)]
fn main() {
#[uproperty(EditAnywhere, name = "MaxHP", default = 100.0)]
max_hp: f32,

#[uproperty(EditAnywhere, name = "NPCClass")]
npc_class: SubclassOf<Actor>,
}

The Unreal name is what Blueprints, ini files, data tables and editor Python see. Changing it on a class that already has Blueprint children or placed instances loses the values they saved under the old name.

Types

Rust typeUnreal type
bool, u8, i32, i64, f32, f64bool, uint8, int32, int64, float, double
StringFString
FNameFName
An engine enum (ECollisionChannel, ETraceTypeQuery…) or a #[uenum] of yoursthat enum
UObjectRef<T>TObjectPtr<T>: an object or an asset
SubclassOf<T>TSubclassOf<T>: a class, such as a Blueprint to spawn
SoftObjectRef<T>TSoftObjectPtr<T>: an asset by path, loaded on demand
OwnedStruct<T>a struct: FVector, FLinearColor, FTransform, FDataTableRowHandle, a #[ustruct] of yours
UeArray<T> of the scalars, objects, classes, FName or structs (UeArray<OwnedStruct<FLinearColor>>)TArray<T>

Maps and sets (TMap, TSet) cannot be declared yet; two arrays side by side, or a Rust-only HashMap, do the job meanwhile.

Fields only Rust sees

A field without an attribute is Rust’s alone: any Rust type, kept outside the Unreal object. It gets the same getter and setter, plus a <field>_mut() that changes it in place:

#![allow(unused)]
fn main() {
self.set_shots_fired(self.shots_fired() + 1);
self.cooldowns_mut().insert(weapon, 2.0);
}

The getter of a Rust-only field returns a clone. Use these fields for state the editor does not need: caches, timers, a state machine, a HashMap. Two things to keep in mind:

  • They start over from Default on every hot reload.
  • An UObjectRef kept only there does not keep its object alive: the garbage collector does not see it. Hold the objects you need to keep in a #[uproperty], or check them with checked() before use (see Object references).

The templates keep a class’s transient state in one plain struct field, read, changed and written back:

#![allow(unused)]
fn main() {
#[derive(Clone, Copy, Default)]
pub struct MoveState {
    pub has_double_jumped: bool,
    pub last_fall_time: f64,
}

let mut state = self.state();
state.has_double_jumped = true;
self.set_state(state);
}

In the editor

Open the class’s Blueprint child, click Class Defaults in the toolbar, and the Details panel lists the EditAnywhere and EditDefaultsOnly properties under their categories. Select an instance in the level to see the EditAnywhere ones per instance.

Screenshot: the Details panel of a Blueprint child showing the Combat and Effects categories of a Rust class. (images/classes/details_categories.png)