重构
在继续向下之前,我们决定对现阶段代码进行重构,这里主要有两个目标:
- 模块不暴露内部实现,所有功能由接口实现。
- 仅在两个模块间需要交互的类型,由模块的头文件定义。
我们遵循.h提供接口,.c负责实现细节的策略,并从模块的角度对文件进行物理划分,其中:
main.c: 负责构建程序主框架,与其它模块通过接口交互Game.h: 提供整个游戏控制接口Game.c: 提供整个游戏实现
Game.h
#pragma once
#define MAP_WIDTH 16 * 35 // 地图区域宽度
#define MAP_HEIGHT 16 * 35 // 地图区域高度
// 移动方向
typedef enum tagMOVEDIR
{
MOVE_LEFT,
MOVE_RIGHT,
MOVE_UP,
MOVE_DOWN
} MOVEDIR;
// 游戏初始化
bool Game_Init();
// 加载指定关卡的地图
bool Game_Load_Level(int level);
// 渲染游戏场景
void Game_Render();
// 玩家移动控制
void Game_Player_Move(MOVEDIR dir);
// 获取当前关卡
int Game_Get_Current_Level();
这里MAP_WIDTH, MAP_HEIGHT宏的定义可能并不直观,另一种方案是提供相应的接口函数,返回地图的宽度信息,如:
void Game_Get_MapSize(int *w, int *h);
由于MOVEDIR需要在Main.c中与键盘交互使用,因此这个枚举属于接口定义的一部分。
剩余接口函数显而易见,完全可以满足现有的设计。
Game.c
整体游戏逻辑的实现,现在由Game.c实现,这里主要有两个变化:
Game_Init(): 实现了资源初始化与关卡加载- 全局变量增加了
static修饰。
#include "Easy2D.h"
#include "Game.h"
#define MAP_ROWS 16 // 地图行数
#define MAP_COLUMNS 16 // 地图列数
#define TILE_WIDTH 35 // 地面贴片元素宽度
#define TILE_HEIGHT 35 // 地面贴片元素宽度
// 以行列表示的位置信息
typedef struct tagPOS
{
char r;
char c;
} POS;
// 纹理信息
typedef struct tagTEXINFO
{
int tex;
int dx;
int dy;
} TEXINFO;
// 场景资源
static TEXINFO g_textures[TEX_COUNT];
// 当前关卡
static int g_cur_level = 1;
// 场景地图
static char g_map[MAP_ROWS][MAP_COLUMNS];
// 玩家贴图
static int g_player_tex = E2D_INVALID_RESID;
// 玩家位置
static POS g_player_pos;
bool Game_Init()
{
if (!Load_Resource())
return false;
if (!Game_Load_Level(1))
return false;
return true;
}
Main.c
现在,主框架程序仅是通过接口将模块驱动起来。
#include <stdio.h>
#include <stdbool.h>
#include "Easy2D.h"
#include "Game.h"
//=============================================================================
// 绘制场景
//=============================================================================
void Render()
{
Game_Render();
}
//=============================================================================
// 键盘事件处理
//=============================================================================
void Keyboard(E2DKeyboardEvent kbe, E2DScancode sc)
{
if (kbe == E2D_KEYDOWN)
{
switch (sc)
{
case E2D_SCANCODE_LEFT:
Game_Player_Move(MOVE_LEFT);
break;
case E2D_SCANCODE_RIGHT:
Game_Player_Move(MOVE_RIGHT);
break;
case E2D_SCANCODE_UP:
Game_Player_Move(MOVE_UP);
break;
case E2D_SCANCODE_DOWN:
Game_Player_Move(MOVE_DOWN);
break;
}
}
}
//=============================================================================
// 鼠标事件处理
//=============================================================================
void Mouse(E2DMouseEvent me, E2DMouseButton button, int x, int y, int lbs, int mbs, int rbs)
{
}
//=============================================================================
// 程序入口点
//=============================================================================
int main()
{
if (!E2D_Init("PushBox", MAP_WIDTH, MAP_HEIGHT))
{
E2D_MessageBox("PushBox", "Easy2D Initialize Failed!", E2D_STOP);
return -1;
}
if (!Game_Init())
{
E2D_MessageBox("PushBox", "Game Initialize Failed!", E2D_STOP);
return -2;
}
E2D_RenderFunc(Render);
E2D_KeyboardFunc(Keyboard);
E2D_MouseFunc(Mouse);
E2D_Run();
E2D_Release();
return 0;
}
关闭控制台
现在我们已经掌握了事件处理,可以关闭控制台了,这是通过配置VS来实现的,有两个配置点:
- 将【链接器】【系统】下的【子系统】选择为【窗口/SUBSYSTEM:WINDOWS】
- 将【链接吕】【高级】下的【入口点】输入为【mainCRTStartup】

