用DevCPP写游戏

· · 个人记录

本文基于Windows

约定

不要太颓废。 文中代码有许多头文件,总共#include一次就够了。

核心内容

用户数据保存

存在哪里?

通常来说,一个健全的游戏总是能记录用户的数据。

当程序被关闭后,内存会被释放,所以我们必定需要讲内容存储在硬盘的文件里。但因此新的问题随之而来,游戏的位置随时可能改变,而且一系列空格与中文使得绝对路径的使用变得困难。但如果使用相对路径,有会使得同一台电脑上的不同游戏个体无法共享数据,这里提供两种解决方案。

如何读写?

推荐使用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__为你要的变量或函数名

如何记录

注意记录方法始终保持一致,这里推荐把信息存在全局变量里。

如何加密

主要分两类:

密码类数据

可以考虑记录 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编程 控件。 通常建议使用buttonedit

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的游戏内部后再进行界面编写,避免大量出错。

安装包

很明显,上述过程有很多是需要初始化的,这就需要安装包出场了。

无论是WinRARsfx还是专业的安装包软件,都支持bat的预处理,因此接下来我们讲述批处理编写。

批处理

对于目录·,应该一律使用"Path",也就是双引号嵌套。 开头为

@echo off

创建桌面快捷方式:

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的,码量非常大,且及易出错,通常要一个暑假才能写出一个游戏,所以除非非常闲,不要去碰Win32Console写写得了,毕竟OIer学算法不是为了去当前端的。