Implement an interact hold input action and a new callback for it, seed bed collect seed action as well, change lavender seed mesh to sprite
This commit is contained in:
Binary file not shown.
BIN
Content/Input/IA_InteractHold.uasset
LFS
Normal file
BIN
Content/Input/IA_InteractHold.uasset
LFS
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -84,14 +84,24 @@ AActor* UInteractionSubsystem::GetClosestInteractable(const UObject* WorldContex
|
||||
if (!Actor.IsValid())
|
||||
continue;
|
||||
|
||||
const float Dist = FVector::DistSquared(Actor->GetActorLocation(), PlayerCharacter->GetActorLocation());
|
||||
// Actors whose real interaction target is not their own origin (a bloomed plant inside a seed bed
|
||||
// grid, for example) report the point everything should be measured against. Distance, the on-screen
|
||||
// test and the "closest wins" pick then all reason about that point instead of the actor's center.
|
||||
// Zero means the actor has no preference, so its own origin is used.
|
||||
FVector FocusLocation = IInteractableActorInterface::Execute_GetInteractionFocusLocation(Actor.Get());
|
||||
if (FocusLocation.IsZero())
|
||||
{
|
||||
FocusLocation = Actor->GetActorLocation();
|
||||
}
|
||||
|
||||
const float Dist = FVector::DistSquared(FocusLocation, PlayerCharacter->GetActorLocation());
|
||||
const float MaxInteractionDistance = IInteractableActorInterface::Execute_GetInteractionDistance(Actor.Get());
|
||||
|
||||
if (FMath::Pow(MaxInteractionDistance, 2) < Dist || Dist > ClosestDist)
|
||||
continue;
|
||||
|
||||
FVector2D ScreenLoc;
|
||||
if (!PC->ProjectWorldLocationToScreen(Actor->GetActorLocation(), ScreenLoc))
|
||||
if (!PC->ProjectWorldLocationToScreen(FocusLocation, ScreenLoc))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "UObject/Interface.h"
|
||||
#include "InteractionSettings.h"
|
||||
#include "InteractableActorInterface.generated.h"
|
||||
@@ -44,4 +45,18 @@ public:
|
||||
{
|
||||
return GetDefault<UInteractionSettings>()->InteractableMaxDistance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Point the interaction system should reason about for this actor: the distance check, the on-screen
|
||||
* test and the "closest wins" pick all measure towards it. Zero means "no preference" and the actor's
|
||||
* own origin is used, which is right for a single-object actor. An actor whose real interaction target
|
||||
* sits somewhere else (a bloomed plant inside a seed bed grid, for example) overrides this so the prompt
|
||||
* lands where the player looks.
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interactable")
|
||||
FVector GetInteractionFocusLocation() const;
|
||||
virtual FVector GetInteractionFocusLocation_Implementation() const
|
||||
{
|
||||
return FVector::ZeroVector;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
#include "Kismet/KismetMaterialLibrary.h"
|
||||
#include "Materials/MaterialInstanceDynamic.h"
|
||||
#include "Materials/MaterialInterface.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "ProjectEleri/Components/EleriPaperSpriteComponent.h"
|
||||
#include "ProjectEleri/Settings/EleriGameSettings.h"
|
||||
#include "NiagaraComponent.h"
|
||||
@@ -134,6 +136,10 @@ ASeedBedActor::ASeedBedActor()
|
||||
USceneComponent* Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
|
||||
Root->SetMobility(EComponentMobility::Movable);
|
||||
SetRootComponent(Root);
|
||||
|
||||
// The prompt is attached to the bed so a slot location can be expressed relative to it and follows it
|
||||
WidgetComponent = CreateDefaultSubobject<UInteractionWidgetComponent>(TEXT("WidgetComponent"));
|
||||
WidgetComponent->SetupAttachment(Root);
|
||||
}
|
||||
|
||||
void ASeedBedActor::BeginPlay()
|
||||
@@ -240,7 +246,165 @@ void ASeedBedActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
void ASeedBedActor::Tick(float DeltaTime)
|
||||
{
|
||||
Super::Tick(DeltaTime);
|
||||
|
||||
// The interaction plugin picks its target every tick, so the slot the prompt points at has to be fresh too
|
||||
UpdateBloomedSlotTarget();
|
||||
|
||||
// Keep the prompt glued to the bloomed plant while the player walks around the bed
|
||||
if (bInteractionUIActive && bHasBloomedSlotInRange)
|
||||
{
|
||||
PlaceInteractionWidgetOverSlot(ClosestBloomedSlot);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------
|
||||
// INTERACTION
|
||||
//------------------------------------------------------------------------------------------------
|
||||
|
||||
void ASeedBedActor::UpdateBloomedSlotTarget()
|
||||
{
|
||||
bHasBloomedSlotInRange = false;
|
||||
ClosestBloomedSlot = FIntVector2::NoneValue;
|
||||
|
||||
const UWorld* World = GetWorld();
|
||||
const APlayerController* PlayerController = World ? World->GetFirstPlayerController() : nullptr;
|
||||
const APawn* PlayerPawn = PlayerController ? PlayerController->GetPawn() : nullptr;
|
||||
if (!IsValid(PlayerPawn))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FIntVector2 FoundSlot;
|
||||
if (FindClosestBloomedSlot(PlayerPawn->GetActorLocation(), FoundSlot))
|
||||
{
|
||||
ClosestBloomedSlot = FoundSlot;
|
||||
bHasBloomedSlotInRange = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool ASeedBedActor::FindClosestBloomedSlot(const FVector& FromWorldLocation, FIntVector2& OutSlotCoordinate) const
|
||||
{
|
||||
// The reach is measured from the slot and not from the center of the bed, so an outer plant is as easy
|
||||
// to reach as a middle one even though the actor origin is far away from it
|
||||
const float ReachSquared = FMath::Square(BloomInteractDistance);
|
||||
|
||||
float ClosestDistanceSquared = ReachSquared;
|
||||
bool bFound = false;
|
||||
|
||||
for (const TPair<FIntVector2, FSeedBedSeedSlot>& SlotPair : SeedBedSeedSlotsMap)
|
||||
{
|
||||
// Only a living plant that has reached its bloom can be collected off the bed
|
||||
if (!SlotHasLivingPlant(SlotPair.Value) || !SlotPair.Value.SlotData.bBloomed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
FVector SlotWorldLocation;
|
||||
if (!GetSlotWorldLocation(SlotPair.Key, SlotWorldLocation))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const float DistanceSquared = FVector::DistSquared(FromWorldLocation, SlotWorldLocation);
|
||||
if (DistanceSquared > ClosestDistanceSquared)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ClosestDistanceSquared = DistanceSquared;
|
||||
OutSlotCoordinate = SlotPair.Key;
|
||||
bFound = true;
|
||||
}
|
||||
|
||||
return bFound;
|
||||
}
|
||||
|
||||
void ASeedBedActor::PlaceInteractionWidgetOverSlot(FIntVector2 SlotCoordinate)
|
||||
{
|
||||
if (!WidgetComponent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// The widget is attached to the root, so a slot location is expressed relative to the bed and follows it
|
||||
WidgetComponent->SetRelativeLocation(GetSlotRelativeLocation(SlotCoordinate) + FVector(0.f, 0.f, InteractionPromptHeight));
|
||||
}
|
||||
|
||||
bool ASeedBedActor::CollectBloomedSlot(FIntVector2 SlotCoordinate)
|
||||
{
|
||||
FSeedBedSeedSlot* Slot = SeedBedSeedSlotsMap.Find(SlotCoordinate);
|
||||
if (!Slot || !SlotHasLivingPlant(*Slot) || !Slot->SlotData.bBloomed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Harvesting empties the slot and takes the plant mesh with it, so the next seed can be planted right away
|
||||
ClearSeedFromSlot(SlotCoordinate, true);
|
||||
|
||||
// The collected plant was the prompt's target, the next tick re-evaluates the bed
|
||||
ClosestBloomedSlot = FIntVector2::NoneValue;
|
||||
bHasBloomedSlotInRange = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ASeedBedActor::IsInteractable_Implementation() const
|
||||
{
|
||||
return bHasBloomedSlotInRange;
|
||||
}
|
||||
|
||||
FText ASeedBedActor::GetInteractionText_Implementation() const
|
||||
{
|
||||
return BloomedPlantInteractionText;
|
||||
}
|
||||
|
||||
void ASeedBedActor::Interact_Implementation(AActor* InteractionInstigator)
|
||||
{
|
||||
if (ClosestBloomedSlot != FIntVector2::NoneValue)
|
||||
{
|
||||
CollectBloomedSlot(ClosestBloomedSlot);
|
||||
}
|
||||
}
|
||||
|
||||
void ASeedBedActor::ToggleInteractableUI_Implementation(bool bActive)
|
||||
{
|
||||
if (bActive && !IInteractableActorInterface::Execute_IsInteractable(this))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IInteractableActorInterface::ToggleInteractableUI_Implementation(bActive);
|
||||
|
||||
bInteractionUIActive = bActive;
|
||||
|
||||
// Move the prompt over the bloomed plant before it is shown, so it never flashes at the actor origin
|
||||
if (bActive)
|
||||
{
|
||||
PlaceInteractionWidgetOverSlot(ClosestBloomedSlot);
|
||||
}
|
||||
|
||||
if (WidgetComponent)
|
||||
{
|
||||
WidgetComponent->ToggleWidget(bActive);
|
||||
}
|
||||
}
|
||||
|
||||
float ASeedBedActor::GetInteractionDistance_Implementation() const
|
||||
{
|
||||
return BloomInteractDistance;
|
||||
}
|
||||
|
||||
FVector ASeedBedActor::GetInteractionFocusLocation_Implementation() const
|
||||
{
|
||||
FVector SlotWorldLocation;
|
||||
if (ClosestBloomedSlot != FIntVector2::NoneValue && GetSlotWorldLocation(ClosestBloomedSlot, SlotWorldLocation))
|
||||
{
|
||||
return SlotWorldLocation;
|
||||
}
|
||||
|
||||
return GetActorLocation();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------
|
||||
// TIME
|
||||
//------------------------------------------------------------------------------------------------
|
||||
@@ -678,6 +842,13 @@ void ASeedBedActor::ApplySlotVisuals(FIntVector2 SlotCoordinate, FSeedBedSeedSlo
|
||||
}
|
||||
}
|
||||
|
||||
// VFX
|
||||
if (IsValid(Slot.NiagaraComponent) && !Slot.SlotData.bBloomed && Slot.NiagaraComponent->IsVisible())
|
||||
{
|
||||
Slot.NiagaraComponent->Deactivate();
|
||||
Slot.NiagaraComponent->SetVisibility(false);
|
||||
}
|
||||
|
||||
// PLANT ----------------------------------------------------------------------------------------
|
||||
if (!IsValid(Slot.PlantSprite))
|
||||
{
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "Components/InteractionWidgetComponent.h"
|
||||
#include "Interface/InteractableActorInterface.h"
|
||||
#include "ProjectEleri/System/TimeOfDayStruct.h"
|
||||
#include "SeedBedActor.generated.h"
|
||||
|
||||
@@ -114,7 +116,7 @@ struct FSeedBedSaveData
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class PROJECTELERI_API ASeedBedActor : public AActor
|
||||
class PROJECTELERI_API ASeedBedActor : public AActor, public IInteractableActorInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
@@ -122,6 +124,32 @@ public:
|
||||
ASeedBedActor();
|
||||
virtual void Tick(float DeltaTime) override;
|
||||
|
||||
//------------------------------------------------------------------------------------------------
|
||||
// INTERACTION
|
||||
//------------------------------------------------------------------------------------------------
|
||||
|
||||
/** True while a living, bloomed plant is close enough to the player to be collected */
|
||||
virtual bool IsInteractable_Implementation() const override;
|
||||
|
||||
/** Text shown on the prompt floating above a bloomed plant */
|
||||
virtual FText GetInteractionText_Implementation() const override;
|
||||
|
||||
/** Harvests the bloomed plant the prompt currently points at */
|
||||
virtual void Interact_Implementation(AActor* InteractionInstigator) override;
|
||||
|
||||
/** Shows / hides the interaction prompt and moves it over the bloomed plant */
|
||||
virtual void ToggleInteractableUI_Implementation(bool bActive) override;
|
||||
|
||||
virtual float GetInteractionDistance_Implementation() const override;
|
||||
|
||||
/** The plugin targets this point instead of the actor origin, so range and on-screen checks are measured
|
||||
* to the bloomed plant rather than to the center of the bed */
|
||||
virtual FVector GetInteractionFocusLocation_Implementation() const override;
|
||||
|
||||
/** Collects the bloomed plant of a slot, returns false when the slot holds nothing harvestable */
|
||||
UFUNCTION(BlueprintCallable, Category = "Seed Bed|Interaction")
|
||||
bool CollectBloomedSlot(FIntVector2 SlotCoordinate);
|
||||
|
||||
//------------------------------------------------------------------------------------------------
|
||||
// DATA
|
||||
//------------------------------------------------------------------------------------------------
|
||||
@@ -319,6 +347,45 @@ protected:
|
||||
UFUNCTION()
|
||||
void HandleHourPassed();
|
||||
|
||||
//------------------------------------------------------------------------------------------------
|
||||
// INTERACTION
|
||||
//------------------------------------------------------------------------------------------------
|
||||
|
||||
/** Re-evaluates which bloomed slot (if any) is within reach of the local player */
|
||||
void UpdateBloomedSlotTarget();
|
||||
|
||||
/** Finds the living, bloomed slot closest to the given world location and within BloomInteractDistance */
|
||||
bool FindClosestBloomedSlot(const FVector& FromWorldLocation, FIntVector2& OutSlotCoordinate) const;
|
||||
|
||||
/** Moves the interaction widget above the given slot */
|
||||
void PlaceInteractionWidgetOverSlot(FIntVector2 SlotCoordinate);
|
||||
|
||||
/** Prompt floating above a bloomed plant, created once and moved to whichever slot is in reach */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Seed Bed|Interaction")
|
||||
UInteractionWidgetComponent* WidgetComponent = nullptr;
|
||||
|
||||
/** How close the player has to be to a bloomed plant for it to become interactable */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Seed Bed|Interaction")
|
||||
float BloomInteractDistance = 250.f;
|
||||
|
||||
/** How far above a slot the interaction prompt floats */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Seed Bed|Interaction")
|
||||
float InteractionPromptHeight = 120.f;
|
||||
|
||||
/** Text shown on the prompt floating above a bloomed plant */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Seed Bed|Interaction")
|
||||
FText BloomedPlantInteractionText = NSLOCTEXT("SeedBedInteraction", "CollectBloomedPlant", "Collect");
|
||||
|
||||
/** Slot the interaction prompt currently points at, FIntVector2::NoneValue when there is none */
|
||||
UPROPERTY(Transient)
|
||||
FIntVector2 ClosestBloomedSlot = FIntVector2::NoneValue;
|
||||
|
||||
/** True while a living, bloomed plant is within BloomInteractDistance of the local player */
|
||||
bool bHasBloomedSlotInRange = false;
|
||||
|
||||
/** True while the interaction plugin is showing this bed's prompt widget */
|
||||
bool bInteractionUIActive = false;
|
||||
|
||||
|
||||
/** Whether the highlight decals of the bed are currently shown */
|
||||
bool bPlantingHighlightActive = false;
|
||||
|
||||
@@ -210,6 +210,11 @@ void AEleriPlayerController::SetupInputComponent()
|
||||
{
|
||||
PlayerEnhancedInputComponent->BindAction(InteractAction, ETriggerEvent::Started, this, &AEleriPlayerController::OnInteract);
|
||||
}
|
||||
if (InteractHoldAction)
|
||||
{
|
||||
PlayerEnhancedInputComponent->BindAction(InteractHoldAction, ETriggerEvent::Started, this, &AEleriPlayerController::OnInteractHoldStart);
|
||||
PlayerEnhancedInputComponent->BindAction(InteractHoldAction, ETriggerEvent::Triggered, this, &AEleriPlayerController::OnInteractHold);
|
||||
}
|
||||
if (InteractExitAction)
|
||||
{
|
||||
PlayerEnhancedInputComponent->BindAction(InteractExitAction, ETriggerEvent::Started, this, &AEleriPlayerController::OnInteractExit);
|
||||
@@ -1177,6 +1182,20 @@ void AEleriPlayerController::OnInteract(const FInputActionValue &Value)
|
||||
PlayerCharacter->OnInteract.Broadcast();
|
||||
}
|
||||
|
||||
void AEleriPlayerController::OnInteractHoldStart(const FInputActionValue& Value)
|
||||
{
|
||||
OnButtonHeld.Broadcast(true, InteractHoldAction);
|
||||
}
|
||||
|
||||
void AEleriPlayerController::OnInteractHold(const FInputActionValue& Value)
|
||||
{
|
||||
if (!IsValid(PlayerCharacter))
|
||||
return;
|
||||
|
||||
UE_LOG(LogTemp, Warning, TEXT("INTERACT HOLD"));
|
||||
OnButtonHeld.Broadcast(false, InteractHoldAction);
|
||||
}
|
||||
|
||||
void AEleriPlayerController::OnInteractExit(const FInputActionValue &Value)
|
||||
{
|
||||
if (!IsValid(PlayerCharacter))
|
||||
|
||||
@@ -46,6 +46,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnWatering, const bool, Started);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnChangeInputContext, const int32, Index);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRemovalModeToggle, const bool, Active);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPlayerToggleFlying, const bool, Active);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnButtonHeld, const bool, StartHolding, const UInputAction*, InputAction);
|
||||
|
||||
DECLARE_DELEGATE_OneParam(FOnMinigameComplete, int32 /*Score*/);
|
||||
|
||||
@@ -130,6 +131,9 @@ public:
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOnPlayerToggleFlying OnPlayerToggleFlying;
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOnButtonHeld OnButtonHeld;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
AMyCharacter* PlayerCharacter;
|
||||
|
||||
@@ -224,6 +228,8 @@ public:
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Action Input")
|
||||
UInputAction* InteractAction;
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Action Input")
|
||||
UInputAction* InteractHoldAction;
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Action Input")
|
||||
UInputAction* InteractExitAction;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Action Input")
|
||||
@@ -372,6 +378,8 @@ public:
|
||||
void ToggleBazarInput(bool bActive);
|
||||
|
||||
void OnInteract(const FInputActionValue& Value);
|
||||
void OnInteractHoldStart(const FInputActionValue& Value);
|
||||
void OnInteractHold(const FInputActionValue& Value);
|
||||
void OnInteractExit(const FInputActionValue& Value);
|
||||
void OnHarvestSeed(const FInputActionValue& Value);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user