Player and interaction¶
The player character, the rune-carrying component on top of it, and the interaction interface every world target implements.
Design vs build: Interaction behavior is defined in the GDD (Implementation status). The build today uses carry — walk to runes and slots, press Interact to pick up, place, or swap. Planned Interact Mode (pointer-based clicking) is not implemented.
ARephraseCharacter¶
Subclass of ACharacter. Owns the camera, the carry component, the interaction trigger box, and the input bindings.
Components:
CameraBoom(USpringArmComponent) — fallback camera arm. Active only when the player is outside allACameraZoneActors and no cinematic is running.FollowCamera(UCameraComponent).RuneCarryComponent(URuneCarryComponent) — the only one of these on the player.InteractionTrigger(UBoxComponent) — overlap-driven interaction discovery. Re-positioned each tick to sit in front of the player.
Input fields (Enhanced Input — see Enhanced Input in Rephrased):
DefaultMappingContext(UInputMappingContext)MoveAction,JumpAction,InteractAction,SplitRuneAction,ResetAction,ToggleDebugModeAction
Auth:
HUDWidgetClass— created at BeginPlay and added to the viewport.TriggerBoxSize(default 150x150x150),TriggerBoxOffset(default 150) — interaction reach.SplitRuneSpawnDistance(default 300) — where split runes land relative to the player.DeathMaterial— applied forApplyDeathMaterial(Duration)(Dangerous component invokes this).
State:
bDebugMode— toggled by G key. DrivesARuneSlot::SetDebugTextVisibleacross every slot.OverlappingSlots,OverlappingRunes,OverlappingInteractables— per-tick overlap arrays.CurrentHighlightedSlot,CurrentHighlightedRune,CurrentHighlightedInteractable— the closest in each list.PreviousHoveredSlot— for stale-state cleanup when hover changes.OriginalCharacterMaterials— captured at first death-material application so restore is clean.
Frame loop (in Tick):
UpdateTriggerBoxPosition— keep the trigger in front of the player.- Closest-of-each-kind walk over the overlapping arrays;
FindClosestRuneSlot()is the relevant helper. HighlightSlot/UpdateSlotHoverState— visual feedback if a slot is the closest.ShowDefinitionUI/HideDefinitionUI— when the closest slot has a rune, show its definition widget.- Generic interactables (memory orbs etc.) get
OnPlayerNearby/OnPlayerLeavenotifications.InteractableNotifyTimes+InteractableNotifyCooldown(0.3s) prevents flicker when the player walks the boundary.
Bound actions:
Move(FInputActionValue)— input-relative movement using the active camera's rotation (so movement always aligns with whatever camera the zone system has chosen).Interact()— if a slot, place/take/swap; if a loose rune, pick it up; if a generic interactable, fire itsOnInteract.SplitRune()— split the carried rune into itsRuneData->RuneSplitDatarunes, spawned atSplitRuneSpawnDistancein front of the player.ResetGame()— debug, restarts the level.ToggleDebugMode()— flipsbDebugModeand fans out the visibility change.
URuneCarryComponent¶
Single-rune inventory. Lives on the player.
Auth:
CarryOffset(default(0, 0, 100)) — where the rune hovers relative to the player.CarrySocketName— optional skeletal socket override.DefaultPickupSound,DefaultDropSound— fallbacks when the rune's data asset has no per-rune audio.
State:
CarriedRune(ARune*) — null when empty-handed.PalaceRuneHomeSlot— set to the palace/test slot the rune was borrowed from. LetsReturnPalaceRuneIfHoldingsend the rune home if the player picks up something else.
API:
PickupRune(Rune)— attach to player.DropRune()— detach without destroying (used when placing in a slot).DestroyCarriedRune()— used when returning to the original palace slot (the destination slot will re-spawn its own rune).IsCarryingRune(),GetCarriedRune()— queries.ReturnPalaceRuneIfHolding()— if the player is carrying a borrowed palace rune and tries to grab a new one, the borrowed rune auto-returns to its home slot.
IInteractableInterface¶
Defines the contract every world target obeys. Implemented by ARuneSlot, AMemoryOrb, and (in subclasses) anything else interactive.
BlueprintNativeEvent methods:
OnPlayerNearby(Player)— player entered the trigger box. Light up, show prompt, etc.OnPlayerLeave(Player)— player exited.OnInteract(Player)— player pressed interact while you're the closest.CanInteract(Player) const— gate. False suppresses the prompt and the interact call.GetInteractionPrompt() const— returns the localized prompt text.
The character only invokes these on the closest matching actor in each category, so widgets don't fight for screen space.
The dual-class UInteractableInterface / IInteractableInterface pair is required by Unreal: U* is the reflection class (so the interface can be referenced from Blueprint), I* is the C++ interface body. Both GENERATED_BODY().
How interaction sees the world¶
InteractionTrigger overlap begin
|
v
ARephraseCharacter::OnTriggerBeginOverlap
|
+-- ARuneSlot -> OverlappingSlots
+-- ARune -> OverlappingRunes
+-- IInteractableInterface (any other) -> OverlappingInteractables
Tick
|
v
pick closest from each list
|
v
notify the closest, suppress the others
Slots and loose runes are detected by class cast. Anything else implementing IInteractableInterface falls into the generic bucket. New interactable kinds (e.g. dialogue NPCs) just need to implement the interface; no character changes required unless they should be highlighted differently.
Death material flow¶
UDangerousComponent calls ApplyDeathMaterial on the player. The character captures OriginalCharacterMaterials, applies DeathMaterial to every slot, schedules RestoreOriginalMaterials on DeathMaterialTimerHandle for the duration. bIsDeathMaterialActive prevents re-trigger flicker if the player keeps colliding.
Source files¶
Source/Rephrased_Demo/RephraseCharacter.hSource/Rephrased_Demo/RuneCarryComponent.hSource/Rephrased_Demo/InteractableInterface.h
Known constraints¶
- Only one rune carried at a time. By design.
- The trigger box is
Tick-positioned because the spring arm rotates with the camera; if we ever move to a fully scripted camera the trigger could attach via socket and skip the per-tick math. InteractableNotifyCooldownis hardcoded at 0.3s. If we add hover-driven gameplay (e.g. dialogue lines triggered by gaze), this will need to become a per-actor knob.- HUD widget is a single
TSubclassOf<UUserWidget>field. Multiple HUDs (gameplay vs menu) are handled today by Blueprint widget switching inside one root widget. See UI.
Change history¶
- 2026-05-23 — Gabriel Li — GDD as design authority; implementation status link.
- 2026-05-08 — Gabriel Li — Added baseline change history section for weekly wiki maintenance.