首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在不使用光子视图Id的情况下生成/实例化多个游戏对象?

如何在不使用光子视图Id的情况下生成/实例化多个游戏对象?
EN

Stack Overflow用户
提问于 2021-10-27 21:55:17
回答 1查看 2.3K关注 0票数 1

游戏定义:

我正在创建一个游戏,包括在任意的地方产卵多个物体(食物)。当玩家碰触食物时,食物就会被销毁。食品的数量将超过2000。

问题:

我希望这些食物能在所有玩家的游戏环境中展现出来。我正在从大师那里实例化它,所有的食物都使用光子视图ID;然而,ViewID的限制只有999。我试着增加最大值,但我担心它会导致带宽问题等问题。

有什么办法,我可以同步所有的食物,所有的播放器,而不用大量的ViewID?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-10-28 11:13:02

创建自己的网络ID和管理器!

根据您的需要,最简单的方法是让一个中央管理器(MasterClient)生成食物实例,并为它们分配一个唯一的ID。然后告诉所有其他客户端也生成这个项目并分配相同的ID (例如使用带有所有所需参数的RPC)。另外,为了处理MasterClient的切换,保留所有现有in的列表,例如在Room属性中,因此在发生开关的情况下,新的主从者可以接管分配唯一in的任务,=>无限制;)

当然,这可能会变得相当“无趣”,你必须在周围玩一点,并测试它真的很好!

注意:以下代码未经测试并在智能手机上输入!但我希望它能给你一个好的起点。

这个类会进入食物预制件,所以每个食物都有这个自定义的网络身份

代码语言:javascript
复制
// Put this on your food prefab(s)
public class FoodID : MonoBehaviour
{
    // The assigned ID
    public uint ID;

    // An event to handle any kind of destroyed food no matter for what reason
    // in general though rather go via the FoodManagement.DestroyFood method instead
    public static event Action<FoodID> onDestroyed;

    private void OnDestroy()
    {
        onDestroyed?.Invoke(this);
    }
}

这将进入您的播放器或现场,以便您的其他脚本可以与它沟通,它有权力发送RPC ;)

代码语言:javascript
复制
public class FoodManagement : MonoBehaviourPunCallbacks
{
    [FormerlySerializedAs("foodPrefab")]
    public FoodID foodIDPrefab;

    // keep track of already ued IDs
    private readonly HashSet<uint> _usedIDs = new HashSet<uint>
    {
        // by default I always block the 0 because it means invalid/unassigned ID ;)
        0
    };

    // keep references from ID to food LOCAL
    private readonly Dictionary<uint, FoodID> _foodInstances = new Dictionary<uint, FoodID>();

    // instance for random number generation used in GetRandomUInt
    private readonly Random _random = new Random();

    private void Awake()
    {
        // Register a callback just to be sure that all kind of Destroy on a Food object is handled forwarded correctly
        FoodID.onDestroyed += DestroyFood;
    }

    private void OnDestroy()
    {
        // In general make sure to remove callbacks once not needed anymore to avoid exceptions
        FoodID.onDestroyed -= DestroyFood;
    }

    // Register a food instance and according ID to the dictionary and hashset
    private void AddFoodInstance(FoodID foodID)
    {
        _usedIDs.Add(foodID.ID);
        _foodInstances.Add(foodID.ID, foodID);
    }

    // Unregister a foo instance and according ID from the dictionary and hashset
    private void RemoveFoodInstance(uint id)
    {
        _usedIDs.Remove(id);
        _foodInstances.Remove(id);
    }

    // Get a unique random uint ID that is not already in use
    private uint GetFreeID()
    {
        uint id;
        do
        {
            id = GetRandomUInt();
        } while (id == 0 || _usedIDs.Contains(id));

        return id;
    }

    // Generates a random uint
    private uint GetRandomUInt()
    {
        var thirtyBits = (uint)_random.Next(1 << 30);
        var twoBits = (uint)_random.Next(1 << 2);
        var fullRange = (thirtyBits << 2) | twoBits;

        return fullRange;
    }

    // Create a new Food instance network wide on the given location
    public void SpawnFood(Vector3 position)
    {
        // Make sure only the current Master client creates unique IDs in order to get no conflicts

        if (PhotonNetwork.IsMasterClient)
        {
            SpawnFoodOnMaster(position);
        }
        else
        {
            photonView.RPC(nameof(SpawnFoodOnMaster), RpcTarget.MasterClient, position);
        }
    }

    // Only the master client creates IDs and forwards th spawning to all clients
    private void SpawnFoodOnMaster(Vector3 position)
    {
        if (!PhotonNetwork.IsMasterClient)
        {
            Debug.LogError($"{nameof(SpawnFoodOnMaster)} invoked on Non-Master client!");
            return;
        }

        var id = GetFreeID();

        photonView.RPC(nameof(RPCSpawnFood), RpcTarget.All, id, position);
    }

    // Finally all clients will spawn the food at given location and register it in their local ID registry
    private void RPCSpawnFood(uint id, Vector3 position)
    {
        var newFood = Instantiate(foodIDPrefab, position, Quaternion.identity);
        newFood.ID = id;

        AddFoodInstance(newFood);
    }

    // Destroy the given Food network wide
    public void DestroyFood(FoodID foodID)
    {
        DestroyFood(foodID.ID);
    }

    // Destroy the Food with given ID network wide
    public void DestroyFood(uint id)
    {
        if (PhotonNetwork.IsMasterClient)
        {
            DestroyFoodOnMaster(id);
        }
        else
        {
            photonView.RPC(nameof(DestroyFoodOnMaster), RpcTarget.MasterClient, id);
        }
    }

    // The same as for the spawning: Only the master client forwards this call
    // Reason: This prevents conflicts if at the same time food is destroyed and created or
    // if two clients try to destroy the same food at the same time
    void DestroyFoodOnMaster(uint id)
    {
        if (!_usedIDs.Contains(id))
        {
            Debug.LogError($"Trying to destroy food with non-registered ID {id}");
            return;
        }

        photonView.RPC(nameof(RPCDestroyFood), RpcTarget.All, id);
    }

    // Destroy Food ith given id network wide and remove it from the registries
    void RPCDestroyFood(uint id)
    {
        if (_foodInstances.TryGetValue(id, out var food))
        {
            if (food) Destroy(food.gameObject);
        }

        RemoveFoodInstance(id);
    }

    // Once you join a new room make sure you receive the current state
    // since our custom ID system is not automatically handled by Photon anymore
    public override void OnJoinedRoom()
    {
        base.OnJoinedRoom();

        if (PhotonNetwork.IsMasterClient) return;

        photonView.RPC(nameof(RequestInitialStateFromMaster), RpcTarget.MasterClient, PhotonNetwork.LocalPlayer);
    }

    // When a new joined clients requests the current state as the master client answer with he current state
    private void RequestInitialStateFromMaster(Player requester)
    {
        if (!PhotonNetwork.IsMasterClient)
        {
            Debug.LogError($"{nameof(RequestInitialStateFromMaster)} invoked on Non-Master client!");
            return;
        }

        var state = _foodInstances.Values.ToDictionary(food => food.ID, food => food.transform.position);

        photonView.RPC(nameof(AnswerInitialState), requester, state);
    }

    // When the master sends us the current state instantiate and register all Food instances
    private void AnswerInitialState(Dictionary<uint, Vector3> state)
    {
        foreach (var kvp in state)
        {
            RPCSpawnFood(kvp.Key, kvp.Value);
        }
    }
}
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69745907

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档