├── .gitignore ├── LICENSE.meta ├── README.md.meta ├── package.json.meta ├── Runtime.meta ├── Runtime ├── TypeDisMatchException.cs ├── GM.Dynamic.Runtime.asmdef.meta ├── StructOverSizeException.cs ├── DynamicField.cs.meta ├── DynamicObject.cs.meta ├── DynamicUtil.cs.meta ├── DynamicRefCollection.cs.meta ├── StructOverSizeException.cs.meta ├── TypeDisMatchException.cs.meta ├── GM.Dynamic.Runtime.asmdef ├── DynamicUtil.cs ├── DynamicObject.cs ├── DynamicField.cs └── DynamicRefCollection.cs ├── package.json ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0b2ddf5829e9d47818954e0e1ab58d29 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 37af19f0cec8649ffa05ae91d74de7c0 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c1fc985d134464a578da72ab0bee2315 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 297044db5f0814959b8113cae0fff7a5 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/TypeDisMatchException.cs: -------------------------------------------------------------------------------- 1 | namespace GM.Dynamic 2 | { 3 | public class TypeDisMatchException : System.Exception 4 | { 5 | public TypeDisMatchException(string message) : base(message) { } 6 | } 7 | } -------------------------------------------------------------------------------- /Runtime/GM.Dynamic.Runtime.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7d7c90aba575346919b530916d24efd9 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/StructOverSizeException.cs: -------------------------------------------------------------------------------- 1 | namespace GM.Dynamic 2 | { 3 | public class StructOverSizeException : System.Exception 4 | { 5 | public StructOverSizeException(string message) : base(message) { } 6 | } 7 | } -------------------------------------------------------------------------------- /Runtime/DynamicField.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 28c311da4ebb04187915c20bba891b1c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/DynamicObject.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dea0aeb64013842d688873f254b65698 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/DynamicUtil.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f5eb5abb96fd4464480966858b11b17e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/DynamicRefCollection.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fac89df652a8d46cc8d1aec180b6b096 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/StructOverSizeException.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5c2eb6bb2207b4f1e868872c4cdffb51 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/TypeDisMatchException.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 35971361500514467a938a6cd8aa26f1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.gm.dynamic", 3 | "version": "1.0.1", 4 | "displayName": "Dynamic For Unity", 5 | "description": "Unity Support Dynamic", 6 | "unity": "2022.3", 7 | "author": { 8 | "name": "Goatman", 9 | "email": "goatman914@gmail.com", 10 | "url": "https://github.com/Goatman1996/" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Runtime/GM.Dynamic.Runtime.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GM.Dynamic.Runtime", 3 | "rootNamespace": "", 4 | "references": [], 5 | "includePlatforms": [], 6 | "excludePlatforms": [], 7 | "allowUnsafeCode": true, 8 | "overrideReferences": false, 9 | "precompiledReferences": [], 10 | "autoReferenced": true, 11 | "defineConstraints": [], 12 | "versionDefines": [], 13 | "noEngineReferences": false 14 | } -------------------------------------------------------------------------------- /Runtime/DynamicUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace GM.Dynamic 5 | { 6 | public static class DynamicUtil 7 | { 8 | private static Dictionary cachedTypeName = new(); 9 | public static string GetTypeNameCached() 10 | { 11 | return GetTypeNameCached(typeof(T)); 12 | } 13 | 14 | public static string GetTypeNameCached(Type type) 15 | { 16 | if (!cachedTypeName.ContainsKey(type)) 17 | { 18 | cachedTypeName.Add(type, type.FullName); 19 | } 20 | return cachedTypeName[type]; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Goatman1996 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Runtime/DynamicObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | 6 | namespace GM.Dynamic 7 | { 8 | public sealed class DynamicObject 9 | { 10 | private DynamicRefCollection fieldCollection = new(); 11 | 12 | public T TryAs() => this.TryAs(DynamicUtil.GetTypeNameCached()); 13 | public T TryAs(string key) 14 | { 15 | return this.fieldCollection.Get(key, false).TryAs(); 16 | } 17 | 18 | public ref T As() => ref this.As(DynamicUtil.GetTypeNameCached()); 19 | public ref T As(string key) 20 | { 21 | this.fieldCollection.Add(key); 22 | return ref this.fieldCollection.Get(key).As(); 23 | } 24 | 25 | public bool Is() => Is(DynamicUtil.GetTypeNameCached()); 26 | public bool Is(string key) 27 | { 28 | var field = this.fieldCollection.Get(key, false); 29 | return field.Is(); 30 | } 31 | 32 | public void Reset() => Reset(DynamicUtil.GetTypeNameCached()); 33 | public void Reset(string key) 34 | { 35 | this.fieldCollection.Remove(key); 36 | } 37 | 38 | public DynamicEnumerable Every() 39 | { 40 | return new DynamicEnumerable(this.fieldCollection); 41 | } 42 | 43 | /// 44 | /// Can only be found by Every(T) 45 | /// Every(T) is able to Delete(Reset) the record 46 | /// 47 | public void RecordObject(T value) where T : class 48 | { 49 | if (value == null) return; 50 | this.fieldCollection.GetEmpty().As() = value; 51 | } 52 | 53 | /// 54 | /// Can only be found by Every(T) 55 | /// Every(T) is able to Delete(Reset) the record 56 | /// 57 | public void RecordStruct(T value) where T : struct 58 | { 59 | this.fieldCollection.GetEmpty().As() = value; 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /Runtime/DynamicField.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace GM.Dynamic 7 | { 8 | [StructLayout(LayoutKind.Sequential)] 9 | public unsafe struct DynamicField 10 | { 11 | private Type dataType; 12 | private DataWrapper dataWrapper; 13 | 14 | internal bool IsEmpty => dataType == null; 15 | 16 | private const int SIZE = 56; 17 | [StructLayout(LayoutKind.Explicit, Size = SIZE)] 18 | private struct DataWrapper { } 19 | 20 | private static DataWrapper DEFAULT; 21 | 22 | public T TryAs() 23 | { 24 | if (!SizeCheck()) 25 | { 26 | throw new StructOverSizeException($"{typeof(T)} size bigger then {SIZE}"); 27 | } 28 | if (!Is()) 29 | { 30 | DEFAULT = default; 31 | var defaultAddr = Unsafe.AsPointer(ref DEFAULT); 32 | return Unsafe.AsRef(defaultAddr); 33 | } 34 | return As(); 35 | } 36 | 37 | public ref T As() 38 | { 39 | if (!SizeCheck()) 40 | { 41 | throw new StructOverSizeException($"{typeof(T)} size bigger then {SIZE}"); 42 | } 43 | if (!Is()) 44 | { 45 | WriteStruct(default); 46 | } 47 | var _dataWrapperAddr = Unsafe.AsPointer(ref this.dataWrapper); 48 | return ref Unsafe.AsRef(_dataWrapperAddr); 49 | } 50 | 51 | private ref object ReadAsObject() 52 | { 53 | var _dataWrapperAddr = Unsafe.AsPointer(ref this.dataWrapper); 54 | return ref Unsafe.AsRef(_dataWrapperAddr); 55 | } 56 | 57 | private bool SizeCheck() 58 | { 59 | var requireSize = Unsafe.SizeOf(); 60 | return requireSize <= SIZE; 61 | } 62 | 63 | public bool Is() 64 | { 65 | if (this.dataType == null) return false; 66 | // Class 67 | if (!this.dataType.IsValueType) 68 | { 69 | var obj = this.ReadAsObject(); 70 | return obj is T; 71 | } 72 | 73 | return this.dataType == typeof(T); 74 | } 75 | 76 | private void WriteStruct(T data) 77 | { 78 | this.dataType = typeof(T); 79 | var _dataWrapperAddr = Unsafe.AsPointer(ref this.dataWrapper); 80 | Unsafe.Write(_dataWrapperAddr, data); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Runtime/DynamicRefCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | 6 | namespace GM.Dynamic 7 | { 8 | internal class DynamicRefCollection 9 | { 10 | private static DynamicField DefaultField; 11 | private DynamicField[] dataList = new DynamicField[8]; 12 | private Dictionary dataIndexDict = new(); 13 | private Dictionary index_Key_Dict = new(); 14 | 15 | internal int Length { get { return dataList.Length; } } 16 | 17 | internal ref DynamicField GetAt(int index) 18 | { 19 | return ref dataList[index]; 20 | } 21 | 22 | internal ref DynamicField Get(string key, bool check = true) 23 | { 24 | if (this.dataIndexDict.ContainsKey(key)) 25 | { 26 | var index = this.dataIndexDict[key]; 27 | return ref this.dataList[index]; 28 | } 29 | else if (check) 30 | { 31 | throw new Exception($"[DynamicRefCollection] Get {key} not found!"); 32 | } 33 | DefaultField = default; 34 | return ref DefaultField; 35 | } 36 | 37 | internal void Add(string key) 38 | { 39 | if (dataIndexDict.ContainsKey(key)) 40 | { 41 | return; 42 | } 43 | int emptyIndex = FindFirstEmptyField(); 44 | dataIndexDict[key] = emptyIndex; 45 | index_Key_Dict[emptyIndex] = key; 46 | } 47 | 48 | internal void Remove(string key) 49 | { 50 | if (!dataIndexDict.ContainsKey(key)) 51 | { 52 | return; 53 | } 54 | int index = dataIndexDict[key]; 55 | dataList[index] = new DynamicField(); 56 | dataIndexDict.Remove(key); 57 | index_Key_Dict.Remove(index); 58 | } 59 | 60 | internal ref DynamicField GetEmpty() 61 | { 62 | int emptyIndex = FindFirstEmptyField(); 63 | return ref this.dataList[emptyIndex]; 64 | } 65 | 66 | internal void RemoveAt(int index) 67 | { 68 | if (index_Key_Dict.ContainsKey(index)) 69 | { 70 | var key = index_Key_Dict[index]; 71 | dataIndexDict.Remove(key); 72 | index_Key_Dict.Remove(index); 73 | } 74 | dataList[index] = new DynamicField(); 75 | } 76 | 77 | private int FindFirstEmptyField() 78 | { 79 | int emptyIndex = -1; 80 | for (int i = 0; i < dataList.Length; i++) 81 | { 82 | if (dataList[i].IsEmpty) 83 | { 84 | emptyIndex = i; 85 | break; 86 | } 87 | } 88 | 89 | var currentLength = dataList.Length; 90 | if (emptyIndex == -1) 91 | { 92 | Array.Resize(ref dataList, currentLength * 2); 93 | emptyIndex = currentLength; 94 | } 95 | return emptyIndex; 96 | } 97 | } 98 | 99 | public struct DynamicEnumerable 100 | { 101 | internal DynamicRefCollection collector; 102 | internal DynamicEnumerable(DynamicRefCollection collector) 103 | { 104 | this.collector = collector; 105 | } 106 | 107 | public DynamicEnumerator GetEnumerator() 108 | { 109 | return new DynamicEnumerator(collector); 110 | } 111 | } 112 | 113 | public struct DynamicEnumerator : IEnumerator 114 | { 115 | internal DynamicRefCollection collector; 116 | 117 | internal DynamicEnumerator(DynamicRefCollection collector) 118 | { 119 | this.collector = collector; 120 | index = -1; 121 | } 122 | 123 | public T Current 124 | { 125 | get 126 | { 127 | var field = collector.GetAt(index); 128 | return field.As(); 129 | } 130 | } 131 | 132 | public ref T RefCurrent 133 | { 134 | get => ref collector.GetAt(index).As(); 135 | } 136 | 137 | public void ResetCurrent() 138 | { 139 | this.collector.RemoveAt(index); 140 | } 141 | 142 | object IEnumerator.Current => throw new NotImplementedException(); 143 | 144 | private int index; 145 | 146 | public void Dispose() 147 | { 148 | 149 | } 150 | 151 | public bool MoveNext() 152 | { 153 | index++; 154 | for (int i = index; i < this.collector.Length; i++) 155 | { 156 | var value = this.collector.GetAt(i); 157 | if (value.Is()) 158 | { 159 | index = i; 160 | return true; 161 | } 162 | } 163 | return false; 164 | } 165 | 166 | public void Reset() 167 | { 168 | index = -1; 169 | } 170 | } 171 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dynamic For Unity 2 | 3 | ### 1.0.1 4 | 将Type.FullName作为Key,以支持泛型 5 | 6 | DynamicObject添加Api 7 | ``` csharp 8 | RecordObject 9 | RecordStruct 10 | 用于添加不需要指定Key索引的存储内容, 11 | 仅可通过DynamicObject.Every(T)查询 12 | 删除方式如下 13 | var enumtor = this.dynamicObject.Every().GetEnumerator(); 14 | while (enumtor.MoveNext()) 15 | { 16 | enumtor.ResetCurrent(); 17 | } 18 | ``` 19 | 20 | ### 简介 21 | 22 | Dynamic For Unity,为Unity实现了dynamic能力 23 | 24 | 主要有如下两个功能 25 | 26 | ### 动态字段 - DynamicField 【核心】 27 | 28 | 在Unity中,实现类似【dynamic关键字】的功能 29 | 30 | DynamicField,可以储存"任意"类型,不论是class还是struct,且类型转换0GC,无装/拆箱,无反射 31 | 32 | 注:["任意"],指具有一定的限制,问题不大,后面会讲 33 | 34 | ### 动态对象 - DynamicObject 35 | 36 | 在Unity中,实现类似【jsObject (就是js中的object)】动态字段的功能 37 | 38 | DynamicObject,可以拥有任意数量,"任意"类型的字段(基于上面的DynamicField实现) 39 | 40 | ### 安装 41 | 42 | Unity最小版本 `2022.3.23f1` 43 | 44 | 第1步. 通过 [NuGetForUnity](https://github.com/GlitchEnzo/NuGetForUnity) 安装 `System.Runtime.CompilerServices.Unsafe 6.0.0` 包 45 | 46 | 第2步. 通过 `OpenUPM` 安装 https://openupm.com/packages/com.gm.dynamic/ 本插件 47 | 48 | ### 使用 49 | 50 | 动态字段 - DynamicField 【核心】 51 | --- 52 | 53 | 以下功能不包含任何GC,且无装箱拆箱,无反射 54 | 55 | DynamicField 可以存储"任意"类型 56 | 57 | API介绍 58 | 59 | ``` csharp 60 | using GM.Dynamic; 61 | 62 | // DynamicField is struct 63 | DynamicField dynamicField; 64 | 65 | // ------------- 66 | // As 是 可读可写的 67 | // ------------- 68 | dynamicField.As() = (T)value; 69 | T value = dynamicField.As(); 70 | // 特别的 一旦As,若当前DynamicField的值类型不匹配,DynamicField的值会重置为default(T) 71 | 72 | 73 | 74 | // ------------- 75 | // Is 类型判断 76 | // ------------- 77 | bool is_T = dynamicField.Is(); 78 | // 对于值类型的T,直接判断是否是同类型 79 | // 对于引用类型,有两种情况 80 | // 1. value != null 时, return value is T 81 | // 2. value == null 时, return false 82 | 83 | 84 | 85 | // ------------- 86 | // TryAs 是 只读的 87 | // ------------- 88 | dynamicField.As() = value_T1; 89 | // 这会得到 default(T2),且不改变原有值,值依旧是 value_T1 90 | T2 value = dynamicField.TryAs(); 91 | ``` 92 | 93 | 以下是详细是示例 94 | 95 | ``` csharp 96 | using GM.Dynamic; 97 | 98 | // DynamicField is struct 99 | private DynamicField dynamicField; 100 | 101 | public int t_Int; 102 | public float t_Float; 103 | public string t_String; 104 | public bool t_Bool; 105 | public Vector3 t_Vector3; 106 | public GameObject t_GameObject; 107 | public Transform t_Transform; 108 | public UnityEngine.Object t_UnityObject; 109 | 110 | // -------------DynamicField.As() 示例 111 | private void As_Sample() 112 | { 113 | // DynamicField.As() 方法是可读可写的 114 | 115 | dynamicField.As() = Time.frameCount; 116 | this.t_Int = dynamicField.As(); 117 | 118 | dynamicField.As() = Time.deltaTime; 119 | this.t_Float = dynamicField.As(); 120 | 121 | dynamicField.As() = "Hello"; 122 | this.t_String = dynamicField.As(); 123 | 124 | dynamicField.As() = Vector3.one; 125 | dynamicField.As().x = 2; 126 | this.t_Vector3 = dynamicField.As(); 127 | 128 | // As T 为class对象时,可自动进行多态判断 129 | dynamicField.As() = this.gameObject; 130 | this.t_UnityObject = dynamicField.As(); 131 | this.t_GameObject = dynamicField.As(); 132 | 133 | dynamicField.As() = this.transform; 134 | this.t_UnityObject = dynamicField.As(); 135 | this.t_Transform = dynamicField.As(); 136 | 137 | // 特别情况 ------ 当类型不匹配时 138 | // 当T和已经保存的值的类型不一致时,会将DynamicField的值重置为default(T) 139 | dynamicField.As() = true; 140 | // 类型不匹配 bool? != bool 141 | // 导致 DynamicField的值重置为default(T) 142 | // 所以下面的结果是 this.t_Bool == false 143 | this.t_Bool = dynamicField.As(); 144 | 145 | dynamicField.As() = this.transform; 146 | // 类型不匹配 Transform != GameObject 147 | // 导致 DynamicField的值重置为default(T) 148 | // 所以下面两个都是null 149 | this.t_GameObject = dynamicField.As(); 150 | this.t_UnityObject = dynamicField.As(); 151 | } 152 | 153 | // -------------DynamicField.TryAs() 示例 154 | private void TryAs_Sample() 155 | { 156 | // DynamicField.TryAs() 方法是只读的 157 | // 所以不可以 dynamicField.TryAs() = 10; 158 | 159 | dynamicField.As() = 10; 160 | // 类型不匹配时,返回default(T),且不修改已保存的值 161 | // 下面的 this.t_Float == 0 162 | this.t_Float = dynamicField.TryAs(); 163 | // this.t_Int 仍然等于 10 164 | this.t_Int = dynamicField.TryAs(); 165 | 166 | dynamicField.As() = this.transform; 167 | // 类型不匹配时,返回default(T),且不修改已保存的值 168 | // 下面的 this.t_GameObject == null 169 | this.t_GameObject = dynamicField.TryAs(); 170 | // this.t_Transform == this.transform 171 | this.t_Transform = dynamicField.TryAs(); 172 | } 173 | 174 | // -------------DynamicField.Is() 示例 175 | private void Is_Sample() 176 | { 177 | // DynamicField.Is() 类型判断 178 | 179 | // 值类型--------- 180 | dynamicField.As() = 10; 181 | Debug.Log(dynamicField.Is());// true 182 | Debug.Log(dynamicField.Is());// false 183 | 184 | // 引用类型--------- 185 | dynamicField.As() = this.transform; 186 | Debug.Log(dynamicField.Is());// true 187 | Debug.Log(dynamicField.Is());// false 188 | 189 | // 引用类型--------- 190 | dynamicField.As() = this.gameObject; 191 | Debug.Log(dynamicField.Is());// true 192 | Debug.Log(dynamicField.Is());// true 193 | Debug.Log(dynamicField.Is());// false 194 | 195 | 196 | dynamicField.As() = null; 197 | // 引用类型,且值为空时,Is均返回false 198 | Debug.Log(dynamicField.Is());// false 199 | Debug.Log(dynamicField.Is());// false 200 | Debug.Log(dynamicField.Is());// false 201 | Debug.Log(dynamicField.Is());// false 202 | } 203 | ``` 204 | 205 | ### "任意" 206 | --- 207 | 重要!!!"任意"类型 指 任何sizeof(T) <= 56 的值类型 和 任意引用类型(sizeof(引用类型) == 地址长度) 208 | --- 209 | 210 | 说明:为什么是56呢,因为虽然是“动态”字段,但是总得存值,总得确认内存分配大小 211 | 212 | 当尝试使用 sizeof(T) > 56 时。会抛出 StructOverSizeException 异常 213 | 214 | TODO:后续可能会考虑一定程度上开放MaximumSize的设置 215 | 216 | 动态对象 - DynamicObject 的使用方法 217 | --- 218 | 219 | 动态对象,是基于DynamicField实现的,模仿了jsObject动态字段的功能 220 | 221 | API介绍 222 | ``` csharp 223 | using GM.Dynamic; 224 | 225 | // DynamicObject is class 226 | DynamicObject dynamicObject = new(); 227 | 228 | // 动态对象,本质上是一个"字典",string为key,DynamicField为value 229 | // DynamicObject 的基本API与 DynamicField 一致: 230 | // ------- As() 等价于 As(typeof(T).Name) 231 | // ------- TryAs() 等价于 TryAs(typeof(T).Name) 232 | // ------- Is() 等价于 Is(typeof(T).Name) 233 | // 事实上 typeof(T).Name 有GC,所以实际代码做了缓存 234 | dynamicObject.As() = 1; 235 | int int_1 = dynamicObject.TryAs(); 236 | bool is_Int = dynamicObject.Is(); 237 | // 上下相等 238 | dynamicObject.As(typeof(int).Name) = 1; 239 | int int_1 = dynamicObject.TryAs(typeof(int).Name); 240 | bool is_Int = dynamicObject.Is(typeof(int).Name); 241 | 242 | // ---------- 243 | // DynamicObject.Reset 244 | // 清空一个字段,可以腾出空间 245 | // ---------- 246 | dynamicObject.Reset("Field Key"); 247 | dynamicObject.Reset();// dynamicObject.Reset(typeof(T).Name); 248 | 249 | // ---------- 250 | // DynamicObject.Every() 251 | // 可遍历所有T类型的字段 252 | // ---------- 253 | DynamicEnumerable every_T_Enumerable = dynamicObject.Every(); 254 | DynamicEnumerator every_T_Enumerator = every_T_Enumerable.GetEnumerator(); 255 | // 可foreach 遍历 (只读遍历) 256 | foreach (T item in every_T_Enumerable){ } 257 | // 可手动遍历 (可写遍历) 258 | while (every_T_Enumerator.MoveNext()) 259 | { 260 | // RefCurrent 可写(假设T = int) 261 | every_T_Enumerator.RefCurrent += 1; 262 | // Current 只读(假设T = int) 263 | int i = every_T_Enumerator.Current; 264 | } 265 | 266 | ``` 267 | 268 | 以下是详细是示例 269 | 270 | ``` csharp 271 | using GM.Dynamic; 272 | 273 | // DynamicObject is class 274 | DynamicObject dynamicObject = new(); 275 | 276 | // DynamicObject 基本 示例 277 | private void Object_Sample() 278 | { 279 | dynamicObject.As("Int_1") = 4; 280 | int int_1 = dynamicObject.As("Int_1"); 281 | 282 | dynamicObject.As("Float_1") = 5f; 283 | float float_1 = dynamicObject.As("Float_1"); 284 | 285 | dynamicObject.As("Bool_1") = true; 286 | // 这不会修改已经存在“Bool_1”的值 287 | float not_Float = dynamicObject.TryAs("Bool_1"); 288 | bool bool_1 = dynamicObject.TryAs("Bool_1"); 289 | 290 | // console = 4_5_0_True 291 | Debug.Log($"{int_1}_{float_1}_{not_Float}_{bool_1}"); 292 | 293 | dynamicObject.As("BattleTarget") = new GameObject(); 294 | if (dynamicObject.TryAs("BattleTarget") != null) 295 | { 296 | // Fight 297 | } 298 | 299 | 300 | 301 | // 下面等于 dynamicObject.As(typeof(GameObject).Name) = this.gameObject 302 | dynamicObject.As() = this.gameObject; 303 | // 下面等于 dynamicObject.As(typeof(Transform).Name) = this.transform 304 | dynamicObject.As() = this.transform; 305 | 306 | // 不指定Key的写法,可以让一个DynamicObject使用起来好像是任何东西的多态一样 307 | if (dynamicObject.Is()) 308 | { 309 | dynamicObject.TryAs().SetActive(false); 310 | } 311 | if (dynamicObject.Is()) 312 | { 313 | dynamicObject.TryAs().position = Vector3.zero; 314 | } 315 | dynamicObject.TryAs()?.Move(Vector3.zero, Quaternion.identity); 316 | } 317 | 318 | // DynamicObject 特别功能 示例 319 | private void Object_Reset_Sample() 320 | { 321 | // 清空一个字段,可以腾出空间 322 | dynamicObject.Reset("Some Field"); 323 | // 等价于 dynamicObject.Reset("GameObject"); 324 | dynamicObject.Reset(); 325 | 326 | dynamicObject.As() = this.gameObject; 327 | dynamicObject.As() = this.transform; 328 | dynamicObject.As("Int_1") = 1; 329 | dynamicObject.As("Int_2") = 2; 330 | dynamicObject.As("Int_3") = 3; 331 | 332 | // 只读 遍历 333 | foreach (var unityObject in dynamicObject.Every()) 334 | { 335 | // 会打印 this.gameObject 336 | // 会打印 this.transform 337 | Debug.Log(unityObject); 338 | } 339 | 340 | // 可写 遍历 341 | var everyInt = dynamicObject.Every().GetEnumerator(); 342 | while (everyInt.MoveNext()) 343 | { 344 | everyInt.RefCurrent += 10; 345 | } 346 | 347 | foreach (var intValue in dynamicObject.Every()) 348 | { 349 | // 会打印 11 12 13 350 | Debug.Log(intValue); 351 | } 352 | } 353 | ``` 354 | 355 | ### 最后 356 | 357 | 觉得有趣的点个Star~ 358 | 359 | 谢谢~ 360 | 361 | --------------------------------------------------------------------------------