Unity 第三人称射击游戏视角控制与武器瞄准
创作时间:
作者:
@小白创作中心
Unity 第三人称射击游戏视角控制与武器瞄准
引用
CSDN
1.
https://blog.csdn.net/realfancy/article/details/104253615
本文将详细介绍如何在Unity中实现第三人称射击游戏的视角控制和武器瞄准功能。通过使用Final IK插件和自定义脚本,我们可以实现平滑的摄像机跟随、自由视角旋转、根据视角角度调整摄像机距离、瞄准时的摄像机位置调整以及遮挡检测等功能。
效果演示
项目资源
- 源工程链接:点击下载(提取码:e7bp)
- 效果视频:Bilibili观看
技术选型
Unity自带的IK功能实现武器瞄准效果较为复杂,因此推荐使用第三方插件Final IK。Final IK提供了强大的反向动力学解决方案,能够轻松实现武器指向目标的功能。
Final IK插件
武器瞄准实现
武器瞄准的核心思路是通过射线检测来确定瞄准目标,并使用Final IK的AimIK组件来控制武器指向该目标。
// 设置AimIK的目标位置
aimIK.solver.target.position = targetPos;
射线检测需要注意屏蔽玩家自身的碰撞体干扰,以避免误判。
视角控制实现
视角控制部分实现了以下功能:
- 摄像机平滑跟随玩家
- 摄像机绕玩家旋转,并限制角度
- 根据视角角度调整摄像机距离
- 瞄准时调整摄像机位置
- 遮挡检测时调整摄像机位置
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
注意事项:初始时将玩家模型放在摄像机视线的左下角,否则需要在射线检测中屏蔽玩家所在的层。
热门推荐
如果想学习机器人制造需要学习什么专业
香卤牛肚的健康烹饪秘籍
洪濑牛肚:在家就能复刻的闽南美味
牛肚:餐桌上的营养瑰宝
宠物陪伴:抑郁症患者家庭的暖心之选
抑郁症家庭护理的实用指南
妙佑医疗国际&周伟翔博士教你如何关爱抑郁症家人
龙是想象中的动物,那么中国龙从何而来?
青铜器上的龙文化
卫哲新周期布局引爆财经圈:三大策略助力企业破局
卫哲详解企业出海新战略:从供应链优势到AI赋能
怎么判断自己是不是长智齿了
华拔牙:从智齿说开去
安吉和乐山谷:生态保护与文化体验的完美融合
安吉田园嘉乐比:秋季遛娃圣地
“绿水青山在安吉”摄影指南:竹海、茶园、草原的拍摄攻略
安吉竹海四季美如画,打卡《卧虎藏龙》取景点
安吉竹博园:竹文化的殿堂,冬日里的静谧乐园
专家建议:如何在照顾抑郁症患者时保护自己的情感健康
陆林院士:构建家庭支持网络,助力抑郁症患者康复
抑郁症患者的家庭护理指南
在潍坊玩滑雪、赏冰瀑,这条线路一定不要错过!
2024Q3医药工业发展指数127.1,行业经营承压
田家运捐赠《田氏家训》小楷书法作品
田氏家族的文化传承:家训、家谱与祭祖仪式
田氏家训:历史渊源与现实意义
探秘宜都:重庆周边的新晋网红打卡地
易烊千玺《小小的我》票房破6亿:一部展现残障群体生活的突破之作
洪崖洞探秘:重庆历史文化的深度游
智能机器人发展对就业市场的影响:挑战与机遇并存