1、插件导入与配置准备
见主机模式基础教程
【Unity Photon Fusion 2】多人联网插件,主机模式基础教程-CSDN博客
2、共享模式基础使用
2.1 场景创建与玩家化身创建
概述
该部分解释了如何设置一个简单的场景以及如何创建一个玩家预制件。在本节结束时,您将拥有一个工作的网络场景,为每个连接的客户端生成一个播放器对象。
2.1.1 设置场景
从一个空的Unity场景开始。右键单击Unity中的Hierarchy选项卡,选择Fusion> Scene > Setup Networking in Scene
这将在场景中添加一个Prototype Runner物体和一个Prototype Network Start物体。
- Prototype Runner包含Network Runner,它是运行Fusion模拟的核心组件。Network Events脚本允许您快速将自己的功能链接到各种网络事件,例如玩家加入或离开会话。
- Prototype Network Start是一个原型组件,它包含一个用于快速加入Fusion房间的引导GUl。
接下来,在场景中添加一个地板,右键单击Unity中的Hierarchy选项卡,选择3D Object> Plane。将GameObject重命名为Floor并将其位置重置为(0, 0, 0)。
2.1.2 创建玩家预制件
接下来创建播放器对象。右键单击层次结构,选择3d Object > 胶囊。将Gameobject重命名为PlayerCharacter。
向其中添加一个Networkobject组件。该组件为对象提供网络标识,以便所有对等体都可以引用它。
添加CharacterController组件。之后移动代码将使用这一点来移动玩家角色。
最后,向其中添加一个NetworkTransform组件。NetworkTransform自动将对象的位置同步到其他客户端。
将PlayerCharacter拖拽到Project窗口中创建预制件并将其从场景中删除。这样玩家角色就可以被生成了。
2.1.3 生成玩家
与单人游戏不同的是,场景中只有一个玩家对象是不够的。对于多人游戏,需要为每个加入会话的玩家生成一个玩家对象。为此,需要一个自定义脚本。创建一个PlayerSpawner脚本并打开它。将以下代码添加到其中:
using Fusion;
using UnityEngine;
public class PlayerSpawner : SimulationBehaviour, IPlayerJoined
{
public GameObject PlayerPrefab;
public void PlayerJoined(PlayerRef player)
{
if (player == Runner.LocalPlayer)
{
Runner.Spawn(PlayerPrefab, new Vector3(0, 1, 0), Quaternion.identity);
}
}
}
代码非常简单。IPlayerJoined接口有一个PlayerJoined函数,每当玩家加入会话时,如果行为与runner在相同的GameObject上,该函数就会被调用。这不仅适用于我们自己的玩家,也适用于从不同设备加入的其他玩家。它只需要为我们自己的播放器生成一个播放器对象。当一个对象用Runner.Spawn()时。它会自动复制到所有其他客户端。
SimulationBehaviour用于访问NetworkRunner,其中包含有关当前会话的所有信息,包括本地玩家的玩家ID。
将PlayerSpawner组件添加到Prototype Runner 对象中,并将PlayerCharacter预制件分配给它。
这样,玩家生成就完全设置好了。进入播放模式,按“Start Shared Client”启动共享模式的Fusion。
玩家胶囊将出现在场景中。玩家还不能移动或与任何东西互动。在下一章中,我们将通过执行玩家移动来赋予玩家生命。
2.2 移动控制与相机控制
概述
在第该部分中,通过添加基于玩家输入和第一人称的玩家移动来扩展现有场景相机跟随正确的玩家对象。
2.2.1 玩家移动控制
在Fusion中,更新每一次tick(如移动)的游戏玩法代码不应该在Update / FixedUpdate中运行,而应该使用FixedUpdateNetwork。这确保了运动是平滑的,并在所有客户端上正确地插入。
创建一个新脚本,命名为PlayerMovement,并将以下代码添加到脚本中:
using Fusion;
using UnityEngine;
public class PlayerMovement : NetworkBehaviour
{
private CharacterController _controller;
public float PlayerSpeed = 2f;
private void Awake()
{
_controller = GetComponent<CharacterController>();
}
public override void FixedUpdateNetwork()
{
// Only move own player and not every other player. Each player controls its own player object.
if (HasStateAuthority == false)
{
return;
}
Vector3 move = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")) * Runner.DeltaTime * PlayerSpeed;
_controller.Move(move);
if (move != Vector3.zero)
{
gameObject.transform.forward = move;
}
}
}
请注意,它继承自NetworkBehaviour而不是MonoBehaviour,后者提供FixedUpdateNetwork,并允许稍后使用Networked Properties。
HasStateAuthority可以用来检查客户端是否控制一个对象。NetworkTransform只将来自StateAuthority的更改同步到其他所有人。如果另一个客户端更改了对象的位置,则该更改将仅是局部更改,并且将来会被来自网络的数据覆盖。
总是使用Runner。在FixedUpdateNetwork中运行的代码的DeltaTime !
处理按钮按下
像GetButtonDown和Up这样的一次性输入会在Unity的Update中被触发。在Fusion中,移动等玩法代码应该在FixedUpdateNetwork中执行。这意味着需要特殊的处理来准确地跟踪按钮状态。
请注意,在FixedUpdateNetwork中检查按钮按下时也会出现相同的问题。
有多种方法可以捕获像FixedUpdateNetwork这样的输入:
1)、查询更新中的按钮按钮。将结果存储在bool中,并将其用于游戏逻辑。最后把它清理干净
固定更新网络
2)、使用Fusion的Networklnput和NetworkButtons。
3)、使用新的Unity输入系统,将Update Mode设置为Manual Update并在FixedUpdateNetwork调用IInputSystem.Update
以上选项仅适用于共享模式。在服务器/主机模式下运行Fusion时,总是使用Networklnput。
2.2.2 玩家跳跃控制
让我们实现一个通过为玩家执行跳跃动作来处理按钮按压的例子。在本例中,使用上一节列出的选项中的选项1。
将PlayerMovement脚本中的代码替换为以下代码:
using Fusion;
using UnityEngine;
public class PlayerMovement : NetworkBehaviour
{
private Vector3 _velocity;
private bool _jumpPressed;
private CharacterController _controller;
public float PlayerSpeed = 2f;
public float JumpForce = 5f;
public float GravityValue = -9.81f;
private void Awake()
{
_controller = GetComponent<CharacterController>();
}
void Update()
{
if (Input.GetButtonDown("Jump"))
{
_jumpPressed = true;
}
}
public override void FixedUpdateNetwork()
{
// Only move own player and not every other player. Each player controls its own player object.
if (HasStateAuthority == false)
{
return;
}
if (_controller.isGrounded)
{
_velocity = new Vector3(0, -1, 0);
}
Vector3 move = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")) * Runner.DeltaTime * PlayerSpeed;
_velocity.y += GravityValue * Runner.DeltaTime;
if (_jumpPressed && _controller.isGrounded)
{
_velocity.y += JumpForce;
}
_controller.Move(move + _velocity * Runner.DeltaTime);
if (move != Vector3.zero)
{
gameObject.transform.forward = move;
}
_jumpPressed = false;
}
}
这段代码向KCC添加了跳跃函数和速度/重力处理。
_jumpPressed用于跟踪按钮状态。该按钮在每次更新时进行轮询,以确保没有漏按,并在FixedUpdateNetwork结束时重置。
if (_controller.isGrounded)
{
_velocity = new Vector3(0, -1, 0);
}
是用来确保KCC保持固定在地面上,即使走在小斜坡上,只有跳跃允许玩家离开地面。
2.2.3 游戏测试
准备好PlayerMovement脚本后,返回Unity并将PlayerMovement组件添加到PlayerCharacter预制中。
现在是测试玩家移动的时候了。要有两个客户端可用,首先创建一个Unity构建。
进入File> Build Settings,将当前场景添加到列表中。然后按下窗口左下方的播放器设置按钮。
在播放器设置中的分辨率和呈现设置全屏模式为窗口,并确保在后台运行被选中。
返回到 Build Settings 窗口并选择 Build and Run 以创建生成。一旦构建完成,在Unity中进入播放模式,以便两个客户端可用。
在两个客户端上按 Start Shared Client 按钮。出现两个玩家,每个玩家由一个客户控制。测试走动和跳跃是否有效。
2.2.4 相机控制
为了完成播放器设置,让我们添加一个摄像机。
设置摄像头的方法有很多种(第三人称、第一人称等),执行方法也各不相同。在多人游戏中,通常需要单个摄像机跟随本地玩家对象。有两种常见的方法可以做到这一点。
1)、当本地播放器对象被生成时,让它实例化一个摄像机。
2)、让摄像机成为场景的一部分。当本地播放器对象生成时,让它将相机附加到自身上。
在本教程中使用第二种方法。
创建一个新脚本并命名为FirstPersonCamera。在其中添加以下代码
using UnityEngine;
public class FirstPersonCamera : MonoBehaviour
{
public Transform Target;
public float MouseSensitivity = 10f;
private float verticalRotation;
private float horizontalRotation;
void LateUpdate()
{
if (Target == null)
{
return;
}
transform.position = Target.position;
float mouseX = Input.GetAxis("Mouse X");
float mouseY = Input.GetAxis("Mouse Y");
verticalRotation -= mouseY * MouseSensitivity;
verticalRotation = Mathf.Clamp(verticalRotation, -70f, 70f);
horizontalRotation += mouseX * MouseSensitivity;
transform.rotation = Quaternion.Euler(verticalRotation, horizontalRotation, 0);
}
}
这是一个非常简单的第一人称摄像机实现。注意,代码不包含任何多人游戏元素。您可以将此相机替换为与目标对象(包括Cinemachine)一起工作的任何其他相机设置。
将FirstPersonCamera行为放在场景中的MainCamera GameObject上。没有必要拖拽一个目标。目标将在运行时由代码设置。
要分配目标,打开PlayerMovement脚本。添加一个变量来存储摄像机:
public Camera Camera;
然后,当玩家对象生成时,如果它是本地玩家,则查找并设置相机。
public override void Spawned()
{
if (HasStateAuthority)
{
Camera = Camera.main;
Camera.GetComponent<FirstPersonCamera>().Target = transform;
}
}
在初始化Networkobjects时,请确保始终使用spawn而不是Awake/Start。在Awake/Start中,NetworkObject可能还没有准备好使用。
HasStateAuthority只对播放器控制的对象为真,所以只对本地播放器对象有效,对其他播放器对象无效。
最后,因为在第一人称游戏中,玩家角色会朝着视觉方向移动,所以需要调整一下移动方向。将计算Vector3移动的行替换为:
Quaternion cameraRotationY = Quaternion.Euler(0, Camera.transform.rotation.eulerAngles.y, 0);
Vector3 move = cameraRotationY * new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")) * Runner.DeltaTime * PlayerSpeed;
2.3 Network Properties 网络属性
概述
本节展示了如何通过网络同步其他数据,除了玩家的位置使用网络属性
2.3.1 网络属性
当您向networkobject添加NetworkTransform组件时,Fusion会同步它们的转换。其他状态,如脚本中的变量,不通过网络同步。要使状态在网络上同步,需要一个[Networked]属性。网络属性将其状态从StateAuthority同步到所有其他客户端。
如果客户端在一个对象上更改了一个它没有StateAuthority的Networked Property,那么这个更改不会在网络上同步,而是作为一个本地预测应用,并且可以在将来被StateAuthority的更改覆盖。如果您希望在每个客户机上更新StateAuthority上的Networked Properties,请注意只更新它。
网络属性的一个简单例子就是玩家的颜色。首先创建一个新脚本并命名为PlayerColor。添加一个Networked属性和一个公共字段来引用对象的MeshRender
using Fusion;
using UnityEngine;
public class PlayerColor : NetworkBehaviour
{
public MeshRenderer MeshRenderer;
[Networked]
public Color NetworkedColor { get; set; }
}
注意,网络属性必须是属性({ get;set; }),不支持常规字段。
接下来在更新中添加代码,允许StateAuthority改变颜色:
void Update()
{
if (HasStateAuthority && Input.GetKeyDown(KeyCode.E))
{
// Changing the material color here directly does not work since this code is only executed on the client pressing the button and not on every client.
NetworkedColor = new Color(Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f), 1f);
}
}
当按钮被按下时,网格渲染的颜色不会立即改变。相反,网络属性被更新。这样,颜色的变化将被复制到所有客户端。剩下的是每个客户端检查网络属性的变化,并相应地调整网格颜色。
在Fusion中,这是通过ChangeDetection完成的。在共享模式下做到这一点的最简单方法是使用OnChangedRender属性。首先添加一个函数,当NetworkedColor改变时执行。
void OnColorChanged()
{
MeshRenderer.material.color = NetworkedColor;
}
然后用OnChangedRender属性调整Networked属性:
[Networked, OnChangedRender(nameof(OnColorChanged))]
public Color NetworkedColor { get; set; }
这个属性在每次渲染帧(Unity Update)中检测到属性变化时调用OnColorChanged函数。
将PlayerColor组件添加到PlayerCharacter预制件中,并在播放器上链接MeshRender。进入播放模式并按 E 键,你可以在编辑视图中检查玩家正在改变颜色。此时,您还可以创建一个构建来验证颜色是否通过网络正确同步。
2.4 RPCs
概述
在最后一个部分,一个网络属性被添加到同步玩家的颜色到所有其他玩家。由于玩家对自己的网络属性拥有StateAuthority,他们可以直接更新自己的颜色值。
然而,这并不适用于网络对象的网络属性,其中StateAuthority是另一个客户端。
这部分包含了一个攻击玩家并在需要修改另一个玩家的网络属性时减少其生命值的例子。
2.4.1 修改其他玩家的网络属性
更改另一个客户端拥有状态权限的网络属性的方法是向对象的状态权限发送RPC(远程过程调用),并让它们更改网络属性
虽然可以通过代码更改网络财产的值。更改将仅在本地应用,而不会在网络上复制。
这方面的一个例子便是使用光线投射武器射击玩家。对于Fusion共享模式,一个简单的实现是让玩家在本地执行光线投射,如果它击中目标,则对目标施加伤害。注意,在服务器/主机模式下光线投射武器的方法是不同的。查看
Fusion Projectiles样本中的服务器/主机模式示例。
创建一个新脚本,并将其命名为Health。添加网络属性和更改检测功能,以记录运行状况值的更改:
using Fusion;
using UnityEngine;
public class Health : NetworkBehaviour
{
[Networked, OnChangedRender(nameof(HealthChanged))]
public float NetworkedHealth { get; set; } = 100;
void HealthChanged()
{
Debug.Log($"Health changed to: {NetworkedHealth}");
}
}
接下来添加一个RPC,射击者可以调用它对敌方玩家造成伤害:
[Rpc(RpcSources.All, RpcTargets.StateAuthority)]
public void DealDamageRpc(float damage)
{
// The code inside here will run on the client which owns this object (has state and input authority).
Debug.Log("Received DealDamageRpc on StateAuthority, modifying Networked variable");
NetworkedHealth -= damage;
}
RpcSources.All 这些都允许任何人调用RPC。默认情况下,只有InputAuthority(与共享模式下的StateAuthority相同)可以调用RPC。
RpcTargets.StateAuthority设置只有StateAuthority接收RPC。这样做是因为州政府会更新健康网络财产。RPC函数内部的代码运行在RpcTarget客户端上,因此在本例中运行在可以更改其自己的Networked Properties的StateAuthority上。
接下来创建一个新脚本并命名为itRaycastAttack。将以下代码添加到其中
using Fusion;
using UnityEngine;
public class RaycastAttack : NetworkBehaviour
{
public float Damage = 10;
public PlayerMovement PlayerMovement;
void Update()
{
if (HasStateAuthority == false)
{
return;
}
Ray ray = PlayerMovement.Camera.ScreenPointToRay(Input.mousePosition);
ray.origin += PlayerMovement.Camera.transform.forward;
if (Input.GetKeyDown(KeyCode.Mouse1))
{
Debug.DrawRay(ray.origin, ray.direction, Color.red, 1f);
}
}
}
这段代码是一个简单的单人游戏光线投射实现。HasStateAuthority检查只用于在StateAuthority上执行这段代码,这样当玩家按下fire按钮时,只有他们自己的玩家角色触发光线投射。
现在要对目标施加伤害,剩下的就是对其调用RPC函数。在调试之后。绘制线添加以下内容:
if (Runner.GetPhysicsScene().Raycast(ray.origin,ray.direction, out var hit))
{
if (hit.transform.TryGetComponent<Health>(out var health))
{
health.DealDamageRpc(Damage);
}
}
完成后,将Health和RaycastAttack组件添加到玩家预制件中并创建一个构建。
2.4.2 其他RPC用例
请求State Authority修改网络属性是RPC最常见的用例。
RPC的其他有效用例包括:
1.在玩家之间发送嘲讽信息、表情或其他不稳定的非游戏互动。
⒉启动游戏(对游戏模式、地图进行投票,或者只是向主持人表明玩家已经准备好了)。
在大多数情况下,状态同步本身足以让玩家保持一致,向网络属性添加变化检测器可以处理大多数过渡情况,其中应用程序关心状态的变化,而不仅仅是实际状态本身。文章来源:https://www.toymoban.com/news/detail-859586.html
2.4.3 游戏测试
恭喜你!你已经成功完成了Fusion共享模式基础教程。为了确保一切正常运行,现在是时候玩和测试游戏了。
在Unity中启动构建并进入播放模式,然后按Start Shared Client。右键点击其他玩家将在控制台上显示其生命值减少的信息。
您还可以在Unity的层次检查器中检查网络属性的健康值。
文章来源地址https://www.toymoban.com/news/detail-859586.html
到了这里,关于【Unity Photon Fusion 2】多人联网插件,共享模式基础教程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!