feat: 젬 분류 사양과 장착 UI 반영

- 옵시디언 기준의 역할/발동 타입 분류를 스킬·젬 데이터와 장착 검증 로직에 반영
- 젬 보관 UI와 퀵슬롯 표시를 새 분류 및 실제 마나/쿨타임 계산 기준으로 갱신
- 테스트 스킬/젬 자산을 에디터 메뉴로 동기화하고 Unity 컴파일 및 플레이 검증 완료
This commit is contained in:
2026-03-26 16:18:45 +09:00
parent 1261d4dc3c
commit a94daf7968
27 changed files with 1738 additions and 59 deletions

View File

@@ -17,7 +17,9 @@ MonoBehaviour:
\uBA74\uC5ED\uC744 \uBD80\uC5EC\uD558\uB294 \uD14C\uC2A4\uD2B8\uC6A9 \uBC29\uC5B4
\uC82C"
icon: {fileID: 0}
category: 3
category: 2
allowedSkillRoles: 7
allowedSkillActivationTypes: 3
allowedSkillTypes: 63
incompatibleCategories:
incompatibleGems: []

View File

@@ -17,6 +17,8 @@ MonoBehaviour:
\uD14C\uC2A4\uD2B8\uC6A9 \uACF5\uACA9 \uC82C"
icon: {fileID: 0}
category: 1
allowedSkillRoles: 1
allowedSkillActivationTypes: 1
allowedSkillTypes: 1
incompatibleCategories:
incompatibleGems: []

View File

@@ -16,7 +16,9 @@ MonoBehaviour:
description: "\uACE0\uC704\uB825 \uAE30\uC220\uC5D0 \uC704\uD611 \uC120\uC810 \uAE30\uB2A5\uC744
\uC5B9\uB294 \uD14C\uC2A4\uD2B8\uC6A9 \uC82C"
icon: {fileID: 0}
category: 2
category: 4
allowedSkillRoles: 3
allowedSkillActivationTypes: 1
allowedSkillTypes: 1
incompatibleCategories:
incompatibleGems: []

View File

@@ -16,7 +16,9 @@ MonoBehaviour:
description: "\uACE0\uC704\uB825 \uAE30\uC220\uC5D0 \uBCF4\uD638\uB9C9 \uBCF4\uC870\uB97C
\uC5B9\uB294 \uD14C\uC2A4\uD2B8\uC6A9 \uC82C"
icon: {fileID: 0}
category: 4
category: 2
allowedSkillRoles: 7
allowedSkillActivationTypes: 1
allowedSkillTypes: 1
incompatibleCategories:
incompatibleGems: []

View File

@@ -16,7 +16,9 @@ MonoBehaviour:
description: "\uC2A4\uD0AC \uC801\uC911 \uB300\uC0C1\uC5D0\uAC8C \uD14C\uC2A4\uD2B8
\uB514\uBC84\uD504\uB97C \uBD80\uC5EC\uD558\uB294 \uC81C\uC5B4 \uC82C"
icon: {fileID: 0}
category: 5
category: 4
allowedSkillRoles: 7
allowedSkillActivationTypes: 1
allowedSkillTypes: 63
incompatibleCategories:
incompatibleGems: []

View File

@@ -16,7 +16,9 @@ MonoBehaviour:
description: "\uBD99\uC740 \uC2A4\uD0AC\uC744 \uD55C \uBC88 \uB354 \uBC18\uBCF5
\uC2DC\uC804\uD558\uB294 \uD14C\uC2A4\uD2B8\uC6A9 \uC82C"
icon: {fileID: 0}
category: 6
category: 4
allowedSkillRoles: 1
allowedSkillActivationTypes: 1
allowedSkillTypes: 1
incompatibleCategories:
incompatibleGems: []

View File

@@ -17,6 +17,8 @@ MonoBehaviour:
\uD14C\uC2A4\uD2B8\uC6A9 \uACF5\uACA9 \uC82C"
icon: {fileID: 0}
category: 1
allowedSkillRoles: 1
allowedSkillActivationTypes: 1
allowedSkillTypes: 1
incompatibleCategories:
incompatibleGems: []

View File

@@ -17,6 +17,8 @@ MonoBehaviour:
\uD14C\uC2A4\uD2B8\uC6A9 \uACF5\uACA9 \uC82C"
icon: {fileID: 0}
category: 1
allowedSkillRoles: 1
allowedSkillActivationTypes: 1
allowedSkillTypes: 1
incompatibleCategories:
incompatibleGems: []

View File

@@ -17,6 +17,8 @@ MonoBehaviour:
\uAC15\uD654\uD558\uB294 \uD14C\uC2A4\uD2B8\uC6A9 \uC82C"
icon: {fileID: 0}
category: 1
allowedSkillRoles: 1
allowedSkillActivationTypes: 1
allowedSkillTypes: 1
incompatibleCategories:
incompatibleGems: []

View File

@@ -16,6 +16,8 @@ MonoBehaviour:
description: "\uC8FC\uBCC0 \uC544\uAD70\uACFC \uC790\uC2E0\uC758 \uCCB4\uB825\uC744
\uD568\uAED8 \uD68C\uBCF5\uD55C\uB2E4."
icon: {fileID: 0}
skillRole: 4
activationType: 1
baseTypes: 4
skillClip: {fileID: -8689311932429934276, guid: 836c26605050496b9fd07dd456e6ea82, type: 3}
endClip: {fileID: 0}

View File

@@ -15,7 +15,9 @@ MonoBehaviour:
skillName: "\uAD6C\uB974\uAE30"
description:
icon: {fileID: 21300000, guid: eafcc94eae3865944b93e64c4e281aa0, type: 3}
baseTypes: 16
skillRole: 2
activationType: 1
baseTypes: 2
skillClip: {fileID: -14460799136228694, guid: d6d51384d6dd17a419c1d8e2a1c0c875, type: 3}
endClip: {fileID: 0}
animationSpeed: 1

View File

@@ -17,7 +17,9 @@ MonoBehaviour:
\uB192\uC774\uACE0 \uC9E7\uC740 \uC2DC\uAC04 \uB3D9\uC548 \uC704\uD611 \uC0DD\uC131\uB7C9\uC744
\uC99D\uAC00\uC2DC\uD0A8\uB2E4."
icon: {fileID: 0}
baseTypes: 40
skillRole: 2
activationType: 1
baseTypes: 2
skillClip: {fileID: -4662563244894722208, guid: de4d0153716747cd9fc90c60f5efb1ae, type: 3}
endClip: {fileID: 0}
animationSpeed: 1

View File

@@ -15,7 +15,9 @@ MonoBehaviour:
skillName: "\uB3CC\uC9C4"
description:
icon: {fileID: 0}
baseTypes: 16
skillRole: 1
activationType: 1
baseTypes: 1
skillClip: {fileID: 0}
endClip: {fileID: 0}
animationSpeed: 1

View File

@@ -16,6 +16,8 @@ MonoBehaviour:
description: "\uC9E7\uC740 \uC2DC\uAC04 \uB3D9\uC548 \uBC1B\uB294 \uD53C\uD574\uB97C
\uC904\uC774\uACE0 \uC704\uD611 \uC0DD\uC131\uB7C9\uC744 \uB192\uC778\uB2E4."
icon: {fileID: 0}
skillRole: 2
activationType: 2
baseTypes: 2
skillClip: {fileID: -592826573199220879, guid: 52e14756abda46499f4739d811043b3d, type: 3}
endClip: {fileID: 0}

View File

@@ -16,7 +16,9 @@ MonoBehaviour:
description: "\uC8FC\uBCC0 \uC544\uAD70\uACFC \uC790\uC2E0\uC5D0\uAC8C \uD53C\uD574\uB97C
\uD761\uC218\uD558\uB294 \uBCF4\uD638\uB9C9\uC744 \uBD80\uC5EC\uD55C\uB2E4."
icon: {fileID: 0}
baseTypes: 6
skillRole: 4
activationType: 1
baseTypes: 4
skillClip: {fileID: -1185230921767219677, guid: f2d90cfa60b04630af1dde00f4d29320, type: 3}
endClip: {fileID: 0}
animationSpeed: 1

View File

@@ -16,7 +16,9 @@ MonoBehaviour:
description: "\uC9E7\uC740 \uC2DC\uAC04 \uB3D9\uC548 \uBB34\uC801\uC774 \uB418\uBA70
\uC704\uD611 \uC0DD\uC131\uB7C9\uC774 \uC57D\uAC04 \uC99D\uAC00\uD55C\uB2E4."
icon: {fileID: 0}
baseTypes: 6
skillRole: 2
activationType: 2
baseTypes: 2
skillClip: {fileID: -7313196749698736815, guid: 95764ba490b24918b73fc1553e34dc1e, type: 3}
endClip: {fileID: 0}
animationSpeed: 1

View File

@@ -15,6 +15,8 @@ MonoBehaviour:
skillName: "\uCE58\uC720"
description: "\uC790\uC2E0\uC758 \uCCB4\uB825\uC744 \uBE60\uB974\uAC8C \uD68C\uBCF5\uD55C\uB2E4."
icon: {fileID: 0}
skillRole: 4
activationType: 1
baseTypes: 4
skillClip: {fileID: -8689311932429934276, guid: 4450ee0d92144ade9f63dd601432d3bf, type: 3}
endClip: {fileID: 0}

View File

@@ -12,6 +12,7 @@ GameObject:
- component: {fileID: 9156608660900377980}
- component: {fileID: 3790117282943784127}
- component: {fileID: 7777120333480524073}
- component: {fileID: 110599025774604826}
m_Layer: 5
m_Name: UI_ActionBar
m_TagString: Untagged
@@ -115,6 +116,50 @@ MonoBehaviour:
- 2
- 3
- 4
--- !u!114 &110599025774604826
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 628750841697537993}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: ca98554fbe3918040881b21c34fbb373, type: 3}
m_Name:
m_EditorClassIdentifier: Colosseum.Game::Colosseum.UI.SkillGemInventoryUI
toggleKey: 21
toggleButtonLabel: "\uC82C"
toggleButtonAnchoredPosition: {x: -48, y: 164}
ownedGemEntries:
- gem: {fileID: 11400000, guid: e020eee86f6c97f4393672759d73602e, type: 2}
quantity: 1
- gem: {fileID: 11400000, guid: 47a98aa9a30748a4da49455ac0fbd142, type: 2}
quantity: 1
- gem: {fileID: 11400000, guid: e86536592f45d2b49b9d25abbad1b184, type: 2}
quantity: 1
- gem: {fileID: 11400000, guid: de5e48980eba8794c93ea7168d592f8f, type: 2}
quantity: 1
- gem: {fileID: 11400000, guid: 2edf7687dc6caa0489ae2111499fcfab, type: 2}
quantity: 1
- gem: {fileID: 11400000, guid: 863dcd9e10827f94ab4574b529ffe683, type: 2}
quantity: 1
- gem: {fileID: 11400000, guid: 66ccf80cf9c50614dbe13ea7f24a6f19, type: 2}
quantity: 1
- gem: {fileID: 11400000, guid: cf3e3e1f9f1f42f499196fa819263dc1, type: 2}
quantity: 1
- gem: {fileID: 11400000, guid: 2c42bf0e90f5dd9488d534c337a44eed, type: 2}
quantity: 1
autoCollectOwnedGemsInEditor: 1
gemSearchFolder: Assets/_Game/Data/SkillGems
autoCollectedGemQuantity: 1
panelBackgroundColor: {r: 0.08, g: 0.08, b: 0.11, a: 0.96}
sectionBackgroundColor: {r: 0.14, g: 0.14, b: 0.18, a: 0.95}
buttonNormalColor: {r: 0.19, g: 0.19, b: 0.24, a: 0.96}
buttonSelectedColor: {r: 0.48, g: 0.32, b: 0.16, a: 0.96}
buttonDisabledColor: {r: 0.12, g: 0.12, b: 0.15, a: 0.65}
statusNormalColor: {r: 0.86, g: 0.85, b: 0.78, a: 1}
statusErrorColor: {r: 1, g: 0.52, b: 0.45, a: 1}
--- !u!1001 &3885202253629243258
PrefabInstance:
m_ObjectHideFlags: 0

View File

@@ -615,7 +615,7 @@ namespace Colosseum.Editor
CrushGemPath,
"파쇄",
"고위력 기술의 단일 피해를 강화하는 테스트용 젬",
SkillGemCategory.Attack,
SkillGemCategory.Damage,
1.15f,
1.1f,
1f,
@@ -625,13 +625,15 @@ namespace Colosseum.Editor
1f,
0,
damageEffect,
allowedSkillRoles: SkillRoleType.Attack,
allowedSkillActivationTypes: SkillActivationType.Instant,
allowedSkillTypes: SkillBaseType.Attack);
CreateOrUpdateGemAsset(
ChallengerGemPath,
"도전자",
"고위력 기술에 위협 선점 기능을 얹는 테스트용 젬",
SkillGemCategory.Threat,
SkillGemCategory.Special,
1f,
1f,
1f,
@@ -641,13 +643,15 @@ namespace Colosseum.Editor
1.5f,
0,
tauntEffect,
allowedSkillRoles: SkillRoleType.Attack | SkillRoleType.Defense,
allowedSkillActivationTypes: SkillActivationType.Instant,
allowedSkillTypes: SkillBaseType.Attack);
CreateOrUpdateGemAsset(
GuardianGemPath,
"수호",
"고위력 기술에 보호막 보조를 얹는 테스트용 젬",
SkillGemCategory.Support,
SkillGemCategory.Survival,
1.05f,
1.1f,
1f,
@@ -657,13 +661,15 @@ namespace Colosseum.Editor
1f,
0,
shieldEffect,
allowedSkillRoles: SkillRoleType.All,
allowedSkillActivationTypes: SkillActivationType.Instant,
allowedSkillTypes: SkillBaseType.Attack);
CreateOrUpdateGemAsset(
RepeatGemPath,
"연속",
"붙은 스킬을 한 번 더 반복 시전하는 테스트용 젬",
SkillGemCategory.Efficiency,
SkillGemCategory.Special,
1.2f,
1.15f,
1.1f,
@@ -673,13 +679,15 @@ namespace Colosseum.Editor
1f,
1,
null,
allowedSkillRoles: SkillRoleType.Attack,
allowedSkillActivationTypes: SkillActivationType.Instant,
allowedSkillTypes: SkillBaseType.Attack);
CreateOrUpdateGemAsset(
FortitudeGemPath,
"강인함",
"스킬 사용 시 자신에게 경직 면역을 부여하는 테스트용 방어 젬",
SkillGemCategory.Defense,
SkillGemCategory.Survival,
1.05f,
1.05f,
1f,
@@ -689,13 +697,15 @@ namespace Colosseum.Editor
1f,
0,
null,
new[] { hitReactionImmuneAbnormality });
new[] { hitReactionImmuneAbnormality },
allowedSkillRoles: SkillRoleType.All,
allowedSkillActivationTypes: SkillActivationType.All);
CreateOrUpdateGemAsset(
WitherGemPath,
"약화",
"스킬 적중 대상에게 테스트 디버프를 부여하는 제어 젬",
SkillGemCategory.Control,
SkillGemCategory.Special,
1.05f,
1.05f,
1f,
@@ -707,13 +717,15 @@ namespace Colosseum.Editor
null,
null,
0,
new[] { testDebuffAbnormality });
new[] { testDebuffAbnormality },
allowedSkillRoles: SkillRoleType.All,
allowedSkillActivationTypes: SkillActivationType.Instant);
CreateOrUpdateGemAsset(
EdgeGemPath,
"예리함",
"고정 추가 피해를 부여하는 테스트용 공격 젬",
SkillGemCategory.Attack,
SkillGemCategory.Damage,
1f,
1f,
1f,
@@ -723,13 +735,15 @@ namespace Colosseum.Editor
1f,
0,
edgeDamageEffect,
allowedSkillRoles: SkillRoleType.Attack,
allowedSkillActivationTypes: SkillActivationType.Instant,
allowedSkillTypes: SkillBaseType.Attack);
CreateOrUpdateGemAsset(
ImpactGemPath,
"충격",
"중간 고정 추가 피해를 부여하는 테스트용 공격 젬",
SkillGemCategory.Attack,
SkillGemCategory.Damage,
1f,
1f,
1f,
@@ -739,13 +753,15 @@ namespace Colosseum.Editor
1f,
0,
impactDamageEffect,
allowedSkillRoles: SkillRoleType.Attack,
allowedSkillActivationTypes: SkillActivationType.Instant,
allowedSkillTypes: SkillBaseType.Attack);
CreateOrUpdateGemAsset(
BreachGemPath,
"관통",
"높은 고정 추가 피해를 부여하는 테스트용 공격 젬",
SkillGemCategory.Attack,
SkillGemCategory.Damage,
1f,
1f,
1f,
@@ -755,6 +771,8 @@ namespace Colosseum.Editor
1f,
0,
breachDamageEffect,
allowedSkillRoles: SkillRoleType.Attack,
allowedSkillActivationTypes: SkillActivationType.Instant,
allowedSkillTypes: SkillBaseType.Attack);
AssetDatabase.SaveAssets();
@@ -793,19 +811,19 @@ namespace Colosseum.Editor
SkillGemData impactGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(ImpactGemPath);
SkillGemData breachGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(BreachGemPath);
SetSkillBaseTypes(slashSkill, SkillBaseType.Attack);
SetSkillBaseTypes(tauntSkill, SkillBaseType.Control | SkillBaseType.Utility);
SetSkillBaseTypes(guardSkill, SkillBaseType.Defense);
SetSkillBaseTypes(dashSkill, SkillBaseType.Mobility);
SetSkillBaseTypes(ironWallSkill, SkillBaseType.Defense | SkillBaseType.Support);
SetSkillBaseTypes(pierceSkill, SkillBaseType.Attack);
SetSkillBaseTypes(gemTestSkill, SkillBaseType.Attack);
SetSkillBaseTypes(healSkill, SkillBaseType.Support);
SetSkillBaseTypes(areaHealSkill, SkillBaseType.Support);
SetSkillBaseTypes(shieldSkill, SkillBaseType.Defense | SkillBaseType.Support);
SetSkillBaseTypes(projectileSkill, SkillBaseType.Attack);
SetSkillBaseTypes(spinSkill, SkillBaseType.Attack);
SetSkillBaseTypes(evadeSkill, SkillBaseType.Mobility);
SetSkillClassification(slashSkill, SkillRoleType.Attack, SkillActivationType.Instant, SkillBaseType.Attack);
SetSkillClassification(tauntSkill, SkillRoleType.Defense, SkillActivationType.Instant, SkillBaseType.Defense);
SetSkillClassification(guardSkill, SkillRoleType.Defense, SkillActivationType.Buff, SkillBaseType.Defense);
SetSkillClassification(dashSkill, SkillRoleType.Attack, SkillActivationType.Instant, SkillBaseType.Attack);
SetSkillClassification(ironWallSkill, SkillRoleType.Defense, SkillActivationType.Buff, SkillBaseType.Defense);
SetSkillClassification(pierceSkill, SkillRoleType.Attack, SkillActivationType.Instant, SkillBaseType.Attack);
SetSkillClassification(gemTestSkill, SkillRoleType.Attack, SkillActivationType.Instant, SkillBaseType.Attack);
SetSkillClassification(healSkill, SkillRoleType.Support, SkillActivationType.Instant, SkillBaseType.Support);
SetSkillClassification(areaHealSkill, SkillRoleType.Support, SkillActivationType.Instant, SkillBaseType.Support);
SetSkillClassification(shieldSkill, SkillRoleType.Support, SkillActivationType.Instant, SkillBaseType.Support);
SetSkillClassification(projectileSkill, SkillRoleType.Attack, SkillActivationType.Instant, SkillBaseType.Attack);
SetSkillClassification(spinSkill, SkillRoleType.Attack, SkillActivationType.Instant, SkillBaseType.Attack);
SetSkillClassification(evadeSkill, SkillRoleType.Defense, SkillActivationType.Instant, SkillBaseType.Defense);
EnsureGemTestSkillSlotCount(gemTestSkill, 3);
@@ -1336,6 +1354,8 @@ namespace Colosseum.Editor
AbnormalityData[] selfAbnormalities = null,
int triggeredAbnormalityIndex = -1,
AbnormalityData[] onHitAbnormalities = null,
SkillRoleType allowedSkillRoles = SkillRoleType.All,
SkillActivationType allowedSkillActivationTypes = SkillActivationType.All,
SkillBaseType allowedSkillTypes = SkillBaseType.All,
SkillGemCategory[] incompatibleCategories = null,
SkillGemData[] incompatibleGems = null)
@@ -1356,6 +1376,8 @@ namespace Colosseum.Editor
serializedGem.FindProperty("gemName").stringValue = gemName;
serializedGem.FindProperty("description").stringValue = description;
serializedGem.FindProperty("category").enumValueIndex = (int)category;
serializedGem.FindProperty("allowedSkillRoles").intValue = (int)allowedSkillRoles;
serializedGem.FindProperty("allowedSkillActivationTypes").intValue = (int)allowedSkillActivationTypes;
serializedGem.FindProperty("manaCostMultiplier").floatValue = manaCostMultiplier;
serializedGem.FindProperty("cooldownMultiplier").floatValue = cooldownMultiplier;
serializedGem.FindProperty("castSpeedMultiplier").floatValue = castSpeedMultiplier;
@@ -1424,17 +1446,42 @@ namespace Colosseum.Editor
EditorUtility.SetDirty(gem);
}
private static void SetSkillBaseTypes(SkillData skill, SkillBaseType baseTypes)
private static void SetSkillClassification(
SkillData skill,
SkillRoleType skillRole,
SkillActivationType activationType,
SkillBaseType baseTypes)
{
if (skill == null)
return;
SerializedObject serializedSkill = new SerializedObject(skill);
bool hasChanges = false;
SerializedProperty skillRoleProperty = serializedSkill.FindProperty("skillRole");
if (skillRoleProperty != null && skillRoleProperty.intValue != (int)skillRole)
{
skillRoleProperty.intValue = (int)skillRole;
hasChanges = true;
}
SerializedProperty activationTypeProperty = serializedSkill.FindProperty("activationType");
if (activationTypeProperty != null && activationTypeProperty.intValue != (int)activationType)
{
activationTypeProperty.intValue = (int)activationType;
hasChanges = true;
}
SerializedProperty baseTypesProperty = serializedSkill.FindProperty("baseTypes");
if (baseTypesProperty == null || baseTypesProperty.intValue == (int)baseTypes)
if (baseTypesProperty != null && baseTypesProperty.intValue != (int)baseTypes)
{
baseTypesProperty.intValue = (int)baseTypes;
hasChanges = true;
}
if (!hasChanges)
return;
baseTypesProperty.intValue = (int)baseTypes;
serializedSkill.ApplyModifiedPropertiesWithoutUndo();
EditorUtility.SetDirty(skill);
}
@@ -1571,7 +1618,7 @@ namespace Colosseum.Editor
if (hasGem)
categoryBuilder.Append(", ");
categoryBuilder.Append(gem.Category);
categoryBuilder.Append(SkillClassificationUtility.GetGemCategoryLabel(gem.Category));
hasGem = true;
}

View File

@@ -32,6 +32,7 @@ namespace Colosseum.Player
private Vector3 velocity;
private Vector2 moveInput; // 로컬 원시 입력 (IsOwner 전용)
private InputSystem_Actions inputActions;
private bool gameplayInputEnabled = true;
private bool isJumping;
private bool wasGrounded;
private Vector3 forcedMovementVelocity;
@@ -97,10 +98,10 @@ namespace Colosseum.Player
private void InitializeInputActions()
{
inputActions = new InputSystem_Actions();
inputActions.Player.Enable();
inputActions.Player.Move.performed += OnMovePerformed;
inputActions.Player.Move.canceled += OnMoveCanceled;
inputActions.Player.Jump.performed += OnJumpPerformed;
SetGameplayInputEnabled(true);
}
private void CleanupInputActions()
@@ -125,10 +126,7 @@ namespace Colosseum.Player
{
if (IsOwner && inputActions != null)
{
inputActions.Player.Enable();
inputActions.Player.Move.performed += OnMovePerformed;
inputActions.Player.Move.canceled += OnMoveCanceled;
inputActions.Player.Jump.performed += OnJumpPerformed;
SetGameplayInputEnabled(gameplayInputEnabled);
}
}
@@ -153,9 +151,33 @@ namespace Colosseum.Player
private void OnMovePerformed(InputAction.CallbackContext context) => moveInput = context.ReadValue<Vector2>();
private void OnMoveCanceled(InputAction.CallbackContext context) => moveInput = Vector2.zero;
/// <summary>
/// 로컬 플레이어의 전투 입력을 일시적으로 차단하거나 복구합니다.
/// </summary>
public void SetGameplayInputEnabled(bool enabled)
{
gameplayInputEnabled = enabled;
if (!IsOwner || inputActions == null)
return;
if (enabled)
{
inputActions.Player.Enable();
return;
}
moveInput = Vector2.zero;
if (netMoveInput.Value != Vector2.zero)
netMoveInput.Value = Vector2.zero;
inputActions.Player.Disable();
}
private void OnJumpPerformed(InputAction.CallbackContext context)
{
if (!IsOwner) return;
if (!gameplayInputEnabled) return;
if (actionState != null && !actionState.CanJump) return;
JumpRequestRpc();

View File

@@ -77,6 +77,7 @@ namespace Colosseum.Player
[SerializeField] private PlayerActionState actionState;
private InputSystem_Actions inputActions;
private bool gameplayInputEnabled = true;
public SkillData[] SkillSlots => skillSlots;
public SkillLoadoutEntry[] SkillLoadoutEntries => skillLoadoutEntries;
@@ -117,7 +118,7 @@ namespace Colosseum.Player
inputActions.Player.Evade.performed += OnEvadePerformed;
}
inputActions.Player.Enable();
SetGameplayInputEnabled(true);
}
public override void OnNetworkDespawn()
@@ -148,10 +149,29 @@ namespace Colosseum.Player
{
if (IsOwner && inputActions != null)
{
inputActions.Player.Enable();
SetGameplayInputEnabled(gameplayInputEnabled);
}
}
/// <summary>
/// 로컬 플레이어의 스킬 입력을 일시적으로 차단하거나 복구합니다.
/// </summary>
public void SetGameplayInputEnabled(bool enabled)
{
gameplayInputEnabled = enabled;
if (!IsOwner || inputActions == null)
return;
if (enabled)
{
inputActions.Player.Enable();
return;
}
inputActions.Player.Disable();
}
/// <summary>
/// 기존 프리팹이나 씬 직렬화 데이터가 6칸으로 남아 있어도
/// 긴급 회피 슬롯까지 포함한 7칸 구성을 항상 보장합니다.
@@ -244,6 +264,9 @@ namespace Colosseum.Player
/// </summary>
private void OnSkillInput(int slotIndex)
{
if (!gameplayInputEnabled)
return;
if (slotIndex < 0 || slotIndex >= skillSlots.Length)
return;

View File

@@ -22,6 +22,32 @@ namespace Colosseum.Skills
All = Attack | Defense | Support | Control | Mobility | Utility,
}
/// <summary>
/// 스킬의 역할 분류입니다.
/// 젬 장착 조건에는 비트 마스크 형태로도 사용합니다.
/// </summary>
[Flags]
public enum SkillRoleType
{
None = 0,
Attack = 1 << 0,
Defense = 1 << 1,
Support = 1 << 2,
All = Attack | Defense | Support,
}
/// <summary>
/// 스킬의 발동 타입 분류입니다.
/// </summary>
[Flags]
public enum SkillActivationType
{
None = 0,
Instant = 1 << 0,
Buff = 1 << 1,
All = Instant | Buff,
}
/// <summary>
/// 스킬 데이터. 스킬의 기본 정보와 효과 목록을 관리합니다.
/// </summary>
@@ -34,8 +60,14 @@ namespace Colosseum.Skills
[SerializeField] private string description;
[SerializeField] private Sprite icon;
[Header("기반 스킬 분류")]
[Tooltip("젬 장착 가능 조건에 사용하는 기반 스킬 분류")]
[Header("스킬 분류")]
[Tooltip("이 스킬의 주 역할입니다.")]
[SerializeField] private SkillRoleType skillRole = SkillRoleType.Attack;
[Tooltip("이 스킬의 발동 타입입니다.")]
[SerializeField] private SkillActivationType activationType = SkillActivationType.Instant;
[Header("레거시 기반 스킬 분류")]
[Tooltip("기존 테스트 데이터와의 호환을 위한 기반 분류입니다.")]
[SerializeField] private SkillBaseType baseTypes = SkillBaseType.None;
[Header("애니메이션")]
@@ -82,6 +114,8 @@ namespace Colosseum.Skills
public string SkillName => skillName;
public string Description => description;
public Sprite Icon => icon;
public SkillRoleType SkillRole => skillRole;
public SkillActivationType ActivationType => activationType;
public SkillBaseType BaseTypes => baseTypes;
public AnimationClip SkillClip => skillClip;
public AnimationClip EndClip => endClip;
@@ -97,5 +131,104 @@ namespace Colosseum.Skills
public bool BlockOtherSkillsWhileCasting => blockOtherSkillsWhileCasting;
public IReadOnlyList<SkillEffect> CastStartEffects => castStartEffects;
public IReadOnlyList<SkillEffect> Effects => effects;
/// <summary>
/// 지정한 장착 조건과 현재 스킬 분류가 맞는지 확인합니다.
/// </summary>
public bool MatchesClassification(SkillRoleType allowedRoles, SkillActivationType allowedActivationTypes)
{
bool matchesRole = allowedRoles == SkillRoleType.None ||
allowedRoles == SkillRoleType.All ||
(allowedRoles & skillRole) != 0;
bool matchesActivationType = allowedActivationTypes == SkillActivationType.None ||
allowedActivationTypes == SkillActivationType.All ||
(allowedActivationTypes & activationType) != 0;
return matchesRole && matchesActivationType;
}
}
/// <summary>
/// 스킬/젬 분류를 UI 친화적인 문자열로 변환하는 유틸리티입니다.
/// </summary>
public static class SkillClassificationUtility
{
public static string GetRoleLabel(SkillRoleType role)
{
return role switch
{
SkillRoleType.Attack => "공격",
SkillRoleType.Defense => "방어",
SkillRoleType.Support => "지원",
SkillRoleType.All => "전체",
_ => "미분류",
};
}
public static string GetActivationTypeLabel(SkillActivationType activationType)
{
return activationType switch
{
SkillActivationType.Instant => "즉발",
SkillActivationType.Buff => "버프",
SkillActivationType.All => "전체",
_ => "미분류",
};
}
public static string GetSkillClassificationLabel(SkillData skill)
{
if (skill == null)
return "미분류";
return $"{GetRoleLabel(skill.SkillRole)}/{GetActivationTypeLabel(skill.ActivationType)}";
}
public static string GetGemCategoryLabel(SkillGemCategory category)
{
return category switch
{
SkillGemCategory.Damage => "데미지",
SkillGemCategory.Survival => "생존",
SkillGemCategory.Mana => "마나",
SkillGemCategory.Special => "특수",
SkillGemCategory.BuffPower => "효과 강화",
SkillGemCategory.Duration => "지속시간",
SkillGemCategory.Area => "범위",
SkillGemCategory.Cost => "비용",
_ => "공용",
};
}
public static string GetAllowedRoleSummary(SkillRoleType roles)
{
if (roles == SkillRoleType.None || roles == SkillRoleType.All)
return "전체";
List<string> labels = new List<string>();
if ((roles & SkillRoleType.Attack) != 0)
labels.Add("공격");
if ((roles & SkillRoleType.Defense) != 0)
labels.Add("방어");
if ((roles & SkillRoleType.Support) != 0)
labels.Add("지원");
return labels.Count > 0 ? string.Join(" + ", labels) : "미분류";
}
public static string GetAllowedActivationSummary(SkillActivationType activationTypes)
{
if (activationTypes == SkillActivationType.None || activationTypes == SkillActivationType.All)
return "전체";
List<string> labels = new List<string>();
if ((activationTypes & SkillActivationType.Instant) != 0)
labels.Add("즉발");
if ((activationTypes & SkillActivationType.Buff) != 0)
labels.Add("버프");
return labels.Count > 0 ? string.Join(" + ", labels) : "미분류";
}
}
}

View File

@@ -13,12 +13,14 @@ namespace Colosseum.Skills
public enum SkillGemCategory
{
Common,
Attack,
Threat,
Defense,
Support,
Control,
Efficiency,
Damage,
Survival,
Mana,
Special,
BuffPower,
Duration,
Area,
Cost,
}
/// <summary>
@@ -66,9 +68,13 @@ namespace Colosseum.Skills
[SerializeField] private SkillGemCategory category = SkillGemCategory.Common;
[Header("장착 제약")]
[Tooltip("장착 가능한 기반 스킬 분류입니다. None 또는 All이면 제한하지 않습니다.")]
[Tooltip("장착 가능한 스킬 역할 조합입니다. None 또는 All이면 역할 제한을 두지 않습니다.")]
[SerializeField] private SkillRoleType allowedSkillRoles = SkillRoleType.All;
[Tooltip("장착 가능한 스킬 발동 타입 조합입니다. None 또는 All이면 타입 제한을 두지 않습니다.")]
[SerializeField] private SkillActivationType allowedSkillActivationTypes = SkillActivationType.All;
[Tooltip("기존 테스트 자산과의 호환을 위한 기반 스킬 분류입니다. 새 사양에서는 역할/발동 타입을 우선 사용합니다.")]
[SerializeField] private SkillBaseType allowedSkillTypes = SkillBaseType.All;
[Tooltip("함께 장착할 수 없는 젬 분류")]
[Tooltip("함께 장착할 수 없는 젬 효과 분류")]
[SerializeField] private SkillGemCategory[] incompatibleCategories = Array.Empty<SkillGemCategory>();
[Tooltip("함께 장착할 수 없는 특정 젬")]
[SerializeField] private List<SkillGemData> incompatibleGems = new();
@@ -107,6 +113,8 @@ namespace Colosseum.Skills
public string Description => description;
public Sprite Icon => icon;
public SkillGemCategory Category => category;
public SkillRoleType AllowedSkillRoles => allowedSkillRoles;
public SkillActivationType AllowedSkillActivationTypes => allowedSkillActivationTypes;
public SkillBaseType AllowedSkillTypes => allowedSkillTypes;
public float ManaCostMultiplier => manaCostMultiplier;
public float CooldownMultiplier => cooldownMultiplier;
@@ -131,6 +139,15 @@ namespace Colosseum.Skills
if (skill == null)
return false;
bool usesRoleRestriction = allowedSkillRoles != SkillRoleType.None && allowedSkillRoles != SkillRoleType.All;
bool usesActivationRestriction = allowedSkillActivationTypes != SkillActivationType.None &&
allowedSkillActivationTypes != SkillActivationType.All;
if (usesRoleRestriction || usesActivationRestriction)
{
return skill.MatchesClassification(allowedSkillRoles, allowedSkillActivationTypes);
}
if (allowedSkillTypes == SkillBaseType.None || allowedSkillTypes == SkillBaseType.All)
return true;

View File

@@ -425,7 +425,11 @@ namespace Colosseum.Skills
if (!gem.CanAttachToSkill(baseSkill))
{
reason = $"기반 스킬 분류 제약을 만족하지 않습니다. Skill={baseSkill.BaseTypes}, Allowed={gem.AllowedSkillTypes}";
string skillClassification = SkillClassificationUtility.GetSkillClassificationLabel(baseSkill);
string allowedClassification =
$"{SkillClassificationUtility.GetAllowedRoleSummary(gem.AllowedSkillRoles)}/" +
$"{SkillClassificationUtility.GetAllowedActivationSummary(gem.AllowedSkillActivationTypes)}";
reason = $"장착 가능한 스킬 조합이 아닙니다. Skill={skillClassification}, Allowed={allowedClassification}";
return false;
}
@@ -476,7 +480,9 @@ namespace Colosseum.Skills
if (gem.IsCategoryIncompatible(otherGem.Category) || otherGem.IsCategoryIncompatible(gem.Category))
{
reason = $"{gem.Category} / {otherGem.Category} 분류 조합은 허용되지 않습니다.";
reason =
$"{SkillClassificationUtility.GetGemCategoryLabel(gem.Category)} / " +
$"{SkillClassificationUtility.GetGemCategoryLabel(otherGem.Category)} 효과 분류 조합은 허용되지 않습니다.";
return false;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ca98554fbe3918040881b21c34fbb373

View File

@@ -283,9 +283,11 @@ namespace Colosseum.UI
SkillData skill = playerSkillInput.GetSkill(mappedSlotIndex);
if (skill == null) continue;
SkillLoadoutEntry loadoutEntry = playerSkillInput.GetSkillLoadout(mappedSlotIndex);
float remainingCooldown = playerSkillInput.GetRemainingCooldown(mappedSlotIndex);
float totalCooldown = skill.Cooldown;
bool hasEnoughMana = networkController == null || networkController.Mana >= skill.ManaCost;
float totalCooldown = loadoutEntry != null ? loadoutEntry.GetResolvedCooldown() : skill.Cooldown;
float requiredMana = loadoutEntry != null ? loadoutEntry.GetResolvedManaCost() : skill.ManaCost;
bool hasEnoughMana = networkController == null || networkController.Mana >= requiredMana;
if (shouldLog && remainingCooldown > 0f)
{