当前位置: 首页 > news >正文

Unity 2022 LTS + Photon Fusion 2:手把手教你搭建第一个多人联机Demo(含完整代码)

Unity 2022 LTS + Photon Fusion 2:30分钟打造多人联机射击原型

当你在Unity 2022 LTS中首次打开Photon Fusion 2的文档时,可能会被那些"状态同步"、"客户端预测"等术语吓到。但别担心,我们今天要做的不是理论研究,而是直接动手创建一个能让你和好友实时互射小球的多人游戏原型。这个过程中,你会自然理解Fusion的核心机制。

1. 环境准备与基础配置

在开始编码前,我们需要确保开发环境正确配置。打开Unity 2022 LTS(建议使用2022.3.9f1版本),创建一个新的3D核心项目。

关键配置步骤:

  1. Photon账号注册
    访问Photon官网注册账号,在Dashboard中创建新应用,选择Fusion类型。记下生成的App ID,这相当于连接Photon服务的通行证。

  2. 项目设置调整
    Edit > Project Settings > Editor中,将Asset Serialization模式改为Force Text。这是Fusion生成网络代码的必要设置。

  3. SDK导入
    下载最新Fusion SDK后,通过Assets > Import Package > Custom Package导入。导入完成后会自动弹出Fusion Hub向导,粘贴之前获取的App ID。

提示:如果导入后出现Mono Cecil缺失错误,可通过Package Manager添加:com.unity.nuget.mono-cecil@1.10.2

2. 搭建基础联机框架

2.1 创建网络管理器

首先创建一个空对象命名为NetworkManager,并挂载新建的NetworkManager.cs脚本:

using Fusion; using UnityEngine; public class NetworkManager : MonoBehaviour, INetworkRunnerCallbacks { private NetworkRunner _runner; public async void StartGame(GameMode mode) { _runner = gameObject.AddComponent<NetworkRunner>(); _runner.ProvideInput = true; await _runner.StartGame(new StartGameArgs() { GameMode = mode, SessionName = "QuickDemoRoom", SceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>() }); } // 实现INetworkRunnerCallbacks接口方法 public void OnPlayerJoined(NetworkRunner runner, PlayerRef player) { /* 后续实现 */ } public void OnPlayerLeft(NetworkRunner runner, PlayerRef player) { /* 后续实现 */ } // ... 其他回调方法暂留空 }

2.2 添加简易UI控制

为快速测试,我们添加简单的GUI按钮:

private void OnGUI() { if (_runner == null) { if (GUI.Button(new Rect(0,0,200,40), "Host")) StartGame(GameMode.Host); if (GUI.Button(new Rect(0,40,200,40), "Join")) StartGame(GameMode.Client); } }

此时运行游戏,点击Host/Join按钮应该能看到控制台日志,但还没有实际游戏内容。

3. 实现玩家角色与移动

3.1 创建玩家预制体

  1. 新建空对象命名为PlayerPrefab
  2. 添加组件:
    • NetworkObject(核心网络标识)
    • NetworkCharacterController(带预测的移动控制)
    • 添加子对象Capsule作为视觉表现

关键配置:

  • 确保NetworkObject的Player Object选项勾选
  • 移除子对象上的碰撞体,使用父对象的CharacterController

3.2 玩家移动脚本

创建PlayerMovement.cs

using Fusion; using UnityEngine; public class PlayerMovement : NetworkBehaviour { private NetworkCharacterController _cc; [Networked] private Angle _yaw { get; set; } private void Awake() { _cc = GetComponent<NetworkCharacterController>(); } public override void FixedUpdateNetwork() { if (GetInput(out NetworkInputData input)) { Vector3 moveDir = Vector3.zero; if (input.IsDown(NetworkInputData.BUTTON_FORWARD)) moveDir += Vector3.forward; if (input.IsDown(NetworkInputData.BUTTON_BACK)) moveDir += Vector3.back; if (input.IsDown(NetworkInputData.BUTTON_LEFT)) moveDir += Vector3.left; if (input.IsDown(NetworkInputData.BUTTON_RIGHT)) moveDir += Vector3.right; _cc.Move(5 * transform.rotation * moveDir * Runner.DeltaTime); if (input.IsDown(NetworkInputData.BUTTON_FIRE)) GetComponent<PlayerShooter>().Fire(); } } }

3.3 输入系统配置

创建NetworkInputData.cs定义输入结构:

using Fusion; using UnityEngine; public struct NetworkInputData : INetworkInput { public const int BUTTON_FORWARD = 1; public const int BUTTON_BACK = 1 << 1; public const int BUTTON_LEFT = 1 << 2; public const int BUTTON_RIGHT = 1 << 3; public const int BUTTON_FIRE = 1 << 4; public NetworkButtons buttons; }

在NetworkManager中实现输入收集:

public void OnInput(NetworkRunner runner, NetworkInput input) { var data = new NetworkInputData(); data.buttons.Set(NetworkInputData.BUTTON_FORWARD, Input.GetKey(KeyCode.W)); data.buttons.Set(NetworkInputData.BUTTON_BACK, Input.GetKey(KeyCode.S)); data.buttons.Set(NetworkInputData.BUTTON_LEFT, Input.GetKey(KeyCode.A)); data.buttons.Set(NetworkInputData.BUTTON_RIGHT, Input.GetKey(KeyCode.D)); data.buttons.Set(NetworkInputData.BUTTON_FIRE, Input.GetMouseButton(0)); input.Set(data); }

4. 实现射击系统

4.1 创建子弹预制体

  1. 新建Sphere对象命名为BulletPrefab
  2. 添加组件:
    • NetworkObject
    • NetworkTransform(同步位置)
    • 新建Bullet.cs脚本

Bullet.cs核心代码:

using Fusion; using UnityEngine; public class Bullet : NetworkBehaviour { [Networked] private TickTimer life { get; set; } [Networked] private Vector3 direction { get; set; } public void Init(Vector3 fireDirection) { direction = fireDirection; life = TickTimer.CreateFromSeconds(Runner, 2f); } public override void FixedUpdateNetwork() { if (life.Expired(Runner)) { Runner.Despawn(Object); return; } transform.position += direction * 10 * Runner.DeltaTime; } }

4.2 玩家射击脚本

创建PlayerShooter.cs

using Fusion; using UnityEngine; public class PlayerShooter : NetworkBehaviour { [SerializeField] private NetworkPrefabRef bulletPrefab; [Networked] private TickTimer cooldown { get; set; } public void Fire() { if (cooldown.ExpiredOrNotRunning(Runner)) { cooldown = TickTimer.CreateFromSeconds(Runner, 0.3f); Runner.Spawn(bulletPrefab, transform.position + transform.forward, Quaternion.LookRotation(transform.forward), Object.InputAuthority, (runner, obj) => { obj.GetComponent<Bullet>().Init(transform.forward); }); } } }

5. 玩家生成与同步

回到NetworkManager完善玩家生成逻辑:

[SerializeField] private NetworkPrefabRef playerPrefab; private Dictionary<PlayerRef, NetworkObject> spawnedPlayers = new Dictionary<PlayerRef, NetworkObject>(); public void OnPlayerJoined(NetworkRunner runner, PlayerRef player) { if (runner.IsServer) { Vector3 spawnPos = new Vector3((player.RawEncoded % 10) * 2 - 10, 0, 0); NetworkObject playerObj = runner.Spawn(playerPrefab, spawnPos, Quaternion.identity, player); spawnedPlayers.Add(player, playerObj); } } public void OnPlayerLeft(NetworkRunner runner, PlayerRef player) { if (spawnedPlayers.TryGetValue(player, out NetworkObject playerObj)) { runner.Despawn(playerObj); spawnedPlayers.Remove(player); } }

6. 场景布置与最终��试

  1. 创建地面(Plane对象)
  2. 为玩家和子弹添加简单材质区分
  3. 确保所有预制体都已拖入Resources文件夹
  4. 在NetworkManager中分配playerPrefab和bulletPrefab引用

测试流程:

  1. 构建项目并运行两个实例
  2. 一个实例点击Host,另一个点击Join
  3. 使用WASD移动,鼠标左键射击
  4. 观察子弹的同步效果和碰撞

7. 性能优化技巧

  1. 网络压缩
    在NetworkRunner组件上调整:

    _runner.Config.Compression = NetworkProjectConfig.CompressionType.LZ4;
  2. 插值设置
    对移动对象调整NetworkTransform:

    GetComponent<NetworkTransform>().InterpolationDataSource = InterpolationDataSources.Snapshots;
  3. 带宽优化
    在NetworkInputData中使用位域压缩:

    public byte inputByte; // 每位代表一个按键状态

这个原型虽然简单,但已经包含了Fusion最核心的功能模块。你可以在此基础上继续扩展:

  • 添加命中检测与分数系统
  • 实现房间列表与匹配机制
  • 加入更复杂的物理交互
  • 优化网络预测算法参数
http://www.jsqmd.com/news/881258/

相关文章:

  • 时间序列预测实战:从LightGBM到GNN与强化学习的算法选型指南
  • 海尔智能家居设备接入HomeAssistant:打造一体化智能家居控制中心
  • 机器学习解码结直肠癌基因协同作用:从WNT通路到联合治疗新靶点
  • 2026年4月头部火锅品牌推荐,地摊火锅/重庆火锅/成都火锅/社区火锅/牛肉火锅/美食/附近火锅,火锅品牌推荐 - 品牌推荐师
  • 如何为Tesla-Menu添加自定义覆盖?终极开发者入门指南
  • 融合物理与AI:基于DtN映射与FEM的椭圆型PDE反问题自监督求解框架
  • 告别音乐平台切换:开源音源聚合方案如何重塑你的听歌体验
  • 从零构建智能对话工作流:SillyTavern脚本系统的深度应用指南
  • JoyCon-Driver 多控制器管理:同时连接4个 JoyCons 的配置指南
  • Unity Android构建报错SDK version is 0的根因与精准修复
  • 2026年4月市面上靠谱的udb测试直销厂家推荐,疲劳曲线测试/压铸件模流分析,udb测试直销厂家推荐 - 品牌推荐师
  • ImageSearch部署指南:从开发环境到生产环境的完整迁移策略
  • OpenPilot深度部署指南:从架构解析到生产级调优
  • G-Helper终极指南:华硕笔记本轻量控制神器,告别Armoury Crate臃肿
  • Forge中的上下文压缩:处理长对话的高效方法
  • Linux服务器升级OpenSSL 3.2.0后,为什么我的curl命令不能用了?一个软链接引发的‘血案’
  • WOFOST模型参数太多看不懂?一篇带你读懂关键参数设置与避坑指南(以小麦/玉米为例)
  • Unity银河战士类游戏开发:状态机、关卡拓扑与Boss行为树实战
  • Hindsight观察系统终极指南:AI智能体的自动知识整合机制 [特殊字符]
  • GHelper终极指南:轻量级华硕笔记本控制工具完整教程
  • vue-axios-github解密:5分钟理解axios拦截器实现请求/响应统一处理
  • Atomic Layout高级技巧:使用Query函数实现自定义媒体查询
  • vue-axios-github架构详解:从路由设计到状态管理的前端安全实践
  • ARMv8-A架构调试机制:断点与观察点实现原理
  • UniShopX部署与运维指南:Docker容器化与生产环境配置
  • Windows宿主机禁用CPU性能计数器导致VMware启动失败
  • AI Agent的节能与绿色计算:优化计算资源消耗的算法与策略
  • 【MySQL】进阶01-存储引擎
  • 如何快速部署PostgreSQL数据建模工具:跨平台完整安装教程
  • Akagi麻将AI助手:5分钟搭建你的实时对局分析系统,告别盲目打牌!