山寨小小军团开发笔记 之 Arrow Projectile

    好久没怎么更新博客了,今天抽空来一篇,讨论一下弓箭的轨迹生成。

一、原理

      弓箭的轨迹本质就是一个数学问题,使用一个 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, 所以图片的旋转预先是这样。

 

 

四、结果

 

posted @ 2015-03-15 17:45  灵魂重新  阅读(429)  评论(0编辑  收藏  举报