2021-11-1524962次浏览
3评论
22收藏
4点赞
分享
本文以策划视角介绍射击游戏中常见体验的构建方向。程序同学关注点可以放在“知道策划可能要做这么个东西,程序可以提前做一些相关思考”。希望这篇文章能够帮到同在制作射击游戏的同学们。
本文以策划视角介绍射击游戏中常见体验的构建方向。程序同学关注点可以放在“知道策划可能要做这么个东西,程序可以提前做一些相关思考”。希望这篇文章能够帮到同在制作射击游戏的同学们。
射击游戏的反馈通过外设输入,经过游戏逻辑处理,最终呈现于屏幕上。外设输入是这个环节中的第一步。本段介绍外设输入相关参数的处理。
射击游戏的鼠标灵敏度可以定义为:鼠标在现实中移动的物理距离,与游戏内镜头旋转角度的对应关系。游戏内的鼠标灵敏度一般由EDPI表示。
EDPI = 鼠标DPI * f(游戏内设置的灵敏度数值)。函数f即为游戏内的灵敏度规则。
主要有线性型和指数型两种。
线性型:代表游戏有CSGO,COD系列,守望先锋,APEX。
相同鼠标DPI下。当灵敏度值为a时,镜头转动360°需要移动鼠标的距离为d;当灵敏度值为2a时,镜头转动360°需要移动鼠标的距离为d/2。
线性型的游戏之间,灵敏度值往往是一个固定倍率的关系。比如CSGO的灵敏度值为1.0,对应COD系列和守望先锋的灵敏度值为3.33,对应APEX的灵敏度值也为1.0。详细的关系可以在https://www.mouse-sensitivity.com/中查到。
指数型:代表游戏仅有PUBG,所以以PUBG为例。
相同鼠标DPI(以1000为例)下。灵敏度值为0,50和100的对比如下图所示。
图线形式如下图所示。x表示游戏灵敏度值,y表示镜头转360°需要移动鼠标的距离。
公式为y=y0/pow(10, x/50)。其中y0表示灵敏度为0时y的值。
上文提到的是鼠标移动与游戏镜头旋转角度的关系。但是,玩家感知到的鼠标灵敏度,是从镜头旋转时景物变化的快慢感知到的,并不是从镜头旋转角度量中感知到的。这里介绍玩家感知景物变化快慢与镜头实际旋转角度的对应关系,即相对灵敏度。
在介绍相对灵敏度之前,需要介绍镜头FOV的概念。
上图中,s为视锥的中垂线。s⊥v,s⊥h,v⊥h。
屏幕分辨率resolution = h / v。
∵h = s*tan(HFOV/2),
v = s*tan(VFOV/2)
∴resolution = tan(HFOV/2) / tan(VFOV/2)
笔者使用的引擎,FOV指的是VFOV。HFOV根据当前的屏幕分辨率,由VFOV换算而来。
玩家感知景物变化快慢,实际上是屏幕最左端的景物标志物滑动到屏幕最右端的速度,即以“屏幕”为单位(例如下图中CS的半屏甩狙)。
射击游戏中往往有多种倍镜。不同放大倍率,本质上对应了不同的镜头FOV。以PUBG为例,TPP下默认的HFOV为80,开2倍镜时为40,开4倍镜时为20,开8倍镜时为10。
这里我们假设某游戏的默认HFOV为120,开倍镜后的HFOV为60,那么可以得到下面两张图(左图为默认,右图为开倍镜)。
现在从玩家体验层考虑。在左图的视野下,玩家环视一周的景物变化为“3屏”;在右图的视野下,玩家环视一周的景物变化为“6屏”。假设玩家在相同时间内,鼠标移动相同距离完成了上述环视一周操作。显然,玩家会觉得右图视野下的鼠标灵敏度更高(为左图的2倍),因为景物变化速度右边是左边的2倍。为了让玩家感觉左右两边的景物变化速度相同,以获得相接近的手感,就需要使右图视野下,鼠标移动相同距离下景物变化也为“3屏”。这种校正的方式,即为相对灵敏度。
综上,在定好了鼠标灵敏度的基础规则后,需要根据HFOV的变化来调整“鼠标移动相同距离时,镜头转动的角度”,以保证手感的一致性。
市面上的两大主流游戏机为PS和Xbox。所用输入设备均为手柄。
玩家通过手柄的摇杆输入方向。摇杆看上去是圆形的,但是拨动摇杆旋转一周得到的源数据却是如下图所示(数据源由引擎支持同学提供)。
从图中可以看出,得到的摇杆输入源数据,并不是标准的1*1正方形,也不是标准的单位圆,而是一个类似圆角正方形的图形。如果游戏逻辑层粗暴地以处理1*1正方形或者处理单位圆的方式,来处理摇杆输入源数据,游戏中摇杆输入对应的表现(可能是镜头或主角动作)和手感必定会造成奇怪的体验。
因此,我们需要引入摇杆输入源数据的修正规则,先把摇杆输入源数据clamp到单位圆上。然后游戏逻辑层再以处理单位圆的方式,处理修正后的摇杆输入数据,就不会有体验层的问题。
可以发现,摇杆输入源数据,实际上由下图的蓝圈和红框取交集而得,修正规则的目标是把这个“圆角正方形”clamp到绿色的单位圆上。
详细规则如下:
(1)确定原始输入幅度
magnitude = sqrt( pow(X, 2) + pow(Y, 2) );
if(magnitude>1){ magnitude = 1;}
(2)剔除摇杆死区得到修正后的输入幅度
activeRange = (magnitude - deadzone)/(1 - deadzone); if(activeRange<0){activeRange = 0;}
(3)计算输入角度
angle = atan2(Y/X)
(4)归一化(clamp到绿色单位圆)
newX = activeRange*cos(angle); newY = activeRange*sin(angle)
得到的newX和newY即为修正后的X和Y。脚本其他需要用到摇杆输入的地方,都必须读取newX和newY。
算法原链接地址:
https://answers.unrealengine.com/questions/226365/analog-stick-input-traces-a-square.html
TPS镜头的基础参数有offset(Vector3类型)和length(Float型)。
其中,offset表示角色模型空间下的镜头焦点位置。以PUBG为例,焦点大约在角色的头部偏右一些的位置。
length为镜头的焦距,表示镜头实际位置与镜头焦点的距离(镜头朝向为镜头位置指向镜头焦点位置组成的方向向量)。因此,抛开角度限制的因素,当镜头焦点位置不变时,镜头实际位置组成的集合为一个球体,球心为镜头焦点位置,球面为镜头位置,半径为镜头焦距。在笔者使用的引擎中,这种镜头模式为FollowPlacer。
但是如果将FollowPlacer直接用于TPS游戏会出现一些问题。还是以PUBG为例,按住Alt键自由观察时,角色的模型空间是没有变化的,只变了镜头。如果此时镜头以模型空间下某个固定的offset为焦点,那么在转动镜头时,角色在屏幕中的位置就会绕圈,而offset点在屏幕中的位置是不变的,这并不是玩家期望的效果。玩家期望的是角色在屏幕中的位置不变。因此需要做补充规则。Offset只是镜头在pitch=0,yaw=0时,焦点在模型空间中的位置。当镜头转动时,焦点需要以焦点到角色轴心的距离为半径,绕着一个圆旋转,这样才能保证角色在屏幕中的位置不变。在笔者使用的引擎中,这种镜头模式为TpsPlacer。
有了原始的TpsPlacer,可以发现,当pitch(俯仰角)较大时,镜头离角色的位置会过远。因此需要根据镜头当前的pitch值修改镜头的length。从侧视图看,镜头位置的集合变为椭圆。
侧视图参数说明(以笔者使用引擎的坐标系为例):
上方向对应y轴正向,右方向对应z轴正向。
O点为主角位置,P点为镜头焦点位置,C为某个pitch角度下的镜头点。
OP为offset。
椭圆上一点到P点的距离记为PivotDistance。PivotDitance由椭圆的半长轴和短半轴以及当前镜头的俯仰角(pitch)决定。
最终,策划想要定义一个TPS镜头的位置,就需要配置offset(pitch和yaw都为0时,焦点在角色模型空间的位置),length_a(pitch=0时的length,也是侧视图椭圆的半长轴),length_b(pitch=±90时的length,也是侧视图椭圆的短半轴),共3个参量。
上文中提到,一个完整的TPS镜头由offset, length_a, length_b三个参数组成。当主角的状态变化(例如进入肩射视角、蹲下、趴下)时,策划需要给出新状态的镜头参数。
例如站姿肩射时,策划需要配置站姿肩射的offset,并将原有的length_a和length_b等比例缩小一个倍率;蹲下时,策划需要配置蹲下状态对应的offset;趴下时,策划需要配置趴下状态对应的offset。保证每个状态都有唯一的offset和length_a, length_b。
PUBG肩射镜头过渡:
然后是过渡的过程。以笔者使用的引擎为例。可以通过脚本的Mover实现过渡(引擎目前提供了“无过渡”和“线性过渡”两种过渡规律。如果想通过脚本实现更复杂的过渡规律,则需要策划给出过渡公式,程序同学自定义Mover)。也可以通过编辑器的CameraPivotCtrl节点,配置halflife参数使用半衰期过渡规律。策划同学在配置镜头参数时,除了需要配置offset和length以外,还需要配置进入该镜头时的transit_time或者halflife。
实际应用中的细节点:状态是唯一的,但是镜头表现一定不能跳变,程序层的插值算法需要保证镜头在任意时刻都是连续的。除非策划有特殊要求,否则任何镜头跳变的情况都会极大降低玩家的游戏体验。
FPS镜头的基础参数有offset(Vector3类型)和length(Float型)。
其中,offset表示pitch=0且yaw=0时,镜头在角色模型空间的位置。length表示旋转轴长度。
当玩家以第一人称旋转视角时,模拟的是角色的瞄准偏移。角色的瞄准偏移,并不是保持视点位置不动,仅作“向上看”和“向下看”的表现。而是在整个向上向下看的过程中,视点的位置也有所变化,如下图所示(做了夸张处理)。
因此引入length来表示旋转半径。
最终,策划想定义一个FPS镜头的位置,就需要配置offset。而length只需要在镜头初始化的时候指定一次即可。
上文中提到,策划只需要指定offset参数即可指定一个状态下的FPS镜头参数。
例如蹲下时,策划需要配置蹲下时的镜头offset值;侧身peek时,策划需要配置左peek和右peek时镜头的△offset值,站姿peek就是站姿的offset+△offset,蹲姿peek就是蹲姿的offset+△offset。
以笔者使用引擎为例。过渡过程可以通过脚本用Mover实现过渡,引擎提供的过渡规律也是“无过渡”和“线性过渡”两种,如果想通过脚本实现更复杂的过渡规律,同样需要策划给出过渡公式,程序同学自定义Mover。
TPS视角下使用的是TpsPlacer,FPS/ADS视角下使用的时FpsPlacer。
TPS(Third Person Shooting)视角:
FPS(First Person Shooting)视角:
ADS(Aiming Down Sight)视角:
以笔者使用的引擎为例,TpsPlacer与FpsPlacer切换时,使用Blender做镜头的过渡。引擎提供的Blender过渡规律也是“无过渡”和“线性过渡”两种,如果想通过脚本实现更复杂的过渡规律,同样需要策划给出过渡公式,程序同学自定义Blender。
以笔者使用的引擎为例。
上文提到Mover时,只提到了其修改镜头位置参数的作用。实际上镜头参数还包含了镜头的FOV值,使用Mover同样可以在脚本中修改镜头的FOV值,以及控制过渡规律。例如屏息操作的镜头反馈。策划需要配置屏息状态下的FOV倍率,以及进出屏息镜头的过渡时间。
使用编辑器的CameraFovCtrl能够实现同样的效果,过渡规律为半衰期过渡。触发屏息操作时,脚本设置控制屏息模块的开关图变量,策划使用开关图变量加上平滑过渡即可。
▼▼▼
▲▲▲
评论 (3)