第6章:最適化とボーナス機能
はじめに
本章では、パフォーマンス最適化とボーナス機能の実装を学びます。
---
1. パフォーマンス最適化
1.1 ダブルバッファリング
// 画面のちらつきを防ぐ
void render_frame(t_game *game) {
// バックバッファに描画
clear_image(game);
render_walls(game);
render_sprites(game); // ボーナス
// 描画完了後に一度に表示
mlx_put_image_to_window(game->mlx, game->win, game->img, 0, 0);
}
1.2 固定小数点演算
// 浮動小数点 → 固定小数点(8ビット小数部)
#define FP_BITS 8
#define FP_SCALE (1 << FP_BITS)
typedef int fixed_t;
fixed_t float_to_fixed(double f) {
return (fixed_t)(f * FP_SCALE);
}
double fixed_to_float(fixed_t fp) {
return (double)fp / FP_SCALE;
}
fixed_t fixed_mul(fixed_t a, fixed_t b) {
return (a * b) >> FP_BITS;
}
1.3 ルックアップテーブル
#define TABLE_SIZE 4096
double sin_table[TABLE_SIZE];
double cos_table[TABLE_SIZE];
void init_trig_tables(void) {
for (int i = 0; i < TABLE_SIZE; i++) {
double angle = 2.0 * PI * i / TABLE_SIZE;
sin_table[i] = sin(angle);
cos_table[i] = cos(angle);
}
}
double fast_sin(double angle) {
// 角度を0〜2πに正規化
while (angle < 0) angle += 2 * PI;
while (angle >= 2 * PI) angle -= 2 * PI;
int index = (int)(angle * TABLE_SIZE / (2 * PI)) % TABLE_SIZE;
return sin_table[index];
}
1.4 テクスチャキャッシュ
// テクスチャのピクセルをキャッシュフレンドリーに並べ替え
void optimize_texture(t_texture *tex) {
// 列優先で再配置(垂直方向のアクセスが多いため)
unsigned int *optimized = malloc(tex->width * tex->height * sizeof(unsigned int));
for (int x = 0; x < tex->width; x++) {
for (int y = 0; y < tex->height; y++) {
optimized[x * tex->height + y] = get_texture_pixel(tex, x, y);
}
}
// 元のテクスチャデータを置き換え
// ...
}
---
2. ミニマップ(ボーナス)
2.1 ミニマップの描画
#define MINIMAP_SCALE 8
#define MINIMAP_SIZE 200
void draw_minimap(t_game *game) {
int startX = 10;
int startY = 10;
// 背景
draw_rect(game, startX, startY, MINIMAP_SIZE, MINIMAP_SIZE, 0x333333);
// マップを描画
for (int y = 0; y < game->map_height; y++) {
for (int x = 0; x < game->map_width; x++) {
int color = (game->map[y][x] == '1') ? 0xFFFFFF : 0x666666;
int px = startX + x * MINIMAP_SCALE;
int py = startY + y * MINIMAP_SCALE;
if (px < MINIMAP_SIZE && py < MINIMAP_SIZE) {
draw_rect(game, px, py, MINIMAP_SCALE - 1, MINIMAP_SCALE - 1, color);
}
}
}
// プレイヤー位置
int playerPx = startX + (int)(game->player.posX * MINIMAP_SCALE);
int playerPy = startY + (int)(game->player.posY * MINIMAP_SCALE);
draw_circle(game, playerPx, playerPy, 3, 0xFF0000);
// プレイヤーの向き
int dirEndX = playerPx + (int)(game->player.dirX * 10);
int dirEndY = playerPy + (int)(game->player.dirY * 10);
draw_line(game, playerPx, playerPy, dirEndX, dirEndY, 0xFF0000);
}
2.2 レイの視覚化
void draw_minimap_rays(t_game *game) {
int startX = 10;
int startY = 10;
int playerPx = startX + (int)(game->player.posX * MINIMAP_SCALE);
int playerPy = startY + (int)(game->player.posY * MINIMAP_SCALE);
// 視野内のレイを描画(間引いて表示)
for (int x = 0; x < game->screenWidth; x += 10) {
double cameraX = 2 * x / (double)game->screenWidth - 1;
double rayDirX = game->player.dirX + game->player.planeX * cameraX;
double rayDirY = game->player.dirY + game->player.planeY * cameraX;
// レイの長さを計算(簡略化)
double len = 50; // または実際の壁までの距離
int endX = playerPx + (int)(rayDirX * len);
int endY = playerPy + (int)(rayDirY * len);
draw_line(game, playerPx, playerPy, endX, endY, 0x00FF00);
}
}
---
3. ドア(ボーナス)
3.1 ドアの状態管理
typedef struct s_door {
int x;
int y;
double open_amount; // 0.0 = 閉, 1.0 = 開
int is_opening;
} t_door;
#define MAX_DOORS 100
t_door doors[MAX_DOORS];
int door_count = 0;
void update_doors(t_game *game) {
for (int i = 0; i < door_count; i++) {
if (doors[i].is_opening) {
doors[i].open_amount += 0.02;
if (doors[i].open_amount >= 1.0) {
doors[i].open_amount = 1.0;
doors[i].is_opening = 0;
}
}
}
}
3.2 ドアのレンダリング
void render_door(t_game *game, t_ray *ray, int x) {
// ドアまでの距離
double doorDist = ray->perpWallDist;
// ドアの開き具合を考慮
t_door *door = get_door_at(ray->mapX, ray->mapY);
if (door) {
// 開いているドアはレイを通過させる
if (door->open_amount >= 1.0) {
// ドアを通過して次の壁を探す
continue_dda(ray, game);
} else {
// 部分的に開いているドアをレンダリング
// テクスチャのオフセットを計算
}
}
}
---
4. スプライト(ボーナス)
4.1 スプライトデータ
typedef struct s_sprite {
double x;
double y;
int texture_id;
} t_sprite;
t_sprite sprites[MAX_SPRITES];
int sprite_count = 0;
4.2 スプライトのソート
void sort_sprites(t_game *game) {
// プレイヤーからの距離でソート(遠い順)
for (int i = 0; i < sprite_count - 1; i++) {
for (int j = i + 1; j < sprite_count; j++) {
double dist_i = pow(sprites[i].x - game->player.posX, 2) +
pow(sprites[i].y - game->player.posY, 2);
double dist_j = pow(sprites[j].x - game->player.posX, 2) +
pow(sprites[j].y - game->player.posY, 2);
if (dist_i < dist_j) {
t_sprite temp = sprites[i];
sprites[i] = sprites[j];
sprites[j] = temp;
}
}
}
}
4.3 スプライトのレンダリング
void render_sprites(t_game *game) {
sort_sprites(game);
for (int i = 0; i < sprite_count; i++) {
// スプライトの相対位置
double spriteX = sprites[i].x - game->player.posX;
double spriteY = sprites[i].y - game->player.posY;
// カメラ空間への変換
double invDet = 1.0 / (game->player.planeX * game->player.dirY -
game->player.dirX * game->player.planeY);
double transformX = invDet * (game->player.dirY * spriteX -
game->player.dirX * spriteY);
double transformY = invDet * (-game->player.planeY * spriteX +
game->player.planeX * spriteY);
// 画面上の位置
int spriteScreenX = (int)((game->screenWidth / 2) *
(1 + transformX / transformY));
// スプライトの高さ
int spriteHeight = abs((int)(game->screenHeight / transformY));
int drawStartY = -spriteHeight / 2 + game->screenHeight / 2;
int drawEndY = spriteHeight / 2 + game->screenHeight / 2;
// スプライトの幅
int spriteWidth = abs((int)(game->screenHeight / transformY));
int drawStartX = -spriteWidth / 2 + spriteScreenX;
int drawEndX = spriteWidth / 2 + spriteScreenX;
// スプライトを描画
for (int stripe = drawStartX; stripe < drawEndX; stripe++) {
if (transformY > 0 && stripe > 0 && stripe < game->screenWidth &&
transformY < z_buffer[stripe]) {
// テクスチャを描画
// ...
}
}
}
}
---
5. マウス操作(ボーナス)
5.1 マウスでの視点回転
int mouse_move(int x, int y, t_game *game) {
static int last_x = -1;
(void)y;
if (last_x == -1) {
last_x = x;
return (0);
}
int dx = x - last_x;
double rot_speed = dx * 0.002;
rotate_player(&game->player, rot_speed);
// マウスを中央に戻す(macOSでは動作しない場合あり)
// mlx_mouse_move(game->mlx, game->win, game->screenWidth / 2, game->screenHeight / 2);
// last_x = game->screenWidth / 2;
last_x = x;
return (0);
}
void setup_mouse_hooks(t_game *game) {
mlx_hook(game->win, 6, 1L<<6, mouse_move, game); // MotionNotify
}
---
6. FPS表示
6.1 フレームレート計算
#include <sys/time.h>
typedef struct s_fps {
struct timeval last_time;
int frame_count;
double fps;
} t_fps;
void update_fps(t_fps *fps) {
struct timeval current_time;
gettimeofday(¤t_time, NULL);
double elapsed = (current_time.tv_sec - fps->last_time.tv_sec) +
(current_time.tv_usec - fps->last_time.tv_usec) / 1000000.0;
fps->frame_count++;
if (elapsed >= 1.0) {
fps->fps = fps->frame_count / elapsed;
fps->frame_count = 0;
fps->last_time = current_time;
}
}
void draw_fps(t_game *game) {
char fps_str[32];
sprintf(fps_str, "FPS: %.1f", game->fps.fps);
mlx_string_put(game->mlx, game->win, 10, game->screenHeight - 20,
0xFFFFFF, fps_str);
}
---
まとめ
本章で学んだこと:
- パフォーマンス最適化: ダブルバッファ、固定小数点、LUT
- ミニマップ: 2D表示とレイ可視化
- ドア: 開閉状態の管理
- スプライト: ソートとレンダリング
- マウス操作: 視点回転
これでcub3Dガイドは完了です。