我是一位Epic官方授权的Unreal教练。 我花了六年从事技术创作,担任雇佣者,制作着效果、视觉效果、机械、用户界面和使用者体验,以及关卡设计和关卡艺术。在某个时候我想:我为什么要教别人如何在Unreal中制作好玩的东西,而自己还没有制作出自己的游戏呢?所以我收拢了一个合伙人,我们动手了。
然后出炉的游戏是 HOLD MY WHEEL,一款5人合作生存游戏,在这个游戏中,你的车就是你的家,你的同伴就是你的食物(如果他们感染了 zombies),而死亡并不是游戏的结尾,而是游戏的开始。该游戏即将在8月3日在Steam上发布。
现在让我说一下关于是如何在游戏内制作的,因为这才是帖子的实质意义所在。
---
没有购买任何店家资产的机制或视觉方面
当Solo游戏开发者制作一个游戏时,他们会往FAB(Epic’s Asset Browser)或某个市场上去,取出预先制作好的包裹,然后开始制作。不要误会了,这种方式是完全可以接受的。因为这才是它们想要的,但我并不感兴趣。
我仅仅购买了一套低调环境包装。对于其他的一切(着色器、视觉效果、后处理程序、物理动态系统、死亡状态、动画和所有机制、复制程序、声音对话)均由手动制作,绝大多数是通过C++和HLSL编写。
为什么?因为当你是一名技术艺术家时,预先购买一个着色器资产感叹是像买了一个简易生成随机数的微动作器一样简单的。同样,你可以写出精确的什么、多久和你所期望的任何事情,而这不需要额外的压力。而且,最重要的是,你可以以一种可以表现出你的创造力的方式来展现出作品。即使是系统通过HLSL、Nitagu和实例的组合生成出来的也是如此的。
---
磁铁枪指针你你用来让手里的车飞行的工具
当时先将车辆体现为一辆大型的汽车,体积在17吨。然后再将它连接在物理约束系统上,并通过距离和力来进行调整。然后你可以在三维空间中让玩家指挥手里的车跑动,看似容易直到有5个同伴站在车顶,车体被抓住,车顶旋转炮火还在进行射击。此时物理引擎就开始在玩家的帧内玩儿耍了。
需要写出自己的解算程序来解决约束之间的冲突。这是因为Epic的五代引擎在运算中不喜欢手握着一辆17噸的物体,并在一群僵尸中狂乱甩动的场景。也需要覆写复制计数逻辑程序,以便在玩家正在剧烈操作的时段,增加复制的数据量;在玩家处于空闲时段时,降低复制数据的量。
---
物理引导的通行规划
在普通的游戏中,航点系统是这样的:你首先就画出路径,然后引擎会把这些路径融入到地形中,并让游戏人物依据这些路径来跑步着。然而在这个游戏中,情况却完全相反。游戏世界是由玩家自己规划出来的桥梁,将障碍物等一堆废铁捆绑起来,然后扔在那里。玩家还可以将地表颠倒而且是如此的随心所欲。对于这种情况,普通的航点系统是极其不堪的。对于汽车、玩家和僵尸的移动导向,计算方式完全依赖于物理光线检测。计算的内容包括车辆的质量、轮毂的抓住力和轴承稳定性等。这种计算方法是软体的模拟,而并不是传统意义上的AI规划。对玩家自己的移动,使用了环境分析和相对简单的环境分析计算方法,对于8x8km的极大版地图,可以显著的提高游戏速度。
---
亡魂状态
当玩家死亡时,他们不会跳转到黑屏中。相反,他们会变成亡魂或僵尸或多种状态,他们仍然是可以继续游戏的,游戏规则也会产生变化。仅仅是将他们的死状态表现得到看似真实也是足够的了。所有的视效果我都亲手进行设计制作。其中最为有趣的要数死后的效果,效果很简单:以动态效果将亡魂的外观加以处理,然后表现的方式来表现死亡时的颜色和死状。
这里有一个效果样本,以及HLSL样本:
https://preview.redd.it/hnkvwycz38wg1.png?width=1055&format=png&auto=webp&s=cf91e7092e4e7ebc8551fb6f1b92f56589739a14
https://preview.redd.it/vmqavycz38wg1.png?width=1720&format=png&auto=webp&s=2a94d02f5345adae14d4d24663d98fe80a14216a
------
Distort:代码样本如下:
distort
float2 uv = UV;
float t = View.GameTime;
float2 d = float2(sin(uv.y*12.0 + t*1.5), cos(uv.x*9.0 + t*1.2)) * 0.008;
d += float2(sin(uv.y*30.0 + t*0.7), sin(uv.x*28.0 + t*0.9)) * 0.004;
float2 c = uv - 0.5;
float r = length(c);
float2 rd = c / max(r, 0.0001);
d += rd * sin(t*2.0 + r*14.0) * 0.01;
return uv + d;
ZombieOverlay:代码如下:
ZombieOverlay
float2 uv = UV;
float3 scene = Scene;
float t = View.GameTime;
scene *= float3(0.55, 1.15, 0.55);
float lum = dot(scene, float3(0.3, 0.59, 0.11));
scene = lerp(float3(lum*0.4, lum, lum*0.4), scene, 0.65);
float2 c = uv - 0.5;
float r = length(c);
scene *= smoothstep(0.9, 0.25, r);
float blobs = 0.0;
for (int i=0; i<5; i++)
{
float fi = float(i);
float2 bc = float2(0.5 + sin(t*0.35 + fi*2.1)*0.32,
0.5 + cos(t*0.45 + fi*1.7)*0.32);
float br = length(uv - bc);
blobs += exp(-br*br*90.0);
}
scene *= 1.0 - saturate(blobs)*0.35;
scene += float3(0.0, 0.25, 0.0) * saturate(blobs);
float edge = smoothstep(0.22, 0.55, r);
float s1 = abs(sin(uv.y*38.0 + sin(uv.x*8.0 + t*0.3)*3.0 + t*0.4));
float s2 = abs(sin(uv.x*42.0 + sin(uv.y*9.0 + t*0.25)*3.0 + t*0.5));
float s3 = abs(sin((uv.x+uv.y)*55.0 + sin(uv.y*6.0 + t*0.2)*2.5));
float vessel = (1.0 - smoothstep(0.0, 0.045, s1))
+ (1.0 - smoothstep(0.0, 0.045, s2))*0.8
+ (1.0 - smoothstep(0.0, 0.035, s3))*0.6;
vessel *= edge;
float2 vp = floor(uv*280.0);
float vh = frac(sin(dot(vp, float2(12.9898, 78.233)))*43758.5453);
vessel *= smoothstep(0.25, 0.25, vh);
scene = lerp(scene, float3(0.65, 0.03, 0.03), saturate(vessel*0.85));
float heart = 0.5 + 0.5*sin(t*2.8);
scene += float3(0.09, 0.0, 0.0) * heart;
scene.g *= 1.05;
return scene;
------
玩家车辆碎裂时造成的屏幕扭曲效果
在游戏中,当玩家车辆发生爆炸时,不仅仅是单纯的世界画面就发生了破碎。整个画面(包括UI、HUD、合作伙伴时间、所有元素)都像是玻璃一样碎裂并飞散。UI元素不会消失,反而会像画面的碎片一样散落在一起。这种效果根本不可能通过SceneCaptureComponent2D组件来完成。这是因为UI并不是主渲染结束时的效果。因此我需要编写一个自定的RDG模板,从而可以在渲染管线完成之后来抓取最终的缓冲片。所以我写了一个取值缓冲片的函数,这块缓冲片直接从最终的缓冲镜像是抓取的。并且经过了几次调优之后把我手里的缓冲片进行了合并。这个合并的缓冲片再次被传入一个UI元素的材质中,这个材质中有一个扭曲效果让画面就像玻璃碎裂一般来播放。
最终效果如下:https://preview.redd.it/csv6afci48wg1.png?width=1915&format=png&auto=webp&s=ff6fe759c14a09abbc934583a2f35e33f9f57398
------
僵尸/人偶/亡灵动画
这里有4种以上的状态。人偶状态中,物理引擎完全是开启的,而动画是关闭的。你横滚、溜溜、还可以推动你自己运动着,但完全无法进行任何控制。
僵尸状态中,我使用了控制桨的混合与动画与蒙版来实现了人吃人的动画。亡灵状态中仅仅是通过一种简单的HLSL着色器的透明效果来实现的。
对于这个游戏来说,这种方式是多虑了。不过我就玩得很开心。
评论 (0)