網上有一個香蕉金剛的跑酷游戲,不過我們這個扔香蕉游戲模仿的并不是這個,而是模仿的微軟在 20 多年前的一個小游戲,不知道誰也有印象呢?
圖片都是從原來的游戲中抓圖弄出來的,顏色也是從原游戲抓圖中取色設置的,應該和原來的風格很像。
你的任務是用香蕉擊中你的對手。
你可以通過鼠標調整投擲香蕉的角度和力度,香蕉會受重力加速度的影響。同時,請注意屏幕底部表示風力的箭頭,香蕉同樣會受風力影響。風力的箭頭越長,表示風力越強。還有,周圍的樓宇會阻擋你的香蕉。(好像有點像憤怒的小鳥)
游戲中涉及到兩個玩家的代表人物和香蕉們,你可能需要自己找到兩張圖,然后通過easyx的貼圖技術弄進去。當然你也可以來找我(在文末)
其他的部分你可以直接查看下面的游戲源代碼:
本項目編譯環境:Visual Studio 2013/2019/2022,EasyX插件
代碼展示:
1.定義變量、函數和一些必要的常量
#include#include #include #include #include // 定義常量 #define PI 3.1415926536 // 圓周率 #define SCRWIDTH 640 // 屏幕寬度 #define SCRHEIGHT 480 // 屏幕高度 #define GRAVITY 9.8 // 重力加速度 #define BACKATTR BLUE // 背景的顏色 #define OBJECTCOLOR 0x55AAFF // 對手的顏色 #define EXPLOSIONCOLOR 0x5500FF // 爆炸的顏色 #define SUNATTR 0x00FFFF // 太陽的顏色 #define SUNHEIGHT 40 // 太陽的高度 #define SUNHAPPY true // 太陽高興 #define SUNSHOCK false // 太陽受驚 // 全局變量 IMAGE g_imgBanana[4]; // 香蕉圖片 IMAGE g_imgGorD; // 大猩猩(雙手放下) IMAGE g_imgGorL; // 大猩猩(左邊的手抬起) IMAGE g_imgGorR; // 大猩猩(右邊的手抬起) POINT g_ptGorilla[2]; // 兩個游戲者的位置 int g_iLastBuilding; // 最后一棟樓的編號 int g_iWind; // 風力 bool g_bSunHit; // 是否擊中太陽 // 函數定義 void Init(); // 初始化 void Intro(); // 游戲介紹 void PlayGame(TCHAR *player1, TCHAR *player2); // 主游戲函數 void MakeCityScape(POINT *aryBCoor); // 創建隨機的游戲場景 void PlaceGorillas (POINT *aryBCoor); // 將游戲者放到樓宇頂端 void DoSun(bool smile); // 繪制太陽 bool DoShot(int idPlayer, int x, int y, int* win); // 接收游戲者輸入,實現扔香蕉攻擊對方 int PlotShot(int startX, int startY, double angle, int velocity, int idPlayer); // 進行香蕉攻擊,使香蕉劃過屏幕 void DrawBanana(int x, int y, int r, bool d); // 繪制香蕉 void DoExplosion(int x, int y); // 香蕉攻擊后的爆炸效果 int ExplodeGorilla(int x, int y); // 游戲者死亡后爆炸 void VictoryDance(int idPlayer); // 繪制跳舞的大猩猩(勝利后執行)
2.初始化游戲圖片元素(香蕉和猩猩本猩)
void Init() { initgraph(SCRWIDTH, SCRHEIGHT); // 創建繪圖窗口 srand((unsigned int)time(NULL)); // 設置隨機種子 // 初始化香蕉圖案 IMAGE tmp; loadimage(&tmp, _T("res\Banana.gif")); SetWorkingImage(&tmp); getimage(&g_imgBanana[0], 0, 0, 9, 7); getimage(&g_imgBanana[1], 9, 0, 9, 7); getimage(&g_imgBanana[2], 18, 0, 9, 7); getimage(&g_imgBanana[3], 27, 0, 9, 7); // 初始化大猩猩圖案 loadimage(&tmp, _T("res\Gorilla.gif"), 0, 0, true); SetWorkingImage(&tmp); getimage(&g_imgGorD, 0, 0, 30, 30); getimage(&g_imgGorL, 30, 0, 30, 30); getimage(&g_imgGorR, 60, 0, 30, 30); SetWorkingImage(NULL); }
3.游戲的簡單介紹
void Intro() { settextstyle(24, 0, _T("宋體")); // 在屏幕中央輸出字符串 RECT r = {0, 40, 640, 80}; drawtext(_T("扔香蕉的大猩猩"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE); settextstyle(16, 0, _T("System")); r.top = 120; r.bottom = 480; drawtext(_T("這個游戲模仿的微軟在 20 多年前的一個小游戲, 不知道誰也有印象呢? ") _T("你的任務是用香蕉擊中你的對手。 你可以通過鼠標調整投擲香蕉的角度和力度, ") _T("香蕉會受重力加速度的影響。 同時,請注意屏幕底部表示風力的箭頭,") _T("香蕉同樣會受風力影響。 風力的箭頭越長,表示風力越強。 ") _T("還有,周圍的樓宇會阻擋你的香蕉。 "), &r, DT_CENTER | DT_VCENTER); r.top = 400; drawtext(_T("按任意鍵繼續"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE); getmessage(EM_CHAR); }
4.主游戲函數
// 參數: // player1, player2:游戲者名稱 void PlayGame(TCHAR *player1, TCHAR *player2) { POINT aryBCoor[31]; // 樓宇群的坐標 int aryScore[2] = {0, 0}; // 兩個游戲者的得分 TCHAR sScore[20]; // 保存得分的字符串 int player = 0; // 攻擊者 setbkcolor(BACKATTR); while(true) { cleardevice(); MakeCityScape(aryBCoor); PlaceGorillas(aryBCoor); DoSun(SUNHAPPY); bool bHit = false; while(bHit == false) { settextcolor(WHITE); RECT r = {0, 0, SCRWIDTH, 20}; drawtext(player1, &r, DT_LEFT | DT_SINGLELINE); drawtext(player2, &r, DT_RIGHT | DT_SINGLELINE); r.top = SCRHEIGHT - 40; r.bottom = SCRHEIGHT - 20; #if _MSC_VER > 1200 _stprintf_s(sScore, _T("%d >Score< %d"), aryScore[0], aryScore[1]); #else _stprintf(sScore, _T("%d >Score< %d"), aryScore[0], aryScore[1]); #endif drawtext(sScore, &r, DT_CENTER | DT_SINGLELINE); int win; // 進行攻擊。擊中任意游戲者即返回 true。同時,更新 win 為勝利者 bHit = DoShot(player, g_ptGorilla[player].x, g_ptGorilla[player].y, &win); // 如果太陽被擊中,重繪太陽 if (g_bSunHit) DoSun(SUNHAPPY); // 如果擊中對手,更新分數 if (bHit == true) aryScore[win]++; // 交替攻擊 player = 1 - player; Sleep(100); } Sleep(1000); }; }
5.創建隨機的游戲場景
// 參數: // aryBCoor[]:存儲每一棟樓的左上角坐標 void MakeCityScape(POINT *aryBCoor) { int x = -10; // 設置隨機的樓群傾斜的趨勢 int slope = rand() % 6; int iNewHt; // 新樓的高度 switch(slope) { case 0: iNewHt = 15; break; // 逐漸升高 case 1: iNewHt = 130; break; // 逐漸降低 case 2: case 3: case 4: iNewHt = 15; break; // 倒 "V" 型(比較常見) case 5: iNewHt = 130; break; // "V" 型 } int iBottomLine = 465; // 建筑的最低端 int iHtInc = 10; // 高度增加值 int iDefBWidth = 37; // 默認的建筑寬度 int iRandomHeight = 120; // 隨機的高度差異 int iWWidth = 3; // 窗戶寬度 int iWHeight = 6; // 窗戶高度 int iWDifV = 15; // 窗戶的垂直間距 int iWDifH = 10; // 窗戶的水平間距 int iCurBuilding = 0; do { switch(slope) { case 0: iNewHt += iHtInc; break; case 1: iNewHt -= iHtInc; break; case 2: case 3: case 4: if (x > SCRWIDTH / 2) iNewHt -= 2 * iHtInc; else iNewHt += 2 * iHtInc; break; case 5: if (x > SCRWIDTH / 2) iNewHt += 2 * iHtInc; else iNewHt -= 2 * iHtInc; break; } // 設置樓宇寬度,并檢查是否超出屏幕 int iBWidth = iDefBWidth + rand() % iDefBWidth; // 設置樓宇高度,并檢查樓宇是否超出屏幕下方 int iBHeight = iNewHt + rand() % iRandomHeight; if (iBHeight < iHtInc) iBHeight = iHtInc; // 檢查樓宇是否太高 if (iBottomLine - iBHeight <= 25) iBHeight = 20; // 保存樓的坐標 aryBCoor[iCurBuilding].x = x; aryBCoor[iCurBuilding].y = iBottomLine - iBHeight; // 繪制樓宇 COLORREF aryBuildingColor[3] = {CYAN, LIGHTGRAY, RED}; // 定義樓宇的三種顏色 int colorID = rand() % 3; setlinecolor(BACKATTR); rectangle(x - 1, iBottomLine + 1, x + iBWidth + 1, iBottomLine - iBHeight - 1); setfillcolor(aryBuildingColor[colorID]); solidrectangle(x, iBottomLine, x + iBWidth, iBottomLine - iBHeight); // 繪制窗戶 int c = x + 3; do { for(int i = iBHeight - 3; i >= 7; i -= iWDifV) { int winColor; if (rand() % 4 == 0) winColor = DARKGRAY; else winColor = YELLOW; setfillcolor(winColor); solidrectangle(c, iBottomLine - i, c + iWWidth, iBottomLine - i + iWHeight); } c += iWDifH; } while(c < x + iBWidth - 3); x += iBWidth + 2; iCurBuilding++; } while(x < SCRWIDTH - 1); g_iLastBuilding = iCurBuilding - 1; // 保存最后一棟樓的編號 // 設置隨機風力 g_iWind = rand() % 61 - 30; // 繪制風向箭頭 if (g_iWind != 0) { int windLine = g_iWind * 3 * (SCRWIDTH / 320); setlinecolor(EXPLOSIONCOLOR); int arrowDir = (g_iWind > 0) ? -2 : 2; line(SCRWIDTH / 2, SCRHEIGHT - 5, SCRWIDTH / 2 + windLine, SCRHEIGHT - 5); line(SCRWIDTH / 2 + windLine, SCRHEIGHT - 5, SCRWIDTH / 2 + windLine + arrowDir, SCRHEIGHT - 5 - 2); line(SCRWIDTH / 2 + windLine, SCRHEIGHT - 5, SCRWIDTH / 2 + windLine + arrowDir, SCRHEIGHT - 5 + 2); } }
6.繪制游戲者和太陽的位置
// 將游戲者放到樓宇頂端(從邊緣數第二個或第三個樓宇上) // 參數: // aryBCoor[]:樓宇數組。保存每棟樓的左上角坐標 void PlaceGorillas(POINT *aryBCoor) { for (int i = 0; i <= 1; i++) { int iBNum = (i == 0) ? rand() % 2 + 1 : g_iLastBuilding - 1 - rand() % 2; int iBWidth = aryBCoor[iBNum + 1].x - aryBCoor[iBNum].x; g_ptGorilla[i].x = aryBCoor[iBNum].x + iBWidth / 2 - g_imgGorD.getwidth() / 2; g_ptGorilla[i].y = aryBCoor[iBNum].y - g_imgGorD.getheight(); putimage(g_ptGorilla[i].x, g_ptGorilla[i].y, &g_imgGorD); } } // 繪制太陽 // 參數: // smile:太陽是否微笑 void DoSun(bool smile) { // 設置太陽的位置 int x = SCRWIDTH / 2; int y = SUNHEIGHT - 15; // 繪制太陽 // 臉 setlinecolor(SUNATTR); setfillcolor(SUNATTR); fillcircle(x, y, 12); // 光芒 for (double a = 0; a < PI * 2; a += PI / 8) line(x, y, int(x + cos(a) * 20 + 0.5), int(y + sin(a) * 16 + 0.5)); // 嘴 setlinecolor(BACKATTR); setfillcolor(BACKATTR); if (smile) // 繪制笑臉 arc(x - 8, y - 8, x + 8, y + 8, (210 * PI / 180), (330 * PI / 180)); else // 繪制受驚表情("o" 型嘴) fillcircle(x, y + 5, 3); // 眼睛 fillcircle(x - 3, y - 2, 1); fillcircle(x + 3, y - 2, 1); }
7.實現按鍵操作,實現扔香蕉功能
// 參數: // idPlayer:游戲者(準備扔香蕉的) // x, y:游戲者的位置 bool DoShot(int idPlayer, int x, int y, int *win) { // 清空鼠標消息緩沖區 flushmessage(EM_MOUSE); // 攻擊的起始位置 int startx = x + (idPlayer == 1 ? g_imgGorD.getwidth() : 0); int starty = y; // 角度輔助線的位置 int mx = startx, my = starty - 90; int oldmx = mx, oldmy = my; double angle = PI / 2; // 投擲角度 int velocity = 2; // 投擲力度 setrop2(R2_XORPEN); setlinecolor(RED); line(startx, starty, mx, my); // 鼠標輸入攻擊角度 ExMessage msg; while(true) { msg = getmessage(EM_MOUSE); if (msg.message == WM_MOUSEMOVE) { if (msg.y > y) { mx = startx + (msg.x > startx ? 90 : -90); my = starty; angle = msg.x > startx ? 0 : PI; } else if (msg.x != startx) { angle = atan((double(starty) - msg.y) / (double(msg.x) - startx)); if (angle < 0) angle += PI; mx = startx + int(cos(angle) * 90 + 0.5); my = starty - int(sin(angle) * 90 + 0.5); } else { mx = msg.x; my = y - 90; angle = PI / 2; } line(startx, starty, oldmx, oldmy); line(startx, starty, mx, my); oldmx = mx; oldmy = my; } else if (msg.message == WM_LBUTTONDOWN) break; } line(startx, starty, oldmx, oldmy); // 鼠標輸入攻擊力度 setlinestyle(PS_SOLID, 8); oldmx = mx = startx + int(cos(angle) * velocity + 0.5); oldmy = my = starty - int(sin(angle) * velocity + 0.5); line(startx, starty, mx, my); while(true) { if (peekmessage(&msg)) { if (msg.message == WM_LBUTTONUP) break; } mx = startx + int(cos(angle) * velocity + 0.5); my = starty - int(sin(angle) * velocity + 0.5); line(startx, starty, oldmx, oldmy); line(startx, starty, mx, my); oldmx = mx; oldmy = my; if (++velocity > 90) velocity = 2; Sleep(20); } velocity *= 2; // 力度擴大一倍 line(startx, starty, oldmx, oldmy); // 恢復設置 setlinestyle(PS_SOLID, 1); setrop2(R2_COPYPEN); // 實施攻擊 g_bSunHit = false; int iPlayerHit = PlotShot(x, y, angle, velocity, idPlayer); // 攻擊結果 if (iPlayerHit == -1) { *win = -1; return false; } else { *win = (iPlayerHit == idPlayer) ? 1 - idPlayer : idPlayer; VictoryDance(*win); return true; } }
8.扔出香蕉,計算坐標,弧度等等
// 進行香蕉攻擊,使香蕉劃過屏幕 // 參數: // startX, startY:游戲者(扔香蕉的)的坐標 // angle:扔出的方向(弧度) // velocity:扔出的力度 // idPlayer:游戲者(扔香蕉的) int PlotShot(int startX, int startY, double angle, int velocity, int idPlayer) { // 投擲力量在 x、y 方向上的分量 double initXVel = cos(angle) * velocity; double initYVel = sin(angle) * velocity; double x, y; double oldx = startX; double oldy = startY; // 繪制游戲者(投擲動作) putimage(startX, startY, idPlayer == 0 ? &g_imgGorL : &g_imgGorR); Sleep(100); // 繪制游戲者(站立動作) putimage(startX, startY, &g_imgGorD); bool bImpact = false; // 是否碰撞 bool bShotInSun = false; // 是否擊中太陽 bool bOnScreen = true; // 香蕉是否在屏幕上 int iPlayerHit = -1; // 是否擊中對手(-1:未擊中;0、1:被擊中者的 ID) bool bNeedErase = false; // 是否需要擦掉舊香蕉 POINT look[4]; // 碰撞檢測的位置(香蕉中心上下左右四個邊的中點) look[2].x = 0; look[3].x = g_imgBanana[0].getwidth() - 1; look[0].x = look[1].x = look[3].x / 2; look[0].y = 0; look[1].y = g_imgBanana[0].getheight() - 1; look[2].y = look[3].y = look[1].y / 2; int startXPos = startX; int startYPos = startY - g_imgBanana[0].getheight(); if (idPlayer == 1) startXPos = startXPos + g_imgGorD.getwidth() - g_imgBanana[0].getwidth(); int pointColor = 0; int rot; double t = 0; while(!bImpact && bOnScreen) { // 擦掉舊香蕉 if (bNeedErase) { bNeedErase = false; DrawBanana(int(oldx + 0.5), int(oldy + 0.5), -1, false); } x = startXPos + (initXVel * t) + (g_iWind / 5.0 * t * t); y = startYPos + (-1 * (initYVel * t) + (GRAVITY * t * t)); if ((x >= SCRWIDTH - 10.0) || (x <= 3) || (y >= SCRHEIGHT - 3.0)) bOnScreen = false; if (bOnScreen && y > 0) { // 檢測是否擊中(對香蕉中心上下左右四個邊的中點做檢測) for (int i = 0; i < 4; i ++) { pointColor = getpixel(int(x + look[i].x + 0.5), int(y + look[i].y + 0.5)); if (pointColor == BACKATTR || pointColor == WHITE) // 目標是背景色或白色字幕,未擊中 { bImpact = false; if (bShotInSun == true && (abs(SCRWIDTH / 2 - int(x)) > 20 || y > SUNHEIGHT)) bShotInSun = false; } else if (pointColor == SUNATTR && y < SUNHEIGHT) // 擊中太陽 { if (!g_bSunHit) DoSun(SUNSHOCK); g_bSunHit = true; bShotInSun = true; } else bImpact = true; if (bImpact) break; } if (!bShotInSun && !bImpact) { // 繪制香蕉 rot = int(t * 10) % 4; DrawBanana(int(x + 0.5), int(y + 0.5), rot, true); bNeedErase = true; } oldx = x; oldy = y; } t += 0.1; Sleep(50); } if (pointColor != OBJECTCOLOR && bImpact) DoExplosion(int(x + g_imgBanana[0].getwidth() / 2 + 0.5), int(y + g_imgBanana[0].getheight() / 2 + 0.5)); else if (pointColor == OBJECTCOLOR) iPlayerHit = ExplodeGorilla(int(x + 0.5), int(y + 0.5)); return iPlayerHit; }
9.當然還是不能忘記不斷對香蕉的位置進行刷新
// 參數: // x, y:香蕉的位置 // r:香蕉的旋轉位置 // d:繪制還是擦除(true:繪制;false:擦除) void DrawBanana(int x, int y, int r, bool d) { static IMAGE oldimg; if (d) { getimage(&oldimg, x, y, g_imgBanana[0].getwidth(), g_imgBanana[0].getheight()); putimage(x, y, &g_imgBanana[r]); } else putimage(x, y, &oldimg); }
10.實現香蕉命中后的爆炸效果以及角色死亡效果
// 香蕉攻擊后的爆炸效果 // 參數: // x, y:爆炸的位置 void DoExplosion(int x, int y) { int r = 10; int i; setlinecolor(EXPLOSIONCOLOR); for (i = 0; i <= r; i++) { circle(x, y, i); Sleep(16); } setlinecolor(BACKATTR); for (i = r; i >= 0; i--) { circle(x, y, i); Sleep(16); } setfillcolor(BACKATTR); fillcircle(x, y, r); } // 游戲者死亡后爆炸 // 參數: // x, y:攻擊的位置 int ExplodeGorilla (int x, int y) { int iPlayerHit = (x < SCRWIDTH / 2) ? 0 : 1; int iPlayerX = g_ptGorilla[iPlayerHit].x + g_imgGorD.getwidth() / 2; int iPlayerY = g_ptGorilla[iPlayerHit].y + g_imgGorD.getheight() / 2; int i; setlinecolor(EXPLOSIONCOLOR); for (i = 1; i <= 10; i++) { circle(x, y, i); Sleep(10); } for (i = 1; i <= 16; i++) { circle(iPlayerX, iPlayerY + 11, i); Sleep(10); } for (i = 1; i <= 32; i++) { setlinecolor((i % 2 == 0) ? 0x54A8FC : 0x5400FC); circle(iPlayerX, iPlayerY, i); Sleep(10); } for (i = 48; i >= 1; i--) { setlinecolor(BACKATTR); circle(iPlayerX, iPlayerY, i); Sleep(10); } fillcircle(iPlayerX, iPlayerY, 48); return iPlayerHit; }
11.最后還可以添加一下死亡之后的游戲動畫(比如跳舞慶祝勝利者)
// 繪制跳舞的大猩猩(勝利后執行) // 參數: // idPlayer:游戲者編號 void VictoryDance(int idPlayer) { for (int i = 0; i < 4; i++) { putimage(g_ptGorilla[idPlayer].x, g_ptGorilla[idPlayer].y, &g_imgGorL); Sleep(200); putimage(g_ptGorilla[idPlayer].x, g_ptGorilla[idPlayer].y, &g_imgGorR); Sleep(200); } }
12.主函數(把所有的功能函數放這里來)
void main() { Init(); Intro(); PlayGame(_T("Player 1"), _T("Player 2")); }
大家趕緊去動手試試吧!
審核編輯:湯梓紅
-
游戲
+關注
關注
2文章
745瀏覽量
26332 -
C語言
+關注
關注
180文章
7608瀏覽量
137111 -
編程
+關注
關注
88文章
3627瀏覽量
93809 -
源碼
+關注
關注
8文章
646瀏覽量
29280
原文標題:C語言項目:扔香蕉的大猩猩(自制游戲)!詳細思路+源碼分享
文章出處:【微信號:cyuyanxuexi,微信公眾號:C語言編程學習基地】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論