// Fill out your copyright notice in the Description page of Project Settings. #include "FireworksPawn.h" // Sets default values AFireworksPawn::AFireworksPawn() { // Set this pawn to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; PawnRoot = CreateDefaultSubobject(TEXT("Root")); RootComponent = PawnRoot; CameraRoot = CreateDefaultSubobject(TEXT("Camera Root")); CameraRoot->SetupAttachment(PawnRoot); VRCamera = CreateDefaultSubobject(TEXT("VR Camera")); VRCamera->SetupAttachment(CameraRoot); MotionControllerR = CreateDefaultSubobject(TEXT("MotionControllerR")); MotionControllerR->SetupAttachment(PawnRoot); MotionControllerR->MotionSource = FName(TEXT("Right")); MotionControllerL = CreateDefaultSubobject(TEXT("MotionControllerL")); MotionControllerL->SetupAttachment(PawnRoot); MotionControllerL->MotionSource = FName(TEXT("Left")); LeftHandMesh = CreateDefaultSubobject(TEXT("LeftHandMesh")); LeftHandMesh->SetupAttachment(MotionControllerL); RightHandMesh = CreateDefaultSubobject(TEXT("RightHandMesh")); RightHandMesh->SetupAttachment(MotionControllerR); RightHandCollision = CreateDefaultSubobject(TEXT("RightHandCollision")); RightHandCollision->SetupAttachment(RightHandMesh); RightHandCollision->SetSphereRadius(10.0f, false); LeftHandCollision = CreateDefaultSubobject(TEXT("LeftHandCollision")); LeftHandCollision->SetupAttachment(LeftHandMesh); LeftHandCollision->SetSphereRadius(10.0f, false); WidgetInteractionL = CreateDefaultSubobject(TEXT("WidgetInteraction_L")); WidgetInteractionL->SetupAttachment(LeftHandMesh); WidgetInteractionR = CreateDefaultSubobject(TEXT("WidgetInteraction_R")); WidgetInteractionR->SetupAttachment(RightHandMesh); LaserL = CreateDefaultSubobject(TEXT("LaserL")); LaserL->SetupAttachment(WidgetInteractionL); LaserR = CreateDefaultSubobject(TEXT("LaserR")); LaserR->SetupAttachment(WidgetInteractionR); } // Sample Hand Velocities for throw approximation void AFireworksPawn::SampleHandVelocities() { // Left hand if (IsGrabbingL) { VelocitySamples_L.Add(LeftHandCollision->GetPhysicsLinearVelocity()); if (VelocitySamples_L.Num() > VelocitySamples) { VelocitySamples_L.RemoveAt(0, 1, true); } } // Right hand if (IsGrabbingR) { VelocitySamples_R.Add(RightHandCollision->GetPhysicsLinearVelocity()); if (VelocitySamples_R.Num() > VelocitySamples) { VelocitySamples_R.RemoveAt(0, 1, true); } } } // Called when the game starts or when spawned void AFireworksPawn::BeginPlay() { Super::BeginPlay(); CameraRoot->AttachToComponent(PawnRoot, FAttachmentTransformRules::KeepRelativeTransform); VRCamera->AttachToComponent(CameraRoot, FAttachmentTransformRules::KeepRelativeTransform); MotionControllerR->AttachToComponent(PawnRoot, FAttachmentTransformRules::KeepRelativeTransform); MotionControllerL->AttachToComponent(PawnRoot, FAttachmentTransformRules::KeepRelativeTransform); LeftHandMesh->AttachToComponent(MotionControllerL, FAttachmentTransformRules::KeepRelativeTransform); RightHandMesh->AttachToComponent(MotionControllerR, FAttachmentTransformRules::KeepRelativeTransform); RightHandCollision->AttachToComponent(RightHandMesh, FAttachmentTransformRules::SnapToTargetNotIncludingScale); LeftHandCollision->AttachToComponent(LeftHandMesh, FAttachmentTransformRules::SnapToTargetNotIncludingScale); WidgetInteractionL->AttachToComponent(LeftHandMesh, FAttachmentTransformRules::KeepRelativeTransform); WidgetInteractionR->AttachToComponent(RightHandMesh, FAttachmentTransformRules::KeepRelativeTransform); LaserL->AttachToComponent(WidgetInteractionL, FAttachmentTransformRules::SnapToTargetNotIncludingScale); LaserR->AttachToComponent(WidgetInteractionR, FAttachmentTransformRules::SnapToTargetNotIncludingScale); } // Called every frame void AFireworksPawn::Tick(float DeltaTime) { Super::Tick(DeltaTime); SampleHandVelocities(); TryUpdateLasers(); TryUpdateTeleportVisual(); } // Called to bind functionality to input void AFireworksPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); } // Setup grab attachment void AFireworksPawn::SetupGrabAttachment(EControllerHand Hand) { FName Socket = Hand == EControllerHand::Left ? TEXT("SocketL") : TEXT("SocketR"); APickableObject* HeldObject = Hand == EControllerHand::Left ? HeldObject_L : HeldObject_R; FTransform HeldObjectTransformLocal = HeldObject->StaticMesh->GetSocketTransform(Socket, ERelativeTransformSpace::RTS_Actor); // Set new values FTransform NewTransform; NewTransform.SetScale3D(FVector(1.f, 1.f, 1.f)); NewTransform.SetLocation(FVector(0.f, 0.f, 0.f)); NewTransform.SetRotation(HeldObjectTransformLocal.GetRotation().Inverse()); // Set new transform HeldObject->SetActorRelativeTransform(NewTransform); HeldObject->AddActorLocalOffset(HeldObjectTransformLocal.GetLocation() * -1.f); } // Try grab bool AFireworksPawn::TryGrab(EControllerHand Hand) { if (Hand == EControllerHand::Left) { // Left hand if (IsValid(HeldObject_L)) { return false; } if (!IsValid(HoveredActorL)) { return false; } HoveredActorL->StaticMesh->SetSimulatePhysics(false); HoveredActorL->StaticMesh->SetRenderCustomDepth(false); HeldObject_L = HoveredActorL; HeldObject_L->AttachToComponent(MotionControllerL, FAttachmentTransformRules::KeepRelativeTransform); SetupGrabAttachment(EControllerHand::Left); HeldObject_L->SetInstigator(this); IsGrabbingL = true; return true; } else { // Right hand if (IsValid(HeldObject_R)) { return false; } if (!IsValid(HoveredActorR)) { return false; } HoveredActorR->StaticMesh->SetSimulatePhysics(false); HoveredActorR->StaticMesh->SetRenderCustomDepth(false); HeldObject_R = HoveredActorR; HeldObject_R->StaticMesh->AttachToComponent(MotionControllerR, FAttachmentTransformRules::KeepRelativeTransform); SetupGrabAttachment(EControllerHand::Right); HeldObject_R->SetInstigator(this); IsGrabbingR = true; return true; } } // Approximate Throw Velocity FVector AFireworksPawn::ApproximateThrowVelocity(EControllerHand Hand) { TArray SampledVelocities = Hand == EControllerHand::Left ? VelocitySamples_L : VelocitySamples_R; FVector ApproximatedVelocity; if (!SampledVelocities.IsEmpty()) { // TODO: discard shortest and longest vector, but first check if average works // Calculate arithmetic average of Sampled Velocities for (int32 i = 0; i < SampledVelocities.Num(); i++) { ApproximatedVelocity += SampledVelocities[i]; // Draw debug lines if (DrawApproximationDebug) { FVector StartLoc = Hand == EControllerHand::Left ? LeftHandMesh->GetComponentLocation() : RightHandMesh->GetComponentLocation(); DrawDebugLine(GetWorld(), StartLoc, StartLoc + SampledVelocities[i], FColor::Magenta, false, 2.f); } } ApproximatedVelocity = ApproximatedVelocity / SampledVelocities.Num(); } // Draw final result debug if (DrawApproximationDebug) { FVector StartLoc = Hand == EControllerHand::Left ? LeftHandMesh->GetComponentLocation() : RightHandMesh->GetComponentLocation(); DrawDebugLine(GetWorld(), StartLoc, StartLoc + ApproximatedVelocity, FColor::Green, false, 2.f); } return ApproximatedVelocity; } // Throw object void AFireworksPawn::ThrowObject(APickableObject* Object, EControllerHand Hand) { Object->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); Object->StaticMesh->SetSimulatePhysics(true); Object->StaticMesh->SetPhysicsLinearVelocity(ApproximateThrowVelocity(Hand) * ThrowVelocityMultiplier); Object->ThrowEffects(); } // Drop bool AFireworksPawn::Drop(EControllerHand Hand) { if (Hand == EControllerHand::Left) { // Left hand if (IsValid(HeldObject_L)) { ThrowObject(HeldObject_L, Hand); HeldObject_L = nullptr; IsGrabbingL = false; } else { return false; } } else { // Right hand if (IsValid(HeldObject_R)) { ThrowObject(HeldObject_R, Hand); HeldObject_R = nullptr; IsGrabbingR = false; } else { return false; } } return true; } // Add firecracker to inventory void AFireworksPawn::AddToInventory(const FFireworkEntry& Entry) { } // Remove firecracker from inventory void AFireworksPawn::RemoveFromInventory(const FFireworkData& Item) { } // Enable laser on specific hand void AFireworksPawn::EnableLaserOnHand(EControllerHand Hand) { if (Hand == EControllerHand::Left) { TryShowLaserL = true; } else { TryShowLaserR = true; } } // Disable laser on hand void AFireworksPawn::DisableLaserOnHand(EControllerHand Hand) { if (Hand == EControllerHand::Left) { TryShowLaserL = false; } else { TryShowLaserR = false; } } // Update laser visuals and trace void AFireworksPawn::TryUpdateLasers() { // --- Left Hand --- // Check if this hand is grabbing anything, if not - continue if (!IsGrabbingL) { // Check if is pointing at any widget - if yes, omit trace and show laser if (WidgetInteractionL->IsOverHitTestVisibleWidget()) { LaserL->SetHiddenInGame(false); LaserL->SetRelativeScale3D(FVector(WidgetInteractionL->GetLastHitResult().Distance, LaserL->GetRelativeScale3D().Y, LaserL->GetRelativeScale3D().Z)); } // If not pointing widget, check if Laser (teleport) button is pressed - if yes, show laser else if (TryShowLaserL) { FVector StartLocation = LaserL->GetComponentLocation(); FVector EndLocation = StartLocation + (WidgetInteractionL->GetForwardVector() * WidgetInteractionL->InteractionDistance); GetWorld()->LineTraceSingleByChannel(CurrentLaserHitL, StartLocation, EndLocation, ECollisionChannel::ECC_Visibility); LaserL->SetHiddenInGame(false); LaserL->SetRelativeScale3D(FVector(CurrentLaserHitL.Distance, LaserL->GetRelativeScale3D().Y, LaserL->GetRelativeScale3D().Z)); } // If the laser is visible, and should not - hide it else if(!LaserL->bHiddenInGame) { LaserL->SetHiddenInGame(true); } } // --- Right Hand --- // Check if this hand is grabbing anything, if not - continue if (!IsGrabbingR) { // Check if is pointing at any widget - if yes, omit trace and show laser if (WidgetInteractionR->IsOverHitTestVisibleWidget()) { LaserR->SetHiddenInGame(false); LaserR->SetRelativeScale3D(FVector(WidgetInteractionR->GetLastHitResult().Distance, LaserR->GetRelativeScale3D().Y, LaserR->GetRelativeScale3D().Z)); } // If not pointing widget, check if Laser (teleport) button is pressed - if yes, show laser else if (TryShowLaserR) { FVector StartLocation = LaserR->GetComponentLocation(); FVector EndLocation = StartLocation + (WidgetInteractionR->GetForwardVector() * WidgetInteractionR->InteractionDistance); GetWorld()->LineTraceSingleByChannel(CurrentLaserHitR, StartLocation, EndLocation, ECollisionChannel::ECC_Visibility); LaserR->SetHiddenInGame(false); LaserR->SetRelativeScale3D(FVector(CurrentLaserHitR.Distance, LaserR->GetRelativeScale3D().Y, LaserR->GetRelativeScale3D().Z)); } // If the laser is visible, and should not - hide it else if (!LaserR->bHiddenInGame) { LaserR->SetHiddenInGame(true); } } } // Try update teleport visual void AFireworksPawn::TryUpdateTeleportVisual() { bool RightLaserCanTeleport = !LaserR->bHiddenInGame && (IsValid(CurrentLaserHitR.GetActor()) ? CurrentLaserHitR.GetActor()->ActorHasTag(TEXT("Ground")) : false); bool LeftLaserCanTeleport = !LaserL->bHiddenInGame && (IsValid(CurrentLaserHitL.GetActor()) ? CurrentLaserHitL.GetActor()->ActorHasTag(TEXT("Ground")) : false); // Firstly cover the case of both hands pointing at ground with laser if (RightLaserCanTeleport && LeftLaserCanTeleport) { if (DominatingHand == EControllerHand::Left) { TeleportVisual->SetActorHiddenInGame(false); TeleportVisual->SetActorLocation(CurrentLaserHitL.Location); return; } else { TeleportVisual->SetActorHiddenInGame(false); TeleportVisual->SetActorLocation(CurrentLaserHitR.Location); return; } } // Check if any hand is pointing laser at the ground if (LeftLaserCanTeleport) { TeleportVisual->SetActorHiddenInGame(false); TeleportVisual->SetActorLocation(CurrentLaserHitL.Location); return; } else if (RightLaserCanTeleport) { TeleportVisual->SetActorHiddenInGame(false); TeleportVisual->SetActorLocation(CurrentLaserHitR.Location); return; } else { TeleportVisual->SetActorHiddenInGame(true); } }