贪吃蛇案例学习笔记
搭建开始场景
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vUoxYwfL-1680535339596)(C:\Users\CoreDawg\AppData\Roaming\Typora\typora-user-images\image-20230328173715457.png)]
做2D游戏一般吧canvas的渲染模式选成camera
- overlay是保证ui一直在最前方渲染,蒙在画面最上方,一般用于3D游戏的小地图等
- 2D游戏使用UGI做实现的话一般会调整成camera模式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rAC3PAI8-1680535339597)(C:\Users\CoreDawg\AppData\Roaming\Typora\typora-user-images\image-20230328174113408.png)]
调整完之后把摄像机拉进来,否则会报错
这之后相机和canvas会合并成一个框
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SB556wBa-1680535339598)(C:\Users\CoreDawg\AppData\Roaming\Typora\typora-user-images\image-20230328174442010.png)]
按住alt键在这里一键吸附四个角
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CSSYrdH8-1680535339599)(C:\Users\CoreDawg\AppData\Roaming\Typora\typora-user-images\image-20230328174715851.png)]
这里是跟左边选择上下拉伸
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aDgqRlTV-1680535339599)(C:\Users\CoreDawg\AppData\Roaming\Typora\typora-user-images\image-20230328175529219.png)]
即使是在UI选择创建image,也能通过附加button组件变成按钮
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W8r0gBuB-1680535339600)(C:\Users\CoreDawg\AppData\Roaming\Typora\typora-user-images\image-20230328175611025.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8e3oqHES-1680535339600)(C:\Users\CoreDawg\AppData\Roaming\Typora\typora-user-images\image-20230328175625224.png)]
这两个组件能做阴影跟描边,凸显按钮的感觉
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gSM0dG1A-1680535339601)(C:\Users\CoreDawg\AppData\Roaming\Typora\typora-user-images\image-20230328191941448.png)]
一组ui需要挂上Toggle Group来说明是一个组,同一时间只能有几个激活
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6rHtqT0o-1680535339602)(C:\Users\CoreDawg\AppData\Roaming\Typora\typora-user-images\image-20230328192229337.png)]
要在这里吧组给拖进来
完成游戏场景的界面
锚点一般靠哪里近归到哪里
给游戏场景添加可视的边界
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qekIr6G4-1680535339602)(C:\Users\CoreDawg\AppData\Roaming\Typora\typora-user-images\image-20230328195410941.png)]
碰撞器的锚点肯定是上边界在上面做锚点,下边界在下面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rNUUhGqj-1680535339602)(C:\Users\CoreDawg\AppData\Roaming\Typora\typora-user-images\image-20230328195532198.png)]
注意box colider 2D对ugi的控件不能自动识别
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zWH7LCsT-1680535339603)(C:\Users\CoreDawg\AppData\Roaming\Typora\typora-user-images\image-20230328200106157.png)]
用image组件实现可视化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KzRY7x1m-1680535339604)(C:\Users\CoreDawg\AppData\Roaming\Typora\typora-user-images\image-20230328200215456.png)]
碰撞器的长宽是独立的,因此改变transform的长宽,让红色进入到画面内,而实际上的碰撞器还是在画面外的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1svo6Ytc-1680535339605)(C:\Users\CoreDawg\AppData\Roaming\Typora\typora-user-images\image-20230328200447914.png)]
通过offset偏移量把碰撞器的位置设置回去,上面走了多少下面就走相反的
游戏逻辑的实现思路讲解
贪吃蛇所有的移动都是对蛇头而言的
蛇头(SH)的功能:
- 移动
-
自身移动
-
带动蛇身(SB)移动 - 让蛇头去管理蛇身
- 吃食物
- 增加蛇身长度 - Add SB
- 销毁食物
Move SH
移动方式:闪现/突变,而不是靠插值慢慢移动过去
可以通过直接操纵transform实现
每隔一段时间移动 用Invoke Repeat搞定
Move SB
主要由两种方法移动蛇身:
- 蛇头先走一步,让后面每一个都继承前面的位置,比较麻烦
- 只挪动最后一个蛇身到蛇头后面,其他的原样不动,比较巧妙
但是因为是双色蛇身,所以选择使用第一种,因为要根据奇偶性给他们上颜色,只挪动一个节点的话会导致蛇身节点颜色陷入混乱,如果是单色蛇身,就可以用第二种方法实现
吃东西
每吃一个食物,在后面加一个节点
死亡
- 撞到边界死亡
- 撞到任意一个蛇身死亡
传送
基于移动方式,只需要把蛇头传送过去,其他蛇身会自动跟随
因此不需要考虑蛇身是怎么移动的
只需要通过一个指令把对应的transform参数变为负的
制作蛇头并让其移动
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SWzdZquf-1680535339605)(C:\Users\CoreDawg\AppData\Roaming\Typora\typora-user-images\image-20230328202650635.png)]
先把蛇头做成预制体
因为移动是通过直接改变transform参数实现的,因此还不需要加刚体一类的组件
只有蛇头移动的脚本/不完善还
using UnityEngine;
public class SnakeHead : MonoBehaviour
{
public float velocity = 0.35f;//速度
public int step;//声明蛇每一步走的距离
private int x;
private int y;//下次移动位置的增量值
private Vector3 headPos;//定义全局坐标下蛇头的位置
//需要一开始就调用Move方法
//用 Start 方法
void Start()
{
//InvokeRepeating 重复调用
//接受三个参数 方法名 执行后多长时间调用 每隔多少时间调用一次
InvokeRepeating("Move", 0, velocity);
//贪吃蛇一开始是静止的,因此要在Start里一开始就让贪吃蛇朝一个方向走起来
x = 0; y = step;//开局贪吃蛇就会往右走
}
//根据按键输入,设置xy增量的值
void Update()
{
//用四个if输入完成增量变化
if (Input.GetKey(KeyCode.W))//不是GetKeyDown是因为玩家会一直按着键不放 KeyCode.w代表着上键
{
x = 0; y = step;
}
if (Input.GetKey(KeyCode.A))
{
x = -step; y = 0;
}
if (Input.GetKey(KeyCode.S))
{
x = 0; y = -step;
}
if (Input.GetKey(KeyCode.D))
{
x = step; y = 0;
}
}
//先用move方法实现蛇头移动
void Move()
{
headPos = gameObject.transform.localPosition;
gameObject.transform.localPosition = new Vector3(headPos.x + x, headPos.y + y, headPos.z);//用增量坐标替换现在的位置
}
}
完善蛇头的移动并让其可以加速
还得再wasd的时候图像发生位置旋转
用四元数的方法去啊 Quaternion.Euler,通过三个浮点数返回一个旋转实现
void Update()
{
//用四个if输入完成增量变化
if (Input.GetKey(KeyCode.W))//不是GetKeyDown是因为玩家会一直按着键不放 KeyCode.w代表着上键
{
//用四元数的方法去啊 Quaternion.Euler,通过三个浮点数返回一个旋转
gameObject.transform.localRotation = Quaternion.Euler(0, 0, 0);//0 0 0是初始方向,即 形向上的防线
x = 0; y = step;
}
if (Input.GetKey(KeyCode.A))
{
gameObject.transform.localRotation = Quaternion.Euler(0, 0, 90);
x = -step; y = 0;
}
if (Input.GetKey(KeyCode.S))
{
gameObject.transform.localRotation = Quaternion.Euler(0, 0, 180);
x = 0; y = -step;
}
if (Input.GetKey(KeyCode.D))
{
gameObject.transform.localRotation = Quaternion.Euler(0, 0, -90);
x = step; y = 0;
}
}
如何解决蛇不应该立马转头的问题,如果立马往反方向走的话,就直接碰到身子寄了
需要额外加一个判断条件,按上生效的条件是没有想吓走,按右生效的条件是没有向左走…
void Update()
{
//用四个if输入完成增量变化
if (Input.GetKey(KeyCode.W) && y != -step)//不是GetKeyDown是因为玩家会一直按着键不放 KeyCode.w代表着上键
{
//用四元数的方法去啊 Quaternion.Euler,通过三个浮点数返回一个旋转
gameObject.transform.localRotation = Quaternion.Euler(0, 0, 0);//0 0 0是初始方向,即 形向上的防线
x = 0; y = step;
}
if (Input.GetKey(KeyCode.A) && x != step)
{
gameObject.transform.localRotation = Quaternion.Euler(0, 0, 90);
x = -step; y = 0;
}
if (Input.GetKey(KeyCode.S) && y != step)
{
gameObject.transform.localRotation = Quaternion.Euler(0, 0, 180);
x = 0; y = -step;
}
if (Input.GetKey(KeyCode.D) && x != -step)
{
gameObject.transform.localRotation = Quaternion.Euler(0, 0, -90);
x = step; y = 0;
}
}
加速功能的实现
原理:按下左shift加速,抬起左shift的时候恢复原来的速度
//加速功能
if (Input.GetKeyDown(KeyCode.LeftShift))
{
//因为在上面循环调用的时候,已经给定了固定速度了,因此速度不会刷新
//要使用CancelInvoke方法
CancelInvoke();
InvokeRepeating("Move", 0, velocity - 0.2f);//减少数值跑得更快
}
if (Input.GetKeyUp(KeyCode.LeftShift))
{
//同理恢复速度
CancelInvoke();
InvokeRepeating("Move", 0, velocity);//恢复速度
}
第一个食物的随机生成
食物要生成在30的整数倍的地方,这是蛇移动会经过的地方
测试得知,上下距离碰撞器有11步数 在-11*30到 11乘以30的地方生成食物
向右有21个距离
向左有14个距离
因为好多不同食物都共用一个预制体,所以先把预制体的图片取消掉
- 生成食物的套路:开始的时候生成一个,然后没吃掉一个再生成新的,如果上一个食物一直没有被吃掉的话,就不生成下一个食物;
空物体用来归类的话最好保持上下左右的缩放,锚点和canvas一样
处理食物被吃掉且被吃掉后再生成
碰撞检测
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uQZSYDme-1680535339605)(C:\Users\CoreDawg\AppData\Roaming\Typora\typora-user-images\image-20230403175850875.png)]
相邻两个碰撞器擦肩而过也会发生碰撞的,因此不管是蛇还是食物,碰撞器实际上都应该比30小一圈
刚体
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BbKiJDUT-1680535339606)(C:\Users\CoreDawg\AppData\Roaming\Typora\typora-user-images\image-20230403180314615.png)]
注意需要时动态的刚体
注意不启用重力
给食物上刚体
给Food加Tag
给蛇头做检测
用OnTruggerEnter2D方法
两种拿到tag的写法
-
常用
if (collision.tag == "Food") { }
-
//也可以写成collision.tag=="Food" if (collision.gameObject.CompareTag("Food")) { }
拿到后销毁
OnTriggerEnter2D方法内
//也可以写成collision.tag=="Food"
if (collision.gameObject.CompareTag("Food"))
{
//销毁
Destroy(collision.gameObject);
}
类下
//通过类名,从静态的单例访问到他
private static FoodMaker _instance;
public static FoodMaker Instance
{
get
{
return _instance;
}
}
在awake里面赋值
void Awake()
{
_instance = this;
}
通知生成一个食物
//通知生成一个食物
FoodMaker.Instance.MakeFood();
处理蛇身的生成
先通过gui - image制作蛇身的预制体
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-84tg4tzK-1680535339607)(C:\Users\CoreDawg\AppData\Roaming\Typora\typora-user-images\image-20230403183455453.png)]‘
每一块想隔的距离是30,但是为什么要设置大小是40呢?因为这样的话能互相遮盖住一部分
用List集合存储所有的蛇身
public List<RectTransform> bodyList = new List<RectTransform>();
注意需要引入命名空间
using System.Collections.Generic;
拿到预制体和数组
//拿到蛇身的预制体和对应的图片
//因为有两种图片,所以设置成数组
public GameObject bodyPrefab;
public Sprite[] bodySprites = new Sprite[2];//指定数组长度是2
用Grow方法写生成
先判断奇偶数//就和if的用法一样
int index = (bodyList.Count % 2 == 0) ? 0 : 1;
然后实例化出来
void Grow()
{
GameObject body = Instantiate(bodyPrefab);
}
给材质
body.GetComponent<Image>().sprite = bodySprites[index];
设置父物体
body.transform.SetParent(canvas, false);
private Transform canvas;//拿到canvas
最后再把身子加到集合里面
bodyList.Add(body.transform);//只需要把坐标加进去就好
处理蛇身的移动之方法一
通过Awake方法拿到Canvas
void Awake()
{
canvas = GameObject.Find("Canvas").transform;
}
用移动最后一个蛇身的方法实现移动(只是实现,不使用)
bodyList.Last().localPosition = headPos;//很好理解。直接用 Last 方法拿到最后一个蛇身的位置然后设置到原先头的位置
处理一下集合
bodyList.Insert(0, bodyList.Last());//更新集合 Insert 插入
bodyList.RemoveAt(bodyList.Count - 1);//删除集合中最后一个数
再加上调用方法
根据报错,最初的时候bodylist返回了 null
因此做一步判断,至少有一个身子的时候,才会执行这些代码
if (bodyList.Count > 0)
{
bodyList.Last().localPosition = headPos;//很好理解。直接用 Last 方法拿到最后一个蛇身的位置然后设置到原先头的位置
bodyList.Insert(0, bodyList.Last());//更新集合 Insert 插入
bodyList.RemoveAt(bodyList.Count - 1);//删除集合中最后一个数
}
两个问题
-
每次都会在0 0 0 处闪现生成
解决办法:
- 把生成位置设置在画面之外,让玩家看不到
- 或者,在Move方法里调用Grow();而不是在吃到后调用
-
蛇的花纹明显不对
处理蛇身的移动之方法二
移动所有蛇身的方法
利用for循环完成坐标的移动
for循环的条件
for (int i =bodyList.Count-2;i >=0;i--)
{
}
通过下标访问蛇身的坐标
bodyList[i + 1].localPosition = bodyList[i].localPosition;
void Move()
{
headPos = gameObject.transform.localPosition; //保存下来蛇头移动前的位置
gameObject.transform.localPosition = new Vector3(headPos.x + x, headPos.y + y, headPos.z); //蛇头向期望位置移动
if (bodyList.Count > 0)
{
//由于是双色蛇身所以弃用的方法
//bodyList.Last().localPosition = headPos; //将蛇尾移动到蛇头移动前的位置
//bodyList.Insert(0, bodyList.Last()); //将蛇尾在List中的位置更新到最前
//bodyList.RemoveAt(bodyList.Count - 1); //移除list最末尾的蛇尾引用
//由于我们是双色蛇身 使用此方法达到显示目的
for (int i =bodyList.Count-2;i >=0;i--) //从后往前开始移动蛇身
{
bodyList[i + 1].localPosition = bodyList[i].localPosition; //每一个蛇身都移动到它前面一个节点的位置
}
bodyList[0].localPosition = headPos; //第一个蛇身移动到蛇头移动前的位置
}
}
让蛇可以通过边界进行传送
给蛇身加上Body标签
通过Body标签死亡
else if (collision.gameObject.CompareTag("Body"))
{
Debug.Log("Die");//暂时替代,还没写死亡方法
}
else
{
Debug.Log("Die");
}
写边界穿越
通过switch语句
操纵xyz的正负数实现穿越
switch (collision.gameObject.name)
{
case "Up":
transform.localPosition = new Vector3(transform.localPosition.x, -transform.localPosition.y, transform.localPosition.z);
break;
case "Down":
transform.localPosition = new Vector3(transform.localPosition.x, -transform.localPosition.y, transform.localPosition.z);
break;
case "Left":
break;
case "Right":
break;
}
问题:蛇头在边界会不断来回鬼畜跳跃
因为碰到便捷的时候就开始不断穿越了
解决方法:穿越一次之后对应坐标对应+/-30,相当于多走了一个格子,就碰不到边界了
case "Up":
transform.localPosition = new Vector3(transform.localPosition.x, -transform.localPosition.y+30, transform.localPosition.z);
break;
case "Down":
transform.localPosition = new Vector3(transform.localPosition.x, -transform.localPosition.y-30, transform.localPosition.z);
奖励目标的生成与获取
先做额外的prefab
生成reward
现持有reward的prefab
拓展生成食物的方法,想给个形参
public void MakeFood(bool isReward)
设置百分之20的几率
FoodMaker.Instance.MakeFood(false);
if (Random.Range(0, 100) < 20)
{
FoodMaker.Instance.MakeFood(true);
}
优化,只调用一次代码,概率命中的时候生成食物加奖励,否则只生成食物
FoodMaker.Instance.MakeFood((Random.Range(0, 100) < 20)?true:false);//检测通过的话创建true,否则就创建false
分数与长度的记录以及背景颜色的切换
先新建一个脚本控制UI
拿到基本的命名空间和数据成员
using UnityEngine;
using UnityEngine.UI;
public class MainUIController : MonoBehaviour
{
public int score = 0;
public int length = 0;
public Text msgText;
public Text scoreText;
public Text lengthText;
}
用一个方法来更新UI
//更新UI的方法
public void UpdateUI(int s = 5, int l = 1)
{
score += s;
length += l;
scoreText.text = "得分:\n" + score;
lengthText.text = "长度:\n" + length;
}
把单例复制过来,改一下类的名字
然后用Awake方法给单例的_instance赋值
然后在蛇头里调用这个方法
阶段等级
说明:从三百分开始,没两百分换一个等级
获取背景
public Image bgImage;
用switch语句在update方法里实现
void Update()
{
switch (score / 100)
{
case 3:
break;
case 5:
break;
case 7:
break;
case 9:
break;
case 11:
break;
}
}
在switch语句里修改颜色
switch (score / 100)
{
case 3:
ColorUtility.TryParseHtmlString("#CCEEFFFF", out tempColor);
bgImage.color = tempColor;
msgText.text = "阶段" + 2;
break;
case 5:
ColorUtility.TryParseHtmlString("#CCEEFFFF", out tempColor);
bgImage.color = tempColor;
msgText.text = "阶段" + 3;
break;
case 7:
ColorUtility.TryParseHtmlString("#CCFFDBFF", out tempColor);
bgImage.color = tempColor;
msgText.text = "阶段" + 4;
break;
case 9:
ColorUtility.TryParseHtmlString("#EBFFCCFF", out tempColor);
bgImage.color = tempColor;
msgText.text = "阶段" + 5;
break;
case 11:
ColorUtility.TryParseHtmlString("#FFDACCFF", out tempColor);
bgImage.color = tempColor;
msgText.text = "无尽模式";
break;
}
暂停游戏与返回菜单的开发
对阶段转换的脚本进行优化
void Update()
{
switch (score / 100)
{
case 0:
case 1:
case 2:
break;
case 3:
case 4:
ColorUtility.TryParseHtmlString("#CCEEFFFF", out tempColor);
bgImage.color = tempColor;
msgText.text = "阶段" + 2;
break;
case 5:
case 6:
ColorUtility.TryParseHtmlString("#CCEEFFFF", out tempColor);
bgImage.color = tempColor;
msgText.text = "阶段" + 3;
break;
case 7:
case 8:
ColorUtility.TryParseHtmlString("#CCFFDBFF", out tempColor);
bgImage.color = tempColor;
msgText.text = "阶段" + 4;
break;
case 9:
case 10:
ColorUtility.TryParseHtmlString("#EBFFCCFF", out tempColor);
bgImage.color = tempColor;
msgText.text = "阶段" + 5;
break;
default:
ColorUtility.TryParseHtmlString("#FFDACCFF", out tempColor);
bgImage.color = tempColor;
msgText.text = "无尽模式";
break;
}
}
暂停
创建一个暂停方法
public void Pause()
{
}
给一个暂停的bool值
public bool IsPause = false;
给暂停按钮和对应图标
public Button pauseButton;
public Sprite[] pausesprites;
暂停的代码
public void Pause()
{
IsPause = !IsPause;
if (IsPause)
{
Time.timeScale = 0;//时间停止
IsPause = false;
pauseButton.GetComponent<Image>().sprite = pausesprites[1];//切换对应图片
}
else
{
Time.timeScale = 1;
pauseButton.GetComponent<Image>().sprite = pausesprites[0];
}
}
空格热键触发问题
Edit - ProjectSetting - Input
在这里找到submit
在里面吧space/空格删掉
暂停时按下加速仍然能移动的问题
在加速的代码上加一段限制条件,只有在没有停止的时候才会加速
if (Input.GetKeyDown(KeyCode.LeftShift) && MainUIController.Instance.isPause == false)
主业场景制作
需要把两个场景放到setting里
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1DjWyvuH-1680535339609)(C:\Users\CoreDawg\AppData\Roaming\Typora\typora-user-images\image-20230403211500844.png)]
主业按钮的制作
public void Home()
{
UnityEngine.SceneManagement.SceneManager.LoadScene(0);
}
蛇死亡的处理和游戏得分的记录
Die方法
void Die()
{
CancelInvoke();
}
给一个isDie的布尔值
private bool isDie = false;
在每个输入前加上死亡判断
if (Input.GetKey(KeyCode.A) && x != step && MainUIController.Instance.isPause == false && isDie == false)
在Die方法里把isDie设为true
void Die()
{
CancelInvoke();
isDie = true;
}
爆炸特效
先拿到物体
public GameObject dieEffect;
直接实例化出来,也不用在乎什么时候销毁,因为场景马上要重载了
void Die()
{
CancelInvoke();
isDie = true;
Instantiate(dieEffect);
}
使用协程
IEnumerator GameOver(float t)
{
yield return new WaitForSeconds(t);
UnityEngine.SceneManagement.SceneManager.LoadScene(1);
}
协程方法的调用
StartCoroutine(GameOver(1.5f));
死亡后得分的记录
要在场景被重载之前记录
PlayerPrefs.SetInt("last1", MainUIController.Instance.length);
PlayerPrefs.SetInt("lasts", MainUIController.Instance.score);
最佳得分判断
if (PlayerPrefs.GetInt("bests",0) < MainUIController.Instance.score)
{
PlayerPrefs.SetInt("best1", MainUIController.Instance.length);
PlayerPrefs.SetInt("bests", MainUIController.Instance.score);
}
对于用户设置的储存
给start场景写个脚本控制UI
先拿到显示得分的文本组件
public TextAlignment lastText;
public TextAlignment bestText;
在awake方法里把得分给拿到
void Awake()
{
lastText.text = "上次:长度" + PlayerPrefs.GetInt("last1", 0) + ",分数" + PlayerPrefs.GetInt("lasts", 0);
bestText.text = "最好:长度" + PlayerPrefs.GetInt("best1", 0) + ",分数" + PlayerPrefs.GetInt("bests", 0);
}
在引擎里挂载赋值即可
开始按钮的制作
加载场景的套路
public void StartGame()
{
UnityEngine.SceneManagement.SceneManager.LoadScene(1);
}
如何把四个Main场景的皮肤和模式带到Start场景里
使用playerprebs的讨论
需要先定义四个
public void BlueSelect(bool isON)
{
}
public void YellowSelect(bool isON)
{
}
public void BorderSelect(bool isON)
{
}
public void NoBorderSelect(bool isON)
{
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OjQrz8gg-1680535339612)(C:\Users\CoreDawg\AppData\Roaming\Typora\typora-user-images\image-20230403221014816.png)]
bool值可以直接在这边动态传参数
写具体选项的内容
通过记录三个string值:奇数和偶数的文件名来记录当前状态
颜色的文件信息储存
public void BlueSelect(bool isOn)
{
if (isOn)
{
PlayerPrefs.SetString("sh", "sh01");
PlayerPrefs.SetString("sb01", "sb0101");
PlayerPrefs.SetString("sb02", "sb0102");
}
}
public void YellowSelect(bool isOn)
{
if (isOn)
{
PlayerPrefs.SetString("sh", "sh02");
PlayerPrefs.SetString("sb01", "sb0201");
PlayerPrefs.SetString("sb02", "sb0202");
}
}
至于边界模式则用int代替bool的用法,规定int0 true,int 1 false
public void BorderSelect(bool isOn)
{
if (isOn)
{
PlayerPrefs.SetInt("border", 1);
}
}
public void NoBorderSelect(bool isOn)
{
if (isOn)
{
PlayerPrefs.SetInt("border", 0);
}
}
问题:需要在一开始的时候从用户手中读取文件信息,记录下来并且按照用户的选择设置好
还需要持有四个toggle
public Toggle blue;
public Toggle yellow;
public Toggle border;
public Toggle noBorder;
通过在Start方法里读取数据实现
if (PlayerPrefs.GetString("sh", "sh01") == "sh01")
{
blue.isOn = true;
PlayerPrefs.SetString("sh", "sh01");
PlayerPrefs.SetString("sb01", "sb0101");
PlayerPrefs.SetString("sb02", "sb0102");
}
else
{
yellow.isOn = true;
PlayerPrefs.SetString("sh", "sh02");
PlayerPrefs.SetString("sb01", "sb0201");
PlayerPrefs.SetString("sb02", "sb0202");
}
在start方法里做边界的判断
if (PlayerPrefs.GetInt("border", 1) == 1)
{
border.isOn = true;
PlayerPrefs.SetInt("border", 1);
}
else
{
noBorder.isOn = true;
PlayerPrefs.SetInt("border", 0);
}
完成换肤与其他配置读取
换肤的制作 - Resources文件夹
把需要在层中更换的资源放到Resources文件夹下
在awake里使用Resources.Load方法加载
void Awake()
{
canvas = GameObject.Find("Canvas").transform;
//通过Resources.Load(string path)方法加载资源,path的书写不需要加Resources/以及文件扩展名
gameObject.GetComponent<Image>().sprite = Resources.Load<Sprite>(PlayerPrefs.GetString("sh", "sh02"));
bodySprites[0] = Resources.Load<Sprite>(PlayerPrefs.GetString("sb01", "sh0201"));
bodySprites[1] = Resources.Load<Sprite>(PlayerPrefs.GetString("sb02", "sh0202"));
}
边界的处理
利用foreach循环拿到所有的transform组件,通过所有的transform组件拿到image组件,拿到gameobject组件
void Start()
{
if (PlayerPrefs.GetInt("border", 1) == 0)
{
hasBorder = false;
foreach(Transform t in bgImage.gameObject.transform)
{
t.gameObject.GetComponent<Image>().enabled = false;//禁用掉这个组件
}
}
}
在蛇头的碰撞判断处用if语句做补充
if (MainUIController.Instance.hasBorder)
{
Die();
}
else
{
switch (collision.gameObject.name)
{
case "Up":
transform.localPosition = new Vector3(transform.localPosition.x, -transform.localPosition.y + 30, transform.localPosition.z);
break;
case "Down":
transform.localPosition = new Vector3(transform.localPosition.x, -transform.localPosition.y - 30, transform.localPosition.z);
break;
case "Left":
transform.localPosition = new Vector3(-transform.localPosition.x + 180, transform.localPosition.y, transform.localPosition.z);
break;
case "Right":
transform.localPosition = new Vector3(-transform.localPosition.x + 240, transform.localPosition.y, transform.localPosition.z);
break;
}
}
声音的添加
bgm
简单粗暴,直接使用audio source组件,拖进bgm,勾选loop
蛇吃东西和死亡的添加
先拿到音频
public AudioClip eatClip;
public AudioClip dieClip;
声音的触发
吃东西
//注意:声音会跟位置有关,要注意看发生物体的位置,在这个案例里,就是要看摄像机的位置
而此时摄像机的z是-10,这样的话选择声音位置是0.可能声音就会比较小
距离摄像机越近,声音越大
void Grow()
{
AudioSource.PlayClipAtPoint(eatClip, Vector3.zero);
}
死亡
void Die()
{
AudioSource.PlayClipAtPoint(dieClip, Vector3.zero);
}
发布游戏与解决发布时出现的诡异bug
n.z);
break;
case "Left":
transform.localPosition = new Vector3(-transform.localPosition.x + 180, transform.localPosition.y, transform.localPosition.z);
break;
case "Right":
transform.localPosition = new Vector3(-transform.localPosition.x + 240, transform.localPosition.y, transform.localPosition.z);
break;
}
}
#### 声音的添加
##### bgm
简单粗暴,直接使用audio source组件,拖进bgm,勾选loop
#### 蛇吃东西和死亡的添加
先拿到音频
```C#
public AudioClip eatClip;
public AudioClip dieClip;
声音的触发
吃东西
//注意:声音会跟位置有关,要注意看发生物体的位置,在这个案例里,就是要看摄像机的位置
而此时摄像机的z是-10,这样的话选择声音位置是0.可能声音就会比较小
距离摄像机越近,声音越大
void Grow()
{
AudioSource.PlayClipAtPoint(eatClip, Vector3.zero);
}
死亡
void Die()
{
AudioSource.PlayClipAtPoint(dieClip, Vector3.zero);
}
发布游戏与解决发布时出现的诡异bug
选择这个能解决分辨率问题,因为能跳出让玩家手动选择分辨率的栏目