// WL788_Look.cs — #788 "tail 방식이 아니라 이전에 만든 고퀄리티 이펙트" : 힘민지 원본 슬래시(EricWang FX_Slash) 룩을 무기 추종 리본에 적용
// Edit : unity command run_script --file AgentScripts/WL788_Look.cs --entry WL788_Look.MakeAssets (스트릭 텍스처 + 리본 머티리얼 생성)
// Play : unity command run_script --file AgentScripts/WL788_Look.cs --entry WL788_Look.Apply --args '[1.6, 1, 1]' (emission, blend(1=One,6=OneMinusSrcAlpha), burst(1=ReuseShowEffect,0=None) · 메모리만)
// Edit : unity command run_script --file AgentScripts/WL788_Look.cs --entry WL788_Look.Bake --args '[1.6, 1, 1]' (설정 SO 굽기)
// Any : unity command run_script --file AgentScripts/WL788_Look.cs --entry WL788_Look.Dump
// 구성: ① 리본 머티리얼 = EricWang `Additive.shader`(원본 슬래시와 같은 셰이더) + 원본 `slash_L01.png` 스트릭을 리본 UV 에 맞게 전치(u=잔상축)·흑백화한 `T_WL_SlashStreak.png`
// ② 클래스별 색 = SlashTrailSettings 클래스 오버라이드 colorAdditive(HDR) — 정점색 경로(useGradients 0)
// ③ 원본 슬래시 프리팹의 플레어·글로우·링은 burstMode ReuseShowEffect + crescentRules(크레센트 스프라이트만 마스크) 로 그대로 재생
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;
public static class WL788_Look
{
const string SrcTexPath = "Assets/ResWork/EricWang/GameVFX Collection(URP)/FX_Slash01/Textures/slash_L01.png";
const string ShaderPath = "Assets/ResWork/EricWang/GameVFX Collection(URP)/Shaders/Additive.shader";
const string TexPath = "Assets/WL/Combat/Textures/T_WL_SlashStreak.png";
const string MatPath = "Assets/WL/Combat/Materials/M_WL_SlashRibbon.mat";
const string SettingsPath = "Assets/WL/Settings/Resources/WL/SlashTrailSettings.asset";
// 클래스별 리본 색(HDR) — 원본 슬래시 프리팹 계열 색: 10101 Blue_Slash · 10102 Orange_Slash · 10501/10502 Lana(노랑·백금)
static readonly (int cid, Color col)[] ClassColors =
{
(10101, new Color(0.55f, 0.85f, 1.60f, 1f)),
(10102, new Color(1.60f, 0.85f, 0.35f, 1f)),
(10501, new Color(1.60f, 1.35f, 0.60f, 1f)),
(10502, new Color(1.30f, 1.20f, 0.80f, 1f)),
(10201, new Color(1.60f, 0.55f, 0.70f, 1f)),
(10202, new Color(1.60f, 0.55f, 0.70f, 1f)),
};
/// 원본 스트릭을 전치(x↔y) + 흑백화해 리본용 텍스처로 굽는다. 리본 UV: u=잔상(1 머리) · v=칼날축.
public static object MakeAssets()
{
if (EditorApplication.isPlaying) return "ABORT: Play 중";
var src = AssetDatabase.LoadAssetAtPath(SrcTexPath);
if (src == null) return "source tex null " + SrcTexPath;
// 읽기 불가 텍스처도 RenderTexture 경유로 픽셀을 얻는다
var rt = RenderTexture.GetTemporary(src.width, src.height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
Graphics.Blit(src, rt);
var prev = RenderTexture.active; RenderTexture.active = rt;
var readable = new Texture2D(src.width, src.height, TextureFormat.RGBA32, false);
readable.ReadPixels(new Rect(0, 0, src.width, src.height), 0, 0); readable.Apply();
RenderTexture.active = prev; RenderTexture.ReleaseTemporary(rt);
int W = src.height, H = src.width; // 전치
var dst = new Texture2D(W, H, TextureFormat.RGBA32, false, false);
var sp = readable.GetPixels();
var dp = new Color[W * H];
for (int y = 0; y < H; y++)
for (int x = 0; x < W; x++)
{
// dst(x,y) = src(y,x) : src 의 세로(스트릭 방향)가 dst 의 가로(u=잔상축)가 된다
var c = sp[x * src.width + y];
float lum = Mathf.Clamp01(0.2126f * c.r + 0.7152f * c.g + 0.0722f * c.b);
float a = Mathf.Max(c.a, lum); // 원본이 가산용(알파 무의미)일 수 있어 밝기를 알파로도 쓴다
dp[y * W + x] = new Color(lum, lum, lum, a);
}
dst.SetPixels(dp); dst.Apply(false, false);
Directory.CreateDirectory(Path.GetDirectoryName(TexPath));
File.WriteAllBytes(TexPath, dst.EncodeToPNG());
Object.DestroyImmediate(readable); Object.DestroyImmediate(dst);
AssetDatabase.ImportAsset(TexPath, ImportAssetOptions.ForceUpdate);
var imp = AssetImporter.GetAtPath(TexPath) as TextureImporter;
if (imp != null)
{
imp.textureType = TextureImporterType.Default; imp.sRGBTexture = true; imp.alphaSource = TextureImporterAlphaSource.FromInput;
imp.mipmapEnabled = false; imp.wrapMode = TextureWrapMode.Clamp; imp.filterMode = FilterMode.Bilinear;
imp.maxTextureSize = 256; imp.textureCompression = TextureImporterCompression.Uncompressed; imp.isReadable = false;
imp.SaveAndReimport();
}
var tex = AssetDatabase.LoadAssetAtPath(TexPath);
var shader = AssetDatabase.LoadAssetAtPath(ShaderPath);
if (shader == null) return "shader null " + ShaderPath;
var mat = AssetDatabase.LoadAssetAtPath(MatPath);
if (mat == null) { mat = new Material(shader); AssetDatabase.CreateAsset(mat, MatPath); }
else mat.shader = shader;
mat.SetTexture("_MainTex", tex);
mat.SetColor("_Color", new Color(0.5f, 0.5f, 0.5f, 1f)); // 원본 슬래시 머티리얼 기본값과 동일
mat.SetFloat("_Emission", 1.6f);
mat.SetFloat("_Blend2", 1f); // One One = 원본과 같은 가산
mat.SetFloat("_CullMode", 0f);
mat.renderQueue = 3000;
EditorUtility.SetDirty(mat); AssetDatabase.SaveAssets(); AssetDatabase.Refresh();
return "made " + TexPath + " (" + W + "x" + H + ") + " + MatPath + " shader=" + shader.name;
}
static string ApplyTo(WL.Combat.SlashTrailSettings s, Material mat, float emission, int blend, int burst)
{
s.materialAdditive = mat;
s.useGradients = false; // 정점색 = 클래스 오버라이드 colorAdditive
s.colorAdditive = new Color(1.2f, 1.2f, 1.2f, 1f); // 오버라이드 없는 클래스 기본(백색)
foreach (var cc in ClassColors)
{
var o = s.FindOverride(cc.cid);
if (o == null) continue;
// 이전 팀이 원본 슬래시 색을 실측해 넣어 둔 colorAdditive 가 있으면 그것을 쓴다(최대 채널 > 0.5 = 설정됨)
bool hasColor = Mathf.Max(o.colorAdditive.r, Mathf.Max(o.colorAdditive.g, o.colorAdditive.b)) > 0.5f
&& !(Mathf.Approximately(o.colorAdditive.r, 1f) && Mathf.Approximately(o.colorAdditive.g, 1f) && Mathf.Approximately(o.colorAdditive.b, 1f));
o.overrideLook = true; if (!hasColor) o.colorAdditive = cc.col; o.widthScale = 1f;
}
s.burstMode = (WL.Combat.BurstMode)burst; // 1 = ReuseShowEffect (원본 플레어·글로우 재생)
s.suppressMode = WL.Combat.SuppressMode.CrescentOnly; // crescentRules 에 적힌 크레센트 스프라이트만 마스크
if (mat != null) { mat.SetFloat("_Emission", emission); mat.SetFloat("_Blend2", blend); }
return "applied mat=" + (mat != null ? mat.name : "null") + " emission=" + emission + " blend=" + blend + " burst=" + s.burstMode + " suppress=" + s.suppressMode + " useGradients=0 classOverrides=" + ClassColors.Length;
}
public static object Apply(float emission, int blend, int burst)
{
if (!Application.isPlaying) return "not playing (Edit 모드에서는 Bake)";
var s = WL.Combat.SlashTrailSettings.Instance; if (s == null) return "settings null";
var mat = AssetDatabase.LoadAssetAtPath(MatPath); if (mat == null) return "mat null — MakeAssets 먼저";
string r = ApplyTo(s, mat, emission, blend, burst);
// BladeTrail 은 생성 시점에 머티리얼을 굽는다 → 이미 떠 있는 트레일의 렌더러를 직접 바꿔 준다(Play 검증용)
int swapped = 0;
foreach (var drv in Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None))
for (int i = 0; i < 4; i++)
{
var t = drv.GetTrail(i); if (t == null) continue;
var mr = t.GetComponent(); if (mr == null) continue;
mr.sharedMaterials = new[] { mat }; swapped++;
}
return r + " · 런타임 트레일 렌더러 교체=" + swapped;
}
public static object Bake(float emission, int blend, int burst)
{
if (EditorApplication.isPlaying) return "ABORT: Play 중";
var s = AssetDatabase.LoadAssetAtPath(SettingsPath); if (s == null) return "settings null";
var mat = AssetDatabase.LoadAssetAtPath(MatPath); if (mat == null) return "mat null — MakeAssets 먼저";
string r = ApplyTo(s, mat, emission, blend, burst);
EditorUtility.SetDirty(s); EditorUtility.SetDirty(mat); AssetDatabase.SaveAssets();
return "baked: " + r;
}
/// Play: 버스트 모드만 바꾼다(0 None · 1 ReuseShowEffect = 원본 슬래시 프리팹의 플레어/글로우 재생 · 2 PrefabArc).
public static object Burst(int mode, int timing)
{
if (!Application.isPlaying) return "not playing";
var s = WL.Combat.SlashTrailSettings.Instance; if (s == null) return "settings null";
s.burstMode = (WL.Combat.BurstMode)mode;
s.burstTiming = (WL.Combat.BurstTiming)timing;
s.suppressMode = WL.Combat.SuppressMode.CrescentOnly;
return "burst=" + s.burstMode + " timing=" + s.burstTiming + " suppress=" + s.suppressMode;
}
public static object Dump()
{
var s = Application.isPlaying ? WL.Combat.SlashTrailSettings.Instance : AssetDatabase.LoadAssetAtPath(SettingsPath);
var sb = new StringBuilder();
if (s == null) return "settings null";
sb.AppendLine("mat=" + (s.materialAdditive != null ? s.materialAdditive.name + " / " + s.materialAdditive.shader.name : "null") + " useGradients=" + s.useGradients + " burst=" + s.burstMode + " suppress=" + s.suppressMode + " drawRibbon=" + s.drawRibbon + " fade=" + s.fadeSeconds);
foreach (var cc in ClassColors) { var o = s.FindOverride(cc.cid); sb.AppendLine(" " + cc.cid + " override=" + (o != null ? o.overrideLook + " col=" + o.colorAdditive : "none")); }
if (s.materialAdditive != null && s.materialAdditive.HasProperty("_Emission")) sb.AppendLine(" emission=" + s.materialAdditive.GetFloat("_Emission") + " blend2=" + s.materialAdditive.GetFloat("_Blend2") + " tex=" + (s.materialAdditive.GetTexture("_MainTex") != null ? s.materialAdditive.GetTexture("_MainTex").name : "none"));
return sb.ToString();
}
}