ゲームロジックと移動
ゲーム状態の管理
ゲームの現在の状態を追跡するため、適切なデータ構造を使用します。
ゲーム状態の種類
typedef enum e_game_state
{
STATE_PLAYING,
STATE_WON,
STATE_LOST,
STATE_PAUSED
} t_game_state;
typedef struct s_game
{
void *mlx_ptr;
void *win_ptr;
t_map map;
t_textures textures;
int moves;
t_game_state state;
} t_game;
状態の初期化
void init_game_state(t_game *game)
{
game->moves = 0;
game->map.collected = 0;
game->state = STATE_PLAYING;
}
プレイヤーの移動システム
移動の基本フロー
1. キー入力を受信
2. 新しい位置を計算
3. 移動の有効性をチェック
4. 移動を実行
5. ゲーム状態を更新
6. 画面を再描画
移動方向の定義
typedef enum e_direction
{
DIR_UP,
DIR_DOWN,
DIR_LEFT,
DIR_RIGHT
} t_direction;
void get_new_position(t_position *pos, t_direction dir, int *new_x, int *new_y)
{
*new_x = pos->x;
*new_y = pos->y;
if (dir == DIR_UP)
(*new_y)--;
else if (dir == DIR_DOWN)
(*new_y)++;
else if (dir == DIR_LEFT)
(*new_x)--;
else if (dir == DIR_RIGHT)
(*new_x)++;
}
キー入力からの移動処理
int handle_input(int keycode, t_game *game)
{
t_direction dir;
int new_x;
int new_y;
// ゲームが終了している場合は入力を無視
if (game->state != STATE_PLAYING)
return (0);
// キーコードを方向に変換
if (keycode == KEY_W || keycode == KEY_UP)
dir = DIR_UP;
else if (keycode == KEY_S || keycode == KEY_DOWN)
dir = DIR_DOWN;
else if (keycode == KEY_A || keycode == KEY_LEFT)
dir = DIR_LEFT;
else if (keycode == KEY_D || keycode == KEY_RIGHT)
dir = DIR_RIGHT;
else if (keycode == KEY_ESC)
{
close_game(game);
return (0);
}
else
return (0);
// 新しい位置を計算
get_new_position(&game->map.player, dir, &new_x, &new_y);
// 移動を試みる
if (can_move_to(game, new_x, new_y))
{
execute_move(game, new_x, new_y);
}
return (0);
}
衝突検出
移動可能性のチェック
int can_move_to(t_game *game, int x, int y)
{
char tile;
// 境界チェック
if (x < 0 || x >= game->map.width ||
y < 0 || y >= game->map.height)
return (0);
tile = game->map.grid[y][x];
// 壁チェック
if (tile == WALL)
return (0);
// 出口チェック(収集物がすべて集まっていない場合)
if (tile == EXIT && game->map.collected < game->map.collectibles)
return (0);
return (1);
}
タイルの種類による処理
typedef enum e_tile_type
{
TILE_WALL,
TILE_FLOOR,
TILE_COLLECTIBLE,
TILE_EXIT,
TILE_PLAYER
} t_tile_type;
t_tile_type get_tile_type(char c)
{
if (c == '1')
return (TILE_WALL);
else if (c == '0')
return (TILE_FLOOR);
else if (c == 'C')
return (TILE_COLLECTIBLE);
else if (c == 'E')
return (TILE_EXIT);
else if (c == 'P')
return (TILE_PLAYER);
return (TILE_FLOOR);
}
int is_walkable(t_game *game, int x, int y)
{
t_tile_type type;
type = get_tile_type(game->map.grid[y][x]);
if (type == TILE_WALL)
return (0);
if (type == TILE_EXIT)
return (game->map.collected == game->map.collectibles);
return (1);
}
移動の実行
基本的な移動処理
void execute_move(t_game *game, int new_x, int new_y)
{
int old_x;
int old_y;
char target_tile;
old_x = game->map.player.x;
old_y = game->map.player.y;
target_tile = game->map.grid[new_y][new_x];
// 移動先のタイルに応じた処理
handle_tile_interaction(game, target_tile);
// マップの更新
game->map.grid[old_y][old_x] = FLOOR;
game->map.grid[new_y][new_x] = PLAYER;
// プレイヤー位置の更新
game->map.player.x = new_x;
game->map.player.y = new_y;
// 移動回数をインクリメント
game->moves++;
print_moves(game->moves);
// 画面の更新
update_tile(game, old_x, old_y);
update_tile(game, new_x, new_y);
}
タイルとの相互作用
void handle_tile_interaction(t_game *game, char tile)
{
if (tile == COLLECTIBLE)
collect_item(game);
else if (tile == EXIT)
try_exit(game);
}
void collect_item(t_game *game)
{
game->map.collected++;
printf("Collectible gathered! [%d/%d]\n",
game->map.collected, game->map.collectibles);
// すべて集めた場合
if (game->map.collected == game->map.collectibles)
{
printf("All collectibles gathered! Find the exit!\n");
// 出口の見た目を変更するため、出口のタイルを再描画
update_tile(game, game->map.exit.x, game->map.exit.y);
}
}
void try_exit(t_game *game)
{
if (game->map.collected == game->map.collectibles)
{
game->state = STATE_WON;
handle_victory(game);
}
else
{
printf("Collect all items first! [%d/%d]\n",
game->map.collected, game->map.collectibles);
}
}
勝利条件の処理
勝利時の処理
void handle_victory(t_game *game)
{
printf("\n");
printf("════════════════════════════════\n");
printf(" CONGRATULATIONS! \n");
printf(" You completed the level! \n");
printf("════════════════════════════════\n");
printf("Total moves: %d\n", game->moves);
printf("════════════════════════════════\n");
printf("\n");
// ボーナス: 勝利画面を表示
display_victory_screen(game);
// 少し待ってから終了
sleep(3);
close_game(game);
}
スコアリングシステム(ボーナス)
typedef struct s_score
{
int moves;
int collectibles;
int time_seconds;
int rating; // 1-5 stars
} t_score;
int calculate_rating(t_score *score)
{
int optimal_moves;
int rating;
// 最適な移動回数を推定
optimal_moves = score->collectibles * 5;
if (score->moves <= optimal_moves)
rating = 5;
else if (score->moves <= optimal_moves * 1.2)
rating = 4;
else if (score->moves <= optimal_moves * 1.5)
rating = 3;
else if (score->moves <= optimal_moves * 2.0)
rating = 2;
else
rating = 1;
return (rating);
}
void display_score(t_game *game)
{
t_score score;
score.moves = game->moves;
score.collectibles = game->map.collectibles;
score.rating = calculate_rating(&score);
printf("Score: ");
print_stars(score.rating);
printf(" (%d moves)\n", score.moves);
}
void print_stars(int count)
{
int i;
i = 0;
while (i < count)
{
printf("★");
i++;
}
while (i < 5)
{
printf("☆");
i++;
}
}
移動回数の表示
ターミナルへの出力
void print_moves(int moves)
{
ft_putstr("Moves: ");
ft_putnbr(moves);
ft_putstr("\n");
}
ウィンドウ上への表示(ボーナス)
void render_move_counter(t_game *game)
{
char *moves_str;
char *text;
moves_str = ft_itoa(game->moves);
text = ft_strjoin("Moves: ", moves_str);
mlx_string_put(game->mlx_ptr, game->win_ptr,
10, 20, 0xFFFFFF, text);
free(moves_str);
free(text);
}
より洗練されたUI表示
void render_ui(t_game *game)
{
int y_offset;
y_offset = 10;
// 移動回数
render_text(game, 10, y_offset, "MOVES", 0xFFFFFF);
y_offset += 20;
render_number(game, 10, y_offset, game->moves, 0x00FF00);
y_offset += 30;
// 収集物
render_text(game, 10, y_offset, "ITEMS", 0xFFFFFF);
y_offset += 20;
render_collectibles(game, 10, y_offset);
}
void render_collectibles(t_game *game, int x, int y)
{
char *collected_str;
char *total_str;
char *text;
collected_str = ft_itoa(game->map.collected);
total_str = ft_itoa(game->map.collectibles);
text = ft_strjoin(collected_str, "/");
text = ft_strjoin_free(text, total_str);
mlx_string_put(game->mlx_ptr, game->win_ptr, x, y,
game->map.collected == game->map.collectibles ?
0x00FF00 : 0xFFFF00, text);
free(collected_str);
free(total_str);
free(text);
}
アンドゥ機能(ボーナス)
移動履歴の保存
typedef struct s_move_history
{
t_position position;
char tile_was;
int collectibles;
struct s_move_history *next;
} t_move_history;
void save_move(t_game *game, int old_x, int old_y, char old_tile)
{
t_move_history *move;
move = malloc(sizeof(t_move_history));
if (!move)
return;
move->position.x = old_x;
move->position.y = old_y;
move->tile_was = old_tile;
move->collectibles = game->map.collected;
move->next = game->history;
game->history = move;
}
void undo_move(t_game *game)
{
t_move_history *last_move;
int curr_x;
int curr_y;
if (!game->history)
{
printf("No moves to undo\n");
return;
}
last_move = game->history;
// 現在の位置を保存
curr_x = game->map.player.x;
curr_y = game->map.player.y;
// プレイヤーを元の位置に戻す
game->map.player.x = last_move->position.x;
game->map.player.y = last_move->position.y;
// タイルを復元
game->map.grid[curr_y][curr_x] = last_move->tile_was;
game->map.grid[last_move->position.y][last_move->position.x] = PLAYER;
// 収集物の数を復元
game->map.collected = last_move->collectibles;
// 移動回数を減らす
game->moves--;
// 履歴から削除
game->history = last_move->next;
free(last_move);
// 画面を再描画
render_game(game);
printf("Move undone. Moves: %d\n", game->moves);
}
デバッグモード
デバッグ情報の表示
void render_debug_info(t_game *game)
{
int y;
y = game->map.height * TILE_SIZE + 20;
printf("=== DEBUG INFO ===\n");
printf("Player: (%d, %d)\n", game->map.player.x, game->map.player.y);
printf("Moves: %d\n", game->moves);
printf("Collected: %d/%d\n", game->map.collected, game->map.collectibles);
printf("State: %d\n", game->state);
// グリッド座標を表示
draw_grid(game);
}
void draw_grid(t_game *game)
{
int x;
int y;
int i;
// 縦線
i = 0;
while (i <= game->map.width)
{
x = i * TILE_SIZE;
y = 0;
while (y < game->map.height * TILE_SIZE)
{
mlx_pixel_put(game->mlx_ptr, game->win_ptr, x, y, 0x404040);
y += 4;
}
i++;
}
// 横線
i = 0;
while (i <= game->map.height)
{
y = i * TILE_SIZE;
x = 0;
while (x < game->map.width * TILE_SIZE)
{
mlx_pixel_put(game->mlx_ptr, game->win_ptr, x, y, 0x404040);
x += 4;
}
i++;
}
}
ゲームのリセット
リセット機能
void reset_game(t_game *game, char *map_file)
{
// 現在の状態をクリア
free_map(&game->map);
destroy_textures(game);
// ゲームを再初期化
if (!init_game(game, map_file))
{
printf("Failed to reset game\n");
close_game(game);
}
// 画面を再描画
render_game(game);
printf("Game reset\n");
}
// Rキーでリセット
int handle_input(int keycode, t_game *game)
{
// ... 他のキー処理 ...
if (keycode == KEY_R)
{
reset_game(game, game->map_file);
return (0);
}
// ... 続き ...
}
ゲームオーバー条件(ボーナス)
敵キャラクターを追加する場合、ゲームオーバー条件が必要になります。
void handle_enemy_collision(t_game *game)
{
game->state = STATE_LOST;
handle_game_over(game);
}
void handle_game_over(t_game *game)
{
printf("\n");
printf("════════════════════════════════\n");
printf(" GAME OVER! \n");
printf("════════════════════════════════\n");
printf("You were caught!\n");
printf("Moves: %d\n", game->moves);
printf("Press R to restart or ESC to quit\n");
printf("════════════════════════════════\n");
printf("\n");
// ゲームオーバー画面を表示
display_game_over_screen(game);
}
int handle_input_game_over(int keycode, t_game *game)
{
if (keycode == KEY_R)
reset_game(game, game->map_file);
else if (keycode == KEY_ESC)
close_game(game);
return (0);
}
パフォーマンス測定
FPS(フレーム/秒)の計算
typedef struct s_performance
{
struct timeval last_time;
int frame_count;
float fps;
} t_performance;
void update_fps(t_performance *perf)
{
struct timeval current_time;
long elapsed_us;
float elapsed_sec;
perf->frame_count++;
gettimeofday(¤t_time, NULL);
elapsed_us = (current_time.tv_sec - perf->last_time.tv_sec) * 1000000
+ (current_time.tv_usec - perf->last_time.tv_usec);
elapsed_sec = elapsed_us / 1000000.0;
if (elapsed_sec >= 1.0)
{
perf->fps = perf->frame_count / elapsed_sec;
perf->frame_count = 0;
perf->last_time = current_time;
printf("FPS: %.2f\n", perf->fps);
}
}
入力のバッファリング
複数のキーを同時に処理する必要がある場合(ボーナス)。
typedef struct s_input_buffer
{
int keys[256];
int last_key;
} t_input_buffer;
int handle_keypress(int keycode, t_game *game)
{
game->input.keys[keycode] = 1;
game->input.last_key = keycode;
return (0);
}
int handle_keyrelease(int keycode, t_game *game)
{
game->input.keys[keycode] = 0;
return (0);
}
void process_input(t_game *game)
{
if (game->input.keys[KEY_W] || game->input.keys[KEY_UP])
try_move(game, DIR_UP);
else if (game->input.keys[KEY_S] || game->input.keys[KEY_DOWN])
try_move(game, DIR_DOWN);
else if (game->input.keys[KEY_A] || game->input.keys[KEY_LEFT])
try_move(game, DIR_LEFT);
else if (game->input.keys[KEY_D] || game->input.keys[KEY_RIGHT])
try_move(game, DIR_RIGHT);
}
まとめ
この章では、ゲームロジックと移動処理について学びました:
- ゲーム状態管理: 状態の種類と遷移
- 移動システム: キー入力から移動の実行まで
- 衝突検出: 壁や障害物との衝突チェック
- アイテム収集: 収集物の処理と追跡
- 勝利条件: ゲームクリアの判定と表示
- ボーナス機能: スコアリング、アンドゥ、リセット
次の章では、アニメーション、ボーナス機能、そしてゲームの仕上げについて学びます。スプライトアニメーション、敵キャラクター、音響効果など、ゲームをより魅力的にする要素を実装していきます。