标签:
前言
今天是NBA总决赛的最后一场,还是抢七大战,伟大的James终于圆梦了,比赛之中突然发现詹姆斯胡子都有点白了,原来原先那些陪伴自己众多篮球明星都已经老了,Kobe也退役了,他们每个人都有梦想并且为之奋斗,詹姆斯为了自己的梦想奋斗了这么多年才圆梦,那么自己也应该努力实现自己心中的梦想,当然只是想实现自己心中的一个小小的梦想而已,不是每个人都可以说出为自己家乡带来总冠军那样的豪言壮语。
---这句话送给伟大的詹姆斯,也送给我自己,更送给所有在梦想道路上前进的人
前段时间由于忙着准备学校的考试,没有时间好好做自己想做的事情,再加上今天早上还有NBA总决赛,所以好久没有写博客,考试考完之后,我就开始了自己做了一个小游戏---SwordsMan,当然这只是我给取得一个名字,并不是那个SwordsMan,和真正的SwordsMan游戏还是有很大的差距的。
游戏之中的场景图是我用Photoshop拼接而成,自己也感觉做的不好,大家看看就好。游戏中的小鸟以及Boss小龙也是用Photoshop制作的,下面来看一下实际的效果图:
游戏初始状态:
游戏技能释放:
每次用技能击中小鸟,会获得积分,当积分达到100的时候,Boss就会出现,如图:
当然Boss会释放技能,来看一下:
Boss的技能是一组动态图,范围是以它为中心的150个像素之内,当然,Boss会先判断玩家的位置,然后在释放技能,不过这里只是初步判断一下,人工智能是一个很大的话题。现在总算明白为什么为什么AI难以设计了,以前还总是嫌弃AI不行,比如我在玩三国志系列游戏的时候总是觉得AI不够智能,现在总算明白AI的难度之大。大家在玩dota或者LoL的时候,可能会感觉电脑弱(不过话说我从来没有感觉到dota之中的AI弱,我表示经常打不过电脑o(╯□╰)o)。
其实我感觉最难的还是碰撞检测,我在代码之中也是做了一些比较简单地检测,其实这一块真的挺难的,首先你得想到各种情况,哪怕是边边角角碰到了,可能也是一种有效的碰撞。这样的碰撞检测在FPS类型的游戏之中显得格外的重要,比如子弹是否击中人,以及击中人的什么部位。这些方面,国外的做的很好,尤其是一些3A级大作。在他们的游戏引擎之中就有专门的检测机制。
下面来看一下代码,为了节省篇幅,我给出重要的代码,生成窗口的代码就省略了。
//头文件 #pragma once #include "GameEngine.h" #include <Windows.h> #include <tchar.h> //使用swprintf_s函数所需的头文件 #include "resource.h" #define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code)&0x8000)?1:0) #define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code)&0x8000)?0:1) struct SwordBullets //代表主人公的剑气 { int x, y; //人物剑气的坐标 bool isExist; //剑气是否存在 }; struct BirdMaster { int x, y; //代表经过的小鸟的坐标 bool isExist; //表示小鸟是否存在 int picNum; //图的标号(一共5个) HBITMAP hbird; //小鸟的位图句柄 }; struct BossMaster { int x, y; //代表boss的贴图坐标 int picNum; //boss图的标号(一共6个) int ipicNum; //boss技能图的标号 HBITMAP hBoss; //boss的位图句柄 int boss_blood; //boss的血量 HBITMAP hBossSkill; //boss技能句柄 int Skill_x, Skill_y; //boss的技能坐标 int boss_x, boss_y; //boss的中心坐标 }; int g_iX = 0, g_iY = 0; //鼠标所在位置 <pre name="code" class="cpp"> //全局设备句柄、全局内存DC句柄、以及缓冲DC HDC g_hdc = NULL, g_mdc = NULL, g_bufdc = NULL; //定义五个位图句柄,分别用来存储背景、人物、剑气、失败、胜利 HBITMAP g_hBackGround = NULL, g_hSwordMan = NULL, g_hSwordBlade = NULL, g_hGameOver = NULL, g_hvictory = NULL; int g_iXSwordMan = 0, g_iYSwordMan = 0; //人物所在位置记录当前人物的坐标,也就是贴图坐标 int g_iBGOffset = 0, g_iBulletNum = 0, g_ibirdNum = 0; //g_iBGOffset为滚动背景所要裁剪的区域宽度,g_iBulletNum记录剑气的数量 SwordBullets Bullet[30]; BirdMaster Bird[10]; BossMaster Boss; int g_Score = 0; //玩家得分 int g_life = 0; //玩家血量 bool g_Isboss = false; //boss是否存在 //时间计数 DWORD g_tPre = 0, g_tNow = 0; GameEngine * g_pGame = NULL; //游戏中检测函数,(传参看似没有必要,主要是为了便于日后代码的维护) bool Is_in_Randge(BirdMaster &bir); bool Is_hit_bird(BirdMaster &bir, SwordBullets &bullet); void bird_hit_man(BirdMaster &bir); void Boss_appear(); void Boss_Initalize(); void Is_hit_boss(BossMaster &bos, SwordBullets &bullet); bool Boss_hit_man(BossMaster &bos); #pragma
#include "Swordsman.h" BOOL GameInitalize(HINSTANCE hInstance) //游戏初始化函数 { PlaySound(L"仙剑三·原版主题曲", NULL, SND_FILENAME | SND_ASYNC | SND_LOOP); try { g_pGame = new GameEngine(hInstance, L"SwordsMan", L"SwordMan", IDI_SWORDMAN, IDI_SWORDMAN_SM); } catch (...) { return FALSE; } g_pGame->SetFrameDelay(20); g_life = 100; g_Score = 0; Boss_Initalize(); //初始化boss的一些信息,满足一定条件出现 return TRUE; } void GameStart(HWND hWindow) //游戏开始函数 { HBITMAP bmp; g_hdc = GetDC(hWindow); g_mdc = CreateCompatibleDC(g_hdc); g_bufdc = CreateCompatibleDC(g_hdc); bmp = CreateCompatibleBitmap(g_hdc, WINDOW_WIDTH, WINDOW_HEIGHT); g_iX = 300; g_iY = 100; g_iXSwordMan = 300; g_iYSwordMan = 100; SelectObject(g_mdc, bmp); //选择空位图 //加载各种位图 g_hSwordMan = (HBITMAP)LoadImage(NULL, L"GameMedia\\swordman.bmp", IMAGE_BITMAP, 317, 283, LR_LOADFROMFILE); g_hSwordBlade = (HBITMAP)LoadImage(NULL, L"GameMedia\\swordblade.bmp", IMAGE_BITMAP, 100, 26, LR_LOADFROMFILE); g_hBackGround = (HBITMAP)LoadImage(NULL, L"GameMedia\\bg.bmp", IMAGE_BITMAP, WINDOW_WIDTH, WINDOW_HEIGHT, LR_LOADFROMFILE); g_hGameOver = (HBITMAP)LoadImage(NULL, L"GameMedia\\gameover.bmp", IMAGE_BITMAP, 1086, 396, LR_LOADFROMFILE); g_hvictory = (HBITMAP)LoadImage(NULL, L"GameMedia\\victory.bmp", IMAGE_BITMAP, 800, 600, LR_LOADFROMFILE); POINT pt, lt, rb; RECT rect; //设置光标的位置 pt.x = 300; pt.y = 100; ClientToScreen(hWindow, &pt); SetCursorPos(pt.x, pt.y); ShowCursor(false); //隐藏鼠标 GetClientRect(hWindow, &rect); lt.x = rect.left; lt.y = rect.top; rb.x = rect.right; rb.y = rect.bottom; ClientToScreen(hWindow, <); ClientToScreen(hWindow, &rb); rect.left = lt.x; rect.top = lt.y; rect.right = rb.x; rect.bottom = rb.y; //限制鼠标的移动区域 ClipCursor(&rect); GamePaint(hWindow); } void GamePlay() { GamePaint(g_pGame->GetWindow()); if (g_ibirdNum <= 10) { g_tPre = GetTickCount(); if (g_tPre - g_tNow > 1500) { for (int j = 0; j < 10; j++) { if (!Bird[j].isExist) { Bird[j].x = 0; Bird[j].y = rand() % WINDOW_HEIGHT; Bird[j].isExist = true; Bird[j].picNum = 0; Bird[j].hbird = (HBITMAP)LoadImage(NULL, L"GameMedia\\bird.bmp", IMAGE_BITMAP, 1250, 360, LR_LOADFROMFILE); g_ibirdNum++; g_tNow = g_tPre; break; } } } } } void GamePaint(HWND hWindow) //游戏绘制函数 { //先在内存DC之中贴上背景图 SelectObject(g_bufdc, g_hBackGround); BitBlt(g_mdc, 0, 0, g_iBGOffset, WINDOW_HEIGHT, g_bufdc, WINDOW_WIDTH - g_iBGOffset, 0, SRCCOPY); BitBlt(g_mdc, g_iBGOffset, 0, WINDOW_WIDTH - g_iBGOffset, WINDOW_HEIGHT, g_bufdc, 0, 0, SRCCOPY); TCHAR str[20] = {}; //计算剑侠的贴图坐标,设定每次进行贴图时,剑侠都会慢慢以10个像素的单位向目标接近 if (g_iX > g_iXSwordMan) //若当前鼠标X坐标大于人物的坐标,则前进(往左) { g_iXSwordMan += 10; if (g_iXSwordMan > g_iX) { g_iXSwordMan = g_iX; } } else //若当前的鼠标X坐标小于人物的坐标,则后退(往右) { g_iXSwordMan -= 10; if (g_iXSwordMan < g_iX) { g_iXSwordMan = g_iX; } } if (g_iY > g_iYSwordMan) { g_iYSwordMan += 10; if (g_iYSwordMan > g_iY) { g_iYSwordMan = g_iY; } } else { g_iYSwordMan -= 10; if (g_iYSwordMan < 0) { g_iYSwordMan = 0; } } //贴上剑侠的图 SelectObject(g_bufdc, g_hSwordMan); TransparentBlt(g_mdc, g_iXSwordMan, g_iYSwordMan, 317, 283, g_bufdc, 0, 0, 317, 283, RGB(0, 0, 0)); //剑气(子弹)的贴图,先判断剑气(子弹)数目“g_iBulletNum”的值是否为“0”。若不为0,则对剑气(子弹)数组中各个还存在的剑气(子弹)按照其所在的坐标(b[i].x,b[i].y)循环进行贴图操作 SelectObject(g_bufdc, g_hSwordBlade); if (g_iBulletNum != 0) { for (int i = 0; i < 30; i++) { if (Bullet[i].isExist) { //检测有无击中boss Is_hit_boss(Boss, Bullet[i]); if (Boss.boss_blood <= 0) //若boss死亡,就贴出胜利的图片 { g_Isboss = false; SelectObject(g_bufdc, g_hvictory); TransparentBlt(g_mdc, 250, 50, 800, 600, g_bufdc, 0, 0, 800, 600, RGB(0, 0, 0)); g_pGame->SetGameState(TRUE); } //依次判断有无击中小鸟 for (int j = 0; j < 10; j++) { if (Is_hit_bird(Bird[j], Bullet[i])) { Bullet[i].isExist = false; g_iBulletNum--; Bird[j].isExist = false; g_ibirdNum--; g_Score += 10; if (g_Score >= 100) { g_Isboss = true; } } } if (Bullet[i].isExist) { TransparentBlt(g_mdc, Bullet[i].x - 70, Bullet[i].y + 100, 100, 33, g_bufdc, 0, 0, 100, 26, RGB(0, 0, 0)); } //设置下一个剑气(子弹)的坐标。剑气(子弹)是从右向左发射的,因此,每次其X轴上的坐标值递减10个单位,这样贴图会产生往左移动的效果。而如果剑气(子弹)下次的坐标已超出窗口的可见范围(h[i].x<0),那么剑气(子弹)设为不存在,并将剑气(子弹)总数g_iBulletNum变量值减1. Bullet[i].x -= 10; if (Bullet[i].x < 0) { g_iBulletNum--; Bullet[i].isExist = false; } } } } //判断小鸟的数量“g_ibirdNum”的值是否为“0”。若不为0,则对剑气(子弹)数组中各个还存在的剑气(子弹)按照其所在的坐标,循环进行贴图操作 if (g_ibirdNum != 0) { for (int i = 0; i < 10; i++) { if (Bird[i].isExist) { if (Bird[i].picNum >= 5) { Bird[i].picNum = 0; } SelectObject(g_bufdc, Bird[i].hbird); BitBlt(g_mdc, Bird[i].x, Bird[i].y, 250, 180, g_bufdc, Bird[i].picNum * 250, 180, SRCAND); BitBlt(g_mdc, Bird[i].x, Bird[i].y, 250, 180, g_bufdc, Bird[i].picNum * 250, 0, SRCPAINT); if (Is_in_Randge(Bird[i])) { if (Bird[i].y < g_iYSwordMan) { Bird[i].y += 10; } else { Bird[i].y -= 10; } Bird[i].x += 10; } else { Bird[i].x += 10; //设定小鸟的前进路线 } if (Bird[i].x > WINDOW_WIDTH) { Bird[i].isExist = false; g_ibirdNum--; } Bird[i].picNum++; bird_hit_man(Bird[i]); } } } //判断boss是否存在 if (g_Isboss) { Boss_appear(); } //创建字体 HFONT hfont; hfont = CreateFont(35, 20, 0, 0, 300, 0, 0, 0, GB2312_CHARSET, 0, 0, 0, 0, TEXT("微软雅黑")); //把字体选入g_mdc SelectObject(g_mdc, hfont); //设置背景颜色透明 SetBkMode(g_mdc, TRANSPARENT); //设置文字颜色 SetTextColor(g_mdc, RGB(255, 0, 0)); //在右上角进行文字输出 swprintf_s(str, L"玩家血量(life): %d%%", g_life); TextOut(g_mdc, 850, 0, str, wcslen(str)); swprintf_s(str, L"玩家得分(score): %d", g_Score); TextOut(g_mdc, 50, 0, str, wcslen(str)); //贴上图 BitBlt(g_hdc, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, g_mdc, 0, 0, SRCCOPY); //背景的移动 g_iBGOffset += 5; if (g_iBGOffset >= WINDOW_WIDTH) //如果大于窗口宽度,就变为0 { g_iBGOffset = 0; } } void GameActivate(HWND hWindow) //游戏被激活 { HDC hdc; RECT rect; GetClientRect(hWindow, &rect); hdc = GetDC(hWindow); DrawText(hdc, L"Welcome to back", -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); ReleaseDC(hWindow, hdc); } void GameDeactivate(HWND hWindow) //游戏未被激活 { HDC hdc; RECT rect; GetClientRect(hWindow, &rect); hdc = GetDC(hWindow); DrawText(hdc, L"Pause", -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); ReleaseDC(hWindow, hdc); } void GameEnd() //游戏结束函数 { //资源的清理 DeleteObject(g_hBackGround); DeleteDC(g_bufdc); DeleteDC(g_mdc); ReleaseDC(g_pGame->GetWindow(), g_hdc); } void HandleKeys() //按键处理函数 { if (KEY_DOWN(VK_ESCAPE)) //按下Esc退出 { DestroyWindow(g_pGame->GetWindow()); return; } if (KEY_DOWN(VK_UP)) //按下up(向上的箭头)玩家向上移动 { g_iYSwordMan -= 50; if (g_iYSwordMan < 0) { g_iYSwordMan = 0; } } if (KEY_DOWN(VK_SHIFT)) //按下shift进行加速 { g_iBGOffset += 15; } if (KEY_DOWN(VK_LBUTTON)) //按下鼠标左键 { for (int i = 0; i < 30; i++) { if (!Bullet[i].isExist) { Bullet[i].x = g_iXSwordMan; Bullet[i].y = g_iYSwordMan + 30; Bullet[i].isExist = true; g_iBulletNum++; break; } } } if (KEY_DOWN(VK_DOWN)) //按下up(向上的箭头)玩家向上移动 { g_iYSwordMan += 50; if (g_iYSwordMan > WINDOW_HEIGHT) { g_iYSwordMan = WINDOW_HEIGHT; } } } bool Is_in_Randge(BirdMaster &bir) //判断玩家是否在小鸟的视野半径内,若是,则飞向玩家 { if (bir.isExist) { if (bir.x < g_iXSwordMan) { if (((bir.x - g_iXSwordMan)*(bir.x - g_iXSwordMan) + (bir.y - g_iYSwordMan)*(bir.y - g_iYSwordMan)) <= 90000) //检测是否在一个圆内 { return true; } } return false; } else { return false; } } bool Is_hit_bird(BirdMaster &bir, SwordBullets &bullet) //判断技能有没有击中小鸟 { if (bir.isExist) { if ((bullet.y + 13 > bir.y + 25) && (bullet.y + 13 < bir.y + 45)) { if ((bullet.x < bir.x + 220) && (bullet.x > bir.x + 125)) { return true; } } } return false; } void bird_hit_man(BirdMaster &bir) //小鸟与玩家的碰撞 { if (bir.isExist) { if (((bir.y + 45) <= g_iYSwordMan + 250) && ((bir.y + 45) >= g_iYSwordMan + 80)) { if (((bir.x + 220) > (g_iXSwordMan + 35)) && ((bir.x + 220) < (g_iXSwordMan + 125))) { bir.isExist = false; g_life -= 10; if (g_life == 0) { SelectObject(g_bufdc, g_hGameOver); //玩家死亡,贴出失败的图片 BitBlt(g_mdc, 400, 100, 543, 396, g_bufdc, 543, 0, SRCAND); BitBlt(g_mdc, 400, 100, 543, 396, g_bufdc, 0, 0, SRCPAINT); g_pGame->SetGameState(TRUE); } } } } } void Boss_appear() //Boss出现 { if (Boss.picNum >= 6) { Boss.picNum = 0; } SelectObject(g_bufdc, Boss.hBoss); //贴上Boss的图片 BitBlt(g_mdc, Boss.x, Boss.y, 220, 200, g_bufdc, Boss.picNum * 220, 200, SRCAND); BitBlt(g_mdc, Boss.x, Boss.y, 220, 200, g_bufdc, Boss.picNum * 220, 0, SRCPAINT); Boss.picNum++; if (((Boss.boss_x - g_iXSwordMan)*(Boss.boss_x - g_iXSwordMan) + (Boss.boss_y - g_iYSwordMan)*(Boss.boss_y - g_iYSwordMan))<360000) { if (Boss.ipicNum >= 6) { Boss.ipicNum = 0; } if (Boss.Skill_x - Boss.x >= 380) { Boss.Skill_x = Boss.x + 200; } if (Boss.Skill_y - Boss.y >= 280) { Boss.Skill_y = Boss.y + 100; } SelectObject(g_bufdc, Boss.hBossSkill); //贴上Boss的技能图 BitBlt(g_mdc, Boss.Skill_x, Boss.Skill_y, 150, 150, g_bufdc, Boss.ipicNum * 150, 150, SRCAND); BitBlt(g_mdc, Boss.Skill_x, Boss.Skill_y, 150, 150, g_bufdc, Boss.ipicNum * 150, 0, SRCPAINT); Boss.ipicNum++; if (Boss_hit_man(Boss)) { g_life -= 20; if (g_life <= 0) //判断玩家是否还有血量,若没有,则贴出失败的图片 { g_life = 0; SelectObject(g_bufdc, g_hGameOver); BitBlt(g_mdc, 400, 100, 543, 396, g_bufdc, 543, 0, SRCAND); BitBlt(g_mdc, 400, 100, 543, 396, g_bufdc, 0, 0, SRCPAINT); g_pGame->SetGameState(TRUE); } } } else { if (Boss.x >= g_iXSwordMan - 400) { Boss.x -= rand() % 40; } else { if (Boss.y > g_iYSwordMan) { Boss.y -= 15; if (Boss.x < g_iXSwordMan) { Boss.x += 10; } else { Boss.x -= 20; } } else if (Boss.y < g_iYSwordMan) { Boss.y += 15; if (Boss.x < g_iXSwordMan) { Boss.x += 10; } else { Boss.x -= 20; } } else { if (Boss.x < g_iXSwordMan) { Boss.x += 10; } else { Boss.x -= 20; } } } } } void Boss_Initalize() //Boss的初始化 { g_Isboss = false; Boss.boss_blood = 100; Boss.hBoss = (HBITMAP)LoadImage(NULL, L"GameMedia\\boss.bmp", IMAGE_BITMAP, 1320, 400, LR_LOADFROMFILE); //加载boss位图 Boss.hBossSkill = (HBITMAP)LoadImage(NULL, L"GameMedia\\bossSkill.bmp", IMAGE_BITMAP, 900, 300, LR_LOADFROMFILE); //加载boss技能图 Boss.picNum = 0; Boss.ipicNum = 0; Boss.x = 0; Boss.y = rand() % WINDOW_HEIGHT; Boss.Skill_x = Boss.x + 200; //技能设置的初始位置 Boss.Skill_y = Boss.y + 100; Boss.boss_x = Boss.x + 150; //Boss的中心位置 Boss.boss_y = Boss.y + 100; } bool Boss_hit_man(BossMaster &bos) //判断Boss是否碰撞到玩家,或者Boss技能是否击中玩家 { bos.Skill_x += 30; if (bos.Skill_y + 75 < g_iYSwordMan) { bos.y += 5; bos.Skill_y += 20; } else if (bos.Skill_y + 75 > g_iYSwordMan) { bos.y -= 5; bos.Skill_y -= 20; } else { bos.y = bos.y; bos.Skill_y = bos.Skill_y; } //判断boss是否碰到玩家 if ((bos.boss_y <= g_iYSwordMan + 250) && (bos.boss_y >= g_iYSwordMan + 80)) { if ((bos.boss_x > (g_iXSwordMan + 35)) && (bos.boss_x < (g_iXSwordMan + 125))) { g_life -= 15; } } //判断boss技能是否击中玩家 if ((bos.Skill_y + 75 <= g_iYSwordMan + 250) && ((bos.Skill_y + 75) >= g_iYSwordMan + 80)) { if (((bos.Skill_x + 75) >(g_iXSwordMan + 35)) && ((bos.Skill_x + 75) < (g_iXSwordMan + 125))) { return true; } } return false; } void Is_hit_boss(BossMaster &bos, SwordBullets &bullet) //玩家是否击中Boss { if ((bullet.y + 13 > (bos.boss_y - 30)) && (bullet.y + 13 < bos.boss_y + 15)) { if ((bullet.x < bos.boss_x + 50) && (bullet.x > bos.boss_x - 30)) { bos.boss_blood -= 15; g_Score += 20; bullet.isExist = false; //子弹击中之后消失 g_iBulletNum--; } } }
不过要想简单也很容易,把Boss血量降到1就可以了,O(∩_∩)O,来一张胜利作为结束:
当然这只是一个很简单的游戏,大家可以看看,也欢迎大家给出一些建议!(PS:窗口生成那一部分WIN32的代码是一个固定的格式,为了节省篇幅所以没有贴出)
标签:
原文地址:http://blog.csdn.net/loving_forever_/article/details/51723326