今天,就要开始做游戏的主要部分了,如何控制游戏。
4.
控制操作
我们的控制就是,先空出一个格子不显示,然后单击到这个空格子的周围的格子的图片时,就移动过去。
为此,我们要先把空格子画出来。昨天我们的图片其实还是完整显示的。要稍微修改一下我们的
OnPaint
函数的代码了。
先看代码。为了省地方,我就把和昨天一样的地方省略了,用省略号代替。
-
- void CPuzzleView::OnPaint(void)
- {
- HDC hdcMem;
- HDC hdcScr = GetDC(m_hWnd);
- hdcMem = CreateCompatibleDC(hdcScr);
-
- RECT rect;
- GetClientRect(m_hWnd, &rect);
-
- HBITMAP hTemp= CreateCompatibleBitmap(hdcScr, rect.right, rect.bottom);
- SelectObject(hdcMem, hTemp);
-
- HDC hdcTempBK = CreateCompatibleDC(hdcMem);
- SelectObject(hdcTempBK, m_hBmpBack);
- BitBlt(hdcMem, 0, 0, SCRWIDTH, SCRHEIGHT, hdcTempBK, 0, 0, SRCCOPY);
- DeleteDC(hdcTempBK);
-
- ……
-
- HDC hdcGameMem = CreateCompatibleDC(hdcMem);
- SelectObject(hdcGameMem, m_hBmpGame);
- if(m_IsGameStarted==true)
- {
- for(int i=0; i<m_FrameNum; ++i)
- {
- for(int j=0; j<m_FrameNum; ++j)
- {
- ……
- if(m_Block[j+m_FrameNum*i] == LASTBLOCK)
- {
- continue;
- }
- ……
- }
- }
- }
- else
- {
- BitBlt(hdcMem, m_Game_x, m_Game_y, m_Game_width, m_Game_height, hdcGameMem, 0, 0, SRCCOPY);
- }
- ……
- }
先看这部分。
-
- RECT rect;
- GetClientRect(m_hWnd, &rect);
-
- HBITMAP hTemp= CreateCompatibleBitmap(hdcScr, rect.right, rect.bottom);
- SelectObject(hdcMem, hTemp);
-
- HDC hdcTempBK = CreateCompatibleDC(hdcMem);
- SelectObject(hdcTempBK, m_hBmpBack);
- BitBlt(hdcMem, 0, 0, SCRWIDTH, SCRHEIGHT, hdcTempBK, 0, 0, SRCCOPY);
按照昨天的方法,虽然正确显示,但是由于
SelectObject(hdcMem, m_hBmpBack);
是绑定到内存
DC
中的位图,这样,当我们对内存
DC
进行操作时,实际上就是修改了
m_hBmpBack
里的内容,最后显示的就是它的,而这个变量是存储我们的背景图的,它一改,就相当于背景改变了,就会发现你想要的空格根本就出不来。
所以,我们先创建了一个兼容屏幕的位图,这个位图我们还没有填充任何东西,如果直接显示,就是黑色的。然后绑定到内存
DC
中,这样,我们以后更改的就是这个位图,和背景位图没关系了。
但是,我们还要显示背景位图,于是我采用了再创建一个
DC
,用来把背景显示出来的方法。
if(m_Block[j+m_FrameNum*i] == LASTBLOCK)
{
continue;//
略掉最后一块不画
}
然后用这个来判断,是不是最后一个方格,如果是的话,就不要画图了。这样就可以空出来最后一块。
其实今天的主要任务是实现单击命令。单击之后要判断,现在只用图来判断就不太好了,所以今天的主要任务是
CPuzzleLogic
里的内容。今天的比较抽象,不太好理解,我尽量说得详细些。
这是主要代码。我们的过程是单击一个空格子周围的图片,图片就移到空格子,其实就是把空格子和单击的格子交换了一下位置,因此,
MovePos(int dstPos, int srcPos)
函数实现了这个功能,这个很简单,就不多说了。
我们要进行的判断是单击的格子四周是否有空格,这个用一下偏移量就可以解决了,因为我们是一维数组,比如我举例一共是
3*3
的,其中最大的
8
是空格(从
0
开始的),如下
0 2 3
1 4 8
5 6 7
很明显的,如果我们单击的是
4
(坐标也为
4
),直接判断坐标
4
-
1
,
4+1
,
4
-
3
,
4+3
位置的是不是
8
就可以了,这没问题。
但是,如果我们点的是
3
(坐标为
2
)
2
-
3
为-
1
,
2+1
就是下一行的开头了,显然这和我们预期的结果不一样,
2
-
3
为-
1
,如果我们去取值判断,就会发生错误。而
2+1
成为下一行第一个,显示这两个在我们看来并没有挨着,是不能交换的,如果
8
恰好在那个位置,我们就可以交换了,这个结果显然是不正确的。
所以,我们要先判断一下单击的位置,它有几个方向可以去查看。
就是这段代码,我用的是一个四位数来表示四个方向,其实最好是用
4
位的二进制,可是左移右移我有时候感觉总会弄错,所以用了
10
进制的。
上面的注释有写什么代表什么,
int temp = Pos%m_BlockNum;
Pos
是当前单击的坐标,无论它在哪里,当它除以行数取余的时候,就是它对应的第一行的位置,这样,我们只要再判断它是否是
0
或者列数减
1
就知道是不是左右两侧那一列上的位置了。把相应的可以判断的位置标示为
1.
下面还要判断是不是最上面的和最下面的行的问题。这个比较容易,最上面一行就是
0~ m_BlockNum-1
,如果大于
m_BlockNum-1
就不在最上面一行,最下面一行就是
m_BlockNum*m_BlockNum-m_BlockNum+1~ m_BlockNum*m_BlockNum
,如果不在这个区域内,则证明不在最下面一行。
当然,在这之前还要判断是否单击在游戏区了,当然,你可能还记得我们在这之前也判断过,多判断一次也不是坏事,还要判断游戏是否在进行中,还要判断单击的是不是那个空格子。都是函数最前面。
移动方法
有了这个位置,就可以写移动的方法了。
我这是用到一个循环,当然你也可以自己控制,不用循环,就是自己去判断每一位是
1
还是
0
,然后决定是否移动。
我来说我这个,定义一个标志变量,标志是否可以移动。
int r = result%10*10;
r = pow((float)r, i);
再定义一个临时变量
r
,先是获取到每位的值,从右往左的,然后乘以
10
,如果是
1
的话就是
10
,是
0
就是
0
,然后根据
i
的值去乘方,
10
的
0
次方为
1
,
10
的
1
次方为
10
……这样,就可以根据乘方的值来判断到底是哪种,然后用
swtich
切换到正确的移动,调用移动函数就
OK
了。
如果成功移动,则设标志位为
true,
有了标志位就可以返回了。
胜利判定
游戏当然要有一个胜利判定了,这个判定很简单,循环看看是否已经符合初始的条件,就是
0
位置是
00
,
1
位置是
01
,第二行是否为
10
,
11
等等。这个比较简单,就不多说了。
再看看
CPuzzleMain
里的
OnClick
函数吧。
-
- void CPuzzleMain::OnClick(int x, int y)
- {
- if(m_View.IsInside(x, y)==true)
- {
- int p = m_View.GetPoint(x, y);
- if(m_Logic.MoveBlock(p))
- {
- m_View.LoadBMPList(m_Logic.GetBlock());
- m_View.OnPaint();
- if(m_Logic.IsVictor())
- {
- m_View.SetGameStarted(false);
- m_Logic.SetGamePlaying(false);
- MessageBox(NULL, _T("恭喜您成功完成了拼图"), _T("恭喜"), MB_OK);
- }
- }
- }
- }
过程很简单,很判断是否在游戏区,然后获取单击的坐标,然后判断是否可以移动,如果可以就把移动后的那个状态内容复制到
View
的状态变量里,再重画图,判断是否胜利了,如果胜利了,就让游戏停止,弹出提示框。
好了,到现在,我们的游戏基本上可以玩了,你还可以自己设置分成多少块。
我们还需要一个初始局面生成的方法,还有其他的美化工作,那将是以后的事,现在主要工作完成了。
[原创+连载]一步一步做拼图游戏,C++版(三):student.csdn.net/space.php
[原创+连载]一步一步做拼图游戏,C++版(二):student.csdn.net/space.php
[原创+连载]一步一步做拼图游戏,C++版(一):student.csdn.net/space.php
-------------------------------------------
代码:download.csdn.net/source/2706170
------------------------------------------------------------------------------------------------------------------------------------------
貌似是我忘写了,单击事件的调用。
在下面添加上一句就可以了。
- LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
- {
- int wmId, wmEvent;
- PAINTSTRUCT ps;
- HDC hdc;
- int x=0, y=0;
- ……
- case WM_LBUTTONDOWN:
- x=LOWORD(lParam);
- y=HIWORD(lParam);
- g_PuzzleMain.OnClick(x, y);
- ……}
x,y是获得坐标,然后调用OnClick函数