游戏定义:
我正在创建一个游戏,包括在任意的地方产卵多个物体(食物)。当玩家碰触食物时,食物就会被销毁。食品的数量将超过2000。
问题:
我希望这些食物能在所有玩家的游戏环境中展现出来。我正在从大师那里实例化它,所有的食物都使用光子视图ID;然而,ViewID的限制只有999。我试着增加最大值,但我担心它会导致带宽问题等问题。
有什么办法,我可以同步所有的食物,所有的播放器,而不用大量的ViewID?
发布于 2021-10-28 11:13:02
创建自己的网络ID和管理器!
根据您的需要,最简单的方法是让一个中央管理器(MasterClient)生成食物实例,并为它们分配一个唯一的ID。然后告诉所有其他客户端也生成这个项目并分配相同的ID (例如使用带有所有所需参数的RPC)。另外,为了处理MasterClient的切换,保留所有现有in的列表,例如在Room属性中,因此在发生开关的情况下,新的主从者可以接管分配唯一in的任务,=>无限制;)
当然,这可能会变得相当“无趣”,你必须在周围玩一点,并测试它真的很好!
注意:以下代码未经测试并在智能手机上输入!但我希望它能给你一个好的起点。
这个类会进入食物预制件,所以每个食物都有这个自定义的网络身份
// 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);
}
}
}https://stackoverflow.com/questions/69745907
复制相似问题