// Fill out your copyright notice in the Description page of Project Settings. #include "Item.h" #include "ShooterCharacter.h" #include "Components/BoxComponent.h" #include "Components/SphereComponent.h" #include "Components/WidgetComponent.h" #include "Camera/CameraComponent.h" #include "Kismet/GameplayStatics.h" #include "Sound/SoundCue.h" #include "Curves/CurveVector.h" // Sets default values AItem::AItem() : ItemName(FString("Default")), ItemCount(0), ItemRarity(EItemRarity::EIR_Common), ItemState(EItemState::EIS_Pickup), // Item interp variables ItemInterpStartLocation(FVector(0.f)), CameraTargetLocation(FVector(0.f)), bInterping(false), ZCurveTime(0.7f), ItemIntepX(0.f), ItemIntepY(0.f), InterpInitialYawOffset(0.f), ItemType(EItemType::EIT_MAX), InterpLocIndex(0), MaterialIndex(0), bCanChangeCustomDepth(true), // Dynamic Material Parameters PulseCurveTime(5.f), GlowAmount(150.f), FresnelExponent(3.f), FresnelReflectFraction(4.f), SlotIndex(0) { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; ItemMesh = CreateDefaultSubobject(TEXT("ItemMesh")); SetRootComponent(ItemMesh); CollisionBox = CreateDefaultSubobject(TEXT("CollisionBox")); CollisionBox->SetupAttachment(ItemMesh); CollisionBox->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); CollisionBox->SetCollisionResponseToChannel(ECollisionChannel::ECC_Visibility, ECollisionResponse::ECR_Block); PickupWidget = CreateDefaultSubobject(TEXT("PickupWidget")); PickupWidget->SetupAttachment(GetRootComponent()); AreaSphere = CreateDefaultSubobject(TEXT("AreaSphere")); AreaSphere->SetupAttachment(GetRootComponent()); } // Called when the game starts or when spawned void AItem::BeginPlay() { Super::BeginPlay(); // Hide Pickup Widget if (PickupWidget) PickupWidget->SetVisibility(false); // Set ActiveStars array based on item rarity setActiveStars(); // Setup overlap for AreaSphere AreaSphere->OnComponentBeginOverlap.AddDynamic(this, &AItem::OnSphereOverlap); AreaSphere->OnComponentEndOverlap.AddDynamic(this, &AItem::OnSphereEndOverlap); //Set Item properties based on ItemState SetItemProperties(ItemState); // Set custom depth to disabled InitializeCustomDepth(); StartPulseTimer(); // We wait a small delay to make sure all actors are spawned, // then we set a small sphere radius and reset it again, // this generates any overlap events that weren't accounted for on spawn FTimerHandle TimerHandle; GetWorldTimerManager().SetTimer(TimerHandle, [&]() { AreaSphereRadius = AreaSphere->GetUnscaledSphereRadius(); AreaSphere->SetSphereRadius(1.f); AreaSphere->SetSphereRadius(AreaSphereRadius); }, 0.5, false); } void AItem::setActiveStars() { // The 0 element isn't used for (int32 i = 0; i <= 5; ++i) { ActiveStars.Add(false); } switch (ItemRarity) { case EItemRarity::EIR_Damaged: ActiveStars[1] = true; break; case EItemRarity::EIR_Common: ActiveStars[1] = true; ActiveStars[2] = true; break; case EItemRarity::EIR_Uncommon: ActiveStars[1] = true; ActiveStars[2] = true; ActiveStars[3] = true; break; case EItemRarity::EIR_Rare: ActiveStars[1] = true; ActiveStars[2] = true; ActiveStars[3] = true; ActiveStars[4] = true; break; case EItemRarity::EIR_Legendary: ActiveStars[1] = true; ActiveStars[2] = true; ActiveStars[3] = true; ActiveStars[4] = true; ActiveStars[5] = true; break; } } void AItem::PlayPickupSound() { if (Character) { if (Character->ShouldPlayPickupSound()) { Character->StartPickupSoundTimer(); if (PickupSound) { UGameplayStatics::PlaySound2D(this, PickupSound); } } } } void AItem::EnableCustomDepth() { if (bCanChangeCustomDepth) ItemMesh->SetRenderCustomDepth(true); } void AItem::DisableCustomDepth() { if (bCanChangeCustomDepth) ItemMesh->SetRenderCustomDepth(false); } void AItem::InitializeCustomDepth() { DisableCustomDepth(); } void AItem::OnConstruction(const FTransform& Transform) { if (MaterialInstance) { DynamicMaterialInstance = UMaterialInstanceDynamic::Create(MaterialInstance, this); ItemMesh->SetMaterial(MaterialIndex, DynamicMaterialInstance); } EnableGlowMaterial(); } void AItem::UpdatePulse() { float ElapsedTime{}; FVector CurveValue{}; switch (ItemState) { case EItemState::EIS_Pickup: if (PulseCurve) { ElapsedTime = GetWorldTimerManager().GetTimerElapsed(PulseTimer); CurveValue = PulseCurve->GetVectorValue(ElapsedTime); } break; case EItemState::EIS_EquipInterping: if (InterpPulseCurve) { ElapsedTime = GetWorldTimerManager().GetTimerElapsed(ItemInterpTimer); CurveValue = InterpPulseCurve->GetVectorValue(ElapsedTime); } break; } if (DynamicMaterialInstance) { DynamicMaterialInstance->SetScalarParameterValue(TEXT("GlowAmount"), CurveValue.X * GlowAmount); DynamicMaterialInstance->SetScalarParameterValue(TEXT("FresnelExponent"), CurveValue.Y * FresnelExponent); DynamicMaterialInstance->SetScalarParameterValue(TEXT("FresnelReflectFraction"), CurveValue.Z * FresnelReflectFraction); } } void AItem::EnableGlowMaterial() { if (DynamicMaterialInstance) { DynamicMaterialInstance->SetScalarParameterValue(TEXT("GlowBlendAlpha"), 0); } } void AItem::DisableGlowMaterial() { if (DynamicMaterialInstance) { DynamicMaterialInstance->SetScalarParameterValue(TEXT("GlowBlendAlpha"), 1); } } void AItem::PlayEquipSound() { if (Character) { if (Character->ShouldPlayEquipSound()) { Character->StartEquipSoundTimer(); if (EquipSound) { UGameplayStatics::PlaySound2D(this, EquipSound); } } } } // Called every frame void AItem::Tick(float DeltaTime) { Super::Tick(DeltaTime); // Handle Item interping when in the EquipInterping state ItemInterp(DeltaTime); // Get curve values from PulseCurve and set dynamic material parameters UpdatePulse(); } void AItem::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) { if (OtherActor) { AShooterCharacter* ShooterCharacter = Cast(OtherActor); if (ShooterCharacter) { ShooterCharacter->IncrementOverlappedItemCount(1); } } } void AItem::OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) { if (OtherActor) { AShooterCharacter* ShooterCharacter = Cast(OtherActor); if (ShooterCharacter) { ShooterCharacter->IncrementOverlappedItemCount(-1); } } } void AItem::ItemInterp(float DeltaTime) { if (!bInterping) return; if (Character && ItemZCurve) { // Elapsed time since we started ItemInterpTimer const float ElapsedTime = GetWorldTimerManager().GetTimerElapsed(ItemInterpTimer); // Get curve value corresponding to ElapsedTime const float CurveValue = ItemZCurve->GetFloatValue(ElapsedTime); // Get the item's initial location when the curve started FVector ItemLocation = ItemInterpStartLocation; // Get location in front of the camera const FVector CameraInterpLocation{ GetInterpLocation() }; // Vector from Item to Camera Interp Location, X and Y are zeroed out const FVector ItemToCamera{ FVector(0.f, 0.f, (CameraInterpLocation - ItemLocation).Z) }; // SCale factor to multiply with CurveValue const float DeltaZ = ItemToCamera.Size(); const FVector CurrentLocation{ GetActorLocation() }; // Interpolated X Value const float InterpXValue = FMath::FInterpTo(CurrentLocation.X, CameraInterpLocation.X, DeltaTime, 30.f); // Interpolated Y Value const float InterpYValue = FMath::FInterpTo(CurrentLocation.Y, CameraInterpLocation.Y, DeltaTime, 30.f); // Set X and Y of ItemLocation to Interped values ItemLocation.X = InterpXValue; ItemLocation.Y = InterpYValue; // Adding curve value to the Z component of the Initial Location (Scaled by DeltaZ) ItemLocation.Z += CurveValue * DeltaZ; SetActorLocation(ItemLocation, true, nullptr, ETeleportType::TeleportPhysics); // Camera rotation this frame const FRotator CameraRotation{Character->GetFollowCamera()->GetComponentRotation()}; // Camera rotation plus initial Yaw Offset const FRotator ItemRotation{0.f, CameraRotation.Yaw + InterpInitialYawOffset, 0.f}; SetActorRotation(ItemRotation, ETeleportType::TeleportPhysics); if (ItemScaleCurve) { const float ScaleCurveValue = ItemScaleCurve->GetFloatValue(ElapsedTime); SetActorScale3D(FVector(ScaleCurveValue, ScaleCurveValue, ScaleCurveValue)); } } } FVector AItem::GetInterpLocation() { if (!Character) return FVector(0.f); switch (ItemType) { case EItemType::EIT_Ammo: return Character->GetInterpLocation(InterpLocIndex).SceneComponent->GetComponentLocation(); break; case EItemType::EIT_Weapon: return Character->GetInterpLocation(0).SceneComponent->GetComponentLocation(); break; } return FVector(0.f); } void AItem::ResetPulseTimer() { StartPulseTimer(); } void AItem::StartPulseTimer() { if (ItemState == EItemState::EIS_Pickup) { GetWorldTimerManager().SetTimer(PulseTimer, this, &AItem::ResetPulseTimer, PulseCurveTime); } } void AItem::SetItemState(EItemState State) { ItemState = State; SetItemProperties(State); } void AItem::SetItemProperties(EItemState State) { switch (State) { case EItemState::EIS_Pickup: // Set Mesh properties ItemMesh->SetSimulatePhysics(false); ItemMesh->SetEnableGravity(false); ItemMesh->SetVisibility(true); ItemMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); ItemMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); // Set AreaSphere properties AreaSphere->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Overlap); AreaSphere->SetCollisionEnabled(ECollisionEnabled::QueryOnly); // Set CollisionBox properties CollisionBox->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); CollisionBox->SetCollisionResponseToChannel(ECollisionChannel::ECC_Visibility, ECollisionResponse::ECR_Block); CollisionBox->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); break; case EItemState::EIS_Equipped: // Set Mesh properties ItemMesh->SetSimulatePhysics(false); ItemMesh->SetEnableGravity(false); ItemMesh->SetVisibility(true); ItemMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); ItemMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); // Set AreaSphere properties AreaSphere->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision); // Set CollisionBox properties CollisionBox->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); CollisionBox->SetCollisionEnabled(ECollisionEnabled::NoCollision); break; case EItemState::EIS_Falling: // Set Mesh properties ItemMesh->SetSimulatePhysics(true); ItemMesh->SetEnableGravity(true); ItemMesh->SetVisibility(true); ItemMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); ItemMesh->SetCollisionResponseToChannel(ECollisionChannel::ECC_WorldStatic, ECollisionResponse::ECR_Block); ItemMesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); // Set AreaSphere properties AreaSphere->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision); // Set CollisionBox properties CollisionBox->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); CollisionBox->SetCollisionEnabled(ECollisionEnabled::NoCollision); break; case EItemState::EIS_EquipInterping: PickupWidget->SetVisibility(false); // Set Mesh properties ItemMesh->SetSimulatePhysics(false); ItemMesh->SetEnableGravity(false); ItemMesh->SetVisibility(true); ItemMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); ItemMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); // Set AreaSphere properties AreaSphere->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision); // Set CollisionBox properties CollisionBox->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); CollisionBox->SetCollisionEnabled(ECollisionEnabled::NoCollision); break; case EItemState::EIS_PickedUp: PickupWidget->SetVisibility(false); // Set Mesh properties ItemMesh->SetSimulatePhysics(false); ItemMesh->SetEnableGravity(false); ItemMesh->SetVisibility(false); ItemMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); ItemMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); // Set AreaSphere properties AreaSphere->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision); // Set CollisionBox properties CollisionBox->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); CollisionBox->SetCollisionEnabled(ECollisionEnabled::NoCollision); break; } } void AItem::StartItemCurve(AShooterCharacter* Char) { // Store a handle to the Character Character = Char; // Get array index in InterpLocations with the lowest ItemCount InterpLocIndex = Character->GetInterpLocationIndex(); // Add 1 to the Item Count for this interp location struct Character->IncrementInterpLocItemCount(InterpLocIndex, 1); PlayPickupSound(); // Store the initial locatino of the Item ItemInterpStartLocation = GetActorLocation(); bInterping = true; SetItemState(EItemState::EIS_EquipInterping); GetWorldTimerManager().ClearTimer(PulseTimer); GetWorldTimerManager().SetTimer(ItemInterpTimer, this, &AItem::FinishInterping, ZCurveTime); // Get initial Yaw of the Camera const float CameraRotationYaw{ static_cast(Character->GetFollowCamera()->GetComponentRotation().Yaw) }; // Get initial Yaw of the Item const float ItemRotationYaw{ static_cast(GetActorRotation().Yaw) }; // Initial Yaw Offset between Camera and Item InterpInitialYawOffset = ItemRotationYaw - CameraRotationYaw; bCanChangeCustomDepth = false; } void AItem::FinishInterping() { bInterping = false; if (Character) { // Subtract 1 from the Item Count of the interp location struct Character->IncrementInterpLocItemCount(InterpLocIndex, -1); Character->GetPickupItem(this); } // Set scale back to normal SetActorScale3D(FVector(1.f)); bCanChangeCustomDepth = true; DisableCustomDepth(); }