Planting interaction blocker implemented, ui showing planting mode

This commit is contained in:
2026-09-14 09:39:31 +02:00
parent f9384cc3a0
commit ca6f0debe9
10 changed files with 161 additions and 57 deletions

View File

@@ -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();

View File

@@ -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;
};