Project_WL/AgentScripts/WL785_Ramp.cs

260 lines
17 KiB
C#
Raw Permalink Normal View History

WL 리뉴얼 세션 2026-09-06~07 (#760~#800) — 전투·배경·UI·타격감 (조직 PD 로그 #746~#800 · 대화로그 §14~§24) - 전투: Knight@Attack1~3_S 3콤보(4 컨트롤러·클래스별 서브클립) · 타겟팅 4규칙(정면 우선·재타겟·즉시 공격·어그로 금지 · WLTargetingSettings) · 대쉬 후 공격(Stander@Chase_Start · DashDriver · 5 m · 사정거리+0.8 · 근거리 적 우선) · 공격 이동 FrameTable(발 접지 실측 표 · AttackRootMotion) · 충돌 반경/투사체 0.3 배(WLCollisionTuning) · 무적(임시)·펫 금지(WLGameplaySettings) - 검기: NamuFX Slash_B 배리언트 Effect_WLSwingArc 원 피팅 정합 배치 + 캘리브레이션(SlashArcMeasure · SlashTrailSettings) · 찌르기 Effect_WLStab 대기 · 램프 리본(BladeTrail · WL_BladeRibbon.shader · T_WL_BladeRibbonRamp) 보존(drawRibbon 0) - 타격감: Assets/WL/Feel(WLHitFeel · 히트스톱 0.03 · 셰이크 0.10 m · 몹 펀치 1.12 · Actor.Get_Damage 훅 1줄 · 원본 RealCamera 셰이크 결함 대체) - 배경/맵: LMHPOLY Demo_01~10 → WL_Nature01~10(프리팹·씬·NavMesh·스포너·BattleMapConfig) · 물(ToonWaterU) · 포스트 블룸 0.9/0.3 · 잔디(BruteForce·드레싱) 제거 · 마젠타 머티리얼 URP 변환 - UI: 세로 HUD(WL_HUD · 하단 5메뉴 폭 전체 · 채팅/전투 패드 숨김 · WLIngameUiOverride) · Title/TitleInfo 1080×1920 Expand + 배경 높이 fit(WLBackgroundFit) · 로딩 SortOrder_5(WLRawImageAspectSync · 초점표 8장) · Loading1~8 ASTC 6×6 - 도구: AgentScripts/*(LightProbe · WL_MapSwitch · WL760~WL800 프로브/집행/검증 · 상단 사용법 주석) · 에디터 락 프로토콜 파일(staging) - 제외(별도 커밋 예정): Assets/LMHPOLY(703 MB) · Assets/Feel(422 MB) · Assets/Shinabro(300 MB) — 에셋 스토어/구 WL 팩 원본 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 23:24:25 +00:00
// 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();
}
}