プログラミングでのRPGの作り方と大変さを伝える:前編
こんにちは。継続の錬金術士なおキーヌです。
ブログ毎日更新は127日目になります。
GWも最終日です。
あっという間に終わりましたね。
日々の仕事のストレス解消や疲れを癒すために遊びに行ったら、逆に疲れてしまったという人も多いんじゃないでしょうか?
私と妻はどちらも出不精でわざわざ混雑しているところに疲弊しにいくのはバカらしいという考えなので、
この10連休は1日だけお出かけして、それ以外は基本家で過ごしていました。
妻はイラストの依頼を受けていたのでそれの納品やらなんやらで特に普段と変わらず。
私はGW中にゲームを1つ作り上げるというチャレンジをしました。
その結果、8.5日で1画面RPGを完成させることが出来ました!
ブログを続けていなかったらたぶん挫折していたと思います。
それに加えて今までのプログラミングスキルの向上が合わさって完成させることができました。
が!問題点として配布がしづらいという状況に陥りました。
配布するためじゃなくて完成させるというのが目的だったので良し!(言い訳
今回はそんなゲーム制作チャレンジをやってみてRPGの作り方と、
色々感じ取れたことを書き残しておこうと思います。
むちゃくちゃ記事が長くなったので前編後編に分けたいと思います。
RPGツクールは偉大だった
結論から言うと、RPGを作りたい!という人は素直にRPGツクールを使うべきだと体感しました。
RPGを1から作ってみた結果、すさまじい作業量のわりに出来上がるゲームのしょぼさに絶望します。
以下、今回私がやったRPGを1から作るための作業リストです。
少し長く小難しい話になりますが、RPGをプログラミングで作ったことのない人の参考になれば幸いです。
- 言語の選定
- ウィンドウを出す
- pixi.jsでステージを作る
- 解像度を考える
- スケールを2倍にする
- スケールを2倍にした弊害
- 画像やファイルをロードするローダーを準備
- マップを作る
- UIの空間を用意する
- 操作キャラクターを出す
- キャラクターを操作する
- グリッド移動の当たり判定の作り方
- 当たり判定(衝突判定用)配列の作り方
言語の選定
C++は難易度が高いしC#だと慣れてないし10日じゃ作り上げるのは厳しそう。
私自身がWeb言語寄りの人間だったため、javascript(node.js)を採用。
ウィンドウを出す
まずこれをしないとゲームが出来ませんね。
選択する言語によって難易度が大幅に変わります。
今回はjavascriptということでElectronというChromiumというブラウザと同じ仕組みをベースに作成しました。
Electronで作られたアプリケーションはSlackやVisual Studio Codeなんかでも有名ですね。
今回はウィンドウを出してjavascriptとhtmlを読み込むだけなのでそこまでコード記述量は多くありません。
メリットとしてはWindowsでもMacでもLinuxでも動くところでしょうか。
デメリットとしては最初にも言いましたが配布するためには凄く面倒な手順を踏まないといけないところです。
今回は配布のことを考えずにとにかく作り上げるということを目標としました。
pixi.jsでステージを作る
pixi.jsという2Dライブラリを使ってレンダリング部分を作りました。
RPGツクールMVでも使われているライブラリですね。
ステージとはhtml5のCanvasタグだと思っていただければOKです。
そこに色々画像を出したり動かしたり出来るための土台になります。
解像度を考える
今回はドット絵に労力をあまり使えないということで1マス16×16の解像度にしました。
ゲーム自体の解像度は大体640×480でしょうか。
その中でマップ描写をするのは320×240です。
今回ツクールを使わなかったのは、この解像度を小さくしたかったからですね。
ツクールMVを使ったらドット絵をオリジナルにするだけで解像度が大きいためそこで時間がとられまくります。
スケールを2倍にする
そのままのスケールで描写するとものすごく小さくなって見辛いため、
描写サイズを2倍にします。
しかしそのまま拡大してしまうとドット絵が滲んでドットゲーの良さが一気に失われます。
ある意味ブラウン管みたいに滲んでいいんですけど、やっぱりくっきりとしたドット絵をみたいですよね。
pixi.jsにはスケールを2倍にしてなおかつドットが滲まないようにくっきりとした状態に出来る
ニアレストネイバー法を使用できます。
これを適用するとドット絵がそのまま拡大されて小さい解像度でも大きく表示できるので、
ドット絵の良さが引き立ちます。
最近のスマホにリメイクされたFFやらRPGツクールのドット絵が微妙なのは
解像度が高すぎてドット絵らしくないからです。
あれはあれで表現の幅がかなり広がるからいいんですけどね。
その分労力も広がります(笑)
スケールを2倍にした弊害
スケールを2倍にすると色々なものが2倍になりました。
Electron側で800×600のウィンドウサイズを固定にしていても、なぜかcanvasサイズは1600×1200の二倍になり
画面をスクロールすると真っ暗な部分が見えたりして困っていました。
ゲーム自体はいい感じに拡大されているのですが余計なものも拡大されてしまいます。
解決法は、htmlというかbodyを800×600に固定し、cssでpositionをrelativeにしてから
canvasのpositionをabsoluteにすることでスクロールをでき無くできます。
これは完全にWeb制作の知識ですね。
デバッグツールを起動すれば見えてしまいますがElectron側で機能を制限すればOKです。
画像やファイルをロードするローダーを準備
本来ゲームを作成する時は自分でファイルをロードする機能を作らなければいけないのですが、
pixi.jsには最初からローダーというものが用意されているのでそれを利用します。
Spriteに適用するテクスチャや設定ファイルは全てここに読み込みます。
ゲーム開始時に必要なものだけを読み込んで起き、他はエリア移動等で必要なものだけ読み込むのがベストですね。
マップを作る
テクスチャのロードができたのでマップ描写に移ります。
マップチップは16×16になったので、スプライトを16×16で生成してマップ用の配列を1次元で作ります。
マップは2次元なのですが、横幅と縦幅を決めておけば1次元にすることも可能です。
描写する際に1つずつ1次元配列を取り出し、横幅の数まで来たら表示座標を1つ縦幅分加算してやれば
1次元配列でも2次元マップの描写が出来ます。
今までは2次元配列でマップを作っていたのですが、それをするとイベントやら判定やらのマップを作るときに
凄まじいメモリ食い虫となってしまうので今回1次元配列を利用することにしました。
UIの空間を用意する
マップ描写が終わったら次はプレイヤーのステータスやメッセージウィンドウを用意します。
コンシューマゲームのRPGであれば画面全体にマップで、メニューボタンを押したらメニュー画面にいくという感じですが
今回はレトロパソコンゲーム風にマップ画面の周りにGUIを表示しました。
ウィンドウ枠を作るには、まず四隅の角用のスプライトを用意して座標を決めてやります。
次に角と角の間になる辺なのですが1つ1つスプライトを使っていては重たくなってしまいます。
例えば上辺の部分は左上角の隣に1つスプライトをおいて横幅のサイズを右上の角まで大きくし、
テクスチャを繰り返し描写にすることで1つのスプライトと小さいテクスチャで辺を作ることが出来ます。
後はこれの繰り返しですね。
操作キャラクターを出す
マップとGUIの描写が完了したので今度は操作するプレイヤーのキャラクターを描写します。
プレイヤーは操作もするので、独立した変数に組み込んでおくのをおすすめします。
作り方はマップやUIで使ったスプライトの機能と、ステータスを保持する専用の変数や配列等を作ると良いです。
RPGといえば1歩歩いたら一定のところまで歩くというグリッドタイプの移動が基本ですね。
今でこそ3Dになってグリグリ自由に歩けますが、2Dドット絵時代はドラクエもFFもポケモンも最初はそうでした。
SFC版のテイルズやスターオーシャンとかは最初からドット移動でしたが
やはりバグも多かったりでグリッド移動に比べて制御が複雑になりがちです。
(この2つがバグがでるのはそこじゃないですが笑)
なので今回はグリッド移動を選択しました。
キャラクターを操作する
グリッドタイプの移動なので、1回キーを押したら次のグリッドにいくまでは移動し続けます。
この処理の作り方は、過去にも言っていますが割った余りを元に計算しています。
止まっている間はxとy座標ともに割った余りが0になっている状態とします。
4方向にしか移動できないので、少し移動すると割った余りがxかyが0以外になります。
すると、割った余りが0じゃない方向に割り切れるまで移動します。
そうすると、マス目にキッチリと止まってくれます。
移動中はキー操作を無効にしていて、xとyが割り切れる状態になったらキー操作を有効にするという風にすれば完璧ですね。
後は当たり判定(配列を参照する衝突判定)を実装すれば移動は完璧です。
グリッド移動の当たり判定の作り方
プレイヤーからみた当たり判定は、キー操作をされた瞬間に進行方向にある配列を参照します。
前後であれば自分の座標プラスマイナス1で参照できますが、上下の場合は横グリッドサイズ丸ごと増減した値にすれば上下の値を取得できます。
マップ配列を1次元にしたのは、この計算がしやすいからというのもありますね。
今回作ったゲームでは敵がその場から動かない状態でしたので作っていませんが、
NPCが動き回るゲームの場合はプレイヤーがいる位置は移動できないという仕組みが必要になります。
当たり判定(衝突判定用)配列の作り方
マップ配列をそのままコピーして、配列の配列にすればOKです。
javascriptだとmapArray[z][x*y]という形でzの値を返れば別のレイヤーを参照するという方法が取れます。
z=0はマップ地面描写用、z=1は机等のマップオブジェクト、z=2は衝突判定用、z=3はイベント&プレイヤーの現在地用。
等にしておけば参照が楽ですね。
マップを1次元配列にしたのはこうやりたかったからなのもあります。
マップの時点で2次元配列にしたら2次元配列の2次元配列を作らないといけなくなり、
1回訳が分からなくなったことがあります。
3次元配列にしてしまうという手もありますが、よほど頭の回転が速い人でないと扱い辛いと思います。
話を戻すと、マップを1つずつ作っていては色んなエリアを作っていく際に時間がかかり過ぎるので、
マップエディタを作った方がいいですね。
1回、マップを自力で作ってからそれに対応したマップエディタを作った方がやることが明確になるので
いきなりマップエディタを作るよりかは先にゲームを作ってみることをオススメします。
──1つ1つのやったことを書いたらすさまじい量の記事になったので分割です。
1つにまとめても良かったのですが読み疲れる可能性が高いので仕方なしですね。
明日にでも続きを上げたいと思います。
それでは。