133 lines
6.6 KiB
C#
133 lines
6.6 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
||
// CombatAnimMapping.cs — 클래스 ↔ 전투 애니메이션 세트 매핑 데이터 (ScriptableObject)
|
||
//
|
||
// PD 지시 #760 (2026-09-06) 1단계 산출물 · **스테이징**.
|
||
// 2단계에 PM 지시로 Assets/WL/Combat/ 로 옮기고 에셋(CombatAnimMapping.asset)을 만든다.
|
||
//
|
||
// 왜 ScriptableObject 인가 (C45 하드코딩 금지):
|
||
// "어떤 클래스가 어떤 Shinabro 세트를 쓰는가" · "이벤트를 몇 초에 심는가" 는
|
||
// 설계 판단이지 코드 상수가 아니다. 세트 교체·타이밍 보정이 코드 재컴파일 없이
|
||
// 되어야 라이브에서 감당된다. 값의 SOT 는 이 에셋 하나다.
|
||
//
|
||
// 왜 클립 GUID 가 아니라 에셋 경로인가:
|
||
// 에디터 전용 셋업 스크립트(WL_CombatAnimSetup)만 이 데이터를 읽는다.
|
||
// 런타임은 이 에셋을 참조하지 않는다(컨트롤러가 이미 클립을 물고 있다).
|
||
// 경로는 사람이 읽고 검증할 수 있고, 이동 시 즉시 에러로 드러난다.
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
|
||
namespace WL.Combat
|
||
{
|
||
/// <summary>애니메이션 이벤트 1개. 시간은 **클립 로컬 초**(정규화 아님 — Unity AnimationEvent 와 같은 단위).</summary>
|
||
[Serializable]
|
||
public sealed class AnimEventSpec
|
||
{
|
||
[Tooltip("함수명. Actor 계열의 public 메서드여야 한다: Projectile / ShowEffect / LookAtTarget / Move / Hit / SkillEvent / FootL / FootR")]
|
||
public string functionName = "Projectile";
|
||
|
||
[Tooltip("클립 시작 기준 초. 0 이상 · 클립 길이 미만.")]
|
||
public float time;
|
||
|
||
[Tooltip("정규화 값으로 지정하고 싶을 때 켠다. 켜면 time 을 무시하고 normalizedTime × 클립길이 로 계산한다.")]
|
||
public bool useNormalized;
|
||
|
||
[Range(0f, 1f)] public float normalizedTime;
|
||
|
||
[Tooltip("stringParameter. {CID} 는 대상 클래스 ID 로 치환된다. 예: Projectile_{CID} · Effect_Slash_{CID}_1")]
|
||
public string stringParameter = "";
|
||
|
||
public int intParameter;
|
||
public float floatParameter;
|
||
|
||
/// <summary>{CID} 치환. 매핑 데이터를 클래스마다 복사하지 않기 위한 최소한의 템플릿이다.</summary>
|
||
public string ResolveString(int classId)
|
||
{
|
||
return string.IsNullOrEmpty(stringParameter)
|
||
? string.Empty
|
||
: stringParameter.Replace("{CID}", classId.ToString());
|
||
}
|
||
|
||
public float ResolveTime(float clipLength)
|
||
{
|
||
return useNormalized ? normalizedTime * clipLength : time;
|
||
}
|
||
}
|
||
|
||
/// <summary>한 컨트롤러 상태에 물릴 클립 1개와 그 클립에 심을 이벤트들.</summary>
|
||
[Serializable]
|
||
public sealed class StateClipSpec
|
||
{
|
||
[Tooltip("AnimatorController 의 상태 이름. 실측 규약: attack / Attack2 / Attack3 / Bow_Attack2 / Sword&Shield_Attack2 …")]
|
||
public string stateName = "attack";
|
||
|
||
[Tooltip("소스 FBX 에셋 경로 (Assets/ 부터). 예: Assets/Res_Addr/Animations/Animation/Paladin/attack.FBX")]
|
||
public string sourceFbxPath = "";
|
||
|
||
[Tooltip("그 FBX 안에서 만들/쓸 서브클립 이름. {CID} 치환 가능. " +
|
||
"예: Sword&Shield_Attack1_{CID}. 비우면 sourceClipName 을 그대로 쓴다(이벤트 편집 없음).")]
|
||
public string subClipName = "";
|
||
|
||
[Tooltip("서브클립을 뜰 원본 클립 이름(임포터의 기존 clipAnimations 중 하나). " +
|
||
"비우면 FBX 의 첫 클립을 쓴다.")]
|
||
public string sourceClipName = "";
|
||
|
||
[Tooltip("이 상태로 들어오는 전이의 exitTime 을 이 값으로 맞춘다. 0 이면 건드리지 않는다. " +
|
||
"실측: 힘민지 기존 콤보 구간 0.70~0.84 · 구 WL 규칙 0.78")]
|
||
[Range(0f, 1f)] public float incomingExitTime;
|
||
|
||
[Tooltip("새로 만들 상태인가. true 면 컨트롤러에 상태를 추가하고 직전 상태에서 전이를 잇는다.")]
|
||
public bool createIfMissing;
|
||
|
||
public List<AnimEventSpec> events = new List<AnimEventSpec>();
|
||
}
|
||
|
||
/// <summary>클래스 1개의 전투 애니메이션 매핑.</summary>
|
||
[Serializable]
|
||
public sealed class ClassCombatAnimSpec
|
||
{
|
||
[Tooltip("ClassConfig.json 의 n_ClassID")]
|
||
public int classId;
|
||
|
||
[Tooltip("사람이 읽기 위한 메모 (예: OneHand → 01_Sword&Shield)")]
|
||
public string note = "";
|
||
|
||
[Tooltip("적용 대상 컨트롤러 경로. 비우면 ClassConfig 의 s_AnimationController 를 읽는다(권장 — SOT 이중화 방지).")]
|
||
public string controllerPathOverride = "";
|
||
|
||
[Tooltip("끄면 이 클래스는 건드리지 않는다. 이미 WL 과 같은 세트인 클래스는 꺼 둔다.")]
|
||
public bool enabled = true;
|
||
|
||
public List<StateClipSpec> states = new List<StateClipSpec>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 전 클래스 전투 애니메이션 매핑 SOT.
|
||
/// 에디터 셋업 스크립트(WL_CombatAnimSetup)만 읽는다 — 런타임 참조 없음.
|
||
/// </summary>
|
||
[CreateAssetMenu(fileName = "CombatAnimMapping", menuName = "WL/Combat Anim Mapping", order = 310)]
|
||
public sealed class CombatAnimMapping : ScriptableObject
|
||
{
|
||
[Tooltip("이 매핑이 만들어진 근거 문서 (추적성 · P16)")]
|
||
[TextArea(2, 4)]
|
||
public string source = "PD 지시 #760 · AgentScripts/staging/WL_Combat2/MAPPING.md";
|
||
|
||
[Tooltip("컨트롤러 백업을 남길 폴더. 비우면 백업하지 않는다(권장하지 않음).")]
|
||
public string backupRoot = "공유/개발팀_백업/WL/Animators_{yyyyMMdd_HHmm}";
|
||
|
||
[Tooltip("모든 전이 블렌드 시간(정규화). 실측: 힘민지 전 컨트롤러 0.25 로 통일돼 있다.")]
|
||
public float transitionDuration = 0.25f;
|
||
|
||
public List<ClassCombatAnimSpec> classes = new List<ClassCombatAnimSpec>();
|
||
|
||
public ClassCombatAnimSpec Find(int classId)
|
||
{
|
||
for (int i = 0; i < classes.Count; i++)
|
||
if (classes[i] != null && classes[i].classId == classId) return classes[i];
|
||
return null;
|
||
}
|
||
}
|
||
}
|