BombsCanvas
区域
class BombsCanvas : public wxPanel
{
public:
// Constructor and destructor
BombsCanvas(wxFrame *parent, BombsGame *game);
void UpdateGridSize();
wxSize GetGridSizeInPixels() const;
virtual ~BombsCanvas();
private:
void OnPaint(wxPaintEvent& event);
void DrawField(wxDC *, int xc1, int yc1, int xc2, int yc2);
void RefreshField(int xc1, int yc1, int xc2, int yc2);
void Uncover(int x, int y);
void OnMouseEvent(wxMouseEvent& event);
void OnChar(wxKeyEvent& event);
BombsGame *m_game;
wxBitmap *m_bmp;
// Cell size in pixels
int m_cellWidth;
int m_cellHeight;
wxDECLARE_EVENT_TABLE();
};
红框区域就是Panel了,我理解是Frame(窗口)中内容部分。我们验证一下。
OnPaint里画红色矩形。果然!
构造函数
BombsCanvas::BombsCanvas(wxFrame *parent, BombsGame *game)
: wxPanel(parent, wxID_ANY)
{
m_game = game;
int sx, sy;
wxClientDC dc(this);
dc.SetFont(BOMBS_FONT);
wxCoord chw, chh;
wxString buf = wxT("M");
dc.GetTextExtent(buf, &chw, &chh);
dc.SetFont(wxNullFont);
dc.SetMapMode(wxMM_METRIC);
int xcm = dc.LogicalToDeviceX(10);
int ycm = dc.LogicalToDeviceY(10);
// To have a square cell, there must be :
// sx*ycm == sy*xcm
if (chw*ycm < chh*xcm)
{
sy = chh;
sx = chh*xcm/ycm;
}
else
{
sx = chw;
sy = chw*ycm/xcm;
}
m_cellWidth = (sx+3+X_UNIT)/X_UNIT;
m_cellHeight = (sy+3+Y_UNIT)/Y_UNIT;
dc.SetMapMode(wxMM_TEXT);
m_bmp = NULL;
}
有点长,实际做的事情不多。
wxPanel(parent, wxID_ANY)
设置了Panel的父窗口,实际传入的就是Frame
m_game = game;
游戏逻辑
int sx, sy;
wxClientDC dc(this);
dc.SetFont(BOMBS_FONT);
wxCoord chw, chh;
wxString buf = wxT("M");
dc.GetTextExtent(buf, &chw, &chh);
dc.SetFont(wxNullFont);
dc.SetMapMode(wxMM_METRIC);
int xcm = dc.LogicalToDeviceX(10);
int ycm = dc.LogicalToDeviceY(10);
// To have a square cell, there must be :
// sx*ycm == sy*xcm
if (chw*ycm < chh*xcm)
{
sy = chh;
sx = chh*xcm/ycm;
}
else
{
sx = chw;
sy = chw*ycm/xcm;
}
m_cellWidth = (sx+3+X_UNIT)/X_UNIT;
m_cellHeight = (sy+3+Y_UNIT)/Y_UNIT;
dc.SetMapMode(wxMM_TEXT);
这一整段就是在计算m_cellWidth m_cellHeight,从名字看就是每个格子的长、宽。
m_bmp = NULL;
准备一个图片做为绘制缓存。
OnPaint函数
void BombsCanvas::OnPaint(wxPaintEvent& WXUNUSED(event))
{
wxPaintDC dc(this);
const int numHorzCells = m_game->GetWidth();
const int numVertCells = m_game->GetHeight();
// Insert your drawing code here.
if (!m_bmp)
{
wxSize size = dc.GetSize();
m_bmp = new wxBitmap(size.GetWidth(), size.GetHeight());
if (m_bmp)
{
wxMemoryDC memDC;
memDC.SelectObject(*m_bmp);
DrawField(&memDC, 0, 0, numHorzCells-1, numVertCells-1);
memDC.SelectObject(wxNullBitmap);
}
}
if (m_bmp)
{
wxMemoryDC memDC;
memDC.SelectObject(*m_bmp);
wxSize size = dc.GetSize();
dc.Blit(0, 0, size.GetWidth(), size.GetHeight(),
&memDC, 0, 0, wxCOPY);
memDC.SelectObject(wxNullBitmap);
}
else
{
DrawField(&dc, 0, 0, numHorzCells-1, numVertCells-1);
}
}
wxPaintDC dc(this);
这是当前绘制DC
const int numHorzCells = m_game->GetWidth();
const int numVertCells = m_game->GetHeight();
计算横竖有几个格子
if (!m_bmp)
{
wxSize size = dc.GetSize();
m_bmp = new wxBitmap(size.GetWidth(), size.GetHeight());
if (m_bmp)
{
wxMemoryDC memDC;
memDC.SelectObject(*m_bmp);
DrawField(&memDC, 0, 0, numHorzCells-1, numVertCells-1);
memDC.SelectObject(wxNullBitmap);
}
}
如果 m_bmp (绘制的缓存图片)不存在,
计算当前绘制设备的大小
创建一个同样大小的Bitmap(位图)
保护性判断了一下位图创建成功没,没有就结束。
创建一个内存dc做为双缓冲
把位图绑定在内存dc上
通过DrawField在内存dc(缓存图片)上画游戏画面
资源清理
简言之,如果没有缓存,创建并把游戏内容画上去
if (m_bmp)
{
wxMemoryDC memDC;
memDC.SelectObject(*m_bmp);
wxSize size = dc.GetSize();
dc.Blit(0, 0, size.GetWidth(), size.GetHeight(),
&memDC, 0, 0, wxCOPY);
memDC.SelectObject(wxNullBitmap);
}
else
{
DrawField(&dc, 0, 0, numHorzCells-1, numVertCells-1);
}
如果有缓存
创建一个内存dc做为双缓冲
把位图绑定在内存dc上
把内存dc拷贝到当前屏幕dc上
清理资源
如果没有缓存,直接在屏幕上画游戏画面
简言之,如果有缓存,把缓存画到屏幕上,如果还没有缓存,直接在屏幕上画
可见,核心的绘制流程在DrawField 函数里
另外,绘制流程使用一个位图缓存,正常情况下,每次刷新界面都是把这个缓存贴在屏幕上。
我们可以参考上述过程但不需要关注DrawField 怎么绘制游戏画面。我们的Demo按照自己的方式绘制就好。
OnMouseEvent
void BombsCanvas::OnMouseEvent(wxMouseEvent& event)
{
const int gridWidth = m_game->GetWidth();
const int gridHeight = m_game->GetHeight();
wxCoord fx, fy;
event.GetPosition(&fx, &fy);
int x = fx/(m_cellWidth*X_UNIT);
int y = fy/(m_cellHeight*Y_UNIT);
if (x<gridWidth && y<gridHeight)
{
if ( (event.RightDown() || (event.LeftDown() && event.ShiftDown()))
&& (m_game->IsHidden(x,y)
|| !m_game->GetNumRemainingCells() ) )
{
// store previous and current field
int prevFocusX = m_game->m_gridFocusX;
int prevFocusY = m_game->m_gridFocusY;
m_game->m_gridFocusX = x;
m_game->m_gridFocusY = y;
RefreshField(prevFocusX, prevFocusY, prevFocusX, prevFocusY);
m_game->Mark(x, y);
RefreshField(x, y, x, y);
return;
}
else if (event.LeftDown() && m_game->IsHidden(x,y)
&& !m_game->IsMarked(x,y))
{
// store previous and current field
int prevGridFocusX = m_game->m_gridFocusX;
int prevGridFocusY = m_game->m_gridFocusY;
m_game->m_gridFocusX = x;
m_game->m_gridFocusY = y;
RefreshField(prevGridFocusX, prevGridFocusY,
prevGridFocusX, prevGridFocusY);
Uncover(x, y);
return;
}
}
}
代码有点多,而且多是游戏逻辑,重点在这里:
m_game->Mark(x, y);
RefreshField(x, y, x, y);
点击后让游戏逻辑处理点击位置,给出合理响应,刷新界面。
再来个图
游戏逻辑负责更新bitmap
绘制逻辑负责把bitmap刷到界面上
消息映射
BombsCanvas 声明中定义了
wxDECLARE_EVENT_TABLE();
BombsCanvas 实现中定义了消息处理函数
wxBEGIN_EVENT_TABLE(BombsCanvas, wxPanel)
EVT_PAINT(BombsCanvas::OnPaint)
EVT_MOUSE_EVENTS(BombsCanvas::OnMouseEvent)
EVT_CHAR(BombsCanvas::OnChar)
wxEND_EVENT_TABLE()
于是,绘制 鼠标 键盘消息分发到了特定函数。还发现一个Bug 键盘并没有相应,有机会研究一下。
至此,除了游戏逻辑,bombs的脉络已经理清楚了。我们可以参考实现自己的Demo了。