380 lines
20 KiB
C#
380 lines
20 KiB
C#
|
|
// WL792_Apply.cs — #792 이펙트 프리팹 생성 · 기하 베이크 · 설정 적용 (에디터 · Edit 모드)
|
|||
|
|
//
|
|||
|
|
// unity command run_script --file AgentScripts/WL792_Apply.cs --entry WL792_Apply.BuildPrefabs
|
|||
|
|
// unity command run_script --file AgentScripts/WL792_Apply.cs --entry WL792_Apply.BakeGeometry
|
|||
|
|
// unity command run_script --file AgentScripts/WL792_Apply.cs --entry WL792_Apply.Configure
|
|||
|
|
// unity command run_script --file AgentScripts/WL792_Apply.cs --entry WL792_Apply.DumpSettings
|
|||
|
|
// unity command run_script --file AgentScripts/WL792_Apply.cs --entry WL792_Apply.BakeCalibrations (Play 뒤)
|
|||
|
|
// unity command run_script --file AgentScripts/WL792_Apply.cs --entry WL792_Apply.Rollback (C8 원복)
|
|||
|
|
//
|
|||
|
|
// 설계 메모
|
|||
|
|
// · 새 이펙트는 `Assets/Res_Addr/Effect/` 아래에 **프리팹 배리언트**로 만든다.
|
|||
|
|
// 그 폴더는 Addressables `Effect` 그룹의 **폴더 엔트리**라 프리팹을 넣기만 하면
|
|||
|
|
// `Assets/Res_Addr/Effect/{name}.prefab` 주소로 자동 등록된다 — 그룹 에셋을 손대지 않는다.
|
|||
|
|
// 따라서 기존 InGameInfo 풀(dic_str_Effect + TurnOff_GO + Show_EffectEx)을 그대로 쓴다(C11).
|
|||
|
|
// · 배리언트라 PD 가 NamuFX 원본(recolor)을 고치면 그대로 따라온다.
|
|||
|
|
// · 루트를 **비활성**으로 저장한다 — Get_Effect 는 `!activeInHierarchy` 인 인스턴스를 재사용하므로
|
|||
|
|
// 활성 상태로 저장하면 풀이 재사용되지 않고 스폰마다 새로 로드된다(누수).
|
|||
|
|
// · 씬을 더럽히지 않는다 — 프리뷰 씬(EditorSceneManager.NewPreviewScene)에서만 작업한다.
|
|||
|
|
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using System.Text;
|
|||
|
|
using UnityEditor;
|
|||
|
|
using UnityEditor.SceneManagement;
|
|||
|
|
using UnityEngine;
|
|||
|
|
using WL.Combat;
|
|||
|
|
|
|||
|
|
public static class WL792_Apply
|
|||
|
|
{
|
|||
|
|
const string Out = "Screenshots_WL/combat6/wl792_apply.txt";
|
|||
|
|
|
|||
|
|
const string SrcSwing = "Assets/NamuFX/Simple Stylized Slash vol2/Prefabs/Slash_B_recolor_1 Variant.prefab";
|
|||
|
|
const string SrcStab = "Assets/ResWork/EricWang/Game VFX - Cartoon Skill Collection Vol2/Prefabs/FX_Blue Stab.prefab";
|
|||
|
|
|
|||
|
|
const string DstSwing = "Assets/Res_Addr/Effect/Effect_WLSwingArc.prefab";
|
|||
|
|
const string DstStab = "Assets/Res_Addr/Effect/Effect_WLStab.prefab";
|
|||
|
|
|
|||
|
|
const string SettingsPath = "Assets/WL/Settings/Resources/WL/SlashTrailSettings.asset";
|
|||
|
|
|
|||
|
|
// 찌르기에서 남길 이미터 — 실측(2026-09-07) 전체 10개 · 고유 머티리얼 9개는
|
|||
|
|
// 모바일 예산(스윙당 추가 드로우 ≤ 3)을 크게 넘는다. 형태를 읽는 데 필요한 셋만 남긴다.
|
|||
|
|
// lb(뒤로 흐르는 스피드 라인) · blue_flash(찌르기 스트리크) · head(선단 플래시)
|
|||
|
|
static readonly string[] StabKeep = { "lb", "blue_flash", "head" };
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|||
|
|
public static void BuildPrefabs()
|
|||
|
|
{
|
|||
|
|
var sb = new StringBuilder();
|
|||
|
|
sb.AppendLine("# WL #792 BuildPrefabs " + System.DateTime.Now.ToString("HH:mm:ss"));
|
|||
|
|
|
|||
|
|
Build(sb, SrcSwing, DstSwing, "Effect_WLSwingArc", false, 1.0f);
|
|||
|
|
Build(sb, SrcStab, DstStab, "Effect_WLStab", true, 0.9f);
|
|||
|
|
|
|||
|
|
AssetDatabase.SaveAssets();
|
|||
|
|
AssetDatabase.Refresh();
|
|||
|
|
Write(sb);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void Build(StringBuilder sb, string srcPath, string dstPath, string name, bool forceOffTime, float offTime)
|
|||
|
|
{
|
|||
|
|
var src = AssetDatabase.LoadAssetAtPath<GameObject>(srcPath);
|
|||
|
|
if (src == null) { sb.AppendLine("!! 원본 없음 " + srcPath); return; }
|
|||
|
|
|
|||
|
|
var scene = EditorSceneManager.NewPreviewScene();
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var inst = (GameObject)PrefabUtility.InstantiatePrefab(src, scene);
|
|||
|
|
inst.name = name;
|
|||
|
|
inst.transform.localPosition = Vector3.zero;
|
|||
|
|
inst.transform.localRotation = Quaternion.identity;
|
|||
|
|
inst.transform.localScale = Vector3.one;
|
|||
|
|
|
|||
|
|
var off = inst.GetComponent<TurnOff_GO>();
|
|||
|
|
if (off == null) off = inst.AddComponent<TurnOff_GO>();
|
|||
|
|
off.NoUseParticleTime = forceOffTime;
|
|||
|
|
off.OffTime = offTime;
|
|||
|
|
|
|||
|
|
inst.SetActive(false);
|
|||
|
|
|
|||
|
|
var saved = PrefabUtility.SaveAsPrefabAsset(inst, dstPath);
|
|||
|
|
sb.AppendLine(string.Format("생성 {0} ← {1} · TurnOff_GO(NoUseParticleTime={2} OffTime={3}) · 루트 비활성 · 배리언트={4}",
|
|||
|
|
dstPath, System.IO.Path.GetFileName(srcPath), forceOffTime, offTime,
|
|||
|
|
saved != null && PrefabUtility.GetPrefabAssetType(saved) == PrefabAssetType.Variant));
|
|||
|
|
Object.DestroyImmediate(inst);
|
|||
|
|
}
|
|||
|
|
catch (System.Exception e) { sb.AppendLine("!! " + name + " 실패: " + e); }
|
|||
|
|
finally { EditorSceneManager.ClosePreviewScene(scene); }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|||
|
|
public static void BakeGeometry()
|
|||
|
|
{
|
|||
|
|
var sb = new StringBuilder();
|
|||
|
|
sb.AppendLine("# WL #792 BakeGeometry " + System.DateTime.Now.ToString("HH:mm:ss"));
|
|||
|
|
|
|||
|
|
var so = AssetDatabase.LoadAssetAtPath<SlashTrailSettings>(SettingsPath);
|
|||
|
|
if (so == null) { sb.AppendLine("!! 설정 에셋 없음 " + SettingsPath); Write(sb); return; }
|
|||
|
|
|
|||
|
|
BakeOne(sb, so, DstSwing, "Effect_WLSwingArc", SwingKind.Swing, null);
|
|||
|
|
BakeOne(sb, so, DstStab, "Effect_WLStab", SwingKind.Stab, StabKeep);
|
|||
|
|
|
|||
|
|
EditorUtility.SetDirty(so);
|
|||
|
|
AssetDatabase.SaveAssets();
|
|||
|
|
Write(sb);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void BakeOne(StringBuilder sb, SlashTrailSettings so, string path, string name, SwingKind kind, string[] keep)
|
|||
|
|
{
|
|||
|
|
var asset = AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
|||
|
|
if (asset == null) { sb.AppendLine("!! 없음 " + path); return; }
|
|||
|
|
|
|||
|
|
GameObject root = null;
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
root = PrefabUtility.LoadPrefabContents(path);
|
|||
|
|
|
|||
|
|
var g = so.FindGeometry(name);
|
|||
|
|
if (g == null) { g = new SlashEffectGeometry { prefabName = name }; so.effectGeometry.Add(g); }
|
|||
|
|
g.kind = kind;
|
|||
|
|
g.bakedAt = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
|||
|
|
|
|||
|
|
if (kind == SwingKind.Swing)
|
|||
|
|
{
|
|||
|
|
var f = SlashArcMeasure.MeasureCrescent(root, null, g.flipSweep);
|
|||
|
|
g.arcValid = f.valid;
|
|||
|
|
if (f.valid)
|
|||
|
|
{
|
|||
|
|
g.emitter = f.emitter;
|
|||
|
|
g.arcNormal = f.normal;
|
|||
|
|
g.arcCenter = f.center;
|
|||
|
|
g.arcBisector = f.bisector;
|
|||
|
|
g.arcStartDir = f.startDir;
|
|||
|
|
g.arcRadiusOuter = f.radiusOuter;
|
|||
|
|
g.arcRadiusInner = f.radiusInner;
|
|||
|
|
g.arcSweepDeg = f.sweepDeg;
|
|||
|
|
}
|
|||
|
|
sb.AppendLine(string.Format("[호] {0} valid={1} emitter='{2}' n={3} c={4} bis={5} rOut={6:F4} rIn={7:F4} sweep={8:F2}°",
|
|||
|
|
name, f.valid, f.emitter, V(f.normal), V(f.center), V(f.bisector), f.radiusOuter, f.radiusInner, f.sweepDeg));
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
var s = SlashArcMeasure.MeasureStab(root, g.flipAxis, keep);
|
|||
|
|
g.stabValid = s.valid;
|
|||
|
|
if (s.valid)
|
|||
|
|
{
|
|||
|
|
g.emitter = s.note;
|
|||
|
|
g.stabAxis = s.axis;
|
|||
|
|
g.stabStart = s.start;
|
|||
|
|
g.stabLength = s.length;
|
|||
|
|
g.stabPlaneNormal = s.planeNormal;
|
|||
|
|
}
|
|||
|
|
sb.AppendLine(string.Format("[찌르기] {0} valid={1} note='{2}' axis={3} start={4} len={5:F4} planeN={6}",
|
|||
|
|
name, s.valid, s.note, V(s.axis), V(s.start), s.length, V(s.planeNormal)));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (System.Exception e) { sb.AppendLine("!! " + name + " 베이크 실패: " + e); }
|
|||
|
|
finally { if (root != null) PrefabUtility.UnloadPrefabContents(root); }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|||
|
|
public static void Configure()
|
|||
|
|
{
|
|||
|
|
var sb = new StringBuilder();
|
|||
|
|
sb.AppendLine("# WL #792 Configure " + System.DateTime.Now.ToString("HH:mm:ss"));
|
|||
|
|
|
|||
|
|
var so = AssetDatabase.LoadAssetAtPath<SlashTrailSettings>(SettingsPath);
|
|||
|
|
if (so == null) { sb.AppendLine("!! 설정 에셋 없음"); Write(sb); return; }
|
|||
|
|
|
|||
|
|
sb.AppendLine(string.Format("변경 전 — useMappedEffects={0} burstMode={1} drawRibbon={2} arcFollowRotation={3}",
|
|||
|
|
so.useMappedEffects, so.burstMode, so.drawRibbon, so.arcFollowRotation));
|
|||
|
|
|
|||
|
|
so.useMappedEffects = true;
|
|||
|
|
so.swingArcEffect = "Effect_WLSwingArc";
|
|||
|
|
so.stabEffect = "Effect_WLStab";
|
|||
|
|
so.stabKeepEmitterNames = StabKeep;
|
|||
|
|
|
|||
|
|
// 리본 OFF — PD #792 는 NamuFX 호를 쓰므로 리본이 겹치면 안 된다.
|
|||
|
|
// 궤적 샘플링은 계속된다(평면·반지름 계산에 필요) · BladeTrail/램프 텍스처 자산은 보존한다(C8).
|
|||
|
|
so.drawRibbon = false;
|
|||
|
|
// 레거시 버스트 경로는 꺼 둔다(#792 경로가 대신한다).
|
|||
|
|
so.burstMode = BurstMode.None;
|
|||
|
|
|
|||
|
|
so.arcFollowPlayer = true;
|
|||
|
|
so.mappedFollowRotation = true;
|
|||
|
|
so.arcFollowSeconds = 0f;
|
|||
|
|
|
|||
|
|
so.arcRadiusFit = ArcRadiusFit.Outer;
|
|||
|
|
so.arcSizeScale = 1f;
|
|||
|
|
so.arcPlaneOffset = 0f;
|
|||
|
|
so.arcRadialOffset = 0f;
|
|||
|
|
so.arcLifetimeSeconds = 0f; // 프리팹 파티클 duration(1.0 s) 을 그대로 쓴다
|
|||
|
|
|
|||
|
|
so.stabAnchor = StabAnchor.Start;
|
|||
|
|
so.stabSizeScale = 1f;
|
|||
|
|
so.stabFaceCamera = true;
|
|||
|
|
so.stabLifetimeSeconds = 0.9f;
|
|||
|
|
|
|||
|
|
// 분류 임계값 — 10101·10501 실측(2026-09-07) 기준.
|
|||
|
|
// 창 안 칼날 누적 회전량: Attack1 366~434° · Attack2 385~424° · Attack3 164°(짧은 창일 땐 67°)
|
|||
|
|
// 150 은 그 간격 한가운데다. 칼끝/손잡이 경로 비 3.0 은 손목 스냅 스윙을 지켜 주는 보조 조건.
|
|||
|
|
so.swingMinSweepDegrees = 150f;
|
|||
|
|
so.swingMinTipHiltRatio = 3f;
|
|||
|
|
so.swingTrimSpeedRatio = 0.2f;
|
|||
|
|
so.stabMinStraightness = 0.86f;
|
|||
|
|
so.stabMinForwardDot = 0.55f;
|
|||
|
|
so.stabMinDistance = 0.18f;
|
|||
|
|
|
|||
|
|
so.verboseLog = true;
|
|||
|
|
|
|||
|
|
EditorUtility.SetDirty(so);
|
|||
|
|
AssetDatabase.SaveAssets();
|
|||
|
|
|
|||
|
|
sb.AppendLine(string.Format("변경 후 — useMappedEffects={0} swing='{1}' stab='{2}' drawRibbon={3} burstMode={4} keep=[{5}]",
|
|||
|
|
so.useMappedEffects, so.swingArcEffect, so.stabEffect, so.drawRibbon, so.burstMode, string.Join(",", StabKeep)));
|
|||
|
|
Write(sb);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|||
|
|
/// <summary>Play 중 런타임이 실측해 모아 둔 캘리브레이션을 SO 에 굽는다(첫 스윙부터 정확해진다).</summary>
|
|||
|
|
public static void BakeCalibrations()
|
|||
|
|
{
|
|||
|
|
var sb = new StringBuilder();
|
|||
|
|
sb.AppendLine("# WL #792 BakeCalibrations " + System.DateTime.Now.ToString("HH:mm:ss"));
|
|||
|
|
|
|||
|
|
var so = AssetDatabase.LoadAssetAtPath<SlashTrailSettings>(SettingsPath);
|
|||
|
|
if (so == null) { sb.AppendLine("!! 설정 에셋 없음"); Write(sb); return; }
|
|||
|
|
|
|||
|
|
var rt = WeaponTrailDriver.RuntimeCalibrations;
|
|||
|
|
sb.AppendLine("런타임 캐시 " + rt.Count + "행");
|
|||
|
|
int added = 0, updated = 0;
|
|||
|
|
foreach (var kv in rt)
|
|||
|
|
{
|
|||
|
|
var c = kv.Value;
|
|||
|
|
if (c == null) continue;
|
|||
|
|
var exist = so.FindCalibration(c.classId, c.clipName);
|
|||
|
|
// 이미 구워 둔 행보다 품질(궤적 길이 × 원피팅 정합도)이 높을 때만 갈아 끼운다.
|
|||
|
|
if (exist == null) { so.swingCalibrations.Add(Clone(c)); added++; }
|
|||
|
|
else if (c.quality > exist.quality) { Copy(c, exist); updated++; }
|
|||
|
|
else { sb.AppendLine(string.Format(" (유지) {0} {1} — 기존 q={2:F2} ≥ 새 q={3:F2}",
|
|||
|
|
c.classId, c.clipName, exist.quality, c.quality)); continue; }
|
|||
|
|
sb.AppendLine(string.Format(" {0,-6} {1,-24} {2,-5} sweep={3,6:F1}° tipR={4:F3} hiltR={5:F3} pivot={6} n={7} bis={8} | {9}",
|
|||
|
|
c.classId, c.clipName, c.kind, c.sweepDeg, c.tipRadius, c.hiltRadius,
|
|||
|
|
V(c.pivot), V(c.planeNormal), V(c.bisector), c.source));
|
|||
|
|
}
|
|||
|
|
sb.AppendLine(string.Format("추가 {0} · 갱신 {1} · 총 {2}행", added, updated, so.swingCalibrations.Count));
|
|||
|
|
|
|||
|
|
EditorUtility.SetDirty(so);
|
|||
|
|
AssetDatabase.SaveAssets();
|
|||
|
|
Write(sb);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static SwingCalibration Clone(SwingCalibration s)
|
|||
|
|
{
|
|||
|
|
var d = new SwingCalibration();
|
|||
|
|
Copy(s, d);
|
|||
|
|
d.classId = s.classId;
|
|||
|
|
d.clipName = s.clipName;
|
|||
|
|
return d;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void Copy(SwingCalibration s, SwingCalibration d)
|
|||
|
|
{
|
|||
|
|
d.kind = s.kind;
|
|||
|
|
d.planeNormal = s.planeNormal;
|
|||
|
|
d.pivot = s.pivot;
|
|||
|
|
d.bisector = s.bisector;
|
|||
|
|
d.startDir = s.startDir;
|
|||
|
|
d.tipRadius = s.tipRadius;
|
|||
|
|
d.hiltRadius = s.hiltRadius;
|
|||
|
|
d.sweepDeg = s.sweepDeg;
|
|||
|
|
d.stabAxis = s.stabAxis;
|
|||
|
|
d.stabStart = s.stabStart;
|
|||
|
|
d.stabLength = s.stabLength;
|
|||
|
|
d.quality = s.quality;
|
|||
|
|
d.source = s.source;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|||
|
|
public static void DumpSettings()
|
|||
|
|
{
|
|||
|
|
var sb = new StringBuilder();
|
|||
|
|
sb.AppendLine("# WL #792 DumpSettings (디스크 대조) " + System.DateTime.Now.ToString("HH:mm:ss"));
|
|||
|
|
|
|||
|
|
var so = AssetDatabase.LoadAssetAtPath<SlashTrailSettings>(SettingsPath);
|
|||
|
|
if (so == null) { sb.AppendLine("!! 없음"); Write(sb); return; }
|
|||
|
|
|
|||
|
|
sb.AppendLine(string.Format("useMappedEffects={0} swing='{1}' stab='{2}' drawRibbon={3} burstMode={4} burstTiming={5}",
|
|||
|
|
so.useMappedEffects, so.swingArcEffect, so.stabEffect, so.drawRibbon, so.burstMode, so.burstTiming));
|
|||
|
|
sb.AppendLine(string.Format("arcRadiusFit={0} arcSizeScale={1} arcPlaneOffset={2} arcRadialOffset={3} arcLifetime={4}",
|
|||
|
|
so.arcRadiusFit, so.arcSizeScale, so.arcPlaneOffset, so.arcRadialOffset, so.arcLifetimeSeconds));
|
|||
|
|
sb.AppendLine(string.Format("stabAnchor={0} stabSizeScale={1} stabFaceCamera={2} stabLifetime={3} keep=[{4}]",
|
|||
|
|
so.stabAnchor, so.stabSizeScale, so.stabFaceCamera, so.stabLifetimeSeconds,
|
|||
|
|
so.stabKeepEmitterNames != null ? string.Join(",", so.stabKeepEmitterNames) : ""));
|
|||
|
|
sb.AppendLine(string.Format("분류 — 칼날회전≥{0}° 또는 칼끝/손잡이≥{1} → 휘두르기 · straight≥{2} minDist={3}m → 찌르기 · 속도트림 {4}",
|
|||
|
|
so.swingMinSweepDegrees, so.swingMinTipHiltRatio, so.stabMinStraightness, so.stabMinDistance, so.swingTrimSpeedRatio));
|
|||
|
|
sb.AppendLine(string.Format("추종 — arcFollowPlayer={0} mappedFollowRotation={1} seconds={2}",
|
|||
|
|
so.arcFollowPlayer, so.mappedFollowRotation, so.arcFollowSeconds));
|
|||
|
|
|
|||
|
|
sb.AppendLine("── effectGeometry " + (so.effectGeometry != null ? so.effectGeometry.Count : 0) + "행");
|
|||
|
|
if (so.effectGeometry != null)
|
|||
|
|
foreach (var g in so.effectGeometry)
|
|||
|
|
sb.AppendLine(string.Format(" {0,-20} kind={1,-5} arcValid={2} n={3} c={4} bis={5} rOut={6:F4} rIn={7:F4} sweep={8:F1}° | stabValid={9} axis={10} start={11} len={12:F4} planeN={13} | {14}",
|
|||
|
|
g.prefabName, g.kind, g.arcValid, V(g.arcNormal), V(g.arcCenter), V(g.arcBisector),
|
|||
|
|
g.arcRadiusOuter, g.arcRadiusInner, g.arcSweepDeg,
|
|||
|
|
g.stabValid, V(g.stabAxis), V(g.stabStart), g.stabLength, V(g.stabPlaneNormal), g.bakedAt));
|
|||
|
|
|
|||
|
|
sb.AppendLine("── swingCalibrations " + (so.swingCalibrations != null ? so.swingCalibrations.Count : 0) + "행");
|
|||
|
|
if (so.swingCalibrations != null)
|
|||
|
|
foreach (var c in so.swingCalibrations)
|
|||
|
|
sb.AppendLine(string.Format(" {0,-6} {1,-24} {2,-5} sweep={3,6:F1}° tipR={4:F3} hiltR={5:F3} n={6} pivot={7} bis={8} stab(axis={9} len={10:F3})",
|
|||
|
|
c.classId, c.clipName, c.kind, c.sweepDeg, c.tipRadius, c.hiltRadius,
|
|||
|
|
V(c.planeNormal), V(c.pivot), V(c.bisector), V(c.stabAxis), c.stabLength));
|
|||
|
|
|
|||
|
|
sb.AppendLine("── 프리팹 존재 여부");
|
|||
|
|
foreach (var p in new[] { DstSwing, DstStab })
|
|||
|
|
{
|
|||
|
|
var a = AssetDatabase.LoadAssetAtPath<GameObject>(p);
|
|||
|
|
sb.AppendLine(string.Format(" {0} = {1}{2}", p, a != null ? "OK" : "없음",
|
|||
|
|
a != null ? " (activeSelf=" + a.activeSelf + ", TurnOff_GO=" + (a.GetComponent<TurnOff_GO>() != null) + ")" : ""));
|
|||
|
|
}
|
|||
|
|
Write(sb);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|||
|
|
/// <summary>
|
|||
|
|
/// 3타(Effect_Slash_{CID}_3)를 찌르기로 강제한다 — 찌르기 경로 검증 + PD 선택지 B.
|
|||
|
|
/// 실측상 10101·10501 의 세 클립은 모두 칼날 회전량 164~434° 로 휘두르기로 분류된다.
|
|||
|
|
/// PD 가 3타를 찌르기 연출로 원하면 이 SO 행 하나로 바꾼다 — 코드 변경 없음.
|
|||
|
|
/// </summary>
|
|||
|
|
public static void ForceStab() { SetStabRemap(true); }
|
|||
|
|
public static void UnforceStab() { SetStabRemap(false); }
|
|||
|
|
|
|||
|
|
static void SetStabRemap(bool on)
|
|||
|
|
{
|
|||
|
|
var sb = new StringBuilder();
|
|||
|
|
var so = AssetDatabase.LoadAssetAtPath<SlashTrailSettings>(SettingsPath);
|
|||
|
|
if (so == null) { sb.AppendLine("!! 없음"); Write(sb); return; }
|
|||
|
|
|
|||
|
|
so.effectRemaps.RemoveAll(r => r != null && r.sourcePrefab != null && r.sourcePrefab.EndsWith("_3"));
|
|||
|
|
if (on)
|
|||
|
|
{
|
|||
|
|
foreach (var cid in new[] { 10101, 10102, 10501, 10502 })
|
|||
|
|
so.effectRemaps.Add(new SlashEffectRemap
|
|||
|
|
{
|
|||
|
|
sourcePrefab = "Effect_Slash_" + cid + "_3",
|
|||
|
|
classId = cid,
|
|||
|
|
kind = SwingKind.Stab
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
EditorUtility.SetDirty(so);
|
|||
|
|
AssetDatabase.SaveAssets();
|
|||
|
|
sb.AppendLine("#792 3타 찌르기 강제 = " + on + " · remap 행 " + so.effectRemaps.Count);
|
|||
|
|
Write(sb);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>C8 롤백 — #792 경로만 끈다. 프리팹·베이크 데이터는 남겨 둔다(다시 켜면 그대로 동작).</summary>
|
|||
|
|
public static void Rollback()
|
|||
|
|
{
|
|||
|
|
var sb = new StringBuilder();
|
|||
|
|
var so = AssetDatabase.LoadAssetAtPath<SlashTrailSettings>(SettingsPath);
|
|||
|
|
if (so == null) { sb.AppendLine("!! 없음"); Write(sb); return; }
|
|||
|
|
so.useMappedEffects = false;
|
|||
|
|
so.drawRibbon = true;
|
|||
|
|
so.burstMode = BurstMode.None;
|
|||
|
|
EditorUtility.SetDirty(so);
|
|||
|
|
AssetDatabase.SaveAssets();
|
|||
|
|
sb.AppendLine("롤백 — useMappedEffects=false · drawRibbon=true · burstMode=None (#785 리본 상태로 복귀)");
|
|||
|
|
Write(sb);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|||
|
|
static string V(Vector3 v) { return string.Format("({0:F4},{1:F4},{2:F4})", v.x, v.y, v.z); }
|
|||
|
|
|
|||
|
|
static void Write(StringBuilder sb)
|
|||
|
|
{
|
|||
|
|
System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(Out));
|
|||
|
|
System.IO.File.AppendAllText(Out, sb.ToString() + "\n", new System.Text.UTF8Encoding(false));
|
|||
|
|
Debug.Log(sb.ToString());
|
|||
|
|
}
|
|||
|
|
}
|