游戏循环是游戏的心脏,驱动整个游戏运行。
游戏循环是一个无限循环,不断执行以下步骤:
┌─────────────────────────────────┐
│ 游戏开始 │
└────────────┬────────────────────┘
↓
┌────────────────────────────────┐
│ 1. 处理输入 (Input) │
└────────────┬───────────────────┘
↓
┌────────────────────────────────┐
│ 2. 更新逻辑 (Update) │
└────────────┬───────────────────┘
↓
┌────────────────────────────────┐
│ 3. 渲染画面 (Render) │
└────────────┬───────────────────┘
↓
继续循环?
╱ ╲
是 否
↓ ↓
继续循环 退出游戏
#include <stdbool.h>
int main(void) {
bool running = true;
// 初始化
init_game();
// 游戏循环
while (running) {
// 1. 处理输入
handle_input(&running);
// 2. 更新游戏状态
update_game();
// 3. 渲染画面
render_game();
// 4. 控制帧率
sleep_ms(16); // 约 60 FPS
}
// 清理
cleanup_game();
return 0;
}
void game_run(Game* game) {
int last_update = 0;
int current_time = 0;
while (game->running) {
// 1. 处理输入
int key = input_get_key();
if (key != ERR) {
game_handle_input(game, key);
}
// 2. 更新游戏(根据难度控制速度)
current_time++;
int update_interval = game->speed;
if (game->state == STATE_PLAYING &&
current_time - last_update >= update_interval) {
game_update(game);
last_update = current_time;
}
// 3. 渲染
game_render(game);
// 4. 短暂休眠,避免 CPU 占用过高
sleep_ms(10);
}
}
void handle_input(bool* running) {
int key = getch(); // ncurses 获取按键
if (key == 'q') {
*running = false;
return;
}
switch (key) {
case KEY_UP:
case 'w':
snake_set_direction(DIR_UP);
break;
case KEY_DOWN:
case 's':
snake_set_direction(DIR_DOWN);
break;
case KEY_LEFT:
case 'a':
snake_set_direction(DIR_LEFT);
break;
case KEY_RIGHT:
case 'd':
snake_set_direction(DIR_RIGHT);
break;
case 'p':
toggle_pause();
break;
}
}
void update_game(Game* game) {
// 1. 移动蛇
snake_move(game->snake);
// 2. 检查食物碰撞
if (snake_eats_food(game->snake, game->food)) {
score_add(&game->score, 10);
snake_grow(game->snake);
food_spawn(game->food);
}
// 3. 检查死亡碰撞
if (snake_hit_wall(game->snake) ||
snake_hit_self(game->snake)) {
game->state = STATE_GAME_OVER;
}
}
void render_game(Game* game) {
// 清屏
clear();
// 绘制边框
draw_border();
// 绘制蛇
snake_render(game->snake);
// 绘制食物
food_render(game->food);
// 绘制分数
draw_score(game->score);
// 刷新屏幕
refresh();
}
// 方法 1:固定延迟
void sleep_ms(int ms) {
struct timespec ts;
ts.tv_sec = ms / 1000;
ts.tv_nsec = (ms % 1000) * 1000000;
nanosleep(&ts, NULL);
}
// 方法 2:基于时间戳
#include <time.h>
double get_time(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec + ts.tv_nsec / 1e9;
}
void game_loop(void) {
double last_time = get_time();
double target_fps = 60.0;
double frame_time = 1.0 / target_fps;
while (running) {
double current = get_time();
double delta = current - last_time;
if (delta >= frame_time) {
update_game();
last_time = current;
}
render_game();
}
}
FPS (Frames Per Second) = 每秒帧数
60 FPS = 每帧 16.67ms
30 FPS = 每帧 33.33ms
10 FPS = 每帧 100ms
贪吃蛇通常 10-20 FPS 就够了
// 计算并显示 FPS
int frame_count = 0;
double fps_timer = 0;
double fps = 0;
void update_fps(double delta) {
frame_count++;
fps_timer += delta;
if (fps_timer >= 1.0) {
fps = frame_count / fps_timer;
frame_count = 0;
fps_timer = 0;
}
}
void render_fps(void) {
printf("FPS: %.1f\n", fps);
}
#include <stdio.h>
#include <ncurses.h>
#include <unistd.h>
#include <stdbool.h>
typedef struct {
int x, y;
int score;
bool running;
} Game;
void init(Game* g) {
initscr();
cbreak();
noecho();
keypad(stdscr, TRUE);
nodelay(stdscr, TRUE);
g->x = 10;
g->y = 10;
g->score = 0;
g->running = true;
}
void handle_input(Game* g) {
int key = getch();
switch (key) {
case 'q': g->running = false; break;
case KEY_UP: g->y--; break;
case KEY_DOWN: g->y++; break;
case KEY_LEFT: g->x--; break;
case KEY_RIGHT: g->x++; break;
}
}
void update(Game* g) {
// 游戏逻辑
}
void render(Game* g) {
clear();
mvprintw(g->y, g->x, "O");
mvprintw(0, 0, "Score: %d | Press 'q' to quit", g->score);
refresh();
}
void cleanup(void) {
endwin();
}
int main(void) {
Game game;
init(&game);
while (game.running) {
handle_input(&game);
update(&game);
render(&game);
usleep(100000); // 100ms = 10 FPS
}
cleanup();
return 0;
}
实现一个简单的小球弹跳动画(使用游戏循环)
为贪吃蛇添加暂停功能(按 P 键)
添加 FPS 显示
下一课:ncurses 库
游戏通常有多种状态(开始菜单、游戏中、游戏结束):
typedef enum {
STATE_MENU,
STATE_PLAYING,
STATE_PAUSED,
STATE_GAME_OVER
} GameState;
void game_loop(Game* game) {
while (game->running) {
handle_input(game);
switch (game->state) {
case STATE_MENU:
update_menu(game);
render_menu(game);
break;
case STATE_PLAYING:
update_game(game);
render_game(game);
break;
case STATE_PAUSED:
render_pause_screen(game);
break;
case STATE_GAME_OVER:
render_game_over(game);
break;
}
usleep(100000);
}
}
详细讲解见 第 14 课:状态机