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,对象池,自定义。

若有问题,请随时联系,感谢点赞浏览!!

猜你喜欢

转载自blog.csdn.net/cartzhang/article/details/68068178