Audio Radial Slider のデフォルトvalueの表示が1にならない件

Audio Radial Slider のデフォルトvalueの表示が1にならない件が3日かけてやっと解決した

階層とメソッド一覧

   hierarchy: AudioVolumeRadialSlider
   hierarchy: AudioRadialSlider
   hierarchy: Widget
   hierarchy: Visual
   hierarchy: Object
   UFunction: SetWidgetLayout
   UFunction: SetValueTextReadOnly
   UFunction: SetUnitsTextReadOnly
   UFunction: SetUnitsText
   UFunction: SetTextLabelBackgroundColor
   UFunction: SetSliderThickness
   UFunction: SetSliderProgressColor
   UFunction: SetSliderBarColor
   UFunction: SetShowUnitsText
   UFunction: SetShowLabelOnlyOnHover
   UFunction: SetOutputRange
   UFunction: SetHandStartEndRatio
   UFunction: SetCenterBackgroundColor
   UFunction: GetSliderValue
   UFunction: GetOutputValue
   UFunction: SetVisibility
   UFunction: SetUserFocus
   UFunction: SetToolTipText
   UFunction: SetToolTip
   UFunction: SetRenderTranslation
   UFunction: SetRenderTransformPivot
   UFunction: SetRenderTransformAngle
   UFunction: SetRenderTransform
   UFunction: SetRenderShear
   UFunction: SetRenderScale
   UFunction: SetRenderOpacity
   UFunction: SetNavigationRuleExplicit
   UFunction: SetNavigationRuleCustomBoundary
   UFunction: SetNavigationRuleCustom
   UFunction: SetNavigationRuleBase
   UFunction: SetNavigationRule
   UFunction: SetKeyboardFocus
   UFunction: SetIsEnabled
   UFunction: SetFocus
   UFunction: SetCursor
   UFunction: SetClipping
   UFunction: SetAllNavigationRules
   UFunction: ResetCursor
   UFunction: RemoveFromParent
   UFunction: OnReply__DelegateSignature
   UFunction: OnPointerEvent__DelegateSignature
   UFunction: K2_RemoveFieldValueChangedDelegate
   UFunction: K2_BroadcastFieldValueChanged
   UFunction: K2_AddFieldValueChangedDelegate
   UFunction: IsVisible
   UFunction: IsRendered
   UFunction: IsInViewport
   UFunction: IsHovered
   UFunction: InvalidateLayoutAndVolatility
   UFunction: HasUserFocusedDescendants
   UFunction: HasUserFocus
   UFunction: HasMouseCaptureByUser
   UFunction: HasMouseCapture
   UFunction: HasKeyboardFocus
   UFunction: HasFocusedDescendants
   UFunction: HasAnyUserFocus
   UFunction: GetWidget__DelegateSignature
   UFunction: GetVisibility
   UFunction: GetTickSpaceGeometry
   UFunction: GetText__DelegateSignature
   UFunction: GetSlateVisibility__DelegateSignature
   UFunction: GetSlateColor__DelegateSignature
   UFunction: GetSlateBrush__DelegateSignature
   UFunction: GetRenderTransformAngle
   UFunction: GetRenderOpacity
   UFunction: GetParent
   UFunction: GetPaintSpaceGeometry
   UFunction: GetOwningPlayer
   UFunction: GetOwningLocalPlayer
   UFunction: GetMouseCursor__DelegateSignature
   UFunction: GetLinearColor__DelegateSignature
   UFunction: GetIsEnabled
   UFunction: GetInt32__DelegateSignature
   UFunction: GetGameInstance
   UFunction: GetFloat__DelegateSignature
   UFunction: GetDesiredSize
   UFunction: GetClipping
   UFunction: GetCheckBoxState__DelegateSignature
   UFunction: GetCachedGeometry
   UFunction: GetBool__DelegateSignature
   UFunction: GetAccessibleText
   UFunction: GetAccessibleSummaryText
   UFunction: GenerateWidgetForString__DelegateSignature
   UFunction: GenerateWidgetForObject__DelegateSignature
   UFunction: ForceVolatile
   UFunction: ForceLayoutPrepass
   UFunction: ExecuteUbergraph
   hierarchy: AudioVolumeRadialSlider
   hierarchy: AudioRadialSlider
   hierarchy: Widget
   hierarchy: Visual
   hierarchy: Object
   UFunction: SetWidgetLayout
   UFunction: SetValueTextReadOnly
   UFunction: SetUnitsTextReadOnly
   UFunction: SetUnitsText
   UFunction: SetTextLabelBackgroundColor
   UFunction: SetSliderThickness
   UFunction: SetSliderProgressColor
   UFunction: SetSliderBarColor
   UFunction: SetShowUnitsText
   UFunction: SetShowLabelOnlyOnHover
   UFunction: SetOutputRange
   UFunction: SetHandStartEndRatio
   UFunction: SetCenterBackgroundColor
   UFunction: GetSliderValue
   UFunction: GetOutputValue
   UFunction: SetVisibility
   UFunction: SetUserFocus
   UFunction: SetToolTipText
   UFunction: SetToolTip
   UFunction: SetRenderTranslation
   UFunction: SetRenderTransformPivot
   UFunction: SetRenderTransformAngle
   UFunction: SetRenderTransform
   UFunction: SetRenderShear
   UFunction: SetRenderScale
   UFunction: SetRenderOpacity
   UFunction: SetNavigationRuleExplicit
   UFunction: SetNavigationRuleCustomBoundary
   UFunction: SetNavigationRuleCustom
   UFunction: SetNavigationRuleBase
   UFunction: SetNavigationRule
   UFunction: SetKeyboardFocus
   UFunction: SetIsEnabled
   UFunction: SetFocus
   UFunction: SetCursor
   UFunction: SetClipping
   UFunction: SetAllNavigationRules
   UFunction: ResetCursor
   UFunction: RemoveFromParent
   UFunction: OnReply__DelegateSignature
   UFunction: OnPointerEvent__DelegateSignature
   UFunction: K2_RemoveFieldValueChangedDelegate
   UFunction: K2_BroadcastFieldValueChanged
   UFunction: K2_AddFieldValueChangedDelegate
   UFunction: IsVisible
   UFunction: IsRendered
   UFunction: IsInViewport
   UFunction: IsHovered
   UFunction: InvalidateLayoutAndVolatility
   UFunction: HasUserFocusedDescendants
   UFunction: HasUserFocus
   UFunction: HasMouseCaptureByUser
   UFunction: HasMouseCapture
   UFunction: HasKeyboardFocus
   UFunction: HasFocusedDescendants
   UFunction: HasAnyUserFocus
   UFunction: GetWidget__DelegateSignature
   UFunction: GetVisibility
   UFunction: GetTickSpaceGeometry
   UFunction: GetText__DelegateSignature
   UFunction: GetSlateVisibility__DelegateSignature
   UFunction: GetSlateColor__DelegateSignature
   UFunction: GetSlateBrush__DelegateSignature
   UFunction: GetRenderTransformAngle
   UFunction: GetRenderOpacity
   UFunction: GetParent
   UFunction: GetPaintSpaceGeometry
   UFunction: GetOwningPlayer
   UFunction: GetOwningLocalPlayer
   UFunction: GetMouseCursor__DelegateSignature
   UFunction: GetLinearColor__DelegateSignature
   UFunction: GetIsEnabled
   UFunction: GetInt32__DelegateSignature
   UFunction: GetGameInstance
   UFunction: GetFloat__DelegateSignature
   UFunction: GetDesiredSize
   UFunction: GetClipping
   UFunction: GetCheckBoxState__DelegateSignature
   UFunction: GetCachedGeometry
   UFunction: GetBool__DelegateSignature
   UFunction: GetAccessibleText
   UFunction: GetAccessibleSummaryText
   UFunction: GenerateWidgetForString__DelegateSignature
   UFunction: GenerateWidgetForObject__DelegateSignature
   UFunction: ForceVolatile
   UFunction: ForceLayoutPrepass
   UFunction: ExecuteUbergraph

● AudioRadialSlider という独自クラスです。ソースを探します:

● わかりました。ではビルド・実行後のOutput Logで Cast OK が出ているか確認してください。それだけ教えてください。

LogTemp: Warning: AudioUI: Cast OK [AudioVolume_Master] before=1.000000
LogTemp: Warning: AudioUI: after=1.000000
LogTemp: Warning: AudioUI: Cast OK [AudioVolume_se] before=1.000000
LogTemp: Warning: AudioUI: after=1.000000
LogViewport: Display: Player bShowMouseCursor Changed, False -> True
PIE: Server logged in
PIE: Play in editor total start time 0.113 seconds.
LogTemp: Warning: AudioUI: Cast OK [AudioVolume_Master] before=1.000000
LogTemp: Warning: AudioUI: after=1.000000
LogTemp: Warning: AudioUI: Cast OK [AudioVolume_se] before=1.000000
LogTemp: Warning: AudioUI: after=1.000000

● before=1.0 — 値はすでに1.0になっています。 でも表示が違う?

これは表示テキスト(ラベル)の問題です。UAudioRadialSlider は Value(0〜1 正規化)を OutputRange
に変換して表示します。OutputRange の設定次第で表示が変わります。

● Searched for 1 pattern, read 1 file (ctrl+o to expand)

● SynchronizeProperties は Value を Slate に送っていません。RebuildWidget を確認します:

● Searched for 1 pattern (ctrl+o to expand)

● 原因判明。SynchronizeProperties が Value を Slate に送っていません。GetCachedWidget() で Slate
ウィジェットを取得して直接 SetValue します:

● Searched for 4 patterns, read 1 file (ctrl+o to expand)

● SAudioRadialSlider::SetSliderValue() を使います。Slate ウィジェットに直接セットします:

● Write(D:\Sandbox\UE573petit25Cl\UE573petit25Cl\Source\UE573petit25Cl\AudioUI.h)

// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "AudioUI.generated.h"

/**
 *  Base class for Audio settings UI.
 *  Initializes AudioVolumeRadialSlider defaults and routes value changes to AudioManager.
 */
UCLASS(abstract)
class UE573PETIT25CL_API UAudioUI : public UUserWidget
{
	GENERATED_BODY()

protected:

	virtual void NativePreConstruct() override;
	virtual void NativeConstruct() override;

public:

	UFUNCTION()
	void InitSliderDefaults();

private:

	UFUNCTION()
	void OnMasterVolumeChanged(float Value);

	UFUNCTION()
	void OnSEVolumeChanged(float Value);
};

● Write(D:\Sandbox\UE573petit25Cl\UE573petit25Cl\Source\UE573petit25Cl\AudioUI.cpp)

// Copyright Epic Games, Inc. All Rights Reserved.

#include "AudioUI.h"
#include "AudioManager.h"
#include "AudioRadialSlider.h"
#include "SAudioRadialSlider.h"
#include "Blueprint/WidgetTree.h"
#include "TimerManager.h"
#include "Engine/World.h"

void UAudioUI::NativePreConstruct()
{
	Super::NativePreConstruct();
	InitSliderDefaults();
}

void UAudioUI::NativeConstruct()
{
	Super::NativeConstruct();

	// Bind slider delegates
	if (UAudioRadialSlider* Master = Cast<UAudioRadialSlider>(GetWidgetFromName(TEXT("AudioVolume_Master"))))
	{
		Master->OnValueChanged.AddDynamic(this, &UAudioUI::OnMasterVolumeChanged);
	}

	if (UAudioRadialSlider* SE = Cast<UAudioRadialSlider>(GetWidgetFromName(TEXT("AudioVolume_se"))))
	{
		SE->OnValueChanged.AddDynamic(this, &UAudioUI::OnSEVolumeChanged);
	}

	if (UWorld* World = GetWorld())
	{
		FTimerHandle Handle;
		World->GetTimerManager().SetTimerForNextTick(this, &UAudioUI::InitSliderDefaults);
	}
}

void UAudioUI::InitSliderDefaults()
{
	if (!WidgetTree) return;

	WidgetTree->ForEachWidget([](UWidget* Widget)
	{
		if (UAudioRadialSlider* Slider = Cast<UAudioRadialSlider>(Widget))
		{
			Slider->Value = 1.0f;

			TSharedPtr<SWidget> CachedWidget = Slider->GetCachedWidget();
			if (CachedWidget.IsValid())
			{
				StaticCastSharedPtr<SAudioRadialSlider>(CachedWidget)->SetSliderValue(1.0f);
			}
		}
	});
}

void UAudioUI::OnMasterVolumeChanged(float Value)
{
	if (UAudioManager* AM = GetGameInstance()->GetSubsystem<UAudioManager>())
	{
		AM->SetMasterVolume(Value);
	}
}

void UAudioUI::OnSEVolumeChanged(float Value)
{
	if (UAudioManager* AM = GetGameInstance()->GetSubsystem<UAudioManager>())
	{
		AM->SetSEVolume(Value);
	}
}

● ビルドして確認してください。

● よかったです!長い道のりでしたね。

原因のまとめ:

  • AudioVolumeRadialSlider は URadialSlider ではなく AudioWidgets プラグインの UAudioVolumeRadialSlider
  • SynchronizeProperties() が Value を Slate に送らない(プラグインの仕様)
  • Slate ウィジェット (SAudioRadialSlider) に直接 SetSliderValue() を呼ぶ必要があった

● お疲れ様でした!

今回の実装まとめ:

  • AudioVolume_Master → AudioManager::SetMasterVolume()
  • AudioVolume_se → AudioManager::SetSEVolume()
  • 初期値は両方 1.0(SAudioRadialSlider::SetSliderValue で Slate に直接セット)

おまけ

AudioManager.h

// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "AudioManager.generated.h"

class USoundBase;
class UAudioComponent;

/**
 *  Game-wide audio manager — BGM playlist + volume control.
 *
 *  Access from anywhere:
 *    UGameInstance* GI = GetGameInstance();          // or GetWorld()->GetGameInstance()
 *    UAudioManager* AM = GI->GetSubsystem<UAudioManager>();
 *
 *  BGM asset note:
 *    USoundBase assets used as BGM tracks must have looping DISABLED so that
 *    OnAudioFinished fires when the track ends and the playlist advances.
 */
UCLASS()
class UE573PETIT25CL_API UAudioManager : public UGameInstanceSubsystem
{
	GENERATED_BODY()

public:

	// -------------------------------------------------------
	// Playlist
	// -------------------------------------------------------

	/** BGM tracks to play in order (or randomly when bRandomPlayback is true) */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="BGM")
	TArray<TObjectPtr<USoundBase>> BGMPlaylist;

	/**
	 *  If true, tracks are played in a random order.
	 *  A full cycle (every track plays once) is guaranteed before any track repeats.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="BGM")
	bool bRandomPlayback = false;

	// -------------------------------------------------------
	// Volume  (0.0 – 1.0)
	// -------------------------------------------------------

	/** Overall volume multiplier applied to all audio */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Volume", meta=(ClampMin=0, ClampMax=1))
	float MasterVolume = 1.0f;

	/** Volume multiplier applied to BGM on top of MasterVolume */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Volume", meta=(ClampMin=0, ClampMax=1))
	float BGMVolume = 1.0f;

	/** Volume multiplier applied to SE on top of MasterVolume */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Volume", meta=(ClampMin=0, ClampMax=1))
	float SEVolume = 1.0f;

public:

	// -------------------------------------------------------
	// BGM API
	// -------------------------------------------------------

	/**
	 *  Starts BGM playlist playback.
	 *  @param TrackIndex  0-based index of the first track to play.
	 *                     Ignored in random mode — a fresh shuffle is always started.
	 */
	UFUNCTION(BlueprintCallable, Category="Audio|BGM")
	void PlayBGM(int32 TrackIndex = 0);

	/** Stops BGM playback and destroys the internal audio component */
	UFUNCTION(BlueprintCallable, Category="Audio|BGM")
	void StopBGM();

	/** Pauses BGM playback */
	UFUNCTION(BlueprintCallable, Category="Audio|BGM")
	void PauseBGM();

	/** Resumes a paused BGM */
	UFUNCTION(BlueprintCallable, Category="Audio|BGM")
	void ResumeBGM();

	/** Skips to the next track immediately */
	UFUNCTION(BlueprintCallable, Category="Audio|BGM")
	void SkipTrack();

	// -------------------------------------------------------
	// Volume API
	// -------------------------------------------------------

	/** Sets the master volume and immediately applies it to the playing BGM */
	UFUNCTION(BlueprintCallable, Category="Audio|Volume")
	void SetMasterVolume(float Volume);

	/** Sets the BGM volume and immediately applies it to the playing BGM */
	UFUNCTION(BlueprintCallable, Category="Audio|Volume")
	void SetBGMVolume(float Volume);

	/** Sets the SE volume (applied to subsequent PlaySE calls) */
	UFUNCTION(BlueprintCallable, Category="Audio|Volume")
	void SetSEVolume(float Volume);

	// -------------------------------------------------------
	// SE API
	// -------------------------------------------------------

	/** Plays a non-spatialized sound effect at MasterVolume * SEVolume */
	UFUNCTION(BlueprintCallable, Category="Audio|SE")
	void PlaySE2D(USoundBase* Sound);

	/** Plays a spatialized sound effect at the given world location at MasterVolume * SEVolume */
	UFUNCTION(BlueprintCallable, Category="Audio|SE", meta=(WorldContext="WorldContextObject"))
	void PlaySE3D(USoundBase* Sound, const FVector& Location, UObject* WorldContextObject);

private:

	/** Currently active BGM audio component */
	UPROPERTY()
	TObjectPtr<UAudioComponent> BGMComponent;

	/** Index of the track that is currently playing */
	int32 CurrentTrackIndex = 0;

	/** Remaining track indices for random mode; rebuilt when the list is exhausted */
	TArray<int32> ShuffledIndices;

	/** Plays the track at the given playlist index */
	void PlayTrackAtIndex(int32 Index);

	/** Called when the current BGM track finishes — advances to the next track */
	UFUNCTION()
	void OnBGMFinished();

	/** Returns the index of the next track to play */
	int32 PickNextTrackIndex();

	/** Applies the current MasterVolume * BGMVolume to the active audio component */
	void RefreshBGMVolume();

	/** Fills ShuffledIndices with a fresh Fisher-Yates permutation of all track indices */
	void RebuildShuffledIndices();
};

AudioManager.cpp

// Copyright Epic Games, Inc. All Rights Reserved.

#include "AudioManager.h"
#include "Components/AudioComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Sound/SoundBase.h"

// -------------------------------------------------------
// BGM API
// -------------------------------------------------------

void UAudioManager::PlayBGM(int32 TrackIndex)
{
	if (BGMPlaylist.IsEmpty())
	{
		return;
	}

	// Stop any currently playing track before starting the playlist
	StopBGM();

	if (bRandomPlayback)
	{
		// Start a fresh shuffle so every track plays before any repeats
		RebuildShuffledIndices();
		PlayTrackAtIndex(ShuffledIndices.Pop(EAllowShrinking::No));
	}
	else
	{
		PlayTrackAtIndex(FMath::Clamp(TrackIndex, 0, BGMPlaylist.Num() - 1));
	}
}

void UAudioManager::StopBGM()
{
	if (BGMComponent)
	{
		// Remove delegate first so Stop() does not accidentally trigger OnBGMFinished
		BGMComponent->OnAudioFinished.RemoveDynamic(this, &UAudioManager::OnBGMFinished);
		BGMComponent->Stop();
		BGMComponent->DestroyComponent();
		BGMComponent = nullptr;
	}
}

void UAudioManager::PauseBGM()
{
	if (BGMComponent)
	{
		BGMComponent->SetPaused(true);
	}
}

void UAudioManager::ResumeBGM()
{
	if (BGMComponent)
	{
		BGMComponent->SetPaused(false);
	}
}

void UAudioManager::SkipTrack()
{
	if (BGMPlaylist.IsEmpty())
	{
		return;
	}

	PlayTrackAtIndex(PickNextTrackIndex());
}

// -------------------------------------------------------
// Volume API
// -------------------------------------------------------

void UAudioManager::SetMasterVolume(float Volume)
{
	MasterVolume = FMath::Clamp(Volume, 0.0f, 1.0f);
	RefreshBGMVolume();
}

void UAudioManager::SetBGMVolume(float Volume)
{
	BGMVolume = FMath::Clamp(Volume, 0.0f, 1.0f);
	RefreshBGMVolume();
}

void UAudioManager::SetSEVolume(float Volume)
{
	SEVolume = FMath::Clamp(Volume, 0.0f, 1.0f);
}

// -------------------------------------------------------
// SE API
// -------------------------------------------------------

void UAudioManager::PlaySE2D(USoundBase* Sound)
{
	if (!Sound)
	{
		return;
	}

	UGameplayStatics::SpawnSound2D(GetGameInstance(), Sound, MasterVolume * SEVolume);
}

void UAudioManager::PlaySE3D(USoundBase* Sound, const FVector& Location, UObject* WorldContextObject)
{
	if (!Sound || !WorldContextObject)
	{
		return;
	}

	UGameplayStatics::SpawnSoundAtLocation(WorldContextObject, Sound, Location,
		FRotator::ZeroRotator, MasterVolume * SEVolume);
}

// -------------------------------------------------------
// Private helpers
// -------------------------------------------------------

void UAudioManager::PlayTrackAtIndex(int32 Index)
{
	if (!BGMPlaylist.IsValidIndex(Index) || !BGMPlaylist[Index])
	{
		return;
	}

	CurrentTrackIndex = Index;

	// Tear down previous component and unsubscribe before spawning a new one
	if (BGMComponent)
	{
		BGMComponent->OnAudioFinished.RemoveDynamic(this, &UAudioManager::OnBGMFinished);
		BGMComponent->Stop();
		BGMComponent->DestroyComponent();
		BGMComponent = nullptr;
	}

	// Spawn a 2D audio component:
	//   bPersistAcrossLevelTransition = true  — BGM survives level loads
	//   bAutoDestroy                  = false — we control the lifetime manually
	BGMComponent = UGameplayStatics::SpawnSound2D(
		GetGameInstance(),
		BGMPlaylist[Index],
		MasterVolume * BGMVolume,
		1.0f,   // PitchMultiplier
		0.0f,   // StartTime
		nullptr,// ConcurrencySettings
		true,   // bPersistAcrossLevelTransition
		false   // bAutoDestroy
	);

	if (BGMComponent)
	{
		BGMComponent->OnAudioFinished.AddDynamic(this, &UAudioManager::OnBGMFinished);
	}
}

void UAudioManager::OnBGMFinished()
{
	if (BGMPlaylist.IsEmpty())
	{
		return;
	}

	PlayTrackAtIndex(PickNextTrackIndex());
}

int32 UAudioManager::PickNextTrackIndex()
{
	if (bRandomPlayback)
	{
		// Rebuild the shuffle pool when every track has been played once
		if (ShuffledIndices.IsEmpty())
		{
			RebuildShuffledIndices();
		}
		return ShuffledIndices.Pop(EAllowShrinking::No);
	}

	// Sequential: wrap around to the beginning after the last track
	return (CurrentTrackIndex + 1) % BGMPlaylist.Num();
}

void UAudioManager::RefreshBGMVolume()
{
	if (BGMComponent)
	{
		BGMComponent->SetVolumeMultiplier(MasterVolume * BGMVolume);
	}
}

void UAudioManager::RebuildShuffledIndices()
{
	const int32 Count = BGMPlaylist.Num();
	ShuffledIndices.Reset(Count);

	for (int32 i = 0; i < Count; ++i)
	{
		ShuffledIndices.Add(i);
	}

	// Fisher-Yates shuffle
	for (int32 i = Count - 1; i > 0; --i)
	{
		const int32 j = FMath::RandRange(0, i);
		ShuffledIndices.Swap(i, j);
	}
}

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です