Project_WL/AgentScripts/WL785_Ramp.cs

260 lines
17 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.

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