小球的平面弹射问题

最近参与的项目总是会涉及到物体的平面反弹问题,于是我就仔细研究了一下,得到一个适用于任何开发工具的通用性方法;下面我将这个方法分享给大家,欢迎大家提出问题、与我讨论


一、问题概述

首先明白任务的需求:对于在场景中任意位置任意角度生成的一根横杆,实现小球与横杆发生碰撞后能够实现平面反射,且要求使用的方法能够适用于任何引擎。

这个问题的需求其实非常简单,需要用到一些高中的数学和物理知识,主要涉及到向量的运算,只要明白其中的原理,实现起来并不是非常困难的事。这篇文章会先从理论上讨论通用的方法,再使用Unity引擎模拟这个过程。

 

二、理论分析

首先,我们的研究对象是在场景中生成的横杆,对于这根横杆,我们需要先得到它的方向向量,这是一件很容易做到的事情,因为横杆的生成也是由我们控制的,只要在生成时保留下来随机的相对于水平线的角度值,就可以根据这个角度值求得方向向量。我们设这个随机的角的弧度值为θ(三角函数的运算通常使用的是弧度值,设角度值为ω,那么弧度值θ=ω*π/180°,之后将不再赘述),横杆的方向向量为\overrightarrow{\alpha },那么\overrightarrow{\alpha }=(1*cos\theta, 1*sin\theta ),这里我们使用1作为模长,是因为我们希望将我们所有的方向向量都转化为单位向量,方便我们进行之后的计算。

图1

接下来我们来求一下横杆的法线的方向向量\overrightarrow{n},因为我们知道一条直线的法线与该直线必然相差90°,所以可以求得          \overrightarrow{n}=(1*cos(\theta +\pi /2), 1*sin(\theta +\pi /2)),同样我们知道法向量其实也可以是\overrightarrow{n}=(1*cos(\theta -\pi /2), 1*sin(\theta -\pi /2)),到目前为止并不能判断哪一条是我们需要的,不过这并没有关系,因为\overrightarrow{n_{1}} =-\overrightarrow{n_{2}}需要时可以随时转化,我们后面会看到这一点。所以这里我们就取\overrightarrow{n}=(1*cos(\theta +\pi /2), 1*sin(\theta +\pi /2))

然后我们随便画出一条入射向量\overrightarrow{AB},再根据该入射向量画出反射向量\overrightarrow{BC},然后根据平行四边形法则再分别画出\overrightarrow{AD}\overrightarrow{DC}。根据平面反射可知,入射角等于反射角,易得向量\overrightarrow{AB}\overrightarrow{BC}\overrightarrow{AD}\overrightarrow{DC}的模长均相等,即ABCD是一个菱形,如图2所示。

图2

我们让\overrightarrow{AB}就等于小球飞行过来时的方向向量,设为(x_{AB}, y_{AB}),所以我们现在的任务就是根据\overrightarrow{AB}和法向量\overrightarrow{n}要求出反射向量\overrightarrow{BC}。接下来就是求解的过程:

首先根据向量运算法则知道\overrightarrow{BC} = \overrightarrow{AD} = \overrightarrow{AB}+ \overrightarrow{BD},现在\overrightarrow{AB}是已知的,重点就是要求向量\overrightarrow{BD}的值,我们又知道\overrightarrow{n}是法线的方向向量,但方向并不确定,在\overrightarrow{n}\overrightarrow{BD}同向的情况下,\overrightarrow{BD} = λ\overrightarrow{n},只要求得λ即|\overrightarrow{BD}|,问题就解决了;如果\overrightarrow{n}\overrightarrow{BD}方向相反,我们只要让\overrightarrow{n}取反,问题一样可以得到解决。所以我们需要先判断\overrightarrow{n}\overrightarrow{BD}是否同向,这里可以计算一下\overrightarrow{AB}\overrightarrow{n}的夹角\theta _{2}(向量夹角计算公式:夹角的余弦值等于两个向量点乘除以两个向量的模的乘积,即cos\theta _{2}=\overrightarrow{n}·\overrightarrow{AB} / |\overrightarrow{n}|*|\overrightarrow{AB}|)很显然在\overrightarrow{n}\overrightarrow{BD}同向的情况下,\theta _{2}必然大于π/2,由此就可以判断\overrightarrow{n}的方向。

接下来我们要求解|\overrightarrow{BD}|的值,显然由于ABCD是菱形,所以|\overrightarrow{BD}|=2*|\overrightarrow{AB}|*cos\angle ABD,幸运的是∠ABD正好与我们前面所求得的\theta _{2}相关,当\theta _{2}>2/\pi时,\angle ABD=\pi -\theta _{2},即|\overrightarrow{BD}|=2*|\overrightarrow{AB}|*cos(\pi -\theta _{2});当\theta _{2}<2/\pi时,\angle ABD=\theta _{2},即|\overrightarrow{BD}|=2*|\overrightarrow{AB}|*cos\theta _{2},至此,反射相关的问题基本上已经得到解决,剩下的只是程序的实现,图3是完整的计算过程。

图3

 不过,除了反射以外,我们同样还需要考虑一下碰撞检测的通用方法。现在我们知道横杆的方向向量和横杆的位置坐标,那么可以很轻松的求出横杆所在的直线方程,设直线方程l:Ax+By=C,接下来再通过点到直线距离公式就可以得到小球到横杆的最短距离d(设小球的位置p(x_{0}, y_{0}),那么d = |Ax_{0}+By_{0}+C|/\sqrt{A^{2}+B^{2}}),这样,我们就可以每一帧都监听d的大小,当d小于一个极小值ε时,即为满足反射条件,如图4所示。

图4

至此,小球反弹问题已经从理论上得到解决,接下来是我们尝试用Unity模拟这个过程。(注:Unity中已存在Vector2.Reflect()函数,传入入射向量和法向量即可实现核心的反射功能,我们在理论阶段的工作相当于实现了Reflect()的底层,为了保证方法在任何引擎中都适用,这是必须的,因此,我们在Unity的模拟工作中也不会使用其自带的Reflect()函数;此外,Unity中也存在一些自带的数学方法,能够实现求两个向量的夹角、角度值和弧度值的转化等等,因为这些问题比较简单,也不是我们这次讨论的核心问题,所以方便起见,我们会在程序模拟的时候使用)

 

三、代码实现

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

public class BallManager : MonoBehaviour
{
    public Vector3 direction;
    //小球移动的方向向量
    public float flySpeed;
    //小球移动速度

    private void Update()
    {
        if (Mathf.Abs(this.transform.position.x) > 9 || Mathf.Abs(this.transform.position.y) > 5)
            GameObject.Destroy(this.gameObject);
        //越界销毁
    }

    private void FixedUpdate()
    {
        this.transform.Translate(direction * flySpeed * Time.fixedDeltaTime, Space.World);
        //小球移动
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag("Wall"))
        {
            float normalAngle = (collision.GetComponent<Transform>().eulerAngles.z + 90) * Mathf.Deg2Rad;
            //法线的角度值与横杆相差90°,Mathf.Deg2Rad是一个常数,可直接将角度值转化为弧度值
            Vector3 normalDir = new Vector3(Mathf.Cos(normalAngle), Mathf.Sin(normalAngle), 0);
            //求法线的方向向量

            direction = Reflect(direction, normalDir);
            //改变小球移动方向为反射向量
        }
    }
    //偷个小懒,直接使用Unity的内置方法进行碰撞检测

    private Vector3 Reflect(Vector3 inDir, Vector3 nDir)
    {
        float angleTemp = 0;
        //定义一个变量保存θ2

        if (Vector3.Angle(inDir, nDir) > 90)
        {
            angleTemp = 180 - Vector3.Angle(inDir, nDir);
            //若向量夹角大于90°,则θ2为向量角的补角
            //Vector3.Angle方法可以直接求得两向量的夹角
        }
        else
        {
            angleTemp = Vector3.Angle(inDir, nDir);
            nDir = -nDir;
            //若向量夹角小于90°,则θ2等于向量角,法向量取反
        }

        Vector3 outDir = Mathf.Cos(angleTemp * Mathf.Deg2Rad) * 2 * nDir + inDir;
        //求反射向量

        return outDir;
    }
    //反射函数
}

 

四、效果演示

 

五、扩展

上面讨论的问题均是在2D情景下实现的,如果扩展到3D情景下原理其实也基本相同,感兴趣的同学可以尝试实现。

图5

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值