在这里我会写下一些我自学 Unity 的过程,以及一些想法,供自己将来查看。
当然,如果有一些坑也可以顺便在这儿吐槽下。。
教程资源
网上的资源有很多啦,这里我参照的就是官网上的教程。
学习计划
计划分成两条线:第一条线是刷教程项目,第二条线是刷文档。“刷”只是个形象的说法,不是走马观花的略读,也不是事无巨细的精读,毕竟要讲究效率。对于程序而言,细节还是很重要的,看文档可以补充遗漏的点。
小结
按照之前的计划,Unity官网上的 tutorials 基本都刷完了。我总体的感觉是,这些内容是入门级的,它可以帮你逐渐熟悉 Unity 的基本操作,做一些简单的demo,并且不需要深厚的编程基础。从质量上讲,第三和第四个项目的质量要远高于其他的项目。如果你是刚接触 Unity,并且想通过做实际的项目来练手的话,这两个教程都是不错的选择。当然缺点就是视频教程,时间会比较长,不过能学到的东西还是很丰富的。(可以参考下面的内容)
这两个项目都是 live training 的视频。这个页面上有到目前为止所有的 live training 的内容,挑着感兴趣的内容看看也是可以的。
总之,今天是10月4号,距离写这篇文章时刚好是一个月的时间。这篇文章应该也不会再更新了,有什么新的进展我应该会开新贴的。感谢大家的关注!
全文完。后面都是附录内容。
最新动态(按时间顺序倒排)
项目6:随机洞穴生成
目标:生成一个2D的洞穴。
方法:
- 使用元胞自动机去模拟生成过程,生产墙(1)和空(0),重载 OnDrawGizmos() 方法以在 Editor 中显示;
- 平滑边缘:考虑以4个顶点是1/0的矩形,随着定点状态的变化,它一共有16种状态,创建并赋值;
- 针对16种可能的mesh,用程序产生了一个Mesh对象 = =!,它需要两个东西,List<Vector3> vertices 存储所有三位坐标,List<int> triangles 按序存储三角形;这个 Mesh 对象放到 Mesh Filter 这个组件中;
- 找到代表墙(1)的边缘部分,生成垂直的平面,这样就构成了立体的墙。
至此为止,生成了洞穴和mesh,后面的都是算法相关的内容(*>.<*)
- 区域检测算法:首先,随机生成的区域有大有小,要去掉太小的,那么就要检测所有的连通区域。
- 区域连接算法:其次,要把这些大小适中的区域都连接起来。
- 绘制连线:如何画一条线,图形学中有讲。
- 添加材质。
项目5:Scavengers 挂起,视频时长不长,已用时:约 5 hours
游戏机制:2D Rougelike,行走需要消耗食物,捡水果可以补充食物,怪物攻击会偷走食物,有足够食物才能到达出口到下一关。怪物会越来越多,你能撑到第几层呢?
设计内容:2D 游戏设计:Sprite 建立,Sprite 动画。完全通过代码生成游戏地图。格点式的行走、障碍物判定。
2D 的动画控制(如:播放攻击动画等)
未实现:Audio and Sound Manager、Adding Mobile Controls
【这里可能会放张图】
项目4:TWO TANKS 完成,视频时长 4.5 hours,已用时:11 hours
游戏机制:本地双人,各控制一辆坦克互相伤害,消灭对方者获胜。
涉及内容:角色控制(前进/后退/转向 式),视角自动调整以容纳多个目标(见下文总结)。圆圈式的炫酷血条,击退效果,范围判定。蓄力攻击(图式:Slider 和 逻辑:状态机)。使用给定的颜色渲染物体。Coroutine的多种应用。互斥的音乐素材播放(同时只有一个音效被播放)
=-=-=-=-=-=-=-= 难度的分割线 =-=-=-=-=-=-=-=
项目1-3总结:总体而言,这三个项目是初级项目,在官方的视频分类中难度写的是“新手”,接下来还有5个项目,难度是中级和高级。所以,在这个地方,我做了个小停顿。Unity现在带给我的感觉是找不着北,里面的嵌套关系有点复杂,经常找不到控件的位置。当然,这还是会慢慢熟悉的。我比较担心的是,以后可能会找不到代码的位置,即找不到控制某段逻辑的代码在哪里了,这就很难debug,特别是当报错信息比较奇怪的时候。
然后我就花了一段时间,把之前三个项目之中所涉及的代码逻辑都画成了流程图的形式,我不确定这种方法是否会对之后的开发有帮助,所以这里就先不具体说了。它的有效性还需要在实际项目中去验证。如果有效的话,我会把这种方法公开,与大家共享。现在的三个项目的逻辑还算少,加在一起一张 A4 纸刚好能画完。
项目3:SURVIVAL SHOOTER 基本完成,视频时长约 4 hours,已用时:9 hours
游戏机制:跟随视角的第三人称射击
涉及内容:动画控制器(有限状态机),捕获鼠标点击位置(详见CameraRay),摄像机平滑跟随,自动导航寻路(Nav Mesh Agent)。。。// 中场休息
涉及内容:用户界面 / HUD 设计,玩家血量模型,子弹弹道(命中判定 + 渲染)。动画播放中调用脚本函数。动画控制器重用,动画效果重用*!设置怪物出生点。创建补间动画。(内置简易版 Flash)
* 预置的动画效果可绑定至其它对象?经试验,是可以的,但需要是同类型的物体。没学过动画搞不清。。
这个项目没有 Web 端版本。
项目2:SPACE SHOOTER 基本完成,已用时 8 hours
游戏机制:弹幕射击,参考 雷电 ,当然是简化版的 ( ´◔ ‸◔')
涉及内容:物体的生命周期(以发射子弹为例,包括生成子弹、子弹自行运动、碰撞处理、销毁子弹等),物体间的关联(例如飞船在摧毁后会播放声音和动画,那么飞船和声音、动画之间需要建立关联)。摄像机视角:透视/仿射。协程。
说是基本完成是因为还有3个小时的额外内容没来得及看,原因一言难尽啊(参见最后一节)。
空口无凭,放个演示项目的地址。以便有直观的认识。
项目1:ROLL-A-BALL 完成,用时 3 hours
游戏机制:控制一个小球滚啊滚啊滚 ( ̄Q ̄)╯
涉及内容:控制物体移动,物体碰撞检测,基本代码逻辑,计分显示。Asset概念:材质、Prefabs。
要点记录
关于MVC
MVC是 Model-View-Controller 的缩写,算是一种设计的规范/框架。具体解释如下:Model 通常描述了一个具体的物体,相关的变量也包含在其中。而 View 呢,一般指那些展示在画面上的东西,例如 Sprites 等。一个 Model 可以关联一个 View(如单纯的显示),多个 View(如不同状态下的显示),或者不关联 View (可能它是一个隐形的开关)。从而, Controller 就起到了粘合剂的作用,它应当包含所有的逻辑代码,比如 Model 中变量的加减或者 View 的显示等等。
在 Unity 中也体现了 MVC 的思想。粗略的看,脚本可以看成是 Controller,GameObject可以看成是 Model,Asset 可以看成 View。脚本中可以暴露一些 Model 和 View 的接口,在主界面中可通过拖拽实现关联。
关于生命周期
生命周期这个词是从别的地方借鉴过来的,套在 Unity 中应该也适用。
一个完整的生命周期应该包括 创建(Create)、显示(Update)、暂停(Deactive/Pause)、恢复(Active/Resume)、终止(Stop)、销毁(Destroy)。有些物体具有自己的运动规律,那么相关代码可以写在创建、显示的过程中。
有些物体(A)会与其他物体(B)发生关联,那么相关逻辑可以写在 A 的触发器代码中。于是这个整体就从概念上代表了这个物体及其行为。
然而,某些情况下,关联是相互的,A、B 间的关联代码也可以写在 B 的触发器中,实现同样的效果。这会在设计上引发混乱,可能还会出幺蛾子(bug),暂时没找到好的解决办法,先放着以后再看。
关于 CameraRay
3D 游戏与 2D 游戏的一大差别在于鼠标点击事件的处理,而这一点对玩家是透明的。设想在一个3D的环境中,鼠标点击了屏幕上的一个位置,那么问题来了,究竟是点到了哪个位置呢?
实际上,点击的不是 一个 位置,而是从摄像机位置到屏幕上点位置发出的射线,这个射线上的点都是点击的位置,这有点反直觉。我们在 3D 场景中所点击的物体,实际上是这条射线碰到的 第一个 物体,后面的物体被挡住了看不见。对于引擎而言,则需要确定你点击的具体位置。这可以用下面这幅图来描述:
解释:左下角是游戏角色所处的场景,右上角的摄像机可以看到角色的后方。当玩家点击屏幕上的点时,产生了从 Camera 到 Screen Point 的射线,而射线与 Quad 平面的交点,才是真正玩家想点击的地方(得到三维坐标)。
Amazing!
顺便再来讲一下 Quad,它是一个有方向的平面,从上方看你能看到它(的贴图材质),而从侧面或者下面则什么也看不到,并且它的碰撞体积也是有方向的。这在 3D 游戏中经常充当墙或者地面的角色,在里面看很正常,但当用某些办法跑到地图外时,看起来就像是贴图错乱,这仅仅是因为 Quad 是有方向的,有些贴图就看不见了而已。
脚本 (Script) 也是 Component
是的,脚本不仅只是包含逻辑。它可以被实例化,分配到多个对象,还可以被其他脚本所引用,甚至可以在其他脚本中将脚本禁用。
脚本需要绑定至某个 GameObject 才能生效,绑定的位置倒并不是本质的。一般的做法,是把脚本绑定到与其逻辑相关的对象上,这样子至少带来两个好处:
- 便于理解和查看,方便做成 Prefab,供将来开发使用
- 减小脚本内寻址的代价
打个比方吧,可以把脚本看作是寄生虫,所依附的对象看成宿主。虫儿需要依附于宿主才能发挥作用,一种虫儿可以依附于多个宿主,一个宿主也可以被多条虫儿所依附。这画面想象起来有点诡异,但这却是两者间的关系。
那么,脚本在执行过程中,需要持有一些对象的引用,包括但不限于 “宿主” 的位置、状态、声音资源、动画资源、粒子效果资源等等。这些资源直接拿来用就好。若是依附错了对象,那么资源的获取就会变得困难,这就是上面 寻址代价小 的含义。
等会儿再写脚本设计过程中需要考虑的另外两个因素,刚刚只是其中之一。。。
视角自动跟随与缩放
在一些游戏中,我们经常需要屏幕能够自动跟随主角移动。这在 Unity 中,是通过改变 Camera 的位置来实现的。这一小节我们就来讨论一下这种移动的方法。
自动跟随
实现自动跟随需要如下几个信息:一个是期望位置(desiredPosition),另一个是平滑时间(smoothTime)。前者会通过一些计算得出,后者就是自己指定啦。最终达成的效果就是 Camera 能在给定时间内平滑移动到指定位置。
为此,我们需要用到如下函数:
public static Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 currentVelocity, float smoothTime);
通常我们会在 FixedUpdate () 函数中这么写
transform.position = Vector3.SmoothDamp(transform.position, m_DesiredPosition, ref m_MoveVelocity, m_DampTime);
这个脚本需要 attach 到 Camera 对应的对象上才会有用,因为 transform.position 指的是 Camera 自己的位置。
自动缩放
实际上,我们需要的是一个全局位置在另一个位置坐标系中的坐标,这时我们就需要 Transform.InverseTransformPoint 这个函数。
Vector3 desiredLocalPos = transform.InverseTransformPoint(m_DesiredPosition);
后面的事就是数学上的计算,不再细说了。
坑
这需要慢慢填 =_=
No. 1
这个坑比较奇葩,既不是名字写错、文档简略、功能不支持这种。而是发生在终于把项目都写完,调试通过,准备导出的时候。
例行公事,导出项目 -> 选择路径 -> 项目目录下建立 Builds 文件夹 -> 选择并导出,然后等读条。
读条时间太长了啊,出去泡了杯咖啡回来。咦,导出的文件怎么找不到了,诶,我的项目文件和资源目录怎么也不见了,场景怎么也打不开了!卧槽!导出到项目根目录去了,然后把其他文件都删掉了啊!坑爹啊!什么都没有了啊,想恢复都恢复不过来了啊!
那让我怎么办啊,代码都白写了啊。幸好是教程的项目,也没损失什么,要是是真实项目的话:
“说起来你可能不信,真是 Unity 自己动的手!” =_=
No. 2
这个坑不知道算不算 unity 的 bug,虽然无伤大雅。在 OnTriggerEnter 的判定中调用 Invoke("Restart", delay),在被调用方法里重新载场入景 SceneManager.LoadScene (0)。然后,在delay的时间后,场景被重载,但 OnTriggerEnter 可能再次被触发,导致二次载入。解决方法也很简单,建立个 flag 变量存储是否已经载入即可。
棒棒哒
说起来你可能不信,Unity 也对我的 SPACE SHOOTER 动了手 =——=
那现在变大神啦?