好久没怎么更新博客了,今天抽空来一篇,讨论一下弓箭的轨迹生成。
一、原理
弓箭的轨迹本质就是一个数学问题,使用一个 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, 所以图片的旋转预先是这样。
四、结果