diff --git a/Assets/_Game/Data/Skills/SkillRegistry.asset b/Assets/_Game/Data/Skills/SkillRegistry.asset
new file mode 100644
index 00000000..4ba17994
--- /dev/null
+++ b/Assets/_Game/Data/Skills/SkillRegistry.asset
@@ -0,0 +1,15 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ 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: 11500000, guid: 144e0ffda72c68941800f40c5755fee8, type: 3}
+ m_Name: SkillRegistry
+ m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.SkillRegistry
+ playerSkills: []
diff --git a/Assets/_Game/Data/Skills/SkillRegistry.asset.meta b/Assets/_Game/Data/Skills/SkillRegistry.asset.meta
new file mode 100644
index 00000000..9e2bc5cf
--- /dev/null
+++ b/Assets/_Game/Data/Skills/SkillRegistry.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 1899abd2213d1374dac386c5c865eb16
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/_Game/Prefabs/Player/Prefab_Player_Default.prefab b/Assets/_Game/Prefabs/Player/Prefab_Player_Default.prefab
index ec71a292..4498779c 100644
--- a/Assets/_Game/Prefabs/Player/Prefab_Player_Default.prefab
+++ b/Assets/_Game/Prefabs/Player/Prefab_Player_Default.prefab
@@ -4809,7 +4809,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject
- GlobalObjectIdHash: 3459939321
+ GlobalObjectIdHash: 291279334
InScenePlacedSourceGlobalObjectIdHash: 291279334
DeferredDespawnTick: 0
Ownership: 1
@@ -5021,13 +5021,26 @@ MonoBehaviour:
ShowTopMostFoldoutHeaderGroup: 1
animator: {fileID: 3426985706796420257}
baseController: {fileID: 9100000, guid: db718381bb2992e469c76c64015e065b, type: 2}
- baseSkillClip: {fileID: -7717634560727564301, guid: 0f6fd9302e489b94d96774e2713b1317, type: 3}
+ baseSkillClip: {fileID: 7400000, guid: 38a21eded51c5b24bb70a48d387aa565, type: 2}
registeredClips:
- - {fileID: -8689311932429934276, guid: ac0adc4c7f982fe4d82eac9c2267f0c6, type: 3}
- - {fileID: -1398844659318911536, guid: 3fd0fa6eb59c9de4a826d989c2cb8fa3, type: 3}
- - {fileID: -1601303454531013713, guid: f5f86dadd6076894ea48d85727602c05, type: 3}
- - {fileID: 77440445257819171, guid: 872e5e3fdddc35548bde8dc94260fcb4, type: 3}
- - {fileID: -6237258013232224992, guid: 36b8562f45216f64a85d9a936740e4e5, type: 3}
+ - {fileID: 7400000, guid: 2dd6c17c531609c4f9916ee823d1b59f, type: 2}
+ - {fileID: 7400000, guid: 92048c8715663824db12edf9e8f37b1a, type: 2}
+ - {fileID: 7400000, guid: fee9942923e37e64eb04557cd4e28cdf, type: 2}
+ - {fileID: 7400000, guid: e920395a39d50ca429748ac26967e22f, type: 2}
+ - {fileID: 7400000, guid: 7a180d15c7b07a64485c8dd4ec7a1fa7, type: 2}
+ - {fileID: 7400000, guid: a8845febff04ecb48b25dac5321c4481, type: 2}
+ - {fileID: 7400000, guid: 190622b0cba48234ba7fc295facac207, type: 2}
+ - {fileID: 7400000, guid: f43438b6095588f4fb4715bd6df16df8, type: 2}
+ - {fileID: 7400000, guid: 38a21eded51c5b24bb70a48d387aa565, type: 2}
+ - {fileID: 7400000, guid: 92a17c8d63463f741a0e9d305a838993, type: 2}
+ - {fileID: 7400000, guid: 79ef70f9bb079cf4799a4f6935b8d984, type: 2}
+ - {fileID: 7400000, guid: b4695213163f18f4099854fe3a39d864, type: 2}
+ - {fileID: 7400000, guid: 27f34978bd8e5174cb07562401cea581, type: 2}
+ - {fileID: 7400000, guid: 12bfabc84bb078b41b91dcb0e73034ff, type: 2}
+ - {fileID: 7400000, guid: 1ec316f7bc59114438737162c529e859, type: 2}
+ - {fileID: 7400000, guid: 47db64c106703ee498e4495d1c434b77, type: 2}
+ - {fileID: 7400000, guid: 665885351f6fc9d4c8b188498edb3d7d, type: 2}
+ - {fileID: 7400000, guid: c2676ac491a6fc94eb042b76a9c3406e, type: 2}
debugMode: 1
showAreaDebug: 1
debugDrawDuration: 1
@@ -5046,6 +5059,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Player.PlayerSkillInput
ShowTopMostFoldoutHeaderGroup: 1
+ skillRegistry: {fileID: 11400000, guid: 1899abd2213d1374dac386c5c865eb16, type: 2}
skillSlots:
- {fileID: 11400000, guid: b7f09e0e899c8fc4bb2cc9204cc6eb4a, type: 2}
- {fileID: 11400000, guid: b8c86399865e91144a3d6fcfddc04fd9, type: 2}
@@ -5053,7 +5067,7 @@ MonoBehaviour:
- {fileID: 11400000, guid: a822c7e8c7cee5546ad594b582208e53, type: 2}
- {fileID: 11400000, guid: 29e1ce0656471b54f84b18a773032a99, type: 2}
- {fileID: 0}
- - {fileID: 0}
+ - {fileID: 11400000, guid: 2ed15dca92a165046b6df17b28f64874, type: 2}
skillLoadoutEntries:
- baseSkill: {fileID: 11400000, guid: b7f09e0e899c8fc4bb2cc9204cc6eb4a, type: 2}
socketedGems:
@@ -5079,7 +5093,7 @@ MonoBehaviour:
socketedGems:
- {fileID: 0}
- {fileID: 0}
- - baseSkill: {fileID: 0}
+ - baseSkill: {fileID: 11400000, guid: 2ed15dca92a165046b6df17b28f64874, type: 2}
socketedGems:
- {fileID: 0}
- {fileID: 0}
@@ -5163,13 +5177,15 @@ MonoBehaviour:
ShowTopMostFoldoutHeaderGroup: 1
characterStats: {fileID: -5132198055668300151}
rightHandName: prop_r
- leftHandName: Hand_L
+ leftHandName: prop_l
backName: Spine
hipName: Hip
- twoHandedName:
+ twoHandedName: prop_r
startingWeapon: {fileID: 11400000, guid: 646964ccbda84e947b97537d7f7813aa, type: 2}
+ startingOffhandWeapon: {fileID: 0}
registeredWeapons:
- {fileID: 11400000, guid: 646964ccbda84e947b97537d7f7813aa, type: 2}
+ registeredOffhands: []
--- !u!114 &3574789915074274759
MonoBehaviour:
m_ObjectHideFlags: 0
diff --git a/Assets/_Game/Scripts/Player/PlayerSkillInput.cs b/Assets/_Game/Scripts/Player/PlayerSkillInput.cs
index 38196d7e..7ba89e1e 100644
--- a/Assets/_Game/Scripts/Player/PlayerSkillInput.cs
+++ b/Assets/_Game/Scripts/Player/PlayerSkillInput.cs
@@ -62,8 +62,12 @@ namespace Colosseum.Player
};
#endif
+ [Header("Skill Registry")]
+ [Tooltip("모든 플레이어 스킬을 관리하는 레지스트리. 참조하면 등록된 스킬이 빌드에 자동 포함됩니다.")]
+ [SerializeField] private SkillRegistry skillRegistry;
+
[Header("Skill Slots")]
- [Tooltip("각 슬롯에 등록할 스킬 데이터 (6개 + 추가 슬롯)")]
+ [Tooltip("각 슬롯에 등록할 스킬 데이터 (6개 + 회피 슬롯). 슬롯 0~5는 디버그용으로 Inspector에서 수동 설정합니다.")]
[SerializeField] private SkillData[] skillSlots = new SkillData[ExpectedSkillSlotCount];
[Tooltip("각 슬롯의 베이스 스킬 + 젬 조합")]
[SerializeField] private SkillLoadoutEntry[] skillLoadoutEntries = new SkillLoadoutEntry[ExpectedSkillSlotCount];
@@ -155,6 +159,7 @@ namespace Colosseum.Player
EnsureSkillSlotCapacity();
EnsureSkillLoadoutCapacity();
SyncLegacySkillsToLoadoutEntries();
+ AutoRegisterPlayerSkills();
}
private void OnEnable()
@@ -235,6 +240,95 @@ namespace Colosseum.Player
}
}
+ #if UNITY_EDITOR
+ ///
+ /// Registry에서 전체 플릴이어 스킬을 자동 수집하고,
+ /// 회피 슬롯(index 6)에 구르기 스킬이 없으면 자동 할당합니다.
+ /// 슬롯 0~5는 Inspector에서 수동 설정한 값을 유지합니다.
+ ///
+ private void AutoRegisterPlayerSkills()
+ {
+ // Registry가 할당되어 있으면 전체 스킬 자동 수집
+ if (skillRegistry != null)
+ skillRegistry.AutoCollectPlayerSkills();
+
+ // 회피 슬롯(마지막 슬롯)에 구르기 자동 할당
+ int evadeSlotIndex = ExpectedSkillSlotCount - 1;
+ SkillData evadeSkill = FindEvadeSkill();
+ if (evadeSkill != null && skillSlots[evadeSlotIndex] != evadeSkill)
+ {
+ skillSlots[evadeSlotIndex] = evadeSkill;
+ skillLoadoutEntries[evadeSlotIndex].SetBaseSkill(evadeSkill);
+ Debug.Log($"[PlayerSkillInput] 회피 스킬 자동 장착: {evadeSkill.SkillName}", this);
+ }
+ }
+
+ ///
+ /// Registry 또는 에셋 폴더에서 구르기 스킬을 찾습니다.
+ ///
+ private SkillData FindEvadeSkill()
+ {
+ // 1. Registry에서 검색
+ if (skillRegistry != null)
+ {
+ SkillData found = skillRegistry.FindPlayerSkillByNameContains("구르기");
+ if (found != null)
+ return found;
+ }
+
+ // 2. 에셋 폴더에서 직접 검색 (Registry 미할당 시 폴백)
+ string[] guids = AssetDatabase.FindAssets("t:SkillData", new[] { "Assets/_Game/Data/Skills" });
+ foreach (string guid in guids)
+ {
+ string path = AssetDatabase.GUIDToAssetPath(guid);
+ string assetName = Path.GetFileNameWithoutExtension(path);
+
+ if (assetName.IndexOf("구르기", StringComparison.OrdinalIgnoreCase) >= 0)
+ return AssetDatabase.LoadAssetAtPath(path);
+ }
+
+ Debug.LogWarning("[PlayerSkillInput] 구르기 스킬을 찾을 수 없습니다.", this);
+ return null;
+ }
+
+ ///
+ /// 에디터에서 접근 가능한 전체 플레이어 스킬 목록 (디버그/빌드 도구용).
+ ///
+ public static IReadOnlyList EditorAllPlayerSkills
+ {
+ get
+ {
+ // Registry에서 수집된 스킬이 있으면 사용
+ string[] guids = AssetDatabase.FindAssets("t:SkillRegistry", null);
+ foreach (string guid in guids)
+ {
+ string path = AssetDatabase.GUIDToAssetPath(guid);
+ SkillRegistry registry = AssetDatabase.LoadAssetAtPath(path);
+ if (registry != null && registry.PlayerSkills.Count > 0)
+ return registry.PlayerSkills;
+ }
+
+ // Registry가 없으면 직접 수집
+ var skills = new List();
+ string[] skillGuids = AssetDatabase.FindAssets("t:SkillData", new[] { "Assets/_Game/Data/Skills" });
+ foreach (string skillGuid in skillGuids)
+ {
+ string skillPath = AssetDatabase.GUIDToAssetPath(skillGuid);
+ string assetName = Path.GetFileNameWithoutExtension(skillPath);
+
+ if (assetName.IndexOf("_Skill_Player_", StringComparison.OrdinalIgnoreCase) >= 0)
+ {
+ SkillData skill = AssetDatabase.LoadAssetAtPath(skillPath);
+ if (skill != null)
+ skills.Add(skill);
+ }
+ }
+
+ return skills;
+ }
+ }
+ #endif
+
///
/// 기존 SkillData 직렬화와 새 로드아웃 엔트리 구조를 동기화합니다.
///
diff --git a/Assets/_Game/Scripts/Skills/SkillRegistry.cs b/Assets/_Game/Scripts/Skills/SkillRegistry.cs
new file mode 100644
index 00000000..ba9e041f
--- /dev/null
+++ b/Assets/_Game/Scripts/Skills/SkillRegistry.cs
@@ -0,0 +1,146 @@
+using System;
+using System.Collections.Generic;
+
+using UnityEngine;
+
+#if UNITY_EDITOR
+using System.IO;
+using System.Text.RegularExpressions;
+
+using UnityEditor;
+#endif
+
+namespace Colosseum.Skills
+{
+ ///
+ /// 프로젝트의 모든 스킬 데이터를 중앙에서 관리하는 ScriptableObject.
+ /// 이 에셋을 프리팹이나 씬에서 참조하면, 등록된 모든 스킬이 빌드에 자동 포함됩니다.
+ ///
+ [CreateAssetMenu(fileName = "SkillRegistry", menuName = "Colosseum/SkillRegistry")]
+ public class SkillRegistry : ScriptableObject
+ {
+ private const string PlayerSkillSearchFolder = "Assets/_Game/Data/Skills";
+ private const string PlayerSkillNameFilter = "_Skill_Player_";
+
+ [Header("Player Skills")]
+ [Tooltip("모든 플레이어 스킬 데이터 (에디터에서 자동 수집됨)")]
+ [SerializeField] private List playerSkills = new();
+
+ ///
+ /// 등록된 모든 플레이어 스킬 목록
+ ///
+ public IReadOnlyList PlayerSkills => playerSkills;
+
+ ///
+ /// 스킬 이름으로 플레이어 스킬을 조회합니다.
+ ///
+ public SkillData GetPlayerSkill(string skillName)
+ {
+ if (string.IsNullOrEmpty(skillName))
+ return null;
+
+ for (int i = 0; i < playerSkills.Count; i++)
+ {
+ SkillData skill = playerSkills[i];
+ if (skill != null && string.Equals(skill.SkillName, skillName, StringComparison.Ordinal))
+ return skill;
+ }
+
+ return null;
+ }
+
+ ///
+ /// 에셋 이름으로 플레이어 스킬을 조회합니다.
+ ///
+ public SkillData GetPlayerSkillByAssetName(string assetName)
+ {
+ if (string.IsNullOrEmpty(assetName))
+ return null;
+
+ for (int i = 0; i < playerSkills.Count; i++)
+ {
+ SkillData skill = playerSkills[i];
+ if (skill != null && string.Equals(skill.name, assetName, StringComparison.Ordinal))
+ return skill;
+ }
+
+ return null;
+ }
+
+ ///
+ /// 이름에 지정 문자열이 포함된 첫 번째 플레이어 스킬을 조회합니다.
+ ///
+ public SkillData FindPlayerSkillByNameContains(string fragment)
+ {
+ if (string.IsNullOrEmpty(fragment))
+ return null;
+
+ for (int i = 0; i < playerSkills.Count; i++)
+ {
+ SkillData skill = playerSkills[i];
+ if (skill != null && skill.name.IndexOf(fragment, StringComparison.OrdinalIgnoreCase) >= 0)
+ return skill;
+ }
+
+ return null;
+ }
+
+#if UNITY_EDITOR
+ ///
+ /// "_Skill_Player_" 이름이 포함된 모든 SkillData를 에디터에서 자동 수집합니다.
+ /// OnValidate에서 호출되어 에셋 변경 시 자동 동기화됩니다.
+ ///
+ public void AutoCollectPlayerSkills()
+ {
+ if (!AssetDatabase.IsValidFolder(PlayerSkillSearchFolder))
+ {
+ Debug.LogWarning($"[SkillRegistry] 스킬 검색 폴더가 없습니다: {PlayerSkillSearchFolder}", this);
+ return;
+ }
+
+ string[] guids = AssetDatabase.FindAssets("t:SkillData", new[] { PlayerSkillSearchFolder });
+ var skills = new List();
+
+ foreach (string guid in guids)
+ {
+ string path = AssetDatabase.GUIDToAssetPath(guid);
+ string assetName = Path.GetFileNameWithoutExtension(path);
+
+ if (assetName.IndexOf(PlayerSkillNameFilter, StringComparison.OrdinalIgnoreCase) >= 0)
+ {
+ SkillData skill = AssetDatabase.LoadAssetAtPath(path);
+ if (skill != null)
+ skills.Add(skill);
+ }
+ }
+
+ skills.Sort((a, b) => string.Compare(a.name, b.name, StringComparison.Ordinal));
+
+ bool changed = playerSkills.Count != skills.Count;
+ if (!changed)
+ {
+ for (int i = 0; i < skills.Count; i++)
+ {
+ if (playerSkills[i] != skills[i])
+ {
+ changed = true;
+ break;
+ }
+ }
+ }
+
+ if (changed)
+ {
+ playerSkills.Clear();
+ playerSkills.AddRange(skills);
+ Debug.Log($"[SkillRegistry] 자동 수집: {skills.Count}개 Player 스킬", this);
+ }
+ }
+
+ private void OnValidate()
+ {
+ AutoCollectPlayerSkills();
+ }
+#endif
+ }
+}
diff --git a/Assets/_Game/Scripts/Skills/SkillRegistry.cs.meta b/Assets/_Game/Scripts/Skills/SkillRegistry.cs.meta
new file mode 100644
index 00000000..82ca5b21
--- /dev/null
+++ b/Assets/_Game/Scripts/Skills/SkillRegistry.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 144e0ffda72c68941800f40c5755fee8
\ No newline at end of file