ThreeKingdomsSlingshot/Tools/build_dash2d.cs

299 lines
13 KiB
C#
Raw Permalink Normal View History

스킬 이펙트 1차 + IB(쿠로네코) 연출 시험 — 돌진·조준 UI·타격감 (PD 2026-11-10) - 기본 공격(돌진) = IB 이펙트 그대로 시험: 앞 빛 물결 · 속도 꼬리(진영색 4색 · 멈춘 자리에서 0.12초 흐려짐) · WL 흙먼지 · 기준점 dash(아래로 갈수록 발 쪽) · 히트 이펙트 제거(PD 지정 대기) - 슬링샷 방향 결정 UI = IB GameTouchArrow 이식(IbAimArrow · IB 유닛 반지름 0.496 기준 비율) + 진행 중 캐릭터 연출(IbUnitMarks: 발밑 원 · 몸 빛 · 조준 중 속성 방울) · 값 표 Resources/Skills/ib_ui.csv - 기병: 복귀 영역(채운 원 · PreviewCavalryReturn) · 복귀 때 돌아서기(UnitView.TurnAround) - 타격감: HitFeel + CameraController.Shake · 표 hit_feel.csv(IB 흔들림 값은 디컴파일에 없어 추정값) - 액티브 스킬 1차 연출(Fable 제안 56 스킬 · 사본 Resources/Fx/TKS_Fx_Sk_*) · SkillVfx 확장(s_camp · f_fwd · f_drift · f_fade · s_over · b_flat · 트레일 페이드) · 셀화 셰이더 포트 - 2D 돌진 시험본(Dash2D · Aura2D test_aura) · 이전 시험 행은 skill_vfx.csv 에 # 주석 - 여포 크기: dz_boss_lvbu 몸 높이 819 → 718(깃털 제외 · 말 탄 유닛 머리 끝 상한 때문에 작아졌음) - 원본 = Assets/VFX/IB(쿠로네코 · GUID 유지 · Tools/wl_vfx_import.py --skip-scripts) · 사본 도구 Tools/make_ib_fx_copies.cs · make_ib_camp_textures.py · build_dash2d.cs - 테스트 PlayMode 118/118 통과 · 자동 재직렬화(png meta · UITheme · ProjectSettings) 제외 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 04:19:44 +00:00
using System.IO;
using UnityEditor;
using UnityEngine;
// 2D 돌진 이펙트 · 몸 뒤덮는 기운(시험) 프리팹 생성 — Unity CLI script eval 로 실행:
// unity command --project-path "E:/NerdNavis/ThreeKingdomsSlingshot" script eval --file Tools/build_dash2d.cs
// 그림 = Tools/make_dash2d_textures.py → Assets/VFX/TKS2D · 진영색 원본 = Tools/dash2d_camps.csv(그림 · 꼬리 · 초승달 색 모두 이 표에서).
// 크기 단위 = 유닛 키 1(표 f_scale 1) · 정면 = +X(표 b_flat = 1).
// 이력(PD 2026-11-10): 1차 검정 외곽선 셀 그림 → 2차 「외곽선 짙음 · 밑 이펙트 투명값」 = 외곽선 없는 검기 · 반투명 바람 줄기 →
// 3차 「검기는 이전 형태가 더 나음 · 대쉬 이펙트는 몸을 벗어나지 않게 · 첨부 같은 몸 뒤덮는 연출을 스킬용으로 시험」 =
// 초승달 Head = 1차 그림(dash2d_crescent · 진영색 곱) · 캐릭터 밑 층 = 유닛 따라 움직이는 Local 공간 · 몸 기준점 반경 0.45 키 안
// (실측 그림 경계 = 좌우 ±0.56 키 · 몸 기준 아래 −0.60 ~ 위 +0.59 키 · 투명 여백 포함) · 기운 = TKS_Fx_Aura2D_<진영>(표 키 test_aura).
string dir = "Assets/VFX/TKS2D/";
string fxDir = "Assets/Resources/Fx/";
string dustHex = "#F3EBDD";
var camps = new System.Collections.Generic.List<string[]>();
foreach (var line in File.ReadAllLines("Tools/dash2d_camps.csv"))
{
var cells = line.Trim('\uFEFF').Split(',');
if (cells.Length < 2 || cells[0] == "s_camp" || cells[0].Length == 0) continue;
camps.Add(new[] { cells[0].Trim(), cells[1].Trim() });
}
AssetDatabase.Refresh();
var texNames = new System.Collections.Generic.List<string> { "dash2d_crescent", "dash2d_trail", "dash2d_speedline", "dash2d_dot", "dash2d_puff", "aura_wisp" };
foreach (var cp in camps)
{
texNames.Add("dash2d_slash_" + cp[0]);
texNames.Add("aura_comet_" + cp[0]);
}
foreach (var n in texNames)
{
var ti = (TextureImporter)AssetImporter.GetAtPath(dir + n + ".png");
ti.textureType = TextureImporterType.Sprite;
ti.spriteImportMode = SpriteImportMode.Single;
ti.spritePixelsPerUnit = n.StartsWith("dash2d_slash_") || n == "dash2d_crescent" ? 512 : 256;
ti.mipmapEnabled = false;
ti.alphaIsTransparency = true;
ti.wrapMode = TextureWrapMode.Clamp;
ti.textureCompression = TextureImporterCompression.Uncompressed;
ti.SaveAndReimport();
}
Material Mat(string name, string tex)
{
var path = dir + "Mat_" + name + ".mat";
var m = AssetDatabase.LoadAssetAtPath<Material>(path);
if (m == null)
{
m = new Material(Shader.Find("Sprites/Default"));
AssetDatabase.CreateAsset(m, path);
}
m.shader = Shader.Find("Sprites/Default");
m.mainTexture = AssetDatabase.LoadAssetAtPath<Texture2D>(dir + tex + ".png");
EditorUtility.SetDirty(m);
return m;
}
var trailMat = Mat("Dash2D_Trail", "dash2d_trail");
var lineMat = Mat("Dash2D_Line", "dash2d_speedline");
var dotMat = Mat("Dash2D_Dot", "dash2d_dot");
var puffMat = Mat("Dash2D_Puff", "dash2d_puff");
var wispMat = Mat("Aura2D_Wisp", "aura_wisp");
Color Hex(string h) { Color c; ColorUtility.TryParseHtmlString(h, out c); return c; }
ParticleSystem.MinMaxCurve Two(float a, float b) { return new ParticleSystem.MinMaxCurve(a, b); }
ParticleSystem.MinMaxGradient Fade(float a0, float aMid, float midT)
{
var g = new Gradient();
g.SetKeys(new[] { new GradientColorKey(Color.white, 0f), new GradientColorKey(Color.white, 1f) },
new[] { new GradientAlphaKey(a0, 0f), new GradientAlphaKey(aMid, midT), new GradientAlphaKey(0f, 1f) });
return new ParticleSystem.MinMaxGradient(g);
}
ParticleSystem NewPs(Transform parent, string name, Vector3 local, Material mat, bool localSpace)
{
var go = new GameObject(name);
go.transform.SetParent(parent, false);
go.transform.localPosition = local;
var ps = go.AddComponent<ParticleSystem>();
ps.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
var m = ps.main;
m.duration = 1f;
m.loop = true;
m.playOnAwake = true;
m.startSpeed = 0f;
m.gravityModifier = 0f;
m.simulationSpace = localSpace ? ParticleSystemSimulationSpace.Local : ParticleSystemSimulationSpace.World;
m.scalingMode = ParticleSystemScalingMode.Hierarchy;
var r = go.GetComponent<ParticleSystemRenderer>();
r.sharedMaterial = mat;
r.renderMode = ParticleSystemRenderMode.Billboard;
r.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
r.receiveShadows = false;
return ps;
}
void Backward(ParticleSystem ps, float speed)
{
var v = ps.velocityOverLifetime;
v.enabled = true;
v.space = ParticleSystemSimulationSpace.Local;
v.x = new ParticleSystem.MinMaxCurve(-speed);
v.y = new ParticleSystem.MinMaxCurve(0f);
v.z = new ParticleSystem.MinMaxCurve(0f);
}
void Box(ParticleSystem ps, Vector3 scale)
{
var s = ps.shape;
s.shapeType = ParticleSystemShapeType.Box;
s.scale = scale;
}
SpriteRenderer Sprite(Transform parent, string name, string tex, Vector3 local, Vector3 scale, Color color)
{
var go = new GameObject(name);
go.transform.SetParent(parent, false);
go.transform.localPosition = local;
go.transform.localScale = scale;
var sr = go.AddComponent<SpriteRenderer>();
sr.sprite = AssetDatabase.LoadAssetAtPath<Sprite>(dir + tex + ".png");
sr.color = color;
return sr;
}
string Save(GameObject root)
{
var path = fxDir + root.name + ".prefab";
PrefabUtility.SaveAsPrefabAsset(root, path);
Object.DestroyImmediate(root);
return path;
}
var sb = new System.Text.StringBuilder();
foreach (var cp in camps)
{
var c = Hex(cp[1]);
var light = Color.Lerp(c, Color.white, 0.15f);
// ── 돌진(기본 공격) ──
var root = new GameObject("TKS_Fx_Dash2D_" + cp[0]);
// 꼬리(유닛 아래) — 짧게(0.04초 = 보병 속도 기준 몸 뒤 약 0.25 키) · 반투명 진영색
var tgo = new GameObject("Trail");
tgo.transform.SetParent(root.transform, false);
var tr = tgo.AddComponent<TrailRenderer>();
tr.sharedMaterial = trailMat;
tr.time = 0.04f;
tr.minVertexDistance = 0.03f;
tr.widthMultiplier = 0.5f;
tr.widthCurve = new AnimationCurve(new Keyframe(0f, 1f), new Keyframe(1f, 0.3f));
var g = new Gradient();
g.SetKeys(new[] { new GradientColorKey(light, 0f), new GradientColorKey(light, 1f) },
new[] { new GradientAlphaKey(0.7f, 0f), new GradientAlphaKey(0.35f, 0.5f), new GradientAlphaKey(0f, 1f) });
tr.colorGradient = g;
tr.textureMode = LineTextureMode.Stretch;
tr.alignment = LineAlignment.View;
tr.numCapVertices = 0;
tr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
tr.receiveShadows = false;
// 바람 줄기(유닛 위 · 표 s_over · 유닛을 따라 움직임) — 몸 안에서 생겨 조금만 뒤로 흐르며 투명해짐(유닛 아래면 몸에 다 가려 안 보임 · 실측)
var lines = NewPs(root.transform, "Lines", new Vector3(-0.05f, 0f, 0f), lineMat, true);
var lm = lines.main;
lm.startLifetime = 0.16f;
lm.startSize3D = true;
lm.startSizeX = Two(0.35f, 0.55f);
lm.startSizeY = Two(0.08f, 0.13f);
lm.startSizeZ = 1f;
lm.startColor = new Color(1f, 1f, 1f, 0.85f); // 유닛 위 층(표 s_over · PD 「몸의 대쉬 효과가 왜 안 보이나」) — 그림이 비치게 반투명(0.7 · 가는 줄 = 거의 안 보임 · 실측)
lm.maxParticles = 30;
var le = lines.emission;
le.rateOverTime = 50f;
Box(lines, new Vector3(0.2f, 0.6f, 0f));
Backward(lines, 0.5f);
var lc = lines.colorOverLifetime;
lc.enabled = true;
lc.color = Fade(1f, 0.7f, 0.5f);
lines.GetComponent<ParticleSystemRenderer>().alignment = ParticleSystemRenderSpace.Local; // 줄기 길이 = 진행 축(+X)
// 바람 알갱이(유닛 위 · 표 s_over) — 작은 흰 점 · 몸 안
var dots = NewPs(root.transform, "Dots", new Vector3(-0.05f, 0f, 0f), dotMat, true);
var dm = dots.main;
dm.startLifetime = 0.15f;
dm.startSize = Two(0.04f, 0.07f);
dm.startColor = new Color(1f, 1f, 1f, 0.75f);
dm.maxParticles = 16;
var de = dots.emission;
de.rateOverTime = 18f;
Box(dots, new Vector3(0.3f, 0.7f, 0f));
Backward(dots, 0.8f);
var dc = dots.colorOverLifetime;
dc.enabled = true;
dc.color = Fade(1f, 0.8f, 0.5f);
// 초승달(유닛 위 · 표 s_over = Head) — 1차 그림(검정 외곽선 · 진영색 2단 · PD 「검기는 이전 형태가 더 나음」)
Sprite(root.transform, "Head", "dash2d_crescent", new Vector3(0.32f, 0f, 0f), new Vector3(1f, 1.25f, 1f), c);
sb.AppendLine(Save(root));
// ── 몸 뒤덮는 기운(시험 · 스킬용 · 표 키 test_aura · 테스트 씬 「기운 시험」 스위치) ──
var aura = new GameObject("TKS_Fx_Aura2D_" + cp[0]);
var cometMat = Mat("Aura2D_Comet_" + cp[0], "aura_comet_" + cp[0]);
// 몸통 기운(유닛 아래) — 앞머리가 몸 앞 가장자리 · 몸을 감싸고 뒤로 꼬리 · 겹쳐 깜빡이는 혜성 그림(살아 있는 불꽃 느낌)
var body = NewPs(aura.transform, "Body", new Vector3(-0.45f, 0f, 0f), cometMat, true);
var bm = body.main;
bm.startLifetime = 0.2f;
bm.startSize3D = true;
bm.startSizeX = Two(1.8f, 2.0f);
bm.startSizeY = Two(1.15f, 1.3f);
bm.startSizeZ = 1f;
bm.startColor = new Color(1f, 1f, 1f, 0.6f);
bm.maxParticles = 10;
var be = body.emission;
be.rateOverTime = 30f;
Box(body, new Vector3(0.06f, 0.06f, 0f));
var bc = body.colorOverLifetime;
bc.enabled = true;
var bg = new Gradient();
bg.SetKeys(new[] { new GradientColorKey(Color.white, 0f), new GradientColorKey(Color.white, 1f) },
new[] { new GradientAlphaKey(0f, 0f), new GradientAlphaKey(1f, 0.25f), new GradientAlphaKey(0f, 1f) });
bc.color = new ParticleSystem.MinMaxGradient(bg);
body.GetComponent<ParticleSystemRenderer>().alignment = ParticleSystemRenderSpace.Local;
// 불꽃 혀(유닛 아래) — 밝은 진영색 · 몸에서 뒤로 흘러 나감
var wisps = NewPs(aura.transform, "Wisps", new Vector3(-0.1f, 0f, 0f), wispMat, true);
var wm = wisps.main;
wm.startLifetime = 0.18f;
wm.startSize3D = true;
wm.startSizeX = Two(0.4f, 0.8f);
wm.startSizeY = Two(0.1f, 0.18f);
wm.startSizeZ = 1f;
wm.startColor = Color.Lerp(c, Color.white, 0.45f);
wm.maxParticles = 30;
var we = wisps.emission;
we.rateOverTime = 35f;
Box(wisps, new Vector3(0.5f, 0.8f, 0f));
Backward(wisps, 3f);
var wc = wisps.colorOverLifetime;
wc.enabled = true;
wc.color = Fade(1f, 0.7f, 0.5f);
wisps.GetComponent<ParticleSystemRenderer>().alignment = ParticleSystemRenderSpace.Local;
// 반짝임(유닛 아래) — 흰 점이 뒤로
var sparks = NewPs(aura.transform, "Sparks", Vector3.zero, dotMat, true);
var sm = sparks.main;
sm.startLifetime = 0.15f;
sm.startSize = Two(0.04f, 0.08f);
sm.startColor = Color.white;
sm.maxParticles = 20;
var se = sparks.emission;
se.rateOverTime = 30f;
Box(sparks, new Vector3(0.6f, 0.9f, 0f));
Backward(sparks, 4f);
var sc = sparks.colorOverLifetime;
sc.enabled = true;
sc.color = Fade(1f, 0.8f, 0.5f);
// 유닛 위(표 s_over = Veil;Rim): 옅은 진영색 막(몸이 기운 속에 있는 느낌) · 앞 가장자리 빛나는 테(2차 검기 그림 재사용)
var veilColor = c;
veilColor.a = 0.18f;
Sprite(aura.transform, "Veil", "dash2d_dot", Vector3.zero, new Vector3(4.4f, 4.4f, 1f), veilColor); // 지름 1.1 키 원(방향마다 같게)
Sprite(aura.transform, "Rim", "dash2d_slash_" + cp[0], new Vector3(0.3f, 0f, 0f), new Vector3(0.85f, 1.15f, 1f), new Color(1f, 1f, 1f, 0.9f));
sb.AppendLine(Save(aura));
}
// 발밑 흙먼지(모든 진영) — 유닛을 따라 움직임 · 발 둘레에서 피었다 사라짐(몸 밖으로 안 남음)
{
var root = new GameObject("TKS_Fx_Dust2D");
var ps = NewPs(root.transform, "Puff", Vector3.zero, puffMat, true);
var m = ps.main;
m.startLifetime = 0.25f;
m.startSize = Two(0.22f, 0.32f);
m.startRotation = Two(0f, Mathf.PI * 2f);
var dust = Hex(dustHex);
dust.a = 0.75f;
m.startColor = dust;
m.maxParticles = 12;
var e = ps.emission;
e.rateOverTime = 14f;
Box(ps, new Vector3(0.5f, 0.05f, 0f));
var v = ps.velocityOverLifetime;
v.enabled = true;
v.space = ParticleSystemSimulationSpace.World;
v.x = Two(-0.1f, 0.1f);
v.y = Two(0.2f, 0.4f);
v.z = Two(0f, 0f);
var sz = ps.sizeOverLifetime;
sz.enabled = true;
sz.size = new ParticleSystem.MinMaxCurve(1f, new AnimationCurve(new Keyframe(0f, 0.6f), new Keyframe(1f, 1.1f)));
var col = ps.colorOverLifetime;
col.enabled = true;
col.color = Fade(1f, 0.75f, 0.5f);
sb.AppendLine(Save(root));
}
AssetDatabase.SaveAssets();
return sb.ToString();