猫史档案馆


关于音游的总体概述与实现

用户:ParseYPasyParseYPasy查看:2 回复:3 评论:2 创建时间:2022-01-24T18:06:31


请忽略掉我之前在求助区发的帖子,我还不熟悉这里的论坛(

#Part 1 音游的总体概述

我们所称的“音游”,全称“音乐游戏”,或“节奏类游戏”,要求玩家跟随音乐节奏进行对应操作。

狭义上,音游的基本元素:

1.玩家能够击打的Note(音符)

2.随音乐节奏出现的Note

3.操作的判定范围(分为范围型与线型,如一条判定线或一个判定区)

广义上,音游指任何跟随节奏的打击类游戏,如Dance Line,Piano Tile(不同的是它是打击出音,而非音出打击)等

音游最大的特点与要求就是:随节奏打击

任何不随节奏打击的“音游”,或没有节奏也要打击的游戏,都不叫“音游”,充其量算是“打击类游戏”,对于一个音游,如果其谱面游玩时完全不按节奏,我们一般称为“FEN谱”

 

一些基本名词:

Note - 音符,是玩家与游戏的交互对象,需要进行击打/按住/划动等操作以得分

Track/Key - 轨道,是Note下落时的路径,可以把Note比喻成列车,只能在一个Track(轨道)上直线移动

判定线(区) - 是玩家进行操作时应当所在的范围(Note必须在线的位置上或区域内击打,有些游戏例外)

Combo(连击)—— 一般指玩家连续打击的音符数,如果漏击了Note就会归零

判定 - 是游戏基于玩家的击打表现给出的成绩判定

判定范围 - 是取得各种判定的时间误差范围

一般有如下判定:

Perfect(完美)——一般来说是最好的判定,时间误差通常在40ms内(对,是毫秒,这是基础时间单位)

Good(好)/Great ———— 次于Perfect,时间误差通常在40-80ms内(也可能超过这个范围,请以游戏规定为准)

Bad(坏)/ Miss(漏击)———— 这种判定会令Combo断掉,并且不增加分数

谱面 ———— 是由设置好的Note与音乐组成的一个“关卡”,一首音乐的游玩“乐谱”

*谱 ———— 指不随节奏而布置Note的谱面,或没有节奏硬塞入Note

 

 

音游一般类型有:

1.下落式

最经典的是多key(多条轨道)下落式,Note从固定的区域落到判定线上,统称为“有轨下落式”

DanceRail3(其30key的数量让其很像下面的无轨下落式),Osu!ManiaDeemo(古树旋律),KalpaProject Sekai节奏医生

一些另类的下落式是没有key这一概念的,Note可以从任意区域落下,甚至不局限于直线,但最终仍会落到判定线上,称为“无轨下落式”

最经典的是PhigrosMalody,Lanota(有轨无轨均有)

又如太鼓达人Muse Dash这类横向轨道,某种意义上也算是下落式(因为是朝着判定线落去)

以及为了适应街机而六面开花的舞立方

2.点击式

这类不多,典型的是Osu!中的戳泡泡,全屏幕打击 ,以及Jubeat (16键打击),A Dance Of Dire And Ice(冰与火之舞,线性打击)

3.其他

Cytus/Cytoid 这类Note不动判定线在动的特殊“下落”式

Arcaea 这类有“天空音符”与“音弧”的4key-无轨混合式

 

音游的Note类型繁多,但大体分为以下几类:

1.Tap

最常见的Note,点击即得分

2.Hold

线性Note,在不同的游戏中,会有“头判”(首次点击时的判定),“尾判”(松开点击时的判定)

3.Drag(有些游戏没有)

Hold与Tap的合并,按住即为最高判定

4.Flick(触控游戏独有)

向特定方向划动即为最高判定

 

 

 

#Part 2 在Kitten 4上的逻辑方法推导

(逻辑部分均以C++编写,我们假定任何片段均为有效程序的一部分)

1.Tap的实现

我们对于Tap,最大的问题在于:
如果单纯

class note{
    ...
    public:
        void check(time_t now_time){
            if(pass_line && press_key)//如果经过了判定线,并且对应键按下
                bust(now_time,press_time);//则根据实际判定时间进行对应判定并销毁这个类
        }
};

有一个问题就是:假定有许多Note都经过了判定线,此时按下一个键就会有很多Note一起被判定!
这对于速度极快的纵连是灾难性的(很容易就可以糊过去了!)

那么怎么解决呢?
一个方法是:每次只判定第一个

那怎么判定第一个note呢?

不难想到,我们可以采取列表的思想。

我们为Note加上id作为唯一标识符,并在其出现时,将其放入对应的列表里。

然后每次点击时,检查列表第一个元素,如果在判定范围内,则判定,并将这个Note与列表内的这个id一并销毁,让下一个id成为列表的首元素。

伪代码如下:

std::queue<Note> note_list;//Note队列(在Kitten 4中实现为列表)
while(true){
    if(press_key && !note_list.empty())/*当对应键按下且队列中有Note*/ {
        if(inbust(note_list.begin()->id)){
            bust(note_list.begin()->id);
            note_list.pop_front();
        }//inbust假定是通过给定的id,查找其判定时间,并决定是否判定的全局方法
    }
}

这样,只需要实现inbust(在Kitten4中可以很轻松的从C++转换为一个打包好的函数)以及bust即可,其他均在Kitten4中有模块实现。

我们再设置两个列表:note_track与note_hittime

double note_hittime[] = {};//note的打击时间
int note_track[] = {};//note的对应轨道号码

由此,在制谱的时候,只需手动加入或者自己跟着音乐打,把自己打的假想谱面记录下来,就可以在游玩谱面的时候获取信息了。

至此,我们完成了Tap按键的制作。

2.Drag的制作

直接上代码了,很简单

...
if(press_key){
    bust(id,perfect);//直接为Perfect判定
}

3.Hold的制作

我觉得,对于一个自制音游,评判其制作水平的最好办法,就是看它有没有实现Hold。迄今为止我还没有在编程猫看到任何一个实现Hold类型Note的教程或作品。

这是最难的Note类型,因为这是一个复合Note,包括头部判定(与尾部判定),中间还要加入判断是否按住的Note,同时要保持贴图的连贯性……

简单来说,它的判定分为两部分:

1.头部判定

2.中间判定

一:头部判定
偷懒的方法是直接将Tap的判定搬过来,换个贴图就行

需要注意的是头部判定没有松开的说法,因此这时候的按键需要分开处理,不能像之前处理叠键一样,等待下一次键击

二:中间判定

偷懒的方法是直接将Drag的判定搬过来,同样换个贴图(但不能全算作Combo)

最重要的是保持连贯性,由于音游的速度快,需要有很高精度的时序控制,一般我们开的加速模式并不能很好保证时钟的稳定性与精度(计时器),如果使用变量进行高精度计时,会出现无法修复的误差,如果使用计时器,则精度难以保证毫秒级判定的准确性

int milisecond = 0;
for(int i = 0;i<1000;i++){
    milisecond++;
    usleep(1000);//unistd.h中的函数,这里我们可以理解为“等待1毫秒”
}
//方法1:精度高,稳定性差(每次都会因为感人的调用速度而慢下不到1ms)

double now_time = clock();//我们假定它就是计时器

//方法2:稳定性强(与游戏的主进程是分开的),但只能提供0.1s精度(10ms)的计时,对区间造成了很大的限制

总的来说,如果官方出了稳定且高精度的计时器,那么音游就很容易制作框架,配以优秀的动效与美术设计,就可以有远超所谓“音游”与那些用画笔做出来的所谓“弹幕游戏”以及“卡点游戏”强的多的作品。

现在,全套理论在这里,静待各位的实现!


回复

上一页1 页 / 共 1下一页
小鱼yuzifu小鱼yuzifu

好!

点赞0


评论


ParseYPasyParseYPasy

第一份实现的demo:https://kitten4.codemao.cn/#1341喵385

点赞0


评论


爵士OIer爵士OIer

钢琴块呢(

点赞0


评论