引言:小生今日分享的是经典贪吃蛇案例,特别感谢Siki学院的老师们。
这里附上原视频链接:
http://www.sikiedu.com/my/course/89
可以搭配起来学习哦!
小生会根据自己理解,做一些代码上的修改!大家也可以有自己的主见!
开发版本:unity 2017.1.1f1
适合人群:初学Unity者
源文件链接请见文末!
开启学习之旅吧!
效果预览:
主要实现功能:WASD键或上下左右键控制蛇移动方向,吃到冰淇淋加分,并且增长蛇身。游戏提供两种蛇的样式可选,而且有两种有无边界模式可选。记录当前得分和历史最高分。
01 场景搭建
新建2D工程,新建StartScene场景,Game场景设置为1280*720大小,导入资源
我们使用UGUI制作UI及人物,开始界面效果如下:
Canvas要设置为Screen Sapce Camera模式
需要注意的是,皮肤和模式分别只能二选一,Toggle Group的Allow Switch off不能勾选
新建主场景MainScene,效果如下:
因为需要开发一个边界模式,新建四个对象,放入场景四边,并添加碰撞体,勾选Is Trigger
注意四面墙分别命名为Up,Down,Left,Right,新建一个父对象统一管理
02 实现思路
有两种主要方式可以实现蛇的移动
第一种方式,从蛇尾的最后开始一节开始,依次向前一节蛇身的位置移动
第二种,蛇头每向前移动一个位置,就将蛇尾的最后一节移动到蛇头刚才的位置
我们选择第一种方式,因为我们的蛇有两种颜色相间的蛇身,如果第二种方式,会让蛇身的颜色混乱
03 开发蛇头
新建一个Image,命名为SnackHead,Source Image修改为蛇头图片,长宽设置为45*45,添加Rigidbody2D和BoxCollider2D组件,勾选Is Trigger,为其添加一个空的父物体Snack,方便管理蛇头和以后添加的蛇身
在SnakeHead上挂载脚本SnakeHead.cs
首先实现蛇头的移动,转头,空格键加速操作
实现主要思想,InvokeRepeating方法持续间隔一定时间调用控制蛇头移动的Move方法,然后Update方法监听按键,判断蛇头移动的XY增量
并实现蛇头转向,利用InvokeRepeating的间隔调用时间来控制蛇头移动的速度
需要注意根据实际情况设计蛇头移动步长Step的大小,最好能让蛇头在有限范围内移动整数步
public float velocity = 0.35f;
//每一步蛇头移动距离
public int step;
//x轴蛇头移动增量
private int x;
//y轴蛇头移动增量
private int y;
private void Start()
{
//初始化,让蛇头可以向上移动
x = 0;
y = step;
//InvokeRepeating等待0秒,然后每隔velocity时间调用Move方法
InvokeRepeating("Move", 0, velocity);
}
private void Update()
{
//虚拟轴控制移动
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
//Input.GetKeyDown键按下瞬间
if (Input.GetKeyDown(KeyCode.Space))
{
//CancelInvoke先取消之前的InvokeRepeating命令
CancelInvoke();
//将间隔调用“Move”方法的时间减小,则蛇移动变快
InvokeRepeating("Move", 0, velocity - 0.2f);
}
//Input.GetKeyUp键抬起瞬间
if (Input.GetKeyUp(KeyCode.Space))
{
CancelInvoke();
InvokeRepeating("Move", 0, velocity);
}
//如果此时y = -step说明蛇正在向下移动,为了防止蛇目前在向下移动,突然向上移动,加y != -step判断,以下同理
{
//设置当头上下左右移动的时候,蛇头的方向和移动方向一致,以下同理
//Quaternion代表四元数,identity表示初始旋转角度,可理解为new Vector(0,0,0)
gameObject.transform.localRotation = Quaternion.identity;
//设置蛇的移动方向,x = 0,y = step说明蛇头在Y轴向上移动,以下同理
x = 0;
y = step;
}
if (v < 0 && y != step)
{
//Quaternion.Euler将欧拉角转化为四元数,需要注意欧拉角要与移动方向匹配
gameObject.transform.localRotation = Quaternion.Euler(new Vector3(0, 0, 180));
x = 0;
y = -step;
}
if (h < 0 && x != step)
{
gameObject.transform.localRotation = Quaternion.Euler(new Vector3(0, 0, 90));
x = -step;
y = 0;
}
if (h > 0 && x != -step)
{
gameObject.transform.localRotation = Quaternion.Euler(new Vector3(0, 0, -90));
x = step;
y = 0;
}
}
void Move()
{
//获取当前蛇头移动的局部坐标
headPos = gameObject.transform.localPosition;
//将蛇头当前的移动位置加上x轴和y轴的移动增量,实现蛇头的移动
gameObject.transform.localPosition = new Vector3(headPos.x + x, headPos.y + y, 0);
}
04 食物生成
开发食物,新建一个Image,命名为SnackBody,source image设置为任意的食物图片,长宽设置为35*35,添加Box Collider2D,勾选is trigger, 需要注意碰撞器大小设置稍微小一点,防止蛇头擦肩而过的时候,发生碰撞,标签设置为Food,最后设为预制体。
设置步长step为30px,计算后得到世界坐标中心点到四周的步数,但为了防止出现食物"卡"在边缘,如下图,将步数各减一步
在Canvas下设置一个子物体FoodRoot,挂载脚本FoodCreator.cs
将FoodCreator设置为单例模式,只需实例化一次,方便调用
我们规定游戏开始,就生成一个食物,蛇每吃掉一个食物就随机生成其他食物,并有一定几率生成特殊奖励
特殊奖励的预制体和食物制作差不多。
public class FoodCreator : MonoBehaviour
{
private static FoodCreator instance;
public static FoodCreator Instance
{
get
{
return instance;
}
}
public int xMinLimit = 11;
public int xMaxLimit = 20;
public int yMinLimit = 11;
public int yMaxLimit = 11;
//要和蛇头移动步长一致
public int step = 30;
public GameObject foodPrefabs;
//设置
public GameObject rewardPrefabs;
//保存食物的Sprites
public Sprite[] foodSprites;
private Transform foodHolder;
private void Awake()
{
if (instance == null)
{
instance = this;
}
}
private void Start()
{
foodHolder = GameObject.FindGameObjectWithTag("FoodRoot").transform;
//游戏开始时,就生成一个食物
CreateFood(false);
}
public void CreateFood(bool isReward)
{
//随机取得一个食物的下标
int index = Random.Range(0, foodSprites.Length);
//实例化food预制体
GameObject food = Instantiate(foodPrefabs);
//将食物的image source改为选中下标的食物
food.GetComponent().sprite = foodSprites[index];
//将food设置为foodHolder的子物体,false会使得food保持局部坐标不变
food.transform.SetParent(foodHolder, false);
//随机取得食物生成位置
int x = Random.Range(-xMinLimit, xMaxLimit + 1);
int y = Random.Range(-yMinLimit, yMaxLimit + 1);
food.transform.localPosition = new Vector3(x * step, y * step, 0);
//判断是否生成奖励
if (isReward)
{
//同理
GameObject reward = Instantiate(rewardPrefabs);
reward.transform.SetParent(foodHolder, false);
x = Random.Range(-xMinLimit, xMaxLimit);
y = Random.Range(-yMinLimit, yMaxLimit);
reward.transform.localPosition = new Vector3(x * step, y * step, 0);
}
}
}
05 处理蛇身的生成
蛇身SnakeBody的制作和前面类似,tag设置为Body,设为预制体
我们使用List来存储蛇身,需要注意引用命名空间using System.Collections.Generic;
在SnakeHead.cs中新增AddBody()方法,然后添加OnTriggerEnter2D()用于触发检测,当碰到食物或者奖励的时候,调用AddBody()方法,实现蛇吃食物增加蛇身的功能
void AddBody()
{
//三元运算符,如果bodyList.Count被2模除则返回0,否则返回1,控制身体奇偶数轮换颜色
int index = (bodyList.Count % 2 == 0) ? 0 : 1;
//new Vector3(2000, 2000, 0)先将身体实例化在屏幕外
GameObject newBody = Instantiate(bodyPrefab, new Vector3(2000, 2000, 0), Quaternion.identity);
newBody.GetComponent().sprite = bodySprites[index];
newBody.transform.SetParent(snackRoot, false);
//将新生成的蛇身加入到bodyList中
bodyList.Add(newBody.transform);
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.CompareTag("Food"))
{
Destroy(collision.gameObject);
AddBody();
//(Random.Range(0, 100) < 20) ? true : false 三元运算符 随机值小于20则返回true,否则false
FoodCreator.Instance.CreateFood((Random.Range(0, 100) < 20) ? true : false);
}
else if (collision.gameObject.CompareTag("Reward"))
{
Destroy(collision.gameObject);
AddBody();
}
}
06 实现蛇移动
实现方法如图所示
在SnakeHead类中添加Move方法,使用 List记录蛇尾的位置信息
public List bodyList = new List();
void Move()
{
//获取当前蛇头移动的局部坐标
headPos = gameObject.transform.localPosition;
//将蛇头当前的移动位置加上x轴和y轴的移动增量,实现蛇头的移动
gameObject.transform.localPosition = new Vector3(headPos.x + x, headPos.y + y, 0);
//刚开始bodyList为空,防止报空指针
if (bodyList.Count > 0)
{
for (int i = bodyList.Count - 2; i >= 0; i--)
{
//将前一节蛇尾的位置赋予后一节
bodyList[i + 1].localPosition = bodyList[i].localPosition;
}
//将原来蛇头的位置赋予给下标为0的蛇尾,也就是蛇头后一节的蛇尾
bodyList[0].localPosition = headPos;
}
//方法二:将蛇尾最后一节移至蛇头的位置
//if (bodyList.Count > 0)
//{
//bodyList.Last()获取list最后的元素
// bodyList.Last().localPosition = headPos;
//Insert将元素插入到指定位置
// bodyList.Insert(0, bodyList.Last());
//RemoveAt移除指定下标的元素
// bodyList.RemoveAt(bodyList.Count - 1);
//}
}