个人网站建设源代码,wordpress链接跳转,vi设计logo,色和尙做爰网站“音乐游戏”一般简称为“音游”#xff0c;玩家需要配合音乐的节奏来进行一定的动作。 《Project SEKAI》作为一个“移动端音游”#xff0c;绝大多数玩家会使用手机、平板电脑等移动设备的触摸屏进行游玩#xff0c;也有极少数的玩家不按常理出牌#xff0c;使用手台、键… “音乐游戏”一般简称为“音游”玩家需要配合音乐的节奏来进行一定的动作。 《Project SEKAI》作为一个“移动端音游”绝大多数玩家会使用手机、平板电脑等移动设备的触摸屏进行游玩也有极少数的玩家不按常理出牌使用手台、键盘乃至于触控板等非常规输入设备进行游戏。同时作为“下落式音游”的一员在游戏中音符会从界面的上方落下玩家需要根据音符时机和类型在正下方的判定区域做出一定的操作当音符与判定线完全重合时便是音符完美的判定时机。 本篇文章作为音符具体判定机制分析的前置攻略将会以使用Unity引擎编写的《Project SEKAI》为例子带领各位读者简要了解一下一款音游是如何实现判定机制的或许对其他音游也能有所启发。为了便于理解下文会尽量避免对程序代码的讨论仅说明基本原理与过程部分细节可能不够严谨还请谅解。 本篇文章主要分为3个部分
第1部分《从触摸屏幕到触摸对象》介绍操作系统和Unity框架处理触摸事件的流程 第2部分《从触摸对象到触摸处理》介绍游戏的主循环和触摸事件处理逻辑 第3部分《从触摸处理到音符判定》介绍音符的判定时间、下落速度、状态机以及判定过程
一、从触摸屏幕到触摸事件
1.1 触摸事件生成与传递 触摸屏作为移动设备上最主要的输入设备当用户触摸时硬件会以一定频率对屏幕进行触控采样采样过程主要是获取用户触摸的位置信息驱动程序会获取来自硬件的事件经过处理后将触摸位置的坐标、用于追踪手指的追踪ID等信息生成内核事件。越高的触控采样率会让硬件采样时间间隔变短对手指运动的追踪也更为准确。例如iPad Pro提供了240 Hz的触控采样率代表每秒钟会从触摸屏中采样240次约4毫秒1次。 在程序的执行过程中如果需要获取触摸事件会通过声明对触摸事件的监听器来实现。当触摸事件来临后操作系统会从内核中取出相关事件并经过一定处理和封装后将触摸事件传递给正在监听这一事件的程序进行处理。 这一过程可以简化为下图 操作系统封装的触摸事件有以下程序关心的内容
触摸动作操作系统会识别出按下、移动、松开等动作 触摸位置触摸位置的坐标 历史触摸位置上一次触摸事件的触摸位置坐标 事件时间触摸事件发生的时间 触摸点编号为当前每个触摸点分配单独的编号 1.2 记录触摸事件 Unity作为游戏引擎会为游戏预处理来自操作系统的触摸事件这样做的目的是使用Unity引擎的游戏可以使用统一的接口获取输入事件有效减少了实际运行的操作系统给游戏带来的差异性。 当接收到触摸事件时Unity会记录这些触摸点的许多信息游戏着重关注以下内容
触摸阶段按下、移动、按住不动、松开等阶段 触摸位置触摸位置的二维坐标以屏幕左下角为原点 手指编号会为正在触摸的手指各分配一个编号 位移向量如果产生了一定滑动位移将会记录滑动的位移向量 例如一根手指在极短时间内快速执行了“按下”、“移动”、“松开”的过程Unity会记录下3个“手指编号”相同、“触摸阶段”不同的触摸事件 二、从触摸事件到触摸处理
2.1 游戏主循环 在设计游戏框架时需要一个游戏主循环Game Loop每隔一段时间循环处理游戏逻辑比如完成音符判定、音符移动位置等。不过无论使用什么策略来确定间隔时间由于硬件的性能限制是很难做到完全等间隔的。例如如果在这一步固定让音符位移10像素会因为时间间隔不均匀导致无法匀速运动需要根据和上一次处理的时间差来进行进一步的运算。 为了方便起见大部分游戏会以“1帧”作为时间间隔进行循环“帧”指的是游戏中的单幅画面例如帧率为每秒60帧代表1秒中之内会有60个画面。同样的每帧之间的时间间隔并非完完全全固定也会因为性能原因产生波动。值得一提的是并非所有游戏会在每帧都处理一遍游戏逻辑例如知名的沙盒游戏《Minecraft》无论显示帧率有多少会以每秒20游戏刻的固定速度进行游戏更新当然如果卡顿了也会导致游戏刻变少。 同样在Unity引擎中游戏主循环也不需要开发者自己来实现Unity为游戏提供了2种不同的循环模式每帧处理一次、与帧率不同的固定频率处理。《Project SEKAI》采用了每帧更新一次的策略判定相关的逻辑也是每一帧执行一次。
2.2 触摸事件获取 Unity收到操作系统传递的触摸事件后做的事情仅仅是记录触摸事件并不会在收到事件后立刻交给游戏处理需要等待游戏在处理游戏逻辑时主动获取上一帧之后的触摸事件。特别的是虽然操作系统在传递触摸事件的时候提供了事件发生的时间但Unity既不会记录事件发生的时间也不会记录收到事件的时间。 例如玩家在第k帧后立即进行触摸操作在触控采样后这一事件进入Unity引擎但游戏到了第k1帧的时候才能得知这一事件且游戏认为按下的时间是第k1帧的时间而非实际按下的时间 这样的设计使得Unity引擎下的音游在判定时会产生与真实输入的时间差会导致“拖判”更容易发生这一时间差的最大值为两帧的间隔时间帧率越高间隔越小拖判也越不明显。 为了从根源上解决拖判的问题游戏可以使用“Native Touch”插件等手段绕开Unity的触摸处理逻辑直接接收操作系统的触摸事件。遗憾的是可能是碍于技术水平或者是性能的问题《Project SEKAI》并没有进行类似的改进而是直接使用了Unity提供的触摸事件。更为遗憾的是虽然提高游戏帧率能在一定程度上改善拖判的问题但拥有120帧显示屏的iPad Pro却被锁死在了60帧不得不接受拖判的现实。
2.3 触摸事件处理 无论使用什么方法获取到触摸事件后都需要对输入事件进行进一步的处理。作为一个下落式音游一个非常重要的处理就是找到点击位置对应的下落轨道。 《Project SEKAI》将主要的游戏区域分为12根轨道Unity作为一个支持3D的引擎这些轨道在三维空间中倾斜一定角度通过近大远小的视觉效果给玩家产生一种音符从远处逐渐接近判定线的“距离感”。当玩家触摸判定线及其上下位置时会通过Unity将二维的屏幕坐标换算为点击位置的三维空间坐标并使用这一坐标计算对应的轨道。值得注意的是这一步得到的轨道并非诸如第1轨、第2轨的某一条轨道轨道而是类似在第1轨的左起33%这样的准确位置。《Project SEKAI》每帧至多处理10个触摸事件。 例如假设只有2条轨道下图所示的这条线便是第2轨的50%位置这条线并非在平面上垂直于判定线而是在空间上垂直 当然在具体实现上可以认为每个轨道的宽度为1直接以1个小数来表示触摸事件的轨道位置例如2.5等。 虽然《Project SEKAI》有12根轨道但实际上音符会拥有一定宽度以达成某种意义上的“无轨下落”音符可以通过左侧和右侧所在的轨道位置来表示其位置和宽度信息。 三、从触摸处理到音符判定
3.1 音符判定时间 音游讲究的是玩家操作与音乐节奏的配合一般来说会根据玩家完成操作的时机和音符与判定区域重合的时间进行对比根据时间差给予一定的结果这一过程称为“判定”部分音游也会允许玩家微调判定区域的位置。判定后根据不同的判定结果可能会获得不同的分数也可能会发生减少生命、断COMBO等惩罚措施。 例如《Project SEKAI》会提供PERFECT、GREAT、GOOD、BAD、MISS五个判定结果GREAT会减少得分、GOOD既会减少得分又会断COMBO、BAD和MISS不仅断COMBO还会扣除生命且不得分。 每种音符有着自己的具体判定时间范围甚至同一判定在延后和提前的情况下时间范围也不一定相同但大体上可以总结为下图比例仅作示意不代表真实比例 可以看出两侧的BAD判定时间范围决定了音符最大的判定时间范围比BAD提前不会进行任何判定、比BAD还延后就会得到MISS。 3.2 音符下落速度 作为一个下落式音游音符需要从屏幕上方逐渐“下落”到达屏幕下方大部分音游都提供了调速功能可以改变音符下落的速度。《Arcaea》等部分音游也会根据BPM的变化来改变音符实际下落速度。 《Project SEKAI》为玩家提供了[1.0, 12.0]区间内步长为0.1的下落速度调速。然而12速的下落速度并不是1速的12倍实际上音符从出现在轨道中开始直到到达判定线的时间以下简称“下落时间”与下落速度有以下关系 也可以通过描点法画一个直观的函数图像请注意这并不是一个连续函数且有定义域的限制 可以看出下落时间最长为4秒1速、最短为0.35秒12速速度约为11.4倍。下落速度决定了音符何时进入画面只有已经出现在画面中的音符才可以参与到判定过程中即使音符判定时间范围为无穷大也不可以盲点还未进入屏幕的音符。
3.3 音符状态机 为了控制音符从初始化到消失的生命周期游戏需要一些方法来管理各个音符给予每个音符高度定制化的状态便是一个非常好的选择这些状态之间可以根据预设的条件进行转移并可以画出直观的状态转移图。 例如如果要设计一个需要点击3次才能完成判定的音符可以画出如下状态转移图 在状态转移图上圆圈代表状态、有向弧代表状态之间的转移有转移条件并可以在转移时执行一些操作比如进行判定、从空白地方指向的状态代表初态上图中的“等待”、同心圆代表终态上图中的“结束”。到达终态后一般认为音符的生命周期已经结束不应该再进行进一步的操作。这样的数学模型也被称为“有限状态自动机”简称“状态机”。
3.4 音符判定过程 为了实现上文所述的状态机在游戏主循环中每次判定处理时需要根据音符原始状态、当前条件比如时间、触摸事件等确定音符的新状态并在状态转移过程中执行进入画面、判定等操作。 这样的操作需要遍历当前可用可以进入画面、未进入终态的音符。工程上除了从第1个音符开始遍历以外还可以使用链表加快遍历速度将音符按时间排序后放入链表进入终态后从链表中删除遍历时从链表头开始当音符超过“当前时间下落时间”后结束遍历。还有一个更为简单的实现方式也能达到不错的性能将音符按时间排序后从第一个非终态的音符依次向后遍历如果这个音符也到达了终态则更新这一位置以此类推直到超过显示范围。 《Project SEKAI》在每一帧执行的音符判定过程主要如下
预处理音符遍历可用音符根据当前时间为音符完成进入画面、离开画面等与触控无关的状态转移 检查音符判定时间遍历可用音符如果当前时间在音符判定范围内就将这个音符放入可分配给触摸事件的音符列表 为触摸事件分配音符遍历触摸事件根据触摸轨道位置、触摸阶段等信息主要检查触摸阶段是否符合当前状态的要求以及触摸轨道位置是否离音符过远不同音符类型宽松程度也不一样。通过检查后为触摸事件分配对应的音符如果有多个可用判定的音符则取到达时间最近的音符如果仍然有多个则取距离最近的音符。1个触摸事件最多只能对应1个音符1个音符可以有多个触摸事件与之对应 对触摸事件所对应的音符进行判定遍历触摸事件寻找离音符中心距离最近的触摸事件使用这一触摸事件和当前时间判定对应的音符判定也会导致音符状态的变更。然后给剩下的触摸事件重新执行分配音符然后继续这一过程 举几个例子方便读者理解下图的红点代表触摸位置假设在音符边缘触摸的位置均符合音符要求 这几种情景下的触控事件分别起到如下效果
情景1下方的音符更早出现触摸事件将用于下方音符的判定 情景2触摸位置离左侧的音符较近触摸事件将用于左侧音符的判定 情景3虽然一开始会将2个触摸事件均按最近距离分配给左侧音符但优先判定离音符中心最近的左侧触摸事件左侧音符判定完成后会将右侧触摸点重新分配给右侧音符完成判定
参考资料
多点触控协议 - Linux 内核文档英文https://www.kernel.org/doc/html/v4.16/input/multi-touch-protocol.html 触摸设备 - Android 开源项目https://source.android.com/devices/input/touch-devices MotionEvent - Android 开发者英文https://developer.android.com/reference/android/view/MotionEvent MonoBehaviour-Update() - Unity 脚本 APIhttps://docs.unity.cn/cn/2019.4/ScriptReference/MonoBehaviour.Update.html Input-touches - Unity 脚本 APIhttps://docs.unity.cn/cn/2019.4/ScriptReference/Input-touches.html UnityEngine.Touch - Unity 脚本 APIhttps://docs.unity.cn/cn/2019.4/ScriptReference/Touch.html TouchPhase - Unity 脚本 APIhttps://docs.unity.cn/cn/2019.4/ScriptReference/TouchPhase.html
结语 需要说明的是操作系统相关内容参考了Android的实现它在操作系统开发方面提供了较为全面的文档与源代码。期待更为专业的玩家编写更详实的解析。