unity3d小游戏_unity3d小游戏源代码 - CSDN
精华内容
参与话题
  • 5款Unity3D制作的小游戏实例,新手必备
  • unity编写一个简单的小游戏

    万次阅读 多人点赞 2018-12-22 21:15:01
    unity编写一个简单的小游戏简易fly bird的制作关于flybird场景的搭建和素材的导入使用素材搭建game和scene制作柱体bird的scriptcolumn的spritecolumn的生成游戏判定游戏结束画面结语 简易fly bird的制作 关于flybird...

    简易fly bird的制作

    关于flybird

    这个游戏想必大家都玩过,今天我们要使用unity来写一个差不多的fly bird,需要unity和vs的配合,这个游戏规则就是控制一个bird通过一个个柱体之间的缝隙。在这里插入图片描述

    场景的搭建和素材的导入

    打开unity,选择文件路径,因为这个游戏是2D的,选择2D后我们create。素材对于我们学生党很难搞,我们百度asset store就找到unity为我们提供素材的地方,在搜索栏搜索bird就好了,在筛选的位置选择价格免费,很方便的,在里面随便选一个bird就好了,import到你的unity项目里。觉得这个素材质量不太好的,自己可以使用ps在网图上面扣下来自己想要的bird。在这里插入图片描述

    使用素材搭建game和scene

    在你的素材包选择你喜欢的bird拖入scene中,我们要完成一个fly的bird,我们需要借用unity中的刚体,就是给bird在右侧的inspector加上脚本Rigidbody 2D。
    这样这个bird就具有我们想让他有的自然下落等一系列物理条件
    运行一下可以看到bird的自然下落,就ok了
    我们的bird在游戏里不可能是一直下落的,这样需要我们给这个场景加上一个边界。我是直接使用ps弄了一个白色柱体(你还可以使用unity自带的素材,也可以按上面导入),游戏边界我们让他有什么条件呢,让他是个类似实体平面,可以让bird停留在上面这时候使用collider2D这个属性(当然我们的bird上也必须有这个collider,里面有个polygon collider2D这个很适合这种不规则形状的物体),先对一个柱体完成上面的操作 剩下的复制(ctrl+d就可以直接复制)就好了 (横竖不一样,调节z的角度为90就OK了),将边框完成。运行一下发现bird会落在白色边界上,这样场景就搭建好了。

    制作柱体

    我们还是使用上面的白色柱体(不喜欢的可以自己随便导入柱体,没有影响的),对柱体加上collider(我们游戏规则,bird必须在两个柱体之间的缝隙通过,两个柱体是不可以穿过),ok后复制一个,调整二者的位置,形成我们想得到的样子,然后我们右键建一个gameobject并命名为column,将上面建好的两个柱体拖到这个column里面,在column上(注意不要点在那两个柱体上)增加一个box collider2D并使用edit collider,然后对这个的is trigger进行勾选(这个的意思让bird可以在这个之间的collider通过,方便我们后面编写script进行计分),让column成为这样并拖入assets中即可成为我们的素材(毕竟以后我们要不断的生成column):
    在这里插入图片描述

    bird的script

    现在开始我们快乐的edit script环节,在assets右键生成一个文件夹,命名为code,在code里面create一个c# script命名为birdcontrol(这几步最好一次完成,否则到时候加脚本到bird上会出现找不到脚本的bug,真的莫名其妙),打开这个birdcontrol,我们会看到自动生成的代码里面有两个函数(可以使用的函数有4个,每一个的具体含义自己百度,我就不赘述了)
    将下面的代码拖到bird的inspector中,对speed赋值,通过空格键,操作bird的移动。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class birdcontrol : MonoBehaviour
    {
    
        Rigidbody2D bird;
    
        public float speed;                                         //使用speed方便在项目里面选择一个合适的倍数
    
        // Use this for initialization
        void Start()                                                //游戏一开始就会调用的函数
        {
            bird = GetComponent<Rigidbody2D>();                     //将游戏里面的bird引入
        }
        // Update is called once per frame
        void Update()                                               //游戏每一帧都会调用的函数
        {
            if (Input.GetKeyDown("space"))
            {
                bird.AddForce(new Vector2(0, 2 * speed));           //vector2相当于一个有xy的矢量
            }
        }
    }
    
    

    column的sprite

    我们完成了bird在竖直方向的移动控制,接下来要完成在水平方向的移动,总所周知运动是相对的,我们可以通过让column的移动,来完成水平方向的移动,让他以一个均匀的速度自动向右移动translation。
    还有当bird通过column时会加一分,这时候使用ontriggerexit2D这个函数来判断。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class columncontrol : MonoBehaviour {
        public float speed;
    	// Use this for initialization
    	void Start () {
    		
    	}
    	
    	// Update is called once per frame
    	void Update () {
            this.transform.Translate(new Vector2(-speed,0));
    	}
    
        private void OnTriggerExit2D(Collider2D collision)
        {
            if (collision.gameObject.tag == "player")
            {
                Debug.Log("1");
            }
        }
    }
    
    

    column的生成

    游戏中不可能只有一个柱体,而且也需要他的上下变动,这时候我们需要在游戏的最右端的一个位置不断地生成位置随机的column,这时候我们需要给他一个生成点spawnpoint,右键一个empty,命名为spawnpoint,然后我们为他添加脚本

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class spawn : MonoBehaviour {
    
        public GameObject column;
        public float colddown = 2f;
        public float NextSpawn;
    	// Use this for initialization
    	void Start () {
    		
    	}
    	
    	// Update is called once per frame
    	void Update () {
            if (Time.time > NextSpawn)
            {
                NextSpawn = Time.time + colddown;
    
                Vector3 spawnP = transform.position;
                spawnP.y += Random.Range(3.0f, -3.0f);
                Instantiate(column, spawnP,transform.rotation);
            }
    	}
    }
    
    

    最后别忘记将column应用上去
    在这里插入图片描述

    游戏判定

    我们都知道当这个bird碰到墙壁,柱体上,游戏都是失败,这时候就会停止游戏,所以我们给他一个hitted和GM的脚本进行判断(hitted是要添加到ground和column上)

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    //GM.cs
    public class GM : MonoBehaviour {
    
        public static int score = 0;
    
        public static bool Active = true;
    
    	// Use this for initialization
    	void Start () {
    		
    	}
    	
    	// Update is called once per frame
    	void Update () {
    		
    	}
    }
    
    
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    //hitted.cs
    public class Hitted : MonoBehaviour {
    
    	// Use this for initialization
    	void Start () {
    		
    	}
    	
    	// Update is called once per frame
    	void Update () {
    		
    	}
    
        private void OnCollisionEnter2D(Collision2D collision)
        {
            if (collision.gameObject.tag == "Player")
            {
                GM.Active = false;
            }
        }
    }
    
    

    一旦bird与其碰上就会立刻停止column的运动和生成,所以我们在上面的也要相应的修改

    if (GM.Active)		//columncontrol.cs
            {
                this.transform.Translate(new Vector2(-speed, 0));
            }
    
    if (Time.time > NextSpawn&&GM.Active)	//spawn.cs
            {
                NextSpawn = Time.time + colddown;
    
                Vector3 spawnP = transform.position;
                spawnP.y += Random.Range(3.0f, -3.0f);
                Instantiate(column, spawnP,transform.rotation);
            }
    

    游戏结束画面

    右键在UI里面选择text,并把其命名为score(用于显示分数)再canvas里面再新建一个text,命名为gameover(显示Game Over!),并在上面的active位置把勾取消(不显示文本,当游戏结束时才会出现),下面是UI.cs

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class UI : MonoBehaviour {
        public GameObject gameover;
        public Text score;
    	// Use this for initialization
    	void Start () {
    		
    	}
    	
    	// Update is called once per frame
    	void Update () {
    
            score.text = "Score" + GM.score;
    
            if (!GM.Active)
            {
                gameover.SetActive(true);		//这里会让gameover显示	
            }
    	}
    }
    
    

    UI.cs要添加到canvas上并在相应的位置把score和gameover应用上去。在这里插入图片描述

    结语

    这个flybird较为简单,但是动手实践的时候会让你受益颇多,也是方便你入门的一个程序,我是用的unity和vs的组合比较方便,至于怎么配置的环境和他们之间的关系,百度一下就好了。

    展开全文
  • 这套教程涵盖了Unity Mesh编程、模拟水算法(water simulations)、方块移动算法(marching-cubes)等等。这是一套比较有深度的教程,可能需要你了解一些Unity和C#相关的知识。 二、原文链接 原文出处:公众号墙外的...

    推荐阅读

    一、前言

    这套教程涵盖了Unity Mesh编程、模拟水算法(water simulations)、方块移动算法(marching-cubes)等等。这是一套比较有深度的教程,可能需要你了解一些Unity和C#相关的知识。

    二、效果图

    在这里插入图片描述

    三、正文

    一、基础篇:生成数据块

    预备开始

    首先,我们先来创建一个空的项目,命名随意即可。

    然后创建在Assets下创建一个文件夹,命名为“Scripts”,并创建三个C#脚本,如下图所示:
    在这里插入图片描述
    Chunk用于存储方块数据和创建网格,并且对网格进行渲染和碰撞;Block用于存放方块需要的信息;MeshData用于存储网格数据。

    Chunk.cs脚本:
    在这里插入图片描述
    首先,我们要求该脚本必须包含上个组件:MeshFilter、MeshRenderer、MeshCollider。我们的数据块(就是由一堆方块组成的大方块~)需要这三个组件完成网格的创建和碰撞。

    然后,我们有三个变量。我们有一个Block类型的三维数组变量 blocks,Block类用于存放方块需要的信息,因此我们这个blocks变量就是用于存放一个数据块的方块的信息。

    chunkSize是一个静态变量,它用于表示我们的数据块各个方向的大小(就是长宽高的大小)。

    最后我们有一个bool类型的变量,用于标志该数据块是否在每帧结束后更新。

    其次有三个函数,分别为GetBlock、UpdateChunk、RenderMesh。GetBlock用于获取对应位置的方块;UpdateChunk用于更新数据块的网格数据,然后将更新的数据提供给RenderMesh去渲染。

    MeshData.cs脚本:
    在这里插入图片描述
    由于这个脚本只是为了存储数据,因此不必继承自MonoBehaviour。

    前三个变量(vertices、triangles、uv)是用于渲染网格用的,后两个用于网格碰撞(colVetices、colTriangles)。

    Block.cs脚本:
    在这里插入图片描述
    同样的,Block脚本也不需要继承自MonoBehaviour,并且它将会是所有方块的基类。主要用于存储方块所需信息。

    BlockData函数用于生成该方块的网格信息。

    我们来设想一下,假如我们的数据块是有25个方块组成的,那么在相邻的方块之间,有一些面就不必渲染出来,浪费系统资源。因此,接下来让我们进行剔除多余面数的处理。

    首先,我们需要一个函数去判断两个方块是否相邻,如果相邻则对各个方向的面进行剔除处理。

    在那之前,让我们先定一个表示方向的枚举(Block脚本中):
    在这里插入图片描述
    然后让我们为Block脚本添加判断的函数:
    在这里插入图片描述
    因为Block脚本是所有方块类的基类,所有我们在Block脚本中对IsSolid函数没有进行判断处理,全部返回true。

    现在让我们开始写一些我们的BlockData函数,在这个函数中,我们会根据当前方块对应方向上相邻方块的面进行剔除处理。
    在这里插入图片描述

    上图中的注释也说得很清楚了。举个栗子:判断当前方块顶上相邻的方块是否有底面,如果有则当前方块就不制作顶面,如下图分析所示:
    在这里插入图片描述
    接下来就是添加需要绘制对应的面的函数了。

    上:
    在这里插入图片描述
    上面的注释也说得很清楚了,但是克森还是给你们秀一秀我的美术功底。
    在这里插入图片描述
    其它的面就不细讲,代码如下:

    下:
    在这里插入图片描述
    东:
    在这里插入图片描述
    北:
    在这里插入图片描述
    南:
    在这里插入图片描述
    西:
    在这里插入图片描述
    添加点之后,我们还要把这些点组合成三角形,因此在函数的最后调用了MeshData里的AddQuadTriangles(),由名字可知道,该函数用于添加面片,因此我们要用这四个顶点组合成一个面片,让我们回到MeshData中添加该函数:
    在这里插入图片描述
    再给大家上一次克森的美术作品,相信大家都能理解了吧。
    在这里插入图片描述
    接下来,让我们创建一个新的脚本,命名为“BlockAir”,让它继承值Block类,如下所示:
    在这里插入图片描述
    Okey,现在让我们回到Chunk脚本中,开始添加方块进行测试咯。添加如下代码:
    在这里插入图片描述
    首先声明两个变量,一个为MeshFilter(网格过滤器)类型,另一个为MeshCollider(网格碰撞器)类型。分别用于存储和设置我们对应数据库上的组件属性。

    首先通过GetComponent方法获取对应物体上的对应的组件,然后初始化了我们的数据块(blocks),我们的数据块是一个161616大小的正方体,里面由一堆小方块组成。当前数据库的小方块类型为 BlockAir。

    然后修改了该数据库blocks[3, 5, 2]的方块数据,修改为Block类型方块。

    最后调用UpdateChunk函数进行数据的更新。

    好的,接下来让我们完善我们的UpdateChunk函数:

    首先声明一个类型为MeshData的变量。然后循环遍历blocks进行数据的更新(就是调用每一个方块的BlockData函数,而BlockData函数则是用来处理方块的网格数据,例如剔除面等等)。

    最后就是调用RenderMesh函数将更新好的网格数据传入,然后进行网格的渲染。

    那么,接下来让我们完成我们的RenderMesh函数:
    在这里插入图片描述
    这个函数很简单,就是先调用Clear函数清除上一次网格的数据,然后重新设置即可。

    这一篇只是简单的介绍怎么生成数据块,还没涉及到贴图和碰撞,所以在RenderMesh函数里只是更新了网格数据。下一篇则教大家如何添加贴图到数据块上。

    说那么多,先看看效果。

    首先创建一个空物体,然后为该物体添加Chunk脚本。你将会发现如下效果:
    在这里插入图片描述
    在这里插入图片描述
    为什么方块会跑那去了,为什么会是紫色的呢?因为该方块没有材质,所以是紫色的。因为我们设置了该方块的位置为(3,5,2),那我们是在哪里设置的呢,其实是在这个地方设置了,如下图所示:
    在这里插入图片描述
    这个时候你可以创建一个Cube物体去比对一下就知道了,下图演示:
    在这里插入图片描述
    看来是这样的没错。好,接下来克森带大家来走一走这个运行时候的步骤:

    1.首先我们先进行实例化数据块,也就是Chunk类里面的blocks变量。

    在这里插入图片描述
    2.调用UpdateChunk更新网格数据,在UpdateChunk中又调用了各个方块的BlockData函数生成网格数据。

    在这里插入图片描述
    3.在BlockData中我们对当前方块根据检测相邻方块进行剔除面操作。
    在这里插入图片描述
    (由于函数太大,所以只截一小部分)

    4.最后调用RenderMesh对网格数据进行更新。

    在这里插入图片描述
    在这里,为什么我们只生成了一个方块呢。因为要想生成方块,就必须调用BlockData函数,而BlockAir的BlockData函数里我们只做了一个返回,并没有生成网格数据,

    在这里插入图片描述
    因此只生成了一个方块,也就是我们在后面修改的那个位置为(3,5,2)的方块,因为在Block类里BlockData函数已经生成了网格数据。

    在这里插入图片描述
    为什么克森不直接将所有的方块实例化为Block类呢,原因是这样做会造成数组下标越界。大家还记得下面这个函数吗?
    在这里插入图片描述
    假设当前方块的y为16,这y+1便会越界。对于这个处理,后续的文章中会有介绍。敬请关注吧。

    忘记说最后一点了,对于为什么会生成一个方块呢。原因就是在下图判断中,如果返回为false则制作该方块对应的面,然后我们的BlockAir的IsSolid函数返回的就是false,因此我们的方块就出来了。
    在这里插入图片描述

    二、基础篇:生成贴图

    上一次我们制作了一些函数去设置网格数据,在这篇文章中,我将教大家怎么为方块贴图。

    贴图嘛,首先你得有图才行呀。因此请你将下面这张图右键保存到你的工程目录中:

    在这里插入图片描述

    打开该图片的导入设置,设置图片类型(Texture Type)为 Advanced,然后将所有的勾都去掉,将过滤模式(Filter Mode)设置为 Point(no filter)。最后的设置如下所示:
    在这里插入图片描述
    好,让我们来看看为什么要这么设置呢?大家可从下面几张图中看出效果:
    在这里插入图片描述
    (point模式)
    在这里插入图片描述
    (Bilinear模式)

    为什么会这样呢?总之请大伙们记住的就是,如果做像素游戏那么就选择Point模式就对了。而Bilinear模式会对纹理进行插值计算,会先找出最接近像素的四个图素进行插值运算,这会使得纹理更为平滑;而Point模式只是对纹理进行简单的插值,会使用包含像素最多的部分的图素来贴图,容易出现所谓的“马赛克”想象。而“马赛克”想象正是我们想要的效果。

    接下来拖拽该图片到上一篇创建好的Chunk物体上,这时你会看到工程目录中多出了一个Metarials文件夹,这是由Unity根据拖拽的纹理自动生成的,这边省去了手动创建纹理的时间。

    接下来打开我们的Block脚本,我们需要使用一个结构体去保存纹理信息,以便对纹理坐标进行修改:

    在这里插入图片描述
    接下来我们创建一个函数,该函数用于根据指定方面的面对纹理进行修改,简单的说就是给指定方向上的面贴指定的纹理:

    在这里插入图片描述
    该函数很简单,就是返回一个修改好的Tile类型的结构体,也就是纹理上的坐标位置。

    接下来我们需要一个float类型的产量,用于表示纹理位置的比例:

    在这里插入图片描述
    为什么会是0.25f呢,下图有解释:
    在这里插入图片描述
    如果还不理解,没关系,我们继续往下走。

    下面的函数用于生成对应方向面的UV位置:

    在这里插入图片描述
    好,暂时不解释,接下来再在faceData*(*代表各个方向上的函数)函数中添加如下代码:
    在这里插入图片描述
    最后在Chunk.cs脚本的RenderMesh函数中添加两行代码来渲染我们的UV贴图即可:
    在这里插入图片描述
    先不做解释,先测试一番看看效果:
    在这里插入图片描述
    一个石块便完美的展示出来了。

    好,接下来让我们来分析分析该石块制作的过程:

    首先,用于生成UV位置的主要函数是如下函数:

    在这里插入图片描述
    然后该函数调用了TexturePosition函数来生成UV的 x 和 y 的位置:
    在这里插入图片描述
    在该函数中将 x 和 y 都设置为0。然后让我们回到FaceUVs函数中来,计算计算最终生成UV的位置:
    在这里插入图片描述
    对应我们纹理中的位置如下图所示:
    在这里插入图片描述
    由于我们在各个方向的面都使用了同一个纹理坐标,因此该方块的每个都面都是上图中的纹理,接下来让我们生成文章前头看到的那个草块。

    让我们新建一个C#脚本,命名为“BlockGrass.cs”,双击打开脚本,为其添加如下代码:
    在这里插入图片描述
    其实这个脚本很简单,我们只是在TexturePosition函数中对某个方面上的面做了些特殊操作,仅此而已。

    然后在初始化创建方块的地方为其添加如下代码:
    在这里插入图片描述
    这时候Play游戏,你将会看到如下图所示效果:

    在这里插入图片描述
    至于为什么会生成,前面已经有做过解释,这里就不再赘述。

    三、基础篇:生成网格碰撞

    上一次我们为方块添加了贴图,这一章我将为方块添加网格碰撞。

    从下图可知,我们的方块已经有网格数据了,然而我们的Mesh Collider的Mesh属性还没有任何网格数据。

    在这里插入图片描述

    接下来,让我们来为方块添加网格碰撞。

    提示
    更新网格碰撞数据是很耗性能的,因此我们应该把网格碰撞设计得越简洁越好。如果你不打算使用Unity自带的物理系统,那么你可以百度一下“AABB”碰撞检测算法,这是最简单的碰撞算法之一了。

    因为网格碰撞会随着网格数据的变化而变化,为了方面网格碰撞的数据能与网格数据同步变化,首先在“MeshDta.cs”脚本中添加下面变量:

    在这里插入图片描述
    当该布尔变量为true的时候,我们在对网格添加面片数据的时候,也会对网格碰撞的面片数据进行更新,如下图所示:
    在这里插入图片描述
    请大家注意一下,我们为网格碰撞添加面片的时候使用的是colVertices.Count,而不是vertices.Count。

    那么接下来,我们为网格碰撞添加顶点,如下图所示:

    在这里插入图片描述
    好了,让我们回到“Block.cs”脚本中,修改一下网格顶点的添加方式:如下图所示:
    在这里插入图片描述
    提示
    一定要将“Block.cs”原来添加顶点的方式换成转换的方式!!!

    这样便可在useRenderDataForCol为true的时候,添加网格的顶点数据的同时也添加了网格碰撞的顶点数据,是不是既方便又简单呀。

    网格碰撞的顶点数据添加好了,接下来添加网格碰撞的三角形数据的步骤和添加顶点的步骤相似。

    首先处理一下添加三角形的函数,如下图所示:

    在这里插入图片描述
    提示
    目前我们只使用了AddQuadTriangies()函数,上图的函数也许会在后面的开发中用的,之所以提前写上是因为该函数和本篇文章相关联。

    接下来回到我们的“Block.cs”脚本的添加下面一行代码就可以跑一跑测试了:
    在这里插入图片描述
    将这行代码添上之后,按下ctrl+s,之后回到Unity点击Play按钮,将会看到如下图所示效果:
    在这里插入图片描述
    OK,这一篇结束

    四、基础篇:添加地形管理

    上一次我们为方块添加了网格碰撞,这一章将会创建一个地形管理相关的类。

    首先创建一个名为“WorldPos.cs”的脚本,双击打开,码入如下代码:

    using System.Collections;
    
    public struct WorldPos
    {
        public int x, y, z;
    
    public WorldPos(int x, int y, int z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }
        // 重写了 Equals 方法,便于比较和方便字典操作(后面有讲)
        public override bool Equals(object obj)
        {
            if (!(obj is WorldPos))
                return false;
    
    WorldPos pos = (WorldPos)obj;
            if (pos.x != x || pos.y != y || pos.z != z)
            {
                return false;
            }
            else
            {
                return true;
            }
        }
    }
    

    这个脚本里的代码很简单,也就是创建了一个带有三个 int 类型的结构体,然后重写了 Equals 方法,至于为什么这么做,接着往下看便知道了。

    接下来创建一个名为“World.cs”的脚本,双击打开脚本,码入如下代码:

    sing UnityEngine;
    using System.Collections.Generic;
    
    public class World : MonoBehaviour {
    
    // 用来管理 chunk
        public Dictionary<WorldPos, Chunk> chunks = new Dictionary<WorldPos, Chunk>();
        // chunk 预设体,用做创建对象的模板
        public GameObject chunkPrefab;
    }
    

    咳咳咳,到这里就知道为什么了要创建“WorldPos.cs”脚本,和重写 Equals 方法了吧。

    那就是因为我们使用了字典结构来管理我们的 Chunk,而 key 为 WorldPos,我们都知道字典的 key 是唯一的,如果想要得到某个 key 对应的值,那么我们就需要传入相等的 key 才能够得到对应的值,因此我们就需要重写 WorldPos 的 Equals 方法。如果不重写 Equals 方法的话,默认对比两个 WorldPos 则是通过它们各自的 Hash 值来对比的,而每个 new 出来的对象的 Hash 都不相同,所以这就是为什么要重写 Equals 方法的主要原因。

    逼逼那么多,也不知道说得对不对,233。

    Ok,让我们回到 Unity 编辑器中,然后按步骤执行如下操作:
    在这里插入图片描述
    在这里插入图片描述
    Ok,接下来让我们运行 Unity,你会发现什么都没有。

    让我们双击打开“Chunk.cs”脚本,修改如下代码:
    在这里插入图片描述
    嚯嚯嚯,初始化代码都去掉了,我们要怎么创建 chunk 方块呀?

    稍等骂爹,我们不是有一个用于管理 chunk 的类吗?对的,接下来让我们在 “World.cs”脚本里对“Chunk”进行初始化。回到“World.cs”脚本,添加如下代码:

    void Start()
        {
            // 初始化世界
            for (int x = 0; x < 1; x++)
            {
                for (int y = 0; y < 1; y++)
                {
                    for (int z = 0; z < 1; z++)
                    {
                        // 之所以乘上 Chunk.chunkSize,是用于确保每一个 Chunk 的范围
                        CreateChunk(x * Chunk.chunkSize, y * Chunk.chunkSize, z * Chunk.chunkSize);
                    }
                }
            }
        }
    

    上图中的代码较为简单,就不细说了,接下来添加“CreateChunk”函数,如下图所示:

     public void CreateChunk(int x, int y, int z)
        {
            // 创建 WorldPos 对象,并初始化
            WorldPos worldPos = new WorldPos(x, y, z);
    
    // 使用预设体创建游戏对象
            GameObject newChunkObject = Instantiate(
                            chunkPrefab, new Vector3(x, y, z),
                            Quaternion.Euler(Vector3.zero)
                        ) as GameObject;
    
    // 获取对象上的 Chuck 组件
            Chunk newChunk = newChunkObject.GetComponent<Chunk>();
    
    // 为组件赋值
            newChunk.pos = worldPos;
            newChunk.world = this;
    
    // 将该 chuck 添加到字典中管理
            chunks.Add(worldPos, newChunk);
    
    // 这段代码其实就是原来 Chunk.cs 脚本里初始化的代码
            for (int xi = 0; xi < Chunk.chunkSize; xi++)
            {
                for (int yi = 0; yi < Chunk.chunkSize; yi++)
                {
                    for (int zi = 0; zi < Chunk.chunkSize; zi++)
                    {
                        SetBlock(x + xi, y + yi, z + zi, new BlockGrass());
                    }
                }
            }
        }
    

    上图代码基本都上了注释,因此不再细说。接下来添加“SetBlock”函数,代码如下:

     public void SetBlock(int x, int y, int z, Block block)
        {
            // 在这里封装了一层,用于做相关检测逻辑
            Chunk chunk = GetChunk(x, y, z);
    
    if (chunk != null)
            {
                // 调用 chunk 的 SetBlock 函数,其实就是为 chunk 里的 blocks 数组设置对应的值,
                // 只不过也在该函数中做了相关检测处理逻辑
                chunk.SetBlock(x - chunk.pos.x, y - chunk.pos.y, z - chunk.pos.z, block);
                chunk.update = true;
            }
        }
    

    接下来先添加“GetChunk”函数,代码如下:

      public Chunk GetChunk(int x, int y, int z)
        {
            // 下面五行代码主要用于计算当前 Block 位置对应的 chunk 于字典中的位置,并为 pos 赋值
            // 因为我们创建 chunk 是使用 CreateChunk(x * Chunk.chunkSize, y * Chunk.chunkSize, z * Chunk.chunkSize); 来创建的
            // 下面的操作只是将其操作反向计算了一下,仅此而已。
            WorldPos pos = new WorldPos();
            float multiple = Chunk.chunkSize;
            pos.x = Mathf.FloorToInt(x / multiple) * Chunk.chunkSize;
            pos.y = Mathf.FloorToInt(y / multiple) * Chunk.chunkSize;
            pos.z = Mathf.FloorToInt(z / multiple) * Chunk.chunkSize;
    
    Chunk chunk = null;
    
    chunks.TryGetValue(pos, out chunk);
    
    return chunk;
        }
    

    接下来让我们回到“Chunk.cs”脚本中,添加如下关联函数,代码如下所示:

     public static bool InRange(int index)
        {
            // 因为我们的 chunk 为一个正方体,
            // 因此这里的逻辑就是判断 x、y、z 的位置时候在该立方体内
            if (index < 0 || index >= chunkSize)
                return false;
    
    return true;
        }
    
    public void SetBlock(int x, int y, int z, Block block)
        {
            // InRange 故名思意就是判断传入的位置是否正确
            if (InRange(x) && InRange(y) && InRange(z))
            {
                blocks[x, y, z] = block;
            }
        }
    

    Ok,码到这里基本功能就完工了,让我们运行 Unity,查看效果,此时会报如下错误:
    在这里插入图片描述
    双击点击错误,便来到了报错的位置。报错的位置于“Chunk.cs”脚本中的“SetBlock”函数,这里为什么会报错,怎么看也没毛病啊。经过克森一系列的猜测,果然如此,组件的一些方法调用顺序出了问题。具体解决方案就是把“Chunk.cs”脚本你的“Start”函数该为“Awake”即可,如下所示:

       void Awake()
        {
            filter = gameObject.GetComponent<MeshFilter>();
            coll = gameObject.GetComponent<MeshCollider>();
            blocks = new Block[chunkSize, chunkSize, chunkSize];
        }
    

    好的,继续运行 Unity,居然还是报错了,错误如下:
    在这里插入图片描述
    双击点击错误,便来到了报错的位置。报错的位置于“Chunk.cs”脚本中的“GetBlock”函数,报错信息为数组下标越界。

    嘿,这个错误非常眼熟呀,之前的文章中也报了这个错误。其实就是因为我们在“Block.cs”脚本的“BlockData”函数中进行了如下判断:
    在这里插入图片描述
    然后我们是在“Chunk.cs”脚本的“UpdateChunk”函数中调用了“BlockData”函数,如下所示:
    在这里插入图片描述
    在这个函数中,我们会传入的最大值为“chunkSize - 1” ,然而在“BlockData”函数中的判断中会进行“* + 1”(*表示 x、y、z 任意一个),最终会传入“GetBlock”函数中的最大参数值为“chunkSize”,因此便造成了数组下标越界。

    Ok,逼逼了那么多,我们该怎么处理了,其实很简单,做个简单的判断即可,这个时候我们的“IsRange”函数便派上用场咯,修改的代码如下所示:

      public Block GetBlock(int x, int y, int z)
        {
            // 判断传入的位置时候位于该 chunk 中,
            // 如果不存在则默认返回一个“BlockAir”方块(因为 BlockAir 方块是一个空方块,不会影响其它逻辑)
            if (InRange(x) && InRange(y) && InRange(z))
                return blocks[x, y, z];
            return new BlockAir();
        }
    

    Ok,这个时候再运行 Unity,便会看到如下图所示:
    在这里插入图片描述
    嚯嚯嚯,看来是成功了,倍儿棒。

    Ok,让我们杂耍一下我们的成果,修改如下代码:
    在这里插入图片描述
    在这里插入图片描述
    运行 Unity,便看到如下图所示:
    在这里插入图片描述
    嚯嚯嚯,是我们想要的效果,不错,可以的,兄Dei。

    接下来让我们看一下生成的 Chunk 网格是怎么样的,具体操作步骤如下所示:
    在这里插入图片描述
    嚯嚯嚯,不错,也是我们想要的效果。chunk 里的多余的面被过滤掉了,这样便节省了贼多性能,哦耶!

    Ok,文章至此基本结束了,最后让我们为“World.cs”脚本添加两个有用的函数,代码如下所示:

        public Block GetBlock(int x, int y, int z)
        {
            Chunk chunk = GetChunk(x, y, z);
    
    if (chunk != null)
            {
                Block block = chunk.GetBlock(
                    x - chunk.pos.x,
                    y - chunk.pos.y,
                    z - chunk.pos.z);
    
    return block;
            }
            else
            {
                return new BlockAir();
            }
        }
    
    
        public void DestroyChunk(int x, int y, int z)
        {
            // 逻辑简单,就是找到指定的 chunk,然后先销毁游戏对象,再移除管理即可(移除对应字典的值)
            Chunk chunk = null;
            if (chunks.TryGetValue(new WorldPos(x, y, z), out chunk))
            {
                Object.Destroy(chunk.gameObject);
                chunks.Remove(new WorldPos(x, y, z));
            }
        }
    

    代码逻辑较为简单,因此就不逼逼了,好了,文章至此就结束吧。

    展开全文
  • Unity3d之AR小游戏

    千次阅读 热门讨论 2018-06-20 12:52:58
    Unity3d之AR小游戏 游戏简介 一个控制小飞龙躲避障碍物的小型AR跑酷游戏,有两个虚拟按钮可以控制飞龙向上或向下移动。 效果 静态图 动态图(太大传不上,见视频) AR模型识别 配置步骤 ...

    Unity3d之AR小游戏


    游戏简介

    一个控制小飞龙躲避障碍物的小型AR跑酷游戏,有两个虚拟按钮可以控制飞龙向上或向下移动。

    效果

    • 静态图

    这里写图片描述

    • 动态图(太大传不上,见视频)

    AR模型识别

    配置步骤

    这里网上教程很多,就不详细阐述了,大致过程就是去高通(vuforia)官网注册一个账号同意相关协议,下载sdk并导入。然后创建一个app关联数据库,数据库中上传待识别的图像,然后等vuforia分析完识别图的特征点之后,将target作为一个unity editor的选项下载下来后也导入unity项目。最后是项目内的配置,需要配置根据之前创建app时密钥配置unity项目,然后将预制ImageTarget的目标设置成你要识别的图片。运行项目时将待识别的图片放置在电脑摄像头范围内即可。

    注意点

    • 图像的星级
      我们会注意到将识别图上传到数据库时,target项右侧会有星级显示,星级越高代表识别图的质量越高。你可能会问,何为识别图质量?识别图质量有什么用?答案是,你踩过坑之后就知道了。经试验,影响识别图最主要的因素就是待识别图的对比度,这里应该要极力避免使用有很多连续相同或相似大色块的图片,因为分析识别图特征点的原理是 根据色块边缘 来决定的,色彩变化越丰富,色块边缘、棱角就越多(如果棱角分布均匀且每个色块都很小那就再好不过了),进而特征点就越多,而特征点越多就意味着识别图质量越高。另外,以下三点则是我总结的识别图质量最主要影响的三个方面:
      • 星级越高越容易识别
      • 星级越高识别速度越快
      • 星级越高更不容易出现抖动
    • 模型的抖动问题(比较棘手)
      识别出来的模型抖动问题一直是AR图像识别中存在的难题,这一定程度上和识别算法有关,很多识别工具也还没有做得很完善。因此我们能做的就是采取一些比较初级的措施来尽可能地避免模型抖动,高级的方法当然还有待研究。初级的方法我们就是要大致了解图像识别的原理,换句话说,就是尽可能地让图像容易识别和模型容易被渲染。我的话虽然识别图是五星的,但是一开始使用了一个比较复杂的模型,导致渲染模型时抖动比较剧烈,网上找了好多有关防抖动的原因,总结起来切入点主要有以下几个(一般推荐前两个):

      • 提高识别图的星级,尽量使用容易识别的图(前面已经提到过)
      • 使用更简单的模型,减少模型的面数,会使降低渲染难度
      • 烘培场景,调整灯光。因为现场实时识别最大的问题就是光线和角度的不稳定或不恰当,导致模型会不停抖动。
      • 优化识别算法(不在我们考虑范围内)
    • 虚拟按钮的检测(尤其注意)
      虚拟按钮第一次接触的话,听起来是个很神奇的东西。因此感觉用虚拟按钮做什么都很有意思。但是这里有个很大的坑,就是 虚拟按钮必须在识别图范围内,否则无论怎么点都点不到!!! 一开始没有注意到,还以为是点击方式的问题,盲目实现导致花费了很多时间到头来发现实现不了,可以说这次项目基本时间都花在这个坑里了,因此最后也就只好交了一个比较简单的版本,这是由于没有弄清楚虚拟按钮的原理导致的。在网上搜了很多技术博客以及逛了许多技术论坛,有关的博客少之又少,因为往年没有布置过AR的作业,所以也没有师兄博客可以参考,总之就是几乎没有发现有人提过类似的问题,于是就不得已只好将目光投向虚拟按钮的实现原理。虚拟按钮的触发原理是,根据识别图被遮挡的特征点所在的位置来判断是否点击了按钮。为什么叫遮挡,遮挡了什么?这时才反应过来是虚拟按钮的点击是通过检测识别图的对应位置是否被遮挡了,所以按钮一定要在识别图范围内,更准确地说,是在特征点分布的范围内。这样就又带来一个问题,如何使在遮挡虚拟按钮时不要让识别目标丢失。因为一开始我用的识别图虽然是五星的,但特征点整体集中在中间区域,也就是我的图有很多留白,总体来说特征点太集中,遮挡一部分时虚拟按钮都还没响应就失去目标了,这是我遇到的又一个问题,至此才真正发现问题所在(当时以为把虚拟按钮放在识别图范围内了还没有效果,一度以为是触发方式的问题)。于是我就将识别图换成了一幅特征点分布比较均匀的图,并且将待识别图载体由原来用手机换成了平板(面积更大),或许还可以将待识别图打印到一张A4的彩印纸上。


    地图的动态维护

    地图结构

    分为好多间隔相同的列,每列有三个障碍物位置,不能全放满(要留条活路),因此允许放0~2个障碍物,这些位置的障碍物都是随机生成的。

    地图初始化(生成)

    一开始初始化足够多的列,这里我是选择初始化5列,只要能保证移动的时候能衔接自然即可。然后针对每列的随机算法是,对每列的三个位置,随机不重复地挑选至多两个位置,然后在该位置上,随机决定该位置是否应该有障碍物。这样就可以完全使地图随机生成了。

    地图维护

    为了减少运行开销,对于移出视野的地图,应该进行销毁。另外,为了生成无尽的地图,应该也要在移动过程中动态生成新的地图拼接在原地图后面。这里我是采用一个队列来维护整个地图以及实现地图的移动,因为队列是不允许直接遍历的,所以我的遍历是将队列头部元素重新插入到队尾来遍历。

    地图部分完整代码

    public class Map : MonoBehaviour {
    
        private const float ydis = 0.5f;//y间隔    
        private float[] ypos;//y位置    
        private const float zdis = 2;//z间隔    
        private float[] zpos;//z位置
        //private GameObject[] barriers;//所有障碍物
        private Queue<GameObject> barriers;//所有障碍物
        private float delta;//移动距离
    
        // Use this for initialization
        void Start () {
            //初始化队列
            barriers = new Queue<GameObject>();     
    
            //初始化y位置
            ypos = new float[3];
            for (int i = 0; i < 3; i++)
            {
                ypos[i] = ydis * i + 0.3f; //0.3是偏移
            }
    
            //初始化z位置
            zpos = new float[5];
            for (int i = 0; i < 5; i++)
            {            
                zpos[i] = zdis * (i + 1f); //保证障碍物全在视野外
            }
    
            Debug.Log(ypos);
            Debug.Log(zpos);
    
            //移入视野前产生5列障碍物,确保移动过程中能无缝衔接
            for (int i = 0; i < 5; i++)
            {
                int count = 0; //每列的障碍物数量
    
                //每列的三个位置
                bool[] state = { false, false, false };
                for (int j = 0; j < 3; j++)
                {
                    //随机一个位置
                    int rpos = (int)Random.Range(0, 3);
                    //该位置必须没有放置过
                    while (state[rpos])
                    {
                        rpos = (int)Random.Range(0, 3);
                    }
                    state[rpos] = true;
    
                    //在该位置随机产生或不产生障碍物
                    float rand = Random.Range(0, 1);
                    if (rand < 0.5)
                    {
                        //加载预制障碍物
                        GameObject barrier = (GameObject)Instantiate(Resources.Load("Barrier", typeof(GameObject)), 
                            new Vector3(0, ypos[rpos], zpos[i]), Quaternion.identity, null);                    
                        barrier.transform.parent = this.transform;
                        barrier.transform.position *= 0.03f;
                        barrier.transform.localScale *= 0.03f;
                        barrier.GetComponent<BoxCollider>().center = barrier.transform.position;
                        barriers.Enqueue(barrier);
                        count++;
                    }
    
                    //每列的障碍物不能超过2个
                    if (count >= 2) break;
                }
            }
    
            //初始化距离
            delta = 0;
        }
    
        // Update is called once per frame
        void Update () {
            //移动
    
            //遍历队列
            int len = barriers.Count;
            while (len > 0)
            {
                //取出第一个
                GameObject front = barriers.Peek();
                front.transform.position -= new Vector3(0, 0, 0.0002f);                       
                barriers.Dequeue();
    
                //Debug.Log("front pos: " + front.transform.position);
    
                //消除移出视野的障碍物                       
                if (front.transform.position.z < -1 * 0.03f)
                {
                    Destroy(front);
                }
                //否则插回队列尾部
                else
                {
                    barriers.Enqueue(front);
                }
    
                len--;
            }
    
            //移动距离增加,注意相对坐标和绝对坐标的转换
            delta += 0.0002f / 0.03f;
    
            //如果移动了一个z间距,那么并产生新一列放在最后 
            if (delta >= zdis)
            {
                Debug.Log(delta);
                int count = 0; //每列的障碍物数量
    
                //每列的三个位置
                bool[] state = { false, false, false}; 
                for (int j = 0; j < 3; j++)
                {
                    //随机一个位置
                    int rpos = (int)Random.Range(0, 3);
                    //该位置必须没有放置过
                    while (state[rpos])
                    {
                        rpos = (int)Random.Range(0, 3);
                    }
                    state[rpos] = true;
                    Debug.Log(rpos);
    
                    //在该位置随机产生或不产生障碍物
                    float rand = Random.Range(0, 1);
                    if (rand < 0.5)
                    {
                        //加载预制障碍物
                        GameObject barrier = (GameObject)Instantiate(Resources.Load("Barrier", typeof(GameObject)),
                            new Vector3(0, ypos[rpos], zpos[4]), Quaternion.identity, null);
                        barrier.transform.parent = this.transform;
                        barrier.transform.position *= 0.03f;
                        barrier.transform.localScale *= 0.03f;
                        barrier.GetComponent<BoxCollider>().center = barrier.transform.position;
                        barriers.Enqueue(barrier);
                        //Debug.Log(barrier);
                        count++;
                    }
    
                    //每列的障碍物不能超过2个
                    if (count >= 2) break;
                }
    
                delta = 0;
            }
    
        }    
    }

    其他

    其他实现如按钮的控制和移动范围的判断,以及按钮的点击放大效果等,比较简单就不列出来了,调参也是比较需要耐心,花时间的。


    展开全文
  • Create菜单下 3D Object菜单下Cube 1.1设置属性x100 z100作为地面  1.2在创建一个立方体物体 1.3 Create菜单下 3D Object菜单下Cube    1.4复制立方体 创建一个空物体放立方体        ...

    游戏开发

     

     1*创建物体

    Create菜单下 3D Object菜单下Cube

    游戏1

    1.1设置属性x100   z100作为地面

    dm 1.2在创建一个立方体物体

    1.3 Create菜单下 3D Object菜单下Cube

    游戏1

     cjlft

     1.4复制立方体

    sdkjdks

    创建一个空物体放立方体

     

    op1

    po2

     

    ppk

    poo5 

    pp-6 

     

     

    ppkl 

    poo8 

    ppoo9 

    ppo 

    opop12 

    代码 

     

    #pragma strict
    
    function Start () {
    
    }
    		var  speed : int =5;	
    		var  s1 : int =0;
    		var  s2 : int =0;
    		var  s3 : int =0;
    		var  s4 : int =0;
    		var  newobject : Transform;
    		var  gutt : int =0;
    		var   xms  : int =0;
    	
    function Update () {
    		//移动物体
    		var   x:float = Input.GetAxis("Horizontal")*Time.deltaTime*speed;
    		var   z:float = Input.GetAxis("Vertical")*Time.deltaTime*speed;
    		if(gameObject.transform.position.x<-45){transform.Translate(300*Time.deltaTime,0,0);}
    		if(gameObject.transform.position.z<-45){transform.Translate(0,0,300*Time.deltaTime);}
    		if(gameObject.transform.position.x>45){transform.Translate(-300*Time.deltaTime,0,0);}
    		if(gameObject.transform.position.z>45){transform.Translate(0,0,-300*Time.deltaTime);}
    	
    		transform.Translate(x,0,z);
    		//print("java"+x);
    	//创建物体
    	if(Input.GetButtonDown("Fire1")){
    	var n : Transform = Instantiate(newobject,transform.position,transform.rotation);
    		//交换方位
    	var fwd : Vector3 = transform.TransformDirection(Vector3.forward);
    	
    	//给物体一个力
    	n.rigidbody.AddForce(fwd*2800);
    	gutt++;
    	//GUITText射击数
    	gameObject.Find("str").GetComponent(GUIText).text="射击数:"+gutt+"消灭数:"+xms;
    	
    	
    	}
    
    	//旋转功能
    	if(Input.GetKey(KeyCode.Q)){
    			transform.Rotate(0,-25*Time.deltaTime,0,Space.Self );
    	
    	}
    	if(Input.GetKey(KeyCode.E)){
    			transform.Rotate(0,25*Time.deltaTime,0,Space.Self );
    	
    	}
    	
    	if(Input.GetKey(KeyCode.Z)){
    			transform.Rotate(-60*Time.deltaTime,0,0,Space.Self );
    	
    	}
    	if(Input.GetKey(KeyCode.X)){
    			transform.Rotate(60*Time.deltaTime,0,0,Space.Self );
    	
    	}
    
    	if(Input.GetKey(KeyCode.R)){
    	if(gameObject.transform.position.y>20){ transform.Translate(0,-300*Time.deltaTime,0);}
      transform.Translate(0,3*Time.deltaTime,0);
    	
    	}
    	if(Input.GetKey(KeyCode.F)){
    		if(gameObject.transform.position.y<1){transform.Translate(0,80*Time.deltaTime,0);}
    	transform.Translate(0,-3*Time.deltaTime,0);
    	
    	}
    
    }

     

     

     

     

     

    ppp15 

     

    opop12 

     结束脚本

     

    #pragma strict
    
    function Start () {
    
    }
    	var s3 : int =0;
    	var s2 : int =0;
    	
    	
    function Update () {
    		
    		
    			if(gameObject.transform.position.z>5)gameObject.Find("Capsule").GetComponent(AudioSource).enabled=true;
    		
    		if(gameObject.transform.position.y<0){
    		s3=gameObject.Find("Camera").GetComponent(shji).xms++;
    		gameObject.Find("Capsule").GetComponent(AudioSource).enabled=false;
    	
    			if(s3>60){
    		gameObject.Find("str").GetComponent(GUIText).text="恭喜通关,恭喜通关,恭喜通关";
    		gameObject.Find("Camera").GetComponent(shji).enabled=false;
    		gameObject.Find("dibo").GetComponent(restart).enabled=true;
    		
    		
    		
    		}
    
    	
    				Destroy(gameObject);
    	
    		
    		
    		}
    	
    
    
    
    
    
    }

    poss16 

     游戏分享

    https://pan.baidu.com/s/1u-WGBJbdgMpEgKjuJ9u4-A       密码wi8w

     

    谢谢大家关注一下   [支持原创]                                                                                               @  怪体杰

    展开全文
  • 游戏会为玩家呈现一个“故事卡”。故事卡上包含一些文字,其中一部分是用于描述玩家当前的状态,另外一部分是在当前情况下玩家可以做出的一系列选择。 根据玩家的不同选择,剧情也会按照不同的分支向前发展,并持续...
  • 一款unity3d做得小游戏

    2020-07-26 23:30:29
    unity3d做的射击游戏,自动刷洗机器人,计分板等,可以无时间限制
  • Unity3D开发小游戏】《战棋小游戏》Unity开发教程

    万次阅读 多人点赞 2020-04-10 16:49:52
    这次想要做的一个小游戏,或者说一个小Demo,其实是一个简单且传统的战棋战斗场景。初步的设计是:在2D世界里创建一张由六边形地块组成的战斗地图,敌我双方依据体力在地图上轮流行动并向对方发动攻击,先消灭掉所有...
  • Unity3D游戏开发——塔防小游戏

    万次阅读 2017-08-07 11:22:35
    游戏效果图: 本文参照siki学院的塔防游戏教程写的。http://www.sikiedu.com,搜索塔防游戏登入即可观看。 游戏流程: 1.首先创建cube调整其大小(以便计算),然后将其设成预设体(以便之后统一操作),利用...
  • 深入浅出Unity3D——第一篇

    万人学习 2019-07-01 10:35:43
    Unity3D基础知识、游戏算法一网打尽。
  • 基于Unity游戏开发(上)

    万人学习 2019-06-24 11:55:37
    本课程是Unity游戏开发的入门课程,课程分为上、下两个部分。上篇主要讲述游戏的基本概念、Unity的开发环境、游戏资源的创建和使用等基础内容。
  • Unity游戏开发入门教程之C#集合

    万人学习 2015-04-21 11:49:17
    该套视频主要针对初级的想学习Unity的学生,主讲语言的基本,简单语法。从数据的类型到方法的重载再到构造方法和析构方法再到后事件的概念与本质,学习难度进一步加深,对于初级的学生来说是一个非常好的,也非常...
  • Unity3D着色器程序设计-CG版

    万人学习 2018-10-22 21:38:06
    本课程将带领大家学习Unity3D中的着色器程序
  • 基于Unity游戏开发(下)

    万人学习 2020-02-26 11:51:26
    本课程是Unity游戏开发的入门课程,课程分为上、下两个部分。下部分主要介绍JavaScript脚本程序开发、动画、粒子、网络和GUI系统
  • 本课程主要讲解了 C# 语言的面向对象特性,是进行 Unity 3D 开发的基础。大部分的语言都支持面向对象,学好面向对象,能极大地提高我们的开发效率。面向对象相关的知识点本身就会有些抽象,概念很多,所以需要认真...
  • 移动端游戏UI设计-二部曲

    万人学习 2018-10-22 21:38:03
    主要实现了移动端游戏的UI架构设计,分两部分,第一部分采用的是有限状态机,第二部分采用传统的模式架构设计。
  • Unity轻松制作塔防游戏

    万人学习 2019-01-31 11:31:57
    玩儿过塔防游戏吗?听说过《保卫萝卜》和《塔防战争》吗?本系列课程使用简单易懂的逻辑带你一起...本课程经过精心录制,带你轻松学习更多知识,只要你对Unity有一些基本操作的认识,就可以跟着老师一起完成这款游戏
  • Unity5完成一个2D的射击游戏
  • unity5项目详解Mecanim动画系统
  • 移动端游戏架构设计

    万人学习 2018-10-22 21:38:03
    目前很多开发者对于游戏架构设计一无所知,只是简单的把脚本与对象进行挂接,导致在后期开发中,版本维护,功能扩展非常不方便,现在网上出现了各种版本的热更新实现,比如Lua,JS,C#Light等, 该框架设计技术...
1 2 3 4 5 ... 20
收藏数 23,612
精华内容 9,444
关键字:

unity3d小游戏