【c# Unity贪吃蛇教程】3D格子制作贪吃蛇

前言

        不神叨叨了,直接说人话。

        已经是第四期C#不同平台制作贪吃蛇了,前三期分别是【c# 控制台贪吃蛇教程】【c# winform贪吃蛇教程】【c# WPF贪吃蛇教程】

        本期用Unity制作3d网格贪吃蛇,项目教程完全小白化,大神不用瞄了,本项目有源码下载。


目录

 一、准备工作

        1.1、创建Resources文件夹

        1.2、创建格子3D模型

        1.3、创建shader

        1.4、创建类Tanchishe3D

        1.5、添加Tag标签

        1.6、声明全部变量

二、主要功能函数

        2.1、加载资源

        2.2、地图和围墙

        2.3、蛇的操作

        2.4、生成食物

        2.5、吃到食物

        2.6、吃到自己

        2.7、撞到墙   

        2.8、控制蛇

        2.9、Shader

三、其他功能函数

        3.1、ugui类

        3.2、3d视角显示

四、运行图示 

五、总结

六、工程下载地址   


         先看开发过效果程图 


 一、准备工作

       如果看过前面文章,可以直接跳过准备工作这栏

        1.1、创建Resources文件夹

        Resources文件夹中可以存放工程的图片,音效,模型等资源,用于读取加载使用

        文件夹在Asset下根目录,属于一级目录,不要放到其他文件下面,否则Resources资源读取不到


        1.2、创建格子3D模型

        右键SampleScence添加一个3D的cube

        该cube可以组成二维地图的矩阵点,也是蛇和食物的组件

        将该cubeSampleScence中拖拽到Resources文件夹中


        1.3、创建shader

        创建shader是为了取代cube模型中的默认材质的shader

        cube是一个模型,他有默认的材质,我们创建一个新的材质球取代默认材质。

        shader,我们可以创建一个属于自己的材质球shader,这方便我们修改渲染。

        接着,我们这里需要将默认材质球的shader渲染修改成我们自己创建的shader

        最后,再将材质球拖拽到cube的MeshRenderer的材质集合中


        1.4、创建类Tanchishe3D

        创建类之前,先在SampleSceneGameSrcipt实体,并将创建的Tanchishe3D类,将该文件拖拽到GameSrcipt实体身上


        1.5、添加Tag标签

        Tag标签主要作为实体的识别标签,方便我们查找物体,我们创建一个标签


        1.6、声明全部变量

        

//2d视角坐标
public Vector3 SHOW2DV3;
//3d视角坐标
public Vector3 SHOW3DV3;
//GameElement类的集合
public List<GameElement> elementList = new List<GameElement>();
//资源加载的顺序
int ResourceIndex = 0;
//蛇身xy
int bdX = 0; int bdY = 0;
//头xy
int hx = 0; int hy = 0;
//移动 步数
int stepNum = 0;
//吃了多少食物
int eatFoodNum = 0;
//格子宽高
public int size = 30;
//行
public int row = 20;
//列
public int column = 40;
//地图坐标
public List<CellData> mapcelldataList = new List<CellData>();
//蛇点集合
public List<CellData> snakecelldataList = new List<CellData>();
//食物坐标
public List<CellData> foodPoint = new List<CellData>();
//移动速度
int moveBasicSpd = 2;
//计时器
float timer = 0;
//是否死亡
public bool isLive = true;
//操作方向
public Dirction mycurDirction = Dirction.D_right;
//UI类
public UImanager uImanager;
/// <summary>
/// 以下是围墙,格子蛇的头,蛇的身子,食物的颜色配置
/// </summary>
public Color cellColor = new Color(128 / 255f, 128 / 255f, 128 / 255f);
public Color wallColor = new Color(255 / 255f, 55 / 255f, 55 / 255f);
public Color snakeHeadColor = new Color(7 / 255f, 150 / 255f, 0);
public Color snakeBodyColor = new Color(225 / 255f, 120 / 255f, 0);
public Color foodColor = new Color(125 / 255f, 0, 175 / 255f);

 不用二维数组,采用类的形式记录信息,并将该类加入到集合List中,构建地图数据 


二、主要功能函数

        2.1、加载资源

        采用异步加载Resources实体

    void LoadCube()
    {
        ResourceRequest request = Resources.LoadAsync<GameObject>("Cube");
        request.completed += LoadOver;
    }

        request.completed += LoadOver 读取完成后,再进行后部操作,避免资源未加载完成,游戏系统已经启动,结果出现bug

        加载后不要马上生成实体,我们可以将实体临时存到一个类中,这里我们创建一个资源类

[System.Serializable]
public class GameElement
{
    public int objID;
    public string objName;
    public Type objType;
    public GameObject obj;
}

        在 inspector 面板上显示类的变量,可以用 [System.Serializable]。需要注意的是,这里不用 { get; set; } ,因为用了,在 inspector 面板中不会显示变量。

        我们从创建一个管理Resources实体的集合,哪里需要生成,我们就在哪里加载

    public List<GameElement> elementList = new List<GameElement>();

        准备好后,可与i用来存放读取的资源,放到 elementList 集合中

    private void LoadOver(AsyncOperation asyncOperation)
    {
        GameObject cubeObj = (asyncOperation as ResourceRequest).asset as GameObject;
        ResourceIndex++;
        GameElement element = new GameElement()
        {
            objID = ResourceIndex,
            objName = cubeObj.name,
            objType = cubeObj.GetType(),
            obj = cubeObj
        };
        elementList.Add(element);
        CreateGrid();
    }

        最后在这个函数方法里面创建地图


        2.2、地图和围墙

private void CreateGrid()
{
    GameElement element = elementList.Find((item) => item.objName == "Cube") as GameElement;
    GameObject wallobj = element.obj;
    int index = 1;
    Color color = new Color();
    // 创建行
    for (int i = 0; i < row; i++)
    {
        for (int j = 0; j < column; j++)
        {
            CellData celldata = new CellData();
            celldata.cellIndex = index;
            celldata.cellVect3 = new Vector3(i, j, 0);
            GameObject obj = Instantiate(wallobj, transform);
            if (i % (row - 1) == 0 || j % (column - 1) == 0)
            {
                celldata.mapType = 1;
                SetCellPainting(obj.GetComponent<MeshRenderer>(), "_MianColor", wallColor);
                obj.transform.position = new Vector3(j, i, -1);
            }
            else
            {
                celldata.mapType = 0;
                SetCellPainting(obj.GetComponent<MeshRenderer>(), "_MianColor", cellColor);
                obj.transform.position = new Vector3(j, i, 0);
            }
            celldata.cellObj = obj;
  
            mapcelldataList.Add(celldata);
            index++;
        }
    }
}

        obj.transform.position = new Vector3(j, i, -1); 是设置坐标的接口

        Instantiate 实例化,生成物体到舞台上 


        2.3、蛇的操作

        创建操作的枚举类型

    public enum Dirction
    {
        D_up,
        S_down,
        A_left,
        D_right
    }

        操作四个方向,并设置蛇方向

    public void KeyDown()
    {
        if (Input.GetKey(KeyCode.UpArrow))
        {
            SnakeDirction(Dirction.S_down, Dirction.D_up);
        }
        else if (Input.GetKey(KeyCode.DownArrow))
        {
            SnakeDirction(Dirction.D_up, Dirction.S_down);
        }
        else if (Input.GetKey(KeyCode.LeftArrow))
        {
            SnakeDirction(Dirction.D_right, Dirction.A_left);
        }
        else if (Input.GetKey(KeyCode.RightArrow))
        {
            SnakeDirction(Dirction.A_left, Dirction.D_right);
        }
    }

         键盘按键与winformwpf不同,unity并不需要事件监听,我们可以直接将KeyDown放入

    private void FixedUpdate()
    {
        KeyDown();
    }

         禁止车掉头

    void SnakeDirction(Dirction d1, Dirction d2)
    {
        mycurDirction = mycurDirction != d1 ? d2 : d1;
    }

        2.4、生成食物
void CreateFood()
{
    List<CellData> tempList = new List<CellData>(mapcelldataList);
    for (int i = 0; i < tempList.Count; i++)
    {
        for (int j = 0; j < snakecelldataList.Count; j++)
        {
            if (snakecelldataList[j].cellVect3 == (tempList[i].cellVect3))
            {
                tempList[i].mapType = 1;
            }
        }
    }
    tempList = new List<CellData>(tempList.Where(x => x.mapType == 0).ToList());
    var random = new System.Random();
    var index = random.Next(tempList.Count);
    CellData cell = tempList[index];
    foodPoint = new List<CellData> { cell };
    DrawFood();
}

        方法是,将地图文件取出,临时放到一个集合,再遍历集合与蛇的集合交集,将他们的交集的mapType = 1

        再筛选出不能生成食物的格子集合,随后随机得到集合中的一个元素,这个元素就是食物生成的Vector3坐标

    void DrawFood()
    {
        CellData cell = GetSnakeCell((int)foodPoint[0].cellVect3.x, (int)foodPoint[0].cellVect3.y);
        SetCellPainting(cell.cellObj.GetComponent<MeshRenderer>(), "_MianColor", foodColor);
        cell.cellObj.transform.position = new Vector3(cell.cellObj.transform.position.x,
            cell.cellObj.transform.position.y, -1);
    }

        2.5、吃到食物

        吃到食物后,立即随机在格子可以行动区域生成食物

   void EatFood(int x, int y)
   {
       if (foodPoint.Count <= 0) return;

       //吃到食物
       if (x == foodPoint[0].cellVect3.x && y == foodPoint[0].cellVect3.y)
       {
           CellData cellData = new CellData();
           cellData = GetSnakeCell(x, y);
           snakecelldataList.Add(cellData);
           cellData.cellObj.transform.position = new Vector3(cellData.cellObj.transform.position.x, 
               cellData.cellObj.transform.position.y, -1);
           eatFoodNum++;
           TextShow();
           CreateFood();
       }
   }


        2.6、吃到自己
    void HitSelf(int x, int y)
    {
        if (snakecelldataList.Where(point => point.cellVect3.x == x && point.cellVect3.y == y).Any())
        {
            isLive = false;
        }
    }

         吃到自己后,在荧幕中央显示结束语,并且将  isLive = false;

         isLive 负责跳出游戏的进程,终止游戏进度。

        同理,蛇撞到墙效果一样。


        2.7、撞到墙   
    void HitWall(int x, int y)
    {
        if (x >= row || y >= column || x < 0 || y < 0)
        {
            isLive = false;
        }
    }

        2.8、控制蛇

        在unity中,与winformwpf不一样了,不用使用Task.Run(() =>unity可以提供UpdateFixedUpdateLateUpdate三个函数提供实时更新

  •  Update:随时每帧更新
  •  FixedUpdate:帧前更新
  •  LateUpdate:帧后跟新

        执行顺序FixedUpdate>Update>LateUpdate

private void RunSnake(float timer)
{
    if (isLive == true)//不把isLive放到while循环条件,放这里可以蛇死亡后跳出循环,不会出现延迟效果
    {
        switch (mycurDirction)
        {
            case Dirction.D_up:
                hx++;
                break;
            case Dirction.S_down:
                hx--;
                break;
            case Dirction.A_left:
                hy--;
                break;
            case Dirction.D_right:
                hy++;
                break;
        }
        HitWall(hx, hy);
        HitSelf(hx, hy);
        stepNum++;
        TextShow();
        if (isLive == false) return;//直接推出更新,确保游戏没有延迟感觉
        DrawSanke(hx, hy);
        bdX = hx;
        bdY = hy;
        EatFood(hx, hy);
    }
}
    private void FixedUpdate()
    {
        KeyDown();
        timer -= (Time.deltaTime * moveBasicSpd);
        if (timer < 0)//每计时一次,蛇走一次
        {
            RunSnake(timer);
            timer = 1;
        }
    }

        timer -= (Time.deltaTime * moveBasicSpd); 是计时方法,当 timer 小于0则复原到1秒,重新反复的倒计时,实现蛇的秒更新

        涂色蛇的头和身子

    void DrawSanke(int x, int y)
    {
        //蛇头格子
        CellData curcell = new CellData();
        curcell = GetSnakeCell(x, y);
        SetCellPainting(curcell.cellObj.GetComponent<MeshRenderer>(), "_MianColor", snakeHeadColor);
        curcell.cellObj.transform.position = new Vector3(curcell.cellObj.transform.position.x,
            curcell.cellObj.transform.position.y, -1);

        //蛇身子格子
        CellData bodycell = new CellData();
        bodycell = GetSnakeCell(bdX, bdY);
        SetCellPainting(bodycell.cellObj.GetComponent<MeshRenderer>(), "_MianColor", snakeBodyColor);
        snakecelldataList.Add(bodycell);
        bodycell.cellObj.transform.position = new Vector3(bodycell.cellObj.transform.position.x,
            bodycell.cellObj.transform.position.y, -1);

        //去除尾巴
        CellData celldata = snakecelldataList[0];
        GameObject endcellobj = celldata.cellObj;
        SetCellPainting(endcellobj.GetComponent<MeshRenderer>(), "_MianColor", cellColor);
        celldata.cellObj.transform.position = new Vector3(celldata.cellObj.transform.position.x,
            celldata.cellObj.transform.position.y, 0);
        snakecelldataList.RemoveAt(0);
    }

        蛇的头部和身子的格子坐标 transform.position 的z坐标设置为 -1,是让蛇突出位置。

        GetComponent<MeshRenderer>() 获取格子的材质图层方法

        涂色格子函数方法

    public void SetCellPainting(MeshRenderer meshreder,string str, Color col)
    {
        meshreder.material.SetColor(str, col);
    }

        将 MeshRenderer material shader 进行颜色修改


        2.9、Shader

        我们写入一个shader,用来为格子渲染

Shader "Custom/Colorshader"
{
    Properties
    {
        _MianColor ("MianColor ",Color)=(1.0,1.0,1.0,1.0)
        _LineColor ("LineColor",Color)=(1.0,1.0,1.0,1.0)
        _LineWidth ("LineWidth", range(0,0.5)) = 0.1
    }
    SubShader
    {
        Tags { "Queue"="Transparent" }
        Pass
        {
            CGPROGRAM
 
            #pragma vertex vert
            #pragma fragment frag
 	        #include "UnityCG.cginc"
            fixed4 _MianColor;
            fixed4 _LineColor;
            fixed _LineWidth;
 
            struct a2v
            {
                float4 vertex:POSITION;
                float2 uv : TEXCOORD0;
                
            };
 
            struct v2f
            {
                float4 vertex:SV_POSITION;
                float2 uv : TEXCOORD0;
        
            };
            
            v2f vert(a2v v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }
 
            fixed4 frag(v2f i):SV_Target
            {
                fixed3 col = fixed4(0,0,0,1);
                col = _MianColor + col + saturate(step(i.uv.x, _LineWidth) + step(1 - _LineWidth, i.uv.x) + 
                step(i.uv.y, _LineWidth) + step(1 - _LineWidth, i.uv.y)) * _LineColor;
                return fixed4(col,1.0);
            }
            
            ENDCG
        }
    }
}

       saturate(x) 函数作用是如果x取值小于0,则返回值为0。如果x取值大于1,则返回值为1。若x在0到1之间,则直接返回x的值

       step(x,y) 函数作用是如果y大于x,则step()函数返回1.0;如果y小于等于x,则返回0.0


三、其他功能函数

        3.1、ugui类

        存放游戏数据的UI类型,内容创建三个文本UI

public class UImanager : MonoBehaviour
{
    public Text myText1;
    public Text myText2;
    public Text myText3;
}

        

    void TextShow()
    {
        uImanager.myText1.text = "移动 步数:" + stepNum;
        uImanager.myText2.text = "吃食物数量:" + eatFoodNum;
        uImanager.myText3.gameObject.SetActive(!isLive);
        uImanager.myText3.text = isLive == false ? "游戏结束!" : string.Empty;
    }

         myText3默认设置为隐藏状态,unity中隐藏GameObject,取消勾选即可


        3.2、3d视角显示
        Camera.main.transform.position = SHOW3DV3;
        Camera.main.transform.eulerAngles = new Vector3(-15, 0, 0);

        Camera.main 是我们的主摄像头,将摄像头的坐标修改,并将 eulerAngles 仰角修改,即可得到一个3D视角


四、运行图示 


五、总结

  •    比起winform和wpf原生开发游戏,unity是专业的游戏开发软件,渲染有自己的渲染系统,自己的帧系统。
  •    Resources加载资源,需要 将资源放置在该文件夹下,也可以在Resources下创建子文件夹分类管理。
  •    unity的键盘按键不需要添加事件进行监听,按键方法可以直接放在Update,FixedUpdate,LateUpdate三个函数中。
  •    shader可以进行动态修改,即渲染可以进行实时更新。
  •    物体上可以挂在脚本,脚本需要继承MonoBehaviour才能挂载。

写着写着就这么多了,可能不是特别全,不介意费时就看看吧。有时间还会接着更新。 


六、工程下载地址   

【c#unity3D贪吃蛇】工程下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学与用

原创作者,在线要饭

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值