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的主要内容,如果未能解决你的问题,请参考以下文章