unity 贪吃蛇作战—2

Posted CZandQZ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了unity 贪吃蛇作战—2相关的知识,希望对你有一定的参考价值。

  今天接着上个讲,上次讲到了玩家蛇和BaseSnake类了,下面讲讲AI蛇了,这里我写的AI是比较简单了。AI蛇的AI逻辑是这样的,蛇主要存在2中状态,一种是漫游状态,漫游状态每隔多少秒,给蛇一个方向,然后蛇慢慢的旋转到给的方向。这里用的方法是前面讲到的行为那一节,简单的画个图解释一下

当AI蛇的方向为A向量,这是我们需要要求蛇往B向量这个方向移动,这时的做法就是

B-A=C;那么蛇的每帧的实际方向向量为Dir=x1*A+C*x2;然后A=Dir向量,其中x1和x2是权重,可以随便赋如果x1很小而x2很大的话 那么蛇想B向量靠近的时间就会很短 相反就会很长。这个权重还有根据具体分析,如果AI蛇检测到前面有一堵墙的话,这里检测没有采用射线检测因为射线检每帧检测会比较消耗性能。所以采用的方法是在蛇头前面添加2个碰撞器,判断是左边的碰撞器还是右边的碰撞器碰撞到障碍(其他的蛇和边界)来获得躲避的向量。接下来贴出具体的AI代码。

using UnityEngine;

namespace Snake

    public class AISnake : BaseSnake
    

        [SerializeField] private TextMesh _textMesh;


        public AIRayCast LeftAiRayCast  set; get; 

        public AIRayCast RightAiRayCast  set; get; 

        public float DogRate  set; get; 

        private float _targetAngle;


        private float _wanderTime;
        private float _totalTime;
        private Vector3 steer;
        private bool b;
        private AISankesMrg _aiSankesMrg;

        public float Denominator  set; get; 

        public float Molecular  set; get; 


        public override void OnUpdate()
        

            #region Hit

            if (LeftAiRayCast.IsHit && RightAiRayCast.IsHit)
            
                b = true;
                _wanderTime = 0;
                _targetAngle = Random.Range(10, 30) + 90;
                steer = Quaternion.Euler(0, 0, _targetAngle) * MoveDir;

            
            if (LeftAiRayCast.IsHit && !RightAiRayCast.IsHit)
            
                b = true;
                _wanderTime = 0;
                _targetAngle = -Random.Range(10, 30) - 90;
                steer = Quaternion.Euler(0, 0, _targetAngle) * MoveDir;

            
            if (!LeftAiRayCast.IsHit && RightAiRayCast.IsHit)
            
                b = true;
                _wanderTime = 0;
                _targetAngle = Random.Range(10, 30) + 90;
                steer = Quaternion.Euler(0, 0, _targetAngle) * MoveDir;
            
            
            #endregion

            #region NotHit

            if (!LeftAiRayCast.IsHit && !RightAiRayCast.IsHit)
            
                _wanderTime += Time.deltaTime;
                if (_wanderTime >= _totalTime)
                
                    b = true;
                    _wanderTime = 0;
                    _totalTime = Random.Range(1, 3);
                    steer = Random.insideUnitCircle.normalized;

                    Denominator = 20;
                    Molecular = 1;
                
            
            else
            
                _wanderTime = 0;
            

            Vector3 temp = steer - MoveDir;
            if (Vector2.Angle(steer, MoveDir) > 1)
            
                if (b)
                
                    Vector3 targetDir = (temp.normalized * Molecular + MoveDir * Denominator).normalized;
                    MoveDir = targetDir;
                
            
            else
            
                b = false;
             

            #endregion

            LeftAiRayCast.IsHit = false;
            RightAiRayCast.IsHit = false;

            MoveSnake();
            RotateHead();

            Vector3 textPos = SnakeHead.transform.position + new Vector3(0, 0.3f, 0);
            _textMesh.transform.position = textPos;
        

        public override void IntilizedSnakeNodes(int originCount, int skinIndex, string nickName = null)
        
            _totalTime = Random.Range(1, 3);
            base.IntilizedSnakeNodes(originCount, skinIndex, nickName);
            SnakeHead.AddComponent<SnakeHeadRayCast>().IntilizedAiSanke(this);
            LeftAiRayCast = GenerateDetection("LeftDetection", new Vector2(-0.05f, 0.21f), new Vector2(0.08f, 0.15f));
            RightAiRayCast = GenerateDetection("RightDetection", new Vector2(0.05f, 0.21f), new Vector2(0.08f, 0.15f));

            Vector3 textPos = SnakeHead.transform.position + new Vector3(0, 0.3f, 0);
            _textMesh.transform.position = textPos;
            _textMesh.text = NickName;

        

        public void IntilizedSnakeMrg(AISankesMrg mrg)
        
            _aiSankesMrg = mrg;
        

        public void Death()
        
            _aiSankesMrg.RemoveSnake(this);
            for (int i = 1; i < SnakeNodes.Count; i++)
            
                var node = SnakeNodes[i];
                node.transform.tag = "Food";
                node.layer = LayerMask.NameToLayer("Food");
                Vector2 pos = node.transform.position;
                pos = pos + Random.insideUnitCircle/10;
                if (i != 1)
                    node.transform.position = pos;
                Destroy(node.GetComponent<BodyIndex>());
                FoodsMrg.Instance.AddFood(SnakeNodes[i]);
            
            Destroy(gameObject);
            _aiSankesMrg.AddToTenSnake();

        


        private AIRayCast GenerateDetection(string name, Vector2 offset, Vector2 size)
        
            var obj = new GameObject(name);
            var boxColider = obj.AddComponent<BoxCollider2D>();
            var aiRayCast = obj.AddComponent<AIRayCast>();
            aiRayCast.IntilizedAiSanke(this);
            obj.AddComponent<Rigidbody2D>().gravityScale = 0;
            boxColider.offset = offset;
            boxColider.size = size;
            boxColider.isTrigger = true;

            obj.transform.SetParent(SnakeHead.transform);
            obj.transform.localPosition = Vector3.zero;
            obj.transform.localEulerAngles = Vector3.zero;

            return aiRayCast;
        
    

首先重写OnUpdate生命周期的方法。还有重写了IntilizedSnakeNodes初始化的一个方法。然后写了一个死亡Death方法,私有方法GenerateDetection作用是生成左右2个检测器,最后一个方法就是初始化它的管理器AISankesMrg,AISankesMrg作用是管理所有的AI蛇。AISankesMrg主要作用是生成蛇,然后销毁蛇,另外一个重要的作用的就是统一管理AI蛇的生命周期。

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

namespace Snake

    public class AISankesMrg
    
        public static List<AISnake> AiSnakes;

        private List<Vector3> _pointList; 
        private GameObject _aiSnakePrefab;
        private int _posIndex;
        private int _snakeCount;
        public AISankesMrg(GameObject prefab, List<Vector3> pointList)
        
            AiSnakes = new List<AISnake>();
            _pointList = pointList;
            _aiSnakePrefab = prefab;
            _snakeCount = 0;
        

        public void OnUpdate()
        
            for (int i = 0; i < AiSnakes.Count; i++)
            
                AiSnakes[i].OnUpdate();
            
        

        public void AddToTenSnake()
        
            int value = 10 - AiSnakes.Count;
            GenerateSnake(value);
        

        public void GenerateSnake(int count)
        
            List<int> ints = SkinsTable.Instance.SkinInts;
            for (int i = 0; i < count; i++)
            
                int skinindex = ints[UnityEngine.Random.Range(0, ints.Count)];
                SkinsTable.Instance.GetItemByIndex(skinindex);
                AddSnake(i + 1 + _snakeCount, skinindex, _pointList[_posIndex]);

                _posIndex++;
                _posIndex %= _pointList.Count;
            
            _snakeCount += count;
          
        

        public void AddSnake(int snakeIndex,int skinIndex,Vector3 pos)
        

            GameObject obj = GameObject.Instantiate(_aiSnakePrefab, new Vector3(0, 2, 0), Quaternion.identity);
            obj.name = "AiSnake" + snakeIndex;
            var aiSnake = obj.GetComponent<AISnake>();
            aiSnake.IntilizedSnakePos(pos, UnityEngine.Random.insideUnitCircle, snakeIndex);
            aiSnake.IntilizedSnakeNodes(10, skinIndex);
            aiSnake.IntilizedSnakeMrg(this);

            AiSnakes.Add(aiSnake);

        

        public void ClearSnakes()
        
            _snakeCount = 0;
            for (int i = 0; i < AiSnakes.Count; i++)
            
                GameObject.Destroy(AiSnakes[i].gameObject);
            
            AiSnakes.Clear();
        

        public void RemoveSnake(AISnake aiSnake)
        
            AiSnakes.Remove(aiSnake);
        

    

接下来贴出PlayerSnake的代码,这段代码就比较简单了,首先重写OnUpdate生命周期,一个初始化蛇

using com.klw.lib.core;
using MainGame;
using UnityEngine;

namespace Snake

    public class PlayerSnake:BaseSnake
    

        [SerializeField] private TextMesh _textMesh;


        public override void IntilizedSnakeNodes(int originCount, int skinIndex, string nickName = null)
        
            base.IntilizedSnakeNodes(originCount, skinIndex, nickName);
             SnakeHead.AddComponent<SnakeHeadRayCast>().IntilizedPlayerSanke(this);
             Vector3 textPos = SnakeHead.transform.position + new Vector3(0, 0.3f, 0);
             _textMesh.transform.position = textPos;
            _textMesh.text = NickName;

             MessagingSystem.Instance.AttachListener(typeof(SpeedUpMessage), SpeedUp);
        

        public override void OnUpdate()
        
            MoveSnake();
            RotateHead();

            Vector3 textPos = SnakeHead.transform.position + new Vector3(0, 0.3f, 0);
            _textMesh.transform.position = textPos;
        

        public void SetMoveDir(Vector3 dirVector3)
        
            var temp = new Vector2(dirVector3.x, dirVector3.z);
            if (temp != Vector2.zero)
                MoveDir = temp;
        

        public void GameOver()
        
            new GameOverMessage().Send();
            MessagingSystem.Instance.DetachListener(typeof (SpeedUpMessage), SpeedUp);
        

        public bool SpeedUp(BaseMessage message)
        
            if (message is SpeedUpMessage)
            
                SpeeRatio = (message as SpeedUpMessage).SpeedRate;
            
            return true;
        

    

IntilizedSnakeNodes方法被重写了,重写之后其实就添加了一个蛇的名字代码和注册了蛇加速消息。

SetMoveDir设置蛇移动的方向,这里使用方法是  _joystackCc.DragAction = (v) => _playerSnake.SetMoveDir(v); ;给虚拟摇杆注册DragAction,就是遥感产生了偏移后同时设置蛇的移动方向。

GameOver方法作用是通知游戏结束。

接下来就是管理有戏整体的类GameLayer,这个类的作用管理UI管理器,声音管理器(这里我还没有写声音),及玩家蛇 AI蛇管理器,这个类就是最高层的管理器了。

using System.Collections.Generic;
using com.klw.lib.core;
using DataBind.Core.Presentation;
using MainGame;
using Snake;
using UI;
using UnityEngine;

namespace Game

    public class GameLayer : MonoBehaviour
    

        public enum GameState
        
            None,
            GenerateFood,
            GeneratePlayerSnake,
            GameOver,
            Restart
        

        private StateMachine<GameState> _fsm;
        private PlayerSnake _playerSnake;
        private AISankesMrg _aiSankesMrg;
      
        
       

        private bool _isPause;

        [SerializeField] private FoodsMrg _foodsMrg;
        [SerializeField] private int _foodPoolCount;
        [SerializeField] private GameObject _playerSnakePrefab;
        [SerializeField] private GameObject _aiSnakePrefab;
        [SerializeField] private CameraControl _cameraControl;
        [SerializeField] private List<Transform> _pointTransforms;
        [SerializeField] private JoystackCc _joystackCc;
        [SerializeField] private Canvas _canvas;

        public List<Vector3> PointList
        
            get
            
                List<Vector3> points = new List<Vector3>();
                for (int i = 0; i < _pointTransforms.Count; i++)
                
                    points.Add(_pointTransforms[i].position);
                

                return points;
            
        

        public static UIAdapter UiAdapter;
        public static PlayerDataPack PlayerDataPack;

        private void Awake()
        
            _isPause = true;
            GetStr.LoadContent();
            PlayerDataPack = new PlayerDataPack();
            UiAdapter = new UIAdapter(PlayerDataPack);
           
            _fsm = StateMachine<GameState>.Initialize(this, GameState.None);
            _aiSankesMrg = new AISankesMrg(_aiSnakePrefab, PointList);
            UiAdapter.OpenPlayGameDialog();

            MessagingSystem.Instance.AttachListener(typeof(GameOverMessage), SetGameOverHandler);
            MessagingSystem.Instance.AttachListener(typeof(GamePlayMessage), SetGamePlayHandler);
            MessagingSystem.Instance.AttachListener(typeof(RestartGameMessage), RestartGameHandler);
           
        

        private void Start()
        
            _fsm.ChangeState(GameState.GenerateFood);
            _joystackCc.DragAction = (v) =>  _playerSnake.SetMoveDir(v); ;
            _canvas.GetComponent<ContextHolder>().Context = UiAdapter.MainGameMenuContext;

            InvokeRepeating("RankSnake", 0, 1);
        

        private void Update()
        
            if (!_isPause)
            

                _playerSnake.OnUpdate();
                _cameraControl.OnUpdate();

                _aiSankesMrg.OnUpdate();
            
        

        private void RankSnake()
        
            List<RankData> snakes = new List<RankData>();
            snakes.Add(new RankData(_playerSnake.NickName,_playerSnake.Count));
            for (int i = 0; i < AISankesMrg.AiSnakes.Count; i++)
            
                var aisnake = AISankesMrg.AiSnakes[i];
                snakes.Add(new RankData(aisnake.NickName, aisnake.Count));
            

            snakes.Sort();
            snakes.Reverse();
            if (UiAdapter.MainGameMenuContext.RankItems.Count == 0)
            
                for (int i = 0; i < 5; i++)
                
                    MainGameMenuContext.RankItem item = new MainGameMenuContext.RankItem();
                    item.NickName = snakes[i].NickName;
                    item.Length = snakes[i].Count;
                    item.RankNum = i + 1;

                    UiAdapter.MainGameMenuContext.RankItems.Add(item);
                
            
            else
            
                for (int i = 0; i < UiAdapter.MainGameMenuContext.RankItems.Count; i++)
                
                    MainGameMenuContext.RankItem item = UiAdapter.MainGameMenuContext.RankItems[i];
                    item.NickName = snakes[i].NickName;
                    item.Length = snakes[i].Count;
                    item.RankNum = i + 1;
                 
            
            
        

        #region FSM

        [FSM("GenerateFood", FSMActionName.Enter)]
        private void GenerateFoodEnter()
        
            for (int i = 0; i < _foodPoolCount; i++)
            
                _foodsMrg.AddFood();
            
            _fsm.ChangeState(GameState.GeneratePlayerSnake);
        


        [FSM("GeneratePlayerSnake", FSMActionName.Enter)]
        private void GeneratePlayerSnakeEnter()
        
            _playerSnake = Instantiate(_playerSnakePrefab).GetComponent<PlayerSnake>();
            _playerSnake.IntilizedSnakePos(new Vector3(0, 0, 0), new Vector3(1, 0, 0), 0);
            _playerSnake.IntilizedSnakeNodes(20, 2);
            PlayerDataPack.SnakeLength = _playerSnake.Count;
            _cameraControl.Intilized(_playerSnake.SnakeHead.transform);

            _aiSankesMrg.GenerateSnake(10);
           

        


        [FSM("GameOver", FSMActionName.Enter)]
        private void GameOverEnter()
        
            UiAdapter.OpenGameOverDialog();
        


        [FSM("Restart", FSMActionName.Enter)]
        private void RestartGameEnter()
        
            _foodsMrg.ClearFoods();
            _aiSankesMrg.ClearSnakes();
            Destroy(_playerSnake.gameObject);
            _playerSnake = null;

            PlayerDataPack.KillNum = 0;
            _fsm.ChangeState(GameState.GenerateFood);
            _isPause = false;
        


        #endregion

        #region Message Handler

        private bool SetGamePlayHandler(BaseMessage message)
        
            var msg = message as GamePlayMessage;
            if (msg != null)
            
                _isPause = msg.IsPause;
            
            return true;
        

        private bool SetGameOverHandler(BaseMessage message)
        
            var msg = message as GameOverMessage;
            if (msg != null)
            
                _fsm.ChangeState(GameState.GameOver);
                _isPause = true;
              
            
            return true;
        

        private bool RestartGameHandler(BaseMessage message)
        
            if (message is RestartGameMessage)
            
                _fsm.ChangeState(GameState.Restart);
            
            return true;
         

        #endregion

        
    

当我们的UI和游戏逻辑产生交互的时候,我们需要一个类似于消息处理机制的思想来交互,说白了这个消息处理机制(这个消息处理我之前写过)就是单例,但是是封装了一下的单例,让我们理解起来比较容易同时爷比较容易管理。这个类的得功能是注册所有的消息,及消息的处理,还有就是游戏状态的改变,然后就是管理整个游戏的生命周期。
最后一节就把所有的代码及剩下的思路讲完,今天就讲到这里

以上是关于unity 贪吃蛇作战—2的主要内容,如果未能解决你的问题,请参考以下文章

javascript 贪吃蛇

蓝桥杯国赛真题9Scratch贪吃蛇 少儿编程scratch蓝桥杯国赛真题讲解

结对-贪吃蛇-需求分析

结对-结对编项目贪吃蛇-设计文档

c语言 贪吃蛇 程序

C语言课程设计,贪吃蛇应该怎么做?