{"id":2248,"date":"2020-06-13T15:45:30","date_gmt":"2020-06-13T07:45:30","guid":{"rendered":"https:\/\/www.clonefactor.com\/wordpress\/?p=2248"},"modified":"2020-06-14T01:33:53","modified_gmt":"2020-06-13T17:33:53","slug":"object-pool-%e7%9a%84%e5%b0%8f%e7%b7%b4%e7%bf%92","status":"publish","type":"post","link":"https:\/\/www.clonefactor.com\/wordpress\/program\/c\/2248\/","title":{"rendered":"Object Pool \u7684\u5c0f\u7df4\u7fd2."},"content":{"rendered":"\n<h2>\u5ee2\u8a00<\/h2>\n<p>\u4e00\u76f4\u5728\u7528 pooling \u7684\u63d2\u4ef6, \u56e0\u70ba\u662f\u63d2\u4ef6\u57fa\u65bc\u7ba1\u7406\u4e0a\u7684\u539f\u56e0\u4e0d\u80fd\u76f4\u63a5\u6539\u5beb\u4ee5\u81f4\u7d93\u5e38\u8981\u898f\u907f\u4e00\u4e9b\u63d2\u4ef6\u7684\u7f3a\u61be,<br \/>Object pool \u7b97\u662f\u7d93\u5e38\u7528\u53c8\u6c92\u6709\u81ea\u5df1\u5beb\u904e\u7684\u6771\u897f. \u9019\u5929\u6709\u7a7a\u5c31\u4f86\u628a\u4ee5\u524d\u9047\u5230\u7684 Pooling \u554f\u984c\u6574\u7406\u4e00\u4e0b\u505a\u500b\u5c11\u63d2\u4ef6.<br \/>\u4ee5\u4e0b\u662f\u5c0f\u5f1f\u7684 Object pool \u7684\u5beb\u6cd5.<br \/>\u7d30\u7bc0\u5728\u4ee3\u78bc\u4e2d\u5df2\u52a0\u5165\u8a3b\u89e3, \u6216\u8a31\u9084\u6709\u5176\u4ed6\u4eba\u80fd\u63d0\u51fa\u4e00\u4e9b\u6211\u5f9e\u4f86\u6c92\u60f3\u904e\u7684\u7528\u6cd5, \u6b61\u8fce\u8a0e\u8ad6.<\/p>\n<h3>\u529f\u80fd<\/h3>\n<p>\u751a\u9ebc\u662f\u7269\u4ef6\u6c60 (<a href=\"https:\/\/zh.wikipedia.org\/wiki\/%E5%AF%B9%E8%B1%A1%E6%B1%A0%E6%A8%A1%E5%BC%8F\">Object Pool<\/a>), \u70ba\u5e38\u7528\u7684\u884d\u751f\u7269 (Token) \u9032\u884c\u986f\u793a\u7ba1\u7406, \u898f\u907f\u7d93\u5e38\u7522\u751f\u53ca\u522a\u9664\u7269\u4ef6\u6240\u9020\u6210\u7684\u8cc7\u6e90\u74f6\u9838&#8230;<br \/>\u4e09\u500b\u57fa\u672c Public API<\/p>\n<ul>\n<li>Spawn(GameObject, Vector3, Quaternion, Transform)<\/li>\n<li>Despawn(GameObject)<\/li>\n<li>IsSpawned(GameObject)<\/li>\n<\/ul>\n<p>\u4e00\u500b\u4e3b\u9ad4\u529f\u80fd<\/p>\n<ul>\n<li>Preloading<\/li>\n<\/ul>\n<h2>\u6982\u5ff5<\/h2>\n<p>\u4ee5 TokenCache \u70ba\u6982\u5ff5\u4e2d\u5fc3, \u5229\u7528 Queue, Hashset \u9032\u884c\u884d\u751f\u7269(Token)\u7ba1\u7406.<\/p>\n<ul>\n<li>Queue \u4f5c\u70ba deactive token \u7684\u66ab\u5b58 list, \u5229\u7528 FIFO \u7684\u7279\u6027\u76f4\u63a5\u62ff\u53d6\u9591\u7f6e\u6700\u4e45\u7684 Token \u4f5c\u70ba\u4e0b\u500b active token.<\/li>\n<li>Hashset \u4f5c\u70ba active token, \u53ef\u4ee5\u5feb\u901f\u7684\u6aa2\u67e5\u662f\u5426\u6b63\u5728\u4f7f\u7528.<\/li>\n<\/ul>\n<p>\u5728\u7a0b\u5e8f\u4e2d m_CacheDict \u4f7f\u7528 private setter \u4f5c\u70ba\u521d\u59cb\u5316\u7684\u624b\u6bb5\u662f\u56e0\u70ba\u521d\u59cb\u9ede\u4e0d\u78ba\u5b9a\u7684\u505a\u6cd5.<br \/>\u56e0\u70ba\u7b2c\u4e00\u500b\u884d\u751f\u7269\u53ef\u4ee5\u662f preload \u751f\u6210\u6216\u8005\u7531\u5916\u90e8\u7a0b\u5e8f\u547c\u53eb\u88ab\u800c\u88ab\u7acb\u5373\u7522\u751f\u7684.<\/p>\n<p><br \/>\u800c\u6700\u5f8c\u7684 ISpawnObject interface, \u5247\u662f\u4fdd\u7559\u65e5\u5f8c\u64f4\u5c55 Token \u4e0a\u7684\u5beb\u6cd5. OnSpawned \/ OnDespawn \u7b49\u4f5c\u70ba callback \u7684\u63a5\u53e3.<\/p>\n<p><iframe loading=\"lazy\" title=\"ObjectPool test log\" width=\"1260\" height=\"945\" src=\"https:\/\/www.youtube.com\/embed\/nqQ4DRQ_26g?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen><\/iframe><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">using System.Collections;\nusing System.Collections.Generic;\nusing UnityEngine;\n\nnamespace Kit\n{\n\tpublic class ObjectPool : MonoBehaviour, System.IDisposable\n\t{\n\t\t[SerializeField] PrefabPreloadSetting[] m_PrefabPreloadSettings = { };\n\n\t\t#region Data structure\n\t\tprivate bool m_IsDisposed;\n\n\t\t\/\/\/ &lt;summary>Preload Setting&lt;\/summary>\n\t\t[System.Serializable]\n\t\tpublic class PrefabPreloadSetting : PrefabSetting\n\t\t{\n\t\t\t[Header(\"Preload\")]\n\t\t\t[Tooltip(\"After Awake(), trigger auto preload in ? second.\")]\n\t\t\tpublic float m_PreloadDelay = 0f;\n\t\t\t[Tooltip(\"The interval between each preload elements, distribute the performace overhead during GameObject.Instantiate\")]\n\t\t\tpublic int m_PreloadFramePeriod = 0;\n\t\t\t[Tooltip(\"Auto preload prefab(s) base on giving amount\")]\n\t\t\tpublic int m_PreloadAmount = 1;\n\t\t\t[Tooltip(\"Keep prefab instance's Awake on their first spawn, instead of force disable on preload\")]\n\t\t\tpublic bool m_KeepAwakeOnFirstSpawn = true;\n\t\t}\n\n\t\t\/\/\/ &lt;summary>Setting for spawn\/despawn prefab behavior&lt;\/summary>\n\t\t[System.Serializable]\n\t\tpublic class PrefabSetting\n\t\t{\n\t\t\t[Header(\"Reference\")]\n\t\t\t[Tooltip(\"Name used for spawning the preloaded prefab(s), e.g. across network\")]\n\t\t\tpublic string m_Name = string.Empty;\n\t\t\tpublic GameObject m_Prefab;\n\t\t\t[Header(\"Rule\")]\n\t\t\t[Tooltip(\"The maximum amount of the current pool.\")]\n\t\t\tpublic int m_PoolLimit = 100;\n\t\t\t[Tooltip(\"Will block any further spawn request, if current pool was reach maximum limit.\")]\n\t\t\tpublic bool m_CullOverLimit = true;\n\t\t\t[Tooltip(\"Ensure the token(s) will re-parent under current pool on hierachy level.\")]\n\t\t\tpublic bool m_ReturnToPoolAfterDespawn = true;\n\n\t\t\tinternal void Validate()\n\t\t\t{\n\t\t\t\tif (m_Name.Length == 0 &amp;&amp; m_Prefab != null)\n\t\t\t\t\tm_Name = m_Prefab.name;\n\t\t\t}\n\t\t}\n\n\t\t\/\/\/ &lt;summary>Internal memory cache to handle spawn flow and keep instance reference&lt;\/summary>\n\t\tprivate struct TokenCache\n\t\t{\n\t\t\tpublic PrefabSetting setting;\n\t\t\tpublic Queue&lt;GameObject> deactiveObjs;\n\t\t\tpublic HashSet&lt;GameObject> activeObjs;\n\t\t\tpublic int totalCount => deactiveObjs.Count + activeObjs.Count;\n\t\t\tpublic TokenCache(PrefabSetting setting)\n\t\t\t{\n\t\t\t\tthis.setting = setting;\n\t\t\t\tdeactiveObjs = new Queue&lt;GameObject>(setting.m_PoolLimit);\n\t\t\t\tactiveObjs = new HashSet&lt;GameObject>();\n\t\t\t}\n\t\t\tinternal void Clear()\n\t\t\t{\n\t\t\t\tforeach (var token in activeObjs)\n\t\t\t\t\ttoken?.SetActive(false);\n\t\t\t\tactiveObjs.Clear();\n\t\t\t\tdeactiveObjs.Clear();\n\t\t\t}\n\t\t}\n\n\t\tprivate Dictionary&lt;GameObject, TokenCache> _cacheDict = null;\n\t\tprivate Dictionary&lt;GameObject \/* prefab *\/, TokenCache> m_CacheDict\n\t\t{\n\t\t\tget\n\t\t\t{\n\t\t\t\tif (_cacheDict == null)\n\t\t\t\t\t_cacheDict = new Dictionary&lt;GameObject, TokenCache>(10);\n\t\t\t\treturn _cacheDict;\n\t\t\t}\n\t\t}\n\n\t\t\/\/\/ &lt;summary>The table to increase the speed to tracking the token and it's prefab group.&lt;\/summary>\n\t\tprivate Dictionary&lt;GameObject \/* token *\/, GameObject \/* prefab *\/> m_AllSpawnedObjs = new Dictionary&lt;GameObject, GameObject>();\n\t\t#endregion \/\/ Data structure\n\n\t\t#region U3D Cycle\n\t\tprivate void OnValidate()\n\t\t{\n\t\t\tfor (int i = 0; i &lt; m_PrefabPreloadSettings.Length; i++)\n\t\t\t\tm_PrefabPreloadSettings[i].Validate();\n\t\t}\n\t\t\n\t\tprivate void Awake()\n\t\t{\n\t\t\tTriggerPreloadHandler();\n\t\t}\n\n\t\tprivate void OnDestroy()\n\t\t{\n\t\t\tDispose();\n\t\t}\n\t\t#endregion \/\/ U3D Cycle\n\n\t\t#region Preload Token\n\t\tprivate void TriggerPreloadHandler()\n\t\t{\n\t\t\tfor (int i = 0; i &lt; m_PrefabPreloadSettings.Length; i++)\n\t\t\t{\n\t\t\t\tif (m_PrefabPreloadSettings[i].m_Prefab == null)\n\t\t\t\t\tDebug.LogError($\"Fail to preload index {i}, missing prefab\", this);\n\t\t\t\telse\n\t\t\t\t\tStartCoroutine(PreloadHandler(m_PrefabPreloadSettings[i]));\n\t\t\t}\n\t\t}\n\t\tprivate IEnumerator PreloadHandler(PrefabPreloadSetting setting)\n\t\t{\n\t\t\tif (ReferenceEquals(setting.m_Prefab, null))\n\t\t\t\tthrow new UnityException(\"Handle null check on higher level.\");\n\t\t\telse if (setting.m_PreloadDelay > 0f)\n\t\t\t\tyield return new WaitForSecondsRealtime(setting.m_PreloadDelay);\n\n\t\t\tbool prefabActiveState = setting.m_Prefab.activeSelf;\n\t\t\tif (setting.m_KeepAwakeOnFirstSpawn &amp;&amp; prefabActiveState)\n\t\t\t\tsetting.m_Prefab.SetActive(false);\n\n\t\t\tLocateOrCreateCache(setting.m_Prefab, setting, out TokenCache cache);\n\n\t\t\tint cnt = Mathf.Min(setting.m_PreloadAmount, setting.m_PoolLimit);\n\t\t\twhile (cache.totalCount &lt; cnt) \/\/ Async spawning parallel maintain preload amount \n\t\t\t{\n\t\t\t\t\/\/ Create instance for prefab.\n\t\t\t\tGameObject go = CreateToken(cache, transform.position, transform.rotation, transform);\n\t\t\t\tgo.SetActive(false);\n\t\t\t\tcache.deactiveObjs.Enqueue(go);\n\n\t\t\t\tint countFrame = setting.m_PreloadFramePeriod;\n\t\t\t\twhile (countFrame-- > 0)\n\t\t\t\t\tyield return null;\n\t\t\t}\n\n\t\t\tif (setting.m_KeepAwakeOnFirstSpawn)\n\t\t\t\tsetting.m_Prefab.SetActive(prefabActiveState);\n\t\t}\n\n\t\tprivate GameObject CreateToken(TokenCache cache, Vector3 position, Quaternion rotation, Transform parent = null)\n\t\t{\n\t\t\tGameObject go = GameObject.Instantiate(cache.setting.m_Prefab, position, rotation, parent);\n\t\t\tgo.name += $\" #{cache.totalCount + 1:0000}\";\n\t\t\treturn go;\n\t\t}\n\n\t\tprivate void LocateOrCreateCache(GameObject prefab, PrefabSetting setting, out TokenCache cache)\n\t\t{\n\t\t\tif (prefab != null &amp;&amp; setting != null &amp;&amp; prefab != setting.m_Prefab)\n\t\t\t\tthrow new UnityException($\"Invalid prefab setting for this asset {prefab}\");\n\t\t\tif (!m_CacheDict.TryGetValue(prefab, out cache))\n\t\t\t{\n\t\t\t\t\/\/ cache not found, create one.\n\t\t\t\tif (setting == null || setting.m_Prefab == null)\n\t\t\t\t{\n\t\t\t\t\t\/\/ spawn on demend, but setting not found.\n\t\t\t\t\tDebug.LogWarning($\"Fail to locate {nameof(PrefabPreloadSetting)} for {prefab}, fallback default setting.\", this);\n\t\t\t\t\tcache = new TokenCache(new PrefabPreloadSetting() { m_Prefab = prefab });\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\/\/ spawn on demend, without using preload setting.\n\t\t\t\t\tcache = new TokenCache(setting);\n\t\t\t\t}\n\t\t\t\tm_CacheDict.Add(prefab, cache);\n\t\t\t}\n\t\t\telse if (setting != null)\n\t\t\t{\n\t\t\t\t\/\/ override setting\n\t\t\t\t\/\/ Case : execution order issue. Spawn() call early then Awake();\n\t\t\t\tcache.setting = setting;\n\t\t\t\tm_CacheDict[prefab] = cache;\n\t\t\t}\n\t\t}\n\t\t#endregion \/\/ Preload Token\n\n\t\t#region Pooling Core\n\t\tpublic GameObject Spawn(string prefabName, Vector3 position, Quaternion rotation, Transform parent = null)\n\t\t{\n\t\t\tif (m_IsDisposed)\n\t\t\t{\n\t\t\t\tDebug.LogError($\"{GetType().Name} : Unable to {nameof(Spawn)} {prefabName} when disposed.\", this);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tforeach (var cache in m_CacheDict.Values)\n\t\t\t\tif (cache.setting.m_Name.Equals(prefabName))\n\t\t\t\t\treturn InternalSpawn(cache, position, rotation, parent, null);\n\t\t\treturn null;\n\t\t}\n\t\tpublic GameObject Spawn(GameObject prefab, Vector3 position, Quaternion rotation, Transform parent = null, PrefabSetting setting = null)\n\t\t{\n\t\t\tif (m_IsDisposed)\n\t\t\t{\n\t\t\t\tDebug.LogError($\"{GetType().Name} : Unable to {nameof(Spawn)} {prefab} when disposed.\", this);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\telse if (prefab == null)\n\t\t\t\tthrow new UnityException(\"Fail to spawn Null prefab.\");\n\n\t\t\tLocateOrCreateCache(prefab, setting, out TokenCache cache);\n\t\t\treturn InternalSpawn(cache, position, rotation, parent, setting);\n\t\t}\n\n\t\tprivate GameObject InternalSpawn(TokenCache cache, Vector3 position, Quaternion rotation, Transform parent = null, PrefabSetting setting = null)\n\t\t{\n\t\t\tGameObject go = null;\n\t\t\tbool notEnoughToken = cache.deactiveObjs.Count == 0;\n\t\t\tif (notEnoughToken)\n\t\t\t{\n\t\t\t\tbool overLimit = cache.totalCount >= cache.setting.m_PoolLimit &amp;&amp; cache.setting.m_CullOverLimit;\n\t\t\t\tif (!overLimit)\n\t\t\t\t{\n\t\t\t\t\t\/\/ Spawn on demend\n\t\t\t\t\tgo = CreateToken(cache, position, rotation, parent);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tgo = cache.deactiveObjs.Dequeue();\n\t\t\t\tif (go.transform.parent != parent)\n\t\t\t\t\tgo.transform.SetParent(parent);\n\t\t\t\tgo.transform.SetPositionAndRotation(position, rotation);\n\t\t\t}\n\n\t\t\tif (go != null)\n\t\t\t{\n\t\t\t\tcache.activeObjs.Add(go);\n\t\t\t\tm_AllSpawnedObjs.Add(go, cache.setting.m_Prefab);\n\t\t\t\tgo.SetActive(true);\n\t\t\t\tgo.BroadcastMessage(nameof(ISpawnObject.OnSpawned), this, SendMessageOptions.DontRequireReceiver);\n\t\t\t}\n\t\t\treturn go;\n\t\t}\n\n\t\t\/\/\/ &lt;summary>Check if the giving gameobject was spawned by this spawn pool&lt;\/summary>\n\t\t\/\/\/ &lt;param name=\"go\">giving gameobject&lt;\/param>\n\t\t\/\/\/ &lt;returns>&lt;\/returns>\n\t\tpublic bool IsSpawned(GameObject go)\n\t\t{\n\t\t\treturn m_IsDisposed ? false : m_AllSpawnedObjs.ContainsKey(go);\n\t\t}\n\n\t\tpublic void Despawn(GameObject go)\n\t\t{\n\t\t\tif (m_IsDisposed)\n\t\t\t\treturn;\n\t\t\telse if (go == null)\n\t\t\t{\n\t\t\t\tDebug.LogError(\"Despawn null reference unit.\", this);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (m_AllSpawnedObjs.TryGetValue(go, out GameObject prefabKey))\n\t\t\t{\n\t\t\t\tTokenCache cache = m_CacheDict[prefabKey];\n\t\t\t\tm_AllSpawnedObjs.Remove(go);\n\t\t\t\tcache.activeObjs.Remove(go);\n\t\t\t\tcache.deactiveObjs.Enqueue(go);\n\t\t\t\tgo.SetActive(false);\n\t\t\t\tif (cache.setting.m_ReturnToPoolAfterDespawn)\n\t\t\t\t\tgo.transform.SetParent(transform);\n\t\t\t\tgo.BroadcastMessage(nameof(ISpawnObject.OnDespawned), this, SendMessageOptions.DontRequireReceiver);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tDebug.LogError($\"{GetType().Name} : {go} was not manage by {GetType().Name}\");\n\t\t\t}\n\t\t}\n\t\t#endregion \/\/ Pooling Core\n\n\t\t#region Dispose\n\t\tpublic void Dispose()\n\t\t{\n\t\t\tif (!m_IsDisposed)\n\t\t\t{\n\t\t\t\tforeach (TokenCache cache in m_CacheDict.Values)\n\t\t\t\t\tcache.Clear();\n\t\t\t\tm_CacheDict.Clear();\n\t\t\t\tm_AllSpawnedObjs.Clear();\n\t\t\t\tSystem.GC.SuppressFinalize(this);\n\t\t\t\tm_IsDisposed = true;\n\t\t\t}\n\t\t}\n\t\t#endregion \/\/ Dispose\n\t}\n\n\t\/\/\/ &lt;summary>An interface to allow spawned object to receive the following callback during Spawn\/Despawn flow.&lt;\/summary>\n\tpublic interface ISpawnObject\n\t{\n\t\t\/\/\/ &lt;summary>Will boardcast to token(s) after spawn flow.&lt;\/summary>\n\t\t\/\/\/ &lt;param name=\"pool\">Handling pool reference&lt;\/param>\n\t\tvoid OnSpawned(ObjectPool pool);\n\n\t\t\/\/\/ &lt;summary>Will boardcast to token(s) after despawn flow.&lt;\/summary>\n\t\t\/\/\/ &lt;param name=\"pool\">Handling pool reference&lt;\/param>\n\t\tvoid OnDespawned(ObjectPool pool);\n\t}\n}<\/pre>\n\n\n\n<p>\u53e6\u5916\u4e00\u6bb5\u4f5c\u70ba Test case \u7684\u7a0b\u5e8f\u78bc<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">using System.Collections;\nusing System.Collections.Generic;\nusing UnityEngine;\n\npublic class RandomSpawn : MonoBehaviour\n{\n\tpublic GameObject m_Prefab;\n\tpublic Kit.ObjectPool pool;\n\tpublic Transform[] m_Parents = { };\n\n\tpublic float m_Radius = 3f;\n\tpublic Vector2 m_Range = Vector2.up;\n\tprivate List&lt;GameObject> m_SpawnedGo = new List&lt;GameObject>();\n\n\tprivate void OnEnable()\n\t{\n\t\tStartCoroutine(PeriodicUpdate());\n\t}\n\n\tprivate IEnumerator PeriodicUpdate()\n\t{\n\t\twhile (this.enabled)\n\t\t{\n\t\t\tif (Random.value > .5f &amp;&amp; m_SpawnedGo.Count > 0)\n\t\t\t{\n\t\t\t\tint pt = Random.Range(0, m_SpawnedGo.Count);\n\t\t\t\tpool.Despawn(m_SpawnedGo[pt]);\n\t\t\t\tm_SpawnedGo.RemoveAt(pt);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tint pt = Random.Range(0, m_Parents.Length);\n\t\t\t\tGameObject go = pool.Spawn(m_Prefab, Random.insideUnitSphere * m_Radius, Quaternion.identity, m_Parents.Length > 0 ? m_Parents[pt] : null);\n\t\t\t\tif (go != null)\n\t\t\t\t\tm_SpawnedGo.Add(go);\n\t\t\t}\n\t\t\tfloat second = Random.Range(m_Range.x, m_Range.y);\n\t\t\tyield return new WaitForSeconds(second);\n\t\t}\n\t}\n}\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>\u5ee2\u8a00 \u4e00\u76f4\u5728\u7528 pooling \u7684\u63d2\u4ef6, \u56e0\u70ba\u662f\u63d2\u4ef6\u57fa\u65bc\u7ba1\u7406\u4e0a\u7684\u539f\u56e0\u4e0d\u80fd\u76f4\u63a5\u6539\u5beb\u4ee5\u81f4\u7d93\u5e38\u8981\u898f\u907f\u4e00\u4e9b\u63d2 &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9,11],"tags":[],"class_list":["post-2248","post","type-post","status-publish","format-standard","hentry","category-c","category-unity3d"],"_links":{"self":[{"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/posts\/2248","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/comments?post=2248"}],"version-history":[{"count":6,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/posts\/2248\/revisions"}],"predecessor-version":[{"id":2257,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/posts\/2248\/revisions\/2257"}],"wp:attachment":[{"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/media?parent=2248"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/categories?post=2248"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/tags?post=2248"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}