iOS向け音楽ゲーム "WAVEAT" を1人で開発した話
Twitterで"WAVEAT"のことについて何を書こうかアンケートを取ったところ、プログラミングについての話を聞きたい人が多数だったので、書いてみまっす。
(といいつつ言うほどプログラミングではないかもしれない…)
この記事で話すこと:
- 画面のデザイン工程
- 開発上で気がついたこと
- 個人でゲームを開発するということ
自分が個人で感じたことを書くだけですので、一例として受け取っていただければ。
WAVEAT
個人的に活動しているiOS向け音楽ゲームプロジェクトです。50曲近くありますので、ぜひダウンロードしてみてくださいね。1作目"WAVEAT"は無料、2作目"WAVEAT ReLIGHT"は有料(買い切り)になります。
プレイ画面はこんな感じ。
何から始める?
「さあ音ゲーを作ろう」と思っても実現までは難しい話です。
ツールアプリ開発の経験はあってもゲーム開発は始めてだったため、何から始めてよいのやら。
ひとまずデザインに着手してみて、その後に手探りで開発を行いました。
デザイン
とりあえず描いてみる
自分の場合、ペンを持って絵を描くことができないタイプの人間なので、いきなりMacのベクターデザインソフト Sketch で画面イメージを作っていきました。
本当はツール系のアプリのUIを考える際に使うもので、ゲーム向けではないかもしれないのですが、正直Adobe系のものよりサクサク動いてくれるのでつい使ってしまいます。
1作目はこんな感じでした。が、このデザインは失敗でした。
TAPとSLIDE(FLICK)のノーツのデザインの差が色と太さだけだったりしたため、1画面に表示するノーツや増えた場合や、オプションで色変更が可能になった場合は誤認させることがありました。
2作目はその反省を活かし、TAPとSLIDE(FLICK)の形を明確に差を付けたものとなりました。
このように、ノーツのデザインを改善するだけでプレイの難易度が下がったように感じたりすることもあります。
動きを考える
さて、これらのノーツがどう動くのか。特に音楽ゲームに関してはノーツの登場の仕方が重要なポイントなので抑えて置いたほうがよいと思っています。
beatmaniaや太鼓の達人のように2Dでスクロールするだけの動きなら画面外から登場するだけなので気にすることはないかもしれませんが、maimaiなど円形の形や、SOUND VOLTEXのように奥から迫ってくる系の場合は少し考えたほうがよいです。
"WAVEAT"の1作目は描画の都合上フェードインするような登場だったのですが、これがプレイヤーにとって負担になるアニメーションになっていたように思えます。
(Dance Dance Revolutionで言う、SUDDENオプションがかかっているような感覚になります)
上級者プレイヤーにとって始点は譜面を予見する上で重要な要素なので、表示領域にも余裕を持たせ、どのノーツからやってくるのかを明確にすることが、プレイ上ストレスを無くす鍵になると思います。
"WAVEAT ReLIGHT" (2作目) は早めに完全描画、奥は線を細くする…といった変更を行っていたりします。
本来は実装に取り掛かる前にAfterEffectsなどで擬似的にノーツの量を増やしつつ動きの確認をしておくと、上記のような失敗もなかったのかなと思いました。
もちろん描画プログラムが得意な人はいきなりコードを書いて動きを見るのもありですが、プログラムに自信がない人は出戻りを発生させないためにも、イメージを先に膨らませるのが大切です。
入力範囲を考える
さらに、このデザイン時点でもう1つ意識すべきだったのは、入力範囲についてです。
ON/OFFがはっきりしている物理ボタンの場合は入力処理は単純ですが、タッチパネルの場合は指でどこでも自由に入力できるため意外と考えることが多かったりします。
TAPの場合、円の外も内も受け付けるのか?幅はどこまでなのか?指が複数入ったらどうする?
SLIDE(FLICK)の場合、具体的にどこでどう指をなぞったら判定を取るのか?始点終点の距離が一定を超えたら…いや、なぞった時のスピードが一定を超えたら…いや、円を横切ったらにする???
…このあたりをふわふわした状態で進めてしまい、開発時に頭を悩ませたり、公開後に仕様変更になったりしました。
開発
ところで何を使って開発するの?
「スマホ向けにゲームを作る」となると、Unityが今は一般的なのかな?と思うのですが、"WAVEAT"ではAppleが提供する2Dゲームフレームワークの"SpriteKit"を使って開発を行いました。
iOSしか動かなかったり、アセットが使えなかったり、情報収集が困難だったり…かなりデメリットが強いように思われるかと思いますが、自分はどうしてもモダンでイケイケなプログラミング言語であるSwiftで開発をしたかったのでSpriteKitを選びました。(本業でSwiftを使っていたというのも大きな理由です。)
今回の企画は商業ではない個人プロジェクトなので、iOS以外の端末の動作保証をするのは端から難しいだろうと考えていましたし、なにより自分がストレスなく開発ができるものを選定するのが一番かなと思います。
ノーツ描画はコードで完結すべきか、画像変形にすべきか
実際にノーツを描画してみる上で悩んだのが、完全にコードで完結すべきか、画像の変形にすべきかという点。結局"WAVEAT"の場合はコードで完結させました。
その理由は「予め線で描画できるノーツのデザインだった」というのもありますが、後々線の太さを自由にオプションで変更可能にできたり、色々と柔軟性を持たせるためでした。動作の軽さもメリットかと思います。その代わり、実装が無駄に大変だったり、ビジュアルを凝ることができないデメリットもありました。
音楽に合わせてノーツを動かせるようにする
描画アニメーションがいい感じに組めたら、いよいよ音楽に合わせて動くようにします。"WAVEAT"の場合は以下のような譜面データをjson形式で持ち、"notes"キー内の二次元配列を受け取るとノーツを描画できるようにしました。(実は某フリーソフトと同じです)
{
"sequence" : [
{
"level" : 12,
"difficulty" : 1,
"notes" : [
[ // 1小節目 4分音符間隔の場合
"00000100", // 0.0拍目
"00000000", // 1.0拍目
"00000000", // 2.0拍目
"00000000" // 3.0拍目
],
[ // 2小節目 4分音符間隔の場合
"00010000", // 4.0拍目
"00000000", // 5.0拍目
"00000000", // 6.0拍目
"00000000" // 7.0拍目
],
[ // 3小節目 8分音符間隔の場合
"00000100", // 8.0拍目
"00000000", // 8.5拍目
"00010000", // 9.0拍目
"00000010", // 9.5拍目
"00100000", // 10.0拍目
"00000000", // 10.5拍目
"00010000", // 11.0拍目
"01000000" // 11.5拍目
],
ノーツ毎に拍数の値を割り振り、それを元に判定ラインに対した位置を相対的に決定しています。
ノーツを流すスピードはBPMの数値を元に変化するようにしています。
こうすることでBEMANIシリーズのような楽曲中のBPM変化にも対応が可能です。
繰り返しますが、楽曲の現在時間から拍数を算出し、上記のようなデータから描画を決定しているだけなのです。
実は、円形であろうが、スクロールであろうが、リズム天国的な感じであろうが、どんな形の音楽ゲームでも描画の仕組みは同じなのです。たぶん。
タイミング判定を実装する
ノーツ毎に拍数を割り振るとともに、楽曲の秒数(orフレーム数)も記録しておくことで、入力されたタイミングの秒数(orフレーム数)と付け合わせて、その差によってタイミング判定が可能になります。
アクションゲームとは違い音楽ゲームは見た目(当たり)で判定は取りません。ノーツのスピードの変化によって判定幅が変わることになってしまいます。BPMによってスピードを変化させたり、オプションで自由にスピード変更を可能にさせるのであれば、秒数(orフレーム数)を元にした判定にするのが良いでしょう。
"WAVEAT"の場合はenumで各判定を定義してます。
秒数の差とノーツの種類を放り込むだけで判定の種類が返ってくる仕組みを作りました。
struct Wave {
enum InputType: Int {
case tap, slide
}
}
enum Judge: Int {
case just, nice, safe, miss
init(timingMin: Double, type: Wave.InputType) {
let t: Double = abs(timingMin)
if t <= Judge.just.getTimingMin(type: type) {
self = .just
} else if t <= Judge.nice.getTimingMin(type: type) {
self = .nice
} else if t <= Judge.safe.getTimingMin(type: type) {
self = .safe
} else {
self = .miss
}
}
func getTimingMin(type: Wave.InputType) -> Double {
switch self {
case .just: return type == .slide ? 0.09 : 0.06
case .nice: return type == .slide ? 0.12 : 0.09
case .safe: return type == .slide ? 0.15 : 0.12
case .miss: return .infinity
}
}
}
ごちゃごちゃ書いてますが、実際に呼び出すのはこれだけ。
// タップが0.08秒早く入力されたら…
Judge(timingMin: -0.08, type: .tap) // .nice
// スライドが0.13秒遅く入力されたら…
Judge(timingMin: 0.13, type: .slide) // .safe
うん。読みやすい。Swift最高。
enumなので実際にはスコアや色情報、コンボとして扱うかどうかなども含ませて、すぐに欲しい情報が取り出せるようにしてます。
自分は頭がよくないので複雑なコードは書きたくないし、あとで読みたくもないので、アウトプットが楽に読めるコードになるよう心掛けてます。
当たり判定の描画を実装する
音ゲーやってる人はよくわかると思うのですが、失敗時はスルーしてそのまま、成功時はエフェクトさせながら消えるのが一般的です。ノーツを自分の手で潰すイメージで。
開発が終わったら必ず誰かに触ってもらう
ふう…これで完成…じゃあもう公開してもいいよね… ← ダメです
テストプレイは誰かに必ずやってもらいましょう。
"WAVEAT"の場合は、コミックマーケットやM3会場などで毎回展示を行っています。この場は、アーケードゲームでいう「ロケテスト」ですし、スマホアプリで言うと「ユーザビリティテスト」でもあると思っています。
ユーザーの声を聞くのはもちろん、実際にプレイヤーがどこの操作に困っているのか、チュートリアルでどこが理解されていないか、注意深く観察するようにしています。(開発者はプレイの成績よりも、その前後の行動を注視するべきです)
展示をして気づいた一例として、タイミングのズレ問題があります。
「出音」「ノーツの描画タイミング」「ユーザーからの入力タイミング」があり、人や環境によってはこれらがズレる可能性あることを意識した方がよいです。
「出音」がズレる時はどんなシーンでしょう?
…ワイヤレススピーカーやイヤホンが使われてるときは音が遅れそうですよね。
「ノーツの描画タイミング」がズレるシーンは?
…外部モニタに映したり、生配信でキャプチャしてるときは描画が遅れそうですよね。
「ユーザーからの入力タイミング」がズレるシーンは?
…実は同じ環境を用意できても、案外自分がピッタリと思うタイミングと、他人がピッタリと思うタイミングが違っていたりします。
音楽ゲームで一番がっかりされる体験は「タイミングが合わない」ことです。
よくできた音楽ゲームはそれぞれのオフセット調整が可能になっていたりするので、実装してあげると喜ばれるかもしれません。
また、スマホアプリなど最近の音楽ゲームは環境依存によりタイミングズレることを最初から許容して、判定をあえて緩く作っている傾向にある気がしています。
ゲームアプリ開発を続けるために
結局個人でのゲームアプリ開発を続けるためには、定期的かつ長く続けられる状況に自分を持っていくことが大事だなと思います。
コミックマーケットやM3を「定期的なタイミング」とし、長く続けるためコードの読みやすさからSwift言語を選定するなど「やりやすい手法を取る」ことで無理がない開発にしています。
また開発環境も大事で、自分の場合は自宅だけでなくカフェを転々としながら開発したりしました。自宅だけだとだらだらしがちですし、気分転換しないと集中が持たなかったです。
さらに、途中からTwitterやDiscordにて関係者に進捗報告を行うことでモチベーションを保つようになりました。批判ではなく未来についてポジティブに楽しく話せる人(非開発者が理想かも)が近くにいるなら、その人に随時進展を見せるのがいいと思います。
まとめ
- 画面デザインはノーツの動きも想像し、プレイヤーへの負担軽減を考える
- タッチパネル型なら指がどう動いたら判定にするかも厳密に想像する
- 最後は必ず他人に触ってもらい観察する
- 自分が開発しやすい(苦にならない)手法を選ぶ
僕が"WAVEAT"という音楽ゲームを個人で作る上で現状気づいた主な点は以上です。(正直まだまだありますが今回はこのへんで)
各部分でまだまだ改善の余地があると思いますが、個人アプリだからこそまだまだ今後もじっくり作めると思っています。
もし個人でゲーム開発を考えている人がいたら、ぜひ参考にしていただければと思います。
この記事が気に入ったらサポートをしてみませんか?