VC++ 实现俄罗斯方块小游戏(附全部源码)
实现效果
实现思路
基于VS2017 MFC工程
俄罗斯方块是学习游戏开发经典项目,小巧而且特别容易实现。我们不去探究复杂的游戏引擎,也不去深入windows底层,直接创建mfc对话框程序就是干!
程序结构
程序结构非常简单,TerisApp就是游戏程序,程序里面有一个对话框TerisDlg,对话框里面有一个游戏TerisGame。
TerisApp
这个没什么可说的,我直接用向导生成的,一行代码没改
TerisDlg
对话框也是主窗口逻辑,主要是支撑显示和交互。
显示
在对话框的显示函数中调用渲染函数
渲染调用最后到达游戏
void CTetrisDlg::Render()
{
CDC* dc = this->GetDC();
CRect rect;
GetClientRect(&rect);
CDC memDC;
memDC.CreateCompatibleDC(dc);
CBitmap bitmap;
bitmap.CreateCompatibleBitmap(dc, rect.Width(), rect.Height());
CBitmap* oldBitmap = memDC.SelectObject(&bitmap);
m_pGame->Paint(memDC);
dc->BitBlt(0, 0, rect.Width(), rect.Height(), &memDC, 0, 0, SRCCOPY);
memDC.SelectObject(oldBitmap);
bitmap.DeleteObject();
memDC.DeleteDC();
this->ReleaseDC(dc);
CDialogEx::OnPaint();
}
定时(心跳)
设置定时器,对话框初始化的时候设置定时器
BOOL CTetrisDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 将“关于...”菜单项添加到系统菜单中。
// IDM_ABOUTBOX 必须在系统命令范围内。
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != nullptr)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// 设置此对话框的图标。 当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
// TODO: 在此添加额外的初始化代码
m_pGame->Init();
this->SetTimer(1,500,NULL);
this->SetTimer(2, 1, NULL);
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
定时调用驱动游戏心跳和渲染
void CTetrisDlg::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
switch (nIDEvent)
{
case 1:
m_pGame->Run();
break;
case 2:
Render();
break;
default:
break;
}
CDialogEx::OnTimer(nIDEvent);
}
键盘事件
主要是传入键盘事件,左右移动
void CTetrisDlg::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
switch (nChar)
{
case VK_UP:
m_pGame->Rotate();
break;
case VK_DOWN:
m_pGame->Down();
break;
case VK_LEFT:
m_pGame->Left();
break;
case VK_RIGHT:
m_pGame->Right();
break;
default:
break;
}
CDialogEx::OnKeyDown(nChar, nRepCnt, nFlags);
}
无法响应键盘重写父类函数
BOOL CTetrisDlg::PreTranslateMessage(MSG* pMsg)
{
// TODO: 在此添加专用代码和/或调用基类
this->SendMessage(pMsg->message, pMsg->wParam, pMsg->lParam);
return CDialogEx::PreTranslateMessage(pMsg);
}
游戏逻辑
头文件
enum TerisConst
{
//总行数
RowLimit = 30,
//总列数
ColLimit = 15,
//绘制起始坐标
BackX = 100,
BackY = 30,
//方块宽度大小
ElementSize = 20,
//移动方块单元数
BlockSize = 4,
//移动方块默认放置位置
BlockIndex = (ColLimit / 2) - (BlockSize) / 2,
//消去一行得分
ScoreDelta = 100,
//HUD
HUD_Block_X = 560,
HUD_Text_X = 500,
//下一个方块
NextBlockTextY = 50,
NextBlockY = 100,
ScoreTextY = 240,
UpTextY = 340,
DownTextY = 380,
LeftTextY = 420,
RightTextY = 460,
};
class CTerisGame
{
public:
CTerisGame();
~CTerisGame();
public:
bool Init();
void Run();
void Paint(CDC& dc);
public:
//左移
void Left();
//右移
void Right();
//旋转
void Rotate();
//速落
void Down();
protected:
//背景方块
int m_nBackArr[RowLimit][ColLimit];
//下落的方块
int m_nFallBlock[BlockSize][BlockSize];
//右边显示的下一个方块
int m_nNextBlock[BlockSize][BlockSize];
//旋转方块缓存
int m_nRotateBlock[BlockSize][BlockSize];
//移动方块下落行
int m_nFallRow;
//移动方块下落列
int m_nFallCol;
//是否游戏结束
bool m_bGameOver;
//分数
int m_nScore;
private:
//画一个小方块
void DrawElement(CDC& dc, int x, int y,