Interaktionsoptionen und UI-Verbesserungen
Neue Methode `RefreshFocusedOptions()` hinzugefügt, um die Optionen des fokussierten Akteurs nach einer Interaktion zu aktualisieren. Änderungen an der Navigation im Kontextmenü: Begrenzte Navigation ersetzt zyklische Navigation. `FInteractionOption` erweitert um `TextColorOverride` und `HighlightColorOverride`, um individuelle Farben für Optionen zu ermöglichen. UI-Widgets (`InteractionOptionEntryWidget`) angepasst, um Hintergrund- und Hervorhebungsfarben dynamisch zu setzen. Hinweistext für sekundäre Interaktionen zeigt nun spezifische Optionennamen an.
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -164,11 +164,13 @@ void UInteractionComponent::TryEndInteract()
|
||||
if (bContextMenuOpen)
|
||||
{
|
||||
ExecuteOption(HighlightedIndex);
|
||||
RefreshFocusedOptions();
|
||||
CloseContextMenu();
|
||||
}
|
||||
else if (!bHoldConsumed && CurrentOptions.Num() >= 1)
|
||||
{
|
||||
ExecuteOption(0);
|
||||
RefreshFocusedOptions();
|
||||
}
|
||||
|
||||
ClearCachedTarget();
|
||||
@@ -181,8 +183,17 @@ void UInteractionComponent::CycleContextMenuOptions(float Direction)
|
||||
return;
|
||||
}
|
||||
|
||||
const int32 Step = Direction > 0.0f ? 1 : -1;
|
||||
HighlightedIndex = (HighlightedIndex + Step + CurrentOptions.Num()) % CurrentOptions.Num();
|
||||
// Scrolling up (positive Direction) moves the highlight toward the top of the list (index 0),
|
||||
// matching how the mouse wheel scrolls content elsewhere - hence the flipped sign here.
|
||||
const int32 Step = Direction > 0.0f ? -1 : 1;
|
||||
const int32 NewHighlightedIndex = FMath::Clamp(HighlightedIndex + Step, 0, CurrentOptions.Num() - 1);
|
||||
|
||||
if (NewHighlightedIndex == HighlightedIndex)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HighlightedIndex = NewHighlightedIndex;
|
||||
OnContextMenuHighlightChanged.Broadcast(HighlightedIndex);
|
||||
}
|
||||
|
||||
@@ -193,6 +204,7 @@ void UInteractionComponent::HandleHoldThresholdReached()
|
||||
if (CurrentOptions.Num() == 2)
|
||||
{
|
||||
ExecuteOption(1);
|
||||
RefreshFocusedOptions();
|
||||
ClearCachedTarget();
|
||||
}
|
||||
else if (CurrentOptions.Num() >= 3)
|
||||
@@ -225,6 +237,18 @@ void UInteractionComponent::ExecuteOption(int32 Index)
|
||||
IInteractable::Execute_ExecuteInteraction(Target, GetOwner(), CurrentOptions[Index].ActionID);
|
||||
}
|
||||
|
||||
void UInteractionComponent::RefreshFocusedOptions()
|
||||
{
|
||||
AActor* Actor = FocusedActor.Get();
|
||||
if (!Actor)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FocusedOptions = IInteractable::Execute_GetInteractionOptions(Actor, GetOwner());
|
||||
OnFocusedInteractableChanged.Broadcast(Actor, FocusedOptions);
|
||||
}
|
||||
|
||||
void UInteractionComponent::ClearCachedTarget()
|
||||
{
|
||||
CurrentTarget = nullptr;
|
||||
|
||||
@@ -130,6 +130,15 @@ protected:
|
||||
/** Executes CurrentOptions[Index] against CurrentTarget, if both are still valid. */
|
||||
void ExecuteOption(int32 Index);
|
||||
|
||||
/**
|
||||
* Re-pulls GetInteractionOptions() for the still-focused actor and re-broadcasts
|
||||
* OnFocusedInteractableChanged. Call right after ExecuteOption - the interaction just run
|
||||
* may have changed what that same actor now offers (e.g. a chest that's now empty), and
|
||||
* UpdateFocus alone would never notice since it only re-pulls options when the focused
|
||||
* actor's identity changes, not when its offered options do. No-op if nothing is focused.
|
||||
*/
|
||||
void RefreshFocusedOptions();
|
||||
|
||||
void ClearCachedTarget();
|
||||
|
||||
/** Actor currently under the look trace and implementing IInteractable, updated every tick by UpdateFocus. */
|
||||
|
||||
@@ -37,4 +37,22 @@ struct FInteractionOption
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
|
||||
FText PromptOverride;
|
||||
|
||||
/**
|
||||
* Optional per-option override for the Context Menu row's text color (both normal and
|
||||
* highlighted state use this same color, so the indication doesn't disappear on hover) -
|
||||
* e.g. red for an action you currently can't perform, gold for a special one. Overrides
|
||||
* UInteractionOptionEntryWidget's NormalTextColor/HighlightedTextColor for this option
|
||||
* only. Alpha 0 (the default) means "use the entry widget's own colors".
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
|
||||
FLinearColor TextColorOverride = FLinearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
|
||||
/**
|
||||
* Optional per-option override for the Context Menu row's highlight tint - overrides
|
||||
* UInteractionOptionEntryWidget's HighlightColor for this option only. Alpha 0 (the
|
||||
* default) means "use the entry widget's own color".
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
|
||||
FLinearColor HighlightColorOverride = FLinearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
};
|
||||
|
||||
@@ -11,6 +11,17 @@ void UInteractionOptionEntryWidget::NativeConstruct()
|
||||
{
|
||||
Super::NativeConstruct();
|
||||
|
||||
// Capture Background's Designer-authored color before ApplyOptionVisuals ever runs, so
|
||||
// "no override" can fall back to it later instead of an arbitrary hardcoded default.
|
||||
if (UImage* BackgroundImage = Cast<UImage>(Background.Get()))
|
||||
{
|
||||
DefaultBackgroundColor = BackgroundImage->GetColorAndOpacity();
|
||||
}
|
||||
else if (UBorder* BackgroundBorder = Cast<UBorder>(Background.Get()))
|
||||
{
|
||||
DefaultBackgroundColor = BackgroundBorder->GetBrushColor();
|
||||
}
|
||||
|
||||
// The ListView only calls NativeOnItemSelectionChanged when a row's selection state
|
||||
// actually changes, so a freshly-spawned row that has never been (de)selected would
|
||||
// otherwise keep whatever visibility Highlight has in the Designer - force it into
|
||||
@@ -59,26 +70,47 @@ void UInteractionOptionEntryWidget::ApplyOptionVisuals()
|
||||
Icon->SetVisibility(ESlateVisibility::Collapsed);
|
||||
}
|
||||
}
|
||||
|
||||
if (Background)
|
||||
{
|
||||
const FLinearColor ResolvedBackgroundColor = Option.HighlightColorOverride.A > 0.0f ? Option.HighlightColorOverride : DefaultBackgroundColor;
|
||||
|
||||
if (UImage* BackgroundImage = Cast<UImage>(Background.Get()))
|
||||
{
|
||||
BackgroundImage->SetColorAndOpacity(ResolvedBackgroundColor);
|
||||
}
|
||||
else if (UBorder* BackgroundBorder = Cast<UBorder>(Background.Get()))
|
||||
{
|
||||
BackgroundBorder->SetBrushColor(ResolvedBackgroundColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UInteractionOptionEntryWidget::ApplyHighlightVisuals()
|
||||
{
|
||||
// Alpha 0 (the struct default) means the option didn't request an override - fall back
|
||||
// to this widget's own Style colors.
|
||||
const FLinearColor ResolvedHighlightColor = Option.HighlightColorOverride.A > 0.0f ? Option.HighlightColorOverride : HighlightColor;
|
||||
const FLinearColor ResolvedTextColor = Option.TextColorOverride.A > 0.0f
|
||||
? Option.TextColorOverride
|
||||
: (bHighlighted ? HighlightedTextColor : NormalTextColor);
|
||||
|
||||
if (Highlight)
|
||||
{
|
||||
Highlight->SetVisibility(bHighlighted ? ESlateVisibility::HitTestInvisible : ESlateVisibility::Hidden);
|
||||
|
||||
if (UImage* HighlightImage = Cast<UImage>(Highlight.Get()))
|
||||
{
|
||||
HighlightImage->SetColorAndOpacity(HighlightColor);
|
||||
HighlightImage->SetColorAndOpacity(ResolvedHighlightColor);
|
||||
}
|
||||
else if (UBorder* HighlightBorder = Cast<UBorder>(Highlight.Get()))
|
||||
{
|
||||
HighlightBorder->SetBrushColor(HighlightColor);
|
||||
HighlightBorder->SetBrushColor(ResolvedHighlightColor);
|
||||
}
|
||||
}
|
||||
|
||||
if (InteractionText)
|
||||
{
|
||||
InteractionText->SetColorAndOpacity(bHighlighted ? HighlightedTextColor : NormalTextColor);
|
||||
InteractionText->SetColorAndOpacity(ResolvedTextColor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,17 @@ protected:
|
||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||
TObjectPtr<UWidget> Highlight;
|
||||
|
||||
/**
|
||||
* Optional persistent row background - bind a Border or an Image named exactly
|
||||
* "Background" in the Designer. Unlike Highlight, this isn't gated on selection - it's
|
||||
* tinted with the option's HighlightColorOverride (from FInteractionOption) whenever one
|
||||
* is set, so a "can't do this" or "special" row stays visually marked regardless of
|
||||
* whether it's currently highlighted. Left at its Designer-authored appearance when the
|
||||
* option has no override.
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||
TObjectPtr<UWidget> Background;
|
||||
|
||||
/** Tint applied to Highlight while this row is highlighted. */
|
||||
UPROPERTY(EditAnywhere, Category = "Interaction|Style")
|
||||
FLinearColor HighlightColor = FLinearColor(1.0f, 0.85f, 0.3f, 1.0f);
|
||||
@@ -78,4 +89,15 @@ protected:
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
bool bHighlighted = false;
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* Background's Designer-authored color, captured once in NativeConstruct before any
|
||||
* option is ever assigned. Rows are recycled by the ListView, so a row previously tinted
|
||||
* by one option's HighlightColorOverride needs an explicit color to fall back to when
|
||||
* reassigned to a different, unconfigured option - rather than just leaving whatever
|
||||
* tint the recycled widget last had.
|
||||
*/
|
||||
FLinearColor DefaultBackgroundColor = FLinearColor::White;
|
||||
};
|
||||
|
||||
@@ -154,7 +154,7 @@ void UInteractionPromptWidget::UpdatePromptVisibility()
|
||||
FString Hint = TEXT("Press E");
|
||||
if (LastFocusedOptions.Num() == 2)
|
||||
{
|
||||
Hint += TEXT("\nHold E for secondary interaction");
|
||||
Hint += FString::Printf(TEXT("\nHold E for %s"), *LastFocusedOptions[1].DisplayName.ToString());
|
||||
}
|
||||
else if (LastFocusedOptions.Num() >= 3)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user