音ゲー技術共有所

Last-modified: 2024-10-08 (火) 15:43:50

音ゲーの発展に伴い、技術格差が拡大しました。
その格差を縮めるために、先進音ゲーは発展途上音ゲーを支援しなければなりません。
発展途上音ゲーが先進音ゲーの要素を自由に取り入れれるようにこのページは作られました。
プログラム初心者の方でも分かるように書いていただけるとありがたいです。
音ゲーの制作者でなくても、プログラムが解読出来るなら、このページを編集して頂いても構いません。
しかし、やはり出来るだけその音ゲーを作った人が加筆するのが、正確性という観点では良いかと思われます。

おもちつきの技術

多くのおもちつきの元となった、SPの技術を取り上げる。
SP以外の作品はおもちWikiを参照。

変数とその詳細

変数詳細
HISCP[曲番号,内容]過去バージョンからのデータの引き継ぎに利用される。
HISC[難易度,曲番号,内容]ハイスコアを保存する
難易度は0がかため、1がやわらかめ(使われてない)
内容は0→おもち%、1→○、2→△、3→×、4→使われていない
例えばHISC[0,0,1]なら最初の曲のかための○の数を表す
BTIPE[0]ボタン設定
BGM_TIT$[曲番号]曲のタイトル
BGM_HARD[難易度,曲番号]曲の難易度
BGM_VOL[曲番号]曲の音量
TROPHY[難易度,曲番号]トロフィーの有無
CMPS$[曲番号]提供者名
FMT[3000]音符の判定を行うフレーム数
音符が3000個を超えるとエラーになってしまう。
また、FMT[1023]までしか初期化されていないため実質1024個しか使えない。*1*2
FMC[3000]赤音符を流すか、青音符を流すかの判定。赤は0、青は1
FMK[3000]音符のスプライトの管理番号
FMR[3000]音符の判定結果。と書かれているが、使われていない。
FMF[3000]1→おもちが飛んでいる。0→飛んでいない。おもちを飛ばす処理は@FLY
FMX[3000]
FMY[3000]
FMV[3000]
FMXV[3000]
おもちを飛ばす処理で使用。

いろいろな制限、仕様

  • 曲数…64曲
    • HISCやBGM_TIT$などのサイズ
    • 91行目のFOR Q=0 TO 128-1(あまり関係ないが)
    • 146行目のFOR Q=0 TO 63
  • 音符数…1024個
    • FM~で始まる変数のサイズ
    • 2023行目のFOR W=0 TO 1023
  • 譜面の行数…129行
    • 2232行目のFOR W=0 TO 128
  • その他
    • 最後の曲(BGM 1)を選んでオプション画面などに行った後選曲画面に戻ると、曲名の表示が間違っている
      • 1535行目IF QQ+Q>=BGM_MAX+1 THEN QQ=QQ-(BGM_MAX+1)

Drop-3n(と旧作)の技術

曲一覧の変数

  • BGM: 曲の並び順での番号
  • ID: BGMPLAYの番号、または設定の場合1000~
  • EX: 難易度 (かんたん=0, むずい=1)
    • 変数名はDrop-e開発当初かんたん・むずいではなく通常譜面・裏譜面にするつもりだったことに由来
  • MODE: ノーマル=0, おもち=1
変数詳細
BGM_ID[BGM]ID
BGM_VOL[BGM]BGMの音量
BGM_TIT$[BGM]曲名
BGM_CMP$[BGM, EX] (3s)
BGM_CMP_$[BGM*2+EX] (3n)
移植元表記
BGM_LV[BGM, EX] (3s)
BGM_LV_[BGM*4+MODE*2+EX] (3n)
レベル (整数ならそのまま、小数なら切り捨てて表示+Advance譜面あり)
BGM_CD[BGM]解禁条件(ConDition)
BGM_HIDE[ID]未解禁で非表示にするなら1
BGM_LOCK[ID]未解禁で???表示にするなら1
BGM_TIME[ID, MODE*2+EX] (3n)演奏時間
BGM_BPM[ID, 2, MODE*2+EX] (3n)BPM (最小と最大がそれぞれ入っている)
BGM_NC[ID, MODE*2+EX] (3n)ノーツ数(Note Count)
  • 設定は@SETTINGのリストから読み込まれる。曲と同じ扱いで変数に入れることで、選曲画面のプログラムを使い回せる。
    • 移植元表記とレベルの場所は設定ごとに表示を変えたいので、設定の場合はBGM_CMP$とBGM_LVを表示する代わりに別プログラムになっている。
      • @CMP_(ID) で変数GT$に文字列を入れると移植元の位置に表示される
      • @MV2_(ID) にレベル部分に表示する処理を書く。(グラフィックのX=280, Y=496に右寄せで描画すれば良い)
      • @LF_(ID), @RT_(ID) がその設定で左右キーを押したときに呼ばれる処理
  • 移植元表記で (SP) などと書くと@COLORSに書かれている色で表示される。
  • BGM_HIDEとBGM_LOCKはBGM_CDの値をもとに設定される
  • BGM_TIME, BGM_BPM, BGM_NCは DAT:D3CC_TIME などのファイルから読み込まれる
    • 譜面データをすべて読んで演奏時間とBPMとノーツ数を調べDATファイルに保存する処理がある(変数CREATECACHEを1にしてはじめると実行される)

セーブ

  • セーブデータは変数SAVに保存されており、@SAVで保存される
    • SAVのうち現在選曲中の曲のデータだけが変数SAV1にコピーされたりする
  • 1行1曲で、 DATA ID, (ノーマルかんたんの)★, スコア, よい, ふつう, だめ, ミス, (ノーマルむずいの)★, スコア, よい, ふつう, だめ, ミス, ...
  • NSPのセーブデータとDrop-3nの譜面の対応は@NSPIDに書かれている
変数内容
SAV1[MODE*2+EX, 0]獲得した★ (なし=0、1個=1、2個=3、3個=7)
SAV1[MODE*2+EX, 1]スコア
SAV1[MODE*2+EX, 2]よい
SAV1[MODE*2+EX, 3]ふつう
SAV1[MODE*2+EX, 4]だめ
SAV1[MODE*2+EX, 5]ミス
  • 設定はそれぞれ別の変数に入っている
    • ファイルには1変数1行で DATA 0, 値 と書き込み、最後をDATA -1とする
    • これにより行数がわかるので、例えばアップデートで設定の項目を追加して設定の行数が変わったときに READして0ならその設定を読み込み-1なら読まない、ということができる

GPRT_L, R, C

  • グラフィックに文字を描画するサブルーチン
  • mkIIではGPUTCHRで文字列をかく機能がないためつくった
    • mkIIにはDEFもないので、変数に引数を入れてGOSUBするという仕様になっている(使いにくい)
    • (Dropシリーズには他にもこのようにさまざまな場所で使い回されるサブルーチンがいくつかある)
  • GT$に描画したい文字列、GX, GYに座標、GPに色、GSにサイズ(8ドット=1、16ドット=2)を入れてGOSUBすると描画される
  • GPRT_Lは文字列の左端を、GPRT_Rは右端を、GPRT_Cは中央を指定した座標にあわせる
  • GT$に { } で囲った文字列を入れGS=2にするとその範囲内だけを8ドットにする (GPRT_Cにはなぜか実装していない)
  • Drop-3nのフォントは英数が高さ8ドットすべてを使って描かれている一方、日本語などデフォルトのプチコンフォントは一番下のドットを使っていないので、英数のみ1ドット上にずらして描画する処理が(KNJLIBのほうに)入っている

タイトルBGM

  • タイトルで一定時間経つとロゴの色が変わる演出がある。(どうでもいい)
  • そのタイミングはタイトルMML内の変数$0で設定されている
    • 最初は$0=0とし、$0=1になったときにフェードアウト、$0=2で色が変わり画面が戻る

選曲画面

  • 各曲の表示はそれぞれスプライト
  • 上画面に透明な0, 1番のスプライトがあり、それぞれの曲の表示は上半分→0, 下半分→1にSPLINKしている
  • 0番と1番を動かすと全体が動く
  • (ちなみにmkIIではスプライトではなく選曲中のものはグラフィックで、それ以外はBGのレイヤー0と1に描画していた)
  • (選曲画面はmkII時代からコードをそのまま持ってきているためGOTOやGOSUBが多用されており読みにくい)

チュートリアル

  • Drop-3sで使われていたチュートリアルのプログラムがDrop-3nではコメントアウトされている。
    • IF BGM==0 などを探してコメントアウトを解除したら3nでも動くかもしれない(未確認)
  • 表示するメッセージとTALKの内容は@GAME_TTRの中に書かれている
  • 勝手にオートプレイにしたり解除するのは譜面側の#AUTOコマンドでやる
    • Drop-4gでは全て譜面側のコマンドを使っている

譜面読み込み(mkII版と3s)

  • mkIIにはMILLISECはありません
  • MAINCNT*3はありますがmkIIも3号と同様、60FPSちょうどではありません
  • なのでSPのようにタイミング合わせMMLを使うしかありませんでした
  • 開始前に譜面を1文字ずつ読み込み、#Lコマンド(Drop-cでは#BPMコマンドも)がきたら $0=0R16 などを(変数の値と休符の長さは変えながら)それまでの音符の個数分並べたものを変数TIMML$に追加します
    • ただしmkIIには文字列変数の中身は256文字までという制限があるので、マクロやループも使って短くしています
      • 余談: ほとんどのおもちつきがMMLをREADしてBGMSETしているのに対しDropシリーズではBGMSETDしている(したがってDATA 0が要る)のはこの制限のせいです
  • 変数TIMML$を再生し、あとはSPと同じです

譜面読み込み(3n)

  • NSPver4の問題点を改良した速度変化対応読み込みシステムです
  • しかし旧3DSで重いというデメリットが発生してしまいました
長いので折りたたむ

MML解析

  • 時間かかるので廃止しました。譜面にBPM変化も書いてください

譜面解析

  • 音符1個ごとにタイミングや速度などを計算し以下の配列に追加します
  • 時刻は最初の音符or休符が判定枠に到達する時刻の1小節前を0とする
    配列概要
    FK音符の種類(ASCの文字コード)
    FT1音符を流し始める時刻(ms)
    FT2判定する時刻(ms)
    FTH$後述
    FKC音符の色(0~8)
    FKBN
    FKBC
    FKBV
    FKBP
    音符のBEEP音(BEEP命令のそれぞれの引数に対応)
    FKX音符を流し始めるx位置(0~100)
    FKMKおもちモード時の種類(#MOCHIKIND)
    FKA音符の飛んでくる角度(#ANGLE)
    FKTY音符のy座標(#TARGETY)
    FKB$追加で音を鳴らす音符の種類(#ADDBEEP)
  • 速度変化がある場合、NSPver4と同様に位置と時刻の対応を計算します
    • #SPEEDコマンドの値は配列SPH_NT$に変化させる音符の記号、SPH_MSに速度変化するタイミング(ms)、SPH_VALに変化後の速度の値が入ります
      • #SPEEDに変化時間が設定されている場合はその間を50msごとに区切って少しずつ速度変化をさせています
    • これを逆順に読み込み、音符の速度が変化するごとに位置と時刻を計算しますが、別々の音符として扱うことはせず、FTH$に "時刻1,位置1,時刻2,位置2,..." という文字列を入れます
  • FT1の音符を流し始める時刻は音符の速度に関わらず必ず FT2 - 1小節の長さ*ONPLIMITの値 です
    • そのためFT1の小さい順にソートすることなくこの配列に入っている順で音符を読み込みます
  • また、休符(譜面内のスペース)も音符と同様に1文字ずつそれぞれの配列に追加しています(FKがスペースの文字コードになる)

譜面開始後

  • 変数STARTMSに譜面開始時の時間(ms)があるのでMILLISEC-STARTMSがFT1の値を超えたら(かつFKがスペースでなければ)音符を表示します
    • 具体的にはFINDEX1という変数(最初は0)があり、FT1[FINDEX1]<MILLISEC-STARTMSとなったら音符を1つ表示してFINDEX1を+1します
  • 以下のようにその音符のSPVARまたは配列に音符の情報を追加します
    • SPVARで足りない分を配列SPVAREX,SPVAREXS$に入れています
      配列*4概要
      SPVAR(SPI, 0)音符サイズ(通常=1, 大音符=2, 判定終了=0)
      SPVAR(SPI, 1)音符を流し始める時刻(時刻0からのフレーム数)
      SPVAR(SPI, 2)音符を判定する時刻(時刻0からのフレーム数)
      SPVAR(SPI, 3)
      SPVAR(SPI, 4)
      SPVAR(SPI, 5)
      SPVAR(SPI, 6)
      音符のBEEP音(BEEP命令のそれぞれの引数に対応)
      SPVAR(SPI, 7)音符を流し始めるx位置(0~100)
      SPVAREX[SPI, 0]音符の飛んでくる角度
      SPVAREX[SPI, 1]音符のy座標
      SPVAREX[SPI, 2]おもちモード時の種類
      SPVAREXS$[SPI, 0]追加で音を鳴らす音符の種類
      SPVAREXS$[SPI, 1]FTH$の文字列
  • 音符の表示時にはFTH$文字列を解析しそれとMILLISECの値から現在の位置を計算します
    • 旧3DSではFTH$の文字列が長いと(=譜面の速度変化が多いと)ここの処理に時間がかかるようです
    • 2次元の可変長配列がほしい
  • 音符の判定時にはSPVARに保存されたフレーム数単位の判定時刻とMAINCNTを比較して判定します
    • MILLISECを使った判定では例えば±33ms以内と設定した場合ちょうど±0msの時刻にフレームの区切りが来ないため音符によって判定の厳しさが変わってしまうのがNSPの判定が緩い厳しい論争が起こった原因と予想しこのようにしてみました
      • 自分はそんな細かい判定の違いには気づかないのでどっちでもいい
    • (あとMAINCNTで判定しているDrop-e,m,c,3sから判定の条件を変更することで余計なバグを起こしたくなかったというのもあります)
  • しかし3DSのMAINCNTは正確には1/60秒から少しずれているという問題があります
    • そのためDrop-3nではフレーム数の計算には1/60秒ではなく自動でその3DS本体のFPS値を計測し使用しています (プログラムの最初の方のDEF CHKFPS)
    • 具体的には、Drop-3nを起動し始めてから常時、前回のVSYNC時から次のVSYNC時までの間のMAINCNTの変化が1、かつMILLISECの変化が15~18の場合(つまりWAITなどをせず処理落ちもしていない場合)のMILLISECの変化量を集計し、平均をとっています
    • 自分のNew3DSでは計算結果は約59.83FPSとなりました(デバッグモードをオンにしたときの上画面左下の表示で確認できます)
      • そのため自分のNew3DSでは判定幅は正確には よい=±33.428ms、ふつう=±66.856ms、だめ=-250.710ms~+83.570ms となります
  • ガチャガチャするとだめ判定を量産するのは仕様です (Fast側のだめ判定の長さがかなり長い)
  • 音符以外を変化させる各種譜面コマンドを譜面の判定時刻と同じタイミングで処理するため、別のタイミングでまた譜面データを読み込んでいます
    • MILLISEC-STARTMSがFT2の値(判定タイミング)を超えたら元の譜面データを1文字読み込み、コマンドがあればそれを処理します
    • 具体的にはFINDEX2という変数があり、FT2[FINDEX2]と比較して1文字読むごとにFINDEX2を+1します
    • 音符も休符も同じように1文字を配列の1要素としているので、配列を1つ読み進めるのと同時に譜面を1文字進めればタイミングは一致するからです

大音符の判定

  • 大音符はSPVAR(SPI, 0)が2、通常音符はSPVAR(SPI, 0)が1です
  • ボタンを1つ押すごとに、よいorふつう判定であればSPVAR(SPI, 0)を1減らします
  • これにより通常音符は1つ以上ボタンを押せば叩くことができ、大音符は2つ以上ボタンを押すことで叩くことができます
    • 大音符に対しボタンを1つ押した場合は、通常音符の得点が入った後、SPVAR(SPI, 0)=1となるので通常音符が譜面に残ります

汎用技術

ここでは汎用的な技術について紹介する。
これらの技術はΣに使われているかもしれないが、普遍的、一般的な技術であり、とくに機密とするよりは公開して皆で共有するのが、おもちつきや音ゲーに限らないプログラミング技術向上に役に立つと考えるし、またそのように考えられているようである(オープンソースなど)。

処理速度問題

処理落ちが発生する場合はネックとなっている個所、とくに実行時間の大部分を占める処理がある一種類の繰り返し処理ということもあり、その繰り返される部分を改善すれば少ない労力で処理落ちを抑えることができます。
もし全体的に実行時間が散らばっている場合は、よく繰り返される少し重い処理から順に手を付けるとよいでしょう。
(参考)パレートの法則 - Wikipedia

主な処理落ちの原因

  • GPAINT…塗りつぶす面積が広いほど処理に時間がかかる。画面外に線を引く等して、画面外まで塗りつぶされないようにすることで対処できる。

データ圧縮

収録曲がかなり多い作品が増えてきているため、圧縮について提案します。

音ゲー作品で最もデータが肥大化する原因は譜面データ本体にあります。その譜面データを思い切って圧縮してみるのはどうでしょうか。

筆者が思い当たる圧縮方法は次のようなものがあります:

  • DATA文を一行にする(文字列合成なども行うとよい)
  • 記号文字を英数字のみにする。不足する場合はヨーロッパ系の文字から使い、日本語文字を後回しにする
  • 既存の圧縮ツールを使用して圧縮する
    • 手前味噌ですが実装例です↓
      実装例

      公開キー【BKD3CKQE】
      DATA文文字列を圧縮展開することを目的に作りました(どの版か分からないのがΣに入ってます)。もっと効率のいいものを作りたいです
      とても長い文字列が連続する区間だけピンポイントで圧縮すると効果が大きいです。圧縮後の方がデータサイズが大きくなることもあるので、圧縮の仕組みや効果をある程度予想しながら使うのが良いです。

      CMPDATASTR$(N)
      Nで指定されたデータ数(0は無制限を意味する)または空文字列を発見するまで次々読み込んで連結し、収録の関数で展開できるDATA文を生成します。
      DECMPDATASTR$()
      CMPDATASTR$関数で作られたDATA文を読み込んで展開し、展開したデータを返します。

現在のところあまり圧縮に関して知見が集まっていないようですが、ここで先陣を切って圧縮に取り組むと、今後の圧縮関連のデファクトスタンダードとなりうるかもしれません。

32分音符とかを使うには?

  1. テンポを倍にする
    • MMLには元のテンポをT○○と書いておき、その前に書くテンポはその倍にする。
    • オプションのテンポ変更が使えなくなる。
    • MML自体を倍取りするという方法もある(Tamburo Drum*5など)
  2. タイミング合わせ用MMLを変える
    • 「BGMSET 150,」を検索すれば見つかるはずです。
    • デフォルトでは"[$0=1 R16 $1=1 R16]"となってますが、ここの16を32とかに変えると32分音符とかになります。
    • ただのMMLなので、[$0=1R16$1=1R16]16[$0=1R32$1=1R32] みたいに途中から変えたりもできます。
    • これを譜面によって変える方法は各自で実装してください。
    • このままでは曲の開始がずれてしまいます。IF Q==4*4+1 THEN BGMPLAY ...っていう箇所があると思いますが、このQの値を(1小節の音符数)+1にすると正しいタイミングで曲が流れます。
    • UNTIL Q==の後ろは無理に1小節の音符数に合わせる必要はありません。Q==1とかでも動きます。
      • Qの値を大きくすると、譜面と曲の開始が遅くなります。逆に小さくすると早く始まります。1が最速。
      • ただし、1文字あたりの音符の長さによっては永遠に始まらないのでQ>0が良いと思われます。
    • ちなみに音遊戯等ではこのずれを防ぐため最初の1小節は必ず16分になってるみたいです。
    • この方法だと細かい音符がちらついて見えてしまいますが、2箇所にあるSPANIM 100+I,"Z",1,0,-((120/TMP*120)*2),-50,1の「-50」を「-250」などに変えることで改善できます。
  3. 全く別の方法にする
    • MILLISECを使う方法
      • NewSPやTG、KRなど。
    • VSYNC回数を数える or MAINCNTを使う方法
      • 太シとTJAS+
      • 3号/BIGの場合、3DSとWiiUでは1フレームの長さがわずかに違うため、タイミングがあわないようです。

プチコン4のソフトウェアキーボードについて

  • PUSHKEY命令で引数に65536以上の数を与えると、ソフトウェアキーボードなどの制御ができる。*6
    • 65576 - ソフトキーボードの表示トグル
    • 65619 - 上書きモード、挿入モードの切り替え
    • 65621 - ソフトキーボードを表示
    • 65622 - ソフトキーボードを非表示
      • ゲーム開始時に実行しておくと、便利になる。
    • 65624 - パフォーマンスメーターの表示トグル
    • 65627 - ソフトキーボードが右に移動
    • 65628 - ソフトキーボードが下に移動
    • 65629 - ソフトキーボードが左に移動
    • 65630 - ソフトキーボードが上に移動
  • UISTATE()という隠し命令があり、0~5の引数を渡すと数値が返ってくる。*7
    • ソフトウェアキーボードの表示状態と位置によって、返値が変わる。
      b00表示状態(0=非表示,1=表示)
      b01常に0?
      b02常に0?
      b03常に0?
      b04位置
      (00=右,01=下,10=左,11=上)
      b05
      10進数での例
      位置表示状態
      0非表示
      1表示
      17
      33
      49
    • 従って、以下のように記述すると、0=非表示,1=表示と、ソフトウェアキーボードの表示状態を取得できる。*8
      UISTATE(0) AND 1

コメント

  • (メトロノームとか鳴らさない場合は們台ありません) -- 2019-12-26 (木) 15:12:49
  • 問題(どういう誤字だよ) -- 2019-12-26 (木) 15:13:06
  • ↑3 鳴らす場合はUNTILの後を変える必要が出てくるわけなんですね -- アオタク 2019-12-26 (木) 16:25:02
  • おもちSZのところの内容を7割削除しました -- すず 2020-02-22 (土) 17:55:13
  • 統合版が正式リリースされてるけどまだ機密事項なのかな -- すず 2020-02-22 (土) 21:10:58
  • NSPとかSigmaとかでもTコマンドで自動でスピードも変わる仕様にすればいいのに(それともBでそれを取り入れるとか) -- 2020-03-01 (日) 14:39:39
  • というかTJASとNSPのTJASモードで#BPMの仕様が違うんですね。 -- 2020-03-01 (日) 14:58:55
  • 310はこれが無ければ出来なかった。 -- Middle 2020-07-31 (金) 14:45:00
  • そろそろページ名を音ゲー技術共有所に変えません? -- 2024-02-18 (日) 05:55:18
  • ページ名を音ゲー技術共有所に変えました。また、SP以外のおもちつきは別Wikiに分けました(勝手に)。*9 -- 2024-02-18 (日) 15:03:50

*1 対処法1:2023行目のFOR文を書き換える。というかFILL命令のほうが速い。
*2 対処法2:2612行目のFOR文の終わりをONPC-1にする。この場合初期化は不要になる
*3 mkIIではMAINCNTL
*4 SPI=スプライトの管理番号
*5 実際には倍取りではない。
*6 反映には1フレームかかる模様
*7 それ以外の数値を渡すとOut of range
*8 -ボタンを押したかの判別などに使える
*9 もしこちらに統合すべきなら、音ゲー技術共有所/おもちつき?に作成して、includeした方がいいかと思います。