每天一个C语言小项目,提升你的编程能力!
网上有一个香蕉金刚的跑酷游戏,不过我们这个扔香蕉游戏模仿的并不是这个,而是模仿的微软在 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")); }
大家赶紧去动手试试吧!
审核编辑:汤梓红
全部0条评论
快来发表一下你的评论吧 !