🟥 前言
首先我们要搞懂,我们要同步什么?
- Transform
- Animator
- 代码是否属于当前用户(每个玩家控制的角色都有相同脚本,输入控制角色的命令,要确定哪个角色属于当前玩家,只执行那个角色的代码,比如摄像头控制代码、角色运动控制代码)
- 某玩家发送给其他玩家的消息(其他玩家减血、胜利等等)
Object Synchronization,即对象同步。Object Synchronization适合频繁同步的属性。
使用 PUN,我们可以很容易地使某些游戏对象“网络感知”。
- PhotonView 组件,可实现对象位置、旋转和其他值的远程复制,完成同步。
- 一些脚本实现 OnPhotonSerializeView() 并成为PhotonView 的观察组件。现在,这个脚本也可被网络同步。
🟧 同步设置
1️⃣ 基础信息
该组件的信息同步,通过 PhotonView 组件完成。
Owner:物体的拥有者
-0Fixed:固定的
-0Takeover:可被接管的
-0Request:可被请求接管
ViewID:运行后显示当前物体是拥有者的第几个物体。最多是999。若超过999,则只是后三位
Observeoption:观察设置,添加上同步组件后,该值默认是UnreliableOnChange(在每次改变时通过“不可靠”传输同步)
2️⃣ Transform与Animator
ObservedComponents:同步组件。我们可以往里面添加该物体的Transform、Animator等,同步这些选项。
添加上这些属性之后,会自动再添加属性。
添加Transform会自动添加PhotoTransformView,勾选需要同步的选项即可
添加Animator会自动添加PhotonAnimatorView中:
- Disable为该属性不同步
- Discrete为每秒同步10次
- Countinuous为该属性每帧同步一次。但比较占用网速。
Animator中Trigger属性要放在栈的最后,否则会可能没被执行(不是很理解怎样放在栈最后。。)
3️⃣ 代码是否属于当前用户
场景中有好几个人控制的角色,都挂有某相同脚本,按下按键,如不判断,这些角色不知道谁该执行当前用户输入的命令。因此要进行判断:
//需继承MonoBehaviourPun
bool isMine = photonView.IsMine;
摄像机设置
在网络中,要先判断摄像机是否属于当前用户,再决定是否跟随。因此PlayerManager增加代码如下。并取消CameraWork的FollowOnStart。
我们之前已经做完了发射激光的判断。现在我们已实现了本机的操作,后面我们要将本机的状态同步到互联网上,同步其他电脑中自己人物的状态。
void Start()
{
Skode_CameraWork _cameraWork = GetComponent<Skode_CameraWork>();
if (_cameraWork != null && photonView.IsMine)
{
_cameraWork.OnStartFollowing();
}
}
4️⃣ 当前是否为离线模式
false为离线。使用教程:传送门
bool isConnected = PhotonNetwork.IsConnected;
5️⃣ 消息的发送与数据同步
目标:
- 我们要实现将本机自己控制的人物的状态,同步到其他客户端中。实现各个客户端中自己人物动作一致
- 实现调用其他玩家的方法等操作
流程:
a、继承并实现
要同步的脚本继承接口 IPunObservable 并实现。
实现OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)方法,用它接收发送数据。
注:若不继承不会报错,但代码不会运行。
b、书写方式
脚本判断自己是发送方还是接收方
写法有规范,要怎样发,怎样接收。
c、Inspector面板配置
要把要同步的脚本(发送信息、接收信息的脚本)赋值进同步组件ObservedComponents
PlayerManager 最新代码:
using UnityEngine;
using Photon.Pun;
public class PlayerManager : MonoBehaviourPunCallbacks, IPunObservable
{
#region Parameters
public GameObject beams;
public float Health = 1f;
//当用户开火时,为True
bool IsFiring;
#endregion
#region Mono CallBacks
void Awake()
{
beams.SetActive(false);
}
void Start()
{
Skode_CameraWork _cameraWork = GetComponent<Skode_CameraWork>();
if (_cameraWork != null && photonView.IsMine)
{
_cameraWork.OnStartFollowing();
}
}
void Update()
{
if (Health <= 0f)
GameManager.ins.Skode_LeaveRoom();
if (PhotonNetwork.IsConnected == true && photonView.IsMine)
ProcessInputs();
if (IsFiring != beams.activeSelf)
beams.SetActive(IsFiring);
}
#endregion
#region IPunObservable CallBacks
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
if (stream.IsWriting)
{
// 我们拥有这个角色:把我们的数据发送给其他人
stream.SendNext(IsFiring);
stream.SendNext(Health);
}
else
{
// 网络角色,接收数据
IsFiring = (bool)stream.ReceiveNext();
Health = (float)stream.ReceiveNext();
}
}
#endregion
#region Private Methods
void ProcessInputs()
{
//鼠标左键
if (Input.GetButtonDown("Fire1"))
IsFiring = true;
if (Input.GetButtonUp("Fire1"))
IsFiring = false;
}
void OnTriggerEnter(Collider other)
{
if (!photonView.IsMine && !other.CompareTag("beam"))
return;
Health -= 0.1f;
}
void OnTriggerStay(Collider other)
{
if (!photonView.IsMine && !other.CompareTag("beam"))
return;
//乘以增量时间,防止因为帧率FPS不同,扣血不同(举例:不乘,每帧执行一次扣血,卡的人比流畅的人扣血少)
Health -= 0.1f * Time.deltaTime;
}
#endregion
}
大家还有什么问题,欢迎在下方留言!
如果你有 技术的问题 或 项目开发
都可以加下方联系方式
和我聊一聊你的故事🧡