Unity 2D游戏物理运动曲线轨迹预测,以愤怒的小鸟为例,轨迹曲线云团圈圈

一、前言

点关注不迷路,持续输出Unity干货文章

嗨,大家好,我是新发。我们先来看一个画面,愤怒的小鸟,发射之前没用轨迹预测。

在这里插入图片描述


我们可以不可以在发射之前就预测出曲线轨迹并显示出来呢?
本文就来教大家如何在Unity中实现物理运动曲线预测吧。

本文最终效果

在这里插入图片描述


本文Demo工程已上传CodeChina,感兴趣的同学可自行下载学习。
地址:https://codechina.csdn.net/linxinfa/UnityAngryBirdCurveTrajectory
注:我使用的Unity版本:2020.2.7f1c1 (64-bit)

在这里插入图片描述

二、思考分析

根据高中物理的斜抛运动路径公式:
水平方向: s x = v x t s_x=v_xt sx=vxt
竖直方向: s y = v y t − ( 1 / 2 ) g t 2 s_y=v_yt-(1/2)gt^2 sy=vyt(1/2)gt2
我们只要知道 v x v_x vx v y v_y vy即可预测出曲线的轨迹,而 ( v x,v y ) (v_x,v_y) (vx,vy)就是速度向量。
那么,我们怎么知道 ( v x,vy)呢?
手指按下的位置作为起始点,手指抬起的位置作为结束点,起始点 - 结束点即可得到一个从结束点指向起始点的向量,这个就是我们要的速度向量 ( v x,vy)

在这里插入图片描述


现在我们翻译成代码,先定义些必要的变量:

/// <summary>
/// 手指的起始点
/// </summary>
private Vector2 m_startPoint;
/// <summary>
/// 手指的结束点
/// </summary>
private Vector2 m_endPoint;
/// <summary>
/// 起始点和结束点的距离
/// </summary>
private float m_distance;
/// <summary>
/// 方向向量,从结束点指向起始点的归一化向量
/// </summary>
private Vector2 m_direction;

起始点和结束点的坐标,可以通过手指的屏幕坐标转世界坐标得到,例:

// 起始点坐标
m_startPoint = Camera.main.ScreenToWorldPoint(Input.mousePosition);
// ...

// 结束点坐标
m_endPoint = Camera.main.ScreenToWorldPoint(Input.mousePosition);

手指抬起的时候,计算速度向量:

// 放大速度倍数
float factor = 4f;

m_distance = Vector2.distance(m_startPoint, m_endPoint);
m_direction = (m_startPoint - m_endPoint).normalized;
Vector2 speed = m_direction * m_distance * factor;

有了这个speed,我们就可以预测轨迹了。
假设鸟的坐标为Vector3 birdPos,根据斜抛路径公式,那么预测曲线轨迹点的坐标(posX,posY)就是这样:

float posX = birdPos.x + speed.x * t;
float posY = birdPos.x + speed.y * t - 0.5f * Physics2D.gravity.magnitude * t * t;

注意,上面我们用了Physics2D.gravity.magnitude这个值,它就是重力加速度g
它的值可以在Project Settings中设置:

在这里插入图片描述


另外,我们需要让鸟根据初始的speed做斜抛运动,这里要用到Rigidbody2DAddForce接口,例:

rigidbody2D.AddForce(speed, ForceMode2D.Impulse);

好了,思考完毕,开始动手实际操作吧。

三、场景搭建

1、导入图片素材

为了方便演示,我们先找点图片素材搭建场景,素材图片

请添加图片描述


请添加图片描述

请添加图片描述

请添加图片描述

2、鸟预设

先做个鸟预设,

在这里插入图片描述


身上挂上必要的物理组件,

在这里插入图片描述


为了让鸟弹到地面上之后有一个弹性碰撞,我们创建一个Physics Material 2D,

在这里插入图片描述


重名名为bounciness(这个图标很形象呀)

在这里插入图片描述


设置摩擦力和弹力分别为0.50.6

在这里插入图片描述

3、地面环境

做个地面预设,左右两边放两个带碰撞体的石柱,防止鸟飞出屏幕外。

在这里插入图片描述


地面添加两个碰撞体,不然鸟会掉到屏幕外。

在这里插入图片描述

4、曲线的点预设

为了描绘曲线,我们用这个小云团作为一个个点,

请添加图片描述


将其做成预设,只需要基本的SpriteRenderer组件即可。

在这里插入图片描述

5、预览效果

运行Unity,预览下效果,可以看到,我们的鸟已经具备物理特性了。

在这里插入图片描述


接下来就是写代码实现我们的业务逻辑了。

四、代码

撸起袖子写代码代码如下,注释我写得比较清楚,就不赘述了。

1、鸟脚本:Bird.cs

// Bird.cs

using UnityEngine;

/// <summary>
/// 鸟
/// </summary>
public class Bird : MonoBehavIoUr
{
    [HideInInspector] public Rigidbody2D rb;
    [HideInInspector] public CircleCollider2D col;
    [HideInInspector] public Vector3 pos
    {
        get { return transform.position; }
    }

    private void Awake()
    {
        rb = GetComponent<Rigidbody2D>();
        col = GetComponent<CircleCollider2D>();
    }

    /// <summary>
    /// 给鸟一个推力,将鸟推出去
    /// </summary>
    /// <param name="speed">速度向量</param>
    public void Push(Vector2 speed)
    {
        rb.AddForce(speed, ForceMode2D.Impulse);
    }

    /// <summary>
    /// 激活物理
    /// </summary>
    public void ActivateRb()
    {
        rb.isKinematic = false;
    }

    /// <summary>
    /// 禁用物理
    /// </summary>
    public void DesActivateRb()
    {
        rb.veLocity = Vector3.zero;
        rb.angularVeLocity = 0f;
        rb.isKinematic = true;
    }
}

2、曲线预测器:Trajectory.cs

// Trajectory.cs

using UnityEngine;

/// <summary>
/// 曲线预测器
/// </summary>
public class Trajectory : MonoBehavIoUr
{
    /// <summary>
    /// 预测点的数量
    /// </summary>
    [Serializefield] private int m_dotsNum = 20;
    /// <summary>
    /// 点物体的父节点
    /// </summary>
    [Serializefield] private GameObject m_dotsParent;
    /// <summary>
    /// 点预设
    /// </summary>
    [Serializefield] private GameObject m_dotsPrefab;
    /// <summary>
    /// 点间距
    /// </summary>
    [Serializefield] private float m_dotSpacing = 0.01f;
    /// <summary>
    /// 点的最小缩放
    /// </summary>
    [Serializefield] [Range(0.01f, 0.3f)] private float m_dotMinScale = 0.1f;
    /// <summary>
    /// 点的最大缩放
    /// </summary>
    [Serializefield] [Range(0.3f, 1f)] private float m_dotMaxScale = 1f;


    private Transform[] m_dotsList;
    private Vector2 m_pos;
    private float m_timeStamp;

    private void Start()
    {
        Hide();
        PrepareDots();
    }

    /// <summary>
    /// 准备轨迹点
    /// </summary>
    private void PrepareDots()
    {
        m_dotsList = new Transform[m_dotsNum];
        m_dotsPrefab.transform.localScale = Vector3.one * m_dotMaxScale;
        float scale = m_dotMaxScale;
        float scaleFactor = scale / m_dotsNum;

        for (int i = 0; i < m_dotsNum; ++i)
        {
            var dot = Instantiate(m_dotsPrefab).transform;
            dot.parent = m_dotsParent.transform;
            dot.localScale = Vector3.one * scale;
            if (scale > m_dotMinScale)
                scale -= scaleFactor;
            m_dotsList[i] = dot;
        }
    }

    /// <summary>
    /// 更新点坐标
    /// </summary>
    /// <param name="birdPos">鸟的坐标</param>
    /// <param name="pushSpeed">初始速度向量</param>
    public void UpdateDots(Vector2 birdPos, Vector2 pushSpeed)
    {
        m_timeStamp = m_dotSpacing;
        
        for (int i = 0; i < m_dotsNum; ++i)
        {
            m_pos.x = birdPos.x + pushSpeed.x * m_timeStamp;
            m_pos.y = (birdPos.y + pushSpeed.y * m_timeStamp) - 0.5f * Physics2D.gravity.magnitude * m_timeStamp * m_timeStamp;
            m_dotsList[i].position = m_pos;
            m_timeStamp += m_dotSpacing;
        }
    }

    /// <summary>
    /// 显示预测轨迹
    /// </summary>
    public void Show()
    {
        m_dotsParent.SetActive(true);
    }

    /// <summary>
    /// 隐藏预测轨迹
    /// </summary>
    public void Hide()
    {
        m_dotsParent.SetActive(false);
    }
}

3、游戏管理器:GameManager.cs

// GameManager.cs
using UnityEngine;

/// <summary>
/// 游戏管理器
/// </summary>
public class GameManager : MonoBehavIoUr
{
    /// <summary>
    /// 鸟
    /// </summary>
    public Bird bird;
    /// <summary>
    /// 轨迹预测器
    /// </summary>
    public Trajectory trajectory;

    /// <summary>
    /// 主摄像机
    /// </summary>
    private Camera m_cam;

    /// <summary>
    /// 力大小
    /// </summary>
    [Serializefield]
    private float m_speedFactor = 4f;

    /// <summary>
    /// 是否拉动中
    /// </summary>
    private bool m_isDragging = false;
    /// <summary>
    /// 手指的起始点
    /// </summary>
    private Vector2 m_startPoint;
    /// <summary>
    /// 手指的结束点
    /// </summary>
    private Vector2 m_endPoint;
    /// <summary>
    /// 起始点和结束点的距离
    /// </summary>
    private float m_distance;
    /// <summary>
    /// 方向向量,从结束点指向起始点的归一化向量
    /// </summary>
    private Vector2 m_direction;
    /// <summary>
    /// 力向量
    /// </summary>
    private Vector2 m_pushSpeed;


    private void Start()
    {
        m_cam = Camera.main;
        bird.DesActivateRb();
    }

    private void Update()
    {
        // 检测鼠标/手指行为
        if(Input.GetMouseButtonDown(0))
        {
            m_isDragging = true;
            OnDragStart();
        }
        if (Input.GetMouseButtonUp(0))
        {
            m_isDragging = false;
            OnDragEnd();
        }

        if (m_isDragging)
        {
            OnDrag();
        }
    }

    /// <summary>
    /// 开始拉
    /// </summary>
    private void OnDragStart()
    {
        // 禁用物理
        bird.DesActivateRb();
        // 起始点
        m_startPoint = m_cam.ScreenToWorldPoint(Input.mousePosition);
        // 显示轨迹
        trajectory.Show();
    }

    /// <summary>
    /// 拉中
    /// </summary>
    private void OnDrag()
    {
        m_endPoint = m_cam.ScreenToWorldPoint(Input.mousePosition);
        m_distance = Vector2.distance(m_startPoint, m_endPoint);
        m_direction = (m_startPoint - m_endPoint).normalized;
        m_pushSpeed = m_direction * m_distance * m_speedFactor;

        trajectory.UpdateDots(bird.pos, m_pushSpeed);
    }

    /// <summary>
    /// 拉结束
    /// </summary>
    private void OnDragEnd()
    {
        bird.ActivateRb();
        bird.Push(m_pushSpeed);
        // 隐藏轨迹
        trajectory.Hide();
    }
}

五、挂脚本

鸟预设上挂Bird脚本。

在这里插入图片描述


曲线预测器挂Trajectory脚本,并赋值对应的参数。

在这里插入图片描述


游戏管理器挂GameManager脚本,并赋值对应的参数。

在这里插入图片描述

六、运行测试

运行Unity,测试效果如下,可以看到,飞行轨迹与我们的预测的曲线轨迹一致。

在这里插入图片描述


完毕。
喜欢Unity的同学,不要忘记点击关注,如果有什么Unity相关的技术难题,也欢迎留言或私信~

相关文章

前言 本文记录unity3D开发环境的搭建 unity安装 unity有中文...
前言 有时候我们希望公告牌跟随镜头旋转永远平行面向屏幕,同...
前言 经过一段时间的学习与实际开发,unity3D也勉强算是强行...
前言 在unity中我们常用的获取鼠标点击的方法有: 1、在3D场...
前言 在之前的例子中,我们都没有用到unity的精髓,例如地形...
这篇文章将为大家详细讲解有关Unity3D中如何通过Animator动画...