这个主要是玩了Meteoric VR 觉得操作蛮不错的,就想复刻一个类似的游戏出来,但是在手势发射物体时遇到了难题;
不能直接拿手心的方向,因为人的手在往前推的过程中手心方向非常不稳定,根本无法做到想打到什么就打到什么,人也很难控制自己每一次推动各个关节的角度都几乎一致;
在充分研究Meteoric VR的操作模式之后,发现他的指示方向(射击方向),跟手的旋转无关,只跟手、摄像机两个点的向量有关,这两个点形成的新的方向具有稳定的特性;
于是沿着这个思路,在官方FirstHand示例场景中,将手势识别+推手检测发射物体的功能复现了出来;
Unity XR手势识别+推手发射物体(适用任何设备)
主要特点:
- 方向指示器的动态调整:根据手部到摄像机的距离和角度差,方向指示器会向左或向右偏移,增加了射击的准确性和动态反应性。
- 推动检测与发射控制:当检测到手部在短时间内快速移动超过设定的阈值时,将触发子弹的发射。这模仿了真实世界中的快速射击动作。
- 冷却时间控制:为防止过快连续射击,脚本中设置了一个冷却时间,确保每次射击后有一定的间隔,避免子弹的无限快速发射。
- 灵活的偏移调整:脚本允许根据玩家与摄像机之间的距离动态调整射击偏移,距离越近,偏移效果越明显,增强游戏的实际操作感和挑战性。
技术细节:
- 使用Unity引擎的Transform组件来计算和更新物体的位置和旋转。
- 利用
Vector3.Lerp
进行方向的平滑过渡,使射击动作更加自然。 - 根据摄像机的前向向量和手部位置动态调整射击方向,提供更为直观稳定的射击体验。
using UnityEngine;
public class GestureShooting : MonoBehaviour
{
public GameObject directionIndicatorPrefab; // 方向指示器的预制体
public GameObject projectilePrefab; // 子弹的预制体
private GameObject directionIndicator; // 实例化的方向指示器对象
private Transform cameraTransform; // 摄像机的Transform组件
public float cameraInfluenceWeight = 0.1f; // 摄像机对计算方向的影响权重
public float maxOffsetDistance = 1f; // 最大偏移距离
public float effectDistance = 0.6f; // 影响偏移的距离阈值
private Vector3 lastHandPosition; // 记录上一帧手的位置
private float pushThreshold = 0.05f; // 手部推动的阈值
private float pushCooldown = 0.2f; // 推动后的冷却时间
private float lastPushTime; // 上次推动的时间
private bool isRightHand; // 标记是否是右手
private void Start()
{
cameraTransform = Camera.main.transform; // 获取摄像机的Transform
lastHandPosition = transform.position; // 初始化手的位置
CreateDirectionIndicator(); // 创建方向指示器
DetermineHandSide();
Debug.Log("Gesture shooting system initialized."); // 输出初始化完成的日志
}
private void DetermineHandSide()
{
Transform current = transform;
for (int i = 0; i < 3; i++) // 检查3层父对象
{
if (current != null)
{
if (current.name.Contains("RightHand") || current.name.Contains("Hand_R"))
{
isRightHand = true;
return;
}
if (current.name.Contains("LeftHand") || current.name.Contains("Hand_L"))
{
isRightHand = false;
return;
}
current = current.parent;
}
}
}
private void Update()
{
Vector3 bulletDirection = CalculateDirection(); // 计算射击方向
UpdateDirectionIndicator(bulletDirection); // 更新方向指示器
CheckHandPush(bulletDirection); // 检查手部是否推动
lastHandPosition = transform.position; // 更新手的位置
lastPushTime -= Time.deltaTime; // 更新冷却时间
}
private void CreateDirectionIndicator()
{
if (directionIndicatorPrefab != null)
{
directionIndicator = Instantiate(directionIndicatorPrefab, transform.position, Quaternion.identity); // 实例化方向指示器
}
else
{
Debug.LogError("Direction indicator prefab is not set."); // 如果预制体未设置,输出错误日志
}
}
private void UpdateDirectionIndicator(Vector3 direction)
{
if (directionIndicator != null)
{
directionIndicator.transform.position = transform.position; // 设置方向指示器的位置
directionIndicator.transform.rotation = Quaternion.LookRotation(-direction); // 设置方向指示器的朝向
}
else
{
Debug.LogError("Direction indicator instance is missing."); // 如果实例丢失,输出错误日志
}
}
private Vector3 CalculateDirection()
{
Vector3 handToCamera = cameraTransform.position - transform.position; // 计算从手到摄像机的向量
Vector3 cameraForward = cameraTransform.forward; // 获取摄像机的前向向量
Vector3 baseDirection = handToCamera.normalized; // 标准化手到摄像机的向量
Vector3 adjustedDirection = Vector3.Lerp(baseDirection, cameraForward, cameraInfluenceWeight); // 根据权重插值计算调整后的方向
float distance = handToCamera.magnitude; // 计算手到摄像机的距离
float distanceFactor = Mathf.Clamp01(1 - distance / effectDistance); // 根据距离计算偏移影响的权重
adjustedDirection += cameraTransform.right * (isRightHand ? maxOffsetDistance : -maxOffsetDistance) * distanceFactor; // 根据手的类型调整偏移方向
return adjustedDirection.normalized; // 返回归一化的方向
}
private void CheckHandPush(Vector3 direction)
{
float moveDistance = Vector3.Distance(transform.position, lastHandPosition); // 计算手移动的距离
if (moveDistance > pushThreshold && lastPushTime <= 0) // 如果移动距离大于阈值且冷却时间结束
{
LaunchProjectile(direction); // 发射子弹
lastPushTime = pushCooldown; // 重置冷却时间
}
}
private void LaunchProjectile(Vector3 direction)
{
if (projectilePrefab != null)
{
GameObject projectile = Instantiate(projectilePrefab, transform.position, Quaternion.LookRotation(-direction)); // 实例化子弹
Rigidbody rb = projectile.GetComponent<Rigidbody>(); // 获取子弹的Rigidbody组件
if (rb != null)
{
rb.AddForce(-direction * 20f, ForceMode.Impulse); // 给子弹添加力
Debug.Log("Projectile launched."); // 输出子弹发射的日志
}
else
{
Debug.LogError("Projectile prefab does not have a Rigidbody component."); // 如果子弹预制体缺少Rigidbody组件,输出错误日志
}
}
else
{
Debug.LogError("Projectile prefab is not set."); // 如果子弹预制体未设置,输出错误日志
}
}
}