2Dゲームを作ってみたというお話

作ったもの

下記のように実行すると、ゲームマップが現れ、ゲーム開始。

$./so_long_bonus map.ber

f:id:rakiyama0229:20210805173614g:plain
player(勇者)を操作して、enemy(ドラゴン)から逃げながら、
collectible(光)を集めてexit(ダークサイドっぽいやつ)まで移動させてる。
一応enemyからはギリギリぶつからず逃げ切った。笑
wall(木)を通り抜けることはできない。
移動はW,A,D,S keyで。

  • so_long_bonus : 今回作った実行ファイル 。
  • map.ber : マップのソース。ゲームスタートの際の各マスの配置(enemyの配置はプログラム側で決める)。
    • 0 : empty
    • 1 : wall
    • C : collectible
    • E : exit
    • P : player
$cat map
1111111111111
10010000000C1
1000011111001
1P0011E000001
1111111111111

github.com

ざっくりとした流れ

まず、今回マップの表示はXサーバを利用している。
そんでもってXサーバを利用する上で便利なライブラリ(今後使うmlx系関数はここから)が事前に用意されている。
1. マップのソースを引数で受け取り、読み込む、(Xサーバの)windowで表示するためのマップ情報を用意する。
2. mlx系関数を使ってwindowに表示するための情報を用意する(各マスの画像の用意など)
3. マップを表示させる。

主に使った関数

  • mlx_init
    Xサーバを利用するために必要な情報を持った構造体のポインタを返してくれる。
  • mlx_xpm_file_to_image
    xpmファイル(今回だと事前に用意した各マスの画像)を元にimage情報を持った構造体のポインタを返してくれる。
  • mlx_new_window
    使用するwindowの情報を持った構造体のポインタを返してくれる。
  • mlx_put_image_to_window
    window内の指定した位置にimageを表示してくれる。
  • mlx_loop
    事前に指定した関数(例えば、マップを表示する関数)を実行するなどの、ループ処理をしてくれる。
    = プログラムが終了処理をするまで、マップを表示し続けてゲームを続行する。
  • mlx_loop_hook
    mlx_loop内で、実行する関数を指定できる。
  • mlx_key_hook
    mlx_loop実行中に、keyを押された際に実行する関数を指定できる。
  • mlx_hook
    mlx_loop実行中に、red cross(xボタン)を押された際など特定のイベント発生時に実行する関数を指定できる。

プログラムの流れ

各情報の用意

  1. 今回あらゆる情報を構造体(t_data *data)にまとめるので、構造体の初期化
  2. 引数で受け取ったmap.berの読み込み、中身のチェック
    map.berの内容をint型の二次元配列data->mapに落とし込む(そっちの方が扱いやすく思えた) f:id:rakiyama0229:20210805194516p:plain
  3. imageやwindowを用意やら、enemyの位置をmapに追加やらの準備
  4. loopに入る前に各関数をセット
    • keyが押された際にplayerの移動などマップの情報を書き換える関数をmlx_key_hookに渡す
    • red crossが押された際にプログラムを終了させる関数へ繋ぐ関数をmlx_hookに渡す
    • loop内で繰り返す関数(マップの表示、enemyを動かす、アニメーションを動かす)をmlx_loop_hookに渡す

f:id:rakiyama0229:20210806013308p:plain

loop実行

基本的に、マップ情報を元にimageを表示する処理(put_map)が常に一箇所で動いていて、
表示内容を変える場合はマップ情報の書き換えのみ他の関数で行っている。 f:id:rakiyama0229:20210809134023p:plain

imageの管理と表示について

windowに表示するimageは三次元配列で管理していて、
以下の情報を元に配列内のimageにアクセスして、それを表示させている。
- data->map(マップ情報、どのマスなのか、playerのマスなのか、emptyのマスなのか)
- data->square_side(どの向きなのか、playerは右向きなのか上向きなのか)
- data->square_act(どの動きなのか、これは向きに加え4種類の動きを用意している)

下の図は表示したいマップの位置のマスがENEMYで、向きは右向き、動きは2番の時の例
f:id:rakiyama0229:20210809133747p:plain マップの情報を書き換えたり、向きの情報を変えたりすれば、マップ表示の際のimage配列へのアクセスが代わり別のimageを表示するので画面に変化が出てくるという仕組み。 さらに動き(data->square_act)の情報を定期的に変更するように関数を動かしておけば、アニメーションのように動きが生まれる。

課題

  • 今回各マスに向きやアニメーションの動きを入れるコードを書いたが、結局全画像を用意することに疲れて全ての動きができていない。playerは正面のみだし、enemyは右向きのみ。
  • メモリの使用量が200MG近くなったり多いのと、実行速度が環境によってかなり遅い様子。解決方法がないか考え中。
  • 同じような文字列多用していたので、defineなど使ってコードをわかりやすくする。