- 该游戏是用win32窗体模块写的,编译器是DevC++。
先上图(没有后期优化,只是简单的给按钮贴了点图,好丑。。。)
开始界面
选牌界面
战斗界面
卡片图鉴
PS:制作中所用全部图片皆为百度找的,只用于练手。
下面开始说我的制作思路
- 首先是构思,写代码前得先设计一个具体的游戏出来,比如这个卡牌游戏,是我高中时代的一个同学设计的实体卡牌,我在大学期间基于练手将他做成电脑游戏。
- 接着是构建游戏的代码的整体框架,win32自带的消息循环体系其实已经给我们搭了框架,我们只需要向其中加入我们想实现的功能。
- 我的这款游戏是卡牌,所以核心在于怎样实现卡片的技能对其他卡片造成的影响。其次,这款游戏的运行基于按钮的点击,所以怎样处理按钮之间的关联也是难点。贴图是我遇到的最后一个难关,网上有关win32贴图的内容真是少之又少,代码部分我会讲解,这里不做称述。
从零开始的制作步骤
- 用自己的编译软件新建win32项目
- 建好后会给你你一堆很长的代码,如下
PS:如果你是零基础,我会把主要消息写在最后,读我的代码时也可参照着看,至于那些陌生的函数,每一个百度上都能搜到具体用法。 - 下面开始构思了
第一步:设计开始界面,比如开始界面
(本贴只以此界面演示界面的制作) - 这个界面由下面两张图片组合而成
- 在绘图的消息循环WM_PAINT中设置好两张图的比例后将其显示(贴图步骤过于复杂,此片段里所用贴图函数为自行构造,后序代码片段会有讲解)
sprintf(ch,"%s\\picture\\start.bmp",path);
LoadPicture(hdc,ch,C_Width*76/100,C_Height,0,0);
sprintf(ch,"%s\\picture\\startmenu.bmp",path);
LoadPicture(hdc,ch,C_Width*34/100,C_Height,C_Width*76/100,0);
- 接着在创造消息循环WM_CREATE中加入四个按钮后回到WM_PAINT中给其贴图
创建:
hwndStart[0]=CreateWindow("Button","开始游戏",WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON | BS_FLAT | BS_BITMAP,
C_Width*81/100,C_Height*50/100,C_Width*14/100,C_Height*7/100, hwndmenu, (HMENU)1,((LPCREATESTRUCT)lParam)->hInstance, NULL);/*创建开始游戏按钮*/
hwndStart[1]=CreateWindow("Button","卡片图鉴",WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON | BS_FLAT | BS_BITMAP,
C_Width*81/100,C_Height*60/100,C_Width*14/100,C_Height*7/100, hwndmenu, (HMENU)2,((LPCREATESTRUCT)lParam)->hInstance, NULL);/*创建卡片图鉴按钮*/
hwndStart[2]=CreateWindow("Button","游戏规则",WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON | BS_FLAT | BS_BITMAP,
C_Width*81/100,C_Height*70/100,C_Width*14/100,C_Height*7/100, hwndmenu, (HMENU)3,((LPCREATESTRUCT)lParam)->hInstance, NULL);/*创建游戏规则按钮*/
hwndStart[3]=CreateWindow("Button","退出游戏",WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON | BS_FLAT | BS_BITMAP,
C_Width*81/100,C_Height*80/100,C_Width*14/100,C_Height*7/100, hwndmenu, (HMENU)4,((LPCREATESTRUCT)lParam)->hInstance, NULL);/*创建退出游戏按钮*/
hwndDeal=CreateWindow("Button","开始发牌",WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON | BS_FLAT | BS_BITMAP,
贴图:
for(i=1;i<=4;i++)
{
sprintf(ch,"%s\\picture\\button\\hwndStart[%d].bmp",path,i-1);/*载入开始界面按钮图片*/
LoadButtonPicture(hwndStart[i-1],ch,C_Width*14/100,C_Height*7/100,0,0);
}
第二步:建立按钮之间的关系,比如开始界面
- 点击了前三个按钮后会跳转至下一个界面,点击退出游戏后会关闭游戏。
- 在按钮已经构建完成后,可以在命令消息循环WM_COMMAND里加入相应命令。(这里用到的参数代码部分会有讲解)
if(win==0)//0:开始菜单
{
switch(LOWORD(wParam))
{
case 1://开始游戏
{
if(HIWORD(wParam)==BN_CLICKED)
{
win=1;//进入窗口1
for(i=1;i<=4;i++)//移除窗口1按钮
MoveWindow(hwndStart[i-1],0,0,0,0,true);
MoveWindow(hwndReturnMenu,C_Width*1/100,C_Height*93/100,C_Width*9/100,C_Height*5/100,true);载入返回主菜
MoveWindow(hwndwin1[0],C_Width*15/100,C_Height*20/100,C_Width*30/100,C_Height*60/100,true);载入对战模式按钮
MoveWindow(hwndwin1[1],C_Width*55/100,C_Height*20/100,C_Width*30/100,C_Height*60/100,true);载入冒险模式按钮
IsRedraw=true;//重新绘图
}
break;
}
case 2://卡片图鉴
{
if(HIWORD(wParam)==BN_CLICKED)
{
win=2;//进入窗口2
win2=0;
for(i=1;i<=4;i++)//移除窗口1按钮
MoveWindow(hwndStart[i-1],0,0,0,0,true);
for(i=1;i<=cardnum;i++)
MoveWindow(hwndCatalogueNumber[i-1],C_Width*4/100+((i-1)%10)*C_Width*10/100,C_Height*29/100+(i-((i-1)%10))/10*C_Height*29/100,C_Width*2/100,C_Height*5/100,true);
MoveWindow(hwndReturnMenu,C_Width*1/100,C_Height*93/100,C_Width*9/100,C_Height*5/100,true);载入返回主菜单按钮
IsRedraw=true;//重新绘图
}
break;
}
case 3://游戏规则
{
if(HIWORD(wParam)==BN_CLICKED)
{
win=3;//进入窗口3
for(i=1;i<=4;i++)//移除窗口1按钮
MoveWindow(hwndStart[i-1],0,0,0,0,true);
MoveWindow(hwndReturnMenu,C_Width*1/100,C_Height*93/100,C_Width*9/100,C_Height*5/100,true);载入返回主菜单按钮
IsRedraw=true;//重新绘图
}
break;
}
case 4://退出游戏
{
if(HIWORD(wParam)==BN_CLICKED)
{
if(MessageBox(hwndmenu,"点击了退出游戏!","提示",true)==1)
PostQuitMessage(0);
}
break;
}
default:
break;
}
}
- 我们可以用比较简单的思路完成除去卡片之间战斗外全部按钮的定义。
第三步,加入卡牌
- 我的做法是构造一个卡牌类,因为图省事,就没有在构建一系列的子类,每当需要一个新的功能时,直接在主类里添加函数。(函数名我觉得翻译过来差不多就是功能,这里不做备注了)
class Card
{
public:
Card(){}
char GetCardName();
int GetCardAttack();
int GetCardLife();
int GetCardAttackDistance();
int GetCardShield();
int GetCardId();
int GetCardPosition();
int GetCardCouple();
int GetCardReverseRoundNum();
bool GetIsBack();
bool GetIsAlreadyAttack();
bool GetIsDie();
bool GetIsFirstReverse();
bool GetIsAlreadyChangePosition();
bool GetIsBeAttacked();
void SetCardAttribute(int cardid,int attack,int life,int attackdistance);
void SetCardPosition(int positon);
void SetCardId(int cardid);
void SetCardLife(int life);
void SetCardShield(int shield);
void SetCardAttack(int attack);
void SetCardCouple(int couple);
void SetCardReverseRoundNum(int reverseroundnum);
void ChangeIsBack();
void ChangeIsDie();
void ChangeIsAlreadyAttack();
void ChangeIsFirstReverse();
void ChangeIsAlreadyChangePosition();
void ChangeIsBeAttacked();
private:
char *cardname;
int cardid;
int attack;
int life;
int shield=2;
int attackdistance;
int position;
int couple=0;
int reverseroundnum=0;
bool isalreadyattack=false;
bool isback=true;
bool isdie=false;
bool isfirstreverse=false;
bool isalreadychangeposition=false;
bool isbeattacked=false;
};
以上三步为核心思路,下面给出全部代码即部分注释,想从零学的一行行读,想找自己需要的内容的朋友,如果找到了点个赞再走吧(^_−)☆
用到的工具类头文件tools.h
#ifndef TOOLS_H
#define TOOLS_H
#include <windows.h>
#include "card.h"
const int cardnum=14;//卡片总数
void LoadPicture(HDC hdc,char *s,int width,int height,int x,int y);
void DrawRect(HDC hdc,int left,int top,int right,int bottom);
void LoadCard(Card card[cardnum]);
void LoadButtonPicture(HWND hwnd,char *s,int width,int height,int x,int y);
#endif//TOOLS_H
工具类函数tools.cpp
#include "tools.h"
void LoadPicture(HDC hdc,char *s,int width,int height,int x,int y)
{
HDC mdc = CreateCompatibleDC(hdc);
HBITMAP hbmp;
hbmp = (HBITMAP)LoadImage(
NULL, // 模块实例句柄
s, // 位图路径。注意双斜杠,单斜杠表示转义,此时文件会加载不成功!!!
IMAGE_BITMAP, // 图片类型
width, //指定图片宽
height, //指定图片高
LR_LOADFROMFILE // 从路径处加载图片
);
SelectObject(mdc, hbmp);
BitBlt(
hdc, // 目的DC
x, y, // 目的DC的 x,y 坐标
width, // 要粘贴的图片的宽
height, // 要粘贴的图片的高
mdc, // 缓存DC
0, 0, // 缓存DC的x,y坐标
SRCCOPY // 粘贴方式
);
DeleteObject(hbmp);
DeleteDC(mdc);
}
void LoadButtonPicture(HWND hwnd,char *s,int width,int height,int x,int y)
{
HBITMAP hbmp;
hbmp = (HBITMAP)LoadImage(
NULL, // 模块实例句柄
s, // 位图路径。注意双斜杠,单斜杠表示转义,此时文件会加载不成功!!!
IMAGE_BITMAP, // 图片类型
width, //指定图片宽
height, //指定图片高
LR_LOADFROMFILE // 从路径处加载图片
);
SendMessage(hwnd,BM_SETIMAGE,(WPARAM)IMAGE_BITMAP,LPARAM(hbmp));
}
void DrawRect(HDC hdc,int left,int top,int right,int bottom)
{
MoveToEx(hdc,left,top, NULL);
LineTo(hdc,right,top);
LineTo(hdc,right,bottom);
LineTo(hdc,left,bottom);
LineTo(hdc,left,top);
}
void LoadCard(Card card[cardnum])
{
card[0].SetCardAttribute(1,0,20,0);//墙
card[1].SetCardAttribute(2,1,1,1);//自爆卡车
card[2].SetCardAttribute(3,8,8,0);//投石器
card[3].SetCardAttribute(4,2,8,0);//能量炮
card[4].SetCardAttribute(5,0,5,0);//扫描仪
card[5].SetCardAttribute(6,5,6,2);//枪
card[6].SetCardAttribute(7,3,10,4);//丘比特
card[7].SetCardAttribute(8,1,6,1);//快刀刺客
card[8].SetCardAttribute(9,5,5,4);//X连弩
card[9].SetCardAttribute(10,5,7,1);//妖刀姬
card[10].SetCardAttribute(11,0,10,0);//屏蔽仪
card[11].SetCardAttribute(12,7,7,1);//豌豆炮
card[12].SetCardAttribute(13,3,7,4);//火炮塔
card[13].SetCardAttribute(14,13,13,1);//巨石
}
卡牌类头文件card.h
#ifndef CARD_H
#define CARD_H
#include <iostream>
class Card
{
public:
Card(){}
char GetCardName();
int GetCardAttack();
int GetCardLife();
int GetCardAttackDistance();
int GetCardShield();
int GetCardId();
int GetCardPosition();
int GetCardCouple();
int GetCardReverseRoundNum();
bool GetIsBack();
bool GetIsAlreadyAttack();
bool GetIsDie();
bool GetIsFirstReverse();
bool GetIsAlreadyChangePosition();
bool GetIsBeAttacked();
void SetCardAttribute(int cardid,int attack,int life,int attackdistance);
void SetCardPosition(int positon);
void SetCardId(int cardid);
void SetCardLife(int life);
void SetCardShield(int shield);
void SetCardAttack(int attack);
void SetCardCouple(int couple);
void SetCardReverseRoundNum(int reverseroundnum);
void ChangeIsBack();
void ChangeIsDie();
void ChangeIsAlreadyAttack();
void ChangeIsFirstReverse();
void ChangeIsAlreadyChangePosition();
void ChangeIsBeAttacked();
private:
char *cardname;
int cardid;
int attack;
int life;
int shield=2;
int attackdistance;
int position;
int couple=0;
int reverseroundnum=0;
bool isalreadyattack=false;
bool isback=true;
bool isdie=false;
bool isfirstreverse=false;
bool isalreadychangeposition=false;
bool isbeattacked=false;
};
#endif//CARD_H
卡牌类函数card.cpp
#include "card.h"
int Card::GetCardAttack()
{
return this->attack;
}
int Card::GetCardAttackDistance()
{
return this->attackdistance;
}
int Card::GetCardLife()
{
return this->life;
}
char Card::GetCardName()
{
return *cardname;
}
int Card::GetCardId()
{
return this->cardid;
}
int Card::GetCardPosition()
{
return this->position;
}
void Card::SetCardPosition(int position)
{
this->position=position;
}
void Card::SetCardId(int cardid)
{
this->cardid=cardid;
}
void Card::SetCardLife(int life)
{
this->life=life;
}
void Card::SetCardAttack(int attack)
{
this->attack=attack;
}
void Card::SetCardAttribute(int cardid,int attack,int life,int attackdistance)
{
this->cardid=cardid;
this->attack=attack;
this->life=life;
this->attackdistance=attackdistance;
}
bool Card::GetIsBack()
{
return this->isback;
}
bool Card::GetIsAlreadyAttack()
{
return this->isalreadyattack;
}
void Card::ChangeIsBack()
{
if(this->isback)
this->isback=false;
else
this->isback=true;
}
void Card::ChangeIsFirstReverse()
{
if(this->isfirstreverse)
this->isfirstreverse=false;
else
this->isfirstreverse=true;
}
void Card::ChangeIsAlreadyAttack()
{
if(this->isalreadyattack)
this->isalreadyattack=false;
else
this->isalreadyattack=true;
}
void Card::ChangeIsDie()
{
if(this->isdie)
this->isdie=false;
else
this->isdie=true;
}
bool Card::GetIsDie()
{
return this->isdie;
}
bool Card::GetIsFirstReverse()
{
return this->isfirstreverse;
}
int Card::GetCardCouple()
{
return this->couple;
}
void Card::SetCardCouple(int couple)
{
this->couple=couple;
}
void Card::ChangeIsAlreadyChangePosition()
{
if(this->isalreadychangeposition)
this->isalreadychangeposition=false;
else
this->isalreadychangeposition=true;
}
bool Card::GetIsAlreadyChangePosition()
{
return this->isalreadychangeposition;
}
int Card::GetCardReverseRoundNum()
{
return this->reverseroundnum;
}
void Card::SetCardReverseRoundNum(int reverseroundnum)
{
this->reverseroundnum=reverseroundnum;
}
int Card::GetCardShield()
{
return this->shield;
}
void Card::SetCardShield(int shield)
{
this->shield=shield;
}
void Card::ChangeIsBeAttacked()
{
if(this->isbeattacked)
this->isbeattacked=false;
else
this->isbeattacked=true;
}
bool Card::GetIsBeAttacked()
{
return this->isbeattacked;
}
- 因为主函数太过庞大,看到这还对此感兴趣的同学可以私信我留下qq,我会抽空一个个发成品和主函数。
最后,下面是win32消息循环介绍
-
这是消息的主循环体,每次产生消息就会调用一遍,比如点击了按钮、画面刷新等等
-
这里用了switch判断语句,根据不同的Message调用不同的case
下图中的WM_DESTROY主要目的就是摧毁这个进程,简单的理解就是点击了退出按钮后会产生一个WM_DESTROY消息,然后执行以下代码。除此之外 -
WM_PAINT:每次产生消息都会进行判断,主要用于贴图和绘制,相关的代码放在此条件下
-
WM_CREATE:是窗口创建CreateWindow时,系统给消息处理程序发送的消息,每次窗口创建,有且只有1个WM_CREATE消息,所以只运行1次,可以在这里初始化滚动条,获取字体宽度、高度,创建子窗口、控件等等
-
WM_COMMAND:点击菜单, 点击加速键,点击子窗口按钮,点击工具栏按钮。这些时候都有command消息产生。
。。。。。。
LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) {
switch(Message) {
/* Upon destruction, tell the main thread to stop */
case WM_DESTROY: {
PostQuitMessage(0);
break;
}
/* All other messages (a lot of them) are processed using default procedures */
default:
return DefWindowProc(hwnd, Message, wParam, lParam);
}
return 0;
}