如何在Photon中实现玩家进出房间时ActorNumber无间隙连续?
问题
用Unity结合Photon开发多人游戏时,玩家按ActorNumber从预定义字典获取出生点生成。但玩家离开后,新玩家的ActorNumber会递增(比如原房间有1、2、3号玩家,2号离开后新玩家拿到4号),导致字典里的2号出生点空置,没法充分利用预定义位置。
需求是:玩家离开后,新玩家能复用空缺的ActorNumber,保持序列连续(比如2号离开后,新玩家直接用2号)。
当前代码:
public class SpawnPlayers : MonoBehaviourPunCallbacks { public GameObject playerPRE; private Dictionary<int, Vector3> waitingRoomSpawnPositions = new Dictionary<int, Vector3>() { {1, new Vector3(0, 0, 0)}, {2, new Vector3(0.85f, 0, 1.25f)}, {3, new Vector3(-0.85f, 0, 1.25f)}, {4, new Vector3(-1.7f, 0, 2.5f)}, {5, new Vector3(1.7f, 0, 2.5f)}, {6, new Vector3(0.85f, 0, 3.75f)}, {7, new Vector3(-0.85f, 0, 3.75f)}, {8, new Vector3(0, 0, 2.5f)} }; public void PlayerSpawn() { int actorNumber = PhotonNetwork.LocalPlayer.ActorNumber; Vector3 spawnPosition = Vector3.zero; if (SceneManager.GetActiveScene().name == "Home") { waitingRoomSpawnPositions.TryGetValue(actorNumber, out spawnPosition); PhotonNetwork.Instantiate(playerPRE.name, spawnPosition, Quaternion.Euler(0, 160f, 0)); } } public override void OnJoinedRoom() { PlayerSpawn(); } }
解决方案
Photon官方云服务的ActorNumber是全局递增的,不会自动复用空缺ID,所以得自己实现位置分配逻辑,下面是两种可行方案:
方案1:自定义位置索引(推荐)
放弃依赖ActorNumber,自己维护房间内的可用位置索引,通过Photon房间属性同步状态:
- 房间创建时,初始化1-8的可用索引列表
- 玩家加入时,取列表第一个可用索引分配,同时移除该索引
- 玩家离开时,将其占用的索引放回列表
- 所有操作通过房间自定义属性同步,确保全客户端一致
修改后的代码:
using UnityEngine; using Photon.Pun; using Photon.Realtime; using System.Collections.Generic; using System.Linq; using UnityEngine.SceneManagement; public class SpawnPlayers : MonoBehaviourPunCallbacks { public GameObject playerPRE; private Dictionary<int, Vector3> waitingRoomSpawnPositions = new Dictionary<int, Vector3>() { {1, new Vector3(0, 0, 0)}, {2, new Vector3(0.85f, 0, 1.25f)}, {3, new Vector3(-0.85f, 0, 1.25f)}, {4, new Vector3(-1.7f, 0, 2.5f)}, {5, new Vector3(1.7f, 0, 2.5f)}, {6, new Vector3(0.85f, 0, 3.75f)}, {7, new Vector3(-0.85f, 0, 3.75f)}, {8, new Vector3(0, 0, 2.5f)} }; private const string AvailablePositionsKey = "AvailablePositions"; public override void OnJoinedRoom() { if (PhotonNetwork.IsMasterClient) { // 房间主人初始化可用位置列表 var availablePositions = Enumerable.Range(1, 8).ToList(); PhotonNetwork.CurrentRoom.SetCustomProperties(new ExitGames.Client.Photon.Hashtable() { { AvailablePositionsKey, availablePositions } }); } // 分配位置并生成玩家 AssignAndSpawnPlayer(); } private void AssignAndSpawnPlayer() { if (!PhotonNetwork.CurrentRoom.CustomProperties.TryGetValue(AvailablePositionsKey, out object availableObj)) return; var availablePositions = availableObj as List<int>; if (availablePositions == null || availablePositions.Count == 0) return; // 取第一个可用位置 int assignedPos = availablePositions[0]; availablePositions.RemoveAt(0); // 房间主人同步更新可用列表 if (PhotonNetwork.IsMasterClient) { PhotonNetwork.CurrentRoom.SetCustomProperties(new ExitGames.Client.Photon.Hashtable() { { AvailablePositionsKey, availablePositions } }); } // 把分配的位置存在玩家自定义属性里,方便离开时回收 PhotonNetwork.LocalPlayer.SetCustomProperties(new ExitGames.Client.Photon.Hashtable() { { "PlayerPos", assignedPos } }); // 生成玩家 if (SceneManager.GetActiveScene().name == "Home") { if (waitingRoomSpawnPositions.TryGetValue(assignedPos, out Vector3 spawnPos)) { PhotonNetwork.Instantiate(playerPRE.name, spawnPos, Quaternion.Euler(0, 160f, 0)); } } } public override void OnPlayerLeftRoom(Player otherPlayer) { if (!PhotonNetwork.IsMasterClient) return; // 回收离开玩家的位置 if (otherPlayer.CustomProperties.TryGetValue("PlayerPos", out object posObj)) { int releasedPos = (int)posObj; if (PhotonNetwork.CurrentRoom.CustomProperties.TryGetValue(AvailablePositionsKey, out object availableObj)) { var availablePositions = availableObj as List<int>; if (availablePositions != null) { availablePositions.Add(releasedPos); availablePositions.Sort(); // 保持列表有序,优先分配小索引 PhotonNetwork.CurrentRoom.SetCustomProperties(new ExitGames.Client.Photon.Hashtable() { { AvailablePositionsKey, availablePositions } }); } } } } public override void OnRoomPropertiesUpdate(ExitGames.Client.Photon.Hashtable propertiesThatChanged) { // 非主人玩家在房间属性更新后,确认自己是否已分配位置 if (!PhotonNetwork.IsMasterClient && !PhotonNetwork.LocalPlayer.CustomProperties.ContainsKey("PlayerPos")) { AssignAndSpawnPlayer(); } } }
方案2:基于玩家列表排序分配位置
如果不需要严格复用原ActorNumber,只是让新玩家补空缺位置,可以:
- 获取房间内所有玩家的列表,按
ActorNumber排序 - 取当前玩家在排序后列表中的位置(从1开始)作为索引,对应字典里的出生点
示例代码片段:
public void PlayerSpawn() { if (SceneManager.GetActiveScene().name != "Home") return; // 获取排序后的玩家列表 var sortedPlayers = PhotonNetwork.PlayerList.OrderBy(p => p.ActorNumber).ToList(); // 取当前玩家在列表中的索引+1作为位置ID int positionId = sortedPlayers.IndexOf(PhotonNetwork.LocalPlayer) + 1; if (waitingRoomSpawnPositions.TryGetValue(positionId, out Vector3 spawnPos)) { PhotonNetwork.Instantiate(playerPRE.name, spawnPos, Quaternion.Euler(0, 160f, 0)); } }
这个方案优点是实现简单,但缺点是玩家离开后,剩余玩家的位置会重新排序(比如原1、3号玩家,新玩家加入后变成1、2、3,原3号玩家会自动切换到2号位置),如果需要固定玩家位置的话不适用。
内容的提问来源于stack exchange,提问作者energy app
相关产品推荐
相关产品推荐

