学习Unity后做游戏
在此先声明,本素材是由siki学院处取得的,无商业用途,仅学习使用。
这次,默认全屏1920*1080;而且蛇头蛇身连接处处理的不恰当,开局默认两个蛇身且没有碰撞体,所以就是开局除了撞墙不会死亡…吃过一个食物才会撞自己死亡;快速按键可以回头撞自己…缓慢的话就不会了,这些BUG我慢慢解决,这回先这样了,动手能力强的也可以解决下,那就废话不多说,开始教程了。
首先有两个场景,一个开始界面,一个游戏场景。开始界面有一个开始游戏Button,两个Toggle Group分别存放模式和皮肤,两个Text存放最高分和最大长度,其他的背景什么的随便丢丢上去就好了(注定不会做UI的我)皮肤先开始用Sprite Render展示的,突然发现设置Pivot不管用,于是先设置了Order in Layer的顺序来展示了…但是后面找到设置Pivot为啥没用了,在Edit=>Project Settings=>Graphics=>Camera Settings=>排序模式设置为透视就好了,设置为自定义的话x,y,z没法设置,感觉就只能设置成这样了,哦对了Toggle设置单选需要在两个Toggle上面设置空的父物体,然后加个组件叫Toggle Group就行了,然后把两个Toggle的Group设置为父物体就能实现单选了。开始游戏按钮需要绑定一个事件,手动绑自动绑都行,手动绑就加个函数绑就行了;自动的话onclick事件上面添加委托,直接lambda表达式就搞定了,就像这样:button.onClick.AddListener(() => SceneManager.LoadScene(1));其他玩家选项和分数的控制的储存一会儿一起说。
然后游戏场景,开始了漫长的数格子过程…我这里决定的是食物和蛇都是占两个格子的,但是都可以一格一格的挪动,所以先把蛇头设置为2*2格开始找四个角的边界记录下来,然后移动两格开始找两步的长度。开始我设置的边界离真实的边界有一格距离,步长为两格,后来发现无尽模式没办法好好的传送到另一边,所以又改了一格一格的挪,边界也改成现在这样了。开始的时候找步长还找了大半天…找了最小公倍数,还一格一格算了有多少格,结果发现走弯路了,根本就不用找…按照我上面说的方法一下就完事了。
记录完以后,就可以自己写个移动方法来测试了,先写个全局变量moveVec2储存每次按键过后的方向,timer计时,然后计时结束以后移动长度为一格步长。
if (h != 0 && moveVec2.x != -h)//水平轴有偏移且正在移动的方向与偏移方向不相同,就是禁止180°转头 一涉及到逻辑就开始歇菜了o_o智商明显不在线
{
moveVec2.x = h;//更换移动方向
moveVec2.y = 0;
if (h > 0)//设置旋转角实现转头
transform.eulerAngles = new Vector3(0, 0, -90);
else
transform.eulerAngles = new Vector3(0, 0, 90);
}
else if (v != 0 && moveVec2.y != -v)//和上面一样的,不写注释了
{
moveVec2.y = v;
moveVec2.x = 0;
if (v > 0)
transform.eulerAngles = new Vector3(0, 0, 0);
else
transform.eulerAngles = new Vector3(0, 0, 180);
}
下面就是更重要的逻辑了——移动蛇身,自己脑子里想不动笔真的是很难一下子想出来,想了好久后画了张图一下子思路就清晰了,这张我画的…
咳咳,讲解一下,首先蛇头是i-1,也就是第0位,后面都是蛇身。蛇头动与身体无关,所以蛇头和身体的连接那一段单独处理。首先记录蛇头位置tpos,第i个蛇身位置pos,第i个蛇身移动到tpos位置,然后pos的值储存在tpos里,再把第i+1个蛇身原本位置储存pos,第i+1个蛇身位置变换为tpos,以此类推。总之tpos是要变换的位置,pos储存之前的位置。(啊喂命名的真随便)代码的话就是这样子的。
if (timer < 0)//到时间了该移动了 难点来了,蛇身跟着蛇头移动,实现方法为后面的跟着前面的移动,蛇头蛇身连接部分单独处理
{
for (int i = 1; i < snakeList.Count; i++)//从第一个蛇身开始遍历
{
Vector3 pos = snakeList[i].transform.position;//存储第i个蛇身位置
if (i == 1)//第一个蛇身单独处理
{
tempPos = snakeList[i - 1].transform.position;//tpos储存蛇头位置 tpos=tempPos,我简写了
snakeList[i].transform.position = tempPos;//蛇身移动到蛇头位置
tempPos = pos;//tpos储存的位置变为第1个蛇身的位置
continue;
}
snakeList[i].transform.position = tempPos;//第i个蛇身的位置变为tpos的位置,即第2个蛇身变为第1个蛇身的位置
tempPos = pos;//tpos储存的位置变为第i个蛇身的位置
}
transform.position += new Vector3(moveVec2.x * runSpeed * footStep / 2, moveVec2.y * runSpeed * footStep / 2);//移动蛇头
timer = 0.3f;//重新计时
}//说难也不难,最难的就是从无到有的过程,需要一遍一遍的自己构思
除了这两个难点以后再就是食物的生成位置判断了,开始只写了一个检测有莫名其妙的bug,后面干脆多写了个射线的检测,这部分我写的逻辑比较乱,没怎么大幅修改,所以这部分仅供参考吧,至少没问题了,有时间我再修改修改。代码就直接贴出来了,注释写的很全就不再解释了。
public void OnEnable()
{
short y;//食物x位置
short x;//食物y位置
while (true)
{
x = (short)Random.Range(0, Mathf.Round((bound[3] - bound[2]) / footStep * 2) + 1);//随机生成x,y位置
y = (short)Random.Range(0, Mathf.Round((bound[0] - bound[1]) / footStep * 2) + 1);
if (!Equals(new Vector2(Mathf.Round(bound[2] + x * footStep), Mathf.Round(bound[1] + y * footStep)), new Vector2(Mathf.Round(SnakeHead.transform.position.x), Mathf.Round(SnakeHead.transform.position.y))))//生成的位置和蛇头的位置不同时跳出循环
break;
}
RaycastHit2D ray = Physics2D.BoxCast(new Vector2(bound[2] + x * footStep / 2, bound[1] + y * footStep / 2), new Vector2(transform.localScale.x, transform.localScale.y), 0, new Vector2(0, 0), LayerMask.GetMask("PlayLayer"));//创建方块射线检测该区域是否有别的物体
if (ray.collider == null)//检测到没有东西
{
GetComponent<SpriteRenderer>().sprite = foodSprite[Random.Range(0, foodSprite.Length)];//随机替换食物图片
transform.position = new Vector3(bound[2] + x * footStep / 2, bound[1] + y * footStep / 2);//生成新位置
GetComponent<SpriteRenderer>().enabled = true;//图片可见
}
}
private void OnTriggerStay2D(Collider2D collision)
{
if (collision.gameObject.layer == LayerMask.NameToLayer("PlayLayer") && !collision.CompareTag("SnakeHead"))//碰到playlayer中不是蛇头的物体的话重新生成食物
{
GetComponent<SpriteRenderer>().enabled = false;//食物图片不可见
OnEnable();
}
}
到这里其他的代码和操作都没有什么需要注意的东西了,哦还有,素材里面还有一个特殊道具,这个我没有实现,有想做的小伙伴可以尝试一下,做长度加二或者加得分都是可以的,本次用到了数据存储相关的东西,其实unity已经定义好了一个类PlayerPrefs用于储存一些小型数据,要是大型的话还是老老实实的用文件储存吧,这个太耗费性能了,但是优点就是读取较快,直接就可以从内存中读取出来,一般用到的就是里面的get,set方法,不过没有bool类型,需要string或者int类型存储,建议使用int,string的话因为是引用,频繁修改需要频繁在堆上开辟新空间,不断的使用GC回收,很耗费性能,其他没什么注意的地方了。
越写才越觉得自己水平低,也可能看别人的代码少,遇事不决就if…到了大型项目的时候这条路肯定是走不通的,大家不要学我,推荐大家写代码入门到一定程度以后把《大话数据结构》看一遍,不研究算法那些东西但是数据结构只要是写代码就必须要懂的,学过数据结构大学才不“白上”,不然以至于进了公司都要被项目经理嘲笑。好了本次就这么多,具体的东西看我的代码和设置吧,直接贴链接了。
链接:https://pan.baidu.com/s/159vq3liZU6Kkztpz_We5wQ
提取码:gvmk