其他分享
首页 > 其他分享> > Unity初级教程贪吃蛇实现(Snake)

Unity初级教程贪吃蛇实现(Snake)

作者:互联网

学习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

标签:蛇头,位置,transform,Unity,贪吃蛇,初级教程,tpos,new,个蛇身
来源: https://blog.csdn.net/xiheng66/article/details/117628659