From 0b02bacb3d7e2eab251a54d9b7bf316248c9ecfc Mon Sep 17 00:00:00 2001 From: sharedacc520k Date: Fri, 24 Jul 2026 18:20:02 +0900 Subject: [PATCH] first --- .gitattributes | 49 ++ .gitignore | 94 ++++ Assets/01_Scenes.meta | 8 + Assets/01_Scenes/SampleScene.unity | 3 + Assets/01_Scenes/SampleScene.unity.meta | 7 + Assets/02_Scripts.meta | 8 + Assets/02_Scripts/Communication.meta | 8 + Assets/02_Scripts/Communication/Dialog.meta | 8 + .../Dialog/AffectionRequirement.cs | 50 ++ .../Dialog/AffectionRequirement.cs.meta | 2 + .../Communication/Dialog/DialogChoice.cs | 9 + .../Communication/Dialog/DialogChoice.cs.meta | 2 + .../Communication/Dialog/DialogCondition.cs | 62 +++ .../Dialog/DialogCondition.cs.meta | 2 + .../Communication/Dialog/DialogGestureItem.cs | 55 ++ .../Dialog/DialogGestureItem.cs.meta | 2 + .../Communication/Dialog/DialogGroup.cs | 8 + .../Communication/Dialog/DialogGroup.cs.meta | 2 + .../Communication/Dialog/DialogNode.cs | 62 +++ .../Communication/Dialog/DialogNode.cs.meta | 2 + .../Communication/Dialog/DialogPlayer.cs | 520 ++++++++++++++++++ .../Communication/Dialog/DialogPlayer.cs.meta | 2 + .../Dialog/DialogVariableSetter.cs | 12 + .../Dialog/DialogVariableSetter.cs.meta | 2 + .../Communication/Dialog/DialogVariables.cs | 51 ++ .../Dialog/DialogVariables.cs.meta | 2 + .../Communication/Dialog/GraphTool.meta | 8 + .../Dialog/GraphTool/Editor.meta | 8 + .../GraphTool/Editor/DialogAffectionNode.cs | 83 +++ .../Editor/DialogAffectionNode.cs.meta | 2 + .../Dialog/GraphTool/Editor/DialogGraph.cs | 43 ++ .../GraphTool/Editor/DialogGraph.cs.meta | 2 + .../GraphTool/Editor/DialogGraphImporter.cs | 236 ++++++++ .../Editor/DialogGraphImporter.cs.meta | 2 + .../GraphTool/Editor/DialogGraphNode.cs | 33 ++ .../GraphTool/Editor/DialogGraphNode.cs.meta | 2 + .../Dialog/GraphTool/Editor/DialogLineNode.cs | 117 ++++ .../GraphTool/Editor/DialogLineNode.cs.meta | 2 + .../GraphTool/Editor/DialogShortText.cs | 18 + .../GraphTool/Editor/DialogShortText.cs.meta | 2 + .../GraphTool/Editor/DialogShortTextDrawer.cs | 22 + .../Editor/DialogShortTextDrawer.cs.meta | 2 + .../GraphTool/Editor/DialogStartNode.cs | 16 + .../GraphTool/Editor/DialogStartNode.cs.meta | 2 + .../Dialog/GraphTool/Editor/DialogText.cs | 17 + .../GraphTool/Editor/DialogText.cs.meta | 2 + .../GraphTool/Editor/DialogTextDrawer.cs | 30 + .../GraphTool/Editor/DialogTextDrawer.cs.meta | 2 + .../Communication/Dialog/GraphTool/README.md | 47 ++ .../Dialog/GraphTool/README.md.meta | 7 + .../Dialog/HiddenBranchResolver.cs | 60 ++ .../Dialog/HiddenBranchResolver.cs.meta | 2 + Assets/02_Scripts/Communication/Voice.meta | 8 + .../Voice/CharacterVoiceObject.cs | 18 + .../Voice/CharacterVoiceObject.cs.meta | 2 + .../02_Scripts/Communication/Voice/LipSync.cs | 109 ++++ .../Communication/Voice/LipSync.cs.meta | 2 + Assets/02_Scripts/ISceneInitializable.cs | 4 + Assets/02_Scripts/ISceneInitializable.cs.meta | 2 + Assets/02_Scripts/Managers.meta | 8 + Assets/02_Scripts/Managers/GameManager.cs | 23 + .../02_Scripts/Managers/GameManager.cs.meta | 2 + Assets/02_Scripts/Managers/InputManager.cs | 40 ++ .../02_Scripts/Managers/InputManager.cs.meta | 2 + Assets/02_Scripts/Managers/LocationManager.cs | 85 +++ .../Managers/LocationManager.cs.meta | 2 + .../02_Scripts/Managers/SceneLoadManager.cs | 156 ++++++ .../Managers/SceneLoadManager.cs.meta | 2 + Assets/02_Scripts/Managers/SoundManager.cs | 237 ++++++++ .../02_Scripts/Managers/SoundManager.cs.meta | 2 + Assets/02_Scripts/Managers/StoryManager.cs | 152 +++++ .../02_Scripts/Managers/StoryManager.cs.meta | 2 + Assets/02_Scripts/Story.meta | 8 + Assets/02_Scripts/Story/CharacterSlot.cs | 20 + Assets/02_Scripts/Story/CharacterSlot.cs.meta | 2 + Assets/02_Scripts/Story/Editor.meta | 8 + .../Story/Editor/StoryBeatDrawer.cs | 58 ++ .../Story/Editor/StoryBeatDrawer.cs.meta | 2 + Assets/02_Scripts/Story/LocationData.cs | 14 + Assets/02_Scripts/Story/LocationData.cs.meta | 2 + Assets/02_Scripts/Story/StoryBeat.cs | 29 + Assets/02_Scripts/Story/StoryBeat.cs.meta | 2 + Assets/02_Scripts/Story/StoryDatabase.cs | 51 ++ Assets/02_Scripts/Story/StoryDatabase.cs.meta | 2 + Assets/02_Scripts/Story/StoryState.cs | 65 +++ Assets/02_Scripts/Story/StoryState.cs.meta | 2 + Assets/02_Scripts/_Data.meta | 8 + Assets/02_Scripts/_Data/Character.meta | 8 + .../_Data/Character/CharacterData.cs | 9 + .../_Data/Character/CharacterData.cs.meta | 2 + Assets/02_Scripts/_Data/Communication.meta | 8 + .../_Data/Communication/Dialog.meta | 8 + .../Communication/Dialog/ExpressionData.cs | 10 + .../Dialog/ExpressionData.cs.meta | 2 + .../_Data/Communication/Dialog/GestureData.cs | 10 + .../Communication/Dialog/GestureData.cs.meta | 2 + .../02_Scripts/_Data/Communication/Voice.meta | 8 + .../_Data/Communication/Voice/VoiceClip.cs | 8 + .../Communication/Voice/VoiceClip.cs.meta | 2 + Assets/02_Scripts/_UI.meta | 8 + Assets/02_Scripts/_UI/Communication.meta | 8 + .../02_Scripts/_UI/Communication/ChoiceHud.cs | 138 +++++ .../_UI/Communication/ChoiceHud.cs.meta | 2 + .../_UI/Communication/DialogChoiceRow.cs | 26 + .../_UI/Communication/DialogChoiceRow.cs.meta | 2 + .../02_Scripts/_UI/Communication/DialogHud.cs | 49 ++ .../_UI/Communication/DialogHud.cs.meta | 2 + Assets/02_Scripts/_UI/View.meta | 8 + Assets/02_Scripts/_UI/View/LoadingScreen.cs | 15 + .../02_Scripts/_UI/View/LoadingScreen.cs.meta | 2 + Assets/02_Scripts/_Util.meta | 8 + Assets/02_Scripts/_Util/Editor.meta | 8 + .../02_Scripts/_Util/Editor/IntRangeDrawer.cs | 35 ++ .../_Util/Editor/IntRangeDrawer.cs.meta | 2 + Assets/02_Scripts/_Util/IntRange.cs | 13 + Assets/02_Scripts/_Util/IntRange.cs.meta | 2 + Assets/07_Data.meta | 8 + Assets/07_Data/Characters.meta | 8 + Assets/07_Data/Characters/TestCharacter.asset | 3 + .../Characters/TestCharacter.asset.meta | 8 + Assets/07_Data/DialogGraph.meta | 8 + Assets/07_Data/DialogGraph/TestDialog.dlg | 397 +++++++++++++ .../07_Data/DialogGraph/TestDialog.dlg.meta | 10 + Assets/07_Data/StoryDatabase.asset | 3 + Assets/07_Data/StoryDatabase.asset.meta | 8 + Assets/11_Audio.meta | 8 + Assets/11_Audio/MainMixer.mixer | 181 ++++++ Assets/11_Audio/MainMixer.mixer.meta | 8 + Assets/99_Settings.meta | 8 + Assets/99_Settings/InputSystem.meta | 8 + Assets/99_Settings/InputSystem/GameInput.cs | 312 +++++++++++ .../99_Settings/InputSystem/GameInput.cs.meta | 2 + .../InputSystem/GameInput.inputactions | 35 ++ .../InputSystem/GameInput.inputactions.meta | 14 + Assets/99_Settings/ProjectConfig.meta | 8 + .../ProjectConfig/DefaultVolumeProfile.asset | 3 + .../DefaultVolumeProfile.asset.meta | 8 + .../ProjectConfig/Mobile_RPAsset.asset | 3 + .../ProjectConfig/Mobile_RPAsset.asset.meta | 8 + .../ProjectConfig/Mobile_Renderer.asset | 3 + .../ProjectConfig/Mobile_Renderer.asset.meta | 8 + .../ProjectConfig/PC_RPAsset.asset | 3 + .../ProjectConfig/PC_RPAsset.asset.meta | 8 + .../ProjectConfig/PC_Renderer.asset | 3 + .../ProjectConfig/PC_Renderer.asset.meta | 8 + .../ProjectConfig/SampleSceneProfile.asset | 3 + .../SampleSceneProfile.asset.meta | 8 + ...niversalRenderPipelineGlobalSettings.asset | 3 + ...salRenderPipelineGlobalSettings.asset.meta | 8 + Packages/manifest.json | 50 ++ Packages/packages-lock.json | 478 ++++++++++++++++ ProjectSettings/AudioManager.asset | 3 + ProjectSettings/ClusterInputManager.asset | 3 + ProjectSettings/DynamicsManager.asset | 3 + ProjectSettings/EditorBuildSettings.asset | 3 + ProjectSettings/EditorSettings.asset | 3 + ProjectSettings/GraphicsSettings.asset | 3 + ProjectSettings/InputManager.asset | 3 + ProjectSettings/MemorySettings.asset | 3 + ProjectSettings/MultiplayerManager.asset | 3 + ProjectSettings/NavMeshAreas.asset | 3 + ProjectSettings/PackageManagerSettings.asset | 3 + ProjectSettings/Physics2DSettings.asset | 3 + .../PhysicsCoreProjectSettings2D.asset | 3 + ProjectSettings/PresetManager.asset | 3 + ProjectSettings/ProjectSettings.asset | 3 + ProjectSettings/ProjectVersion.txt | 2 + ProjectSettings/QualitySettings.asset | 3 + ProjectSettings/SceneTemplateSettings.json | 121 ++++ ProjectSettings/ShaderGraphSettings.asset | 3 + ProjectSettings/TagManager.asset | 3 + ProjectSettings/TimeManager.asset | 3 + ProjectSettings/URPProjectSettings.asset | 3 + ProjectSettings/UnityConnectSettings.asset | 3 + ProjectSettings/VFXManager.asset | 3 + ProjectSettings/VersionControlSettings.asset | 3 + ProjectSettings/XRSettings.asset | 3 + 177 files changed, 5172 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 Assets/01_Scenes.meta create mode 100644 Assets/01_Scenes/SampleScene.unity create mode 100644 Assets/01_Scenes/SampleScene.unity.meta create mode 100644 Assets/02_Scripts.meta create mode 100644 Assets/02_Scripts/Communication.meta create mode 100644 Assets/02_Scripts/Communication/Dialog.meta create mode 100644 Assets/02_Scripts/Communication/Dialog/AffectionRequirement.cs create mode 100644 Assets/02_Scripts/Communication/Dialog/AffectionRequirement.cs.meta create mode 100644 Assets/02_Scripts/Communication/Dialog/DialogChoice.cs create mode 100644 Assets/02_Scripts/Communication/Dialog/DialogChoice.cs.meta create mode 100644 Assets/02_Scripts/Communication/Dialog/DialogCondition.cs create mode 100644 Assets/02_Scripts/Communication/Dialog/DialogCondition.cs.meta create mode 100644 Assets/02_Scripts/Communication/Dialog/DialogGestureItem.cs create mode 100644 Assets/02_Scripts/Communication/Dialog/DialogGestureItem.cs.meta create mode 100644 Assets/02_Scripts/Communication/Dialog/DialogGroup.cs create mode 100644 Assets/02_Scripts/Communication/Dialog/DialogGroup.cs.meta create mode 100644 Assets/02_Scripts/Communication/Dialog/DialogNode.cs create mode 100644 Assets/02_Scripts/Communication/Dialog/DialogNode.cs.meta create mode 100644 Assets/02_Scripts/Communication/Dialog/DialogPlayer.cs create mode 100644 Assets/02_Scripts/Communication/Dialog/DialogPlayer.cs.meta create mode 100644 Assets/02_Scripts/Communication/Dialog/DialogVariableSetter.cs create mode 100644 Assets/02_Scripts/Communication/Dialog/DialogVariableSetter.cs.meta create mode 100644 Assets/02_Scripts/Communication/Dialog/DialogVariables.cs create mode 100644 Assets/02_Scripts/Communication/Dialog/DialogVariables.cs.meta create mode 100644 Assets/02_Scripts/Communication/Dialog/GraphTool.meta create mode 100644 Assets/02_Scripts/Communication/Dialog/GraphTool/Editor.meta create mode 100644 Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogAffectionNode.cs create mode 100644 Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogAffectionNode.cs.meta create mode 100644 Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogGraph.cs create mode 100644 Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogGraph.cs.meta create mode 100644 Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogGraphImporter.cs create mode 100644 Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogGraphImporter.cs.meta create mode 100644 Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogGraphNode.cs create mode 100644 Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogGraphNode.cs.meta create mode 100644 Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogLineNode.cs create mode 100644 Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogLineNode.cs.meta create mode 100644 Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogShortText.cs create mode 100644 Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogShortText.cs.meta create mode 100644 Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogShortTextDrawer.cs create mode 100644 Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogShortTextDrawer.cs.meta create mode 100644 Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogStartNode.cs create mode 100644 Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogStartNode.cs.meta create mode 100644 Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogText.cs create mode 100644 Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogText.cs.meta create mode 100644 Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogTextDrawer.cs create mode 100644 Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogTextDrawer.cs.meta create mode 100644 Assets/02_Scripts/Communication/Dialog/GraphTool/README.md create mode 100644 Assets/02_Scripts/Communication/Dialog/GraphTool/README.md.meta create mode 100644 Assets/02_Scripts/Communication/Dialog/HiddenBranchResolver.cs create mode 100644 Assets/02_Scripts/Communication/Dialog/HiddenBranchResolver.cs.meta create mode 100644 Assets/02_Scripts/Communication/Voice.meta create mode 100644 Assets/02_Scripts/Communication/Voice/CharacterVoiceObject.cs create mode 100644 Assets/02_Scripts/Communication/Voice/CharacterVoiceObject.cs.meta create mode 100644 Assets/02_Scripts/Communication/Voice/LipSync.cs create mode 100644 Assets/02_Scripts/Communication/Voice/LipSync.cs.meta create mode 100644 Assets/02_Scripts/ISceneInitializable.cs create mode 100644 Assets/02_Scripts/ISceneInitializable.cs.meta create mode 100644 Assets/02_Scripts/Managers.meta create mode 100644 Assets/02_Scripts/Managers/GameManager.cs create mode 100644 Assets/02_Scripts/Managers/GameManager.cs.meta create mode 100644 Assets/02_Scripts/Managers/InputManager.cs create mode 100644 Assets/02_Scripts/Managers/InputManager.cs.meta create mode 100644 Assets/02_Scripts/Managers/LocationManager.cs create mode 100644 Assets/02_Scripts/Managers/LocationManager.cs.meta create mode 100644 Assets/02_Scripts/Managers/SceneLoadManager.cs create mode 100644 Assets/02_Scripts/Managers/SceneLoadManager.cs.meta create mode 100644 Assets/02_Scripts/Managers/SoundManager.cs create mode 100644 Assets/02_Scripts/Managers/SoundManager.cs.meta create mode 100644 Assets/02_Scripts/Managers/StoryManager.cs create mode 100644 Assets/02_Scripts/Managers/StoryManager.cs.meta create mode 100644 Assets/02_Scripts/Story.meta create mode 100644 Assets/02_Scripts/Story/CharacterSlot.cs create mode 100644 Assets/02_Scripts/Story/CharacterSlot.cs.meta create mode 100644 Assets/02_Scripts/Story/Editor.meta create mode 100644 Assets/02_Scripts/Story/Editor/StoryBeatDrawer.cs create mode 100644 Assets/02_Scripts/Story/Editor/StoryBeatDrawer.cs.meta create mode 100644 Assets/02_Scripts/Story/LocationData.cs create mode 100644 Assets/02_Scripts/Story/LocationData.cs.meta create mode 100644 Assets/02_Scripts/Story/StoryBeat.cs create mode 100644 Assets/02_Scripts/Story/StoryBeat.cs.meta create mode 100644 Assets/02_Scripts/Story/StoryDatabase.cs create mode 100644 Assets/02_Scripts/Story/StoryDatabase.cs.meta create mode 100644 Assets/02_Scripts/Story/StoryState.cs create mode 100644 Assets/02_Scripts/Story/StoryState.cs.meta create mode 100644 Assets/02_Scripts/_Data.meta create mode 100644 Assets/02_Scripts/_Data/Character.meta create mode 100644 Assets/02_Scripts/_Data/Character/CharacterData.cs create mode 100644 Assets/02_Scripts/_Data/Character/CharacterData.cs.meta create mode 100644 Assets/02_Scripts/_Data/Communication.meta create mode 100644 Assets/02_Scripts/_Data/Communication/Dialog.meta create mode 100644 Assets/02_Scripts/_Data/Communication/Dialog/ExpressionData.cs create mode 100644 Assets/02_Scripts/_Data/Communication/Dialog/ExpressionData.cs.meta create mode 100644 Assets/02_Scripts/_Data/Communication/Dialog/GestureData.cs create mode 100644 Assets/02_Scripts/_Data/Communication/Dialog/GestureData.cs.meta create mode 100644 Assets/02_Scripts/_Data/Communication/Voice.meta create mode 100644 Assets/02_Scripts/_Data/Communication/Voice/VoiceClip.cs create mode 100644 Assets/02_Scripts/_Data/Communication/Voice/VoiceClip.cs.meta create mode 100644 Assets/02_Scripts/_UI.meta create mode 100644 Assets/02_Scripts/_UI/Communication.meta create mode 100644 Assets/02_Scripts/_UI/Communication/ChoiceHud.cs create mode 100644 Assets/02_Scripts/_UI/Communication/ChoiceHud.cs.meta create mode 100644 Assets/02_Scripts/_UI/Communication/DialogChoiceRow.cs create mode 100644 Assets/02_Scripts/_UI/Communication/DialogChoiceRow.cs.meta create mode 100644 Assets/02_Scripts/_UI/Communication/DialogHud.cs create mode 100644 Assets/02_Scripts/_UI/Communication/DialogHud.cs.meta create mode 100644 Assets/02_Scripts/_UI/View.meta create mode 100644 Assets/02_Scripts/_UI/View/LoadingScreen.cs create mode 100644 Assets/02_Scripts/_UI/View/LoadingScreen.cs.meta create mode 100644 Assets/02_Scripts/_Util.meta create mode 100644 Assets/02_Scripts/_Util/Editor.meta create mode 100644 Assets/02_Scripts/_Util/Editor/IntRangeDrawer.cs create mode 100644 Assets/02_Scripts/_Util/Editor/IntRangeDrawer.cs.meta create mode 100644 Assets/02_Scripts/_Util/IntRange.cs create mode 100644 Assets/02_Scripts/_Util/IntRange.cs.meta create mode 100644 Assets/07_Data.meta create mode 100644 Assets/07_Data/Characters.meta create mode 100644 Assets/07_Data/Characters/TestCharacter.asset create mode 100644 Assets/07_Data/Characters/TestCharacter.asset.meta create mode 100644 Assets/07_Data/DialogGraph.meta create mode 100644 Assets/07_Data/DialogGraph/TestDialog.dlg create mode 100644 Assets/07_Data/DialogGraph/TestDialog.dlg.meta create mode 100644 Assets/07_Data/StoryDatabase.asset create mode 100644 Assets/07_Data/StoryDatabase.asset.meta create mode 100644 Assets/11_Audio.meta create mode 100644 Assets/11_Audio/MainMixer.mixer create mode 100644 Assets/11_Audio/MainMixer.mixer.meta create mode 100644 Assets/99_Settings.meta create mode 100644 Assets/99_Settings/InputSystem.meta create mode 100644 Assets/99_Settings/InputSystem/GameInput.cs create mode 100644 Assets/99_Settings/InputSystem/GameInput.cs.meta create mode 100644 Assets/99_Settings/InputSystem/GameInput.inputactions create mode 100644 Assets/99_Settings/InputSystem/GameInput.inputactions.meta create mode 100644 Assets/99_Settings/ProjectConfig.meta create mode 100644 Assets/99_Settings/ProjectConfig/DefaultVolumeProfile.asset create mode 100644 Assets/99_Settings/ProjectConfig/DefaultVolumeProfile.asset.meta create mode 100644 Assets/99_Settings/ProjectConfig/Mobile_RPAsset.asset create mode 100644 Assets/99_Settings/ProjectConfig/Mobile_RPAsset.asset.meta create mode 100644 Assets/99_Settings/ProjectConfig/Mobile_Renderer.asset create mode 100644 Assets/99_Settings/ProjectConfig/Mobile_Renderer.asset.meta create mode 100644 Assets/99_Settings/ProjectConfig/PC_RPAsset.asset create mode 100644 Assets/99_Settings/ProjectConfig/PC_RPAsset.asset.meta create mode 100644 Assets/99_Settings/ProjectConfig/PC_Renderer.asset create mode 100644 Assets/99_Settings/ProjectConfig/PC_Renderer.asset.meta create mode 100644 Assets/99_Settings/ProjectConfig/SampleSceneProfile.asset create mode 100644 Assets/99_Settings/ProjectConfig/SampleSceneProfile.asset.meta create mode 100644 Assets/99_Settings/ProjectConfig/UniversalRenderPipelineGlobalSettings.asset create mode 100644 Assets/99_Settings/ProjectConfig/UniversalRenderPipelineGlobalSettings.asset.meta create mode 100644 Packages/manifest.json create mode 100644 Packages/packages-lock.json create mode 100644 ProjectSettings/AudioManager.asset create mode 100644 ProjectSettings/ClusterInputManager.asset create mode 100644 ProjectSettings/DynamicsManager.asset create mode 100644 ProjectSettings/EditorBuildSettings.asset create mode 100644 ProjectSettings/EditorSettings.asset create mode 100644 ProjectSettings/GraphicsSettings.asset create mode 100644 ProjectSettings/InputManager.asset create mode 100644 ProjectSettings/MemorySettings.asset create mode 100644 ProjectSettings/MultiplayerManager.asset create mode 100644 ProjectSettings/NavMeshAreas.asset create mode 100644 ProjectSettings/PackageManagerSettings.asset create mode 100644 ProjectSettings/Physics2DSettings.asset create mode 100644 ProjectSettings/PhysicsCoreProjectSettings2D.asset create mode 100644 ProjectSettings/PresetManager.asset create mode 100644 ProjectSettings/ProjectSettings.asset create mode 100644 ProjectSettings/ProjectVersion.txt create mode 100644 ProjectSettings/QualitySettings.asset create mode 100644 ProjectSettings/SceneTemplateSettings.json create mode 100644 ProjectSettings/ShaderGraphSettings.asset create mode 100644 ProjectSettings/TagManager.asset create mode 100644 ProjectSettings/TimeManager.asset create mode 100644 ProjectSettings/URPProjectSettings.asset create mode 100644 ProjectSettings/UnityConnectSettings.asset create mode 100644 ProjectSettings/VFXManager.asset create mode 100644 ProjectSettings/VersionControlSettings.asset create mode 100644 ProjectSettings/XRSettings.asset diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..b7d5dce --- /dev/null +++ b/.gitattributes @@ -0,0 +1,49 @@ +## Unity ## +*.cs diff=csharp text +*.cginc text +*.shader text +*.mat merge=unityyamlmerge eol=lf +*.physicsMaterial2D merge=unityyamlmerge eol=lf +*.physicMaterial merge=unityyamlmerge eol=lf +*.meta merge=unityyamlmerge eol=lf +*.controller merge=unityyamlmerge eol=lf +## git-lfs ## +*.anim filter=lfs diff=lfs merge=lfs -text +*.asset filter=lfs diff=lfs merge=lfs -text +*.prefab filter=lfs diff=lfs merge=lfs -text +*.unity filter=lfs diff=lfs merge=lfs -text +#Image +*.jpg filter=lfs diff=lfs merge=lfs -text +*.jpeg filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text +*.gif filter=lfs diff=lfs merge=lfs -text +*.psd filter=lfs diff=lfs merge=lfs -text +*.ai filter=lfs diff=lfs merge=lfs -text +#Audio +*.mp3 filter=lfs diff=lfs merge=lfs -text +*.wav filter=lfs diff=lfs merge=lfs -text +*.ogg filter=lfs diff=lfs merge=lfs -text +#Video +*.mp4 filter=lfs diff=lfs merge=lfs -text +*.mov filter=lfs diff=lfs merge=lfs -text +#3D Object +*.FBX filter=lfs diff=lfs merge=lfs -text +*.fbx filter=lfs diff=lfs merge=lfs -text +*.blend filter=lfs diff=lfs merge=lfs -text +*.obj filter=lfs diff=lfs merge=lfs -text +#ETC +*.a filter=lfs diff=lfs merge=lfs -text +*.exr filter=lfs diff=lfs merge=lfs -text +*.tga filter=lfs diff=lfs merge=lfs -text +*.pdf filter=lfs diff=lfs merge=lfs -text +*.zip filter=lfs diff=lfs merge=lfs -text +*.dll filter=lfs diff=lfs merge=lfs -text +*.unitypackage filter=lfs diff=lfs merge=lfs -text +*.aif filter=lfs diff=lfs merge=lfs -text +*.ttf filter=lfs diff=lfs merge=lfs -text +*.rns filter=lfs diff=lfs merge=lfs -text +*.reason filter=lfs diff=lfs merge=lfs -text +*.lxo filter=lfs diff=lfs merge=lfs -text +*.bytes filter=lfs diff=lfs merge=lfs -text +*.hdr filter=lfs diff=lfs merge=lfs -text +*.cubemap filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa16a98 --- /dev/null +++ b/.gitignore @@ -0,0 +1,94 @@ +# ---> Unity +# This .gitignore file should be placed at the root of your Unity project directory +# +# Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore +# +/[Ll]ibrary/ +/[Tt]emp/ +/[Oo]bj/ +/[Bb]uild/ +/[Bb]uilds/ +/[Ll]ogs/ +/[Uu]ser[Ss]ettings/ + +# MemoryCaptures can get excessive in size. +# They also could contain extremely sensitive data +/[Mm]emoryCaptures/ + +# Recordings can get excessive in size +/[Rr]ecordings/ + +# Uncomment this line if you wish to ignore the asset store tools plugin +# /[Aa]ssets/AssetStoreTools* + +# Autogenerated Jetbrains Rider plugin +/[Aa]ssets/Plugins/Editor/JetBrains* + +# Visual Studio cache directory +.vs/ + +# IDE settings (자동 생성 — 사용자별 설정) +.vscode/ +.idea/ + +# OS generated files +.DS_Store +Thumbs.db +desktop.ini + +# Crash dumps +*.dmp +mono_crash* + +# Gradle cache directory +.gradle/ + +# Autogenerated VS/MD/Consulo solution and project files +ExportedObj/ +.consulo/ +*.csproj +*.unityproj +*.sln +*.slnx +*.suo +*.tmp +*.user +*.userprefs +*.pidb +*.booproj +*.svd +*.pdb +*.mdb +*.opendb +*.VC.db + +# Unity3D generated meta files +*.pidb.meta +*.pdb.meta +*.mdb.meta + +# Unity3D generated file on crash reports +sysinfo.txt + +# Builds +*.apk +*.aab +*.unitypackage +*.unitypackage.meta +*.app + +# Crashlytics generated file +crashlytics-build.properties + +# Packed Addressables +/[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin* + +# Temporary auto-generated Android Assets +/[Aa]ssets/[Ss]treamingAssets/aa.meta +/[Aa]ssets/[Ss]treamingAssets/aa/* + +Assets/XR/AndroidXR/AndroidXRSettingsInitializer* +ProjectSettings/Packages/com.unity.learn.iet-framework/Settings.json +ProjectSettings/Packages/com.unity.testtools.codecoverage/Settings.json + +*.log diff --git a/Assets/01_Scenes.meta b/Assets/01_Scenes.meta new file mode 100644 index 0000000..0ef3534 --- /dev/null +++ b/Assets/01_Scenes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 15f3618f98b3afe47bcc89f8a9ac83ea +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/01_Scenes/SampleScene.unity b/Assets/01_Scenes/SampleScene.unity new file mode 100644 index 0000000..42200ef --- /dev/null +++ b/Assets/01_Scenes/SampleScene.unity @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0e30a6453a81773203498f22f8b671fb2dc5e1d19692aacb68a51afd3dd0294 +size 15897 diff --git a/Assets/01_Scenes/SampleScene.unity.meta b/Assets/01_Scenes/SampleScene.unity.meta new file mode 100644 index 0000000..9531828 --- /dev/null +++ b/Assets/01_Scenes/SampleScene.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 99c9720ab356a0642a771bea13969a05 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/02_Scripts.meta b/Assets/02_Scripts.meta new file mode 100644 index 0000000..930eccd --- /dev/null +++ b/Assets/02_Scripts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e4f7e411c38464a49b3b6f2309246976 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/02_Scripts/Communication.meta b/Assets/02_Scripts/Communication.meta new file mode 100644 index 0000000..4e2d344 --- /dev/null +++ b/Assets/02_Scripts/Communication.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fb2d49222f1c03f45aa898517c6904fe +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/02_Scripts/Communication/Dialog.meta b/Assets/02_Scripts/Communication/Dialog.meta new file mode 100644 index 0000000..2775930 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b5dd56c374fcdca41927418e4717e370 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/02_Scripts/Communication/Dialog/AffectionRequirement.cs b/Assets/02_Scripts/Communication/Dialog/AffectionRequirement.cs new file mode 100644 index 0000000..c338cee --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/AffectionRequirement.cs @@ -0,0 +1,50 @@ +using System; +using UnityEngine; + +// 호감도 비교 연산자. (호감도 [연산자] Value) +public enum AffectionCompare +{ + [InspectorName("≥")] AtLeast, + [InspectorName(">")] GreaterThan, + [InspectorName("≤")] AtMost, + [InspectorName("<")] LessThan, + [InspectorName("=")] Equal, + [InspectorName("≠")] NotEqual, +} + +// 다음 조건과 묶는 방식. AND가 OR보다 우선순위가 높다 (A and B or C → (A and B) or C). +public enum AffectionJoin +{ + [InspectorName("AND")] And, + [InspectorName("OR")] Or, +} + +// 호감도 분기 노드가 검사하는 조건 하나. (캐릭터, 연산자, 값) + 다음 조건과의 연결자. +// 여러 개를 이어 붙여 조건식을 만든다 — DialogNode.AffectionRequirements 참고. +[Serializable] +public class AffectionRequirement +{ + // 검사 대상 캐릭터. 비우면 대화 주인 NPC의 호감도를 본다. + public CharacterData Character; + + // 호감도를 Value와 어떻게 비교할지 + public AffectionCompare Compare = AffectionCompare.AtLeast; + + // 비교 기준값 + public int Value; + + // 다음 조건과 묶는 방식 (마지막 조건에서는 무시된다) + public AffectionJoin JoinWithNext = AffectionJoin.And; + + // 이 조건 하나의 성립 여부 + public bool IsMet(int affection) => Compare switch + { + AffectionCompare.AtLeast => affection >= Value, + AffectionCompare.GreaterThan => affection > Value, + AffectionCompare.AtMost => affection <= Value, + AffectionCompare.LessThan => affection < Value, + AffectionCompare.Equal => affection == Value, + AffectionCompare.NotEqual => affection != Value, + _ => false, + }; +} diff --git a/Assets/02_Scripts/Communication/Dialog/AffectionRequirement.cs.meta b/Assets/02_Scripts/Communication/Dialog/AffectionRequirement.cs.meta new file mode 100644 index 0000000..81b6012 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/AffectionRequirement.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f38182435621e88449adc22d629f6e6b \ No newline at end of file diff --git a/Assets/02_Scripts/Communication/Dialog/DialogChoice.cs b/Assets/02_Scripts/Communication/Dialog/DialogChoice.cs new file mode 100644 index 0000000..7bb82ad --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/DialogChoice.cs @@ -0,0 +1,9 @@ +using System; + +[Serializable] +public class DialogChoice +{ + public DialogNode DestinationNode; + public string ChoiceText; + public string Code; // 선택 시 기록/식별용 코드 (선택 입력, 영문 권장) +} diff --git a/Assets/02_Scripts/Communication/Dialog/DialogChoice.cs.meta b/Assets/02_Scripts/Communication/Dialog/DialogChoice.cs.meta new file mode 100644 index 0000000..822d67a --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/DialogChoice.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: bd5ea8e7c904dd24e967d91b86004efb \ No newline at end of file diff --git a/Assets/02_Scripts/Communication/Dialog/DialogCondition.cs b/Assets/02_Scripts/Communication/Dialog/DialogCondition.cs new file mode 100644 index 0000000..d7df5f1 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/DialogCondition.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +// DialogGroup 하나가 활성화되기 위한 조건 묶음. 모든 항목을 만족해야 한다(AND). +// 진행도/호감도는 범위(양끝 포함)로 지정한다. 기본 범위 = 조건 없음. 트리거 목록은 비워두면 조건 없음. +[Serializable] +public class DialogCondition : ISerializationCallbackReceiver +{ + const int MaxProgressDefault = 9999; + const int MaxAffectionDefault = 100; + + [Tooltip("메인 진행도가 이 범위 안이어야 함 (양끝 포함). 기본 0~9999 = 조건 없음")] + public IntRange MainProgress = new(0, MaxProgressDefault); + + [Tooltip("이 NPC의 호감도가 이 범위 안이어야 함 (양끝 포함). 기본 0~100 = 조건 없음. " + + "예: 0~10 = 호감도가 낮을 때만, 50~100 = 호감도가 쌓인 뒤에만")] + public IntRange Affection = new(0, MaxAffectionDefault); + + [Tooltip("켜져 있어야 하는 트리거 Id들 (전부 필요). StoryManager.SetTrigger로 켠다")] + public List RequiredTriggerIds = new(); + + [Tooltip("켜지면 이 대화가 비활성화되는 트리거 Id들 (하나라도 켜졌으면 비활성화). " + + "예: 특정 사건 이후엔 더 못 보는 대사")] + public List ExcludedTriggerIds = new(); + + // 인스펙터에서 리스트에 새로 추가된 항목은 필드 초기화식이 무시되고 전부 0으로 생성된다. + // 그대로면 범위가 0~0(사실상 잠긴 대화)이 되므로, 처음 만들어진 인스턴스에만 기본 범위를 넣어준다. + [SerializeField, HideInInspector] private bool _initialized = true; + + public void OnBeforeSerialize() { } + + public void OnAfterDeserialize() + { + if (_initialized) return; + _initialized = true; + MainProgress = new IntRange(0, MaxProgressDefault); + Affection = new IntRange(0, MaxAffectionDefault); + } + + // affectionTarget: 호감도 조건을 검사할 캐릭터 (보통 대화를 거는 NPC 자신) + public bool IsMet(CharacterData affectionTarget) + { + var story = StoryManager.Instance; + + if (!MainProgress.Contains(story.MainProgress)) + return false; + + if (!Affection.Contains(story.GetAffection(affectionTarget))) + return false; + + foreach (var id in RequiredTriggerIds) + if (!string.IsNullOrEmpty(id) && !story.HasTrigger(id)) + return false; + + foreach (var id in ExcludedTriggerIds) + if (!string.IsNullOrEmpty(id) && story.HasTrigger(id)) + return false; + + return true; + } +} diff --git a/Assets/02_Scripts/Communication/Dialog/DialogCondition.cs.meta b/Assets/02_Scripts/Communication/Dialog/DialogCondition.cs.meta new file mode 100644 index 0000000..d6636ac --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/DialogCondition.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1f307619d54e9a74990a68cda19bff5b \ No newline at end of file diff --git a/Assets/02_Scripts/Communication/Dialog/DialogGestureItem.cs b/Assets/02_Scripts/Communication/Dialog/DialogGestureItem.cs new file mode 100644 index 0000000..e235cdf --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/DialogGestureItem.cs @@ -0,0 +1,55 @@ +using UnityEngine; +using UnityEngine.Events; + +// 대화 중 "건네기/터치" 제스처를 수행하는 오브젝트(꽃다발·김치·손 등)에 붙인다. +// 이 오브젝트가 NPC의 받는 존(_targetTag 태그의 트리거)에 닿으면, 자신의 GestureKey로 히든 분기를 발동시킨다. +// +// 어떤 아이템인지는 GestureKey로 구분된다: +// - 노드가 HiddenGestureKey="kimchi"로 무장 → 김치(GestureKey="kimchi")만 발동, 꽃다발("bouquet")은 무시. +// 그래서 "김치를 줘야 하는데 꽃다발을 주면 이벤트가 안 진행"이 자동으로 보장된다. +// +// 콜라이더가 있는 오브젝트에 붙일 것 (잡는 아이템은 보통 XRGrabInteractable이 있는 루트). +// 존은 무장 여부와 무관하게 항상 닿을 수 있지만, 무장 안 된 순간에 닿으면 아무 일도 안 일어난다. +public class DialogGestureItem : MonoBehaviour +{ + [Tooltip("이 오브젝트의 제스처 키. 대화 노드의 HiddenGestureKey와 일치할 때만 발동 (예: kimchi, bouquet, slap)")] + [SerializeField] private string _gestureKey; + + [Tooltip("이 태그를 가진 트리거(NPC 받는 존)에 닿아야 발동. 비우면 태그 무시(아무 트리거나)")] + [SerializeField] private string _targetTag = "GiveZone"; + + [Header("Consume (건넨 뒤 사라지게)")] + [Tooltip("켜면 발동 시 이 오브젝트가 사라진다(건넨 연출). 손처럼 사라지면 안 되는 건 꺼둘 것")] + [SerializeField] private bool _consumeOnGiven = true; + + [Tooltip("사라지는 방식: 켜면 완전 파괴(Destroy), 끄면 비활성화(SetActive false — 재사용 가능)")] + [SerializeField] private bool _destroy = true; + + [Tooltip("사라지기 직전 호출 (파티클·사운드 연출용)")] + [SerializeField] private UnityEvent _onConsumed; + + private void OnTriggerEnter(Collider other) + { + if (!string.IsNullOrEmpty(_targetTag) && !other.CompareTag(_targetTag)) return; + + // 노드가 이 키로 무장 중일 때만 true — 그때만 소비한다(엉뚱한 타이밍/엉뚱한 아이템은 안 사라짐). + if (!HiddenBranchResolver.Fire(_gestureKey)) return; + + if (_consumeOnGiven) + Consume(); + } + + private void Consume() + { + // 스크립트가 자식 콜라이더에 붙어 있어도 아이템 전체(Rigidbody 루트)를 처리 + var body = GetComponentInParent(); + var target = body != null ? body.gameObject : gameObject; + + _onConsumed?.Invoke(); + + if (_destroy) + Destroy(target); // XRGrabInteractable도 파괴 시 손에서 자동 해제됨 + else + target.SetActive(false); // 비활성화도 XRI가 선택을 취소해 손에서 놓게 됨 + } +} diff --git a/Assets/02_Scripts/Communication/Dialog/DialogGestureItem.cs.meta b/Assets/02_Scripts/Communication/Dialog/DialogGestureItem.cs.meta new file mode 100644 index 0000000..6a934b0 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/DialogGestureItem.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c11477ec44fef124bb7279924beb66af \ No newline at end of file diff --git a/Assets/02_Scripts/Communication/Dialog/DialogGroup.cs b/Assets/02_Scripts/Communication/Dialog/DialogGroup.cs new file mode 100644 index 0000000..70127b7 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/DialogGroup.cs @@ -0,0 +1,8 @@ +using UnityEngine; + +[CreateAssetMenu(menuName = "Communication/Dialog Group")] +public class DialogGroup : ScriptableObject +{ + public string DialogGroupName; + public DialogNode StartNode; +} diff --git a/Assets/02_Scripts/Communication/Dialog/DialogGroup.cs.meta b/Assets/02_Scripts/Communication/Dialog/DialogGroup.cs.meta new file mode 100644 index 0000000..3325b9e --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/DialogGroup.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 72ef984dbd5eb29498ece3d2dae297ea \ No newline at end of file diff --git a/Assets/02_Scripts/Communication/Dialog/DialogNode.cs b/Assets/02_Scripts/Communication/Dialog/DialogNode.cs new file mode 100644 index 0000000..d0b4991 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/DialogNode.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using UnityEngine; + +[CreateAssetMenu(menuName = "Communication/Dialog Node")] +public class DialogNode : ScriptableObject +{ + [Header("Speaker")] + public CharacterData Speaker; + + [Tooltip("비우면 Speaker의 이름 그대로, 채우면 이 이름으로 표시 (예: ???)")] + public string SpeakerNameOverride; + + [Header("Content")] + [TextArea(2,5)] public string TalkText; + public GestureData Gesture; + public ExpressionData Expression; + public VoiceClip Voice; + // 진행은 항상 플레이어 입력(OnDialogNext)으로 넘어간다. + + [Header("Presentation")] + public AudioClip Bgm; // 있으면 이 대사부터 전용 BGM 재생, 비어있으면 기본 BGM으로 복귀 + + [Header("Flow")] + public DialogNode Next; // 선택지 없을 때 자동으로 갈 노드 + public List Choices; // 있으면 플레이어 선택 대기 + + [Header("Hidden Branch")] + [Tooltip("이 대사가 재생되는 동안 특정 행동(제스처)을 하면 Next/선택지 대신 이 노드로 몰래 분기. " + + "비우면 히든 분기 없음 — 평소엔 존재하지 않는 것처럼 보인다")] + public DialogNode HiddenBranch; + + [Tooltip("히든 분기를 여는 제스처 키 (아이템의 DialogGestureItem의 Gesture Key와 일치). " + + "예: kimchi, bouquet, slap. 비우면 아무 제스처 오브젝트나 발동 가능")] + public string HiddenGestureKey; + + [Tooltip("히든 분기를 탔을 때 기록할 선택지 Code (선택지의 Code와 동일한 용도). " + + "비우면 기록 안 함")] + public string HiddenCode; + + [Header("Affection Branch")] + [Tooltip("켜면 이 노드는 대사 없이 호감도 조건만 검사해 즉시 라우팅한다: " + + "조건을 만족하면 AffectionPassBranch로, 아니면 Next로")] + public bool AffectionCheck; + + [Tooltip("검사할 호감도 조건들 (캐릭터·연산자·값 + 다음 조건과의 and/or). " + + "캐릭터를 비우면 대화 주인 NPC. AND가 OR보다 우선. 비어 있으면 무조건 통과")] + public List AffectionRequirements = new(); + + [Tooltip("조건을 만족했을 때 갈 노드 (실패하면 Next로)")] + public DialogNode AffectionPassBranch; + + [Header("ChoiceQuestion")] + [TextArea(2,5)] public string ChoiceQuestion; + + [Header("Story")] + [Tooltip("0이 아니면 이 노드 재생 시 화자(비우면 대화 주인 NPC)의 호감도를 이만큼 증감")] + public int Affection; + + [Tooltip("0이 아니면 이 노드 재생 시 메인 진행도를 이만큼 증가. " + + "분기 끝 노드마다 다른 값을 주면 선택지에 따라 진행도를 다르게 줄 수 있다")] + public int Progress; +} diff --git a/Assets/02_Scripts/Communication/Dialog/DialogNode.cs.meta b/Assets/02_Scripts/Communication/Dialog/DialogNode.cs.meta new file mode 100644 index 0000000..00eb4d7 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/DialogNode.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: fb30abdb4b5671b458096d48d11c7f27 \ No newline at end of file diff --git a/Assets/02_Scripts/Communication/Dialog/DialogPlayer.cs b/Assets/02_Scripts/Communication/Dialog/DialogPlayer.cs new file mode 100644 index 0000000..a9d97be --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/DialogPlayer.cs @@ -0,0 +1,520 @@ +using System; +using System.Collections.Generic; +using UnityEngine.InputSystem; +using UnityEngine; + +[RequireComponent(typeof(CharacterVoiceObject))] +public class DialogPlayer : MonoBehaviour +{ + // 대화 후보는 인스펙터가 아니라 StoryDatabase에서 온다 — + // 현재 장소(LocationManager.Current) + 이 캐릭터의 비트 중 조건을 만족하는 것들. + // 여럿이면 선택 메뉴가 뜨고, DB 목록에서 위에 있을수록 우선순위가 높다. + + private CharacterVoiceObject _voice; // 이 NPC의 캐릭터 정보 (호감도 조건 대상) + private Animator _animator; + + // 대화 중 제스처/표정을 재생한 Animator들의 원래 상태 — 대화 종료 시 전부 복원. + // (끼어든 다른 NPC의 Animator도 포함되므로 딕셔너리로 추적한다) + private readonly Dictionary _touchedAnimators = new(); + public bool IsPlaying { get; private set; } + + // 지금 실제 대사를 재생 중인 DialogPlayer (전역 1개) — 이때 다른 NPC의 Play()는 무시된다. + // 선택 메뉴만 떠 있는 단계는 여기 안 잡힌다 (그 경우는 새 NPC가 메뉴를 취소하고 시작). + private static DialogPlayer _entryInProgress; + + // ── 전역 대화 진행 신호 (대화 중 월드 상호작용 차단용) ──────── + // 선택 메뉴 단계부터 대사 종료까지, 어느 NPC든 대화가 진행 중이면 true. + // DialogInteractionBlocker가 구독해서 인터랙터를 잠근다. + public static bool IsAnyActive => _activeCount > 0; + public static event Action AnyActiveChanged; + private static int _activeCount; + + private static void PushActive() + { + if (++_activeCount == 1) AnyActiveChanged?.Invoke(true); + } + + private static void PopActive() + { + if (_activeCount <= 0) return; + if (--_activeCount == 0) AnyActiveChanged?.Invoke(false); + } + + // Enter Play Mode에서 도메인 리로드를 꺼도 이전 상태가 안 남게 + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + private static void ResetStatics() + { + _entryInProgress = null; + _activeCount = 0; + AnyActiveChanged = null; + } + + private void Awake() + { + _voice = GetComponent(); + _animator = GetComponentInChildren(); // 화자를 못 찾을 때의 폴백 Animator + } + + private void OnDestroy() + { + // 재생 도중 파괴(씬 전환 등)돼도 전역 잠금이 남지 않도록 + if (_entryInProgress == this) _entryInProgress = null; + } + + public async Awaitable Play() + { + if (IsPlaying) return; + + // 다른 NPC가 실제 대사를 재생 중이면 무시 — 대화 중 다른 NPC 상호작용 차단 + if (_entryInProgress != null) return; + + // 다른 NPC의 선택 메뉴가 떠 있으면 먼저 취소한다. + // (취소된 쪽의 Play가 이 자리에서 HUD 숨김 등 정리를 마친 뒤에 이쪽이 시작된다) + if (ChoiceHud.Instance != null) + ChoiceHud.Instance.CancelPending(); + + // 선택 메뉴가 떠 있는 동안에도 재진입을 막아야 하므로 여기서 잠근다. + IsPlaying = true; + PushActive(); + try + { + var playable = FindPlayableBeats(); + if (playable.Count == 0) + { + Debug.Log($"[DialogPlayer] 조건에 맞는 대화가 없음: {name}"); + return; + } + + StoryBeat beat = playable.Count == 1 ? playable[0] : await SelectBeat(playable); + if (beat == null) return; // 선택 대기 중 취소됨 (다른 NPC와 대화 시작, 씬 전환 등) + + _entryInProgress = this; // 여기서부터 실제 대사 재생 — 끝날 때까지 다른 NPC 상호작용 무시 + await PlayBeat(beat); + } + catch (OperationCanceledException) + { + // 대화 도중 선택지 대기가 취소됨 (씬 전환으로 ChoiceHud 비활성화 등) — PlayBeat의 finally에서 정리됨 + } + finally + { + if (_entryInProgress == this) _entryInProgress = null; + IsPlaying = false; + PopActive(); + } + } + + // 조건/선택 메뉴를 건너뛰고 특정 대화 그룹을 강제로 시작한다 (EventZoneTrigger 등 UnityEvent 연결용). + // DB에 등록된 비트면 그 설정(진행도 보상 등)을 그대로 쓰고, 없으면 임시 비트로 재생한다. + // 완료 여부는 검사하지 않으므로(강제) 1회성이 필요하면 호출하는 쪽(존의 Trigger Once)에서 보장할 것. + public void PlayGroup(DialogGroup group) + { + _ = PlayGroupForced(group); + } + + private async Awaitable PlayGroupForced(DialogGroup group) + { + if (group == null || IsPlaying) return; + if (_entryInProgress != null) return; // 다른 NPC가 대사 재생 중 + + // 다른 NPC의 선택 메뉴가 떠 있으면 먼저 취소 + if (ChoiceHud.Instance != null) + ChoiceHud.Instance.CancelPending(); + + IsPlaying = true; + PushActive(); + try + { + // DB에 등록된 비트가 있으면 그 설정(진행도 보상 등)을 그대로 사용, 없으면 임시 비트로 재생 + var lm = LocationManager.Instance; + StoryBeat beat = lm != null && lm.Database != null ? lm.Database.FindBeat(group) : null; + beat ??= new StoryBeat { Group = group }; + + _entryInProgress = this; + await PlayBeat(beat); + } + catch (OperationCanceledException) + { + // 씬 전환 등으로 취소됨 — PlayBeat의 finally에서 정리됨 + } + finally + { + if (_entryInProgress == this) _entryInProgress = null; + IsPlaying = false; + PopActive(); + } + } + + // 현재 장소에서 이 캐릭터가 걸 수 있는 대화 비트들 (DB 목록 순서 = 우선순위) + private List FindPlayableBeats() + { + var lm = LocationManager.Instance; + if (lm == null || lm.Database == null) + { + Debug.LogWarning($"[DialogPlayer] LocationManager 또는 StoryDatabase가 없음: {name}"); + return new List(); + } + return lm.Database.GetPlayableBeats(lm.Current, _voice.Character); + } + + // 재생 가능한 대화가 여럿일 때 ChoiceHud로 플레이어에게 고르게 한다. 취소되면 null. + private async Awaitable SelectBeat(List playable) + { + if (ChoiceHud.Instance == null) + return playable[0]; // 선택 UI가 없으면 기존처럼 최상단 우선 + + var options = new List(playable.Count); + foreach (var beat in playable) + { + string label = !string.IsNullOrWhiteSpace(beat.MenuLabel) + ? beat.MenuLabel + : beat.Group.DialogGroupName; + options.Add(new DialogChoice { ChoiceText = label }); // Code 없음 → 선택 이력에 기록 안 됨 + } + + try + { + int picked = await ChoiceHud.Instance.Show(null, options); + return playable[picked]; + } + catch (OperationCanceledException) + { + // 대기 중 ChoiceHud가 비활성화됨(씬 전환 등) — 재생하지 않음 + return null; + } + } + + private async Awaitable PlayBeat(StoryBeat beat) + { + try + { + var node = beat.Group.StartNode; + int routingHops = 0; // 연속 라우팅 횟수 — 라우팅 노드끼리 순환하면 대기 없는 무한 루프가 되므로 차단 + while (node != null) + { + // 호감도 라우팅 노드 — 대사 없이 즉시 분기 (플레이어에겐 분기 자체가 보이지 않는다) + if (node.AffectionCheck) + { + if (++routingHops > 100) + { + Debug.LogError($"[DialogPlayer] 라우팅 노드가 순환합니다 — 대화 중단: {beat.Group.name}"); + break; + } + node = IsAffectionMet(node) ? node.AffectionPassBranch : node.Next; + continue; + } + routingHops = 0; // 실제 대사 노드에 도달 — 카운터 리셋 + + // 이 노드가 히든 분기를 가지면, 노드가 재생되는 동안 제스처 감시를 무장한다. + // (무장 안 된 노드는 아래 대기/선택이 기존과 완전히 동일하게 동작) + bool armed = node.HiddenBranch != null; + if (armed) HiddenBranchResolver.Arm(node.HiddenGestureKey); + try + { + bool diverted = await PlayNode(node); // 대사 표시 + 대기(무장 시 제스처 감시 포함) + + if (diverted) + { + RecordHiddenChoice(node); + node = node.HiddenBranch; // 대사 도중 제스처 발동 → 몰래 분기 + } + else if (node.Choices != null && node.Choices.Count > 0) + { + int picked = await WaitForChoice(node); // 메뉴(무장 시 제스처 감시 포함) + if (picked == DivertIndex) + { + RecordHiddenChoice(node); + node = node.HiddenBranch; // 메뉴 도중 제스처 발동 → 메뉴엔 없던 히든 분기 + } + else + { + RecordChoice(node, picked); + node = node.Choices[picked].DestinationNode; + } + } + else + { + node = node.Next; + } + } + finally + { + if (armed) HiddenBranchResolver.Disarm(); + } + } + + // 여기까지 왔으면 자연 종료(끝까지 재생) — 이때만 완료로 기록한다. + // (중간에 오브젝트 파괴 등으로 끊기면 예외로 빠져나가 기록되지 않음) + var story = StoryManager.Instance; + bool firstTime = story.MarkDialogCompleted(beat.Group.name); + if (firstTime && beat.ProgressOnComplete > 0) + story.MainProgress += beat.ProgressOnComplete; + story.Save(); + + Debug.Log($"[DialogPlayer] 대화 종료: {beat.Group.name}"); + } + finally + { + if (DialogHud.Instance != null) + DialogHud.Instance.Hide(); + if (SoundManager.Instance != null) + SoundManager.Instance.ClearOverrideBGM(); // 대화가 끝나면 기본 BGM으로 복귀 + RestoreDefaultAnimations(); + } + } + + // 대화 중 제스처/표정을 재생했던 모든 Animator(끼어든 NPC 포함)를 원래 상태로 복원 + private void RestoreDefaultAnimations() + { + foreach (var kvp in _touchedAnimators) + { + var anim = kvp.Key; + if (anim == null) continue; + anim.CrossFade(kvp.Value.gestureHash, 0.3f, 0, normalizedTimeOffset: 0f); + if (kvp.Value.hasExpression) + anim.CrossFade(kvp.Value.expressionHash, 0.3f, 1, normalizedTimeOffset: 0f); + } + _touchedAnimators.Clear(); + } + + // 처음 건드리는 Animator면 현재 상태를 기억해 둔다 (대화 종료 시 복원 기준) + private void CaptureInitialAnimState(Animator anim) + { + if (_touchedAnimators.ContainsKey(anim)) return; + bool hasExpression = anim.layerCount > 1; + _touchedAnimators[anim] = ( + anim.GetCurrentAnimatorStateInfo(0).fullPathHash, + hasExpression ? anim.GetCurrentAnimatorStateInfo(1).fullPathHash : 0, + hasExpression); + } + + // 반환: true = 대기 도중 히든 제스처가 발동됨(→ HiddenBranch로 분기), false = 평범하게 진행 + private async Awaitable PlayNode(DialogNode node) + { + // DialogHud에 대사 표시 (이름은 노드 오버라이드 우선) + if (DialogHud.Instance != null) + DialogHud.Instance.Show(node.Speaker, node.TalkText, node.SpeakerNameOverride); + + // 호감도 증감 — 화자 기준, 화자가 비어 있으면 대화 주인 NPC + if (node.Affection != 0) + { + var affectionTarget = node.Speaker != null ? node.Speaker : _voice.Character; + StoryManager.Instance.AddAffection(affectionTarget, node.Affection); + } + + // 메인 진행도 증가 — 분기 끝 노드마다 다른 값을 주면 선택지에 따라 진행도가 달라진다 + if (node.Progress != 0) + StoryManager.Instance.MainProgress += node.Progress; + + // 전용 BGM: 설정돼 있으면 교체, 비어 있으면 기본 BGM으로 복귀 + if (SoundManager.Instance != null) + { + if (node.Bgm != null) + SoundManager.Instance.PlayOverrideBGM(node.Bgm); + else + SoundManager.Instance.ClearOverrideBGM(); + } + + // 보이스 재생 + if (node.Voice != null && node.Speaker != null) + { + var voiceObj = CharacterVoiceObject.Find(node.Speaker); + if (voiceObj != null && node.Voice.Clip != null) + voiceObj.Play(node.Voice.Clip); + } + + // 제스처/표정은 화자(Target)의 Animator에 적용 — 끼어든 NPC의 대사/연출 노드도 그 캐릭터가 움직인다. + // 화자를 못 찾으면 대화 주인 NPC의 Animator로 폴백. + if (node.Gesture != null || node.Expression != null) + { + var gestureObj = node.Speaker != null ? CharacterVoiceObject.Find(node.Speaker) : null; + var anim = gestureObj != null ? gestureObj.GetComponentInChildren() : _animator; + if (anim == null) anim = _animator; + + if (anim != null) + { + CaptureInitialAnimState(anim); // 대화 종료 시 복원할 원래 상태 기억 + if (node.Gesture != null) + anim.CrossFade(node.Gesture.StateName, node.Gesture.CrossFadeDuration, node.Gesture.AnimationLayer); + if (node.Expression != null) + anim.CrossFade(node.Expression.StateName, node.Expression.CrossFadeDuration, node.Expression.AnimationLayer); + } + } + + // 진행은 항상 플레이어 입력 대기. + // 히든 분기가 무장된 노드는 대기 중 제스처를 함께 감시(레이스)한다. + if (HiddenBranchResolver.Armed) + return await WaitAdvanceOrDivert(); + + await WaitForAdvanceInput(); + return false; + } + + // 호감도 분기 노드의 조건식 평가. + // 각 조건의 JoinWithNext로 이어 붙이며, AND가 OR보다 우선순위가 높다: + // A and B or C → (A and B) or C + // 즉 "AND 그룹들을 OR로 합치는" 형태로 계산한다. 조건이 없으면 통과로 본다. + private bool IsAffectionMet(DialogNode node) + { + var requirements = node.AffectionRequirements; + if (requirements == null || requirements.Count == 0) return true; + + var story = StoryManager.Instance; + bool result = false; // 지금까지 닫힌 AND 그룹들을 OR로 합친 값 + bool group = true; // 현재 진행 중인 AND 그룹 + + for (int i = 0; i < requirements.Count; i++) + { + var req = requirements[i]; + if (req == null) continue; + + var target = req.Character != null ? req.Character : _voice.Character; + group &= req.IsMet(story.GetAffection(target)); + + // 다음 연결자가 OR이거나 마지막이면 AND 그룹을 닫고 OR로 합친다 + bool isLast = i == requirements.Count - 1; + if (isLast || req.JoinWithNext == AffectionJoin.Or) + { + result |= group; + group = true; + } + } + + return result; + } + + // 선택 기록: 선택지 Code를 StoryState에 영구 기록 (대화 활성화 조건 판정에 사용) + private void RecordChoice(DialogNode node, int index) + { + string code = (node.Choices != null && index >= 0 && index < node.Choices.Count) + ? node.Choices[index].Code : null; + code = DialogVariables.Format(code); // {token} 치환 → 동적으로 생성된 코드 반영 + + if (!string.IsNullOrEmpty(code)) + StoryManager.Instance.RecordChoice(code); + } + + // 히든 분기 기록: 노드의 HiddenCode를 선택지 Code와 동일하게 StoryState에 영구 기록 + private void RecordHiddenChoice(DialogNode node) + { + string code = DialogVariables.Format(node.HiddenCode); // {token} 치환 + if (!string.IsNullOrEmpty(code)) + StoryManager.Instance.RecordChoice(code); + } + + // 히든 분기 신호값 — 선택지 인덱스(0..n-1)와 절대 겹치지 않는 값. + private const int DivertIndex = -1; + + private async Awaitable WaitForChoice(DialogNode node) + { + if (ChoiceHud.Instance == null) + { + Debug.LogWarning("[DialogPlayer] ChoiceHud 없음 — 0번 자동 선택"); + return 0; + } + + // 무장 안 된 노드는 기존과 동일 — 그냥 메뉴 선택을 기다린다. + if (!HiddenBranchResolver.Armed) + return await ChoiceHud.Instance.Show(node.ChoiceQuestion, node.Choices); + + // 무장된 노드: 메뉴가 떠 있는 동안 제스처가 들어오면 메뉴를 취소하고 히든으로 분기. + if (HiddenBranchResolver.Fired) + return DivertIndex; // 메뉴가 뜨기 전에 이미 발동한 경우 + + void OnFired() => ChoiceHud.Instance.CancelPending(); // 제스처 → 메뉴 즉시 취소 + HiddenBranchResolver.FiredEvent += OnFired; + try + { + return await ChoiceHud.Instance.Show(node.ChoiceQuestion, node.Choices); + } + catch (OperationCanceledException) + { + // 우리가 제스처로 취소한 것이면 히든 분기, 아니면(씬 전환 등) 위로 전파 + if (HiddenBranchResolver.Fired) return DivertIndex; + throw; + } + finally + { + HiddenBranchResolver.FiredEvent -= OnFired; + } + } + + // 노드 대기 + 히든 제스처 감시(레이스). 무장된 노드에서만 호출된다. + // 반환: true = 대기 도중 제스처 발동(→히든 분기), false = 평범하게 진행(입력) + private async Awaitable WaitAdvanceOrDivert() + { + var im = InputManager.Instance; + bool advance = false; + void OnAdvance() => advance = true; + if (im != null) im.OnDialogNext_Event += OnAdvance; + + // InputManager가 없으면 기존 WaitForAdvanceInput처럼 1초 후 진행 + float timeLeft = im == null ? 1f : -1f; + + try + { + while (true) + { + if (HiddenBranchResolver.Fired) return true; // 제스처 발동 → 히든 분기 + if (advance) return false; // 진행 입력 → 평범하게 진행 + if (timeLeft >= 0f) + { + timeLeft -= Time.deltaTime; + if (timeLeft <= 0f) return false; + } + await Awaitable.NextFrameAsync(destroyCancellationToken); + } + } + catch (OperationCanceledException) + { + return false; // 대기 중 파괴/씬 전환 — 기존과 동일하게 조용히 종료 + } + finally + { + if (im != null) im.OnDialogNext_Event -= OnAdvance; + } + } + + // 대화 진행 입력(OnDialogNext) 한 번을 대기 + private async Awaitable WaitForAdvanceInput() + { + var im = InputManager.Instance; + if (im == null) + { + // 입력 매니저 없으면 안전하게 잠깐 대기 후 진행 + await Awaitable.WaitForSecondsAsync(1f); + return; + } + + bool pressed = false; + void Handler() => pressed = true; + im.OnDialogNext_Event += Handler; + try + { + while (!pressed) + await Awaitable.NextFrameAsync(destroyCancellationToken); + } + catch (OperationCanceledException) + { + // 대기 중 오브젝트 파괴 시 조용히 종료 + } + finally + { + im.OnDialogNext_Event -= Handler; + } + } + + //테스트용 + private void Update() + { + if (Mouse.current == null) return; + if (!Mouse.current.leftButton.wasPressedThisFrame) return; + if (Camera.main == null) return; + + var ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue()); + if (Physics.Raycast(ray, out var hit) && hit.transform.IsChildOf(transform)) + { + Debug.Log("캐릭터 클릭"); + _ = Play(); // 테스트용 fire-and-forget (Update는 await 불가) + } + } +} diff --git a/Assets/02_Scripts/Communication/Dialog/DialogPlayer.cs.meta b/Assets/02_Scripts/Communication/Dialog/DialogPlayer.cs.meta new file mode 100644 index 0000000..7fef412 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/DialogPlayer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9838e0e0a3edefa4e92ddbb98aaa3ce5 \ No newline at end of file diff --git a/Assets/02_Scripts/Communication/Dialog/DialogVariableSetter.cs b/Assets/02_Scripts/Communication/Dialog/DialogVariableSetter.cs new file mode 100644 index 0000000..1ddb949 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/DialogVariableSetter.cs @@ -0,0 +1,12 @@ +using UnityEngine; + +// 인스펙터에 지정한 key로 DialogVariables에 값을 넣는 헬퍼. +// 예: TMP_InputField의 On End Edit(string) → 이 컴포넌트의 Set(string) 에 연결하면 +// 플레이어가 입력한 글자가 {key} 토큰으로 대화에 들어간다. +public class DialogVariableSetter : MonoBehaviour +{ + [SerializeField] private string _key; + + public void Set(string value) => DialogVariables.Set(_key, value); // UnityEvent 연결용 + public void SetKey(string key) => _key = key; +} diff --git a/Assets/02_Scripts/Communication/Dialog/DialogVariableSetter.cs.meta b/Assets/02_Scripts/Communication/Dialog/DialogVariableSetter.cs.meta new file mode 100644 index 0000000..ef010ca --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/DialogVariableSetter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6f182cc352a11ed48b27e690bdb10520 \ No newline at end of file diff --git a/Assets/02_Scripts/Communication/Dialog/DialogVariables.cs b/Assets/02_Scripts/Communication/Dialog/DialogVariables.cs new file mode 100644 index 0000000..d20b16c --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/DialogVariables.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Text; +using UnityEngine; + +// 대화 텍스트의 {key} 토큰을 런타임 값으로 치환하는 전역 저장소. +// 예) DialogVariables.Set("playerName", "철수"); +// 대사 "안녕 {playerName}!" → "안녕 철수!" +// +// 표시 직전(DialogHud / ChoiceHud)에서 Format()을 거치므로, 그래프엔 그냥 {key}만 써두면 된다. +public static class DialogVariables +{ + private static readonly Dictionary _values = new(); + + public static void Set(string key, string value) => _values[key] = value ?? string.Empty; + public static void Remove(string key) => _values.Remove(key); + public static void Clear() => _values.Clear(); + public static bool TryGet(string key, out string value) => _values.TryGetValue(key, out value); + + // "{key}" 토큰을 등록된 값으로 치환. 등록 안 된 키는 그대로 둔다(빠진 값 디버깅용). + public static string Format(string text) + { + if (string.IsNullOrEmpty(text) || text.IndexOf('{') < 0) return text; + + var sb = new StringBuilder(text.Length); + int i = 0; + while (i < text.Length) + { + if (text[i] == '{') + { + int close = text.IndexOf('}', i + 1); + if (close > i) + { + string key = text.Substring(i + 1, close - i - 1); + if (_values.TryGetValue(key, out var val)) + { + sb.Append(val); + i = close + 1; + continue; + } + } + } + sb.Append(text[i]); + i++; + } + return sb.ToString(); + } + + // 플레이 시작마다 초기화 (Enter Play Mode에서 도메인 리로드를 꺼도 이전 값이 안 남게) + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + private static void ResetOnPlay() => _values.Clear(); +} diff --git a/Assets/02_Scripts/Communication/Dialog/DialogVariables.cs.meta b/Assets/02_Scripts/Communication/Dialog/DialogVariables.cs.meta new file mode 100644 index 0000000..d971ff7 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/DialogVariables.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f6bb90f809bd62e409383e02949f32c3 \ No newline at end of file diff --git a/Assets/02_Scripts/Communication/Dialog/GraphTool.meta b/Assets/02_Scripts/Communication/Dialog/GraphTool.meta new file mode 100644 index 0000000..6f17bd5 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/GraphTool.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cb0dad36a50363647a4b9cd61d078b20 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor.meta b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor.meta new file mode 100644 index 0000000..a17308a --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bad231f70f838c84288d07f754f51ed7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogAffectionNode.cs b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogAffectionNode.cs new file mode 100644 index 0000000..c27e268 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogAffectionNode.cs @@ -0,0 +1,83 @@ +using System; +using Unity.GraphToolkit.Editor; + +namespace DinoLove.Dialog.GraphTool.Editor +{ + // 호감도 분기 노드. 대사 없이 호감도 조건식만 검사해 두 경로 중 하나로 즉시 라우팅한다. + // DialogNode(AffectionCheck=true) 하나로 변환된다. + // + // Condition Count로 조건 줄을 늘린다. 조건 하나는 [Target / 연산자 / 값] 세 줄이고, + // 조건 사이마다 [and · or] 연결자 줄이 하나씩 생긴다: + // + // Target 1 윤지후 + // Compare 1 ≥ + // Affection 1 30 + // Join 1 AND + // Target 2 잔디 + // Compare 2 < + // Affection 2 20 + // ├─ True → + // └─ False → + // + // AND가 OR보다 우선순위가 높다 (A and B or C → (A and B) or C). + [Serializable] + internal class DialogAffectionNode : DialogGraphNode + { + public const string PORT_PASS_OUT = "PassOut"; + public const string PORT_FAIL_OUT = "FailOut"; + + public const string OPTION_CONDITION_COUNT = "ConditionCount"; + + // 조건별 포트 이름 규칙 (임포터와 공유) + public static string TargetPort(int i) => $"Target{i}"; + public static string ComparePort(int i) => $"Compare{i}"; + public static string ValuePort(int i) => $"Value{i}"; + public static string JoinPort(int i) => $"Join{i}"; + + protected override void OnDefineOptions(IOptionDefinitionContext context) + { + context.AddOption(OPTION_CONDITION_COUNT) + .WithDisplayName("Condition Count") + .WithTooltip("검사할 호감도 조건 개수 (캐릭터마다 하나씩)") + .WithDefaultValue(1) + .Delayed(); + } + + protected override void OnDefinePorts(IPortDefinitionContext context) + { + AddExecInput(context); + + int conditionCount = 1; + GetNodeOptionByName(OPTION_CONDITION_COUNT)?.TryGetValue(out conditionCount); + if (conditionCount < 1) conditionCount = 1; + + for (int i = 0; i < conditionCount; i++) + { + context.AddInputPort(TargetPort(i)) + .WithDisplayName($"Target {i + 1}") + .WithTooltip("누구의 호감도를 검사할지. 비우면 대화 주인 NPC") + .Build(); + context.AddInputPort(ComparePort(i)) + .WithDisplayName($"Compare {i + 1}") + .WithTooltip("호감도를 아래 값과 어떻게 비교할지") + .Build(); + context.AddInputPort(ValuePort(i)) + .WithDisplayName($"Affection {i + 1}") + .WithTooltip("비교 기준값") + .Build(); + + // 조건 사이에만 연결자를 둔다 (마지막 조건 뒤에는 이어질 게 없으므로 생략) + if (i < conditionCount - 1) + { + context.AddInputPort(JoinPort(i)) + .WithDisplayName($"Join {i + 1}") + .WithTooltip("다음 조건과 묶는 방식. AND가 OR보다 우선") + .Build(); + } + } + + AddExecOutput(context, PORT_PASS_OUT, "True →"); + AddExecOutput(context, PORT_FAIL_OUT, "False →"); + } + } +} diff --git a/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogAffectionNode.cs.meta b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogAffectionNode.cs.meta new file mode 100644 index 0000000..7f7d8eb --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogAffectionNode.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 25bc0691603f93e4196ae428c028e855 \ No newline at end of file diff --git a/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogGraph.cs b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogGraph.cs new file mode 100644 index 0000000..0b3cd65 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogGraph.cs @@ -0,0 +1,43 @@ +using System.Linq; +using Unity.GraphToolkit.Editor; +using UnityEditor; + +namespace DinoLove.Dialog.GraphTool.Editor +{ + // 대화 그래프. + // 기존 Communication/Dialog 시스템(DialogGroup / DialogNode / DialogChoice)을 + // 노드 그래프로 저작하기 위한 에디터 전용 그래프 타입이다. + // 임포트 시 DialogGraphImporter가 이 그래프를 DialogGroup 에셋으로 변환한다. + [Graph(AssetExtension)] + internal class DialogGraph : Graph + { + // ScriptedImporter가 사용하는 확장자. 프로젝트 내에서 유일해야 한다. + public const string AssetExtension = "dlg"; // DiaLoG + + const string k_DefaultName = "New Dialog Graph"; + + [MenuItem("Assets/Create/Communication/Dialog Graph")] + static void CreateAssetFile() + { + GraphDatabase.PromptInProjectBrowserToCreateNewAsset(k_DefaultName); + } + + // 그래프가 바뀔 때마다 호출되어 에러/경고를 보고한다. + public override void OnGraphChanged(GraphLogger infos) + { + base.OnGraphChanged(infos); + + var startNodes = GetNodes().OfType().ToList(); + switch (startNodes.Count) + { + case 0: + infos.LogError("Start 노드가 필요합니다. (Dialog Start Node를 추가하세요)", this); + break; + case >= 2: + foreach (var extra in startNodes.Skip(1)) + infos.LogWarning("Start 노드는 하나만 사용됩니다. 가장 먼저 생성된 노드만 적용됩니다.", extra); + break; + } + } + } +} diff --git a/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogGraph.cs.meta b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogGraph.cs.meta new file mode 100644 index 0000000..934accb --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogGraph.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f25a0745082bd7c439679495337d3598 \ No newline at end of file diff --git a/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogGraphImporter.cs b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogGraphImporter.cs new file mode 100644 index 0000000..419f182 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogGraphImporter.cs @@ -0,0 +1,236 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Unity.GraphToolkit.Editor; +using UnityEditor.AssetImporters; +using UnityEngine; + +namespace DinoLove.Dialog.GraphTool.Editor +{ + // .dlg 그래프 에셋을 기존 런타임 타입(DialogGroup / DialogNode / DialogChoice)으로 변환한다. + // 생성된 DialogNode들은 서브에셋으로, DialogGroup이 메인 에셋으로 등록된다. + // 따라서 DialogPlayer는 수정 없이 임포트된 .dlg 에셋(= DialogGroup)을 그대로 사용한다. + [ScriptedImporter(15, DialogGraph.AssetExtension)] // 버전 올리면 기존 .dlg 에셋이 재임포트됨 + internal class DialogGraphImporter : ScriptedImporter + { + public override void OnImportAsset(AssetImportContext ctx) + { + var graph = GraphDatabase.LoadGraphForImporter(ctx.assetPath); + if (graph == null) + { + Debug.LogError($"[DialogGraphImporter] 그래프 로드 실패: {ctx.assetPath}"); + return; + } + + // 메인 에셋: DialogGroup (이름은 파일명 기준 — StoryState 대화 이력의 키로 쓰임) + var groupName = Path.GetFileNameWithoutExtension(ctx.assetPath); + var group = ScriptableObject.CreateInstance(); + group.name = groupName; + group.DialogGroupName = groupName; + ctx.AddObjectToAsset("Group", group); + ctx.SetMainObject(group); + + var startNode = graph.GetNodes().OfType().FirstOrDefault(); + if (startNode == null) + return; // OnGraphChanged에서 에러 로깅됨 + + var firstGraphNode = GetConnectedNode(startNode, DialogGraphNode.EXEC_OUT); + if (firstGraphNode == null) + return; // Start만 있고 연결 없음 + + // 1패스: 도달 가능한 모든 라인 노드 → DialogNode 인스턴스 생성 (중복 제거) + var map = new Dictionary(); + var order = new List(); + var queue = new Queue(); + queue.Enqueue(firstGraphNode); + + while (queue.Count > 0) + { + var gn = queue.Dequeue(); + if (gn == null || map.ContainsKey(gn) + || (gn is not DialogLineNode && gn is not DialogAffectionNode)) + continue; + + var dn = ScriptableObject.CreateInstance(); + dn.Choices = new List(); + map[gn] = dn; + order.Add(gn); + + foreach (var next in GetSuccessors(gn)) + if (next != null && !map.ContainsKey(next)) + queue.Enqueue(next); + } + + // 서브에셋 등록 + 이름 지정 + for (int i = 0; i < order.Count; i++) + { + var dn = map[order[i]]; + dn.name = $"Node_{i:00}"; + ctx.AddObjectToAsset(dn.name, dn); + } + + // 2패스: 데이터/링크 채우기 + foreach (var gn in order) + { + var dn = map[gn]; + + // 호감도 분기 노드 — 대사 없이 라우팅만: 조건 통과 → AffectionPassBranch, 실패 → Next + if (gn is DialogAffectionNode affectionNode) + { + dn.AffectionCheck = true; + + int conditionCount = 1; + var countOption = affectionNode.GetNodeOptionByName(DialogAffectionNode.OPTION_CONDITION_COUNT); + if (countOption != null && countOption.TryGetValue(out int storedCount) && storedCount > 0) + conditionCount = storedCount; + + dn.AffectionRequirements = new List(conditionCount); + for (int i = 0; i < conditionCount; i++) + { + dn.AffectionRequirements.Add(new AffectionRequirement + { + Character = GetInputPortValue(gn.GetInputPortByName(DialogAffectionNode.TargetPort(i))), + Compare = GetInputPortValue(gn.GetInputPortByName(DialogAffectionNode.ComparePort(i))), + Value = GetInputPortValue(gn.GetInputPortByName(DialogAffectionNode.ValuePort(i))), + // 마지막 조건에는 Join 포트가 없다 → 기본 And (평가 시 무시됨) + JoinWithNext = i < conditionCount - 1 + ? GetInputPortValue(gn.GetInputPortByName(DialogAffectionNode.JoinPort(i))) + : AffectionJoin.And + }); + } + + var passDest = GetConnectedNode(gn, DialogAffectionNode.PORT_PASS_OUT); + dn.AffectionPassBranch = passDest != null && map.TryGetValue(passDest, out var passDn) ? passDn : null; + + var failDest = GetConnectedNode(gn, DialogAffectionNode.PORT_FAIL_OUT); + dn.Next = failDest != null && map.TryGetValue(failDest, out var failDn) ? failDn : null; + continue; + } + + var line = (DialogLineNode)gn; + + dn.Speaker = GetInputPortValue(gn.GetInputPortByName(DialogLineNode.PORT_SPEAKER)); + dn.SpeakerNameOverride = GetInputPortValue(gn.GetInputPortByName(DialogLineNode.PORT_NAME_OVERRIDE)).Value; + dn.TalkText = GetInputPortValue(gn.GetInputPortByName(DialogLineNode.PORT_TALK)).Value; + dn.Gesture = GetInputPortValue(gn.GetInputPortByName(DialogLineNode.PORT_GESTURE)); + dn.Expression = GetInputPortValue(gn.GetInputPortByName(DialogLineNode.PORT_EXPRESSION)); + dn.Voice = GetInputPortValue(gn.GetInputPortByName(DialogLineNode.PORT_VOICE)); + dn.Bgm = GetInputPortValue(gn.GetInputPortByName(DialogLineNode.PORT_BGM)); + dn.Affection = GetInputPortValue(gn.GetInputPortByName(DialogLineNode.PORT_AFFECTION)); + dn.Progress = GetInputPortValue(gn.GetInputPortByName(DialogLineNode.PORT_PROGRESS)); + + int choiceCount = 0; + line.GetNodeOptionByName(DialogLineNode.OPTION_CHOICE_COUNT)?.TryGetValue(out choiceCount); + + if (choiceCount <= 0) + { + var next = GetConnectedNode(gn, DialogGraphNode.EXEC_OUT); + dn.Next = next != null && map.TryGetValue(next, out var nextDn) ? nextDn : null; + } + else + { + dn.ChoiceQuestion = GetInputPortValue(gn.GetInputPortByName(DialogLineNode.PORT_QUESTION)).Value; + for (int i = 0; i < choiceCount; i++) + { + var choiceText = GetInputPortValue(gn.GetInputPortByName(DialogLineNode.ChoiceTextPort(i))).Value; + var choiceCode = GetInputPortValue(gn.GetInputPortByName(DialogLineNode.ChoiceCodePort(i))); + var dest = GetConnectedNode(gn, DialogLineNode.ChoiceOutPort(i)); + dn.Choices.Add(new DialogChoice + { + ChoiceText = choiceText, + Code = choiceCode, + DestinationNode = dest != null && map.TryGetValue(dest, out var destDn) ? destDn : null + }); + } + } + + // 히든 분기 (켜져 있으면) — 목적지 노드 + 제스처 키 + 기록 코드 + bool hasHidden = false; + line.GetNodeOptionByName(DialogLineNode.OPTION_HIDDEN_BRANCH)?.TryGetValue(out hasHidden); + if (hasHidden) + { + var hiddenDest = GetConnectedNode(gn, DialogLineNode.PORT_HIDDEN_OUT); + dn.HiddenBranch = hiddenDest != null && map.TryGetValue(hiddenDest, out var hiddenDn) ? hiddenDn : null; + + string gestureKey = null; + line.GetNodeOptionByName(DialogLineNode.OPTION_HIDDEN_GESTURE)?.TryGetValue(out gestureKey); + dn.HiddenGestureKey = gestureKey; + + string hiddenCode = null; + line.GetNodeOptionByName(DialogLineNode.OPTION_HIDDEN_CODE)?.TryGetValue(out hiddenCode); + dn.HiddenCode = hiddenCode; + } + } + + group.StartNode = map.TryGetValue(firstGraphNode, out var startDn) ? startDn : null; + } + + // 노드의 실행 흐름상 후속 노드들 (선형이면 1개, N지선다면 N개) + static IEnumerable GetSuccessors(INode node) + { + // 호감도 분기 노드는 두 출력 모두 후속 + if (node is DialogAffectionNode) + { + yield return GetConnectedNode(node, DialogAffectionNode.PORT_PASS_OUT); + yield return GetConnectedNode(node, DialogAffectionNode.PORT_FAIL_OUT); + yield break; + } + + if (node is not DialogLineNode line) + yield break; + + int choiceCount = 0; + line.GetNodeOptionByName(DialogLineNode.OPTION_CHOICE_COUNT)?.TryGetValue(out choiceCount); + + if (choiceCount <= 0) + { + yield return GetConnectedNode(node, DialogGraphNode.EXEC_OUT); + } + else + { + for (int i = 0; i < choiceCount; i++) + yield return GetConnectedNode(node, DialogLineNode.ChoiceOutPort(i)); + } + + // 히든 분기 후속도 도달 가능해야 DialogNode로 생성된다 + bool hasHidden = false; + line.GetNodeOptionByName(DialogLineNode.OPTION_HIDDEN_BRANCH)?.TryGetValue(out hasHidden); + if (hasHidden) + yield return GetConnectedNode(node, DialogLineNode.PORT_HIDDEN_OUT); + } + + // 출력 실행 포트에 연결된 노드 (없으면 null) + static INode GetConnectedNode(INode node, string outputPortName) + { + var port = node.GetOutputPortByName(outputPortName); + return port?.FirstConnectedPort?.GetNode(); + } + + // 입력 포트 값 읽기. (연결된 변수/상수 노드 → 임베드 값 → 기본값 순) + static T GetInputPortValue(IPort port) + { + T value = default; + if (port == null) + return value; + + if (port.IsConnected) + { + switch (port.FirstConnectedPort.GetNode()) + { + case IVariableNode variableNode: + variableNode.Variable.TryGetDefaultValue(out value); + return value; + case IConstantNode constantNode: + constantNode.TryGetValue(out value); + return value; + } + } + else + { + port.TryGetValue(out value); + } + + return value; + } + } +} diff --git a/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogGraphImporter.cs.meta b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogGraphImporter.cs.meta new file mode 100644 index 0000000..b5a67fd --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogGraphImporter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2ae5ca89bbed445479d9023586f0c041 \ No newline at end of file diff --git a/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogGraphNode.cs b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogGraphNode.cs new file mode 100644 index 0000000..39f6da3 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogGraphNode.cs @@ -0,0 +1,33 @@ +using System; +using Unity.GraphToolkit.Editor; + +namespace DinoLove.Dialog.GraphTool.Editor +{ + // 대화 그래프 노드들의 공통 베이스. + // 실행 흐름(Execution) 포트를 추가하는 헬퍼를 제공한다. + // 실행 포트는 화살촉(Arrowhead) 커넥터를 쓰고, 데이터 포트(원형)와 구분된다. + [Serializable] + internal abstract class DialogGraphNode : Node + { + public const string EXEC_IN = "In"; + public const string EXEC_OUT = "Out"; + + // 입력 실행 포트 (이 노드로 들어오는 흐름) + protected void AddExecInput(IPortDefinitionContext context) + { + context.AddInputPort(EXEC_IN) + .WithDisplayName(string.Empty) + .WithConnectorUI(PortConnectorUI.Arrowhead) + .Build(); + } + + // 출력 실행 포트 (이 노드에서 나가는 흐름) + protected void AddExecOutput(IPortDefinitionContext context, string portName, string displayName) + { + context.AddOutputPort(portName) + .WithDisplayName(displayName) + .WithConnectorUI(PortConnectorUI.Arrowhead) + .Build(); + } + } +} diff --git a/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogGraphNode.cs.meta b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogGraphNode.cs.meta new file mode 100644 index 0000000..6af1028 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogGraphNode.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: be386f8d9f138f8449c9e84034f5e1ef \ No newline at end of file diff --git a/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogLineNode.cs b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogLineNode.cs new file mode 100644 index 0000000..62848ce --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogLineNode.cs @@ -0,0 +1,117 @@ +using System; +using Unity.GraphToolkit.Editor; +using UnityEngine; + +namespace DinoLove.Dialog.GraphTool.Editor +{ + // 대사 한 노드. DialogNode 한 개로 변환된다. + // + // ChoiceCount 옵션으로 분기 방식을 정한다: + // - 0 : 선형 진행. 출력 실행 포트 "Out" 하나(→ DialogNode.Next) + // - 1 이상 : 가변 N지선다. ChoiceQuestion + 각 선택지마다 + // [Choice{i} Text 입력 포트] + [Choice{i} 출력 실행 포트] 생성 + // (→ DialogNode.Choices / ChoiceQuestion) + [Serializable] + internal class DialogLineNode : DialogGraphNode + { + public const string PORT_SPEAKER = "Speaker"; + public const string PORT_NAME_OVERRIDE = "SpeakerNameOverride"; + public const string PORT_TALK = "TalkText"; + 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_AFFECTION = "Affection"; + public const string PORT_PROGRESS = "Progress"; + public const string PORT_QUESTION = "ChoiceQuestion"; + public const string PORT_HIDDEN_OUT = "HiddenOut"; + + public const string OPTION_CHOICE_COUNT = "ChoiceCount"; + public const string OPTION_HIDDEN_BRANCH = "HasHiddenBranch"; + public const string OPTION_HIDDEN_GESTURE = "HiddenGestureKey"; + public const string OPTION_HIDDEN_CODE = "HiddenCode"; + + // 선택지별 포트 이름 규칙 (임포터와 공유) + public static string ChoiceTextPort(int i) => $"Choice{i}Text"; + public static string ChoiceCodePort(int i) => $"Choice{i}Code"; + public static string ChoiceOutPort(int i) => $"Choice{i}Out"; + + protected override void OnDefineOptions(IOptionDefinitionContext context) + { + context.AddOption(OPTION_CHOICE_COUNT) + .WithDisplayName("Choice Count") + .WithTooltip("0이면 선형 진행(Next), 1 이상이면 가변 N지선다 분기") + .WithDefaultValue(0) + .Delayed(); + + context.AddOption(OPTION_HIDDEN_BRANCH) + .WithDisplayName("Hidden Branch") + .WithTooltip("켜면 맨 아래에 'Hidden →' 출력 포트가 생긴다. 이 대사 재생 중 특정 제스처(싸대기/터치)를 하면 " + + "Next/선택지 대신 그 포트로 연결된 노드로 몰래 분기한다") + .WithDefaultValue(false); + + context.AddOption(OPTION_HIDDEN_GESTURE) + .WithDisplayName("Hidden Gesture Key") + .WithTooltip("히든 분기를 여는 제스처 키 (DialogGestureZone의 Gesture Key와 일치). 비우면 아무 제스처 존이나 발동") + .Delayed(); + + context.AddOption(OPTION_HIDDEN_CODE) + .WithDisplayName("Hidden Code") + .WithTooltip("히든 분기를 탔을 때 기록할 선택지 Code. 비우면 기록 안 함") + .Delayed(); + } + + protected override void OnDefinePorts(IPortDefinitionContext context) + { + AddExecInput(context); + + // DialogNode의 라인 데이터 (모두 선택 입력, 비워두면 default) + context.AddInputPort(PORT_SPEAKER).WithDisplayName("Speaker").Build(); + context.AddInputPort(PORT_NAME_OVERRIDE).WithDisplayName("Name Override") + .WithTooltip("비우면 Speaker 이름 그대로, 채우면 이 이름으로 표시 (예: ???)").Build(); + context.AddInputPort(PORT_TALK).WithDisplayName("Talk Text").Build(); + context.AddInputPort(PORT_GESTURE).WithDisplayName("Gesture").Build(); + context.AddInputPort(PORT_EXPRESSION).WithDisplayName("Expression").Build(); + context.AddInputPort(PORT_VOICE).WithDisplayName("Voice").Build(); + context.AddInputPort(PORT_BGM).WithDisplayName("BGM") + .WithTooltip("있으면 이 대사부터 전용 BGM 재생, 비우면 기본 BGM으로 복귀").Build(); + context.AddInputPort(PORT_AFFECTION).WithDisplayName("Affection ±") + .WithTooltip("0이 아니면 이 대사 재생 시 화자(비우면 대화 주인 NPC)의 호감도를 이만큼 증감").Build(); + context.AddInputPort(PORT_PROGRESS).WithDisplayName("Progress +") + .WithTooltip("0이 아니면 이 대사 재생 시 메인 진행도를 이만큼 증가 (분기 끝 노드에 달아 선택지별로 다르게)").Build(); + + int choiceCount = 0; + GetNodeOptionByName(OPTION_CHOICE_COUNT)?.TryGetValue(out choiceCount); + + if (choiceCount <= 0) + { + // 선형 진행 + AddExecOutput(context, EXEC_OUT, string.Empty); + } + else + { + // 가변 N지선다 + // (string 포트는 GraphToolkit 기본 에디터의 IME 중복입력 버그가 있어 DialogText로 통일) + context.AddInputPort(PORT_QUESTION).WithDisplayName("Choice Question").Build(); + + for (int i = 0; i < choiceCount; i++) + { + context.AddInputPort(ChoiceTextPort(i)) + .WithDisplayName($"Choice {i + 1} Text") + .Build(); + context.AddInputPort(ChoiceCodePort(i)) + .WithDisplayName($"Choice {i + 1} Code") + .Build(); + AddExecOutput(context, ChoiceOutPort(i), $"Choice {i + 1} →"); + } + } + + // 히든 분기 출력 — 켜져 있으면 선형/선택지 관계없이 맨 아래에 붙는다. + // 이 포트에 연결된 노드가 DialogNode.HiddenBranch가 된다. + bool hasHidden = false; + GetNodeOptionByName(OPTION_HIDDEN_BRANCH)?.TryGetValue(out hasHidden); + if (hasHidden) + AddExecOutput(context, PORT_HIDDEN_OUT, "Hidden →"); + } + } +} diff --git a/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogLineNode.cs.meta b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogLineNode.cs.meta new file mode 100644 index 0000000..c70c3f2 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogLineNode.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: be6c5a15779e30f40910985e5d7cfbd9 \ No newline at end of file diff --git a/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogShortText.cs b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogShortText.cs new file mode 100644 index 0000000..1e7b24e --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogShortText.cs @@ -0,0 +1,18 @@ +using System; + +namespace DinoLove.Dialog.GraphTool.Editor +{ + // 그래프 노드의 한 줄짜리 텍스트 포트용 래퍼 타입 (이름 오버라이드 등 짧은 텍스트). + // DialogText와 마찬가지로 string 포트의 IME 중복입력 버그를 피하기 위한 타입이며, + // 전용 DialogShortTextDrawer가 한 줄 TextField로 렌더한다. + // 임포트 시 Value 문자열만 전달된다(런타임은 이 타입을 모름). + // + // public인 이유: GraphToolkit이 포트 임베드 값을 편집할 때 이 타입을 감싸는 + // 래퍼 ScriptableObject를 Reflection.Emit으로 다른 어셈블리에 생성하므로 + // 접근 가능해야 한다. + [Serializable] + public struct DialogShortText + { + public string Value; + } +} diff --git a/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogShortText.cs.meta b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogShortText.cs.meta new file mode 100644 index 0000000..8775d81 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogShortText.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9432f3355b50d3d458cff5e56b6c9206 \ No newline at end of file diff --git a/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogShortTextDrawer.cs b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogShortTextDrawer.cs new file mode 100644 index 0000000..d0f4643 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogShortTextDrawer.cs @@ -0,0 +1,22 @@ +using UnityEditor; +using UnityEditor.UIElements; +using UnityEngine.UIElements; + +namespace DinoLove.Dialog.GraphTool.Editor +{ + // DialogShortText를 한 줄 입력 필드로 그린다. (DialogTextDrawer의 싱글라인 버전) + [CustomPropertyDrawer(typeof(DialogShortText))] + internal class DialogShortTextDrawer : PropertyDrawer + { + public override VisualElement CreatePropertyGUI(SerializedProperty property) + { + var valueProp = property.FindPropertyRelative(nameof(DialogShortText.Value)); + + var field = new TextField(); + if (valueProp != null) + field.BindProperty(valueProp); + + return field; + } + } +} diff --git a/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogShortTextDrawer.cs.meta b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogShortTextDrawer.cs.meta new file mode 100644 index 0000000..fb35f17 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogShortTextDrawer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 15b46c322dec78d438a144f875d67d42 \ No newline at end of file diff --git a/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogStartNode.cs b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogStartNode.cs new file mode 100644 index 0000000..94d399a --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogStartNode.cs @@ -0,0 +1,16 @@ +using System; +using Unity.GraphToolkit.Editor; + +namespace DinoLove.Dialog.GraphTool.Editor +{ + // 대화의 진입점. 출력 실행 포트 하나만 가진다. + // 임포터는 이 노드에 연결된 첫 노드를 DialogGroup.StartNode로 설정한다. + [Serializable] + internal class DialogStartNode : DialogGraphNode + { + protected override void OnDefinePorts(IPortDefinitionContext context) + { + AddExecOutput(context, EXEC_OUT, string.Empty); + } + } +} diff --git a/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogStartNode.cs.meta b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogStartNode.cs.meta new file mode 100644 index 0000000..0c03994 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogStartNode.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4f76763c19a33214ca518048c8a89799 \ No newline at end of file diff --git a/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogText.cs b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogText.cs new file mode 100644 index 0000000..30314b9 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogText.cs @@ -0,0 +1,17 @@ +using System; + +namespace DinoLove.Dialog.GraphTool.Editor +{ + // 그래프 노드의 TalkText 포트를 여러 줄(멀티라인)로 편집하기 위한 래퍼 타입. + // 전용 DialogTextDrawer가 multiline TextField로 렌더한다. + // 임포트 시 Value 문자열만 DialogNode.TalkText로 전달된다(런타임은 이 타입을 모름). + // + // public인 이유: GraphToolkit이 포트 임베드 값을 편집할 때 이 타입을 감싸는 + // 래퍼 ScriptableObject를 Reflection.Emit으로 다른 어셈블리에 생성하므로 + // 접근 가능해야 한다. + [Serializable] + public struct DialogText + { + public string Value; + } +} diff --git a/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogText.cs.meta b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogText.cs.meta new file mode 100644 index 0000000..c8ed070 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogText.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9ad54cf039f672845a54666166b5021c \ No newline at end of file diff --git a/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogTextDrawer.cs b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogTextDrawer.cs new file mode 100644 index 0000000..432f8fc --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogTextDrawer.cs @@ -0,0 +1,30 @@ +using UnityEditor; +using UnityEditor.UIElements; +using UnityEngine.UIElements; + +namespace DinoLove.Dialog.GraphTool.Editor +{ + // DialogText를 여러 줄 입력 필드로 그린다. + // GraphToolkit의 포트 값 에디터(ConstantField)는 CustomPropertyDrawer가 있는 타입을 + // Unity PropertyField로 렌더하므로, 이 드로어가 노드/인스펙터의 TalkText 칸을 멀티라인으로 만든다. + [CustomPropertyDrawer(typeof(DialogText))] + internal class DialogTextDrawer : PropertyDrawer + { + public override VisualElement CreatePropertyGUI(SerializedProperty property) + { + var valueProp = property.FindPropertyRelative(nameof(DialogText.Value)); + + var field = new TextField + { + multiline = true + }; + field.style.minHeight = 72; // 약 4~5줄 높이 + field.style.whiteSpace = WhiteSpace.Normal; // 줄바꿈(wrap) 허용 + + if (valueProp != null) + field.BindProperty(valueProp); + + return field; + } + } +} diff --git a/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogTextDrawer.cs.meta b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogTextDrawer.cs.meta new file mode 100644 index 0000000..341459a --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/GraphTool/Editor/DialogTextDrawer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d31004aee38951a4faaff37d886d3a65 \ No newline at end of file diff --git a/Assets/02_Scripts/Communication/Dialog/GraphTool/README.md b/Assets/02_Scripts/Communication/Dialog/GraphTool/README.md new file mode 100644 index 0000000..0417f9d --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/GraphTool/README.md @@ -0,0 +1,47 @@ +# Dialog Graph Tool + +`Communication/Dialog` 시스템(`DialogGroup` / `DialogNode` / `DialogChoice`)을 +**노드 그래프로 저작**하기 위한 에디터 전용 도구입니다. +Unity GraphToolkit(`com.unity.graphtoolkit`, experimental) 기반. + +## 동작 개요 +- `.dlg` 그래프 에셋을 노드로 편집 → 저장하면 `DialogGraphImporter`가 + 기존 런타임 타입(`DialogGroup` + 여러 `DialogNode`)으로 자동 변환합니다. +- 변환 결과: **메인 에셋 = `DialogGroup`**, 서브에셋 = 각 `DialogNode`. +- `DialogPlayer`는 **수정 없이** 임포트된 `.dlg`(=DialogGroup)를 그대로 사용합니다. + +## 사용법 +1. Project 창에서 우클릭 → `Create > Communication > Dialog Graph` 로 `.dlg` 생성. +2. 더블클릭해 그래프 에디터를 엽니다. +3. 노드 추가: + - **Dialog Start Node** : 진입점. 출력 화살표를 첫 대사 노드에 연결. (그래프당 1개) + - **Dialog Line Node** : 대사 1줄. Speaker/TalkText/Gesture/Expression/Voice/BGM 입력. + 진행은 항상 플레이어 입력으로 넘어갑니다. + - `Choice Count = 0` → 선형. `Out` 출력을 다음 노드로 연결(= `DialogNode.Next`). + - `Choice Count = N` → 가변 N지선다. `Choice Question` + 선택지마다 + `Choice i Text`(텍스트) 와 `Choice i →`(분기 출력) 생성. + 각 분기 출력을 목적지 노드에 연결(= `DialogNode.Choices[i].DestinationNode`). +4. 저장(임포트)되면 `.dlg` 에셋이 `DialogGroup`이 됩니다. + 이를 `StoryDatabase`의 비트(장소·캐릭터·조건)에 등록하면 끝 — + 해당 장소에서 그 캐릭터에게 말을 걸면 조건에 따라 재생됩니다. + +## 구성 파일 (모두 Editor 전용) +- `DialogGraph.cs` — 그래프 타입/생성 메뉴/검증 +- `DialogGraphNode.cs` — 공통 베이스(실행 포트 헬퍼) +- `DialogStartNode.cs` — 진입 노드 +- `DialogLineNode.cs` — 대사 + 가변 N지선다 노드 +- `DialogText.cs` — TalkText 멀티라인 입력용 래퍼 타입 +- `DialogTextDrawer.cs` — DialogText를 여러 줄 TextField로 그리는 CustomPropertyDrawer +- `DialogGraphImporter.cs`— .dlg → DialogGroup/DialogNode 변환 + +## TalkText 멀티라인 +- TalkText 포트는 `string`이 아니라 `DialogText` 타입을 쓴다. +- GraphToolkit은 `[CustomPropertyDrawer]`가 있는 타입을 Unity PropertyField로 렌더하므로, + `DialogTextDrawer`가 노드의 TalkText 칸을 여러 줄(멀티라인)로 만든다. +- 임포터는 `DialogText.Value`만 꺼내 `DialogNode.TalkText`(string)에 넣는다 — 런타임은 영향 없음. +- 높이를 더 키우려면 `DialogTextDrawer`의 `minHeight` 값을 조정. + +## 메모 +- GraphToolkit은 experimental(0.4.0-exp.2)이라 API가 바뀔 수 있습니다. +- 분기 출력이 비어 있으면 해당 선택지의 `DestinationNode`는 null이 되어 대화가 종료됩니다. +- 여러 경로에서 같은 노드로 연결하면(루프 포함) 하나의 `DialogNode`로 합쳐집니다. diff --git a/Assets/02_Scripts/Communication/Dialog/GraphTool/README.md.meta b/Assets/02_Scripts/Communication/Dialog/GraphTool/README.md.meta new file mode 100644 index 0000000..c931f91 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/GraphTool/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1fa58401314123a4b90fa0fda5240a18 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/02_Scripts/Communication/Dialog/HiddenBranchResolver.cs b/Assets/02_Scripts/Communication/Dialog/HiddenBranchResolver.cs new file mode 100644 index 0000000..4db4214 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/HiddenBranchResolver.cs @@ -0,0 +1,60 @@ +using System; +using UnityEngine; + +// 히든 분기 감시 허브 (씬 오브젝트 불필요, 순수 static). +// +// 대화 노드가 재생되는 동안 DialogPlayer가 Arm(key)로 무장하고, 끝나면 Disarm()한다. +// 그 창(무장 구간) 안에서 DialogGestureZone이 Fire(key)를 호출하면 Fired가 켜지고, +// DialogPlayer가 이를 감지해 Next(또는 선택지) 대신 노드의 HiddenBranch로 몰래 분기한다. +// +// 무장돼 있지 않으면 어떤 Fire도 무시된다 → 평소엔 존재하지 않는 것처럼 동작(=히든). +public static class HiddenBranchResolver +{ + private static string _armedKey; + + // 지금 히든 분기를 받을 수 있는 상태인가 (노드 재생 중) + public static bool Armed { get; private set; } + + // 이번 무장 구간에서 제스처가 발동했는가 + public static bool Fired { get; private set; } + + // 발동 즉시 알림 — 떠 있는 선택지 메뉴를 그 자리에서 취소시키는 용도 + public static event Action FiredEvent; + + // Enter Play Mode(도메인 리로드 off)에서 이전 상태가 안 남게 + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + private static void ResetStatics() + { + _armedKey = null; + Armed = false; + Fired = false; + FiredEvent = null; + } + + // 노드 진입 시 무장. key가 비면 아무 제스처 존이나 발동시킬 수 있다. + public static void Arm(string key) + { + _armedKey = key; + Armed = true; + Fired = false; + } + + // 노드 종료 시 해제. + public static void Disarm() + { + Armed = false; + _armedKey = null; + Fired = false; + } + + // 제스처 존이 호출. 무장 중이고 key가 맞을 때만 1회 발동. + // 반환: 이번 호출로 실제 발동했으면 true (호출 측이 "그때만" 아이템 소비 등에 쓸 수 있게). + public static bool Fire(string key) + { + if (!Armed || Fired) return false; + if (!string.IsNullOrEmpty(_armedKey) && _armedKey != key) return false; + Fired = true; + FiredEvent?.Invoke(); + return true; + } +} diff --git a/Assets/02_Scripts/Communication/Dialog/HiddenBranchResolver.cs.meta b/Assets/02_Scripts/Communication/Dialog/HiddenBranchResolver.cs.meta new file mode 100644 index 0000000..ca59658 --- /dev/null +++ b/Assets/02_Scripts/Communication/Dialog/HiddenBranchResolver.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1622ac8790b08674cbb2164b2a8dd1f7 \ No newline at end of file diff --git a/Assets/02_Scripts/Communication/Voice.meta b/Assets/02_Scripts/Communication/Voice.meta new file mode 100644 index 0000000..8df93f8 --- /dev/null +++ b/Assets/02_Scripts/Communication/Voice.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 98959040e708aa04891672a6c21a9479 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/02_Scripts/Communication/Voice/CharacterVoiceObject.cs b/Assets/02_Scripts/Communication/Voice/CharacterVoiceObject.cs new file mode 100644 index 0000000..e07abd7 --- /dev/null +++ b/Assets/02_Scripts/Communication/Voice/CharacterVoiceObject.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using UnityEngine; + +public class CharacterVoiceObject : MonoBehaviour +{ + public CharacterData Character; + public AudioSource VoiceSource; + + private static readonly Dictionary _registry = new(); + + private void OnEnable() => _registry[Character] = this; + private void OnDisable() => _registry.Remove(Character); + + public static CharacterVoiceObject Find(CharacterData data) + => _registry.TryGetValue(data, out var obj) ? obj : null; + + public void Play(AudioClip clip) => VoiceSource.PlayOneShot(clip); +} diff --git a/Assets/02_Scripts/Communication/Voice/CharacterVoiceObject.cs.meta b/Assets/02_Scripts/Communication/Voice/CharacterVoiceObject.cs.meta new file mode 100644 index 0000000..e4965c4 --- /dev/null +++ b/Assets/02_Scripts/Communication/Voice/CharacterVoiceObject.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: aa8a1a66f46e7ef4f99770437e10ae34 \ No newline at end of file diff --git a/Assets/02_Scripts/Communication/Voice/LipSync.cs b/Assets/02_Scripts/Communication/Voice/LipSync.cs new file mode 100644 index 0000000..a2debdd --- /dev/null +++ b/Assets/02_Scripts/Communication/Voice/LipSync.cs @@ -0,0 +1,109 @@ +using System; +using UnityEngine; + +// 보이스 진폭에 따라 입 관련 블렌드셰이프 그룹의 weight를 직접 제어 +// LateUpdate에서 갱신해 Animator가 같은 프레임에 0으로 세팅한 값을 덮어씀 +[RequireComponent(typeof(CharacterVoiceObject))] +public class LipSync : MonoBehaviour +{ + [Serializable] + private struct LipShape + { + public string Name; + [Range(0f, 100f)] public float MaxWeight; // amplitude=1일 때 도달할 weight + } + + [Header("Refs")] + [SerializeField] private SkinnedMeshRenderer _meshRenderer; + + // BMAC_OpenMouse_Big 클립의 입 관련 셰이프 프리셋 + [Header("Mouth Preset (입 최대 시 weight)")] + [SerializeField] private LipShape[] _shapes = + { + new() { Name = "Expression_SurpriesedMouth", MaxWeight = 50f }, + new() { Name = "Expression_MouthSad_L", MaxWeight = 10f }, + new() { Name = "Expression_MouthSad_R", MaxWeight = 10f }, + new() { Name = "Expression_MouthWide_L", MaxWeight = 30f }, + new() { Name = "Expression_MouthWide_R", MaxWeight = 30f }, + new() { Name = "Expression_LipsOh", MaxWeight = 100f }, + new() { Name = "Expression_LipsO", MaxWeight = 5f }, + }; + + [Header("Tuning")] + [SerializeField, Range(0f, 20f)] private float _amplitudeScale = 6f; // RMS → 0~1 매핑 배수 + [SerializeField, Range(0f, 0.05f)] private float _noiseFloor = 0.005f; + [SerializeField, Range(0f, 30f)] private float _smoothingSpeed = 15f; + [SerializeField] private int _sampleSize = 256; + + private AudioSource _audioSource; + private int[] _indices; + private float[] _sampleBuffer; + private float _currentAmplitude; + + private void Awake() + { + var voiceObj = GetComponent(); + _audioSource = voiceObj != null ? voiceObj.VoiceSource : null; + + // 메시 자동 탐색 — 첫 번째 셰이프 이름을 가진 SkinnedMeshRenderer 사용 + if (_meshRenderer == null && _shapes.Length > 0) + { + string probe = _shapes[0].Name; + foreach (var smr in GetComponentsInChildren(true)) + { + if (smr.sharedMesh != null && smr.sharedMesh.GetBlendShapeIndex(probe) >= 0) + { + _meshRenderer = smr; + break; + } + } + } + + // 인덱스 캐시 + _indices = new int[_shapes.Length]; + if (_meshRenderer != null && _meshRenderer.sharedMesh != null) + { + var mesh = _meshRenderer.sharedMesh; + for (int i = 0; i < _shapes.Length; i++) + { + _indices[i] = mesh.GetBlendShapeIndex(_shapes[i].Name); + if (_indices[i] < 0) + Debug.LogWarning($"[LipSync] 블렌드셰이프 없음: {_shapes[i].Name}", this); + } + } + else + { + for (int i = 0; i < _indices.Length; i++) _indices[i] = -1; + } + + if (_audioSource == null) + Debug.LogWarning("[LipSync] CharacterVoiceObject.VoiceSource 미할당", this); + + _sampleBuffer = new float[_sampleSize]; + } + + private void LateUpdate() + { + if (_audioSource == null || _meshRenderer == null) return; + + // PlayOneShot도 잡히도록 항상 샘플링 — 무음은 노이즈 플로어로 컷 + _audioSource.GetOutputData(_sampleBuffer, 0); + + float sumSq = 0f; + for (int i = 0; i < _sampleBuffer.Length; i++) + sumSq += _sampleBuffer[i] * _sampleBuffer[i]; + + float rms = Mathf.Sqrt(sumSq / _sampleBuffer.Length); + rms = Mathf.Max(0f, rms - _noiseFloor); + float target = Mathf.Clamp01(rms * _amplitudeScale); + + _currentAmplitude = Mathf.Lerp(_currentAmplitude, target, Time.deltaTime * _smoothingSpeed); + + // Animator가 같은 프레임에 0으로 덮은 값을 LateUpdate에서 다시 씌움 + for (int i = 0; i < _shapes.Length; i++) + { + if (_indices[i] < 0) continue; + _meshRenderer.SetBlendShapeWeight(_indices[i], _currentAmplitude * _shapes[i].MaxWeight); + } + } +} diff --git a/Assets/02_Scripts/Communication/Voice/LipSync.cs.meta b/Assets/02_Scripts/Communication/Voice/LipSync.cs.meta new file mode 100644 index 0000000..580b292 --- /dev/null +++ b/Assets/02_Scripts/Communication/Voice/LipSync.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ae0d9abbe32fe5b4e8e4886143e1e5e2 \ No newline at end of file diff --git a/Assets/02_Scripts/ISceneInitializable.cs b/Assets/02_Scripts/ISceneInitializable.cs new file mode 100644 index 0000000..0a35c0d --- /dev/null +++ b/Assets/02_Scripts/ISceneInitializable.cs @@ -0,0 +1,4 @@ +public interface ISceneInitializable +{ + public void OnSceneLoaded(); +} \ No newline at end of file diff --git a/Assets/02_Scripts/ISceneInitializable.cs.meta b/Assets/02_Scripts/ISceneInitializable.cs.meta new file mode 100644 index 0000000..5748e0a --- /dev/null +++ b/Assets/02_Scripts/ISceneInitializable.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 38039c1904e850146a01668e869851be \ No newline at end of file diff --git a/Assets/02_Scripts/Managers.meta b/Assets/02_Scripts/Managers.meta new file mode 100644 index 0000000..ee36650 --- /dev/null +++ b/Assets/02_Scripts/Managers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6265a74f863637546a971e91b58529e4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/02_Scripts/Managers/GameManager.cs b/Assets/02_Scripts/Managers/GameManager.cs new file mode 100644 index 0000000..d5318bc --- /dev/null +++ b/Assets/02_Scripts/Managers/GameManager.cs @@ -0,0 +1,23 @@ +using UnityEngine; + +public class GameManager : MonoBehaviour,ISceneInitializable +{ + public static GameManager Instance { get; private set; } + + private void Awake() + { + if (Instance == null) + { + Instance = this; //만들어진 자신을 인스턴스로 설정 + } + else + { + Destroy(gameObject); //이미 인스턴스가 있으면 자신을 파괴 + } + } + + public void OnSceneLoaded() + { + + } +} diff --git a/Assets/02_Scripts/Managers/GameManager.cs.meta b/Assets/02_Scripts/Managers/GameManager.cs.meta new file mode 100644 index 0000000..b0ac462 --- /dev/null +++ b/Assets/02_Scripts/Managers/GameManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 197a05ee04c0ac746adfedb27e01fdf5 \ No newline at end of file diff --git a/Assets/02_Scripts/Managers/InputManager.cs b/Assets/02_Scripts/Managers/InputManager.cs new file mode 100644 index 0000000..6361ae3 --- /dev/null +++ b/Assets/02_Scripts/Managers/InputManager.cs @@ -0,0 +1,40 @@ +using System; +using UnityEngine; +using UnityEngine.Events; +using UnityEngine.InputSystem; + +public class InputManager : MonoBehaviour, GameInput.IPlayerActions +{ + // 외부에서 InputManager.Instance.OnXxx_Event += handler 형태로 구독. + public static InputManager Instance { get; private set; } + + private GameInput _input; + + public event Action OnDialogNext_Event; + + private void Awake() + { + if (Instance == null) + { + Instance = this; //만들어진 자신을 인스턴스로 설정 + } + else + { + Destroy(gameObject); //이미 인스턴스가 있으면 자신을 파괴 + } + + _input = new GameInput(); + _input.Player.SetCallbacks(this); + } + + // GameInput은 활성/비활성 토글이 필요한 자원 ?. 처리로 Awake보다 OnEnable이 먼저 호출되는 경우 보호. + private void OnEnable() => _input?.Player.Enable(); + private void OnDisable() => _input?.Player.Disable(); + private void OnDestroy() => _input?.Dispose(); + + public void OnDialogNext(InputAction.CallbackContext ctx) + { + if (ctx.phase == InputActionPhase.Started) + OnDialogNext_Event?.Invoke(); + } +} diff --git a/Assets/02_Scripts/Managers/InputManager.cs.meta b/Assets/02_Scripts/Managers/InputManager.cs.meta new file mode 100644 index 0000000..b9de246 --- /dev/null +++ b/Assets/02_Scripts/Managers/InputManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 657e64bd5c92e4843a0d5b9892e05e45 \ No newline at end of file diff --git a/Assets/02_Scripts/Managers/LocationManager.cs b/Assets/02_Scripts/Managers/LocationManager.cs new file mode 100644 index 0000000..7553fed --- /dev/null +++ b/Assets/02_Scripts/Managers/LocationManager.cs @@ -0,0 +1,85 @@ +using UnityEngine; + +// 게임 씬에 하나. 현재 장소 프리팹을 띄우고, 장소 안 캐릭터 슬롯들의 등장 여부를 갱신한다. +// DialogPlayer가 대화 후보를 조회할 때 (Database, Current)를 여기서 가져간다. +public class LocationManager : MonoBehaviour +{ + public static LocationManager Instance { get; private set; } + + [SerializeField] private StoryDatabase _database; + + [Tooltip("장소 프리팹을 붙일 부모 (비우면 이 오브젝트)")] + [SerializeField] private Transform _locationRoot; + + [Tooltip("게임 시작 시 입장할 장소")] + [SerializeField] private LocationData _startLocation; + + public StoryDatabase Database => _database; + public LocationData Current { get; private set; } + + private GameObject _currentInstance; + + // 대화 중 진행도/트리거가 바뀌었을 때의 슬롯 갱신 예약. + // 대화 도중 화자를 꺼 버리면 재생이 깨지므로, 대화가 끝난 뒤로 미룬다. + private bool _refreshPending; + + private void Awake() + { + if (Instance != null && Instance != this) { Destroy(gameObject); return; } + Instance = this; + if (_locationRoot == null) _locationRoot = transform; + } + + private void Start() + { + if (StoryManager.Instance != null) + StoryManager.Instance.Changed += OnStoryChanged; + DialogPlayer.AnyActiveChanged += OnDialogActiveChanged; + + if (_startLocation != null) + MoveTo(_startLocation); + } + + private void OnDestroy() + { + if (Instance != this) return; + if (StoryManager.Instance != null) + StoryManager.Instance.Changed -= OnStoryChanged; + DialogPlayer.AnyActiveChanged -= OnDialogActiveChanged; + Instance = null; + } + + // 장소 이동 (이동 버튼의 UnityEvent 연결용). 대화 중에는 무시된다. + public void MoveTo(LocationData location) + { + if (location == null || DialogPlayer.IsAnyActive) return; + + Current = location; + if (_currentInstance != null) Destroy(_currentInstance); + _currentInstance = location.Prefab != null + ? Instantiate(location.Prefab, _locationRoot) + : null; + RefreshSlots(); + } + + // 대화로 진행도/트리거가 바뀌면 등장 캐릭터도 달라질 수 있다 + private void OnStoryChanged() + { + if (DialogPlayer.IsAnyActive) { _refreshPending = true; return; } + RefreshSlots(); + } + + private void OnDialogActiveChanged(bool active) + { + if (active || !_refreshPending) return; + _refreshPending = false; + RefreshSlots(); + } + + private void RefreshSlots() + { + if (_currentInstance == null || _database == null) return; + foreach (var slot in _currentInstance.GetComponentsInChildren(includeInactive: true)) + slot.Refresh(_database, Current); + } +} diff --git a/Assets/02_Scripts/Managers/LocationManager.cs.meta b/Assets/02_Scripts/Managers/LocationManager.cs.meta new file mode 100644 index 0000000..ea4c460 --- /dev/null +++ b/Assets/02_Scripts/Managers/LocationManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6244c1dafce01c04db303bbaf499f989 \ No newline at end of file diff --git a/Assets/02_Scripts/Managers/SceneLoadManager.cs b/Assets/02_Scripts/Managers/SceneLoadManager.cs new file mode 100644 index 0000000..4f18f59 --- /dev/null +++ b/Assets/02_Scripts/Managers/SceneLoadManager.cs @@ -0,0 +1,156 @@ +using System; +using UnityEngine; +using UnityEngine.SceneManagement; + +public class SceneLoadManager : MonoBehaviour +{ + public static SceneLoadManager Instance { get; private set; } + + [SerializeField] private GameObject _loadingRoot; + [SerializeField] private LoadingScreen _loadingScreen; + + private void Awake() + { + if (Instance == null) + { + Instance = this; // 만들어진 자신을 인스턴스로 설정 + } + 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 OnSceneLoaded(Scene scene, LoadSceneMode mode) + { + MonoBehaviour[] allObjs = FindObjectsByType(); + + foreach (var obj in allObjs) + { + if (obj is ISceneInitializable initializable) + { + // 씬에서 ISceneInitializable 인터페이스를 가진 오브젝트의 초기화 로직을 실행 + initializable.OnSceneLoaded(); + } + } + } + + public void SetSceneLoadingProgressValue(float value) + { + + } + + public async Awaitable SetSceneLoadingActive(bool isActive,float alphaTime) + { + if (isActive) + { + _loadingRoot.SetActive(true); + } + + + if (alphaTime > 0f) + { + await FadeLoadingCanvas(!isActive,alphaTime); + } + + if (!isActive) + _loadingRoot.SetActive(false); + } + + public async Awaitable FadeLoadingCanvas(bool isOut,float fadeTime) + { + float startAlpha = isOut ? 1f : 0f; + float endAlpha = isOut ? 0f : 1f; + + float timer = 0; + _loadingScreen.LoadingScreenCanvasGroup.alpha = startAlpha; + + while(timer < fadeTime) + { + timer += Time.deltaTime; + _loadingScreen.LoadingScreenCanvasGroup.alpha = Mathf.Lerp(startAlpha, endAlpha, timer / fadeTime); + await Awaitable.NextFrameAsync(this.destroyCancellationToken); + } + + _loadingScreen.LoadingScreenCanvasGroup.alpha = endAlpha; + } + + public void RequestSceneChange(string sceneName) + { + _ = SceneChange(sceneName); + } + + private async Awaitable SceneChange(string sceneName) + { + try + { + //로딩바 수치 0으로 설정 + SetSceneLoadingProgressValue(0f); + + //로딩창을 1초에 걸쳐 나타나게함 + await SetSceneLoadingActive(true,1f); + + AsyncOperation op = SceneManager.LoadSceneAsync(sceneName); + + //자동 전환을 하고 싶지 않을 경우 해당값을 false로 두었다가 true로 바꾸면 그 때 전환됨 + op.allowSceneActivation = false; + + //화면에 보여줄 로딩 수치 + float displayProgress = 0f; + + //op.progress 0.9가 데이터 로딩이 끝난 기준 allowSceneActivation이 트루면 다음으로 넘어가면서 op.isDone이 true가 된다. + 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(1); + + // 잠시 대기했다가 전환 + await Awaitable.WaitForSecondsAsync(1.0f, this.destroyCancellationToken); + + // 로딩바가 1초에 걸쳐 사라짐 + await SetSceneLoadingActive(false,1f); + + // 다음씬으로 넘어가도 됨을 알림 + op.allowSceneActivation = true; + + // 씬 활성화가 완전히 끝날 때까지 대기 + // allowSceneActivation가 true가 되고 완전히 전환되기까지는 몇프레임 걸림. op.isDone 은 이 과정이 끝난 뒤에 true가 됨. + while(!op.isDone) + { + await Awaitable.NextFrameAsync(this.destroyCancellationToken); + } + + } + catch (OperationCanceledException) + { + Debug.Log("씬 전환 작업이 취소됨"); + } + } +} \ No newline at end of file diff --git a/Assets/02_Scripts/Managers/SceneLoadManager.cs.meta b/Assets/02_Scripts/Managers/SceneLoadManager.cs.meta new file mode 100644 index 0000000..7fc2926 --- /dev/null +++ b/Assets/02_Scripts/Managers/SceneLoadManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e3e48e3fcdff9c843abca3c5583a43ad \ No newline at end of file diff --git a/Assets/02_Scripts/Managers/SoundManager.cs b/Assets/02_Scripts/Managers/SoundManager.cs new file mode 100644 index 0000000..ffc6d78 --- /dev/null +++ b/Assets/02_Scripts/Managers/SoundManager.cs @@ -0,0 +1,237 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using UnityEngine; +using UnityEngine.Audio; + +public class SoundManager : MonoBehaviour, ISceneInitializable +{ + 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 _sfxPool = new(); + + //BGM 전환(페이드)을 취소하기 위한 토큰. + private CancellationTokenSource _bgmCts; + + private void Awake() + { + if (Instance == null) + { + Instance = this; //만들어진 자신을 인스턴스로 설정 + Initialize(); + } + else + { + Destroy(gameObject); //이미 인스턴스가 있으면 자신을 파괴 (씬마다 놓아도 첫 번째만 남음) + } + } + + // 씬이 바뀌면 이전 씬에서 남은 전용(Override) BGM을 정리한다 (ISceneInitializable). + // 새 씬의 SceneBgm.Start가 SetDefaultBGM으로 그 씬의 곡을 넘겨주면 그대로 재생된다. + public void OnSceneLoaded() => ClearOverrideBGM(); + + 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) + { + if (_defaultBgm == clip) return; //같은 곡이면 페이드 재시작 없이 유지 + _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 공간음향 - 특정 위치에서 들림) + //minDistance: 이 거리 안에선 풀 볼륨으로 들림. 하늘 높이 터지는 폭죽처럼 멀리서 나는 큰 소리는 + //크게(예: 30~50) 잡아야 거리 감쇠로 사라지지 않는다. maxDistance: 감쇠 계산 상한. + public void PlaySFXAt(AudioClip clip, Vector3 position, float volume = 1f, + float minDistance = 1f, float maxDistance = 500f) + { + if (clip == null) return; + + AudioSource source = GetSfxSource(); + source.transform.position = position; + source.spatialBlend = 1f; //3D + source.rolloffMode = AudioRolloffMode.Logarithmic; + source.minDistance = minDistance; + source.maxDistance = maxDistance; + 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(); + 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); + } +} \ No newline at end of file diff --git a/Assets/02_Scripts/Managers/SoundManager.cs.meta b/Assets/02_Scripts/Managers/SoundManager.cs.meta new file mode 100644 index 0000000..6a74dae --- /dev/null +++ b/Assets/02_Scripts/Managers/SoundManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6de75b1b76588eb4280f8d111def36b7 \ No newline at end of file diff --git a/Assets/02_Scripts/Managers/StoryManager.cs b/Assets/02_Scripts/Managers/StoryManager.cs new file mode 100644 index 0000000..84a2c7e --- /dev/null +++ b/Assets/02_Scripts/Managers/StoryManager.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.IO; +using UnityEngine; + +// 이야기 상태(StoryState)의 유일한 접근 창구. +// 진행도/호감도/이력의 조회·변경과 저장/로드를 담당한다. +// 씬에 미리 배치하지 않아도 첫 접근 때 자동 생성된다. +public class StoryManager : MonoBehaviour +{ + public static StoryManager Instance { get; private set; } + + [Tooltip("플레이 시작 시 메인 진행도 초기값 — 씬을 직접 열어 테스트할 때 그 씬 기준 진행도로 시작하게. " + + "이 매니저가 살아남는 씬(플레이를 시작한 씬)의 값만 적용되고, 씬 전환으로 넘어온 경우엔 무시된다")] + [Min(0)] [SerializeField] private int _initialMainProgress; + + [Serializable] + private struct InitialAffection + { + public CharacterData Character; + public int Amount; + } + + [Tooltip("플레이 시작 시 호감도 초기값 — 이 씬만 직접 열어 테스트할 때 앞 과정 없이 원하는 호감도로 시작하게. " + + "진행도 초기값과 마찬가지로 플레이를 시작한 씬의 값만 적용되고, 씬 전환으로 넘어온 경우엔 무시된다")] + [SerializeField] private List _initialAffections = new(); + + private StoryState _state = new(); + + // 상태가 바뀔 때마다 발행 (호감도 게이지 등 UI 갱신용) + public event Action Changed; + + private void Awake() + { + if (Instance == null) + { + Instance = this; //만들어진 자신을 인스턴스로 설정 + _state.MainProgress = _initialMainProgress; // 테스트용 초기 진행도 (씬 전환으로 넘어온 중복은 여기 못 옴) + + // 테스트용 초기 호감도 (첫 인스턴스에서만 — 씬 전환으로 넘어온 경우엔 이 자리에 못 옴) + foreach (var init in _initialAffections) + if (init.Character != null) + _state.Affection[IdOf(init.Character)] = init.Amount; + } + else + { + Destroy(gameObject); //이미 인스턴스가 있으면 자신을 파괴 + } + } + + // ── 메인 진행도 ────────────────────────────────────────────── + public int MainProgress + { + get => _state.MainProgress; + set + { + if (_state.MainProgress == value) return; + _state.MainProgress = value; + Changed?.Invoke(); + } + } + + // UnityEvent 연결용 (버튼·이벤트존·노드 이벤트 등에서 진행도 조작) + public void AddMainProgress(int amount) => MainProgress += amount; + public void SetMainProgress(int value) => MainProgress = value; + + // ── 호감도 ────────────────────────────────────────────────── + 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 HasTrigger(string id) => _state.Triggers.Contains(id); + + // UnityEvent 연결용 (버튼·이벤트 등에서 트리거 켜기). 대화 조건 RequiredTriggerIds에서 검사한다. + public void SetTrigger(string id) + { + if (string.IsNullOrEmpty(id) || !_state.Triggers.Add(id)) return; + Changed?.Invoke(); + } + + // ── 선택 이력 ──────────────────────────────────────────────── + 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 void CallLoad() => Load(); + // 저장 파일이 있으면 불러온다. 성공 여부 반환. + public bool Load() + { + if (!File.Exists(SavePath)) + { + Debug.LogWarning("세이브 파일이 없습니다."); + return false; + } + + try + { + var loaded = StoryState.FromJson(File.ReadAllText(SavePath)); + if (loaded == null) + { + Debug.LogWarning("세이브 파일이 잘못되었습니다."); + return false; + } + + _state = loaded; + Changed?.Invoke(); + return true; + } + catch (Exception e) + { + Debug.LogWarning($"[StoryManager] 저장 파일 로드 실패: {e.Message}"); + return false; + } + } + + // Enter Play Mode에서 도메인 리로드를 꺼도 이전 인스턴스 참조가 안 남게 + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + private static void ResetStatics() => Instance = null; +} diff --git a/Assets/02_Scripts/Managers/StoryManager.cs.meta b/Assets/02_Scripts/Managers/StoryManager.cs.meta new file mode 100644 index 0000000..10389df --- /dev/null +++ b/Assets/02_Scripts/Managers/StoryManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f3f27662344dffe4a867f63cf1605d63 \ No newline at end of file diff --git a/Assets/02_Scripts/Story.meta b/Assets/02_Scripts/Story.meta new file mode 100644 index 0000000..a19a171 --- /dev/null +++ b/Assets/02_Scripts/Story.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4f3d4248a3d3f7743a90b8f88e1903bc +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/02_Scripts/Story/CharacterSlot.cs b/Assets/02_Scripts/Story/CharacterSlot.cs new file mode 100644 index 0000000..ffd6423 --- /dev/null +++ b/Assets/02_Scripts/Story/CharacterSlot.cs @@ -0,0 +1,20 @@ +using UnityEngine; + +// 장소 프리팹 안의 캐릭터 자리 하나. +// 이 캐릭터가 지금 이 장소에서 걸 수 있는 대화(StoryBeat)가 하나라도 있으면 캐릭터를 켜고, 없으면 끈다. +// 갱신은 LocationManager가 장소 입장 시 / 스토리 상태 변화 시 Refresh를 호출해서 이뤄진다. +// +// 배치법: 이 컴포넌트는 항상 켜져 있는 빈 오브젝트에 붙이고, +// 캐릭터 본체(CharacterVoiceObject + DialogPlayer)는 그 자식으로 두고 _character에 연결. +// 참고: 조건 없는 잡담 비트를 하나 등록해 두면 그 캐릭터는 항상 등장한다. +public class CharacterSlot : MonoBehaviour +{ + [Tooltip("캐릭터 본체 (CharacterVoiceObject + DialogPlayer가 붙은 자식 오브젝트)")] + [SerializeField] private CharacterVoiceObject _character; + + public void Refresh(StoryDatabase database, LocationData location) + { + if (_character == null || _character.Character == null) return; + _character.gameObject.SetActive(database.HasPlayableBeat(location, _character.Character)); + } +} diff --git a/Assets/02_Scripts/Story/CharacterSlot.cs.meta b/Assets/02_Scripts/Story/CharacterSlot.cs.meta new file mode 100644 index 0000000..91d552c --- /dev/null +++ b/Assets/02_Scripts/Story/CharacterSlot.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b8df8bed7639e304cbe98206c74e9f13 \ No newline at end of file diff --git a/Assets/02_Scripts/Story/Editor.meta b/Assets/02_Scripts/Story/Editor.meta new file mode 100644 index 0000000..1ea8c92 --- /dev/null +++ b/Assets/02_Scripts/Story/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8f8b437cb3f4c66489eed95f0c1158a7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/02_Scripts/Story/Editor/StoryBeatDrawer.cs b/Assets/02_Scripts/Story/Editor/StoryBeatDrawer.cs new file mode 100644 index 0000000..bebbd1b --- /dev/null +++ b/Assets/02_Scripts/Story/Editor/StoryBeatDrawer.cs @@ -0,0 +1,58 @@ +using UnityEditor; +using UnityEngine; + +// StoryBeat를 리스트에서 접었을 때 "Element N" 대신 요약 제목을 보여준다. +// Note가 있으면 Note 우선, 없으면 "[진행도] 장소 · 캐릭터 · 대화"를 자동 조합. +[CustomPropertyDrawer(typeof(StoryBeat))] +public class StoryBeatDrawer : PropertyDrawer +{ + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) + => EditorGUI.GetPropertyHeight(property, label, includeChildren: true); + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + string summary = BuildSummary(property); + if (!string.IsNullOrEmpty(summary)) + label = new GUIContent(summary, label.tooltip); + + EditorGUI.PropertyField(position, property, label, includeChildren: true); + } + + private static string BuildSummary(SerializedProperty property) + { + string note = property.FindPropertyRelative("Note").stringValue; + if (!string.IsNullOrWhiteSpace(note)) + return note; + + string location = NameOf(property.FindPropertyRelative("Location").objectReferenceValue); + string character = NameOf(property.FindPropertyRelative("Character").objectReferenceValue); + string group = NameOf(property.FindPropertyRelative("Group").objectReferenceValue); + + // 아무것도 안 채워진 새 항목은 기본 라벨(Element N) 유지 + if (location == null && character == null && group == null) + return null; + + var progress = property.FindPropertyRelative("Condition").FindPropertyRelative("MainProgress"); + int min = progress.FindPropertyRelative("Min").intValue; + int max = progress.FindPropertyRelative("Max").intValue; + string range = max >= 9999 ? $"[{min}~]" : $"[{min}~{max}]"; + + return $"{range} {location ?? "?"} · {character ?? "?"} · {group ?? "?"}"; + } + + // 에셋별 표시 이름 (지정 안 됐으면 에셋 이름, 그마저 없으면 null) + private static string NameOf(Object obj) + { + switch (obj) + { + case LocationData loc: + return string.IsNullOrWhiteSpace(loc.DisplayName) ? loc.name : loc.DisplayName; + case CharacterData ch: + return string.IsNullOrWhiteSpace(ch.Name) ? ch.name : ch.Name; + case DialogGroup grp: + return string.IsNullOrWhiteSpace(grp.DialogGroupName) ? grp.name : grp.DialogGroupName; + default: + return obj != null ? obj.name : null; + } + } +} diff --git a/Assets/02_Scripts/Story/Editor/StoryBeatDrawer.cs.meta b/Assets/02_Scripts/Story/Editor/StoryBeatDrawer.cs.meta new file mode 100644 index 0000000..99c8796 --- /dev/null +++ b/Assets/02_Scripts/Story/Editor/StoryBeatDrawer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: eade7ff33c9514b479d3f1e4ba2da912 \ No newline at end of file diff --git a/Assets/02_Scripts/Story/LocationData.cs b/Assets/02_Scripts/Story/LocationData.cs new file mode 100644 index 0000000..9259a91 --- /dev/null +++ b/Assets/02_Scripts/Story/LocationData.cs @@ -0,0 +1,14 @@ +using UnityEngine; + +// 게임 속 장소 하나 (로비, 사무실, 법정 …). +// StoryBeat가 "어느 장소의 대화인지"를 이 에셋으로 가리키고, +// LocationManager가 이동 시 Prefab을 띄운다. +[CreateAssetMenu(menuName = "Story/Location")] +public class LocationData : ScriptableObject +{ + [Tooltip("장소 표시 이름 (이동 메뉴 등 UI용)")] + public string DisplayName; + + [Tooltip("이 장소의 프리팹 — 배경 + 캐릭터 슬롯(CharacterSlot)들 + 이동 버튼 등")] + public GameObject Prefab; +} diff --git a/Assets/02_Scripts/Story/LocationData.cs.meta b/Assets/02_Scripts/Story/LocationData.cs.meta new file mode 100644 index 0000000..f50017c --- /dev/null +++ b/Assets/02_Scripts/Story/LocationData.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e92ba75bdc5f28f4983e164d45c3b27d \ No newline at end of file diff --git a/Assets/02_Scripts/Story/StoryBeat.cs b/Assets/02_Scripts/Story/StoryBeat.cs new file mode 100644 index 0000000..2e6f536 --- /dev/null +++ b/Assets/02_Scripts/Story/StoryBeat.cs @@ -0,0 +1,29 @@ +using System; +using UnityEngine; + +// 스토리 비트 하나 — "어느 장소에서, 어느 캐릭터가, 어떤 조건일 때, 무슨 대화를 하는가". +// StoryDatabase의 목록이 게임 전체 스토리 흐름의 단일 원본이다. +[Serializable] +public class StoryBeat +{ + [Tooltip("에디터 식별용 메모 (게임에는 안 나옴)")] + public string Note; + + [Tooltip("이 대화가 일어나는 장소")] + public LocationData Location; + + [Tooltip("대화를 거는 캐릭터")] + public CharacterData Character; + + [Tooltip("재생할 대화 그래프 (.dlg = DialogGroup)")] + public DialogGroup Group; + + [Tooltip("활성 조건 (진행도/호감도 범위 + 트리거)")] + public DialogCondition Condition = new(); + + [Tooltip("이 대화를 처음 완료하면 메인 진행도 +N (필수 대화가 아니면 0)")] + [Min(0)] public int ProgressOnComplete; + + [Tooltip("대화 선택 메뉴에 표시할 이름 (비우면 그룹 이름 사용)")] + public string MenuLabel; +} diff --git a/Assets/02_Scripts/Story/StoryBeat.cs.meta b/Assets/02_Scripts/Story/StoryBeat.cs.meta new file mode 100644 index 0000000..c317379 --- /dev/null +++ b/Assets/02_Scripts/Story/StoryBeat.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1b03cd6e1bb5fbb44b82670d620875c3 \ No newline at end of file diff --git a/Assets/02_Scripts/Story/StoryDatabase.cs b/Assets/02_Scripts/Story/StoryDatabase.cs new file mode 100644 index 0000000..7d681f7 --- /dev/null +++ b/Assets/02_Scripts/Story/StoryDatabase.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using UnityEngine; + +// 스토리 비트 전체 목록 — "누가 어디서 언제 무슨 대화를 하는가"의 단일 원본. +// 캐릭터 슬롯 활성화(HasPlayableBeat)와 DialogPlayer의 대화 후보(GetPlayableBeats)가 +// 전부 이 에셋을 조회한다. 게임에 하나만 만들어 LocationManager에 꽂아 쓴다. +[CreateAssetMenu(menuName = "Story/Story Database")] +public class StoryDatabase : ScriptableObject +{ + [Tooltip("스토리 비트 목록 — 위에 있을수록 대화 선택 메뉴에서 우선순위가 높다. " + + "진행도 순으로 정렬해 두면 이 목록이 곧 시나리오 진행표가 된다")] + [SerializeField] private List _beats = new(); + + public IReadOnlyList Beats => _beats; + + // 이 장소·캐릭터의 비트 중 지금 조건을 만족하는 것들 (목록 순서 유지) + public List GetPlayableBeats(LocationData location, CharacterData character) + { + var result = new List(); + foreach (var beat in _beats) + if (IsPlayable(beat, location, character)) + result.Add(beat); + return result; + } + + // 이 장소에서 이 캐릭터가 걸 수 있는 대화가 하나라도 있는가 (캐릭터 슬롯 활성화 판정) + public bool HasPlayableBeat(LocationData location, CharacterData character) + { + foreach (var beat in _beats) + if (IsPlayable(beat, location, character)) + return true; + return false; + } + + // 강제 재생(DialogPlayer.PlayGroup)용 — 이 그룹이 등록된 첫 비트 (없으면 null) + public StoryBeat FindBeat(DialogGroup group) + { + if (group == null) return null; + foreach (var beat in _beats) + if (beat.Group == group) + return beat; + return null; + } + + private static bool IsPlayable(StoryBeat beat, LocationData location, CharacterData character) + { + if (beat.Location != location || beat.Character != character || beat.Group == null) + return false; + return beat.Condition == null || beat.Condition.IsMet(character); + } +} diff --git a/Assets/02_Scripts/Story/StoryDatabase.cs.meta b/Assets/02_Scripts/Story/StoryDatabase.cs.meta new file mode 100644 index 0000000..fcedcdf --- /dev/null +++ b/Assets/02_Scripts/Story/StoryDatabase.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0c7c50927085b814097de120bc52f6c2 \ No newline at end of file diff --git a/Assets/02_Scripts/Story/StoryState.cs b/Assets/02_Scripts/Story/StoryState.cs new file mode 100644 index 0000000..42ad2a8 --- /dev/null +++ b/Assets/02_Scripts/Story/StoryState.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +// 이야기 진행 상태 데이터 (메인 진행도 / 호감도 / 대화 이력 / 선택 이력). +// 상태 보관과 JSON 변환만 담당한다 — 게임 로직에서는 StoryManager를 통해 접근할 것. +public class StoryState +{ + public int MainProgress; + public readonly Dictionary Affection = new(); // 캐릭터 Id → 호감도 + public readonly HashSet CompletedDialogs = new(); // 완료한 DialogGroup 이름 + public readonly HashSet ChosenCodes = new(); // 골랐던 선택지 Code + public readonly HashSet Triggers = new(); // 켜진 트리거 Id (대화 조건 검사용) + + public void Clear() + { + MainProgress = 0; + Affection.Clear(); + CompletedDialogs.Clear(); + ChosenCodes.Clear(); + Triggers.Clear(); + } + + // ── JSON 변환 ──────────────────────────────────────────────── + // JsonUtility가 Dictionary/HashSet을 직렬화하지 못해 리스트로 바꿔 저장한다. + [Serializable] + private class JsonData + { + public int MainProgress; + public List AffectionIds = new(); + public List AffectionValues = new(); + public List CompletedDialogs = new(); + public List ChosenCodes = new(); + public List Triggers = 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); + data.Triggers.AddRange(Triggers); + return JsonUtility.ToJson(data, prettyPrint: true); + } + + // json이 유효하지 않으면 null 반환 + public static StoryState FromJson(string json) + { + var data = JsonUtility.FromJson(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); + state.Triggers.UnionWith(data.Triggers ?? new List()); + return state; + } +} diff --git a/Assets/02_Scripts/Story/StoryState.cs.meta b/Assets/02_Scripts/Story/StoryState.cs.meta new file mode 100644 index 0000000..666810d --- /dev/null +++ b/Assets/02_Scripts/Story/StoryState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d47259c7403ae7e42b37a411170bc21c \ No newline at end of file diff --git a/Assets/02_Scripts/_Data.meta b/Assets/02_Scripts/_Data.meta new file mode 100644 index 0000000..1c7a157 --- /dev/null +++ b/Assets/02_Scripts/_Data.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 65db5ab1573a5994aa96d657de132d43 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/02_Scripts/_Data/Character.meta b/Assets/02_Scripts/_Data/Character.meta new file mode 100644 index 0000000..377f057 --- /dev/null +++ b/Assets/02_Scripts/_Data/Character.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f7bdce60fa4722c409858d6a5a234ec3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/02_Scripts/_Data/Character/CharacterData.cs b/Assets/02_Scripts/_Data/Character/CharacterData.cs new file mode 100644 index 0000000..8f15688 --- /dev/null +++ b/Assets/02_Scripts/_Data/Character/CharacterData.cs @@ -0,0 +1,9 @@ +using UnityEngine; + +[CreateAssetMenu(menuName = "Character/CharacterData")] +public class CharacterData : ScriptableObject +{ + public string Id; + public string Name; + public Sprite Portrait; +} diff --git a/Assets/02_Scripts/_Data/Character/CharacterData.cs.meta b/Assets/02_Scripts/_Data/Character/CharacterData.cs.meta new file mode 100644 index 0000000..7a183a8 --- /dev/null +++ b/Assets/02_Scripts/_Data/Character/CharacterData.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c44a4226e5f3e344a96eac21f829952f \ No newline at end of file diff --git a/Assets/02_Scripts/_Data/Communication.meta b/Assets/02_Scripts/_Data/Communication.meta new file mode 100644 index 0000000..b8f2463 --- /dev/null +++ b/Assets/02_Scripts/_Data/Communication.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8078f79ae4624044fa52f6d4986a10bd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/02_Scripts/_Data/Communication/Dialog.meta b/Assets/02_Scripts/_Data/Communication/Dialog.meta new file mode 100644 index 0000000..5849832 --- /dev/null +++ b/Assets/02_Scripts/_Data/Communication/Dialog.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 864a90870193471458445ecc75905891 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/02_Scripts/_Data/Communication/Dialog/ExpressionData.cs b/Assets/02_Scripts/_Data/Communication/Dialog/ExpressionData.cs new file mode 100644 index 0000000..7b6868f --- /dev/null +++ b/Assets/02_Scripts/_Data/Communication/Dialog/ExpressionData.cs @@ -0,0 +1,10 @@ +using UnityEngine; + +[CreateAssetMenu(menuName = "Communication/Expression")] +public class ExpressionData : ScriptableObject +{ + [HideInInspector] public int AnimationLayer = 1; + public string StateName; + public float CrossFadeDuration = 0.2f; + public AnimationClip AnimClip; +} \ No newline at end of file diff --git a/Assets/02_Scripts/_Data/Communication/Dialog/ExpressionData.cs.meta b/Assets/02_Scripts/_Data/Communication/Dialog/ExpressionData.cs.meta new file mode 100644 index 0000000..c3d0810 --- /dev/null +++ b/Assets/02_Scripts/_Data/Communication/Dialog/ExpressionData.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b705522cfca24d141b3d560aeffb2c14 \ No newline at end of file diff --git a/Assets/02_Scripts/_Data/Communication/Dialog/GestureData.cs b/Assets/02_Scripts/_Data/Communication/Dialog/GestureData.cs new file mode 100644 index 0000000..98421fe --- /dev/null +++ b/Assets/02_Scripts/_Data/Communication/Dialog/GestureData.cs @@ -0,0 +1,10 @@ +using UnityEngine; + +[CreateAssetMenu(menuName = "Communication/Gesture")] +public class GestureData : ScriptableObject +{ + [HideInInspector] public int AnimationLayer = 0; + public string StateName; + public float CrossFadeDuration = 0.2f; + public AnimationClip AnimClip; +} diff --git a/Assets/02_Scripts/_Data/Communication/Dialog/GestureData.cs.meta b/Assets/02_Scripts/_Data/Communication/Dialog/GestureData.cs.meta new file mode 100644 index 0000000..080886d --- /dev/null +++ b/Assets/02_Scripts/_Data/Communication/Dialog/GestureData.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7287df3d52d586a4085d412938dae461 \ No newline at end of file diff --git a/Assets/02_Scripts/_Data/Communication/Voice.meta b/Assets/02_Scripts/_Data/Communication/Voice.meta new file mode 100644 index 0000000..d006564 --- /dev/null +++ b/Assets/02_Scripts/_Data/Communication/Voice.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e5b1fe96389ab554db24396abacdc278 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/02_Scripts/_Data/Communication/Voice/VoiceClip.cs b/Assets/02_Scripts/_Data/Communication/Voice/VoiceClip.cs new file mode 100644 index 0000000..50bdfe5 --- /dev/null +++ b/Assets/02_Scripts/_Data/Communication/Voice/VoiceClip.cs @@ -0,0 +1,8 @@ +using UnityEngine; + +[CreateAssetMenu(menuName = "Communication/VoiceClip")] +public class VoiceClip : ScriptableObject +{ + public string VoiceCode; //해당 음성 고유코드 + public AudioClip Clip; +} \ No newline at end of file diff --git a/Assets/02_Scripts/_Data/Communication/Voice/VoiceClip.cs.meta b/Assets/02_Scripts/_Data/Communication/Voice/VoiceClip.cs.meta new file mode 100644 index 0000000..6b66801 --- /dev/null +++ b/Assets/02_Scripts/_Data/Communication/Voice/VoiceClip.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: fc306ebaeba77b94dbb631e9fdc0e864 \ No newline at end of file diff --git a/Assets/02_Scripts/_UI.meta b/Assets/02_Scripts/_UI.meta new file mode 100644 index 0000000..cb208d5 --- /dev/null +++ b/Assets/02_Scripts/_UI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5dc4fbcfba3dd54459c9c67d3c054a66 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/02_Scripts/_UI/Communication.meta b/Assets/02_Scripts/_UI/Communication.meta new file mode 100644 index 0000000..4152a1e --- /dev/null +++ b/Assets/02_Scripts/_UI/Communication.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 609e19799be7a7c498681b5d014d0f40 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/02_Scripts/_UI/Communication/ChoiceHud.cs b/Assets/02_Scripts/_UI/Communication/ChoiceHud.cs new file mode 100644 index 0000000..e63c10b --- /dev/null +++ b/Assets/02_Scripts/_UI/Communication/ChoiceHud.cs @@ -0,0 +1,138 @@ +using System.Collections.Generic; +using TMPro; +using UnityEngine; + +// 스크린 스페이스 선택지 UI 싱글턴. +// DialogPlayer가 Show()를 await해서 선택된 인덱스를 받아감. +public class ChoiceHud : MonoBehaviour +{ + public static ChoiceHud Instance { get; private set; } + + [Header("Refs")] + [SerializeField] private GameObject _root; + [SerializeField] private Transform _rowContainer; + [SerializeField] private DialogChoiceRow _rowPrefab; + [SerializeField] private GameObject _dialogSelectPanelObj; + [SerializeField] private TMP_Text ChoiceQuestion; + [SerializeField] private RectTransform _ScrollRect; + + + [Header("Layout")] + [Tooltip("선택지 한 줄의 높이")] + [SerializeField] private float _rowHeight = 40f; + [Tooltip("이 줄 수까지만 높이를 늘리고, 넘어가면 스크롤로 본다")] + [SerializeField] private int _maxVisibleRows = 3; + + private readonly List _rows = new(); + private AwaitableCompletionSource _completion; + + private void Awake() + { + if (Instance != null && Instance != this) { Destroy(gameObject); return; } + Instance = this; + if (_root == null) _root = gameObject; + Hide(); + } + + private void OnDestroy() + { + if (Instance == this) Instance = null; + } + + private void OnDisable() + { + // 진행 중 대기 정리 (씬 전환 등으로 비활성화될 때) + _completion?.TrySetCanceled(); + _completion = null; + } + + // 대기 중인 선택을 취소하고 메뉴를 닫는다 (다른 NPC와 대화를 새로 시작할 때 등). + // 취소된 쪽의 await Show(...)는 OperationCanceledException으로 빠져나간다. + public void CancelPending() + { + _completion?.TrySetCanceled(); + } + + public async Awaitable Show(string choiceQuestion, List choices) + { + if (choices == null || choices.Count == 0) return 0; + + // 이전 호출이 아직 대기 중이면 먼저 취소한다 (마지막 호출이 이긴다). + // 취소된 쪽은 아래 finally까지 이 자리에서 정리를 마치고 빠져나간다. + CancelPending(); + + SetQuestion(choiceQuestion); + ClearRows(); + for (int i = 0; i < choices.Count; i++) + { + var row = Instantiate(_rowPrefab, _rowContainer); + row.Bind(i, DialogVariables.Format(choices[i].ChoiceText)); // {key} 토큰 치환 + row.OnClicked += HandleClicked; + _rows.Add(row); + } + ResizeToRowCount(choices.Count); + + _root.SetActive(true); + var completion = new AwaitableCompletionSource(); + _completion = completion; + + int result; + try + { + result = await completion.Awaitable; + } + finally + { + // 내가 아직 현재 세션일 때만 정리 — 취소 직후 다른 NPC가 새로 띄운 메뉴를 지우면 안 된다 + if (_completion == completion) + { + _completion = null; + Hide(); + } + } + return result; + } + + private void HandleClicked(int index) + { + _completion?.TrySetResult(index); + } + + // 선택지 개수에 맞춰 스크롤 영역 높이 조절 (1개=40, 2개=80, 3개=120 …). + // _maxVisibleRows를 넘는 개수는 높이를 더 늘리지 않고 스크롤로 본다. + private void ResizeToRowCount(int count) + { + if (_ScrollRect == null) return; + int visibleRows = Mathf.Min(count, _maxVisibleRows); + _ScrollRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, visibleRows * _rowHeight); + } + + private void Hide() + { + SetQuestion(null); + ClearRows(); + if (_root != null) _root.SetActive(false); + } + + // 질문이 비어 있으면 질문 텍스트 자체를 숨긴다. (기본값: 질문 없음 → 안 보임) + // 질문은 보통 노드의 대사(TalkText)로 대신하므로 ChoiceQuestion은 비워둬도 된다. + private void SetQuestion(string question) + { + if (ChoiceQuestion == null) return; + string text = DialogVariables.Format(question); // {key} 토큰 치환 + bool hasQuestion = !string.IsNullOrWhiteSpace(text); + ChoiceQuestion.text = hasQuestion ? text : string.Empty; + ChoiceQuestion.gameObject.SetActive(hasQuestion); + } + + private void ClearRows() + { + foreach (var row in _rows) + { + if (row == null) continue; + row.OnClicked -= HandleClicked; + Destroy(row.gameObject); + } + _rows.Clear(); + } +} diff --git a/Assets/02_Scripts/_UI/Communication/ChoiceHud.cs.meta b/Assets/02_Scripts/_UI/Communication/ChoiceHud.cs.meta new file mode 100644 index 0000000..6b564c3 --- /dev/null +++ b/Assets/02_Scripts/_UI/Communication/ChoiceHud.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 66c54b72d69da6b4e9c30ef648aade40 \ No newline at end of file diff --git a/Assets/02_Scripts/_UI/Communication/DialogChoiceRow.cs b/Assets/02_Scripts/_UI/Communication/DialogChoiceRow.cs new file mode 100644 index 0000000..37f1f4e --- /dev/null +++ b/Assets/02_Scripts/_UI/Communication/DialogChoiceRow.cs @@ -0,0 +1,26 @@ +using System; +using TMPro; +using UnityEngine; +using UnityEngine.UI; + +public class DialogChoiceRow : MonoBehaviour +{ + [SerializeField] private TMP_Text _text; + [SerializeField] private Button _button; + + public event Action OnClicked; + + private int _index; + + private void Awake() + { + if (_button != null) + _button.onClick.AddListener(() => OnClicked?.Invoke(_index)); + } + + public void Bind(int index, string text) + { + _index = index; + if (_text != null) _text.text = text; + } +} diff --git a/Assets/02_Scripts/_UI/Communication/DialogChoiceRow.cs.meta b/Assets/02_Scripts/_UI/Communication/DialogChoiceRow.cs.meta new file mode 100644 index 0000000..cca6128 --- /dev/null +++ b/Assets/02_Scripts/_UI/Communication/DialogChoiceRow.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 63cb7682c2757f74cad43848f593838f \ No newline at end of file diff --git a/Assets/02_Scripts/_UI/Communication/DialogHud.cs b/Assets/02_Scripts/_UI/Communication/DialogHud.cs new file mode 100644 index 0000000..efeca61 --- /dev/null +++ b/Assets/02_Scripts/_UI/Communication/DialogHud.cs @@ -0,0 +1,49 @@ +using TMPro; +using UnityEngine; + +// 화면 하단 등에 고정된 스크린 스페이스 대사 HUD 싱글턴. +// DialogPlayer가 대사 노드를 재생할 때 Show()로 화자 이름 + 대사를 표시한다. +public class DialogHud : MonoBehaviour +{ + public static DialogHud Instance { get; private set; } + + [Header("Refs")] + [SerializeField] private GameObject _panel; // 대사 패널(토글 대상). 보통 이 오브젝트의 자식. + [SerializeField] private TMP_Text _speakerName; + [SerializeField] private TMP_Text _dialogueText; + + private void Awake() + { + if (Instance != null && Instance != this) { Destroy(gameObject); return; } + Instance = this; + Hide(); + } + + private void OnDestroy() + { + if (Instance == this) Instance = null; + } + + // 화자 이름 + 대사 표시. + // - speakerNameOverride가 비어있지 않으면 CharacterData.Name 대신 그 이름을 표시한다 (예: "???") + public void Show(CharacterData speaker, string text, string speakerNameOverride = null) + { + if (_speakerName != null) + { + string speakerName = !string.IsNullOrEmpty(speakerNameOverride) ? speakerNameOverride + : speaker != null ? speaker.Name : string.Empty; + _speakerName.text = DialogVariables.Format(speakerName); // {key} 토큰 치환 + } + if (_dialogueText != null) + _dialogueText.text = DialogVariables.Format(text); // {key} 토큰 치환 + + if (_panel != null) _panel.SetActive(true); + } + + public void Hide() + { + if (_dialogueText != null) _dialogueText.text = string.Empty; + if (_speakerName != null) _speakerName.text = string.Empty; + if (_panel != null) _panel.SetActive(false); + } +} diff --git a/Assets/02_Scripts/_UI/Communication/DialogHud.cs.meta b/Assets/02_Scripts/_UI/Communication/DialogHud.cs.meta new file mode 100644 index 0000000..f7e9489 --- /dev/null +++ b/Assets/02_Scripts/_UI/Communication/DialogHud.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8c591cc9e4d86544fa1f82ba1732b43f \ No newline at end of file diff --git a/Assets/02_Scripts/_UI/View.meta b/Assets/02_Scripts/_UI/View.meta new file mode 100644 index 0000000..4cfd21b --- /dev/null +++ b/Assets/02_Scripts/_UI/View.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 29e6ad56907b84e488fdf6e8cb460da4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/02_Scripts/_UI/View/LoadingScreen.cs b/Assets/02_Scripts/_UI/View/LoadingScreen.cs new file mode 100644 index 0000000..6b3f871 --- /dev/null +++ b/Assets/02_Scripts/_UI/View/LoadingScreen.cs @@ -0,0 +1,15 @@ +using TMPro; +using UnityEngine; +using UnityEngine.UI; + +public class LoadingScreen : MonoBehaviour +{ + public Image LoadingImage; + public TextMeshProUGUI LoadingTextMeshProUGUI; + public CanvasGroup LoadingScreenCanvasGroup; + + private void Awake() + { + LoadingScreenCanvasGroup = GetComponent(); + } +} \ No newline at end of file diff --git a/Assets/02_Scripts/_UI/View/LoadingScreen.cs.meta b/Assets/02_Scripts/_UI/View/LoadingScreen.cs.meta new file mode 100644 index 0000000..746ad40 --- /dev/null +++ b/Assets/02_Scripts/_UI/View/LoadingScreen.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ceb9bbda4425c9643ae577548ba97493 \ No newline at end of file diff --git a/Assets/02_Scripts/_Util.meta b/Assets/02_Scripts/_Util.meta new file mode 100644 index 0000000..6c7a377 --- /dev/null +++ b/Assets/02_Scripts/_Util.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 08427dc2c0f47e8448eddbc2c3591635 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/02_Scripts/_Util/Editor.meta b/Assets/02_Scripts/_Util/Editor.meta new file mode 100644 index 0000000..a23e668 --- /dev/null +++ b/Assets/02_Scripts/_Util/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f216d086afefbc04bbc61ce6903c421a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/02_Scripts/_Util/Editor/IntRangeDrawer.cs b/Assets/02_Scripts/_Util/Editor/IntRangeDrawer.cs new file mode 100644 index 0000000..92a5070 --- /dev/null +++ b/Assets/02_Scripts/_Util/Editor/IntRangeDrawer.cs @@ -0,0 +1,35 @@ +using UnityEditor; +using UnityEngine; + +// IntRange를 "라벨 [Min] ~ [Max]" 한 줄로 그린다. +[CustomPropertyDrawer(typeof(IntRange))] +public class IntRangeDrawer : PropertyDrawer +{ + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) + => EditorGUIUtility.singleLineHeight; + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + EditorGUI.BeginProperty(position, label, property); + position = EditorGUI.PrefixLabel(position, label); + + int indent = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; // PrefixLabel 뒤 필드가 들여쓰기에 밀리지 않게 + + const float tildeWidth = 14f; + float fieldWidth = (position.width - tildeWidth) * 0.5f; + var minRect = new Rect(position.x, position.y, fieldWidth, position.height); + var tildeRect = new Rect(minRect.xMax, position.y, tildeWidth, position.height); + var maxRect = new Rect(tildeRect.xMax, position.y, fieldWidth, position.height); + + EditorGUI.PropertyField(minRect, property.FindPropertyRelative("Min"), GUIContent.none); + EditorGUI.LabelField(tildeRect, "~", CenteredLabel); + EditorGUI.PropertyField(maxRect, property.FindPropertyRelative("Max"), GUIContent.none); + + EditorGUI.indentLevel = indent; + EditorGUI.EndProperty(); + } + + static GUIStyle _centered; + static GUIStyle CenteredLabel => _centered ??= new GUIStyle(EditorStyles.label) { alignment = TextAnchor.MiddleCenter }; +} diff --git a/Assets/02_Scripts/_Util/Editor/IntRangeDrawer.cs.meta b/Assets/02_Scripts/_Util/Editor/IntRangeDrawer.cs.meta new file mode 100644 index 0000000..99d3f4d --- /dev/null +++ b/Assets/02_Scripts/_Util/Editor/IntRangeDrawer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 878034b1fe8afe64eaeb9b2e9b329778 \ No newline at end of file diff --git a/Assets/02_Scripts/_Util/IntRange.cs b/Assets/02_Scripts/_Util/IntRange.cs new file mode 100644 index 0000000..3b90a4b --- /dev/null +++ b/Assets/02_Scripts/_Util/IntRange.cs @@ -0,0 +1,13 @@ +using System; + +// 정수 범위 (양끝 포함). 인스펙터에서는 "라벨 [Min] ~ [Max]" 한 줄로 그려진다 (IntRangeDrawer). +[Serializable] +public struct IntRange +{ + public int Min; + public int Max; + + public IntRange(int min, int max) { Min = min; Max = max; } + + public bool Contains(int value) => value >= Min && value <= Max; +} diff --git a/Assets/02_Scripts/_Util/IntRange.cs.meta b/Assets/02_Scripts/_Util/IntRange.cs.meta new file mode 100644 index 0000000..e9380f0 --- /dev/null +++ b/Assets/02_Scripts/_Util/IntRange.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0ca113007f09def4f82463aef8aca122 \ No newline at end of file diff --git a/Assets/07_Data.meta b/Assets/07_Data.meta new file mode 100644 index 0000000..f93adce --- /dev/null +++ b/Assets/07_Data.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8aafb3b420e6c6d40949d3acf2ccc2d0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/07_Data/Characters.meta b/Assets/07_Data/Characters.meta new file mode 100644 index 0000000..a03066f --- /dev/null +++ b/Assets/07_Data/Characters.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: da61428585296804c9a4d897f201b1f2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/07_Data/Characters/TestCharacter.asset b/Assets/07_Data/Characters/TestCharacter.asset new file mode 100644 index 0000000..8ab9e66 --- /dev/null +++ b/Assets/07_Data/Characters/TestCharacter.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc17868a02b221bc83af7cbd3f86a0d4a6ac497a0bc24a4b29d9c884de103670 +size 523 diff --git a/Assets/07_Data/Characters/TestCharacter.asset.meta b/Assets/07_Data/Characters/TestCharacter.asset.meta new file mode 100644 index 0000000..4137aea --- /dev/null +++ b/Assets/07_Data/Characters/TestCharacter.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e07e7378d9bcc4f458036eb19c476048 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/07_Data/DialogGraph.meta b/Assets/07_Data/DialogGraph.meta new file mode 100644 index 0000000..84b164c --- /dev/null +++ b/Assets/07_Data/DialogGraph.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d96871b2807a4494ebeb12a6f9c4a7e2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/07_Data/DialogGraph/TestDialog.dlg b/Assets/07_Data/DialogGraph/TestDialog.dlg new file mode 100644 index 0000000..49ece09 --- /dev/null +++ b/Assets/07_Data/DialogGraph/TestDialog.dlg @@ -0,0 +1,397 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 61 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 12501, guid: 0000000000000000e000000000000000, type: 0} + m_Name: TestDialog + m_EditorClassIdentifier: UnityEditor.dll::Unity.GraphToolkit.Editor.Implementation.GraphObjectImp + m_GraphModel: + rid: 6600512887158735084 + references: + version: 2 + RefIds: + - rid: -2 + type: {class: , ns: , asm: } + - rid: 6600512887158735084 + type: {class: GraphModelImp, ns: Unity.GraphToolkit.Editor.Implementation, asm: UnityEditor.GraphToolkitModule} + data: + m_Guid: + m_Value0: 9794163788213571237 + m_Value1: 6016515793404660238 + m_HashGuid: + serializedVersion: 2 + Hash: a5ce76dc12dceb870e8a69bb3cf57e53 + m_Name: + m_GraphNodeModels: + - rid: 6600512887158735087 + - rid: 6600512887158735111 + - rid: 6600512887158735114 + m_GraphWireModels: + - rid: 6600512887158735113 + - rid: 6600512887158735115 + m_GraphStickyNoteModels: [] + m_GraphPlacematModels: [] + m_GraphVariableModels: [] + m_GraphPortalModels: [] + m_SectionModels: + - rid: 6600512887158735085 + m_LocalSubgraphs: [] + m_LastKnownBounds: + serializedVersion: 2 + x: -404 + y: -43 + width: 1186 + height: 450 + m_GraphElementMetaData: + - m_Guid: + m_Value0: 8557079674992562767 + m_Value1: 2087592960078497513 + m_HashGuid: + serializedVersion: 2 + Hash: 4f92a586a0dac076e9c61595bc9ef81c + m_Category: 0 + m_Index: 0 + - m_Guid: + m_Value0: 13680180985569699724 + m_Value1: 14420258866515227426 + m_HashGuid: + serializedVersion: 2 + Hash: 8c67ef2b20c4d9bd2233d18b090d1fc8 + m_Category: 0 + m_Index: 1 + - m_Guid: + m_Value0: 10165275467295493703 + m_Value1: 18408072349542380157 + m_HashGuid: + serializedVersion: 2 + Hash: 470e24f82d50128d7df22992459c76ff + m_Category: 2 + m_Index: 0 + - m_Guid: + m_Value0: 4814898993618757843 + m_Value1: 13954766896149483658 + m_HashGuid: + serializedVersion: 2 + Hash: d32808d01ff5d1428ad05e788e4aa9c1 + m_Category: 0 + m_Index: 2 + - m_Guid: + m_Value0: 7298122618344812091 + m_Value1: 2621210583193601063 + m_HashGuid: + serializedVersion: 2 + Hash: 3baeafb1da23486527c4bf3c23696024 + m_Category: 2 + m_Index: 1 + m_EntryPoint: + rid: 6600512887158735087 + m_Graph: + rid: 6600512887158735086 + - rid: 6600512887158735085 + type: {class: SectionModel, ns: Unity.GraphToolkit.Editor, asm: UnityEditor.GraphToolkitModule} + data: + m_Guid: + m_Value0: 13894749353265220958 + m_Value1: 16881712273075319024 + m_HashGuid: + serializedVersion: 2 + Hash: 5e4d7654eb10d4c0f0b00185dee347ea + m_Version: 2 + m_Items: [] + m_Title: + - rid: 6600512887158735086 + type: {class: DialogGraph, ns: DinoLove.Dialog.GraphTool.Editor, asm: Assembly-CSharp-Editor} + data: + - rid: 6600512887158735087 + type: {class: UserNodeModelImp, ns: Unity.GraphToolkit.Editor.Implementation, asm: UnityEditor.GraphToolkitModule} + data: + m_Guid: + m_Value0: 8557079674992562767 + m_Value1: 2087592960078497513 + m_HashGuid: + serializedVersion: 2 + Hash: 4f92a586a0dac076e9c61595bc9ef81c + m_Version: 2 + m_Position: {x: -85.50522, y: -42.782944} + m_Title: + m_Tooltip: + m_NodePreviewModel: + rid: -2 + m_State: 0 + m_InputConstantsById: + m_KeyList: + - __option_ChoiceCount + - __option_HasHiddenBranch + - __option_HiddenGestureKey + - __option_HiddenCode + - Speaker + - SpeakerNameOverride + - TalkText + - Gesture + - Expression + - Voice + - Bgm + - Affection + - Progress + m_ValueList: + - rid: 6600512887158735088 + - rid: 6600512887158735090 + - rid: 6600512887158735091 + - rid: 6600512887158735092 + - rid: 6600512887158735093 + - rid: 6600512887158735094 + - rid: 6600512887158735096 + - rid: 6600512887158735097 + - rid: 6600512887158735098 + - rid: 6600512887158735099 + - rid: 6600512887158735100 + - rid: 6600512887158735108 + - rid: 6600512887158735109 + m_InputPortInfos: + expandedPortsById: + m_KeyList: [] + m_ValueList: + m_OutputPortInfos: + expandedPortsById: + m_KeyList: [] + m_ValueList: + m_Collapsed: 0 + m_CurrentModeIndex: 0 + m_ElementColor: + m_Color: {r: 0, g: 0, b: 0, a: 0} + m_HasUserColor: 0 + m_Node: + rid: 6600512887158735110 + - rid: 6600512887158735088 + type: {class: 'Constant`1[[System.Int32, mscorlib]]', ns: Unity.GraphToolkit.Editor, asm: UnityEditor.GraphToolkitModule} + data: + m_Value: 0 + - rid: 6600512887158735090 + type: {class: 'Constant`1[[System.Boolean, mscorlib]]', ns: Unity.GraphToolkit.Editor, asm: UnityEditor.GraphToolkitModule} + data: + m_Value: 0 + - rid: 6600512887158735091 + type: {class: 'Constant`1[[System.String, mscorlib]]', ns: Unity.GraphToolkit.Editor, asm: UnityEditor.GraphToolkitModule} + data: + m_Value: + - rid: 6600512887158735092 + type: {class: 'Constant`1[[System.String, mscorlib]]', ns: Unity.GraphToolkit.Editor, asm: UnityEditor.GraphToolkitModule} + data: + m_Value: + - rid: 6600512887158735093 + type: {class: 'Constant`1[[CharacterData, Assembly-CSharp]]', ns: Unity.GraphToolkit.Editor, asm: UnityEditor.GraphToolkitModule} + data: + m_Value: {fileID: 0} + - rid: 6600512887158735094 + type: {class: 'Constant`1[[DinoLove.Dialog.GraphTool.Editor.DialogShortText, Assembly-CSharp-Editor]]', ns: Unity.GraphToolkit.Editor, asm: UnityEditor.GraphToolkitModule} + data: + m_Value: + Value: + - rid: 6600512887158735096 + type: {class: 'Constant`1[[DinoLove.Dialog.GraphTool.Editor.DialogText, Assembly-CSharp-Editor]]', ns: Unity.GraphToolkit.Editor, asm: UnityEditor.GraphToolkitModule} + data: + m_Value: + Value: + - rid: 6600512887158735097 + type: {class: 'Constant`1[[GestureData, Assembly-CSharp]]', ns: Unity.GraphToolkit.Editor, asm: UnityEditor.GraphToolkitModule} + data: + m_Value: {fileID: 0} + - rid: 6600512887158735098 + type: {class: 'Constant`1[[ExpressionData, Assembly-CSharp]]', ns: Unity.GraphToolkit.Editor, asm: UnityEditor.GraphToolkitModule} + data: + m_Value: {fileID: 0} + - rid: 6600512887158735099 + type: {class: 'Constant`1[[VoiceClip, Assembly-CSharp]]', ns: Unity.GraphToolkit.Editor, asm: UnityEditor.GraphToolkitModule} + data: + m_Value: {fileID: 0} + - rid: 6600512887158735100 + type: {class: 'Constant`1[[UnityEngine.AudioClip, UnityEngine.AudioModule]]', ns: Unity.GraphToolkit.Editor, asm: UnityEditor.GraphToolkitModule} + data: + m_Value: {fileID: 0} + - rid: 6600512887158735108 + type: {class: 'Constant`1[[System.Int32, mscorlib]]', ns: Unity.GraphToolkit.Editor, asm: UnityEditor.GraphToolkitModule} + data: + m_Value: 0 + - rid: 6600512887158735109 + type: {class: 'Constant`1[[System.Int32, mscorlib]]', ns: Unity.GraphToolkit.Editor, asm: UnityEditor.GraphToolkitModule} + data: + m_Value: 0 + - rid: 6600512887158735110 + type: {class: DialogLineNode, ns: DinoLove.Dialog.GraphTool.Editor, asm: Assembly-CSharp-Editor} + data: + - rid: 6600512887158735111 + type: {class: UserNodeModelImp, ns: Unity.GraphToolkit.Editor.Implementation, asm: UnityEditor.GraphToolkitModule} + data: + m_Guid: + m_Value0: 13680180985569699724 + m_Value1: 14420258866515227426 + m_HashGuid: + serializedVersion: 2 + Hash: 8c67ef2b20c4d9bd2233d18b090d1fc8 + m_Version: 2 + m_Position: {x: -404.02042, y: 43.72515} + m_Title: + m_Tooltip: + m_NodePreviewModel: + rid: -2 + m_State: 0 + m_InputConstantsById: + m_KeyList: [] + m_ValueList: [] + m_InputPortInfos: + expandedPortsById: + m_KeyList: [] + m_ValueList: + m_OutputPortInfos: + expandedPortsById: + m_KeyList: [] + m_ValueList: + m_Collapsed: 0 + m_CurrentModeIndex: 0 + m_ElementColor: + m_Color: {r: 0, g: 0, b: 0, a: 0} + m_HasUserColor: 0 + m_Node: + rid: 6600512887158735112 + - rid: 6600512887158735112 + type: {class: DialogStartNode, ns: DinoLove.Dialog.GraphTool.Editor, asm: Assembly-CSharp-Editor} + data: + - rid: 6600512887158735113 + type: {class: WireModel, ns: Unity.GraphToolkit.Editor, asm: UnityEditor.GraphToolkitModule} + data: + m_Guid: + m_Value0: 10165275467295493703 + m_Value1: 18408072349542380157 + m_HashGuid: + serializedVersion: 2 + Hash: 470e24f82d50128d7df22992459c76ff + m_Version: 2 + m_FromPortReference: + m_NodeModelGuid: + m_Value0: 13680180985569699724 + m_Value1: 14420258866515227426 + m_NodeModelHashGuid: + serializedVersion: 2 + Hash: 8c67ef2b20c4d9bd2233d18b090d1fc8 + m_UniqueId: Out + m_PortDirection: 2 + m_PortOrientation: 0 + m_Title: + m_ToPortReference: + m_NodeModelGuid: + m_Value0: 8557079674992562767 + m_Value1: 2087592960078497513 + m_NodeModelHashGuid: + serializedVersion: 2 + Hash: 4f92a586a0dac076e9c61595bc9ef81c + m_UniqueId: In + m_PortDirection: 1 + m_PortOrientation: 0 + m_Title: + - rid: 6600512887158735114 + type: {class: UserNodeModelImp, ns: Unity.GraphToolkit.Editor.Implementation, asm: UnityEditor.GraphToolkitModule} + data: + m_Guid: + m_Value0: 4814898993618757843 + m_Value1: 13954766896149483658 + m_HashGuid: + serializedVersion: 2 + Hash: d32808d01ff5d1428ad05e788e4aa9c1 + m_Version: 2 + m_Position: {x: 418.47815, y: -42.950325} + m_Title: + m_Tooltip: + m_NodePreviewModel: + rid: -2 + m_State: 0 + m_InputConstantsById: + m_KeyList: + - __option_ConditionCount + - Target0 + - Compare0 + - Value0 + m_ValueList: + - rid: 6600512887158735116 + - rid: 6600512887158735117 + - rid: 6600512887158735118 + - rid: 6600512887158735119 + m_InputPortInfos: + expandedPortsById: + m_KeyList: [] + m_ValueList: + m_OutputPortInfos: + expandedPortsById: + m_KeyList: [] + m_ValueList: + m_Collapsed: 0 + m_CurrentModeIndex: 0 + m_ElementColor: + m_Color: {r: 0, g: 0, b: 0, a: 0} + m_HasUserColor: 0 + m_Node: + rid: 6600512887158735120 + - rid: 6600512887158735115 + type: {class: WireModel, ns: Unity.GraphToolkit.Editor, asm: UnityEditor.GraphToolkitModule} + data: + m_Guid: + m_Value0: 7298122618344812091 + m_Value1: 2621210583193601063 + m_HashGuid: + serializedVersion: 2 + Hash: 3baeafb1da23486527c4bf3c23696024 + m_Version: 2 + m_FromPortReference: + m_NodeModelGuid: + m_Value0: 8557079674992562767 + m_Value1: 2087592960078497513 + m_NodeModelHashGuid: + serializedVersion: 2 + Hash: 4f92a586a0dac076e9c61595bc9ef81c + m_UniqueId: Out + m_PortDirection: 2 + m_PortOrientation: 0 + m_Title: + m_ToPortReference: + m_NodeModelGuid: + m_Value0: 4814898993618757843 + m_Value1: 13954766896149483658 + m_NodeModelHashGuid: + serializedVersion: 2 + Hash: d32808d01ff5d1428ad05e788e4aa9c1 + m_UniqueId: In + m_PortDirection: 1 + m_PortOrientation: 0 + m_Title: + - rid: 6600512887158735116 + type: {class: 'Constant`1[[System.Int32, mscorlib]]', ns: Unity.GraphToolkit.Editor, asm: UnityEditor.GraphToolkitModule} + data: + m_Value: 1 + - rid: 6600512887158735117 + type: {class: 'Constant`1[[CharacterData, Assembly-CSharp]]', ns: Unity.GraphToolkit.Editor, asm: UnityEditor.GraphToolkitModule} + data: + m_Value: {fileID: 0} + - rid: 6600512887158735118 + type: {class: EnumConstant, ns: Unity.GraphToolkit.Editor, asm: UnityEditor.GraphToolkitModule} + data: + m_Value: + m_EnumType: + m_Identification: AffectionCompare, Assembly-CSharp, Version=0.0.0.0, + Culture=neutral, PublicKeyToken=null + m_Value: 0 + m_EnumType: + m_Identification: AffectionCompare, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, + PublicKeyToken=null + - rid: 6600512887158735119 + type: {class: 'Constant`1[[System.Int32, mscorlib]]', ns: Unity.GraphToolkit.Editor, asm: UnityEditor.GraphToolkitModule} + data: + m_Value: 0 + - rid: 6600512887158735120 + type: {class: DialogAffectionNode, ns: DinoLove.Dialog.GraphTool.Editor, asm: Assembly-CSharp-Editor} + data: diff --git a/Assets/07_Data/DialogGraph/TestDialog.dlg.meta b/Assets/07_Data/DialogGraph/TestDialog.dlg.meta new file mode 100644 index 0000000..7ef8916 --- /dev/null +++ b/Assets/07_Data/DialogGraph/TestDialog.dlg.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: f3f991da9650cb64e830146ada558888 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 2ae5ca89bbed445479d9023586f0c041, type: 3} diff --git a/Assets/07_Data/StoryDatabase.asset b/Assets/07_Data/StoryDatabase.asset new file mode 100644 index 0000000..30cee80 --- /dev/null +++ b/Assets/07_Data/StoryDatabase.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3ae8cc22ef487079844869afb42d247f77c45cfaa7ffd3aa943a27667408789b +size 1094 diff --git a/Assets/07_Data/StoryDatabase.asset.meta b/Assets/07_Data/StoryDatabase.asset.meta new file mode 100644 index 0000000..4bd9e6d --- /dev/null +++ b/Assets/07_Data/StoryDatabase.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 55dde168117e98f40be6b4ce143e6ee7 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/11_Audio.meta b/Assets/11_Audio.meta new file mode 100644 index 0000000..9b088d3 --- /dev/null +++ b/Assets/11_Audio.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3903b376ec6aace46860d0b2306302ca +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/11_Audio/MainMixer.mixer b/Assets/11_Audio/MainMixer.mixer new file mode 100644 index 0000000..443d639 --- /dev/null +++ b/Assets/11_Audio/MainMixer.mixer @@ -0,0 +1,181 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!243 &-4679298999658146737 +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: a0d89f0aa3113594dadc92f89647299b + m_Children: [] + m_Volume: dc3a52cfb876b80428fbd48b9b3f45b6 + m_Pitch: 88fcaed077aeef04b8163ce426840277 + m_Send: 00000000000000000000000000000000 + m_Effects: + - {fileID: 5360547217510990021} + m_UserColorIndex: 0 + m_Mute: 0 + m_Solo: 0 + m_BypassEffects: 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: d3013a12987449e4cb4a8dca2959a944 + name: BGMVolume + - guid: ba9f907ee748774458c9787bae1a7b31 + name: MasterVolume + - guid: dc3a52cfb876b80428fbd48b9b3f45b6 + name: SFXVolume + - guid: b49b5deff508d1141bda1858efa40105 + name: VoiceVolume + m_AudioMixerGroupViews: + - guids: + - d2182f95408850744b1bff57d7565c37 + - 97d67eab3ce19604384c75c8334dec0c + - a0d89f0aa3113594dadc92f89647299b + - 872dc4a651b1e194cbd4099a2d90f6b5 + 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: d2182f95408850744b1bff57d7565c37 + m_Children: + - {fileID: 3836792059586206971} + - {fileID: -4679298999658146737} + - {fileID: 7661026699808390198} + m_Volume: ba9f907ee748774458c9787bae1a7b31 + m_Pitch: 10a38739f2be0044fab32f03a5660c7f + 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: 1ba7b6511a2ca694e9e47ec1024269e3 + m_EffectName: Attenuation + m_MixLevel: 0ddefaffc75e9d44881d234236dc8a9e + 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: 9697e606b9ae3a34696065f9b2a0d5e0 + m_FloatValues: {} + m_TransitionOverrides: {} +--- !u!244 &1191665652155800442 +AudioMixerEffectController: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_EffectID: b7f0bd692381de84e988778e83521771 + m_EffectName: Attenuation + m_MixLevel: 0415a976016544a45b8d2f954556c024 + m_Parameters: [] + m_SendTarget: {fileID: 0} + m_EnableWetMix: 0 + m_Bypass: 0 +--- !u!243 &3836792059586206971 +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: 97d67eab3ce19604384c75c8334dec0c + m_Children: [] + m_Volume: d3013a12987449e4cb4a8dca2959a944 + m_Pitch: 186450f2ec57fff4391c9bd151272638 + m_Send: 00000000000000000000000000000000 + m_Effects: + - {fileID: 8201172989575524993} + m_UserColorIndex: 0 + m_Mute: 0 + m_Solo: 0 + m_BypassEffects: 0 +--- !u!244 &5360547217510990021 +AudioMixerEffectController: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_EffectID: b5dd04ab9528c364f8f188d60b68e688 + m_EffectName: Attenuation + m_MixLevel: aed4300c656e2aa4abe6a8fae38b242f + m_Parameters: [] + m_SendTarget: {fileID: 0} + m_EnableWetMix: 0 + m_Bypass: 0 +--- !u!243 &7661026699808390198 +AudioMixerGroupController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Voice + m_AudioMixer: {fileID: 24100000} + m_GroupID: 872dc4a651b1e194cbd4099a2d90f6b5 + m_Children: [] + m_Volume: b49b5deff508d1141bda1858efa40105 + m_Pitch: 37f0b7f63f82f4947af70d5bf4043503 + m_Send: 00000000000000000000000000000000 + m_Effects: + - {fileID: 1191665652155800442} + m_UserColorIndex: 0 + m_Mute: 0 + m_Solo: 0 + m_BypassEffects: 0 +--- !u!244 &8201172989575524993 +AudioMixerEffectController: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_EffectID: aabb7e25b36de954fbf04cd503a2d63a + m_EffectName: Attenuation + m_MixLevel: 15cd2be3c5d46bb4da655d600c10239a + m_Parameters: [] + m_SendTarget: {fileID: 0} + m_EnableWetMix: 0 + m_Bypass: 0 diff --git a/Assets/11_Audio/MainMixer.mixer.meta b/Assets/11_Audio/MainMixer.mixer.meta new file mode 100644 index 0000000..77254da --- /dev/null +++ b/Assets/11_Audio/MainMixer.mixer.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 22e6b44f720aa8f4cb56f457346a952e +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 24100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/99_Settings.meta b/Assets/99_Settings.meta new file mode 100644 index 0000000..39b94dd --- /dev/null +++ b/Assets/99_Settings.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 709f11a7f3c4041caa4ef136ea32d874 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/99_Settings/InputSystem.meta b/Assets/99_Settings/InputSystem.meta new file mode 100644 index 0000000..2b6ee0c --- /dev/null +++ b/Assets/99_Settings/InputSystem.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 94e023684802d584a8897fdf7f26cd47 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/99_Settings/InputSystem/GameInput.cs b/Assets/99_Settings/InputSystem/GameInput.cs new file mode 100644 index 0000000..e95a2e3 --- /dev/null +++ b/Assets/99_Settings/InputSystem/GameInput.cs @@ -0,0 +1,312 @@ +//------------------------------------------------------------------------------ +// +// This code was auto-generated by com.unity.inputsystem:InputActionCodeGenerator +// version 1.19.0 +// from Assets/99_Settings/InputSystem/GameInput.inputactions +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine.InputSystem; +using UnityEngine.InputSystem.Utilities; + +/// +/// Provides programmatic access to , , and instances defined in asset "Assets/99_Settings/InputSystem/GameInput.inputactions". +/// +/// +/// This class is source generated and any manual edits will be discarded if the associated asset is reimported or modified. +/// +/// +/// +/// using namespace UnityEngine; +/// using UnityEngine.InputSystem; +/// +/// // Example of using an InputActionMap named "Player" from a UnityEngine.MonoBehaviour implementing callback interface. +/// public class Example : MonoBehaviour, MyActions.IPlayerActions +/// { +/// private MyActions_Actions m_Actions; // Source code representation of asset. +/// private MyActions_Actions.PlayerActions m_Player; // Source code representation of action map. +/// +/// void Awake() +/// { +/// m_Actions = new MyActions_Actions(); // Create asset object. +/// m_Player = m_Actions.Player; // Extract action map object. +/// m_Player.AddCallbacks(this); // Register callback interface IPlayerActions. +/// } +/// +/// void OnDestroy() +/// { +/// m_Actions.Dispose(); // Destroy asset object. +/// } +/// +/// void OnEnable() +/// { +/// m_Player.Enable(); // Enable all actions within map. +/// } +/// +/// void OnDisable() +/// { +/// m_Player.Disable(); // Disable all actions within map. +/// } +/// +/// #region Interface implementation of MyActions.IPlayerActions +/// +/// // Invoked when "Move" action is either started, performed or canceled. +/// public void OnMove(InputAction.CallbackContext context) +/// { +/// Debug.Log($"OnMove: {context.ReadValue<Vector2>()}"); +/// } +/// +/// // Invoked when "Attack" action is either started, performed or canceled. +/// public void OnAttack(InputAction.CallbackContext context) +/// { +/// Debug.Log($"OnAttack: {context.ReadValue<float>()}"); +/// } +/// +/// #endregion +/// } +/// +/// +public partial class @GameInput: IInputActionCollection2, IDisposable +{ + /// + /// Provides access to the underlying asset instance. + /// + public InputActionAsset asset { get; } + + /// + /// Constructs a new instance. + /// + public @GameInput() + { + asset = InputActionAsset.FromJson(@"{ + ""version"": 1, + ""name"": ""GameInput"", + ""maps"": [ + { + ""name"": ""Player"", + ""id"": ""fade7ada-4185-4174-a09d-320a0c0d795f"", + ""actions"": [ + { + ""name"": ""DialogNext"", + ""type"": ""Button"", + ""id"": ""8ae8ab6b-7801-4d27-a238-85004f8ad448"", + ""expectedControlType"": """", + ""processors"": """", + ""interactions"": """", + ""initialStateCheck"": false + } + ], + ""bindings"": [ + { + ""name"": """", + ""id"": ""36fe51e9-2b54-4add-ac38-ef9651d1118c"", + ""path"": ""/space"", + ""interactions"": """", + ""processors"": """", + ""groups"": """", + ""action"": ""DialogNext"", + ""isComposite"": false, + ""isPartOfComposite"": false + } + ] + } + ], + ""controlSchemes"": [] +}"); + // Player + m_Player = asset.FindActionMap("Player", throwIfNotFound: true); + m_Player_DialogNext = m_Player.FindAction("DialogNext", throwIfNotFound: true); + } + + ~@GameInput() + { + UnityEngine.Debug.Assert(!m_Player.enabled, "This will cause a leak and performance issues, GameInput.Player.Disable() has not been called."); + } + + /// + /// Destroys this asset and all associated instances. + /// + public void Dispose() + { + UnityEngine.Object.Destroy(asset); + } + + /// + public InputBinding? bindingMask + { + get => asset.bindingMask; + set => asset.bindingMask = value; + } + + /// + public ReadOnlyArray? devices + { + get => asset.devices; + set => asset.devices = value; + } + + /// + public ReadOnlyArray controlSchemes => asset.controlSchemes; + + /// + public bool Contains(InputAction action) + { + return asset.Contains(action); + } + + /// + public IEnumerator GetEnumerator() + { + return asset.GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + public void Enable() + { + asset.Enable(); + } + + /// + public void Disable() + { + asset.Disable(); + } + + /// + public IEnumerable bindings => asset.bindings; + + /// + public InputAction FindAction(string actionNameOrId, bool throwIfNotFound = false) + { + return asset.FindAction(actionNameOrId, throwIfNotFound); + } + + /// + public int FindBinding(InputBinding bindingMask, out InputAction action) + { + return asset.FindBinding(bindingMask, out action); + } + + // Player + private readonly InputActionMap m_Player; + private List m_PlayerActionsCallbackInterfaces = new List(); + private readonly InputAction m_Player_DialogNext; + /// + /// Provides access to input actions defined in input action map "Player". + /// + public struct PlayerActions + { + private @GameInput m_Wrapper; + + /// + /// Construct a new instance of the input action map wrapper class. + /// + public PlayerActions(@GameInput wrapper) { m_Wrapper = wrapper; } + /// + /// Provides access to the underlying input action "Player/DialogNext". + /// + public InputAction @DialogNext => m_Wrapper.m_Player_DialogNext; + /// + /// Provides access to the underlying input action map instance. + /// + public InputActionMap Get() { return m_Wrapper.m_Player; } + /// + public void Enable() { Get().Enable(); } + /// + public void Disable() { Get().Disable(); } + /// + public bool enabled => Get().enabled; + /// + /// Implicitly converts an to an instance. + /// + public static implicit operator InputActionMap(PlayerActions set) { return set.Get(); } + /// + /// Adds , and callbacks provided via on all input actions contained in this map. + /// + /// Callback instance. + /// + /// If is null or have already been added this method does nothing. + /// + /// + public void AddCallbacks(IPlayerActions instance) + { + if (instance == null || m_Wrapper.m_PlayerActionsCallbackInterfaces.Contains(instance)) return; + m_Wrapper.m_PlayerActionsCallbackInterfaces.Add(instance); + @DialogNext.started += instance.OnDialogNext; + @DialogNext.performed += instance.OnDialogNext; + @DialogNext.canceled += instance.OnDialogNext; + } + + /// + /// Removes , and callbacks provided via on all input actions contained in this map. + /// + /// + /// Calling this method when have not previously been registered has no side-effects. + /// + /// + private void UnregisterCallbacks(IPlayerActions instance) + { + @DialogNext.started -= instance.OnDialogNext; + @DialogNext.performed -= instance.OnDialogNext; + @DialogNext.canceled -= instance.OnDialogNext; + } + + /// + /// Unregisters and unregisters all input action callbacks via . + /// + /// + public void RemoveCallbacks(IPlayerActions instance) + { + if (m_Wrapper.m_PlayerActionsCallbackInterfaces.Remove(instance)) + UnregisterCallbacks(instance); + } + + /// + /// Replaces all existing callback instances and previously registered input action callbacks associated with them with callbacks provided via . + /// + /// + /// If is null, calling this method will only unregister all existing callbacks but not register any new callbacks. + /// + /// + /// + /// + public void SetCallbacks(IPlayerActions instance) + { + foreach (var item in m_Wrapper.m_PlayerActionsCallbackInterfaces) + UnregisterCallbacks(item); + m_Wrapper.m_PlayerActionsCallbackInterfaces.Clear(); + AddCallbacks(instance); + } + } + /// + /// Provides a new instance referencing this action map. + /// + public PlayerActions @Player => new PlayerActions(this); + /// + /// Interface to implement callback methods for all input action callbacks associated with input actions defined by "Player" which allows adding and removing callbacks. + /// + /// + /// + public interface IPlayerActions + { + /// + /// Method invoked when associated input action "DialogNext" is either , or . + /// + /// + /// + /// + void OnDialogNext(InputAction.CallbackContext context); + } +} diff --git a/Assets/99_Settings/InputSystem/GameInput.cs.meta b/Assets/99_Settings/InputSystem/GameInput.cs.meta new file mode 100644 index 0000000..43d51de --- /dev/null +++ b/Assets/99_Settings/InputSystem/GameInput.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4078cf7855848d64b9255f435eb20da7 \ No newline at end of file diff --git a/Assets/99_Settings/InputSystem/GameInput.inputactions b/Assets/99_Settings/InputSystem/GameInput.inputactions new file mode 100644 index 0000000..9e09917 --- /dev/null +++ b/Assets/99_Settings/InputSystem/GameInput.inputactions @@ -0,0 +1,35 @@ +{ + "version": 1, + "name": "GameInput", + "maps": [ + { + "name": "Player", + "id": "fade7ada-4185-4174-a09d-320a0c0d795f", + "actions": [ + { + "name": "DialogNext", + "type": "Button", + "id": "8ae8ab6b-7801-4d27-a238-85004f8ad448", + "expectedControlType": "", + "processors": "", + "interactions": "", + "initialStateCheck": false + } + ], + "bindings": [ + { + "name": "", + "id": "36fe51e9-2b54-4add-ac38-ef9651d1118c", + "path": "/space", + "interactions": "", + "processors": "", + "groups": "", + "action": "DialogNext", + "isComposite": false, + "isPartOfComposite": false + } + ] + } + ], + "controlSchemes": [] +} \ No newline at end of file diff --git a/Assets/99_Settings/InputSystem/GameInput.inputactions.meta b/Assets/99_Settings/InputSystem/GameInput.inputactions.meta new file mode 100644 index 0000000..ccb8c79 --- /dev/null +++ b/Assets/99_Settings/InputSystem/GameInput.inputactions.meta @@ -0,0 +1,14 @@ +fileFormatVersion: 2 +guid: 52424b1ab4ef6914597f881627a2712d +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 8404be70184654265930450def6a9037, type: 3} + generateWrapperCode: 1 + wrapperCodePath: + wrapperClassName: + wrapperCodeNamespace: diff --git a/Assets/99_Settings/ProjectConfig.meta b/Assets/99_Settings/ProjectConfig.meta new file mode 100644 index 0000000..5c992f9 --- /dev/null +++ b/Assets/99_Settings/ProjectConfig.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fea1e40560f70e04d920c9a83b5540c9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/99_Settings/ProjectConfig/DefaultVolumeProfile.asset b/Assets/99_Settings/ProjectConfig/DefaultVolumeProfile.asset new file mode 100644 index 0000000..bbaa559 --- /dev/null +++ b/Assets/99_Settings/ProjectConfig/DefaultVolumeProfile.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a6f825e8d07fe8b9f3b52e7d4e2dfa0e22c555b22335adb19f0ecc66dfa38dd +size 23987 diff --git a/Assets/99_Settings/ProjectConfig/DefaultVolumeProfile.asset.meta b/Assets/99_Settings/ProjectConfig/DefaultVolumeProfile.asset.meta new file mode 100644 index 0000000..53b314a --- /dev/null +++ b/Assets/99_Settings/ProjectConfig/DefaultVolumeProfile.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ab09877e2e707104187f6f83e2f62510 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/99_Settings/ProjectConfig/Mobile_RPAsset.asset b/Assets/99_Settings/ProjectConfig/Mobile_RPAsset.asset new file mode 100644 index 0000000..fcef451 --- /dev/null +++ b/Assets/99_Settings/ProjectConfig/Mobile_RPAsset.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba642b95fe751d42a081a346b4dee94ced67b3e7c8ed8c4f2929f6eb51d55b18 +size 4672 diff --git a/Assets/99_Settings/ProjectConfig/Mobile_RPAsset.asset.meta b/Assets/99_Settings/ProjectConfig/Mobile_RPAsset.asset.meta new file mode 100644 index 0000000..3660d15 --- /dev/null +++ b/Assets/99_Settings/ProjectConfig/Mobile_RPAsset.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5e6cbd92db86f4b18aec3ed561671858 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/99_Settings/ProjectConfig/Mobile_Renderer.asset b/Assets/99_Settings/ProjectConfig/Mobile_Renderer.asset new file mode 100644 index 0000000..e26ada9 --- /dev/null +++ b/Assets/99_Settings/ProjectConfig/Mobile_Renderer.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa93274c9521fa941170becec9e87c5fa36b8587eefd5eeb5c0eed0d1d4b1dbe +size 1713 diff --git a/Assets/99_Settings/ProjectConfig/Mobile_Renderer.asset.meta b/Assets/99_Settings/ProjectConfig/Mobile_Renderer.asset.meta new file mode 100644 index 0000000..a3588b1 --- /dev/null +++ b/Assets/99_Settings/ProjectConfig/Mobile_Renderer.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 65bc7dbf4170f435aa868c779acfb082 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/99_Settings/ProjectConfig/PC_RPAsset.asset b/Assets/99_Settings/ProjectConfig/PC_RPAsset.asset new file mode 100644 index 0000000..f9710a7 --- /dev/null +++ b/Assets/99_Settings/ProjectConfig/PC_RPAsset.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7cfa894e760f2aa087d24b2fa260e2efa497b58b89d52e4c2b28371cf6336aaf +size 4691 diff --git a/Assets/99_Settings/ProjectConfig/PC_RPAsset.asset.meta b/Assets/99_Settings/ProjectConfig/PC_RPAsset.asset.meta new file mode 100644 index 0000000..e286b2f --- /dev/null +++ b/Assets/99_Settings/ProjectConfig/PC_RPAsset.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4b83569d67af61e458304325a23e5dfd +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/99_Settings/ProjectConfig/PC_Renderer.asset b/Assets/99_Settings/ProjectConfig/PC_Renderer.asset new file mode 100644 index 0000000..1d091f9 --- /dev/null +++ b/Assets/99_Settings/ProjectConfig/PC_Renderer.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d0b01d8ded3327263b6690be128a3de58d17c514943ae42fe743a3e084a9c79 +size 3439 diff --git a/Assets/99_Settings/ProjectConfig/PC_Renderer.asset.meta b/Assets/99_Settings/ProjectConfig/PC_Renderer.asset.meta new file mode 100644 index 0000000..ddae6a5 --- /dev/null +++ b/Assets/99_Settings/ProjectConfig/PC_Renderer.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f288ae1f4751b564a96ac7587541f7a2 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/99_Settings/ProjectConfig/SampleSceneProfile.asset b/Assets/99_Settings/ProjectConfig/SampleSceneProfile.asset new file mode 100644 index 0000000..8d640bc --- /dev/null +++ b/Assets/99_Settings/ProjectConfig/SampleSceneProfile.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a8677d4bac051d24caa11cd622e39f968155ee277bfce132274f1de225692fe9 +size 3703 diff --git a/Assets/99_Settings/ProjectConfig/SampleSceneProfile.asset.meta b/Assets/99_Settings/ProjectConfig/SampleSceneProfile.asset.meta new file mode 100644 index 0000000..b82270c --- /dev/null +++ b/Assets/99_Settings/ProjectConfig/SampleSceneProfile.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 10fc4df2da32a41aaa32d77bc913491c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/99_Settings/ProjectConfig/UniversalRenderPipelineGlobalSettings.asset b/Assets/99_Settings/ProjectConfig/UniversalRenderPipelineGlobalSettings.asset new file mode 100644 index 0000000..c26fd0b --- /dev/null +++ b/Assets/99_Settings/ProjectConfig/UniversalRenderPipelineGlobalSettings.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a319a393751e02a1da426c81c6804cea087b0e36bc6af5aeae03e56cc437545 +size 26897 diff --git a/Assets/99_Settings/ProjectConfig/UniversalRenderPipelineGlobalSettings.asset.meta b/Assets/99_Settings/ProjectConfig/UniversalRenderPipelineGlobalSettings.asset.meta new file mode 100644 index 0000000..81b84f2 --- /dev/null +++ b/Assets/99_Settings/ProjectConfig/UniversalRenderPipelineGlobalSettings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 18dc0cd2c080841dea60987a38ce93fa +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/manifest.json b/Packages/manifest.json new file mode 100644 index 0000000..f3b0b0e --- /dev/null +++ b/Packages/manifest.json @@ -0,0 +1,50 @@ +{ + "dependencies": { + "com.unity.ai.navigation": "2.0.14", + "com.unity.collab-proxy": "2.13.3", + "com.unity.graphtoolkit": "0.4.0-exp.2", + "com.unity.ide.rider": "3.0.38", + "com.unity.ide.visualstudio": "2.0.26", + "com.unity.inputsystem": "1.19.0", + "com.unity.multiplayer.center": "1.0.1", + "com.unity.render-pipelines.universal": "17.5.0", + "com.unity.test-framework": "1.7.0", + "com.unity.timeline": "1.8.12", + "com.unity.ugui": "2.5.0", + "com.unity.visualscripting": "1.9.11", + "com.unity.modules.accessibility": "1.0.0", + "com.unity.modules.adaptiveperformance": "1.0.0", + "com.unity.modules.ai": "1.0.0", + "com.unity.modules.androidjni": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.cloth": "1.0.0", + "com.unity.modules.director": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.physics2d": "1.0.0", + "com.unity.modules.physicscore2d": "1.0.0", + "com.unity.modules.screencapture": "1.0.0", + "com.unity.modules.terrain": "1.0.0", + "com.unity.modules.terrainphysics": "1.0.0", + "com.unity.modules.tilemap": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.uielements": "1.0.0", + "com.unity.modules.umbra": "1.0.0", + "com.unity.modules.unityanalytics": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.unitywebrequesttexture": "1.0.0", + "com.unity.modules.unitywebrequestwww": "1.0.0", + "com.unity.modules.vectorgraphics": "1.0.0", + "com.unity.modules.vehicles": "1.0.0", + "com.unity.modules.video": "1.0.0", + "com.unity.modules.wind": "1.0.0", + "com.unity.modules.xr": "1.0.0" + } +} diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json new file mode 100644 index 0000000..9f325ed --- /dev/null +++ b/Packages/packages-lock.json @@ -0,0 +1,478 @@ +{ + "dependencies": { + "com.unity.ai.navigation": { + "version": "2.0.14", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.ai": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.burst": { + "version": "1.8.29", + "depth": 2, + "source": "registry", + "dependencies": { + "com.unity.mathematics": "1.2.1", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.collab-proxy": { + "version": "2.13.3", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.collections": "2.6.2" + }, + "url": "https://packages.unity.com" + }, + "com.unity.collections": { + "version": "6.5.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.burst": "1.8.25", + "com.unity.nuget.mono-cecil": "1.11.6", + "com.unity.test-framework": "1.4.6", + "com.unity.test-framework.performance": "3.2.0" + } + }, + "com.unity.ext.nunit": { + "version": "2.1.0", + "depth": 1, + "source": "builtin", + "dependencies": {} + }, + "com.unity.graphtoolkit": { + "version": "0.5.0-exp.1", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.ide.rider": { + "version": "3.0.38", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ext.nunit": "1.0.6" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ide.visualstudio": { + "version": "2.0.26", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.test-framework": "1.1.33" + }, + "url": "https://packages.unity.com" + }, + "com.unity.inputsystem": { + "version": "1.19.0", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.uielements": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.mathematics": { + "version": "1.4.0", + "depth": 3, + "source": "builtin", + "dependencies": {} + }, + "com.unity.multiplayer.center": { + "version": "1.0.1", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.uielements": "1.0.0" + } + }, + "com.unity.nuget.mono-cecil": { + "version": "1.11.6", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.render-pipelines.core": { + "version": "17.5.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.burst": "1.8.14", + "com.unity.collections": "2.4.3", + "com.unity.modules.terrain": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.render-pipelines.universal": { + "version": "17.5.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.render-pipelines.core": "17.5.0", + "com.unity.shadergraph": "17.5.0", + "com.unity.render-pipelines.universal-config": "17.5.0" + } + }, + "com.unity.render-pipelines.universal-config": { + "version": "17.5.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.render-pipelines.core": "17.5.0" + } + }, + "com.unity.searcher": { + "version": "4.9.4", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.shadergraph": { + "version": "17.5.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.render-pipelines.core": "17.5.0", + "com.unity.searcher": "4.9.3" + } + }, + "com.unity.test-framework": { + "version": "1.7.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.ext.nunit": "2.1.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.test-framework.performance": { + "version": "3.5.0", + "depth": 2, + "source": "registry", + "dependencies": { + "com.unity.test-framework": "1.1.33", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.timeline": { + "version": "1.8.12", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.director": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ugui": { + "version": "2.5.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.physics2d": "1.0.0", + "com.unity.modules.physics": "1.0.0" + } + }, + "com.unity.visualscripting": { + "version": "1.9.11", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ugui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.modules.accessibility": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.adaptiveperformance": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.subsystems": "1.0.0" + } + }, + "com.unity.modules.ai": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.androidjni": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.animation": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.assetbundle": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.audio": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.cloth": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0" + } + }, + "com.unity.modules.director": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.animation": "1.0.0" + } + }, + "com.unity.modules.hierarchycore": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.imageconversion": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.imgui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.jsonserialize": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.particlesystem": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.physics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.physics2d": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physicscore2d": "1.0.0" + } + }, + "com.unity.modules.physicscore2d": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.screencapture": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.subsystems": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.terrain": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.terrainphysics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.terrain": "1.0.0" + } + }, + "com.unity.modules.tilemap": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics2d": "1.0.0" + } + }, + "com.unity.modules.ui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.uielements": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.hierarchycore": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.animation": "1.0.0" + } + }, + "com.unity.modules.umbra": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.unityanalytics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.unitywebrequest": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.unitywebrequestassetbundle": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0" + } + }, + "com.unity.modules.unitywebrequestaudio": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.audio": "1.0.0" + } + }, + "com.unity.modules.unitywebrequesttexture": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.unitywebrequestwww": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.vectorgraphics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.uielements": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0", + "com.unity.modules.imgui": "1.0.0" + } + }, + "com.unity.modules.vehicles": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0" + } + }, + "com.unity.modules.video": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0" + } + }, + "com.unity.modules.wind": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.xr": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.subsystems": "1.0.0" + } + } + } +} diff --git a/ProjectSettings/AudioManager.asset b/ProjectSettings/AudioManager.asset new file mode 100644 index 0000000..b19c873 --- /dev/null +++ b/ProjectSettings/AudioManager.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aeedeb3243f07b61dbbe854aa08b5ef6f0b5274dd9852ad9f6129dde3e105a31 +size 413 diff --git a/ProjectSettings/ClusterInputManager.asset b/ProjectSettings/ClusterInputManager.asset new file mode 100644 index 0000000..53b8289 --- /dev/null +++ b/ProjectSettings/ClusterInputManager.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40e65dd214bf254955d2c8829ca8ce2130fd3510485560ce012e9a1ddd93e623 +size 114 diff --git a/ProjectSettings/DynamicsManager.asset b/ProjectSettings/DynamicsManager.asset new file mode 100644 index 0000000..104a796 --- /dev/null +++ b/ProjectSettings/DynamicsManager.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7c9a9dc08b98990109d4346f68745e15e238de400f96c583e53e1c83c743a78 +size 1254 diff --git a/ProjectSettings/EditorBuildSettings.asset b/ProjectSettings/EditorBuildSettings.asset new file mode 100644 index 0000000..9b00bf3 --- /dev/null +++ b/ProjectSettings/EditorBuildSettings.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4fa917824cf85c161fdaf1576b81186c27fa87cf802859c348c4ce2121e8b54f +size 374 diff --git a/ProjectSettings/EditorSettings.asset b/ProjectSettings/EditorSettings.asset new file mode 100644 index 0000000..86fcdf9 --- /dev/null +++ b/ProjectSettings/EditorSettings.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:20e7399984b858ef7485c4e58f26a42345169749cf0d3a63efaaa82b33f8a883 +size 1687 diff --git a/ProjectSettings/GraphicsSettings.asset b/ProjectSettings/GraphicsSettings.asset new file mode 100644 index 0000000..68d26aa --- /dev/null +++ b/ProjectSettings/GraphicsSettings.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ac0d402c463fc830a46a1ddfab132e617bb2ee00dbc9834784d6df415014768 +size 2957 diff --git a/ProjectSettings/InputManager.asset b/ProjectSettings/InputManager.asset new file mode 100644 index 0000000..f002ee5 --- /dev/null +++ b/ProjectSettings/InputManager.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a1bb836a1008d26d0860d143e9d9df7ca41731d30a72901b518063819c62df2 +size 9731 diff --git a/ProjectSettings/MemorySettings.asset b/ProjectSettings/MemorySettings.asset new file mode 100644 index 0000000..cd6af44 --- /dev/null +++ b/ProjectSettings/MemorySettings.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e8379a461b10ae09f3f696911088c16875d63e87ed5f2d95df9e1892703f9fd +size 1192 diff --git a/ProjectSettings/MultiplayerManager.asset b/ProjectSettings/MultiplayerManager.asset new file mode 100644 index 0000000..a241e60 --- /dev/null +++ b/ProjectSettings/MultiplayerManager.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aba89fd1a1ddad182727ad82727e536d27372e90328a8196da2a4560398983dd +size 157 diff --git a/ProjectSettings/NavMeshAreas.asset b/ProjectSettings/NavMeshAreas.asset new file mode 100644 index 0000000..c788824 --- /dev/null +++ b/ProjectSettings/NavMeshAreas.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80106cfa7e6181a1928b142c075f3e510d527441e3d79bc83590e4ac91e7cd66 +size 1308 diff --git a/ProjectSettings/PackageManagerSettings.asset b/ProjectSettings/PackageManagerSettings.asset new file mode 100644 index 0000000..3d567f4 --- /dev/null +++ b/ProjectSettings/PackageManagerSettings.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd1bf3bc0a23b06888202f7a141bb58ab75b013672339ccbdc2addb78220d7ea +size 1157 diff --git a/ProjectSettings/Physics2DSettings.asset b/ProjectSettings/Physics2DSettings.asset new file mode 100644 index 0000000..36189c0 --- /dev/null +++ b/ProjectSettings/Physics2DSettings.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3cbdbf0be15244b7b67d2f07bdb8e8981911c11a8c7fe8035f5261272fec658 +size 2028 diff --git a/ProjectSettings/PhysicsCoreProjectSettings2D.asset b/ProjectSettings/PhysicsCoreProjectSettings2D.asset new file mode 100644 index 0000000..6a51bcb --- /dev/null +++ b/ProjectSettings/PhysicsCoreProjectSettings2D.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bedc5339a832c60dc3f11b84abc4f4384dbfa20b0cd9ad6c444b907e751fc35e +size 151 diff --git a/ProjectSettings/PresetManager.asset b/ProjectSettings/PresetManager.asset new file mode 100644 index 0000000..f781dbe --- /dev/null +++ b/ProjectSettings/PresetManager.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:29c125923fe6e29f90190d4fce1c82d660e7051546e2c9d02908d18c9cf2436c +size 146 diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset new file mode 100644 index 0000000..745f61a --- /dev/null +++ b/ProjectSettings/ProjectSettings.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0972ec2537570b0645eafc9928ad78448f9fd0bfb0a72d65793939fd2c4ca3ac +size 25489 diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt new file mode 100644 index 0000000..07f1ecb --- /dev/null +++ b/ProjectSettings/ProjectVersion.txt @@ -0,0 +1,2 @@ +m_EditorVersion: 6000.5.5f1 +m_EditorVersionWithRevision: 6000.5.5f1 (d16e074b49fd) diff --git a/ProjectSettings/QualitySettings.asset b/ProjectSettings/QualitySettings.asset new file mode 100644 index 0000000..1b536a9 --- /dev/null +++ b/ProjectSettings/QualitySettings.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6814d6cc3eb850dd7d2a069a8b17ba9f7591deba8b1758a855978bd9c8627643 +size 3662 diff --git a/ProjectSettings/SceneTemplateSettings.json b/ProjectSettings/SceneTemplateSettings.json new file mode 100644 index 0000000..ede5887 --- /dev/null +++ b/ProjectSettings/SceneTemplateSettings.json @@ -0,0 +1,121 @@ +{ + "templatePinStates": [], + "dependencyTypeInfos": [ + { + "userAdded": false, + "type": "UnityEngine.AnimationClip", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.Animations.AnimatorController", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.AnimatorOverrideController", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.Audio.AudioMixerController", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.ComputeShader", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.Cubemap", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.GameObject", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.LightingDataAsset", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.LightingSettings", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Material", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.MonoScript", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.PhysicsMaterial", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.PhysicsMaterial2D", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.PostProcessing.PostProcessProfile", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.PostProcessing.PostProcessResources", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.VolumeProfile", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.SceneAsset", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.Shader", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.ShaderVariantCollection", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.Texture", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Texture2D", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Timeline.TimelineAsset", + "defaultInstantiationMode": 0 + } + ], + "defaultDependencyTypeInfo": { + "userAdded": false, + "type": "", + "defaultInstantiationMode": 1 + }, + "newSceneOverride": 0 +} \ No newline at end of file diff --git a/ProjectSettings/ShaderGraphSettings.asset b/ProjectSettings/ShaderGraphSettings.asset new file mode 100644 index 0000000..30d0e44 --- /dev/null +++ b/ProjectSettings/ShaderGraphSettings.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cdba1e5d65bcf6e3269d76ed3e60b6c21f98322002996642c9d74f1eb89f6b91 +size 556 diff --git a/ProjectSettings/TagManager.asset b/ProjectSettings/TagManager.asset new file mode 100644 index 0000000..6d49bef --- /dev/null +++ b/ProjectSettings/TagManager.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:23fc56b484aa3d7edf5914cd3591f7a823ccef5e3f3496f8a51f30388ead63dc +size 657 diff --git a/ProjectSettings/TimeManager.asset b/ProjectSettings/TimeManager.asset new file mode 100644 index 0000000..38794bf --- /dev/null +++ b/ProjectSettings/TimeManager.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a83e54adbbda7c9f4851103a0c6ab7f6448a3343d4ea5b7620452fa08416ecd +size 202 diff --git a/ProjectSettings/URPProjectSettings.asset b/ProjectSettings/URPProjectSettings.asset new file mode 100644 index 0000000..8160870 --- /dev/null +++ b/ProjectSettings/URPProjectSettings.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c317fe22b9488785c0611f0642429922e5d324430d929bffb41648130f30c218 +size 461 diff --git a/ProjectSettings/UnityConnectSettings.asset b/ProjectSettings/UnityConnectSettings.asset new file mode 100644 index 0000000..ec5284e --- /dev/null +++ b/ProjectSettings/UnityConnectSettings.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7dd1e8290598f5ce86919ba1c98b42b766c8c42191a30fa2c4d12195c4ecd00 +size 1063 diff --git a/ProjectSettings/VFXManager.asset b/ProjectSettings/VFXManager.asset new file mode 100644 index 0000000..e0b8a4e --- /dev/null +++ b/ProjectSettings/VFXManager.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f066c6483919961332415ee64204f74fe52f38c02e82892eb02b7a5b2e381f0e +size 308 diff --git a/ProjectSettings/VersionControlSettings.asset b/ProjectSettings/VersionControlSettings.asset new file mode 100644 index 0000000..6e1e402 --- /dev/null +++ b/ProjectSettings/VersionControlSettings.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7bda45bf7a394745fee1316e6c71fa676004d591ad1f3aa247fd3e4ceb86071 +size 188 diff --git a/ProjectSettings/XRSettings.asset b/ProjectSettings/XRSettings.asset new file mode 100644 index 0000000..bfefb85 --- /dev/null +++ b/ProjectSettings/XRSettings.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:edd2beb4a2e388312b4b7da241aa51070434005d82c59363f1237dd2e567585f +size 158