因为是第一人称射击游戏,所以主角在视野中是不可见的,但我们依然需要为主角创建碰撞体并控制其移动。
1. 在菜单栏选择【GameObject】→【Create Empty】创建一个空的游戏体,在Inspector窗口将它的Tag设为Player,这就是我们的主角。
2. 在Inspector窗口选择【Add Component】→【Physics】→【Character Controller】为主角添加一个角色控制器组件。通过使用角色控制器组件提供的功能,我们可以实现在控制主角移动的同时与场景的碰撞产生交互,比如在行走时不会穿到墙里面去。在Character Controller组件中调整碰撞体的位置和大小。
3. 在Inspector窗口选择【Add Component】→【Physics】→【Rigidbody】为主角再添加一个Rigidbody组件。取消勾选【Use Gravity】来去除重力模拟。勾选【Is Kinematic】使其不受物理演算的影响,这样我们才可以用脚本来控制主角的移动。
4. 创建脚本Player.cs。
using UnityEngine;
[AddComponentMenu("MyGame/Player")] // 这一句的作用是把该脚本放到【Component】→【MyGame】下的【Player】,便于自行管理脚本
public class Player : MonoBehaviour // 需要由Unity控制生命周期的脚本都要继承MonoBehaviour类,并且要挂在一个游戏体上
{
public int life = 5;
private new Transform transform; // 此处使用new关键字隐藏父类的同名成员
private CharacterController controller;
private float speed = 3.0f;
private float gravity = 2.0f;
///
/// 初始化,获取Transform和Character Controller组件
///
void Start()
{
transform = GetComponent();
controller = GetComponent();
}
///
/// 控制主角行动,如果生命为0则什么也不做
///
void Update()
{
if (life <= 0)
{
return;
}
Control();
}
///
/// 在Unity编辑器中为主角显示一个图标
///
void OnDrawGizmos()
{
Gizmos.DrawIcon(GetComponent().position, "Spawn.tif");
}
///
/// 控制主角的重力运动和前后左右移动
///
private void Control()
{
float x = 0, y = 0, z = 0;
// 重力运动
y -= gravity * Time.deltaTime;
// 前后移动
if (Input.GetKey(KeyCode.W))
{
z += speed * Time.deltaTime;
}
else if (Input.GetKey(KeyCode.S))
{
z -= speed * Time.deltaTime;
}
// 左右移动
if (Input.GetKey(KeyCode.A))
{
x -= speed * Time.deltaTime;
}
else if (Input.GetKey(KeyCode.D))
{
x += speed * Time.deltaTime;
}
// 使用Character Controller而不是Transform提供的Move方法
// 因为Character Controller提供的Move方法会自动进行碰撞检测
controller.Move(transform.TransformDirection(new Vector3(x, y, z)));
}
}
Unity会自行管理脚本的生命周期,因而不允许脚本中直接调用构造函数,所有的初始化操作在Start方法中进行,在程序运行时每一帧都会调用Update方法。
因为在Update方法中每帧都去调用游戏体的组件会造成一定的效率问题,所以在初始化时就调用一次所需的组件并将其保存起来。
此处在Start方法中获取了Character Controller方法,在Control方法中通过键盘W、S、A、D控制获得主角前后左右的移动距离,最后使用Character Controller提供的Move方法移动主角,在移动的同时Character Controller会自动计算游戏体与场景之间的碰撞。
Input类是Unity对各种输入的包装类,包括了几乎所有的键盘、鼠标和触控操作函数。
Time类是Unity对游戏时间的包装类,deltaTime表示每帧经过的时间。
Vector3类是Unity对三维向量的包装类。
transform.TransformDirection方法将以当前游戏体为参考的坐标系内的向量转换为游戏世界坐标系内的向量。
5. 选中Player游戏体,在Inspector窗口中选择【Add Component】→【MyGame】→【Player】将Player.cs脚本指定给主角的游戏体。在Inspector窗口找到Script组件,设置Life属性为5(脚本中所有的公共域的初始值都可以在Unity编辑器中对应游戏体的Script组件中设置)。此时运行游戏,就可以按键盘W、S、A、D键来控制主角的前后左右移动了。但由于摄像机是固定的,所以难以察觉到主角的移动。
6. 修改Player.cs脚本,使摄像机能伴随主角的移动而移动。
首先添加用于控制摄像机的属性:
private Transform cameraTransform; // 摄像机的Transform组件
private Vector3 cameraRotation; // 摄像机旋转角度
private float cameraHeight = 1.7f; // 摄像机高度(即主角的眼睛高度)
修改Start方法,初始化相机的位置和旋转角度,并锁定鼠标:
void Start()
{
transform = GetComponent();
controller = GetComponent();
// 获取摄像机
cameraTransform = Camera.main.GetComponent();
// 设置摄像机初始位置
Vector3 position = transform.position;
position.y += cameraHeight;
cameraTransform.position = position;
// 设置摄像机的初始旋转方向
cameraTransform.rotation = transform.rotation;
cameraRotation = cameraTransform.eulerAngles;
// 锁定鼠标
Cursor.lockState = CursorLockMode.Locked;
}
在Control方法中移动和旋转摄像机使其与主角的位置和旋转角度保持一致:
private void Control()
{
// 获取鼠标移动距离
float rh = Input.GetAxis("Mouse X");
float rv = Input.GetAxis("Mouse Y");
// 旋转摄像机
cameraRotation.x -= rv;
cameraRotation.y += rh;
cameraTransform.eulerAngles = cameraRotation;
// 使主角的面向方向与摄像机一致
Vector3 rotation = cameraTransform.eulerAngles;
rotation.x = 0;
rotation.z = 0;
transform.eulerAngles = rotation;
// 控制主角运动,代码略……
// 使摄像机的位置与主角一致
Vector3 position = transform.position;
position.y += cameraHeight;
cameraTransform.position = position;
}
通过控制鼠标来旋转摄像机的方向,使主角跟随摄像机的方向绕Y轴旋转。在移动主角的时候,又使摄像机跟随主角运动。
7. 接下来,我们把武器绑定到摄像机上,使其跟随主角移动。
选中Main Camera,在Inspector窗口中将摄像机的位置和旋转角度都设为0,将【Clipping Planes】→【Near】值设为0.1,使其可以看到更近处的物体(Clipping Plane是摄像机的剪切面,相当于视野的近端和远端)。
在Project窗口Assets/Prefabs文件夹下找到M16.Prefeb,这是一个简单的枪支模型。将它拖动到Hierarchy窗口中的Main Camera上使其成为摄像机的子物体。选择M16的子物体weapon,调整它的位置和角度,在Camera Preview窗口中预览效果直到满意为止。
8. 现在运行游戏,效果如下图:
关于主角的配置到此就基本完成了,现在我们已经可以端着枪在地图中自由行走了。下一篇将会添加敌人,敌人会自动寻路并试图攻击主角哦,敬请期待。