914 lines
52 KiB
C#
914 lines
52 KiB
C#
// run_script entry points for the "background looks dark" investigation (2026-09-05 · WL = himminji base · Unity 6000.3 · URP 17)
|
|
// unity command run_script --file AgentScripts/LightProbe.cs --entry LightProbe.Report
|
|
// unity command run_script --file AgentScripts/LightProbe.cs --entry LightProbe.Render --args '["00_current"]'
|
|
// unity command run_script --file AgentScripts/LightProbe.cs --entry LightProbe.SetAmbient --args '["trilight", 1.0]' // skybox | flat | trilight | update
|
|
// unity command run_script --file AgentScripts/LightProbe.cs --entry LightProbe.SetTint --args '[0.55, 0.8, 1.0]' // Toon Pro shadow/middle/light tint (property block · not persisted)
|
|
// unity command run_script --file AgentScripts/LightProbe.cs --entry LightProbe.SetLight --args '[1.0]' // directional light intensity
|
|
// unity command run_script --file AgentScripts/LightProbe.cs --entry LightProbe.Invoke --args '["TitleInfo", "OnClick_DevLogin"]'
|
|
// PowerShell 5.1: escape the JSON quotes — --args '[\"trilight\", 1.0]'
|
|
// Everything here is read-only or runtime-only (property blocks / RenderSettings in Play mode revert on stop). Renders go to Screenshots_WL/dark/ (outside Assets).
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
|
|
public static class LightProbe
|
|
{
|
|
const string OutDir = "Screenshots_WL/dark";
|
|
|
|
static void Section(StringBuilder sb, string name, Action body)
|
|
{
|
|
try { body(); }
|
|
catch (Exception e) { sb.Append("[").Append(name).Append(" failed: ").Append(e.GetType().Name).Append(' ').Append(e.Message).Append("]\n"); }
|
|
}
|
|
|
|
// property-or-field reader without compile-time dependency (URP/Core RP types)
|
|
static object Member(object obj, string name)
|
|
{
|
|
if (obj == null) return null;
|
|
var t = obj.GetType();
|
|
var pi = t.GetProperty(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
if (pi != null) return pi.GetValue(obj);
|
|
var fi = t.GetField(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
if (fi != null) return fi.GetValue(obj);
|
|
return "(no " + name + ")";
|
|
}
|
|
|
|
// ---------- state report ----------
|
|
public static object Report()
|
|
{
|
|
var sb = new StringBuilder();
|
|
Section(sb, "scene", () =>
|
|
{
|
|
sb.Append("playing=").Append(Application.isPlaying).Append(" activeScene=").Append(UnityEngine.SceneManagement.SceneManager.GetActiveScene().name).Append(" loadedScenes=");
|
|
for (int i = 0; i < UnityEngine.SceneManagement.SceneManager.sceneCount; i++) sb.Append(UnityEngine.SceneManagement.SceneManager.GetSceneAt(i).name).Append(',');
|
|
sb.Append('\n');
|
|
});
|
|
Section(sb, "renderSettings", () =>
|
|
{
|
|
sb.Append("ambientMode=").Append(RenderSettings.ambientMode).Append(" ambientIntensity=").Append(RenderSettings.ambientIntensity.ToString("F2"))
|
|
.Append(" reflectionIntensity=").Append(RenderSettings.reflectionIntensity.ToString("F2")).Append(" fog=").Append(RenderSettings.fog).Append('\n');
|
|
sb.Append("ambientLight=").Append(C(RenderSettings.ambientLight)).Append(" sky=").Append(C(RenderSettings.ambientSkyColor))
|
|
.Append(" equator=").Append(C(RenderSettings.ambientEquatorColor)).Append(" ground=").Append(C(RenderSettings.ambientGroundColor)).Append('\n');
|
|
sb.Append("skybox=").Append(RenderSettings.skybox ? RenderSettings.skybox.name + " (" + RenderSettings.skybox.shader.name + ")" : "null")
|
|
.Append(" sun=").Append(RenderSettings.sun ? RenderSettings.sun.name : "null").Append('\n');
|
|
var dirs = new[] { Vector3.up, Vector3.down, Vector3.forward, Vector3.right };
|
|
var res = new Color[dirs.Length];
|
|
RenderSettings.ambientProbe.Evaluate(dirs, res);
|
|
sb.Append("ambientProbe up=").Append(C(res[0])).Append(" down=").Append(C(res[1])).Append(" fwd=").Append(C(res[2])).Append(" right=").Append(C(res[3])).Append('\n');
|
|
});
|
|
Section(sb, "quality", () =>
|
|
{
|
|
sb.Append("quality=").Append(QualitySettings.GetQualityLevel()).Append(':').Append(QualitySettings.names[QualitySettings.GetQualityLevel()])
|
|
.Append(" shadows=").Append(QualitySettings.shadows).Append(" colorSpace=").Append(QualitySettings.activeColorSpace)
|
|
.Append(" pipeline=").Append(GraphicsSettings.currentRenderPipeline ? GraphicsSettings.currentRenderPipeline.name : "null").Append('\n');
|
|
var rp = GraphicsSettings.currentRenderPipeline;
|
|
if (rp != null)
|
|
foreach (var pn in new[] { "supportsMainLightShadows", "supportsHDR", "msaaSampleCount", "shadowDistance", "useRenderingLayers", "supportsCameraDepthTexture" })
|
|
sb.Append(" urp.").Append(pn).Append('=').Append(Member(rp, pn)).Append('\n');
|
|
});
|
|
Section(sb, "lights", () =>
|
|
{
|
|
var lights = UnityEngine.Object.FindObjectsByType<Light>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
|
sb.Append("lights=").Append(lights.Length).Append('\n');
|
|
foreach (var l in lights)
|
|
sb.Append(" ").Append(Path(l.transform)).Append(" type=").Append(l.type).Append(" enabled=").Append(l.enabled).Append(" activeInHierarchy=").Append(l.gameObject.activeInHierarchy)
|
|
.Append(" intensity=").Append(l.intensity.ToString("F2")).Append(" color=").Append(C(l.color)).Append(" euler=").Append(l.transform.eulerAngles.ToString("F0"))
|
|
.Append(" shadows=").Append(l.shadows).Append(" cullingMask=").Append(l.cullingMask).Append(" renderingLayerMask=").Append(l.renderingLayerMask).Append('\n');
|
|
});
|
|
Section(sb, "cameras", () =>
|
|
{
|
|
var cams = UnityEngine.Object.FindObjectsByType<Camera>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
|
sb.Append("cameras=").Append(cams.Length).Append(" main=").Append(Camera.main ? Path(Camera.main.transform) : "null").Append('\n');
|
|
var acdType = Type.GetType("UnityEngine.Rendering.Universal.UniversalAdditionalCameraData, Unity.RenderPipelines.Universal.Runtime");
|
|
foreach (var c in cams)
|
|
{
|
|
sb.Append(" ").Append(Path(c.transform)).Append(" enabled=").Append(c.enabled).Append(" depth=").Append(c.depth).Append(" pos=").Append(c.transform.position.ToString("F1"))
|
|
.Append(" euler=").Append(c.transform.eulerAngles.ToString("F0")).Append(" fov=").Append(c.fieldOfView.ToString("F1")).Append(" ortho=").Append(c.orthographic)
|
|
.Append(" cullingMask=").Append(c.cullingMask).Append(" clearFlags=").Append(c.clearFlags);
|
|
var acd = acdType != null ? c.GetComponent(acdType) : null;
|
|
if (acd != null)
|
|
foreach (var pn in new[] { "renderType", "renderPostProcessing", "volumeLayerMask", "antialiasing", "renderShadows" })
|
|
sb.Append(' ').Append(pn).Append('=').Append(Member(acd, pn));
|
|
sb.Append('\n');
|
|
}
|
|
});
|
|
Section(sb, "volumes", () =>
|
|
{
|
|
var volType = Type.GetType("UnityEngine.Rendering.Volume, Unity.RenderPipelines.Core.Runtime");
|
|
if (volType == null) { sb.Append("volumes: type not found\n"); return; }
|
|
var vols = UnityEngine.Object.FindObjectsByType(volType, FindObjectsInactive.Include, FindObjectsSortMode.None);
|
|
sb.Append("volumes=").Append(vols.Length).Append('\n');
|
|
foreach (var v in vols)
|
|
{
|
|
var beh = (Behaviour)v;
|
|
var prof = Member(v, "sharedProfile") as ScriptableObject;
|
|
sb.Append(" ").Append(Path(beh.transform)).Append(" enabled=").Append(beh.enabled).Append(" activeInHierarchy=").Append(beh.gameObject.activeInHierarchy)
|
|
.Append(" global=").Append(Member(v, "isGlobal")).Append(" weight=").Append(Member(v, "weight")).Append(" priority=").Append(Member(v, "priority"))
|
|
.Append(" profile=").Append(prof ? prof.name : "null");
|
|
var comps = prof != null ? Member(prof, "components") as System.Collections.IList : null;
|
|
if (comps != null)
|
|
{
|
|
sb.Append(" components=[");
|
|
foreach (var comp in comps)
|
|
{
|
|
var so = comp as ScriptableObject;
|
|
if (so == null) continue;
|
|
var active = Member(so, "active");
|
|
sb.Append(so.GetType().Name).Append(active is bool b && !b ? "(off)" : "").Append(',');
|
|
}
|
|
sb.Append(']');
|
|
}
|
|
sb.Append('\n');
|
|
}
|
|
});
|
|
Section(sb, "census", () =>
|
|
{
|
|
var rends = UnityEngine.Object.FindObjectsByType<Renderer>(FindObjectsSortMode.None);
|
|
var byShader = new Dictionary<string, int>();
|
|
int slots = 0;
|
|
foreach (var r in rends)
|
|
{
|
|
if (r is ParticleSystemRenderer) continue;
|
|
foreach (var m in r.sharedMaterials)
|
|
{
|
|
if (m == null || m.shader == null) continue;
|
|
slots++;
|
|
byShader[m.shader.name] = byShader.TryGetValue(m.shader.name, out var n) ? n + 1 : 1;
|
|
}
|
|
}
|
|
sb.Append("renderers=").Append(rends.Length).Append(" materialSlots=").Append(slots).Append('\n');
|
|
foreach (var kv in byShader) sb.Append(" ").Append(kv.Value).Append(" ").Append(kv.Key).Append('\n');
|
|
});
|
|
return sb.ToString();
|
|
}
|
|
|
|
// ---------- render from the main camera's pose ----------
|
|
public static object Render(string tag)
|
|
{
|
|
var cam = Camera.main;
|
|
if (cam == null) return "no Camera.main";
|
|
return RenderFrom(cam, tag);
|
|
}
|
|
|
|
static object RenderFrom(Camera cam, string tag)
|
|
{
|
|
System.IO.Directory.CreateDirectory(OutDir);
|
|
int w = 1280, h = 720;
|
|
var rt = new RenderTexture(w, h, 24, RenderTextureFormat.ARGB32);
|
|
var go = new GameObject("__lightProbeCam");
|
|
var c = go.AddComponent<Camera>();
|
|
c.CopyFrom(cam);
|
|
c.targetTexture = rt;
|
|
c.enabled = false;
|
|
c.Render();
|
|
var prev = RenderTexture.active;
|
|
RenderTexture.active = rt;
|
|
var tex = new Texture2D(w, h, TextureFormat.RGB24, false);
|
|
tex.ReadPixels(new Rect(0, 0, w, h), 0, 0);
|
|
tex.Apply();
|
|
RenderTexture.active = prev;
|
|
var px = tex.GetPixels32();
|
|
double sum = 0; int dark = 0, clip = 0;
|
|
foreach (var p in px) { double l = 0.2126 * p.r + 0.7152 * p.g + 0.0722 * p.b; sum += l; if (l < 40) dark++; if (l > 245) clip++; }
|
|
float mean = (float)(sum / px.Length / 255.0);
|
|
float darkRatio = (float)dark / px.Length;
|
|
float clipRatio = (float)clip / px.Length;
|
|
var path = System.IO.Path.Combine(OutDir, tag + ".png");
|
|
System.IO.File.WriteAllBytes(path, tex.EncodeToPNG());
|
|
UnityEngine.Object.DestroyImmediate(tex);
|
|
c.targetTexture = null;
|
|
UnityEngine.Object.DestroyImmediate(go);
|
|
rt.Release();
|
|
UnityEngine.Object.DestroyImmediate(rt);
|
|
return path + " meanLum=" + mean.ToString("F3") + " darkRatio(<40/255)=" + darkRatio.ToString("F3") + " clipRatio(>245/255)=" + clipRatio.ToString("F3") + " camPos=" + cam.transform.position.ToString("F1") + " camEuler=" + cam.transform.eulerAngles.ToString("F0");
|
|
}
|
|
|
|
// Trilight with explicit linear colors (A/B of candidate MapData defaults without recompiling)
|
|
public static object SetTrilightRGB(float sr, float sg, float sb, float er, float eg, float eb, float gr, float gg, float gb)
|
|
{
|
|
RenderSettings.ambientMode = AmbientMode.Trilight;
|
|
RenderSettings.ambientSkyColor = new Color(sr, sg, sb, 1f);
|
|
RenderSettings.ambientEquatorColor = new Color(er, eg, eb, 1f);
|
|
RenderSettings.ambientGroundColor = new Color(gr, gg, gb, 1f);
|
|
var dirs = new[] { Vector3.up, Vector3.down };
|
|
var res = new Color[2];
|
|
RenderSettings.ambientProbe.Evaluate(dirs, res);
|
|
return "trilight sky=" + C(RenderSettings.ambientSkyColor) + " equator=" + C(RenderSettings.ambientEquatorColor) + " ground=" + C(RenderSettings.ambientGroundColor) + " probe up=" + C(res[0]) + " down=" + C(res[1]);
|
|
}
|
|
|
|
// Edit-mode only: set the directional light intensity inside a map prefab asset (data lives in the prefab · C45)
|
|
public static object SetPrefabDirectionalLight(string prefabPath, float intensity)
|
|
{
|
|
if (Application.isPlaying) return "refused: not in Play mode (asset edit)";
|
|
var root = PrefabUtility.LoadPrefabContents(prefabPath);
|
|
int n = 0; var before = new StringBuilder();
|
|
foreach (var l in root.GetComponentsInChildren<Light>(true))
|
|
if (l.type == LightType.Directional) { before.Append(l.name).Append('=').Append(l.intensity.ToString("F2")).Append(' '); l.intensity = intensity; n++; }
|
|
PrefabUtility.SaveAsPrefabAsset(root, prefabPath);
|
|
PrefabUtility.UnloadPrefabContents(root);
|
|
return prefabPath + " directional lights " + n + " (was " + before.ToString().Trim() + ") -> " + intensity;
|
|
}
|
|
|
|
public static object InvokeInt(string typeName, string method, int arg)
|
|
{
|
|
var t = Type.GetType(typeName + ", Assembly-CSharp");
|
|
if (t == null) return "type not found: " + typeName;
|
|
var obj = UnityEngine.Object.FindFirstObjectByType(t, FindObjectsInactive.Include);
|
|
if (obj == null) return "instance not found: " + typeName;
|
|
var mi = t.GetMethod(method, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(int) }, null);
|
|
if (mi == null) return "method(int) not found: " + method;
|
|
var r = mi.Invoke(obj, new object[] { arg });
|
|
return "invoked " + typeName + "." + method + "(" + arg + ") -> " + (r ?? "void");
|
|
}
|
|
|
|
// Dump public instance fields (Color/float/int/bool/enum/string) of the first live instance of a game type
|
|
public static object DumpFields(string typeName)
|
|
{
|
|
var t = Type.GetType(typeName + ", Assembly-CSharp");
|
|
if (t == null) return "type not found: " + typeName;
|
|
var objs = UnityEngine.Object.FindObjectsByType(t, FindObjectsInactive.Include, FindObjectsSortMode.None);
|
|
if (objs.Length == 0) return "no instance: " + typeName;
|
|
var sb = new StringBuilder();
|
|
foreach (var o in objs)
|
|
{
|
|
var comp = o as Component;
|
|
sb.Append(comp != null ? Path(comp.transform) : o.name).Append(" (").Append(o.GetInstanceID()).Append(")\n");
|
|
foreach (var fi in t.GetFields(BindingFlags.Public | BindingFlags.Instance))
|
|
{
|
|
var ft = fi.FieldType;
|
|
if (ft == typeof(Color) || ft == typeof(float) || ft == typeof(int) || ft == typeof(bool) || ft.IsEnum || ft == typeof(string))
|
|
{
|
|
var v = fi.GetValue(o);
|
|
sb.Append(" ").Append(fi.Name).Append(" = ").Append(v is Color c ? C(c) : (v ?? "null").ToString()).Append('\n');
|
|
}
|
|
}
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
|
|
// Edit-mode only: write MapData ambient colors into a map prefab (values live in the prefab · C45)
|
|
public static object SetPrefabMapDataAmbient(string prefabPath, float sr, float sg, float sb, float er, float eg, float eb, float gr, float gg, float gb)
|
|
{
|
|
if (Application.isPlaying) return "refused: not in Play mode (asset edit)";
|
|
var t = Type.GetType("MapData, Assembly-CSharp");
|
|
if (t == null) return "MapData type not found";
|
|
var root = PrefabUtility.LoadPrefabContents(prefabPath);
|
|
var md = root.GetComponentInChildren(t, true);
|
|
if (md == null) { PrefabUtility.UnloadPrefabContents(root); return "no MapData in " + prefabPath; }
|
|
t.GetField("ambientSky").SetValue(md, new Color(sr, sg, sb, 1f));
|
|
t.GetField("ambientEquator").SetValue(md, new Color(er, eg, eb, 1f));
|
|
t.GetField("ambientGround").SetValue(md, new Color(gr, gg, gb, 1f));
|
|
PrefabUtility.SaveAsPrefabAsset(root, prefabPath);
|
|
PrefabUtility.UnloadPrefabContents(root);
|
|
return prefabPath + " MapData ambient -> sky(" + sr + "," + sg + "," + sb + ") equator(" + er + "," + eg + "," + eb + ") ground(" + gr + "," + gg + "," + gb + ")";
|
|
}
|
|
|
|
// Edit-mode: read the compiled C# default of a MonoBehaviour field by instantiating a throwaway component (detects stale compiles)
|
|
public static object CheckDefault(string typeName, string field)
|
|
{
|
|
var t = Type.GetType(typeName + ", Assembly-CSharp");
|
|
if (t == null) return "type not found: " + typeName;
|
|
var go = new GameObject("__defaultProbe");
|
|
try
|
|
{
|
|
var c = go.AddComponent(t);
|
|
var fi = t.GetField(field);
|
|
if (fi == null) return "field not found: " + field;
|
|
var v = fi.GetValue(c);
|
|
return typeName + "." + field + " compiled default = " + (v is Color col ? C(col) : (v ?? "null").ToString());
|
|
}
|
|
finally { UnityEngine.Object.DestroyImmediate(go); }
|
|
}
|
|
|
|
// Edit-mode: force asset re-import of changed scripts and request compilation (the editor does not auto-refresh while unfocused)
|
|
public static object RefreshAndCompile()
|
|
{
|
|
if (Application.isPlaying) return "refused: Play mode";
|
|
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
|
|
UnityEditor.Compilation.CompilationPipeline.RequestScriptCompilation();
|
|
return "AssetDatabase.Refresh + RequestScriptCompilation issued";
|
|
}
|
|
|
|
// Edit-mode: force re-import of map prefabs so in-memory prefab assets drop stale serialized field values
|
|
// (after adding/changing a [SerializeField] default, assets already loaded in the editor keep the OLD value across domain reloads)
|
|
public static object ReimportMapPrefabs()
|
|
{
|
|
if (Application.isPlaying) return "refused: Play mode";
|
|
var guids = AssetDatabase.FindAssets("t:Prefab", new[] { "Assets/Res_Addr/Map" });
|
|
var sb = new StringBuilder();
|
|
foreach (var g in guids)
|
|
{
|
|
var p = AssetDatabase.GUIDToAssetPath(g);
|
|
AssetDatabase.ImportAsset(p, ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport);
|
|
sb.Append(System.IO.Path.GetFileNameWithoutExtension(p)).Append(' ');
|
|
}
|
|
Resources.UnloadUnusedAssets();
|
|
return "reimported " + guids.Length + ": " + sb.ToString().Trim();
|
|
}
|
|
|
|
// Edit-mode: add a game component (by type name · Assembly-CSharp) to a prefab root if missing, then save the prefab
|
|
public static object AddComponentToPrefabRoot(string prefabPath, string typeName)
|
|
{
|
|
if (Application.isPlaying) return "refused: Play mode";
|
|
var t = Type.GetType(typeName + ", Assembly-CSharp");
|
|
if (t == null) return "type not found: " + typeName;
|
|
var root = PrefabUtility.LoadPrefabContents(prefabPath);
|
|
try
|
|
{
|
|
if (root.GetComponent(t) != null) return prefabPath + " already has " + typeName;
|
|
root.AddComponent(t);
|
|
PrefabUtility.SaveAsPrefabAsset(root, prefabPath);
|
|
return prefabPath + " += " + typeName + " (root)";
|
|
}
|
|
finally { PrefabUtility.UnloadPrefabContents(root); }
|
|
}
|
|
|
|
// Dump ALL instance fields (public + private) incl. List<bool>/List<int>/List<string> of the first live instance
|
|
public static object DumpFieldsAll(string typeName)
|
|
{
|
|
var t = Type.GetType(typeName + ", Assembly-CSharp");
|
|
if (t == null) return "type not found: " + typeName;
|
|
var o = UnityEngine.Object.FindFirstObjectByType(t, FindObjectsInactive.Include);
|
|
if (o == null) return "no instance: " + typeName;
|
|
var sb = new StringBuilder();
|
|
var comp = o as Component;
|
|
sb.Append(comp != null ? Path(comp.transform) + " activeInHierarchy=" + comp.gameObject.activeInHierarchy : o.name).Append('\n');
|
|
for (var tt = t; tt != null && tt != typeof(MonoBehaviour); tt = tt.BaseType)
|
|
foreach (var fi in tt.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly))
|
|
{
|
|
var v = fi.GetValue(o);
|
|
string s;
|
|
if (v == null) s = "null";
|
|
else if (v is System.Collections.IList list && !(v is Array && ((Array)v).Rank > 1))
|
|
{
|
|
var parts = new List<string>();
|
|
foreach (var it in list) parts.Add(it is UnityEngine.Object uo ? (uo ? uo.name : "null") : (it ?? "null").ToString());
|
|
s = "[" + string.Join(",", parts) + "] (" + list.Count + ")";
|
|
}
|
|
else if (v is UnityEngine.Object uo2) s = uo2 ? uo2.name + (uo2 is GameObject g ? " active=" + g.activeInHierarchy : "") : "null";
|
|
else if (v is Color c) s = C(c);
|
|
else s = v.ToString();
|
|
if (s.Length > 160) s = s.Substring(0, 160) + "…";
|
|
sb.Append(" ").Append(fi.Name).Append(" = ").Append(s).Append('\n');
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
|
|
// Edit-mode: set MapData ambient mode/intensity/updateEnvironment flag in a map prefab (C45 · values live in the prefab)
|
|
public static object SetPrefabMapDataAmbientMode(string prefabPath, string mode, float intensity, bool updateEnv)
|
|
{
|
|
if (Application.isPlaying) return "refused: not in Play mode (asset edit)";
|
|
var t = Type.GetType("MapData, Assembly-CSharp");
|
|
if (t == null) return "MapData type not found";
|
|
AmbientMode am;
|
|
switch (mode) { case "skybox": am = AmbientMode.Skybox; break; case "flat": am = AmbientMode.Flat; break; case "trilight": am = AmbientMode.Trilight; break; default: return "unknown mode " + mode; }
|
|
var root = PrefabUtility.LoadPrefabContents(prefabPath);
|
|
try
|
|
{
|
|
var md = root.GetComponentInChildren(t, true);
|
|
if (md == null) return "no MapData in " + prefabPath;
|
|
t.GetField("ambientMode").SetValue(md, am);
|
|
t.GetField("ambientIntensity").SetValue(md, intensity);
|
|
t.GetField("updateEnvironmentFromSkybox").SetValue(md, updateEnv);
|
|
var sky = t.GetField("material_skybox").GetValue(md) as Material;
|
|
PrefabUtility.SaveAsPrefabAsset(root, prefabPath);
|
|
return prefabPath + " MapData ambientMode=" + am + " intensity=" + intensity + " updateEnv=" + updateEnv + " skybox=" + (sky ? sky.name : "NULL");
|
|
}
|
|
finally { PrefabUtility.UnloadPrefabContents(root); }
|
|
}
|
|
|
|
// Edit-mode: point an AnimatorController state's motion to an AnimationClip sub-asset (by clip name) of an FBX/anim asset
|
|
public static object SetControllerStateMotion(string controllerPath, string stateName, string clipAssetPath, string clipName)
|
|
{
|
|
if (Application.isPlaying) return "refused: Play mode";
|
|
var ctrl = AssetDatabase.LoadAssetAtPath<UnityEditor.Animations.AnimatorController>(controllerPath);
|
|
if (ctrl == null) return "controller not found: " + controllerPath;
|
|
AnimationClip clip = null;
|
|
foreach (var o in AssetDatabase.LoadAllAssetRepresentationsAtPath(clipAssetPath))
|
|
if (o is AnimationClip ac && (string.IsNullOrEmpty(clipName) || ac.name == clipName)) { clip = ac; break; }
|
|
if (clip == null) clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(clipAssetPath);
|
|
if (clip == null) return "clip not found: " + clipAssetPath + " / " + clipName;
|
|
int n = 0; string was = "";
|
|
foreach (var layer in ctrl.layers)
|
|
foreach (var cs in layer.stateMachine.states)
|
|
if (cs.state.name == stateName) { was = cs.state.motion ? cs.state.motion.name : "null"; cs.state.motion = clip; n++; }
|
|
if (n == 0) return "state not found: " + stateName;
|
|
EditorUtility.SetDirty(ctrl);
|
|
AssetDatabase.SaveAssets();
|
|
return controllerPath + " state '" + stateName + "' motion " + was + " -> " + clip.name + " (" + clip.length.ToString("F2") + "s · " + n + " state(s))";
|
|
}
|
|
|
|
// Play-mode: what is the player's animator playing right now?
|
|
public static object PlayerAnimInfo()
|
|
{
|
|
var mv = Type.GetType("MyValue, Assembly-CSharp");
|
|
if (mv == null) return "MyValue not found";
|
|
var pcProp = mv.GetProperty("MyPC", BindingFlags.Public | BindingFlags.Static) ?? null;
|
|
object pc = pcProp != null ? pcProp.GetValue(null) : mv.GetField("MyPC", BindingFlags.Public | BindingFlags.Static)?.GetValue(null);
|
|
var comp = pc as Component;
|
|
if (comp == null) return "MyPC null";
|
|
var anim = comp.GetComponentInChildren<Animator>();
|
|
if (anim == null) return "no Animator on " + Path(comp.transform);
|
|
var sb = new StringBuilder();
|
|
sb.Append("pc=").Append(Path(comp.transform)).Append(" pos=").Append(comp.transform.position.ToString("F2"))
|
|
.Append(" controller=").Append(anim.runtimeAnimatorController ? anim.runtimeAnimatorController.name : "null").Append(" speed=").Append(anim.speed).Append('\n');
|
|
var info = anim.GetCurrentAnimatorClipInfo(0);
|
|
foreach (var ci in info) sb.Append(" clip=").Append(ci.clip.name).Append(" w=").Append(ci.weight.ToString("F2")).Append(" len=").Append(ci.clip.length.ToString("F2")).Append('\n');
|
|
var st = anim.GetCurrentAnimatorStateInfo(0);
|
|
sb.Append(" stateHash=").Append(st.shortNameHash).Append(" normalizedTime=").Append(st.normalizedTime.ToString("F2")).Append('\n');
|
|
return sb.ToString();
|
|
}
|
|
|
|
// Play-mode: are weapons attached under the player's weapon sockets?
|
|
public static object PlayerWeaponInfo()
|
|
{
|
|
var mv = Type.GetType("MyValue, Assembly-CSharp");
|
|
var pc = mv?.GetProperty("MyPC", BindingFlags.Public | BindingFlags.Static)?.GetValue(null) as Component;
|
|
if (pc == null) return "MyPC null";
|
|
var fi = pc.GetType().GetField("tfs_weapon", BindingFlags.Public | BindingFlags.Instance);
|
|
var sockets = fi?.GetValue(pc) as Transform[];
|
|
if (sockets == null) return "tfs_weapon not found";
|
|
var sb = new StringBuilder(); int total = 0;
|
|
for (int i = 0; i < sockets.Length; i++)
|
|
{
|
|
var s = sockets[i]; if (s == null) { sb.Append(" [").Append(i).Append("] null\n"); continue; }
|
|
sb.Append(" [").Append(i).Append("] ").Append(Path(s)).Append(" children=").Append(s.childCount);
|
|
for (int c = 0; c < s.childCount; c++) { var ch = s.GetChild(c); total++; sb.Append(" · ").Append(ch.name).Append(ch.gameObject.activeInHierarchy ? "(on)" : "(off)"); var r = ch.GetComponentInChildren<Renderer>(); if (r) sb.Append(" renderer=").Append(r.enabled ? "on" : "off"); }
|
|
sb.Append('\n');
|
|
}
|
|
return "weapon objects=" + total + "\n" + sb.ToString();
|
|
}
|
|
|
|
// Play-mode: drive the player's joystick path n times (same frame) and report distance → speed = dist / (n · dt)
|
|
public static object JoystickBurst(int n)
|
|
{
|
|
var mv = Type.GetType("MyValue, Assembly-CSharp");
|
|
var pcProp = mv?.GetProperty("MyPC", BindingFlags.Public | BindingFlags.Static);
|
|
var pc = pcProp?.GetValue(null) as Component;
|
|
if (pc == null) return "MyPC null";
|
|
var mi = pc.GetType().GetMethod("Run_byJoystick", BindingFlags.Public | BindingFlags.Instance);
|
|
var gm = pc.GetType().GetMethod("Get_MoveSpeed", BindingFlags.Public | BindingFlags.Instance);
|
|
if (mi == null) return "Run_byJoystick not found";
|
|
var before = pc.transform.position;
|
|
var dir = pc.transform.forward; dir.y = 0; dir.Normalize();
|
|
for (int i = 0; i < n; i++) mi.Invoke(pc, new object[] { dir });
|
|
var after = pc.transform.position;
|
|
float dist = Vector3.Distance(before, after);
|
|
float dt = Time.deltaTime;
|
|
return "n=" + n + " dt=" + dt.ToString("F4") + " dist=" + dist.ToString("F3") + " -> speed≈" + (dt > 0 ? (dist / (n * dt)).ToString("F2") : "?") + " m/s · Get_MoveSpeed=" + (gm != null ? gm.Invoke(pc, null).ToString() : "?");
|
|
}
|
|
|
|
// Play-mode: list active UI Images/RawImages covering most of the screen (overlay/fade suspects) with color & alpha
|
|
public static object OverlayCensus()
|
|
{
|
|
var sb = new StringBuilder();
|
|
int n = 0;
|
|
foreach (var g in UnityEngine.Object.FindObjectsByType<UnityEngine.UI.Graphic>(FindObjectsSortMode.None))
|
|
{
|
|
if (!g.isActiveAndEnabled) continue;
|
|
var rt = g.rectTransform;
|
|
var canvas = g.canvas; if (canvas == null) continue;
|
|
var corners = new Vector3[4]; rt.GetWorldCorners(corners);
|
|
var cam = canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : canvas.worldCamera;
|
|
Vector2 min = new Vector2(float.MaxValue, float.MaxValue), max = new Vector2(float.MinValue, float.MinValue);
|
|
foreach (var c in corners)
|
|
{
|
|
Vector2 sp = cam ? (Vector2)cam.WorldToScreenPoint(c) : (Vector2)c;
|
|
min = Vector2.Min(min, sp); max = Vector2.Max(max, sp);
|
|
}
|
|
float cover = Mathf.Clamp01((max.x - min.x) / Screen.width) * Mathf.Clamp01((max.y - min.y) / Screen.height);
|
|
if (cover < 0.6f) continue;
|
|
var col = g.color; var cg = g.GetComponentInParent<CanvasGroup>();
|
|
float a = col.a * (cg ? cg.alpha : 1f);
|
|
if (a <= 0.01f) continue;
|
|
n++;
|
|
sb.Append(" ").Append(Path(g.transform)).Append(" type=").Append(g.GetType().Name).Append(" cover=").Append(cover.ToString("F2"))
|
|
.Append(" color=").Append(C(col)).Append(" a=").Append(a.ToString("F2")).Append(" canvas=").Append(canvas.name).Append(" order=").Append(canvas.sortingOrder)
|
|
.Append(" mat=").Append(g.material ? g.material.name : "null");
|
|
if (g is UnityEngine.UI.Image im) sb.Append(" sprite=").Append(im.sprite ? im.sprite.name : "null");
|
|
if (g is UnityEngine.UI.RawImage ri) sb.Append(" tex=").Append(ri.texture ? ri.texture.name : "null");
|
|
sb.Append('\n');
|
|
}
|
|
return "fullscreen-ish visible graphics = " + n + "\n" + sb.ToString();
|
|
}
|
|
|
|
// Edit-mode: add (or update) a directional light child on a map prefab root · values live in the prefab (C45)
|
|
public static object AddDirectionalLightToPrefab(string prefabPath, string goName, float intensity, float r, float g, float b, float rotX, float rotY, float rotZ)
|
|
{
|
|
if (Application.isPlaying) return "refused: Play mode";
|
|
var root = PrefabUtility.LoadPrefabContents(prefabPath);
|
|
try
|
|
{
|
|
Light light = null;
|
|
foreach (var l in root.GetComponentsInChildren<Light>(true)) if (l.type == LightType.Directional) { light = l; break; }
|
|
string action;
|
|
if (light == null)
|
|
{
|
|
var go = new GameObject(goName);
|
|
go.transform.SetParent(root.transform, false);
|
|
light = go.AddComponent<Light>();
|
|
light.type = LightType.Directional;
|
|
light.shadows = LightShadows.Soft;
|
|
action = "added";
|
|
}
|
|
else action = "updated " + light.name;
|
|
light.intensity = intensity;
|
|
light.color = new Color(r, g, b, 1f);
|
|
light.transform.rotation = Quaternion.Euler(rotX, rotY, rotZ);
|
|
PrefabUtility.SaveAsPrefabAsset(root, prefabPath);
|
|
return prefabPath + " directional light " + action + ": intensity=" + intensity + " color=" + C(light.color) + " euler=(" + rotX + "," + rotY + "," + rotZ + ")";
|
|
}
|
|
finally { PrefabUtility.UnloadPrefabContents(root); }
|
|
}
|
|
|
|
// Edit-mode: set a bool field on MapData inside a map prefab (e.g. showWeaponInLobby)
|
|
public static object SetPrefabMapDataBool(string prefabPath, string field, bool value)
|
|
{
|
|
if (Application.isPlaying) return "refused: Play mode";
|
|
var t = Type.GetType("MapData, Assembly-CSharp");
|
|
var fi = t?.GetField(field);
|
|
if (fi == null) return "MapData field not found: " + field + " (recompile first?)";
|
|
var root = PrefabUtility.LoadPrefabContents(prefabPath);
|
|
try
|
|
{
|
|
var md = root.GetComponentInChildren(t, true);
|
|
if (md == null) return "no MapData";
|
|
fi.SetValue(md, value);
|
|
PrefabUtility.SaveAsPrefabAsset(root, prefabPath);
|
|
return prefabPath + " MapData." + field + "=" + value;
|
|
}
|
|
finally { PrefabUtility.UnloadPrefabContents(root); }
|
|
}
|
|
|
|
// Edit-mode: set a float field on the first component of a given type (Assembly-CSharp) inside a prefab
|
|
public static object SetPrefabComponentFloat(string prefabPath, string typeName, string field, float value)
|
|
{
|
|
if (Application.isPlaying) return "refused: Play mode";
|
|
var t = Type.GetType(typeName + ", Assembly-CSharp");
|
|
var fi = t?.GetField(field, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
if (fi == null) return "field not found: " + typeName + "." + field;
|
|
var root = PrefabUtility.LoadPrefabContents(prefabPath);
|
|
try
|
|
{
|
|
var c = root.GetComponentInChildren(t, true);
|
|
if (c == null) return "no " + typeName + " in " + prefabPath;
|
|
var was = fi.GetValue(c);
|
|
fi.SetValue(c, Convert.ChangeType(value, fi.FieldType));
|
|
PrefabUtility.SaveAsPrefabAsset(root, prefabPath);
|
|
return prefabPath + " " + typeName + "." + field + " " + was + " -> " + value;
|
|
}
|
|
finally { PrefabUtility.UnloadPrefabContents(root); }
|
|
}
|
|
|
|
// Edit-mode: set a float serialized property (supports paths like arr_cCamData.Array.data[0].cameraHeight) on the first component of a type inside a prefab
|
|
public static object SetPrefabSerializedFloat(string prefabPath, string typeName, string propertyPath, float value)
|
|
{
|
|
if (Application.isPlaying) return "refused: Play mode";
|
|
var t = Type.GetType(typeName + ", Assembly-CSharp");
|
|
if (t == null) return "type not found: " + typeName;
|
|
var root = PrefabUtility.LoadPrefabContents(prefabPath);
|
|
try
|
|
{
|
|
var c = root.GetComponentInChildren(t, true);
|
|
if (c == null) return "no " + typeName + " in " + prefabPath;
|
|
var ser = new SerializedObject(c);
|
|
var p = ser.FindProperty(propertyPath);
|
|
if (p == null) return "property not found: " + propertyPath;
|
|
string was = p.propertyType == SerializedPropertyType.Float ? p.floatValue.ToString("F3") : p.propertyType.ToString();
|
|
if (p.propertyType != SerializedPropertyType.Float) return "not a float: " + propertyPath + " (" + p.propertyType + ")";
|
|
p.floatValue = value;
|
|
ser.ApplyModifiedPropertiesWithoutUndo();
|
|
PrefabUtility.SaveAsPrefabAsset(root, prefabPath);
|
|
return prefabPath + " " + typeName + "." + propertyPath + " " + was + " -> " + value;
|
|
}
|
|
finally { PrefabUtility.UnloadPrefabContents(root); }
|
|
}
|
|
|
|
// Edit-mode: set a float field on a ScriptableObject asset (e.g. CameraLeadSettings.asset)
|
|
public static object SetAssetFloat(string assetPath, string field, float value)
|
|
{
|
|
if (Application.isPlaying) return "refused: Play mode";
|
|
var so = AssetDatabase.LoadMainAssetAtPath(assetPath) as ScriptableObject;
|
|
if (so == null) return "asset not found: " + assetPath;
|
|
var ser = new SerializedObject(so);
|
|
var p = ser.FindProperty(field);
|
|
if (p == null) return "field not found: " + field + " (recompiled?)";
|
|
string was = p.propertyType == SerializedPropertyType.Float ? p.floatValue.ToString("F3") : p.propertyType.ToString();
|
|
if (p.propertyType == SerializedPropertyType.Float) p.floatValue = value;
|
|
else if (p.propertyType == SerializedPropertyType.Boolean) p.boolValue = value > 0.5f;
|
|
else return "unsupported type " + p.propertyType;
|
|
ser.ApplyModifiedPropertiesWithoutUndo(); EditorUtility.SetDirty(so); AssetDatabase.SaveAssets();
|
|
return assetPath + " " + field + " " + was + " -> " + value;
|
|
}
|
|
|
|
// Edit-mode: set MapData.ambientIntensity only
|
|
public static object SetPrefabAmbientIntensity(string prefabPath, float intensity)
|
|
{
|
|
if (Application.isPlaying) return "refused: Play mode";
|
|
var t = Type.GetType("MapData, Assembly-CSharp");
|
|
var root = PrefabUtility.LoadPrefabContents(prefabPath);
|
|
try
|
|
{
|
|
var md = root.GetComponentInChildren(t, true);
|
|
if (md == null) return "no MapData";
|
|
t.GetField("ambientIntensity").SetValue(md, intensity);
|
|
PrefabUtility.SaveAsPrefabAsset(root, prefabPath);
|
|
return prefabPath + " ambientIntensity=" + intensity;
|
|
}
|
|
finally { PrefabUtility.UnloadPrefabContents(root); }
|
|
}
|
|
|
|
// Edit-mode: enable/disable every Volume (post-processing) component inside a prefab · reversible data change
|
|
public static object SetPrefabVolumesEnabled(string prefabPath, bool enabled)
|
|
{
|
|
if (Application.isPlaying) return "refused: Play mode";
|
|
var volType = Type.GetType("UnityEngine.Rendering.Volume, Unity.RenderPipelines.Core.Runtime");
|
|
if (volType == null) return "Volume type not found";
|
|
var root = PrefabUtility.LoadPrefabContents(prefabPath);
|
|
try
|
|
{
|
|
var sb = new StringBuilder(); int n = 0;
|
|
foreach (var c in root.GetComponentsInChildren(volType, true))
|
|
{
|
|
var beh = (Behaviour)c; beh.enabled = enabled; n++;
|
|
var prof = Member(c, "sharedProfile") as ScriptableObject;
|
|
sb.Append(Path(beh.transform)).Append(" profile=").Append(prof ? prof.name : "null").Append(" -> enabled=").Append(enabled).Append("; ");
|
|
}
|
|
if (n == 0) return "no Volume in " + prefabPath;
|
|
PrefabUtility.SaveAsPrefabAsset(root, prefabPath);
|
|
return prefabPath + " volumes=" + n + ": " + sb.ToString();
|
|
}
|
|
finally { PrefabUtility.UnloadPrefabContents(root); }
|
|
}
|
|
|
|
// Edit-mode: clone a VolumeProfile asset (keeps all overrides) so we never edit the pack's original
|
|
public static object ClonePostProfile(string srcPath, string dstPath)
|
|
{
|
|
if (Application.isPlaying) return "refused: Play mode";
|
|
var dir = System.IO.Path.GetDirectoryName(dstPath).Replace('\\', '/');
|
|
if (!AssetDatabase.IsValidFolder(dir)) { var parent = System.IO.Path.GetDirectoryName(dir).Replace('\\', '/'); AssetDatabase.CreateFolder(parent, System.IO.Path.GetFileName(dir)); }
|
|
if (AssetDatabase.LoadMainAssetAtPath(dstPath) != null) return dstPath + " already exists";
|
|
bool ok = AssetDatabase.CopyAsset(srcPath, dstPath);
|
|
AssetDatabase.SaveAssets();
|
|
return (ok ? "cloned " : "FAILED ") + srcPath + " -> " + dstPath;
|
|
}
|
|
|
|
// Edit-mode: set a volume parameter on a profile: component type short name (e.g. ColorAdjustments), parameter field (e.g. postExposure), value.
|
|
// kind: "float" | "bool"(active flag of the component when field=="active") | "int"
|
|
public static object SetProfileParam(string profilePath, string componentType, string field, float value)
|
|
{
|
|
if (Application.isPlaying) return "refused: Play mode";
|
|
var prof = AssetDatabase.LoadMainAssetAtPath(profilePath) as ScriptableObject;
|
|
if (prof == null) return "profile not found: " + profilePath;
|
|
var comps = Member(prof, "components") as System.Collections.IList;
|
|
if (comps == null) return "no components list";
|
|
foreach (var c in comps)
|
|
{
|
|
var so = c as ScriptableObject; if (so == null || so.GetType().Name != componentType) continue;
|
|
var ser = new SerializedObject(so);
|
|
if (field == "active") { var a = ser.FindProperty("active"); if (a == null) return "no active"; a.boolValue = value > 0.5f; ser.ApplyModifiedPropertiesWithoutUndo(); EditorUtility.SetDirty(so); AssetDatabase.SaveAssets(); return componentType + ".active=" + a.boolValue; }
|
|
var pv = ser.FindProperty(field + ".m_Value"); var po = ser.FindProperty(field + ".m_OverrideState");
|
|
if (pv == null) return componentType + "." + field + " not found";
|
|
string was = pv.propertyType == SerializedPropertyType.Float ? pv.floatValue.ToString("F3") : pv.propertyType == SerializedPropertyType.Integer ? pv.intValue.ToString() : pv.propertyType.ToString();
|
|
if (pv.propertyType == SerializedPropertyType.Float) pv.floatValue = value; else if (pv.propertyType == SerializedPropertyType.Integer) pv.intValue = (int)value; else return "unsupported type " + pv.propertyType;
|
|
if (po != null) po.boolValue = true;
|
|
ser.ApplyModifiedPropertiesWithoutUndo(); EditorUtility.SetDirty(so); AssetDatabase.SaveAssets();
|
|
return componentType + "." + field + " " + was + " -> " + value;
|
|
}
|
|
return "component not found: " + componentType;
|
|
}
|
|
|
|
// Edit-mode: assign a profile to every Volume in a prefab and set enabled
|
|
public static object SetPrefabVolumeProfile(string prefabPath, string profilePath, bool enabled)
|
|
{
|
|
if (Application.isPlaying) return "refused: Play mode";
|
|
var volType = Type.GetType("UnityEngine.Rendering.Volume, Unity.RenderPipelines.Core.Runtime");
|
|
var prof = AssetDatabase.LoadMainAssetAtPath(profilePath);
|
|
if (prof == null) return "profile not found: " + profilePath;
|
|
var root = PrefabUtility.LoadPrefabContents(prefabPath);
|
|
try
|
|
{
|
|
int n = 0; var sb = new StringBuilder();
|
|
foreach (var c in root.GetComponentsInChildren(volType, true))
|
|
{
|
|
var ser = new SerializedObject(c);
|
|
var sp = ser.FindProperty("sharedProfile");
|
|
if (sp == null) return "Volume has no 'sharedProfile' serialized property (fields: " + string.Join(",", ListProps(ser)) + ")";
|
|
sp.objectReferenceValue = prof;
|
|
var en = ser.FindProperty("m_Enabled"); if (en != null) en.boolValue = enabled;
|
|
ser.ApplyModifiedPropertiesWithoutUndo();
|
|
((Behaviour)c).enabled = enabled;
|
|
n++; sb.Append(Path(((Component)c).transform)).Append(' ');
|
|
}
|
|
PrefabUtility.SaveAsPrefabAsset(root, prefabPath);
|
|
return prefabPath + " volumes=" + n + " (" + sb.ToString().Trim() + ") -> profile " + prof.name + " enabled=" + enabled;
|
|
}
|
|
finally { PrefabUtility.UnloadPrefabContents(root); }
|
|
}
|
|
|
|
public static object ReadStatic(string typeName, string member)
|
|
{
|
|
var t = Type.GetType(typeName + ", Assembly-CSharp");
|
|
if (t == null) return "type not found: " + typeName;
|
|
var pi = t.GetProperty(member, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
|
|
if (pi != null) return member + " = " + (pi.GetValue(null) ?? "null");
|
|
var fi = t.GetField(member, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
|
|
if (fi != null) return member + " = " + (fi.GetValue(null) ?? "null");
|
|
return "static member not found: " + member;
|
|
}
|
|
|
|
// Render through the REAL main camera (post-processing/HDR included) into a temp RT → PNG + luminance. Truth for brightness.
|
|
public static object RenderMain(string tag)
|
|
{
|
|
var cam = Camera.main;
|
|
if (cam == null) return "no Camera.main";
|
|
System.IO.Directory.CreateDirectory(OutDir);
|
|
int w = 1280, h = 720;
|
|
var rt = new RenderTexture(w, h, 24, RenderTextureFormat.ARGB32);
|
|
var prevTarget = cam.targetTexture;
|
|
cam.targetTexture = rt;
|
|
cam.Render();
|
|
cam.targetTexture = prevTarget;
|
|
var prev = RenderTexture.active;
|
|
RenderTexture.active = rt;
|
|
var tex = new Texture2D(w, h, TextureFormat.RGB24, false);
|
|
tex.ReadPixels(new Rect(0, 0, w, h), 0, 0);
|
|
tex.Apply();
|
|
RenderTexture.active = prev;
|
|
var px = tex.GetPixels32();
|
|
double sum = 0; int dark = 0, clip = 0;
|
|
foreach (var p in px) { double l = 0.2126 * p.r + 0.7152 * p.g + 0.0722 * p.b; sum += l; if (l < 40) dark++; if (l > 245) clip++; }
|
|
var path = System.IO.Path.Combine(OutDir, tag + ".png");
|
|
System.IO.File.WriteAllBytes(path, tex.EncodeToPNG());
|
|
UnityEngine.Object.DestroyImmediate(tex);
|
|
rt.Release();
|
|
UnityEngine.Object.DestroyImmediate(rt);
|
|
return path + " meanLum=" + ((float)(sum / px.Length / 255.0)).ToString("F3") + " darkRatio(<40/255)=" + ((float)dark / px.Length).ToString("F3") + " clipRatio(>245/255)=" + ((float)clip / px.Length).ToString("F3");
|
|
}
|
|
|
|
// ---------- runtime experiments (revert when Play stops) ----------
|
|
public static object SetAmbient(string mode, float intensity)
|
|
{
|
|
switch (mode)
|
|
{
|
|
case "skybox":
|
|
RenderSettings.ambientMode = AmbientMode.Skybox;
|
|
RenderSettings.ambientIntensity = intensity;
|
|
DynamicGI.UpdateEnvironment();
|
|
break;
|
|
case "update":
|
|
DynamicGI.UpdateEnvironment();
|
|
break;
|
|
case "flat":
|
|
RenderSettings.ambientMode = AmbientMode.Flat;
|
|
RenderSettings.ambientLight = Color.white * intensity;
|
|
break;
|
|
case "trilight":
|
|
RenderSettings.ambientMode = AmbientMode.Trilight;
|
|
RenderSettings.ambientSkyColor = Color.white * intensity;
|
|
RenderSettings.ambientEquatorColor = (Color)new Color32(180, 148, 123, 255) * intensity;
|
|
RenderSettings.ambientGroundColor = (Color)new Color32(79, 79, 115, 255) * intensity;
|
|
break;
|
|
default: return "unknown mode " + mode;
|
|
}
|
|
var dirs = new[] { Vector3.up, Vector3.down };
|
|
var res = new Color[2];
|
|
RenderSettings.ambientProbe.Evaluate(dirs, res);
|
|
return "mode=" + RenderSettings.ambientMode + " intensity=" + RenderSettings.ambientIntensity + " probe up=" + C(res[0]) + " down=" + C(res[1]);
|
|
}
|
|
|
|
public static object SetTint(float shadow, float middle, float light)
|
|
{
|
|
int n = 0;
|
|
var mpb = new MaterialPropertyBlock();
|
|
foreach (var r in UnityEngine.Object.FindObjectsByType<Renderer>(FindObjectsSortMode.None))
|
|
{
|
|
bool toon = false;
|
|
foreach (var m in r.sharedMaterials) if (m != null && m.shader != null && m.shader.name == "Toon Shaders Pro/URP/Toon") { toon = true; break; }
|
|
if (!toon) continue;
|
|
r.GetPropertyBlock(mpb);
|
|
mpb.SetColor("_ShadowTint", new Color(shadow, shadow, shadow, 1));
|
|
mpb.SetColor("_MiddleTint", new Color(middle, middle, middle, 1));
|
|
mpb.SetColor("_LightTint", new Color(light, light, light, 1));
|
|
r.SetPropertyBlock(mpb);
|
|
n++;
|
|
}
|
|
return "propertyBlock applied to " + n + " Toon Pro renderers (shadow=" + shadow + " middle=" + middle + " light=" + light + ")";
|
|
}
|
|
|
|
public static object ClearTint()
|
|
{
|
|
int n = 0;
|
|
foreach (var r in UnityEngine.Object.FindObjectsByType<Renderer>(FindObjectsSortMode.None)) { r.SetPropertyBlock(null); n++; }
|
|
return "cleared property blocks on " + n + " renderers";
|
|
}
|
|
|
|
public static object SetLight(float intensity)
|
|
{
|
|
int n = 0;
|
|
foreach (var l in UnityEngine.Object.FindObjectsByType<Light>(FindObjectsSortMode.None))
|
|
if (l.type == LightType.Directional) { l.intensity = intensity; n++; }
|
|
return "directional lights set to " + intensity + ": " + n;
|
|
}
|
|
|
|
// ---------- reflection helper to drive game UI without compile-time dependency ----------
|
|
public static object Invoke(string typeName, string method)
|
|
{
|
|
var t = Type.GetType(typeName + ", Assembly-CSharp");
|
|
if (t == null) return "type not found: " + typeName;
|
|
var obj = UnityEngine.Object.FindFirstObjectByType(t, FindObjectsInactive.Include);
|
|
if (obj == null) return "instance not found: " + typeName;
|
|
var mi = t.GetMethod(method, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
if (mi == null) return "method not found: " + method;
|
|
var r = mi.Invoke(obj, null);
|
|
return "invoked " + typeName + "." + method + " -> " + (r ?? "void");
|
|
}
|
|
|
|
public static object FindUI(string nameContains)
|
|
{
|
|
var sb = new StringBuilder();
|
|
foreach (var go in UnityEngine.Object.FindObjectsByType<GameObject>(FindObjectsInactive.Include, FindObjectsSortMode.None))
|
|
if (go.name.IndexOf(nameContains, StringComparison.OrdinalIgnoreCase) >= 0)
|
|
sb.Append(Path(go.transform)).Append(" active=").Append(go.activeInHierarchy).Append('\n');
|
|
return sb.Length == 0 ? "none" : sb.ToString();
|
|
}
|
|
|
|
public static object ClickButton(string goName)
|
|
{
|
|
foreach (var b in UnityEngine.Object.FindObjectsByType<UnityEngine.UI.Button>(FindObjectsInactive.Include, FindObjectsSortMode.None))
|
|
if (b.gameObject.name == goName) { b.onClick.Invoke(); return "clicked " + Path(b.transform) + " active=" + b.gameObject.activeInHierarchy; }
|
|
return "button not found: " + goName;
|
|
}
|
|
|
|
// ---------- utils ----------
|
|
static List<string> ListProps(SerializedObject so)
|
|
{
|
|
var names = new List<string>();
|
|
var it = so.GetIterator();
|
|
if (it.NextVisible(true)) do { names.Add(it.propertyPath); } while (it.NextVisible(false));
|
|
return names;
|
|
}
|
|
static string C(Color c) => "(" + c.r.ToString("F2") + "," + c.g.ToString("F2") + "," + c.b.ToString("F2") + ")";
|
|
static string Path(Transform t)
|
|
{
|
|
var s = t.name;
|
|
while (t.parent != null) { t = t.parent; s = t.name + "/" + s; }
|
|
return s;
|
|
}
|
|
|
|
// ───────────── #760 dev-login (경합 방지) ─────────────
|
|
// 배경: OnClick_DevLogin 을 테이블 로딩 전(Input_ID 가 비어 있을 때) 호출하면 PlayerPrefs(Option_UserID) 에 "" 가
|
|
// 저장되고, 이후 LoginWithCustomID 가 InvalidParams 로 거부되어 "받아오는 중" 에 멈춘다(2026-09-06 실측).
|
|
static Type WlTypeByName(string name)
|
|
{
|
|
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
|
{
|
|
Type t = null;
|
|
try { t = asm.GetType(name); } catch { }
|
|
if (t != null) return t;
|
|
}
|
|
return null;
|
|
}
|
|
public static object GetPref(string key)
|
|
{
|
|
return key + " = '" + PlayerPrefs.GetString(key, "<none>") + "' has=" + PlayerPrefs.HasKey(key);
|
|
}
|
|
public static object SetPref(string key, string val)
|
|
{
|
|
PlayerPrefs.SetString(key, val); PlayerPrefs.Save();
|
|
return key + " := '" + PlayerPrefs.GetString(key) + "'";
|
|
}
|
|
/// <summary>테이블 로딩이 끝난 뒤에만 OnClick_DevLogin 호출. Input_ID 가 비어 있으면 id(기본 TestID) 로 채운다.</summary>
|
|
public static object DevLogin(string id)
|
|
{
|
|
if (!Application.isPlaying) return "not playing";
|
|
var tcType = WlTypeByName("TableChecker");
|
|
if (tcType != null)
|
|
{
|
|
object ins = null;
|
|
var pi = tcType.GetProperty("Ins", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
|
if (pi != null) ins = pi.GetValue(null);
|
|
if (ins == null) { var fi = tcType.GetField("Ins", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy); if (fi != null) ins = fi.GetValue(null); }
|
|
var m = tcType.GetMethod("CheckAllLoad");
|
|
if (ins != null && m != null && !(bool)m.Invoke(ins, null)) return "tables not loaded yet";
|
|
}
|
|
var tType = WlTypeByName("TitleInfo");
|
|
if (tType == null) return "TitleInfo type not found";
|
|
var objs = UnityEngine.Object.FindObjectsByType(tType, FindObjectsInactive.Include, FindObjectsSortMode.None);
|
|
if (objs.Length == 0) return "TitleInfo instance not found";
|
|
var ti = objs[0];
|
|
var fInput = tType.GetField("Input_ID", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
var input = fInput != null ? fInput.GetValue(ti) as TMPro.TMP_InputField : null;
|
|
if (input == null) return "Input_ID null";
|
|
if (string.IsNullOrEmpty(input.text)) input.text = string.IsNullOrEmpty(id) ? "TestID" : id;
|
|
var call = tType.GetMethod("OnClick_DevLogin");
|
|
if (call == null) return "OnClick_DevLogin not found";
|
|
call.Invoke(ti, null);
|
|
return "dev login invoked id='" + input.text + "'";
|
|
}
|
|
}
|