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

Pickups

Use case: a coin that counts

The goal: the player walks into a coin, the game mode counts it, the UI shows the count, and the Blueprint plays the effect.

#![allow(unused)]
fn main() {
use bindings::engine::{
    ECollisionChannel, ECollisionEnabled, ECollisionResponse, GameplayStatics,
    PrimitiveComponentExt, SceneComponent, SphereComponent, SphereComponentExt
};

#[uclass(parent = Actor)]
pub struct Coin {
    #[component(root)]
    root: SceneComponent,

    #[component(attach = "root", name = "Collision")]
    sphere: SphereComponent,
}

#[uclass_impl]
impl Coin {
    #[class_defaults]
    fn class_defaults(&mut self) -> RustealResult<()> {
        let sphere = self.sphere()?.checked()?;
        sphere.set_sphere_radius(60.0, Some(false));
        sphere.set_collision_enabled(ECollisionEnabled::QueryOnly);
        sphere.set_collision_response_to_all_channels(ECollisionResponse::ECR_Ignore);
        sphere.set_collision_response_to_channel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
        Ok(())
    }

    #[ufunction(Override)]
    fn receive_actor_begin_overlap(&mut self, other_actor: UObjectRef<Actor>) {
        if let Err(e) = self.pick_up(other_actor) {
            log::warn!("[MyGame] pickup failed: {e}");
        }
    }

    #[ufunction(BlueprintImplementableEvent, name = "BP_OnPickedUp")]
    fn bp_on_picked_up(&self) {}
}

impl Coin {
    fn pick_up(&mut self, other_actor: UObjectRef<Actor>) -> RustealResult<()> {
        let Ok(character) = other_actor.cast::<Character>() else {
            return Ok(());
        };

        if !character.checked()?.is_player_controlled() {
            return Ok(());
        }

        let game_mode = GameplayStatics::get_game_mode(self.as_ref().upcast_to());

        if let Ok(mut game_mode) = CoinGameMode::from_obj(game_mode) {
            game_mode.add_coin()?;
        }

        self.as_ref().checked()?.set_actor_enable_collision(false);
        self.bp_on_picked_up();

        Ok(())
    }
}
}

The collision only overlaps pawns, so the coin does not block anything. The Blueprint child gives it a mesh, and its BP On Picked Up graph plays a sound and hides or destroys it. The game mode keeps the count and updates the UI: see UI, “A counter on screen”.

Use case: a weapon pickup that respawns

The goal: a pickup gives its weapon to whoever walks in, hides, and comes back after a few seconds.

#![allow(unused)]
fn main() {
#[ufunction(Override)]
fn receive_actor_begin_overlap(&mut self, other_actor: UObjectRef<Actor>) {
    let Some(mut holder) = weapon_holder(other_actor) else {
        return;
    };

    holder.add_weapon_class(self.weapon_class());

    let Ok(me) = self.as_ref().checked() else {
        return;
    };

    me.set_actor_hidden_in_game(true);
    me.set_actor_enable_collision(false);

    KismetSystemLibrary::k2_set_timer(me.as_ref().upcast_to(), "RespawnPickup", self.respawn_time(), false, None, None, None);
}

#[ufunction]
fn respawn_pickup(&mut self) {
    if let Ok(me) = self.as_ref().checked() {
        me.set_actor_hidden_in_game(false);
        me.set_actor_enable_collision(true);
    }
}

#[ufunction(Override)]
fn receive_end_play(&mut self, _end_play_reason: u8) {
    KismetSystemLibrary::k2_clear_timer(self.as_ref().upcast_to(), "RespawnPickup");
}
}

weapon_holder turns the actor into a Rust trait that both the player and the NPCs implement (see Interfaces). Which weapon the pickup gives comes from a data table row: see Structs and data tables.