- 引入 UnityGameFramework
- 使用 Luban 替代原来的 UGF 的数据表
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
using GameFramework;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Editor.ResourceTools;
|
||||
|
||||
namespace SepCore.Editor
|
||||
{
|
||||
public sealed class DefaultBuildEventHandler : IBuildEventHandler
|
||||
{
|
||||
public bool ContinueOnFailure
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPreprocessAllPlatforms(string productName, string companyName, string gameIdentifier, string gameFrameworkVersion, string unityVersion, string applicableGameVersion, int internalResourceVersion,
|
||||
Platform platforms, AssetBundleCompressionType assetBundleCompression, string compressionHelperTypeName, bool additionalCompressionSelected, bool forceRebuildAssetBundleSelected, string buildEventHandlerTypeName, string outputDirectory, BuildAssetBundleOptions buildAssetBundleOptions,
|
||||
string workingPath, bool outputPackageSelected, string outputPackagePath, bool outputFullSelected, string outputFullPath, bool outputPackedSelected, string outputPackedPath, string buildReportPath)
|
||||
{
|
||||
string streamingAssetsPath = GameFramework.Utility.Path.GetRegularPath(Path.Combine(Application.dataPath, "StreamingAssets"));
|
||||
string[] fileNames = Directory.GetFiles(streamingAssetsPath, "*", SearchOption.AllDirectories);
|
||||
foreach (string fileName in fileNames)
|
||||
{
|
||||
if (fileName.Contains(".gitkeep"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
File.Delete(fileName);
|
||||
}
|
||||
|
||||
GameFramework.Utility.Path.RemoveEmptyDirectory(streamingAssetsPath);
|
||||
}
|
||||
|
||||
public void OnPostprocessAllPlatforms(string productName, string companyName, string gameIdentifier, string gameFrameworkVersion, string unityVersion, string applicableGameVersion, int internalResourceVersion,
|
||||
Platform platforms, AssetBundleCompressionType assetBundleCompression, string compressionHelperTypeName, bool additionalCompressionSelected, bool forceRebuildAssetBundleSelected, string buildEventHandlerTypeName, string outputDirectory, BuildAssetBundleOptions buildAssetBundleOptions,
|
||||
string workingPath, bool outputPackageSelected, string outputPackagePath, bool outputFullSelected, string outputFullPath, bool outputPackedSelected, string outputPackedPath, string buildReportPath)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnPreprocessPlatform(Platform platform, string workingPath, bool outputPackageSelected, string outputPackagePath, bool outputFullSelected, string outputFullPath, bool outputPackedSelected, string outputPackedPath)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnBuildAssetBundlesComplete(Platform platform, string workingPath, bool outputPackageSelected, string outputPackagePath, bool outputFullSelected, string outputFullPath, bool outputPackedSelected, string outputPackedPath, AssetBundleManifest assetBundleManifest)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnOutputUpdatableVersionListData(Platform platform, string versionListPath, int versionListLength, int versionListHashCode, int versionListCompressedLength, int versionListCompressedHashCode)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnPostprocessPlatform(Platform platform, string workingPath, bool outputPackageSelected, string outputPackagePath, bool outputFullSelected, string outputFullPath, bool outputPackedSelected, string outputPackedPath, bool isSuccess)
|
||||
{
|
||||
if (!outputPackageSelected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (platform != Platform.Windows)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string streamingAssetsPath = GameFramework.Utility.Path.GetRegularPath(Path.Combine(Application.dataPath, "StreamingAssets"));
|
||||
string[] fileNames = Directory.GetFiles(outputPackagePath, "*", SearchOption.AllDirectories);
|
||||
foreach (string fileName in fileNames)
|
||||
{
|
||||
string destFileName = GameFramework.Utility.Path.GetRegularPath(Path.Combine(streamingAssetsPath, fileName.Substring(outputPackagePath.Length)));
|
||||
FileInfo destFileInfo = new FileInfo(destFileName);
|
||||
if (!destFileInfo.Directory.Exists)
|
||||
{
|
||||
destFileInfo.Directory.Create();
|
||||
}
|
||||
|
||||
File.Copy(fileName, destFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 64311189c3f9ae140b59a31db9831950
|
||||
timeCreated: 1528026151
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
//------------------------------------------------------------
|
||||
// Game Framework
|
||||
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
|
||||
// Homepage: https://gameframework.cn/
|
||||
// Feedback: mailto:ellan@gameframework.cn
|
||||
//------------------------------------------------------------
|
||||
|
||||
using GameFramework;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Editor;
|
||||
using UnityGameFramework.Editor.ResourceTools;
|
||||
|
||||
namespace SepCore.Editor
|
||||
{
|
||||
public static class GameFrameworkConfigs
|
||||
{
|
||||
[BuildSettingsConfigPath] public static string BuildSettingsConfig =
|
||||
GameFramework.Utility.Path.GetRegularPath(Path.Combine(Application.dataPath,
|
||||
"GameMain/Configs/BuildSettings.xml"));
|
||||
|
||||
[ResourceCollectionConfigPath] public static string ResourceCollectionConfig =
|
||||
GameFramework.Utility.Path.GetRegularPath(Path.Combine(Application.dataPath,
|
||||
"GameMain/Configs/ResourceCollection.xml"));
|
||||
|
||||
[ResourceEditorConfigPath] public static string ResourceEditorConfig =
|
||||
GameFramework.Utility.Path.GetRegularPath(Path.Combine(Application.dataPath,
|
||||
"GameMain/Configs/ResourceEditor.xml"));
|
||||
|
||||
[ResourceBuilderConfigPath] public static string ResourceBuilderConfig =
|
||||
GameFramework.Utility.Path.GetRegularPath(Path.Combine(Application.dataPath,
|
||||
"GameMain/Configs/ResourceBuilder.xml"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36618c709c84656438dd91e565226606
|
||||
timeCreated: 1528026150
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,58 @@
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using System.IO;
|
||||
|
||||
namespace SepCore.Editor
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
public class SceneSwitchLeft
|
||||
{
|
||||
static SceneSwitchLeft()
|
||||
{
|
||||
// 注册到全局工具栏绘制事件
|
||||
SceneView.duringSceneGui += OnSceneGUI;
|
||||
}
|
||||
|
||||
static void OnSceneGUI(SceneView sceneView)
|
||||
{
|
||||
// 在 Scene 视图的左上角绘制一个下拉菜单
|
||||
Handles.BeginGUI();
|
||||
|
||||
GUILayout.BeginArea(new Rect(10, 10, 200, 100));
|
||||
if (EditorGUILayout.DropdownButton(new GUIContent("快速切换场景"), FocusType.Passive,
|
||||
EditorStyles.toolbarDropDown))
|
||||
{
|
||||
ShowSceneMenu();
|
||||
}
|
||||
|
||||
GUILayout.EndArea();
|
||||
|
||||
Handles.EndGUI();
|
||||
}
|
||||
|
||||
static void ShowSceneMenu()
|
||||
{
|
||||
GenericMenu menu = new GenericMenu();
|
||||
|
||||
// 查找项目中所有启用(Enabled)的场景
|
||||
string[] sceneGuids = AssetDatabase.FindAssets("t:Scene");
|
||||
|
||||
foreach (string guid in sceneGuids)
|
||||
{
|
||||
string path = AssetDatabase.GUIDToAssetPath(guid);
|
||||
string name = Path.GetFileNameWithoutExtension(path);
|
||||
|
||||
menu.AddItem(new GUIContent(name), false, () =>
|
||||
{
|
||||
if (EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
|
||||
{
|
||||
EditorSceneManager.OpenScene(path);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
menu.ShowAsContext();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e7f70b2d5386e4941ab913255891bb99
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "SepCore.Editor",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:363c5eb08ff8e6a439b85e37b8c20d96",
|
||||
"GUID:a2d8a19598eca814496b089021d08d60",
|
||||
"GUID:a04b272484018094ab427253f20c416c",
|
||||
"GUID:11ec41dbc3557e6499b2845ba531a9b0",
|
||||
"GUID:73a59a40d41e9ce48b3e0515aeec4217"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0bf8adba443c0d04ba34d7379815d50d
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b109521fca5d74d5bbdc6f3b44fc87d3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,963 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using SepCore.UI;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Callbacks;
|
||||
using UnityEditor.Compilation;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SepCore.Editor
|
||||
{
|
||||
public static class UIAssetsTools
|
||||
{
|
||||
public const string GeneratedNamespace = "SepCore.UI";
|
||||
public const string GeneratedDirectory = "Assets/GameMain/Scripts/UI";
|
||||
|
||||
private const string PendingGenerationKey = "UI.Serialization.PendingGeneration";
|
||||
private static bool finalizeQueued;
|
||||
|
||||
private static readonly HashSet<string> CSharpKeywords = new HashSet<string>(StringComparer.Ordinal)
|
||||
{
|
||||
"abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class",
|
||||
"const", "continue", "decimal", "default", "delegate", "do", "double", "else", "enum", "event",
|
||||
"explicit", "extern", "false", "finally", "fixed", "float", "for", "foreach", "goto", "if",
|
||||
"implicit", "in", "int", "interface", "internal", "is", "lock", "long", "namespace", "new",
|
||||
"null", "object", "operator", "out", "override", "params", "private", "protected", "public",
|
||||
"readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc", "static",
|
||||
"string", "struct", "switch", "this", "throw", "true", "try", "typeof", "uint", "ulong",
|
||||
"unchecked", "unsafe", "ushort", "using", "virtual", "void", "volatile", "while"
|
||||
};
|
||||
|
||||
[Serializable]
|
||||
private sealed class PendingGenerationBatch
|
||||
{
|
||||
public List<PendingGeneration> entries = new List<PendingGeneration>();
|
||||
public string primaryScriptPath;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private sealed class PendingGeneration
|
||||
{
|
||||
public string rootGlobalId;
|
||||
public string prefabAssetPath;
|
||||
public string prefabRootRelativePath;
|
||||
public string scenePath;
|
||||
public string hierarchyPath;
|
||||
public string viewClassFullName;
|
||||
public string formClassFullName;
|
||||
public string viewScriptPath;
|
||||
public string formScriptPath;
|
||||
}
|
||||
|
||||
private sealed class GenerationUnit
|
||||
{
|
||||
public UISerializationRoot root;
|
||||
public string viewClassName;
|
||||
public string formClassName;
|
||||
public string viewScriptPath;
|
||||
public string formScriptPath;
|
||||
public string viewSource;
|
||||
public string formSource;
|
||||
public bool viewChanged;
|
||||
public bool formChanged;
|
||||
|
||||
public bool SourceChanged => viewChanged || formChanged;
|
||||
}
|
||||
|
||||
private sealed class Binding
|
||||
{
|
||||
public Component component;
|
||||
public UISerializationRoot nestedRoot;
|
||||
public string variableName;
|
||||
public string csharpTypeName;
|
||||
}
|
||||
|
||||
[MenuItem("Assets/Create View Script", false, 2000)]
|
||||
private static void GenerateFromAssetsMenu()
|
||||
{
|
||||
GenerateWithDialog(GetSelectedRoot());
|
||||
}
|
||||
|
||||
[MenuItem("Assets/Create View Script", true)]
|
||||
private static bool ValidateAssetsMenu()
|
||||
{
|
||||
return GetSelectedRoot() != null;
|
||||
}
|
||||
|
||||
[MenuItem("GameObject/Create View Script", false, 30)]
|
||||
private static void GenerateFromGameObjectMenu()
|
||||
{
|
||||
GenerateWithDialog(GetSelectedRoot());
|
||||
}
|
||||
|
||||
[MenuItem("GameObject/Create View Script", true)]
|
||||
private static bool ValidateGameObjectMenu()
|
||||
{
|
||||
return GetSelectedRoot() != null;
|
||||
}
|
||||
|
||||
[MenuItem("CONTEXT/UISerializationRoot/Create View Script")]
|
||||
private static void GenerateFromComponentMenu(MenuCommand command)
|
||||
{
|
||||
GenerateWithDialog(command.context as UISerializationRoot);
|
||||
}
|
||||
|
||||
public static void GenerateWithDialog(UISerializationRoot root)
|
||||
{
|
||||
try
|
||||
{
|
||||
Generate(root, true);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogException(exception);
|
||||
EditorUtility.DisplayDialog("Create UI View Script", exception.Message, "OK");
|
||||
}
|
||||
}
|
||||
|
||||
public static string Generate(UISerializationRoot root, bool confirmOverwrite)
|
||||
{
|
||||
if (root == null)
|
||||
{
|
||||
throw new InvalidOperationException("Select a GameObject or Prefab with UISerializationRoot.");
|
||||
}
|
||||
|
||||
List<UISerializationRoot> generationRoots = CollectGenerationRoots(root);
|
||||
List<GenerationUnit> units = BuildGenerationUnits(generationRoots);
|
||||
GenerationUnit primaryUnit = units[units.Count - 1];
|
||||
|
||||
List<string> changedExistingFiles = units
|
||||
.Where(unit => unit.SourceChanged)
|
||||
.SelectMany(unit => new[] { unit.viewScriptPath, unit.formScriptPath })
|
||||
.Where(File.Exists)
|
||||
.ToList();
|
||||
if (confirmOverwrite && changedExistingFiles.Count > 0)
|
||||
{
|
||||
string message = "Overwrite the following generated View/Form scripts?\n\n" +
|
||||
string.Join("\n", changedExistingFiles.ToArray());
|
||||
if (!EditorUtility.DisplayDialog("Regenerate UI View/Form", message, "Overwrite", "Cancel"))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
EnsureFolder(GeneratedDirectory);
|
||||
SaveDirtyRootAssets(units);
|
||||
|
||||
var pendingBatch = new PendingGenerationBatch
|
||||
{
|
||||
primaryScriptPath = primaryUnit.viewScriptPath
|
||||
};
|
||||
for (int i = 0; i < units.Count; i++)
|
||||
{
|
||||
pendingBatch.entries.Add(CreatePendingGeneration(units[i]));
|
||||
}
|
||||
SessionState.SetString(PendingGenerationKey, JsonUtility.ToJson(pendingBatch));
|
||||
|
||||
bool anySourceChanged = false;
|
||||
for (int i = 0; i < units.Count; i++)
|
||||
{
|
||||
GenerationUnit unit = units[i];
|
||||
if (!unit.SourceChanged)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
EnsureFolder(Path.GetDirectoryName(unit.viewScriptPath));
|
||||
if (unit.viewChanged)
|
||||
{
|
||||
File.WriteAllText(unit.viewScriptPath, unit.viewSource, new UTF8Encoding(false));
|
||||
anySourceChanged = true;
|
||||
}
|
||||
|
||||
if (unit.formChanged)
|
||||
{
|
||||
File.WriteAllText(unit.formScriptPath, unit.formSource, new UTF8Encoding(false));
|
||||
anySourceChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (anySourceChanged)
|
||||
{
|
||||
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
|
||||
}
|
||||
|
||||
UnityEngine.Object scriptAsset = AssetDatabase.LoadAssetAtPath<MonoScript>(primaryUnit.viewScriptPath);
|
||||
if (scriptAsset != null)
|
||||
{
|
||||
EditorGUIUtility.PingObject(scriptAsset);
|
||||
}
|
||||
|
||||
if (anySourceChanged)
|
||||
{
|
||||
CompilationPipeline.RequestScriptCompilation();
|
||||
}
|
||||
else
|
||||
{
|
||||
QueuePendingGeneration();
|
||||
}
|
||||
|
||||
Debug.Log("Generated " + units.Count + " UI View/Form script pair(s); primary script: " +
|
||||
primaryUnit.viewScriptPath);
|
||||
return primaryUnit.viewScriptPath;
|
||||
}
|
||||
|
||||
public static string GetViewClassName(UISerializationRoot root)
|
||||
{
|
||||
if (root == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return GetBaseName(root) + "View";
|
||||
}
|
||||
|
||||
public static string GetFormClassName(UISerializationRoot root)
|
||||
{
|
||||
if (root == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return GetBaseName(root) + "Form";
|
||||
}
|
||||
|
||||
public static string GetBaseName(UISerializationRoot root)
|
||||
{
|
||||
if (root == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string sourceName = root.gameObject.name.Replace("(Clone)", string.Empty).Trim();
|
||||
if (sourceName.EndsWith("Form", StringComparison.Ordinal) && sourceName.Length > 4)
|
||||
{
|
||||
sourceName = sourceName.Substring(0, sourceName.Length - 4);
|
||||
}
|
||||
else if (sourceName.EndsWith("UI", StringComparison.Ordinal) && sourceName.Length > 2)
|
||||
{
|
||||
sourceName = sourceName.Substring(0, sourceName.Length - 2);
|
||||
}
|
||||
|
||||
string identifier = ToPascalIdentifier(sourceName);
|
||||
if (string.IsNullOrEmpty(identifier))
|
||||
{
|
||||
throw new InvalidOperationException("The UI root name cannot be converted to a C# class name.");
|
||||
}
|
||||
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public static bool IsValidIdentifier(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value) || CSharpKeywords.Contains(value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!char.IsLetter(value[0]) && value[0] != '_')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 1; i < value.Length; i++)
|
||||
{
|
||||
if (!char.IsLetterOrDigit(value[i]) && value[i] != '_')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[InitializeOnLoadMethod]
|
||||
private static void InitializePendingGeneration()
|
||||
{
|
||||
QueuePendingGeneration();
|
||||
}
|
||||
|
||||
[DidReloadScripts]
|
||||
private static void OnScriptsReloaded()
|
||||
{
|
||||
QueuePendingGeneration();
|
||||
}
|
||||
|
||||
private static void QueuePendingGeneration()
|
||||
{
|
||||
if (finalizeQueued || string.IsNullOrEmpty(SessionState.GetString(PendingGenerationKey, string.Empty)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
finalizeQueued = true;
|
||||
EditorApplication.delayCall += RunQueuedPendingGeneration;
|
||||
}
|
||||
|
||||
private static void RunQueuedPendingGeneration()
|
||||
{
|
||||
finalizeQueued = false;
|
||||
FinalizePendingGeneration();
|
||||
}
|
||||
|
||||
private static void FinalizePendingGeneration()
|
||||
{
|
||||
string json = SessionState.GetString(PendingGenerationKey, string.Empty);
|
||||
if (string.IsNullOrEmpty(json))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PendingGenerationBatch batch = JsonUtility.FromJson<PendingGenerationBatch>(json);
|
||||
if (batch == null || batch.entries == null || batch.entries.Count == 0)
|
||||
{
|
||||
SessionState.EraseString(PendingGenerationKey);
|
||||
Debug.LogError("The pending UI View generation data is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
var resolvedViewTypes = new List<Type>(batch.entries.Count);
|
||||
var resolvedFormTypes = new List<Type>(batch.entries.Count);
|
||||
for (int i = 0; i < batch.entries.Count; i++)
|
||||
{
|
||||
PendingGeneration pending = batch.entries[i];
|
||||
Type viewType = FindViewType(pending.viewClassFullName);
|
||||
if (viewType == null)
|
||||
{
|
||||
Debug.LogError("Generated UI view type was not found after compilation: " + pending.viewClassFullName);
|
||||
return;
|
||||
}
|
||||
|
||||
Type formType = FindViewType(pending.formClassFullName);
|
||||
if (formType == null)
|
||||
{
|
||||
Debug.LogError("Generated UI form type was not found after compilation: " + pending.formClassFullName);
|
||||
return;
|
||||
}
|
||||
|
||||
resolvedViewTypes.Add(viewType);
|
||||
resolvedFormTypes.Add(formType);
|
||||
}
|
||||
|
||||
SessionState.EraseString(PendingGenerationKey);
|
||||
UnityEngine.Object primaryTarget = null;
|
||||
for (int i = 0; i < batch.entries.Count; i++)
|
||||
{
|
||||
PendingGeneration pending = batch.entries[i];
|
||||
Type viewType = resolvedViewTypes[i];
|
||||
Type formType = resolvedFormTypes[i];
|
||||
if (!string.IsNullOrEmpty(pending.prefabAssetPath))
|
||||
{
|
||||
primaryTarget = ApplyToPrefab(pending, viewType, formType);
|
||||
continue;
|
||||
}
|
||||
|
||||
UISerializationRoot root = ResolveSceneRoot(pending);
|
||||
if (root == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Could not resolve the UI root after compilation. The scripts were generated at " +
|
||||
pending.viewScriptPath + " and " + pending.formScriptPath + ".");
|
||||
}
|
||||
|
||||
ApplyGeneratedComponents(root, viewType, formType, true);
|
||||
EditorSceneManager.MarkSceneDirty(root.gameObject.scene);
|
||||
primaryTarget = root.gameObject;
|
||||
}
|
||||
|
||||
if (primaryTarget != null)
|
||||
{
|
||||
Selection.activeObject = primaryTarget;
|
||||
EditorGUIUtility.PingObject(primaryTarget);
|
||||
}
|
||||
|
||||
Debug.Log("Attached and populated " + batch.entries.Count + " generated UI View/Form pair(s).");
|
||||
}
|
||||
|
||||
private static List<UISerializationRoot> CollectGenerationRoots(UISerializationRoot selectedRoot)
|
||||
{
|
||||
bool persistentContext = EditorUtility.IsPersistent(selectedRoot);
|
||||
var result = new List<UISerializationRoot>();
|
||||
var visited = new HashSet<UISerializationRoot>();
|
||||
CollectGenerationRootsRecursive(selectedRoot, persistentContext, visited, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void CollectGenerationRootsRecursive(
|
||||
UISerializationRoot current,
|
||||
bool persistentContext,
|
||||
HashSet<UISerializationRoot> visited,
|
||||
List<UISerializationRoot> result)
|
||||
{
|
||||
current = GetCanonicalRoot(current, persistentContext);
|
||||
if (current == null || !visited.Add(current))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
current.RefreshItems();
|
||||
for (int i = 0; i < current.NestedViewReferences.Count; i++)
|
||||
{
|
||||
UISerializationRoot nestedRoot = current.NestedViewReferences[i].Root;
|
||||
if (nestedRoot != null)
|
||||
{
|
||||
CollectGenerationRootsRecursive(nestedRoot, persistentContext, visited, result);
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(current);
|
||||
}
|
||||
|
||||
private static UISerializationRoot GetCanonicalRoot(UISerializationRoot root, bool persistentContext)
|
||||
{
|
||||
if (!persistentContext || root == null)
|
||||
{
|
||||
return root;
|
||||
}
|
||||
|
||||
UISerializationRoot source = PrefabUtility.GetCorrespondingObjectFromSource(root);
|
||||
return source != null ? source : root;
|
||||
}
|
||||
|
||||
private static List<GenerationUnit> BuildGenerationUnits(List<UISerializationRoot> roots)
|
||||
{
|
||||
var units = new List<GenerationUnit>(roots.Count);
|
||||
var classOwners = new Dictionary<string, UISerializationRoot>(StringComparer.Ordinal);
|
||||
for (int i = 0; i < roots.Count; i++)
|
||||
{
|
||||
UISerializationRoot root = roots[i];
|
||||
root.RefreshItems();
|
||||
string viewClassName = GetViewClassName(root);
|
||||
string formClassName = GetFormClassName(root);
|
||||
string baseName = GetBaseName(root);
|
||||
UISerializationRoot existingOwner;
|
||||
if (classOwners.TryGetValue(viewClassName, out existingOwner) && existingOwner != root)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Multiple serialization roots generate the same View class '" + viewClassName +
|
||||
"'. Rename one of the UI roots.");
|
||||
}
|
||||
|
||||
classOwners[viewClassName] = root;
|
||||
if (classOwners.TryGetValue(formClassName, out existingOwner) && existingOwner != root)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Multiple serialization roots generate the same Form class '" + formClassName +
|
||||
"'. Rename one of the UI roots.");
|
||||
}
|
||||
|
||||
classOwners[formClassName] = root;
|
||||
|
||||
List<Binding> bindings = CollectBindings(root);
|
||||
string folderPath = GeneratedDirectory + "/" + baseName;
|
||||
string viewScriptPath = folderPath + "/" + viewClassName + ".cs";
|
||||
string formScriptPath = folderPath + "/" + formClassName + ".cs";
|
||||
string viewSource = BuildViewSource(viewClassName, bindings);
|
||||
string formSource = BuildFormSource(formClassName, viewClassName);
|
||||
units.Add(new GenerationUnit
|
||||
{
|
||||
root = root,
|
||||
viewClassName = viewClassName,
|
||||
formClassName = formClassName,
|
||||
viewScriptPath = viewScriptPath,
|
||||
formScriptPath = formScriptPath,
|
||||
viewSource = viewSource,
|
||||
formSource = formSource,
|
||||
viewChanged = !File.Exists(viewScriptPath) || File.ReadAllText(viewScriptPath) != viewSource,
|
||||
formChanged = !File.Exists(formScriptPath) || File.ReadAllText(formScriptPath) != formSource
|
||||
});
|
||||
}
|
||||
|
||||
return units;
|
||||
}
|
||||
|
||||
private static List<Binding> CollectBindings(UISerializationRoot root)
|
||||
{
|
||||
var bindings = new List<Binding>();
|
||||
var names = new HashSet<string>(StringComparer.Ordinal);
|
||||
|
||||
for (int i = 0; i < root.SerializationItems.Count; i++)
|
||||
{
|
||||
UISerializationItem item = root.SerializationItems[i];
|
||||
if (item == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
item.RefreshComponents();
|
||||
for (int j = 0; j < item.ComponentReferences.Count; j++)
|
||||
{
|
||||
UISerializationItem.ComponentReference reference = item.ComponentReferences[j];
|
||||
if (reference == null || !reference.GenerateReference)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Component component = reference.Component;
|
||||
string variableName = NormalizeAndValidateVariableName(reference.VariableName, item.transform, names);
|
||||
if (component == null)
|
||||
{
|
||||
throw new InvalidOperationException("A selected UI reference is missing on " + GetHierarchyPath(item.transform) + ".");
|
||||
}
|
||||
if (component.GetType().Namespace != null && component.GetType().Namespace.StartsWith("UnityEditor", StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Editor-only components cannot be referenced by a runtime View: " + component.GetType().FullName + ".");
|
||||
}
|
||||
|
||||
bindings.Add(new Binding
|
||||
{
|
||||
component = component,
|
||||
variableName = variableName,
|
||||
csharpTypeName = GetCSharpTypeName(component.GetType())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < root.NestedViewReferences.Count; i++)
|
||||
{
|
||||
UISerializationRoot.NestedViewReference reference = root.NestedViewReferences[i];
|
||||
if (reference == null || !reference.GenerateReference)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UISerializationRoot nestedRoot = reference.Root;
|
||||
if (nestedRoot == null)
|
||||
{
|
||||
throw new InvalidOperationException("A nested UI View reference is missing below " + GetHierarchyPath(root.transform) + ".");
|
||||
}
|
||||
|
||||
string variableName = NormalizeAndValidateVariableName(reference.VariableName, nestedRoot.transform, names);
|
||||
bindings.Add(new Binding
|
||||
{
|
||||
nestedRoot = nestedRoot,
|
||||
variableName = variableName,
|
||||
csharpTypeName = "global::" + GeneratedNamespace + "." + GetViewClassName(nestedRoot)
|
||||
});
|
||||
}
|
||||
|
||||
return bindings;
|
||||
}
|
||||
|
||||
private static string NormalizeAndValidateVariableName(string value, Transform owner, HashSet<string> names)
|
||||
{
|
||||
string variableName = value == null ? string.Empty : value.Trim();
|
||||
if (!IsValidIdentifier(variableName))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Invalid C# field name '" + variableName + "' on " + GetHierarchyPath(owner) + ".");
|
||||
}
|
||||
if (!names.Add(variableName))
|
||||
{
|
||||
throw new InvalidOperationException("Duplicate UI field name '" + variableName + "'.");
|
||||
}
|
||||
if (typeof(MonoBehaviour).GetMember(variableName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Length > 0)
|
||||
{
|
||||
throw new InvalidOperationException("UI field name '" + variableName + "' conflicts with a MonoBehaviour member.");
|
||||
}
|
||||
|
||||
return variableName;
|
||||
}
|
||||
|
||||
private static string BuildViewSource(string className, List<Binding> bindings)
|
||||
{
|
||||
var builder = new StringBuilder(512 + bindings.Count * 96);
|
||||
builder.AppendLine("// <auto-generated />");
|
||||
builder.AppendLine("using UnityEngine;");
|
||||
builder.AppendLine();
|
||||
builder.Append("namespace ").AppendLine(GeneratedNamespace);
|
||||
builder.AppendLine("{");
|
||||
builder.AppendLine(" [DisallowMultipleComponent]");
|
||||
builder.Append(" public partial class ").Append(className).AppendLine(" : MonoBehaviour");
|
||||
builder.AppendLine(" {");
|
||||
|
||||
for (int i = 0; i < bindings.Count; i++)
|
||||
{
|
||||
Binding binding = bindings[i];
|
||||
builder.Append(" [SerializeField] public ")
|
||||
.Append(binding.csharpTypeName)
|
||||
.Append(' ')
|
||||
.Append(binding.variableName)
|
||||
.AppendLine(";");
|
||||
}
|
||||
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine("}");
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static string BuildFormSource(string className, string viewClassName)
|
||||
{
|
||||
var builder = new StringBuilder(256);
|
||||
builder.AppendLine("// <auto-generated />");
|
||||
builder.AppendLine("using UnityEngine;");
|
||||
builder.AppendLine();
|
||||
builder.Append("namespace ").AppendLine(GeneratedNamespace);
|
||||
builder.AppendLine("{");
|
||||
builder.AppendLine(" [DisallowMultipleComponent]");
|
||||
builder.Append(" public partial class ").Append(className).AppendLine(" : UGuiForm");
|
||||
builder.AppendLine(" {");
|
||||
builder.Append(" [SerializeField] private ").Append(viewClassName).AppendLine(" _view = null;");
|
||||
builder.AppendLine();
|
||||
builder.Append(" public ").Append(viewClassName).AppendLine(" View => _view;");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine("}");
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static PendingGeneration CreatePendingGeneration(GenerationUnit unit)
|
||||
{
|
||||
UISerializationRoot root = unit.root;
|
||||
bool isPrefabAsset = EditorUtility.IsPersistent(root) && PrefabUtility.IsPartOfPrefabAsset(root);
|
||||
string prefabAssetPath = isPrefabAsset ? AssetDatabase.GetAssetPath(root.gameObject) : string.Empty;
|
||||
string relativePath = string.Empty;
|
||||
if (isPrefabAsset)
|
||||
{
|
||||
GameObject prefabRoot = AssetDatabase.LoadAssetAtPath<GameObject>(prefabAssetPath);
|
||||
if (prefabRoot == null)
|
||||
{
|
||||
throw new InvalidOperationException("Could not load Prefab asset: " + prefabAssetPath);
|
||||
}
|
||||
|
||||
relativePath = GetRelativePath(prefabRoot.transform, root.transform);
|
||||
}
|
||||
|
||||
GlobalObjectId globalObjectId = GlobalObjectId.GetGlobalObjectIdSlow(root);
|
||||
return new PendingGeneration
|
||||
{
|
||||
rootGlobalId = globalObjectId.ToString(),
|
||||
prefabAssetPath = prefabAssetPath,
|
||||
prefabRootRelativePath = relativePath,
|
||||
scenePath = root.gameObject.scene.path,
|
||||
hierarchyPath = GetHierarchyPath(root.transform),
|
||||
viewClassFullName = GeneratedNamespace + "." + unit.viewClassName,
|
||||
formClassFullName = GeneratedNamespace + "." + unit.formClassName,
|
||||
viewScriptPath = unit.viewScriptPath,
|
||||
formScriptPath = unit.formScriptPath
|
||||
};
|
||||
}
|
||||
|
||||
private static UnityEngine.Object ApplyToPrefab(PendingGeneration pending, Type viewType, Type formType)
|
||||
{
|
||||
GameObject contents = PrefabUtility.LoadPrefabContents(pending.prefabAssetPath);
|
||||
try
|
||||
{
|
||||
Transform rootTransform = string.IsNullOrEmpty(pending.prefabRootRelativePath)
|
||||
? contents.transform
|
||||
: contents.transform.Find(pending.prefabRootRelativePath);
|
||||
UISerializationRoot root = rootTransform == null ? null : rootTransform.GetComponent<UISerializationRoot>();
|
||||
if (root == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The Prefab no longer contains the expected UISerializationRoot at '" +
|
||||
pending.prefabRootRelativePath + "': " + pending.prefabAssetPath);
|
||||
}
|
||||
|
||||
ApplyGeneratedComponents(root, viewType, formType, false);
|
||||
PrefabUtility.SaveAsPrefabAsset(contents, pending.prefabAssetPath);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrefabUtility.UnloadPrefabContents(contents);
|
||||
}
|
||||
|
||||
AssetDatabase.ImportAsset(pending.prefabAssetPath, ImportAssetOptions.ForceUpdate);
|
||||
return AssetDatabase.LoadAssetAtPath<GameObject>(pending.prefabAssetPath);
|
||||
}
|
||||
|
||||
public static void ApplyGeneratedComponentsToRoot(UISerializationRoot root)
|
||||
{
|
||||
if (root == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
root.RefreshItems();
|
||||
for (int i = 0; i < root.NestedViewReferences.Count; i++)
|
||||
{
|
||||
UISerializationRoot nestedRoot = root.NestedViewReferences[i].Root;
|
||||
if (nestedRoot != null)
|
||||
{
|
||||
ApplyGeneratedComponentsToRoot(nestedRoot);
|
||||
}
|
||||
}
|
||||
|
||||
Type viewType = FindViewType(GeneratedNamespace + "." + GetViewClassName(root));
|
||||
Type formType = FindViewType(GeneratedNamespace + "." + GetFormClassName(root));
|
||||
if (viewType == null || formType == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyGeneratedComponents(root, viewType, formType, false);
|
||||
}
|
||||
|
||||
private static void ApplyGeneratedComponents(UISerializationRoot root, Type viewType, Type formType,
|
||||
bool useUndo)
|
||||
{
|
||||
Component view = ApplyViewReferences(root, viewType, useUndo);
|
||||
Component form = root.GetComponent(formType);
|
||||
if (form == null)
|
||||
{
|
||||
form = useUndo ? Undo.AddComponent(root.gameObject, formType) : root.gameObject.AddComponent(formType);
|
||||
}
|
||||
else if (useUndo)
|
||||
{
|
||||
Undo.RecordObject(form, "Populate UI form view reference");
|
||||
}
|
||||
|
||||
var serializedForm = new SerializedObject(form);
|
||||
serializedForm.UpdateIfRequiredOrScript();
|
||||
SerializedProperty viewProperty = serializedForm.FindProperty("_view");
|
||||
if (viewProperty == null)
|
||||
{
|
||||
throw new InvalidOperationException("Generated field was not found: _view on " + formType.FullName + ".");
|
||||
}
|
||||
|
||||
viewProperty.objectReferenceValue = view;
|
||||
serializedForm.ApplyModifiedProperties();
|
||||
EditorUtility.SetDirty(form);
|
||||
if (useUndo && PrefabUtility.IsPartOfPrefabInstance(form))
|
||||
{
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(form);
|
||||
}
|
||||
}
|
||||
|
||||
private static Component ApplyViewReferences(UISerializationRoot root, Type viewType, bool useUndo)
|
||||
{
|
||||
root.RefreshItems();
|
||||
List<Binding> bindings = CollectBindings(root);
|
||||
Component view = root.GetComponent(viewType);
|
||||
if (view == null)
|
||||
{
|
||||
view = useUndo ? Undo.AddComponent(root.gameObject, viewType) : root.gameObject.AddComponent(viewType);
|
||||
}
|
||||
else if (useUndo)
|
||||
{
|
||||
Undo.RecordObject(view, "Populate UI view references");
|
||||
}
|
||||
|
||||
var serializedView = new SerializedObject(view);
|
||||
serializedView.UpdateIfRequiredOrScript();
|
||||
for (int i = 0; i < bindings.Count; i++)
|
||||
{
|
||||
Binding binding = bindings[i];
|
||||
SerializedProperty property = serializedView.FindProperty(binding.variableName);
|
||||
if (property == null)
|
||||
{
|
||||
throw new InvalidOperationException("Generated field was not found: " + binding.variableName + ".");
|
||||
}
|
||||
|
||||
UnityEngine.Object target = binding.component;
|
||||
if (binding.nestedRoot != null)
|
||||
{
|
||||
string nestedViewFullName = GeneratedNamespace + "." + GetViewClassName(binding.nestedRoot);
|
||||
Type nestedViewType = FindViewType(nestedViewFullName);
|
||||
if (nestedViewType == null)
|
||||
{
|
||||
throw new InvalidOperationException("Nested UI view type was not found: " + nestedViewFullName + ".");
|
||||
}
|
||||
|
||||
target = binding.nestedRoot.GetComponent(nestedViewType);
|
||||
if (target == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Nested UI root '" + GetHierarchyPath(binding.nestedRoot.transform) +
|
||||
"' does not have its generated View component '" + nestedViewFullName + "'.");
|
||||
}
|
||||
}
|
||||
|
||||
property.objectReferenceValue = target;
|
||||
}
|
||||
|
||||
serializedView.ApplyModifiedProperties();
|
||||
EditorUtility.SetDirty(view);
|
||||
if (useUndo && PrefabUtility.IsPartOfPrefabInstance(view))
|
||||
{
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(view);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private static Type FindViewType(string fullName)
|
||||
{
|
||||
return TypeCache.GetTypesDerivedFrom<MonoBehaviour>()
|
||||
.FirstOrDefault(type => type.FullName == fullName);
|
||||
}
|
||||
|
||||
private static UISerializationRoot ResolveSceneRoot(PendingGeneration pending)
|
||||
{
|
||||
GlobalObjectId parsed;
|
||||
if (GlobalObjectId.TryParse(pending.rootGlobalId, out parsed))
|
||||
{
|
||||
UnityEngine.Object resolved = GlobalObjectId.GlobalObjectIdentifierToObjectSlow(parsed);
|
||||
UISerializationRoot root = resolved as UISerializationRoot;
|
||||
if (root != null)
|
||||
{
|
||||
return root;
|
||||
}
|
||||
|
||||
GameObject resolvedObject = resolved as GameObject;
|
||||
if (resolvedObject != null)
|
||||
{
|
||||
root = resolvedObject.GetComponent<UISerializationRoot>();
|
||||
if (root != null)
|
||||
{
|
||||
return root;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UISerializationRoot[] loadedRoots = Resources.FindObjectsOfTypeAll<UISerializationRoot>();
|
||||
for (int i = 0; i < loadedRoots.Length; i++)
|
||||
{
|
||||
UISerializationRoot root = loadedRoots[i];
|
||||
if (root != null && !EditorUtility.IsPersistent(root) && root.gameObject.scene.path == pending.scenePath &&
|
||||
GetHierarchyPath(root.transform) == pending.hierarchyPath)
|
||||
{
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static UISerializationRoot GetSelectedRoot()
|
||||
{
|
||||
GameObject selected = Selection.activeGameObject;
|
||||
if (selected == null)
|
||||
{
|
||||
selected = Selection.activeObject as GameObject;
|
||||
}
|
||||
|
||||
return selected == null ? null : selected.GetComponent<UISerializationRoot>();
|
||||
}
|
||||
|
||||
private static string GetCSharpTypeName(Type type)
|
||||
{
|
||||
if (type.IsArray)
|
||||
{
|
||||
return GetCSharpTypeName(type.GetElementType()) + "[]";
|
||||
}
|
||||
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
string definition = type.GetGenericTypeDefinition().FullName ?? type.Name;
|
||||
int arityMarker = definition.IndexOf('`');
|
||||
if (arityMarker >= 0)
|
||||
{
|
||||
definition = definition.Substring(0, arityMarker);
|
||||
}
|
||||
|
||||
string arguments = string.Join(", ", type.GetGenericArguments().Select(GetCSharpTypeName).ToArray());
|
||||
return "global::" + definition.Replace('+', '.') + "<" + arguments + ">";
|
||||
}
|
||||
|
||||
return "global::" + (type.FullName ?? type.Name).Replace('+', '.');
|
||||
}
|
||||
|
||||
private static void SaveDirtyRootAssets(List<GenerationUnit> units)
|
||||
{
|
||||
var savedPaths = new HashSet<string>(StringComparer.Ordinal);
|
||||
for (int i = 0; i < units.Count; i++)
|
||||
{
|
||||
UISerializationRoot root = units[i].root;
|
||||
if (root == null || !EditorUtility.IsPersistent(root))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string assetPath = AssetDatabase.GetAssetPath(root);
|
||||
if (string.IsNullOrEmpty(assetPath) || !savedPaths.Add(assetPath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AssetDatabase.SaveAssetIfDirty(AssetDatabase.GUIDFromAssetPath(assetPath));
|
||||
}
|
||||
}
|
||||
|
||||
private static string ToPascalIdentifier(string value)
|
||||
{
|
||||
var builder = new StringBuilder(value.Length + 8);
|
||||
bool capitalizeNext = true;
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
{
|
||||
char current = value[i];
|
||||
if (!char.IsLetterOrDigit(current) && current != '_')
|
||||
{
|
||||
capitalizeNext = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (builder.Length == 0 && char.IsDigit(current))
|
||||
{
|
||||
builder.Append('_');
|
||||
}
|
||||
|
||||
builder.Append(capitalizeNext ? char.ToUpperInvariant(current) : current);
|
||||
capitalizeNext = false;
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static string GetRelativePath(Transform root, Transform target)
|
||||
{
|
||||
if (root == target)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var parts = new Stack<string>();
|
||||
Transform current = target;
|
||||
while (current != null && current != root)
|
||||
{
|
||||
parts.Push(current.name);
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
if (current != root)
|
||||
{
|
||||
throw new InvalidOperationException("The serialization root is not inside the expected Prefab asset.");
|
||||
}
|
||||
|
||||
return string.Join("/", parts.ToArray());
|
||||
}
|
||||
|
||||
private static string GetHierarchyPath(Transform target)
|
||||
{
|
||||
string path = target.name;
|
||||
Transform current = target.parent;
|
||||
while (current != null)
|
||||
{
|
||||
path = current.name + "/" + path;
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private static void EnsureFolder(string folder)
|
||||
{
|
||||
string[] parts = folder.Split('/');
|
||||
string current = parts[0];
|
||||
for (int i = 1; i < parts.Length; i++)
|
||||
{
|
||||
string next = current + "/" + parts[i];
|
||||
if (!AssetDatabase.IsValidFolder(next))
|
||||
{
|
||||
AssetDatabase.CreateFolder(current, parts[i]);
|
||||
}
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 48c1b6017a08547aa94ad17c9611d7b2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,65 @@
|
||||
using System.Collections.Generic;
|
||||
using SepCore.UI;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SepCore.Editor
|
||||
{
|
||||
[CustomEditor(typeof(UISerializationItem))]
|
||||
internal sealed class UISerializationItemEditor : UnityEditor.Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
var item = (UISerializationItem)target;
|
||||
item.RefreshComponents();
|
||||
serializedObject.UpdateIfRequiredOrScript();
|
||||
|
||||
SerializedProperty references = serializedObject.FindProperty("componentReferences");
|
||||
EditorGUILayout.LabelField("Components", EditorStyles.boldLabel);
|
||||
|
||||
var selectedNames = new HashSet<string>();
|
||||
for (int i = 0; i < references.arraySize; i++)
|
||||
{
|
||||
SerializedProperty entry = references.GetArrayElementAtIndex(i);
|
||||
SerializedProperty component = entry.FindPropertyRelative("component");
|
||||
SerializedProperty generate = entry.FindPropertyRelative("generateReference");
|
||||
SerializedProperty variableName = entry.FindPropertyRelative("variableName");
|
||||
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
generate.boolValue = EditorGUILayout.Toggle(generate.boolValue, GUILayout.Width(18f));
|
||||
|
||||
using (new EditorGUI.DisabledScope(true))
|
||||
{
|
||||
EditorGUILayout.ObjectField(component.objectReferenceValue, typeof(Component), true);
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (generate.boolValue)
|
||||
{
|
||||
variableName.stringValue = EditorGUILayout.TextField("Variable Name", variableName.stringValue);
|
||||
string normalized = variableName.stringValue == null ? string.Empty : variableName.stringValue.Trim();
|
||||
if (!UIAssetsTools.IsValidIdentifier(normalized))
|
||||
{
|
||||
EditorGUILayout.HelpBox("Enter a valid C# field name.", MessageType.Error);
|
||||
}
|
||||
else if (!selectedNames.Add(normalized))
|
||||
{
|
||||
EditorGUILayout.HelpBox("This field name is already selected on this item.", MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Refresh Components"))
|
||||
{
|
||||
Undo.RecordObject(item, "Refresh UI serialization components");
|
||||
item.RefreshComponents();
|
||||
serializedObject.UpdateIfRequiredOrScript();
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5f17238526324f3d98afa8e8c999814
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,211 @@
|
||||
using System.Collections.Generic;
|
||||
using SepCore.UI;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SepCore.Editor
|
||||
{
|
||||
[CustomEditor(typeof(UISerializationRoot))]
|
||||
internal sealed class UISerializationRootEditor : UnityEditor.Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
var root = (UISerializationRoot)target;
|
||||
root.RefreshItems();
|
||||
serializedObject.UpdateIfRequiredOrScript();
|
||||
|
||||
int referenceCount = 0;
|
||||
bool hasValidationError = false;
|
||||
var selectedNames = new HashSet<string>();
|
||||
for (int i = 0; i < root.SerializationItems.Count; i++)
|
||||
{
|
||||
UISerializationItem item = root.SerializationItems[i];
|
||||
if (item == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = 0; j < item.ComponentReferences.Count; j++)
|
||||
{
|
||||
UISerializationItem.ComponentReference reference = item.ComponentReferences[j];
|
||||
if (reference.GenerateReference)
|
||||
{
|
||||
referenceCount++;
|
||||
string variableName = reference.VariableName == null ? string.Empty : reference.VariableName.Trim();
|
||||
if (!UIAssetsTools.IsValidIdentifier(variableName) || !selectedNames.Add(variableName))
|
||||
{
|
||||
hasValidationError = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SerializedProperty nestedViews = serializedObject.FindProperty("nestedViewReferences");
|
||||
for (int i = 0; i < nestedViews.arraySize; i++)
|
||||
{
|
||||
SerializedProperty entry = nestedViews.GetArrayElementAtIndex(i);
|
||||
if (entry.FindPropertyRelative("generateReference").boolValue)
|
||||
{
|
||||
referenceCount++;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("View Class", UIAssetsTools.GetViewClassName(root));
|
||||
EditorGUILayout.LabelField("Form Class", UIAssetsTools.GetFormClassName(root));
|
||||
EditorGUILayout.LabelField("Owned Items", root.SerializationItems.Count.ToString());
|
||||
EditorGUILayout.LabelField("Nested Views", nestedViews.arraySize.ToString());
|
||||
EditorGUILayout.LabelField("Generated References", referenceCount.ToString());
|
||||
EditorGUILayout.Space(4f);
|
||||
|
||||
EditorGUILayout.LabelField("Owned Items", EditorStyles.boldLabel);
|
||||
for (int i = 0; i < root.SerializationItems.Count; i++)
|
||||
{
|
||||
UISerializationItem item = root.SerializationItems[i];
|
||||
if (item == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
EditorGUILayout.BeginHorizontal(EditorStyles.helpBox);
|
||||
EditorGUILayout.LabelField(GetRelativePath(root.transform, item.transform));
|
||||
if (GUILayout.Button("Select", GUILayout.Width(56f)))
|
||||
{
|
||||
Selection.activeGameObject = item.gameObject;
|
||||
EditorGUIUtility.PingObject(item.gameObject);
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(4f);
|
||||
EditorGUILayout.LabelField("Nested Views", EditorStyles.boldLabel);
|
||||
if (nestedViews.arraySize == 0)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Nested UISerializationRoot components will appear here and form serialization boundaries.", MessageType.Info);
|
||||
}
|
||||
|
||||
for (int i = 0; i < nestedViews.arraySize; i++)
|
||||
{
|
||||
SerializedProperty entry = nestedViews.GetArrayElementAtIndex(i);
|
||||
SerializedProperty nestedRootProperty = entry.FindPropertyRelative("root");
|
||||
SerializedProperty generate = entry.FindPropertyRelative("generateReference");
|
||||
SerializedProperty variableName = entry.FindPropertyRelative("variableName");
|
||||
var nestedRoot = nestedRootProperty.objectReferenceValue as UISerializationRoot;
|
||||
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
generate.boolValue = EditorGUILayout.Toggle(generate.boolValue, GUILayout.Width(18f));
|
||||
using (new EditorGUI.DisabledScope(true))
|
||||
{
|
||||
EditorGUILayout.ObjectField(nestedRoot, typeof(UISerializationRoot), true);
|
||||
}
|
||||
if (nestedRoot != null && GUILayout.Button("Select", GUILayout.Width(56f)))
|
||||
{
|
||||
Selection.activeGameObject = nestedRoot.gameObject;
|
||||
EditorGUIUtility.PingObject(nestedRoot.gameObject);
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (nestedRoot != null)
|
||||
{
|
||||
EditorGUILayout.LabelField("View Class", UIAssetsTools.GetViewClassName(nestedRoot));
|
||||
}
|
||||
|
||||
if (generate.boolValue)
|
||||
{
|
||||
variableName.stringValue = EditorGUILayout.TextField("Variable Name", variableName.stringValue);
|
||||
string normalized = variableName.stringValue == null ? string.Empty : variableName.stringValue.Trim();
|
||||
if (!UIAssetsTools.IsValidIdentifier(normalized))
|
||||
{
|
||||
hasValidationError = true;
|
||||
EditorGUILayout.HelpBox("Enter a valid C# field name.", MessageType.Error);
|
||||
}
|
||||
else if (!selectedNames.Add(normalized))
|
||||
{
|
||||
hasValidationError = true;
|
||||
EditorGUILayout.HelpBox("This field name conflicts with another generated reference.", MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
if (hasValidationError)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Fix invalid or duplicate generated field names before creating View scripts.", MessageType.Error);
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
EditorGUILayout.Space(4f);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("Refresh Items"))
|
||||
{
|
||||
Undo.RecordObject(root, "Refresh UI serialization items");
|
||||
root.RefreshItems();
|
||||
}
|
||||
using (new EditorGUI.DisabledScope(hasValidationError))
|
||||
{
|
||||
if (GUILayout.Button("Create View & Form Scripts"))
|
||||
{
|
||||
UIAssetsTools.GenerateWithDialog(root);
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private static string GetRelativePath(Transform root, Transform target)
|
||||
{
|
||||
if (target == root)
|
||||
{
|
||||
return root.name;
|
||||
}
|
||||
|
||||
string path = target.name;
|
||||
Transform current = target.parent;
|
||||
while (current != null && current != root)
|
||||
{
|
||||
path = current.name + "/" + path;
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
[InitializeOnLoad]
|
||||
internal static class UISerializationRootWatcher
|
||||
{
|
||||
private static bool refreshQueued;
|
||||
|
||||
static UISerializationRootWatcher()
|
||||
{
|
||||
EditorApplication.hierarchyChanged += QueueRefresh;
|
||||
}
|
||||
|
||||
private static void QueueRefresh()
|
||||
{
|
||||
if (refreshQueued || EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
refreshQueued = true;
|
||||
EditorApplication.delayCall += RefreshLoadedRoots;
|
||||
}
|
||||
|
||||
private static void RefreshLoadedRoots()
|
||||
{
|
||||
refreshQueued = false;
|
||||
UISerializationRoot[] roots = Resources.FindObjectsOfTypeAll<UISerializationRoot>();
|
||||
for (int i = 0; i < roots.Length; i++)
|
||||
{
|
||||
UISerializationRoot root = roots[i];
|
||||
if (root == null || EditorUtility.IsPersistent(root) || !root.gameObject.scene.IsValid() || !root.gameObject.scene.isLoaded)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
root.RefreshItems();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8c6a8249053ff4cf0a368b8a25b9946b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c30c9f4e90334fe0b836c0f7cf5cc75
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user