void URotatingMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) { // skip if we don't want component updated when not rendered or if updated component can't move if (ShouldSkipUpdate(DeltaTime)) { return; } Super::TickComponent(DeltaTime, TickType, ThisTickFunction); if (!IsValid(UpdatedComponent)) { return; } // Compute new rotation const FQuat OldRotation = UpdatedComponent->GetComponentQuat(); const FQuat DeltaRotation = (RotationRate * DeltaTime).Quaternion(); const FQuat NewRotation = bRotationInLocalSpace ? (OldRotation * DeltaRotation) : (DeltaRotation * OldRotation); // Compute new location FVector DeltaLocation = FVector::ZeroVector; if (!PivotTranslation.IsZero()) { const FVector OldPivot = OldRotation.RotateVector(PivotTranslation); const FVector NewPivot = NewRotation.RotateVector(PivotTranslation); DeltaLocation = (OldPivot - NewPivot); // ConstrainDirectionToPlane() not necessary because it's done by MoveUpdatedComponent() below. } const bool bEnableCollision = false; MoveUpdatedComponent(DeltaLocation, NewRotation, bEnableCollision); }
bool UMovementComponent::SafeMoveUpdatedComponent(const FVector& Delta, const FQuat& NewRotation, bool bSweep, FHitResult& OutHit, ETeleportType Teleport) { if (UpdatedComponent == NULL) { OutHit.Reset(1.f); return false; } bool bMoveResult = MoveUpdatedComponent(Delta, NewRotation, bSweep, &OutHit, Teleport); // Handle initial penetrations if (OutHit.bStartPenetrating && UpdatedComponent) { const FVector RequestedAdjustment = GetPenetrationAdjustment(OutHit); if (ResolvePenetration(RequestedAdjustment, OutHit, NewRotation)) { // Retry original move bMoveResult = MoveUpdatedComponent(Delta, NewRotation, bSweep, &OutHit, Teleport); } } return bMoveResult; }
void UObstacleMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); if (!UpdatedComponent) { return; } AActor* ActorOwner = UpdatedComponent->GetOwner(); if (!ActorOwner || ActorOwner->IsPendingKill()) { return; } FVector CurrentLocation = UpdatedComponent->GetComponentLocation(); FRotator CurrentRotation = UpdatedComponent->GetComponentRotation(); if (MovingRight) { CurrentLocation.Y += MovementSpeed * DeltaTime; if (CurrentLocation.Y >= RightMovementTarget) { MovingRight = !MovingRight; CurrentLocation.Y = RightMovementTarget; } } else { CurrentLocation.Y -= MovementSpeed * DeltaTime; if (CurrentLocation.Y <= LeftMovementTarget) { MovingRight = !MovingRight; CurrentLocation.Y = LeftMovementTarget; } } FHitResult Hit(1.0f); MoveUpdatedComponent(CurrentLocation - UpdatedComponent->GetComponentLocation(), CurrentRotation, true, &Hit); // If we hit a trigger that destroyed us, abort. if (ActorOwner->IsPendingKill() || !UpdatedComponent) { return; } UpdateComponentVelocity(); }
void UProjectileMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) { QUICK_SCOPE_CYCLE_COUNTER( STAT_ProjectileMovementComponent_TickComponent ); // skip if don't want component updated when not rendered or updated component can't move if (HasStoppedSimulation() || ShouldSkipUpdate(DeltaTime)) { return; } Super::TickComponent(DeltaTime, TickType, ThisTickFunction); AActor* ActorOwner = UpdatedComponent->GetOwner(); if ( !ActorOwner || !CheckStillInWorld() ) { return; } if (UpdatedComponent->IsSimulatingPhysics()) { return; } float RemainingTime = DeltaTime; uint32 NumBounces = 0; int32 Iterations = 0; FHitResult Hit(1.f); while( RemainingTime >= MIN_TICK_TIME && (Iterations < MaxSimulationIterations) && !ActorOwner->IsPendingKill() && !HasStoppedSimulation() ) { Iterations++; // subdivide long ticks to more closely follow parabolic trajectory const float TimeTick = ShouldUseSubStepping() ? GetSimulationTimeStep(RemainingTime, Iterations) : RemainingTime; RemainingTime -= TimeTick; Hit.Time = 1.f; const FVector OldVelocity = Velocity; const FVector MoveDelta = ComputeMoveDelta(OldVelocity, TimeTick); const FRotator NewRotation = (bRotationFollowsVelocity && !OldVelocity.IsNearlyZero(0.01f)) ? OldVelocity.Rotation() : ActorOwner->GetActorRotation(); // Move the component if (bShouldBounce) { // If we can bounce, we are allowed to move out of penetrations, so use SafeMoveUpdatedComponent which does that automatically. SafeMoveUpdatedComponent( MoveDelta, NewRotation, true, Hit ); } else { // If we can't bounce, then we shouldn't adjust if initially penetrating, because that should be a blocking hit that causes a hit event and stop simulation. TGuardValue<EMoveComponentFlags> ScopedFlagRestore(MoveComponentFlags, MoveComponentFlags | MOVECOMP_NeverIgnoreBlockingOverlaps); MoveUpdatedComponent(MoveDelta, NewRotation, true, &Hit ); } // If we hit a trigger that destroyed us, abort. if( ActorOwner->IsPendingKill() || HasStoppedSimulation() ) { return; } // Handle hit result after movement if( !Hit.bBlockingHit ) { PreviousHitTime = 1.f; bIsSliding = false; // Only calculate new velocity if events didn't change it during the movement update. if (Velocity == OldVelocity) { Velocity = ComputeVelocity(Velocity, TimeTick); } } else { // Only calculate new velocity if events didn't change it during the movement update. if (Velocity == OldVelocity) { // re-calculate end velocity for partial time Velocity = (Hit.Time > KINDA_SMALL_NUMBER) ? ComputeVelocity(OldVelocity, TimeTick * Hit.Time) : OldVelocity; } // Handle blocking hit float SubTickTimeRemaining = TimeTick * (1.f - Hit.Time); const EHandleBlockingHitResult HandleBlockingResult = HandleBlockingHit(Hit, TimeTick, MoveDelta, SubTickTimeRemaining); if (HandleBlockingResult == EHandleBlockingHitResult::Abort || HasStoppedSimulation()) { break; } else if (HandleBlockingResult == EHandleBlockingHitResult::Deflect) { NumBounces++; HandleDeflection(Hit, OldVelocity, NumBounces, SubTickTimeRemaining); PreviousHitTime = Hit.Time; PreviousHitNormal = ConstrainNormalToPlane(Hit.Normal); } else if (HandleBlockingResult == EHandleBlockingHitResult::AdvanceNextSubstep) { // Reset deflection logic to ignore this hit PreviousHitTime = 1.f; } else { // Unhandled EHandleBlockingHitResult checkNoEntry(); } // A few initial bounces should add more time and iterations to complete most of the simulation. if (NumBounces <= 2 && SubTickTimeRemaining >= MIN_TICK_TIME) { RemainingTime += SubTickTimeRemaining; Iterations--; } } } UpdateComponentVelocity(); }
bool UMovementComponent::ResolvePenetrationImpl(const FVector& ProposedAdjustment, const FHitResult& Hit, const FQuat& NewRotationQuat) { // SceneComponent can't be in penetration, so this function really only applies to PrimitiveComponent. const FVector Adjustment = ConstrainDirectionToPlane(ProposedAdjustment); if (!Adjustment.IsZero() && UpdatedPrimitive) { // See if we can fit at the adjusted location without overlapping anything. AActor* ActorOwner = UpdatedComponent->GetOwner(); if (!ActorOwner) { return false; } UE_LOG(LogMovement, Verbose, TEXT("ResolvePenetration: %s.%s at location %s inside %s.%s at location %s by %.3f (netmode: %d)"), *ActorOwner->GetName(), *UpdatedComponent->GetName(), *UpdatedComponent->GetComponentLocation().ToString(), *GetNameSafe(Hit.GetActor()), *GetNameSafe(Hit.GetComponent()), Hit.Component.IsValid() ? *Hit.GetComponent()->GetComponentLocation().ToString() : TEXT("<unknown>"), Hit.PenetrationDepth, (uint32)GetNetMode()); // We really want to make sure that precision differences or differences between the overlap test and sweep tests don't put us into another overlap, // so make the overlap test a bit more restrictive. const float OverlapInflation = CVarPenetrationOverlapCheckInflation.GetValueOnGameThread(); bool bEncroached = OverlapTest(Hit.TraceStart + Adjustment, NewRotationQuat, UpdatedPrimitive->GetCollisionObjectType(), UpdatedPrimitive->GetCollisionShape(OverlapInflation), ActorOwner); if (!bEncroached) { // Move without sweeping. MoveUpdatedComponent(Adjustment, NewRotationQuat, false, nullptr, ETeleportType::TeleportPhysics); UE_LOG(LogMovement, Verbose, TEXT("ResolvePenetration: teleport by %s"), *Adjustment.ToString()); return true; } else { // Disable MOVECOMP_NeverIgnoreBlockingOverlaps if it is enabled, otherwise we wouldn't be able to sweep out of the object to fix the penetration. TGuardValue<EMoveComponentFlags> ScopedFlagRestore(MoveComponentFlags, EMoveComponentFlags(MoveComponentFlags & (~MOVECOMP_NeverIgnoreBlockingOverlaps))); // Try sweeping as far as possible... FHitResult SweepOutHit(1.f); bool bMoved = MoveUpdatedComponent(Adjustment, NewRotationQuat, true, &SweepOutHit, ETeleportType::TeleportPhysics); UE_LOG(LogMovement, Verbose, TEXT("ResolvePenetration: sweep by %s (success = %d)"), *Adjustment.ToString(), bMoved); // Still stuck? if (!bMoved && SweepOutHit.bStartPenetrating) { // Combine two MTD results to get a new direction that gets out of multiple surfaces. const FVector SecondMTD = GetPenetrationAdjustment(SweepOutHit); const FVector CombinedMTD = Adjustment + SecondMTD; if (SecondMTD != Adjustment && !CombinedMTD.IsZero()) { bMoved = MoveUpdatedComponent(CombinedMTD, NewRotationQuat, true, nullptr, ETeleportType::TeleportPhysics); UE_LOG(LogMovement, Verbose, TEXT("ResolvePenetration: sweep by %s (MTD combo success = %d)"), *CombinedMTD.ToString(), bMoved); } } // Still stuck? if (!bMoved) { // Try moving the proposed adjustment plus the attempted move direction. This can sometimes get out of penetrations with multiple objects const FVector MoveDelta = ConstrainDirectionToPlane(Hit.TraceEnd - Hit.TraceStart); if (!MoveDelta.IsZero()) { bMoved = MoveUpdatedComponent(Adjustment + MoveDelta, NewRotationQuat, true, nullptr, ETeleportType::TeleportPhysics); UE_LOG(LogMovement, Verbose, TEXT("ResolvePenetration: sweep by %s (adjusted attempt success = %d)"), *(Adjustment + MoveDelta).ToString(), bMoved); } } return bMoved; } } return false; }
void UInterpToMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) { QUICK_SCOPE_CYCLE_COUNTER(STAT_InterpToMovementComponent_TickComponent); Super::TickComponent(DeltaTime, TickType, ThisTickFunction); // skip if don't want component updated when not rendered or updated component can't move if (!UpdatedComponent || ShouldSkipUpdate(DeltaTime)) { return; } AActor* ActorOwner = UpdatedComponent->GetOwner(); if (!ActorOwner || !CheckStillInWorld()) { return; } if (UpdatedComponent->IsSimulatingPhysics()) { return; } if((bStopped == true ) || ( ActorOwner->IsPendingKill() ) ) { return; } if( ControlPoints.Num()== 0 ) { return; } // This will update any control points coordinates that are linked to actors. UpdateControlPoints(false); float RemainingTime = DeltaTime; int32 NumBounces = 0; int32 Iterations = 0; FHitResult Hit(1.f); FVector WaitPos = FVector::ZeroVector; if (bIsWaiting == true) { WaitPos = UpdatedComponent->GetComponentLocation(); } while (RemainingTime >= MIN_TICK_TIME && (Iterations < MaxSimulationIterations) && !ActorOwner->IsPendingKill() && UpdatedComponent) { Iterations++; const float TimeTick = ShouldUseSubStepping() ? GetSimulationTimeStep(RemainingTime, Iterations) : RemainingTime; RemainingTime -= TimeTick; // Calculate the current time with this tick iteration float Time = FMath::Clamp(CurrentTime + ((DeltaTime*TimeMultiplier)*CurrentDirection),0.0f,1.0f); FVector MoveDelta = ComputeMoveDelta(Time); // Update the rotation on the spline if required FRotator CurrentRotation = UpdatedComponent->GetComponentRotation(); // Move the component if ((bPauseOnImpact == false ) && (BehaviourType != EInterpToBehaviourType::OneShot)) { // If we can bounce, we are allowed to move out of penetrations, so use SafeMoveUpdatedComponent which does that automatically. SafeMoveUpdatedComponent(MoveDelta, CurrentRotation, true, Hit); } else { // If we can't bounce, then we shouldn't adjust if initially penetrating, because that should be a blocking hit that causes a hit event and stop simulation. TGuardValue<EMoveComponentFlags> ScopedFlagRestore(MoveComponentFlags, MoveComponentFlags | MOVECOMP_NeverIgnoreBlockingOverlaps); MoveUpdatedComponent(MoveDelta, CurrentRotation, true, &Hit); } //DrawDebugPoint(GetWorld(), UpdatedComponent->GetComponentLocation(), 16, FColor::White,true,5.0f); // If we hit a trigger that destroyed us, abort. if (ActorOwner->IsPendingKill() || !UpdatedComponent) { return; } // Handle hit result after movement if (!Hit.bBlockingHit) { // If we were 'waiting' were not any more - broadcast we are off again if( bIsWaiting == true ) { OnWaitEndDelegate.Broadcast(Hit, Time); bIsWaiting = false; } else { CalculateNewTime(CurrentTime, TimeTick, Hit, true, bStopped); if (bStopped == true) { return; } } } else { if (HandleHitWall(Hit, TimeTick, MoveDelta)) { break; } NumBounces++; float SubTickTimeRemaining = TimeTick * (1.f - Hit.Time); // A few initial bounces should add more time and iterations to complete most of the simulation. if (NumBounces <= 2 && SubTickTimeRemaining >= MIN_TICK_TIME) { RemainingTime += SubTickTimeRemaining; Iterations--; } } } if( bIsWaiting == false ) { FHitResult DummyHit; CurrentTime = CalculateNewTime(CurrentTime, DeltaTime, DummyHit, false, bStopped); } UpdateComponentVelocity(); }
void UMainCharacterMovementComponent::TickComponent(float deltaTime, enum ELevelTick tickType, FActorComponentTickFunction *thisTickFunction) { UPawnMovementComponent::TickComponent(deltaTime, tickType, thisTickFunction); if (!PawnOwner || !UpdatedComponent || ShouldSkipUpdate(deltaTime)) { return; } if (m_character) { const FVector posInRoom = m_character->GetRelativePositionInRoom(); // pos should be center of room if (auto* room = m_character->GetCurrentRoom()) { int ladderPosInRoom = room->GetLadderPos(); bool isInsideLadderRegion = abs(posInRoom.X - ladderPosInRoom) <= (ARoom::c_ladderWidth * 0.5); // handle climbing up if (isInsideLadderRegion) { if (m_wantsToClimbUp) { if (room->HasLadderUp()) { if (auto* neighbour = room->GetNeighbour(RoomNeighbour::Up)) { m_isMoving = false; m_character->TeleportToRoom(neighbour); } } } // handle climbing down if (m_wantsToClimbDown) { if (room->HasLadderDown()) { if (auto* neighbour = room->GetNeighbour(RoomNeighbour::Down)) { m_isMoving = false; m_character->TeleportToRoom(neighbour); } } } } // handle left and right FVector desiredMovementThisFrame = ConsumeInputVector().GetClampedToMaxSize(1.0f) * deltaTime * m_walkSpeed; if (!desiredMovementThisFrame.IsNearlyZero()) { m_isMoving = true; if (desiredMovementThisFrame.X < 0.0f) { m_direction = FacingDirection::Left; } else if (desiredMovementThisFrame.X > 0.0f) { m_direction = FacingDirection::Right; } const float leftWallPos = room->GetLeftWallXPos(); const float rightWallPos = room->GetRightWallXPos(); FVector desiredPosInRoom = posInRoom - desiredMovementThisFrame; // no neighbour == wall // poor collision detection that won't work in low FPS (but hey, who gives a f***s?) // hint: not me if (!room->GetNeighbour(RoomNeighbour::Left) && desiredMovementThisFrame.X < 0 && desiredPosInRoom.X < leftWallPos) { desiredMovementThisFrame.X = 0; m_isMoving = false; } if (!room->GetNeighbour(RoomNeighbour::Right) && desiredMovementThisFrame.X > 0 && desiredPosInRoom.X > rightWallPos) { desiredMovementThisFrame.X = 0; m_isMoving = false; } FHitResult hit; MoveUpdatedComponent(desiredMovementThisFrame, UpdatedComponent->GetComponentRotation().Quaternion(), true, &hit); } else { m_isMoving = false; } } } m_wantsToClimbUp = false; m_wantsToClimbDown = false; }