2026-07-05 글로벌 프리팹 추가

This commit is contained in:
2026-07-05 22:21:59 +09:00
parent 2e62e509e9
commit 46c712d2f1
29 changed files with 787 additions and 180 deletions

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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) 입력까지 대기

View File

@@ -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)

View File

@@ -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));

View File

@@ -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();