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

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

时间: 2024-08-08 01:12:49

Untiy3D联网插件——Photon的自定义对象池使用方法的相关文章

JS 创建自定义对象的方式方法

一.概述 还记得刚开始做项目的时候,看到别人封装的js工具类百思不得其解,看来看去看不懂,深挖一下,其实就是自己没有耐下心去看,但是遇到问题不解决,总会遇到的,今天还是遇到了,就去找了找帖子,重新思考与实践一下,豁然开朗~!在此记录一下迟来顿开的茅塞. 关于JS 对象,啊,对象么,不就是一个个实例么,是的,js 也可以创建类,创建对象,创建对象方法,我们今天就具体说一下. 二.创建与使用(开始) es 标准给我们提供了String.Math.Array等等这些js对象,当我们使用的时候只需要ne

自定义进程池的方法

一 .比较low的线程池 import queue,time,threading class My_theading_pool(object): def __init__(self,num = 20): self.queue = queue.Queue(num) #在类中分装一个队列,队列中最多容纳20 for i in range(num): self.queue.put(threading.Thread) #在队列的20个位置上放置线程 def get_thead(self): return

屏幕坐标和世界坐标的转换+对象池技术(3D打地鼠小游戏)

游戏中可能经常会遇到需要某个物体跟着鼠标移动,然后又需要把物体放在某个鼠标指定的位置 实现方式 Camera.main.WorldToScreenPoint Camera.main.ScreenToWorldPoint 3D打地鼠实例 我这里用到的素材都比较简陋,几乎全是用Unity做的 首先是锤子 就是两个Cylinder,在把手的位置放一个空物体用于模拟锤子的动作,命名为Hammer,把锤子作为Hammer的子物体,给Hammer添加Animation动画: 在三个关键帧位置设置Hammer

探索对象池技术

对象池技术是一种常见的对象缓存手段.’对象’意味着池中的内容是一种结构化实体,这也就是一般意义上面向对象中的对象模型:’池’(或动词池化)意味着将有生命周期的对象缓存到’池子’中进行管理,即用即取.缓存的目的大多是为了提升性能,对象池技术的目的也即如此.所以,对象池技术的本质简单来说就是:将具有生命周期的结构化对象缓存到带有一定管理功能的容器中,以提高对象的访问性能. 处理网络连接是对象池使用最多的场景.比如一些RPC框架的NettyChannel缓存(如motan),以及数据库连接池的Conn

游戏编程模式-对象池

“使用固定的对象池重用对象,取代单独的分配和释放对象,以此来达到提升性能和优化内存使用的目的.” 动机 假设我们正在致力于游戏的视觉效果优化.当英雄释放魔法时,我们想让一个火花在屏幕上炸裂.这通常需要一个粒子系统(一个用来生成大量小的图形并在它们生存周期产生动画的引擎)来实现.而这个粒子系统实现这个火花的时候会产生大量的粒子,我们需要非常快速的创建这些粒子同时在这些粒子“死亡”的时候释放这些粒子对象.在这里,我们会碰到一个严重的问题——内存碎片化. 碎片化地害处 为游戏和移动设备编程在很多方面都

jQuery基础(常用插件 表单验证,图片放大镜,自定义对象级,jQuery UI,面板折叠)

1.表单验证插件--validate   该插件自带包含必填.数字.URL在内容的验证规则,即时显示异常信息,此外,还允许自定义验证规则,插件调用方法如下: $(form).validate({options}) 其中form参数表示表单元素名称,options参数表示调用方法时的配置对象,所有的验证规则和异常信息显示的位置都在该对象中进行设置.     2.表单插件--form 通过表单form插件,调用ajaxForm()方法,实现ajax方式向服务器提交表单数据,并通过方法中的option

[译]Unity3D内存管理——对象池(Object Pool)

从一个简单的对象池类开始说起 对象池背后的理念其实是非常简单的.我们将对象存储在一个池子中,当需要时在再次使用,而不是每次都实例化一个新的对象.池的最重要的特性,也就是对象池设计模式的本质是允许我们获取一个“新的”对象而不管它真的是一个新的对象还是循环使用的对象.该模式可以用以下简单的几行代码实现: public class ObjectPool<T> where T : class, new() { private Stack<T> m_objectStack = new Sta

轻松把玩HttpClient之封装HttpClient工具类(二),插件式配置HttpClient对象

上一篇文章中,简单分享一下封装HttpClient工具类的思路及部分代码,本文将分享如何实现插件式配置HttpClient对象. 如果你看过我前面的几篇关于HttpClient的文章或者官网示例,应该都知道HttpClient对象在创建时,都可以设置各种参数,但是却没有简单的进行封装,比如对我来说比较重要的3个:代理.ssl(包含绕过证书验证和自定义证书验证).超时.还需要自己写.所以这里我就简单封装了一下,顺便还封装了一个连接池的配置. 其实说是插件式配置,那是高大上的说法,说白了,就是采用了

自定义连接池(装饰者模式)

连接池概述: 管理数据库的连接, 作用: 提高项目的性能. 就是在连接池初始化的时候存入一定数量的连接,用的时候通过方法获取,不用的时候归还连接即可. 所有的连接池必须实现一个接口 javax.sql.DataSource接口 获取连接方法: Connection getConnection() 归还连接的方法就是以前的释放资源的方法.调用connection.close(); 增强方法: 1.继承 2.装饰者模式(静态代理) 3.动态代理 装饰者模式: 使用步骤: 1.装饰者和被装饰者实现同一