介绍
蛇游戏系列的延续。这个版本使用C + Raylib,并且将会有另一个版本使用Zig + Raylib。你可能想知道为什么我要写这个贪吃蛇游戏的多个版本?我的游戏开发会话通常是在 Godot 4 中完成的,但我一直很好奇在 Raylib 中进行游戏开发时有何不同。
什么是Raylib ?
摘自网站:享受视频游戏编程的编程库;没有花哨的界面,没有可视化助手,没有 GUI 工具或编辑器……只是以纯粹的斯巴达程序员的方式编码。
要点:“纯粹的斯巴达程序员之道”。
是的,与使用 Godot 4 相比,使用 Raylib 时我确实感觉自己像一个斯巴达战士!相比之下,Godot 中的 Gamedev 简直就是在公园里散步。
注意:与ASCII Snake代码相比,您会看到一个超级相似的代码。
#1 图书馆
#include <raylib.h> #include <stdio.h> #include <stdlib.h> #include <time.h>
#2 常量、结构和全局变量
#define GRID_SIZE 25 #define SCREEN_WIDTH 800 #define SCREEN_HEIGHT 600 #define MAX_SCORE 256 typedef struct { Vector2 position; } Vec2; Vec2 head = 0; Vec2 segments[MAX_SCORE + 1]; Vec2 dir = 1; Vec2 berry; int score = 0; char score_message[32]; bool skip = false; bool is_running = true;
#3 函数声明
void game_over(); void init(); void quit_game(); void restart_game(); bool collide(Vec2 a, Vec2 b); bool collide_snake_body(Vec2 point); Vec2 spawn_berry(); void process_input(); void draw(); void update();
#4 碰撞:碰撞函数
- 碰撞:检查两个点(a 和 b)是否具有相同的坐标。
- collide_snake_body:检查给定点是否是蛇身体的一部分。
bool collide(Vec2 a, Vec2 b) { return (a.position.x == b.position.x && a.position.y == b.position.y); } bool collide_snake_body(Vec2 point) { for (int i = 0; i < score; i++) { if (collide(point, segments[i])) { return true; } } return false; }
#5 产卵浆果
为新浆果生成随机位置,确保它不会与蛇的身体或头部重叠。
Vec2 spawn_berry() { Vec2 berry = 1; while (collide(head, berry) || collide_snake_body(berry)) { berry.position.x = 1 + rand() % (GRID_SIZE - 2); berry.position.y = 1 + rand() % (GRID_SIZE - 2); } return berry; }
#6 退出并重新启动
- quit_game:彻底退出游戏,恢复终端状态。
- restart_game:将游戏状态重置为初始值。
void quit_game() { CloseWindow(); } void restart_game() { head.position.x = 0; head.position.y = 0; dir.position.x = 1; dir.position.y = 0; score = 0; sprintf(score_message, "[ Score: %d ]", score); is_running = true; }
#7 初始化函数
通过设置窗口、随机数生成器和初始游戏状态来初始化游戏。
void init() { srand(time(NULL)); InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "snake"); SetTargetFPS(8); berry = spawn_berry(); sprintf(score_message, "[ Score: %d ]", score); }
#8 运动 – process_input
处理用户输入,例如按下箭头键和按下空格/退出键。
void process_input() { if (IsKeyPressed(KEY_LEFT)) { if (dir.position.x == 1) return; dir.position.x = -1; dir.position.y = 0; } if (IsKeyPressed(KEY_RIGHT)) { if (dir.position.x == -1) return; dir.position.x = 1; dir.position.y = 0; } if (IsKeyPressed(KEY_UP)) { if (dir.position.y == 1) return; dir.position.x = 0; dir.position.y = -1; } if (IsKeyPressed(KEY_DOWN)) { if (dir.position.y == -1) return; dir.position.x = 0; dir.position.y = 1; } if (IsKeyPressed(KEY_SPACE)) { if (!is_running) restart_game(); } if (IsKeyPressed(KEY_ESCAPE)) { is_running = false; quit_game(); } }
#9 游戏结束状态
显示游戏结束屏幕以及重新启动和退出选项。
void game_over() { int gameOverWidth = MeasureText("Game Over", 30); int gameOverX = (SCREEN_WIDTH - gameOverWidth) / 2; DrawText("GAME OVER", gameOverX - 20, SCREEN_HEIGHT/2 - 20, 36, RED); int restartWidth = MeasureText("[SPACE] to restart, [ESC] to quit", 20); int restartX = (SCREEN_WIDTH - restartWidth) / 2; DrawText("[SPACE] to restart, [ESC] to quit", restartX + 5, SCREEN_HEIGHT/2 + 50, 20, ORANGE); }
#10 更新游戏状态
通过移动蛇、检查碰撞和更新分数来更新游戏状态。
void update() { for (int i = score; i > 0; i--) { segments[i] = segments[i - 1]; } segments[0] = head; head.position.x += dir.position.x; head.position.y += dir.position.y; if (collide_snake_body(head) || head.position.x < 0 || head.position.y < 0 \ || head.position.x >= GRID_SIZE || head.position.y >= GRID_SIZE) { is_running = false; } if (collide(head, berry)) { if (score < MAX_SCORE) { score += 1; sprintf(score_message, "[ Score: %d ]", score); } else { printf("You Win!"); } berry = spawn_berry(); } }
#11 绘图功能
渲染游戏屏幕,包括蛇、浆果、得分和游戏结束屏幕(如果适用)。
void draw() { BeginDrawing(); ClearBackground(BLACK); DrawRectangle(berry.position.x * SCREEN_WIDTH / GRID_SIZE, berry.position.y * SCREEN_HEIGHT / GRID_SIZE, SCREEN_WIDTH / GRID_SIZE, SCREEN_HEIGHT / GRID_SIZE, BLUE); for (int i = 0; i < score; i++) { DrawRectangle(segments[i].position.x * SCREEN_WIDTH / GRID_SIZE, segments[i].position.y * SCREEN_HEIGHT / GRID_SIZE, SCREEN_WIDTH / GRID_SIZE, SCREEN_HEIGHT / GRID_SIZE, GREEN); } DrawRectangle(head.position.x * SCREEN_WIDTH / GRID_SIZE, head.position.y * SCREEN_HEIGHT / GRID_SIZE, SCREEN_WIDTH / GRID_SIZE, SCREEN_HEIGHT / GRID_SIZE, YELLOW); DrawRectangleLines(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, WHITE); int score_text = MeasureText("[ Score: {} ]", 24); int score_x = (SCREEN_WIDTH - score_text) / 2; DrawText(score_message, score_x, 10, 24, WHITE); int lang_text = MeasureText("C + Raylib", 24); int lang_x = (SCREEN_WIDTH - lang_text) / 2; DrawText("C + Raylib", lang_x, SCREEN_HEIGHT - 60, 24, WHITE); if (!is_running) { game_over(); } EndDrawing(); }
#12 主要
初始化游戏并运行游戏循环。
int main(void) { init(); while (!WindowShouldClose()) { process_input(); if (skip) { skip = false; continue; } if (is_running) { update(); } draw(); } quit_game(); return 0; }