void UProjectileMovementComponent::HandleImpact(const FHitResult& Hit, float TimeSlice, const FVector& MoveDelta) { bool bStopSimulating = false; if (bShouldBounce) { const FVector OldVelocity = Velocity; Velocity = ComputeBounceResult(Hit, TimeSlice, MoveDelta); // Trigger bounce events OnProjectileBounce.Broadcast(Hit, OldVelocity); // Event may modify velocity or threshold, so check velocity threshold now. Velocity = LimitVelocity(Velocity); if (Velocity.SizeSquared() < FMath::Square(BounceVelocityStopSimulatingThreshold)) { bStopSimulating = true; } } else { bStopSimulating = true; } if (bStopSimulating) { StopSimulating(Hit); } }
bool UProjectileMovementComponent::HandleSliding(FHitResult& Hit, float& SubTickTimeRemaining) { FHitResult InitialHit(Hit); const FVector OldHitNormal = ConstrainDirectionToPlane(Hit.Normal); // Velocity is now parallel to the impact surface. // Perform the move now, before adding gravity/accel again, so we don't just keep hitting the surface. SafeMoveUpdatedComponent(Velocity * SubTickTimeRemaining, UpdatedComponent->GetComponentQuat(), true, Hit); if (HasStoppedSimulation()) { return false; } // A second hit can deflect the velocity (through the normal bounce code), for the next iteration. if (Hit.bBlockingHit) { const float TimeTick = SubTickTimeRemaining; SubTickTimeRemaining = TimeTick * (1.f - Hit.Time); if (HandleBlockingHit(Hit, TimeTick, Velocity * TimeTick, SubTickTimeRemaining) == EHandleBlockingHitResult::Abort || HasStoppedSimulation()) { return false; } } else { // Find velocity after elapsed time const FVector PostTickVelocity = ComputeVelocity(Velocity, SubTickTimeRemaining); // If pointing back into surface, apply friction and acceleration. const FVector Force = (PostTickVelocity - Velocity); const float ForceDotN = (Force | OldHitNormal); if (ForceDotN < 0.f) { const FVector ProjectedForce = FVector::VectorPlaneProject(Force, OldHitNormal); const FVector NewVelocity = Velocity + ProjectedForce; const FVector FrictionForce = -NewVelocity.GetSafeNormal() * FMath::Min(-ForceDotN * Friction, NewVelocity.Size()); Velocity = ConstrainDirectionToPlane(NewVelocity + FrictionForce); } else { Velocity = PostTickVelocity; } // Check min velocity if (Velocity.SizeSquared() < FMath::Square(BounceVelocityStopSimulatingThreshold)) { StopSimulating(InitialHit); return false; } SubTickTimeRemaining = 0.f; } return true; }
void UInterpToMovementComponent::HandleImpact(const FHitResult& Hit, float Time, const FVector& MoveDelta) { if( bPauseOnImpact == false ) { switch(BehaviourType ) { case EInterpToBehaviourType::OneShot: OnInterpToStop.Broadcast(Hit, Time); bStopped = true; StopSimulating(Hit); return; case EInterpToBehaviourType::OneShot_Reverse: if( CurrentDirection == -1.0f) { OnInterpToStop.Broadcast(Hit, Time); bStopped = true; StopSimulating(Hit); return; } else { ReverseDirection(Hit, Time, true); } break; case EInterpToBehaviourType::Loop_Reset: { CurrentTime = 0.0f; OnResetDelegate.Broadcast(Hit, CurrentTime); } break; default: ReverseDirection(Hit, Time, true); break; } } else { if( bIsWaiting == false ) { OnWaitBeginDelegate.Broadcast(Hit, Time); bIsWaiting = true; } } }
bool UProjectileMovementComponent::HandleDeflection(FHitResult& Hit, const FVector& OldVelocity, const uint32 NumBounces, float& SubTickTimeRemaining) { const FVector Normal = ConstrainNormalToPlane(Hit.Normal); // Multiple hits within very short time period? const bool bMultiHit = (PreviousHitTime < 1.f && Hit.Time <= KINDA_SMALL_NUMBER); // if velocity still into wall (after HandleBlockingHit() had a chance to adjust), slide along wall const float DotTolerance = 0.01f; bIsSliding = (bMultiHit && FVector::Coincident(PreviousHitNormal, Normal)) || ((Velocity.GetSafeNormal() | Normal) <= DotTolerance); if (bIsSliding) { if (bMultiHit && (PreviousHitNormal | Normal) <= 0.f) { //90 degree or less corner, so use cross product for direction FVector NewDir = (Normal ^ PreviousHitNormal); NewDir = NewDir.GetSafeNormal(); Velocity = Velocity.ProjectOnToNormal(NewDir); if ((OldVelocity | Velocity) < 0.f) { Velocity *= -1.f; } Velocity = ConstrainDirectionToPlane(Velocity); } else { //adjust to move along new wall Velocity = ComputeSlideVector(Velocity, 1.f, Normal, Hit); } // Check min velocity. if (Velocity.SizeSquared() < FMath::Square(BounceVelocityStopSimulatingThreshold)) { StopSimulating(Hit); return false; } // Velocity is now parallel to the impact surface. if (SubTickTimeRemaining > KINDA_SMALL_NUMBER) { if (!HandleSliding(Hit, SubTickTimeRemaining)) { return false; } } } return true; }
bool UInterpToMovementComponent::CheckStillInWorld() { if (!UpdatedComponent) { return false; } const UWorld* MyWorld = GetWorld(); if (!MyWorld) { return false; } // check the variations of KillZ AWorldSettings* WorldSettings = MyWorld->GetWorldSettings(true); if (!WorldSettings->bEnableWorldBoundsChecks) { return true; } AActor* ActorOwner = UpdatedComponent->GetOwner(); if (!IsValid(ActorOwner)) { return false; } if (ActorOwner->GetActorLocation().Z < WorldSettings->KillZ) { UDamageType const* DmgType = WorldSettings->KillZDamageType ? WorldSettings->KillZDamageType->GetDefaultObject<UDamageType>() : GetDefault<UDamageType>(); ActorOwner->FellOutOfWorld(*DmgType); return false; } // Check if box has poked outside the world else if (UpdatedComponent && UpdatedComponent->IsRegistered()) { const FBox& Box = UpdatedComponent->Bounds.GetBox(); if (Box.Min.X < -HALF_WORLD_MAX || Box.Max.X > HALF_WORLD_MAX || Box.Min.Y < -HALF_WORLD_MAX || Box.Max.Y > HALF_WORLD_MAX || Box.Min.Z < -HALF_WORLD_MAX || Box.Max.Z > HALF_WORLD_MAX) { UE_LOG(LogInterpToMovementComponent, Warning, TEXT("%s is outside the world bounds!"), *ActorOwner->GetName()); ActorOwner->OutsideWorldBounds(); // not safe to use physics or collision at this point ActorOwner->SetActorEnableCollision(false); FHitResult Hit(1.f); StopSimulating(Hit); return false; } } return true; }