目录
7.创建角色
新建一个立方体Player,取消它的外形渲染,然后将位置放在(0,0,0),同时将预制体拉入场景中,成为Player的子物体,同时它的位置也设置为(0,0,0),最后将Mesh Renderer组件移除,这样就看不到网格轮廓
由于坦克物体太大,因此缩放一下
同时将Player的碰撞属性设置一下
因为坦克的一些部件是单独的,而转向过程中需要多个物体同时进行,所以要进行合体
8.在坦克上面创建血条
首先创建一个画布,将画布设置世界空间,即可以被摄像机看到
在画布下新建一个text,然后设置相关属性,表示坦克的名字
角色创建完毕后,将该物体保存成预制体,并在场景中删除
9.为游戏对象添加NetworkTransform
NetworkTransform是一个可以在客户端同步物体对象位置、旋转和缩放到服务器的组件,添加它时必须要加入NetworkIdentity,之后将信息从服务器同步到其余客户端
10.角色移动
在Player物体中添加Player脚本
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Mirror;
public class Player : NetworkBehaviour
{
private Rigidbody rb;
public float MoveSpeed = 8; //水平和垂直的速度
private void Awake()
{
rb = transform.GetComponent<Rigidbody>();
}
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");
moveDir.y = Input.GetAxisRaw("Vertical");
Move(moveDir);
}
}
void Move(Vector2 direction = default(Vector2))
{
if (direction != Vector2.zero) // 如果方向变量不是零向量
{
transform.rotation = Quaternion.LookRotation(new Vector3(direction.x, direction.y, 0)); // 调整方向
//transform.forward是一个变量,它是根据当前方向计算出的方向变量
Vector3 movementDir = transform.forward * MoveSpeed * Time.deltaTime; // 当前方向的单位向量 * 速度 * 时间 = 当前方向的偏移量
rb.MovePosition(rb.position + movementDir);
}
}
}
其中速度*Time.deltaTime保证在不同帧数下角色移动的速度是一样的,也就是与帧数无关。
知识补充:
(1)Update是在每次渲染新的一帧的时候才会调用,也就是说,这个函数的更新频率和设备的性能有关。在性能好的机器上可能fps60,差的可能小些。这会导致同一个游戏在不同的机器上效果不一致,有的快有的慢,因为Update的执行间隔不一样了。
(2)FixedUpdate是在固定的时间间隔执行,不受游戏帧率的影响。
Tick:在处理Rigidbody的时候最好用FixedUpdate
。
11.摄像机相随
新建CamFollow脚本,添加到GameScene场景的主摄像机上
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CamFollow : MonoBehaviour
{
public Transform target;
public float distance = 10f;
public float height = 5f;
void Update()
{
if (!target) return; //没有获得玩家坐标就return
//获取当前摄像机的旋转角度,返回一个四元数
Quaternion currentRotation = Quaternion.Euler(0, transform.eulerAngles.y, 0);
Vector3 pos = target.position; //得到玩家的坐标
// 四元素*(0,0,1)=当前旋转方向上1单位的距离,再*距离=当前旋转方向上的距离(一个Vector)
pos -= currentRotation * Vector3.forward * Mathf.Abs(distance);
pos.y = target.position.y + Mathf.Abs(height); //在y方向上偏移一定的高度,相当于将摄像机的x和z轴定死
transform.position = pos; // 将计算完后的pos赋值给摄像机
transform.LookAt(target); // 当前摄像机指向玩家
// 画蛇添足,对游戏效果没有影响,留坑
//transform.position = target.position - transform.forward * Mathf.Abs(distance);
}
}
由于Player是预制体,所以无法得到target的transform,因此需要在Player脚本中写一段代码,表示被创建时,将它的transform信息传给主摄像机的CamFollow脚本的target中即可,就实现了映射。
【坑点一】重写OnStartLocalPlayer无效,在Awake函数中有效,添加的代码如下:
Camera.main.GetComponent<CamFollow>().target = transform;
【
填坑】
不小心把Player中Networkidentity中弄成了强制隐藏,因为界面上会有Mirror的标志,所以设置成隐藏,没想到这里的隐藏是使它失效,应该设置成默认。
最后添加以下函数有效
public override void OnStartLocalPlayer()
{
Camera.main.GetComponent<CamFollow>().target = transform;
}
如此可以实现摄像机跟随
12.炮台移动
在Player.cs的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位置的坐标 - 玩家的坐标 = 相对玩家的交点坐标
}
RotateTurret(new Vector2(hitPos.x, hitPos.z)); // 因为炮台转向的是在一个平面上进行的,在unity中是x和z轴
在Player.cs添加如下函数:
void RotateTurret(Vector2 direction = default(Vector2)) // 防空出错
{
if (direction == Vector2.zero) return;
// 因为炮台的z轴要指向射线和平面的交点,所以用LookRotation函数,只需要用到forward参数即可(z轴指向forward),
// 最后返回一个表示旋转的四元数,并得到它转换后欧拉角中绕y轴旋转的角度
int newRotation = (int) (Quaternion.LookRotation(new Vector3(direction.x, 0, direction.y)).eulerAngles.y);
//因为旋转,实际上是绕y轴旋转的,所以将y轴旋转的角度封装成四元数赋值给炮台的rotation即可
turret.rotation = Quaternion.Euler(-90, newRotation, 0); //因为炮台本来就旋转了-90,所以这里要不变
}
将炮台加入变量中