260 lines
17 KiB
C#
260 lines
17 KiB
C#
// WL785_Ramp.cs — #785 리본 가시성 보강 (PM · 2026-09-06 23:5x)
|
||
// 문제: 리본 폭 방향 정점이 2개(손잡이=주황 · 칼끝=파랑)뿐이라 GPU 가 두 색을 선형 보간 → 중앙이 회색/연보라로 죽는다(실측: 연보라 띠).
|
||
// 해결: 폭 방향 색을 정점색이 아니라 **램프 텍스처(v축)** 로 준다. 정점색은 흰색×알파만 나른다.
|
||
// Play : unity command run_script --file AgentScripts/WL785_Ramp.cs --entry WL785_Ramp.Apply --args '[0.12, 0.30, 1.0]' (edgeSoft, tailSoft, intensity · 메모리만)
|
||
// Edit : unity command run_script --file AgentScripts/WL785_Ramp.cs --entry WL785_Ramp.Bake --args '[0.12, 0.30, 1.0]' (PNG 저장 + 임포터 + 머티리얼 + 설정 SO 굽기)
|
||
// Dump : unity command run_script --file AgentScripts/WL785_Ramp.cs --entry WL785_Ramp.Dump
|
||
// 램프 규약: x = u(잔상 나이 · 1 = 머리) · y = v(칼날축 · 0 = 손잡이 · 1 = 칼끝). 64×64 RGBA32 · Clamp · Bilinear · 밉맵 없음 · 모바일 16 KB.
|
||
using System.IO;
|
||
using System.Text;
|
||
using UnityEditor;
|
||
using UnityEngine;
|
||
|
||
public static class WL785_Ramp
|
||
{
|
||
const string TexPath = "Assets/WL/Combat/Textures/T_WL_BladeRibbonRamp.png";
|
||
const string MatPath = "Assets/WL/Combat/Materials/M_WL_BladeRibbon.mat";
|
||
const string SettingsPath = "Assets/WL/Settings/Resources/WL/SlashTrailSettings.asset";
|
||
const int Size = 64;
|
||
|
||
// 폭(v) 단면: 손잡이 진주황 → 노랑 → 밝은 크림(코어) → 하늘색 → 칼끝 파랑. 회색 구간을 거치지 않는 색 경로.
|
||
static Color Cross(float v)
|
||
{
|
||
var g = new Gradient();
|
||
g.SetKeys(new[]
|
||
{
|
||
new GradientColorKey(new Color(1.00f, 0.40f, 0.08f), 0.00f),
|
||
new GradientColorKey(new Color(1.00f, 0.78f, 0.22f), 0.28f),
|
||
new GradientColorKey(new Color(1.00f, 0.96f, 0.80f), 0.50f),
|
||
new GradientColorKey(new Color(0.40f, 0.82f, 1.00f), 0.72f),
|
||
new GradientColorKey(new Color(0.12f, 0.32f, 1.00f), 1.00f),
|
||
}, new[] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(1f, 1f) });
|
||
return g.Evaluate(v);
|
||
}
|
||
|
||
public static Texture2D Build()
|
||
{
|
||
var tex = new Texture2D(Size, Size, TextureFormat.RGBA32, false, false);
|
||
tex.name = "T_WL_BladeRibbonRamp";
|
||
tex.wrapMode = TextureWrapMode.Clamp; tex.filterMode = FilterMode.Bilinear;
|
||
var px = new Color[Size * Size];
|
||
for (int y = 0; y < Size; y++)
|
||
{
|
||
float v = (y + 0.5f) / Size;
|
||
var c = Cross(v);
|
||
for (int x = 0; x < Size; x++)
|
||
{
|
||
float u = (x + 0.5f) / Size; // 1 = 머리(현재 칼 위치), 0 = 꼬리
|
||
float tailTint = Mathf.SmoothStep(0f, 1f, Mathf.Clamp01(u / 0.45f)); // 꼬리로 갈수록 청보라로 기움 + 약간 어둡게
|
||
var cc = Color.Lerp(new Color(0.45f, 0.30f, 1.00f), c, tailTint) * Mathf.Lerp(0.82f, 1f, tailTint);
|
||
cc.a = 1f;
|
||
px[y * Size + x] = cc;
|
||
}
|
||
}
|
||
tex.SetPixels(px); tex.Apply(false, false);
|
||
return tex;
|
||
}
|
||
|
||
static string ApplyLook(Material mat, Texture2D tex, float edgeSoft, float tailSoft, float intensity)
|
||
{
|
||
var s = WL.Combat.SlashTrailSettings.Instance;
|
||
if (s == null) return "settings null";
|
||
// 정점색은 흰색만 (색은 램프가 담당) · 알파는 잔상축 그라디언트가 담당
|
||
var white = new Gradient();
|
||
white.SetKeys(new[] { new GradientColorKey(Color.white, 0f), new GradientColorKey(Color.white, 1f) },
|
||
new[] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(1f, 1f) });
|
||
s.gradientAlongBlade = white;
|
||
var swing = new Gradient();
|
||
swing.SetKeys(new[] { new GradientColorKey(Color.white, 0f), new GradientColorKey(Color.white, 1f) },
|
||
new[] { new GradientAlphaKey(0.0f, 0f), new GradientAlphaKey(0.85f, 0.15f), new GradientAlphaKey(1f, 0.5f), new GradientAlphaKey(1f, 1f) });
|
||
s.gradientAlongSwing = swing;
|
||
s.bladeGradientWeight = 1f;
|
||
s.colorIntensity = intensity;
|
||
s.useGradients = true;
|
||
if (mat != null)
|
||
{
|
||
mat.SetTexture("_BaseMap", tex);
|
||
mat.SetFloat("_EdgeSoftness", edgeSoft);
|
||
mat.SetFloat("_TailSoftness", tailSoft);
|
||
mat.SetFloat("_HeadSoftness", 0.03f);
|
||
mat.SetFloat("_Intensity", 1f);
|
||
}
|
||
return "look applied: ramp=" + (tex != null ? tex.name : "null") + " edge=" + edgeSoft + " tail=" + tailSoft + " intensity=" + intensity + " bladeW=1 alpha(0→0.85@0.15→1@0.5)";
|
||
}
|
||
|
||
/// <summary>Play 중 메모리만 (에셋 무수정): 런타임 램프 생성 → 머티리얼/설정 인스턴스에 적용.</summary>
|
||
public static object Apply(float edgeSoft, float tailSoft, float intensity)
|
||
{
|
||
if (!Application.isPlaying) return "not playing (Edit 모드에서는 Bake 사용)";
|
||
var s = WL.Combat.SlashTrailSettings.Instance;
|
||
if (s == null) return "settings null";
|
||
var mat = s.materialAdditive;
|
||
var tex = Build();
|
||
return ApplyLook(mat, tex, edgeSoft, tailSoft, intensity);
|
||
}
|
||
|
||
/// <summary>Edit 모드: PNG 저장 · 임포터 · 머티리얼 텍스처 · 설정 SO 필드를 디스크에 굽는다.</summary>
|
||
public static object Bake(float edgeSoft, float tailSoft, float intensity)
|
||
{
|
||
if (EditorApplication.isPlaying) return "ABORT: Play 중";
|
||
Directory.CreateDirectory(Path.GetDirectoryName(TexPath));
|
||
var tex = Build();
|
||
File.WriteAllBytes(TexPath, tex.EncodeToPNG());
|
||
Object.DestroyImmediate(tex);
|
||
AssetDatabase.ImportAsset(TexPath, ImportAssetOptions.ForceUpdate);
|
||
var imp = AssetImporter.GetAtPath(TexPath) as TextureImporter;
|
||
if (imp == null) return "importer null " + TexPath;
|
||
imp.textureType = TextureImporterType.Default; imp.sRGBTexture = true; imp.alphaSource = TextureImporterAlphaSource.None;
|
||
imp.mipmapEnabled = false; imp.wrapMode = TextureWrapMode.Clamp; imp.filterMode = FilterMode.Bilinear;
|
||
imp.maxTextureSize = 64; imp.textureCompression = TextureImporterCompression.Uncompressed; imp.isReadable = false;
|
||
imp.SaveAndReimport();
|
||
var ramp = AssetDatabase.LoadAssetAtPath<Texture2D>(TexPath);
|
||
var mat = AssetDatabase.LoadAssetAtPath<Material>(MatPath);
|
||
var s = AssetDatabase.LoadAssetAtPath<WL.Combat.SlashTrailSettings>(SettingsPath);
|
||
if (ramp == null || mat == null || s == null) return "load fail ramp=" + (ramp != null) + " mat=" + (mat != null) + " settings=" + (s != null);
|
||
// ApplyLook 은 Instance 를 쓰므로 에셋을 직접 채운다
|
||
var white = new Gradient();
|
||
white.SetKeys(new[] { new GradientColorKey(Color.white, 0f), new GradientColorKey(Color.white, 1f) },
|
||
new[] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(1f, 1f) });
|
||
var swing = new Gradient();
|
||
swing.SetKeys(new[] { new GradientColorKey(Color.white, 0f), new GradientColorKey(Color.white, 1f) },
|
||
new[] { new GradientAlphaKey(0.0f, 0f), new GradientAlphaKey(0.85f, 0.15f), new GradientAlphaKey(1f, 0.5f), new GradientAlphaKey(1f, 1f) });
|
||
s.gradientAlongBlade = white; s.gradientAlongSwing = swing; s.bladeGradientWeight = 1f; s.colorIntensity = intensity; s.useGradients = true;
|
||
EditorUtility.SetDirty(s);
|
||
mat.SetTexture("_BaseMap", ramp);
|
||
mat.SetFloat("_EdgeSoftness", edgeSoft); mat.SetFloat("_TailSoftness", tailSoft); mat.SetFloat("_HeadSoftness", 0.03f); mat.SetFloat("_Intensity", 1f);
|
||
EditorUtility.SetDirty(mat);
|
||
AssetDatabase.SaveAssets(); AssetDatabase.Refresh();
|
||
return "baked: " + TexPath + " (" + ramp.width + "x" + ramp.height + ") · mat _BaseMap set · settings bladeW=1 intensity=" + intensity + " edge=" + edgeSoft + " tail=" + tailSoft;
|
||
}
|
||
|
||
// ── #788: 램프 색 × 원본 슬래시 스트릭(EricWang slash_L01 전치·흑백) 결합 텍스처 ─────────────────
|
||
const string StreakPath = "Assets/WL/Combat/Textures/T_WL_SlashStreak.png";
|
||
const string CombinedPath = "Assets/WL/Combat/Textures/T_WL_BladeRibbonSlash.png";
|
||
const string BladeMatPath = "Assets/WL/Combat/Materials/M_WL_BladeRibbon.mat";
|
||
|
||
static Texture2D ReadPixels(Texture2D src)
|
||
{
|
||
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 t = new Texture2D(src.width, src.height, TextureFormat.RGBA32, false);
|
||
t.ReadPixels(new Rect(0, 0, src.width, src.height), 0, 0); t.Apply();
|
||
RenderTexture.active = prev; RenderTexture.ReleaseTemporary(rt);
|
||
return t;
|
||
}
|
||
|
||
/// <summary>u(가로)=잔상 나이(1 머리) · v(세로)=칼날축. 색 = 램프 단면 · 알파 = 스트릭 밝기(코어 불투명 · 가장자리 갈라짐).</summary>
|
||
public static Texture2D BuildCombined(float alphaGain, float alphaGamma, float floorAlpha)
|
||
{
|
||
var streakAsset = AssetDatabase.LoadAssetAtPath<Texture2D>(StreakPath);
|
||
Texture2D streak = streakAsset != null ? ReadPixels(streakAsset) : null;
|
||
int W = 128, H = 64;
|
||
var tex = new Texture2D(W, H, TextureFormat.RGBA32, false, false) { name = "T_WL_BladeRibbonSlash", wrapMode = TextureWrapMode.Clamp, filterMode = FilterMode.Bilinear };
|
||
var px = new Color[W * H];
|
||
for (int y = 0; y < H; y++)
|
||
{
|
||
float v = (y + 0.5f) / H;
|
||
var c = Cross(v);
|
||
for (int x = 0; x < W; x++)
|
||
{
|
||
float u = (x + 0.5f) / W;
|
||
float tailTint = Mathf.SmoothStep(0f, 1f, Mathf.Clamp01(u / 0.45f));
|
||
var cc = Color.Lerp(new Color(0.45f, 0.30f, 1.00f), c, tailTint) * Mathf.Lerp(0.82f, 1f, tailTint);
|
||
float s = 1f;
|
||
if (streak != null)
|
||
{
|
||
var sp = streak.GetPixelBilinear(u, v);
|
||
s = Mathf.Max(sp.r, sp.a);
|
||
}
|
||
// 스트릭 밝기를 알파로: 코어(밝음)는 불투명, 갈라진 가장자리는 투명. floorAlpha 로 띠의 바탕을 조금 남긴다.
|
||
float a = Mathf.Clamp01(floorAlpha + (1f - floorAlpha) * Mathf.Pow(Mathf.Clamp01(s * alphaGain), alphaGamma));
|
||
// 밝은 코어는 살짝 백색화(참고 이미지의 흰 심)
|
||
cc = Color.Lerp(cc, Color.white, Mathf.Clamp01((s - 0.75f) * 2f) * 0.5f);
|
||
cc.a = a;
|
||
px[y * W + x] = cc;
|
||
}
|
||
}
|
||
tex.SetPixels(px); tex.Apply(false, false);
|
||
if (streak != null) Object.DestroyImmediate(streak);
|
||
return tex;
|
||
}
|
||
|
||
/// <summary>Play: 결합 텍스처를 리본 머티리얼(WL/Blade Ribbon)에 즉시 적용(메모리) + 떠 있는 트레일 렌더러를 그 머티리얼로.</summary>
|
||
public static object ApplyCombined(float edgeSoft, float tailSoft, float alphaGain, float alphaGamma, float floorAlpha)
|
||
{
|
||
if (!Application.isPlaying) return "not playing";
|
||
var s = WL.Combat.SlashTrailSettings.Instance; if (s == null) return "settings null";
|
||
var mat = AssetDatabase.LoadAssetAtPath<Material>(BladeMatPath); if (mat == null) return "blade mat null";
|
||
s.materialAdditive = mat;
|
||
var tex = BuildCombined(alphaGain, alphaGamma, floorAlpha);
|
||
string r = ApplyLook(mat, tex, edgeSoft, tailSoft, 1f);
|
||
int swapped = 0;
|
||
foreach (var drv in Object.FindObjectsByType<WL.Combat.WeaponTrailDriver>(FindObjectsInactive.Include, FindObjectsSortMode.None))
|
||
for (int i = 0; i < 4; i++) { var t = drv.GetTrail(i); if (t == null) continue; var mr = t.GetComponent<MeshRenderer>(); if (mr == null) continue; mr.sharedMaterials = new[] { mat }; swapped++; }
|
||
return r + " · combined " + tex.width + "x" + tex.height + " · 트레일 렌더러 교체=" + swapped;
|
||
}
|
||
|
||
/// <summary>Edit: 결합 텍스처 PNG 저장 + 임포터 + 머티리얼 + 설정 SO.</summary>
|
||
public static object BakeCombined(float edgeSoft, float tailSoft, float alphaGain, float alphaGamma, float floorAlpha)
|
||
{
|
||
if (EditorApplication.isPlaying) return "ABORT: Play 중";
|
||
var tex = BuildCombined(alphaGain, alphaGamma, floorAlpha);
|
||
File.WriteAllBytes(CombinedPath, tex.EncodeToPNG());
|
||
Object.DestroyImmediate(tex);
|
||
AssetDatabase.ImportAsset(CombinedPath, ImportAssetOptions.ForceUpdate);
|
||
var imp = AssetImporter.GetAtPath(CombinedPath) as TextureImporter;
|
||
if (imp != null)
|
||
{
|
||
imp.textureType = TextureImporterType.Default; imp.sRGBTexture = true; imp.alphaSource = TextureImporterAlphaSource.FromInput; imp.alphaIsTransparency = false;
|
||
imp.mipmapEnabled = false; imp.wrapMode = TextureWrapMode.Clamp; imp.filterMode = FilterMode.Bilinear;
|
||
imp.maxTextureSize = 128; imp.textureCompression = TextureImporterCompression.Uncompressed; imp.isReadable = false;
|
||
imp.SaveAndReimport();
|
||
}
|
||
var ramp = AssetDatabase.LoadAssetAtPath<Texture2D>(CombinedPath);
|
||
var mat = AssetDatabase.LoadAssetAtPath<Material>(BladeMatPath);
|
||
var s = AssetDatabase.LoadAssetAtPath<WL.Combat.SlashTrailSettings>(SettingsPath);
|
||
if (ramp == null || mat == null || s == null) return "load fail";
|
||
var white = new Gradient();
|
||
white.SetKeys(new[] { new GradientColorKey(Color.white, 0f), new GradientColorKey(Color.white, 1f) }, new[] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(1f, 1f) });
|
||
var swing = new Gradient();
|
||
swing.SetKeys(new[] { new GradientColorKey(Color.white, 0f), new GradientColorKey(Color.white, 1f) },
|
||
new[] { new GradientAlphaKey(0.0f, 0f), new GradientAlphaKey(0.85f, 0.15f), new GradientAlphaKey(1f, 0.5f), new GradientAlphaKey(1f, 1f) });
|
||
s.materialAdditive = mat; s.gradientAlongBlade = white; s.gradientAlongSwing = swing; s.bladeGradientWeight = 1f; s.colorIntensity = 1f; s.useGradients = true;
|
||
EditorUtility.SetDirty(s);
|
||
mat.SetTexture("_BaseMap", ramp); mat.SetFloat("_EdgeSoftness", edgeSoft); mat.SetFloat("_TailSoftness", tailSoft); mat.SetFloat("_HeadSoftness", 0.03f); mat.SetFloat("_Intensity", 1f);
|
||
EditorUtility.SetDirty(mat); AssetDatabase.SaveAssets(); AssetDatabase.Refresh();
|
||
return "baked " + CombinedPath + " (" + ramp.width + "x" + ramp.height + ") · mat/settings 갱신 · edge=" + edgeSoft + " tail=" + tailSoft + " gain=" + alphaGain + " gamma=" + alphaGamma + " floor=" + floorAlpha;
|
||
}
|
||
|
||
/// <summary>Edit: 램프 리본 기본 상태로 되돌린다 — Play 중 런타임 튜닝이 에셋 인스턴스에 남은 것(머티리얼 텍스처 dangling · burstMode · 클래스 오버라이드)을 명시적으로 굽는다.</summary>
|
||
public static object RestoreRampMode(float edgeSoft, float tailSoft, float intensity, int burstMode)
|
||
{
|
||
if (EditorApplication.isPlaying) return "ABORT: Play 중";
|
||
string r = (string)Bake(edgeSoft, tailSoft, intensity);
|
||
var s = AssetDatabase.LoadAssetAtPath<WL.Combat.SlashTrailSettings>(SettingsPath);
|
||
var mat = AssetDatabase.LoadAssetAtPath<Material>(BladeMatPath);
|
||
if (s == null || mat == null) return r + " | restore fail";
|
||
s.materialAdditive = mat;
|
||
s.burstMode = (WL.Combat.BurstMode)burstMode;
|
||
s.suppressMode = WL.Combat.SuppressMode.CrescentOnly;
|
||
s.useGradients = true; s.drawRibbon = true;
|
||
int n = 0;
|
||
foreach (var cid in new[] { 10101, 10102, 10201, 10202, 10501, 10502 }) { var o = s.FindOverride(cid); if (o != null && o.overrideLook) { o.overrideLook = false; n++; } }
|
||
EditorUtility.SetDirty(s); AssetDatabase.SaveAssets();
|
||
return r + " | restored: mat=" + s.materialAdditive.name + " tex=" + (mat.GetTexture("_BaseMap") != null ? mat.GetTexture("_BaseMap").name : "none") + " burst=" + s.burstMode + " overridesCleared=" + n;
|
||
}
|
||
|
||
public static object Dump()
|
||
{
|
||
var mat = AssetDatabase.LoadAssetAtPath<Material>(MatPath);
|
||
var s = AssetDatabase.LoadAssetAtPath<WL.Combat.SlashTrailSettings>(SettingsPath);
|
||
var sb = new StringBuilder();
|
||
if (mat != null) sb.AppendLine("mat tex=" + (mat.GetTexture("_BaseMap") != null ? mat.GetTexture("_BaseMap").name : "none") + " edge=" + mat.GetFloat("_EdgeSoftness") + " head=" + mat.GetFloat("_HeadSoftness") + " tail=" + mat.GetFloat("_TailSoftness") + " int=" + mat.GetFloat("_Intensity") + " blend=" + mat.GetFloat("_SrcBlend") + "/" + mat.GetFloat("_DstBlend"));
|
||
if (s != null) sb.AppendLine("settings drawRibbon=" + s.drawRibbon + " useGradients=" + s.useGradients + " bladeW=" + s.bladeGradientWeight + " intensity=" + s.colorIntensity + " fade=" + s.fadeSeconds + " blade(0)=" + s.gradientAlongBlade.Evaluate(0f) + " blade(1)=" + s.gradientAlongBlade.Evaluate(1f) + " swingA(0.15)=" + s.gradientAlongSwing.Evaluate(0.15f).a + " swingA(1)=" + s.gradientAlongSwing.Evaluate(1f).a);
|
||
var imp = AssetImporter.GetAtPath(TexPath) as TextureImporter;
|
||
if (imp != null) sb.AppendLine("tex importer srgb=" + imp.sRGBTexture + " mip=" + imp.mipmapEnabled + " wrap=" + imp.wrapMode + " max=" + imp.maxTextureSize + " comp=" + imp.textureCompression);
|
||
return sb.ToString();
|
||
}
|
||
}
|