提交修改

This commit is contained in:
Bob.Song
2025-10-29 17:59:36 +08:00
parent b390cf2051
commit 5750c4fe56
2148 changed files with 13962 additions and 5549 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 78c51fb0052fac049a29ae6289cda649
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,16 @@
using System;
using UnityEditor;
namespace Fantasy
{
internal static class CheckUnityVersion
{
[InitializeOnLoadMethod]
private static void OnInitializeOnLoad()
{
#if !UNITY_2022_3_OR_NEWER
Debug.LogError("Fantasy支持的最低版本为Unity2022.3.622f2");
#endif
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 208434147045e0d4bae1287de7e28fa3
timeCreated: 1725943424

View File

@@ -0,0 +1,18 @@
{
"name": "Fantasy.Editor",
"rootNamespace": "",
"references": [
"GUID:0b7224b83ba514121aa026f3857f820a"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: b5d7370e11934b547a4e8dac960ea72e
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,56 @@
using System.IO;
using UnityEditor;
using UnityEngine;
namespace Fantasy
{
[InitializeOnLoad]
public static class FantasyStartup
{
private const string ScriptAssemblies = "Library/ScriptAssemblies/";
static FantasyStartup()
{
if (!FantasySettingsScriptableObject.Instance.autoCopyAssembly)
{
return;
}
var hotUpdatePath = FantasySettingsScriptableObject.Instance.hotUpdatePath;
if (string.IsNullOrEmpty(hotUpdatePath))
{
Debug.LogError("请先在菜单Fantasy-Fantasy Settings里设置自动拷贝程序集输出目录位置");
return;
}
if (!Directory.Exists(hotUpdatePath))
{
Directory.CreateDirectory(hotUpdatePath);
}
else
{
foreach (var file in Directory.GetFiles(hotUpdatePath))
{
File.Delete(file);
}
}
// ReSharper disable once StringLastIndexOfIsCultureSpecific.1
if (hotUpdatePath.LastIndexOf("/") != hotUpdatePath.Length - 1)
{
FantasySettingsScriptableObject.Instance.hotUpdatePath += "/";
hotUpdatePath = FantasySettingsScriptableObject.Instance.hotUpdatePath;
}
foreach (var instanceHotUpdateAssemblyDefinition in FantasySettingsScriptableObject.Instance.hotUpdateAssemblyDefinitions)
{
var dll = instanceHotUpdateAssemblyDefinition.name;
File.Copy($"{ScriptAssemblies}{dll}.dll", $"{hotUpdatePath}/{dll}.dll.bytes", true);
File.Copy($"{ScriptAssemblies}{dll}.pdb", $"{hotUpdatePath}/{dll}.pdb.bytes", true);
}
AssetDatabase.Refresh();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 13e9d2b99522b124680b7723aaded8d9
timeCreated: 1688276977

View File

@@ -0,0 +1,64 @@
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
namespace Fantasy
{
public class LinkXmlGenerator
{
private const string LinkPath = "Assets/link.xml";
// 在Unity编辑器中运行该方法来生成link.xml文件
public static void GenerateLinkXml()
{
using (var writer = new StreamWriter("Assets/link.xml"))
{
var assemblyHashSet = new HashSet<string>();
foreach (var assembly in FantasySettingsScriptableObject.Instance.includeAssembly)
{
assemblyHashSet.Add(assembly);
}
if (FantasySettingsScriptableObject.Instance?.linkAssemblyDefinitions != null)
{
foreach (var assemblyDefinition in FantasySettingsScriptableObject.Instance.linkAssemblyDefinitions)
{
assemblyHashSet.Add(assemblyDefinition.name);
}
}
if (assemblyHashSet.Count == 0)
{
return;
}
writer.WriteLine("<linker>");
foreach (var assembly in assemblyHashSet)
{
GenerateLinkXml(writer, assembly, LinkPath);
Debug.Log($"{assembly} Link generation completed");
}
writer.WriteLine("</linker>");
}
AssetDatabase.Refresh();
Debug.Log("link.xml generated successfully!");
}
private static void GenerateLinkXml(StreamWriter writer, string assemblyName, string outputPath)
{
var assembly = System.Reflection.Assembly.Load(assemblyName);
var types = assembly.GetTypes();
writer.WriteLine($" <assembly fullname=\"{assembly.GetName().Name}\">");
foreach (var type in types)
{
var typeName = type.FullName.Replace('<', '+').Replace('>', '+');
writer.WriteLine($" <type fullname=\"{typeName}\" preserve=\"all\"/>");
}
writer.WriteLine(" </assembly>");
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3360ff806a3d27d478e0e554ebbb291b
timeCreated: 1722743236

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4481b9186fbddb04f96e2e6e56e9ebfc
timeCreated: 1688277110

View File

@@ -0,0 +1,13 @@
using UnityEditor;
namespace Fantasy
{
public class FantasySettings
{
[MenuItem("Fantasy/Fantasy Settings")]
public static void OpenFantasySettings()
{
SettingsService.OpenProjectSettings("Project/Fantasy Settings");
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6a31e33b6eab0e847a6e836cf0f9835d
timeCreated: 1686913667

View File

@@ -0,0 +1,725 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace Fantasy
{
public class FantasySettingsProvider : SettingsProvider
{
private SerializedObject _serializedObject;
private SerializedProperty _autoCopyAssembly;
private SerializedProperty _hotUpdatePath;
private SerializedProperty _hotUpdateAssemblyDefinitions;
private SerializedProperty _linkAssemblyDefinitions;
private SerializedProperty _includeAssembly;
private bool _showLinkXmlConfig = true; // 控制是否显示 Link.xml 配置
public FantasySettingsProvider() : base("Project/Fantasy Settings", SettingsScope.Project) { }
public override void OnActivate(string searchContext, VisualElement rootElement)
{
Init();
base.OnActivate(searchContext, rootElement);
}
public override void OnDeactivate()
{
base.OnDeactivate();
FantasySettingsScriptableObject.Save();
}
private void Init()
{
_serializedObject?.Dispose();
_serializedObject = new SerializedObject(FantasySettingsScriptableObject.Instance);
_autoCopyAssembly = _serializedObject.FindProperty("autoCopyAssembly");
_hotUpdatePath = _serializedObject.FindProperty("hotUpdatePath");
_hotUpdateAssemblyDefinitions = _serializedObject.FindProperty("hotUpdateAssemblyDefinitions");
_linkAssemblyDefinitions = _serializedObject.FindProperty("linkAssemblyDefinitions");
_includeAssembly = _serializedObject.FindProperty("includeAssembly");
}
public override void OnGUI(string searchContext)
{
if (_serializedObject == null || !_serializedObject.targetObject)
{
Init();
}
using (CreateSettingsWindowGUIScope())
{
_serializedObject!.Update();
// ============ Fantasy 框架集成检测 ============
DrawCscRspInstallationSection();
DrawSectionDivider();
EditorGUILayout.Space(10);
// ============ 程序集自动拷贝设置 ============
EditorGUI.BeginChangeCheck();
DrawAssemblyCopySection();
DrawSectionDivider();
// ============ Link.xml 生成设置 ============
DrawLinkXmlSection();
if (EditorGUI.EndChangeCheck())
{
_serializedObject.ApplyModifiedProperties();
FantasySettingsScriptableObject.Save();
EditorApplication.RepaintHierarchyWindow();
}
base.OnGUI(searchContext);
}
}
private IDisposable CreateSettingsWindowGUIScope()
{
var unityEditorAssembly = System.Reflection.Assembly.GetAssembly(typeof(EditorWindow));
var type = unityEditorAssembly.GetType("UnityEditor.SettingsWindow+GUIScope");
return Activator.CreateInstance(type) as IDisposable;
}
/// <summary>
/// 绘制 csc.rsp 安装状态区域
/// </summary>
private void DrawCscRspInstallationSection()
{
bool isInstalled = CheckCscRspStatus(out bool fileExists, out bool hasDefine);
// 状态框
if (isInstalled)
{
// 已安装 - 绿色背景框
GUIStyle boxStyle = new GUIStyle(GUI.skin.box)
{
padding = new RectOffset(10, 10, 10, 10),
margin = new RectOffset(0, 0, 5, 5)
};
Color originalColor = GUI.color;
GUI.color = new Color(0.7f, 1f, 0.7f); // 绿色背景
EditorGUILayout.BeginVertical(boxStyle);
GUI.color = originalColor;
GUIStyle installedButtonStyle = new GUIStyle(GUI.skin.button)
{
fontSize = 13,
fontStyle = FontStyle.Bold,
fixedHeight = 35
};
Color originalBgColor = GUI.backgroundColor;
GUI.backgroundColor = new Color(0.3f, 0.8f, 0.3f); // 绿色
GUI.enabled = false; // 禁用按钮,只显示状态
GUILayout.Button("✓ FANTASY_UNITY 已安装", installedButtonStyle);
GUI.enabled = true;
GUI.backgroundColor = originalBgColor;
EditorGUILayout.Space(3);
EditorGUILayout.HelpBox("编译器配置正确,框架功能已启用", MessageType.Info);
EditorGUILayout.EndVertical();
}
else
{
// 未安装 - 橙黄色背景框
GUIStyle boxStyle = new GUIStyle(GUI.skin.box)
{
padding = new RectOffset(10, 10, 10, 10),
margin = new RectOffset(0, 0, 5, 5)
};
Color originalColor = GUI.color;
GUI.color = Color.red; // 橙黄色背景
EditorGUILayout.BeginVertical(boxStyle);
GUI.color = originalColor;
EditorGUILayout.Space(8);
// 醒目的大按钮
GUIStyle buttonStyle = new GUIStyle(GUI.skin.button)
{
fontSize = 13,
fontStyle = FontStyle.Bold,
fixedHeight = 35
};
Color originalBgColor = GUI.backgroundColor;
GUI.backgroundColor = new Color(0.3f, 0.8f, 1f);
if (GUILayout.Button("点击安装 FANTASY_UNITY", buttonStyle))
{
InstallFantasyUnityDefine();
}
GUI.backgroundColor = originalBgColor;
EditorGUILayout.Space(3);
EditorGUILayout.HelpBox("安装后可能需要重新编译项目才能生效", MessageType.Info);
EditorGUILayout.EndVertical();
}
}
/// <summary>
/// 检测 csc.rsp 文件状态
/// </summary>
/// <param name="fileExists">文件是否存在</param>
/// <param name="hasDefine">是否包含 FANTASY_UNITY 定义</param>
/// <returns>是否已正确安装</returns>
private bool CheckCscRspStatus(out bool fileExists, out bool hasDefine)
{
string cscRspPath = Path.Combine(Application.dataPath, "csc.rsp");
fileExists = File.Exists(cscRspPath);
hasDefine = false;
if (fileExists)
{
string content = File.ReadAllText(cscRspPath);
// 使用正则表达式精确匹配 FANTASY_UNITY确保是完整的单词不是其他定义的一部分
// 匹配条件FANTASY_UNITY 后面是分号、空白字符、换行或文件结束
hasDefine = System.Text.RegularExpressions.Regex.IsMatch(
content,
@"\bFANTASY_UNITY\b"
);
}
return fileExists && hasDefine;
}
/// <summary>
/// 安装 FANTASY_UNITY 定义到 csc.rsp 文件
/// </summary>
private void InstallFantasyUnityDefine()
{
string cscRspPath = Path.Combine(Application.dataPath, "csc.rsp");
try
{
if (!File.Exists(cscRspPath))
{
// 创建新文件
File.WriteAllText(cscRspPath, "-define:FANTASY_UNITY\n");
}
else
{
// 读取现有内容
string content = File.ReadAllText(cscRspPath);
// 使用正则表达式精确检测,避免误判(例如 FANTASY_UNITY123
bool hasFantasyUnity = System.Text.RegularExpressions.Regex.IsMatch(
content,
@"\bFANTASY_UNITY\b"
);
if (!hasFantasyUnity)
{
// 查找是否已有 -define: 行
string[] lines = content.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
bool defineLineFound = false;
for (int i = 0; i < lines.Length; i++)
{
if (lines[i].TrimStart().StartsWith("-define:"))
{
// 添加到现有的 define 行
if (lines[i].EndsWith(";"))
{
lines[i] = lines[i] + "FANTASY_UNITY";
}
else
{
lines[i] = lines[i] + ";FANTASY_UNITY";
}
defineLineFound = true;
break;
}
}
if (defineLineFound)
{
content = string.Join("\n", lines) + "\n";
}
else
{
// 添加新的 define 行到文件开头
content = "-define:FANTASY_UNITY\n" + content;
}
File.WriteAllText(cscRspPath, content);
}
}
AssetDatabase.Refresh();
EditorUtility.DisplayDialog("成功", "FANTASY_UNITY 已经安装成功。\n\n重新编译后生效。", "确定");
}
catch (Exception ex)
{
Debug.LogError($"安装 FANTASY_UNITY 失败: {ex.Message}");
EditorUtility.DisplayDialog("错误", $"安装失败:\n{ex.Message}", "确定");
}
}
/// <summary>
/// 绘制分隔线
/// </summary>
private void DrawSectionDivider()
{
EditorGUILayout.Space(5);
Rect rect = EditorGUILayout.GetControlRect(false, 1);
rect.height = 1;
// 绘制细线
EditorGUI.DrawRect(rect, new Color(0.5f, 0.5f, 0.5f, 0.3f));
EditorGUILayout.Space(5);
}
/// <summary>
/// 绘制程序集自动拷贝设置区域
/// </summary>
private void DrawAssemblyCopySection()
{
// 标题
GUIStyle titleStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 14,
alignment = TextAnchor.MiddleLeft
};
EditorGUILayout.LabelField("程序集自动拷贝", titleStyle);
EditorGUILayout.Space(5);
// 功能说明
GUIStyle descStyle = new GUIStyle(EditorStyles.wordWrappedLabel)
{
fontSize = 11,
normal = { textColor = Color.gray }
};
EditorGUILayout.LabelField("Unity 编译后自动将指定的程序集文件DLL + PDB复制到目标目录用于热更新或其他用途", descStyle);
EditorGUILayout.Space(8);
// 主开关区域
GUIStyle boxStyle = new GUIStyle(GUI.skin.box)
{
padding = new RectOffset(12, 12, 12, 12),
margin = new RectOffset(0, 0, 0, 8)
};
EditorGUILayout.BeginVertical(boxStyle);
// 开关 - 更大更明显
EditorGUILayout.BeginHorizontal();
GUIStyle bigToggleStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 13
};
bool newValue = EditorGUILayout.ToggleLeft("启用自动拷贝", _autoCopyAssembly.boolValue, bigToggleStyle);
if (newValue != _autoCopyAssembly.boolValue)
{
_autoCopyAssembly.boolValue = newValue;
}
GUILayout.FlexibleSpace();
if (_autoCopyAssembly.boolValue)
{
GUIStyle statusLabel = new GUIStyle(EditorStyles.miniLabel)
{
fontSize = 11,
normal = { textColor = new Color(0f, 0.6f, 0f) }
};
EditorGUILayout.LabelField("● 已启用", statusLabel, GUILayout.Width(60));
}
else
{
GUIStyle statusLabel = new GUIStyle(EditorStyles.miniLabel)
{
fontSize = 11,
normal = { textColor = Color.gray }
};
EditorGUILayout.LabelField("○ 已禁用", statusLabel, GUILayout.Width(60));
}
EditorGUILayout.EndHorizontal();
// 如果启用了自动拷贝,显示详细配置
if (_autoCopyAssembly.boolValue)
{
EditorGUILayout.Space(15);
// 步骤 1设置输出目录
DrawStepHeader("1", "设置输出目录");
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PropertyField(_hotUpdatePath, GUIContent.none);
if (GUILayout.Button("选择文件夹", GUILayout.Width(80)))
{
string path = EditorUtility.OpenFolderPanel("选择程序集输出目录", _hotUpdatePath.stringValue, "");
if (!string.IsNullOrEmpty(path))
{
_hotUpdatePath.stringValue = path;
}
}
EditorGUILayout.EndHorizontal();
// 路径状态提示
if (string.IsNullOrEmpty(_hotUpdatePath.stringValue))
{
EditorGUILayout.Space(3);
GUIStyle warningStyle = new GUIStyle(EditorStyles.miniLabel)
{
normal = { textColor = new Color(0.8f, 0.4f, 0f) }
};
EditorGUILayout.LabelField("⚠ 请先设置输出目录", warningStyle);
}
else if (!Directory.Exists(_hotUpdatePath.stringValue))
{
EditorGUILayout.Space(3);
GUIStyle warningStyle = new GUIStyle(EditorStyles.miniLabel)
{
normal = { textColor = new Color(0.6f, 0.6f, 0f) }
};
EditorGUILayout.LabelField(" 目录不存在,编译时将自动创建", warningStyle);
}
else
{
EditorGUILayout.Space(3);
GUIStyle successStyle = new GUIStyle(EditorStyles.miniLabel)
{
normal = { textColor = new Color(0f, 0.6f, 0f) }
};
EditorGUILayout.LabelField("✓ 目录已配置", successStyle);
}
EditorGUILayout.Space(15);
// 步骤 2选择要拷贝的程序集
DrawStepHeader("2", "程序集列表");
GUIStyle hintStyle = new GUIStyle(EditorStyles.miniLabel)
{
fontSize = 10,
normal = { textColor = Color.gray }
};
EditorGUILayout.LabelField("选择需要自动拷贝的热更新程序集", hintStyle);
EditorGUILayout.Space(10);
// 程序集列表区域
if (_hotUpdateAssemblyDefinitions.arraySize == 0)
{
// 空状态提示框
Color originalColor = GUI.color;
GUI.color = new Color(0.95f, 0.95f, 0.95f);
EditorGUILayout.BeginVertical(GUI.skin.box);
GUI.color = originalColor;
EditorGUILayout.Space(10);
GUIStyle emptyIconStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 20,
alignment = TextAnchor.MiddleCenter,
normal = { textColor = new Color(0.6f, 0.6f, 0.6f) }
};
EditorGUILayout.LabelField("📦", emptyIconStyle);
GUIStyle emptyTextStyle = new GUIStyle(EditorStyles.label)
{
fontSize = 11,
alignment = TextAnchor.MiddleCenter,
normal = { textColor = Color.gray }
};
EditorGUILayout.LabelField("暂未添加任何程序集", emptyTextStyle);
EditorGUILayout.Space(5);
GUIStyle addHintStyle = new GUIStyle(EditorStyles.miniLabel)
{
fontSize = 10,
alignment = TextAnchor.MiddleCenter,
normal = { textColor = new Color(0.3f, 0.6f, 1f) }
};
EditorGUILayout.LabelField("点击下方的 + 按钮添加程序集", addHintStyle);
EditorGUILayout.Space(10);
EditorGUILayout.EndVertical();
EditorGUILayout.Space(5);
}
// 程序集列表
EditorGUILayout.PropertyField(_hotUpdateAssemblyDefinitions, new GUIContent(""), true);
if (_hotUpdateAssemblyDefinitions.arraySize > 0)
{
EditorGUILayout.Space(5);
GUIStyle successStyle = new GUIStyle(EditorStyles.miniLabel)
{
normal = { textColor = new Color(0f, 0.6f, 0f) }
};
EditorGUILayout.LabelField($"✓ 已添加 {_hotUpdateAssemblyDefinitions.arraySize} 个程序集", successStyle);
}
// 配置完成状态总结
bool isConfigured = !string.IsNullOrEmpty(_hotUpdatePath.stringValue) && _hotUpdateAssemblyDefinitions.arraySize > 0;
if (isConfigured)
{
Color originalColor = GUI.color;
GUI.color = new Color(0.8f, 1f, 0.8f);
EditorGUILayout.BeginVertical(GUI.skin.box);
GUI.color = originalColor;
GUIStyle summaryStyle = new GUIStyle(EditorStyles.miniLabel)
{
fontSize = 11,
normal = { textColor = new Color(0f, 0.5f, 0f) }
};
EditorGUILayout.LabelField($"✓ 配置完成!编译后将自动拷贝 {_hotUpdateAssemblyDefinitions.arraySize} 个程序集到目标目录", summaryStyle);
EditorGUILayout.EndVertical();
}
}
else
{
EditorGUILayout.Space(8);
EditorGUILayout.HelpBox("启用后可在 Unity 编译完成时自动拷贝程序集文件", MessageType.Info);
}
EditorGUILayout.EndVertical();
}
/// <summary>
/// 绘制步骤标题
/// </summary>
private void DrawStepHeader(string stepNumber, string title)
{
EditorGUILayout.BeginHorizontal();
// 步骤编号
GUIStyle stepStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 12,
normal = { textColor = new Color(0.3f, 0.6f, 1f) }
};
EditorGUILayout.LabelField($"步骤 {stepNumber}:", stepStyle, GUILayout.Width(60));
// 步骤标题
GUIStyle titleStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 12
};
EditorGUILayout.LabelField(title, titleStyle);
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(5);
}
/// <summary>
/// 绘制 Link.xml 生成设置区域
/// </summary>
private void DrawLinkXmlSection()
{
// 标题
GUIStyle titleStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 14,
alignment = TextAnchor.MiddleLeft
};
EditorGUILayout.LabelField("Link.xml 代码裁剪配置", titleStyle);
EditorGUILayout.Space(5);
// 功能说明
GUIStyle descStyle = new GUIStyle(EditorStyles.wordWrappedLabel)
{
fontSize = 11,
normal = { textColor = Color.gray }
};
EditorGUILayout.LabelField("配置 Link.xml 文件以防止 IL2CPP 编译时过度裁剪反射或动态调用的代码,确保运行时正常", descStyle);
EditorGUILayout.Space(8);
GUIStyle boxStyle = new GUIStyle(GUI.skin.box)
{
padding = new RectOffset(12, 12, 12, 12),
margin = new RectOffset(0, 0, 0, 8)
};
EditorGUILayout.BeginVertical(boxStyle);
// 显示配置开关
EditorGUILayout.BeginHorizontal();
GUIStyle bigToggleStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 13
};
_showLinkXmlConfig = EditorGUILayout.ToggleLeft("显示配置", _showLinkXmlConfig, bigToggleStyle);
GUILayout.FlexibleSpace();
if (_showLinkXmlConfig)
{
GUIStyle statusLabel = new GUIStyle(EditorStyles.miniLabel)
{
fontSize = 11,
normal = { textColor = new Color(0.3f, 0.6f, 1f) }
};
EditorGUILayout.LabelField("● 已展开", statusLabel, GUILayout.Width(60));
}
else
{
GUIStyle statusLabel = new GUIStyle(EditorStyles.miniLabel)
{
fontSize = 11,
normal = { textColor = Color.gray }
};
EditorGUILayout.LabelField("○ 已折叠", statusLabel, GUILayout.Width(60));
}
EditorGUILayout.EndHorizontal();
// 如果显示配置,则展示详细内容
if (_showLinkXmlConfig)
{
EditorGUILayout.Space(15);
// 程序集定义配置标题
GUIStyle sectionTitleStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 13
};
EditorGUILayout.LabelField("需要保护的程序集", sectionTitleStyle);
EditorGUILayout.Space(3);
GUIStyle hintStyle = new GUIStyle(EditorStyles.miniLabel)
{
fontSize = 10,
normal = { textColor = Color.gray }
};
EditorGUILayout.LabelField("选择需要在 IL2CPP 编译时防止代码裁剪的程序集", hintStyle);
EditorGUILayout.Space(3);
// 默认包含程序集说明
EditorGUILayout.BeginHorizontal();
GUILayout.Space(5);
GUIStyle defaultInfoStyle = new GUIStyle(EditorStyles.miniLabel)
{
fontSize = 10,
normal = { textColor = new Color(0.4f, 0.6f, 0.8f) }
};
EditorGUILayout.LabelField(" 默认包含Assembly-CSharp、Fantasy.Unity", defaultInfoStyle);
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(10);
// 程序集列表区域
if (_linkAssemblyDefinitions.arraySize == 0)
{
// 空状态提示框
Color originalColor = GUI.color;
GUI.color = new Color(0.95f, 0.95f, 0.95f);
EditorGUILayout.BeginVertical(GUI.skin.box);
GUI.color = originalColor;
EditorGUILayout.Space(10);
GUIStyle emptyIconStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 20,
alignment = TextAnchor.MiddleCenter,
normal = { textColor = new Color(0.6f, 0.6f, 0.6f) }
};
EditorGUILayout.LabelField("📋", emptyIconStyle);
GUIStyle emptyTextStyle = new GUIStyle(EditorStyles.label)
{
fontSize = 11,
alignment = TextAnchor.MiddleCenter,
normal = { textColor = Color.gray }
};
EditorGUILayout.LabelField("暂未添加任何程序集", emptyTextStyle);
EditorGUILayout.Space(5);
GUIStyle addHintStyle = new GUIStyle(EditorStyles.miniLabel)
{
fontSize = 10,
alignment = TextAnchor.MiddleCenter,
normal = { textColor = new Color(0.3f, 0.6f, 1f) }
};
EditorGUILayout.LabelField("点击下方的 + 按钮添加程序集", addHintStyle);
EditorGUILayout.Space(10);
EditorGUILayout.EndVertical();
EditorGUILayout.Space(5);
}
// 程序集列表
EditorGUILayout.PropertyField(_linkAssemblyDefinitions, new GUIContent(""), true);
if (_linkAssemblyDefinitions.arraySize > 0)
{
EditorGUILayout.Space(5);
GUIStyle successStyle = new GUIStyle(EditorStyles.miniLabel)
{
normal = { textColor = new Color(0f, 0.6f, 0f) }
};
EditorGUILayout.LabelField($"✓ 已添加 {_linkAssemblyDefinitions.arraySize} 个程序集", successStyle);
}
EditorGUILayout.Space(15);
// 生成按钮区域
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
GUIStyle buttonStyle = new GUIStyle(GUI.skin.button)
{
fontSize = 13,
fontStyle = FontStyle.Bold,
fixedHeight = 35,
fixedWidth = 200
};
Color originalBgColor = GUI.backgroundColor;
GUI.backgroundColor = new Color(0.4f, 0.7f, 1f);
if (GUILayout.Button("生成 Link.xml 文件", buttonStyle))
{
LinkXmlGenerator.GenerateLinkXml();
EditorUtility.DisplayDialog("操作成功", "Link.xml 文件已生成", "确定");
}
GUI.backgroundColor = originalBgColor;
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
}
else
{
EditorGUILayout.Space(8);
EditorGUILayout.HelpBox("点击上方开关展开配置", MessageType.Info);
}
EditorGUILayout.EndVertical();
}
static FantasySettingsProvider _provider;
[SettingsProvider]
public static SettingsProvider CreateMyCustomSettingsProvider()
{
if (FantasySettingsScriptableObject.Instance && _provider == null)
{
_provider = new FantasySettingsProvider();
using (var so = new SerializedObject(FantasySettingsScriptableObject.Instance))
{
_provider.keywords = GetSearchKeywordsFromSerializedObject(so);
}
}
return _provider;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 19a618703b49e3d4fbcd8223540a77f3
timeCreated: 1688380387

View File

@@ -0,0 +1,25 @@
using UnityEditor;
using UnityEditor.Compilation;
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.Serialization;
namespace Fantasy
{
[ScriptableObjectPath("ProjectSettings/FantasySettings.asset")]
public class FantasySettingsScriptableObject : ScriptableObjectSingleton<FantasySettingsScriptableObject>, ISerializationCallbackReceiver
{
[FormerlySerializedAs("AutoCopyAssembly")]
public bool autoCopyAssembly = false;
[FormerlySerializedAs("HotUpdatePath")]
public string hotUpdatePath;
[FormerlySerializedAs("HotUpdateAssemblyDefinitions")]
public AssemblyDefinitionAsset[] hotUpdateAssemblyDefinitions;
[FormerlySerializedAs("LinkAssemblyDefinitions")]
public AssemblyDefinitionAsset[] linkAssemblyDefinitions;
[FormerlySerializedAs("IncludeAssembly")]
public string[] includeAssembly = new[] { "Assembly-CSharp", "Fantasy.Unity" };
public void OnBeforeSerialize() { }
public void OnAfterDeserialize() { }
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6733837e208a20a488d61c50fb71919c
timeCreated: 1688277120

View File

@@ -0,0 +1,100 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEditorInternal;
using UnityEngine;
// ReSharper disable AssignNullToNotNullAttribute
namespace Fantasy
{
public class ScriptableObjectSingleton<T> : ScriptableObject where T : ScriptableObject
{
private static T _instance;
public static T Instance
{
get
{
if (_instance == null)
{
_instance = Load();
}
return _instance;
}
}
private static T Load()
{
var scriptableObjectPath = GetScriptableObjectPath();
if (string.IsNullOrEmpty(scriptableObjectPath))
{
return null;
}
var loadSerializedFileAndForget = InternalEditorUtility.LoadSerializedFileAndForget(scriptableObjectPath);
if (loadSerializedFileAndForget.Length <= 0)
{
return CreateInstance<T>();
}
return loadSerializedFileAndForget[0] as T;
}
public static void Save(bool saveAsText = true)
{
if (_instance == null)
{
Debug.LogError("Cannot save ScriptableObjectSingleton: no instance!");
return;
}
var scriptableObjectPath = GetScriptableObjectPath();
if (string.IsNullOrEmpty(scriptableObjectPath))
{
return;
}
var directoryName = Path.GetDirectoryName(scriptableObjectPath);
if (!Directory.Exists(directoryName))
{
Directory.CreateDirectory(directoryName);
}
UnityEngine.Object[] obj = { _instance };
InternalEditorUtility.SaveToSerializedFileAndForget(obj, scriptableObjectPath, saveAsText);
}
private static string GetScriptableObjectPath()
{
var scriptableObjectPathAttribute = typeof(T).GetCustomAttribute(typeof(ScriptableObjectPathAttribute)) as ScriptableObjectPathAttribute;
return scriptableObjectPathAttribute?.ScriptableObjectPath;
}
}
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class ScriptableObjectPathAttribute : Attribute
{
internal readonly string ScriptableObjectPath;
public ScriptableObjectPathAttribute(string scriptableObjectPath)
{
if (string.IsNullOrEmpty(scriptableObjectPath))
{
throw new ArgumentException("Invalid relative path (it is empty)");
}
if (scriptableObjectPath[0] == '/')
{
scriptableObjectPath = scriptableObjectPath.Substring(1);
}
ScriptableObjectPath = scriptableObjectPath;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a8963b5817140ad42a1a5cae9d6a4336
timeCreated: 1688278016

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c6603a5cb5b34f44fb04cfca6e80092d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,229 @@
using UnityEngine;
using UnityEditor;
using UnityEngine.Networking;
using System.IO;
using System;
namespace UnityWebSocket.Editor
{
internal class SettingsWindow : EditorWindow
{
static SettingsWindow window = null;
[MenuItem("Tools/UnityWebSocket", priority = 100)]
internal static void Open()
{
if (window != null)
{
window.Close();
}
window = GetWindow<SettingsWindow>(true, "UnityWebSocket");
window.minSize = window.maxSize = new Vector2(600, 310);
window.Show();
window.BeginCheck();
}
private void OnGUI()
{
DrawLogo();
DrawVersion();
DrawSeparator(80);
DrawSeparator(186);
DrawHelper();
DrawFooter();
}
Texture2D logoTex = null;
private void DrawLogo()
{
if (logoTex == null)
{
logoTex = new Texture2D(66, 66);
logoTex.LoadImage(Convert.FromBase64String(LOGO_BASE64.VALUE));
for (int i = 0; i < 66; i++) for (int j = 0; j < 15; j++) logoTex.SetPixel(i, j, Color.clear);
logoTex.Apply();
}
var logoPos = new Rect(10, 10, 66, 66);
GUI.DrawTexture(logoPos, logoTex);
var title = "<color=#3A9AD8><b>UnityWebSocket</b></color>";
var titlePos = new Rect(80, 20, 500, 50);
GUI.Label(titlePos, title, TextStyle(24));
}
private void DrawSeparator(int y)
{
EditorGUI.DrawRect(new Rect(10, y, 580, 1), Color.white * 0.5f);
}
private GUIStyle TextStyle(int fontSize = 10, TextAnchor alignment = TextAnchor.UpperLeft, float alpha = 0.85f)
{
var style = new GUIStyle();
style.fontSize = fontSize;
style.normal.textColor = (EditorGUIUtility.isProSkin ? Color.white : Color.black) * alpha;
style.alignment = alignment;
style.richText = true;
return style;
}
private void DrawVersion()
{
GUI.Label(new Rect(440, 10, 150, 10), "Current Version: " + Settings.VERSION, TextStyle(alignment: TextAnchor.MiddleLeft));
if (string.IsNullOrEmpty(latestVersion))
{
GUI.Label(new Rect(440, 30, 150, 10), "Checking for Updates...", TextStyle(alignment: TextAnchor.MiddleLeft));
}
else if (latestVersion == "unknown")
{
}
else
{
GUI.Label(new Rect(440, 30, 150, 10), "Latest Version: " + latestVersion, TextStyle(alignment: TextAnchor.MiddleLeft));
if (Settings.VERSION == latestVersion)
{
if (GUI.Button(new Rect(440, 50, 150, 18), "Check Update"))
{
latestVersion = "";
changeLog = "";
BeginCheck();
}
}
else
{
if (GUI.Button(new Rect(440, 50, 150, 18), "Update to | " + latestVersion))
{
ShowUpdateDialog();
}
}
}
}
private void ShowUpdateDialog()
{
var isOK = EditorUtility.DisplayDialog("UnityWebSocket",
"Update UnityWebSocket now?\n" + changeLog,
"Update Now", "Cancel");
if (isOK)
{
UpdateVersion();
}
}
private void UpdateVersion()
{
Application.OpenURL(Settings.GITHUB + "/releases");
}
private void DrawHelper()
{
GUI.Label(new Rect(330, 200, 100, 18), "GitHub:", TextStyle(10, TextAnchor.MiddleRight));
if (GUI.Button(new Rect(440, 200, 150, 18), "UnityWebSocket"))
{
Application.OpenURL(Settings.GITHUB);
}
GUI.Label(new Rect(330, 225, 100, 18), "Report:", TextStyle(10, TextAnchor.MiddleRight));
if (GUI.Button(new Rect(440, 225, 150, 18), "Report an Issue"))
{
Application.OpenURL(Settings.GITHUB + "/issues/new");
}
GUI.Label(new Rect(330, 250, 100, 18), "Email:", TextStyle(10, TextAnchor.MiddleRight));
if (GUI.Button(new Rect(440, 250, 150, 18), Settings.EMAIL))
{
var uri = new Uri(string.Format("mailto:{0}?subject={1}", Settings.EMAIL, "UnityWebSocket Feedback"));
Application.OpenURL(uri.AbsoluteUri);
}
GUI.Label(new Rect(330, 275, 100, 18), "QQ群:", TextStyle(10, TextAnchor.MiddleRight));
if (GUI.Button(new Rect(440, 275, 150, 18), Settings.QQ_GROUP))
{
Application.OpenURL(Settings.QQ_GROUP_LINK);
}
}
private void DrawFooter()
{
EditorGUI.DropShadowLabel(new Rect(10, 230, 400, 20), "Developed by " + Settings.AUHTOR);
EditorGUI.DropShadowLabel(new Rect(10, 250, 400, 20), "All rights reserved");
}
UnityWebRequest req;
string changeLog = "";
string latestVersion = "";
void BeginCheck()
{
EditorApplication.update -= VersionCheckUpdate;
EditorApplication.update += VersionCheckUpdate;
req = UnityWebRequest.Get(Settings.GITHUB + "/releases/latest");
req.SendWebRequest();
}
private void VersionCheckUpdate()
{
#if UNITY_2020_3_OR_NEWER
if (req == null
|| req.result == UnityWebRequest.Result.ConnectionError
|| req.result == UnityWebRequest.Result.DataProcessingError
|| req.result == UnityWebRequest.Result.ProtocolError)
#elif UNITY_2018_1_OR_NEWER
if (req == null || req.isNetworkError || req.isHttpError)
#else
if (req == null || req.isError)
#endif
{
EditorApplication.update -= VersionCheckUpdate;
latestVersion = "unknown";
return;
}
if (req.isDone)
{
EditorApplication.update -= VersionCheckUpdate;
latestVersion = req.url.Substring(req.url.LastIndexOf("/") + 1).TrimStart('v');
if (Settings.VERSION != latestVersion)
{
var text = req.downloadHandler.text;
var st = text.IndexOf("content=\"" + latestVersion);
st = st > 0 ? text.IndexOf("\n", st) : -1;
var end = st > 0 ? text.IndexOf("\" />", st) : -1;
if (st > 0 && end > st)
{
changeLog = text.Substring(st + 1, end - st - 1).Trim();
changeLog = changeLog.Replace("\r", "");
changeLog = changeLog.Replace("\n", "\n- ");
changeLog = "\nCHANGE LOG: \n- " + changeLog + "\n";
}
}
Repaint();
}
}
}
internal static class LOGO_BASE64
{
internal const string VALUE = "iVBORw0KGgoAAAANSUhEUgAAAEIAAABCCAMAAADUivDaAAAAq1BMVEUAAABKmtcvjtYzl" +
"9szmNszl9syl9k0mNs0mNwzmNs0mNszl9szl9s0mNs0mNwzmNw0mNwyltk0mNw0mNwzl9s0mNsymNs0mNszmNwzmNwzm" +
"NszmNs0mNwzl9w0mNwzmNw0mNs0mNs0mNwzl9wzmNs0mNwzmNs0mNwzl90zmNszmNszl9szmNsxmNszmNszmNw0mNwzm" +
"Nw0mNs2neM4pe41mt43ouo2oOY5qfM+UHlaAAAAMnRSTlMAAwXN3sgI+/069MSCK6M/MA74h9qfFHB8STWMJ9OSdmNcI" +
"8qya1IeF+/U0EIa57mqmFTYJe4AAAN3SURBVFjD7ZbpkppAFEa/bgVBREF2kEVGFNeZsM77P1kadURnJkr8k1Qlx1Khu" +
"/pw7+2lwH/+YcgfMBBLG7VocwDamzH+wJBB8Qhjve2f0TdrGwjei6o4Ub/nM/APw5Z7vvSB/qrCrqbD6fBEVtigeMxks" +
"fX9zWbj+z1jhqgSBplQ50eGo4614WXlRAzgrRhmtSfvxAn7pB0N5ObaKKZZuU5/d37IBcBgUQwqDuf7Z2gUmVAl4NGNr" +
"/UeHxV5n39ulbaKLI86h6HilmM5M1aN126lpNhtl59yeTsp8nUMvpNC1J3bh5FtfVRk+bJrJunn5d4U4piJ/Vw9eXgsj" +
"4ZpZaCjg9waZkIpnBWLJ44OwoNu60F2UnSaEkKv4XnAlCpm6B4F/aKMDiyGi2L8SEEAVdxNLuzmgV7nFwObEe2xQVuX+" +
"RV1lWetga3w+cN1sXgvm4cJH8OEgZC1DPKhfF/BIymmQrMjq/x65FUeEkDup8GxoexZmznHCvANtXU/CAq13yimhQGtm" +
"H4VCPnBBL1fTKo3CqEcvq7Lb/OwHxWTYlyw+JmjKoVvDLVOQB4pVsM8K8smgvLCxZDlIijwyOEc+nr/msMwK0+GQWGBd" +
"tmhjv8icTds1s2ammaFh04QLLe69NK7guP6mTDMaw3o6nAX/Z7EXUskPSvWEWg4srVlp5NTDXv9Lce9HGN5eeG4nj5Yz" +
"ACteU2wQLo4MBtJfd1nw5nG1/s9zwUQ6pykL1TQjqdeuvQW0naz2XKLYL4Cwzr4vj+OQdD96CSp7Lrynp4aeFF0xdm5q" +
"6OFtFfPv7URxpWJNjd/N+3+I9+1klMav12Qtgbt9R2JaIopjkzaPtOFq4KxUpqfUMSFnQrySWjLoQzRZS4HMH84ME1ej" +
"S1YJpQZ3B+sR1uCQJSBdGdCk1eAEgORR88KK05W8dh2MA+A/SKCYu3mCJ0Ek7HBx4HHeuwYy5G3x8hSMTJcOMFbinCsn" +
"hO1V1aszGULvA0g4UFsb4VA0hAFcyo6cgLsAoT7uUtGAH5wQKQle0wuLyxLTaNyJEYwxw4wSljLK1TP8CAaOyhBMMEsj" +
"OBoXgo7VGElFkSWL+vef1RF2YNXeRWYzQBTpkhC8KaZHhuIogArkQLKClBZjU26B2IZgGz+cpZkHl8g3fYUaW/YP2kb2" +
"M/V97JY/vZN859n+QmO7XtC9Bf2jAAAAABJRU5ErkJggg==";
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c797632152a42a04e944c54c3bdacad8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: