【2D割草类游戏 - 03】游戏架构
由于只是一个轻量的练手小游戏,所以没有进行头文件分离,自然也就没有过于追求扩展性
游戏架构也比较简单
类定义
总共定义了以下类:
- 图集类
用于实现单例模式 - 动画类
用于渲染动画 - 玩家类
- 子弹类
- 敌人类
- 按钮类
- 开始游戏按钮类
- 退出游戏按钮类
数据结构
将敌人存储在vector中为敌人指针列表
将子弹存储在vector中为子弹列表
主循环
主循环内的逻辑为:输入处理->数据处理->渲染画面
输入处理
在整个游戏中,需要接收数据输入的只有玩家角色和按钮
输入处理并没有将信息过滤后直接传递至数据处理部分,而是分别在对应的需要接收数据的类中定义了处理方法,并直接将原始数据交给对应的对象进行输入
主循环中:
// 输入处理
while (peekmessage(&msg))
{
if(is_game_started)
player.ProcessEvent(msg);
else
{
btn_start_game.ProcessEvent(msg);
btn_quit_game.ProcessEvent(msg);
}
}
按钮类中的数据输入:
// 事件响应
void ProcessEvent(const ExMessage& msg)
{
switch (msg.message)
{
case WM_MOUSEMOVE:
if (status == Status::Idle && CheckCursorHit(msg.x, msg.y))
status = Status::Hovered;
else if (status == Status::Hovered && CheckCursorHit(msg.x, msg.y))
status = Status::Idle;
break;
case WM_LBUTTONDOWN:
if (CheckCursorHit(msg.x, msg.y))
status = Status::Pushed;
break;
case WM_LBUTTONUP:
if (status == Status::Pushed)
OnClick();
break;
default:
break;
}
}
玩家类中的数据输入:
// 处理玩家操作消息
void ProcessEvent(const ExMessage& msg)
{
switch (msg.message)
{
case WM_KEYDOWN:
switch (msg.vkcode)
{
case VK_UP:
is_move_up = true;
break;
case VK_DOWN:
is_move_down = true;
break;
case VK_LEFT:
is_move_left = true;
break;
case VK_RIGHT:
is_move_right = true;
break;
}
break;
case WM_KEYUP:
switch (msg.vkcode)
{
case VK_UP:
is_move_up = false;
break;
case VK_DOWN:
is_move_down = false;
break;
case VK_LEFT:
is_move_left = false;
break;
case VK_RIGHT:
is_move_right = false;
break;
}
break;
}
}
数据处理
首先判断了游戏是否开始,配合开始游戏按钮使用
处理顺序:
- 调用了玩家类中的移动方法,处理玩家移动
- 调用全局子弹更新函数,更新子弹位置
- 调用全局敌人生成函数,尝试生成敌人
- 调用敌人类中的移动方法,更新敌人位置
- 碰撞检测
- 遍历敌人数组,移除生命值归零的敌人
碰撞检测
首先检测敌人与玩家碰撞:
在敌人类中定义了检测与玩家碰撞方法
遍历敌人列表,逐个调用该对象的检测与玩家碰撞方法,传入玩家对象
// 检测敌人与玩家碰撞
for (Enemy* enemy : enemy_list)
{
if (enemy->CheckPlayerCollision(player))
{
static TCHAR text[128];
_stprintf_s(text, _T("最终得分:%d !"), score);
MessageBox(GetHWnd(), text, _T("游戏结束"), MB_OK);
running = false;
break;
}
}
然后检测子弹与敌人的碰撞:
在敌人类中定义了检测与子弹碰撞方法
遍历敌人列表,对于每个敌人遍历子弹列表,逐个调用该对象的检测与子弹碰撞方法,传入子弹对象
// 检测子弹和敌人的碰撞
for (Enemy* enemy : enemy_list)
{
for (const Bullet& bullet : bullet_list)
{
if (enemy->CheckBulletCollision(bullet))
{
mciSendString(_T("play hit from 0"), NULL, 0, NULL);
enemy->Hurt();
score++;
}
}
}
渲染画面
- 清空画面
- 调用各个背景绘制画面
- 调用玩家类中的绘制方法
- 遍历子弹与敌人列表,调用对应的绘制方法
- 刷新画面缓冲区
稳定帧率
使用GetTickCount方法配合Sleep函数稳定帧率
评论区