文章目录
一、前言
嗨,大家好,我是新发。我们先来看一个画面,愤怒的小鸟,发射之前没用轨迹预测。
我们可以不可以在发射之前就预测出曲线轨迹并显示出来呢?
本文就来教大家如何在
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
做斜抛运动,这里要用到Rigidbody2D
的AddForce
接口,例:
rigidbody2D.AddForce(speed, ForceMode2D.Impulse);
好了,思考完毕,开始动手实际操作吧。
三、场景搭建
1、导入图片素材
2、鸟预设
先做个鸟预设,
身上挂上必要的物理组件,
为了让鸟弹到地面上之后有一个弹性碰撞,我们创建一个
Physics Material 2D
,重名名为
bounciness
(这个图标很形象呀)设置摩擦力和弹力分别为
0.5
、0.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
相关的技术难题,也欢迎留言或私信~