每天一個(gè)C語(yǔ)言小項(xiàng)目,提升你的編程能力!
密室逃脫相信大部分都玩過了吧?本游戲就是一種用C語(yǔ)言寫的類似的游戲,因?yàn)橛檬蛛娡舱彰髡衣罚杂悬c(diǎn)像礦工的樣子,還是叫它礦井逃生吧!(以下是游戲的簡(jiǎn)單介紹和源代碼展示)
游戲說明:
礦井里的電路又出問題了。迅速借助你的頭燈,在漆黑的礦井里找到出口逃出去吧。
控制說明:
方向鍵:移動(dòng)
A/S/D/W:移動(dòng)
鼠標(biāo):控制照射方向
F2:重來一局
ESC:退出游戲
效果圖展示:

游戲一開始會(huì)給你一副地圖(能記住多少就看你自己咯),然后地圖會(huì)慢慢隱入黑暗(會(huì)關(guān)燈變黑一樣),只留下一個(gè)拿著小手電筒的礦工,如下圖所示:

手電筒的照明方向可以隨著你的鼠標(biāo)的移動(dòng)而移動(dòng),然后通過上下左右方向鍵來控制走向,直至走到最下方的出口。
簡(jiǎn)單了解游戲后我們就來試試吧!
本項(xiàng)目編譯環(huán)境:Visual Studio 2019/2022,EasyX插件
代碼展示:
1.定義變量和一些必要的常量
#include #include #include #include // 定義常量 #define PI 3.141592653589 // 圓周率 #define UNIT_GROUND 0 // 表示地面 #define UNIT_WALL 1 // 表示墻 #define LIGHT_A PI / 3 // 燈光的角度范圍 #define LIGHT_R 120 // 燈光的照射距離 #define WIDTH 480 // 礦井的寬度 #define HEIGHT 480 // 礦井的高度 #define SCREENWIDTH 640 // 屏幕寬度 #define SCREENHEIGHT 480 // 屏幕高度 #define UNIT 20 // 每個(gè)墻壁單位的大小 #define PLAYER_R 5 // 游戲者的半徑 // 定義常量 const SIZE g_utMap = {23, 23}; // 礦井地圖的尺寸(基于 UNIT 單位) const POINT g_utPlayer = {1, 1}; // 游戲者的位置(基于 UNIT 單位) const POINT g_utExit = {21, 22}; // 出口位置(基于 UNIT 單位) const POINT g_ptOffset = {10, 10}; // 礦井顯示在屏幕上的偏移量 ////////////////////////////////////////////////////// // 定義全局變量 // POINT g_ptPlayer; // 游戲者的位置 POINT g_ptMouse; // 鼠標(biāo)位置 IMAGE g_imgMap(WIDTH, HEIGHT); // 礦井平面圖 DWORD* g_bufMap; // 礦井平面圖的顯示緩沖區(qū)指針 IMAGE g_imgRender(WIDTH, HEIGHT); // 渲染 DWORD* g_bufRender; // 渲染的顯示緩沖區(qū)指針 DWORD* g_bufScreen; // 屏幕的顯示緩沖區(qū)指針 // 枚舉用戶的控制命令 enum CMD { CMD_QUIT = 1, CMD_UP = 2, CMD_DOWN = 4, CMD_LEFT = 8, CMD_RIGHT = 16, CMD_RESTART = 32 };
2.函數(shù)聲明
// 初始化 void Welcome(); // 繪制游戲界面 void ReadyGo(); // 準(zhǔn)備開始游戲 void InitGame(); // 初始化游戲數(shù)據(jù) // 礦井生成 void MakeMaze(int width, int height); // 初始化(注:寬高必須是奇數(shù)) void TravelMaze(int x, int y, BYTE** aryMap); // 遍歷 (x, y) 四周 void DrawWall(int x, int y, bool left, bool top, bool right, bool bottom); // 畫一面墻 // 繪制 void Paint(); // 繪制視野范圍內(nèi)的礦井 void Lighting(int _x, int _y, double _a); // 在指定位置和角度“照明” void DrawPlayer(); // 繪制游戲者 void DrawExit(); // 繪制出口 // 處理用戶控制 int GetCmd(); // 獲取用戶輸入的命令 void OnUp(); // 向上移動(dòng) void OnLeft(); // 向左移動(dòng) void OnRight(); // 向右移動(dòng) void OnDown(); // 向下移動(dòng) bool CheckWin(); // 檢查是否到出口
3.準(zhǔn)備開始游戲
void ReadyGo()
{
// 初始化
InitGame();
// 停電前兆
int time[7] = {1000, 50, 500, 50, 50, 50, 50};
int i, x, y;
for (i = 0; i < 7; i++)
{
if (i % 2 == 0)
{
putimage(0, 0, &g_imgMap);
DrawPlayer();
DrawExit();
}
else
clearrectangle(0, 0, WIDTH - 1, HEIGHT - 1);
Sleep(time[i]);
}
// 電力緩慢中斷
for (i = 255; i >= 0; i -= 5)
{
for (y = (HEIGHT - 1) * SCREENWIDTH; y >= 0; y -= SCREENWIDTH)
for (x = 0; x < WIDTH; x++)
if (g_bufScreen[y + x] != 0)
g_bufScreen[y + x] = g_bufScreen[y + x] - 0x050505;
DrawPlayer();
DrawExit();
Sleep(50);
}
// 繪制游戲區(qū)
Paint();
}
4.繪制游戲界面
void Welcome()
{
setfillcolor(DARKGRAY);
solidrectangle(WIDTH, 0, SCREENWIDTH - 1, SCREENHEIGHT - 1);
// 設(shè)置字體樣式
settextcolor(WHITE);
setbkmode(TRANSPARENT);
// 繪制標(biāo)題
settextstyle(24, 0, _T("宋體"));
outtextxy(512, 40, _T("礦井逃生"));
// 繪制操作說明
RECT r = {488, 100, 632, 470};
settextstyle(12, 0, _T("宋體"));
drawtext(_T("[游戲說明]
礦井里的電路又出問題了。迅速借助你的頭燈,在漆黑的礦井里 找到出口逃出去吧。
[控制說明]
方向鍵: 移動(dòng)
A/S/D/W:移動(dòng)
鼠標(biāo): 控制照射 方向
F2: 重來一局
ESC:退出游戲"), &r, DT_WORDBREAK);
outtextxy(495, 465, _T("Powered by yw80@qq.com"));
}
5.初始化游戲數(shù)據(jù)
void InitGame()
{
// 獲得窗口顯示緩沖區(qū)指針
g_bufRender = GetImageBuffer(&g_imgRender);
g_bufMap = GetImageBuffer(&g_imgMap);
g_bufScreen = GetImageBuffer(NULL);
// 設(shè)置 Render 環(huán)境
SetWorkingImage(&g_imgRender);
setbkmode(TRANSPARENT);
SetWorkingImage(NULL);
// 創(chuàng)建礦井
MakeMaze(g_utMap.cx, g_utMap.cy);
// 設(shè)置游戲者位置
g_ptPlayer.x = g_utPlayer.x * UNIT + UNIT / 2 + g_ptOffset.x;
g_ptPlayer.y = g_utPlayer.y * UNIT + UNIT / 2 + g_ptOffset.y;
}
6.生成礦井:初始化(注:寬高必須是奇數(shù))
void MakeMaze(int width, int height)
{
if (width % 2 != 1 || height % 2 != 1)
return;
int x, y;
// 定義礦井二維數(shù)組,并初始化全部為墻壁
// 寬高比實(shí)際多 2,是因?yàn)閮啥烁饔幸粋€(gè)“哨兵”,用于方便處理數(shù)據(jù)
BYTE** aryMap = new BYTE*[width + 2];
for(x = 0; x < width + 2; x++)
{
aryMap[x] = new BYTE[height + 2];
memset(aryMap[x], UNIT_WALL, height + 2);
}
// 定義邊界(哨兵功能)
for (x = 0; x <= width + 1; x++)
aryMap[x][0] = aryMap[x][height + 1] = UNIT_GROUND;
for (y = 1; y <= height; y++)
aryMap[0][y] = aryMap[width + 1][y] = UNIT_GROUND;
// 從任意點(diǎn)開始遍歷生成礦井
TravelMaze(((rand() % (width - 1)) & 0xfffe) + 2, ((rand() % (height - 1)) & 0xfffe) + 2, aryMap);
// 設(shè)置出口
aryMap[g_utExit.x + 1][g_utExit.y + 1] = UNIT_GROUND;
// 將礦井繪制在 IMAGE 對(duì)象上
SetWorkingImage(&g_imgMap);
cleardevice();
for (y = 1; y <= height; y++)
for (x = 1; x <= width; x++)
if (aryMap[x][y] == UNIT_WALL)
DrawWall(x, y, aryMap[x - 1][y] == UNIT_WALL,
aryMap[x][y - 1] == UNIT_WALL,
aryMap[x + 1][y] == UNIT_WALL,
aryMap[x][y + 1] == UNIT_WALL);
SetWorkingImage(NULL);
}
7.生成礦井:遍歷 (x, y) 四周
void TravelMaze(int x, int y, BYTE** aryMap)
{
// 定義遍歷方向
int d[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
// 將遍歷方向亂序
int n, t, i;
for(i = 0; i < 4; i++)
{
n = rand() % 4;
t = d[i][0], d[i][0] = d[n][0], d[n][0] = t;
t = d[i][1], d[i][1] = d[n][1], d[n][1] = t;
}
// 嘗試周圍四個(gè)方向
aryMap[x][y] = UNIT_GROUND;
for(i = 0; i < 4; i++)
if (aryMap[x + 2 * d[i][0]][y + 2 * d[i][1]] == UNIT_WALL)
{
aryMap[x + d[i][0]][y + d[i][1]] = UNIT_GROUND;
TravelMaze(x + d[i][0] * 2, y + d[i][1] * 2, aryMap); // 遞歸
}
}
8.生成礦井:畫一面墻
void DrawWall(int x, int y, bool left, bool top, bool right, bool bottom)
{
// 墻壁厚 4 pixel
int cx, cy;
cx = x * UNIT - UNIT / 2 - 2 + 10;
cy = y * UNIT - UNIT / 2 - 2 + 10;
if (left) solidrectangle(x * UNIT - UNIT + 10, cy, cx + 4, cy + 4);
if (top) solidrectangle(cx, y * UNIT - UNIT + 10, cx + 4, cy + 4);
if (right) solidrectangle(cx, cy, x * UNIT + 9, cy + 4);
if (bottom) solidrectangle(cx, cy, cx + 4, y * UNIT + 9);
}
9.繪制視野范圍內(nèi)的礦井
void Paint()
{
// 設(shè)置繪圖目標(biāo)為 Render 對(duì)象
SetWorkingImage(&g_imgRender);
// 清空 Render 對(duì)象
cleardevice();
// 計(jì)算視野角度
double dx, dy, a;
dx = g_ptMouse.x - g_ptPlayer.x;
dy = g_ptMouse.y - g_ptPlayer.y;
if (dx == 0 && dy == 0)
a = 0;
else if (dx != 0 && dy != 0)
a = atan(dy / dx);
else if (dx == 0)
a = (dy > 0) ? PI / 2 : PI * 3 / 2;
else
a = 0;
if (dx < 0) a += PI;
if (a < 0) a += PI * 2;
// 繪制燈光
Lighting(g_ptPlayer.x, g_ptPlayer.y, a);
// 畫游戲者
DrawPlayer();
// 畫出口
DrawExit();
// 設(shè)置繪圖目標(biāo)為窗口
SetWorkingImage(NULL);
// 顯示到窗口上
putimage(0, 0, &g_imgRender);
}
10.做手電筒(在指定位置和角度“照明”)
void Lighting(int _x, int _y, double _a)
{
int i; // 定義循環(huán)變量
int x, y; // 定義臨時(shí)坐標(biāo)
double a; // 定義臨時(shí)角度
// 計(jì)算燈光照亮的角度區(qū)域
double a1 = _a - LIGHT_A / 2;
double a2 = _a + LIGHT_A / 2;
for(a = a1; a < a2; a += PI / 360) // 扇形循環(huán)
{
for(int r = 0; r < LIGHT_R; r++) // 半徑循環(huán)
{
// 計(jì)算照射到的位置
x = (int)(_x + cos(a) * r);
y = (int)(_y + sin(a) * r);
// 光線超出屏幕范圍,終止
// (為了簡(jiǎn)化全憑模糊運(yùn)算,不處理最上和最下一行)
if (x < 0 || x >= WIDTH || y <= 0 || y >= HEIGHT - 1)
break;
// 光線碰到建筑物,終止
if(g_bufMap[y * WIDTH + x])
break;
// 光線疊加
g_bufRender[y * WIDTH + x] += 0x202000; // 0x202000 是很淡的黃色
}
}
// 計(jì)算光照扇形區(qū)域的最小包圍矩形
// 方法:獲得 7 個(gè)點(diǎn)的最值:圓心、圓弧兩端、圓與 xy 軸的 4 個(gè)交點(diǎn)
// 第一步:初始化 7 個(gè)點(diǎn)
POINT pt[7];
pt[0].x = _x; pt[0].y = _y;
pt[1].x = int(_x + LIGHT_R * cos(a1) + 0.5); pt[1].y = int(_y + LIGHT_R * sin(a1) + 0.5);
pt[2].x = int(_x + LIGHT_R * cos(a2) + 0.5); pt[2].y = int(_y + LIGHT_R * sin(a2) + 0.5);
for (a = ceil(a1 * 4 / (2 * PI)) * (PI / 2), i = 3; a < a2; a += PI / 2, i++)
{
pt[i].x = int(_x + LIGHT_R * cos(a) + 0.5);
pt[i].y = int(_y + LIGHT_R * sin(a) + 0.5);
}
// 第二步:獲取 7 個(gè)點(diǎn)的最大最小值,得到最小包圍矩形
i--;
RECT r = {pt[i].x, pt[i].y, pt[i].x, pt[i].y};
for (--i; i >= 0; i--)
{
if (pt[i].x < r.left) r.left = pt[i].x;
if (pt[i].x > r.right) r.right = pt[i].x;
if (pt[i].y < r.top) r.top = pt[i].y;
if (pt[i].y > r.bottom) r.bottom = pt[i].y;
}
// 調(diào)整矩形范圍
if (r.left < 0) r.left = 0;
if (r.top < 1) r.top = 1;
if (r.right >= WIDTH) r.right = WIDTH - 1;
if (r.bottom >= HEIGHT - 1) r.bottom = HEIGHT - 2;
// 修正曝光過度的點(diǎn)
for (y = r.top; y <= r.bottom; y++)
for (x = r.left; x <= r.right; x++)
{
i = y * WIDTH + x;
if (g_bufRender[i] > 0xffff00)
g_bufRender[i] = 0xffff00;
}
// 將光線模糊處理(避開建筑物)
for (y = r.top; y <= r.bottom; y++)
for (x = r.left; x <= r.right; x++)
{
i = y * WIDTH + x;
if (!g_bufMap[i])
g_bufRender[i] = RGB(
(GetRValue(g_bufRender[i - WIDTH]) + GetRValue(g_bufRender[i - 1]) + GetRValue(g_bufRender[i])
+ GetRValue(g_bufRender[i + 1]) + GetRValue(g_bufRender[i + WIDTH])) / 5,
(GetGValue(g_bufRender[i - WIDTH]) + GetGValue(g_bufRender[i - 1]) + GetGValue(g_bufRender[i])
+ GetGValue(g_bufRender[i + 1]) + GetGValue(g_bufRender[i + WIDTH])) / 5,
(GetBValue(g_bufRender[i - WIDTH]) + GetBValue(g_bufRender[i - 1]) + GetBValue(g_bufRender[i])
+ GetBValue(g_bufRender[i + 1]) + GetBValue(g_bufRender[i + WIDTH])) / 5);
}
}
11.繪制游戲者和游戲出口
void DrawPlayer()
{
// 畫安全帽
setlinecolor(DARKGRAY);
circle(g_ptPlayer.x, g_ptPlayer.y, 5);
}
void DrawExit()
{
settextstyle(12, 0, _T("宋體"));
outtextxy(g_utExit.x * UNIT + g_ptOffset.x, g_utExit.y * UNIT + g_ptOffset.y + 8, _T("出口"));
}
12.實(shí)現(xiàn)按鍵控制(上下左右/ASDW)
int GetCmd()
{
int c = 0;
if ((GetAsyncKeyState(VK_LEFT) & 0x8000) ||
(GetAsyncKeyState('A') & 0x8000)) c |= CMD_LEFT;
if ((GetAsyncKeyState(VK_RIGHT) & 0x8000) ||
(GetAsyncKeyState('D') & 0x8000)) c |= CMD_RIGHT;
if ((GetAsyncKeyState(VK_UP) & 0x8000) ||
(GetAsyncKeyState('W') & 0x8000)) c |= CMD_UP;
if ((GetAsyncKeyState(VK_DOWN) & 0x8000) ||
(GetAsyncKeyState('S') & 0x8000)) c |= CMD_DOWN;
if (GetAsyncKeyState(VK_F2) & 0x8000) c |= CMD_RESTART;
if (GetAsyncKeyState(VK_ESCAPE) & 0x8000) c |= CMD_QUIT;
MOUSEMSG m;
while(MouseHit())
{
m = GetMouseMsg();
g_ptMouse.x = m.x;
g_ptMouse.y = m.y;
}
return c;
}
// 向上移動(dòng)
void OnUp()
{
int i = (g_ptPlayer.y - 6) * WIDTH + (g_ptPlayer.x - 5) + 1;
int j;
for (j = 0; j < 5; j++, i += 2)
if (g_bufMap[i])
break;
if (j == 5)
g_ptPlayer.y--;
}
// 向左移動(dòng)
void OnLeft()
{
int i = (g_ptPlayer.y - 5) * WIDTH + (g_ptPlayer.x - 5);
int j;
for (j = 0; j < 5; j++, i += WIDTH)
if (g_bufMap[i])
break;
if (j == 5)
g_ptPlayer.x--;
}
// 向右移動(dòng)
void OnRight()
{
int i = (g_ptPlayer.y - 5) * WIDTH + (g_ptPlayer.x + 5) + 1;
int j;
for (j = 0; j < 5; j++, i += WIDTH)
if (g_bufMap[i])
break;
if (j == 5)
g_ptPlayer.x++;
}
// 向下移動(dòng)
void OnDown()
{
int i = (g_ptPlayer.y + 5) * WIDTH + (g_ptPlayer.x - 5) + 1;
int j;
for (j = 0; j < 5; j++, i += 2)
if (g_bufMap[i])
break;
if (j == 5)
g_ptPlayer.y++;
}
13.判斷游戲者是否到了出口,做個(gè)彈窗
bool CheckWin()
{
return (g_ptPlayer.y >= g_utExit.y * UNIT + UNIT / 2 + g_ptOffset.y);
}
14.主函數(shù)放到最后寫,思路更清晰
void main()
{
// 初始化
initgraph(SCREENWIDTH, SCREENHEIGHT); // 創(chuàng)建繪圖窗口
srand((unsigned)time(NULL)); // 設(shè)置隨機(jī)種子
// 顯示主界面
Welcome();
// 游戲過程
int c;
do
{
ReadyGo();
while(true)
{
// 獲得用戶輸入
c = GetCmd();
// 處理用戶輸入
if (c & CMD_UP) OnUp();
if (c & CMD_DOWN) OnDown();
if (c & CMD_LEFT) OnLeft();
if (c & CMD_RIGHT) OnRight();
if (c & CMD_RESTART)
{
if (MessageBox(GetHWnd(), _T("您要重來一局嗎?"), _T("詢問"), MB_OKCANCEL | MB_ICONQUESTION) == IDOK)
break;
}
if (c & CMD_QUIT)
{
if (MessageBox(GetHWnd(), _T("您確定要退出游戲嗎?"), _T("詢問"), MB_OKCANCEL | MB_ICONQUESTION) == IDOK)
break;
}
// 繪制場(chǎng)景
Paint();
// 判斷是否走出礦井
if (CheckWin())
{
// 是否再來一局
HWND hwnd = GetHWnd();
if (MessageBox(hwnd, _T("恭喜你走出來了!
您想再來一局嗎?"), _T("恭喜"), MB_YESNO | MB_ICONQUESTION) != IDYES)
c = CMD_QUIT;
break;
}
// 延時(shí)
Sleep(16);
}
}
while(!(c & CMD_QUIT));
// 關(guān)閉圖形模式
closegraph();
}
大家趕緊去動(dòng)手試試吧!
審核編輯:湯梓紅
-
游戲
+關(guān)注
關(guān)注
2文章
788瀏覽量
27352 -
C語(yǔ)言
+關(guān)注
關(guān)注
183文章
7642瀏覽量
145111 -
編程
+關(guān)注
關(guān)注
90文章
3710瀏覽量
96979 -
源碼
+關(guān)注
關(guān)注
8文章
682瀏覽量
31185
原文標(biāo)題:C語(yǔ)言項(xiàng)目:礦井逃生游戲(密室)!詳細(xì)思路+源碼分享
文章出處:【微信號(hào):cyuyanxuexi,微信公眾號(hào):C語(yǔ)言編程學(xué)習(xí)基地】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
C語(yǔ)言項(xiàng)目:礦井逃生游戲(密室)!詳細(xì)思路+源碼分享
評(píng)論