一、前言
在之前的C语言小游戏开发系列我们已经介绍了扫雷游戏的开发,本篇我们继续此系列第二篇,同样是比较简单但好玩的一个游戏--贪吃蛇。因为有了之前的游戏框架,我们只需要直接搬来原来的框架即可,可以省去不少活。
先看看游戏效果预览
下面进行详细的逻辑介绍。
二、玩法介绍
贪吃蛇是一款经典的游戏,玩家需要控制一条蛇在屏幕上移动,不断吃掉食物并成长,同时要避免碰到墙壁或自己的身体。蛇在移动过程中会遇到食物,玩家需要让蛇的头部碰到食物,蛇就会自动将食物吞下,并且蛇的长度会相应增加。碰到墙壁或自己的身体就游戏结束。
三、主要逻辑及难点说明
贪吃蛇游戏的核心逻辑在于蛇的移动和食物的生成。蛇的移动需要精确控制,避免蛇的头部碰到自己的身体或者墙壁,食物的生成需要有一定的随机性,使得游戏有一定的变化性。
我们首先将游戏分三个场景进行绘制,分别是开始前的菜单显示、游戏进行中的场景显示、以及游戏结束界面的显示。我们可以直接用一个全局整型变量g_nGameState来区分场景。在游戏进行时我们主要绘制蛇行走的地图,这个在前面扫雷游戏中有所介绍,大同小异,主要就是一个一维数组实现的二维平面。
int g_nMap[ C_MAP_WIDTH * C_MAP_HEIGHT] = {0};
然后是蛇的表示,我们用一个坐标数组来表示蛇身体所处地图位置。
POINT g_ptSnake[C_SNAKE_MAXLEN];
地图绘制就是根据地图表示的地形绘制出相应内容
for(j=0;j<C_MAP_HEIGHT;++j)
{
for(i=0;i<C_MAP_WIDTH;++i)
{
switch( g_nMap[j*C_MAP_WIDTH+i] )
{
case enumMAPTYPE_STONE:
solidrectangle( i*C_IMAGE_BLOCK, j*C_IMAGE_BLOCK, i*C_IMAGE_BLOCK+C_IMAGE_BLOCK, j*C_IMAGE_BLOCK+C_IMAGE_BLOCK);
break;
case enumMAPTYPE_FOOD:
setfillcolor(RGB(0xAA,0x0,0x0));
solidcircle( i*C_IMAGE_BLOCK+10, j*C_IMAGE_BLOCK+10, 5);
setfillcolor(RGB(0xAA,0xAA,0xAA));
break;
}
}
}
蛇的行走就只需要根据当前方向进行坐标移动就可以了
switch( g_nSnakDir )
{
case enumDIR_LEFT:
SnakeMoveTo(g_ptSnake[0].x-1,g_ptSnake[0].y);
break;
case enumDIR_RIGHT:
SnakeMoveTo(g_ptSnake[0].x+1,g_ptSnake[0].y);
break;
case enumDIR_UP:
SnakeMoveTo(g_ptSnake[0].x,g_ptSnake[0].y-1);
break;
case enumDIR_DOWN:
SnakeMoveTo(g_ptSnake[0].x,g_ptSnake[0].y+1);
break;
}
以上就是贪吃蛇涉及的主要设计难点,都比较容易理解,就不展开讨论了。
四、主体逻辑的所有源代码
以下就是贪吃蛇的全部主要代码,只是示例程序,所以只设置了一关、有兴趣的同学可以自行扩展代码。
// GameSnake.cpp : 定义控制台应用程序的入口点。
//
#include "../import/include/SimpleCG.h"
#include "GameUI.h"
#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
#define C_SNAK_VERSION 1001
#define C_IMAGE_WIDTH 640
#define C_IMAGE_HEIGHT 480
#define C_IMAGE_BLOCK 20
#define C_MAP_WIDTH C_IMAGE_WIDTH/C_IMAGE_BLOCK
#define C_MAP_HEIGHT C_IMAGE_HEIGHT/C_IMAGE_BLOCK
#define C_SNAKE_MAXLEN 50
enum ENUM_GAMESTATE
{
enumGS_MENU
, enumGS_RUNNING
, enumGS_GAMEOVER
};
enum ENUM_MAPTYPE
{
enumMAPTYPE_NULL
, enumMAPTYPE_STONE
, enumMAPTYPE_FOOD
};
enum ENUM_DIRECTION
{
enumDIR_LEFT
, enumDIR_RIGHT
, enumDIR_UP
, enumDIR_DOWN
};
int g_nMap[ C_MAP_WIDTH * C_MAP_HEIGHT] = {0};
int g_nSnakDir = enumDIR_LEFT;
POINT g_ptSnake[C_SNAKE_MAXLEN];
int g_nSnakLen = 2;
int g_nGameState = enumGS_MENU;
char g_nTitle[] = { 0x4f,0x49,0x49,0x79,0x0,0x7f,0xe,0x30,0x7f,0x0,0x7e,0x9,0x9,0x7e,0x0,0x7f,0x8,0x14,0x63,0x0,0x7f,0x49,0x49,0x49 };
char g_pGame[] = { 0x7f,0x41,0x5d,0x49,0x7b,0x0,0x7c,0x12,0x11,0x12,0x7c,0x0,0x7f,0x2,0x4,0x2,0x7f,0x0,0x7f,0x49,0x49,0x49,0x49 };
char g_pOver[] = { 0x7f,0x41,0x41,0x41,0x7f,0x0,0x1f,0x20,0x40,0x20,0x1f,0x0,0x7f,0x49,0x49,0x49,0x49,0x0,0x7f,0x9,0x19,0x29,0x46 };
int g_nMenu = 0;
int g_nScore = 0;
int g_nSpeed = 1;
LRESULT OnKeyDown(HWND hWnd, WPARAM wParam, LPARAM lParam);
LRESULT OnLButtonDown( HWND hWnd, WPARAM wParam, int nX, int nY );
LRESULT OnRButtonDown( HWND hWnd, WPARAM wParam, int nX, int nY );
LRESULT OnMouseMove( HWND hWnd, WPARAM wParam, int nX, int nY );
//绘制标题
void DrawTitle( int nX, int nY, char *pTitle, int nSize);
void MakeFood()
{
bool bFoodSet = false;
int nRand = 0;
while(!bFoodSet)
{
nRand=rand()%(C_MAP_WIDTH*C_MAP_HEIGHT);
if(g_nMap[nRand] == enumMAPTYPE_NULL )
{
g_nMap[nRand] = enumMAPTYPE_FOOD;
bFoodSet = true;
}
}
}
//初始化游戏
void InitGame(HWND hWnd)
{
int i=0;
int j=0;
for(j=0;j<C_MAP_HEIGHT;++j)
{
for(i=0;i<C_MAP_WIDTH;++i)
{
if(i==0||i==C_MAP_WIDTH-1||j==0||j==C_MAP_HEIGHT-1)
{
g_nMap[j*C_MAP_WIDTH+i] = enumMAPTYPE_STONE;
}
else
{
g_nMap[j*C_MAP_WIDTH+i] = enumMAPTYPE_NULL;
}
}
}
g_nSpeed = 1;
g_nSnakLen = 2;
g_ptSnake[0].x = 10;
g_ptSnake[0].y = 10;
g_ptSnake[1].x = 11;
g_ptSnake[1].y = 10;
g_nSnakDir = enumDIR_LEFT;
srand(GetTickCount());
MakeFood();
//按键处理初始化
//SetKeyboardProcess( enumINMSG_KEYDOWN, OnKeyDown );
GetKeyboardInput()->onKeyDown = OnKeyDown;
GetMouseInput()->onMouseDownLeft = OnLButtonDown;
GetMouseInput()->onMouseDownRight = OnRButtonDown;
GetMouseInput()->onMouseMove = OnMouseMove;
g_nGameState = enumGS_MENU;
}
//移动蛇
void SnakeMoveTo(int x, int y)
{
int i=0;
if(x<0 || y<0 || x>=C_MAP_WIDTH || y>=C_MAP_HEIGHT )
return;
//GetFood
if(g_nMap[y*C_MAP_WIDTH+x]==enumMAPTYPE_FOOD)
{
g_nMap[y*C_MAP_WIDTH+x]=enumMAPTYPE_NULL;
g_ptSnake[g_nSnakLen].x = g_ptSnake[g_nSnakLen-1].x;
g_ptSnake[g_nSnakLen].y = g_ptSnake[g_nSnakLen-1].y;
if( g_nSnakLen<C_SNAKE_MAXLEN-1)
g_nSnakLen++;
g_nScore += 10 * g_nSpeed;
g_nSpeed = g_nSnakLen / 5 + 1;
MakeFood();
}
else if(g_nMap[y*C_MAP_WIDTH+x]==enumMAPTYPE_STONE )//dead
{
g_nGameState = enumGS_GAMEOVER;
return;
}
for(i=g_nSnakLen-1;i>0;--i)
{
if( g_ptSnake[i].x==x && g_ptSnake[i].y==y )
{
g_nGameState = enumGS_GAMEOVER;
return;
}
g_ptSnake[i].x = g_ptSnake[i-1].x;
g_ptSnake[i].y = g_ptSnake[i-1].y;
}
g_ptSnake[0].x = x;
g_ptSnake[0].y = y;
}
//按方向移动
void MoveFoward()
{
switch( g_nSnakDir )
{
case enumDIR_LEFT:
SnakeMoveTo(g_ptSnake[0].x-1,g_ptSnake[0].y);
break;
case enumDIR_RIGHT:
SnakeMoveTo(g_ptSnake[0].x+1,g_ptSnake[0].y);
break;
case enumDIR_UP:
SnakeMoveTo(g_ptSnake[0].x,g_ptSnake[0].y-1);
break;
case enumDIR_DOWN:
SnakeMoveTo(g_ptSnake[0].x,g_ptSnake[0].y+1);
break;
}
}
//更新游戏状态
void UpdateGame( UINT nElapse )
{
static int s_nLastTick = 0;
int nWait = 9-g_nSpeed;
if(nWait<0)
nWait = 1;
if( ( nElapse - s_nLastTick )<nWait*50 )
return;
switch(g_nGameState)
{
case enumGS_MENU:
break;
case enumGS_RUNNING:
s_nLastTick = nElapse;
MoveFoward();
break;
case enumGS_GAMEOVER:
break;
}
}
//绘制地图
void DrawMap()
{
int i=0;
int j=0;
setfillcolor(RGB(0xAA,0xAA,0xAA));
for(j=0;j<C_MAP_HEIGHT;++j)
{
for(i=0;i<C_MAP_WIDTH;++i)
{
switch( g_nMap[j*C_MAP_WIDTH+i] )
{
case enumMAPTYPE_STONE:
solidrectangle( i*C_IMAGE_BLOCK, j*C_IMAGE_BLOCK, i*C_IMAGE_BLOCK+C_IMAGE_BLOCK, j*C_IMAGE_BLOCK+C_IMAGE_BLOCK);
break;
case enumMAPTYPE_FOOD:
setfillcolor(RGB(0xAA,0x0,0x0));
solidcircle( i*C_IMAGE_BLOCK+10, j*C_IMAGE_BLOCK+10, 5);
setfillcolor(RGB(0xAA,0xAA,0xAA));
break;
}
}
}
}
//绘制标题
void DrawTitle( int nX, int nY, char *pTitle, int nSize)
{
int i=0;
int j=0;
for(i=0;i<nSize;++i)
{
int nNum = pTitle[i];
for(j=0;j<8;++j)
{
if(nNum&0x1)
{
solidrectangle( nX+i*C_IMAGE_BLOCK, nY+j*C_IMAGE_BLOCK, nX+i*C_IMAGE_BLOCK+C_IMAGE_BLOCK, nY+j*C_IMAGE_BLOCK+C_IMAGE_BLOCK);
}
nNum>>=1;
}
}
}
//绘制蛇
void DrawSnake()
{
int i=0;
setfillcolor(RGB(0x0,0xAA,0x0));
for(i=0; i<g_nSnakLen; ++i)
{
solidrectangle( g_ptSnake[i].x*C_IMAGE_BLOCK, g_ptSnake[i].y*C_IMAGE_BLOCK, g_ptSnake[i].x*C_IMAGE_BLOCK+C_IMAGE_BLOCK, g_ptSnake[i].y*C_IMAGE_BLOCK+C_IMAGE_BLOCK);
}
}
void DrawMenu()
{
setfillcolor(RGB(0x0,0xAA,0x0));
DrawTitle( 60, 60, g_nTitle, sizeof(g_nTitle)/sizeof(g_nTitle[0]));
DrawButton( 200, 260, 120, 30, RGB(0x0,0xDD,0x0), 0, _T("开始游戏"));
DrawButton( 200, 300, 120, 30, RGB(0x0,0xDD,0x0), 0, _T("退出游戏"));
setlinecolor(0);
fillcircle( 180, g_nMenu * 40 + 260 + 15, 10 );
}
//绘制游戏
void RenderGame()
{
clearrectangle(0,0,C_IMAGE_WIDTH, C_IMAGE_HEIGHT);
switch(g_nGameState)
{
case enumGS_MENU:
DrawMenu();
break;
case enumGS_RUNNING:
DrawMap();
DrawSnake();
RenderUI();
break;
case enumGS_GAMEOVER:
DrawTitle( 100, 60, g_pGame, sizeof(g_pGame)/sizeof(g_pGame[0]));
DrawTitle( 100, 220, g_pOver, sizeof(g_pOver)/sizeof(g_pOver[0]));
settextcolor(0);
printfRectEx( 0, 380, C_IMAGE_WIDTH, 20, SCG_TEXT_MIDDLE, _T("分数: %d"), g_nScore );
//outtextRectEx( 0, 400, C_IMAGE_WIDTH, 100, _T("Press any key to continue..."), SCG_TEXT_MIDDLE);
outtextRectEx( 0, 400, C_IMAGE_WIDTH, 100, _T("按任意键继续..."), SCG_TEXT_MIDDLE);
break;
}
}
//按键消息响应函数
LRESULT OnKeyDown(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
switch( g_nGameState )
{
case enumGS_MENU:
switch( wParam )
{
case VK_DOWN:
if( g_nMenu<1 )
++g_nMenu;
break;
case VK_UP:
if( g_nMenu> 0 )
--g_nMenu;
break;
case VK_SPACE:
case VK_RETURN:
if( g_nMenu == 1)
PostQuitMessage(0);
else
g_nGameState = enumGS_RUNNING;
break;
}
break;
case enumGS_RUNNING:
switch( wParam )
{
case VK_DOWN:
if(g_nSnakDir == enumDIR_LEFT || g_nSnakDir == enumDIR_RIGHT)
{
g_nSnakDir = enumDIR_DOWN;
MoveFoward();
}
return 1;
break;
case VK_UP:
if(g_nSnakDir == enumDIR_LEFT || g_nSnakDir == enumDIR_RIGHT)
{
g_nSnakDir = enumDIR_UP;
MoveFoward();
}
return 1;
break;
case VK_LEFT:
if(g_nSnakDir == enumDIR_UP || g_nSnakDir == enumDIR_DOWN)
{
g_nSnakDir = enumDIR_LEFT;
MoveFoward();
}
return 1;
break;
case VK_RIGHT:
if(g_nSnakDir == enumDIR_UP || g_nSnakDir == enumDIR_DOWN)
{
g_nSnakDir = enumDIR_RIGHT;
MoveFoward();
}
return 1;
break;
}
break;
case enumGS_GAMEOVER:
InitGame(NULL);
break;
}
return 0;
}
LRESULT OnLButtonDown( HWND hWnd, WPARAM wParam, int nX, int nY )
{
switch( g_nGameState )
{
case enumGS_MENU:
if( g_nMenu == 1)
PostQuitMessage(0);
else
g_nGameState = enumGS_RUNNING;
break;
case enumGS_RUNNING:
switch(g_nSnakDir )
{
case enumDIR_LEFT:
g_nSnakDir = enumDIR_DOWN;
break;
case enumDIR_RIGHT:
g_nSnakDir = enumDIR_UP;
break;
case enumDIR_UP:
g_nSnakDir = enumDIR_LEFT;
break;
case enumDIR_DOWN:
g_nSnakDir = enumDIR_RIGHT;
break;
}
break;
case enumGS_GAMEOVER:
InitGame(NULL);
break;
}
return false;
}
LRESULT OnRButtonDown( HWND hWnd, WPARAM wParam, int nX, int nY )
{
if( g_nGameState == enumGS_RUNNING)
{
switch(g_nSnakDir )
{
case enumDIR_LEFT:
g_nSnakDir = enumDIR_UP;
break;
case enumDIR_RIGHT:
g_nSnakDir = enumDIR_DOWN;
break;
case enumDIR_UP:
g_nSnakDir = enumDIR_RIGHT;
break;
case enumDIR_DOWN:
g_nSnakDir = enumDIR_LEFT;
break;
}
}
return false;
}
LRESULT OnMouseMove( HWND hWnd, WPARAM wParam, int nX, int nY )
{
if( g_nGameState == enumGS_MENU)
{
if( nY>=300)
g_nMenu = 1;
else
g_nMenu = 0;
}
return false;
}
int _tmain(int argc, _TCHAR* argv[])
{
SCG_GameLoopInfo info;
info.nFPS = 60;
info.nHeight = C_IMAGE_HEIGHT;
info.nWidth = C_IMAGE_WIDTH;
info.pFunDrawProcess = RenderGame;
info.pFunFrameUpdate = UpdateGame;
info.pFunInitGame = InitGame;
info.pFunInput = NULL;
if( !StartGameLoopEx( &info ))
return 1;
return 0;
}
五、代码及相关图形库下载
贪吃蛇完整VS2010工程可在以下地址下载。
gamesnake · master · b2b160 / SimpleCG_Demo · GitCode
编译此程序需安装SimpleCG库,安装方法如下:
SimpleCG库安装方法
如果只想执行程序可在如下地址下载
贪吃蛇执行程序