processingで音ゲーを作りたい! その1
経緯
最近友人が音ゲー制作を始めたらしい.俺も便乗する.目標
よくあるレーン式の音ゲーを作る.例)ガルパ,deemoなど音ゲーに必要な要素は?
音ゲーの動作に必要な情報は以下のものがあるだろう.- ノーツの流れてくるタイミング
- ノーツの流れてくるレーン
- ノーツの流れる速度
- 曲の速さ(BPM)
これらの情報はあらかじめ数値で指定できるとよい.
小節線を流す
今回はその1ということで1レーンだけ作ることにする.一気にやると死ぬので…まず小節線を流してみよう.こんな感じで書いてみた.
<プログラム>
float speed,bpm,bs,lanewidth,tapY; int beat,bars; void setup(){ size(640,360); stroke(255,255,255); lanewidth = width*0.1;//1レーンの太さ tapY = height*0.9; speed = 0.1;//ノーツの速度(1フレームあたりに進む量) bpm = 120;//曲の速さ beat = 4;//曲の拍子 bs = (60/bpm)*1000*beat;//一小節あたりの秒数(マイクロ秒) bars = 100;//小節数 } void draw(){ background(0); drawbar(tapY); drawlane(); drawbars(); } void drawlane(){ line((width-lanewidth)/2,0,(width-lanewidth)/2,height); line((width+lanewidth)/2,0,(width+lanewidth)/2,height); } void drawbars(){ for(int i = 1;i <= bars;i++){ float y = ttp(i*bs); if(0 < y && y < height){ drawbar(y); } } } /*引数に「何秒後にtapYに描画するか」を指定すると それに基づき現在のY座標を返す関数*/ float ttp(float seconds){ float position = speed*(millis()-seconds)+tapY; return(position); } /*横線もとい小節線を引く*/ void drawbar(float y){ push(); stroke(200,200,50); translate(width/2,0); line(-lanewidth/2,y,lanewidth/2,y); pop(); }
<実行結果>
やっていることとしては以下の通りだ.
- ノーツのタイミングを指定する
- タイミングを秒数に変換する
- 描画
たったこれだけ.
ttp関数について解説.時間変化によるY座標の移動を関数で表してみよう.ノーツの速度は一定だから,変位のグラフは一次関数の形になる.よって,
Y座標 = 速度×時間+定数
Y = speed×t + b
t = secondsのときY = tapYになるから,それらを代入すると,
tapY = speed×seconds +b
b = tapY - speed×seconds
よって,
Y = speed×t - speed×seconds +tapY
Y = speed(t-seconds) + tapY
となる.processingにはプログラムを実行してからの時間を取得できるmillis()関数があるから,tの部分にはこれをいれればいい.
余談になるがttpというのはtime to positionの略.TOEIC300点なので間違っているかもしれない.
あと地味に便利な機能でpush()とpop()というのがある.座標を保存できるpushMatrix()とpopMatrix()というのもあるのだが,長い上に座標の情報しか保存しておけない.その点push()とpop()は関数名が短いし,線の色や太さなどの情報も記録しておけるのでおすすめだ.
ノーツを流す!
小節線を流せてしまえば正直こちらのもの.あとはこれをノーツに置き換えればいいだけだ.手始めにタップノーツクラスを作る.<プログラム>
class tap{ float timing; /*何小節目か,と1小節を何分割したうちの何番目のタイミングか, の情報からノーツが何秒後にくればいいかを計算する.*/ tap(int bnum0,int tnum0,int snum0){ timing = bs*((bnum0-1)+float(tnum0-1)/snum0); } void drawnote(){ float y = ttp(timing); push(); stroke(0,0,0); fill(255,255,255); translate(width/2,0); ellipse(0,y,lanewidth,lanewidth); pop(); } }
コンストラクタで初期値を決定する.bnum0はノーツのある小節番号,tnum/snumでその小節でのタイミングを表す.つまり,小節を何分割したうちの何番目のタイミングなのかということだ.小節の先頭を0とし,末尾を1として考えるので(tnum-1)/snumとなる.
次にこのクラスをオブジェクト化していく.
tap[] tap = new tap[2]; void setup(){ tap[0] = new tap(3,1,4); tap[1] = new tap(3,2,4); }
最後に実際の描画だ.bars関数の名前をdrawnotes関数にして,そのなかにノーツの描画に関するプログラムを書いていく.
void drawnotes(){ for(int i = 1;i <= bars;i++){ float y = ttp(i*bs); if(0 < y && y < height){ drawbar(y); } } for(int i = 0;i < tap.length;i++){ tap[i].drawnote(); } }
<実行結果>
譜面データを作ろう!
とりあえずノーツは流せたけどノーツの記述くそだるくね?ってことで譜面データを作れるようにしよう.processingではjsonが使えるからそれで作っていく.まずjsonの記述をこんな感じにしよう.
{ "スピード":0.1, "小節数":100, "BPM":120, "拍子":4, "タップ":[ "4,1,4", "4,2,4", "4,3,4", "4,4,4" ] }
ノーツのスピードや曲のbpmなどの情報もここから取得するようにしよう.次に実際にjsonを読み込んでいく.
JSONObject data; JSONArray gettap; void setup(){ data = loadJSONObject("譜面.json"); speed = data.getFloat("スピード");//ノーツの速度(1フレームあたりに進む量) bpm = data.getFloat("BPM");//曲の速さ beat = data.getInt("拍子");//曲の拍子 bars = data.getInt("小節数");//小節数 gettap = data.getJSONArray("タップ"); tap = new tap[gettap.size()]; for(int i = 0;i < gettap.size();i++){ String str = gettap.getString(i); int temp[] = int(split(str,",")); tap[i] = new tap(temp[0],temp[1],temp[2]); } }
こんな感じでおけ.あとは実行すればjsonに書いた個数のノーツが流れてくるよ.
次回は4レーンにしていきたい.