小游戏“程序猿大战产品狗”
致大神“浅墨”,毛星云,网名「浅墨」,微软MVP,《Windows游戏编程之从零开始》作者。
不知还有多少人记得, 2021-12-13,知名游戏开发大神毛星云(网名「浅墨」)不幸去世。
我也是在网上看到他去世的消息后,才知道他是个大神级的人物的。做为一个热(上)爱(班)学(摸)习(鱼)的程序猿来说,学习一下游戏开发,也是挺让人高兴的一件事。于是下载了《Windows游戏编程之从零开始》,开始学习。
按照书上第9章《勇者斗恶龙》的内容,修改成了一个小游戏,《程序猿VS产品狗》,截图和代码如下,有兴趣的小伙伴,可以下载下来,运行Release里面的exe来玩耍。
大神“浅墨”R.I.P.
main.cpp
/* 程序名称::Fighting 2022/01/25 Create by Junior */ // 属性-常规页-字符集【使用 Unicode 字符集】 #include <Windows.h> #include <time.h> #include <tchar.h> #pragma comment(lib,"winmm.lib") #pragma comment(lib,"Msimg32.lib") #define WINDOW_WIDTH 800 #define WINDOW_HEIGHT 600 #define PARTICLE_NUMBER 50 #define WINDOW_TITLE L"[ESC退出]~~~程序员 VS 产品狗~~~" struct CHARACTER { int NowHp; int MaxHp; int NowMp; int MaxMp; int Level; int Strength; int Intelligence; int Agility; }; CHARACTER Coder, PM; struct SNOW { int x; int y; BOOL exist; }; enum ActionTypes { Action_Type_NORMAL, Action_Type_CRITICAL, Action_Type_MAGIC, Action_Type_MISS, Action_Type_RECOVER }; ActionTypes CoderActionType, BossActionType; HDC g_hdc = NULL, g_mdc=NULL, g_bufdc=NULL; DWORD g_tPre = 0, g_tNow = 0; RECT g_rect; SNOW SnowFlowers[PARTICLE_NUMBER]; int g_SnowNumb = 0; int g_iFrameNum, g_iTxtNum; wchar_t text[8][100]; BOOL g_bCanAttack, g_bGameOver; HBITMAP g_hBackGround, g_hGameOver, g_hVictory, g_hSnow; HBITMAP g_hPMBitmap, g_hCoderBitmap, g_hRecoverSkill; HBITMAP g_hSkillButton1, g_hSkillButton2, g_hSkillButton3, g_hSkillButton4; HBITMAP g_hCoderSkill1, g_hCoderSkill2, g_hCoderSkill3; HBITMAP g_hBossSkill1, g_hBossSkill2, g_hBossSkill3; LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); BOOL Game_Init(HWND hwnd); VOID Game_Main(HWND hwnd); BOOL Game_ShutDown(HWND hwnd); VOID Die_Check(int nowHp, bool isCoder); VOID Message_Insert(wchar_t* str); VOID CoderAction_Logic(); VOID BossAction_Logic(); VOID CoderAction_Paint(); VOID BossAction_Paint(); VOID Snow_Paint(); //-----------------------------------【WinMain( )函数】-------------------------------------- // 描述:Windows应用程序的入口函数,我们的程序从这里开始 //------------------------------------------------------------------------------------------------ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { //【1】窗口创建四步曲之一:开始设计一个完整的窗口类 WNDCLASSEX wndClass = { 0 }; wndClass.cbSize = sizeof(WNDCLASSEX); wndClass.style = CS_HREDRAW | CS_VREDRAW; wndClass.lpfnWndProc = WndProc; wndClass.cbClsExtra = 0; wndClass.cbWndExtra = 0; wndClass.hInstance = hInstance; wndClass.hIcon = (HICON)::LoadImage(NULL, L"icon.ico", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE); wndClass.hCursor = LoadCursor(NULL, IDC_ARROW); //wndClass.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH); wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wndClass.lpszMenuName = NULL; wndClass.lpszClassName = L"ForFnHC"; //【2】窗口创建四步曲之二:注册窗口类 if (!RegisterClassEx(&wndClass)) return -1; //【3】窗口创建四步曲之三:正式创建窗口 HWND hwnd = CreateWindow(L"ForFnHC", WINDOW_TITLE, WS_MAXIMIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, hInstance, NULL); //【4】窗口创建四步曲之四:窗口的移动、显示与更新 MoveWindow(hwnd, 250, 80, WINDOW_WIDTH, WINDOW_HEIGHT, true); ShowWindow(hwnd, nShowCmd); UpdateWindow(hwnd); if (!Game_Init(hwnd)) { MessageBox(hwnd, L"资源初始化失败", L"消息窗口", 0); return FALSE; } PlaySound(L"GameMedia\\梦幻西游原声-战斗1-森林.wav", NULL, SND_FILENAME | SND_ASYNC | SND_LOOP); //【5】消息循环过程 MSG msg = { 0 }; while (msg.message != WM_QUIT) { if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } else { g_tNow = GetTickCount(); if (g_tNow - g_tPre >= 60) { Game_Main(hwnd); } } } UnregisterClass(L"ForFnHC", wndClass.hInstance); return 0; } // 接口实现 LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_SIZE: //MoveWindow(hwnd, 250, 80, WINDOW_WIDTH, WINDOW_HEIGHT, true); break; case WM_KEYDOWN: if (wParam == VK_ESCAPE) PostQuitMessage(0); //DestroyWindow(hwnd); break; case WM_DESTROY: Game_ShutDown(hwnd); PostQuitMessage(0); break; case WM_LBUTTONDOWN: if (!g_bCanAttack) { int x = LOWORD(lParam); int y = HIWORD(lParam); if (x >= 530 && x <= 570 && y >= 420 && y <= 470) { g_bCanAttack = true; CoderActionType = Action_Type_NORMAL; } if (x >= 590 && x <= 640 && y >= 420 && y <= 470) { g_bCanAttack = true; CoderActionType = Action_Type_MAGIC; } if (x >= 650 && x <= 700 && y >= 420 && y <= 470) { g_bCanAttack = true; CoderActionType = Action_Type_RECOVER; } } if (g_bGameOver) { Game_Init(hwnd); } break; default: return DefWindowProc(hwnd, message, wParam, lParam); } return 0; } BOOL Game_Init(HWND hwnd) { srand((unsigned)time(NULL)); HBITMAP bmp; g_hdc = GetDC(hwnd); g_mdc = CreateCompatibleDC(g_hdc); g_bufdc = CreateCompatibleDC(g_hdc); bmp = CreateCompatibleBitmap(g_hdc, WINDOW_WIDTH, WINDOW_HEIGHT); SelectObject(g_mdc, bmp); 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); g_hPMBitmap = (HBITMAP)LoadImage(NULL, L"GameMedia\\monster11.bmp", IMAGE_BITMAP, 280, 280, LR_LOADFROMFILE); g_hCoderBitmap = (HBITMAP)LoadImage(NULL, L"GameMedia\\hero11.bmp", IMAGE_BITMAP, 280, 280, LR_LOADFROMFILE); g_hCoderSkill1 = (HBITMAP)LoadImage(NULL, L"GameMedia\\heroslash.bmp", IMAGE_BITMAP, 364, 140, LR_LOADFROMFILE); g_hCoderSkill2 = (HBITMAP)LoadImage(NULL, L"GameMedia\\heromagic.bmp", IMAGE_BITMAP, 374, 288, LR_LOADFROMFILE); g_hCoderSkill3 = (HBITMAP)LoadImage(NULL, L"GameMedia\\herocritical.bmp", IMAGE_BITMAP, 574, 306, LR_LOADFROMFILE); g_hBackGround = (HBITMAP)LoadImage(NULL, L"GameMedia\\bg.bmp", IMAGE_BITMAP, WINDOW_WIDTH, WINDOW_HEIGHT, LR_LOADFROMFILE); // TODO: 图是64*64的,这里用的50*50? g_hSkillButton1 = (HBITMAP)LoadImage(NULL, L"GameMedia\\skillbutton1.bmp", IMAGE_BITMAP, 50, 50, LR_LOADFROMFILE); g_hSkillButton2 = (HBITMAP)LoadImage(NULL, L"GameMedia\\skillbutton2.bmp", IMAGE_BITMAP, 50, 50, LR_LOADFROMFILE); g_hSkillButton3 = (HBITMAP)LoadImage(NULL, L"GameMedia\\skillbutton3.bmp", IMAGE_BITMAP, 50, 50, LR_LOADFROMFILE); //g_hSkillButton4 = (HBITMAP)LoadImage(NULL, L"GameMedia\\skillbutton4.bmp", IMAGE_BITMAP, 50, 50, LR_LOADFROMFILE); g_hBossSkill1 = (HBITMAP)LoadImage(NULL, L"GameMedia\\monsterslash.bmp", IMAGE_BITMAP, 234, 188, LR_LOADFROMFILE); g_hBossSkill2 = (HBITMAP)LoadImage(NULL, L"GameMedia\\monstermagic.bmp", IMAGE_BITMAP, 387, 254, LR_LOADFROMFILE); g_hBossSkill3 = (HBITMAP)LoadImage(NULL, L"GameMedia\\monstercritical.bmp", IMAGE_BITMAP, 574, 306, LR_LOADFROMFILE); g_hSnow = (HBITMAP)LoadImage(NULL, L"GameMedia\\snow.bmp", IMAGE_BITMAP, 25, 25, LR_LOADFROMFILE); g_hRecoverSkill = (HBITMAP)LoadImage(NULL, L"GameMedia\\recover.bmp", IMAGE_BITMAP, 150, 150, LR_LOADFROMFILE); GetClientRect(hwnd, &g_rect); g_bGameOver = false; //Game_Paint(hwnd); Coder.NowHp = Coder.MaxHp = 1000; Coder.Level = 6; Coder.NowMp = Coder.MaxMp = 60; Coder.Strength = 10; Coder.Agility = 20; Coder.Intelligence = 10; PM.NowHp = PM.MaxHp = 2000; PM.Level = 10; PM.Strength = 10; PM.Agility = 10; PM.Intelligence = 10; g_iTxtNum = 0; // 设置字体 HFONT hFont; hFont = CreateFont(20, 0, 0, 0, 700, 0, 0, 0, GB2312_CHARSET, 0, 0, 0, 0, TEXT("微软雅黑")); SelectObject(g_mdc, hFont); SetBkMode(g_mdc, TRANSPARENT); Game_Main(hwnd); return TRUE; } VOID Game_Main(HWND hwnd) { wchar_t str[100]; // 在mdc中贴上图片 SelectObject(g_bufdc, g_hBackGround); BitBlt(g_mdc, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, g_bufdc, 0, 0, SRCCOPY); if (!g_bGameOver) { Snow_Paint(); } SetTextColor(g_mdc, RGB(255, 53, 39)); for (int i = 0; i < g_iTxtNum; i++) { TextOut(g_mdc, 20, 410 + i * 18, text[i], wcslen(text[i])); } // 贴上产品狗图及显示血量 if (PM.NowHp > 0) { SelectObject(g_bufdc, g_hPMBitmap); TransparentBlt(g_mdc, 20, 60, 280, 280, g_bufdc, 0, 0, 280, 280, RGB(0, 0, 191)); swprintf_s(str, L"%d / %d", PM.NowHp, PM.MaxHp); SetTextColor(g_mdc, RGB(255, 10, 10)); TextOut(g_mdc, 100, 360, str, wcslen(str)); } // 贴上程序猿图及显示血量/魔法值 if (Coder.NowHp > 0) { SelectObject(g_bufdc, g_hCoderBitmap); TransparentBlt(g_mdc, 450, 60, 280, 280, g_bufdc, 0, 0, 280, 280, RGB(33, 150, 243)); swprintf_s(str, L"%d / %d", Coder.NowHp, Coder.MaxHp); SetTextColor(g_mdc, RGB(255, 10, 10)); TextOut(g_mdc, 580, 360, str, wcslen(str)); swprintf_s(str, L"%d / %d", Coder.NowMp, Coder.MaxMp); SetTextColor(g_mdc, RGB(10, 10, 255)); TextOut(g_mdc, 590, 380, str, wcslen(str)); } if (g_bGameOver) { if (Coder.NowHp <= 0) { SelectObject(g_bufdc, g_hGameOver); BitBlt(g_mdc, 120, 60, 543, 396, g_bufdc, 543, 0, SRCAND); BitBlt(g_mdc, 120, 60, 543, 396, g_bufdc, 0, 0, SRCPAINT); } else { SelectObject(g_bufdc, g_hVictory); TransparentBlt(g_mdc, 0,0,800,600,g_bufdc,0,0,800,600,RGB(0,0,0)); } } else if (!g_bCanAttack) { SelectObject(g_bufdc, g_hSkillButton1); BitBlt(g_mdc, 530, 420, 60, 50, g_bufdc, 0, 0, SRCAND); SelectObject(g_bufdc, g_hSkillButton2); BitBlt(g_mdc, 590, 420, 60, 50, g_bufdc, 0, 0, SRCAND); SelectObject(g_bufdc, g_hSkillButton3); BitBlt(g_mdc, 650, 420, 60, 50, g_bufdc, 0, 0, SRCAND); //SelectObject(g_bufdc, g_hSkillButton4); //BitBlt(g_mdc, 710, 420, 50, 50, g_bufdc, 0, 0, SRCAND); } else// 游戏没有结束,且程序猿为不可攻击状态 { g_iFrameNum++; if (g_iFrameNum >= 5 && g_iFrameNum <= 10) { if (g_iFrameNum == 5) { CoderAction_Logic(); Die_Check(PM.NowHp, false); } CoderAction_Paint(); } if (g_iFrameNum == 15) { BossAction_Logic(); } if (g_iFrameNum >= 26 && g_iFrameNum <= 30) { BossAction_Paint(); } if (g_iFrameNum == 30) { g_bCanAttack = false; g_iFrameNum = 0; if (!g_bGameOver) { int mpRecover = 2 * (rand() % Coder.Intelligence) + 6; Coder.NowMp += mpRecover; if (Coder.NowMp > Coder.MaxMp) Coder.NowMp = Coder.MaxMp; swprintf_s(str, L"回合结束,程序猿恢复了【%d】点魔法值!", mpRecover); Message_Insert(str); } } } // 将 mdc的内容全部贴到 hdc 上 BitBlt(g_hdc, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, g_mdc, 0, 0, SRCCOPY); g_tPre = GetTickCount(); } BOOL Game_ShutDown(HWND hwnd) { //释放资源对象 DeleteObject(g_hBackGround); DeleteObject(g_hGameOver); DeleteObject(g_hVictory); DeleteObject(g_hSnow); DeleteObject(g_hPMBitmap); DeleteObject(g_hCoderBitmap); DeleteObject(g_hRecoverSkill); DeleteObject(g_hSkillButton1); DeleteObject(g_hSkillButton2); DeleteObject(g_hSkillButton3); DeleteObject(g_hSkillButton4); DeleteObject(g_hCoderSkill1); DeleteObject(g_hCoderSkill2); DeleteObject(g_hCoderSkill3); DeleteObject(g_hBossSkill1); DeleteObject(g_hBossSkill2); DeleteObject(g_hBossSkill3); DeleteDC(g_bufdc); DeleteDC(g_mdc); ReleaseDC(hwnd, g_hdc); return TRUE; } VOID Message_Insert(wchar_t * str) { if (g_iTxtNum < 8) { swprintf_s(text[g_iTxtNum], str); g_iTxtNum++; } else { for (int i = 0; i < g_iTxtNum; i++) { swprintf_s(text[i], text[i + 1]); } swprintf_s(text[7], str); } } VOID Die_Check(int NowHp, bool isCoder) { wchar_t str[100]; if (NowHp <= 0) { g_bGameOver = true; if (isCoder) { PlaySound(L"GameMedia\\failure.wav", NULL, SND_FILENAME | SND_ASYNC); swprintf_s(str, L":( 胜败乃兵家常事,大侠请重新来过……"); Message_Insert(str); } else { PlaySound(L"GameMedia\\victory.wav", NULL, SND_FILENAME | SND_ASYNC); swprintf_s(str, L"少年,你赢了,有两下子啊~~~!!!"); Message_Insert(str); } } } VOID CoderAction_Logic() { int damage = 0; wchar_t str[100]; switch (CoderActionType) { case Action_Type_NORMAL: if (rand() % 4 == 1) { CoderActionType = Action_Type_CRITICAL; damage = (int)(4.5 * (3 * (rand() % Coder.Agility) + Coder.Level * Coder.Strength + 20)); PM.NowHp -= damage; swprintf_s(str, L"【万键归宗】被触发,这下牛逼了,4.5倍暴击……对产品狗造成了【%d】点伤害!", damage); } else { damage = 3 * (rand() % Coder.Agility) + Coder.Level * Coder.Strength + 20; PM.NowHp -= damage; swprintf_s(str, L"程序猿使用了普通攻击【键盘斩】,伤害一般般……对产品狗造成了【%d】点伤害!", damage); } Message_Insert(str); break; case Action_Type_MAGIC: if (Coder.NowMp >= 30) { damage = 5 * (2 * (rand() % Coder.Agility) + Coder.Level * Coder.Intelligence); PM.NowHp -= damage; Coder.NowMp -= 30; swprintf_s(str, L"程序猿使用了【黑眼圈凝视】……对产品狗造成了【%d】点伤害!", damage); } else { CoderActionType = Action_Type_MISS; swprintf_s(str, L"魔法值不足30点,施法失败,这回合白费了~~~!!!"); } Message_Insert(str); break; case Action_Type_RECOVER: if (Coder.NowMp >= 40) { Coder.NowMp -= 40; int recoverHp = 5 * (5 * (rand() % Coder.Intelligence) + 40); Coder.NowHp += recoverHp; if (Coder.NowHp > Coder.MaxHp) Coder.NowHp = Coder.MaxHp; swprintf_s(str, L"程序猿使用了【抽根烟】,恢复了【%d】点生命值!", recoverHp); } else { CoderActionType = Action_Type_MISS; swprintf_s(str, L"魔法值不足40点,施法失败,这回合白费了~~~!!!"); } Message_Insert(str); break; } } VOID CoderAction_Paint() { switch (CoderActionType) { case Action_Type_NORMAL: SelectObject(g_bufdc, g_hCoderSkill1); TransparentBlt(g_mdc, 50, 170, 264, 140, g_bufdc, 0, 0, 364, 140, RGB(0, 0, 0)); break; case Action_Type_CRITICAL: SelectObject(g_bufdc, g_hCoderSkill3); TransparentBlt(g_mdc, 20, 60, 574, 306, g_bufdc, 0, 0, 574, 306, RGB(0, 0, 0)); break; case Action_Type_MAGIC: SelectObject(g_bufdc, g_hCoderSkill2); TransparentBlt(g_mdc, 50, 100, 374, 288, g_bufdc, 0, 0, 374, 288, RGB(0, 0, 0)); break; case Action_Type_RECOVER: SelectObject(g_bufdc, g_hRecoverSkill); TransparentBlt(g_mdc, 560, 170, 150, 150, g_bufdc, 0, 0, 150, 150, RGB(0, 0, 0)); break; } } VOID BossAction_Logic() { srand((unsigned)time(NULL)); if (PM.NowHp > PM.MaxHp / 2) { switch (rand() % 3) { case 0: BossActionType = Action_Type_NORMAL; break; case 1: BossActionType = Action_Type_CRITICAL; break; case 2: BossActionType = Action_Type_MAGIC; break; } } else { switch (rand() % 3) { case 0: BossActionType = Action_Type_MAGIC; break; case 1: BossActionType = Action_Type_CRITICAL; break; case 2: BossActionType = Action_Type_RECOVER; break; } } } VOID BossAction_Paint() { int damage = 0, recover = 0; wchar_t str[100]; switch (BossActionType) { case Action_Type_NORMAL: SelectObject(g_bufdc, g_hBossSkill1); TransparentBlt(g_mdc, 500, 150, 234, 188, g_bufdc, 0, 0, 234, 188, RGB(0, 0, 0)); if (g_iFrameNum == 30) { damage = rand() % PM.Agility + PM.Level * PM.Strength; Coder.NowHp -= damage; swprintf_s(str, L"产品狗释放了【薅头发】……,对程序猿造成了【%d】点伤害!", damage); Message_Insert(str); Die_Check(Coder.NowHp, true); } break; case Action_Type_CRITICAL: SelectObject(g_bufdc, g_hBossSkill3); TransparentBlt(g_mdc, 280, 100, 574, 306, g_bufdc, 0, 0, 574, 306, RGB(0, 0, 0)); if (g_iFrameNum == 30) { damage = 2 * (rand() % PM.Agility + PM.Level * PM.Strength); Coder.NowHp -= damage; swprintf_s(str, L"产品狗使用了【老板说的】……,对程序猿造成了【%d】点伤害!", damage); Message_Insert(str); Die_Check(Coder.NowHp, true); } break; case Action_Type_MAGIC: SelectObject(g_bufdc, g_hBossSkill2); TransparentBlt(g_mdc, 450, 150, 387, 254, g_bufdc, 0, 0, 387, 254, RGB(0, 0, 0)); if (g_iFrameNum == 30) { damage = 2* (2*rand() % PM.Agility + PM.Intelligence * PM.Strength); Coder.NowHp -= damage; recover = (int)(damage * 0.2); PM.NowHp += recover; swprintf_s(str, L"产品狗释放了【猴子偷桃】……,对程序猿造成了【%d】点伤害,自身恢复【%d】点生命值!", damage, recover); Message_Insert(str); Die_Check(Coder.NowHp, true); } break; case Action_Type_RECOVER: SelectObject(g_bufdc, g_hRecoverSkill); TransparentBlt(g_mdc, 150, 150, 150, 150, g_bufdc, 0, 0, 150, 150, RGB(0, 0, 0)); if (g_iFrameNum == 30) { recover = 2 * PM.Intelligence * PM.Intelligence; PM.NowHp += recover; swprintf_s(str, L"产品狗使用了【去找老板谈】……,恢复了【%d】点生命值!", recover); Message_Insert(str); } break; } } VOID Snow_Paint() { if (g_SnowNumb < PARTICLE_NUMBER) { SnowFlowers[g_SnowNumb].x = rand() % g_rect.right; SnowFlowers[g_SnowNumb].y = 0; SnowFlowers[g_SnowNumb].exist = true; g_SnowNumb++; } for (int i = 0; i < PARTICLE_NUMBER; i++) { if (SnowFlowers[i].exist) { SelectObject(g_bufdc, g_hSnow); TransparentBlt(g_mdc, SnowFlowers[i].x, SnowFlowers[i].y, 25, 25, g_bufdc, 0, 0, 25, 25, RGB(158, 158, 158)); if (rand() % 2 == 0) SnowFlowers[i].x += rand() % 6; else SnowFlowers[i].x -= rand() % 6; SnowFlowers[i].y += 10; if (SnowFlowers[i].y > g_rect.bottom) { SnowFlowers[i].x = rand() % g_rect.right; SnowFlowers[i].y = 0; } } } }