经过了4个月不懈的努力,我和图灵教育合作的这本3D游戏开发书预计下个月就要出版了。这里MOMO先打一下广告,图灵的出版社编辑成员都非常给力,尤其是编辑小花为这本书付出了很大的努力,还有杨海玲老师,不然我也无法完成这本书的编写。等这本书出版了大家记得买喔,哇咔咔~ 下面,这篇文章是MOMO 3D游戏开发书籍中的一小段章节的修改版本,本篇文章我们将探讨一下Unity3D中如何来制作2D游戏。目前市面上已经有非常成熟的2D游戏引擎,比如cocos2d 或cocos2d-x等,并且都是免费的开发者可以直接用来制作2D游戏。然而使用3D引擎来制作2D游戏会让游戏画面更加附有立体感,因为2D游戏中Z轴永远是0,而3D游戏中Z轴是可变化的。
接着说说在Unity3D中制作2D游戏的原理。在Unity3D中绘制贴图的方式大致可分为两种,第一种是在GUI中绘制贴图,第二种是在网格面上绘制贴图。先说说第一种,GUI主要用来制作简单的游戏2D界面,比如游戏主界面中绘制的“游戏名称”、“开始游戏”、“保存游戏”、“退出游戏”一些按钮或界面中一切其它的高级控件,文本框,输入框等等、GUI只能制作简单的图形化界面,因为它的渲染效率非常低,它与3D世界中网格模型的渲染机制完全不一样。第二种的属于将图片绘制在3D中网格平面中,它的渲染效率远远高于GUI中,在制作2D游戏时都是将所有贴图绘制在平面模型对象之上,最后用摄像机以90度垂直的角度照射这这些平面。
下面我们开始学习在Unity3D中制作2D游戏的原理。2D游戏又可以分为两种,区别是物体碰撞时带物理引擎或不带物理引擎。带物理引擎就好比《奋斗的小鸟》一样,大家应该都玩过,小鸟发射后下落击落其它物体将发生物理的碰撞。(我没有细看这款游戏,但是我知道2D 与3D引擎都都可制作),不带物理引擎的就好比《捕鱼达人》这种游戏,游戏中碰撞都是由代码自己来完成的,经典的2D碰撞监测包括:矩形与矩形的碰撞、点与矩形的碰撞、圆与圆的碰撞等。今天这篇文章我们主要讨论第二种不带物理引擎的2D游戏。
如下图所示,我们盘点一下2D游戏中必备的几个元素。
摄像机:无论是3D游戏还是2D游戏摄像机都是非常重要的属性,移动摄像机即可更改屏幕中显示的内容,游戏地图的坐标永远都不会发生改变。
地图:2D游戏中的地图一般是由tile拼接而成,它可由地图编辑器生成然后将每一块tile绘制在整个贴图中,最后将贴图贴在平面网格面之上即可。还有一种作法是将两个或两个以上屏幕大小平面以队列的形式排在屏幕后面,当摄像机移动超出第一块面显示范围时,将它的坐标移动在第二块面后面,此时地图就形成了一个排序的队列。为了让地图的效果更加完美,一般地图可以由好几层来组成,比如背景层、与主角的遮挡曾、物理层等等。
地图拼接:地图的排序队列中两张图应当是可以无缝拼接,这个应当是由美术来提供资源,这里我就不那么细致了将远离说明白即可。
主角:它的范围就比较广的,敌人、物品等等出现在地图之上的都可以使用它。如果控制主角移动,摄像机移动的同时主角也当跟随移动,并且保持屏幕中的移动比例,除非摄像机无法移动,这时将直接移动主角在屏幕中的坐标。 说的有点绕了呵呵,大家仔细想想哈哈。。
然而上面的一切面是由Plane面来完成。
再Unity层次视图中选择摄像机对象,右侧监测面板视图中我们看看摄像机组件的一些属性,如下图所示。需要注意的就是Projection 投影类型。
首先我们应当修改摄像机的属性,默认摄像机投影的类型是Perspective,它保持摄像机以扩散的的形式照射着不利于2D平面的展示。这里我们应当选择Orthographic,这样摄像机将直直的照射在显示的区域。
Perspective类型
Orthographic
从侧面观察摄像机,通过这两张图我相信大家应当能看懂为什么2D游戏要用Orthographic了吧,摄像机的投影类型是可以在代码中动态的修改的。
1 2 3 4 5 6 7 8 |
//得到游戏中摄像机对象 Camera camera = Camera.mainCamera; //设置摄像机投影类型OrthoGraphic camera.isOrthoGraphic = true; //设置摄像机投影类型Perspective camera.isOrthoGraphic = false; |
在代码中取得摄像机投影的区域大小,它也可以动态的修改,这样就可是实现摄像机拉近与拉远的效果。根据投影区域的大小配合着整个地图的宽高来写判断条件,避免移动摄像机时超过地图的范围。
1 2 3 4 |
Camera camera = Camera.mainCamera; Debug.Log(camera.orthographicSize); |
接着我们使用代码来得到地图面的宽高,这段代码写的就比较精细,因为网格面是可以缩放的,首先得到网格面的宽与高,然后分别乘以缩放系数就可以得到真实面的宽与高,然而Unity中的坐标是以“米”为单位。下面代码中用到了中文,如果要想在编辑器中显示中文C#语言需要修改编码格式为UTF-16。JavaScript修改编码格式UTF-8或UTF-16即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
using UnityEngine; using System.Collections; public class Test : MonoBehaviour { void Start () { GameObject plane = GameObject.Find("Plane0"); //得到面默认宽度 float size_x = plane.GetComponent<MeshFilter>().mesh.bounds.size.x; //得到面宽度的缩放比例 float scal_x = plane.transform.localScale.x; //得到面默认高度 float size_z = plane.GetComponent<MeshFilter>().mesh.bounds.size.z; //得到面高度缩放比例 float scal_z = plane.transform.localScale.z; //原始宽度乘以缩放比例计算出真实宽度 float mapWidth = size_x * scal_x; float mapHeight = size_z * scal_z; Debug.Log("得到面的位置:"+plane.transform.position); Debug.Log("得到面的宽度:"+ mapWidth); Debug.Log("得到面的高度:"+ mapHeight); } } |
有了摄像机照射的区域以及背景地图的宽高尺寸那么就可以在代码中编写逻辑判断条件啦。下面我们来使用简单的代码控制摄像机移动以及主角移动。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
using UnityEngine; using System.Collections; public class Controller : MonoBehaviour { //动画数组 private Object[] anim; //主角对象 private GameObject hero; //限制一秒多少帧 private float fps = 10; //帧序列 private int nowFram; //记录当前时间 private float time; void Start () { //得到资源名称为down文件夹中的所有对象资源 anim = Resources.LoadAll("down"); //得到主角的对象 hero = GameObject.Find("hero"); } void FixedUpdate () { //上、下、左、右平移摄像机 if (Input.GetKey (KeyCode.A)) { transform.Translate(-0.01f,0,0); } if(Input.GetKey (KeyCode.D)) { transform.Translate(0.01f,0,0); } if (Input.GetKey (KeyCode.W)) { transform.Translate(0,0.01f,0); } if(Input.GetKey (KeyCode.S)) { transform.Translate(0,-0.01f,0); } //上、下、左、右平移主角 if (Input.GetKey (KeyCode.J)) { hero.transform.Translate(0.001f,0,0); } if(Input.GetKey (KeyCode.L)) { hero.transform.Translate(-0.001f,0,0); } if (Input.GetKey (KeyCode.I)) { hero.transform.Translate(0,0,-0.001f); } if(Input.GetKey (KeyCode.K)) { hero.transform.Translate(0,0,0.001f); } DrawAnimation(anim); } void DrawAnimation(Object[] tex) { //计算限制帧的时间 time += Time.deltaTime; //超过限制帧切换贴图 if(time >= 1.0 / fps){ //帧序列切换 nowFram++; //限制帧清空 time = 0; //超过帧动画总数从第0帧开始 if(nowFram >= tex.Length) { nowFram = 0; } } //将对应的贴图赋予主角对象,强制将资源文件转换成贴图 hero.renderer.material.mainTexture = (Texture)tex[nowFram]; } } |
代码中我们使用 Resources.LoadAll(“down”);来加载主角动画资源,这里将主角一组4帧的行走动画放在项目资源视图中 Resources文件夹中的down文件夹内。值得注意的是,使用Resources来加载资源就必须将资源放在Resources文件夹中,否则提示无法找到喔。在书中我以将人物四宫格行走动画加入在其中,因为这里只是一个是示例,所以我只加载了向下行走的4帧动画。我们看看资源在项目资源视图中的保存结构。
还有一个比较重要的地方就是要修改材质的shder类型,因为默认的材质是Diffuse,它是不支持透明的。如果材质不支持透明。主角的背景将会是白色。如下图所示,这里选择Transparent/Diffuse。保存为Transparent家族中的材质都是支持透明的。
最后2D游戏效果图映入我们眼帘了哦。按键W、S、A、D控制摄像机移动,按键J、K、I、L、控制主角移动。
总结一下这篇文章,本文我们在多个Plane对象身上贴上材质资源,再让摄像机直直的照射着它。实现2D游戏的基本原理,本文没有涉及到Unity3D的物理引擎,不要紧在下章中我将向大家介绍一下Unity3D中的刚体组件与角色控制器组件如何来实现模型的物理效果,包括物理引擎与3D或2D游戏的结合。另外大家一定要期待我的新书喔,哇咔咔~嘿嘿。
- 本文固定链接: https://www.xuanyusong.com/archives/793
- 转载请注明: 雨松MOMO 于 雨松MOMO程序研究院 发表
请问摄像机投影的区域大小怎样动态的修改啊?用什么代码呢?
求大神赐教。我是看着您的书来做3.3.4的练习。但是最后程序给了一个源于InvalidCastException: Cannot cast from source type to destination type.的警告。点击之后发现问题出自hero.renderer.material.mainTexture = tex[nowFram]; 这是为何啊~~
我也遇到了,请问你解决了吗
自己吓鼓捣终于发现问题所在:在你新建一个物体时,你将贴图附在物体上是引擎会在贴图所在目录下自动产生一个Materials文件夹进而形成一个Shader。当这个初始化物体的贴图是你帧动画的一张图片时,系统就会报错。所以,用帧动画外其他的图片初始化物体贴图就可以解决了。鼓捣一下午,终于解决了,还开心~~
我也是这个问题,一开始还可以运行,关了再启动就一直是这个错误…强制转换也不管用,JS和C#都试过了。大神指导下吧
讲的真不错,谢谢分享教程,我想问下像 2d横版的话 主角可以上下移动的那种 又是什么原理呢,看起来不像只是 y轴移动。。
不知道为什么 把2d图片贴到 box等看到的图像都是倒着的。 请问有什么办法解决。
默认模型uv的问题, 你让美术帮你重新建一个cube 分好UV就可以了。。
明白了,非常感谢。
MOMO 怎么修改C#语言需要修改编码格式为UTF-16?具体步骤能说一下吗?谢谢先!
在unitron中就可以修改
用gui制作了一些控制人物移动的 按钮。但是发现 按钮不能一直长按,必须点击一下才能触发一次 if判断。请问 如何才能在按下按钮鼠标未抬起的状态 依旧通过if判断呢?
有一个很简单的办法。。OnClick 按下。 时候开始一个bool 然后如果没抬起它就是 true 判断这个bool就可以了。
请教一下雨松,我想自己开发个类似传奇的2D或2.5D的游戏,学习Unity3D可以做到吗?涉及到游戏服务端自定义脚本的问题也可以解决吗?谢谢~
当然可以做到, 服务器用C++来写吧。。 用sorket来连接。。。
当然可以做到, 服务器用C 来写吧。。 用sorket来连接。。。
买了书,感觉某些技术点这里要丰富一些,支持UP主的无私奉献~~~~!
感谢你的支持, 我会抽时间一直维护技术博客滴。。
终于找到这里了,兴奋哪
一起兴奋, 蛤蛤!!!
两月前我在王府井书店抢购了你写的书,很棒!我刚从公主坟游戏学院毕业,学策划的,很喜欢Unity3d,这两天自学了C#,很喜欢。我可是学院的高材生啊,有需要策划的团队推荐个哟,qq:857860326
嗯如有有合适的我会告诉你! 有空常我这里 呵呵!
嗯呢,这几天正在突飞猛进地学习Unity3D,想着快些把当初写的连环图式的策划案制作成3D的DEMO,其中如遇到困难,一定会多多向雨松MOMO提问学习:)
如何实现2D图像的鼠标点击功能?比如一个可移动的物体,需要处理它的鼠标点击功能,Texture2D画出的图是不是对象,难道要用plane游戏对象绑定物体的贴图再绑定鼠标相关的脚本才能实现?
用射线来判断
在请问一下我看C#编程的时候,总出现error CS1010: Newline in constandCS1525: Unexpected symbol”结束等待”这样的错误呢?我找了半天也不知道哪个地方出错了,可以帮忙回答下吗?
脚本错误吧,,
但这个是下载的书里对应的那个程序呀
你是哪一章出的问题?
从第4章开始用C#编程的那个部分,只要用C#就会出现很多这样的错误。是跟我用MonoDevelop有关么?编码格式不对?
没见过这个错误, 在MAC下没有问题。。
哥们,是monodev这个傻蛋编辑器不支持中文造成的,要么就自己手动转换文件编码为utf-16,要么就别用它。或者用资源文件代替中文字符串
最近刚买了你的书,可是看第三章的3.3.4节的时候,总说在hero.renderer.material.mainTexture = tex[nowFram]; 说null pointer,而且还想问下里的程序里Hierarhcy建的plane ,hero ,hero_p是做什么的呢?刚学有好多不会的呢
hierarhcy建的对象可以说是 游戏对象 通过脚本去访问这些游戏对象 执行一些操作
想学学shader的编写,有什么好推荐的么??
官网上有shader的教程 你可以看看
请问这个例子可以下载吗?求提供hh13774978@126.com
不好意思 写的有点早了 不过我的书的源码中有这个例子, 你直接下载吧。
如果和想实现一个巨大的tile地图,并且tile信息是请求服务器获得的。那么这个地图拼接我怎么实现?使用Plane可以实现吗?还是把每一个地图块都绘制到一个Cube上,然后再拼接?
没做过Unity。。希望LZ给个思路。
貌似 越来越N13的说。。。
哈哈 感谢支持
我也想入手新书 名字还没定么
momo加油
支持松松~
大牛啊。。不知道在unity3d圣典上是哪个板块的版主。我经常进那个论坛。多多像大牛学习。。强赞一个,还有不知道即将发行的新书叫什么名字。。主要进行哪些方面的讲解。到时候一定要支持一下