第5章:マップパースと実装
はじめに
本章では、.cubファイルの解析とゲーム実装の詳細を学びます。
---
1. .cubファイル形式
1.1 ファイル構造
NO ./path_to_north_texture.xpm
SO ./path_to_south_texture.xpm
WE ./path_to_west_texture.xpm
EA ./path_to_east_texture.xpm
F 220,100,0
C 225,30,0
1111111111111111111111111
1000000000110000000000001
1011000001110000000000001
111111111011000001110000000000001
100000000011000001110111111111111
11110111111111011100000010001
11110111111111011101010010001
11000000110101011100000010001
10000000000000001100000010001
10000000000000001101010010001
11000001110101011111011110N0111
11110111 1110101101111010001
11111111 1111111111111111111
1.2 識別子
| 識別子 | 意味 | 形式 | |--------|------|------| | NO | 北向き壁テクスチャ | パス | | SO | 南向き壁テクスチャ | パス | | WE | 西向き壁テクスチャ | パス | | EA | 東向き壁テクスチャ | パス | | F | 床の色 | R,G,B | | C | 天井の色 | R,G,B |
1.3 マップ文字
| 文字 | 意味 | |------|------| | 0 | 空きスペース(歩ける) | | 1 | 壁 | | N | プレイヤー開始位置(北向き) | | S | プレイヤー開始位置(南向き) | | E | プレイヤー開始位置(東向き) | | W | プレイヤー開始位置(西向き) | | 空白 | マップ外(壁としても機能) |
---
2. パーサーの実装
2.1 データ構造
typedef struct s_config {
char *north_path;
char *south_path;
char *west_path;
char *east_path;
int floor_color;
int ceiling_color;
char **map;
int map_width;
int map_height;
double player_x;
double player_y;
char player_dir;
} t_config;
2.2 ファイル読み込み
int parse_cub_file(t_config *config, char *filename) {
int fd;
char *line;
fd = open(filename, O_RDONLY);
if (fd < 0)
return (error_exit("Cannot open file"));
// テクスチャと色のパース
while ((line = get_next_line(fd)) != NULL) {
if (is_empty_line(line)) {
free(line);
continue;
}
if (is_map_line(line)) {
// マップのパース開始
parse_map(config, fd, line);
break;
}
parse_config_line(config, line);
free(line);
}
close(fd);
return (validate_config(config));
}
2.3 設定行のパース
int parse_config_line(t_config *config, char *line) {
char *trimmed;
char *value;
trimmed = ft_strtrim(line, " \t\n");
if (!trimmed)
return (1);
if (ft_strncmp(trimmed, "NO ", 3) == 0)
config->north_path = ft_strdup(trimmed + 3);
else if (ft_strncmp(trimmed, "SO ", 3) == 0)
config->south_path = ft_strdup(trimmed + 3);
else if (ft_strncmp(trimmed, "WE ", 3) == 0)
config->west_path = ft_strdup(trimmed + 3);
else if (ft_strncmp(trimmed, "EA ", 3) == 0)
config->east_path = ft_strdup(trimmed + 3);
else if (ft_strncmp(trimmed, "F ", 2) == 0)
config->floor_color = parse_color(trimmed + 2);
else if (ft_strncmp(trimmed, "C ", 2) == 0)
config->ceiling_color = parse_color(trimmed + 2);
else
return (error_exit("Unknown identifier"));
free(trimmed);
return (0);
}
2.4 マップのパース
int parse_map(t_config *config, int fd, char *first_line) {
t_list *lines;
char *line;
lines = NULL;
ft_lstadd_back(&lines, ft_lstnew(ft_strdup(first_line)));
while ((line = get_next_line(fd)) != NULL) {
if (is_empty_line(line) && is_map_started(lines)) {
free(line);
break; // マップ終了
}
ft_lstadd_back(&lines, ft_lstnew(ft_strdup(line)));
free(line);
}
return (convert_list_to_map(config, lines));
}
int convert_list_to_map(t_config *config, t_list *lines) {
t_list *current;
int i;
int max_width;
config->map_height = ft_lstsize(lines);
max_width = get_max_line_width(lines);
config->map_width = max_width;
config->map = malloc(sizeof(char *) * (config->map_height + 1));
if (!config->map)
return (1);
i = 0;
current = lines;
while (current) {
config->map[i] = pad_line((char *)current->content, max_width);
current = current->next;
i++;
}
config->map[i] = NULL;
ft_lstclear(&lines, free);
return (find_player_position(config));
}
---
3. マップ検証
3.1 壁で囲まれているか確認
int validate_map(t_config *config) {
int x, y;
// 各空きスペースが壁で囲まれているか確認
for (y = 0; y < config->map_height; y++) {
for (x = 0; x < config->map_width; x++) {
if (is_floor(config->map[y][x])) {
if (!is_surrounded(config, x, y))
return (error_exit("Map is not closed"));
}
}
}
return (0);
}
int is_surrounded(t_config *config, int x, int y) {
// 8方向をチェック
int dirs[8][2] = {
{-1, -1}, {0, -1}, {1, -1},
{-1, 0}, {1, 0},
{-1, 1}, {0, 1}, {1, 1}
};
for (int i = 0; i < 8; i++) {
int nx = x + dirs[i][0];
int ny = y + dirs[i][1];
// 境界外
if (nx < 0 || nx >= config->map_width ||
ny < 0 || ny >= config->map_height)
return (0);
// 空白(マップ外)
if (config->map[ny][nx] == ' ')
return (0);
}
return (1);
}
3.2 プレイヤー位置の検出
int find_player_position(t_config *config) {
int x, y;
int player_count = 0;
for (y = 0; y < config->map_height; y++) {
for (x = 0; x < config->map_width; x++) {
char c = config->map[y][x];
if (c == 'N' || c == 'S' || c == 'E' || c == 'W') {
config->player_x = x + 0.5;
config->player_y = y + 0.5;
config->player_dir = c;
config->map[y][x] = '0'; // 床に置き換え
player_count++;
}
}
}
if (player_count != 1)
return (error_exit("Invalid player count"));
return (0);
}
---
4. プレイヤーの初期化
4.1 方向の設定
void init_player_direction(t_player *player, char dir) {
switch (dir) {
case 'N':
player->dirX = 0;
player->dirY = -1;
player->planeX = 0.66;
player->planeY = 0;
break;
case 'S':
player->dirX = 0;
player->dirY = 1;
player->planeX = -0.66;
player->planeY = 0;
break;
case 'E':
player->dirX = 1;
player->dirY = 0;
player->planeX = 0;
player->planeY = 0.66;
break;
case 'W':
player->dirX = -1;
player->dirY = 0;
player->planeX = 0;
player->planeY = -0.66;
break;
}
}
---
5. 入力処理
5.1 キー入力
#define KEY_W 13 // macOS
#define KEY_A 0
#define KEY_S 1
#define KEY_D 2
#define KEY_LEFT 123
#define KEY_RIGHT 124
#define KEY_ESC 53
typedef struct s_keys {
int w;
int a;
int s;
int d;
int left;
int right;
} t_keys;
int key_press(int keycode, t_game *game) {
if (keycode == KEY_ESC)
exit_game(game);
if (keycode == KEY_W)
game->keys.w = 1;
if (keycode == KEY_A)
game->keys.a = 1;
if (keycode == KEY_S)
game->keys.s = 1;
if (keycode == KEY_D)
game->keys.d = 1;
if (keycode == KEY_LEFT)
game->keys.left = 1;
if (keycode == KEY_RIGHT)
game->keys.right = 1;
return (0);
}
int key_release(int keycode, t_game *game) {
if (keycode == KEY_W)
game->keys.w = 0;
if (keycode == KEY_A)
game->keys.a = 0;
if (keycode == KEY_S)
game->keys.s = 0;
if (keycode == KEY_D)
game->keys.d = 0;
if (keycode == KEY_LEFT)
game->keys.left = 0;
if (keycode == KEY_RIGHT)
game->keys.right = 0;
return (0);
}
5.2 移動処理
#define MOVE_SPEED 0.05
#define ROT_SPEED 0.03
void update_player(t_game *game) {
t_player *p = &game->player;
// 前進
if (game->keys.w) {
double newX = p->posX + p->dirX * MOVE_SPEED;
double newY = p->posY + p->dirY * MOVE_SPEED;
if (is_walkable(game, newX, p->posY))
p->posX = newX;
if (is_walkable(game, p->posX, newY))
p->posY = newY;
}
// 後退
if (game->keys.s) {
double newX = p->posX - p->dirX * MOVE_SPEED;
double newY = p->posY - p->dirY * MOVE_SPEED;
if (is_walkable(game, newX, p->posY))
p->posX = newX;
if (is_walkable(game, p->posX, newY))
p->posY = newY;
}
// 左移動(ストレイフ)
if (game->keys.a) {
double newX = p->posX - p->planeX * MOVE_SPEED;
double newY = p->posY - p->planeY * MOVE_SPEED;
if (is_walkable(game, newX, p->posY))
p->posX = newX;
if (is_walkable(game, p->posX, newY))
p->posY = newY;
}
// 右移動(ストレイフ)
if (game->keys.d) {
double newX = p->posX + p->planeX * MOVE_SPEED;
double newY = p->posY + p->planeY * MOVE_SPEED;
if (is_walkable(game, newX, p->posY))
p->posX = newX;
if (is_walkable(game, p->posX, newY))
p->posY = newY;
}
// 左回転
if (game->keys.left)
rotate_player(p, -ROT_SPEED);
// 右回転
if (game->keys.right)
rotate_player(p, ROT_SPEED);
}
5.3 衝突判定
int is_walkable(t_game *game, double x, double y) {
int mapX = (int)x;
int mapY = (int)y;
// 境界チェック
if (mapX < 0 || mapX >= game->map_width ||
mapY < 0 || mapY >= game->map_height)
return (0);
// 壁チェック
if (game->map[mapY][mapX] == '1')
return (0);
return (1);
}
---
6. ウィンドウ管理
6.1 終了処理
int close_window(t_game *game) {
cleanup_game(game);
exit(0);
return (0);
}
void cleanup_game(t_game *game) {
// テクスチャ解放
if (game->tex_north.img)
mlx_destroy_image(game->mlx, game->tex_north.img);
// ... 他のテクスチャも同様
// イメージ解放
if (game->img)
mlx_destroy_image(game->mlx, game->img);
// ウィンドウ解放
if (game->win)
mlx_destroy_window(game->mlx, game->win);
// マップ解放
free_map(game->map);
}
6.2 イベント登録
void setup_hooks(t_game *game) {
mlx_hook(game->win, 2, 1L<<0, key_press, game); // KeyPress
mlx_hook(game->win, 3, 1L<<1, key_release, game); // KeyRelease
mlx_hook(game->win, 17, 0, close_window, game); // DestroyNotify
mlx_loop_hook(game->mlx, game_loop, game);
}
---
まとめ
本章で学んだこと:
- .cubファイル形式: 識別子とマップ
- パーサー: 設定行とマップの解析
- マップ検証: 壁で囲まれているか確認
- 入力処理: キー入力と移動
- ウィンドウ管理: 終了処理とイベント
次章では、最適化とボーナス機能を学びます。