unity 貪喫蛇作戰—1

   前段時間看見有一個遊戲比較火,一直沒有時間模仿着寫,最近空閒 抽出幾天時間寫寫試試吧。做出來的只能算是一個demo吧。貪喫蛇的原理應該很簡單吧,基本說一下,就是當蛇動起來的時候,蛇頭的位置被重新賦值,蛇的第二段賦原先蛇頭的位置,第三段賦原先第二段的的位置。所以原理其實很簡單。就是一個數組每幀對數組進行修改,然後新對蛇段位置重新賦值。下面先給幾張圖片





該項目只能算一個demo因爲有好多細節的東西沒有做,比如接sdk,其他的ui的一些設置都沒有,目前只有一個模式,遊戲分爲玩家蛇和一些AI蛇,所以我們需要建2個類他們分別爲PlayerSnake和AISanke,同時他們都有一些共同點,他們的移動方式是一樣的都是蛇頭先賦值 然後蛇身依次根據前面一個蛇身的位置進行賦值。並且他們都有蛇頭和蛇身之分,只是他們的控制方式不同,具體的講就是他們得到蛇頭的移動方向方式不同,一種是通過虛擬搖桿來控制蛇頭的移動方向,另外一種是通過具體的AI的控制蛇頭的移動方向。所以玩家蛇應該來說比較好寫,所以我們需建一個BaseSnake 讓玩家蛇和AI蛇都繼承BaseSnake,BaseSnake裏面就應該只有蛇的運動原理及一些基本的屬性和字段了。先貼出BaseSnake代碼

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using Random = UnityEngine.Random;

namespace Snake
{
    public abstract class BaseSnake : MonoBehaviour,IComparable
    {

        public GameObject SnakeHead { set; get; }

        public float SpeeRatio { set; get; }

        public string NickName { set; get; }

        public int SnakeIndex { set; get; }

        public List<GameObject> SnakeNodes { set; get; }

        public int Count { get { return SnakeNodes.Count; } }

        private List<Vector3> _wayPoints;

        public Vector3 MoveDir;

        #region protected Variable
        
        protected Vector3 _tempDir;
        protected RaycastHit _hit;

        protected int _snakeNodeCount;
        protected int _snakeSkin;
        protected SkinsTable.SkinItem _skinItem;
        protected int _eatFoodCount;

        #endregion

        public virtual void OnAwake()
        {
            SnakeNodes=new List<GameObject>();
            _wayPoints=new List<Vector3>();

            SpeeRatio = 1;
            NickName = GetStr.GetNickName();
        }

        public virtual void OnUpdate()
        {
            
        }
       

        #region Private Method

        private void AddWayPoint(Vector3 point)
        {
            _wayPoints.Add(point);
        }

        private void AddNodeObj(GameObject obj)
        {
            SnakeNodes.Add(obj);
        }

        private void AddSnakeNodes(int count, Vector3 originVector3)
        {
            Vector3 originPos = originVector3;
            for (int i = 0; i < count; i++)
            {
                if (i <= count - 1)
                {
                    GenerateBlankBody(originPos);
                }

                var body = new GameObject("Body", typeof(SpriteRenderer));

                body.tag = "Body";
                body.layer = LayerMask.NameToLayer("Body");
                body.transform.position = originVector3 - 0.2f * MoveDir.normalized * (i + 1);
                originPos = originVector3 - 0.2f * MoveDir.normalized * (i + 1);

                if (_skinItem.Body1Sprites != null && _skinItem.Body2Sprites != null)
                    body.GetComponent<SpriteRenderer>().sprite = i % 2 == 0 ? _skinItem.Body1Sprites : _skinItem.Body2Sprites;
                else
                {
                    body.GetComponent<SpriteRenderer>().sprite = _skinItem.Body1Sprites;
                }

                body.AddComponent<CircleCollider2D>().isTrigger = true;
                body.AddComponent<BodyIndex>().ParentSnakeIndex = SnakeIndex;

                float value = MathTool.GetAngleByVector(new Vector2(MoveDir.x, MoveDir.y));
                body.transform.eulerAngles = new Vector3(0, 0, value - 90);
                body.transform.SetParent(transform);

                AddNodeObj(body);
                AddWayPoint(body.transform.position);

            }
        }

        private void GenerateBlankBody(Vector3 originVector3)
        {
            Vector3 pos = Vector3.zero;
            for (int i = 1; i < 4; i++)
            {
                pos = originVector3 - MoveDir.normalized * 0.2f / 4f * i;
                AddWayPoint(pos);
            }
        }

        private void MoveToTargetPos(Vector3 targetPos)
        {
            for (int i = _wayPoints.Count - 1; i >= 1; i--)
            {
                _wayPoints[i] = _wayPoints[i - 1];
            }
            _wayPoints[0] = new Vector3(targetPos.x, targetPos.y, 0);
            for (int i = 0; i < SnakeNodes.Count; i++)
            {
                SnakeNodes[i].transform.position = _wayPoints[i * 4];
            }

        }

        #endregion


        #region Public Method

        public virtual  void IntilizedSnakePos(Vector3 pos, Vector3 direction,int snakeIndex)
        {
            transform.position = pos;
            MoveDir = direction;
            SnakeIndex = snakeIndex;
        }

        public virtual void IntilizedSnakeNodes(int originCount, int skinIndex,string nickName=null)
        {
            OnAwake();
            _skinItem = SkinsTable.Instance.GetItemByIndex(skinIndex);
            SnakeHead = new GameObject("SnakeHead", typeof(SpriteRenderer));

            SnakeHead.tag = "Head";
            SnakeHead.layer = LayerMask.NameToLayer("Body");

           

            SnakeHead.transform.SetParent(transform);
            SnakeHead.transform.localPosition = Vector3.zero;
            SnakeHead.GetComponent<SpriteRenderer>().sprite = _skinItem.HeadSprite;
            SnakeHead.AddComponent<Rigidbody2D>().gravityScale = 0;
            var circleColider = SnakeHead.AddComponent<CircleCollider2D>();
            circleColider.isTrigger = true;
            circleColider.radius = 0.13f;
            SnakeHead.AddComponent<BodyIndex>().ParentSnakeIndex = SnakeIndex;
            float angle = MathTool.GetAngleByVector(new Vector2(MoveDir.x, MoveDir.y));
            SnakeHead.transform.eulerAngles = new Vector3(0, 0, angle - 90);


            Vector3 tempPos = SnakeHead.transform.position;

            AddNodeObj(SnakeHead);
            AddWayPoint(SnakeHead.transform.position);
            AddSnakeNodes(originCount, tempPos);
        }

        public void EatFood(GameObject food)
        {

            var foodColor = food.GetComponent<FoodColor>();
            _eatFoodCount += foodColor.Weight;
            int count = _eatFoodCount / 4;
            _eatFoodCount %= 4;
            if (count != 0)
            {
                AddSnakeNodes(count);
            }


            if (foodColor.Weight <= 1)
            {

                foodColor.IntilizedColor();
                foodColor.IntilizedWeight(1);

                float x = Random.Range(-13f, 13f);
                float y = Random.Range(-7f, 7f);
                food.transform.localPosition = new Vector3(x, y, 0);
            }
            else
            {
                Destroy(food);
            }
           
        }

        #endregion


        #region Protected Method

        protected void RotateHead()
        {
            float value = MathTool.GetAngleByVector(new Vector2(MoveDir.x, MoveDir.y));
            SnakeHead.transform.eulerAngles = new Vector3(0, 0, value - 90);
            Vector3 tempPos = SnakeHead.transform.position;
            for (int i = 1; i < SnakeNodes.Count; i++)
            {
                Vector3 dir = (tempPos - SnakeNodes[i].transform.position);
                var angle = MathTool.GetAngleByVector(new Vector2(dir.x, dir.y));
                SnakeNodes[i].transform.eulerAngles = new Vector3(0, 0, angle);
            }

        }

        protected void MoveSnake()
        {
            for (int i = 0; i < SpeeRatio; i++)
            {
                Vector3 endPos = Vector3.zero;
                if (SnakeNodes[0] != null)
                    endPos = SnakeNodes[0].transform.position + 0.2f / 4f * MoveDir.normalized;
                MoveToTargetPos(endPos);
            }
        }

        protected void AddSnakeNodes(int count)
        {
            Vector3 tempPos = SnakeNodes[SnakeNodes.Count - 1].transform.position;
            AddSnakeNodes(count, tempPos);
        } 

        #endregion

        public int CompareTo(object obj)
        {
            var snake = obj as BaseSnake;
            if (this.Count > snake.Count)
                return 1;
            if (this.Count == snake.Count)
                return 0;
            if (this.Count < snake.Count)
                return -1;
            return -1;
        }
    }
}
總共代碼就200多行,這些私有方法就不用看了,只需看看Public方法和一些Protect方法了,首先讓2個有關生命週期的方法OnAwake和OnUpdate方法虛化,目的是讓他的2個子類去重寫它,另外有2個初始化的方法也被虛化了,其實IntilizedSnakePos這個方法沒有虛化的必要性,因爲在他們的子類中都沒有重寫,這樣寫的原因只是爲了看起來比較好看一些。另外還有一個EatFood,這個方法玩家蛇和AI蛇都應該具有的行爲。至於RotateHead,MoveSnake和AddSnakeNodes都是一些關於蛇移動處理和蛇身添加處理的方法,RotateHead主要旋轉蛇頭並且旋轉蛇身的方向。MoveSnake主要是移動整條蛇。最後一個方法純粹就是給蛇添加蛇身。旋轉蛇頭涉及一個簡單的數學知識。這裏可以簡單的講講,就是我們再二維座標中隨便給出一個向量,我們就知道它的方向,例如(1,0)我們知道它對應得旋轉量爲(0,0,0),(0,1)對應得旋轉量爲(0,0,90),所以我們的根據具體的向量來計算出蛇頭的朝向問題。首先我們需要讓蛇頭的移動向量和(1,0)做點乘,然後通過點乘求得一個角度,這個角度在0到180度之間,因爲點乘的求得只是夾角。例如向量(0,1)我們求得夾角爲90,那麼它對應的歐拉角可能是(0,0,90)和(0,0,-90)了,所以我們得從其中選取正確的一個值,這是我們再做一下簡單的叉乘,用叉乘求出來一個向量,判斷向量實在朝上還是朝下,如果是朝上說明正90,相反就是-90了,具體方法如下

 public static float GetAngleByVector(Vector2 dir)
        {
            Vector2 relativeVector = new Vector2(1, 0);
            float value = Vector2.Dot(dir.normalized, relativeVector);
            float angle = Mathf.Acos(value) * Mathf.Rad2Deg;

            float value1 = relativeVector.x*dir.y - relativeVector.y*dir.x;
            angle *= value1 > 0 ? 1 : -1;
            return angle;
        }
遊戲雖然是很簡單,但這樣說的目的只是在怎麼去組織自己的代碼。下節繼續把這個demo說完。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章