首页 > 代码库 > Untiy3D联网插件——Photon的自定义对象池使用方法
Untiy3D联网插件——Photon的自定义对象池使用方法
本文章由cartzhang编写,转载请注明出处。 所有权利保留。
文章链接:http://blog.csdn.net/cartzhang/article/details/68068178
作者:cartzhang
一、 写在前面
最开始接触Photon的时候,没有怎么理解代码,我们自己的写的对象池与Photon结合使用起来非常不方便。
需要每次从池里取对象,然后手动设置ViewID,这样很烦人,从感觉来说,就是photon的打开方式不对。
直到有天再次耐心去读了Photon的代码才有发现,感觉是对的,不至于人家没有考虑到池的用法,不仅可以用,而且可以任意使用自己的类,下面就介绍一下怎么使用,且在后面给出了PhotoNetwork的优化和改进。
本篇主要内容,
一是,photon接入自定义的内存池,且做了部分优化
二是,对photon实例化方法做了扩展,方便使用。
二、Photon中池的使用
通过查找 PhotonNetwork.Instantiate的函数调用过程,可以知道主要的实例化代码都在NetworkingPeer.cs中,查看DoInstantiate函数类的代码,
if (ObjectPool != null)
{
GameObject go = ObjectPool.Instantiate(prefabName, position, rotation);
PhotonView[] photonViews = go.GetPhotonViewsInChildren();
if (photonViews.Length != viewsIDs.Length)
{
throw new Exception("Error in Instantiation! The resource‘s PhotonView count is not the same as in incoming data.");
}
for (int i = 0; i < photonViews.Length; i++)
{
photonViews[i].didAwake = false;
photonViews[i].viewID = 0;
photonViews[i].prefix = objLevelPrefix;
photonViews[i].instantiationId = instantiationId;
photonViews[i].isRuntimeInstantiated = true;
photonViews[i].instantiationDataField = incomingInstantiationData;
photonViews[i].didAwake = true;
photonViews[i].viewID = viewsIDs[i]; // with didAwake true and viewID == 0, this will also register the view
}
// Send OnPhotonInstantiate callback to newly created GO.
// GO will be enabled when instantiated from Prefab and it does not matter if the script is enabled or disabled.
go.SendMessage(OnPhotonInstantiateString, new PhotonMessageInfo(photonPlayer, serverTime, null), SendMessageOptions.DontRequireReceiver);
return go;
}
若有对象池,就使用对象池来创建网络对象。
是不是豁然开朗,就是在这里使用的对象池。
那怎么接入自定义的对象池呢?
三、使用自定义对象池
通过代码发现在PhotonClasses.cs中,有一个接口IpunPrefabPool,而上面代码中的
internal IPunPrefabPool ObjectPool;
就是这个接口类型的对象。
public interface IPunPrefabPool
{
/// <summary>
/// This is called when PUN wants to create a new instance of an entity prefab. Must return valid GameObject with PhotonView.
/// </summary>
/// <param name="prefabId">The id of this prefab.</param>
/// <param name="position">The position we want the instance instantiated at.</param>
/// <param name="rotation">The rotation we want the instance to take.</param>
/// <returns>The newly instantiated object, or null if a prefab with <paramref name="prefabId"/> was not found.</returns>
GameObject Instantiate(string prefabId, Vector3 position, Quaternion rotation);
/// <summary>
/// This is called when PUN wants to destroy the instance of an entity prefab.
/// </summary>
/// <remarks>
/// A pool needs some way to find out which type of GameObject got returned via Destroy().
/// It could be a tag or name or anything similar.
/// </remarks>
/// <param name="gameObject">The instance to destroy.</param>
void Destroy(GameObject gameObject);
}
很显然,这就是等待我们使用,来接入自定义池的地方。
说明下,我这里使用的是自定义的对象池,也在其他博客中有介绍:
http://blog.csdn.net/cartzhang/article/details/54096845
http://blog.csdn.net/cartzhang/article/details/55051570
有需要的同学可以参考。
实现接入自定义的池,首先是要实现接口类,然后在这里面做了写代码优化。
public class NetPoolManager : PoolManager, IPunPrefabPool
{
public static Dictionary<string, GameObject> prefabResoucePrefabCache = new Dictionary<string, GameObject>();
private bool bOnce = false;
public void Awake()
{
PhotonNetwork.PrefabPool = this;
InitailResoucesCache();
}
public GameObject Instantiate(string prefabId, Vector3 position, Quaternion rotation)
{
Debug.Log("net instantiate " + prefabId);
GameObject gameObj = GetGameObjFromCache(prefabId);
return gameObject.InstantiateFromPool(gameObj, position, rotation).gameObject;
}
public void Destroy(GameObject gameObject)
{
gameObject.DestroyToPool(gameObject);
}
private void InitailResoucesCache()
{
string prefabTmpName = string.Empty;
if (!bOnce)
{
bOnce = true;
UnityEngine.Object[] all_resources = Resources.LoadAll("", typeof(GameObject));
for (int i = 0; i < all_resources.Length; i++)
{
GameObject Go = all_resources[i] as GameObject;
prefabTmpName = Go.name;
if (null != Go && !string.IsNullOrEmpty(prefabTmpName))
{
if (!prefabResoucePrefabCache.ContainsKey(prefabTmpName))
{
prefabResoucePrefabCache.Add(prefabTmpName, Go);
}
else
{
Debug.LogError(prefabTmpName + " have more than one prefab have the same name ,check all resoures folder.");
}
}
}
}
}
private GameObject GetGameObjFromCache(string prefabName)
{
GameObject resourceGObj = null;
if (!prefabResoucePrefabCache.TryGetValue(prefabName, out resourceGObj))
{
Debug.LogError("please check ,if current " + prefabName + "not in resouce folder");
}
if (resourceGObj == null)
{
Debug.LogError("Could not Instantiate the prefab [" + prefabName + "]. Please verify this gameobject in a Resources folder.");
}
return resourceGObj;
}
}
其中,Awake中,需要把this指针赋值给PhotonNetwork.PrefabPool,这样在使用调用ObjectPool的过程中就不会为null了。
再说下优化部分,就是之前使用Photon的过程中,都需要把所有的预置体Prefab,放到Resources文件夹下的根目录下,因为他们没有办法找其他子文件夹,而是直接调用名称。
看代码:
resourceGameObject = (GameObject)Resources.Load(prefabName, typeof (GameObject));
在NetPoolManager类中,可以在Resources中创建子文件夹,且可以被顺利实例化调用。
优化分两步,
第一,收集所有resources文件夹,包括子文件夹内的预置体,并保存一个表。
第二,每次需要的时候可以直接取到GameObject对象,进行实例化。
下面是优缺点:
优点就是可以很快的取到生产池内对象,不需要每次都从resource加载。
缺点,目前子文件夹内的预置体不能有重名的,且若太多的话,加载可能需要些时间,且内存会增加,因为游戏不关闭,就会一直在内存中存放。
四、PhotonNetwork的扩展优化
在调用PhotonNetwork.Instantiate()实例化的过程中,需要输入的是一个string类型的预置体的名称,每次使用都需要获取预置体或对象的名称来作为输入参数,这个很不人性化啊,所以就写了一个扩展。
public static GameObject Instantiate(string prefabName, Vector3 position, Quaternion rotation, int group)
{
return Instantiate(prefabName, position, rotation, group, null);
}
本来打算直接扩展,但是它是静态类,不支持。
就是想到了public static partial class PhotonNetwork
然后创建了PhotonNetworkExtent.cs文件,来实现直接船体Transform的方法。
当然你可以根据自己需要来改或实现其他,这里只是抛砖引玉。
//////////////////////////////////////////////////////////////////////////
using UnityEngine;
using ExitGames.Client.Photon;
using System.Collections.Generic;
/// Author: cartzhang
/// Time: 2017-03-22
/// extent photonNetWork.
/// this can instantiate by transform,what is more,can auto get prefab from
/// subfold in resources.
public static partial class PhotonNetwork
{
private static bool bInitialOnce = false;
private static Dictionary<string, GameObject> PrefabResoucePaths = new Dictionary<string, GameObject>();
/// <summary>
/// @cartzhang use prefab to load.
/// </summary>
/// <param name="prefabTransform"></param>
/// <param name="position"></param>
/// <param name="rotation"></param>
/// <param name="group"></param>
/// <returns></returns>
public static GameObject Instantiate(Transform prefabTransform, Vector3 position, Quaternion rotation, int group)
{
return Instantiate(prefabTransform, position, rotation, group, null);
}
/// <summary>
/// @ TODO
/// </summary>
/// <param name="prefabTransform"></param>
/// <param name="position"></param>
/// <param name="rotation"></param>
/// <param name="group"></param>
/// <param name="data"></param>
/// <returns></returns>
public static GameObject Instantiate(Transform prefabTransform, Vector3 position, Quaternion rotation, int group, object[] data)
{
#if USE_SELF_CACHE
// whether use himself cache.
if (!bInitialOnce)
{
bInitialOnce = true;
GetAllResourceFileGameObjectFullPath();
}
#endif
string prefabName = prefabTransform.name;
if (prefabName.Length < 1)
{
Debug.LogError("Failed to Instance prefab: " + prefabTransform.name + " input is not a prefab");
}
if (!connected || (InstantiateInRoomOnly && !inRoom))
{
Debug.LogError("Failed to Instantiate prefab: " + prefabName + ". Client should be in a room. Current connectionStateDetailed: " + PhotonNetwork.connectionStateDetailed);
return null;
}
GameObject prefabGo = null;
if (!UsePrefabCache || !SLQJ.NetPoolManager.prefabResoucePrefabCache.TryGetValue(prefabName, out prefabGo))
{
//prefabGo = (GameObject)Resources.Load(prefabName, typeof(GameObject));
//if (UsePrefabCache)
//{
// PrefabResoucePaths.Add(prefabName, prefabGo);
//}
}
if (prefabGo == null)
{
Debug.LogError("Failed to Instantiate prefab: " + prefabName + ". Verify the Prefab is in a Resources folder (and not in a subfolder)");
return null;
}
// a scene object instantiated with network visibility has to contain a PhotonView
if (prefabGo.GetComponent<PhotonView>() == null)
{
Debug.LogError("Failed to Instantiate prefab:" + prefabName + ". Prefab must have a PhotonView component.");
return null;
}
Component[] views = (Component[])prefabGo.GetPhotonViewsInChildren();
int[] viewIDs = new int[views.Length];
for (int i = 0; i < viewIDs.Length; i++)
{
//Debug.Log("Instantiate prefabName: " + prefabName + " player.ID: " + player.ID);
viewIDs[i] = AllocateViewID(player.ID);
}
// Send to others, create info
Hashtable instantiateEvent = networkingPeer.SendInstantiate(prefabName, position, rotation, group, viewIDs, data, false);
// Instantiate the GO locally (but the same way as if it was done via event). This will also cache the instantiationId
return networkingPeer.DoInstantiate(instantiateEvent, networkingPeer.LocalPlayer, prefabGo);
}
}
到此,本篇就完毕了。
主要讲了photon接入自定义的内存池,且做了部分优化和Photon实例化方法做了扩展,方便使用。
通过这篇,希望你可以给自己的对象池,添加网络功能了。
若有问题,请随时联系!!
五、更多相关
【1】http://blog.csdn.net/cartzhang/article/details/54096845
【2】http://blog.csdn.net/cartzhang/article/details/55051570
标签:Photon,对象池,自定义。
若有问题,请随时联系,感谢点赞浏览!!
Untiy3D联网插件——Photon的自定义对象池使用方法