c# - 如何在不使用 Photon View Id 的情况下生成/实例化多个游戏对象?
问题描述
游戏定义:
我正在创建一个游戏,涉及在随机位置产生多个对象(食物)。当玩家触摸食物时,食物将被摧毁。食品数量将超过2000种。
问题:
我希望这些食物能够在所有玩家的游戏环境中展示。我正在从 Master 实例化它,所有食物都使用 Photon View ID;但是,ViewID 的限制只有 999。我尝试增加最大值,但我担心它会导致带宽问题等问题。
有什么方法可以在不使用大量 ViewID 的情况下将食物同步给所有玩家?
解决方案
创建您自己的网络 ID 和管理器!
根据您的需要,最简单的方法是让中央管理器 (MasterClient) 生成食物实例并为它们分配一个唯一 ID。然后告诉所有其他客户端也生成此项目并分配相同的 ID(例如,使用带有所有必需参数的 RPC)。此外,为了处理 MasterClient 的切换,请保留所有现有 ID 的列表,例如在 Room 属性中,以便在切换的情况下,新的 masterclient 可以接管分配唯一 ID => 无限制的工作;)
当然,这可能会变得非常“hacky”,你必须玩一下并测试它!
注意:以下代码未经测试并在智能手机上输入!但我希望它能给你一个好的起点。
此类将进入 Food 预制件,因此每种食物都具有此自定义网络标识
// 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;)
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);
}
}
}
推荐阅读
- azure - Azure 机器学习 cli - 服务与端点
- vb.net - VB.Net 无法使用 For 循环从另一个表单更改 Label.text
- sql - 聚合可能不会出现在 WHERE 子句中,除非它出现在 HAVING 子句中包含的子查询中
- php - Laravel 8:如何在刀片上正确返回通知
- javascript - 如何在 iOS 中使用 webauthn 并做出反应?
- c - 如何使用 C 中的函数从字符串中仅提取数字?
- c# - Roslyn / CSharpScript - 声明和初始化动态变量以在脚本中使用它们
- ios - 配置了多个 rest api。无法推断要调用哪个 API
- c - 如何在 macOS Big Sur 中添加 C-Libraries?
- r - libreadline.so.6:无法打开共享对象文件:没有这样的文件或目录