299 lines
13 KiB
C#
299 lines
13 KiB
C#
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();
|