前言
在一些射击类的游戏中,经常会有一些类似炮台的武器,可以上下左右旋转,对目标进行瞄准。又或者是人物身上的枪支,在准心转动的时候,保持着武器朝向准心的方向。本篇简单的进行了效果的实现,如下图:
首先需要了解下有关pitch,yaw,roll的含义
pitch是围绕X轴旋转,也叫做俯仰角(武器上下转动),如图:
yaw是围绕Y轴旋转,也叫偏航角(武器左右转动),如图:
roll是围绕Z轴旋转,也叫翻滚角,如图:
根据需求,我们一般只需要实现武器的pitch和yaw旋转即可,来达到瞄准指定位置的效果。
思路
首先类似于炮台,一般炮身和炮管会分开转动,比如炮身只可左右转动(yaw),炮管上下转动(pitch)。我们需要两个Transform来分别控制旋转,其中炮管要作为炮身子控件。但是枪支那种一般就不会分成两个控件,所以我们把两个Transform同时指向同一控件即可。同时再用一个Transform指向瞄准物,同时我们可以设置一些最大旋转角度限制。
需求1,武器一直瞄准瞄准物,但是当瞄准物在武器身后的时候不进行瞄准处理:可以计算武器到瞄准物和武器正前方直接的夹角,若夹角<90度,即做瞄准操作,否则则不处理。
需求2,yaw旋转:即根据Y轴旋转,eulerAngles.y进行变动,物体呈左右转动之势。因为是左右旋转,所以我们可以忽略武器和瞄准物的Y轴高度差距,使两者在同一高度的情况下(可以使用Vector3.ProjectOnPlane,将武器到瞄准物的向量映射到武器的xz平面上),计算武器forward的向量和武器到瞄准物的向量之间的夹角,若夹角为0则已瞄准,否则则改动eulerAngles.y的值,使Transform.forward变动,从而让夹角为0。(我们可以利用向量的叉乘Vector3.Cross,来判断两个向量之间的左右关系)
需求3,picth旋转:基本和yaw旋转同理,只是改变的是eulerAngles.x的值。
实现
直接上代码,绑定在武器的父节点GameObject上,绑定好对应关联即可
using UnityEngine;
public class AutoRotationGun : MonoBehaviour
{
//最大旋转角度限制
public int MaxPitchAngle = 60;
public int MaxYawAngle = 60;
//旋转速度
public int Speed = 20;
//pitch转动的物体
public Transform PitchTransform;
//yaw转动的物体
public Transform YawTransform;
//瞄准物
public Transform TargetTransform;
Vector3 mPitchTarget, mYawTarget;
float mCurrentPitchAngle, mCurrentYawAngle;
Transform mTransform;
float mPitchAngleOffset, mYawAngleOffset, mAimAngleOffset;
Vector3 mPitchCross, mYawCross;
void Start()
{
mTransform = transform;
}
void Update()
{
if (TargetTransform != null && IsCanAim())
{
//旋转不使用PitchTransform.Rotate(x,y,z),防止z轴的变化
if (PitchTransform == YawTransform)
{
PitchTransform.localEulerAngles = new Vector3(PitchRotation(Speed * Time.deltaTime), YawRotation(Speed * Time.deltaTime), 0);
}
else
{
PitchTransform.localEulerAngles = new Vector3(PitchRotation(Speed * Time.deltaTime), 0, 0);
YawTransform.localEulerAngles = new Vector3(0, YawRotation(Speed * Time.deltaTime), 0);
}
}
#if UNITY_EDITOR
Debug.DrawRay(PitchTransform.position, PitchTransform.forward * 100, Color.red);
#endif
}
bool IsCanAim()
{
//武器到瞄准物的向量与武器的正前面的夹角在90度以内才可瞄准
if (PitchTransform != null && YawTransform != null)
{
//mAimAngleOffset = Mathf.Acos(Vector3.Dot(transform.forward, (TargetTransform.position - PitchTransform.position).normalized)) * Mathf.Rad2Deg;
mAimAngleOffset = Vector3.Angle(mTransform.forward, TargetTransform.position - PitchTransform.position);
if (mAimAngleOffset < 90)
{
return true;
}
}
return false;
}
float PitchRotation(float speed)
{
//当前旋转角度
mCurrentPitchAngle = PitchTransform.localEulerAngles.x;
if (mCurrentPitchAngle > 180)
{
mCurrentPitchAngle = -(360 - mCurrentPitchAngle);
}
//武器到瞄准物的向量,映射到武器的yz屏幕上的法向量
mPitchTarget = Vector3.ProjectOnPlane(TargetTransform.position - PitchTransform.position, PitchTransform.right).normalized;
//计算当前瞄准方向与预期方向的夹角。大于精度则需要pitch转动来调整
mPitchAngleOffset = Vector3.Angle(PitchTransform.forward, mPitchTarget);
if (mPitchAngleOffset == 0) {
return mCurrentPitchAngle;
}
if (mPitchAngleOffset < speed) {
speed = mPitchAngleOffset;
}
//计算两个向量的叉乘,用于判断向量的左右关系
mPitchCross = Vector3.Cross(mPitchTarget, PitchTransform.forward).normalized;
if (mCurrentPitchAngle > -MaxPitchAngle && mPitchCross == PitchTransform.right) {
return mCurrentPitchAngle - speed;
}
else if (mCurrentPitchAngle < MaxPitchAngle && mPitchCross != PitchTransform.right) {
return mCurrentPitchAngle + speed;
}
return mCurrentPitchAngle;
}
float YawRotation(float speed)
{
mCurrentYawAngle = YawTransform.localEulerAngles.y;
if (mCurrentYawAngle > 180)
{
mCurrentYawAngle = -(360 - mCurrentYawAngle);
}
mYawTarget = Vector3.ProjectOnPlane(TargetTransform.position - YawTransform.position, YawTransform.up).normalized;
mYawAngleOffset = Vector3.Angle(YawTransform.forward, mYawTarget);
if (mYawAngleOffset == 0) {
return mCurrentYawAngle;
}
if (mYawAngleOffset < speed) {
speed = mYawAngleOffset;
}
mYawCross = Vector3.Cross(mYawTarget, YawTransform.forward).normalized;
if (mCurrentYawAngle > -MaxYawAngle && mYawCross == YawTransform.up) {
return mCurrentYawAngle - speed;
}
else if (mCurrentYawAngle < MaxYawAngle && mYawCross != YawTransform.up) {
return mCurrentYawAngle + speed;
}
return mCurrentYawAngle;
}
}