用DevCPP编写游戏
本文基于Windows
。
约定
不要太颓废。
文中代码有许多头文件,总共#include
一次就够了。
核心内容
用户数据保存
存在哪里?
通常来说,一个健全的游戏总是能记录用户的数据。
当程序被关闭后,内存会被释放,所以我们必定需要讲内容存储在硬盘的文件里。但因此新的问题随之而来,游戏的位置随时可能改变,而且一系列空格与中文使得绝对路径的使用变得困难。但如果使用相对路径,有会使得同一台电脑上的不同游戏个体无法共享数据,这里提供两种解决方案。
- 如果您希望电脑上的不同用户共享同一内容(注意,这里的用户不是游戏中的用户,而是系统启动界面的不同用户,即在
C:\Users
文件夹下的各文件名),您可以选择在C:\ProgramData
下建立游戏用户文件夹。 - 如果您希望电脑上的不同用户分开储存,您可以选择在
%appdata%
下建立游戏用户文件夹(通常来说这个路径在系统中会被处理成C:\Users\用户名\appdata\Roaming
如何读写?
推荐使用fstream
头文件下的流,语法具体如下:
#include<fstream>
#include<string.h>
//......
std::string FilePath= __balabala__;//你的用户文件路径,最好是绝对路径。
void GetUserInfo()
{
std::ifstream fin(FilePath.c_str());//接下来像cin一样使用
fin>>__wabibabu__;//各类信息
//.......
fin.close();
}
void SaveUserInfo()
{
std::ofstream fout(FilePath.c_str());//接下来像cout一样使用
fout<<__rowrowrow__<<endl;//各类信息
//.......
fout.close();
}
这里顺带说一声,写游戏最好不要用using namespace std;
,如果嫌std::
麻烦,请使用using std::__name__;
,其中__name__
为你要的变量或函数名。
如何记录
- 如果是数字或者字符串数据,直接存储。
- 如果是类似于获得物品等,可以考虑二进制,如果有等级,则为 理论最大等级 + 1 \text{理论最大等级} + 1 理论最大等级+1 进制。如果太大,则分段存储。
注意记录方法始终保持一致,这里推荐把信息存在全局变量里。
如何加密
主要分两类:
- 密码类数据
- 用户进度数据
密码类数据
可以考虑记录 hash ,而非原串,可以考虑 OI 中的加密方式。
typedef unsigned long long ull;
ull GetStringHash(std::string str)
{
ull ans=0;
const ll BASE=131
int n=str.size();
for(int i=0; i<n; i++)
ans=ans*BASE+str[i];
return ans;
}
注意该函数应始终保持一致。
用户进度数据
我们可以认为全部是整数可以表达的,所以我们只讨论整数储存,此时有两种方法。
特殊进制配映射
可以使用户不知道如何篡改。
typedef unsigned long long ull;
//......
const int BASE=-2;
std::string i2s(ull t)//加密
{
if(t==0)
return ".";
ull r=t/BASE;
t=t%BASE;
if(t<0)
t=t+2,r=r+1;
return i2s(r)+(t?'_':'.');
}
ull s2i(std::string s)//解密
{
int n=s.size();
ull ans=0;
for(int i=0; i<n; i++)
ans=ans*BASE+(s[i]=='_'?1:0);
return ans;
}
BASE
不一定是-2
,同时不一定.
代表0
,_
代表1
,您可以自己做出选择。
把所有数首位相连成字符串,记录 hash
可以判断用户是否进行了篡改。
//这里给出拼接代码
#include<sstream>
//......
std::string UserInfo2String()
{
std::stringstream str;
str<<__dengdualang__;//the information
//......
std::string ans;
str>>ans;
return ans;
}
音乐
储存
推荐储存在游戏文件夹同一位置以使用相对路径,或者与用户信息一起存储,此时需要安装时操作,下文会有讲解。
背景音乐播放
我们采用MCI
进行音乐播放。注意,此时您的游戏必须是DevCPP
中的项目,可以通过file
->new
->Project
->Basic
->Console Application
(控制台) or Windows Application
(窗口程序)建立。
你需要在Project
->Project options
->parameters
->linker
->中加入以下参数-lwinmm -mwindows
。
接下来给出参考程序部分:
#include<Windows.h>
#include<Mmsystem.h>
//......
//BGM应为mp3格式
void SendMCIMessage(string y)//发送消息给MCI
{//注意以下函数并非定义于std域内,不要手痒
char buf[260];
MCIERROR err;
err=mciSendString(y.c_str(),buf,sizeof(buf),NULL);
if(err)//发送失败会报错
{
mciGetErrorString(err,buf,sizeof(buf));
MessageBox(NULL,buf,"ERROR",MB_OK);
MessageBox(NULL,y.c_str(),"FAIL",MB_OK);
}
}
void PlayBGM(std::string BGMpath)//相对路径或者绝对路径皆可,最好绝对路径
{
std::string t;
t="open "+BGMpath+" Alias bgm";
SendMCIMessage(t);
SendMCIMessage("play bgm repeat");//洗脑循环
}
void StopBGM()
{
SendMCIMessage("stop bgm");//注意要在一个线程内才有效,否则会报错 指定的设备未打开,或不被MCI所识别
}
音效播放
推荐使用MCI
或者PlaySound
函数。
MCI
void PlayMySound(std::string SoundPath)
{
std::string t;
t="open "+SoundPath+" Alias sound";/
/同时只能播放一个,如要同时多个,采用不能名字,即Sound换其他名字
SendMCIMessage(t);
SendMCIMessage("play sound");//异步执行
//如果指令为play sound wait,则会等待播放完毕,这会导致线程停滞,不建议使用,可以考虑记录音效时长配clock(),到达时间后操作。
}
PlaySound
注意,只能播放不超过10MB的wav
文件。
自行搜索,笔者更推荐MCI
。
其他内容
游戏机制是十分复杂的,我不得不承认这比OI中大模拟还难,所以需要我们的程序编写有序,您应该可以看到,笔者的函数命名十分有规律。
这里希望您善用.h
文件,同时使用DevCPP
左侧边栏的Classes
部件。
好看的游戏
控制台
五彩斑斓
注意setletter
函数和个变量意义。
#include<Windows.h>
#include<string.h>
//推荐至于同目录下 output.h内,在main.cpp中#include "output.h"
//fore: 前景 back: 背景
WORD _white_fore=FOREGROUND_INTENSITY|FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE;
WORD _red_fore=FOREGROUND_INTENSITY|FOREGROUND_RED;
WORD _green_fore=FOREGROUND_INTENSITY|FOREGROUND_GREEN;
WORD _blue_fore=FOREGROUND_INTENSITY|FOREGROUND_BLUE;
WORD _yellow_fore=FOREGROUND_INTENSITY|FOREGROUND_RED|FOREGROUND_GREEN;
WORD _purple_fore=FOREGROUND_INTENSITY|FOREGROUND_RED|FOREGROUND_BLUE;
WORD _cyan_fore=FOREGROUND_INTENSITY|FOREGROUND_GREEN|FOREGROUND_BLUE;
WORD _grey_fore=FOREGROUND_INTENSITY;
WORD _white_back=BACKGROUND_INTENSITY|BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_BLUE;
WORD _red_back=BACKGROUND_INTENSITY|BACKGROUND_RED;
WORD _green_back=BACKGROUND_INTENSITY|BACKGROUND_GREEN;
WORD _blue_back=BACKGROUND_INTENSITY|BACKGROUND_BLUE;
WORD _yellow_back=BACKGROUND_INTENSITY|BACKGROUND_RED|BACKGROUND_GREEN;
WORD _purple_back=BACKGROUND_INTENSITY|BACKGROUND_RED|BACKGROUND_BLUE;
WORD _cyan_back=BACKGROUND_INTENSITY|BACKGROUND_GREEN|BACKGROUND_BLUE;
WORD _grey_back=BACKGROUND_INTENSITY;
WORD _white_deep_fore=FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE;
WORD _red_deep_fore=FOREGROUND_RED;
WORD _green_deep_fore=FOREGROUND_GREEN;
WORD _blue_deep_fore=FOREGROUND_BLUE;
WORD _yellow_deep_fore=FOREGROUND_RED|FOREGROUND_GREEN;
WORD _purple_deep_fore=FOREGROUND_RED|FOREGROUND_BLUE;
WORD _cyan_deep_fore=FOREGROUND_GREEN|FOREGROUND_BLUE;
WORD _black_fore;
WORD _white_deep_back=BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_BLUE;
WORD _red_deep_back=BACKGROUND_RED;
WORD _green_deep_back=BACKGROUND_GREEN;
WORD _blue_deep_back=BACKGROUND_BLUE;
WORD _yellow_deep_back=BACKGROUND_RED|BACKGROUND_GREEN;
WORD _purple_deep_back=BACKGROUND_RED|BACKGROUND_BLUE;
WORD _cyan_deep_back=BACKGROUND_GREEN|BACKGROUND_BLUE;
WORD _black_back;
WORD _now;//现在情况,可以不记录
void setletter(WORD color)//设置文字样式
{
_now=color;
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),color);
}
满天星
采用setxy
函数更换输出位置。
#include <Windows.h>
//......
void setxy(short x,short y)//设置输出位置
{
COORD pos= {x,y};
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),pos);
}
Win32应用程序
要说的太多了,部分内容可以学习DevCPP
里的模版,位于File
->New
->Project
下,有很多模版。
窗口注册
参考资料:BDFS C++ Windows编程
。
推荐采用如下代码
#include <Windows.h>
//.....
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine,
int nCmdShow)
{
WNDCLASSEX wc;
memset(&wc,0,sizeof(wc));
wc.cbSize = sizeof(WNDCLASSEX);
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszClassName = "AskWindowsClass";
wc.lpszMenuName = "MAIN";//你的菜单名称,下文有所叙述
wc.hIcon = LoadIcon(hInstance, "A");//项目图标,于 Project->Project Options->General->Icons下选择
wc.hIconSm = LoadIcon(hInstance, "A")
if(!RegisterClassEx(&wc))
{
MessageBox(NULL, "Window Registration Failed!","Error!",
MB_ICONEXCLAMATION|MB_OK);
return 1;
}
HWND hwnd = CreateWindowEx(WS_EX_CLIENTEDGE,"AskWindowsClass","Game",//窗口类名和窗口名
WS_VISIBLE|WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
520,//窗口大小
364,
NULL,NULL,hInst,NULL);
if(hwnd == NULL)
{
MessageBox(NULL, "Window Creation Failed!","Error!",MB_ICONEXCLAMATION|MB_OK);
return;
}
ShowWindow(hwnd,1);
UpdateWindow(hwnd);
while(GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);//开始消息循环
DispatchMessage(&msg);
}
};
处理消息
给出消息处理函数的例子,如有其他需求请自行百度。
LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
switch(Message)
{
case WM_CREATE://当窗口创建时
DWORD style;
style=GetWindowLong(hwnd, GWL_STYLE);
style=style&(~WS_THICKFRAME)|WS_DLGFRAME;
SetWindowLong(hwnd, GWL_STYLE, style);//设置窗口大小不可改变
break;
case WM_CHAR://键盘按键
switch(wParam)
{
case 'p':按下p
//Do something.
break;
}
break;
case WM_COMMAND:
switch(LOWORD(wParam))//一堆子消息
{
case CM_CLOSE://其实这是一个数值,我们通常在main.h中这样写
/*
#define CM_CLOSE 1000
其中1000是1-32767中随意的数值,通常取[1000,10000)
*/
DestroyWindow(hwnd)
break;
}
break;
case WM_CLOSE://请求关闭
SendMessage(hwnd,WM_COMMAND,CM_CLOSE,0);//这是发送消息的函数,hwnd为窗口句柄,WM_COMMAND为消息类型,CM_CLOSE为参数,往上看,您很容易理解
break;
case WM_DESTROY://窗口已经被销毁
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, Message, wParam, lParam);
}
return 0;
}
窗口菜单
请在项目中新建main.rc
,这是一个资源文件。
#include "main.h"//你的子消息定义文件
MAIN MENU//如WinMain函数中wc的menu属性,名字保持相同
{
POPUP "&Work"//一个组,&之后的是快捷按,Alt+建激活选项
{
MENUITEM "&Add", CM_add//一个按键,点击后会类似于SendMessage(hwnd,WM_COMMAND,CM_add,0);向消息处理函数发送消息
MENUITEM SEPARATOR//分割线
}
MENUITEM "&Close", CM_close
}
绘图
自行BDFS
控件
补充内容BDFSC++ Windows编程 控件
。
通常建议使用button
和edit
。
HWND BTcsk,EDask,EDtodo;
BTcsk/*窗口句柄*/ = CreateWindow("button"/*按钮*/, "cancel"/*上面的文字*/,
WS_CHILD|WS_VISIBLE|WS_BORDER,
225, 246, /*左上角坐标*/150, 20,/*窗口大小*/
hwnd, (HMENU)CC_ask/*响应的子消息,类似SendMessage(hwnd,WM_COMMAND,CM_ask,0)*/
hInst/*句柄,同Winmain中的hInstance,需要传参或者定义为全局*/, NULL
);
Edask = CreateWindow("edit"/*文本框*/, "",
WS_CHILD|WS_VISIBLE|WS_BORDER|ES_AUTOHSCROLL/*各参数,可自行百度*/,
75, 227, 300, 19,
hwnd, NULL, hInst, NULL
);
SendMessage(Edtodo/*窗口句柄*/,EM_SETREADONLY,1,0);//设置文本框只读
句柄建议命名类型大写+消息小写。
输出图片
自行百度C++ Windows编程 输出图片
,不做展开。
杂项
笔者认为这已经比大模拟复杂了,我们在这里接触了许多新语法,所以容易出现bug,推荐先编写完console的游戏内部后再进行界面编写,避免大量出错。
安装包
很明显,上述过程有很多是需要初始化的,这就需要安装包出场了。
无论是WinRAR
的sfx
还是专业的安装包软件,都支持bat
的预处理,因此接下来我们讲述批处理编写。
批处理
对于目录·,应该一律使用"Path"
,也就是双引号嵌套。
开头为
@echo off
- 创建目录: 在
CMD
中输入md \?
并仔细查看。 - 移动文件: 在
CMD
中输入move \?
并仔细查看。
创建桌面快捷方式:
set Program=D:\bvsg\bvsg.exe %文件位置%
set LnkName=bvsg %快捷方式名称%
set WorkDir=D:\bvsg %文件目录%
set Desc=bvsg
if not defined WorkDir call:GetWorkDir "%Program%"
(echo Set WshShell=CreateObject("WScript.Shell"^)
echo strDesKtop=WshShell.SPEcialFolders("DesKtop"^)
echo Set oShellLink=WshShell.CreateShortcut(strDesKtop^&"\%LnkName%.lnk"^)
echo oShellLink.TargetPath="%Program%"
echo oShellLink.WorkingDirectory="%WorkDir%"
echo oShellLink.Windowstyle=1
echo oShellLink.Description="%Desc%"
echo oShellLink.Save)>makelnk.vbs
makelnk.vbs
del /f /q makelnk.vbs
exit
goto :eof
:GetWorkDir
set WorkDir=%~dp1
set WorkDir=%WorkDir:~,-1%
goto :eof
sfx的使用
自行BDFS,WinRAR 自解压
。
总结
讲了这么多颓废的方式,不要真颓废啊。
我们发现,要写一个游戏,尤其是Win32
的,码量非常大,且及易出错,通常要一个暑假才能写出一个游戏,所以除非非常闲,不要去碰Win32
,Console
写写得了,毕竟OIer学算法不是为了去当前端的。