Files
Naked-Desire/Source/NakedDesire/UI/RadialMenu/RadialMenuController.h
T

132 lines
5.2 KiB
C++

// RadialMenuController.h
// Local-only radial weapon selector for the Naked Desire project.
// No replication, no RPCs: this is single-player UI logic.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "RadialMenuController.generated.h"
class URadialMenuWidget;
/**
* One entry in the radial menu. Swap this out for your real item type later
* (e.g. a soft pointer to a UPrimaryDataAsset) — the controller only ever
* reads DisplayName + Icon to build slices, so the rest of the menu is agnostic.
*/
USTRUCT(BlueprintType)
struct FRadialMenuEntry
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Radial Menu")
FText DisplayName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Radial Menu")
TObjectPtr<UTexture2D> Icon = nullptr;
// Optional payload tag so the owning code knows what to equip on confirm.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Radial Menu")
FName ItemId = NAME_None;
// Set false to draw the slice greyed-out (e.g. weapon not yet unlocked).
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Radial Menu")
bool bEnabled = true;
};
// Fires when the player confirms a slice. Index is into the Entries array; -1 = cancelled.
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRadialSelectionConfirmed, int32, SelectedIndex);
UCLASS(ClassGroup = (UI), meta = (BlueprintSpawnableComponent))
class URadialMenuController : public UActorComponent
{
GENERATED_BODY()
public:
URadialMenuController();
// --- Public API ---------------------------------------------------------
// Call on "hold" pressed. Opens the menu, dilates time, shows the cursor.
UFUNCTION(BlueprintCallable, Category = "Radial Menu")
void OpenMenu();
// Call on "hold" released. Confirms the hovered slice and closes.
UFUNCTION(BlueprintCallable, Category = "Radial Menu")
void CloseAndConfirm();
// Close without selecting anything (e.g. pressed a cancel key).
UFUNCTION(BlueprintCallable, Category = "Radial Menu")
void CloseAndCancel();
UFUNCTION(BlueprintPure, Category = "Radial Menu")
bool IsOpen() const { return bIsOpen; }
UFUNCTION(BlueprintPure, Category = "Radial Menu")
int32 GetHoveredIndex() const { return HoveredIndex; }
// Update the hovered slice from a direction vector (X right, Y up), measured
// in the same frame as the menu. Used by both the mouse path (controller
// tick) and the gamepad path (widget forwards the right-stick vector here,
// since CommonUI consumes the stick before the controller can read it).
// Magnitude below the dead zone leaves the current hover unchanged.
void UpdateHoverFromDirection(float DirX, float DirY, float Magnitude);
// The data the menu draws. Populate from wherever you like (data table,
// inventory component, hardcoded list) before calling OpenMenu().
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Radial Menu")
TArray<FRadialMenuEntry> Entries;
UPROPERTY(BlueprintAssignable, Category = "Radial Menu")
FOnRadialSelectionConfirmed OnSelectionConfirmed;
// --- Tunables -----------------------------------------------------------
// How long after opening (real seconds) confirms are ignored, so the tap
// that opens the wheel can't immediately select. ~0.15s is imperceptible
// but covers the same-frame / next-frame case. Set 0 to disable.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Radial Menu|Tuning",
meta = (ClampMin = "0.0"))
float OpenInputGuardSeconds = 0.15f;
// Time scale while the menu is open. GTA sits around 0.2; 0.0 = full pause.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Radial Menu|Tuning",
meta = (ClampMin = "0.0", ClampMax = "1.0"))
float OpenTimeDilation = 0.2f;
// Dead zone (gamepad) / radius in px (mouse) below which we keep the last
// hovered slice instead of snapping to a new one. Stops jitter near center.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Radial Menu|Tuning")
float SelectionDeadZone = 0.25f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Radial Menu|Tuning")
TSubclassOf<URadialMenuWidget> WidgetClass;
virtual void TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction) override;
private:
// Converts a screen-space or stick direction into a slice index.
// Angle is measured clockwise from straight up (12 o'clock), matching GTA.
int32 AngleToSegment(float AngleDegrees) const;
// Reads current input (mouse delta or right stick) and updates HoveredIndex.
void UpdateHoverFromInput();
void SetTimeDilation(float Scale);
UPROPERTY(Transient)
TObjectPtr<URadialMenuWidget> ActiveWidget = nullptr;
bool bIsOpen = false;
int32 HoveredIndex = -1;
// Real-time timestamp when the menu opened. CloseAndConfirm ignores confirms
// that arrive within OpenInputGuardSeconds, so the tap that opens the wheel
// can't also register as a selection on the same/next frame.
double OpenRealTimeSeconds = 0.0;
// Cached so we can restore exactly what the game had before we opened.
float CachedTimeDilation = 1.0f;
};