Unity 足球发射的三种方式,直线、抛物线、曲线,模拟踢球时的物理效果

 脚本分别实现了三个功能,直线运动,抛物线运动,曲线运动,直线和抛物线从俯视图看能看出效果,曲线的效果得从正前方的摄像视角看,才看得出。如果要改变摄像机视角,比如要从正前方跟随人物,侧面视角跟随人物等等,只要确保鼠标点击屏幕的坐标点被转换出来的坐标是正确的即可,看得懂的小伙伴可以自行改动哈!主页写了足球网的物理效果,搭配足球的脚本可以模拟出足球射进球网时的效果

素材保密哈,Ceshi就是Ball脚本,我的摄像机是俯视看着球的,如果小伙伴在测试当中,跟我差不多设置的话,球能发送出去,但发射位置不对大概率是屏幕点击转换坐标的问题

下面是球的设置,只需要拉到主摄像机即可,其他参数随便调

下面是摄像机的参数设置

下面是脚本代码

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

public class Ball : MonoBehaviour
{

    public Camera mainCamera;
    public LaunchType currentLaunchType = LaunchType.Straight; // 当前发射类型
    public float forceMultiplier = 1.5f; // 力的倍率
    public float launchAngle = 45f; // 发射角度

    public float straightSpeed = 10f; // 直线速度

    public float curveForce = 1f; // 曲线力
    public float curveDuration = 1f; // 曲线持续时间

    public float spinForce = 20f; // 旋转力

    public float drag = 0.5f; // 空气阻力
    public float angularDrag = 0.5f; // 角阻力
    private LineRenderer pathLineRenderer; // 用于实际路径显示的线渲染器

    private new Rigidbody rigidbody; // 刚体组件
    private Coroutine curveCoroutine; // 曲线运动协程

    private Vector3 targetPosition; // 目标位置

    private void Start()
    {
        // 获取Rigidbody组件并设置阻力
        rigidbody = GetComponent<Rigidbody>();
        rigidbody.drag = drag; // 设置刚体阻力
        rigidbody.angularDrag = angularDrag; // 设置刚体角阻力

        // 初始化pathLineRenderer
        if (transform.GetChild(0).gameObject.GetComponent<LineRenderer>() == null)
        {
            pathLineRenderer = transform.GetChild(0).gameObject.AddComponent<LineRenderer>();
            pathLineRenderer.widthMultiplier = 0.1f; // 设置线的宽度
            pathLineRenderer.material = new Material(Shader.Find("Sprites/Default")); // 设置线的材质
            pathLineRenderer.startColor = Color.cyan;
            pathLineRenderer.endColor = Color.blue;
        }
        pathLineRenderer.positionCount = 0; // 初始点数为0.
    }


    private void Update()
    {
        // 更新目标位置
        UpdateTargetPosition();
    }

    // 更新目标位置
    private void UpdateTargetPosition()
    {
        // 使用鼠标点击获取目标位置
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ray, out RaycastHit hitInfo))
            {
                switch (currentLaunchType)
                {
                    case LaunchType.Parabolic:
                        SetFootballParabolic(hitInfo.point);
                        break;
                    case LaunchType.Straight:
                        SetFootballStraight(hitInfo.point);
                        break;
                    case LaunchType.Curved:
                        SetFootballCurved(forceMultiplier,launchAngle,curveForce,curveDuration, hitInfo.point);
                        break;
                    default:
                        break;
                }
            }
        }
    }

    // 带球
    public void Dribbling(float speed)
    {
        transform.Rotate(Vector3.right * speed); // 让自身向上自转
    }

    // 抛物线
    public void SetFootballParabolic(Vector3 targetTransform, float _forceMultiplier = 0, float _launchAngle = 0)
    {
        float distance = Vector3.Distance(targetTransform, transform.position);
        float forceMultiplier = 0, launchAngle = 0;
        if (distance < 20)
        {
            forceMultiplier = 1.7f + _forceMultiplier;
            launchAngle = 25f + _launchAngle;
        }

        if (distance > 20 && distance < 30)
        {
            forceMultiplier = 1.7f + _forceMultiplier;
            launchAngle = 20f + _launchAngle;
        }

        if (distance > 30 && distance < 50)
        {
            forceMultiplier = 1.7f + _forceMultiplier;
            launchAngle = 45f + _launchAngle; ;
        }

        if (distance > 50)
        {
            forceMultiplier = 1.6f + _forceMultiplier;
            launchAngle = 35f + _launchAngle;
        }
        //以上的抛物线计算力度和角度只是简单的,没有用到算法计算
        SetFooballParameter(forceMultiplier, launchAngle, 0, 0, 0, 20);
        ShootingBallMethod(LaunchType.Parabolic, targetTransform);
    }

    // 直线
    public void SetFootballStraight(Vector3 targetTransform, float _straightSpeed = 0)
    {
        float distance = Vector3.Distance(targetTransform, transform.position);
        float speed = ((distance / 3f) * 1.8f) + ((drag * 10) + (angularDrag * 10)) + _straightSpeed;
        SetFooballParameter(0, 0, speed, 0, 0, 20);
        ShootingBallMethod(LaunchType.Straight, targetTransform);
    }

    // 曲线
    public void SetFootballCurved(float _forceMultiplier, float _launchAngle, float _curveForce, float _curveDuration, Vector3 targetTransform)
    {
        SetFooballParameter(_forceMultiplier, _launchAngle, 0, _curveForce, _curveDuration, 20);
        ShootingBallMethod(LaunchType.Curved, targetTransform);
    }

    // 设置参数
    private void SetFooballParameter(float _forceMultiplier, float _launchAngle, float _straightSpeed, float _curveForce, float _curveDuration, float _spinForce)
    {
        this.forceMultiplier = _forceMultiplier;
        this.launchAngle = _launchAngle;
        this.straightSpeed = _straightSpeed;
        this.curveForce = _curveForce;
        this.curveDuration = _curveDuration;
        this.spinForce = _spinForce;
    }

    // 发射球
    private void ShootingBallMethod(LaunchType launchType, Vector3 targetTransform)
    {
        currentLaunchType = launchType;
        targetPosition = targetTransform;

        // 计算目标方向并忽略Y轴
        Vector3 direction = targetPosition - transform.position;
        direction.y = 0;

        Vector3 velocity = Vector3.zero;
        string positionRelativeToBall = DetermineClickPosition(direction.normalized); // 获取发射点的位置

        // 根据发射类型计算初速度
        switch (currentLaunchType)
        {
            case LaunchType.Parabolic:
                velocity = CalculateVelocity(transform.position, targetPosition, launchAngle) * forceMultiplier;
                break;
            case LaunchType.Straight:
                if (direction.magnitude > 0)
                {
                    direction.Normalize();
                    velocity = direction * straightSpeed;
                }
                else
                {
                    Debug.LogWarning("Direction vector magnitude is zero. Aborting straight kick.");
                }
                break;
            case LaunchType.Curved:
                if (direction.magnitude > 0)
                {
                    direction.Normalize();
                    velocity = CalculateVelocity(transform.position, targetPosition, launchAngle) * forceMultiplier;
                }
                else
                {
                    Debug.LogWarning("Direction vector magnitude is zero. Aborting curved kick.");
                }
                break;
        }

        // 检查是否计算出有效的速度
        if (float.IsNaN(velocity.x) || float.IsNaN(velocity.y) || float.IsNaN(velocity.z))
        {
            Debug.LogError("Calculated velocity is NaN. Aborting kick.");
            return;
        }

        // 设置刚体速度并启动轨迹协程
        rigidbody.velocity = velocity;
        pathLineRenderer.positionCount = 0;
        StartCoroutine(TrailEffect());

        // 如果是曲线发射类型,启动曲线运动协程
        if (currentLaunchType == LaunchType.Curved)
        {
            if (curveCoroutine != null)
            {
                StopCoroutine(curveCoroutine);
            }
            curveCoroutine = StartCoroutine(ApplyCurve(positionRelativeToBall, velocity));
        }

        // 应用旋转力
        ApplySpin(positionRelativeToBall);
    }

    // 曲线运动协程
    private IEnumerator ApplyCurve(string clickPosition, Vector3 initialVelocity)
    {
        Vector3 curveDirection = Vector3.zero;
        if (clickPosition == "right")
        {
            curveDirection = Vector3.left;
        }
        else if (clickPosition == "left")
        {
            curveDirection = Vector3.right;
        }

        float elapsedTime = 0f;
        while (elapsedTime < curveDuration)
        {
            rigidbody.AddForce(curveDirection * curveForce, ForceMode.Acceleration);
            elapsedTime += Time.deltaTime;
            yield return null;
        }

        Debug.Log($"Curved Final Velocity after curve force: {rigidbody.velocity}");
    }

    // 应用旋转力 模拟球旋转
    private void ApplySpin(string clickPosition)
    {
        Vector3 spinDirection = Vector3.zero;
        switch (currentLaunchType)
        {
            case LaunchType.Parabolic:
                spinDirection = Vector3.forward;
                rigidbody.AddTorque(spinDirection * spinForce * 2.0f, ForceMode.Impulse);
                break;
            case LaunchType.Straight:
                spinDirection = Vector3.up;
                rigidbody.AddTorque(spinDirection * spinForce * 1.5f, ForceMode.Impulse);
                break;
            case LaunchType.Curved:
                if (clickPosition == "right")
                    spinDirection = Vector3.down;
                else if (clickPosition == "left")
                    spinDirection = Vector3.up;
                rigidbody.AddTorque(spinDirection * spinForce, ForceMode.Impulse);
                break;
        }

        //Debug.Log($"Applied Spin: {spinDirection * spinForce}");
    }

    // 计算发射速度
    private Vector3 CalculateVelocity(Vector3 startPoint, Vector3 endPoint, float angle)
    {
        float gravity = Physics.gravity.magnitude; // 获取重力的模拟值
        float radianAngle = angle * Mathf.Deg2Rad; // 角度转弧度
        float horizontalDistance = Vector3.Distance(new Vector3(startPoint.x, 0, startPoint.z), new Vector3(endPoint.x, 0, endPoint.z)); // 计算起点和终点在水平面上的距离
        float heightDifference = endPoint.y - startPoint.y; // 计算起点和终点的高度差

        // 计算初速度
        float initialVelocity = (1 / Mathf.Cos(radianAngle)) * Mathf.Sqrt((0.5f * gravity * Mathf.Pow(horizontalDistance, 2)) / (horizontalDistance * Mathf.Tan(radianAngle) + heightDifference));
        Vector3 velocity = new Vector3(0, initialVelocity * Mathf.Sin(radianAngle), initialVelocity * Mathf.Cos(radianAngle)); // 初速度向量
        Vector3 launchDirection = (new Vector3(endPoint.x, 0, endPoint.z) - new Vector3(startPoint.x, 0, startPoint.z)).normalized; // 发射方向
        Vector3 finalVelocity = new Vector3(launchDirection.x * velocity.z, velocity.y, launchDirection.z * velocity.z); // 最终速度向量

        return finalVelocity;
    }

    // 确定点击位置相对球的位置,判断方向
    private string DetermineClickPosition(Vector3 clickpoint)
    {

        // 计算点击方向向量
        Vector3 clickDirection = clickpoint - transform.position;  //parent
        clickDirection.y = 0; // 忽略y轴

        // 获取物体前向
        Vector3 forward = transform.forward; //parent
        forward.y = 0; // 忽略y轴

        // 计算角度
        float angle = Vector3.SignedAngle(forward, clickDirection.normalized, Vector3.up);

        // 根据角度判断点击方向
        string direction = GetDirection(angle);
        Debug.Log("Click direction: " + direction);
        return direction;
    }
    string GetDirection(float angle)
    {
        // 如果角度接近 0 或接近 180,则认为是中心
        if (Mathf.Abs(angle) < 10 || Mathf.Abs(angle) > 170)
        {
            return "center";
        }
        else if (angle > 0)
        {
            return "right";
        }
        else
        {
            return "left";
        }
    }


    // 轨迹效果协程
    private IEnumerator TrailEffect()
    {
        float trailTime = 10f;
        float startTime = Time.time;
        while (Time.time < startTime + trailTime)
        {
            pathLineRenderer.positionCount++;
            pathLineRenderer.SetPosition(pathLineRenderer.positionCount - 1, rigidbody.position);
            yield return null;
        }
        pathLineRenderer.positionCount = 0;
    }
}

  • 9
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
首先,您需要在Unity场景中创建一个Plane对象,然后将其旋转使其成为一个倾斜的平面,以模拟抛物线的形状。 接下来,您需要在场景中创建一个对象来表示发射点,可以使用一个简单的3D球体或其他形状。将其放置在Plane的平面上。 然后,您需要编写一个脚本来控制发射抛物线路径。 以下是一个简单的示例脚本: ```csharp using UnityEngine; public class ProjectileLauncher : MonoBehaviour { public GameObject projectilePrefab; public float launchSpeed; public float gravityModifier; private Vector3 launchPosition; void Start() { launchPosition = transform.position; } void Update() { if (Input.GetButtonDown("Fire1")) { LaunchProjectile(); } } void LaunchProjectile() { GameObject projectile = Instantiate(projectilePrefab, launchPosition, Quaternion.identity); Rigidbody rb = projectile.GetComponent<Rigidbody>(); rb.velocity = transform.forward * launchSpeed; Physics.gravity *= gravityModifier; } } ``` 在该脚本中,您需要将projectilePrefab设置为您要发射的物体的预制件,launchSpeed设置为发射速度,gravityModifier设置为重力变化因子。 当按下Fire1按钮(默认为鼠标左键),LaunchProjectile函数将创建一个实例化的projectile对象,并将其设置为沿着发射点的前方方向和指定的速度飞行。然后,将场景中的重力乘以gravityModifier以模拟物体的抛物线路径。 最后,您需要在projectile对象上附加一个Rigidbody组件,以便其受到物理引擎的影响,并在其碰撞器上附加一个脚本来检测击中目标。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值