You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 05:45:02