You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity中如何实现类似愤怒小鸟的3D拖拽射击机制?

问题描述

开发3D游戏时实现类似《愤怒小鸟》的拖拽发射机制:点击球体沿蓝色线拖拽,释放后球体沿红色线运动击中胶囊体,但实际球体沿绿色线运动,完全没有深度感。尝试使用ScreenToWorldPoint()时出现异常,点击物体后物体会消失。

附当前代码(DragAndShoot.cs):

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(Collider))]
public class DragAndShoot : MonoBehaviour
{
    private Vector3 mousePressDownPos;
    private Vector3 mouseReleasePos;
    private Rigidbody rb;

    private bool isShoot;
    
    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }

    void Update() {
        //if isShoot is true, then the ball is already shot. Wait for it to stop moving.
        //after it has stopped moving, we can shoot again.
        if(isShoot && rb.velocity.magnitude < 0.1f)
        {
            isShoot = false;
        }
    }

    private void OnMouseDown()
    {
        mousePressDownPos = Input.mousePosition;
    }

    private void OnMouseUp()
    {
        mouseReleasePos = Input.mousePosition;
        Shoot(mouseReleasePos-mousePressDownPos);
    }

    private float forceMultiplier = 3;
    void Shoot(Vector3 Force)
    {
        if(isShoot)    
            return;
        
        rb.AddForce(-Force * forceMultiplier);
        isShoot = true;
    }
}
问题排查与修复建议

核心问题分析

  • 屏幕坐标直接用于3D力向量错误:Input.mousePosition返回的是屏幕像素坐标(X/Y对应屏幕宽高,Z值始终为0),直接用这个向量计算力,只会在X/Y平面施加力,完全没有Z轴(深度)分量,导致球体运动无3D深度感。
  • ScreenToWorldPoint使用错误:直接传入Input.mousePosition时,Z值为0,会将点转换到相机近裁剪面的位置,若相机朝向与物体位置不匹配,会导致物体瞬移到相机附近甚至视野外,看起来像消失。

修复步骤

1. 获取拖拽的世界空间偏移量

将屏幕拖拽操作转换为世界空间中的方向向量,通过射线检测+平面相交的方式,获取拖拽的3D空间坐标:

private Camera mainCam;
private Vector3 dragStartWorldPos;

void Start()
{
    rb = GetComponent<Rigidbody>();
    mainCam = Camera.main; // 提前缓存主相机,避免重复调用
}

private void OnMouseDown()
{
    // 发射射线,计算鼠标在物体所在平面上的交点
    Ray ray = mainCam.ScreenPointToRay(Input.mousePosition);
    Plane dragPlane = new Plane(mainCam.transform.forward, transform.position);
    if (dragPlane.Raycast(ray, out float enter))
    {
        dragStartWorldPos = ray.GetPoint(enter);
    }
}

private void OnMouseUp()
{
    Ray ray = mainCam.ScreenPointToRay(Input.mousePosition);
    Plane dragPlane = new Plane(mainCam.transform.forward, transform.position);
    if (dragPlane.Raycast(ray, out float enter))
    {
        Vector3 dragEndWorldPos = ray.GetPoint(enter);
        Vector3 forceDir = dragStartWorldPos - dragEndWorldPos; // 反向拖拽方向,对应愤怒小鸟的发射逻辑
        Shoot(forceDir);
    }
}

2. 修正力的施加方式

改用世界空间向量施加力,同时使用Impulse模式提升发射手感:

private float forceMultiplier = 10f; // 可根据需求调整系数大小
void Shoot(Vector3 forceDir)
{
    if (isShoot) return;
    
    // 使用冲量模式瞬间施加力,更符合拖拽发射的手感
    rb.AddForce(forceDir * forceMultiplier, ForceMode.Impulse);
    isShoot = true;
}

3. 避免物体瞬移的正确用法

如果需要单独使用ScreenToWorldPoint,必须传入有效Z值(比如物体到相机的距离):

// 示例:获取物体到相机的距离作为Z深度
float depth = Vector3.Distance(mainCam.transform.position, transform.position);
Vector3 worldPos = mainCam.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, depth));

额外优化建议

  • 添加拖拽视觉反馈:比如拖拽时绘制轨迹线,或显示球体的预览位置,提升玩家操作感知。
  • 限制最大拖拽距离:通过Vector3.ClampMagnitude限制力向量长度,避免球体被发射过远:
    float maxDragDistance = 2f; // 自定义最大拖拽距离
    forceDir = Vector3.ClampMagnitude(forceDir, maxDragDistance);
    

内容的提问来源于stack exchange,提问作者yetty

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 06:27:50