首页 > 编程语言 >【c# Unity-Shader版本贪吃蛇教程】一张Plane渲染的Shader贪吃蛇

【c# Unity-Shader版本贪吃蛇教程】一张Plane渲染的Shader贪吃蛇

时间:2025-01-10 15:00:41浏览次数:3  
标签:c# 0.5 Shader shader int 贪吃蛇 new cellVect2

前言

        开局一张plane,其余靠shader编。本游戏为shader绘制贪吃蛇,没有3D模型,想了解3D版本的开发,可以跳转到【c# Unity贪吃蛇教程】

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

        本期用Unity制作shader制作,主要学习shader的网格绘制shader矩形的移动shader坐标等内容。

        项目教程完全小白化,大神不用瞄了,本项目有源码下载。


目录

 一、Shader内容

        1.1、创建Shader文件

        1.2、Shader变量

        1.3、Shader单个格子绘制

        1.4、Shader的CGPROGRAM

        1.5、Shader顶点着色

        1.7、Shader网格绘制

        1.8、Shader网格绘制

        1.9、Shader蛇的绘制

 二、主要功能函数

        2.1、地图数据中转

        2.2、蛇的操作

        2.3、画蛇

        2.4、生成食物

        2.5、吃到食物

三、运行图示 

四、总结

五、工程下载地址   


 一、Shader内容

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

        1.1、创建Shader文件

        创建一个Unlit shader

       

       


        1.2、Shader变量

        清除内部内容,写入文件路径和名称

Shader "Custom/DrawGrid"

        接着写入背景色,和贪吃蛇二维网格的变量

		_backgroundColor("BackgroundColor",Color) = (1.0,1.0,1.0,1.0)//面板背景色
 		_gridColor("GridColor",Color) = (0.5,0.5,0.5)//  网格的颜色
		_lineColor("LineColor",Color) = (0.0,0.0,1.0)//坐标轴的颜色
 		_tickWidth("TickWidth",Range(0.01,1)) = 0.1 //网格的间距
 		_gridWidth("GridWidth",Range(0.0001,0.1)) = 0.008//网格的宽度

       写入格子宽高

		_CellWidth("CellWidth",Range(0.1,1)) = 0.008//格子的宽度
		_CellHeight("CellHeight",Range(0.1,1)) = 0.008//格子的高度

        写入蛇头部的变量

		_HeadCellColor("HeadCellColor",Color)=(1,1,1,1)//蛇头的颜色
		_HeadCellCenterPos("HeadCellCenterPos",Vector)=(0.5,0.5,0,0)//蛇头的位置

        写入蛇身子变量

		_CellColor("CellColor",Color)=(1,1,1,1)//蛇身的颜色
		_CellCenterPos("_CellCenterPos",Vector)=(0.5,0.5,0,0)//蛇身的坐标
		_CountNum("CountNum",int) = 1//身子格子的数量

        写入食物变量

		_FoodCellColor("FoodCellColor",Color)=(1,1,1,1)//食物的颜色
		_FoodCellCenterPos("FoodCellCenterPos",Vector)=(0.5,0.5,0,0)//食物的坐标

        1.3、Shader单个格子绘制

        游戏中,我们的蛇,食物需要用到格子,这里我们绘制一个格子方法

		float2 Cellrect(float2 pos,float width,float height,float2 center)
		{
			float2 realp = pos - center;
			float2 rectwidth = width * 0.5;
			float2 rectheight = height * 0.5;
			float hotz = step(-rectwidth.x, realp.x) - step(rectwidth.x, realp.x);
			float vert = step(-rectheight.y, realp.y) - step(rectheight.y, realp.y);
			return hotz * vert;
		} 

        1.4、Shader的CGPROGRAM

        CGPROGRAM 是定义Vertex/Fragment 的地方,它本身是放在Pass块内

		Pass  
		{ 
			CGPROGRAM  
			#pragma vertex vert  
			#pragma fragment frag  
            ....略


        1.5、Shader顶点着色

        顶点结构主要做空间变换以及纹理映射

			struct appdata  
			{  
				float4 vertex:POSITION;  
				float2 uv:TEXCOORD0;  
			};  
			struct v2f  
			{  
				float2 uv:TEXCOORD0;  
				float4 position:TEXCOORD1;
				float4 vertex:SV_POSITION;  
			};  
			v2f vert(appdata v)  
			{  
				v2f o;  
				o.vertex = UnityObjectToClipPos(v.vertex);  
				o.position=v.vertex;
				o.uv = v.uv;  
				return o;  
			}  

        POSITION是模型空间下的顶点坐标

        SV_POSITION是裁剪空间下的坐标

        TEXCOORD是纹理集

        UnityObjectToClipPos用于将物体空间的顶点坐标转换到裁剪空间的坐标


        1.7、Shader网格绘制

        顶点结构主要做空间变换以及纹理映射

				//定义网格的的间距  
				const float tickWidth = _tickWidth;  
				if (mod((cpos.x + 0.0125) * 2, tickWidth) < _gridWidth)  
				{  
					col = gridColor;  
				}  
				if (mod((cpos.y + 0.025), tickWidth) < _gridWidth)  
				{  
					col = gridColor;  
				}  

        1.8、Shader网格绘制

        在之前的贪吃蛇中,我们采用了二维数组或者一位数组结合数据类,这里我们直接用shader绘制网格。  

			//围墙
			col = col + saturate(step(i.uv.x, _wallWidth - .025) + step(1 - _wallWidth, i.uv.x - .025) + 
			step(i.uv.y, _wallWidth) + step(1 - _wallWidth, i.uv.y)) * _wallColor;

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

        step(a,x) 如果x <a  则返回0 如果x>=0 则返回1


        1.9、Shader蛇的绘制
//头部格子
float3 headcellpos = float3(_HeadCellCenterPos.x , _HeadCellCenterPos.y , _HeadCellCenterPos.z);			
float headcellrect= Cellrect(cpos, _CellWidth, _CellHeight, headcellpos);
float3 hcol = headcellrect * _HeadCellColor;

//身体格子
float bodycellrect = 0;
float3 bodycol = float3(0, 0, 0);
float2 allgridpos = float2(0, 0);
for (int j = 0; j < _CountNum; j++) 
{
	float3 cellpos = float3(_CellCenterPos.x , _CellCenterPos.y , _CellCenterPos.z);			
    bodycellrect = Cellrect(cpos, _CellWidth, _CellHeight, cellpos);	
	bodycol = bodycellrect * _CellColor;
}

//食物格子
float3 foodpos = float3(_FoodCellCenterPos.x - 4.9 + 0.02, _FoodCellCenterPos.y - 4.8 + 0.05, _FoodCellCenterPos.z);	
float foodrect = Cellrect(cpos, _CellWidth, _CellHeight, foodpos);
float3 fcol = _FoodCellColor * foodrect;

 二、主要功能函数

         前面几期已经讲过了,这里说下不同的函数

        2.1、地图数据中转

        因为shader变量里面没法做数据集合和数据存取,我们依然需要一个数据集合变量作为数据存取地方。

        然后就是将数据生成对应的地图数据

    private void CreateGrid()
    {
        int index = 0;
        // 创建行
        for (int i = 0; i < row; i++)
        {
            for (int j = 0; j < column; j++)
            {
                CellData celldata = new CellData();
                celldata.cellIndex = index;
                celldata.cellVect2 = new Vector3(i, j);
                if (i % (row - 1) == 0 || j % (column - 1) == 0)
                {
                    celldata.mapType = 1;
                }
                else
                {
                    celldata.mapType = 0;
                }
                mapcelldataList.Add(celldata);
                index++;
            }
        }
    }

        2.2、蛇的操作

        与之前不同,不再用Update ,FixedUpdate作为动作更新,和计时相关操作。这里采用新的方法,协程计时更新游戏画面。

 IEnumerator SnakeTime()
 {
     while (true)
     {
         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) 
                 yield break;//直接推出更新,确保游戏没有延迟感觉
             EatFood(hx,hy);
             DrawSanke(hx, hy);
             yield return new WaitForSeconds(0.4f);
             List<CellData> templist = new List<CellData>();
             templist.AddRange(snakecelldataList);
             for (int i = 0; i < templist.Count; i++)
             {
                 if (i != 0)
                 {
                     Debug.Log(i + "  A  " + templist[i - 1].cellIndex + " " + templist[i - 1].cellVect2);
                     Debug.Log(i + "  B  " + templist[i].cellIndex + " " + templist[i].cellVect2);
                     snakecelldataList[i] = templist[i - 1];
                 }
             }
             bdX = hx;
             bdY = hy;
         }
     }
 }

        while是循环执行画面更新

        yield return new WaitForSeconds(0.4f);每0.4秒更新一次画面

        yield break;跳出这个游戏更新


        2.3、画蛇

        画蛇的方式与之前的课程也不同了,这里采用遍历蛇的集合,来更新蛇的涂色。

void DrawSanke(int x, int y)
{
    int count = snakecelldataList.Count - 1;
    girdMeshRenderer.material.SetInt("_CountNum", count);

    girdMeshRenderer.material.SetColor("_HeadCellColor", snakeHeadColor);
    CellData headcell = new CellData();
    headcell = GetSnakeCell(x, y); ;
    Vector2 vct2 = new Vector2(headcell.cellVect2.y * 0.25f - 4.9f + 0.02f,
        headcell.cellVect2.x * 0.5f - 4.8f + 0.05f);
    girdMeshRenderer.material.SetVector("_HeadCellCenterPos", new Vector4(vct2.x, vct2.y, 0, 0));
    snakecelldataList[0] = headcell;

    if (count > 0)
    {
        Vector4[] gridPositions = new Vector4[count];

        for (int i = 0; i < snakecelldataList.Count; i++)
        {
            if (i != 0)
            {
                girdMeshRenderer.material.SetColor("_CellColor", snakeBodyColor);
                CellData bodycell = snakecelldataList[i];
                Vector2 bvct2 = new Vector2(bodycell.cellVect2.y * 0.25f - 4.9f + 0.02f,
                    bodycell.cellVect2.x * 0.5f - 4.8f + 0.05f);
                girdMeshRenderer.material.SetVector("_CellCenterPos", new Vector4(bvct2.x, bvct2.y, 0, 0));
            }
        }
    }
}

        Shader.SetColor 设置shader某个变量的颜色

        Shader.SetInt 设置shader某个整型变量

        Shader.SetVector 设置shader的坐标


        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].cellVect2 == (tempList[i].cellVect2))
             {
                 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();
 }

        绘制食物

    void DrawFood()
    {
        CellData cell = GetSnakeCell((int)foodPoint[0].cellVect2.x, (int)foodPoint[0].cellVect2.y);
        girdMeshRenderer.material.SetColor("_FoodCellColor", foodColor);
        Vector2 vct2 = new Vector2(cell.cellVect2.y * 0.25f, cell.cellVect2.x * 0.5f);
        girdMeshRenderer.material.SetVector("_FoodCellCenterPos", new Vector4(vct2.x, vct2.y, 0, 0));
    }

        对shader中的变量操作


        2.5、吃到食物

        吃到食物的方法与之前课程也有不同,改为吃到食物后不立即更新蛇的长度,而是将吃到的食物临时加入集合,后续加长蛇身

        吃到食物

    void EatFood(int x, int y)
    {
        if (foodPoint.Count <= 0) return;
        //吃到食物
        if (x == foodPoint[0].cellVect2.x && y == foodPoint[0].cellVect2.y)
        {
            eatfoodPointIndexList.Add(foodPoint[0].cellIndex);
            CreateFood();
            eatFoodNum++;
            TextShow();
        }
        SnakeAdd(); 
    }

        加长蛇身

    void SnakeAdd()
    {
        if (eatfoodPointIndexList.Count <= 0)
            return;
        CellData cellData = new CellData();
        cellData = mapcelldataList.Find(x => x.cellIndex == eatfoodPointIndexList[0]);
        snakecelldataList.Add(cellData);
        eatfoodPointIndexList.RemoveAt(0);
    }

三、运行图示 


四、总结

  •    Shader的网格和蛇的信息要与脚本的变量对应,前者是存粹的显示模块,后者才是数据,前者是数据的映射。
  •    Shader这个模式中,协程比update好用,有兴趣的可以实时task.run。

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


五、工程下载地址   

【c#unity-shader网格贪吃蛇】工程下载

标签:c#,0.5,Shader,shader,int,贪吃蛇,new,cellVect2
From: https://blog.csdn.net/longstar777/article/details/145041160

相关文章

  • 【STM32】MCU运行多段代码,Flash程序更新的实现方式之一
    【STM32】MCU运行多段代码,Flash程序更新的实现方式之一文章目录BootLeader跳转到BootLeader跳转到Flash其他位置MCU运行多段代码其他程序更新烧录方式附录:Cortex-M架构的SysTick系统定时器精准延时和MCU位带操作SysTick系统定时器精准延时延时函数阻塞延时非阻塞延时......
  • JavaScript系列(16)--原型继承
    JavaScript原型继承......
  • AI编程工具怎么选?GitHub Copilot、AI Assistant与Cursor,谁是你的最佳拍档?
    大家好,欢迎来到程序视点!我是小二哥。在大模型技术迅猛发展的今天,AI编程已经很普遍了!从AI编程插件工具,到AI编程IDE,已经有很多的选择了!小二哥这里主要提及GitHubCopilot,JetBrainsAIAssistant和Cursor。主要原因是:对比多款产品后,这三款工具是目前读者小伙伴中受众最高的,也是小......
  • cursor无限使用指南
    准备邮箱新邮箱每次用新邮箱都去各大邮箱网站注册一个新的邮箱google邮箱别名在原有邮箱的「local-part」后面添加「+任意字符」,就对应了google邮箱别名例如现在拥有一个google邮箱:tucaoB@gmail.com那么对应的别名邮箱可以是:tucaoB+1@gmail.com、tucaoB+2@gmail.com、t......
  • CH585的SPI驱动WS2812
    目录链接:https://pan.baidu.com/s/1Su5dgmVWLre5kH2fYiGwQQ?pwd=wch6CH573系列/583系列/592系列MCU,在使用SPI模拟WS2812波形时,MISO-PA15引脚上的实时电平,会影响MOSI-PA14引脚上的空闲电平状态,故建议SPI驱动WS2812的场景下,固定PA15的电平,不要接其他外设。异常场景:当PA15引脚上......
  • python SQLAlchemy ORM——从零开始学习03 如何针对数据库信息进行排序
    03如何进行排序3-1准备工作:因为要排序,所以需要随机多谢数据,model见后文。也需要random进行随机frommodelimportUser,Enginefromsqlalchemy.ormimportsessionmakerimportrandomSession=sessionmaker(bind=Engine)session=Session()defadd_random():na......
  • CRC校验:原理、计算方法、优缺点及MATLAB代码示例
    引言        在数字通信和数据存储领域,数据的完整性和可靠性是至关重要的。为了确保数据在传输或存储过程中不发生错误,人们开发了许多错误检测与校正技术。其中,循环冗余校验(CyclicRedundancyCheck,简称CRC)是一种广泛应用的错误检测机制。本文将详细介绍CRC校验的基本......
  • 移植Android百度OCR:百度超轻量级中文OCR Android模型整合到自有工程中的问题与解决
    文章目录移植百度超轻量级中文OCR模型到Android项目的踩坑经历步骤一:下载并准备模型工程目录简介TestInferOcrTask步骤二:问题总结软件闪退识别结果为空log日志一直提示权限未赋予步骤三解决方案总结参考资料移植百度超轻量级中文OCR模型到Android项目的踩坑经历......
  • 通过循环展开减少循环控制的开销 c++实现
    循环展开是一种优化技术,通过减少循环控制的开销来提高程序性能。在C++中,可以通过手动展开循环来实现这一点。以下是如何在C++中实现循环展开的示例。示例:向量加法的循环展开我们将创建一个简单的向量加法示例,展示如何通过循环展开来提高性能。1.基本向量加法首先实现一个......
  • 【C#学习笔记】C#中委托
    概述C#的委托是一种类型安全的函数指针,用于引用方法,委托允许方法作为参数传递,或者将方法赋值给委托变量,并通过委托调用方法。委托类型:委托定义了方法的的签名([方法的参数类型和返回值]),所以,委托只能引用符合签名的方法。委托实例:委托是一个引用类型,可以实例化并指向一个或......