A Demo Project for the UnrealEngineSDK
Loading...
Searching...
No Matches
VRSimpleCharacterMovementComponent.cpp
Go to the documentation of this file.
1// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
2
3/*=============================================================================
4 Movement.cpp: Character movement implementation
5
6=============================================================================*/
7
9#include "GameFramework/PhysicsVolume.h"
10#include "GameFramework/GameNetworkManager.h"
11#include "IHeadMountedDisplay.h"
13#include "NavigationSystem.h"
14#include "GameFramework/Character.h"
15#include "GameFramework/GameState.h"
16#include "Engine/Engine.h"
17#include "Components/PrimitiveComponent.h"
18#include "Animation/AnimMontage.h"
19//#include "PhysicsEngine/DestructibleActor.h"
20
21// @todo this is here only due to circular dependency to AIModule. To be removed
22#include "Navigation/PathFollowingComponent.h"
23#include "AI/Navigation/AvoidanceManager.h"
24#include "Components/CapsuleComponent.h"
25#include "Components/BrushComponent.h"
26//#include "Components/DestructibleComponent.h"
27
28#include "Engine/DemoNetDriver.h"
29#include "Engine/NetworkObjectList.h"
30
31DEFINE_LOG_CATEGORY(LogSimpleCharacterMovement);
32
33DECLARE_CYCLE_STAT(TEXT("Char ReplicateMoveToServerVRSimple"), STAT_CharacterMovementReplicateMoveToServerVRSimple, STATGROUP_Character);
34DECLARE_CYCLE_STAT(TEXT("Char CallServerMoveVRSimple"), STAT_CharacterMovementCallServerMoveVRSimple, STATGROUP_Character);
35
36// Defines for build configs
37#if DO_CHECK && !UE_BUILD_SHIPPING // Disable even if checks in shipping are enabled.
38#define devCodeSimple( Code ) checkCode( Code )
39#else
40#define devCodeSimple(...)
41#endif
42
43//#include "PerfCountersHelpers.h"
44//DECLARE_CYCLE_STAT(TEXT("Char PhysWalking"), STAT_CharPhysWalking, STATGROUP_Character);
45//DECLARE_CYCLE_STAT(TEXT("Char PhysFalling"), STAT_CharPhysFalling, STATGROUP_Character);
46//DECLARE_CYCLE_STAT(TEXT("Char PhysNavWalking"), STAT_CharPhysNavWalking, STATGROUP_Character);
47const float MAX_STEP_SIDE_ZZ = 0.08f; // maximum z value for the normal on the vertical side of steps
48const float VERTICAL_SLOPE_NORMAL_ZZ = 0.001f; // Slope is vertical if Abs(Normal.Z) <= this threshold. Accounts for precision problems that sometimes angle normals slightly off horizontal for vertical surface.
49
51 : Super(ObjectInitializer)
52{
53 PostPhysicsTickFunction.bCanEverTick = true;
54 PostPhysicsTickFunction.bStartWithTickEnabled = false;
55 PrimaryComponentTick.TickGroup = TG_PrePhysics;
56 VRRootCapsule = NULL;
57 //VRCameraCollider = NULL;
58
59 this->bRequestedMoveUseAcceleration = false;
60 //this->MaxAcceleration = 200048.0f;
61 //this->BrakingDecelerationWalking = 200048.0f;
62 this->bUpdateOnlyIfRendered = false;
63 this->AirControl = 0.0f;
64
65 bSkipHMDChecks = false;
66 bIsFirstTick = true;
67 //LastAdditionalVRInputVector = FVector::ZeroVector;
68 AdditionalVRInputVector = FVector::ZeroVector;
69 CustomVRInputVector = FVector::ZeroVector;
70
71 SetNetworkMoveDataContainer(VRNetworkMoveDataContainer);
72 SetMoveResponseDataContainer(VRMoveResponseDataContainer);
73
74 //bMaintainHorizontalGroundVelocity = true;
75}
76
77bool UVRSimpleCharacterMovementComponent::VRClimbStepUp(const FVector& GravDir, const FVector& Delta, const FHitResult &InHit, FStepDownResult* OutStepDownResult)
78{
79 //SCOPE_CYCLE_COUNTER(STAT_CharStepUp);
80
81 if (!CanStepUp(InHit) || MaxStepHeight <= 0.f)
82 {
83 return false;
84 }
85
86 const FVector OldLocation = UpdatedComponent->GetComponentLocation();
87 float PawnRadius, PawnHalfHeight;
88 CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleSize(PawnRadius, PawnHalfHeight);
89
90 // Don't bother stepping up if top of capsule is hitting something.
91 const float InitialImpactZ = InHit.ImpactPoint.Z;
92 if (InitialImpactZ > OldLocation.Z + (PawnHalfHeight - PawnRadius))
93 {
94 return false;
95 }
96
97 if (GravDir.IsZero())
98 {
99 return false;
100 }
101
102 // Gravity should be a normalized direction
103 ensure(GravDir.IsNormalized());
104
105 float StepTravelUpHeight = MaxStepHeight;
106 float StepTravelDownHeight = StepTravelUpHeight;
107 const float StepSideZ = -1.f * (InHit.ImpactNormal | GravDir);
108 float PawnInitialFloorBaseZ = OldLocation.Z - PawnHalfHeight;
109 float PawnFloorPointZ = PawnInitialFloorBaseZ;
110
111 if (IsMovingOnGround() && CurrentFloor.IsWalkableFloor())
112 {
113 // Since we float a variable amount off the floor, we need to enforce max step height off the actual point of impact with the floor.
114 const float FloorDist = FMath::Max(0.f, CurrentFloor.GetDistanceToFloor());
115 PawnInitialFloorBaseZ -= FloorDist;
116 StepTravelUpHeight = FMath::Max(StepTravelUpHeight - FloorDist, 0.f);
117 StepTravelDownHeight = (MaxStepHeight + MAX_FLOOR_DIST*2.f);
118
119 const bool bHitVerticalFace = !IsWithinEdgeTolerance(InHit.Location, InHit.ImpactPoint, PawnRadius);
120 if (!CurrentFloor.bLineTrace && !bHitVerticalFace)
121 {
122 PawnFloorPointZ = CurrentFloor.HitResult.ImpactPoint.Z;
123 }
124 else
125 {
126 // Base floor point is the base of the capsule moved down by how far we are hovering over the surface we are hitting.
127 PawnFloorPointZ -= CurrentFloor.FloorDist;
128 }
129 }
130
131 // Don't step up if the impact is below us, accounting for distance from floor.
132 if (InitialImpactZ <= PawnInitialFloorBaseZ)
133 {
134 return false;
135 }
136
137 // Scope our movement updates, and do not apply them until all intermediate moves are completed.
138 FScopedMovementUpdate ScopedStepUpMovement(UpdatedComponent, EScopedUpdate::DeferredUpdates);
139
140 // step up - treat as vertical wall
141 FHitResult SweepUpHit(1.f);
142 const FQuat PawnRotation = UpdatedComponent->GetComponentQuat();
143 MoveUpdatedComponent(-GravDir * StepTravelUpHeight, PawnRotation, true, &SweepUpHit);
144
145 if (SweepUpHit.bStartPenetrating)
146 {
147 // Undo movement
148 ScopedStepUpMovement.RevertMove();
149 return false;
150 }
151
152 // step fwd
153 FHitResult Hit(1.f);
154 MoveUpdatedComponent(Delta, PawnRotation, true, &Hit);
155
156 // Check result of forward movement
157 if (Hit.bBlockingHit)
158 {
159 if (Hit.bStartPenetrating)
160 {
161 // Undo movement
162 ScopedStepUpMovement.RevertMove();
163 return false;
164 }
165
166 // If we hit something above us and also something ahead of us, we should notify about the upward hit as well.
167 // The forward hit will be handled later (in the bSteppedOver case below).
168 // In the case of hitting something above but not forward, we are not blocked from moving so we don't need the notification.
169 if (SweepUpHit.bBlockingHit && Hit.bBlockingHit)
170 {
171 HandleImpact(SweepUpHit);
172 }
173
174 // pawn ran into a wall
175 HandleImpact(Hit);
176 if (IsFalling())
177 {
178 return true;
179 }
180
181 // Don't adjust or slide, just fail here in VR
182 ScopedStepUpMovement.RevertMove();
183 return false;
184 /*
185 // adjust and try again
186 const float ForwardHitTime = Hit.Time;
187 const float ForwardSlideAmount = SlideAlongSurface(Delta, 1.f - Hit.Time, Hit.Normal, Hit, true);
188
189 if (IsFalling())
190 {
191 ScopedStepUpMovement.RevertMove();
192 return false;
193 }
194
195 // If both the forward hit and the deflection got us nowhere, there is no point in this step up.
196 if (ForwardHitTime == 0.f && ForwardSlideAmount == 0.f)
197 {
198 ScopedStepUpMovement.RevertMove();
199 return false;
200 }*/
201 }
202
203 // Step down
204 MoveUpdatedComponent(GravDir * StepTravelDownHeight, UpdatedComponent->GetComponentQuat(), true, &Hit);
205
206 // If step down was initially penetrating abort the step up
207 if (Hit.bStartPenetrating)
208 {
209 ScopedStepUpMovement.RevertMove();
210 return false;
211 }
212
213 FStepDownResult StepDownResult;
214 if (Hit.IsValidBlockingHit())
215 {
216 // See if this step sequence would have allowed us to travel higher than our max step height allows.
217 const float DeltaZ = Hit.ImpactPoint.Z - PawnFloorPointZ;
218 if (DeltaZ > MaxStepHeight)
219 {
220 //UE_LOG(LogSimpleCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (too high Height %.3f) up from floor base %f to %f"), DeltaZ, PawnInitialFloorBaseZ, NewLocation.Z);
221 ScopedStepUpMovement.RevertMove();
222 return false;
223 }
224
225 // Reject unwalkable surface normals here.
226 if (!IsWalkable(Hit))
227 {
228 // Reject if normal opposes movement direction
229 const bool bNormalTowardsMe = (Delta | Hit.ImpactNormal) < 0.f;
230 if (bNormalTowardsMe)
231 {
232 //UE_LOG(LogSimpleCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (unwalkable normal %s opposed to movement)"), *Hit.ImpactNormal.ToString());
233 ScopedStepUpMovement.RevertMove();
234 return false;
235 }
236
237 // Also reject if we would end up being higher than our starting location by stepping down.
238 // It's fine to step down onto an unwalkable normal below us, we will just slide off. Rejecting those moves would prevent us from being able to walk off the edge.
239 if (Hit.Location.Z > OldLocation.Z)
240 {
241 //UE_LOG(LogSimpleCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (unwalkable normal %s above old position)"), *Hit.ImpactNormal.ToString());
242 ScopedStepUpMovement.RevertMove();
243 return false;
244 }
245 }
246
247 // Reject moves where the downward sweep hit something very close to the edge of the capsule. This maintains consistency with FindFloor as well.
248 if (!IsWithinEdgeTolerance(Hit.Location, Hit.ImpactPoint, PawnRadius))
249 {
250 //UE_LOG(LogSimpleCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (outside edge tolerance)"));
251 ScopedStepUpMovement.RevertMove();
252 return false;
253 }
254
255 // Don't step up onto invalid surfaces if traveling higher.
256 if (DeltaZ > 0.f && !CanStepUp(Hit))
257 {
258 //UE_LOG(LogSimpleCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (up onto surface with !CanStepUp())"));
259 ScopedStepUpMovement.RevertMove();
260 return false;
261 }
262
263 // See if we can validate the floor as a result of this step down. In almost all cases this should succeed, and we can avoid computing the floor outside this method.
264 if (OutStepDownResult != NULL)
265 {
266 FindFloor(UpdatedComponent->GetComponentLocation(), StepDownResult.FloorResult, false, &Hit);
267
268 // Reject unwalkable normals if we end up higher than our initial height.
269 // It's fine to walk down onto an unwalkable surface, don't reject those moves.
270 if (Hit.Location.Z > OldLocation.Z)
271 {
272 // We should reject the floor result if we are trying to step up an actual step where we are not able to perch (this is rare).
273 // In those cases we should instead abort the step up and try to slide along the stair.
274 if (!StepDownResult.FloorResult.bBlockingHit && StepSideZ < MAX_STEP_SIDE_ZZ)
275 {
276 ScopedStepUpMovement.RevertMove();
277 return false;
278 }
279 }
280
281 StepDownResult.bComputedFloor = true;
282 }
283 }
284
285 // Copy step down result.
286 if (OutStepDownResult != NULL)
287 {
288 *OutStepDownResult = StepDownResult;
289 }
290
291 // Don't recalculate velocity based on this height adjustment, if considering vertical adjustments.
292 bJustTeleported |= !bMaintainHorizontalGroundVelocity;
293
294 return true;
295}
296
297void UVRSimpleCharacterMovementComponent::PhysFlying(float deltaTime, int32 Iterations)
298{
299 if (deltaTime < MIN_TICK_TIME)
300 {
301 return;
302 }
303
304 RestorePreAdditiveRootMotionVelocity();
306
307 if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity())
308 {
309 if (bCheatFlying && Acceleration.IsZero())
310 {
311 Velocity = FVector::ZeroVector;
312 }
313 const float Friction = 0.5f * GetPhysicsVolume()->FluidFriction;
314 CalcVelocity(deltaTime, Friction, true, BrakingDecelerationFlying);
315 }
316
317 ApplyRootMotionToVelocity(deltaTime);
318 ApplyVRMotionToVelocity(deltaTime);
319
320 Iterations++;
321 bJustTeleported = false;
322
323 FVector OldLocation = UpdatedComponent->GetComponentLocation();
324 const FVector Adjusted = Velocity * deltaTime;
325 FHitResult Hit(1.f);
326 SafeMoveUpdatedComponent(Adjusted, UpdatedComponent->GetComponentQuat(), true, Hit);
327
328 if (Hit.Time < 1.f)
329 {
330 const FVector GravDir = FVector(0.f, 0.f, -1.f);
331 const FVector VelDir = Velocity.GetSafeNormal();
332 const float UpDown = GravDir | VelDir;
333
334 bool bSteppedUp = false;
335 if ((FMath::Abs(Hit.ImpactNormal.Z) < 0.2f) && (UpDown < 0.5f) && (UpDown > -0.2f) && CanStepUp(Hit))
336 {
337 float stepZ = UpdatedComponent->GetComponentLocation().Z;
338 bSteppedUp = StepUp(GravDir, Adjusted * (1.f - Hit.Time), Hit);
339 if (bSteppedUp)
340 {
341 OldLocation.Z = UpdatedComponent->GetComponentLocation().Z + (OldLocation.Z - stepZ);
342 }
343 }
344
345 if (!bSteppedUp)
346 {
347 //adjust and try again
348 HandleImpact(Hit, deltaTime, Adjusted);
349 SlideAlongSurface(Adjusted, (1.f - Hit.Time), Hit.Normal, Hit, true);
350 }
351 }
352
353 if (!bJustTeleported && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity())
354 {
355 Velocity = (UpdatedComponent->GetComponentLocation() - OldLocation) / deltaTime;
356 }
357}
358
359void UVRSimpleCharacterMovementComponent::PhysNavWalking(float deltaTime, int32 Iterations)
360{
361 //SCOPE_CYCLE_COUNTER(STAT_CharPhysNavWalking);
362
363 if (deltaTime < MIN_TICK_TIME)
364 {
365 return;
366 }
367
368 if ((!CharacterOwner || !CharacterOwner->Controller) && !bRunPhysicsWithNoController && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity())
369 {
370 Acceleration = FVector::ZeroVector;
371 Velocity = FVector::ZeroVector;
372 return;
373 }
374
375 RestorePreAdditiveRootMotionVelocity();
377
378 // Ensure velocity is horizontal.
379 MaintainHorizontalGroundVelocity();
380 devCodeSimple(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysNavWalking: Velocity contains NaN before CalcVelocity (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString()));
381
382 //bound acceleration
383 Acceleration.Z = 0.f;
384 if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity())
385 {
386 CalcVelocity(deltaTime, GroundFriction, false, BrakingDecelerationWalking);
387 devCodeSimple(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysNavWalking: Velocity contains NaN after CalcVelocity (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString()));
388 }
389
390 ApplyRootMotionToVelocity(deltaTime);
391 ApplyVRMotionToVelocity(deltaTime);
392
393 if (IsFalling())
394 {
395 // Root motion could have put us into Falling
396 StartNewPhysics(deltaTime, Iterations);
397 return;
398 }
399
400 Iterations++;
401
402 FVector DesiredMove = Velocity;
403 DesiredMove.Z = 0.f;
404
405 const FVector OldLocation = GetActorFeetLocation();
406 const FVector DeltaMove = DesiredMove * deltaTime;
407 const bool bDeltaMoveNearlyZero = DeltaMove.IsNearlyZero();
408
409 FVector AdjustedDest = OldLocation + DeltaMove;
410 FNavLocation DestNavLocation;
411
412 bool bSameNavLocation = false;
413 if (CachedNavLocation.NodeRef != INVALID_NAVNODEREF)
414 {
415 if (bProjectNavMeshWalking)
416 {
417 const float DistSq2D = (OldLocation - CachedNavLocation.Location).SizeSquared2D();
418 const float DistZ = FMath::Abs(OldLocation.Z - CachedNavLocation.Location.Z);
419
420 const float TotalCapsuleHeight = CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleHalfHeight() * 2.0f;
421 const float ProjectionScale = (OldLocation.Z > CachedNavLocation.Location.Z) ? NavMeshProjectionHeightScaleUp : NavMeshProjectionHeightScaleDown;
422 const float DistZThr = TotalCapsuleHeight * FMath::Max(0.f, ProjectionScale);
423
424 bSameNavLocation = (DistSq2D <= KINDA_SMALL_NUMBER) && (DistZ < DistZThr);
425 }
426 else
427 {
428 bSameNavLocation = CachedNavLocation.Location.Equals(OldLocation);
429 }
430
431 if (bDeltaMoveNearlyZero && bSameNavLocation)
432 {
433 if (const INavigationDataInterface * NavData = GetNavData())
434 {
435 if (!NavData->IsNodeRefValid(CachedNavLocation.NodeRef))
436 {
437 CachedNavLocation.NodeRef = INVALID_NAVNODEREF;
438 bSameNavLocation = false;
439 }
440 }
441 }
442 }
443
444
445 if (bDeltaMoveNearlyZero && bSameNavLocation)
446 {
447 DestNavLocation = CachedNavLocation;
448 //UE_LOG(LogNavMeshMovement, VeryVerbose, TEXT("%s using cached navmesh location! (bProjectNavMeshWalking = %d)"), *GetNameSafe(CharacterOwner), bProjectNavMeshWalking);
449 }
450 else
451 {
452 // SCOPE_CYCLE_COUNTER(STAT_CharNavProjectPoint);
453
454 // Start the trace from the Z location of the last valid trace.
455 // Otherwise if we are projecting our location to the underlying geometry and it's far above or below the navmesh,
456 // we'll follow that geometry's plane out of range of valid navigation.
457 if (bSameNavLocation && bProjectNavMeshWalking)
458 {
459 AdjustedDest.Z = CachedNavLocation.Location.Z;
460 }
461
462 // Find the point on the NavMesh
463 const bool bHasNavigationData = FindNavFloor(AdjustedDest, DestNavLocation);
464 if (!bHasNavigationData)
465 {
466 SetMovementMode(MOVE_Walking);
467 return;
468 }
469
470 CachedNavLocation = DestNavLocation;
471 }
472
473 if (DestNavLocation.NodeRef != INVALID_NAVNODEREF)
474 {
475 FVector NewLocation(AdjustedDest.X, AdjustedDest.Y, DestNavLocation.Location.Z);
476 if (bProjectNavMeshWalking)
477 {
478 // SCOPE_CYCLE_COUNTER(STAT_CharNavProjectLocation);
479 const float TotalCapsuleHeight = CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleHalfHeight() * 2.0f;
480 const float UpOffset = TotalCapsuleHeight * FMath::Max(0.f, NavMeshProjectionHeightScaleUp);
481 const float DownOffset = TotalCapsuleHeight * FMath::Max(0.f, NavMeshProjectionHeightScaleDown);
482 NewLocation = ProjectLocationFromNavMesh(deltaTime, OldLocation, NewLocation, UpOffset, DownOffset);
483 }
484
485 FVector AdjustedDelta = NewLocation - OldLocation;
486
487 if (!AdjustedDelta.IsNearlyZero())
488 {
489 FHitResult HitResult;
490 SafeMoveUpdatedComponent(AdjustedDelta, UpdatedComponent->GetComponentQuat(), bSweepWhileNavWalking, HitResult);
491 }
492
493 // Update velocity to reflect actual move
494 if (!bJustTeleported && !HasAnimRootMotion() && !CurrentRootMotion.HasVelocity())
495 {
496 Velocity = (GetActorFeetLocation() - OldLocation) / deltaTime;
497 MaintainHorizontalGroundVelocity();
498 }
499
500 bJustTeleported = false;
501 }
502 else
503 {
504 StartFalling(Iterations, deltaTime, deltaTime, DeltaMove, OldLocation);
505 }
506}
507
508void UVRSimpleCharacterMovementComponent::PhysFalling(float deltaTime, int32 Iterations)
509{
510 //SCOPE_CYCLE_COUNTER(STAT_CharPhysFalling);
511
512 if (deltaTime < MIN_TICK_TIME)
513 {
514 return;
515 }
516
517 FVector FallAcceleration = GetFallingLateralAcceleration(deltaTime);
518 FallAcceleration.Z = 0.f;
519 const bool bHasLimitedAirControl = ShouldLimitAirControl(deltaTime, FallAcceleration);
520
521 float remainingTime = deltaTime;
522 while ((remainingTime >= MIN_TICK_TIME) && (Iterations < MaxSimulationIterations))
523 {
524 Iterations++;
525 float timeTick = GetSimulationTimeStep(remainingTime, Iterations);
526 remainingTime -= timeTick;
527
528 const FVector OldLocation = UpdatedComponent->GetComponentLocation();
529 const FQuat PawnRotation = UpdatedComponent->GetComponentQuat();
530 bJustTeleported = false;
531
532 RestorePreAdditiveRootMotionVelocity();
533 //RestorePreAdditiveVRMotionVelocity();
534
535 const FVector OldVelocity = Velocity;
536
537 // Apply input
538 const float MaxDecel = GetMaxBrakingDeceleration();
539 if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity())
540 {
541 // Compute Velocity
542 {
543 // Acceleration = FallAcceleration for CalcVelocity(), but we restore it after using it.
544 TGuardValue<FVector> RestoreAcceleration(Acceleration, FallAcceleration);
545 Velocity.Z = 0.f;
546 CalcVelocity(timeTick, FallingLateralFriction, false, BrakingDecelerationFalling);
547 Velocity.Z = OldVelocity.Z;
548 }
549 }
550
551 // Compute current gravity
552 const FVector Gravity(0.f, 0.f, GetGravityZ());
553
554 float GravityTime = timeTick;
555
556 // If jump is providing force, gravity may be affected.
557 bool bEndingJumpForce = false;
558 if (CharacterOwner->JumpForceTimeRemaining > 0.0f)
559 {
560 // Consume some of the force time. Only the remaining time (if any) is affected by gravity when bApplyGravityWhileJumping=false.
561 const float JumpForceTime = FMath::Min(CharacterOwner->JumpForceTimeRemaining, timeTick);
562 GravityTime = bApplyGravityWhileJumping ? timeTick : FMath::Max(0.0f, timeTick - JumpForceTime);
563
564 // Update Character state
565 CharacterOwner->JumpForceTimeRemaining -= JumpForceTime;
566 if (CharacterOwner->JumpForceTimeRemaining <= 0.0f)
567 {
568 CharacterOwner->ResetJumpState();
569 bEndingJumpForce = true;
570 }
571 }
572
573 // Apply gravity
574 Velocity = NewFallVelocity(Velocity, Gravity, GravityTime);
575
576 // See if we need to sub-step to exactly reach the apex. This is important for avoiding "cutting off the top" of the trajectory as framerate varies.
577 static const auto CVarForceJumpPeakSubstep = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ForceJumpPeakSubstep"));
578 if (CVarForceJumpPeakSubstep->GetInt() != 0 && OldVelocity.Z > 0.f && Velocity.Z <= 0.f && NumJumpApexAttempts < MaxJumpApexAttemptsPerSimulation)
579 {
580 const FVector DerivedAccel = (Velocity - OldVelocity) / timeTick;
581 if (!FMath::IsNearlyZero(DerivedAccel.Z))
582 {
583 const float TimeToApex = -OldVelocity.Z / DerivedAccel.Z;
584
585 // The time-to-apex calculation should be precise, and we want to avoid adding a substep when we are basically already at the apex from the previous iteration's work.
586 const float ApexTimeMinimum = 0.0001f;
587 if (TimeToApex >= ApexTimeMinimum && TimeToApex < timeTick)
588 {
589 const FVector ApexVelocity = OldVelocity + DerivedAccel * TimeToApex;
590 Velocity = ApexVelocity;
591 Velocity.Z = 0.f; // Should be nearly zero anyway, but this makes apex notifications consistent.
592
593 // We only want to move the amount of time it takes to reach the apex, and refund the unused time for next iteration.
594 remainingTime += (timeTick - TimeToApex);
595 timeTick = TimeToApex;
596 Iterations--;
597 NumJumpApexAttempts++;
598 }
599 }
600 }
601
602 //UE_LOG(LogCharacterMovement, Log, TEXT("dt=(%.6f) OldLocation=(%s) OldVelocity=(%s) NewVelocity=(%s)"), timeTick, *(UpdatedComponent->GetComponentLocation()).ToString(), *OldVelocity.ToString(), *Velocity.ToString());
603
604 ApplyRootMotionToVelocity(timeTick);
605 //ApplyVRMotionToVelocity(timeTick);
606
607 if (bNotifyApex && (Velocity.Z < 0.f))
608 {
609 // Just passed jump apex since now going down
610 bNotifyApex = false;
611 NotifyJumpApex();
612 }
613
614 // Compute change in position (using midpoint integration method).
615 FVector Adjusted = (0.5f * (OldVelocity + Velocity) * timeTick) + (AdditionalVRInputVector );
616
617 // Special handling if ending the jump force where we didn't apply gravity during the jump.
618 if (bEndingJumpForce && !bApplyGravityWhileJumping)
619 {
620 // We had a portion of the time at constant speed then a portion with acceleration due to gravity.
621 // Account for that here with a more correct change in position.
622 const float NonGravityTime = FMath::Max(0.f, timeTick - GravityTime);
623 Adjusted = ((OldVelocity * NonGravityTime) + (0.5f * (OldVelocity + Velocity) * GravityTime)) + (AdditionalVRInputVector );
624 }
625
626
627 // Move
628 FHitResult Hit(1.f);
629 SafeMoveUpdatedComponent(Adjusted, PawnRotation, true, Hit);
630
631 if (!HasValidData())
632 {
633 return;
634 }
635
636 float LastMoveTimeSlice = timeTick;
637 float subTimeTickRemaining = timeTick * (1.f - Hit.Time);
638
639 if (IsSwimming()) //just entered water
640 {
641 remainingTime += subTimeTickRemaining;
642 StartSwimming(OldLocation, OldVelocity, timeTick, remainingTime, Iterations);
643 return;
644 }
645 else if (Hit.bBlockingHit)
646 {
647 if (IsValidLandingSpot(UpdatedComponent->GetComponentLocation(), Hit))
648 {
649 remainingTime += subTimeTickRemaining;
650 ProcessLanded(Hit, remainingTime, Iterations);
651 return;
652 }
653 else
654 {
655 // Compute impact deflection based on final velocity, not integration step.
656 // This allows us to compute a new velocity from the deflected vector, and ensures the full gravity effect is included in the slide result.
657 Adjusted = Velocity * timeTick;
658
659 // See if we can convert a normally invalid landing spot (based on the hit result) to a usable one.
660 if (!Hit.bStartPenetrating && ShouldCheckForValidLandingSpot(timeTick, Adjusted, Hit))
661 {
662 const FVector PawnLocation = UpdatedComponent->GetComponentLocation();
663 FFindFloorResult FloorResult;
664 FindFloor(PawnLocation, FloorResult, false);
665 if (FloorResult.IsWalkableFloor() && IsValidLandingSpot(PawnLocation, FloorResult.HitResult))
666 {
667 remainingTime += subTimeTickRemaining;
668 ProcessLanded(FloorResult.HitResult, remainingTime, Iterations);
669 return;
670 }
671 }
672
673 HandleImpact(Hit, LastMoveTimeSlice, Adjusted);
674
675 // If we've changed physics mode, abort.
676 if (!HasValidData() || !IsFalling())
677 {
678 return;
679 }
680
681 // Limit air control based on what we hit.
682 // We moved to the impact point using air control, but may want to deflect from there based on a limited air control acceleration.
683 FVector VelocityNoAirControl = OldVelocity;
684 FVector AirControlAccel = Acceleration;
685 if (bHasLimitedAirControl)
686 {
687 // Compute VelocityNoAirControl
688 {
689 // Find velocity *without* acceleration.
690 TGuardValue<FVector> RestoreAcceleration(Acceleration, FVector::ZeroVector);
691 TGuardValue<FVector> RestoreVelocity(Velocity, OldVelocity);
692 Velocity.Z = 0.f;
693 CalcVelocity(timeTick, FallingLateralFriction, false, MaxDecel);
694 VelocityNoAirControl = FVector(Velocity.X, Velocity.Y, OldVelocity.Z);
695 VelocityNoAirControl = NewFallVelocity(VelocityNoAirControl, Gravity, GravityTime);
696 }
697
698
699 const bool bCheckLandingSpot = false; // we already checked above.
700 AirControlAccel = (Velocity - VelocityNoAirControl) / timeTick;
701 const FVector AirControlDeltaV = LimitAirControl(LastMoveTimeSlice, AirControlAccel, Hit, bCheckLandingSpot) * LastMoveTimeSlice;
702 Adjusted = (VelocityNoAirControl + AirControlDeltaV) * LastMoveTimeSlice;
703 }
704
705 const FVector OldHitNormal = Hit.Normal;
706 const FVector OldHitImpactNormal = Hit.ImpactNormal;
707 FVector Delta = ComputeSlideVector(Adjusted, 1.f - Hit.Time, OldHitNormal, Hit);
708
709 // Compute velocity after deflection (only gravity component for RootMotion)
710 if (subTimeTickRemaining > KINDA_SMALL_NUMBER && !bJustTeleported)
711 {
712 const FVector NewVelocity = (Delta / subTimeTickRemaining);
713 Velocity = HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocityWithIgnoreZAccumulate() ? FVector(Velocity.X, Velocity.Y, NewVelocity.Z) : NewVelocity;
714 }
715
716 if (subTimeTickRemaining > KINDA_SMALL_NUMBER && (Delta | Adjusted) > 0.f)
717 {
718 // Move in deflected direction.
719 SafeMoveUpdatedComponent(Delta, PawnRotation, true, Hit);
720
721 if (Hit.bBlockingHit)
722 {
723 // hit second wall
724 LastMoveTimeSlice = subTimeTickRemaining;
725 subTimeTickRemaining = subTimeTickRemaining * (1.f - Hit.Time);
726
727 if (IsValidLandingSpot(UpdatedComponent->GetComponentLocation(), Hit))
728 {
729 remainingTime += subTimeTickRemaining;
730 ProcessLanded(Hit, remainingTime, Iterations);
731 return;
732 }
733
734 HandleImpact(Hit, LastMoveTimeSlice, Delta);
735
736 // If we've changed physics mode, abort.
737 if (!HasValidData() || !IsFalling())
738 {
739 return;
740 }
741
742 // Act as if there was no air control on the last move when computing new deflection.
743 if (bHasLimitedAirControl && Hit.Normal.Z > VERTICAL_SLOPE_NORMAL_ZZ)
744 {
745 const FVector LastMoveNoAirControl = VelocityNoAirControl * LastMoveTimeSlice;
746 Delta = ComputeSlideVector(LastMoveNoAirControl, 1.f, OldHitNormal, Hit);
747 }
748
749 FVector PreTwoWallDelta = Delta;
750 TwoWallAdjust(Delta, Hit, OldHitNormal);
751
752 // Limit air control, but allow a slide along the second wall.
753 if (bHasLimitedAirControl)
754 {
755 const bool bCheckLandingSpot = false; // we already checked above.
756 const FVector AirControlDeltaV = LimitAirControl(subTimeTickRemaining, AirControlAccel, Hit, bCheckLandingSpot) * subTimeTickRemaining;
757
758 // Only allow if not back in to first wall
759 if (FVector::DotProduct(AirControlDeltaV, OldHitNormal) > 0.f)
760 {
761 Delta += (AirControlDeltaV * subTimeTickRemaining);
762 }
763 }
764
765 // Compute velocity after deflection (only gravity component for RootMotion)
766 if (subTimeTickRemaining > KINDA_SMALL_NUMBER && !bJustTeleported)
767 {
768 const FVector NewVelocity = (Delta / subTimeTickRemaining);
769 Velocity = HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocityWithIgnoreZAccumulate() ? FVector(Velocity.X, Velocity.Y, NewVelocity.Z) : NewVelocity;
770 }
771
772 // bDitch=true means that pawn is straddling two slopes, neither of which he can stand on
773 bool bDitch = ((OldHitImpactNormal.Z > 0.f) && (Hit.ImpactNormal.Z > 0.f) && (FMath::Abs(Delta.Z) <= KINDA_SMALL_NUMBER) && ((Hit.ImpactNormal | OldHitImpactNormal) < 0.f));
774 SafeMoveUpdatedComponent(Delta, PawnRotation, true, Hit);
775 if (Hit.Time == 0.f)
776 {
777 // if we are stuck then try to side step
778 FVector SideDelta = (OldHitNormal + Hit.ImpactNormal).GetSafeNormal2D();
779 if (SideDelta.IsNearlyZero())
780 {
781 SideDelta = FVector(OldHitNormal.Y, -OldHitNormal.X, 0).GetSafeNormal();
782 }
783 SafeMoveUpdatedComponent(SideDelta, PawnRotation, true, Hit);
784 }
785
786 if (bDitch || IsValidLandingSpot(UpdatedComponent->GetComponentLocation(), Hit) || Hit.Time == 0.f)
787 {
788 remainingTime = 0.f;
789 ProcessLanded(Hit, remainingTime, Iterations);
790 return;
791 }
792 else if (GetPerchRadiusThreshold() > 0.f && Hit.Time == 1.f && OldHitImpactNormal.Z >= GetWalkableFloorZ())
793 {
794 // We might be in a virtual 'ditch' within our perch radius. This is rare.
795 const FVector PawnLocation = UpdatedComponent->GetComponentLocation();
796 const float ZMovedDist = FMath::Abs(PawnLocation.Z - OldLocation.Z);
797 const float MovedDist2DSq = (PawnLocation - OldLocation).SizeSquared2D();
798 if (ZMovedDist <= 0.2f * timeTick && MovedDist2DSq <= 4.f * timeTick)
799 {
800 Velocity.X += 0.25f * GetMaxSpeed() * (RandomStream.FRand() - 0.5f);
801 Velocity.Y += 0.25f * GetMaxSpeed() * (RandomStream.FRand() - 0.5f);
802 Velocity.Z = FMath::Max<float>(JumpZVelocity * 0.25f, 1.f);
803 Delta = Velocity * timeTick;
804 SafeMoveUpdatedComponent(Delta, PawnRotation, true, Hit);
805 }
806 }
807 }
808 }
809 }
810 }
811
812 if (Velocity.SizeSquared2D() <= KINDA_SMALL_NUMBER * 10.f)
813 {
814 Velocity.X = 0.f;
815 Velocity.Y = 0.f;
816 }
817 }
818}
819
820void UVRSimpleCharacterMovementComponent::PhysWalking(float deltaTime, int32 Iterations)
821{
822// SCOPE_CYCLE_COUNTER(STAT_CharPhysWalking);
823
824 if (deltaTime < MIN_TICK_TIME)
825 {
826 return;
827 }
828
829 if (!CharacterOwner || (!CharacterOwner->Controller && !bRunPhysicsWithNoController && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && (CharacterOwner->GetLocalRole() != ROLE_SimulatedProxy)))
830 {
831 Acceleration = FVector::ZeroVector;
832 Velocity = FVector::ZeroVector;
833 return;
834 }
835
836 if (!UpdatedComponent->IsQueryCollisionEnabled())
837 {
838 SetMovementMode(MOVE_Walking);
839 return;
840 }
841
842 devCodeSimple(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysWalking: Velocity contains NaN before Iteration (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString()));
843
844 bJustTeleported = false;
845 bool bCheckedFall = false;
846 bool bTriedLedgeMove = false;
847 float remainingTime = deltaTime;
848
849 //bool bHasLastAdditiveVelocity = false;
850 //FVector LastPreAdditiveVRVelocity;
851 // Perform the move
852 while ((remainingTime >= MIN_TICK_TIME) && (Iterations < MaxSimulationIterations) && CharacterOwner && (CharacterOwner->Controller || bRunPhysicsWithNoController || HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocity() || (CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy)))
853 {
854 Iterations++;
855 bJustTeleported = false;
856 const float timeTick = GetSimulationTimeStep(remainingTime, Iterations);
857 remainingTime -= timeTick;
858
859 // Save current values
860 UPrimitiveComponent * const OldBase = GetMovementBase();
861 const FVector PreviousBaseLocation = (OldBase != NULL) ? OldBase->GetComponentLocation() : FVector::ZeroVector;
862 const FVector OldLocation = UpdatedComponent->GetComponentLocation();
863 const FFindFloorResult OldFloor = CurrentFloor;
864
865 RestorePreAdditiveRootMotionVelocity();
867
868 // Ensure velocity is horizontal.
869 MaintainHorizontalGroundVelocity();
870 const FVector OldVelocity = Velocity;
871 Acceleration.Z = 0.f;
872
873 // Apply acceleration
874 if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity())
875 {
876 CalcVelocity(timeTick, GroundFriction, false, BrakingDecelerationWalking);
877 devCodeSimple(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysWalking: Velocity contains NaN after CalcVelocity (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString()));
878 }
879
880 ApplyRootMotionToVelocity(timeTick);
881 ApplyVRMotionToVelocity(deltaTime);//timeTick);
882
883 devCodeSimple(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysWalking: Velocity contains NaN after Root Motion application (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString()));
884
885 //LastPreAdditiveVRVelocity = Velocity;
886 //bHasLastAdditiveVelocity = true;
887 //Velocity += AdditionalVRInputVector / timeTick;
888
889 if (IsFalling())
890 {
891 // Root motion could have put us into Falling.
892 // No movement has taken place this movement tick so we pass on full time/past iteration count
893 StartNewPhysics(remainingTime + timeTick, Iterations - 1);
894 return;
895 }
896
897
898 // Compute move parameters
899 const FVector MoveVelocity = Velocity;
900 const FVector Delta = (timeTick * MoveVelocity);// +AdditionalVRInputVector;
901
902 const bool bZeroDelta = Delta.IsNearlyZero();
903 FStepDownResult StepDownResult;
904
905
906 if (bZeroDelta)
907 {
908 remainingTime = 0.f;
909 }
910 else
911 {
912 // try to move forward
913 MoveAlongFloor(MoveVelocity, timeTick, &StepDownResult);
914
915 if (IsFalling())
916 {
917 // pawn decided to jump up
918 const float DesiredDist = Delta.Size();
919 if (DesiredDist > KINDA_SMALL_NUMBER)
920 {
921 const float ActualDist = (UpdatedComponent->GetComponentLocation() - OldLocation).Size2D();
922 remainingTime += timeTick * (1.f - FMath::Min(1.f, ActualDist / DesiredDist));
923 }
924 StartNewPhysics(remainingTime, Iterations);
925 return;
926 }
927 else if (IsSwimming()) //just entered water
928 {
929 StartSwimming(OldLocation, OldVelocity, timeTick, remainingTime, Iterations);
930 return;
931 }
932 }
933
934 // Update floor.
935 // StepUp might have already done it for us.
936 if (StepDownResult.bComputedFloor)
937 {
938 CurrentFloor = StepDownResult.FloorResult;
939 }
940 else
941 {
942 FindFloor(UpdatedComponent->GetComponentLocation(), CurrentFloor, bZeroDelta, NULL);
943 }
944
945 // check for ledges here
946 const bool bCheckLedges = !CanWalkOffLedges();
947 if (bCheckLedges && !CurrentFloor.IsWalkableFloor())
948 {
949 // calculate possible alternate movement
950 const FVector GravDir = FVector(0.f, 0.f, -1.f);
951 const FVector NewDelta = bTriedLedgeMove ? FVector::ZeroVector : GetLedgeMove(OldLocation, Delta, GravDir);
952 if (!NewDelta.IsZero())
953 {
954 // first revert this move
955 RevertMove(OldLocation, OldBase, PreviousBaseLocation, OldFloor, false);
956
957 // avoid repeated ledge moves if the first one fails
958 bTriedLedgeMove = true;
959
960 // Try new movement direction
961 Velocity = NewDelta / timeTick;
962 remainingTime += timeTick;
963 continue;
964 }
965 else
966 {
967 // see if it is OK to jump
968 // @todo collision : only thing that can be problem is that oldbase has world collision on
969 bool bMustJump = bZeroDelta || (OldBase == NULL || (!OldBase->IsQueryCollisionEnabled() && MovementBaseUtility::IsDynamicBase(OldBase)));
970 if ((bMustJump || !bCheckedFall) && CheckFall(OldFloor, CurrentFloor.HitResult, Delta, OldLocation, remainingTime, timeTick, Iterations, bMustJump))
971 {
972 return;
973 }
974 bCheckedFall = true;
975
976 // revert this move
977 RevertMove(OldLocation, OldBase, PreviousBaseLocation, OldFloor, true);
978 remainingTime = 0.f;
979 break;
980 }
981 }
982 else
983 {
984 // Validate the floor check
985 if (CurrentFloor.IsWalkableFloor())
986 {
987 if (ShouldCatchAir(OldFloor, CurrentFloor))
988 {
989 HandleWalkingOffLedge(OldFloor.HitResult.ImpactNormal, OldFloor.HitResult.Normal, OldLocation, timeTick);
990 if (IsMovingOnGround())
991 {
992 // If still walking, then fall. If not, assume the user set a different mode they want to keep.
993 StartFalling(Iterations, remainingTime, timeTick, Delta, OldLocation);
994 }
995 return;
996 }
997
998 AdjustFloorHeight();
999 SetBase(CurrentFloor.HitResult.Component.Get(), CurrentFloor.HitResult.BoneName);
1000 }
1001 else if (CurrentFloor.HitResult.bStartPenetrating && remainingTime <= 0.f)
1002 {
1003 // The floor check failed because it started in penetration
1004 // We do not want to try to move downward because the downward sweep failed, rather we'd like to try to pop out of the floor.
1005 FHitResult Hit(CurrentFloor.HitResult);
1006 Hit.TraceEnd = Hit.TraceStart + FVector(0.f, 0.f, MAX_FLOOR_DIST);
1007 const FVector RequestedAdjustment = GetPenetrationAdjustment(Hit);
1008 ResolvePenetration(RequestedAdjustment, Hit, UpdatedComponent->GetComponentQuat());
1009 bForceNextFloorCheck = true;
1010 }
1011
1012 // check if just entered water
1013 if (IsSwimming())
1014 {
1015 StartSwimming(OldLocation, Velocity, timeTick, remainingTime, Iterations);
1016 return;
1017 }
1018
1019 // See if we need to start falling.
1020 if (!CurrentFloor.IsWalkableFloor() && !CurrentFloor.HitResult.bStartPenetrating)
1021 {
1022 const bool bMustJump = bJustTeleported || bZeroDelta || (OldBase == NULL || (!OldBase->IsQueryCollisionEnabled() && MovementBaseUtility::IsDynamicBase(OldBase)));
1023 if ((bMustJump || !bCheckedFall) && CheckFall(OldFloor, CurrentFloor.HitResult, Delta, OldLocation, remainingTime, timeTick, Iterations, bMustJump))
1024 {
1025 return;
1026 }
1027 bCheckedFall = true;
1028 }
1029 }
1030
1031
1032 // Allow overlap events and such to change physics state and velocity
1033 if (IsMovingOnGround())
1034 {
1035 // Make velocity reflect actual move
1036 if (!bJustTeleported && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && timeTick >= MIN_TICK_TIME)
1037 {
1038 // TODO-RootMotionSource: Allow this to happen during partial override Velocity, but only set allowed axes?
1039 Velocity = (UpdatedComponent->GetComponentLocation() - OldLocation) / timeTick;
1040 MaintainHorizontalGroundVelocity();
1041 }
1042 }
1043
1044 // If we didn't move at all this iteration then abort (since future iterations will also be stuck).
1045 if (UpdatedComponent->GetComponentLocation() == OldLocation)
1046 {
1047 remainingTime = 0.f;
1048 break;
1049 }
1050 }
1051
1052 if (IsMovingOnGround())
1053 {
1054 MaintainHorizontalGroundVelocity();
1055 }
1056}
1057
1058
1059void UVRSimpleCharacterMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
1060{
1061 if (!bSkipHMDChecks)
1062 {
1063 if (CharacterOwner->IsLocallyControlled())
1064 {
1065 FQuat curRot;
1066 bool bWasHeadset = false;
1067
1068 if (GEngine->XRSystem.IsValid() && GEngine->XRSystem->IsHeadTrackingAllowedForWorld(*GetWorld()))
1069 {
1070 bWasHeadset = true;
1071
1072 if (GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, curRot, curCameraLoc))
1073 {
1074 curCameraRot = curRot.Rotator();
1075 }
1076 else
1077 {
1080 }
1081 }
1082 else if (VRCameraComponent)
1083 {
1084 FVector curRelLoc = VRCameraComponent->GetRelativeLocation();
1085 curCameraLoc = curRelLoc;
1086 curCameraRot = VRCameraComponent->GetRelativeRotation();
1087 VRCameraComponent->SetRelativeLocation(FVector(0, 0, curRelLoc.Z));
1088 }
1089
1090 if (!bIsFirstTick)
1091 {
1092 FVector DifferenceFromLastFrame = (curCameraLoc - lastCameraLoc);
1093
1094 // Can adjust the relative tolerances to remove jitter and some update processing
1095 if (!DifferenceFromLastFrame.IsNearlyZero(0.001f) /*|| !(curCameraRot - lastCameraRot).IsNearlyZero(0.001f)*/)
1096 {
1097 if (VRRootCapsule)
1098 {
1099 DifferenceFromLastFrame *= VRRootCapsule->GetComponentScale(); // Scale up with character
1100 AdditionalVRInputVector = VRRootCapsule->GetComponentRotation().RotateVector(DifferenceFromLastFrame); // Apply over a second
1101 AdditionalVRInputVector.Z = 0.0f; // Don't use the Z value anyway, and lets me repurpose it for the CapsuleHalfHeight
1102 }
1103 }
1104 else
1105 {
1106 AdditionalVRInputVector = FVector::ZeroVector;
1107 }
1108 }
1109 else
1110 bIsFirstTick = false;
1111
1112 if (bWasHeadset)
1113 {
1116 }
1117 else
1118 lastCameraLoc = FVector::ZeroVector; // Technically this would be incorrect for Z, but we don't use Z anyway
1119 }
1120
1121 Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
1122
1123 if (AVRSimpleCharacter * owningChar = Cast<AVRSimpleCharacter>(GetOwner()))
1124 {
1125 if (VRRootCapsule)
1126 owningChar->VRSceneComponent->SetRelativeLocation(FVector(0, 0, -VRRootCapsule->GetUnscaledCapsuleHalfHeight()));
1127
1128 owningChar->GenerateOffsetToWorld();
1129 }
1130 }
1131 else
1132 Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
1133}
1134
1136{
1137 Super::SetUpdatedComponent(NewUpdatedComponent);
1138
1139 if (UpdatedComponent)
1140 {
1141 // Fill the VRRootCapsule if we can
1142 VRRootCapsule = Cast<UCapsuleComponent>(UpdatedComponent);
1143
1144 if (AVRSimpleCharacter * simpleChar = Cast<AVRSimpleCharacter>(GetOwner()))
1145 {
1146 VRCameraComponent = Cast<UCameraComponent>(simpleChar->VRReplicatedCamera);
1147 }
1148
1149 // Stop the tick forcing
1150 //UpdatedComponent->PrimaryComponentTick.RemovePrerequisite(this, PrimaryComponentTick);
1151
1152 // Start forcing the root to tick before this, the actor tick will still tick after the movement component
1153 // We want the root component to tick first because it is setting its offset location based off of tick
1154 //this->PrimaryComponentTick.AddPrerequisite(UpdatedComponent, UpdatedComponent->PrimaryComponentTick);
1155 }
1156}
1157
1158
1161{
1162 QUICK_SCOPE_CYCLE_COUNTER(VRCharacterMovementServerMove_PerformMovement);
1163 //SCOPE_CYCLE_COUNTER(STAT_VRCharacterMovementServerMove);
1164 //CSV_SCOPED_TIMING_STAT(CharacterMovement, CharacterMovementServerMove);
1165
1166 if (!HasValidData() || !IsActive())
1167 {
1168 return;
1169 }
1170
1171 bool bAutoAcceptPacket = false;
1172 FNetworkPredictionData_Server_Character* ServerData = GetPredictionData_Server_Character();
1173 check(ServerData);
1174
1175 if (MovementMode == MOVE_Custom && CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Seated)
1176 {
1177 return;
1178 }
1179 else if (bJustUnseated)
1180 {
1181 ServerData->CurrentClientTimeStamp = MoveData.TimeStamp;
1182 bAutoAcceptPacket = true;
1183 bJustUnseated = false;
1184 }
1185
1186 const float ClientTimeStamp = MoveData.TimeStamp;
1187 FVector_NetQuantize10 ClientAccel = MoveData.Acceleration;
1188 const uint8 ClientMoveFlags = MoveData.CompressedMoveFlags;
1189 const FRotator ClientControlRotation = MoveData.ControlRotation;
1190
1191 if (!bAutoAcceptPacket && !VerifyClientTimeStamp(ClientTimeStamp, *ServerData))
1192 {
1193 const float ServerTimeStamp = ServerData->CurrentClientTimeStamp;
1194 // This is more severe if the timestamp has a large discrepancy and hasn't been recently reset.
1195 static const auto CVarNetServerMoveTimestampExpiredWarningThreshold = IConsoleManager::Get().FindConsoleVariable(TEXT("net.NetServerMoveTimestampExpiredWarningThreshold"));
1196 if (ServerTimeStamp > 1.0f && FMath::Abs(ServerTimeStamp - ClientTimeStamp) > CVarNetServerMoveTimestampExpiredWarningThreshold->GetFloat())
1197 {
1198 UE_LOG(LogNetPlayerMovement, Warning, TEXT("ServerMove: TimeStamp expired: %f, CurrentTimeStamp: %f, Character: %s"), ClientTimeStamp, ServerTimeStamp, *GetNameSafe(CharacterOwner));
1199 }
1200 else
1201 {
1202 UE_LOG(LogNetPlayerMovement, Log, TEXT("ServerMove: TimeStamp expired: %f, CurrentTimeStamp: %f, Character: %s"), ClientTimeStamp, ServerTimeStamp, *GetNameSafe(CharacterOwner));
1203 }
1204 return;
1205 }
1206
1207
1208 // Convert to our stored move data array
1209 const FVRCharacterNetworkMoveData* MoveDataVR = (const FVRCharacterNetworkMoveData*)&MoveData;
1210
1211 // Scope these, they nest with Outer references so it should work fine, this keeps the update rotation and move autonomous from double updating the char
1212 FVRCharacterScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates);
1213
1214 bool bServerReadyForClient = true;
1215 APlayerController* PC = Cast<APlayerController>(CharacterOwner->GetController());
1216 if (PC)
1217 {
1218 bServerReadyForClient = PC->NotifyServerReceivedClientData(CharacterOwner, ClientTimeStamp);
1219 if (!bServerReadyForClient)
1220 {
1221 ClientAccel = FVector::ZeroVector;
1222 }
1223 }
1224
1225 const UWorld* MyWorld = GetWorld();
1226 const float DeltaTime = ServerData->GetServerMoveDeltaTime(ClientTimeStamp, CharacterOwner->GetActorTimeDilation(*MyWorld));
1227
1228 if (DeltaTime > 0.f)
1229 {
1230 ServerData->CurrentClientTimeStamp = ClientTimeStamp;
1231 ServerData->ServerAccumulatedClientTimeStamp += DeltaTime;
1232 ServerData->ServerTimeStamp = MyWorld->GetTimeSeconds();
1233 ServerData->ServerTimeStampLastServerMove = ServerData->ServerTimeStamp;
1234
1235 if (PC)
1236 {
1237 PC->SetControlRotation(ClientControlRotation);
1238 }
1239
1240 if (!bServerReadyForClient)
1241 {
1242 return;
1243 }
1244
1245 // Perform actual movement
1246 if ((MyWorld->GetWorldSettings()->GetPauserPlayerState() == NULL))
1247 {
1248 if (PC)
1249 {
1250 PC->UpdateRotation(DeltaTime);
1251 }
1252
1253 if (!MoveDataVR->ConditionalMoveReps.RequestedVelocity.IsZero())
1254 {
1255 RequestedVelocity = MoveDataVR->ConditionalMoveReps.RequestedVelocity;
1256 bHasRequestedVelocity = true;
1257 }
1258
1261 AdditionalVRInputVector = MoveDataVR->LFDiff;
1263
1265 {
1266 if (BaseVRCharacterOwner->VRReplicateCapsuleHeight && MoveDataVR->LFDiff.Z > 0.0f && !FMath::IsNearlyEqual(MoveDataVR->LFDiff.Z, VRRootCapsule->GetUnscaledCapsuleHalfHeight()))
1267 {
1269 // BaseChar->ReplicatedCapsuleHeight.CapsuleHeight = LFDiff.Z;
1270 //VRRootCapsule->SetCapsuleHalfHeight(LFDiff.Z, false);
1271 }
1272 }
1273
1274 MoveAutonomous(ClientTimeStamp, DeltaTime, ClientMoveFlags, ClientAccel);
1275 bHasRequestedVelocity = false;
1276 }
1277
1278 UE_CLOG(CharacterOwner && UpdatedComponent, LogSimpleCharacterMovement, VeryVerbose, TEXT("ServerMove Time %f Acceleration %s Velocity %s Position %s Rotation %s DeltaTime %f Mode %s MovementBase %s.%s (Dynamic:%d)"),
1279 ClientTimeStamp, *ClientAccel.ToString(), *Velocity.ToString(), *UpdatedComponent->GetComponentLocation().ToString(), *UpdatedComponent->GetComponentRotation().ToCompactString(), DeltaTime, *GetMovementName(),
1280 *GetNameSafe(GetMovementBase()), *CharacterOwner->GetBasedMovement().BoneName.ToString(), MovementBaseUtility::IsDynamicBase(GetMovementBase()) ? 1 : 0);
1281 }
1282
1283 // #TODO: Handle this better at some point? Client also denies it later on during correction (ApplyNetworkMovementMode in base movement)
1284 // Pre handling the errors, lets avoid rolling back to/from custom movement modes, they tend to be scripted and this can screw things up
1285 const uint8 CurrentPackedMovementMode = PackNetworkMovementMode();
1286 if (CurrentPackedMovementMode != MoveData.MovementMode)
1287 {
1288 TEnumAsByte<EMovementMode> NetMovementMode(MOVE_None);
1289 TEnumAsByte<EMovementMode> NetGroundMode(MOVE_None);
1290 uint8 NetCustomMode(0);
1291 UnpackNetworkMovementMode(MoveData.MovementMode, NetMovementMode, NetCustomMode, NetGroundMode);
1292
1293 // Custom movement modes aren't going to be rolled back as they are client authed for our pawns
1294 if (NetMovementMode == EMovementMode::MOVE_Custom || MovementMode == EMovementMode::MOVE_Custom)
1295 {
1296 if (NetCustomMode == (uint8)EVRCustomMovementMode::VRMOVE_Climbing || CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Climbing)
1297 SetMovementMode(NetMovementMode, NetCustomMode);
1298 }
1299 }
1300
1301 // Validate move only after old and first dual portion, after all moves are completed.
1302 if (MoveData.NetworkMoveType == FCharacterNetworkMoveData::ENetworkMoveType::NewMove)
1303 {
1304 ServerMoveHandleClientError(ClientTimeStamp, DeltaTime, ClientAccel, MoveData.Location, MoveData.MovementBase, MoveData.MovementBaseBoneName, MoveData.MovementMode);
1305 //ServerMoveHandleClientError(ClientTimeStamp, DeltaTime, ClientAccel, MoveData.Location, MoveData.MovementBase, MoveData.MovementBaseBoneName, MoveData.MovementMode);
1306 }
1307}
1308
1309void UVRSimpleCharacterMovementComponent::ReplicateMoveToServer(float DeltaTime, const FVector& NewAcceleration)
1310{
1311 SCOPE_CYCLE_COUNTER(STAT_CharacterMovementReplicateMoveToServerVRSimple);
1312 check(CharacterOwner != NULL);
1313
1314 // Can only start sending moves if our controllers are synced up over the network, otherwise we flood the reliable buffer.
1315 APlayerController* PC = Cast<APlayerController>(CharacterOwner->GetController());
1316 if (PC && PC->AcknowledgedPawn != CharacterOwner)
1317 {
1318 return;
1319 }
1320
1321 // Bail out if our character's controller doesn't have a Player. This may be the case when the local player
1322 // has switched to another controller, such as a debug camera controller.
1323 if (PC && PC->Player == nullptr)
1324 {
1325 return;
1326 }
1327
1328 FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character();
1329 if (!ClientData)
1330 {
1331 return;
1332 }
1333
1334 // Update our delta time for physics simulation.
1335 DeltaTime = ClientData->UpdateTimeStampAndDeltaTime(DeltaTime, *CharacterOwner, *this);
1336
1337 // Find the oldest (unacknowledged) important move (OldMove).
1338 // Don't include the last move because it may be combined with the next new move.
1339 // A saved move is interesting if it differs significantly from the last acknowledged move
1340 FSavedMovePtr OldMove = NULL;
1341 if (ClientData->LastAckedMove.IsValid())
1342 {
1343 const int32 NumSavedMoves = ClientData->SavedMoves.Num();
1344 for (int32 i = 0; i < NumSavedMoves - 1; i++)
1345 {
1346 const FSavedMovePtr& CurrentMove = ClientData->SavedMoves[i];
1347 if (CurrentMove->IsImportantMove(ClientData->LastAckedMove))
1348 {
1349 OldMove = CurrentMove;
1350 break;
1351 }
1352 }
1353 }
1354
1355 // Get a SavedMove object to store the movement in.
1356 FSavedMovePtr NewMovePtr = ClientData->CreateSavedMove();
1357 FSavedMove_Character* const NewMove = NewMovePtr.Get();
1358 if (NewMove == nullptr)
1359 {
1360 return;
1361 }
1362
1363 NewMove->SetMoveFor(CharacterOwner, DeltaTime, NewAcceleration, *ClientData);
1364 const UWorld* MyWorld = GetWorld();
1365
1366 // see if the two moves could be combined
1367 // do not combine moves which have different TimeStamps (before and after reset).
1368 if (const FSavedMove_Character* PendingMove = ClientData->PendingMove.Get())
1369 {
1370 if (PendingMove->CanCombineWith(NewMovePtr, CharacterOwner, ClientData->MaxMoveDeltaTime * CharacterOwner->GetActorTimeDilation(*MyWorld)))
1371 {
1372 //SCOPE_CYCLE_COUNTER(STAT_CharacterMovementCombineNetMove);
1373
1374 // Only combine and move back to the start location if we don't move back in to a spot that would make us collide with something new.
1375 const FVector OldStartLocation = PendingMove->GetRevertedLocation();
1376 const bool bAttachedToObject = (NewMovePtr->StartAttachParent != nullptr);
1377 if (bAttachedToObject || !OverlapTest(OldStartLocation, PendingMove->StartRotation.Quaternion(), UpdatedComponent->GetCollisionObjectType(), GetPawnCapsuleCollisionShape(SHRINK_None), CharacterOwner))
1378 {
1379 // Avoid updating Mesh bones to physics during the teleport back, since PerformMovement() will update it right away anyway below.
1380 // Note: this must be before the FScopedMovementUpdate below, since that scope is what actually moves the character and mesh.
1381 AVRBaseCharacter * BaseCharacter = Cast<AVRBaseCharacter>(CharacterOwner);
1382 FScopedMeshBoneUpdateOverrideVR ScopedNoMeshBoneUpdate(CharacterOwner->GetMesh(), EKinematicBonesUpdateToPhysics::SkipAllBones);
1383
1384 // Accumulate multiple transform updates until scope ends.
1385 FScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, EScopedUpdate::DeferredUpdates);
1386 UE_LOG(LogSimpleCharacterMovement, VeryVerbose, TEXT("CombineMove: add delta %f + %f and revert from %f %f to %f %f"), DeltaTime, ClientData->PendingMove->DeltaTime, UpdatedComponent->GetComponentLocation().X, UpdatedComponent->GetComponentLocation().Y, OldStartLocation.X, OldStartLocation.Y);
1387
1388 NewMove->CombineWith(PendingMove, CharacterOwner, PC, OldStartLocation);
1389
1390 if (PC)
1391 {
1392 // We reverted position to that at the start of the pending move (above), however some code paths expect rotation to be set correctly
1393 // before character movement occurs (via FaceRotation), so try that now. The bOrientRotationToMovement path happens later as part of PerformMovement() and PhysicsRotation().
1394 CharacterOwner->FaceRotation(PC->GetControlRotation(), NewMove->DeltaTime);
1395 }
1396
1397 SaveBaseLocation();
1398 NewMove->SetInitialPosition(CharacterOwner);
1399
1400 // Remove pending move from move list. It would have to be the last move on the list.
1401 if (ClientData->SavedMoves.Num() > 0 && ClientData->SavedMoves.Last() == ClientData->PendingMove)
1402 {
1403 const bool bAllowShrinking = false;
1404 ClientData->SavedMoves.Pop(bAllowShrinking);
1405 }
1406 ClientData->FreeMove(ClientData->PendingMove);
1407 ClientData->PendingMove = nullptr;
1408 PendingMove = nullptr; // Avoid dangling reference, it's deleted above.
1409 }
1410 else
1411 {
1412 UE_LOG(LogSimpleCharacterMovement, Verbose, TEXT("Not combining move [would collide at start location]"));
1413 }
1414 }
1415 /*else
1416 {
1417 UE_LOG(LogSimpleCharacterMovement, Verbose, TEXT("Not combining move [not allowed by CanCombineWith()]"));
1418 }*/
1419 }
1420
1421 // Acceleration should match what we send to the server, plus any other restrictions the server also enforces (see MoveAutonomous).
1422 Acceleration = NewMove->Acceleration.GetClampedToMaxSize(GetMaxAcceleration());
1423 AnalogInputModifier = ComputeAnalogInputModifier(); // recompute since acceleration may have changed.
1424
1425 // Perform the move locally
1426 CharacterOwner->ClientRootMotionParams.Clear();
1427 CharacterOwner->SavedRootMotion.Clear();
1428 PerformMovement(NewMove->DeltaTime);
1429
1430 NewMove->PostUpdate(CharacterOwner, FSavedMove_Character::PostUpdate_Record);
1431
1432 // Add NewMove to the list
1433 if (CharacterOwner->IsReplicatingMovement())
1434 {
1435 check(NewMove == NewMovePtr.Get());
1436 ClientData->SavedMoves.Push(NewMovePtr);
1437
1438 //const bool bCanDelayMove = (CharacterMovementCVars::NetEnableMoveCombining != 0) && CanDelaySendingMove(NewMove);
1439 static const auto CVarNetEnableMoveCombiningVRSimple = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetEnableMoveCombining"));
1440 const bool bCanDelayMove = (CVarNetEnableMoveCombiningVRSimple->GetInt() != 0) && CanDelaySendingMove(NewMovePtr);
1441
1442 if (bCanDelayMove && ClientData->PendingMove.IsValid() == false)
1443 {
1444 // Decide whether to hold off on move
1445 const float NetMoveDelta = FMath::Clamp(GetClientNetSendDeltaTime(PC, ClientData, NewMovePtr), 1.f / 120.f, 1.f / 5.f);
1446
1447 if ((MyWorld->TimeSeconds - ClientData->ClientUpdateTime) * MyWorld->GetWorldSettings()->GetEffectiveTimeDilation() < NetMoveDelta)
1448 {
1449 // Delay sending this move.
1450 ClientData->PendingMove = NewMovePtr;
1451 return;
1452 }
1453 }
1454
1455 // Uncomment 4.16
1456 ClientData->ClientUpdateTime = MyWorld->TimeSeconds;
1457
1458 UE_CLOG(CharacterOwner&& UpdatedComponent, LogSimpleCharacterMovement, VeryVerbose, TEXT("ClientMove Time %f Acceleration %s Velocity %s Position %s Rotation %s DeltaTime %f Mode %s MovementBase %s.%s (Dynamic:%d) DualMove? %d"),
1459 NewMove->TimeStamp, *NewMove->Acceleration.ToString(), *Velocity.ToString(), *UpdatedComponent->GetComponentLocation().ToString(), *UpdatedComponent->GetComponentRotation().ToCompactString(), NewMove->DeltaTime, *GetMovementName(),
1460 *GetNameSafe(NewMove->EndBase.Get()), *NewMove->EndBoneName.ToString(), MovementBaseUtility::IsDynamicBase(NewMove->EndBase.Get()) ? 1 : 0, ClientData->PendingMove.IsValid() ? 1 : 0);
1461
1462 bool bSendServerMove = true;
1463
1464#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
1465
1466 // Testing options: Simulated packet loss to server
1467 const float TimeSinceLossStart = (MyWorld->RealTimeSeconds - ClientData->DebugForcedPacketLossTimerStart);
1468 static const auto CVarNetForceClientServerMoveLossDuration = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetForceClientServerMoveLossDuration"));
1469 static const auto CVarNetForceClientServerMoveLossPercent = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetForceClientServerMoveLossPercent"));
1470 if (ClientData->DebugForcedPacketLossTimerStart > 0.f && (TimeSinceLossStart < CVarNetForceClientServerMoveLossDuration->GetFloat()))
1471 {
1472 bSendServerMove = false;
1473 UE_LOG(LogSimpleCharacterMovement, Log, TEXT("Drop ServerMove, %.2f time remains"), CVarNetForceClientServerMoveLossDuration->GetFloat() - TimeSinceLossStart);
1474 }
1475 else if (CVarNetForceClientServerMoveLossPercent->GetFloat() != 0.f && (RandomStream.FRand() < CVarNetForceClientServerMoveLossPercent->GetFloat()))
1476 {
1477 bSendServerMove = false;
1478 ClientData->DebugForcedPacketLossTimerStart = (CVarNetForceClientServerMoveLossDuration->GetFloat() > 0) ? MyWorld->RealTimeSeconds : 0.0f;
1479 UE_LOG(LogSimpleCharacterMovement, Log, TEXT("Drop ServerMove, %.2f time remains"), CVarNetForceClientServerMoveLossDuration->GetFloat());
1480 }
1481 else
1482 {
1483 ClientData->DebugForcedPacketLossTimerStart = 0.f;
1484 }
1485#endif
1486
1487 // Send move to server if this character is replicating movement
1488 if (bSendServerMove)
1489 {
1490 SCOPE_CYCLE_COUNTER(STAT_CharacterMovementCallServerMoveVRSimple);
1491 if (ShouldUsePackedMovementRPCs())
1492 {
1493 CallServerMovePacked(NewMove, ClientData->PendingMove.Get(), OldMove.Get());
1494 }
1495 /*else
1496 {
1497 CallServerMove(NewMove, OldMove.Get());
1498 }*/
1499 }
1500 }
1501
1502 ClientData->PendingMove = NULL;
1503}
1504
1506{
1507 // Should only be called on client or listen server (for remote clients) in network games
1508 check(CharacterOwner != NULL);
1509 checkSlow(CharacterOwner->GetLocalRole() < ROLE_Authority || (CharacterOwner->GetRemoteRole() == ROLE_AutonomousProxy && GetNetMode() == NM_ListenServer));
1510 checkSlow(GetNetMode() == NM_Client || GetNetMode() == NM_ListenServer);
1511
1512 if (!ClientPredictionData)
1513 {
1515 MutableThis->ClientPredictionData = new FNetworkPredictionData_Client_VRSimpleCharacter(*this);
1516 }
1517
1518 return ClientPredictionData;
1519}
1520
1522{
1523 // Should only be called on server in network games
1524 check(CharacterOwner != NULL);
1525 check(CharacterOwner->GetLocalRole() == ROLE_Authority);
1526 checkSlow(GetNetMode() < NM_Client);
1527
1528 if (!ServerPredictionData)
1529 {
1531 MutableThis->ServerPredictionData = new FNetworkPredictionData_Server_VRSimpleCharacter(*this);
1532 }
1533
1534 return ServerPredictionData;
1535}
1536
1537
1539{
1540 //VRCapsuleLocation = FVector::ZeroVector;
1541 //VRCapsuleRotation = FRotator::ZeroRotator;
1542 LFDiff = FVector::ZeroVector;
1543 //CustomVRInputVector = FVector::ZeroVector;
1544 //RequestedVelocity = FVector::ZeroVector;
1545
1547}
1548
1550{
1551 // See if we can get the VR capsule location
1552 if (AVRSimpleCharacter * VRC = Cast<AVRSimpleCharacter>(C))
1553 {
1554 if (VRC->VRMovementReference)
1555 {
1556 LFDiff = VRC->VRMovementReference->AdditionalVRInputVector;
1557
1558 //CustomVRInputVector = VRC->VRMovementReference->CustomVRInputVector;
1559
1560 /* if (VRC->VRMovementReference->HasRequestedVelocity())
1561 RequestedVelocity = VRC->VRMovementReference->RequestedVelocity;
1562 else
1563 RequestedVelocity = FVector::ZeroVector;*/
1564 }
1565 else
1566 {
1567 LFDiff = FVector::ZeroVector;
1568 //CustomVRInputVector = FVector::ZeroVector;
1569 // RequestedVelocity = FVector::ZeroVector;
1570 }
1571
1572 }
1574}
1575
1577{
1578 UVRSimpleCharacterMovementComponent * CharMove = Cast<UVRSimpleCharacterMovementComponent>(Character->GetCharacterMovement());
1579
1580 // Set capsule location prior to testing movement
1581 // I am overriding the replicated value here when movement is made on purpose
1582 if (CharMove)
1583 {
1584 CharMove->AdditionalVRInputVector = FVector(LFDiff.X, LFDiff.Y, 0.0f);
1585 }
1586
1587 if (AVRBaseCharacter * BaseChar = Cast<AVRBaseCharacter>(CharMove->GetCharacterOwner()))
1588 {
1589 if (BaseChar->VRReplicateCapsuleHeight && LFDiff.Z != CharMove->VRRootCapsule->GetUnscaledCapsuleHalfHeight())
1590 {
1591 BaseChar->SetCharacterHalfHeightVR(LFDiff.Z, false);
1592 //CharMove->VRRootCapsule->SetCapsuleHalfHeightVR(LFDiff.Z, false);
1593 }
1594 }
1595
1597}
DECLARE_CYCLE_STAT(TEXT("Char ReplicateMoveToServerVRSimple"), STAT_CharacterMovementReplicateMoveToServerVRSimple, STATGROUP_Character)
#define devCodeSimple(...)
const float MAX_STEP_SIDE_ZZ
DEFINE_LOG_CATEGORY(LogSimpleCharacterMovement)
const float VERTICAL_SLOPE_NORMAL_ZZ
bool VRReplicateCapsuleHeight
UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRBaseCharacter")
virtual void SetCharacterHalfHeightVR(float HalfHeight, bool bUpdateOverlaps=true)
UFUNCTION(BlueprintCallable, Category = "BaseVRCharacter")
virtual void PrepMoveFor(ACharacter *Character) override
virtual void SetInitialPosition(ACharacter *C)
virtual void PrepMoveFor(ACharacter *Character) override
virtual bool VerifyClientTimeStamp(float TimeStamp, FNetworkPredictionData_Server_Character &ServerData) override
virtual void PerformMovement(float DeltaSeconds) override
virtual float SlideAlongSurface(const FVector &Delta, float Time, const FVector &Normal, FHitResult &Hit, bool bHandleImpact) override
virtual void MoveAutonomous(float ClientTimeStamp, float DeltaTime, uint8 CompressedFlags, const FVector &NewAccel) override
FVRCharacterNetworkMoveDataContainer VRNetworkMoveDataContainer
FVRCharacterMoveResponseDataContainer VRMoveResponseDataContainer
AVRBaseCharacter * BaseVRCharacterOwner
UPROPERTY(Transient, DuplicateTransient)
FNetworkPredictionData_Server * GetPredictionData_Server() const override
bool bSkipHMDChecks
UPROPERTY(BlueprintReadWrite, Category = VRMovement)
UCapsuleComponent * VRRootCapsule
UPROPERTY(BlueprintReadOnly, Transient, Category = VRMovement)
virtual bool VRClimbStepUp(const FVector &GravDir, const FVector &Delta, const FHitResult &InHit, FStepDownResult *OutStepDownResult=nullptr) override
void SetUpdatedComponent(USceneComponent *NewUpdatedComponent) override
virtual void ReplicateMoveToServer(float DeltaTime, const FVector &NewAcceleration) override
void PhysFalling(float deltaTime, int32 Iterations) override
UVRSimpleCharacterMovementComponent(const FObjectInitializer &ObjectInitializer=FObjectInitializer::Get())
FNetworkPredictionData_Client * GetPredictionData_Client() const override
void PhysWalking(float deltaTime, int32 Iterations) override
void PhysNavWalking(float deltaTime, int32 Iterations) override
void PhysFlying(float deltaTime, int32 Iterations) override
UCameraComponent * VRCameraComponent
UPROPERTY(BlueprintReadOnly, Transient, Category = VRMovement)
void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override
virtual void ServerMove_PerformMovement(const FCharacterNetworkMoveData &MoveData) override
EVRConjoinedMovementModes ReplicatedMovementMode
FVector RequestedVelocity
UPROPERTY(Transient)
FVector CustomVRInputVector
UPROPERTY(Transient)
FVRMoveActionArray MoveActionArray
UPROPERTY(Transient)