178 lines
8.2 KiB
C#
178 lines
8.2 KiB
C#
|
|
// WL787_Anim.cs — PD 지시 #787 대쉬 상태 추가 (에디트 모드 · UnityEditor.Animations API)
|
|||
|
|
// unity command run_script --file AgentScripts/WL787_Anim.cs --entry WL787_Anim.Dump
|
|||
|
|
// unity command run_script --file AgentScripts/WL787_Anim.cs --entry WL787_Anim.Apply
|
|||
|
|
// unity command run_script --file AgentScripts/WL787_Anim.cs --entry WL787_Anim.Remove (롤백)
|
|||
|
|
//
|
|||
|
|
// 하는 일: 근접 4컨트롤러(ClassConfig 의 OneHand·Shield)의 Base Layer 에
|
|||
|
|
// · 상태 `dash` 를 추가하고 Stander@Chase_Start 의 클립을 물린다
|
|||
|
|
// · `dash -> idle` 전이(hasExitTime 1.0 · 0.15s)를 하나 건다 — 대쉬가 그냥 끝났을 때 자세가 굳지 않게
|
|||
|
|
// 진입은 전이가 아니라 DashDriver 의 Animator.CrossFadeInFixedTime 이 한다.
|
|||
|
|
// → Any State 전이를 만들지 않으므로 기존 상태 전이망을 전혀 건드리지 않는다(회귀 위험 최소).
|
|||
|
|
//
|
|||
|
|
// 🔴 이름 충돌 주의: 이 레포에는 전역 네임스페이스에 MonoBehaviour `AnimatorController` 가 있다.
|
|||
|
|
// 반드시 UnityEditor.Animations.AnimatorController 로 **완전 한정**해서 쓴다.
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using System.Linq;
|
|||
|
|
using System.Text;
|
|||
|
|
using UnityEditor;
|
|||
|
|
using UnityEngine;
|
|||
|
|
|
|||
|
|
public static class WL787_Anim
|
|||
|
|
{
|
|||
|
|
const string ClassTable = "Assets/ResWork/Table/Export/ClassConfig.json";
|
|||
|
|
const string ChaseFbx = "Assets/Res_Addr/Animations/Animation/Common_PlatformAnim/Stander@Chase_Start.FBX";
|
|||
|
|
const string ChaseClip = "Chase_Start";
|
|||
|
|
const string DashState = "dash";
|
|||
|
|
const string IdleState = "idle";
|
|||
|
|
|
|||
|
|
static string[] TargetControllers()
|
|||
|
|
{
|
|||
|
|
var txt = System.IO.File.ReadAllText(ClassTable).TrimStart('');
|
|||
|
|
var list = new List<string>();
|
|||
|
|
foreach (System.Text.RegularExpressions.Match m in
|
|||
|
|
System.Text.RegularExpressions.Regex.Matches(txt, "\\{[^{}]*\\}"))
|
|||
|
|
{
|
|||
|
|
string row = m.Value;
|
|||
|
|
var bt = System.Text.RegularExpressions.Regex.Match(row, "\"e_BattleType\":\\s*\"([^\"]*)\"");
|
|||
|
|
var ac = System.Text.RegularExpressions.Regex.Match(row, "\"s_AnimationController\":\\s*\"([^\"]*)\"");
|
|||
|
|
if (!bt.Success || !ac.Success) continue;
|
|||
|
|
if (bt.Groups[1].Value != "OneHand" && bt.Groups[1].Value != "Shield") continue;
|
|||
|
|
if (!list.Contains(ac.Groups[1].Value)) list.Add(ac.Groups[1].Value);
|
|||
|
|
}
|
|||
|
|
return list.ToArray();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static AnimationClip LoadChaseClip()
|
|||
|
|
{
|
|||
|
|
foreach (var o in AssetDatabase.LoadAllAssetsAtPath(ChaseFbx))
|
|||
|
|
{
|
|||
|
|
var c = o as AnimationClip;
|
|||
|
|
if (c != null && c.name == ChaseClip) return c;
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static UnityEditor.Animations.AnimatorState Find(
|
|||
|
|
UnityEditor.Animations.AnimatorController c, string name)
|
|||
|
|
{
|
|||
|
|
foreach (var layer in c.layers)
|
|||
|
|
foreach (var cs in layer.stateMachine.states)
|
|||
|
|
if (cs.state != null && cs.state.name == name) return cs.state;
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public static object Dump()
|
|||
|
|
{
|
|||
|
|
var sb = new StringBuilder();
|
|||
|
|
var clip = LoadChaseClip();
|
|||
|
|
sb.AppendLine("clip " + ChaseClip + " = " + (clip != null ? clip.name + " len=" + clip.length.ToString("0.000") + "s" : "(NOT FOUND)"));
|
|||
|
|
foreach (var path in TargetControllers())
|
|||
|
|
{
|
|||
|
|
var c = AssetDatabase.LoadAssetAtPath<UnityEditor.Animations.AnimatorController>(path);
|
|||
|
|
if (c == null) { sb.AppendLine("MISSING " + path); continue; }
|
|||
|
|
var dash = Find(c, DashState);
|
|||
|
|
sb.AppendLine("## " + System.IO.Path.GetFileName(path) +
|
|||
|
|
" states=" + c.layers[0].stateMachine.states.Length +
|
|||
|
|
" dash=" + (dash == null ? "(없음)" :
|
|||
|
|
(dash.motion != null ? dash.motion.name : "(motion null)") +
|
|||
|
|
" out=" + dash.transitions.Length));
|
|||
|
|
}
|
|||
|
|
return sb.ToString();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public static object Apply()
|
|||
|
|
{
|
|||
|
|
if (EditorApplication.isPlaying) return "ABORT: Play 중";
|
|||
|
|
if (EditorApplication.isCompiling) return "ABORT: 컴파일 중";
|
|||
|
|
|
|||
|
|
var clip = LoadChaseClip();
|
|||
|
|
if (clip == null) return "ABORT: 클립을 못 찾았다 — " + ChaseFbx + " / " + ChaseClip;
|
|||
|
|
|
|||
|
|
var sb = new StringBuilder("clip=" + clip.name + " len=" + clip.length.ToString("0.000") + "s\n");
|
|||
|
|
int added = 0, wired = 0;
|
|||
|
|
|
|||
|
|
foreach (var path in TargetControllers())
|
|||
|
|
{
|
|||
|
|
var c = AssetDatabase.LoadAssetAtPath<UnityEditor.Animations.AnimatorController>(path);
|
|||
|
|
if (c == null) { sb.AppendLine("MISSING " + path); continue; }
|
|||
|
|
|
|||
|
|
var sm = c.layers[0].stateMachine;
|
|||
|
|
var dash = Find(c, DashState);
|
|||
|
|
if (dash == null)
|
|||
|
|
{
|
|||
|
|
// idle 아래쪽에 눈에 띄게 놓는다(에디터에서 사람이 찾기 쉽게).
|
|||
|
|
var idleForPos = Find(c, IdleState);
|
|||
|
|
Vector3 pos = new Vector3(-260f, 260f, 0f);
|
|||
|
|
foreach (var cs in sm.states)
|
|||
|
|
if (idleForPos != null && cs.state == idleForPos) pos = cs.position + new Vector3(0f, 130f, 0f);
|
|||
|
|
dash = sm.AddState(DashState, pos);
|
|||
|
|
added++;
|
|||
|
|
sb.AppendLine(" + 상태 추가 dash (" + System.IO.Path.GetFileName(path) + ")");
|
|||
|
|
}
|
|||
|
|
dash.motion = clip;
|
|||
|
|
dash.speed = 1f;
|
|||
|
|
dash.writeDefaultValues = false;
|
|||
|
|
|
|||
|
|
// dash -> idle : 대쉬가 공격으로 이어지지 않고 그냥 끝났을 때의 안전 출구.
|
|||
|
|
var idle = Find(c, IdleState);
|
|||
|
|
if (idle != null && !dash.transitions.Any(t => t.destinationState == idle))
|
|||
|
|
{
|
|||
|
|
var tr = dash.AddTransition(idle);
|
|||
|
|
tr.hasExitTime = true;
|
|||
|
|
tr.exitTime = 1f;
|
|||
|
|
tr.hasFixedDuration = true;
|
|||
|
|
tr.duration = 0.15f;
|
|||
|
|
tr.offset = 0f;
|
|||
|
|
wired++;
|
|||
|
|
sb.AppendLine(" + 전이 dash -> idle exit=1.00 dur=0.15");
|
|||
|
|
}
|
|||
|
|
else if (idle == null) sb.AppendLine(" [WARN] idle 상태를 못 찾음 — 출구 전이 생략 " + path);
|
|||
|
|
|
|||
|
|
EditorUtility.SetDirty(c);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
AssetDatabase.SaveAssets();
|
|||
|
|
AssetDatabase.Refresh();
|
|||
|
|
sb.AppendLine("결과: 상태추가=" + added + " 전이추가=" + wired);
|
|||
|
|
return sb.ToString();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// WLCombatMotionSettings.asset 을 다시 직렬화해 #787 신규 필드를 YAML 에 실제로 적어 둔다.
|
|||
|
|
/// (없어도 C# 필드 초기값으로 동작하지만, PD·PM 이 파일에서 값을 읽고 고칠 수 있어야 한다 · C45)
|
|||
|
|
/// </summary>
|
|||
|
|
public static object TouchSettings()
|
|||
|
|
{
|
|||
|
|
const string p = "Assets/WL/Settings/Resources/WL/WLCombatMotionSettings.asset";
|
|||
|
|
var s = AssetDatabase.LoadAssetAtPath<WL.Combat.WLCombatMotionSettings>(p);
|
|||
|
|
if (s == null) return "ABORT: " + p + " 없음";
|
|||
|
|
EditorUtility.SetDirty(s);
|
|||
|
|
AssetDatabase.SaveAssets();
|
|||
|
|
AssetDatabase.Refresh();
|
|||
|
|
return "OK dashEnabled=" + s.dashEnabled + " min=" + s.dashMinDistance + " max=" + s.dashMaxDistance +
|
|||
|
|
" stop=" + s.dashStopDistance + " speed=" + s.dashMaxSpeed + " maxSec=" + s.dashMaxSeconds +
|
|||
|
|
" look=" + s.dashLookAtTarget + " cool=" + s.dashCooldownSeconds +
|
|||
|
|
" state=" + s.dashStateName + " clip=" + s.dashClipName;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>롤백 — dash 상태를 지운다(전이도 함께 사라진다).</summary>
|
|||
|
|
public static object Remove()
|
|||
|
|
{
|
|||
|
|
if (EditorApplication.isPlaying) return "ABORT: Play 중";
|
|||
|
|
var sb = new StringBuilder();
|
|||
|
|
foreach (var path in TargetControllers())
|
|||
|
|
{
|
|||
|
|
var c = AssetDatabase.LoadAssetAtPath<UnityEditor.Animations.AnimatorController>(path);
|
|||
|
|
if (c == null) continue;
|
|||
|
|
var dash = Find(c, DashState);
|
|||
|
|
if (dash == null) { sb.AppendLine(" (없음) " + System.IO.Path.GetFileName(path)); continue; }
|
|||
|
|
c.layers[0].stateMachine.RemoveState(dash);
|
|||
|
|
EditorUtility.SetDirty(c);
|
|||
|
|
sb.AppendLine(" - 삭제 dash " + System.IO.Path.GetFileName(path));
|
|||
|
|
}
|
|||
|
|
AssetDatabase.SaveAssets();
|
|||
|
|
AssetDatabase.Refresh();
|
|||
|
|
return sb.ToString();
|
|||
|
|
}
|
|||
|
|
}
|