一,什么是FPS游戏
第一人称射击类游戏,FPS(First-person shooting game), 严格来说第一人称射击游戏属于ACT类游戏的一个分支,但和RTS类游戏一样,由于其在世界上的迅速风靡,使之发展成了一个单独的类型。
FPS(First-person Shooting game)第一人称视角射击游戏顾名思义就是以玩家的主观视角来进行射击游戏。玩家们不再像别的游戏一样操纵屏幕中的虚拟人物来进行游戏,而是身临其境的体验游戏带来的视觉冲击,这就大大增强了游戏的主动性和真实感。早期第一人称类游戏所带给玩家的一般都是的屏幕光线的刺激,简单快捷的游戏节奏。随着游戏硬件的逐步完善,以及各种游戏的不断结合。第一人称射击类游戏提供了更加丰富的剧情以及精美的画面和生动的音效。
二,功能实现思路与过程
(1)新建角色
新建项目,载入或者新建角色模型与枪械模型,另外,由于项目视角是第一人称,所以建议将枪械设为摄像头的子物体,并为了以后的对战功能的开发,同样建议将完成之后的角色模型集合以预制体的方式保存,完成之后的效果图如下
(2)实现移动功能的思路
要实现键盘输入控制移动功能,首先必须对键盘的输入进行实时获取,获取的方法,是在脚本的Update()中进行获取(PS:Update(): 当游戏正在运行,同时脚本是可用的,这个方法会在每帧刷新时调用),获取用户的输入,得到用户移动矢量的模(移动矢量的模即移动距离)。
/**获取用户移动矢量的模的代码如下**/
float _xMov = Input.GetAxisRaw("Horizontal");//获取水平方向输入
float _zMov = Input.GetAxisRaw("Vertical");//获取垂直方向输入
再乘以用户移动矢量的标准矢量(PS:transform.right,transform.forward本质为标准矢量,且根据矢量的概念,一个非零矢量除以它的模,可得所需标准矢量。从而反推移动矢量的标准矢量乘以移动矢量的模可得出移动矢量),获取移动矢量,考虑到玩家的移动受垂直与水平两个移动矢量的影响,玩家运动的简易模型如下图
根据矢量三角形法则可知,玩家移动矢量等于水平矢量+垂直矢量(法则简略图如下)
/**水平矢量,垂直矢量与玩家移动矢量的计算代码如下**/
Vector3 _moveHoruzontal = transform.right * _xMov;//水平移动矢量=水平方向标准矢量*水平方向移动矢量的模(键盘水平方向输入)
Vector3 _moveVertical = transform.forward * _zMov;//垂直移动矢量=垂直方向标准矢量*垂直方向移动矢量的模(键盘垂直方向输入)
Vector3 _velocity = (_moveHoruzontal + _moveVertical).normalized * speed;//将玩家移动矢量归一化为标准矢量,在乘以
然后将玩家移动矢量归一化(normalized)为标准矢量,在将归一化的标准矢量乘以速度矢量的模,获取玩家速度矢量,最后将速度矢量乘以时间加上当前玩家刚体的位置,得出玩家最后应该移动到的位置。
(3)实现旋转功能的思路
同理在脚本的Update()中进行获取用户的鼠标输入,将获取的鼠标X,Y轴移动角度乘以用户视野旋转速度,得到摄像头与玩家刚体旋转角度
(4)实现推进上升功能的思路
同理在脚本的Update()中进行获取用户的键盘输入,当监听到用户输入空格的时候,让角色刚体像正上方移动。
(5)具体代码
PlayerMoter.cs(代码如下)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMoter : MonoBehaviour
{
[SerializeField]
private Camera cam;//摄像头
private Vector3 velocity = Vector3.zero;
private Vector3 rotation = Vector3.zero;
private float cameraRotationX = 0f;
private float currentCamerRotationX = 0f;
private Vector3 thrusterForce = Vector3.zero;
[SerializeField]
private float cameraRotationLimit = 85f;
private Rigidbody rb;
void Start()
{
rb = GetComponent<Rigidbody>();//获取刚体
}
public void Move(Vector3 _velocity)//移动
{
velocity = _velocity;
}
public void Rotate(Vector3 _rotation)//旋转
{
rotation = _rotation;
}
// Update is called once per frame
void Update()
{
}
private void FixedUpdate()
{
PerformMovement();
PeformRotation();
}
public void RotarCamera(float _cameraRotation)//摄像头旋转
{
cameraRotationX = _cameraRotation;
}
public void ApplyThruster(Vector3 _thrusterForce)//应用推进力
{
thrusterForce = _thrusterForce;
}
void PerformMovement()//移动
{
if(velocity!=Vector3.zero)
{
rb.MovePosition(rb.position + velocity * Time.fixedDeltaTime);//
}
if(thrusterForce!=Vector3.zero)
{
rb.AddForce(thrusterForce * Time.fixedDeltaTime,ForceMode.Acceleration);
}
}
void PeformRotation()//旋转
{
rb.MoveRotation(rb.rotation * Quaternion.Euler(rotation));
if(cam!=null)
{
currentCamerRotationX -= cameraRotationX;
currentCamerRotationX = Mathf.Clamp(currentCamerRotationX,-cameraRotationLimit,cameraRotationLimit);
cam.transform.localEulerAngles = new Vector3(currentCamerRotationX, 0f, 0f);
}
}
}
PlayerControl.cs(代码如下)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(ConfigurableJoint))]
[RequireComponent(typeof(PlayerMoter))]
public class PlayerControl : MonoBehaviour
{
[SerializeField]
private float speed = 5f;
private PlayerMoter moter;
[SerializeField]
private float lookSensitivity = 3f;//视角旋转速度
[SerializeField]
private float thrustserForce = 1000f;//推进速度
[Header("Spring setting")]
[SerializeField]
private JointDriveMode jointMode=JointDriveMode.Position;
[SerializeField]
private float jointSpring = 20f;
[SerializeField]
private float jointMaxForce = 40f;
private PlayerMoter motor;
private ConfigurableJoint joint;
// Start is called before the first frame update
void Start()
{
moter = GetComponent<PlayerMoter>();
joint = GetComponent<ConfigurableJoint>();
}
// Update is called once per frame
void Update()
{
float _xMov = Input.GetAxisRaw("Horizontal");//获取水平方向输入
float _zMov = Input.GetAxisRaw("Vertical");//获取垂直方向输入
//transform.right,transform.forward本质为标准矢量,即有方向且模为1的矢量
Vector3 _moveHoruzontal = transform.right * _xMov;//水平移动矢量=水平方向标准矢量*水平方向移动矢量的模(键盘水平方向输入)
Vector3 _moveVertical = transform.forward * _zMov;//垂直移动矢量=垂直方向标准矢量*垂直方向移动矢量的模(键盘垂直方向输入)
Vector3 _velocity = (_moveHoruzontal + _moveVertical).normalized * speed;//将玩家移动矢量归一化为标准矢量,在乘以
moter.Move(_velocity);
float _yRot = Input.GetAxisRaw("Mouse X");//获取鼠标X轴移动
Vector3 _rotation = new Vector3(0f, _yRot, 0f) * lookSensitivity;
moter.Rotate(_rotation);
float _xRot = Input.GetAxis("Mouse Y");//获取鼠标Y轴移动
float _cameraRotation = _xRot * lookSensitivity;
moter.RotarCamera(_cameraRotation);
Vector3 _thrusterForce = Vector3.zero;
if(Input.GetButton("Jump"))
{
_thrusterForce = Vector3.up * thrustserForce;//
SetJointSettings(0f);
}
else
{
SetJointSettings(jointSpring);
}
moter.ApplyThruster(_thrusterForce);
}
private void SetJointSettings(float _jointSpring)
{
joint.yDrive = new JointDrive {
mode = jointMode,
positionSpring= _jointSpring,
maximumForce=jointMaxForce
};
}
}
PlayerScript.cs(代码如下)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerScript : MonoBehaviour
{
private const int Player_Up = 0;
private const int Player_Right = 1;
private const int Player_Down = 2;
private const int Player_Left = 3;
private int state = 0;
public int moveSpeed = 2;
void Awake()
{
state = Player_Up;
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
float KeyVertical = Input.GetAxis("Vertical");
float KeyHorizontal = Input.GetAxis("Horizontal");
if(KeyVertical==-1)
{
setPlayerState(Player_Down);
}
else if(KeyVertical==1)
{
setPlayerState(Player_Up);
}
if(KeyHorizontal==1)
{
setPlayerState(Player_Right);
}
else if(KeyHorizontal==-1)
{
setPlayerState(Player_Left);
}
}
void setPlayerState(int NewState)
{
int rotateValue = (NewState - state) * 90;
Vector3 transformValue = new Vector3();
switch(NewState)
{
case Player_Up:
transformValue = Vector3.forward * Time.deltaTime;
break;
case Player_Down:
transformValue = (-Vector3.forward) * Time.deltaTime;
break;
case Player_Left:
transformValue = Vector3.left * Time.deltaTime;
break;
case Player_Right:
transformValue = (-Vector3.left) * Time.deltaTime;
break;
}
transform.Rotate(Vector3.up, rotateValue);
transform.Translate(transformValue * moveSpeed, Space.World);
state = NewState;
}
}
(5)脚本的使用方法
将PlayerScript.cs与PlayerControl.cs绑定在玩家角色对象上,并给PlayerControl脚本绑定摄像头(如下图)
(6)最终效果
【1】参考教程 https://www.bilibili.com/video/BV1Hz4y1R7ZY