【音ゲー】Unityでスマホ向けロングノーツを作ってみる【サマーブログリレー2021 1日目】
この記事はICONサマーブログリレー2021 1日目の記事です。
こんにちは。プログラミング班班長のCrepeです。
この記事は某音ゲーにドはまりした私が勝手に実用性度外視&趣味全開で製作したものを垂れ流す記事です。
「音ゲー作ってるんだけどロングノーツが作れん!」とか、「音ゲーの裏側を見てみたい!」って人は参考にしてみてください。(参考になるとは言ってない)
はじめに
Unityで音ゲーを作りたいって思う人は多分そこそこいると思うのですが、いざ調べてみると意外と情報がありません。特にスマホゲーム用のロングノーツまで踏み込んだものはこの記事を書いている間は見つかりませんでした。
というわけで、自分でひぃひぃ言いながら実装したんですが、いろいろあって製作を断念するので今回はそれを供養とメモ書きのために垂れ流そうと思います。きっと強い人が改良してくれるでしょう。してくれ。
ちなみに、この記事ではロングノーツの話しかしませんので、音ゲー自体の作り方、考え方には触れていません。そういった根本的な部分を知りたい人は以下の記事を参考にするといいと思います。私も大変お世話になりました。
音ゲーって作れるの? カテゴリーの記事一覧 - ICON公式ブログiconcreator.hatenablog.com
今回の記事では、音ゲーの基本用語を知っていればとりあえずついていけます。ですが、ちゃんと理解したい場合はUnityやC#の基礎知識が必要です。そんなのないよって人は分からんところは軽く読み飛ばしてください。結構踏み込んだ内容もあるので、経験者でも製作するゲームの内容や自分の力量に合わせてほどほどに参考にしてください。
本記事の目標(達成したとは言ってない)
今回の記事で主に扱うのは、レーンをまたぐタイプのロングノーツです。もちろん、レーンをまたがないタイプも対応できます。また、サムネのような曲線タイプのロングノーツにも触れていきます。
下の画像はSEGAの神ゲー『プロジェクトセカイ カラフルステージ feat.初音ミク』のスクリーンショットになります。何を隠そう、冒頭で言ってた某音ゲーとはこれのことです。通称プロセカと呼ばれるこのゲームは多彩な譜面遊びで有名なのですが、このゲームのロングノーツはレーンをまたぐわ、曲がるわ、太さは変わるわですごいです。これにできるだけ近づけたいというわけですね。
基本的(?)な実装方法
とりあえず、プロセカ再現云々はおいといて方針を考えていきます。まずは直線タイプについてですが、最も簡単なロングノーツは、CubeのScaleを「ノーツをタップするタイミング」と「ノーツを離すタイミング」をもとに設定してあげれば実現できます。立方体をうにょーんと伸ばしてロングノーツにしようという魂胆ですね。ロングノーツを実装するうえで最も簡単な方法でしょう。スマホ音ゲーでないなら十分選択肢に入ると思います。
しかし、この手法ではレーンをまたぐロングノーツは実現できません。立方体を斜めにうにょーんとすることは難しいのです。
次に、UnityのLine Rendererを使う方法を考えてみます。Line Rendererはその名の通り線を描画するcomponentです。これなら確かに斜めの直線を実現できますし、なんなら曲線も何とかなります。太さを変えることも(一応)できます。
どうでしょう?これはこれですごい既視感を感じますね。*1
しかし、実はこの手法では端の部分の形が良くありません。この部分を隠すためにロングノーツの端に円形のノーツを置くことになり、端のノーツの形が決まってしまいます。現に、長方形のノーツでははみ出てしまいます。
この程度なら何とかなる気もしますが、ノーツの太さを大きくしたい場合、この手法では端を隠すことが困難になります。
とはいえ、太さを変えようと思わなければ十分ロングノーツを実現できます。 これが実用的にも実装難易度的にも一番いいんじゃないかなと思います。この記事の意味とは。
ほんへ
概要
ということで、いよいよ本題に移ります。レーンをまたげて、太さも変えたい。できれば曲線にもしたい。そのためには平行四辺形が描画できればよいです。
下図のように平行四辺形のロングノーツであればきれいにレーンをまたぐことができます。太さを変えても、ロングノーツの端が判定線と平行になるので長方形のノーツで容易に端点を隠すことができます。
また、曲線のノーツは平行四辺形の集合体と考えることができます。それぞれの平行四辺形を曲線に沿うように配置すれば、きれいな曲線ノーツが描けます。
また、ロングノーツの両端が常に判定線と平行になるため、ロングノーツ同士の接続が可能になります。つまり、途中でロングノーツを曲げたり、太さを変えたり、文字を描くなど様々な表現が可能になります。(実装難易度も爆上がりしますが)
じゃあ、平行四辺形をどうやって描画するんだって話ですが、UnityのMesh FilterとMesh Rendererを利用します。MeshというのはUnityで3Dモデルを構成する最小単位みたいなもので、基本は三角形です。(多分)
平行四辺形は四角形の仲間なので、これを使って描画することができます。というか3Dモデルができるんだから理論上はどんな形だって表現できます。とはいえ、こんなのをいちいち生成していったらきりがないので、本来であればアセット等の素材を使ったり、ツールでモデルを作って、Unityにインポートします。しかし、今回作るのはロングノーツです。「四角形の1つや2つくらいなら直接生成してしまえ! 」というわけですね。
使い方の詳細は以下に示すコードをみて雰囲気をつかんでいただくか、ネット上にある公式リファレンスや記事を参考にしてください。
直線型の実装
というわけで、実際に実装してみます。筆者の環境はUnity 2020.3.9f1です。標準機能だけしか使ってないので多少違っても大丈夫です。多分。
ちなみに、この記事ではロングノーツを1つだけしか生成しません。実際は譜面データを読み込んで、それをもとにたくさんノーツを生成することになります。そこまで話すと多分薄い本ができてしまう*2ので、今回は最小限の部分だけの説明になります。予めご了承ください。
なお、これ以降の画像ではわかりやすさのために適当にレーンと判定線を作ってあります。
適当にレーンと判定線を作ったといいましたが、判定線のpositionは(0, 0, 0)
とするといいです。音ゲーをちゃんと作るってなったときに、実装が若干楽になります。ノーツの幅はレーンと同じと仮定して、今回は2としました。また、左から0,1,2,3番目のレーンと数えます。こうすると実装が若干楽になる気がしますが、もはや好みの問題でしょう。以下がコードになります。
まぁ、四角形を描くだけなんで説明は下記の記事に任せます。ほぼ同じことをしています。レーンの位置とノーツの幅から頂点の位置を計算するのがちょっと大変ですね。
このスクリプトを適当なGameObjectにattachしてそのまま実行すると、おそらく下の写真のようになると思います。
materialも何もないのであれですが、material貼って始点と終点にそれっぽいものを置いてみるとこうなります。先ほどあげた画像ですね。まぁまぁ様になってるんじゃないでしょうか。(ほんとか?)
曲線型の実装
続いて曲線型です。まずは、曲線の表現から始めます。 今回用いるのはベジェ曲線と呼ばれるものです。詳しい解説は以下の記事を参考にしてください。おそらく、記事の最初にあるアニメーションを見れば雰囲気がわかると思います。
今回は2次ベジェ曲線を使います。2次ベジェ曲線は、曲線の始点、終点、制御点の3点を決定すれば描けます。 始点、終点は言うまでもなくロングノーツの始点、終点そのものですので、制御点だけ何とかなれば実装できそうな気がしてきます。今回は制御点を適当に決めて進めていきますが、是非自分でもいろいろ試してみてください。
まぁ、まずは曲線を描いてみましょう。先ほどちらっと出てきたLine Rendererを使って目に見えるようにしましょう。
適当なオブジェクトにくっつけて、実行すると以下のように曲線が出てきます。上から見てみるといい感じになっているのがわかるかと思います。
ちょっとコードの解説をしますと、レーンやタップのタイミングから曲線の始点、終点、制御点の位置を計算して、それをもとに曲線を生成、描画とするいう流れになっています。始点と終点はレーンの真ん中に来るようにしています。
GetCurve
で曲線を生成します。コンピューターは連続的な値は扱えないので、splitNum
(+1)個に分割して離散的に曲線の各点を計算していきます。
そんでもって、GetCurve
で得られた各点の配列をLine Rendererに渡してあげれば、曲線を描画してくれます。Line Rendererの細かい説明は省略します。ググってください。
なお、Line Rendererのみでロングノーツを実装する場合はrenderer.widthMultiplier
の値を変えればOKです。ここが線の太さになります。
この後は、この曲線をもとに平行四辺形で肉付けをしていくことになります。
先ほど直線ロングノーツを生成した時に作成したGenerete
関数をそのまま流用します。以下のようにコードを追加してください。テスト用にLine Rendererで曲線の描画をそのまま行っていますが、不要であれば該当箇所は削除して構いません。
これまた実行してみると以下の画像のようになります。
このままだとよくわかんないので、materialつけて見た目を整えるとこんな感じになります。
青い曲線は確認用にLine Rendererが描画している曲線です。こいつのせいでSEGA発の別の音ゲーっぽくなりましたがちゃんと曲線に沿って肉づけされているのが分かりますね。
追加部分では、直線ロングノーツを生成したときと同じ処理を曲線に沿って何回も繰り返すようにしています。先ほど示した直線ロングノーツのスクリプトを見返すと結構似ていることがわかると思います。
細かいお話
最後に太さを変えたり、途中で曲げたりするときの話を少ししていきます。概要で述べたように、この方法で実装したロングノーツは連結ができますので、いろいろ遊ぶことができるんですが、引き換えに実装難易度が爆上がりします。というのも基本は曲線ロングノーツでやったように四角形を繰り返し生成するだけなんですが、始点、終点に加え中継点も考えないといけなくなり、さらに太さも考えるとなると扱うパラメータが増え、複雑になります。プロセカってすごいですね(小並感)。
そんなわけで、ここまで書いといてあれですが、あまりおすすめはしません。おとなしくLine Rendererを使った方が早いと思います。
とはいえめんどくさくなるだけで、算数ができれば何とかなりますので挑戦する価値はあると思います。ゲームとしてのクオリティも上がりますしね。
ソースコードと解説は省略します。ごめんなさい。パラメータの扱い方はほとんど好みによりますし、解説しようとすると薄い本が厚くなってしまいます。
決して1日目にして遅刻しそうだから省略するわけではありません。
真相は君の目で確かめてみよう!私もやったんだからさ。(丸投げ)
もっと細かいお話
軽量化
今回紹介したやり方はメッシュの数やGameObjectの数が多いので、そのままだと動作がやや重いことがあります そういう時は[ProjectSettings]->[Player]->[OtherSettings]にあるDynamicBatchingにチェックを入れるとマシになった気がします。(作ったのが半年前なので記憶があいまい)
また、今回は曲線の分割数(splitNum)を10としましたが、分割数が多いほど細かく滑らかな曲線になり、その分動作が重くなります。実際にはロングノーツの長さによって可変にするべきでしょう。私のコードでは( endPos.z - startPos.z ) / 1.5f ;
って書いてありました。なんか調整していた記憶があります。実機のスペックや、好ましいと思う曲線の滑らかさと相談して調整するといいと思います。
判定の実装
始点と終点についてはタイミングが決まっているのでそれに合わせて判定して、それとは別に指が離れたかどうかの判定を一定間隔でRayをとばして判定してました。Rayを飛ばしても十分滑らかに動作していましたが、個体差があるかもしれません。Colliderは、ロングノーツのMeshを生成するときに、それとは別に判定用のちょっと大きめのMeshを生成して、Mesh Colliderに渡していました。
細かい演出
指で押しているロングノーツは判定線のところで切れていて、そうでないものはそのまま通り過ぎるあれです。
私の場合、これは複数のCameraとLayersをうまく使って実装しました。Unityでは複数のカメラに映ったものを1つの画面に重ねて表示することができます。また、Cameraには描画範囲を設定でき、それを超えるとGameObjectが見えなくなります。
Meshの形とか関係なく見えなくなるので、これを応用して、ロングノーツに指が振れている間はロングノーツが判定線で切れる(ように見える)ようにします。
UnityのCameraはLayerによって描画対象を指定することができます。なので、ロングノーツに指が振れている間はそのロングノーツのLayerを変えて描画範囲を判定線ギリギリに調整したカメラで写すという処理をしてました。Layerを変えるだけなので、毎フレーム呼ばなければとりあえず判定に支障はありませんでした。
注意点としては、複数のカメラが同じLayerを写していると描画が複数回行われるらしく動作が重くなります。Layerの管理はしっかりしましょう。
おわりに
さて、技術記事に見せかけたよくわからん記事でしたが、いかがだったでしょうか。個人で音ゲー作るのにここまでする必要はないかもしれませんが、作っててめっちゃ楽しかったです。自分の好きなゲームに近い仕様になったので達成感もありました。最後は駆け足になりましたが、一部分だけでも参考になったのなら僥倖です。少しはプロ班っぽい記事になった…よね?
おまけ
プロセカはいいぞ。
apps.apple.com各種表記
この作品はユニティちゃんライセンス条項の元に提供されています