🎮

[課題振り返り] so_long編

2025/01/26に公開

はじめに

今回は42の課題の1つであるso_longを振り返っていきたいと思います。

課題概要

C言語でminilibxを使用して簡単な2Dゲームを作成します。
(minilibxはC言語を使用した簡単なグラフィカル操作が行えるライブラリです)
各キーで上下左右に動けるようにします。

学んだこと

学んだことを書いていきます。
so_longはそれまでの課題の中でもおそらく最も多くコードを書いた課題なのですが、新しく学んだこともそうですが、それまでの知識を生かしたりC言語の書き方がより成長した課題でした。
なので以下に書いていくこと以上の学びがあったと思います。

大量の変数の管理の仕方

これまでの課題はプログラム全体で管理・保存するべき変数の個数があまり多くなかったので、関数から関数へと引数としてそのまま渡すだけで解決していました。
ですが、so_longでは大量の変数を扱う必要があります。
なので、以下のような構造体を作成しました。

typedef struct s_map{
	char	**map;
	size_t	player_count;
	size_t	item_count;
	size_t	exit_count;
	size_t	width;
	size_t	height;
}t_map;

typedef struct s_player{
	size_t	x;
	size_t	y;
	size_t	item_count;
	size_t	move_count;
}t_player;

typedef struct s_images{
	void	*tile;
	void	*wall;
	void	*item;
	void	*exit;
	void	*player;
	int		size;
}t_images;

typedef struct s_info{
	void		*mlx;
	void		*window;
	t_map		map_info;
	t_player	player_info;
	t_images	images_info;
}t_info;

こうすることでt_info型の変数を1つ持つことでゲームの全ての情報を扱えます。

このような構造体で扱うのは、今となっては当たり前のことをしているようですが、当時は扱いやすさに感動していました。

hook関数

hook関数とはminilibxを使用して設定できるもので、例えば以下のように動作します。
「キーボードの押下があったら"key_press"関数を実行する」
このように設定するとき"key_press"関数をhook関数と呼びます。

今回の課題では3つのhookを設定します。

  1. キーボード押下 -> map上のプレイヤーの位置更新
  2. windowが閉じられた -> プログラムを終了
  3. windowがリサイズや他のwindowと重なった -> 再描画

このhook関数という存在を知れたのはとても大きかったです。
hook関数はminilibxだけの考え方ではなく、特にゲーム開発においては絶大な力を発揮します。
実際に筆者は42の最後の課題「PongゲームWebアプリ」を作る際にPongゲームのロジックの部分を担当したのですが、その時にこの「so_long」と「cub3d(3Dゲームを作る課題)」はとても生かされたと感じました。
それはhook関数がユーザーのアクションを受けて実行されるからだと思います。
他にもユーザーのアクションを受ける方法は広い意味でコマンドライン引数やscanf()などがありますが、それらよりも自由度が高いこともありとても勉強になりました。

広大なマップをプレイヤーの移動にあわせて画面を切り替える

広すぎるマップの場合はマップをページに分割しプレイヤーがいる位置のページをwindowにレンダリングするようにしました。そして移動に合わせてそのページから出る場合は次のページに画面全体がレンダリングし直されるようにしました。
該当するのが以下のコードです。

	page_x = (info->player_info.x / MAX_SCREEN_SIZE) * MAX_SCREEN_SIZE;
	page_y = (info->player_info.y / MAX_SCREEN_SIZE) * MAX_SCREEN_SIZE;
	y = 0;
	while (y < MAX_SCREEN_SIZE + 1 && map[page_y + y])
	{
		x = 0;
		while (x < MAX_SCREEN_SIZE + 1 && map[page_y + y][page_x + x] != '\0')
		{
			img = get_image_of_there(info, map[page_y + y][page_x + x]);
			mlx_put_image_to_window(info->mlx, info->window,
				img, x * PIXEL_BITS, y * PIXEL_BITS);
			x++;
		}
		y++;
	}

スクリーンサイズで割ってからスクリーンサイズをかけることで、数学的には何をしていないようですが、プログラムでは整数で扱っているので最初に割った時点でその剰余が無視されます。その後にかけることでそのページのインデックス(ページの左上の座標)がもとまります。
そこを基準として全体をレンダリングすることで、この機能を実装しました。

Makefile -C, -L, -l

-C: 指定したディレクトリのMakefileに対してコマンドを実行する
以下のように書くことで./libft/Makefileに対してmakeを実行することができます。

make -C ./libft/

-L: ライブラリのパスを指定
-l: ライブラリの名前を指定
たとえば./libft/libft.aというライブラリを使用したい場合は以下のように書きます。

-L./libft/ -lft

-lはlibと.aを省略して記述します。

これら3つのオプションを利用することでディレクトリ構成が綺麗になり、余計な操作も減ります。

おわりに

実はこの課題は僕のプログラミング経験の中で衝撃を受けた課題でした。
はじめに2Dゲームを作れと言われたときは本当に何をどうすればゲームなんて作れるのか、と思いました。
ゲームってすごいツールを使ってつくられていると思ってたからです。(もちろんそういうのもあるとは思います)"歩く"と書けばプレイヤーが歩いたり、"止まる"と書けばプレイヤーが止まったり。

まさかC言語と画面に簡単な描画ができるライブラリだけで実装できると思っていなかったのですが、
この課題を通してゲームを作るというよりそれをゲームとして見せるために必要なことを実装していくという感覚を覚えることができました。
以後の課題で3DゲームやPongゲームを作った際にもこの感覚のおかげで初めから何をすれば良いのか浮かびやすかったです。

Discussion