将几何变成音乐 ——冰与火之舞
本文章参考《节奏医生》制作手记编写。
前言
节奏游戏(Rhythm Games)正如其名,是一种基于音乐节奏游玩的游戏类型。
此类型的游戏在如今变得非常常见,但我们不去讨论常见的原因,而是来讨论一下怎么去做。
“关于节奏游戏的一般架构的文档并不多”,这是很多人的共识。但也正因为如此,所以这篇文章才会出现。
此种游戏有许多关键机制。这些机制不局限于引擎或语言,甚至有些引擎已经内置了(例如Godot4引擎的音乐节奏系统)
内置是很幸运的,然而更多的是自己闭门造车的不幸。
第一章:分解
此章节用于分解一般节奏游戏架构中的各个部分,并且给出其在GameMaker中的解决方案。
节拍系统
系统本身
这一系统用于保持节拍。无论是使用物体(Object)还是脚本(Script)实现,该系统都有相类似的特性。
它应该有一个简单的函数/变量来给出歌曲的位置,供需要同步到节拍的所有内容使用。这意味着在同一时间内,背景音乐应该有且仅有一首正在播放。
让我们创建一个叫'obj_clock'的物体,在其创造事件(Create Event)写入以下初始化代码:
bpm = [{time:0,value:120}] //这里是一个数组,包含了BPM节点
time_ms = 0 //音乐播放了多长时间?单位是毫秒(MS)
beat = 0 //当前是第几拍
pausing = false //是否暂停音乐
offset = 0.00 //延迟数:无论是什么音频系统,播放音频都带有延迟,因此需要这个。
// 以下所有函数都可以不用管理
function time_to_beat_bpm(time,bpm){
var crotchet = bpm / 60.0
return time*crotchet/1000000.0
}
function beat_to_time_bpm(beat,bpm){
var crotchet = 60.0 / bpm
return beat*crotchet*1000000.0
}
function time_get_bpm_by_time(time){
for(var i=0;i<array_length(bpm);i++){
var bpmtime = bpm[i].time
if(bpmtime == time){
return i
}
}
}
function time_to_beat(time){
var bpmStu = time_get_previous_bpm(time)
var beattime = bpmStu.time
var vaule = bpmStu.value
var willTime = time - beattime
var beatcz = time_to_beat_bpm(willTime,vaule)
if(time == 0){
return 0
}
return time_to_beat(beattime) + beatcz
}
function beat_to_time(beat){
var time = 0
for(var i = 0;i<array_length(bpm);i++){
var bpmtime = bpm[i].time
time = bpmtime
if(time_to_beat(time)>beat){
break
}
}
var prebpm = time_get_previous_bpm(time)
return prebpm.time + beat_to_time_bpm(beat-time_to_beat(prebpm.time),prebpm.value)
}
function time_get_previous_bpm(time){
var maxBPM = 0
for(var i = 0;i<array_length(bpm);i++){
var bpmtime = bpm[i].time
if(bpmtime<time){
maxBPM = maxBPM < bpmtime ? bpmtime : maxBPM
}
}
var bpmListID = time_get_bpm_by_time(maxBPM)
var bpmStu = bpm[bpmListID]
return bpmStu
}
function reset_time(time){
time_ms = time * 1000000 + offset
}
在步事件(Step Event)中输入以下代码:
time_ms += delta_time
beat = time_to_beat(time_ms)*4
现在,在该物体被创建那一刻起,所有物体都可以开始同步音乐:只要你判定obj_clock的beat就可以。
系统之外
一个好的节奏游戏应该处处遵循节奏:音符随着节奏而落下,人物随着节奏而摇摆。
看看《节奏医生》所搭建的场景吧,哪怕是一个不起眼的,正在移动的表带都是跟随节奏的。
这便是能够大幅度提高玩家体验感的一个操作。我知道你是GameMaker用户,在日常的编码中早已习惯了计算帧数,因为有计时器(alarm)的存在。但是在实际的游玩中,依凭帧数的游戏总是无法和音乐进行同步:因此,请尽最大可能的使用beat来代替原本的帧数计算。
如果你没有使用过协程(coroutines),那么你大概经常碰到这样的场景:在某个步事件中写入了这样的代码。
// 我们要实现一个动画
time++
if(time == 20){
//TODO
}
if(time == 30){
//TODO
}
if(time == 50){
//TODO
}
if(time == 60){
//TODO
}
if(time == 140){
//TODO
}
if(time == 150){
//TODO
}
if(time == 160){
//TODO
}
if(time == 250){
//TODO
}
if(time == 780){
//TODO
}
if(time == 1000){
//TODO
}
我非常推荐读者去学习一下协程,至少能让你的代码看起来没有这么杂乱。
话题说回节奏游戏,你最好将time都改成obj_clock.beat一类的东西来保证音乐同步。
文章未完工