added main and pause menus
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
|
||||
#include "ConfirmModalWidget.h"
|
||||
|
||||
#include "Components/Button.h"
|
||||
#include "Components/TextBlock.h"
|
||||
|
||||
void UConfirmModalWidget::Setup(const FText& InTitle, const FText& InMessage)
|
||||
{
|
||||
PendingTitle = InTitle;
|
||||
PendingMessage = InMessage;
|
||||
|
||||
if (TitleText)
|
||||
TitleText->SetText(PendingTitle);
|
||||
if (MessageText)
|
||||
MessageText->SetText(PendingMessage);
|
||||
}
|
||||
|
||||
void UConfirmModalWidget::NativeOnActivated()
|
||||
{
|
||||
Super::NativeOnActivated();
|
||||
|
||||
if (TitleText)
|
||||
TitleText->SetText(PendingTitle);
|
||||
if (MessageText)
|
||||
MessageText->SetText(PendingMessage);
|
||||
|
||||
ConfirmButton->OnClicked.AddUniqueDynamic(this, &UConfirmModalWidget::HandleConfirmClicked);
|
||||
CancelButton->OnClicked.AddUniqueDynamic(this, &UConfirmModalWidget::HandleCancelClicked);
|
||||
}
|
||||
|
||||
void UConfirmModalWidget::HandleConfirmClicked()
|
||||
{
|
||||
OnConfirmed.Broadcast();
|
||||
DeactivateWidget();
|
||||
}
|
||||
|
||||
void UConfirmModalWidget::HandleCancelClicked()
|
||||
{
|
||||
OnCancelled.Broadcast();
|
||||
DeactivateWidget();
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "CommonActivatableWidget.h"
|
||||
#include "ConfirmModalWidget.generated.h"
|
||||
|
||||
class UButton;
|
||||
class UTextBlock;
|
||||
|
||||
// Reusable yes/no confirmation popup. Pushed onto a stack by the menu that owns it;
|
||||
// the opener calls Setup() for the copy and binds OnConfirmed / OnCancelled to react.
|
||||
UCLASS(Abstract)
|
||||
class NAKEDDESIRE_API UConfirmModalWidget : public UCommonActivatableWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// Sets the popup copy. Safe to call before the widget tree exists (cached and applied on activate).
|
||||
void Setup(const FText& InTitle, const FText& InMessage);
|
||||
|
||||
// C++-only result events — the opener binds whichever it cares about.
|
||||
DECLARE_MULTICAST_DELEGATE(FOnConfirmModalResult);
|
||||
FOnConfirmModalResult OnConfirmed;
|
||||
FOnConfirmModalResult OnCancelled;
|
||||
|
||||
protected:
|
||||
virtual void NativeOnActivated() override;
|
||||
|
||||
private:
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UButton> ConfirmButton;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UButton> CancelButton;
|
||||
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UTextBlock> TitleText;
|
||||
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UTextBlock> MessageText;
|
||||
|
||||
FText PendingTitle;
|
||||
FText PendingMessage;
|
||||
|
||||
UFUNCTION()
|
||||
void HandleConfirmClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleCancelClicked();
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
|
||||
#include "CreditsWidget.h"
|
||||
|
||||
#include "Components/Button.h"
|
||||
|
||||
void UCreditsWidget::NativeOnActivated()
|
||||
{
|
||||
Super::NativeOnActivated();
|
||||
|
||||
BackButton->OnClicked.AddUniqueDynamic(this, &UCreditsWidget::HandleBackClicked);
|
||||
}
|
||||
|
||||
void UCreditsWidget::HandleBackClicked()
|
||||
{
|
||||
DeactivateWidget();
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "CommonActivatableWidget.h"
|
||||
#include "CreditsWidget.generated.h"
|
||||
|
||||
class UButton;
|
||||
|
||||
// Credits screen. Pushed onto a stack by the menu; the Back button closes it.
|
||||
UCLASS(Abstract)
|
||||
class NAKEDDESIRE_API UCreditsWidget : public UCommonActivatableWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
protected:
|
||||
virtual void NativeOnActivated() override;
|
||||
|
||||
private:
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UButton> BackButton;
|
||||
|
||||
UFUNCTION()
|
||||
void HandleBackClicked();
|
||||
};
|
||||
@@ -0,0 +1,126 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
|
||||
#include "MainMenuWidget.h"
|
||||
|
||||
#include "ConfirmModalWidget.h"
|
||||
#include "Components/Button.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "Kismet/KismetSystemLibrary.h"
|
||||
#include "NakedDesire/SaveGame/SaveSubsystem.h"
|
||||
#include "Widgets/CommonActivatableWidgetContainer.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "MainMenu"
|
||||
|
||||
UMainMenuWidget::UMainMenuWidget()
|
||||
{
|
||||
// Sensible default for the slice; override per-asset on WBP_MainMenu.
|
||||
GameplayMap = TSoftObjectPtr<UWorld>(FSoftObjectPath(TEXT("/Game/Test/Maps/L_TestCity.L_TestCity")));
|
||||
}
|
||||
|
||||
void UMainMenuWidget::NativeOnActivated()
|
||||
{
|
||||
Super::NativeOnActivated();
|
||||
|
||||
// Continue is only available when a save exists in the active slot.
|
||||
bool bHasSave = false;
|
||||
if (const UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this))
|
||||
{
|
||||
if (const USaveSubsystem* SaveSubsystem = GameInstance->GetSubsystem<USaveSubsystem>())
|
||||
bHasSave = SaveSubsystem->DoesSaveExist();
|
||||
}
|
||||
ContinueButton->SetIsEnabled(bHasSave);
|
||||
|
||||
ContinueButton->OnClicked.AddUniqueDynamic(this, &UMainMenuWidget::OnContinueClicked);
|
||||
NewGameButton->OnClicked.AddUniqueDynamic(this, &UMainMenuWidget::OnNewGameClicked);
|
||||
SettingsButton->OnClicked.AddUniqueDynamic(this, &UMainMenuWidget::OnSettingsClicked);
|
||||
CreditsButton->OnClicked.AddUniqueDynamic(this, &UMainMenuWidget::OnCreditsClicked);
|
||||
QuitButton->OnClicked.AddUniqueDynamic(this, &UMainMenuWidget::OnQuitClicked);
|
||||
}
|
||||
|
||||
void UMainMenuWidget::OnContinueClicked()
|
||||
{
|
||||
OpenGameplayLevel();
|
||||
}
|
||||
|
||||
void UMainMenuWidget::OnNewGameClicked()
|
||||
{
|
||||
if (!ConfirmModalClass || !ModalStack)
|
||||
{
|
||||
HandleNewGameConfirmed();
|
||||
return;
|
||||
}
|
||||
|
||||
UConfirmModalWidget* Modal = ModalStack->AddWidget<UConfirmModalWidget>(ConfirmModalClass);
|
||||
if (!Modal)
|
||||
return;
|
||||
|
||||
Modal->Setup(
|
||||
LOCTEXT("NewGameTitle", "New Game"),
|
||||
LOCTEXT("NewGameBody", "Start a new game? Any existing progress will be lost."));
|
||||
Modal->OnConfirmed.AddUObject(this, &UMainMenuWidget::HandleNewGameConfirmed);
|
||||
}
|
||||
|
||||
void UMainMenuWidget::OnSettingsClicked()
|
||||
{
|
||||
if (SettingsWidgetClass && ModalStack)
|
||||
ModalStack->AddWidget(SettingsWidgetClass);
|
||||
}
|
||||
|
||||
void UMainMenuWidget::OnCreditsClicked()
|
||||
{
|
||||
if (CreditsWidgetClass && ModalStack)
|
||||
ModalStack->AddWidget(CreditsWidgetClass);
|
||||
}
|
||||
|
||||
void UMainMenuWidget::OnQuitClicked()
|
||||
{
|
||||
if (!ConfirmModalClass || !ModalStack)
|
||||
{
|
||||
HandleQuitConfirmed();
|
||||
return;
|
||||
}
|
||||
|
||||
UConfirmModalWidget* Modal = ModalStack->AddWidget<UConfirmModalWidget>(ConfirmModalClass);
|
||||
if (!Modal)
|
||||
return;
|
||||
|
||||
Modal->Setup(
|
||||
LOCTEXT("QuitTitle", "Quit Game"),
|
||||
LOCTEXT("QuitBody", "Quit to desktop?"));
|
||||
Modal->OnConfirmed.AddUObject(this, &UMainMenuWidget::HandleQuitConfirmed);
|
||||
}
|
||||
|
||||
void UMainMenuWidget::HandleNewGameConfirmed()
|
||||
{
|
||||
if (UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this))
|
||||
{
|
||||
if (USaveSubsystem* SaveSubsystem = GameInstance->GetSubsystem<USaveSubsystem>())
|
||||
SaveSubsystem->StartNewGame();
|
||||
}
|
||||
|
||||
OpenGameplayLevel();
|
||||
}
|
||||
|
||||
void UMainMenuWidget::HandleQuitConfirmed()
|
||||
{
|
||||
UKismetSystemLibrary::QuitGame(this, GetOwningPlayer(), EQuitPreference::Quit, false);
|
||||
}
|
||||
|
||||
void UMainMenuWidget::OpenGameplayLevel()
|
||||
{
|
||||
if (GameplayMap.IsNull())
|
||||
return;
|
||||
|
||||
if (APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0))
|
||||
{
|
||||
PC->SetShowMouseCursor(false);
|
||||
|
||||
const FInputModeGameOnly InputMode;
|
||||
PC->SetInputMode(InputMode);
|
||||
}
|
||||
|
||||
UGameplayStatics::OpenLevelBySoftObjectPtr(this, GameplayMap);
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,80 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "CommonActivatableWidget.h"
|
||||
#include "MainMenuWidget.generated.h"
|
||||
|
||||
class UButton;
|
||||
class UCommonActivatableWidgetStack;
|
||||
class UConfirmModalWidget;
|
||||
|
||||
// Front-end main menu. Owns the Continue / New Game / Settings / Credits / Quit flow.
|
||||
// Confirmation popups, credits and settings are pushed onto ModalStack (a stack in the
|
||||
// widget tree). The gameplay / menu maps are data-driven via the soft map references.
|
||||
UCLASS(Abstract)
|
||||
class NAKEDDESIRE_API UMainMenuWidget : public UCommonActivatableWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UMainMenuWidget();
|
||||
|
||||
protected:
|
||||
virtual void NativeOnActivated() override;
|
||||
|
||||
private:
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UButton> ContinueButton;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UButton> NewGameButton;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UButton> SettingsButton;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UButton> CreditsButton;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UButton> QuitButton;
|
||||
|
||||
// Stack the confirm popup / credits / settings screens are pushed onto.
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UCommonActivatableWidgetStack> ModalStack;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, Category = "UI")
|
||||
TSubclassOf<UConfirmModalWidget> ConfirmModalClass;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, Category = "UI")
|
||||
TSubclassOf<UCommonActivatableWidget> CreditsWidgetClass;
|
||||
|
||||
// Optional — wired once the settings screen exists. Settings button no-ops until then.
|
||||
UPROPERTY(EditDefaultsOnly, Category = "UI")
|
||||
TSubclassOf<UCommonActivatableWidget> SettingsWidgetClass;
|
||||
|
||||
// Level "New Game" / "Continue" load into (the §0.2 slice district).
|
||||
UPROPERTY(EditDefaultsOnly, Category = "UI", meta = (AllowedClasses = "/Script/Engine.World"))
|
||||
TSoftObjectPtr<UWorld> GameplayMap;
|
||||
|
||||
UFUNCTION()
|
||||
void OnContinueClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void OnNewGameClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void OnSettingsClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void OnCreditsClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void OnQuitClicked();
|
||||
|
||||
void HandleNewGameConfirmed();
|
||||
void HandleQuitConfirmed();
|
||||
|
||||
void OpenGameplayLevel();
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
|
||||
#include "PauseMenuWidget.h"
|
||||
|
||||
#include "ConfirmModalWidget.h"
|
||||
#include "Components/Button.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "Widgets/CommonActivatableWidgetContainer.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "PauseMenu"
|
||||
|
||||
UPauseMenuWidget::UPauseMenuWidget()
|
||||
{
|
||||
// Default to the front-end map; override per-asset on WBP_PauseMenu.
|
||||
MainMenuMap = TSoftObjectPtr<UWorld>(FSoftObjectPath(TEXT("/Game/Maps/MainMenu.MainMenu")));
|
||||
}
|
||||
|
||||
void UPauseMenuWidget::NativeOnActivated()
|
||||
{
|
||||
Super::NativeOnActivated();
|
||||
|
||||
GetWorld()->GetTimerManager().SetTimer(PauseTimerHandle, this, &UPauseMenuWidget::PauseGame, 0.1f);
|
||||
|
||||
ResumeButton->OnClicked.AddUniqueDynamic(this, &UPauseMenuWidget::OnResumeClicked);
|
||||
SettingsButton->OnClicked.AddUniqueDynamic(this, &UPauseMenuWidget::OnSettingsClicked);
|
||||
MainMenuButton->OnClicked.AddUniqueDynamic(this, &UPauseMenuWidget::OnMainMenuClicked);
|
||||
}
|
||||
|
||||
void UPauseMenuWidget::NativeOnDeactivated()
|
||||
{
|
||||
Super::NativeOnDeactivated();
|
||||
|
||||
// Unpause whenever the menu closes — Resume, back action, or being popped.
|
||||
if (UGameplayStatics::IsGamePaused(this))
|
||||
UGameplayStatics::SetGamePaused(this, false);
|
||||
}
|
||||
|
||||
void UPauseMenuWidget::OnResumeClicked()
|
||||
{
|
||||
DeactivateWidget();
|
||||
}
|
||||
|
||||
void UPauseMenuWidget::OnSettingsClicked()
|
||||
{
|
||||
if (SettingsWidgetClass && ModalStack)
|
||||
ModalStack->AddWidget(SettingsWidgetClass);
|
||||
}
|
||||
|
||||
void UPauseMenuWidget::OnMainMenuClicked()
|
||||
{
|
||||
if (!ConfirmModalClass || !ModalStack)
|
||||
{
|
||||
HandleMainMenuConfirmed();
|
||||
return;
|
||||
}
|
||||
|
||||
UConfirmModalWidget* Modal = ModalStack->AddWidget<UConfirmModalWidget>(ConfirmModalClass);
|
||||
if (!Modal)
|
||||
return;
|
||||
|
||||
Modal->Setup(
|
||||
LOCTEXT("MainMenuTitle", "Main Menu"),
|
||||
LOCTEXT("MainMenuBody", "Return to the main menu? Unsaved progress may be lost."));
|
||||
Modal->OnConfirmed.AddUObject(this, &UPauseMenuWidget::HandleMainMenuConfirmed);
|
||||
}
|
||||
|
||||
void UPauseMenuWidget::HandleMainMenuConfirmed()
|
||||
{
|
||||
// Unpause before travel — level transitions while paused are unreliable.
|
||||
UGameplayStatics::SetGamePaused(this, false);
|
||||
|
||||
if (MainMenuMap.IsNull())
|
||||
return;
|
||||
|
||||
UGameplayStatics::OpenLevelBySoftObjectPtr(this, MainMenuMap);
|
||||
}
|
||||
|
||||
void UPauseMenuWidget::PauseGame()
|
||||
{
|
||||
UGameplayStatics::SetGamePaused(this, true);
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,64 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "CommonActivatableWidget.h"
|
||||
#include "PauseMenuWidget.generated.h"
|
||||
|
||||
class UButton;
|
||||
class UCommonActivatableWidgetStack;
|
||||
class UConfirmModalWidget;
|
||||
|
||||
// In-game pause menu. Pauses the game while active and offers Resume / Settings / Main Menu.
|
||||
// The Main Menu action confirms first. Confirm popup / settings push onto ModalStack.
|
||||
UCLASS(Abstract)
|
||||
class NAKEDDESIRE_API UPauseMenuWidget : public UCommonActivatableWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPauseMenuWidget();
|
||||
|
||||
protected:
|
||||
virtual void NativeOnActivated() override;
|
||||
virtual void NativeOnDeactivated() override;
|
||||
|
||||
private:
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UButton> ResumeButton;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UButton> SettingsButton;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UButton> MainMenuButton;
|
||||
|
||||
// Stack the confirm popup / settings screen are pushed onto (nested so this menu stays active).
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UCommonActivatableWidgetStack> ModalStack;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, Category = "UI")
|
||||
TSubclassOf<UConfirmModalWidget> ConfirmModalClass;
|
||||
|
||||
// Optional — wired once the settings screen exists. Settings button no-ops until then.
|
||||
UPROPERTY(EditDefaultsOnly, Category = "UI")
|
||||
TSubclassOf<UCommonActivatableWidget> SettingsWidgetClass;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, Category = "UI", meta = (AllowedClasses = "/Script/Engine.World"))
|
||||
TSoftObjectPtr<UWorld> MainMenuMap;
|
||||
|
||||
FTimerHandle PauseTimerHandle;
|
||||
|
||||
UFUNCTION()
|
||||
void OnResumeClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void OnSettingsClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void OnMainMenuClicked();
|
||||
|
||||
void HandleMainMenuConfirmed();
|
||||
void PauseGame();
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
|
||||
#include "AudioSettingsTab.h"
|
||||
|
||||
#include "Components/Slider.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "NakedDesire/Global/AudioSettingsSubsystem.h"
|
||||
#include "NakedDesire/Global/NakedDesireUserSettings.h"
|
||||
|
||||
void UAudioSettingsTab::NativeConstruct()
|
||||
{
|
||||
Super::NativeConstruct();
|
||||
|
||||
MasterSlider->OnValueChanged.AddUniqueDynamic(this, &UAudioSettingsTab::OnMasterChanged);
|
||||
MusicSlider->OnValueChanged.AddUniqueDynamic(this, &UAudioSettingsTab::OnMusicChanged);
|
||||
SfxSlider->OnValueChanged.AddUniqueDynamic(this, &UAudioSettingsTab::OnSfxChanged);
|
||||
|
||||
RefreshFromSettings();
|
||||
}
|
||||
|
||||
void UAudioSettingsTab::RefreshFromSettings()
|
||||
{
|
||||
const UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings();
|
||||
if (!Settings)
|
||||
return;
|
||||
|
||||
MasterSlider->SetValue(Settings->GetGlobalVolume());
|
||||
MusicSlider->SetValue(Settings->GetMusicVolume());
|
||||
SfxSlider->SetValue(Settings->GetSfxVolume());
|
||||
}
|
||||
|
||||
void UAudioSettingsTab::OnMasterChanged(float Value)
|
||||
{
|
||||
if (UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings())
|
||||
Settings->SetGlobalVolume(Value);
|
||||
ApplyLive();
|
||||
}
|
||||
|
||||
void UAudioSettingsTab::OnMusicChanged(float Value)
|
||||
{
|
||||
if (UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings())
|
||||
Settings->SetMusicVolume(Value);
|
||||
ApplyLive();
|
||||
}
|
||||
|
||||
void UAudioSettingsTab::OnSfxChanged(float Value)
|
||||
{
|
||||
if (UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings())
|
||||
Settings->SetSfxVolume(Value);
|
||||
ApplyLive();
|
||||
}
|
||||
|
||||
void UAudioSettingsTab::ApplyLive()
|
||||
{
|
||||
if (const UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this))
|
||||
{
|
||||
if (UAudioSettingsSubsystem* Audio = GameInstance->GetSubsystem<UAudioSettingsSubsystem>())
|
||||
Audio->ApplyVolumes();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "CommonUserWidget.h"
|
||||
#include "AudioSettingsTab.generated.h"
|
||||
|
||||
class USlider;
|
||||
|
||||
// Audio settings tab. Master / Music / SFX volume sliders. Changes apply live through
|
||||
// UAudioSettingsSubsystem (routed via the configured SoundMix) and persist on close.
|
||||
UCLASS(Abstract)
|
||||
class NAKEDDESIRE_API UAudioSettingsTab : public UCommonUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
void RefreshFromSettings();
|
||||
|
||||
protected:
|
||||
virtual void NativeConstruct() override;
|
||||
|
||||
private:
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<USlider> MasterSlider;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<USlider> MusicSlider;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<USlider> SfxSlider;
|
||||
|
||||
UFUNCTION()
|
||||
void OnMasterChanged(float Value);
|
||||
|
||||
UFUNCTION()
|
||||
void OnMusicChanged(float Value);
|
||||
|
||||
UFUNCTION()
|
||||
void OnSfxChanged(float Value);
|
||||
|
||||
void ApplyLive();
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
|
||||
#include "GameplaySettingsTab.h"
|
||||
|
||||
#include "Components/CheckBox.h"
|
||||
#include "Components/ComboBoxString.h"
|
||||
#include "Kismet/KismetInternationalizationLibrary.h"
|
||||
#include "NakedDesire/Global/NakedDesireUserSettings.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
// Index-aligned: combo display name → UE culture code. Add a row here when a new
|
||||
// translation ships, and make sure the culture is in DefaultGame.ini CulturesToStage.
|
||||
struct FLanguageOption
|
||||
{
|
||||
const TCHAR* DisplayName;
|
||||
const TCHAR* CultureCode;
|
||||
};
|
||||
|
||||
constexpr FLanguageOption LanguageOptions[] = {
|
||||
{ TEXT("English"), TEXT("en") },
|
||||
{ TEXT("Українська"), TEXT("uk") },
|
||||
{ TEXT("日本語"), TEXT("ja") }
|
||||
};
|
||||
}
|
||||
|
||||
void UGameplaySettingsTab::NativeConstruct()
|
||||
{
|
||||
Super::NativeConstruct();
|
||||
|
||||
CensorshipCheckBox->OnCheckStateChanged.AddUniqueDynamic(this, &UGameplaySettingsTab::OnCensorshipChanged);
|
||||
LanguageCombo->OnSelectionChanged.AddUniqueDynamic(this, &UGameplaySettingsTab::OnLanguageChanged);
|
||||
|
||||
RefreshFromSettings();
|
||||
}
|
||||
|
||||
void UGameplaySettingsTab::RefreshFromSettings()
|
||||
{
|
||||
const UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings();
|
||||
if (!Settings)
|
||||
return;
|
||||
|
||||
CensorshipCheckBox->SetIsChecked(Settings->GetIsCensorshipEnabled());
|
||||
|
||||
// Language: match the active culture (e.g. "uk-UA") against our supported codes by prefix.
|
||||
LanguageCombo->ClearOptions();
|
||||
const FString CurrentCulture = UKismetInternationalizationLibrary::GetCurrentLanguage();
|
||||
int32 SelectedIndex = 0;
|
||||
for (int32 i = 0; i < static_cast<int32>(UE_ARRAY_COUNT(LanguageOptions)); ++i)
|
||||
{
|
||||
LanguageCombo->AddOption(LanguageOptions[i].DisplayName);
|
||||
if (CurrentCulture.StartsWith(LanguageOptions[i].CultureCode))
|
||||
SelectedIndex = i;
|
||||
}
|
||||
LanguageCombo->SetSelectedIndex(SelectedIndex);
|
||||
}
|
||||
|
||||
void UGameplaySettingsTab::OnCensorshipChanged(bool bIsChecked)
|
||||
{
|
||||
UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings();
|
||||
if (!Settings)
|
||||
return;
|
||||
|
||||
Settings->SetIsCensorshipEnabled(bIsChecked);
|
||||
// Broadcasts OnSettingsChanged so the censorship meshes update immediately.
|
||||
Settings->ApplyNonResolutionSettings();
|
||||
}
|
||||
|
||||
void UGameplaySettingsTab::OnLanguageChanged(FString SelectedItem, ESelectInfo::Type SelectionType)
|
||||
{
|
||||
// Skip the programmatic SetSelectedIndex during RefreshFromSettings.
|
||||
if (SelectionType == ESelectInfo::Direct)
|
||||
return;
|
||||
|
||||
const int32 Index = LanguageCombo->GetSelectedIndex();
|
||||
if (Index < 0 || Index >= static_cast<int32>(UE_ARRAY_COUNT(LanguageOptions)))
|
||||
return;
|
||||
|
||||
// Applies immediately (all LOCTEXT re-evaluates) and persists to the game config.
|
||||
UKismetInternationalizationLibrary::SetCurrentLanguageAndLocale(LanguageOptions[Index].CultureCode, /*SaveToConfig*/ true);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "CommonUserWidget.h"
|
||||
#include "GameplaySettingsTab.generated.h"
|
||||
|
||||
class UCheckBox;
|
||||
class UComboBoxString;
|
||||
|
||||
// Gameplay settings tab. Censorship toggle (§ compliance) — applies live through
|
||||
// UNakedDesireUserSettings::OnSettingsChanged, which UCensorshipComponent listens to —
|
||||
// plus the UI/text language selector (English / Ukrainian / Japanese).
|
||||
UCLASS(Abstract)
|
||||
class NAKEDDESIRE_API UGameplaySettingsTab : public UCommonUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// Re-reads the current values into the controls. Called by the settings screen on show.
|
||||
void RefreshFromSettings();
|
||||
|
||||
protected:
|
||||
virtual void NativeConstruct() override;
|
||||
|
||||
private:
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UCheckBox> CensorshipCheckBox;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UComboBoxString> LanguageCombo;
|
||||
|
||||
UFUNCTION()
|
||||
void OnCensorshipChanged(bool bIsChecked);
|
||||
|
||||
UFUNCTION()
|
||||
void OnLanguageChanged(FString SelectedItem, ESelectInfo::Type SelectionType);
|
||||
};
|
||||
@@ -0,0 +1,137 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
|
||||
#include "GraphicsSettingsTab.h"
|
||||
|
||||
#include "Components/CheckBox.h"
|
||||
#include "Components/ComboBoxString.h"
|
||||
#include "Kismet/KismetSystemLibrary.h"
|
||||
#include "NakedDesire/Global/NakedDesireUserSettings.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
// Combo order → EWindowMode. Index-aligned with the WindowModeCombo entries below.
|
||||
constexpr EWindowMode::Type WindowModes[] = {
|
||||
EWindowMode::Fullscreen,
|
||||
EWindowMode::WindowedFullscreen,
|
||||
EWindowMode::Windowed
|
||||
};
|
||||
|
||||
FString ResolutionToText(const FIntPoint& Resolution)
|
||||
{
|
||||
return FString::Printf(TEXT("%d x %d"), Resolution.X, Resolution.Y);
|
||||
}
|
||||
}
|
||||
|
||||
void UGraphicsSettingsTab::NativeConstruct()
|
||||
{
|
||||
Super::NativeConstruct();
|
||||
|
||||
QualityCombo->OnSelectionChanged.AddUniqueDynamic(this, &UGraphicsSettingsTab::OnQualityChanged);
|
||||
WindowModeCombo->OnSelectionChanged.AddUniqueDynamic(this, &UGraphicsSettingsTab::OnWindowModeChanged);
|
||||
ResolutionCombo->OnSelectionChanged.AddUniqueDynamic(this, &UGraphicsSettingsTab::OnResolutionChanged);
|
||||
VSyncCheckBox->OnCheckStateChanged.AddUniqueDynamic(this, &UGraphicsSettingsTab::OnVSyncChanged);
|
||||
|
||||
RefreshFromSettings();
|
||||
}
|
||||
|
||||
void UGraphicsSettingsTab::RefreshFromSettings()
|
||||
{
|
||||
const UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings();
|
||||
if (!Settings)
|
||||
return;
|
||||
|
||||
// Quality presets (Scalability levels 0-3).
|
||||
QualityCombo->ClearOptions();
|
||||
QualityCombo->AddOption(TEXT("Low"));
|
||||
QualityCombo->AddOption(TEXT("Medium"));
|
||||
QualityCombo->AddOption(TEXT("High"));
|
||||
QualityCombo->AddOption(TEXT("Epic"));
|
||||
const int32 Quality = FMath::Clamp(Settings->GetOverallScalabilityLevel(), 0, 3);
|
||||
QualityCombo->SetSelectedIndex(Quality);
|
||||
|
||||
// Window mode.
|
||||
WindowModeCombo->ClearOptions();
|
||||
WindowModeCombo->AddOption(TEXT("Fullscreen"));
|
||||
WindowModeCombo->AddOption(TEXT("Borderless Windowed"));
|
||||
WindowModeCombo->AddOption(TEXT("Windowed"));
|
||||
const EWindowMode::Type CurrentMode = Settings->GetFullscreenMode();
|
||||
int32 ModeIndex = 0;
|
||||
for (int32 i = 0; i < UE_ARRAY_COUNT(WindowModes); ++i)
|
||||
{
|
||||
if (WindowModes[i] == CurrentMode)
|
||||
{
|
||||
ModeIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
WindowModeCombo->SetSelectedIndex(ModeIndex);
|
||||
|
||||
// Resolutions.
|
||||
ResolutionCombo->ClearOptions();
|
||||
AvailableResolutions.Reset();
|
||||
UKismetSystemLibrary::GetSupportedFullscreenResolutions(AvailableResolutions);
|
||||
const FIntPoint CurrentResolution = Settings->GetScreenResolution();
|
||||
int32 ResolutionIndex = INDEX_NONE;
|
||||
for (int32 i = 0; i < AvailableResolutions.Num(); ++i)
|
||||
{
|
||||
ResolutionCombo->AddOption(ResolutionToText(AvailableResolutions[i]));
|
||||
if (AvailableResolutions[i] == CurrentResolution)
|
||||
ResolutionIndex = i;
|
||||
}
|
||||
// Current resolution may not be in the supported list (e.g. windowed) — surface it anyway.
|
||||
if (ResolutionIndex == INDEX_NONE)
|
||||
{
|
||||
ResolutionCombo->AddOption(ResolutionToText(CurrentResolution));
|
||||
AvailableResolutions.Add(CurrentResolution);
|
||||
ResolutionIndex = AvailableResolutions.Num() - 1;
|
||||
}
|
||||
ResolutionCombo->SetSelectedIndex(ResolutionIndex);
|
||||
|
||||
VSyncCheckBox->SetIsChecked(Settings->IsVSyncEnabled());
|
||||
}
|
||||
|
||||
void UGraphicsSettingsTab::OnQualityChanged(FString SelectedItem, ESelectInfo::Type SelectionType)
|
||||
{
|
||||
if (SelectionType == ESelectInfo::Direct)
|
||||
return;
|
||||
|
||||
UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings();
|
||||
if (!Settings)
|
||||
return;
|
||||
|
||||
Settings->SetOverallScalabilityLevel(QualityCombo->GetSelectedIndex());
|
||||
}
|
||||
|
||||
void UGraphicsSettingsTab::OnWindowModeChanged(FString SelectedItem, ESelectInfo::Type SelectionType)
|
||||
{
|
||||
if (SelectionType == ESelectInfo::Direct)
|
||||
return;
|
||||
|
||||
UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings();
|
||||
if (!Settings)
|
||||
return;
|
||||
|
||||
const int32 Index = FMath::Clamp(WindowModeCombo->GetSelectedIndex(), 0, static_cast<int32>(UE_ARRAY_COUNT(WindowModes)) - 1);
|
||||
Settings->SetFullscreenMode(WindowModes[Index]);
|
||||
}
|
||||
|
||||
void UGraphicsSettingsTab::OnResolutionChanged(FString SelectedItem, ESelectInfo::Type SelectionType)
|
||||
{
|
||||
if (SelectionType == ESelectInfo::Direct)
|
||||
return;
|
||||
|
||||
UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings();
|
||||
if (!Settings)
|
||||
return;
|
||||
|
||||
const int32 Index = ResolutionCombo->GetSelectedIndex();
|
||||
if (AvailableResolutions.IsValidIndex(Index))
|
||||
Settings->SetScreenResolution(AvailableResolutions[Index]);
|
||||
}
|
||||
|
||||
void UGraphicsSettingsTab::OnVSyncChanged(bool bIsChecked)
|
||||
{
|
||||
if (UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings())
|
||||
Settings->SetVSyncEnabled(bIsChecked);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "CommonUserWidget.h"
|
||||
#include "GraphicsSettingsTab.generated.h"
|
||||
|
||||
class UComboBoxString;
|
||||
class UCheckBox;
|
||||
|
||||
// Graphics settings tab. Overall quality, window mode, resolution and VSync, backed by the
|
||||
// inherited UGameUserSettings. Changes stage into the settings object; the settings screen's
|
||||
// Apply button calls ApplySettings to commit resolution/quality changes.
|
||||
UCLASS(Abstract)
|
||||
class NAKEDDESIRE_API UGraphicsSettingsTab : public UCommonUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
void RefreshFromSettings();
|
||||
|
||||
protected:
|
||||
virtual void NativeConstruct() override;
|
||||
|
||||
private:
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UComboBoxString> QualityCombo;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UComboBoxString> WindowModeCombo;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UComboBoxString> ResolutionCombo;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UCheckBox> VSyncCheckBox;
|
||||
|
||||
// Index-aligned with ResolutionCombo entries.
|
||||
TArray<FIntPoint> AvailableResolutions;
|
||||
|
||||
UFUNCTION()
|
||||
void OnQualityChanged(FString SelectedItem, ESelectInfo::Type SelectionType);
|
||||
|
||||
UFUNCTION()
|
||||
void OnWindowModeChanged(FString SelectedItem, ESelectInfo::Type SelectionType);
|
||||
|
||||
UFUNCTION()
|
||||
void OnResolutionChanged(FString SelectedItem, ESelectInfo::Type SelectionType);
|
||||
|
||||
UFUNCTION()
|
||||
void OnVSyncChanged(bool bIsChecked);
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
|
||||
#include "SettingsScreenWidget.h"
|
||||
|
||||
#include "AudioSettingsTab.h"
|
||||
#include "GameplaySettingsTab.h"
|
||||
#include "GraphicsSettingsTab.h"
|
||||
#include "Components/Button.h"
|
||||
#include "Components/WidgetSwitcher.h"
|
||||
#include "NakedDesire/Global/NakedDesireUserSettings.h"
|
||||
|
||||
void USettingsScreenWidget::NativeOnActivated()
|
||||
{
|
||||
Super::NativeOnActivated();
|
||||
|
||||
GameplayTabButton->OnClicked.AddUniqueDynamic(this, &USettingsScreenWidget::ShowGameplayTab);
|
||||
AudioTabButton->OnClicked.AddUniqueDynamic(this, &USettingsScreenWidget::ShowAudioTab);
|
||||
GraphicsTabButton->OnClicked.AddUniqueDynamic(this, &USettingsScreenWidget::ShowGraphicsTab);
|
||||
ApplyButton->OnClicked.AddUniqueDynamic(this, &USettingsScreenWidget::OnApplyClicked);
|
||||
BackButton->OnClicked.AddUniqueDynamic(this, &USettingsScreenWidget::OnBackClicked);
|
||||
|
||||
// Pull live values into every tab so the controls reflect the current state.
|
||||
GameplayTab->RefreshFromSettings();
|
||||
AudioTab->RefreshFromSettings();
|
||||
GraphicsTab->RefreshFromSettings();
|
||||
|
||||
ShowGameplayTab();
|
||||
}
|
||||
|
||||
void USettingsScreenWidget::NativeOnDeactivated()
|
||||
{
|
||||
Super::NativeOnDeactivated();
|
||||
|
||||
// Persist whatever the player changed, even if they didn't hit Apply.
|
||||
PersistSettings();
|
||||
}
|
||||
|
||||
void USettingsScreenWidget::ShowGameplayTab()
|
||||
{
|
||||
TabSwitcher->SetActiveWidget(GameplayTab);
|
||||
}
|
||||
|
||||
void USettingsScreenWidget::ShowAudioTab()
|
||||
{
|
||||
TabSwitcher->SetActiveWidget(AudioTab);
|
||||
}
|
||||
|
||||
void USettingsScreenWidget::ShowGraphicsTab()
|
||||
{
|
||||
TabSwitcher->SetActiveWidget(GraphicsTab);
|
||||
}
|
||||
|
||||
void USettingsScreenWidget::OnApplyClicked()
|
||||
{
|
||||
// Commits resolution + scalability changes staged by the graphics tab.
|
||||
if (UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings())
|
||||
Settings->ApplySettings(false);
|
||||
|
||||
PersistSettings();
|
||||
}
|
||||
|
||||
void USettingsScreenWidget::OnBackClicked()
|
||||
{
|
||||
DeactivateWidget();
|
||||
}
|
||||
|
||||
void USettingsScreenWidget::PersistSettings()
|
||||
{
|
||||
if (UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings())
|
||||
Settings->SaveSettings();
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "CommonActivatableWidget.h"
|
||||
#include "SettingsScreenWidget.generated.h"
|
||||
|
||||
class UButton;
|
||||
class UWidgetSwitcher;
|
||||
class UGameplaySettingsTab;
|
||||
class UAudioSettingsTab;
|
||||
class UGraphicsSettingsTab;
|
||||
|
||||
// Settings screen with Gameplay / Audio / Graphics tabs. Pushed onto a menu's ModalStack
|
||||
// (assign as SettingsWidgetClass on the main / pause menus). Apply commits + persists;
|
||||
// closing the screen also persists pending changes.
|
||||
UCLASS(Abstract)
|
||||
class NAKEDDESIRE_API USettingsScreenWidget : public UCommonActivatableWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
protected:
|
||||
virtual void NativeOnActivated() override;
|
||||
virtual void NativeOnDeactivated() override;
|
||||
|
||||
private:
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UWidgetSwitcher> TabSwitcher;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UButton> GameplayTabButton;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UButton> AudioTabButton;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UButton> GraphicsTabButton;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UButton> ApplyButton;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UButton> BackButton;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UGameplaySettingsTab> GameplayTab;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UAudioSettingsTab> AudioTab;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UGraphicsSettingsTab> GraphicsTab;
|
||||
|
||||
UFUNCTION()
|
||||
void ShowGameplayTab();
|
||||
|
||||
UFUNCTION()
|
||||
void ShowAudioTab();
|
||||
|
||||
UFUNCTION()
|
||||
void ShowGraphicsTab();
|
||||
|
||||
UFUNCTION()
|
||||
void OnApplyClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void OnBackClicked();
|
||||
|
||||
void PersistSettings();
|
||||
};
|
||||
Reference in New Issue
Block a user