玩家被困在一個迷宮里,擁有一盞油燈,油燈能夠照亮以玩家為中心的一片圓形區域,隨著時間的流逝,油燈的照明力會逐漸下降,迷宮內隨機分布著一些加油站(黃色的圓角矩形),經過這些加油站能夠恢復油燈的照明力,找到地圖右下角的終點(綠色圓角矩形)。就算過關。
游戲采用圖塊(N * N 的正方形)的方式構建地圖,且墻壁,地面,玩家,終點采用四個獨立的函數繪制,如果想改變地圖的風格,只需要修改這些函數的內容即可。
迷宮生成采用的深度優先算法,有明顯的主路。
完整的游戲源代碼如下:
?
////////////////////////////////////////////// // 程序名稱:迷宮 // #include#include #include using std::stack; // 使用STL的棧 using std::vector; // 使用STL的數組容器 // 游戲信息 #define WIN_WIDTH 400 // 窗口的寬度(單位:像素) #define WIN_HEIGHT 300 // 窗口的高度(單位:像素) // !!注:由于隨機生成算法的原因,地圖寬高只能為奇數 #define GAME_WIDTH 41 // 地圖的寬度(單位:塊) #define GAME_HEIGHT 51 // 地圖的高度(單位:塊) #define WALL 1 // 墻壁的數字標記 #define GROUND 0 // 地面的數字標記 #define FILLSTATE 2 // 加油站的數字標記 #define ENDPOS 3 // 終點的數字標記 #define MAXVIEW 8.0 // 最大的視野 #define MINVIEW 1 // 最小的視野 #define FILLNUM 10 // 加油站的數量 #define DARKTIME 12 // 視野下降1圖塊所需的時間 // 全局變量列表 int g_BlockSize; // 塊大小 int g_GameMap[GAME_HEIGHT][GAME_WIDTH]; // 地圖(寬高單位為塊) POINT g_EndPos; // 終點位置 POINT g_PlayerPos; // 玩家在地圖上的位置 POINT g_CameraPos; // 攝像機(屏幕左上角)在地圖上的位置 IMAGE g_MapImage; // 地圖的圖片(由于地圖是固定的,在不改變縮放的情況下只需要繪制一次) double g_ViewArray; // 視野 UINT g_BeginTime; // 游戲開始時的時間 UINT g_LastFillTime; // 上次為油燈加油的時間 // 函數列表 void initGame(); // 初始化游戲 void endGame(); // 結束游戲 void draw(); // 繪制函數 bool upDate(); // 數據更新函數 void absDelay(int delay); // 絕對延遲 bool canMove(POINT pos); // 判斷某個位置是否可以移動 void computeCameraPos(); // 計算攝像機在地圖上的位置 void rePaintMap(); // 重繪地圖 void drawWall(POINT pos); // 繪制墻壁圖塊的函數 void drawGround(POINT pos); // 繪制地面圖塊的函數 void drawFillState(POINT pos); // 繪制油燈圖塊的函數 void drawEndPos(POINT pos); // 繪制終點 void drawPlayer(); // 繪制人物的函數 void drawView(); // 繪制視野 int main() { initGame(); while (1) { if (!upDate()) break; // 更新 draw(); // 繪制 absDelay(16); // 絕對延遲 16 毫秒,控制每秒 60 幀 } endGame(); return 0; } void initGame() { g_BlockSize = 32; // 初始圖塊大小為 32 個像素 srand(GetTickCount()); // 初始化隨機數生成 // 初始化間隔室 for (int i = 0; i < GAME_HEIGHT; i++) { for (int j = 0; j < GAME_WIDTH; j++) { if (i % 2 == 0 || j % 2 == 0) // 奇數行奇數列設為墻壁 g_GameMap[i][j] = WALL; else g_GameMap[i][j] = GROUND; } } // 隨機生成地圖(使用深度優先遍歷) stack stepStack; // 步驟棧 vector stepPoint; // 四周的點 POINT nowPoint; // 當前步的所在點 stepStack.push({ 1,1 }); // 寫入初始點 (1,1) 作為起點 nowPoint = { 1,1 }; g_GameMap[1][1] = 0xFFFF; // 標記這個點 while (!stepStack.empty()) // 只要步驟棧不空就繼續循環 { // 得到四周的點 POINT tempPoint; for (int i = -1; i <= 1; i += 2) { tempPoint = { nowPoint.x,nowPoint.y + i * 2 }; // 計算點 // 判斷坐標是否合法 if (tempPoint.x >= 0 && tempPoint.x <= GAME_WIDTH - 1 && tempPoint.y >= 0 && tempPoint.y <= GAME_HEIGHT - 1 && g_GameMap[tempPoint.y][tempPoint.x] != 0xFFFF) { stepPoint.push_back(tempPoint); } tempPoint = { nowPoint.x + i * 2 ,nowPoint.y }; // 計算點 // 判斷坐標是否合法 if (tempPoint.x >= 0 && tempPoint.x <= GAME_WIDTH - 1 && tempPoint.y >= 0 && tempPoint.y <= GAME_HEIGHT - 1 && g_GameMap[tempPoint.y][tempPoint.x] != 0xFFFF) { stepPoint.push_back(tempPoint); } } // 根據周圍點的量選擇操作 if (stepPoint.empty()) // 如果周圍點都被遍歷過了 { stepStack.pop(); // 出棧當前點 if (!stepStack.empty()) nowPoint = stepStack.top(); // 更新當前點 } else { stepStack.push(stepPoint[rand() % stepPoint.size()]); // 入棧當前點 g_GameMap[(nowPoint.y + stepStack.top().y) / 2][(nowPoint.x + stepStack.top().x) / 2] = 0; // 打通墻壁 nowPoint = stepStack.top(); // 更新當前點 g_GameMap[nowPoint.y][nowPoint.x] = 0xFFFF; // 標記當前點 } stepPoint.clear(); // 清空周圍點以便下一次循環 } // 清洗標記點 for (int i = 0; i < GAME_HEIGHT; i++) { for (int j = 0; j < GAME_WIDTH; j++) { if (g_GameMap[i][j] == 0xFFFF) g_GameMap[i][j] = 0; } } // 隨機生成加油站的位置 for (int i = 0; i < FILLNUM; i++) { POINT fillPoint = { rand() % GAME_WIDTH,rand() % GAME_HEIGHT }; // 保證在空地生成加油站 while (g_GameMap[fillPoint.y][fillPoint.x] != GROUND) fillPoint = { rand() % GAME_WIDTH,rand() % GAME_HEIGHT }; // 標記油燈 g_GameMap[fillPoint.y][fillPoint.x] = FILLSTATE; } g_GameMap[GAME_HEIGHT - 2][GAME_WIDTH - 2] = ENDPOS; // 標記終點 g_EndPos = { GAME_WIDTH - 2,GAME_HEIGHT - 2 }; // 確定終點位置 g_ViewArray = MAXVIEW; // 初始視野是最大的 g_BeginTime = GetTickCount(); // 開始計時 g_LastFillTime = GetTickCount(); // 油燈加油的時間 rePaintMap(); // 繪制地圖 g_PlayerPos = { g_BlockSize * 3 / 2,g_BlockSize * 3 / 2 }; // 初始化人的位置 computeCameraPos(); // 計算攝像機的位置 initgraph(WIN_WIDTH, WIN_HEIGHT); // 初始化畫布 setbkmode(TRANSPARENT); // 設置背景為透明 BeginBatchDraw(); // 開始緩沖繪制 } void endGame() { EndBatchDraw(); // 結束緩沖繪制 closegraph(); // 關閉畫布 } void draw() { // 清空設備 cleardevice(); // 繪制視野 drawView(); // 繪制人 drawPlayer(); // 繪制時間 TCHAR timeStr[256]; int loseTime = GetTickCount() - g_BeginTime; // 計算流失的時間 _stprintf_s(timeStr, _T("游戲時間:%02d:%02d"), loseTime / 1000 / 60, loseTime / 1000 % 60); settextcolor(RGB(140, 140, 140)); outtextxy((WIN_WIDTH - textwidth(timeStr)) / 2, 3, timeStr); FlushBatchDraw(); // 刷新屏幕 } bool upDate() { POINT nextPos = g_PlayerPos; // 下一個位置 // 計算下一個位置 if (GetKeyState(VK_UP) & 0x8000) nextPos.y -= 2; if (GetKeyState(VK_DOWN) & 0x8000) nextPos.y += 2; if (GetKeyState(VK_LEFT) & 0x8000) nextPos.x -= 2; if (GetKeyState(VK_RIGHT) & 0x8000) nextPos.x += 2; // 如果下一個位置不合法 if (!canMove(nextPos)) { if (canMove({ g_PlayerPos.x, nextPos.y })) // y 軸移動合法 nextPos = { g_PlayerPos.x, nextPos.y }; else if (canMove({ nextPos.x, g_PlayerPos.y })) // x 軸移動合法 nextPos = { nextPos.x, g_PlayerPos.y }; else // 都不合法 nextPos = g_PlayerPos; } // 如果是油燈則更新時間 if (g_GameMap[nextPos.y / g_BlockSize][nextPos.x / g_BlockSize] == FILLSTATE) g_LastFillTime = GetTickCount(); // 如果是終點則通關 else if (g_GameMap[nextPos.y / g_BlockSize][nextPos.x / g_BlockSize] == ENDPOS) { outtextxy(WIN_WIDTH / 2 - 40, WIN_HEIGHT / 2 - 12, _T("恭喜過關!")); FlushBatchDraw(); Sleep(1000); return false; } g_PlayerPos = nextPos; // 更新位置 computeCameraPos(); // 計算攝像機的位置 // 根據時間縮減視野 static unsigned int lastTime = GetTickCount(); int loseTime = GetTickCount() - g_LastFillTime; // 計算流失的時間 g_ViewArray = MAXVIEW - loseTime / 1000.0 / DARKTIME; // 每一段時間油燈的照明力會下降一個圖塊 if (g_ViewArray < MINVIEW) g_ViewArray = MINVIEW; // 處理鼠標消息 MOUSEMSG mouseMsg; // 鼠標信息 int lastBlockSize = g_BlockSize; // 保存原本的大小 while (MouseHit()) { mouseMsg = GetMouseMsg(); if (mouseMsg.uMsg = WM_MOUSEWHEEL) // 滾輪消息 { g_BlockSize += mouseMsg.wheel / 120; } } // 如果沒有滾輪消息就退出 if (lastBlockSize == g_BlockSize) return true; // 處理滾輪消息 if (g_BlockSize >= 10 && g_BlockSize <= 50) // 塊大小沒有達到極限值 { // 保證縮放后的地圖不會比窗口小 if (GAME_WIDTH * g_BlockSize < WIN_WIDTH || GAME_HEIGHT * g_BlockSize < WIN_HEIGHT) g_BlockSize = lastBlockSize; rePaintMap(); // 重繪地圖 // 重新計算玩家在地圖上的位置 POINT mapPos = { g_PlayerPos.x / lastBlockSize,g_PlayerPos.y / lastBlockSize }; // 計算在地圖上的位置 g_PlayerPos.x = mapPos.x * g_BlockSize + g_BlockSize / 2; // 計算映射后的位置 g_PlayerPos.y = mapPos.y * g_BlockSize + g_BlockSize / 2; // 計算映射后的位置 computeCameraPos(); // 重新計算攝像機位置 } // 保證圖塊不會過大和過小 if (g_BlockSize < 10) g_BlockSize = 10; if (g_BlockSize > 50) g_BlockSize = 50; return true; } void absDelay(int delay) { static int curtime = GetTickCount(); static int pretime = GetTickCount(); while (curtime - pretime < delay) { curtime = GetTickCount(); Sleep(1); } pretime = curtime; } bool canMove(POINT pos) { // 只要外接矩形的四個頂點不在墻壁內就必定合法 return g_GameMap[(pos.y - 3) / g_BlockSize][(pos.x - 3) / g_BlockSize] != WALL && g_GameMap[(pos.y + 3) / g_BlockSize][(pos.x + 3) / g_BlockSize] != WALL && g_GameMap[(pos.y - 3) / g_BlockSize][(pos.x + 3) / g_BlockSize] != WALL && g_GameMap[(pos.y + 3) / g_BlockSize][(pos.x - 3) / g_BlockSize] != WALL; } void computeCameraPos() { // 以人物位置為中心計算攝像機的理論位置 g_CameraPos.x = g_PlayerPos.x - WIN_WIDTH / 2; g_CameraPos.y = g_PlayerPos.y - WIN_HEIGHT / 2; // 防止攝像機越界 if (g_CameraPos.x < 0) g_CameraPos.x = 0; if (g_CameraPos.y < 0) g_CameraPos.y = 0; if (g_CameraPos.x > GAME_WIDTH * g_BlockSize - WIN_WIDTH) g_CameraPos.x = GAME_WIDTH * g_BlockSize - WIN_WIDTH; if (g_CameraPos.y > GAME_HEIGHT * g_BlockSize - WIN_HEIGHT) g_CameraPos.y = GAME_HEIGHT * g_BlockSize - WIN_HEIGHT; } void rePaintMap() { g_MapImage.Resize(GAME_WIDTH * g_BlockSize, GAME_HEIGHT * g_BlockSize); // 重置地圖圖片大小 SetWorkingImage(&g_MapImage); // 設置地圖圖片為當前工作圖片 for (int i = 0; i < GAME_HEIGHT; i++) { for (int j = 0; j < GAME_WIDTH; j++) { switch (g_GameMap[i][j]) { case WALL: drawWall({ j*g_BlockSize,i*g_BlockSize }); // 繪制墻壁 break; case FILLSTATE: drawFillState({ j*g_BlockSize,i*g_BlockSize }); // 繪制加油站 break; case GROUND: drawGround({ j*g_BlockSize,i*g_BlockSize }); // 繪制地面 break; case ENDPOS: drawEndPos({ j*g_BlockSize,i*g_BlockSize }); break; } } } SetWorkingImage(); // 復位工作圖片 } void drawWall(POINT pos) { setfillcolor(RGB(254, 109, 19)); solidrectangle(pos.x, pos.y, pos.x + g_BlockSize, pos.y + g_BlockSize); } void drawGround(POINT pos) { setfillcolor(RGB(255, 255, 255)); solidrectangle(pos.x, pos.y, pos.x + g_BlockSize, pos.y + g_BlockSize); } void drawFillState(POINT pos) { drawGround(pos); // 繪制圓角矩形 pos.x += g_BlockSize / 5; pos.y += g_BlockSize / 5; setfillcolor(RGB(252, 213, 11)); solidroundrect(pos.x, pos.y, pos.x + g_BlockSize / 5 * 3, pos.y + g_BlockSize / 5 * 3, g_BlockSize / 8, g_BlockSize / 8); } void drawEndPos(POINT pos) { drawGround(pos); // 繪制圓角矩形 pos.x += g_BlockSize / 5; pos.y += g_BlockSize / 5; setfillcolor(RGB(87, 116, 48)); solidroundrect(pos.x, pos.y, pos.x + g_BlockSize / 5 * 3, pos.y + g_BlockSize / 5 * 3, g_BlockSize / 8, g_BlockSize / 8); } void drawPlayer() { setfillcolor(RGB(252, 213, 11)); solidcircle(g_PlayerPos.x - g_CameraPos.x, g_PlayerPos.y - g_CameraPos.y, 3); } void drawView() { // 鎖定視野 HRGN viewArr; int r = int(g_BlockSize * g_ViewArray + 0.5); // 計算視野半徑 POINT orgin = g_PlayerPos; orgin.x -= g_CameraPos.x; // 計算在屏幕上的位置 orgin.y -= g_CameraPos.y; // 計算在屏幕上的位置 viewArr = CreateEllipticRgn(orgin.x - r, orgin.y - r, orgin.x + r, orgin.y + r); // 創建一個圓形的區域 setcliprgn(viewArr); // 鎖定區域 // 繪制地圖 putimage(0, 0, WIN_WIDTH, WIN_HEIGHT, &g_MapImage, g_CameraPos.x, g_CameraPos.y); // 刪除區域 DeleteObject(viewArr); // 消除區域 setcliprgn(NULL); }
?
大家趕緊去動手試試吧!
審核編輯:湯梓紅
評論
查看更多