|
本文由参与GGJ 2017的游戏开发者Ciro Continisio为大家分享他与美术设计师Jana Kilianová组成的二人团队,在48小时内开发出小游戏《Splash Clash》的过程与经验。
Global Game Jam(GGJ) 2017已于1月22日落幕。全球共有三万多游戏开发者在700多个不同的城市参与了为期三天的游戏创作活动,围绕着今年的主题“Waves”制作了7000款游戏。
《Splash Clash》就是其中一款优秀的游戏。这款可以被轻松掌握的双人对战游戏由两个像素角色通过跳跃产生波浪,借助波浪将对手推出平台。下面我们从波浪特效、物理、场景设置和粒子这几个方面一起来看看开发者是如何在48小时内将它开发出来的。
波浪特效
一开始我们希望实现较为炫酷且逼真的水特效,所以不愿意使用一些近似的方法,比如以环形去扩散三维对象或者使用粒子。最终的方案是为Displacement着色器使用一张圆的纹理,这样就可以从平面(Plane)产生波浪,对这个圆的纹理进行缩放,就可以实现动态的波浪。需要说明的一点是:本文分享的是如何在GGJ中快速实现这样的效果,并非介绍实现该效果的最佳方案。可能还会有性能更高的解决方案。
初步模拟
在开始制作前,我们先在Photoshop中绘制了圆形纹理并利用网上找到的角色,例如下图的蝙蝠侠,进行了初步模拟。
我们在Unity Manual中找到了简单易用的位移(Displacement)着色器,它还支持曲面细分(Tessellation),这样就不用担心几何问题了。将该着色器用于水材质后效果如下:
如上图所示,Tessellation数值已被设为最大值,它用于控制表面被细分的次数。下面的Displacement滑动条则用于控制波浪凸起的高度。
可行性分析
初步模拟过后,首先要确认该想法的可行性。因此,我们创建了白色并含有透明通道的圆形纹理,并通过函数将其固定在不透明纹理上。将其作为位移贴图后的效果如下:
粗糙的开始
接下来,我们扩展了函数功能,以便于固定多个圆。
多个静止圆形
我们必须合成多个透明的圆形来实现表面上移动的多个波浪。如上图所示,目前这些圆形纹理的分辨率相当高,基本都是256 x 256的。
由于波浪之间是相互独立的,所以每帧都必须删除整个纹理再重新计算缩放的圆形。动态效果如下:
临时纯色纹理的随机动态波浪效果
然而此时不得不大幅度减小纹理尺寸了,因为之前的方法完全未经优化:一张512x512的Bump纹理表示每个波浪都有262144的像素读写操作,而且256x256的波浪纹理是随时间缩放的,每帧都需要重写整个纹理。采用这种做法,屏幕上仅有4个波浪时,每帧就会产生非常恐怖的1048576次像素操作。
所以我们先缩小了所有纹理尺寸,将圆环纹理缩至32x32,Bump纹理为64x64。经过少许测试后遇到了个新问题:小纹理在缩小后再次放大会导致纹理变形,不再是圆环。
这个问题的解决方案是缓存原始的圆环纹理,每帧都使用原始纹理重新生成缩放后的圆环,而非一直使用同一张纹理,因为多次缩放会导致变形。也就是说每个波浪都有一个原始纹理的引用,但实际并未使用原始纹理,而是仅复制其像素到重新缩放的纹理上。
可是这种方案在Plane上效果正常,但如果将材质应用到圆盘上,就会出现圆盘边缘挤出边界的异常效果:
圆盘左边边缘超出边界导致出现镂空
于是我们试着对着色器做些修改,不再沿着像素法线进行扩散,改为只向上扩散。将以下代码:
- v.vertex.xyz += v.normal * d;
复制代码
改为:
- v.vertex.xyz += float3(0,1,0) * d;
复制代码
这样就可以获得正常的波浪效果了!
波浪冲撞完成,但波浪效果还不太明显
实现波浪冲撞后,就要考虑彩色纹理了。如上图所示,波浪虽然有一道来自于方向光的阴影以便突出显示,但这还远远不够,还需要显示得更为明显,让玩家可以更好地用于判断跳跃的时机。
我们起初还是使用同样的方法,将白色的圆环纹理放在彩色圆盘纹理上方,与bump/displacement中的纹理位置完全一致:
从上方的动画中可以清楚地看到白色圆环如何显示低分辨率的纹理,尤其是当圆环很大时。
此时我们决定将调整着色器,在波浪上方就绘制白色像素。毕竟这样每帧只需读取一半像素,运行效率会更快。这就需要更改surf函数中,对应计算像素颜色的代码,如下所示:
- half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color * (30 * IN.worldPos.y + 1);
复制代码
使用IN.worldPos.y可以确保照顾到了像素在世界空间的高度。像素越高,则亮度越强,也越接近于白色。效果如下:
至此,波浪特效制作完毕。
物理
图形问题解决后,我们下面来实现角色被波浪推开的物理效果。这个过程不太复杂,只需将每个波浪作为带有球形碰撞器(Sphere Collider)的游戏对象,并将Collider用作触发器即可。由于纹理是缩放的,碰撞器也会随之缩放。
将碰撞器进行缩放以适配位移贴图中的波浪
一旦碰撞器与某个角色发生碰撞(OnTriggerEnter),就检测角色的Y坐标是否低于某个特定的阈值,该阈值与波浪强度相关,是随时间减小的浮点数。如果是低于这个特定的阈值,则角色被Rigidbody上添加的力推开,这个力的大小也同样与波浪强度成比例关系。
我们使用标签(Tag)对碰撞器进行过滤,保证玩家1产生的波浪仅与玩家2碰撞,反之亦然。这里稍微提一下Drag,用纯物理方法对角色添加外力(AddForce)使其快速加速时,通常会使用Drag方法进行平衡以防止角色漂移。但添加太多Drag会让角色在跳跃时降落缓慢,从而产生一些奇怪的漂浮物理效果。
我们在此例中实现了个伪Drag,但仅将Rigidbody速度的x与z分量乘以一个随意的因子,不动y分量(重力):
- rb.velocity = new Vector3(rb.velocity.x * .8f, rb.velocity.y, rb.velocity.z * .8f);
复制代码
场景设置
从下图中可以看出,这个游戏是2D与3D混合的,角色是沿X轴旋转30度的Sprite,以适配相机旋转,场景下方掉落的水滴与岩石也同样旋转。盛水的圆盘是游戏中唯一货真价实的3D对象,因为要用它来实现复杂而逼真的波浪。
动图
角色都带有Capsule Collider(胶囊碰撞器),以及一个锁定旋转的Rigidbody,保证角色只会走动不会倾斜。
粒子
我们为跳跃动作添加了一些粒子作为反馈,增加一种“水花四溅”的有趣体验:
动图
其中,掉落水滴的粒子是使用一个大的圆形发射器。我还将前面的粒子与后方粒子分开,后方粒子则是通过另一个粒子系统生成,放在180度的圆形模块上。后方粒子的颜色不变,且生命周期非常短,因为它们完全不可见。
动图
通常我们会尽可能让所有参数(生命周期、速度、重力等等)都随机变化,但这里我们使用了固定数值,因为希望粒子能实现像素风的感觉。同理,我们对粒子随生命周期的颜色渐变也使用了固定模式而非混合,这样粒子会快速改变颜色,而不是如往常一样渐变消失。
总结
总的来说,本文涉及的一些解决方案都不是完美且未经过优化的,更像是一个为实现灵感的Demo。但因为这是追求快速极致的Game Jam,所以性能不那么优秀也没关系。
《Splash Clash》的开发者Ciro Continisio向我们分享了参加Game Jam活动时的时间分配方法:将更多时间花在代码质量上而非优化,在编写代码时采用更稳健的方法,少一些混乱代码,多一些逻辑上的类结构与信息流,以避免在最后关头出现Bug。
via:Unity官方
相关阅读:《彼岸》:我的GameJam之旅和心得分享
|
|