Photon 多人游戏:房间、匹配与同步
Photon 多人游戏入门:房间、匹配与同步
在 Unity 中快速搭建实时多人游戏,Photon Unity Networking (PUN) 是一个广受好评的免费解决方案。本教程将从零开始,带你掌握最核心的三大机制:房间管理、自动匹配与对象同步。无论你是独立开发者还是想为自己的作品添加联机功能,阅读完本文后你将具备构建一个可玩多人场景的能力。
1. 环境准备与基础概念
在开始编码前,请确保已安装并注册 Photon Unity Networking 2(免费版可支持 20 CCU):
- 从 Unity Asset Store 导入 PUN 2 - Free。
- 在 Photon 官网注册并获取 App ID,粘贴到 Unity 的
Window > Photon Unity Networking > PUN Wizard中完成初始化。 - 创建一个空场景,添加
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 模式,并设置合适的 Position 与 Rotation 的插值速度。
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. 实现简单的匹配系统
为了让游戏更有趣,可以设计一个自动匹配流程。以下是常见的逻辑框架:
- 快速匹配:调用
JoinRandomRoom,若失败则创建房间。 - 带条件的匹配:创建房间时设定自定义属性(如地图名、模式),然后通过 SQL 过滤房间列表(需付费版)。免费版可通过房间名称规则手动匹配。
- 重连机制:利用
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.SerializationRate和PhotonNetwork.SendRate调整发送频率。 - 合并不重要的同步到低频率的缓存流。
9. 下一步:进阶学习
完成基础房间、匹配与同步后,你可以进一步探索:
- Photon Voice:加入语音聊天。
- Photon Chat:构建大厅内的聊天系统。
- 自定义属性(Player Custom Properties)用于显示玩家等级、皮肤等。
- Photon Bolt 或 Fusion:更适合物理同步与状态机的高级网络框架。
此刻你已掌握构建多人游戏核心的最重要技能。动手实践,修改参数,你将拥有一个完全由你掌控的网络世界。
本教程所使用的 PUN 2 版本为 2.40+,Unity 版本 2020 LTS 及以上。免费套餐完全适合开发阶段,祝你创作愉快!