Planting interaction blocker implemented, ui showing planting mode
This commit is contained in:
@@ -21,6 +21,7 @@ void UInteractionSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
void UInteractionSubsystem::Deinitialize()
|
||||
{
|
||||
InteractableActorList.Empty();
|
||||
InteractionUIBlockers.Empty();
|
||||
|
||||
if (UInteractionSubsystem* IS = GetWorld()->GetSubsystem<UInteractionSubsystem>())
|
||||
{
|
||||
@@ -104,19 +105,23 @@ AActor* UInteractionSubsystem::GetClosestInteractable(const UObject* WorldContex
|
||||
Closest = Actor.Get();
|
||||
}
|
||||
|
||||
if (IS->CurrentlyInteractableObject != Closest)
|
||||
// UI-only gate: blocking never changes what this function returns to gameplay code,
|
||||
// it only decides which actor is allowed to show the interaction UI.
|
||||
AActor* UIActor = IS->HasInteractionUIBlocks() ? nullptr : Closest;
|
||||
|
||||
if (IS->CurrentlyInteractableObject.Get() != UIActor)
|
||||
{
|
||||
if (IS->CurrentlyInteractableObject.IsValid())
|
||||
{
|
||||
// UE_LOG(LogTemp, Warning, TEXT("Turning off last interactable: %s"), *IS->CurrentlyInteractableObject.Get()->GetActorNameOrLabel())
|
||||
IInteractableActorInterface::Execute_ToggleInteractableUI(IS->CurrentlyInteractableObject.Get(), false);
|
||||
}
|
||||
if (Closest)
|
||||
if (UIActor)
|
||||
{
|
||||
// UE_LOG(LogTemp, Warning, TEXT("Turning ON interactable: %s"), *Closest->GetActorNameOrLabel())
|
||||
IInteractableActorInterface::Execute_ToggleInteractableUI(Closest, true);
|
||||
// UE_LOG(LogTemp, Warning, TEXT("Turning ON interactable: %s"), *UIActor->GetActorNameOrLabel())
|
||||
IInteractableActorInterface::Execute_ToggleInteractableUI(UIActor, true);
|
||||
}
|
||||
IS->CurrentlyInteractableObject = Closest;
|
||||
IS->CurrentlyInteractableObject = UIActor;
|
||||
}
|
||||
|
||||
return Closest;
|
||||
@@ -131,9 +136,104 @@ void UInteractionSubsystem::UpdateInteractableText(AActor* Interactable)
|
||||
|
||||
if (InteractionSubsystem->CurrentlyInteractableObject != Interactable) return;
|
||||
|
||||
// The interaction UI is being held hidden by a blocker - leave it alone.
|
||||
if (InteractionSubsystem->HasInteractionUIBlocks()) return;
|
||||
|
||||
IInteractableActorInterface::Execute_ToggleInteractableUI(InteractionSubsystem->CurrentlyInteractableObject.Get(), true);
|
||||
}
|
||||
|
||||
void UInteractionSubsystem::AddInteractionUIBlock(const UObject* WorldContextObject, UObject* Blocker)
|
||||
{
|
||||
if (!IsValid(WorldContextObject) || !IsValid(WorldContextObject->GetWorld()) || !IsValid(Blocker))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UInteractionSubsystem* IS = WorldContextObject->GetWorld()->GetSubsystem<UInteractionSubsystem>();
|
||||
if (!IS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Drop dead entries before touching the array so a recycled weak pointer can never be "revived".
|
||||
IS->PruneInteractionUIBlockers();
|
||||
|
||||
const bool bAlreadyBlocking = IS->InteractionUIBlockers.ContainsByPredicate(
|
||||
[Blocker](const TWeakObjectPtr<UObject>& Entry) { return Entry.Get() == Blocker; });
|
||||
|
||||
if (bAlreadyBlocking)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const TWeakObjectPtr<UObject> BlockerRef(Blocker);
|
||||
IS->InteractionUIBlockers.Add(BlockerRef);
|
||||
|
||||
UE_LOG(LogTemp, Verbose, TEXT("Interaction UI blocked by %s (%d blocker(s) active)"),
|
||||
*Blocker->GetName(), IS->InteractionUIBlockers.Num());
|
||||
|
||||
// A widget might be on screen right now, take it down instead of waiting for the next tick.
|
||||
IS->HideCurrentInteractableUI();
|
||||
}
|
||||
|
||||
void UInteractionSubsystem::RemoveInteractionUIBlock(const UObject* WorldContextObject, UObject* Blocker)
|
||||
{
|
||||
if (!IsValid(WorldContextObject) || !IsValid(WorldContextObject->GetWorld()) || !IsValid(Blocker))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UInteractionSubsystem* IS = WorldContextObject->GetWorld()->GetSubsystem<UInteractionSubsystem>();
|
||||
if (!IS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IS->InteractionUIBlockers.RemoveAll(
|
||||
[Blocker](const TWeakObjectPtr<UObject>& Entry) { return Entry.Get() == Blocker; });
|
||||
|
||||
// Nothing else to do - the next Tick re-evaluates the closest interactable and brings the UI back.
|
||||
}
|
||||
|
||||
bool UInteractionSubsystem::IsInteractionUIBlocked(const UObject* WorldContextObject)
|
||||
{
|
||||
if (!IsValid(WorldContextObject) || !IsValid(WorldContextObject->GetWorld()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UInteractionSubsystem* IS = WorldContextObject->GetWorld()->GetSubsystem<UInteractionSubsystem>();
|
||||
return IS ? IS->HasInteractionUIBlocks() : false;
|
||||
}
|
||||
|
||||
bool UInteractionSubsystem::HasInteractionUIBlocks() const
|
||||
{
|
||||
for (const TWeakObjectPtr<UObject>& Blocker : InteractionUIBlockers)
|
||||
{
|
||||
if (Blocker.IsValid())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void UInteractionSubsystem::PruneInteractionUIBlockers()
|
||||
{
|
||||
InteractionUIBlockers.RemoveAll([](const TWeakObjectPtr<UObject>& Entry) { return !Entry.IsValid(); });
|
||||
}
|
||||
|
||||
void UInteractionSubsystem::HideCurrentInteractableUI()
|
||||
{
|
||||
if (CurrentlyInteractableObject.IsValid())
|
||||
{
|
||||
IInteractableActorInterface::Execute_ToggleInteractableUI(CurrentlyInteractableObject.Get(), false);
|
||||
}
|
||||
|
||||
CurrentlyInteractableObject = nullptr;
|
||||
}
|
||||
|
||||
void UInteractionSubsystem::RescanWorld()
|
||||
{
|
||||
UWorld* World = GetWorld();
|
||||
|
||||
@@ -32,6 +32,22 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category = "Interactable")
|
||||
static void UpdateInteractableText(AActor* Interactable);
|
||||
|
||||
/**
|
||||
* Keeps the interaction UI hidden until the same Blocker calls RemoveInteractionUIBlock.
|
||||
* Only the UI is affected - GetClosestInteractable() and Execute_Interact() keep working normally,
|
||||
* so gameplay interaction is never disabled by this.
|
||||
* Any number of blockers can be registered at the same time; the UI only comes back once the
|
||||
* last one has released it.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Interactable|UI", meta = (WorldContext = "WorldContextObject"))
|
||||
static void AddInteractionUIBlock(const UObject* WorldContextObject, UObject* Blocker);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Interactable|UI", meta = (WorldContext = "WorldContextObject"))
|
||||
static void RemoveInteractionUIBlock(const UObject* WorldContextObject, UObject* Blocker);
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Interactable|UI", meta = (WorldContext = "WorldContextObject"))
|
||||
static bool IsInteractionUIBlocked(const UObject* WorldContextObject);
|
||||
|
||||
protected:
|
||||
|
||||
UFUNCTION()
|
||||
@@ -40,9 +56,26 @@ protected:
|
||||
void RegisterInteractableActor(AActor* InteractableActor);
|
||||
void UnregisterInteractableActor(AActor* InteractableActor);
|
||||
|
||||
/** True while at least one still-alive blocker is registered */
|
||||
bool HasInteractionUIBlocks() const;
|
||||
|
||||
/** Drops blockers that have been destroyed, so a stale entry can never keep the UI hidden forever */
|
||||
void PruneInteractionUIBlockers();
|
||||
|
||||
/** Immediately takes down whatever interaction UI is currently shown */
|
||||
void HideCurrentInteractableUI();
|
||||
|
||||
UPROPERTY()
|
||||
TArray<TWeakObjectPtr<AActor>> InteractableActorList;
|
||||
|
||||
UPROPERTY()
|
||||
TWeakObjectPtr<AActor> CurrentlyInteractableObject;
|
||||
|
||||
/**
|
||||
* Objects currently holding the interaction UI hidden.
|
||||
* Weak on purpose: if a blocker is destroyed without releasing, the entry stops counting by itself.
|
||||
* Deliberately not a UPROPERTY - TWeakObjectPtr needs no GC reflection and this array must never
|
||||
* keep a blocker alive.
|
||||
*/
|
||||
TArray<TWeakObjectPtr<UObject>> InteractionUIBlockers;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user