问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Unity 第三人称射击游戏视角控制与武器瞄准

创作时间:
作者:
@小白创作中心

Unity 第三人称射击游戏视角控制与武器瞄准

引用
CSDN
1.
https://blog.csdn.net/realfancy/article/details/104253615

本文将详细介绍如何在Unity中实现第三人称射击游戏的视角控制和武器瞄准功能。通过使用Final IK插件和自定义脚本,我们可以实现平滑的摄像机跟随、自由视角旋转、根据视角角度调整摄像机距离、瞄准时的摄像机位置调整以及遮挡检测等功能。

效果演示

项目资源

技术选型

Unity自带的IK功能实现武器瞄准效果较为复杂,因此推荐使用第三方插件Final IK。Final IK提供了强大的反向动力学解决方案,能够轻松实现武器指向目标的功能。

Final IK插件

武器瞄准实现

武器瞄准的核心思路是通过射线检测来确定瞄准目标,并使用Final IK的AimIK组件来控制武器指向该目标。

// 设置AimIK的目标位置
aimIK.solver.target.position = targetPos;

射线检测需要注意屏蔽玩家自身的碰撞体干扰,以避免误判。

视角控制实现

视角控制部分实现了以下功能:

  1. 摄像机平滑跟随玩家
  2. 摄像机绕玩家旋转,并限制角度
  3. 根据视角角度调整摄像机距离
  4. 瞄准时调整摄像机位置
  5. 遮挡检测时调整摄像机位置

1. 摄像机平滑跟随

通过记录摄像机与玩家的位置偏移向量,并使用插值让摄像机平滑移动到目标位置。

playerOffset = player.position - transform.position;
transform.position = Vector3.Lerp(transform.position, player.position - playerOffset, moveSpeed * Time.deltaTime);

2. 摄像机绕玩家旋转

使用transform.RotateAround函数实现摄像机绕玩家旋转,并通过四元数旋转更新playerOffset向量。

transform.RotateAround(player.position, Vector3.up, axisX);
transform.RotateAround(player.position, transform.right, -axisY);

3. 视角角度调整摄像机距离

根据视角角度调整摄像机与玩家的距离,向上看时拉近,向下看时拉远。

if (x < 0) {
    localOffsetAngle = (x / minAngle) * localOffsetAngleUp;
} else {
    localOffsetAngle = -(x / maxAngle) * localOffsetAngleDown;
}

4. 瞄准时调整摄像机位置

当鼠标右键按下时进入瞄准状态,调整摄像机位置。

if (Input.GetMouseButtonDown(1)) {
    isAiming = true;
}
if (Input.GetMouseButtonUp(1)) {
    isAiming = false;
}

5. 遮挡检测

通过射线检测判断是否有遮挡,并调整摄像机位置。

for(localOffsetCollider=0; !CheckView(checkPos);localOffsetCollider+=0.2f) {
    checkPos = transform.position + cam.transform.forward * (localOffset+localOffsetCollider);
}

完整代码实现

视角控制部分的完整代码:

using UnityEngine;

public class TPSCamera : MonoBehaviour
{
    public static TPSCamera _instance;
    public Camera cam;
    public Transform player;
    public Vector3 playerOffset;
    public float rotateSpeed;
    public float moveSpeed;
    public float minAngle;
    public float maxAngle;
    public float localOffsetSpeed = 8;
    public float localOffsetAim = 2;
    private float localOffsetAngle = 0;
    public float localOffsetAngleUp = 1.5f;
    public float localOffsetAngleDown = 1.5f;
    private float localOffsetCollider = 0;
    private bool isAiming = false;

    private void Awake()
    {
        _instance = this;
        player = GameObject.Find("Player").transform;
        playerOffset = player.position - transform.position;
        cam = transform.GetComponentInChildren<Camera>();
    }

    private void Update()
    {
        if (Input.GetMouseButtonDown(1)) {
            isAiming = true;
        }
        if (Input.GetMouseButtonUp(1)) {
            isAiming = false;
        }
        SetPosAndRot();
        Cursor.visible = false;
    }

    public void SetPosAndRot()
    {
        transform.position = Vector3.Lerp(transform.position, player.position - playerOffset, moveSpeed * Time.deltaTime);
        float axisX = Input.GetAxis("Mouse X") * rotateSpeed * Time.deltaTime;
        float axisY = Input.GetAxis("Mouse Y") * rotateSpeed * Time.deltaTime;
        Quaternion rotX = Quaternion.AngleAxis(axisX, Vector3.up);
        Quaternion rotY = Quaternion.AngleAxis(-axisY, transform.right);
        transform.RotateAround(player.position, Vector3.up, axisX);
        Vector3 posPre = transform.position;
        Quaternion rotPre = transform.rotation;
        transform.RotateAround(player.position, transform.right, -axisY);
        float x = (transform.rotation).eulerAngles.x;
        if (x > 180) x -= 360;
        if (x < minAngle || x > maxAngle) {
            transform.position = posPre;
            transform.rotation = rotPre;
            playerOffset = rotX * playerOffset;
        } else {
            playerOffset = rotX * rotY * playerOffset;
            if (x < 0) {
                localOffsetAngle = (x / minAngle) * localOffsetAngleUp;
            } else {
                localOffsetAngle = -(x / maxAngle) * localOffsetAngleDown;
            }
        }
        SetLocalOffset();
    }

    public void SetLocalOffset()
    {
        float localOffset = 0;
        localOffset += localOffsetAngle;
        if (isAiming) {
            localOffset += localOffsetAim;
        }
        Vector3 checkPos = transform.position + cam.transform.forward * localOffset;
        for(localOffsetCollider=0; !CheckView(checkPos);localOffsetCollider+=0.2f) {
            checkPos = transform.position + cam.transform.forward * (localOffset+localOffsetCollider);
        }
        localOffset += localOffsetCollider;
        Vector3 offsetPos = new Vector3(0, 0, localOffset);
        cam.transform.localPosition = Vector3.Lerp(cam.transform.localPosition, offsetPos, localOffsetSpeed * Time.deltaTime);
    }

    private bool CheckView(Vector3 checkPos)
    {
        RaycastHit hit;
        Vector3 endPos = player.position + player.up * player.GetComponent<CapsuleCollider>().height * 0.5f;
        Debug.DrawLine(checkPos,endPos, Color.blue);
        if (Physics.Raycast(checkPos,endPos-checkPos,out hit,(endPos-checkPos).magnitude)){
            if (hit.transform == player) {
                return true;
            } else {
                return false;
            }
        }
        return true;
    }
}

武器瞄准控制部分的完整代码:

using RootMotion.FinalIK;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ShootControl : MonoBehaviour
{
    public TPSCamera tpsCamera;
    public Camera cam;
    public float range;
    private float offsetDis;
    public Vector3 targetPos;
    private AimIK aimIK;
    public float speed;
    public float rotateSpeed;

    private void Awake()
    {
        tpsCamera = GameObject.Find("TPSCameraParent").GetComponent<TPSCamera>();
        cam = tpsCamera.GetComponentInChildren<Camera>();
        aimIK = GetComponent<AimIK>();
        offsetDis = Vector3.Distance(transform.position, cam.transform.position);
    }

    private void Update()
    {
        SetTarget();
        OnKeyEvent();
    }

    public void SetTarget()
    {
        RaycastHit hit;
        if (Physics.Raycast(cam.transform.position, cam.transform.forward, out hit, range)) {
            targetPos = hit.point;
        } else {
            targetPos = cam.transform.position + (cam.transform.forward * range);
        }
        Debug.DrawRay(cam.transform.position, cam.transform.forward * range, Color.green);
        if (Input.GetMouseButtonDown(1)) {
            aimIK.enabled = true;
        }
        if (Input.GetMouseButton(1)) {
            RotateBodyToTarget();
        } else {
            aimIK.Disable();
        }
    }

    private void RotateBodyToTarget()
    {
        Vector3 rotEulerAngles = cam.transform.eulerAngles;
        rotEulerAngles.x = 0;
        transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.Euler(rotEulerAngles), rotateSpeed * Time.deltaTime);
        SetAimIKTarget();
    }

    private void SetAimIKTarget()
    {
        aimIK.solver.target.position = targetPos;
    }

    private void OnKeyEvent()
    {
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        if (h != 0 || v != 0) {
            Vector3 moveDir = new Vector3(h, 0, v);
            transform.Translate(moveDir * speed * Time.deltaTime);
            RotateBodyToTarget();
        }
    }
}

组件配置

  • TPSCamera.cs:挂在摄像机的父物体上
  • ShootControl.cs:挂在玩家身上
  • AimIK组件:需要在玩家身上挂载,配置Target和FirePos


注意事项:初始时将玩家模型放在摄像机视线的左下角,否则需要在射线检测中屏蔽玩家所在的层。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号