Photon 多人游戏:房间、匹配与同步

FreeGuideOnline 最新 2026-06-17

Photon 多人游戏入门:房间、匹配与同步

在 Unity 中快速搭建实时多人游戏,Photon Unity Networking (PUN) 是一个广受好评的免费解决方案。本教程将从零开始,带你掌握最核心的三大机制:房间管理自动匹配对象同步。无论你是独立开发者还是想为自己的作品添加联机功能,阅读完本文后你将具备构建一个可玩多人场景的能力。


1. 环境准备与基础概念

在开始编码前,请确保已安装并注册 Photon Unity Networking 2(免费版可支持 20 CCU):

  1. 从 Unity Asset Store 导入 PUN 2 - Free。
  2. 在 Photon 官网注册并获取 App ID,粘贴到 Unity 的 Window > Photon Unity Networking > PUN Wizard 中完成初始化。
  3. 创建一个空场景,添加 PhotonServerSettings 资源(可自动生成)。

PUN 的核心思想:GameObject 想要在网络上同步,只需为其挂载 PhotonView 组件,并由拥有该对象的客户端通过 PhotonView.RPC() 或自定义 OnPhotonSerializeView 来发送数据。


2. 连接与大厅

所有多人交互始于连接。创建一个名为 NetworkManager 的脚本,负责连接逻辑。

using Photon.Pun;
using Photon.Realtime;
using UnityEngine;

public class NetworkManager : MonoBehaviourPunCallbacks
{
    void Start()
    {
        PhotonNetwork.ConnectUsingSettings(); // 使用 PUN Wizard 中配置的设置连接
    }

    public override void OnConnectedToMaster()
    {
        Debug.Log("已连接到 Photon Master 服务器");
        PhotonNetwork.JoinLobby(); // 进入大厅,以查看房间列表
    }
}

挂载该脚本到场景中的空物体上,运行后即可看到连接日志。


3. 房间管理:创建与加入

多人游戏的核心是房间。每个战斗、世界都是一个房间。PUN 提供了简洁的 API。

3.1 创建房间

public void CreateRoom(string roomName)
{
    RoomOptions options = new RoomOptions();
    options.MaxPlayers = 4;          // 最大玩家数
    options.IsVisible = true;        // 是否在大厅中列出
    options.IsOpen = true;           // 是否允许加入
    PhotonNetwork.CreateRoom(roomName, options, TypedLobby.Default);
}

3.2 随机加入房间

不想让玩家手动选择房间时,可使用快速匹配:

public void JoinRandomRoom()
{
    PhotonNetwork.JoinRandomRoom();
}

// 回调:如果没有可加入的房间
public override void OnJoinRandomFailed(short returnCode, string message)
{
    Debug.Log("没有可用房间,正在创建新房间...");
    CreateRoom("Room_" + Random.Range(0, 1000));
}

3.3 按列表加入

显示房间列表要求客户端已加入大厅,然后用 PhotonNetwork.GetCustomRoomList(TypedLobby.Default) 获取列表,让玩家自行选择。这部分涉及 UI 构建,但核心加入方式为:

public void JoinRoomByName(string roomName)
{
    PhotonNetwork.JoinRoom(roomName);
}

4. 玩家实例化与场景同步

进入房间后,需要为每个玩家生成可控制的角色。利用 PhotonNetwork.Instantiate 可以在所有客户端同步创建 GameObject。

4.1 设置资源路径

将玩家预制体放入 Resources 文件夹(例如 Resources/PlayerPrefab)。因为 PUN 是通过名称字符串来加载的。

4.2 生成本地玩家

NetworkManager 中添加房间进入回调:

public override void OnJoinedRoom()
{
    Debug.Log($"已加入房间 {PhotonNetwork.CurrentRoom.Name}");
    // 实例化玩家对象,位置可随机
    PhotonNetwork.Instantiate("PlayerPrefab", 
        new Vector3(Random.Range(-5,5), 0, Random.Range(-5,5)), 
        Quaternion.identity);
}

PhotonNetwork.Instantiate 会自动在对象上附加 PhotonView,并确保网络同步。


5. 实时位置同步

场景中最常见的同步需求是玩家移动。PUN 提供了两种主流方式:组件自动同步手动序列化。对于初学者,推荐使用 Photon Transform View 组件。

5.1 使用 Photon Transform View

在玩家预制体上添加 PhotonView(已被 PUN 自动添加),再挂载 PhotonTransformView 组件。此时,拥有该对象的客户端在移动 Transform 时会自动发送位置、旋转信息到其他客户端。

但注意,默认的更新频率可能引起抖动。可以对 PhotonTransformView 进行平滑调整:启用 Interpolate 模式,并设置合适的 PositionRotation 的插值速度。

5.2 更精确的同步:手动 RPC 或 OnPhotonSerializeView

对于需要更低延迟或自定义逻辑的情况,可以继承 IPunObservable,在 OnPhotonSerializeView 中手动编写同步代码。

using Photon.Pun;
using UnityEngine;

public class PlayerSync : MonoBehaviour, IPunObservable
{
    private Vector3 networkPosition;
    private Quaternion networkRotation;

    public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
        if (stream.IsWriting)
        {
            // 本地拥有者发送位置与旋转
            stream.SendNext(transform.position);
            stream.SendNext(transform.rotation);
        }
        else
        {
            // 远程客户端接收并缓存
            networkPosition = (Vector3)stream.ReceiveNext();
            networkRotation = (Quaternion)stream.ReceiveNext();
        }
    }

    void Update()
    {
        if (!photonView.IsMine)
        {
            // 非拥有者平滑插值到接收到的位置
            transform.position = Vector3.Lerp(transform.position, networkPosition, Time.deltaTime * 10);
            transform.rotation = Quaternion.Slerp(transform.rotation, networkRotation, Time.deltaTime * 10);
        }
    }
}

将该脚本挂载到玩家预制体,并移除 PhotonTransformView 以避免冲突。


6. 远程过程调用(RPC)实现动作同步

位置只是基础,像射击、跳跃、播放特效等动作需要更灵活的同步方式:RPC

6.1 定义 RPC 方法

在一个带有 PhotonView 的脚本中编写:

[PunRPC]
void Fire()
{
    // 播放动画,生成子弹特效等
    GetComponent<Animator>().SetTrigger("Shoot");
}

6.2 调用 RPC

RPC 可以针对所有客户端、仅指定玩家、或全部缓存玩家调用。常用的是 PhotonView.RPC("Fire", RpcTarget.All)

if (Input.GetButtonDown("Fire1"))
{
    photonView.RPC("Fire", RpcTarget.All);
}

这样,当本地玩家按下射击键时,所有客户端上的该对象都会执行 Fire 方法,实现视觉同步。


7. 实现简单的匹配系统

为了让游戏更有趣,可以设计一个自动匹配流程。以下是常见的逻辑框架:

  1. 快速匹配:调用 JoinRandomRoom,若失败则创建房间。
  2. 带条件的匹配:创建房间时设定自定义属性(如地图名、模式),然后通过 SQL 过滤房间列表(需付费版)。免费版可通过房间名称规则手动匹配。
  3. 重连机制:利用 PhotonNetwork.ReconnectToMaster()PhotonNetwork.RejoinRoom() 处理断线重连。

一个扩展的自动匹配方法示例:

public void QuickMatch()
{
    PhotonNetwork.JoinRandomRoom(new ExitGames.Client.Photon.Hashtable(), 0, MatchmakingMode.FillRoom, TypedLobby.Default, null, null);
}

public override void OnJoinRandomFailed(short returnCode, string message)
{
    CreateRoom("Match_" + Random.Range(0, 9999));
}

8. 常见问题与优化

8.1 场景切换与同步加载

使用 PhotonNetwork.LoadLevel("GameScene") 替换 Unity 自己的 SceneManager.LoadScene,确保所有客户端同步切换。

8.2 玩家离开处理

重写 OnPlayerLeftRoom 回调,清理该玩家留下的对象或更新 UI。

public override void OnPlayerLeftRoom(Player otherPlayer)
{
    Debug.Log($"{otherPlayer.NickName} 离开了房间");
}

8.3 减少带宽消耗

  • 将不需要频繁同步的对象标记为 PhotonView.Observation 组件关闭。
  • 使用 PhotonNetwork.SerializationRatePhotonNetwork.SendRate 调整发送频率。
  • 合并不重要的同步到低频率的缓存流。

9. 下一步:进阶学习

完成基础房间、匹配与同步后,你可以进一步探索:

  • Photon Voice:加入语音聊天。
  • Photon Chat:构建大厅内的聊天系统。
  • 自定义属性(Player Custom Properties)用于显示玩家等级、皮肤等。
  • Photon BoltFusion:更适合物理同步与状态机的高级网络框架。

此刻你已掌握构建多人游戏核心的最重要技能。动手实践,修改参数,你将拥有一个完全由你掌控的网络世界。

本教程所使用的 PUN 2 版本为 2.40+,Unity 版本 2020 LTS 及以上。免费套餐完全适合开发阶段,祝你创作愉快!