ThreeKingdomsSlingshot/Tools/build_dash2d.cs

299 lines
13 KiB
C#
Raw Permalink 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.

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();