好久没怎么更新博客了,今天抽空来一篇,讨论一下弓箭的轨迹生成。
一、原理
弓箭的轨迹本质就是一个数学问题,使用一个 bezier 曲线公式就可以插值生成。得到轨迹后,做一个lookAt就可以了。
二、Bezier 曲线原理
2015-5-15 :相关原理介绍,我就不重复了
http://zh.wikipedia.org/wiki/%E8%B2%9D%E8%8C%B2%E6%9B%B2%E7%B7%9A
http://devres.zoomquiet.io/data/20110728232822/index.html
我这里贴一下应用代码
public class Bezier
{
public Vector3 p0 = Vector3.zero;
public Vector3 p1 = Vector3.zero;
public Vector3 p2 = Vector3.zero;
public Bezier(Vector3 v0, Vector3 v1, Vector3 v2)
{
this.p0 = v0;
this.p1 = v1;
this.p2 = v2;
}
public void UpdateTargetPos(Vector3 v2)
{
p2 = v2;
}
public Vector3 GetPointAtTime(float t)
{
float x = (1 - t) * (1 - t) * p0.x + 2 * t * (1 - t) * p1.x + t * t * p2.x;
float y = (1 - t) * (1 - t) * p0.y + 2 * t * (1 - t) * p1.y + t * t * p2.y;
float z = (1 - t) * (1 - t) * p0.z + 2 * t * (1 - t) * p1.z + t * t * p2.z;
return new Vector3(x, y, z);
}
}
三、例子分析
搭建一个测试场景,从中心点发射弓箭诡异

Center上挂载一个脚本
BezierTest.cs
public GameObject arrowPrefab;
public Transform left;
public Transform right;
void Test(bool fireRight)
{
Transform end = fireRight ? right : left;
///在中心点生成弓箭
GameObject curArrow = arrowPool.Spawn(transform.position, Quaternion.Euler(Vector3.zero));
///计算LookTarget的点 与 贝塞尔曲线的第三个控制点
Vector3[] points = Re_LookTarget_MiddlePerpendicularPoint(curArrow.transform, end);
///初始化发射
ArrowControl arrowControl = curArrow.GetComponent<ArrowControl>();
arrowControl.Init(
points[0],
points[1],
end.position,
3.0f,
delegate()
{
arrowPool.Unspawn(curArrow);
});
}
Vector3[] Re_LookTarget_MiddlePerpendicularPoint(Transform self, Transform enemy)
{
Vector3 direction = (enemy.position - self.position).normalized;
float segment = Vector2.Distance(enemy.position, self.position) / 2.0f;
Vector3 lookTarget = (self.position + enemy.position) / 2.0f;
Vector3 perpendicular_direction;
if (direction.x > 0)
{
perpendicular_direction = new Vector3(-direction.y, direction.x, 0);
}
else
{
perpendicular_direction = new Vector3(direction.y, -direction.x, 0);
}
///perpendicular line
Vector3 middle_pendicular = lookTarget + perpendicular_direction * segment;
return new Vector3[] { lookTarget, middle_pendicular };
}
void OnGUI()
{
if (GUI.Button(new Rect(10, 10, 150, 100), "Fire Left")) Test(false);
if (GUI.Button(new Rect(160, 10, 150, 100), "Fire Right")) Test(true);
}
预设上挂载的控制脚本
ArrowControl.cs
float alltimer;
Util.Bezier bezier = null;
float timer = 0f;
Callback recycleFunction;
Vector3 lookAtTarget;
//bool defaultRightNeedTurn = false;;
public void Init(Vector3 lookAtTarget, Vector3 middle, Vector3 end, float alltimer, Callback recycleFunction)
{
this.lookAtTarget = lookAtTarget;
this.bezier = new Util.Bezier(transform.position, middle, end);
this.alltimer = alltimer;
Flip(end.x, transform.position.x);
this.recycleFunction = recycleFunction;
}
void Flip(float end_X, float self_X)
{
Transform pic = transform.Find("pic");
if (end_X < self_X) {
pic.localScale = new Vector3(-1.0f, 1.0f, 1.0f);
}
else {
pic.localScale = new Vector3( 1.0f, 1.0f, 1.0f);
}
}
public void Clear()
{
this.bezier = null;
timer = 0f;
}
void Update()
{
if (this.bezier != null)
{
timer += Time.deltaTime;
float t = timer / alltimer;
//Debug.Log("timer" + timer + " t " + t);
if (t >= 1.0f) {
Clear();
if (recycleFunction != null) recycleFunction();
}
else {
transform.LookAt(lookAtTarget, Vector3.forward);
transform.position = bezier.GetPointAtTime(t);
}
}
}
这里注意,对于LookAt这个行为,我没有很好的数学方法,所以用了U3D的API。
但是这个API 只能保证Z轴始终指向target, 所以图片的旋转预先是这样。

四、结果
