前言
同笔者之前发布的文章一样,此文章案例是笔者在阅读《Unity3D 脚本编程和游戏开发》中,跟随书籍进行的实例练习。
此案例虽然代码简单,但是需要读者初步了解3D游戏中的数学知识(包括但不限于向量、四元数)
案例对应下载链接: 点击下载
效果展示
功能实现
第一人称射击游戏角色控制器的实现主要有两个功能:
- 角色移动控制
- 鼠标控制角色旋转
当然其中还包括玩家物体的创建,我们可以随意创建玩家物体,只要保证将Main Camera
物体作为玩家的子物体即可。
在效果中视角右下方的“枪”是几个简单几何图形拼凑的,读者可以自行拼凑,只需要将物体放置在玩家的子物体中即可。
角色移动控制
我们实现的思路为:获取角色面朝方向(transform.forward)和角色的右边方向(transform.right),获取玩家的输入,将玩家的输入与角色的方向“结合”(相乘)获取到角色的移动向量。之后控制角色向移动方向移动即可。
代码:
private void Move()
{
//获取玩家输入
float horizontalInput = Input.GetAxisRaw("Horizontal");
float verticcalInput = Input.GetAxisRaw("Vertical");
//获取角色正前方向以及右方向
Vector3 forwardVec = new Vector3(transform.forward.x, 0, transform.forward.z);
Vector3 rightVec = transform.right;
//计算得出移动方向
Vector3 moveDirection = verticcalInput * forwardVec + horizontalInput * rightVec;
//将移动方向归一化
moveDirection = moveDirection.normalized;
//将角色向目标方向移动
transform.position += moveDirection * moveSpeed * Time.deltaTime;
}
鼠标控制视角
在完成这个功能之前,我们需要明白第一人称视角下的角色在沿X轴旋转和沿Y轴旋转时旋转的坐标轴的不同:当玩家视角水平旋转时(沿Y轴旋转),玩家视角应当沿世界坐标轴的Y轴旋转;当玩家视角垂直旋转时(沿X轴旋转),玩家视角应当沿本地坐标轴旋转。
那么知道这些我们只需要实现以下几个步骤就可以很简单的实现视角旋转的功能:
- 获取鼠标的X、Y轴输入
- 根据鼠标的X、Y输入获取对应沿Y轴、沿X轴的旋转角度(X轴输入对应沿Y轴旋转,Y轴输入对应沿X轴旋转)
- 沿Y轴旋转使用世界坐标系旋转,沿X轴旋转使用本地坐标系旋转
对应代码:
private void MouseLook()
{
//1.获取X、Y输入
float mouseX = Input.GetAxis("Mouse X");
float mouseY = -Input.GetAxis("Mouse Y");
//2.根据鼠标的X、Y输入获取对应沿Y轴、沿X轴的旋转角度(X轴输入对应沿Y轴旋转,Y轴输入对应沿X轴旋转)
Quaternion quaternionX = Quaternion.Euler(0, mouseX, 0);
Quaternion quaternionY = Quaternion.Euler(mouseY, 0, 0);
//3.沿Y轴旋转使用世界坐标系旋转,沿X轴旋转使用本地坐标系旋转
transform.rotation = quaternionX * transform.rotation;
transform.rotation = transform.rotation * quaternionY;
}
但是,当我们使用这个方法进行视角移动时,会发现:有时当我们的视角向上移动到90°以上时,视角会“倒过去”,所以我们就需要限制视角的移动角度:
private void MouseLook()
{
float mouseX = Input.GetAxis("Mouse X");
float mouseY = -Input.GetAxis("Mouse Y");
//鼠标X轴的移动表示鼠标沿Y轴旋转
Quaternion quaternionX = Quaternion.Euler(0, mouseX, 0);
//鼠标Y轴的移动表示鼠标沿X轴旋转
Quaternion quaternionY = Quaternion.Euler(mouseY, 0, 0);
//镜头水平旋转时,沿世界坐标系旋转
transform.rotation = quaternionX * transform.rotation;
//镜头垂直旋转,沿本地坐标轴旋转
transform.rotation = transform.rotation * quaternionY;
//限制俯仰角度
float angle = transform.eulerAngles.x;
//使用欧拉角时,防止出现-1°到359°混乱的情况
if(angle > 180)
{
angle -= 360;
}
if(angle < -180)
{
angle += 360;
}
//限制抬头、低头角度
if(angle > 80)
{
transform.eulerAngles = new Vector3(80, transform.eulerAngles.y, 0);
}
if(angle < -80)
{
transform.eulerAngles = new Vector3(-80, transform.eulerAngles.y, 0);
}
}
完整代码
在完整代码中,笔者加了一个控制手电筒开关的小功能,原理非常简单:在玩家物体下创建一个挂载有Spot Light
组件的物体,在开始时获取物体,当输入F时将物体激活/关闭。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FPSPlayerController : MonoBehaviour
{
public float moveSpeed = 50f;
public Light flashLight;
// Start is called before the first frame update
void Start()
{
//隐藏鼠标
Cursor.visible = false;
//锁定鼠标指针到屏幕中央
Cursor.lockState = CursorLockMode.Locked;
if(flashLight == null)
{
flashLight = GetComponentInChildren<Light>();
}
}
// Update is called once per frame
void Update()
{
Move();
MouseLook();
//控制手电筒开关
if (Input.GetKeyDown(KeyCode.F))
{
flashLight.enabled = !flashLight.enabled;
}
}
private void MouseLook()
{
float mouseX = Input.GetAxis("Mouse X");
float mouseY = -Input.GetAxis("Mouse Y");
//鼠标X轴的移动表示鼠标沿Y轴旋转
Quaternion quaternionX = Quaternion.Euler(0, mouseX, 0);
//鼠标Y轴的移动表示鼠标沿X轴旋转
Quaternion quaternionY = Quaternion.Euler(mouseY, 0, 0);
//镜头水平旋转时,沿世界坐标系旋转
transform.rotation = quaternionX * transform.rotation;
//镜头垂直旋转,沿本地坐标轴旋转
transform.rotation = transform.rotation * quaternionY;
//限制俯仰角度
float angle = transform.eulerAngles.x;
//使用欧拉角时,防止出现-1°到359°混乱的情况
if(angle > 180)
{
angle -= 360;
}
if(angle < -180)
{
angle += 360;
}
//限制抬头、低头角度
if(angle > 80)
{
transform.eulerAngles = new Vector3(80, transform.eulerAngles.y, 0);
}
if(angle < -80)
{
transform.eulerAngles = new Vector3(-80, transform.eulerAngles.y, 0);
}
}
private void Move()
{
float horizontalInput = Input.GetAxisRaw("Horizontal");
float verticcalInput = Input.GetAxisRaw("Vertical");
//Y轴为0,确保移动方向都在水平上
Vector3 forwardVec = new Vector3(transform.forward.x, 0, transform.forward.z);
//角色右方向与抬头无关,可以直接调用
Vector3 rightVec = transform.right;
//获取移动方向
Vector3 moveDirection = verticcalInput * forwardVec + horizontalInput * rightVec;
moveDirection = moveDirection.normalized;
transform.position += moveDirection * moveSpeed * Time.deltaTime;
}
}