游戏开发(三)——WIN32 黑白棋(三)——游戏画面的现实

来源:互联网 发布:clip studio paint mac 编辑:程序博客网 时间:2024/04/29 23:41

整个游戏分3部分介绍。

1、棋局的现实

2、AI的现实

3、游戏画面的现实

提供一下完整项目下载


这是第三部分:画面的显示


这部分其实就比较简单的,说白了就是api的堆砌。


主要了解下windows的消息机制,以及怎么画图


主要是分别封装了下对棋盘,棋子,以及当前轮到谁,当前比分是多少,就是游戏画面上不同的部分的绘制。

void DrawReversiBoard();void DrawReversiPieces(EnumReversiPiecesType type, int row_y, int column_x);void DrawReversiResult(EnumReversiResult res, EnumReversiPiecesType type);void DrawPiecesCount(BYTE black, BYTE white);

4个函数,分别绘制不同的部分。


然后主要的一个问题,就是游戏画面并不是直接绘制到屏幕上的,后台单独创建了一个HDC,所有的画面是先绘制到这个HDC上,然后再从这个HDC上用BitBlt拷贝到屏幕上,BitBlt得速度要比单独一部分一部分的直接绘制到屏幕,要快很多,通常都是用这样的内部缓存来解决直接绘制的闪屏的问题。


然后列一下主要用到的API:

画线的:MoveToEx,LineTo

画矩形的:Rectangle

画椭圆的:Ellipse

选择什么样的画笔(即画的线,圆、矩形的边框线是什么样子的)GetStockObject(BLACK_PEN),

BLACK_PEN是系统提供的几种画笔之一,是黑色的画笔。你也可以自定义画笔。

选择什么样的画刷(即圆,矩形的内部用什么样式、颜色填充)GetStockObject(BLACK_BRUSH),

BLACK_BRUSH是系统提供的几种画刷之一,是用黑色填充。你也可以自定义画刷。

输出文字的:TextOut

设置文字的颜色:SetTextColor

设置文字背景的颜色:SetBkColor


从屏幕DC创建一个绘图层CreateCompatibleDC,得到一个HDC,以上的圆、矩形、线条都是绘制到这个HDC上。

创建一个位图CreateCompatibleBitmap,得到一个HBITMAP,最终就是用这个HBITMAP,然后BitBlt到屏幕的DC上。

SelectObject(HDC, HBITMAP);即在这个HDC绘图层上,应用这个HBITMAP。


然后游戏窗口上有个菜单这个东西,这个东西怎么做的呢:

在项目中加一个叫XXX.rc的资源文件,比如叫game.rc。这是WIN32项目的资源文件。

在里面写上如下内容

#include "resource.h"#include "afxres.h"///////////////////////////////////////////////////////////////////////////////// Menu//REVERSI MENU DISCARDABLEBEGIN    POPUP "Begin"    BEGIN        MENUITEM "Player First",              IDM_PLAYER_FIRST        MENUITEM "AI First",                  IDM_AI_FIRST    ENDEND
当然你也可以在IDE工具里面直接做,现在一般都支持可视化的菜单创建,点点鼠标就出来了。

只不过其实质就是类似如上的一段代码表示的。


其中resource.h

#define IDM_PLAYER_FIRST101#define IDM_AI_FIRST102

就是定义的菜单项对应的键值,玩家先手对应的101,AI先手对应的102。

这个键值在windows消息机制中会用到,用来区分鼠标点的是哪一个菜单用的。


#include "afxres.h"

这是MFC库的一个头文件,VC6.0下面可能会报这个头文件找不到,首先你看一下VC安装路径下\VC98\MFC\INCLUDE这个目录有没有include进来,如果加进来了,但是确实没有这个文件,就另外到网上搜一个下载下来,直接粘贴到VC6.0的安装目录里面去一样可以使用。VC6.0之后的版本的IDE,基本上都没问题。


windows消息机制中,本游戏使用到的消息:

WM_CREATE:窗口的创建。在这个消息中做各种对象的初始化,然后创建一个定时器SetTimer。

WM_TIMER:上面创建的定时器,会定时发出的消息。在这个消息中,刷新游戏画面,不管有没有画面上变动。比如SetTimer设定为17毫秒一次,则每隔17毫秒会有这个消息被收到并处理,即每隔17毫秒游戏画面刷新一次。17毫秒一次大约就是每秒60帧的频率(1000 / 60 = 16.667)。

WM_DESTROY:窗口的关闭释放的消息。在这个消息中做一些必要的回收,比如上面的定时器要KillTimer掉。

WM_KEYDOWN:是键盘按键消息。这个消息中带有参数wParam,代表不同的键值。这里判断一下是不是VK_ESCAPE(ESC键),如果玩家按了ESC,等同于点了左上角的X的效果,所以PostMessage(hwnd, WM_DESTROY, 0, 0)手动发出一个WM_DESTROY的消息。然后程序接下来就会收到WM_DESTROY并处理,最终退出。

WM_LBUTTONDOWN,鼠标左键的单击消息。这个消息带两个参数,分别代表的是相对窗口左上角(0,0)这个像素点的相对坐标位置。在这个消息中,将鼠标点的(x,y)

换算成棋盘的格子坐标,然后处理玩家在这个位置落子的逻辑处理,即调用Player对象的Play方法,然后PostMessage(hwnd, WM_TIMER, 0, 0),手动发一个定时器消息,刷新一下游戏画面。之后判断是否是AI可以落子,如果轮到AI落子了,则PostMessage(hwnd, WM_DO_AI, 0, 0)手动发一个WM_DO_AI的消息通知AI落子

WM_RBUTTONDOWN,鼠标右键的单击消息。在这个消息中处理悔棋逻辑。

WM_DO_AI,这是一个自定义消息#define WM_DO_AI WM_USER + 0x0001。在这个消息中,AI就执行搜索,找出最优的那一个位置的坐标,并落子在这个位置。然后同样手动发一个定时器消息,刷新一下游戏画面。之后判断是否还是AI落子,因为AI落子之后,玩家可能无子可下,所以判断下是否下一步还是AI落子,是的话继续PostMessage(hwnd, WM_DO_AI, 0, 0),让下一个WM_DO_AI继续处理AI的落子罗辑。如果AI落子之后轮到玩家落子,则什么都不做,等玩家落子即可。

WM_COMMAND,这个就是菜单消息了,带一个参数,表示的菜单的编号,即上面resource.h中定义的菜单编号,如果游戏过程中玩家点了菜单,那么表示重新开局了,将各个对象重新初始化一下,棋局重新开始。


最后说到创建窗口了。

首先需要RegisterClass,注册一个窗口,主要注册的是窗口的小图标啦,鼠标样式了,窗口的名字了,菜单的名字了,窗口的消息处理函数啦,等类似这样的信息。

然后CreateWindow,主要是窗口的长宽,初始坐标位置,窗口的样式,比如有没有标题那一栏啦,右上角有没有最大化,最小化按钮啦,等等


下面贴一下棋局的画面绘制的代码

ReversiScene.h

#ifndef _ReversiScene_h_#define _ReversiScene_h_#include <windows.h>#include "ReversiCommon.h"#include "ReversiBitBoard.h"//棋盘一个单元格长宽各60像素const int REVERSI_GAME_GRID = 60;//棋子半径,举例单元格边界5像素const int REVERSI_PIECES_R = REVERSI_GAME_GRID / 2 - 5;//棋盘左上角在窗口上的坐标位置const int REVERSI_GAME_ROW_Y = 10;const int REVERSI_GAME_COLUMN_X = 10;//游戏场景大小const int REVERSI_SCENE_WIDTH = 700;const int REVERSI_SCENE_HEIGHT = 600;class ReversiScene{public:ReversiScene();~ReversiScene();void Init(HDC hDC);void Draw(ReversiBitBoard& reversi);void DrawToScreen();private:void DrawReversiBoard();void DrawReversiPieces(EnumReversiPiecesType type, int row_y, int column_x);void DrawReversiResult(EnumReversiResult res, EnumReversiPiecesType type);void DrawPiecesCount(BYTE black, BYTE white);HDC m_hDC;HDC m_hMemDC;HBITMAP m_hMemBitmap;ReversiBitBoard m_LastMap;};#endif


ReversiScene.cpp

#include "ReversiScene.h"ReversiScene::ReversiScene(){m_hMemDC = NULL;m_hMemBitmap = NULL;}ReversiScene::~ReversiScene(){DeleteObject(m_hMemDC);DeleteObject(m_hMemBitmap);}void ReversiScene::Init(HDC hDC){m_hDC = hDC;m_hMemDC = CreateCompatibleDC(m_hDC);m_hMemBitmap = CreateCompatibleBitmap(m_hMemDC, REVERSI_SCENE_WIDTH, REVERSI_SCENE_HEIGHT);SelectObject(m_hMemDC, m_hMemBitmap);DrawReversiBoard();}void ReversiScene::Draw(ReversiBitBoard& reversi){EnumReversiPiecesType type1;EnumReversiPiecesType type2;for (int i = 0; i < REVERSI_MAX_ROW; i++){for (int j = 0; j < REVERSI_MAX_COLUMN; j++){type1 = reversi.GetPieces(i, j);type2 = m_LastMap.GetPieces(i, j);if (type1 != type2){DrawReversiPieces(type1, i, j);m_LastMap.SetPieces(type1, i, j);}}}DrawReversiResult(reversi.IsGameOver(), reversi.GetCurrType());int black = reversi.GetCount(enum_ReversiPieces_Black);int white = reversi.GetCount(enum_ReversiPieces_White);DrawPiecesCount(black, white);}void ReversiScene::DrawToScreen(){BitBlt(m_hDC, 0, 0, REVERSI_SCENE_WIDTH, REVERSI_SCENE_HEIGHT, m_hMemDC, 0, 0, SRCCOPY);}void ReversiScene::DrawReversiBoard(){int begin_row_y = REVERSI_GAME_ROW_Y;int begin_column_x = REVERSI_GAME_COLUMN_X;SelectObject(m_hMemDC, GetStockObject(WHITE_PEN));SelectObject(m_hMemDC, GetStockObject(WHITE_BRUSH));Rectangle(m_hMemDC, 0, 0, REVERSI_SCENE_WIDTH, REVERSI_SCENE_HEIGHT) ;SelectObject(m_hMemDC, GetStockObject(BLACK_PEN));for (int i = 0; i <= REVERSI_MAX_ROW; i++){MoveToEx(m_hMemDC, begin_row_y + i * REVERSI_GAME_GRID, begin_column_x, NULL);LineTo(m_hMemDC, begin_row_y + i * REVERSI_GAME_GRID, begin_column_x + REVERSI_MAX_COLUMN * REVERSI_GAME_GRID);}for (int i = 0; i <= REVERSI_MAX_COLUMN; i++){MoveToEx(m_hMemDC, begin_row_y, begin_column_x + i * REVERSI_GAME_GRID, NULL);LineTo(m_hMemDC, begin_row_y + REVERSI_MAX_ROW * REVERSI_GAME_GRID, begin_column_x + i * REVERSI_GAME_GRID);}}void ReversiScene::DrawReversiPieces(EnumReversiPiecesType type, int row_y, int column_x){int begin_row_y = REVERSI_GAME_ROW_Y + 1 + REVERSI_GAME_GRID / 2 - REVERSI_PIECES_R + row_y * REVERSI_GAME_GRID;int begin_column_x = REVERSI_GAME_COLUMN_X + 1 + REVERSI_GAME_GRID / 2 - REVERSI_PIECES_R + column_x * REVERSI_GAME_GRID;int end_row_y = REVERSI_GAME_ROW_Y - (REVERSI_GAME_GRID / 2 - REVERSI_PIECES_R) + (row_y + 1) * REVERSI_GAME_GRID;int end_column_x = REVERSI_GAME_COLUMN_X - (REVERSI_GAME_GRID / 2 - REVERSI_PIECES_R) + (column_x + 1) * REVERSI_GAME_GRID;if (enum_ReversiPieces_Black == type){SelectObject(m_hMemDC, GetStockObject(BLACK_PEN));SelectObject(m_hMemDC, GetStockObject(BLACK_BRUSH));}else if (enum_ReversiPieces_White == type){SelectObject(m_hMemDC, GetStockObject(BLACK_PEN));SelectObject(m_hMemDC, GetStockObject(WHITE_BRUSH));}else{SelectObject(m_hMemDC, GetStockObject(WHITE_PEN));SelectObject(m_hMemDC, GetStockObject(WHITE_BRUSH));}Ellipse(m_hMemDC, begin_column_x, begin_row_y, end_column_x, end_row_y);}void ReversiScene::DrawReversiResult(EnumReversiResult res, EnumReversiPiecesType type){WCHAR wsCurrRes[20];if (enum_Reversi_Playing == res){if (enum_ReversiPieces_Black == type){wcscpy_s(wsCurrRes, 20, L"当前轮到:黑子");}else{wcscpy_s(wsCurrRes, 20, L"当前轮到:白子");}}else if (enum_Reversi_Win_Black == res){wcscpy_s(wsCurrRes, 20, L"黑胜!游戏结束");}else if (enum_Reversi_Win_White == res){wcscpy_s(wsCurrRes, 20, L"白胜!游戏结束");}else{wcscpy_s(wsCurrRes, 20, L"平局!游戏结束");}SetTextColor(m_hMemDC, RGB(0, 0, 0));SetBkColor(m_hMemDC, RGB(255, 255, 255));TextOut(m_hMemDC, REVERSI_GAME_COLUMN_X + REVERSI_MAX_COLUMN * REVERSI_GAME_GRID + 20, 100, wsCurrRes, wcslen(wsCurrRes)) ;}void ReversiScene::DrawPiecesCount(BYTE black, BYTE white){WCHAR wsCurrBlack[20];WCHAR wsCurrWhite[20];wsprintf(wsCurrBlack, L"当前黑子:%02d", black);wsprintf(wsCurrWhite, L"当前白子:%02d", white);SetTextColor(m_hMemDC, RGB(0, 0, 0));SetBkColor(m_hMemDC, RGB(255, 255, 255));TextOut(m_hMemDC, REVERSI_GAME_COLUMN_X + REVERSI_MAX_COLUMN * REVERSI_GAME_GRID + 20, 130, wsCurrBlack, wcslen(wsCurrBlack)) ;TextOut(m_hMemDC, REVERSI_GAME_COLUMN_X + REVERSI_MAX_COLUMN * REVERSI_GAME_GRID + 20, 160, wsCurrWhite, wcslen(wsCurrWhite)) ;}

最后游戏的主罗辑game.cpp

#define WIN32_LEAN_AND_MEAN #include <windows.h>#include "Resource.h"#include "ReversiBitBoard.h"#include "ReversiScene.h"#include "ReversiPlayer.h"#include "ReversiAI.h"ReversiBitBoard g_Reversi;ReversiPlayer g_Player;ReversiAI g_AI;ReversiScene g_Scene;const int WINDOWS_WIDTH = REVERSI_SCENE_WIDTH + 6;const int WINDOWS_HEIGHT = REVERSI_SCENE_HEIGHT + 25;#define WM_DO_AI WM_USER + 0x0001LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow){WCHAR szAppName[] = L"Reversi" ;HWND hwnd ;MSG msg ;WNDCLASS wndclass ;wndclass.style = CS_HREDRAW | CS_VREDRAW;wndclass.lpfnWndProc = WndProc;wndclass.cbClsExtra = 0;wndclass.cbWndExtra = 0;wndclass.hInstance = hInstance;wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION);wndclass.hCursor = LoadCursor (NULL, IDC_ARROW);wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);wndclass.lpszMenuName = szAppName;wndclass.lpszClassName = szAppName;if (!RegisterClass (&wndclass)){MessageBox (NULL, L"This program requires Windows NT!", szAppName, MB_ICONERROR);return 0;}hwnd = CreateWindow(szAppName,szAppName,WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,//重叠窗口,有标题栏,有系统菜单,有最小化,没有最大化0,0,WINDOWS_WIDTH,WINDOWS_HEIGHT,NULL,NULL,hInstance,NULL);ShowWindow(hwnd, iCmdShow);UpdateWindow(hwnd);while(GetMessage(&msg, NULL, 0, 0)){TranslateMessage(&msg);DispatchMessage(&msg);}return 0;}LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){switch (message){case WM_CREATE:{g_Scene.Init(GetDC(hwnd));g_Reversi.Init();g_Player.Init(enum_ReversiPieces_Black);g_AI.Init(enum_ReversiPieces_White);g_Scene.Draw(g_Reversi);SetTimer(hwnd, 1, 17, NULL);}return 0;case WM_DESTROY:{KillTimer(hwnd, 1);PostQuitMessage(0);}return 0;case WM_COMMAND:{switch (LOWORD(wParam)){case IDM_PLAYER_FIRST:{g_Reversi.Init();g_Player.Init(enum_ReversiPieces_Black);g_AI.Init(enum_ReversiPieces_White);g_Scene.Draw(g_Reversi);}return 0;case IDM_AI_FIRST:{g_Reversi.Init();g_Player.Init(enum_ReversiPieces_White);g_AI.Init(enum_ReversiPieces_Black);g_AI.Play(g_Reversi);g_Scene.Draw(g_Reversi);}return 0;}}return 0;case WM_KEYDOWN:{switch (wParam){case VK_ESCAPE:{PostMessage(hwnd, WM_DESTROY, 0, 0);}return 0;}}return 0;case WM_DO_AI:{if (g_Reversi.GetCurrType() == g_AI.GetPlayerType() && g_Reversi.CanPlay(g_AI.GetPlayerType())){g_AI.Play(g_Reversi);g_Scene.Draw(g_Reversi);PostMessage(hwnd, WM_TIMER, 0, 0);if (g_Reversi.GetCurrType() == g_AI.GetPlayerType()){PostMessage(hwnd, WM_DO_AI, 0, 0);}}}return 0;case WM_LBUTTONDOWN:{int y = HIWORD(lParam);int x = LOWORD(lParam);int row_y = (y - REVERSI_GAME_ROW_Y) / REVERSI_GAME_GRID;int column_x = (x - REVERSI_GAME_COLUMN_X) / REVERSI_GAME_GRID;if (g_Reversi.GetCurrType() == g_Player.GetPlayerType() &&g_Reversi.CanPlay(g_Player.GetPlayerType(), row_y, column_x)){g_Player.Play(g_Reversi, row_y, column_x);g_Scene.Draw(g_Reversi);PostMessage(hwnd, WM_TIMER, 0, 0);if (g_Reversi.CanPlay(g_AI.GetPlayerType())){PostMessage(hwnd, WM_DO_AI, 0, 0);}}}return 0;case WM_RBUTTONDOWN:{g_Player.Cancel(g_Reversi);g_Scene.Draw(g_Reversi);PostMessage(hwnd, WM_TIMER, 0, 0);}return 0;case WM_TIMER: {g_Scene.DrawToScreen();}return 0;}return DefWindowProc (hwnd, message, wParam, lParam) ;}



1 0
原创粉丝点击