Project_WL/Assets/WL/Combat/CombatAnimMapping.cs

133 lines
6.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ─────────────────────────────────────────────────────────────────────────────
// 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;
}
}
}