feat(gameplay): 更新单位属性和材质配置

- 调整多个单位的基础生命值和攻击力数值平衡
- 修改CoreBattleGround材质的纹理缩放比例
- 更新地砖材质颜色为白色
- 优化纹理资源设置,调整最大纹理尺寸并添加PlayableAds平台支持
- 修复弓箭手技能目标查找逻辑,改用统一的敌人检测方法
- 为水晶矿添加中立阵营设置和光球视觉效果同步
- 调整预制体中的全局缩放参数
- 为狼人和石巨人添加NavMeshAgent组件支持
- 优化水晶矿血条显示位置计算方式
```
This commit is contained in:
huadexuan committed 2026-09-20 21:52:31 +08:00
1 parent 0e7de5f885
commit 53d95802e5
448 files changed
+133333 -472

No files matched your search

@@ -0,0 +1,26 @@
using System.Text.RegularExpressions;
using Codely.Microsoft.CodeAnalysis;
using Codely.Microsoft.CodeAnalysis.CSharp;
using SyntaxTree = Codely.Microsoft.CodeAnalysis.SyntaxTree;
namespace UnityTcp.Editor.Tools
{
internal class FixAmbiguousReference : ScriptFixProvider
{
public override bool CanFix(Diagnostic diagnostic) => diagnostic.Id == "CS0104";
public override bool ApplyFix(ref SyntaxTree tree, Diagnostic diagnostic, ScriptFixContext context)
{
var message = diagnostic.GetMessage();
var aliasMatch = Regex.Match(message, @"(?<=')[^']*(?=' is)");
var nsMatch = Regex.Match(message, @"(?<=between ')[^']*(?=' and)");
if (!nsMatch.Success || !aliasMatch.Success)
return false;
var updated = tree.AddUsingAliasDirective(nsMatch.Value, aliasMatch.Value);
if (ReferenceEquals(updated, tree)) return false;
tree = updated;
return true;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f8bbefc3ee0e8bf46906e72e1eece553
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,222 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Codely.Microsoft.CodeAnalysis;
using Codely.Microsoft.CodeAnalysis.CSharp;
using UnityEngine;
using SyntaxTree = Codely.Microsoft.CodeAnalysis.SyntaxTree;
using UnityTcp.Editor.Helpers;
namespace UnityTcp.Editor.Tools
{
// Handles CS0246 / CS0234 by searching all loaded assemblies for the missing type
// and adding the discovered assembly + its namespace on demand.
// For CS0234 where the type exists in a different namespace (e.g. UnityEngine.SerializedObject
// when SerializedObject actually lives in UnityEditor), this provider also rewrites the
// source code to replace the wrong namespace prefix with the correct one.
internal class FixMissingAssemblyReference : ScriptFixProvider
{
static readonly string[] k_DiagnosticIds = { "CS0246", "CS0234", "CS0103" };
public override bool CanFix(Diagnostic diagnostic) =>
k_DiagnosticIds.Contains(diagnostic.Id);
public override bool ApplyFix(ref SyntaxTree tree, Diagnostic diagnostic, ScriptFixContext context)
{
var message = diagnostic.GetMessage();
ExtractNames(message, out var missingName, out var parentNamespace);
if (string.IsNullOrEmpty(missingName))
return false;
// CS0234: 'X' does not exist in namespace 'Y' → the missing thing is the namespace Y.X.
// Try resolving it as a namespace first.
if (!string.IsNullOrEmpty(parentNamespace))
{
var fullNamespace = parentNamespace + "." + missingName;
if (TryResolveNamespace(fullNamespace, context))
return true;
// CS0234 with a parent namespace that doesn't contain the type:
// the type might exist in a different namespace. Try to find the correct one
// and rewrite the source code's namespace prefix.
var correctNamespace = FindCorrectNamespace(missingName, parentNamespace);
if (correctNamespace != null)
{
// Also ensure the assembly reference + import are registered.
TryResolveTypeName(missingName, context);
// Rewrite source: parentNamespace.missingName → correctNamespace.missingName
var updatedTree = RewriteNamespacePrefix(tree, parentNamespace, correctNamespace, missingName);
if (updatedTree != null)
{
tree = updatedTree;
CodelyLogger.Log($"[FixMissingAssemblyReference] Rewrote '{parentNamespace}.{missingName}' → '{correctNamespace}.{missingName}'");
return true;
}
}
}
// Fallback / CS0246: resolve by type name.
return TryResolveTypeName(missingName, context);
}
// Searches all assemblies for any exported type whose namespace equals or starts with
// fullNamespace, then registers ALL matching assemblies.
bool TryResolveNamespace(string fullNamespace, ScriptFixContext context)
{
var prefix = fullNamespace + ".";
var resolved = false;
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (assembly.IsDynamic)
continue;
var location = assembly.Location;
if (string.IsNullOrEmpty(location) || context.AddedLocations.Contains(location))
continue;
try
{
var hasMatch = assembly.GetExportedTypes()
.Any(t => t.IsPublic &&
(t.Namespace == fullNamespace ||
(t.Namespace != null && t.Namespace.StartsWith(prefix, StringComparison.Ordinal))));
if (!hasMatch)
continue;
context.References.Add(MetadataReference.CreateFromFile(location));
context.AddedLocations.Add(location);
if (!context.Imports.Contains(fullNamespace))
context.Imports.Add(fullNamespace);
CodelyLogger.Log($"[FixMissingAssemblyReference] Resolved namespace '{fullNamespace}' → {assembly.GetName().Name}");
resolved = true;
}
catch
{
// Some assemblies throw on GetExportedTypes; skip them.
}
}
return resolved;
}
// Searches all assemblies for exported types whose simple name matches typeName,
// then registers ALL matching assemblies and their namespaces.
bool TryResolveTypeName(string typeName, ScriptFixContext context)
{
var resolved = false;
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (assembly.IsDynamic)
continue;
var location = assembly.Location;
if (string.IsNullOrEmpty(location) || context.AddedLocations.Contains(location))
continue;
try
{
var matches = assembly.GetExportedTypes()
.Where(t => t.Name == typeName && t.IsPublic)
.ToList();
if (matches.Count == 0)
continue;
context.References.Add(MetadataReference.CreateFromFile(location));
context.AddedLocations.Add(location);
foreach (var t in matches)
{
if (!string.IsNullOrEmpty(t.Namespace) && !context.Imports.Contains(t.Namespace))
context.Imports.Add(t.Namespace);
}
var names = string.Join(", ", matches.Select(t => t.FullName));
CodelyLogger.Log($"[FixMissingAssemblyReference] Resolved '{typeName}' → {assembly.GetName().Name} ({names})");
resolved = true;
}
catch
{
// Some assemblies throw on GetExportedTypes; skip them.
}
}
return resolved;
}
// CS0234: "The type or namespace name 'X' does not exist in the namespace 'Y'"
static readonly Regex k_Cs0234Pattern =
new Regex(@"'([^']+)'\s+does not exist in the namespace\s+'([^']+)'", RegexOptions.Compiled);
// CS0246: "The type or namespace name 'X' could not be found ..."
static readonly Regex k_SingleQuotePattern =
new Regex(@"'([^']+)'", RegexOptions.Compiled);
// Extracts the missing name and, for CS0234, the parent namespace.
static void ExtractNames(string message, out string missingName, out string parentNamespace)
{
var m = k_Cs0234Pattern.Match(message);
if (m.Success)
{
missingName = m.Groups[1].Value;
parentNamespace = m.Groups[2].Value;
return;
}
var m2 = k_SingleQuotePattern.Match(message);
missingName = m2.Success ? m2.Groups[1].Value : null;
parentNamespace = null;
}
// Searches all assemblies for a public type whose simple name matches typeName
// but whose namespace differs from wrongNamespace. Returns the first matching
// namespace, or null if no such type is found.
internal static string FindCorrectNamespace(string typeName, string wrongNamespace)
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (assembly.IsDynamic)
continue;
try
{
var match = assembly.GetExportedTypes()
.FirstOrDefault(t => t.IsPublic
&& t.Name == typeName
&& t.Namespace != null
&& t.Namespace != wrongNamespace);
if (match != null)
return match.Namespace;
}
catch
{
// Some assemblies throw on GetExportedTypes; skip them.
}
}
return null;
}
// Rewrites all occurrences of wrongNamespace.typeName in the source code to
// correctNamespace.typeName by performing a text replacement and reparsing.
static SyntaxTree RewriteNamespacePrefix(SyntaxTree tree, string wrongNamespace, string correctNamespace, string typeName)
{
var source = tree.GetText().ToString();
var wrongPrefix = $"{wrongNamespace}.{typeName}";
var correctPrefix = $"{correctNamespace}.{typeName}";
if (!source.Contains(wrongPrefix))
return null;
var updatedSource = source.Replace(wrongPrefix, correctPrefix);
return SyntaxFactory.ParseSyntaxTree(updatedSource);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8dff369db1c9a604893f04886ed906c9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,51 @@
using System.Text.RegularExpressions;
using Codely.Microsoft.CodeAnalysis;
using Codely.Microsoft.CodeAnalysis.CSharp;
using Codely.Microsoft.CodeAnalysis.Text;
using SyntaxTree = Codely.Microsoft.CodeAnalysis.SyntaxTree;
namespace UnityTcp.Editor.Tools
{
internal class FixMissingBrace : ScriptFixProvider
{
// Jagged array rank: Type[][] or new Type[][]
static readonly Regex k_JaggedArrayPattern = new Regex(
@"\[\]\s*\[\]", RegexOptions.Compiled);
// Implicit array creation with named tuple: new[] { (name: ...)
static readonly Regex k_NamedTupleArrayPattern = new Regex(
@"new\s*\[\].*\(\s*\w+\s*:", RegexOptions.Compiled);
public override bool CanFix(Diagnostic diagnostic) => diagnostic.Id == "CS1513";
public override bool ApplyFix(ref SyntaxTree tree, Diagnostic diagnostic, ScriptFixContext context)
{
var source = tree.ToString();
var lineSpan = diagnostic.Location.GetLineSpan();
int diagLine = lineSpan.Span.Start.Line;
// Guard: skip CS1513 caused by script-incompatible syntax rather than
// actual missing braces. These patterns confuse the script parser and
// inserting } would make things worse.
var lines = source.Split('\n');
if (diagLine >= 0 && diagLine < lines.Length)
{
var lineText = lines[diagLine].Trim();
if (k_JaggedArrayPattern.IsMatch(lineText))
return false;
if (k_NamedTupleArrayPattern.IsMatch(lineText))
return false;
}
// Insert } at the diagnostic position
var sourceText = SourceText.From(source);
int insertPos = sourceText.Lines.GetPosition(lineSpan.Span.Start);
var newSource = source.Insert(insertPos, "}\n");
var updated = SyntaxFactory.ParseSyntaxTree(newSource);
if (ReferenceEquals(updated, tree)) return false;
tree = updated;
return true;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2bb64ceba5600d54e8d51455ee673ef8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,48 @@
using System.Collections.Generic;
using System.Linq;
using Codely.Microsoft.CodeAnalysis;
using Codely.Microsoft.CodeAnalysis.CSharp;
using SyntaxTree = Codely.Microsoft.CodeAnalysis.SyntaxTree;
namespace UnityTcp.Editor.Tools
{
internal class FixMissingImports : ScriptFixProvider
{
static readonly string[] k_DiagnosticIds = { "CS0246", "CS0103", "CS1061" };
readonly Dictionary<string, string[]> k_NamespaceKeywords = new Dictionary<string, string[]>
{
{ "System.Linq", new[] { "Where", "Select", "OrderBy", "Concat", "Any", "First", "Last", "ToList", "ToArray", "GroupBy", "Distinct" } },
{ "System.Collections.Generic", new[] { "List<>", "Dictionary<>", "HashSet<>", "Queue<>", "Stack<>", "SortedList<>" } },
{ "UnityEditor", new[] { "MonoScript", "AssetDatabase", "EditorUtility", "Selection", "SceneView", "PrefabUtility", "Undo", "EditorGUILayout", "SerializedObject", "SerializedProperty" } },
{ "UnityEngine.SceneManagement", new[] { "SceneManager", "Scene", "LoadSceneMode" } },
{ "UnityEditor.SceneManagement", new[] { "EditorSceneManager" } },
};
public override bool CanFix(Diagnostic diagnostic)
{
if (!k_DiagnosticIds.Contains(diagnostic.Id))
return false;
var message = diagnostic.GetMessage();
return k_NamespaceKeywords.Values.Any(kws => kws.Any(kw => message.Contains($"'{kw}'")));
}
public override bool ApplyFix(ref SyntaxTree tree, Diagnostic diagnostic, ScriptFixContext context)
{
var message = diagnostic.GetMessage();
foreach (var pair in k_NamespaceKeywords)
{
if (pair.Value.Any(kw => message.Contains($"'{kw}'")))
{
if (!context.Imports.Contains(pair.Key))
{
context.Imports.Add(pair.Key);
return true;
}
return false;
}
}
return false;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 533b0cca22c2a3f4180a93f10585b5e2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,19 @@
using Codely.Microsoft.CodeAnalysis;
using Codely.Microsoft.CodeAnalysis.CSharp;
using SyntaxTree = Codely.Microsoft.CodeAnalysis.SyntaxTree;
namespace UnityTcp.Editor.Tools
{
internal class FixMissingParenthesis : ScriptFixProvider
{
public override bool CanFix(Diagnostic diagnostic) => diagnostic.Id == "CS1026";
public override bool ApplyFix(ref SyntaxTree tree, Diagnostic diagnostic, ScriptFixContext context)
{
var updated = tree.InsertTextAtLocation(diagnostic.Location, ")");
if (ReferenceEquals(updated, tree)) return false;
tree = updated;
return true;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c3c1ebc774b153c4bba0119e184fe503
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,19 @@
using Codely.Microsoft.CodeAnalysis;
using Codely.Microsoft.CodeAnalysis.CSharp;
using SyntaxTree = Codely.Microsoft.CodeAnalysis.SyntaxTree;
namespace UnityTcp.Editor.Tools
{
internal class FixMissingSemicolon : ScriptFixProvider
{
public override bool CanFix(Diagnostic diagnostic) => diagnostic.Id == "CS1002";
public override bool ApplyFix(ref SyntaxTree tree, Diagnostic diagnostic, ScriptFixContext context)
{
var updated = tree.InsertTextAtLocation(diagnostic.Location, ";");
if (ReferenceEquals(updated, tree)) return false;
tree = updated;
return true;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e183b257f3396a64697ab7b36fdec226
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,19 @@
using Codely.Microsoft.CodeAnalysis;
using Codely.Microsoft.CodeAnalysis.CSharp;
using SyntaxTree = Codely.Microsoft.CodeAnalysis.SyntaxTree;
namespace UnityTcp.Editor.Tools
{
internal class FixMissingSquareBracket : ScriptFixProvider
{
public override bool CanFix(Diagnostic diagnostic) => diagnostic.Id == "CS1003";
public override bool ApplyFix(ref SyntaxTree tree, Diagnostic diagnostic, ScriptFixContext context)
{
var updated = tree.InsertTextAtLocation(diagnostic.Location, "]");
if (ReferenceEquals(updated, tree)) return false;
tree = updated;
return true;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d45c5e11512e28f4cacc0f2c04039542
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,80 @@
using System.Collections.Generic;
using System.Linq;
using Codely.Microsoft.CodeAnalysis;
using Codely.Microsoft.CodeAnalysis.CSharp;
using Codely.Microsoft.CodeAnalysis.CSharp.Syntax;
using UnityEngine;
using SyntaxTree = Codely.Microsoft.CodeAnalysis.SyntaxTree;
using UnityTcp.Editor.Helpers;
namespace UnityTcp.Editor.Tools
{
// Handles CS0103 for common UnityEngine.Object static methods called without qualification
// in Roslyn scripting context (e.g., FindObjectOfType<T>() → UnityEngine.Object.FindObjectOfType<T>()).
//
// In MonoBehaviour scripts these methods are inherited and can be called unqualified,
// but Roslyn scripting has no base class, so they must be explicitly qualified.
internal class FixUnqualifiedUnityStaticMethod : ScriptFixProvider
{
static readonly HashSet<string> k_UnityObjectMethods = new HashSet<string>
{
"FindObjectOfType",
"FindObjectsOfType",
"FindAnyObjectByType",
"FindObjectsByType",
"Instantiate",
"Destroy",
"DestroyImmediate",
"DontDestroyOnLoad"
};
const string k_Qualifier = "UnityEngine.Object.";
public override bool CanFix(Diagnostic diagnostic)
{
if (diagnostic.Id != "CS0103")
return false;
var message = diagnostic.GetMessage();
return k_UnityObjectMethods.Any(m => message.Contains($"'{m}'"));
}
public override bool ApplyFix(ref SyntaxTree tree, Diagnostic diagnostic, ScriptFixContext context)
{
// Extract method name from diagnostic message
var message = diagnostic.GetMessage();
string methodName = null;
foreach (var m in k_UnityObjectMethods)
{
if (message.Contains($"'{m}'"))
{
methodName = m;
break;
}
}
if (methodName == null)
return false;
// Locate the exact node via diagnostic span. Spans are always fresh here because
// CompileAndAutoFix recompiles after every tree-modifying fix before calling ApplyFix again.
var root = tree.GetRoot();
var spanNode = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
SyntaxNode target = null;
for (var n = spanNode; n != null; n = n.Parent)
{
if (n is IdentifierNameSyntax id && id.Identifier.Text == methodName) { target = n; break; }
if (n is GenericNameSyntax gen && gen.Identifier.Text == methodName) { target = n; break; }
}
if (target == null || target.Parent is MemberAccessExpressionSyntax)
return false;
var source = tree.GetText().ToString();
tree = SyntaxFactory.ParseSyntaxTree(source.Insert(target.SpanStart, k_Qualifier));
CodelyLogger.Log($"[FixUnqualifiedUnityStaticMethod] Qualified '{methodName}' → '{k_Qualifier}{methodName}'");
return true;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9be25a0a8b570944e8d8f87bb8d934b3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,22 @@
using System.Collections.Generic;
using Codely.Microsoft.CodeAnalysis;
namespace UnityTcp.Editor.Tools
{
internal class ScriptFixContext
{
public List<string> Imports { get; }
public List<MetadataReference> References { get; }
public HashSet<string> AddedLocations { get; }
public ScriptFixContext(
List<string> imports,
List<MetadataReference> references,
HashSet<string> addedLocations)
{
Imports = imports;
References = references;
AddedLocations = addedLocations;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 92f47075059ef8b4a944a4a2b0bfafbe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,15 @@
using Codely.Microsoft.CodeAnalysis;
using Codely.Microsoft.CodeAnalysis.CSharp;
using SyntaxTree = Codely.Microsoft.CodeAnalysis.SyntaxTree;
namespace UnityTcp.Editor.Tools
{
internal abstract class ScriptFixProvider
{
public abstract bool CanFix(Diagnostic diagnostic);
// Returns true if a fix was applied.
// `tree` is updated via ref; context.Imports / context.References may be mutated.
public abstract bool ApplyFix(ref SyntaxTree tree, Diagnostic diagnostic, ScriptFixContext context);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2ba8b9b9f2c239d4e890b1b4e4279348
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,41 @@
using System.Linq;
using Codely.Microsoft.CodeAnalysis;
using Codely.Microsoft.CodeAnalysis.CSharp;
using Codely.Microsoft.CodeAnalysis.CSharp.Syntax;
using Codely.Microsoft.CodeAnalysis.Text;
using SyntaxTree = Codely.Microsoft.CodeAnalysis.SyntaxTree;
namespace UnityTcp.Editor.Tools
{
internal static class ScriptSyntaxUtils
{
internal static SyntaxTree InsertTextAtLocation(this SyntaxTree tree, Location location, string text)
{
var lineSpan = location.GetLineSpan().Span;
var sourceText = SourceText.From(tree.ToString());
var position = sourceText.Lines.GetPosition(lineSpan.Start);
var newSource = tree.ToString().Insert(position, text);
return SyntaxFactory.ParseSyntaxTree(newSource);
}
internal static SyntaxTree AddUsingAliasDirective(this SyntaxTree tree, string namespaceName, string aliasName)
{
if (!(tree.GetRoot() is CompilationUnitSyntax root))
return tree;
bool exists = root.Usings.Any(u =>
u.Name.ToString() == namespaceName && u.Alias?.Name.ToString() == aliasName);
if (exists)
return tree;
var usingDirective = SyntaxFactory
.UsingDirective(SyntaxFactory.ParseName(namespaceName))
.WithAlias(SyntaxFactory.NameEquals(SyntaxFactory.IdentifierName(aliasName)))
.NormalizeWhitespace()
.WithTrailingTrivia(SyntaxFactory.CarriageReturnLineFeed);
var newRoot = root.AddUsings(usingDirective).NormalizeWhitespace();
return CSharpSyntaxTree.Create(newRoot);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 57e3fbb1d95d4b647a36e4f169d8c4ba
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: