前言

阅读本博客的前置技能:

C++ 基础

好像没了 qwq

为什么开这个坑

很惭愧,虽然每每想要认真的做一个游戏 demo 时,我要么只是停留在了纸面描述上,要么只是建了几个新类,用一个又一个框架/引擎新建工程,做了一些很简单的东西,却从来没有做出来过一个完整的游戏(控制台游戏除外哈哈)。所以乘着对 Windows 桌面程序学习的势头,在此再开一坑,希望不要再浅尝辄止,做出一个能玩的完整 demo. 并同时把开发过程完完整整地记录下来,并将疑惑不解或者是有理解的地方讲出来,加深印象,也帮助他人. (虽然看得人不会很多,我的博客读起来估计也不会那么舒服,毕竟咱也不会写书)

我的想法

在开始代码工程之前,我们应当明确,我们是要做一个什么类型的游戏。接着,使用面向对象的思想,构思一下这个游戏,有哪些类,有哪些接口,怎么定义继承关系,来尽可能地减少工程复杂度,使要做的游戏更加清晰。最好是写设计文档,将游戏的雏形描绘在设计上,切忌一上来便新建文件夹。马克思说:“最蹩脚的建筑师从一开始就比最灵巧的蜜蜂高明的地方,是他在用蜂蜡建筑蜂房之前已经在自己头脑中把它建成了。”(当然,该系列博客中适当略去了前期工作,重点是如何用代码真正实现,做一个完完整整的游戏)

例如,在这里我想实现的是一个类似魔塔的 2D RPG 游戏,希望做出一部 2D RPG 的知名手游《明日方舟》的同人单机游戏。与魔塔不同的是,我希望能够在游戏中增加一些 Rougelike 元素,提高游戏的可玩性。至于具体设计嘛,设计文档中有一些描述,这里当然略过啦!

那么,让我们上手开发出一个属于自己的 Windows 游戏吧!


一、新建工程

使用的集成开发环境:Visual Studio 2019 Community (安装了c++开发环境)

打开VS,创建一个 Windows 桌面应用程序工程。

取好项目名称,选好目录。我的项目名称是 “PhantomAndCrimsonSolitaire”(傀影与猩红狐钻).

创建完成后,我们发现工程下多了好多好多文件。如果之前接触过 Windows 桌面程序,那对这些一定是不陌生的。

查看源文件中的 PhantomAndCrimsonSolitaire.cpp,这是整个工程最重要的一段代码,是应用程序的入口点:

其代码如下:

// PhantomAndCrimsonSolitaire.cpp : 定义应用程序的入口点。
//#include "framework.h"
#include "PhantomAndCrimsonSolitaire.h"#define MAX_LOADSTRING 100// 全局变量:
HINSTANCE hInst;                                // 当前实例
WCHAR szTitle[MAX_LOADSTRING];                  // 标题栏文本
WCHAR szWindowClass[MAX_LOADSTRING];            // 主窗口类名// 此代码模块中包含的函数的前向声明:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);int APIENTRY wWinMain(_In_ HINSTANCE hInstance,_In_opt_ HINSTANCE hPrevInstance,_In_ LPWSTR    lpCmdLine,_In_ int       nCmdShow)
{UNREFERENCED_PARAMETER(hPrevInstance);UNREFERENCED_PARAMETER(lpCmdLine);// TODO: 在此处放置代码。// 初始化全局字符串LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);LoadStringW(hInstance, IDC_PHANTOMANDCRIMSONSOLITAIRE, szWindowClass, MAX_LOADSTRING);MyRegisterClass(hInstance);// 执行应用程序初始化:if (!InitInstance (hInstance, nCmdShow)){return FALSE;}HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_PHANTOMANDCRIMSONSOLITAIRE));MSG msg;// 主消息循环:while (GetMessage(&msg, nullptr, 0, 0)){if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)){TranslateMessage(&msg);DispatchMessage(&msg);}}return (int) msg.wParam;
}//
//  函数: MyRegisterClass()
//
//  目标: 注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{WNDCLASSEXW wcex;wcex.cbSize = sizeof(WNDCLASSEX);wcex.style          = CS_HREDRAW | CS_VREDRAW;wcex.lpfnWndProc    = WndProc;wcex.cbClsExtra     = 0;wcex.cbWndExtra     = 0;wcex.hInstance      = hInstance;wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_PHANTOMANDCRIMSONSOLITAIRE));wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_PHANTOMANDCRIMSONSOLITAIRE);wcex.lpszClassName  = szWindowClass;wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));return RegisterClassExW(&wcex);
}//
//   函数: InitInstance(HINSTANCE, int)
//
//   目标: 保存实例句柄并创建主窗口
//
//   注释:
//
//        在此函数中,我们在全局变量中保存实例句柄并
//        创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{hInst = hInstance; // 将实例句柄存储在全局变量中HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);if (!hWnd){return FALSE;}ShowWindow(hWnd, nCmdShow);UpdateWindow(hWnd);return TRUE;
}//
//  函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目标: 处理主窗口的消息。
//
//  WM_COMMAND  - 处理应用程序菜单
//  WM_PAINT    - 绘制主窗口
//  WM_DESTROY  - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{switch (message){case WM_COMMAND:{int wmId = LOWORD(wParam);// 分析菜单选择:switch (wmId){case IDM_ABOUT:DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);break;case IDM_EXIT:DestroyWindow(hWnd);break;default:return DefWindowProc(hWnd, message, wParam, lParam);}}break;case WM_PAINT:{PAINTSTRUCT ps;HDC hdc = BeginPaint(hWnd, &ps);// TODO: 在此处添加使用 hdc 的任何绘图代码...EndPaint(hWnd, &ps);}break;case WM_DESTROY:PostQuitMessage(0);break;default:return DefWindowProc(hWnd, message, wParam, lParam);}return 0;
}// “关于”框的消息处理程序。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{UNREFERENCED_PARAMETER(lParam);switch (message){case WM_INITDIALOG:return (INT_PTR)TRUE;case WM_COMMAND:if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL){EndDialog(hDlg, LOWORD(wParam));return (INT_PTR)TRUE;}break;}return (INT_PTR)FALSE;
}

怎么去理解这个 cpp 呢?或许可以去看一看《Windows 游戏编程之从零开始》前九章的内容(主要是前四章),也可以上微软官方文档学习:演练:使用 C++ Windows创建 (桌面) | Microsoft Docs

现在进行修改。

  1. 多加两个宏 WINDOW_WIDTHWINDOW_HEIGHT 来方便地修改窗口大小以及一些图片的显示参数。

  1. 我不需要窗口菜单。或者我们以后需要可以再加上,现在开一个没有实际内容的空菜单是没有意义的。窗口的注册被封装在了函数 MyRegisterClass() 中,查看,将窗口类的 lpszMenuName 修改为 NULL,这样就不会有窗口菜单出现了。

  1. 新建一个 hdc 绘制背景图片的函数并实现。

```cpp
VOID BackGround_Paint(HWND hwnd)
{hdc = GetDC(hwnd);hBackGround = (HBITMAP)LoadImage(NULL, L"主题图_傀影与猩红孤钻.bmp", IMAGE_BITMAP, WINDOW_WIDTH, WINDOW_HEIGHT, LR_LOADFROMFILE);mdc = CreateCompatibleDC(hdc);SelectObject(mdc, hBackGround);BitBlt(hdc, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, mdc, 0, 0, SRCCOPY);
}
```进行调用:

最后,再修改一下工程里的 ico 文件,尝试调试运行:

二、创建类

一个基础的窗体已经创建好了,现在我们需要根据设定把各个类的原型创建出来。

在资源管理器中选择添加 -> 新建项 -> 新建 C++ 类,系统就会自动创建其头文件和实现的 .cpp 文件.


我关于游戏中类的设定(摘自我自己的游戏设定):

干员类

每个干员的固定属性有:

生命值
防御力
法术抗性
攻击力
攻击类型
生命值成长
防御力成长
攻击力成长
技能接口

动态属性有:
当前等级
当前生命值

每个干员有4个等级,只可以通过晋升的方法来增加等级(没有经验值系统哦亲)。低星级的干员升级获得的收益更高,但是等级上限更低。

敌人类

敌人属性有:

生命值

防御力

法术抗性

战利品等级

根据战利品等级在战斗结束后会发放不同的奖励。


地图元素

地图(单元格)

地图用于承载地图元素,地图元素包括:地面、掉落物、陷阱、人物、障碍物、门、NPC和敌人. 每层地图都是由12x12单元格构成的.

地面

地面只有贴图的差别,地面所在的单元格可以叠放人物、门、NPC和敌人。通常情况下,地面是最先绘制的。当需要一些隐藏互动时,其也可能在之后被绘制。不论如何,同一个单元格只能叠放两个元素,且其中一个一定是地面。

事实上,设定地面类统一到地图元素类中的目的其实就是为了更方便地确定绘制优先级。

或许还可以为地图样式增彩吧。

掉落物

掉落物可以也仅可以被人物拾取。 拾取后进行交互。交互后,人物移动到掉落物所在单元格,且掉落物将消失。

交互应当为可以重写的接口,广泛使用。

障碍物

地图中不可通过的部分。和地面一样,也只有贴图的差别。只不过其上不可以叠放任何东西。

门实际上应当继承自障碍物。其继承了交互接口:开门方法。

交互后将消失。

陷阱

陷阱有可能会有先于地面绘制的情况。当然也可以直接显示出来。总之陷阱在人物通过时总是会进行交互。

交互后将消失。

人物

人物是交互的重要主体。交互不论如何重写,总是要和人物交互的。人物元素总是将当前出战的干员绘制在其所属的单元格上。

当交互后返回了人物死亡信息时游戏将结束;

​ 返回了干员倒地信息时强制更换干员;

​ …

NPC与敌人

NPC和敌人都可以进行交互。但是交互后不会立刻消失,而需要更多的判定。敌人的属性细节见敌人类

地图生成

理想的方法是使用随机种子生成地图。但在尚不清楚方法的情况下肯定是需要做一些固定地图来测试的。

边栏

SideBar需要存储相当的信息。

它存储游戏进行的实时信息,

干员库

干员库作为一个堆栈实现。其中要动态保存各种干员信息。干员库需要实时展示在窗口上。当干员库为空,上场干员也为NULL时,对于交互返回人物死亡信息。如果只有上场干员为NULL,返回干员倒地信息。

上场干员

无需多说.

图鉴(待定)

图鉴在测试模式中无可厚非,不用也可以测试。其实现过程中的难点无非是如何确定安装游戏后第一次进行某种操作,以此解锁图鉴。因此待定。

银行

银行中保存你当前的源石锭和部署费用。

例如我要写一个 “边栏” 类的原型,在此之前我已经写了角色类 Character 及其子类干员类 COperator的原型。那么我,创建一个名为 SideBar 的类,并将干员类 COperator 的头文件 Coperator.h 包含进 SideBar 类的头文件。并给出干员库、上场干员、源石锭和部署费用的定义:

#pragma once
#include <vector>
#include "Coperator.h"
class SideBar
{std::vector<COperator> operators; // 当前干员COperator currentOperator; // 当前出战的干员int ingot; // 源石锭int cost; // 部署费用
};

其他类的头文件:

角色类 Character.h

#pragma onceenum AttackType
{Physical,Magic
};class Character
{protected:double healthPoint; // 生命值double defensePoint; // 防御力double magicResisitance; // 法抗double attackDamage; // 攻击力AttackType attackType; // 攻击类型double Attack(Character& another); // 一次攻击响应的函数
};

敌人类 CEnemy.h

#pragma once
#include "Character.h"
class CEnemy :public Character
{unsigned int bootyLevel; // 战利品等级
};

干员类 COperator.h

#pragma once
#include "Character.h"
class COperator :public Character
{double healthPointGrow; // 生命值成长double defensePointGrow; // 防御力成长double attackDamageGrow; // 攻击力成长double healthPointNow; // 当前生命double levelNow; // 当前等级void (*Ability)(int, double&); // 技能函数指针void LevelUp(); // 升级void UseAbility(); // 使用技能
};

地图元素类(基类)MapElement.h

#pragma once
class MapElement
{protected:unsigned int priority; // 绘制优先级unsigned int px; // 在地图上的x坐标unsigned int py; // 在地图上的y坐标const bool walkable = 1; // 能否在其上行走. 对于确定的类型,这一值是确定的.const bool interactable = 1; // 走入是否会发生交互
};

地图元素干员类 MCharacter.h

#pragma once
#include "MapElement.h"
class MCharacter :public MapElement
{};

地图元素门类 MDoor.h

#pragma once
#include "MapElement.h"
class MDoor :public MapElement
{const bool walkable = 0;const bool interactable = 1;
};

地图元素掉落物类 MDrop.h

#pragma once
#include "MapElement.h"
class MDrop :public MapElement
{const bool walkable = 1;const bool interactable = 1;
};

地图元素地面类 MGround.h

#pragma once
#include "MapElement.h"
class MGround :public MapElement
{const bool interactable = 0;const bool walkable = 1;
};

地图元素 NPC 类 MNonPlayerCharacter.h

#pragma once
#include "MapElement.h"
class MNonPlayerCharacter :public MapElement
{const bool walkable = 0;const bool interactable = 1;
};

地图元素障碍物类 MObstacle.h

#pragma once
#include "MapElement.h"
class MObstacle :public MapElement
{const bool walkable = 0;const bool interactable = 0;
};

小憩

到这里,我的游戏制作完成了第一步:新建文件夹 QAQ

下一次更新要实现的,是地图的显示。尝试让我们 12x12 的地图完整地显示在窗口中。


有任何问题都可以问我捏。: )

从零开始用 Windows C++ 桌面程序制作方舟同人游戏(一)相关推荐

  1. 【原创游戏】合金弹头S——Unity制作的同人游戏

    --纪念合金弹头陪伴的童年,回味横版过关射击快感 合金弹头S是一款Unity制作的同人游戏,由于当时我只是一个学了3个月游戏的同学,所以尚有一些不足之处,但这是我用Unity做的第一个原创作品 下载地 ...

  2. Go实战--使用golang开发Windows Gui桌面程序(lxn/walk)

    生命不止,继续 go go go!!! golang官方并没有提供Windows gui库,但是今天还是要跟大家分享一下使用golang开发Windows桌面程序,当然又是面向github编程了. 知 ...

  3. windows之桌面程序引导功能

    文章目录 一.首先是获取桌面窗口句柄 二.获取桌面图标位置 三.效果展示 经常使用windows系统的同学可能都会遇到这样一种情况,刚按照完的应用程序,可能会在桌面产生一个提示信息,指示当前快捷方式可 ...

  4. 基于Windows的桌面程序——记事本的制作

    1.新建项目 启动Visual Studio .NET ,在"文件"菜单上,指向"添加",然后单击"新建项目"打开"新建项目&qu ...

  5. 【android】从零开始学习安卓录制回放程序制作需要多少天?

    文章目录 序 功能需求 总结 编写目标程序 测试程序编写 测试程序界面代码 Xposed学习 夜神模拟器下载安装 测试程序Hook思路 hook EditText hook click listene ...

  6. windows下安装程序制作

    引用链接: https://blog.csdn.net/signjing/article/details/7855855 工具: 1.脚本编辑工具 hmnisedit_downcc.zip 百度云盘链 ...

  7. Windows下Qt程序打包

    Windows下Qt程序打包 将windeployqt.exe 目录添加到系统环境变量 windeployqt.exe目录如下: 命令行打包 1.打开命令行 2.执行打包命令 windeployqt ...

  8. .net Windows服务程序和安装程序制作图解

    最近项目中用到window服务程序,以前没接触过,比较陌生,花了两天的时间学习了下,写了个简单的服务,但在制作安装程序的时候,参照网上很多资料,却都制作不成功,可能是开发环境或项目配置的不同,这里把自 ...

  9. python卸载_如何为Python程序制作Windows安装包?

    我们使用Python可以编写很多提高工作和学习效率的小工具,在编写完之后,如果我们想将其分享给更多的人使用,那么最便捷的方法就是将其打包为可执行程序. 在Windows环境下,我们使用Pyinstal ...

最新文章

  1. AAAI 2021 最「严」一届发榜,1692 篇论文中选,录取率仅为 21%
  2. 重磅!亚马逊将在2019年全面弃用Oracle数据库
  3. C++ 对象内存布局 (4)
  4. SAP Hybris使用recipe进行安装时,是如何执行ant命令的?
  5. 《自然》年度十大人物出炉!中国科学家入选
  6. 一次性杯子机器人挂件手工制作_变废为宝 ———环保笔筒制作
  7. html打包成APP,h5打包成apk,浅显易懂!
  8. NI 国家仪器 各版本软件下载链接
  9. 落户雄安,千方科技助力新区打造智慧出行样板
  10. 计算机windows7桌面是指什么,在windows7中,桌面指的是什么
  11. 2019云栖大会归来有感
  12. php独孤九剑,(独孤九剑)--PHP 视频学习 -- 文件系统
  13. 5 个免费的受欢迎的 SQLite 管理工具
  14. 使用插入排序、归并排序对链表进行排序
  15. 进程监控工具 Procmon有Linux版本了
  16. 雷电模拟器重置开机密码
  17. long + ulong_ULONG_MAX常数,带C ++示例
  18. 随心测试_Python Se_005鼠标悬停操作
  19. xamarin android 微信,转换微信SDK为Xamarin绑定库 Android5.5.8 iOS1.8.6.2
  20. R语言可视化——动态心型图

热门文章

  1. 百度大脑驾驶证识别使用攻略
  2. JdbcTemplate空指针异常
  3. outlook邮箱收件服务器密码,微软邮箱(hotmail+outlook):应用密码获取+STARTTLS加密...
  4. 如何将pdf修改编辑
  5. C# GDAL 数字图像处理Part7 仿射变换图像配准
  6. 2021哈工大计算机专业考研参考书,哈尔滨工业大学计算机专业考研参考书目推荐...
  7. 武汉工程大计算机学校地址,武汉工程学院
  8. 我们会是最后一批用五笔的中国人吗?
  9. [ARM]GIC相关知识
  10. word恢复忘记保存的文档