はじめに
誰でもゲームが作れる良い世の中になった... でも音ゲーに関する情報が少なすぎる!ただのノーマルノーツを降らせるだけの解説は見かけるけど、ロングノーツなりフリックノーツなりギミックなりを解説しているサイトがほとんど見つからない!
そんなわけで、 少しでも音ゲー制作の助けになれば、という目的で自分からサイトを書き始めました。今回はロングノーツの生成を解説します。
譜面ファイル形式
譜面ファイルは譜面エディタを作らないと生成が難しいので、Setch氏のNoteEditorを借りることにしましょう。
{
"name": "test",
"maxBlock": 6,
"BPM": 120,
"offset": 0,
"notes": [
{
"LPB": 4,
"num": 4,
"block": 0,
"type": 2,
"notes": [
{
"LPB": 4,
"num": 8,
"block": 3,
"type": 2,
"notes": []
},
{
"LPB": 4,
"num": 12,
"block": 5,
"type": 2,
"notes": []
}
]
}
]
}
offsetは曲と譜面のズレ、
LPBは一拍を何分割するか、
numは開始線を0,次の線を1とするどこの線に配置されているかの値、
blockはレーン番号です。
NoteEditorでのロングノーツはtype:2が該当します。
各ノーツの時間は60/BPM/LPB*numで求められます。
ノーツの種類でインデックスを分ける
jsonファイルの中にはもちろんロングノーツだけでなく、ノーマルノーツの情報も一緒にnotes[]内に入っています。ただfor()で回してロングノーツを生成するのではノーマルノーツの位置にもロングノーツが生成されてしまいます。その為、ノーマルノーツとロングノーツのインデックスを分けて、そのインデックスから別々に生成する必要があります。
注意1
List<int> Nindexs = new List<int>();
List<int> Lindexs = new List<int>();
int targetN = 1;
int targetL =2
for (int i = 0; i < Test.notes.Length; i++)
{
var type = Test.notes[i].type;
if(type == targetN)
Nindexs.Add(i);
else if(type == targetL)
Lindexs.Add(i);
}
for(int i = 0; i < Nindexs.Count; i++)
{
//ノーマルノーツの生成
}
for(int i = 0; i < Lindexs.Count; i++)
{
//ロングノーツの生成
}
jsonファイルをJsonUtilityかなにかでTestに変換したものとしてのサンプルコードです。
i番目のtypeが1だったらノーマルノーツ生成のためのインデックスにiを追加、i番目のtypeが2だったらロングノーツ生成のためのインデックスにiを追加し、それぞれのインデックスでノーツを生成します。
ロングノーツ生成(本題)
生成の流れ↓
- 始点、中間点(終点)の時間・位置を計算
- メッシュ(ロングノーツの帯部分)を生成。頂点を点の位置から計算
- 次の中間点(終点)の時間・位置を計算
- 2.3.の繰り返し
- ロングノーツ1つ出来上がり
注意1
[SerializeField] GameObject Obj;//点のプレハブ
float noteSpeed;//ノーツが降る速さ
int[] triangles = new int[6];
triangles[0] = 0;
triangles[1] = 2;
triangles[2] = 1;
triangles[3] = 2;
triangles[4] = 3;
triangles[5] = 1;
for(int u = 0; u < Lindexs.Count; u++)
{
int i = Lindexs[u];//json内でのロングノーツのインデックス
float time = (60 / Test.BPM / Test.notes[i].LPB * Test.notes[i].num) + Test.offset * 0.01f;
float z = time * noteSpeed;
Instantiate(Obj, Test.notes[i].block - 2.5, 0.01f, z, Quaternion.identity);
var up_left = new Vector3(Test.notes[i].block - 3, 0.01, z);
var up_right = new Vector3(Test.notes[i].block - 2, 0.01, z);
for(int j = 0; j < Test.notes[i].notes.Length; j++)
{
float _time = (60 / Test.BPM / Test.notes[i].notes[j].LPB * Test.notes[i].notes[j].num) + Test.offset * 0.01;
float _z = _time * noteSpeed;
Instantiate(Obj, Test.notes[i].notes[j].block - 2.5, 0.01f, _z, Quaternion.identity);
var low_left = new Vector3(Test.notes[i].notes[j].block - 2, 0.01, _z);
var low_right = new Vector3(Test.notes[i].notes[j].block - 3, 0.01, _z);
var vertices = new Vector3[4];
vertices[0] = up_left;
vertices[1] = up_right;
vertices[2] = low_left;
vertices[3] = low_right;
//ロングノーツの帯部分を生成
var lineObj = GameObject.CreatePrimitive(PrimitiveType.Quad);
Mesh mesh = new Mesh();
mesh.SetVertices(vertices);
mesh.SetTriangles(triangles, 0);
//ロングノーツの帯部分の始点を更新
up_left = low_left;
up_right = low_right;
}
}
ロングノーツの帯部分は一気に生成するのではなく、
平行四辺形を複数生成して実現しています。その為jの値が変わる(操作する点が変わる)度にメッシュの頂点位置を更新しています。
おわりに
初めてサイト作りましたけども、思いの外時間がかかるものですね。
(全てスマホで済まそうとするのが悪い)
わかりずらかったり、そもそもコードが間違ってるとかもあるかもしれないのでご了承下さい。少しでも音ゲー作りの手助けになれば幸いです。