一、前言
前面我们学习了SimpleCG的游戏开发框架,从本篇开始,我们用一系列小游戏的开发来加深对框架的了解.我们先以windows的经典游戏--扫雷,作为首个例子。游戏预览如下
二、框架搭建
因为游戏程序的大体框架差不多,所以我们可以搭建一个通用的主程序。如下所示:
// GameMine.cpp : 定义控制台应用程序的入口点。
//
#include "../import/include/SimpleCG.h"
#define C_IMAGE_WIDTH 640
#define C_IMAGE_HEIGHT 480
#ifdef _DEBUG
#pragma comment(lib,"../import/lib/SimpleCG_MDd.lib")
#else
#ifdef _WIN64
#pragma comment(lib,"../import/lib/x64/MTRelease/SimpleCG_MT64.lib")
#else
#pragma comment(lib,"../import/lib/SimpleCG_MT.lib")
#endif
#endif
LRESULT OnKeyDown(HWND hWnd, WPARAM wParam, LPARAM lParam);
//绘制游戏
void RenderGame()
{
}
//更新游戏状态
void UpdateGame( UINT nElapse )
{
static int s_nLastTick = 0;
if( ( nElapse - s_nLastTick )<120 )
return;
s_nLastTick = nElapse;
}
//按键消息响应函数
LRESULT OnKeyDown(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
switch( wParam )
{
case VK_SPACE:
break;
case VK_UP:
break;
case VK_DOWN:
break;
case VK_LEFT:
break;
case VK_RIGHT:
break;
}
return 0;
}
LRESULT OnLButtonDown( HWND hWnd, WPARAM wParam, int nX, int nY )
{
return false;
}
LRESULT OnLButtonUp( HWND hWnd, WPARAM wParam, int nX, int nY )
{
return false;
}
LRESULT OnRButtonDown( HWND hWnd, WPARAM wParam, int nX, int nY )
{
return false;
}
LRESULT OnRButtonUp( HWND hWnd, WPARAM wParam, int nX, int nY )
{
return false;
}
LRESULT OnMouseMove( HWND hWnd, WPARAM wParam, int nX, int nY )
{
return false;
}
//初始化游戏
void InitGame(HWND hWnd)
{
int i=0;
int j=0;
srand(GetTickCount());
GetKeyboardInput()->onKeyDown = OnKeyDown;
GetMouseInput()->onMouseDownLeft = OnLButtonDown;
GetMouseInput()->onMouseDownRight = OnRButtonDown;
GetMouseInput()->onMouseUpLeft = OnLButtonUp;
GetMouseInput()->onMouseUpRight = OnRButtonUp;
GetMouseInput()->onMouseMove = OnMouseMove;
}
int _tmain(int argc, _TCHAR* argv[])
{
SCG_GameLoopInfo info;
info.nFPS = 60;
info.nHeight = C_IMAGE_BLOCK*g_nMapHeight+C_IMAGE_BLOCK;//C_IMAGE_HEIGHT;
info.nWidth = C_IMAGE_BLOCK*g_nMapWidth;//C_IMAGE_WIDTH;
info.pFunDrawProcess = RenderGame;
info.pFunFrameUpdate = UpdateGame;
info.pFunInitGame = InitGame;
info.pFunInput = NULL;
if( !StartGameLoopEx( &info ))
return 1;
return 0;
}
这个框架基本上就是前面文章所介绍的游戏开发框架。然后有了这个主框架,我们根据我们要开发的扫雷游戏,我们分别建立游戏逻辑的实现文件mine.cpp,以及头文件mine.h文件。在这两个文件中我们实现扫雷的相关逻辑,然后再把相关代码接入主程序即可。
三、游戏逻辑实现mine.cpp及mine.h文件
mine.h头文件
#ifndef _MINE_H
#define _MINE_H
#include "../import/include/SimpleCG.h"
#define C_IMAGE_BLOCK 20
enum ENUM_MAPTYPE
{
enumMAPTYPE_BACK
, enumMAPTYPE_OPEN
, enumMAPTYPE_BACKLIGHT
, enumMAPTYPE_BACKDOWN
};
enum ENUM_MAPFLAG
{
enumMAPFLAG_NULL
, enumMAPFLAG_FLAG
, enumMAPFLAG_QUESTION
};
enum ENUM_MAPTEXT
{
enumMAPTEXT_NULL
, enumMAPTEXT_NUMBER1
, enumMAPTEXT_NUMBER2
, enumMAPTEXT_NUMBER3
, enumMAPTEXT_NUMBER4
, enumMAPTEXT_NUMBER5
, enumMAPTEXT_NUMBER6
, enumMAPTEXT_NUMBER7
, enumMAPTEXT_NUMBER8
, enumMAPTEXT_NUMBER9
, enumMAPTEXT_BOMB
};
extern int g_nMapWidth;
extern int g_nMapHeight;
//绘制内容
void Init( int nBomb, int nX, int nY );
//计算炸弹数量
int CalBoombCount( int nX, int nY );
//绘制内容
void DrawMap( );
//绘制界面
void DrawUI( );
//绘制单个方块,绝对坐标
void DrawBlockPos( int nX, int nY, int nType );
//绘制单个方块
void DrawBlock( int nX, int nY, int nType );
//是否在地图上
bool IsOnMap( int nX, int nY );
//坐标转化
int ScreenToMapX( int nX );
int ScreenToMapY( int nY );
//鼠标消息
LRESULT MapOnLButtonDown( WPARAM wParam, int nX, int nY );
LRESULT MapOnLButtonUp( WPARAM wParam, int nX, int nY );
LRESULT MapOnRButtonDown( WPARAM wParam, int nX, int nY );
LRESULT MapOnRButtonUp( WPARAM wParam, int nX, int nY );
LRESULT MapOnMouseMove( WPARAM wParam, int nX, int nY );
//打开操作
void DoOpen( int nX, int nY );
//设置标记
void SetFlag( int nX, int nY, int nFlag );
//设置标记
void AddFlag( int nX, int nY );
#endif
mine.cpp实现文件
/*===========================================================*\
|简介: 扫雷
|功能: 扫雷
|作者: Bill
|主页: http://simplecg.qqpet.com
|博客: https://blog.csdn.net/b2b160
|贴吧: https://tieba.baidu.com/f?kw=simplecg
|日期: 2023-09-15
\*===========================================================*/
#include "Mine.h"
int g_nMapWidth = 9;
int g_nMapHeight = 9;
int g_nMap[ 10 * 10] = {0};
int g_nMapText[ 10 * 10] = {0};
int g_nXMapPos = 0;
int g_nYMapPos = C_IMAGE_BLOCK;
int g_nBomb = 10;
int g_nLeftBomb = g_nBomb;
int g_nCurX = -1;
int g_nCurY = -1;
int g_nCurDownX = -1;
int g_nCurDownY = -1;
int g_nOpended = 0;
UINT g_nStartTime = 0;
UINT g_nGameRunning = 0;
//绘制内容
void Init( int nBomb, int nX, int nY )
{
int i=0;
int j=0;
memset(g_nMap,0, sizeof(g_nMapText));
memset(g_nMapText,0, sizeof(g_nMapText));
int nTotal = nX*nY;
if( nBomb>nTotal )
nBomb = nTotal;
g_nBomb = nBomb;
g_nLeftBomb = g_nBomb;
g_nMapWidth = nX;
g_nMapHeight = nY;
g_nOpended = 0;
for(i=0;i<nBomb;++i)
{
int nBombIndex = rand() % (nX*nY);
if( g_nMapText[nBombIndex]!=enumMAPTEXT_BOMB)
{
g_nMapText[nBombIndex]=enumMAPTEXT_BOMB;
}
else
{
int nStop = nBombIndex++;
while( nBombIndex != nStop )
{
if( g_nMapText[nBombIndex]!=enumMAPTEXT_BOMB )
{
g_nMapText[nBombIndex]=enumMAPTEXT_BOMB;
break;
}
++nBombIndex;
if(nBombIndex>=nTotal )
nBombIndex = 0;
}
}
}
for(j=0;j<g_nMapHeight;++j)
{
for(i=0;i<g_nMapWidth;++i)
{
g_nMap[j*g_nMapWidth+i]=enumMAPTYPE_BACK;
if(g_nMapText[j*g_nMapWidth+i] != enumMAPTEXT_BOMB )
g_nMapText[j*g_nMapWidth+i]=CalBoombCount(i,j);
}
}
g_nStartTime = GetTickCount();
g_nGameRunning = TRUE;
}
//计算炸弹数量
int IsBoomb( int nX, int nY )
{
if( nX<0 || nX>=g_nMapWidth )
return 0;
if( nY<0 || nY>=g_nMapHeight )
return 0;
if( g_nMapText[nY*g_nMapWidth+nX]==enumMAPTEXT_BOMB )
return 1;
return 0;
}
//计算炸弹数量
int CalBoombCount( int nX, int nY )
{
int nCount = 0;
if( IsBoomb(nX-1,nY-1 ) )
++nCount;
if( IsBoomb(nX,nY-1 ) )
++nCount;
if( IsBoomb(nX+1,nY-1 ) )
++nCount;
if( IsBoomb(nX-1,nY ) )
++nCount;
if( IsBoomb(nX+1,nY ) )
++nCount;
if( IsBoomb(nX-1,nY+1 ) )
++nCount;
if( IsBoomb(nX,nY+1 ) )
++nCount;
if( IsBoomb(nX+1,nY+1 ) )
++nCount;
return nCount;
}
//绘制内容
void DrawOpen( int nX, int nY )
{
static COLORREF s_nColor[3]={RGB(200,0x0,0x0),RGB(0x0,0x0,200),RGB(22,122,11)};
int nXPos = g_nXMapPos + nX*C_IMAGE_BLOCK;
int nYPos = g_nYMapPos + nY*C_IMAGE_BLOCK;
settextcolor(s_nColor[g_nMapText[nY*g_nMapWidth+nX]%3]);
if( g_nMapText[nY*g_nMapWidth+nX]==enumMAPTEXT_BOMB)
printfRectEx( nXPos, nYPos, C_IMAGE_BLOCK, C_IMAGE_BLOCK, DT_CENTER, _T("X"));
else if(g_nMapText[nY*g_nMapWidth+nX]>0)
printfRectEx( nXPos, nYPos, C_IMAGE_BLOCK, C_IMAGE_BLOCK, DT_CENTER, _T("%d"), g_nMapText[nY*g_nMapWidth+nX]);
}
//绘制内容
void DrawMap( )
{
int i=0;
int j=0;
for(j=0;j<g_nMapHeight;++j)
{
for(i=0;i<g_nMapWidth;++i)
{
switch( g_nMap[j*g_nMapWidth+i] & 0xFFFF )
{
case enumMAPTYPE_OPEN:
DrawBlock( i, j, g_nMap[j*g_nMapWidth+i] );
DrawOpen( i, j );
break;
case enumMAPTYPE_BACK:
if( g_nCurDownX==i && g_nCurDownY==j )
DrawBlock( i, j, enumMAPTYPE_BACKDOWN);
else if( g_nCurX==i && g_nCurY==j )
DrawBlock( i, j, enumMAPTYPE_BACKLIGHT | (g_nMap[j*g_nMapWidth+i]&0xFFFF0000) );
else
DrawBlock( i, j, g_nMap[j*g_nMapWidth+i] );
break;
}
}
}
}
//绘制界面
void DrawUI( )
{
int nOld;
if( FALSE == g_nGameRunning )
return;
UINT nTime = GetTickCount()-g_nStartTime;
clearrectangle(g_nXMapPos, g_nYMapPos-C_IMAGE_BLOCK, g_nXMapPos + C_IMAGE_BLOCK*g_nMapWidth, g_nYMapPos);
nOld = settextcolor(SCG_RGB(0,0,0xFF));
printfRectEx( g_nXMapPos, g_nYMapPos-C_IMAGE_BLOCK, C_IMAGE_BLOCK*g_nMapWidth, C_IMAGE_BLOCK, DT_VCENTER, _T("时间:%d.%d"), nTime/1000,nTime%1000);
printfRectEx( g_nXMapPos, g_nYMapPos-C_IMAGE_BLOCK, C_IMAGE_BLOCK*g_nMapWidth, C_IMAGE_BLOCK, DT_RIGHT|DT_VCENTER, _T("炸:%d"), g_nLeftBomb);
settextcolor(nOld);
}
//绘制单个方块,绝对坐标
void DrawBlockPos( int nX, int nY, int nType )
{
int nFlag = (nType>>16) & 0xFFFF;
switch( nType & 0xFFFF )
{
case enumMAPTYPE_OPEN:
setlinewidth(1);
setfillcolor(RGB(194,194,194));
setlinecolor(RGB(125,125,125));
fillrectangle( nX, nY, nX + C_IMAGE_BLOCK, nY + C_IMAGE_BLOCK);
break;
case enumMAPTYPE_BACK:
setlinewidth(3);
setfillcolor(RGB(194,194,194));
setlinecolor(RGB(125,125,125));
fillrectangle( nX+1, nY+1, nX + C_IMAGE_BLOCK-1, nY + C_IMAGE_BLOCK-1);
setlinecolor(RGB(231,231,231));
line(nX+2, nY+1, nX-3 + C_IMAGE_BLOCK,nY+1);
line(nX+1, nY+2, nX+1,nY-3 + C_IMAGE_BLOCK);
break;
case enumMAPTYPE_BACKDOWN:
setlinewidth(1);
setfillcolor(RGB(194,194,194));
setlinecolor(RGB(125,125,125));
fillrectangle( nX, nY, nX + C_IMAGE_BLOCK, nY + C_IMAGE_BLOCK);
break;
case enumMAPTYPE_BACKLIGHT:
setlinewidth(3);
setfillcolor(RGB(221,221,221));
setlinecolor(RGB(125,125,125));
fillrectangle( nX+1, nY+1, nX + C_IMAGE_BLOCK-1, nY + C_IMAGE_BLOCK-1);
setlinecolor(RGB(231,231,231));
line(nX+2, nY+1, nX-3 + C_IMAGE_BLOCK,nY+1);
line(nX+1, nY+2, nX+1,nY-3 + C_IMAGE_BLOCK);
break;
}
if( nFlag )
{
switch( nFlag )
{
case enumMAPFLAG_FLAG:
{
POINT pt[]={{nX+14,nY+2},{nX+6,nY+12},{nX+14,nY+12}};
setlinewidth(1);
setfillcolor(RGB(255,0,0));
solidpolygon( pt, sizeof(pt)/sizeof(pt[0]) );
setfillcolor(0);
solidrectangle( nX+13, nY+4, nX + 16, nY + C_IMAGE_BLOCK-1);
}
break;
case enumMAPFLAG_QUESTION:
settextcolor(0);
printfRectEx( nX, nY, C_IMAGE_BLOCK, C_IMAGE_BLOCK, DT_CENTER, _T("?"));
break;
}
}
}
//绘制单个方块,地图坐标
void DrawBlock( int nX, int nY, int nType )
{
int nXPos = g_nXMapPos + nX*C_IMAGE_BLOCK;
int nYPos = g_nYMapPos + nY*C_IMAGE_BLOCK;
DrawBlockPos(nXPos, nYPos, nType);
}
//是否在地图上
bool IsOnMap( int nX, int nY )
{
if(nX>=g_nXMapPos && nX<g_nXMapPos + g_nMapWidth*C_IMAGE_BLOCK && nY>=g_nYMapPos && nY<g_nYMapPos + g_nMapHeight*C_IMAGE_BLOCK )
return true;
return false;
}
//坐标转化
int ScreenToMapX( int nX )
{
int nRet = -1;
if(nX>=g_nXMapPos && nX<g_nXMapPos + g_nMapWidth*C_IMAGE_BLOCK )
{
nRet =( nX-g_nXMapPos)/C_IMAGE_BLOCK;
}
return nRet;
}
int ScreenToMapY( int nY )
{
int nRet = -1;
if(nY>=g_nYMapPos && nY<g_nYMapPos + g_nMapHeight*C_IMAGE_BLOCK )
{
nRet =( nY-g_nYMapPos)/C_IMAGE_BLOCK;
}
return nRet;
}
//鼠标消息
LRESULT MapOnLButtonDown( WPARAM wParam, int nX, int nY )
{
int nMapX = ScreenToMapX(nX);
int nMapY = ScreenToMapY(nY);
if( g_nCurDownX != nMapX || g_nCurDownY != nMapY )
{
g_nCurDownX = nMapX;
g_nCurDownY = nMapY;
return true;
}
return false;
}
LRESULT MapOnLButtonUp( WPARAM wParam, int nX, int nY )
{
if( g_nCurDownX >=0 && g_nCurDownX<g_nMapWidth && g_nCurDownY >=0 && g_nCurDownY<g_nMapHeight)
{
DoOpen( g_nCurDownX, g_nCurDownY );
g_nCurDownX = -1;
g_nCurDownY = -1;
return true;
}
return false;
}
LRESULT MapOnRButtonDown( WPARAM wParam, int nX, int nY )
{
return false;
}
LRESULT MapOnRButtonUp( WPARAM wParam, int nX, int nY )
{
int nMapX = ScreenToMapX(nX);
int nMapY = ScreenToMapY(nY);
AddFlag( nMapX, nMapY );
return false;
}
LRESULT MapOnMouseMove( WPARAM wParam, int nX, int nY )
{
int nMapX = ScreenToMapX(nX);
int nMapY = ScreenToMapY(nY);
if( g_nCurX != nMapX || g_nCurY != nMapY )
{
g_nCurX = nMapX;
g_nCurY = nMapY;
return true;
}
return false;
}
void MarkOpen(int nX, int nY)
{
g_nMap[nY*g_nMapWidth+nX] = enumMAPTYPE_OPEN;
++g_nOpended;
if(g_nOpended>=g_nMapWidth*g_nMapHeight-g_nBomb)
{
TCHAR pText[256];
UINT nTime = GetTickCount()-g_nStartTime;
wsprintf(pText, _T("You Win!用时%d.%d秒!是否重新开始?"), nTime/1000,nTime%1000);
if( MessageBox(NULL,pText,_T(""),MB_YESNO) == IDYES )
Init(g_nBomb, g_nMapWidth, g_nMapHeight);
else
g_nGameRunning = FALSE;
}
}
//打开操作
void DoOpenLoop( int nX, int nY )
{
if(g_nMap[nY*g_nMapWidth+nX] == enumMAPTYPE_OPEN)
return;
MarkOpen(nX, nY);
int i=0;
int j=0;
for(j=-1;j<=1;++j)
{
for(i=-1;i<=1;++i)
{
if((i+nX)<0||(i+nX)>=g_nMapWidth)
continue;
if((j+nY)<0||(j+nY)>=g_nMapHeight)
continue;
if(g_nMap[(j+nY)*g_nMapWidth+(i+nX)] == enumMAPTYPE_BACK)
{
if(g_nMapText[(j+nY)*g_nMapWidth+(i+nX)] == enumMAPTEXT_NULL)
{
DoOpenLoop( i+nX, j+nY );
}
else if(g_nMapText[(j+nY)*g_nMapWidth+(i+nX)] != enumMAPTEXT_BOMB)
{
MarkOpen(i+nX, (j+nY));
}
}
}
}
}
void DoOpen( int nX, int nY )
{
if(g_nMapText[nY*g_nMapWidth+nX] == enumMAPTEXT_BOMB )
{
g_nMap[nY*g_nMapWidth+nX] = enumMAPTYPE_OPEN;
if( MessageBox(NULL,_T("游戏结束,是否重新开始?"),_T(""),MB_YESNO) == IDYES )
{
Init(g_nBomb, g_nMapWidth, g_nMapHeight);
return;
}
else
{
g_nGameRunning = FALSE;
return;
}
}
else if(g_nMapText[nY*g_nMapWidth+nX] == enumMAPTEXT_NULL)
{
DoOpenLoop( nX, nY );
}
if(g_nMap[nY*g_nMapWidth+nX] != enumMAPTYPE_OPEN )
{
MarkOpen(nX, nY);
}
}
//设置标记
void SetFlag( int nX, int nY, int nFlag )
{
int nOld = g_nMap[nY*g_nMapWidth+nX];
g_nMap[nY*g_nMapWidth+nX] = (nOld & 0xFFFF) | (nFlag<<16);
}
//设置标记
void AddFlag( int nX, int nY )
{
if((g_nMap[nY*g_nMapWidth+nX] &0xFFFF)==enumMAPTYPE_OPEN)
return;
int nOld = g_nMap[nY*g_nMapWidth+nX];
int nOldFlag = (nOld>>16)&0xFFFF;
++nOldFlag;
if(nOldFlag == enumMAPFLAG_FLAG )
--g_nLeftBomb;
else if(nOldFlag == enumMAPFLAG_QUESTION )
++g_nLeftBomb;
if(nOldFlag>enumMAPFLAG_QUESTION)
{
nOldFlag = 0;
}
g_nMap[nY*g_nMapWidth+nX] = (nOld & 0xFFFF) | (nOldFlag<<16);
}
四、逻辑接入主程序
在主程序中做以下几个修改,就将扫雷的逻辑接入主程序并将画面展现出来,同时接收用户的输入操作并反应。首先当然是包含逻辑头文件Mine.h
#include "Mine.h"
然后绘制画面
//绘制游戏
void RenderGame()
{
setbackmode(enumBKM_TRANSPARENT);
DrawMap();
setbackmode(enumBKM_OPAQUE);
//绘制界面
DrawUI( );
}
我们没有在更新函数中做任何更新,所以不需要改变UpdateGame
然后在鼠标输入操作中,我们需要判断是否在地图上并根据输入进行状态改变
LRESULT OnLButtonDown( HWND hWnd, WPARAM wParam, int nX, int nY )
{
if( IsOnMap(nX,nY))
return MapOnLButtonDown( wParam, nX, nY );
return false;
}
LRESULT OnLButtonUp( HWND hWnd, WPARAM wParam, int nX, int nY )
{
if( IsOnMap(nX,nY))
return MapOnLButtonUp( wParam, nX, nY );
return false;
}
LRESULT OnRButtonDown( HWND hWnd, WPARAM wParam, int nX, int nY )
{
if( IsOnMap(nX,nY))
return MapOnRButtonDown( wParam, nX, nY );
return false;
}
LRESULT OnRButtonUp( HWND hWnd, WPARAM wParam, int nX, int nY )
{
if( IsOnMap(nX,nY))
return MapOnRButtonUp( wParam, nX, nY );
return false;
}
LRESULT OnMouseMove( HWND hWnd, WPARAM wParam, int nX, int nY )
{
if( IsOnMap(nX,nY))
return MapOnMouseMove( wParam, nX, nY );
return false;
}
最后在初始化阶段进行游戏初始化即可,此处初始化10个炸弹,9x9地图
Init( 10, 9, 9 );
四、代码下载
所有代码可在以下地址察看或下载,
gamemine · master · b2b160 / SimpleCG_Demo · GitCode
编译此程序需安装SimpleCG库,安装方法如下:
SimpleCG库安装方法
如果只想执行程序可在如下地址下载
扫雷exe压缩文件