CARLA
Loading...
Searching...
No Matches
WalkerController.cpp
Go to the documentation of this file.
1// Copyright (c) 2026 Computer Vision Center (CVC) at the Universitat Autonoma
2// de Barcelona (UAB).
3//
4// This work is licensed under the terms of the MIT license.
5// For a copy, see <https://opensource.org/licenses/MIT>.
6
8#include "Carla.h"
11
12#include <util/ue-header-guard-begin.h>
13#include "Components/PoseableMeshComponent.h"
14#include "Components/PrimitiveComponent.h"
15#include "Containers/Map.h"
16#include "GameFramework/Character.h"
17#include "GameFramework/CharacterMovementComponent.h"
18#include "GameFramework/Pawn.h"
19#include "Navigation/CrowdFollowingComponent.h"
20#include "Navigation/PathFollowingComponent.h"
21#include <util/ue-header-guard-end.h>
22
23AWalkerController::AWalkerController(const FObjectInitializer &ObjectInitializer)
24 // Swap the stock path following component for the Detour crowd one so
25 // navigating walkers avoid each other (same pattern as
26 // ADetourCrowdAIController).
27 : Super(ObjectInitializer.SetDefaultSubobjectClass<UCrowdFollowingComponent>(TEXT("PathFollowingComponent")))
28{
29 PrimaryActorTick.bCanEverTick = true;
30}
31
33{
34 Super::OnPossess(InPawn);
35
36 ACharacter *CurrentCharacter = Cast<ACharacter>(InPawn);
37 if (CurrentCharacter == nullptr)
38 {
39 UE_LOG(LogCarla, Error, TEXT("Walker is not a character!"));
40 return;
41 }
42
43 UCharacterMovementComponent *MovementComponent = CurrentCharacter->GetCharacterMovement();
44 if (MovementComponent == nullptr)
45 {
46 UE_LOG(LogCarla, Error, TEXT("Walker missing character movement component!"));
47 return;
48 }
49 MovementComponent->MaxWalkSpeed = GetMaximumWalkSpeed();
50 MovementComponent->JumpZVelocity = 500.0f;
51 CurrentCharacter->JumpMaxCount = 2;
52}
53
55{
56 // Manual control always wins: a client applying a WalkerControl while the
57 // server is path-following expects the walker to obey it (this is also the
58 // path the legacy client-side navigation drives through).
60 {
62 }
63 Control = InControl;
64}
65
66bool AWalkerController::StartNavigation(const bool bIsCrosser)
67{
68 if (GetCharacter() == nullptr)
69 {
70 return false;
71 }
73 {
74 // The crosser draw is per-walker; repeated Start calls keep the first.
75 bNavCrosser = bIsCrosser;
76 }
78 return true;
79}
80
81bool AWalkerController::GoToNavLocation(const FVector &WorldLocation)
82{
84 {
85 return false;
86 }
87 const EPathFollowingRequestResult::Type Result = MoveToLocation(
88 WorldLocation,
89 /*AcceptanceRadius=*/50.0f,
90 /*bStopOnOverlap=*/true,
91 /*bUsePathfinding=*/true,
92 /*bProjectDestinationToNavigation=*/true,
93 /*bCanStrafe=*/true,
94 // Non-crossers plan on a filter that excludes road/crosswalk areas;
95 // crossers use the areas' own costs (crosswalk cheap, road expensive).
96 /*FilterClass=*/bNavCrosser
97 ? TSubclassOf<UNavigationQueryFilter>(UCarlaWalkerCrosserNavFilter::StaticClass())
98 : TSubclassOf<UNavigationQueryFilter>(UCarlaWalkerNavFilter::StaticClass()));
99 if (Result == EPathFollowingRequestResult::Failed)
100 {
101 UE_LOG(LogCarla, Warning,
102 TEXT("Walker %s: MoveToLocation to (%s) failed (no navmesh under target?)"),
103 *GetName(), *WorldLocation.ToCompactString());
104 return false;
105 }
106 return true;
107}
108
109bool AWalkerController::SetNavMaxSpeed(float SpeedCmPerSec)
110{
111 NavMaxSpeed = FMath::Clamp(SpeedCmPerSec, 0.0f, GetMaximumWalkSpeed());
112 ACharacter *CurrentCharacter = GetCharacter();
113 if (CurrentCharacter == nullptr)
114 {
115 return false;
116 }
118 {
119 if (auto *Movement = CurrentCharacter->GetCharacterMovement())
120 {
121 Movement->MaxWalkSpeed = NavMaxSpeed;
122 }
123 }
124 return true;
125}
126
128{
130 {
131 StopMovement();
132 SetNavigationActive(false);
133 }
134 return true;
135}
136
138{
139 bNavigationActive = bActive;
140 ACharacter *CurrentCharacter = GetCharacter();
141 if (CurrentCharacter == nullptr)
142 {
143 return;
144 }
145 if (auto *Movement = CurrentCharacter->GetCharacterMovement())
146 {
147 // While navigating, MaxWalkSpeed IS the walking speed (path following
148 // moves at full input scale). The manual path drives MaxWalkSpeed from
149 // the commanded speed each tick, so on leaving navigation the ceiling
150 // is just a safe starting point until the next manual control arrives.
151 Movement->MaxWalkSpeed = bActive ? NavMaxSpeed : GetMaximumWalkSpeed();
152 // A previously applied manual control (or the interim acceleration
153 // compensation) must not leak into navigation mode.
154 Movement->MaxAcceleration = Movement->GetClass()
155 ->GetDefaultObject<UCharacterMovementComponent>()->MaxAcceleration;
156 }
157 if (bActive)
158 {
159 // Drop any leftover manual input so it cannot fight the path following.
161 }
162}
163
165{
166 ACharacter *CurrentCharacter = GetCharacter();
167 if (!CurrentCharacter) return;
168
169 TArray<USkeletalMeshComponent *> SkeletalMeshes;
170 CurrentCharacter->GetComponents<USkeletalMeshComponent>(SkeletalMeshes, false);
171 USkeletalMeshComponent *SkeletalMesh = SkeletalMeshes.IsValidIndex(0) ? SkeletalMeshes[0] : nullptr;
172 if (!SkeletalMesh) return;
173
174 // get the walker animation class
175 auto *AnimInst = SkeletalMesh->GetAnimInstance();
176 if (!AnimInst) return;
177 UWalkerAnim *WalkerAnim = Cast<UWalkerAnim>(AnimInst);
178 if (!WalkerAnim) return;
179
180 // get current pose
181 FPoseSnapshot TempSnapshot;
182 SkeletalMesh->SnapshotPose(TempSnapshot);
183
184 // copy pose
185 // WalkerAnim->Snap = TempSnapshot;
186
187 for (int i=0; i<TempSnapshot.BoneNames.Num(); ++i)
188 {
189 FWalkerBoneControlOutData Transforms;
190 Transforms.World = SkeletalMesh->GetSocketTransform(TempSnapshot.BoneNames[i], ERelativeTransformSpace::RTS_World);
191 Transforms.Component = SkeletalMesh->GetSocketTransform(TempSnapshot.BoneNames[i], ERelativeTransformSpace::RTS_Actor);
192 Transforms.Relative = SkeletalMesh->GetSocketTransform(TempSnapshot.BoneNames[i], ERelativeTransformSpace::RTS_ParentBoneSpace);
193 // Transforms.Relative = TempSnapshot.LocalTransforms[i];
194
195 WalkerBones.BoneTransforms.Add(TempSnapshot.BoneNames[i].ToString(), Transforms);
196 }
197}
198
200{
201 ACharacter* CurrentCharacter = GetCharacter();
202 if (!CurrentCharacter) return;
203
204 TArray<USkeletalMeshComponent *> SkeletalMeshes;
205 CurrentCharacter->GetComponents<USkeletalMeshComponent>(SkeletalMeshes, false);
206 USkeletalMeshComponent *SkeletalMesh = SkeletalMeshes.IsValidIndex(0) ? SkeletalMeshes[0] : nullptr;
207 if (!SkeletalMesh) return;
208
209 // get the walker animation class
210 auto *AnimInst = SkeletalMesh->GetAnimInstance();
211 if (!AnimInst) return;
212 UWalkerAnim *WalkerAnim = Cast<UWalkerAnim>(AnimInst);
213 if (!WalkerAnim) return;
214
215 // if pose is empty, then get a first version
216 if (WalkerAnim->Snap.BoneNames.Num() == 0)
217 {
218 // get current pose
219 SkeletalMesh->SnapshotPose(WalkerAnim->Snap);
220 }
221
222 TMap<FName, FTransform> BonesMap;
223 for (const TPair<FString, FTransform> &pair : WalkerBones.BoneTransforms)
224 {
225 FName BoneName = FName(*pair.Key);
226 BonesMap.Add(BoneName, pair.Value);
227 }
228
229 // assign common bones
230 for (int i=0; i<WalkerAnim->Snap.BoneNames.Num(); ++i)
231 {
232 FTransform *Trans = BonesMap.Find(WalkerAnim->Snap.BoneNames[i]);
233 if (Trans)
234 {
235 WalkerAnim->Snap.LocalTransforms[i] = *Trans;
236 }
237 }
238}
239
241{
242 ACharacter* CurrentCharacter = GetCharacter();
243 if (!CurrentCharacter) return;
244
245 TArray<USkeletalMeshComponent *> SkeletalMeshes;
246 CurrentCharacter->GetComponents<USkeletalMeshComponent>(SkeletalMeshes, false);
247 USkeletalMeshComponent *SkeletalMesh = SkeletalMeshes.IsValidIndex(0) ? SkeletalMeshes[0] : nullptr;
248 if (!SkeletalMesh) return;
249
250 // get the walker animation class
251 auto *AnimInst = SkeletalMesh->GetAnimInstance();
252 if (!AnimInst) return;
253 UWalkerAnim *WalkerAnim = Cast<UWalkerAnim>(AnimInst);
254 if (!WalkerAnim) return;
255
256 // set current pose
257 WalkerAnim->Blend = Blend;
258}
259
261{
262 ACharacter* CurrentCharacter = GetCharacter();
263 if (!CurrentCharacter) return;
264
265 TArray<USkeletalMeshComponent *> SkeletalMeshes;
266 CurrentCharacter->GetComponents<USkeletalMeshComponent>(SkeletalMeshes, false);
267 USkeletalMeshComponent *SkeletalMesh = SkeletalMeshes.IsValidIndex(0) ? SkeletalMeshes[0] : nullptr;
268 if (!SkeletalMesh) return;
269
270 // get the walker animation class
271 auto *AnimInst = SkeletalMesh->GetAnimInstance();
272 if (!AnimInst) return;
273 UWalkerAnim *WalkerAnim = Cast<UWalkerAnim>(AnimInst);
274 if (!WalkerAnim) return;
275
276 // get current pose
277 SkeletalMesh->SnapshotPose(WalkerAnim->Snap);
278}
279
280void AWalkerController::Tick(float DeltaSeconds)
281{
282 TRACE_CPUPROFILER_EVENT_SCOPE(AWalkerController::Tick);
283 Super::Tick(DeltaSeconds);
284
285 ACharacter* CurrentCharacter = GetCharacter();
286 if (!CurrentCharacter) return;
287
288 // In navigation mode the crowd following component feeds the movement
289 // input; injecting the manual control here as well would double-drive
290 // the character.
291 if (bNavigationActive) return;
292
293 // Commanded speed becomes MaxWalkSpeed and the input is direction-only
294 // at full magnitude: encoding the speed in the input magnitude (the
295 // historical Control.Speed / 4096 scheme) also scaled the acceleration
296 // (UCharacterMovementComponent::ScaleInputAcceleration), so a 1.5 m/s
297 // walker accelerated at ~4% of MaxAcceleration and crawled for seconds
298 // after every spawn or retarget.
299 if (auto *Movement = CurrentCharacter->GetCharacterMovement())
300 {
301 Movement->MaxWalkSpeed = FMath::Clamp(Control.Speed, 0.0f, GetMaximumWalkSpeed());
302 }
303 if (Control.Speed >= 1.0f)
304 {
305 CurrentCharacter->AddMovementInput(Control.Direction, 1.0f);
306 }
307
308 if (Control.Jump)
309 {
310 CurrentCharacter->Jump();
311 }
312}
bool bNavCrosser
Whether this walker may traverse road/crosswalk nav areas.
bool StartNavigation(bool bIsCrosser=false)
– Server-side navmesh navigation ------------------------------------—
bool StopNavigation()
Abort any navigation request and return to manual control.
void SetNavigationActive(bool bActive)
void Tick(float DeltaSeconds) override
float GetMaximumWalkSpeed() const
Maximum walk speed allowed in centimetres per second.
void ApplyWalkerControl(const FWalkerControl &InControl)
void BlendPose(float Blend)
void SetBonesTransform(const FWalkerBoneControlIn &WalkerBones)
FWalkerControl Control
float NavMaxSpeed
Walking speed applied while navigating, cm/s (defaults to the usual pedestrian speed the Python clien...
AWalkerController(const FObjectInitializer &ObjectInitializer)
bool SetNavMaxSpeed(float SpeedCmPerSec)
Walking speed used while navigating, in cm/s.
bool GoToNavLocation(const FVector &WorldLocation)
Path-follow to WorldLocation (world-local centimeters) on the navmesh through the crowd following com...
void OnPossess(APawn *InPawn) override
void GetBonesTransform(FWalkerBoneControlOut &WalkerBones)
FPoseSnapshot Snap
Definition WalkerAnim.h:27
float Blend
Definition WalkerAnim.h:24