Files
T
huadexuan c09e15dd0e ```
feat(Materials): 更新CoreBattleGround材质贴图和缩放参数

更换了CoreBattleGround材质的主纹理,将纹理GUID从73e4d6cf089824c0cadfcefa5b15d306
更新为c6af6f473739fb546ac7775e5c957da1,并调整纹理缩放参数从{x: 3, y: 4}修改为
{x: 1, y: 1},同时移除了多余的_MetallicGlossMap配置项。
```
2026-09-28 17:58:15 +08:00

215 lines
9.4 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Codely.Microsoft.CodeAnalysis.Scripting;
using UnityEditor;
using UnityEngine;
namespace UnityTcp.Editor.Tools
{
// Static host class exposed to script code via an unconditional imports entry (HandleCommand
// always adds this namespace, even when the caller overrides `imports`). No globals/preamble
// injection is used -- the API is stateless (Id/ById) or reads shared static session state
// (Vars), so a plain static class is sufficient and has zero injection cost. A script declaring
// its own `var Repl = ...;` shadows this class for the rest of the session; that's an accepted,
// documented trade-off rather than something worth guarding against.
// partial: the ApiHelper nested class (ported from cn.tuanjie.csharp-repl, reflection signature
// lookup with a disk-cached index) lives in Repl.ApiHelper.cs to keep this file to the
// Id/ById/Vars surface.
public static partial class Repl
{
// Stashes a cross-domain-reload handle for `obj`, built on GlobalObjectId. A domain reload
// wipes every `var` a REPL session holds, so a script can call Repl.Id(x) before a
// reload-inducing operation (unlocking ReplGuard, entering Play Mode) and hand the token to
// Repl.ById() afterward to get a live reference back, instead of re-resolving by name/path
// (which breaks on duplicate names or objects with no stable path).
public static string Id(UnityEngine.Object obj)
{
if (obj == null)
return "OBJECT <null> NOT FOUND (missing or destroyed)";
// "Unsaved" cannot be determined from GlobalObjectId.targetObjectId == 0 alone: a
// brand-new GameObject in an already-saved scene gets a nonzero targetObjectId
// immediately (predictive local-file-id assignment) and round-trips fine even before
// its own save. Two checks instead: (1) a scene GameObject/Component whose containing
// scene has never been saved has nothing on disk to resolve against after a real
// scene close/reopen, even though it round-trips within the current in-memory session;
// (2) a round-trip through the same resolver ById() uses, which also catches a pure
// in-memory object with no scene and no asset path (e.g. an unsaved ScriptableObject).
if (!EditorUtility.IsPersistent(obj))
{
var transform = AsTransform(obj);
if (transform != null && string.IsNullOrEmpty(transform.gameObject.scene.path))
return "UNSAVED " + PathFor(obj) + " (id does not survive reload -- save the scene first)";
}
var gid = GlobalObjectId.GetGlobalObjectIdSlow(obj);
var resolved = GlobalObjectId.GlobalObjectIdentifierToObjectSlow(gid);
if (resolved != obj)
return "UNSAVED " + PathFor(obj) + " (id does not survive reload -- save the scene first)";
return gid.ToString();
}
// Returns null and logs a Debug.LogWarning with the reason on failure (missing/malformed
// token, scene not loaded, object deleted). Deliberately Debug.LogWarning rather than the
// internal CodelyLogger -- this bridge's log capture surfaces Application.logMessageReceived
// output in the response's `logs` field, so the reason for a null comes back with the call
// instead of requiring a separate console read.
public static UnityEngine.Object ById(string token)
{
if (string.IsNullOrEmpty(token))
{
Debug.LogWarning("Repl.ById('') NOT FOUND: empty token");
return null;
}
if (!GlobalObjectId.TryParse(token, out var gid))
{
Debug.LogWarning($"Repl.ById('{token}') NOT FOUND: malformed token");
return null;
}
var obj = GlobalObjectId.GlobalObjectIdentifierToObjectSlow(gid);
if (obj == null)
{
Debug.LogWarning(
$"Repl.ById('{token}') NOT FOUND: object not resolvable (its scene may not be loaded, or it was deleted)");
return null;
}
return obj;
}
// Read-only snapshot of the active session's top-level variables, taken from the
// ScriptState left behind by the last successfully completed submission (a script calling
// Repl.Vars mid-submission only sees variables from earlier submissions, same as a real
// REPL). Empty during a one-shot execution (enable_repl:false) even if a session happens to
// exist in the background -- one-shot must not read session state, only bypass it.
public static ReplVars Vars
{
get
{
var vars = new ReplVars();
var state = ExecuteCSharpScript.CurrentSessionStateForVars;
if (state == null)
return vars;
foreach (var variable in state.Variables)
vars.Set(variable.Name, variable.Type, variable.Value);
return vars;
}
}
static Transform AsTransform(UnityEngine.Object obj)
{
switch (obj)
{
case GameObject go:
return go.transform;
case Component comp:
return comp.transform;
default:
return null;
}
}
static string PathFor(UnityEngine.Object obj)
{
if (EditorUtility.IsPersistent(obj))
{
var path = AssetDatabase.GetAssetPath(obj);
return string.IsNullOrEmpty(path) ? obj.name : path;
}
var transform = AsTransform(obj);
return transform != null ? GetPath(transform) : obj.name;
}
static string GetPath(Transform transform)
{
if (transform == null)
return string.Empty;
var stack = new Stack<string>();
var cur = transform;
while (cur != null)
{
stack.Push(cur.name);
cur = cur.parent;
}
return string.Join("/", stack.ToArray());
}
}
// Read-only view over a REPL session's top-level variables. Not a raw forward of
// ScriptState.Variables: it collapses same-named redeclarations across submissions down to the
// last one (matching what a real REPL session sees -- a later `var x = ...;` shadows an
// earlier one), and it renders as a Name/Type/value table via ToString() instead of the default
// dictionary/collection output, so submitting a bare `Repl.Vars` line is a session-state
// overview. Deliberately no write API: unlike Python's globals(), there's no scenario here where
// the caller isn't already in scope to just assign the variable directly, and ScriptVariable's
// setter throws for variables declared with `readonly`/const-like semantics -- exposing writes
// would only add an error surface with no real use case.
public sealed class ReplVars : IReadOnlyDictionary<string, object>
{
readonly List<string> m_Order = new List<string>();
readonly Dictionary<string, object> m_Values = new Dictionary<string, object>();
readonly Dictionary<string, Type> m_Types = new Dictionary<string, Type>();
internal ReplVars() { }
internal void Set(string name, Type type, object value)
{
if (!m_Values.ContainsKey(name))
m_Order.Add(name);
m_Values[name] = value;
m_Types[name] = type;
}
public object this[string key] => m_Values[key];
public IEnumerable<string> Keys => m_Order;
public IEnumerable<object> Values
{
get
{
foreach (var name in m_Order)
yield return m_Values[name];
}
}
public int Count => m_Order.Count;
public bool ContainsKey(string key) => m_Values.ContainsKey(key);
public bool TryGetValue(string key, out object value) => m_Values.TryGetValue(key, out value);
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
foreach (var name in m_Order)
yield return new KeyValuePair<string, object>(name, m_Values[name]);
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
// Only variables are visible here (methods/types declared in the session have no
// ScriptVariable entry -- a boundary of ScriptState.Variables itself, not something this
// wrapper can fill in).
public override string ToString()
{
if (m_Order.Count == 0)
return "(no session variables)";
var sb = new StringBuilder();
sb.Append("Name".PadRight(24)).Append(' ').Append("Type".PadRight(24)).Append(' ').Append("Value").Append('\n');
foreach (var name in m_Order)
{
var typeName = m_Types[name]?.FullName ?? "?";
var preview = ExecuteCSharpScript.FormatResultValue(m_Values[name]) ?? "null";
sb.Append(name.PadRight(24)).Append(' ').Append(typeName.PadRight(24)).Append(' ').Append(preview).Append('\n');
}
return sb.ToString();
}
}
}