🎮

Cub3D - C言語で3DFPSゲーム作ってみた

2024/07/30に公開

MiniLibXを使用したRayCaster :C言語3DFPSゲームの作成

象徴的な Wolfenstein 3D に触発されたこのプロジェクトでは、C と MiniLibX を使用して、レイキャスティングの基礎を探求し、ダイナミックな 3D 迷路ゲームを作成します。この記事では、独自のファースト パーソン シューティング (FPS) をゼロから構築するプロセスをガイドし、主要な概念、アルゴリズム、および実用的な手順に焦点を当てます。

1. レイキャスティングの概要

  • レイキャスティングとは?
    レイキャスティングは、3Dグラフィックスの基礎的な技術であり、視点から発せられる「光線」をシーンに投影し、オブジェクトとの交差点を計算することで画像を生成します。この技術は、簡単でありながら効率的な方法で3D効果を作り出すことができるため、初期の3Dゲームに多く使用されました。

  • 歴史的背景と関連性
    レイキャスティングは、1992年に発売された「Wolfenstein 3D」によって有名になりました。この技術は、当時のハードウェアの限界を超えることなく、リアルタイムで魅力的な3Dグラフィックスを提供しました。このプロジェクトでは、その象徴的な技術を再現し、現代の環境で動作する3DFPSゲームを構築します。

2. 開発環境の設定

  • 必要なツールとライブラリ

    まず、C言語で3D FPSゲームを開発するために必要なツールとライブラリを紹介します。以下のツールをインストールしましょう:

    • GCC (GNU Compiler Collection): C言語のコンパイラ。
    • Make: プロジェクトのビルドを自動化するツール。
    • MiniLibX: グラフィックスライブラリで、簡単にウィンドウや画像の描画ができます。
      これらのツールは、LinuxまたはmacOSで簡単にインストールできます。以下のコマンドをターミナルに入力してください。
    sudo apt-get install gcc make libx11-dev libglfw3-dev
    
  • MiniLibX のインストール
    MiniLibXは、42の学生が使用するシンプルなグラフィックスライブラリです。以下の手順でインストールします。今回は、下記のソースコードを使用しました。
    https://github.com/codam-coding-college/MLX42

    公式リポジトリからソースコードをクローンします。
    リポジトリのディレクトリに移動し、ライブラリをビルドします。

    git clone https://github.com/codam-coding-college/MLX42.git
    cd minilibx-linux
    make
    

3. プロジェクト要件の理解

  • cub3D の概要
    cub3Dは、42 Tokyoの課題の一つであり、レイキャスティングを用いた3DFPSゲームの基本を学ぶためのプロジェクトです。このプロジェクトを通じて、C言語での高度なプログラミング技術と、グラフィックプログラミングの基本を習得します。

  • 必須機能

    • プレイヤーの視点で描画される3D迷路
    • WASDキーによる移動
    • マウスルックによる視点の変更
    • 壁、床、天井のテクスチャ描画
      - エラーハンドリングとパフォーマンスの最適化

4. ゲーム ウィンドウの作成

  • MiniLibX の初期化
    まず、MiniLibXを使用してウィンドウを作成します。参考コード。
    #include "mlx.h"
    
    int main(void)
    {
        void *mlx = mlx_init();
        void *win = mlx_new_window(mlx, 800, 600, "cub3D");
    
        mlx_loop(mlx);
        return 0;
    }
    
  • ウィンドウイベントの処理
    ウィンドウが作成された後は、キーボードやマウスのイベントを処理する必要があります。今回は、MLXライブラリ内で宣言された変数を使用しました。

5. レイ キャスティングの実装

  • レイ キャスティング アルゴリズムの基礎
    レイキャスティングは、視点から放射状に伸びる複数の「レイ」を使って、各レイが最初に壁に当たる位置を計算します。これにより、プレイヤーの視界に見える壁の位置と距離を特定します。

  • 距離と壁の交差の計算

    void	dda(t_cub3d *data, char **map)
    {
    	while (data->ray.hit == 0)
    	{
    		if (data->ray.side_dist_x < data->ray.side_dist_y)
    		{
    			data->ray.side_dist_x += data->ray.delta_dist_x;
    			data->ray.map_x += data->ray.step_x;
    			data->ray.side = 0;
    		}
    		else
    		{
    			data->ray.side_dist_y += data->ray.delta_dist_y;
    			data->ray.map_y += data->ray.step_y;
    			data->ray.side = 1;
    		}
            		if (data->ray.map_x <= 0 || data->ray.map_x >= data->map.width_map || data->ray.map_y <= 0 || data->ray.map_y >= data->map.height_map)
    		{
                printf("Out of map bounds: map_x=%d, map_y=%d\n", data->ray.map_x, data->ray.map_y);
                break;  // Break the loop if out of bounds
    		}
    		if (map[data->ray.map_y][data->ray.map_x] == '1')
    		{
    			data->ray.hit = 1;
    		}
    	}
    }
    

6. 迷路のレンダリング

  • テクスチャの読み込みと表示
    壁にテクスチャを適用することで、ゲームのビジュアルを向上させます。MiniLibXを使用してテクスチャを読み込み、描画します。

    void render_wall(t_cub3d *data, t_ray *ray, int x) 
    {
    	t_draw	draw;
    
    	init_draw(&draw, data);
    
    	if (!draw.texture_img) {
    		fprintf(stderr, "Failed to get texture image\n");
    		return;
    	}
    
    	int tex_x = (int)(draw.wall_x * (double)(draw.texture_img->width));
    	if (ray->side == 0 && ray->ray_dir_x < 0)
    		tex_x = draw.texture_img->width - tex_x - 1;
    	if (ray->side == 1 && ray->ray_dir_y > 0)
    		tex_x = draw.texture_img->width - tex_x - 1;
    
    	for (int y = draw.draw_start; y < draw.draw_end; y++) {
    		int d = y * 256 - HEIGHT_WIN * 128 + draw.line_height * 128;
    		int tex_y = ((d * draw.texture_img->height) / draw.line_height) / 256;
    		uint32_t color = get_texel_image(draw.texture_img, tex_x, tex_y);
    		mlx_put_pixel(data->map.img, x, y, color);
    	}
    }
    
  • 床と天井の色の処理
    床と天井の色を設定し、ゲームの背景を彩ります。

    void	draw_ceil_floor(t_cub3d *data)
    {
    	int	i;
    	int	j;
    
    	j = 0;
    	while (j < HEIGHT_WIN)
    	{
    		i = 0;
    		while (i < WIDTH_WIN)
    		{
    			if (j < HEIGHT_WIN / 2)
    				mlx_put_pixel(data->map.img, i, j, data->textures.sky_hex);
    			else
    				mlx_put_pixel(data->map.img, i, j, data->textures.floor_hex);
    			i++;
    		}
    		j++;
    	}
    }
    

7. プレイヤーの移動とコントロール

  • WASD 移動の実装

     ```c:
        void    controle_player(t_cub3d *data)
    {
    	float new_x = data->player.pos_x;
    	float new_y = data->player.pos_y;
    
    	if (mlx_is_key_down(data->mlx, MLX_KEY_W))
    	{
    		new_x += data->player.dir_x * MOVE_STEP;
    		new_y += data->player.dir_y * MOVE_STEP;
    	}
    	if (mlx_is_key_down(data->mlx, MLX_KEY_S))
    	{
    		new_x -= data->player.dir_x * MOVE_STEP;
    		new_y -= data->player.dir_y * MOVE_STEP;
    	}
    	if (mlx_is_key_down(data->mlx, MLX_KEY_A))
    	{
    		new_x -= data->player.plane_x * MOVE_STEP;
    		new_y -= data->player.plane_y * MOVE_STEP;
    	}
    	if (mlx_is_key_down(data->mlx, MLX_KEY_D))
    	{
    		new_x += data->player.plane_x * MOVE_STEP;
    		new_y += data->player.plane_y * MOVE_STEP;
    	}
    	move_player(data, new_x, new_y);
    }
    
  • マウス ルックの実装

    void	move_mouse(double xp, double yp, void *param)
    {
    	t_cub3d	*data;
    
    	data = (t_cub3d *)param;
    	if (xp >= 0 && xp <= WIDTH_WIN && yp >= 0 && yp <= HEIGHT_WIN)
    	{
    		if (xp > data->map.old_x)
    		{
    			data->player.angle += (SPEED_ROTATE + 0.1);
    			if (data->player.angle >= 360)
    				data->player.angle -= 360;
    		}
    		else
    		{
    			data->player.angle -= SPEED_ROTATE;
    			if (data->player.angle <= 0)
    				data->player.angle += 360;
    		}
    		data->map.old_x = xp;
    	}
    }
    

8. エラー処理と最適化

  • エラーを適切に管理
    適宜、エラー処理してメッセージを表示し、プログラムのクラッシュを防ぎます。何が原因で適切に実行されないのかわかりやすく管理します。

  • パフォーマンスの最適化
    ゲームのパフォーマンスを向上させるための最適化技術を導入します。例えば、ループの回数を減らす、不要な計算を避けるなど。

9. 結論

  • 学んだ教訓

レイキャスターをゼロから構築することで、グラフィカル プログラミング、アルゴリズム、ゲーム開発に関する貴重な洞察が得られます。このプロジェクトは、魅力的なエクスペリエンスを作成する上での数学と効率的なコーディング手法の威力を実証します。もともと、2Dで動くゲームを作った経験はありましたが、それを元に3Dレンダリングする計算までも実装するというなかなか深い理解が必要な課題でした。ともにこの課題に取り組んだペアパートナーには、自分が苦手とする数学的計算の部分を担当してもらい、なんとか協力して完成までたどり着くことができました。課題としては、『始める前にコーディングのルールを決めること』、『もっとGitを使いこなすこと』という2点がありました。ちゃんとルールを決めていなかったために二度手間になるような作業が若干あったり、Gitのcommitメッセージがわかりづらくなってしまいました。ここは、経験として次回の開発に活かしたいと思っています。
今の時代、AIがコードを書いてくれたり、様々な便利な言語があったりする中で、こんな原始的なC言語でにゲームを作る人間はほぼいないと思います。レイキャスティングの基礎、MiniLibXの活用方法、ゲームロジックの実装など、多くのスキルを習得できました。約1ヶ月半、、、お疲れ様でした。( ´Д`)y━・~~

Discussion