音符输入
在本章中,您将会学习到:
- 如何处理玩家输入
- 判定窗口的概念与设置
- 输入偏移的处理
基础输入
让我们先实现非常基础的输入交互: 如果玩家点击,音符消失。
在 touch
回调函数中,我们可以遍历 touches
来寻找刚开始的触摸。如果找到,销毁音符并返回。
为了防止音符在准备销毁的那一帧的 updatePatallel
回调函数中仍然被绘制,我们需要添加一个简单的销毁检查:
class Note: public Archetype {
// ...
SonolusApi touch() {
FUNCBEGIN
FOR (i, 0, touches.size, 1) {
IF (!touches[i].started) CONTINUE; FI
EntityDespawn.set(0, true);
Return(0);
} DONE
return VOID;
}
SonolusApi updateParallel() {
FUNCBEGIN
IF (EntityDespawn.get(0)) Return(0); FI
// ...
return VOID;
}
};
TIP
实际上,Sonolus 的性能优化做的非常好,基本上所有支持的设备在游玩任意引擎时都能达到至少 60
帧。
在这种情况下,即使在 updateParallel
回调函数中不做销毁检查,玩家看上去也没有太大区别。
判定窗口
虽然我们的引擎现在开始工作了,但它绝对不像正常的节奏游戏那样工作。
一个音符当且仅当当前时间在音符的判定窗口时才能被击打: 如果时间太早,击打不会触发判定; 如果时间太晚,这将会被判定为 Miss 并且音符会自行销毁。
对我们的引擎来说,让我们定义,当你在目标时间 50ms
以内击打时,你将获得 Perfect 判定; 100ms
以内会获得 Great 判定; 200ms
以内会获得 Good 判定; 如果偏差时间更大,将不会触发判定或是获得 Miss 判定。
class Windows {
public:
let minPerfect = -0.05;
let maxPerfect = 0.05;
let minGreat = -0.1;
let maxGreat = 0.1;
let minGood = -0.2;
let maxGood = 0.2;
}windows;
输入偏移
当玩家物理上触摸屏幕时,在此次输入在 Sonolus 里被注册并广播到所有的 touch
回调函数前有一些延迟。这很可能来自硬件的延迟并且无可避免。
输入偏移可以允许玩家告诉 Sonolus 将这部分时间考虑在内。
举个例子,玩家在 00:01.00
时触摸了屏幕,但需要花费一些时间并在 00:01.06
被 touch
回调函数检测到。如果玩家正确校准了他们的输入并给您 0.06
的输入偏移,引擎就可以将其从触摸时间上减去,并基于他们真实的触摸时间 00:01.00
正确判定玩家输入。
Sonolus 提供的触摸时间已经考虑了输入偏移。然而,涉及到其它方面的输入偏移,引擎仍需要手动考虑它们,并确保给与所有玩家一个公平的游玩体验。
过早输入
让我们计算一下玩家能够击打的最早时间:
class Note: public Archetype {
// ...
Variable<EntityMemoryId> minInputTime;
SonolusApi initialize() {
FUNCBEGIN
minInputTime = targetTime + windows.minGood + RuntimeEnvironment.get(3);
// ...
return VOID;
}
};
接下来我们需要让 touch
回调函数只会在最小输入时间后执行:
class Note: public Archetype {
// ...
SonolusApi touch() {
FUNCBEGIN
IF (times.now < minInputTime) Return(0); FI
// ...
return VOID;
}
// ...
};
过晚输入
与过早输入类似,让我们计算一下玩家能够击打的最晚时间:
class Note: public Archetype {
// ...
Variable<EntityMemoryId> maxInputTime;
SonolusApi initialize() {
FUNCBEGIN
// ...
maxInputTime = targetTime + windows.maxGood + RuntimeEnvironment.get(3);
// ...
return VOID;
}
};
如果时间已经超过最大输入时间,我们应该让音符自动销毁:
class Note: public Archetype {
// ...
SonolusApi updateParallel() {
FUNCBEGIN
IF (times.now > maxInputTime) EntityDespawn.set(0, true); FI
// ...
return VOID;
}
};