13.坦克和炮台的同步
【坑点二】Unet版本的hook函数在Mirror中是不一样的。
【填坑】
以前是[SyncVar(hook="onFunction")],且onFunction函数的参数为一个;
现在是[SyncVar(hook=nameof(onFunction))],其中onFunction的参数必须2个,第一个是旧值,第二个是新值。如onFunction(size oldValue, size newValue)。
由于NetworkTransform实现的是Player整体位置的同步,而炮台的旋转无法实现同步,所以需要另外重写,player.cs更新如下代码实现炮台移动同步
public class Player : NetworkBehaviour
{
……
//将以下变量在面板中隐藏
[HideInInspector]
//当turretRotation变量发生改变时调用OnTurretRotation函数
[SyncVar(hook=nameof(RotateTurret))] public int turretRotation;
……
private void FixedUpdate()
{
……
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); // 返回从摄像机到当前鼠标点的一个射线
Plane plane = new Plane(Vector3.up, Vector3.up); // 新建一个平面,因为玩家对象在(0,0,0),所以参数中的法线和经过的点都是(0,1,0)
float distance = 0f;
Vector3 hitPos = Vector3.zero;
// 判断平面和射线是否相交,并将射线起点到交点的距离返回给distance
if (plane.Raycast(ray, out distance))
{
hitPos = ray.GetPoint(distance) - transform.position; // 获得射线在distance位置的坐标 - 玩家的坐标 = 相对玩家的交点坐标
}
Vector2 direction = new Vector2(hitPos.x, hitPos.z);
if (direction != Vector2.zero)
{
// 因为炮台的z轴要指向射线和平面的交点,所以用LookRotation函数,只需要用到forward参数即可(z轴指向forward),
// 最后返回一个表示旋转的四元数,并得到它转换后欧拉角中绕y轴旋转的角度
turretRotation = (int) (Quaternion.LookRotation(new Vector3(direction.x, 0, direction.y)).eulerAngles.y);
turret.rotation = Quaternion.Euler(-90, turretRotation, 0); // 客户端实现转向
CmdRotateTurret(turretRotation); // 同步到服务器
}
……
}
……
[Command]
void CmdRotateTurret(int value)
{
turretRotation = value; //在服务器上修改该值
}
void RotateTurret(int oldValue, int newValue) // 得到在服务器上的值,然后运行函数,把结果同步到客户端
{
//因为炮台旋转,实际上是绕y轴旋转的,所以将y轴旋转的角度封装成四元数赋值给炮台的rotation即可
turret.rotation = Quaternion.Euler(-90, newValue, 0); //因为炮台本来就旋转了90,所以这里要不变
}
……
}
测试
![](https://img-blog.csdnimg.cn/b5545234a7484097aa0f99ea2c6bde59.gif)
而坦克移动的同步,由于没有把NetworkTransform的配置读明白,就自己写了实现了,在player.cs更新如下代码实现坦克移动同步
public class Player : NetworkBehaviour
{
……
private void FixedUpdate()
{
if (!isLocalPlayer) return; // 如果不是本地角色,就跳过
Vector2 moveDir;
// 获取键盘输入的水平和纵向的值,分别是1和-1,代表两个方向,并传给moveDir这个方向变量
if (Input.GetAxisRaw("Horizontal") == 0 && Input.GetAxisRaw("Vertical") == 0)
{
moveDir.x = 0;
moveDir.y = 0;
}
else
{
moveDir.x = Input.GetAxisRaw("Horizontal"); //左返回-1,右返回1
moveDir.y = Input.GetAxisRaw("Vertical"); //下返回-1,上返回1
CmdMove(moveDir,camFollow.camTransform.eulerAngles.y); // 将客户端的移动方向和摄像机方向传给服务器端,以实现同步
}
……
}
[Command] //让服务端运行的函数,函数开头加Cmd,运行结束后同步到所有客户端
void CmdMove(Vector2 direction, float cam_y)
{
if (direction != Vector2.zero) // 如果方向变量不是零向量
{
transform.rotation = Quaternion.LookRotation(new Vector3(direction.x, 0, direction.y))*Quaternion.Euler(0,cam_y,0); // 调整方向,因为u3d的平面是x和z轴
//transform.forward是一个变量,它是根据当前方向计算出的方向变量
Vector3 movementDir = transform.forward * MoveSpeed * Time.deltaTime; // 当前方向的单位向量 * 速度 * 时间 = 当前方向的偏移量
rb.MovePosition(rb.position + movementDir);
}
}
……
}
测试
![](https://img-blog.csdnimg.cn/d91bf1e266044227a45493a96c03e7d4.gif)
【坑点三】
客户端
运行坦克时的速度比
服务端
运行坦克时慢,难道是网络传输延迟,但是局域网的延迟应该忽略不计。
【填坑】
当读了NetworkTransform的文档发现,只需要一步就可以实现上述操作
![](https://img-blog.csdnimg.cn/f5a3159d8ce34409b8265d054446e542.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAfkxvbWlzc34=,size_9,color_FFFFFF,t_70,g_se,x_16)
这样在代码中就不用写[command]了,代码如下
public class Player : NetworkBehaviour
{
……
private void FixedUpdate()
{
if (!isLocalPlayer) return; // 如果不是本地角色,就跳过
Vector2 moveDir;
// 获取键盘输入的水平和纵向的值,分别是1和-1,代表两个方向,并传给moveDir这个方向变量
if (Input.GetAxisRaw("Horizontal") == 0 && Input.GetAxisRaw("Vertical") == 0)
{
moveDir.x = 0;
moveDir.y = 0;
}
else
{
moveDir.x = Input.GetAxisRaw("Horizontal"); //左返回-1,右返回1
moveDir.y = Input.GetAxisRaw("Vertical"); //下返回-1,上返回1
if (moveDir != Vector2.zero) // 如果方向变量不是零向量
{
// 调整方向,因为u3d的平面是x和z轴,然后再乘上摄像机方向的四元数
transform.rotation = Quaternion.LookRotation(new Vector3(moveDir.x, 0, moveDir.y))
//transform.forward是一个变量,它是根据当前方向计算出的方向变量
Vector3 movementDir = transform.forward * MoveSpeed * Time.deltaTime; // 当前方向的单位向量 * 速度 * 时间 = 当前方向的偏移量
rb.MovePosition(rb.position + movementDir);
}
}
……
}
……
}
测试(可以看到这样的配置写法,客户端坦克的运行速度就和服务器一致了)
![](https://img-blog.csdnimg.cn/e7f47c1cf12c43cb8e3b81da7bb69d8e.gif)
14.右键视角的切换
在CamFollow.cs添加如下代码:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CamFollow : MonoBehaviour
{
……
private Vector3 m_camRot;
[HideInInspector] public Transform camTransform;
private void Awake()
{
camTransform = transform;
}
void Update()
{
……
if (Input.GetMouseButton(1))
{
m_camRot = camTransform.eulerAngles;
float rh = Input.GetAxis("Mouse X"); // 返回鼠标在x轴上的偏移量,左负
m_camRot.y += rh * 4; // 乘上倍数,增加右键切换速度
// 暂不支持纵向切换,因为视角会跑到地图外,留坑
// float rv = Input.GetAxis("Mouse Y"); // 返回鼠标在y轴上的偏移量,下负
// m_camRot.x -= rv * 4;
camTransform.eulerAngles = m_camRot;
}
}
}
在Player.cs添加如下代码:
public class Player : NetworkBehaviour
{
……
private void FixedUpdate()
{
……
else
{
moveDir.x = Input.GetAxisRaw("Horizontal"); //左返回-1,右返回1
moveDir.y = Input.GetAxisRaw("Vertical"); //下返回-1,上返回1
if (moveDir != Vector2.zero) // 如果方向变量不是零向量
{
// 调整方向,因为u3d的平面是x和z轴,然后再乘上摄像机方向的四元数
transform.rotation = Quaternion.LookRotation(new Vector3(moveDir.x, 0, moveDir.y))*Quaternion.Euler(0,camFollow.camTransform.eulerAngles.y,0);
//transform.forward是一个变量,它是根据当前方向计算出的方向变量
Vector3 movementDir = transform.forward * MoveSpeed * Time.deltaTime; // 当前方向的单位向量 * 速度 * 时间 = 当前方向的偏移量
rb.MovePosition(rb.position + movementDir);
}
}
……
}
……
}
通过鼠标右键的监听,改变摄像机的旋转角度后,坦克的方向没有变,所以通过如下代码可以让坦克的方向随摄像机一起改变:
transform.rotation = Quaternion.LookRotation(new Vector3(direction.x, 0, direction.y))*Quaternion.Euler(0,camFollow.camTransform.eulerAngles.y,0);
主要是在之前代码的后面乘上了Quaternion.Euler(0,camFollow.camTransform.eulerAngles.y,0),得到摄像机在y轴上旋转的角度,然后再把坦克在y轴上旋转该角度。
测试
![](https://img-blog.csdnimg.cn/e3cd1ee35a924cc493d6e643b14140cb.gif)