2026-07-05 글로벌 프리팹 추가
This commit is contained in:
BIN
Assets/01_Scenes/Hischool.unity
LFS
BIN
Assets/01_Scenes/Hischool.unity
LFS
Binary file not shown.
BIN
Assets/01_Scenes/MyHouse.unity
LFS
BIN
Assets/01_Scenes/MyHouse.unity
LFS
Binary file not shown.
BIN
Assets/01_Scenes/Street1.unity
LFS
BIN
Assets/01_Scenes/Street1.unity
LFS
Binary file not shown.
Binary file not shown.
@@ -7,5 +7,5 @@ public class AffectionModifier : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private CharacterData _character;
|
||||
|
||||
public void Add(int delta) => StoryState.AddAffection(_character, delta);
|
||||
public void Add(int delta) => StoryManager.Instance.AddAffection(_character, delta);
|
||||
}
|
||||
|
||||
@@ -22,18 +22,20 @@ public class DialogCondition
|
||||
// affectionTarget: 호감도 조건을 검사할 캐릭터 (보통 대화를 거는 NPC 자신)
|
||||
public bool IsMet(CharacterData affectionTarget)
|
||||
{
|
||||
if (StoryState.MainProgress < MinMainProgress)
|
||||
var story = StoryManager.Instance;
|
||||
|
||||
if (story.MainProgress < MinMainProgress)
|
||||
return false;
|
||||
|
||||
if (MinAffection > 0 && StoryState.GetAffection(affectionTarget) < MinAffection)
|
||||
if (MinAffection > 0 && story.GetAffection(affectionTarget) < MinAffection)
|
||||
return false;
|
||||
|
||||
foreach (var group in RequiredDialogs)
|
||||
if (group != null && !StoryState.IsDialogCompleted(group.name))
|
||||
if (group != null && !story.IsDialogCompleted(group.name))
|
||||
return false;
|
||||
|
||||
foreach (var code in RequiredChoiceCodes)
|
||||
if (!string.IsNullOrEmpty(code) && !StoryState.HasChosen(code))
|
||||
if (!string.IsNullOrEmpty(code) && !story.HasChosen(code))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
||||
@@ -19,6 +19,10 @@ public class DialogNode : ScriptableObject
|
||||
//Voice 없음 → LineDuration 대기
|
||||
|
||||
|
||||
[Header("Presentation")]
|
||||
public AudioClip Bgm; // 있으면 이 대사부터 전용 BGM 재생, 비어있으면 기본 BGM으로 복귀
|
||||
public GameObject Vfx; // 이 대사 시작 시 화자 위치에서 1회 재생할 이펙트 프리팹
|
||||
|
||||
[Header("Behavior")]
|
||||
public bool LookAtPlayer;
|
||||
public bool WaitForInput; // true면 LineDuration 무시하고 B버튼(OnDialogNext) 입력까지 대기
|
||||
|
||||
@@ -86,7 +86,7 @@ private int FindPlayableIndex()
|
||||
{
|
||||
var entry = _dialogs[i];
|
||||
if (entry.Group == null) continue;
|
||||
if (!entry.Repeatable && StoryState.IsDialogCompleted(entry.Group.name)) continue;
|
||||
if (!entry.Repeatable && StoryManager.Instance.IsDialogCompleted(entry.Group.name)) continue;
|
||||
if (entry.Condition != null && !entry.Condition.IsMet(_voice.Character)) continue;
|
||||
return i;
|
||||
}
|
||||
@@ -117,10 +117,11 @@ private async Awaitable PlayEntry(DialogEntry entry)
|
||||
|
||||
// 여기까지 왔으면 자연 종료(끝까지 재생) — 이때만 완료로 기록한다.
|
||||
// (중간에 오브젝트 파괴 등으로 끊기면 예외로 빠져나가 기록되지 않음)
|
||||
bool firstTime = StoryState.MarkDialogCompleted(entry.Group.name);
|
||||
var story = StoryManager.Instance;
|
||||
bool firstTime = story.MarkDialogCompleted(entry.Group.name);
|
||||
if (firstTime && entry.ProgressOnComplete > 0)
|
||||
StoryState.MainProgress += entry.ProgressOnComplete;
|
||||
StoryState.Save();
|
||||
story.MainProgress += entry.ProgressOnComplete;
|
||||
story.Save();
|
||||
|
||||
Debug.Log($"[DialogPlayer] 대화 종료: {entry.Group.name}");
|
||||
}
|
||||
@@ -129,6 +130,8 @@ private async Awaitable PlayEntry(DialogEntry entry)
|
||||
IsPlaying = false;
|
||||
if (DialogHud.Instance != null)
|
||||
DialogHud.Instance.Hide();
|
||||
if (SoundManager.Instance != null)
|
||||
SoundManager.Instance.ClearOverrideBGM(); // 대화가 끝나면 기본 BGM으로 복귀
|
||||
RestoreDefaultAnimations();
|
||||
RestoreRotations();
|
||||
}
|
||||
@@ -195,6 +198,28 @@ private async Awaitable PlayNode(DialogNode node)
|
||||
|
||||
RaiseNodeEvent(node.EventKey); // EventKey 있으면 매칭 이벤트 호출
|
||||
|
||||
// 전용 BGM: 설정돼 있으면 교체, 비어 있으면 기본 BGM으로 복귀
|
||||
if (SoundManager.Instance != null)
|
||||
{
|
||||
if (node.Bgm != null)
|
||||
SoundManager.Instance.PlayOverrideBGM(node.Bgm);
|
||||
else
|
||||
SoundManager.Instance.ClearOverrideBGM();
|
||||
}
|
||||
|
||||
// 전용 VFX: 화자 위치에서 1회 재생
|
||||
if (node.Vfx != null)
|
||||
{
|
||||
var speakerObj = node.Speaker != null ? CharacterVoiceObject.Find(node.Speaker) : null;
|
||||
var anchor = speakerObj != null ? speakerObj.transform : transform;
|
||||
var vfx = Instantiate(node.Vfx, anchor.position, anchor.rotation);
|
||||
|
||||
// 파티클이면 재생 길이만큼, 아니면 5초 뒤 자동 제거
|
||||
var ps = vfx.GetComponentInChildren<ParticleSystem>();
|
||||
float life = ps != null ? ps.main.duration + ps.main.startLifetime.constantMax : 5f;
|
||||
Destroy(vfx, life);
|
||||
}
|
||||
|
||||
// 보이스 재생
|
||||
if (node.Voice != null && node.Speaker != null)
|
||||
{
|
||||
@@ -253,7 +278,7 @@ private void RecordChoice(DialogNode node, int index)
|
||||
code = DialogVariables.Format(code); // {token} 치환 → 동적으로 생성된 코드 반영
|
||||
|
||||
if (!string.IsNullOrEmpty(code))
|
||||
StoryState.RecordChoice(code);
|
||||
StoryManager.Instance.RecordChoice(code);
|
||||
}
|
||||
|
||||
private async Awaitable<int> WaitForChoice(DialogNode node)
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace DinoLove.Dialog.GraphTool.Editor
|
||||
// .dlg 그래프 에셋을 기존 런타임 타입(DialogGroup / DialogNode / DialogChoice)으로 변환한다.
|
||||
// 생성된 DialogNode들은 서브에셋으로, DialogGroup이 메인 에셋으로 등록된다.
|
||||
// 따라서 DialogPlayer는 수정 없이 임포트된 .dlg 에셋(= DialogGroup)을 그대로 사용한다.
|
||||
[ScriptedImporter(1, DialogGraph.AssetExtension)]
|
||||
[ScriptedImporter(2, DialogGraph.AssetExtension)] // 버전 올리면 기존 .dlg 에셋이 재임포트됨
|
||||
internal class DialogGraphImporter : ScriptedImporter
|
||||
{
|
||||
public override void OnImportAsset(AssetImportContext ctx)
|
||||
@@ -79,6 +79,8 @@ public override void OnImportAsset(AssetImportContext ctx)
|
||||
dn.Gesture = GetInputPortValue<GestureData>(gn.GetInputPortByName(DialogLineNode.PORT_GESTURE));
|
||||
dn.Expression = GetInputPortValue<ExpressionData>(gn.GetInputPortByName(DialogLineNode.PORT_EXPRESSION));
|
||||
dn.Voice = GetInputPortValue<VoiceClip>(gn.GetInputPortByName(DialogLineNode.PORT_VOICE));
|
||||
dn.Bgm = GetInputPortValue<AudioClip>(gn.GetInputPortByName(DialogLineNode.PORT_BGM));
|
||||
dn.Vfx = GetInputPortValue<GameObject>(gn.GetInputPortByName(DialogLineNode.PORT_VFX));
|
||||
dn.LineDuration = GetInputPortValue<float>(gn.GetInputPortByName(DialogLineNode.PORT_DURATION));
|
||||
dn.LookAtPlayer = GetInputPortValue<bool>(gn.GetInputPortByName(DialogLineNode.PORT_LOOKAT));
|
||||
dn.WaitForInput = GetInputPortValue<bool>(gn.GetInputPortByName(DialogLineNode.PORT_WAITINPUT));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using Unity.GraphToolkit.Editor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DinoLove.Dialog.GraphTool.Editor
|
||||
{
|
||||
@@ -18,6 +19,8 @@ internal class DialogLineNode : DialogGraphNode
|
||||
public const string PORT_GESTURE = "Gesture";
|
||||
public const string PORT_EXPRESSION = "Expression";
|
||||
public const string PORT_VOICE = "Voice";
|
||||
public const string PORT_BGM = "Bgm";
|
||||
public const string PORT_VFX = "Vfx";
|
||||
public const string PORT_DURATION = "LineDuration";
|
||||
public const string PORT_LOOKAT = "LookAtPlayer";
|
||||
public const string PORT_WAITINPUT = "WaitForInput";
|
||||
@@ -55,6 +58,10 @@ protected override void OnDefinePorts(IPortDefinitionContext context)
|
||||
context.AddInputPort<GestureData>(PORT_GESTURE).WithDisplayName("Gesture").Build();
|
||||
context.AddInputPort<ExpressionData>(PORT_EXPRESSION).WithDisplayName("Expression").Build();
|
||||
context.AddInputPort<VoiceClip>(PORT_VOICE).WithDisplayName("Voice").Build();
|
||||
context.AddInputPort<AudioClip>(PORT_BGM).WithDisplayName("BGM")
|
||||
.WithTooltip("있으면 이 대사부터 전용 BGM 재생, 비우면 기본 BGM으로 복귀").Build();
|
||||
context.AddInputPort<GameObject>(PORT_VFX).WithDisplayName("VFX Prefab")
|
||||
.WithTooltip("이 대사 시작 시 화자 위치에서 1회 재생할 이펙트 프리팹").Build();
|
||||
context.AddInputPort<float>(PORT_DURATION).WithDisplayName("Line Duration").Build();
|
||||
context.AddInputPort<bool>(PORT_LOOKAT).WithDisplayName("Look At Player").Build();
|
||||
context.AddInputPort<bool>(PORT_WAITINPUT).WithDisplayName("Wait For Input").Build();
|
||||
|
||||
4
Assets/02_Scripts/ISceneInitializable.cs
Normal file
4
Assets/02_Scripts/ISceneInitializable.cs
Normal file
@@ -0,0 +1,4 @@
|
||||
public interface ISceneInitializable
|
||||
{
|
||||
public void OnSceneLoaded();
|
||||
}
|
||||
2
Assets/02_Scripts/ISceneInitializable.cs.meta
Normal file
2
Assets/02_Scripts/ISceneInitializable.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38039c1904e850146a01668e869851be
|
||||
@@ -2,15 +2,5 @@
|
||||
|
||||
public class GameManager : MonoBehaviour
|
||||
{
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
public static GameManager Instance { get; private set; }
|
||||
}
|
||||
|
||||
169
Assets/02_Scripts/Managers/SceneLoadManager.cs
Normal file
169
Assets/02_Scripts/Managers/SceneLoadManager.cs
Normal file
@@ -0,0 +1,169 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
public class SceneLoadManager : MonoBehaviour
|
||||
{
|
||||
public static SceneLoadManager Instance { get; private set; }
|
||||
|
||||
[SerializeField] private GameObject _loadingRoot;
|
||||
[SerializeField] private Camera _loadingCam;
|
||||
[SerializeField] private Transform _loadingCamTargetTransform;
|
||||
|
||||
private bool _isChangingScene = false;
|
||||
|
||||
public bool IsChangingScene => _isChangingScene;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance == null)
|
||||
{
|
||||
Instance = this; // 만들어진 자신을 인스턴스로 설정
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(gameObject); // 이미 인스턴스가 있으면 자신을 파괴
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
SceneManager.sceneLoaded += OnSceneLoaded;
|
||||
OnSceneLoaded(SceneManager.GetActiveScene(), LoadSceneMode.Single);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (Instance == this)
|
||||
{
|
||||
SceneManager.sceneLoaded -= OnSceneLoaded;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_loadingRoot != null && _loadingCamTargetTransform != null)
|
||||
{
|
||||
_loadingRoot.transform.position = _loadingCamTargetTransform.position;
|
||||
}
|
||||
}
|
||||
|
||||
// 씬이 로드되었을때 호출
|
||||
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
||||
{
|
||||
MonoBehaviour[] allObjs = FindObjectsByType<MonoBehaviour>();
|
||||
|
||||
foreach (var obj in allObjs)
|
||||
{
|
||||
if (obj is ISceneInitializable initializable)
|
||||
{
|
||||
// 씬에서 ISceneInitializable 인터페이스를 가진 오브젝트의 초기화 로직을 실행
|
||||
initializable.OnSceneLoaded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSceneLoadingProgressValue(float value)
|
||||
{
|
||||
// 여기에 로딩바 UI 연결 예정
|
||||
}
|
||||
|
||||
public void RequestSceneChange(string sceneName)
|
||||
{
|
||||
if (_isChangingScene)
|
||||
{
|
||||
Debug.Log("이미 씬 전환 중입니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(sceneName))
|
||||
{
|
||||
Debug.LogWarning("이동할 씬 이름이 비어있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
_ = SceneChange(sceneName);
|
||||
}
|
||||
|
||||
private async Awaitable SceneChange(string sceneName)
|
||||
{
|
||||
try
|
||||
{
|
||||
_isChangingScene = true;
|
||||
|
||||
// 로딩바 수치 0으로 설정
|
||||
SetSceneLoadingProgressValue(0f);
|
||||
|
||||
if (_loadingRoot != null)
|
||||
{
|
||||
_loadingRoot.SetActive(true);
|
||||
}
|
||||
|
||||
AsyncOperation op = SceneManager.LoadSceneAsync(sceneName);
|
||||
|
||||
if (op == null)
|
||||
{
|
||||
Debug.LogError($"씬 로드 실패: {sceneName}");
|
||||
_isChangingScene = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 자동 전환을 하고 싶지 않을 경우 false로 두었다가 true로 바꾸면 그 때 전환됨
|
||||
op.allowSceneActivation = false;
|
||||
|
||||
// 화면에 보여줄 로딩 수치
|
||||
float displayProgress = 0f;
|
||||
|
||||
// op.progress 0.9가 데이터 로딩이 끝난 기준
|
||||
while (op.progress < 0.9f)
|
||||
{
|
||||
// 실제 로딩 수치
|
||||
float realProgress = Mathf.Clamp01(op.progress / 0.9f);
|
||||
|
||||
// 보여줄 값을 실제값을 향해 부드럽게 이동
|
||||
displayProgress = Mathf.MoveTowards(displayProgress, realProgress, Time.deltaTime * 0.5f);
|
||||
|
||||
// 로딩바 UI에 값 적용
|
||||
SetSceneLoadingProgressValue(displayProgress);
|
||||
|
||||
// 자기자신이 파괴될때 토큰에 취소요청을 보냄
|
||||
await Awaitable.NextFrameAsync(this.destroyCancellationToken);
|
||||
}
|
||||
|
||||
// 로딩바 수치 1(100%)로 설정
|
||||
SetSceneLoadingProgressValue(1f);
|
||||
|
||||
// 잠시 대기했다가 전환
|
||||
await Awaitable.WaitForSecondsAsync(1.0f, this.destroyCancellationToken);
|
||||
|
||||
// 다음씬으로 넘어가도 됨을 알림
|
||||
op.allowSceneActivation = true;
|
||||
|
||||
// 씬 활성화가 완전히 끝날 때까지 대기
|
||||
while (!op.isDone)
|
||||
{
|
||||
await Awaitable.NextFrameAsync(this.destroyCancellationToken);
|
||||
}
|
||||
|
||||
// VR용 로직
|
||||
// 트래킹이 중단되면 안되기 때문에 카메라를 유지해야 한다
|
||||
if (Camera.main != null)
|
||||
{
|
||||
_loadingCamTargetTransform = Camera.main.transform;
|
||||
}
|
||||
|
||||
if (_loadingRoot != null)
|
||||
{
|
||||
_loadingRoot.SetActive(false);
|
||||
}
|
||||
|
||||
_isChangingScene = false;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Debug.Log("씬 전환 작업이 취소됨");
|
||||
_isChangingScene = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/02_Scripts/Managers/SceneLoadManager.cs.meta
Normal file
2
Assets/02_Scripts/Managers/SceneLoadManager.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e3e48e3fcdff9c843abca3c5583a43ad
|
||||
@@ -1,16 +1,226 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Audio;
|
||||
|
||||
public class SoundManager : MonoBehaviour
|
||||
{
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
public static SoundManager Instance { get; private set; }
|
||||
|
||||
[Header("Mixer")]
|
||||
[SerializeField] private AudioMixer _mainMixer; //메인 믹서 하나
|
||||
[SerializeField] private AudioMixerGroup _bgmGroup;
|
||||
[SerializeField] private AudioMixerGroup _sfxGroup;
|
||||
|
||||
[Header("BGM")]
|
||||
[SerializeField] private AudioSource _bgmSource; //BGM 전용 단일 오디오 소스
|
||||
[SerializeField, Range(0f, 1f)] private float _bgmVolume = 1f;
|
||||
[SerializeField] private float _bgmFadeDuration = 1.5f;
|
||||
[SerializeField] private AudioClip _defaultBgm; //평상시 BGM (시작 시 자동 재생)
|
||||
|
||||
//대화 전용 BGM 등 일시적으로 기본 BGM을 덮는 곡. null이면 기본 BGM 재생 중.
|
||||
private AudioClip _overrideBgm;
|
||||
|
||||
[Header("SFX Pool")]
|
||||
[SerializeField] private int _sfxPoolSize = 20; //시작 시 미리 생성할 SFX 소스 개수
|
||||
|
||||
//SFX 풀 - 대기중인 소스
|
||||
private readonly Queue<AudioSource> _sfxPool = new();
|
||||
|
||||
//BGM 전환(페이드)을 취소하기 위한 토큰.
|
||||
private CancellationTokenSource _bgmCts;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
|
||||
if (Instance == null)
|
||||
{
|
||||
Instance = this; //만들어진 자신을 인스턴스로 설정
|
||||
Initialize();
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(gameObject); //이미 인스턴스가 있으면 자신을 파괴
|
||||
}
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
private void Initialize()
|
||||
{
|
||||
|
||||
//BGM 소스 기본 설정
|
||||
if (_bgmSource != null)
|
||||
{
|
||||
_bgmSource.outputAudioMixerGroup = _bgmGroup;
|
||||
_bgmSource.playOnAwake = false;
|
||||
_bgmSource.loop = true;
|
||||
}
|
||||
|
||||
//SFX 풀 미리 생성
|
||||
for (int i = 0; i < _sfxPoolSize; i++)
|
||||
{
|
||||
_sfxPool.Enqueue(CreateSfxSource());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (_defaultBgm != null)
|
||||
StartBgmChange(_defaultBgm);
|
||||
}
|
||||
|
||||
//=========================== BGM ===========================
|
||||
|
||||
//기본 BGM 교체 (씬/분위기 전환용). 전용 BGM 재생 중이면 곡은 유지되고 복귀 시 반영됨.
|
||||
public void SetDefaultBGM(AudioClip clip)
|
||||
{
|
||||
_defaultBgm = clip;
|
||||
if (_overrideBgm == null)
|
||||
StartBgmChange(clip);
|
||||
}
|
||||
|
||||
//전용 BGM으로 전환 (대화 노드 등). 이미 같은 곡이 재생 중이면 아무것도 안 함.
|
||||
public void PlayOverrideBGM(AudioClip clip)
|
||||
{
|
||||
if (clip == null || _overrideBgm == clip) return;
|
||||
_overrideBgm = clip;
|
||||
StartBgmChange(clip);
|
||||
}
|
||||
|
||||
//전용 BGM을 멈추고 기본 BGM으로 복귀. 전용 BGM이 없었으면 아무것도 안 함.
|
||||
public void ClearOverrideBGM()
|
||||
{
|
||||
if (_overrideBgm == null) return;
|
||||
_overrideBgm = null;
|
||||
StartBgmChange(_defaultBgm);
|
||||
}
|
||||
|
||||
//진행 중이던 전환(페이드)을 취소하고 새 전환을 시작
|
||||
private void StartBgmChange(AudioClip clip)
|
||||
{
|
||||
if (_bgmSource == null) return;
|
||||
_bgmCts?.Cancel();
|
||||
_bgmCts?.Dispose();
|
||||
_bgmCts = new CancellationTokenSource();
|
||||
_ = ChangeBGM(clip, _bgmCts.Token);
|
||||
}
|
||||
|
||||
//페이드아웃 → 클립 교체 → 페이드인 (단일 소스라 순차 진행)
|
||||
private async Awaitable ChangeBGM(AudioClip clip, CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
//현재 재생중이면 먼저 페이드아웃
|
||||
if (_bgmSource.isPlaying)
|
||||
{
|
||||
await BGMFade(_bgmSource.volume, 0f, token);
|
||||
_bgmSource.Stop();
|
||||
}
|
||||
|
||||
if (clip == null) return; //BGM이 없으면 무음 유지
|
||||
|
||||
//새 BGM 페이드인
|
||||
_bgmSource.clip = clip;
|
||||
_bgmSource.volume = 0f;
|
||||
_bgmSource.Play();
|
||||
await BGMFade(0f, _bgmVolume, token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
//전환이 취소됨
|
||||
}
|
||||
}
|
||||
|
||||
//BGM 소스 볼륨을 from→to로 부드럽게 이동
|
||||
private async Awaitable BGMFade(float from, float to, CancellationToken token)
|
||||
{
|
||||
float t = 0f;
|
||||
while (t < _bgmFadeDuration)
|
||||
{
|
||||
t += Time.deltaTime;
|
||||
_bgmSource.volume = Mathf.Lerp(from, to, t / _bgmFadeDuration);
|
||||
await Awaitable.NextFrameAsync(token);
|
||||
}
|
||||
_bgmSource.volume = to;
|
||||
}
|
||||
|
||||
//=========================== SFX ===========================
|
||||
|
||||
//2D SFX 재생 (UI 사운드 등 위치 무관)
|
||||
public void PlaySFX(AudioClip clip, float volume = 1f)
|
||||
{
|
||||
if (clip == null) return;
|
||||
|
||||
AudioSource source = GetSfxSource();
|
||||
source.transform.localPosition = Vector3.zero;
|
||||
source.spatialBlend = 0f; //2D
|
||||
Play(source, clip, volume);
|
||||
_ = ReturnAfterPlay(source, clip.length); //재생이 끝나면 풀에 반납
|
||||
}
|
||||
|
||||
//3D SFX 재생 (VR 공간음향 - 특정 위치에서 들림)
|
||||
public void PlaySFXAt(AudioClip clip, Vector3 position, float volume = 1f)
|
||||
{
|
||||
if (clip == null) return;
|
||||
|
||||
AudioSource source = GetSfxSource();
|
||||
source.transform.position = position;
|
||||
source.spatialBlend = 1f; //3D
|
||||
Play(source, clip, volume);
|
||||
_ = ReturnAfterPlay(source, clip.length); //재생이 끝나면 풀에 반납
|
||||
}
|
||||
|
||||
private void Play(AudioSource source, AudioClip clip, float volume)
|
||||
{
|
||||
source.clip = clip;
|
||||
source.volume = volume;
|
||||
source.Play();
|
||||
}
|
||||
|
||||
//풀에서 사용 가능한 SFX 소스를 가져옴 (없으면 새로 생성 - 자동 증설)
|
||||
private AudioSource GetSfxSource()
|
||||
{
|
||||
AudioSource source = _sfxPool.Count > 0 ? _sfxPool.Dequeue() : CreateSfxSource();
|
||||
source.gameObject.SetActive(true);
|
||||
return source;
|
||||
}
|
||||
|
||||
//재생 길이만큼 대기 후 소스를 풀로 반납
|
||||
private async Awaitable ReturnAfterPlay(AudioSource source, float duration)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Awaitable.WaitForSecondsAsync(duration, this.destroyCancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return; //매니저 파괴 시 종료
|
||||
}
|
||||
|
||||
source.Stop();
|
||||
source.clip = null;
|
||||
source.gameObject.SetActive(false);
|
||||
_sfxPool.Enqueue(source);
|
||||
}
|
||||
|
||||
//SFX용 AudioSource를 자식 오브젝트로 생성
|
||||
private AudioSource CreateSfxSource()
|
||||
{
|
||||
GameObject go = new("SFX_Source");
|
||||
go.transform.SetParent(transform);
|
||||
go.SetActive(false);
|
||||
|
||||
AudioSource source = go.AddComponent<AudioSource>();
|
||||
source.outputAudioMixerGroup = _sfxGroup;
|
||||
source.playOnAwake = false;
|
||||
return source;
|
||||
}
|
||||
|
||||
//=========================== Mixer ===========================
|
||||
|
||||
//믹서 노출 파라미터로 볼륨 조절 (0~1 → dB 변환)
|
||||
public void SetVolume(string exposedParam, float normalized)
|
||||
{
|
||||
//0에 가까우면 -80dB(무음), 그 외에는 로그 스케일로 변환
|
||||
float db = normalized <= 0.0001f ? -80f : Mathf.Log10(normalized) * 20f;
|
||||
_mainMixer.SetFloat(exposedParam, db);
|
||||
}
|
||||
}
|
||||
111
Assets/02_Scripts/Managers/StoryManager.cs
Normal file
111
Assets/02_Scripts/Managers/StoryManager.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
// 이야기 상태(StoryState)의 유일한 접근 창구.
|
||||
// 진행도/호감도/이력의 조회·변경과 저장/로드를 담당한다.
|
||||
// 씬에 미리 배치하지 않아도 첫 접근 때 자동 생성된다.
|
||||
public class StoryManager : MonoBehaviour
|
||||
{
|
||||
public static StoryManager Instance { get; private set; }
|
||||
|
||||
private StoryState _state = new();
|
||||
|
||||
// 상태가 바뀔 때마다 발행 (호감도 게이지 등 UI 갱신용)
|
||||
public event Action Changed;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
|
||||
// ── 메인 진행도 ──────────────────────────────────────────────
|
||||
public int MainProgress
|
||||
{
|
||||
get => _state.MainProgress;
|
||||
set
|
||||
{
|
||||
if (_state.MainProgress == value) return;
|
||||
_state.MainProgress = value;
|
||||
Changed?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
// ── 호감도 ──────────────────────────────────────────────────
|
||||
public int GetAffection(CharacterData character)
|
||||
=> character != null && _state.Affection.TryGetValue(IdOf(character), out var v) ? v : 0;
|
||||
|
||||
public void AddAffection(CharacterData character, int delta)
|
||||
{
|
||||
if (character == null || delta == 0) return;
|
||||
_state.Affection[IdOf(character)] = GetAffection(character) + delta;
|
||||
Changed?.Invoke();
|
||||
}
|
||||
|
||||
// CharacterData의 Id를 키로 사용 (비어있으면 에셋 이름)
|
||||
private static string IdOf(CharacterData c) => string.IsNullOrEmpty(c.Id) ? c.name : c.Id;
|
||||
|
||||
// ── 대화 이력 ────────────────────────────────────────────────
|
||||
public bool IsDialogCompleted(string groupName) => _state.CompletedDialogs.Contains(groupName);
|
||||
|
||||
// 완료 기록. 처음 완료한 경우에만 true (진행도 보상 중복 방지용)
|
||||
public bool MarkDialogCompleted(string groupName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(groupName) || !_state.CompletedDialogs.Add(groupName))
|
||||
return false;
|
||||
Changed?.Invoke();
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── 선택 이력 ────────────────────────────────────────────────
|
||||
public bool HasChosen(string code) => _state.ChosenCodes.Contains(code);
|
||||
|
||||
public void RecordChoice(string code)
|
||||
{
|
||||
if (string.IsNullOrEmpty(code) || !_state.ChosenCodes.Add(code)) return;
|
||||
Changed?.Invoke();
|
||||
}
|
||||
|
||||
// ── 저장 / 로드 ──────────────────────────────────────────────
|
||||
// 플레이 시작 시엔 항상 빈 상태로 시작한다 (테스트 반복이 꼬이지 않게).
|
||||
// 이어하기를 만들 때 타이틀 화면 등에서 Load()를 호출하면 된다.
|
||||
private static string SavePath => Path.Combine(Application.persistentDataPath, "story_state.json");
|
||||
|
||||
public void Save() => File.WriteAllText(SavePath, _state.ToJson());
|
||||
|
||||
// 저장 파일이 있으면 불러온다. 성공 여부 반환.
|
||||
public bool Load()
|
||||
{
|
||||
if (!File.Exists(SavePath)) return false;
|
||||
try
|
||||
{
|
||||
var loaded = StoryState.FromJson(File.ReadAllText(SavePath));
|
||||
if (loaded == null) return false;
|
||||
_state = loaded;
|
||||
Changed?.Invoke();
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning($"[StoryManager] 저장 파일 로드 실패: {e.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 새 게임용 초기화 (저장 파일은 다음 Save 때 덮어써짐)
|
||||
public void ResetAll()
|
||||
{
|
||||
_state.Clear();
|
||||
Changed?.Invoke();
|
||||
}
|
||||
|
||||
// Enter Play Mode에서 도메인 리로드를 꺼도 이전 인스턴스 참조가 안 남게
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void ResetStatics() => Instance = null;
|
||||
}
|
||||
2
Assets/02_Scripts/Managers/StoryManager.cs.meta
Normal file
2
Assets/02_Scripts/Managers/StoryManager.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3f27662344dffe4a867f63cf1605d63
|
||||
@@ -1,135 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
// 이야기 진행 상태의 중앙 저장소 (메인 진행도 / 호감도 / 대화 이력 / 선택 이력).
|
||||
// DialogCondition이 대화 활성화 판정의 근거로 조회하고, DialogPlayer가 기록한다.
|
||||
//
|
||||
// 저장: Save()가 JSON 파일로 기록한다. DialogPlayer가 대화 완료 시마다 호출한다.
|
||||
// 로드: 플레이 시작 시 항상 빈 상태로 시작한다 (테스트 반복이 꼬이지 않게).
|
||||
// 이어하기를 만들 때 타이틀 화면 등에서 Load()를 호출하면 된다.
|
||||
public static class StoryState
|
||||
{
|
||||
private static int _mainProgress;
|
||||
private static readonly Dictionary<string, int> _affection = new();
|
||||
private static readonly HashSet<string> _completedDialogs = new();
|
||||
private static readonly HashSet<string> _chosenCodes = new();
|
||||
|
||||
// 상태가 바뀔 때마다 발행 (호감도 게이지 등 UI 갱신용)
|
||||
public static event Action Changed;
|
||||
|
||||
// ── 메인 진행도 ──────────────────────────────────────────────
|
||||
public static int MainProgress
|
||||
{
|
||||
get => _mainProgress;
|
||||
set
|
||||
{
|
||||
if (_mainProgress == value) return;
|
||||
_mainProgress = value;
|
||||
Changed?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
// ── 호감도 ──────────────────────────────────────────────────
|
||||
public static int GetAffection(CharacterData character)
|
||||
=> character != null && _affection.TryGetValue(IdOf(character), out var v) ? v : 0;
|
||||
|
||||
public static void AddAffection(CharacterData character, int delta)
|
||||
{
|
||||
if (character == null || delta == 0) return;
|
||||
_affection[IdOf(character)] = GetAffection(character) + delta;
|
||||
Changed?.Invoke();
|
||||
}
|
||||
|
||||
// CharacterData의 Id를 키로 사용 (비어있으면 에셋 이름)
|
||||
private static string IdOf(CharacterData c) => string.IsNullOrEmpty(c.Id) ? c.name : c.Id;
|
||||
|
||||
// ── 대화 이력 ────────────────────────────────────────────────
|
||||
public static bool IsDialogCompleted(string groupName) => _completedDialogs.Contains(groupName);
|
||||
|
||||
// 완료 기록. 처음 완료한 경우에만 true (진행도 보상 중복 방지용)
|
||||
public static bool MarkDialogCompleted(string groupName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(groupName) || !_completedDialogs.Add(groupName))
|
||||
return false;
|
||||
Changed?.Invoke();
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── 선택 이력 ────────────────────────────────────────────────
|
||||
public static bool HasChosen(string code) => _chosenCodes.Contains(code);
|
||||
|
||||
public static void RecordChoice(string code)
|
||||
{
|
||||
if (string.IsNullOrEmpty(code) || !_chosenCodes.Add(code)) return;
|
||||
Changed?.Invoke();
|
||||
}
|
||||
|
||||
// ── 저장 / 로드 ──────────────────────────────────────────────
|
||||
[Serializable]
|
||||
private class SaveData
|
||||
{
|
||||
public int MainProgress;
|
||||
public List<string> AffectionIds = new();
|
||||
public List<int> AffectionValues = new();
|
||||
public List<string> CompletedDialogs = new();
|
||||
public List<string> ChosenCodes = new();
|
||||
}
|
||||
|
||||
private static string SavePath => Path.Combine(Application.persistentDataPath, "story_state.json");
|
||||
|
||||
public static void Save()
|
||||
{
|
||||
var data = new SaveData { MainProgress = _mainProgress };
|
||||
foreach (var kvp in _affection)
|
||||
{
|
||||
data.AffectionIds.Add(kvp.Key);
|
||||
data.AffectionValues.Add(kvp.Value);
|
||||
}
|
||||
data.CompletedDialogs.AddRange(_completedDialogs);
|
||||
data.ChosenCodes.AddRange(_chosenCodes);
|
||||
File.WriteAllText(SavePath, JsonUtility.ToJson(data, prettyPrint: true));
|
||||
}
|
||||
|
||||
// 저장 파일이 있으면 불러온다. 성공 여부 반환.
|
||||
public static bool Load()
|
||||
{
|
||||
if (!File.Exists(SavePath)) return false;
|
||||
|
||||
var data = JsonUtility.FromJson<SaveData>(File.ReadAllText(SavePath));
|
||||
if (data == null) return false;
|
||||
|
||||
_mainProgress = data.MainProgress;
|
||||
_affection.Clear();
|
||||
for (int i = 0; i < data.AffectionIds.Count && i < data.AffectionValues.Count; i++)
|
||||
_affection[data.AffectionIds[i]] = data.AffectionValues[i];
|
||||
_completedDialogs.Clear();
|
||||
_completedDialogs.UnionWith(data.CompletedDialogs);
|
||||
_chosenCodes.Clear();
|
||||
_chosenCodes.UnionWith(data.ChosenCodes);
|
||||
Changed?.Invoke();
|
||||
return true;
|
||||
}
|
||||
|
||||
// 새 게임용 초기화 (저장 파일은 다음 Save 때 덮어써짐)
|
||||
public static void ResetAll()
|
||||
{
|
||||
_mainProgress = 0;
|
||||
_affection.Clear();
|
||||
_completedDialogs.Clear();
|
||||
_chosenCodes.Clear();
|
||||
Changed?.Invoke();
|
||||
}
|
||||
|
||||
// 플레이 시작마다 초기화 (Enter Play Mode에서 도메인 리로드를 꺼도 이전 값이 안 남게)
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void ResetOnPlay()
|
||||
{
|
||||
_mainProgress = 0;
|
||||
_affection.Clear();
|
||||
_completedDialogs.Clear();
|
||||
_chosenCodes.Clear();
|
||||
Changed = null;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b8ee6a43c79b269439e262f79b4d84c9
|
||||
TextScriptImporter:
|
||||
guid: 9720d44bfa10d5c4eaadac3921ee3d42
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
60
Assets/02_Scripts/Story/StoryState.cs
Normal file
60
Assets/02_Scripts/Story/StoryState.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
// 이야기 진행 상태 데이터 (메인 진행도 / 호감도 / 대화 이력 / 선택 이력).
|
||||
// 상태 보관과 JSON 변환만 담당한다 — 게임 로직에서는 StoryManager를 통해 접근할 것.
|
||||
public class StoryState
|
||||
{
|
||||
public int MainProgress;
|
||||
public readonly Dictionary<string, int> Affection = new(); // 캐릭터 Id → 호감도
|
||||
public readonly HashSet<string> CompletedDialogs = new(); // 완료한 DialogGroup 이름
|
||||
public readonly HashSet<string> ChosenCodes = new(); // 골랐던 선택지 Code
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
MainProgress = 0;
|
||||
Affection.Clear();
|
||||
CompletedDialogs.Clear();
|
||||
ChosenCodes.Clear();
|
||||
}
|
||||
|
||||
// ── JSON 변환 ────────────────────────────────────────────────
|
||||
// JsonUtility가 Dictionary/HashSet을 직렬화하지 못해 리스트로 바꿔 저장한다.
|
||||
[Serializable]
|
||||
private class JsonData
|
||||
{
|
||||
public int MainProgress;
|
||||
public List<string> AffectionIds = new();
|
||||
public List<int> AffectionValues = new();
|
||||
public List<string> CompletedDialogs = new();
|
||||
public List<string> ChosenCodes = new();
|
||||
}
|
||||
|
||||
public string ToJson()
|
||||
{
|
||||
var data = new JsonData { MainProgress = MainProgress };
|
||||
foreach (var kvp in Affection)
|
||||
{
|
||||
data.AffectionIds.Add(kvp.Key);
|
||||
data.AffectionValues.Add(kvp.Value);
|
||||
}
|
||||
data.CompletedDialogs.AddRange(CompletedDialogs);
|
||||
data.ChosenCodes.AddRange(ChosenCodes);
|
||||
return JsonUtility.ToJson(data, prettyPrint: true);
|
||||
}
|
||||
|
||||
// json이 유효하지 않으면 null 반환
|
||||
public static StoryState FromJson(string json)
|
||||
{
|
||||
var data = JsonUtility.FromJson<JsonData>(json);
|
||||
if (data == null) return null;
|
||||
|
||||
var state = new StoryState { MainProgress = data.MainProgress };
|
||||
for (int i = 0; i < data.AffectionIds.Count && i < data.AffectionValues.Count; i++)
|
||||
state.Affection[data.AffectionIds[i]] = data.AffectionValues[i];
|
||||
state.CompletedDialogs.UnionWith(data.CompletedDialogs);
|
||||
state.ChosenCodes.UnionWith(data.ChosenCodes);
|
||||
return state;
|
||||
}
|
||||
}
|
||||
BIN
Assets/04_Models/GlobalManagers.prefab
LFS
Normal file
BIN
Assets/04_Models/GlobalManagers.prefab
LFS
Normal file
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4295b27a1fa8a0b40871de6ca150df1f
|
||||
TextScriptImporter:
|
||||
guid: 025a2182ebe600a4cb21d80e5cda93df
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
@@ -1 +0,0 @@
|
||||
dumy
|
||||
142
Assets/11_Audio/MainMixer.mixer
Normal file
142
Assets/11_Audio/MainMixer.mixer
Normal file
@@ -0,0 +1,142 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!243 &-6754184347155731858
|
||||
AudioMixerGroupController:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: BGM
|
||||
m_AudioMixer: {fileID: 24100000}
|
||||
m_GroupID: 5f7a54950e921514597efabce8ad9538
|
||||
m_Children: []
|
||||
m_Volume: 51a45267944f16c48bd0c1b47674c9c1
|
||||
m_Pitch: 0432219ffb2de904eb51b08e7b4039c1
|
||||
m_Send: 00000000000000000000000000000000
|
||||
m_Effects:
|
||||
- {fileID: -1111153668822042352}
|
||||
m_UserColorIndex: 0
|
||||
m_Mute: 0
|
||||
m_Solo: 0
|
||||
m_BypassEffects: 0
|
||||
--- !u!244 &-1496916555325082197
|
||||
AudioMixerEffectController:
|
||||
m_ObjectHideFlags: 3
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name:
|
||||
m_EffectID: 68da5166ea21bb94cbec906753adafa5
|
||||
m_EffectName: Attenuation
|
||||
m_MixLevel: d6c6d88f5807fe9429cc6d012351dffb
|
||||
m_Parameters: []
|
||||
m_SendTarget: {fileID: 0}
|
||||
m_EnableWetMix: 0
|
||||
m_Bypass: 0
|
||||
--- !u!244 &-1111153668822042352
|
||||
AudioMixerEffectController:
|
||||
m_ObjectHideFlags: 3
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name:
|
||||
m_EffectID: 0178421faec543141ada37a78498bdff
|
||||
m_EffectName: Attenuation
|
||||
m_MixLevel: 49c759871323a304a86250f0bccc3e89
|
||||
m_Parameters: []
|
||||
m_SendTarget: {fileID: 0}
|
||||
m_EnableWetMix: 0
|
||||
m_Bypass: 0
|
||||
--- !u!241 &24100000
|
||||
AudioMixerController:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: MainMixer
|
||||
m_OutputGroup: {fileID: 0}
|
||||
m_MasterGroup: {fileID: 24300002}
|
||||
m_Snapshots:
|
||||
- {fileID: 24500006}
|
||||
m_StartSnapshot: {fileID: 24500006}
|
||||
m_SuspendThreshold: -80
|
||||
m_EnableSuspend: 1
|
||||
m_UpdateMode: 0
|
||||
m_ExposedParameters:
|
||||
- guid: 51a45267944f16c48bd0c1b47674c9c1
|
||||
name: BGMVolume
|
||||
- guid: e7d5a562f07ab704297aae40c95eb2d1
|
||||
name: SFXVolume
|
||||
m_AudioMixerGroupViews:
|
||||
- guids:
|
||||
- b7bf8477d3683044bab436ed49001ce7
|
||||
- 5f7a54950e921514597efabce8ad9538
|
||||
- 00fb9f3f9e7d98846a8c3ad043a8c007
|
||||
name: View
|
||||
m_CurrentViewIndex: 0
|
||||
m_TargetSnapshot: {fileID: 24500006}
|
||||
--- !u!243 &24300002
|
||||
AudioMixerGroupController:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Master
|
||||
m_AudioMixer: {fileID: 24100000}
|
||||
m_GroupID: b7bf8477d3683044bab436ed49001ce7
|
||||
m_Children:
|
||||
- {fileID: -6754184347155731858}
|
||||
- {fileID: 3851611846971593479}
|
||||
m_Volume: c4255cb7b5c23704d92df287bff3135e
|
||||
m_Pitch: 8a954a14461e63f428752c6604c6591d
|
||||
m_Send: 00000000000000000000000000000000
|
||||
m_Effects:
|
||||
- {fileID: 24400004}
|
||||
m_UserColorIndex: 0
|
||||
m_Mute: 0
|
||||
m_Solo: 0
|
||||
m_BypassEffects: 0
|
||||
--- !u!244 &24400004
|
||||
AudioMixerEffectController:
|
||||
m_ObjectHideFlags: 3
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name:
|
||||
m_EffectID: cef3cb8bb6e71844ca4a0a1a8a3b622b
|
||||
m_EffectName: Attenuation
|
||||
m_MixLevel: 8e4408bf002cf774d8781dc5d086c342
|
||||
m_Parameters: []
|
||||
m_SendTarget: {fileID: 0}
|
||||
m_EnableWetMix: 0
|
||||
m_Bypass: 0
|
||||
--- !u!245 &24500006
|
||||
AudioMixerSnapshotController:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Snapshot
|
||||
m_AudioMixer: {fileID: 24100000}
|
||||
m_SnapshotID: 65a440c2a830da0468487d2a92c4cb88
|
||||
m_FloatValues: {}
|
||||
m_TransitionOverrides: {}
|
||||
--- !u!243 &3851611846971593479
|
||||
AudioMixerGroupController:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: SFX
|
||||
m_AudioMixer: {fileID: 24100000}
|
||||
m_GroupID: 00fb9f3f9e7d98846a8c3ad043a8c007
|
||||
m_Children: []
|
||||
m_Volume: e7d5a562f07ab704297aae40c95eb2d1
|
||||
m_Pitch: 7f222167bc918324aad889222a57cc16
|
||||
m_Send: 00000000000000000000000000000000
|
||||
m_Effects:
|
||||
- {fileID: -1496916555325082197}
|
||||
m_UserColorIndex: 0
|
||||
m_Mute: 0
|
||||
m_Solo: 0
|
||||
m_BypassEffects: 0
|
||||
8
Assets/11_Audio/MainMixer.mixer.meta
Normal file
8
Assets/11_Audio/MainMixer.mixer.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a913377bdac526e4c942efee91cd9f36
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 24100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1 +0,0 @@
|
||||
dumy
|
||||
Binary file not shown.
Reference in New Issue
Block a user