《游戏编程入门 4th》笔记(2 / 14):监听Windows消息

文章目录

    • 编写一个Windows程序
      • 理解InitInstance
        • InitInstance函数调用
        • InitInstance的结构
      • 理解MyRegisterClass
        • MyRegisterClass函数调用
        • MyRegisterClass的作用
      • 揭露WinProc的秘密
        • WinProc函数调用
        • WinProc的大秘密
    • 什么是游戏循环
      • The Old WinMain
        • 对持续性的需要
        • 实时终止器
      • WinMain和循环
        • PeekMessage函数接口
        • 将PeekMessage插到WinMain中
        • 状态驱动的游戏
    • GameLoop项目
        • 在Windows中绘制位图
        • 运行GameLoop程序

编写一个Windows程序

这次创建一个标准窗口并在这个窗口上绘制文本和图形。

DirectX SDK随后章节有安装步骤,还要配置C++编译器,目前还未需要用到。

创建一个Win32项目,添加main.cpp。(创建过程参照第1章)

main.cpp源码如下:

#include <windows.h>
#include <iostream>
using namespace std;
const string ProgramTitle = "Hello Windows";// The window event callback function
LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{RECT drawRect;PAINTSTRUCT ps;HDC hdc;switch (message){case WM_PAINT:{hdc = BeginPaint(hWnd, &ps); //start drawing for (int n = 0; n < 20; n++){int x = n * 20;int y = n * 20;drawRect = { x, y, x + 100, y + 20 };DrawText(hdc, ProgramTitle.c_str(), ProgramTitle.length(), &drawRect, DT_CENTER);}EndPaint(hWnd, &ps); //stop drawing}break;case WM_DESTROY:PostQuitMessage(0);break;}return DefWindowProc(hWnd, message, wParam, lParam);
}// Helper function to set up the window properties
ATOM MyRegisterClass(HINSTANCE hInstance)
{//set the new window's propertiesWNDCLASSEX wc;wc.cbSize = sizeof(WNDCLASSEX);wc.style = CS_HREDRAW | CS_VREDRAW;wc.lpfnWndProc = (WNDPROC)WinProc;wc.cbClsExtra = 0;wc.cbWndExtra = 0;wc.hInstance = hInstance;wc.hIcon = NULL;wc.hCursor = LoadCursor(NULL, IDC_ARROW);wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);wc.lpszMenuName = NULL;wc.lpszClassName = ProgramTitle.c_str();wc.hIconSm = NULL;return RegisterClassEx(&wc);
}// Helper function to create the window and refresh it
bool InitInstance(HINSTANCE hInstance, int nCmdShow)
{//create a new windowHWND hWnd = CreateWindow(ProgramTitle.c_str(),        //window classProgramTitle.c_str(),        //title barWS_OVERLAPPEDWINDOW,         //window styleCW_USEDEFAULT, CW_USEDEFAULT, //position of window640, 480,                    //dimensions of the windowNULL,                        //parent window (not used)NULL,	                        //menu (not used)hInstance,                   //application instanceNULL);                       //window parameters (not used)//was there an error creating the window?if (hWnd == 0) return 0;//display the windowShowWindow(hWnd, nCmdShow);UpdateWindow(hWnd);return 1;
}// Entry point for a Windows program
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow)
{//create the windowMyRegisterClass(hInstance);if (!InitInstance(hInstance, nCmdShow)) return 0;// main message loopMSG msg;while (GetMessage(&msg, NULL, 0, 0)){TranslateMessage(&msg);DispatchMessage(&msg);}return msg.wParam;
}

运行结果:

理解InitInstance

InitInstance creates the program window.

Note that InitInstance is not a Windows function like WinMain, but simply a helper function.

The instance handle is a global variable used in the program to keep track of the main instance.

InitInstance函数调用

bool InitInstance( HINSTANCE hInstance, int nCmdShow )

  • HINSTANCE hInstance. The first parameter is passed by WinMain with the program instance that it receives from Windows. InitInstance will check this with the global instance to see whether the new instance needs to be killed. ()When this happens, the main instance of the program is set as the foreground window. To the user, it will seem as if running the program again just brought the original instance forward.

  • int nCmdShow. The second parameter is passed to InitInstance by WinMain, which
    receives the parameter from Windows.

The InitInstance function returns a bool value, which is either 1 (true) or 0 (false), and simply tells WinMain whether startup succeeded or failed.

InitInstance的结构

The whole point of InitInstance is to create the new window needed by this application and display it.

// Helper function to create the window and refresh it
bool InitInstance(HINSTANCE hInstance, int nCmdShow)
{//create a new windowHWND hWnd = CreateWindow(ProgramTitle.c_str(),        //window classProgramTitle.c_str(),        //title barWS_OVERLAPPEDWINDOW,         //window styleCW_USEDEFAULT, CW_USEDEFAULT, //position of window640, 480,                    //dimensions of the windowNULL,                        //parent window (not used)NULL,	                        //menu (not used)hInstance,                   //application instanceNULL);                       //window parameters (not used)//was there an error creating the window?if (hWnd == 0) return 0;//display the windowShowWindow(hWnd, nCmdShow);UpdateWindow(hWnd);return 1;
}// Entry point for a Windows program
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow)
{...//WinMain took this return value very seriously. If InitInstance doesn’t like something that is going on, WinMain will end the programif (!InitInstance(hInstance, nCmdShow)) return 0;...
}

The hWnd value is passed to these functions by the CreateWindow function. At the point of creation, the window existed in Windows but was not yet visible. UpdateWindow tells the new window to draw itself by sending a WM_PAINT message to the window handler.

Oddly enough, the program talks to itself quite often in this manner; this is common in Windows programming.

理解MyRegisterClass

MyRegisterClass is a very simple function that sets up the values for the main window class used by your program.

WinMain calls InitInstance and sets up the program window by calling MyRegisterClass.

MyRegisterClass函数调用

ATOM MyRegisterClass( HINSTANCE hInstance )

hInstance is the very same instance passed to InitInstance by WinMain. This variable gets around!

As you recall, hInstance stores the current instance of the running program, and is copied into a global variable in InitInstance.

The ATOM data type returned by MyRegisterClass is defined as a WORD, which is further defined as an unsigned short in one of the Windows header files.

MyRegisterClass的作用

Each member of the structure is defined in MyRegisterClass in order, so there is no need to list the struct.

These properties are pretty standard fare for a Windows program. The reason why we aren’t overly concerned is because we will replace the window when DirectX takes over.

So, who cares what special properties the window uses when it’s destined to be soon overrun with rendered output? But, at this early stage, it’s important to cover all the bases.

// Helper function to set up the window properties
ATOM MyRegisterClass(HINSTANCE hInstance)
{//set the new window's propertiesWNDCLASSEX wc;wc.cbSize = sizeof(WNDCLASSEX);/*The window style, wc.style, is set to CS_HREDRAW | CS_VREDRAW. The pipe symbol is a method for combining bits. - The CS_HREDRAW value causes the program window to be completely redrawn if a movement or size adjustment changes the width. - Likewise, CS_VREDRAW causes the window to be completely redrawn when the height is adjusted.*/wc.style = CS_HREDRAW | CS_VREDRAW;/*这参数很重要The variable, wc.lpfnWinProc, requires a little more explanation, as it is not simply a variable, but a pointer to a callback function. **This is of great importance, as without this value setting, messages will not be delivered to the program window (hWnd).** The callback window procedure is automatically called when a Windows message comes along with that hWnd value. This applies to all messages, including user input and window repaint. Any button presses, screen updates, or other events will go through this callback procedure. You may give this function any name you like, such as BigBadGameWindowProc, as long as it has a return value of LRESULT CALLBACK and the appropriate parameters.*/wc.lpfnWndProc = (WNDPROC)WinProc;/*The struct variables wc.cbClsExtra and wc.cbWndExtra should be set to zero most of the time. These values just add extra bytes of memory to a window instance, and you really do not need to use them.*/wc.cbClsExtra = 0;wc.cbWndExtra = 0;/*wc.hInstance is set to the hInstance parameter passed to MyRegisterClass. The main window needs to know what instance it is using. If you really want to confuse your program, set each new instance to point to the same program window. Now that would be funny! This should never happen because new instances of your game should be killed rather than being allowed to run.*/wc.hInstance = hInstance;/*wc.hIcon and wc.hCursor are pretty self-explanatory易懂. The LoadIcon function is normally used to load an icon image from a resource, and the MAKEINTRESOURCE macro returns a string value for the resource identifier. This macro is not something that is commonly used for a game (unless the game needs to run in a window).*/wc.hIcon = NULL;wc.hCursor = LoadCursor(NULL, IDC_ARROW);/*wc.hbrBackground is set to the handle for a brush used for drawing the background of the program window. The stock object, WHITE_BRUSH, is used by default. This may be a bitmap image, a custom brush, or any other color.*/wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);/*wc.lpszMenuName is set to the name of the program menu, also a resource. I will not be using menus in the sample programs in this book.*/wc.lpszMenuName = NULL;/*wc.lpszClassName  gives the window a specific class name and is used for message handling, along with hWnd. This can also be hard coded to a string value.*/wc.lpszClassName = ProgramTitle.c_str();wc.hIconSm = NULL;/*MyRegisterClass calls the RegisterClassEx function. This function is passed the WNDCLASS variable, wc, that was set up with the window details. A return value of zero indicates failure. If the window is successfully registered with Windows, the value will be passed back to InitInstance.*/return RegisterClassEx(&wc);
}

揭露WinProc的秘密

WinProc is the window callback procedure that Windows uses to communicate events to
your program. A callback function is a function that gets called back.

Recall回想 that MyRegisterClass set up the WNDCLASS struct that was passed to RegisterClassEx. Once the class is registered, the window can then be created and displayed on the screen.

One of the fields in the struct, lpfnWinProc, is set to the name of a window callback procedure, typically called WinProc. This function will handle all of the messages sent to the main program window. As a result, WinProc will typically be the longest function in the main program source code file.

shows how WinProc handles event messages

WinProc函数调用

LRESULT CALLBACK WinProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

You will want to get to know this function, because it is the key to initializing Direct3D.

The parameters are straightforward and represent the real “engine” of a windows program. Recall that this information was retrieved earlier by the GetMessage function in
WinMain.

Do not confuse InitInstance with WinProc, though. InitInstance is only run once to set the options, after which WinProc takes over, receiving and handling messages.

  • HWND hWnd. The first parameter is the window handle. Typically in a game, you will
    create a new handle to a device context, known as an hDC, using the hWnd as a
    parameter. Before DirectX came along, the window handle had to be retained
    because it was used any time a window or control was referenced. In a DirectX
    program, the window handle is used initially to create the window.
  • UINT message. The second parameter is the message that is being sent to the window
    callback procedure. The message could be anything, and you might not even need to use it. For this reason, there is a way to pass the message along to the default message
    handler.
  • n WPARAM wParam and LPARAM lParam. The last two parameters are value parameters
    passed along with certain command messages. I’ll explain this in the next section.

WinProc的大秘密

// The window event callback function
LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{RECT drawRect;/*The PAINTSTRUCT variable, ps, is used in the WM_PAINT message handler to start and stop a screen update, sort of like unlocking and then locking the device context while making updates (so the screen is not garbled in the process). */PAINTSTRUCT ps;/*The variable, hdc, is also used in the WM_PAINT message handler to retrieve the device context of the program’s window.*/HDC hdc;switch (message){/*When you take the next step and start writing Direct3D code, **this will be the only message of concern**, as WM_PAINT is not needed in a Direct3D program.*/case WM_PAINT:{/*The BeginPaint function is called to lock the device context for an update (using the window handle and PAINTSTRUCT variables). BeginPaint returns the device context for the program window. This is necessary at every refresh because, although it is uncommon, the device context is not guaranteed to be constant while the program is running. (For instance, imagine that memory runs low and your program is filed away into virtual memory and then retrieved again—such an event would almost certainly generate a new device context.)*/hdc = BeginPaint(hWnd, &ps); //start drawing /*In the for loop, a rectangle object (of type RECT) called drawRect is set to draw a message on the window from the upper left down toward the bottom in a stair-like manner. */for (int n = 0; n < 20; n++){int x = n * 20;int y = n * 20;drawRect = { x, y, x + 100, y + 20 };/*DrawText prints text at the destination device context. The DT_CENTER parameter at the end tells DrawText to center the message at the top center of the passed rectangle.*/DrawText(hdc, ProgramTitle.c_str(), ProgramTitle.length(), &drawRect, DT_CENTER);}/*The last line of the paint message handler calls EndPaint to shut down the graphics system for that iteration of the message handler.*/EndPaint(hWnd, &ps); //stop drawing}break;/*The WM_DESTROY message identifier tells the window that it is time to shut down; your program should gracefully close down by removing objects from memory and then call the PostQuitMessage function to end the program. */case WM_DESTROY:PostQuitMessage(0);break;}return DefWindowProc(hWnd, message, wParam, lParam);
}

WM_PAINT is not called continuously, as in a real-time loop, but only when the window must be redrawn重画. Therefore, WM_PAINT is not a suitable place to insert the screen refresh code for a game.

什么是游戏循环

There’s a lot more to Windows programming than we will cover in these pages but we’refocusing on just the limited code needed to get DirectX going. A real Windows application would have a menu, a status bar, a toolbar, and dialogs—which is why your average Windows programming book tends to be so long. I want to focus on game creation rather than spending many pages on the logistics of the operating system. What I’d really like to do is get away from the Windows code and come up with just a simple, run-of-the-mill main function, which is standard in C++ programs (but which is missing from Windows programs, which use winMain).

One way to do this is to put all of the basic Windows code (including winMain) inside one source code file (such as winmain.cpp) and then use another source code file (such as game.cpp) just for the game. Then, it would be a simple matter to call some sort of mainfunction from within winMain, and your “game code” would start running right after the program window is created. This is actually a standard practice on many systems and libraries,abstracting away the operating system and presenting the programmer with a standard interface.

The Old WinMain

There’s just one problem with this version of WinMain: It doesn’t have a continuous loop, just a limited loop that processes any pending messages and then exits.

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow)
{//create the windowMyRegisterClass(hInstance);if (!InitInstance(hInstance, nCmdShow)) return 0;// main message loopMSG msg;while (GetMessage(&msg, NULL, 0, 0)){TranslateMessage(&msg);DispatchMessage(&msg);}return msg.wParam;
}

an illustration of the current WinMain

对持续性的需要

When you have 2D sprites or 3D models being rendered, with enemy characters moving around and with guns and explosions in the background, you need things to keep moving regardless of Windows messages! In short, listed above is a stodgy, inanimate version of WinMain that is totally unsuitable for a game. You need something that keeps on running regardless of whether there are event messages coming in.

The key to creating a real-time loop that keeps running all of the time regardless of what Windows is doing is modifying the while loop in WinMain. First of all, the while loop is conditional upon a message being present, while a game should keep running through the loop regardless of whether there’s a message. This definitely needs to be changed!

实时终止器

Notice how the main loop terminates if there are no messages, but will keep on processing any messages that are present.

What would happen if the main game loop were called from this version of WinMain? Well, once in a while the game loop would execute and things would be updated on the screen, but more often it would do nothing at all.

Why is that? Because this is an event-driven while loop, and we need a common, runof-the-mill procedural while loop that keeps going, and going, and going…regardless of what’s happening. A real-time game loop has to keep running non-stop until the game ends.


The Figure shows a new version of WinMain with a real-time game loop that doesn’t just loop through the events but keeps on going regardless of the events (such as mouse movement and key presses).

WinMain和循环

The key to making a real-time loop is modifying the while loop in WinMain so that it runs indefinitely, and then checking for messages inside the while loop. By indefinitely, I mean that the loop will keep running forever unless something interrupts the loop and causes it to exit (by calling exit or return inside the loop).

In addition to using an endless loop, there’s an alternative to calling the GetMessage function to detect event messages coming in. The alternate function is called PeekMessage. As the name implies, this function can look at incoming messages without necessarily retrieving them out of the message queue.(两者区别)

Now, as you don’t want the message queue to fill up (wasting memory), you can use PeekMessage in place of GetMessage, regardless of whether there are messages. If there are messages, fine, go ahead and process them. Otherwise, just return control to the next line of code. As it turns out,

  • GetMessage is not very polite and doesn’t let us keep the game loop going unless a message is actually sitting in the message queue to be processed.

  • PeekMessage, on the other hand, is polite and will just pass control on to the next statement if no message is waiting.

(MyNote:PeekMessage比较优雅,省内存。)

PeekMessage函数接口

BOOL PeekMessage(LPMSG lpMsg, //pointer to message structHWND hWnd, //window handleUINT wMsgFilterMin, //first messageUINT wMsgFilterMax, //last messageUINT wRemoveMsg); //removal flag
  • LPMSG lpMsg. This parameter is a pointer to the message structure that describes the
    message (type, parameters, and so on).
  • HWND hWnd. This is a handle to the window that is associated with the event.
  • UINT wMsgFilterMin. This is the first message that has been received.
  • UINT wMsgFilterMax. This is the last message that has been received.
  • UINT wRemoveMsg. This is a flag that determines how the message will be handled after
    it has been read.
    • PM_NOREMOVE: to leave the message in the message queue,
    • PM_REMOVE: to remove the message from the queue after it has been read.

将PeekMessage插到WinMain中

bool gameover = false;
while (!gameover)
{if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){//handle any event messagesTranslateMessage(&msg);DispatchMessage(&msg);}//process game loop (this is new)Game_Run();
}

you’ll notice that PeekMessage is now called instead of GetMessage, and you’ll recognize the PM_REMOVE parameter, which causes any event messages to be pulled out of the queue and processed.

In actuality, there are really no messages coming into a DirectX program (except perhaps WM_QUIT) because most of the processing takes place in the DirectX libraries.

在此基础上添加游戏逻辑: Game_Init, Game_Run, and Game_End.

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow)
{MSG msg;MyRegisterClass(hInstance);if (!InitInstance(hInstance, nCmdShow)) return 0;//initialize the game <-----------------------------关注点1if (!Game_Init()) return 0;// main message loop bool gameover = false;while (!gameover){if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){//decode and pass messages on to WndProcTranslateMessage(&msg);DispatchMessage(&msg);}//process game loop <-----------------------------关注点2Game_Run();}//do cleanup <-----------------------------关注点3Game_End();//end programreturn msg.wParam;
}

状态驱动的游戏

A frequent subject of debate among game programmers involves how to design a state system. Some argue that a game should be state-driven from the start, and all function calls should be abstracted in the extreme so that code is portable to other platforms.

For instance, some people write code wherein all the Windows code is hidden away, and they’ll then have a similar Mac or Linux version, at which point it’s possible to port much of the game to those platforms without too much difficulty. (MyNote:设计原则:将变的抽取,将不变的保留。)

It’s such a good habit to develop! Even while being stressed out over getting a game finished and pounding out code for 16 hours at a time, if you are a true professional, you’ll manage that while also sparing节约 some neurons for higher-level things, such as writing clean code.

(MyNote:写干净代码,是不错的目标。)

GameLoop项目

参考第1章创建Win32空项目工程。

The code I’ll present here will be the basis for all of the programs that will follow, with only very few changes to come.

创建main.cpp文件,内容代码如下:

#include <windows.h>
#include <iostream>
#include <time.h>
using namespace std;const string APPTITLE = "Game Loop";
HWND window;
HDC device;
bool gameover = false;// Loads and draws a bitmap from a file and then frees the memory
// (not really suitable for a game loop but it's self contained)
void DrawBitmap(char *filename, int x, int y)
{//load the bitmap imageHBITMAP image = (HBITMAP)LoadImage(0, filename, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);//read the bitmap's propertiesBITMAP bm;GetObject(image, sizeof(BITMAP), &bm);//create a device context for the bitmapHDC hdcImage = CreateCompatibleDC(device);SelectObject(hdcImage, image);//draw the bitmap to the window (bit block transfer)BitBlt(device,                  //destination device contextx, y,                    //x,y positionbm.bmWidth, bm.bmHeight, //size of source bitmaphdcImage,                //source device context0, 0,                    //upper-left source positionSRCCOPY);                //blit method//delete the device context and bitmapDeleteDC(hdcImage);DeleteObject((HBITMAP)image);
}// Startup and loading code goes here
bool Game_Init()
{//start up the random number generatorsrand(time(NULL));return 1;
}// Update function called from inside game loop
void Game_Run()
{if (gameover == true) return;//get the drawing surfaceRECT rect;GetClientRect(window, &rect);//draw bitmap at random locationint x = rand() % (rect.right - rect.left);int y = rand() % (rect.bottom - rect.top);DrawBitmap("c.bmp", x, y);
}// Shutdown code
void Game_End()
{//free the deviceReleaseDC(window, device);
}// Window callback function
LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{switch (message){case WM_DESTROY:gameover = true;PostQuitMessage(0);break;}return DefWindowProc(hWnd, message, wParam, lParam);
}// MyRegiserClass function sets program window properties
ATOM MyRegisterClass(HINSTANCE hInstance)
{//create the window class structureWNDCLASSEX wc;wc.cbSize = sizeof(WNDCLASSEX);//fill the struct with infowc.style = CS_HREDRAW | CS_VREDRAW;wc.lpfnWndProc = (WNDPROC)WinProc;wc.cbClsExtra = 0;wc.cbWndExtra = 0;wc.hInstance = hInstance;wc.hIcon = NULL;wc.hCursor = LoadCursor(NULL, IDC_ARROW);wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);wc.lpszMenuName = NULL;wc.lpszClassName = APPTITLE.c_str();wc.hIconSm = NULL;//set up the window with the class inforeturn RegisterClassEx(&wc);
}// Helper function to create the window and refresh it
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{//create a new windowwindow = CreateWindow(APPTITLE.c_str(),              //window classAPPTITLE.c_str(),              //title barWS_OVERLAPPEDWINDOW,   //window styleCW_USEDEFAULT,         //x position of windowCW_USEDEFAULT,         //y position of window640,                   //width of the window480,                   //height of the windowNULL,                  //parent windowNULL,                  //menuhInstance,             //application instanceNULL);                 //window parameters//was there an error creating the window?if (window == 0) return 0;//display the windowShowWindow(window, nCmdShow);UpdateWindow(window);//get device context for drawingdevice = GetDC(window);return 1;
}// Entry point function
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{MSG msg;//create windowMyRegisterClass(hInstance);if (!InitInstance(hInstance, nCmdShow)) return 0;//initialize the gameif (!Game_Init()) return 0;// main message loopwhile (!gameover){//process Windows eventsif (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){TranslateMessage(&msg);DispatchMessage(&msg);}//process game loopGame_Run();}//free game resourcesGame_End();return msg.wParam;
}

在Windows中绘制位图

The DrawBitmap function in this demo draws a bitmap. The function loads a bitmap file into memory, and then draws it at a random location on the window (using the window’s device context).

Be sure to copy the c.bmp file into the project folder (where the .vcxproj file is located) for this program.

You would never want to do this in a real game, because it loads the darned bitmap file every single time it goes through the loop! That’s insanely slow and wasteful, but it is okay for demonstration purposes

// Loads and draws a bitmap from a file and then frees the memory
// (not really suitable for a game loop but it's self contained)
void DrawBitmap(char *filename, int x, int y)
{//load the bitmap imageHBITMAP image = (HBITMAP)LoadImage(0, filename, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);//read the bitmap's propertiesBITMAP bm;GetObject(image, sizeof(BITMAP), &bm);//create a device context for the bitmapHDC hdcImage = CreateCompatibleDC(device);SelectObject(hdcImage, image);//draw the bitmap to the window (bit block transfer)BitBlt(device,                  //destination device contextx, y,                    //x,y positionbm.bmWidth, bm.bmHeight, //size of source bitmaphdcImage,                //source device context0, 0,                    //upper-left source positionSRCCOPY);                //blit method//delete the device context and bitmapDeleteDC(hdcImage);DeleteObject((HBITMAP)image);
}

To draw the bitmap repeatedly, the Game_Run function passes the bitmap filename and a random x,y location (bound within the limits of the window’s width and height) to the DrawBitmap function:

// Update function called from inside game loop
void Game_Run()
{if (gameover == true) return;//get the drawing surfaceRECT rect;GetClientRect(window, &rect);//draw bitmap at random locationint x = rand() % (rect.right - rect.left);int y = rand() % (rect.bottom - rect.top);DrawBitmap("c.bmp", x, y);
}

运行GameLoop程序

You should see a window appear with an image drawn repeatedly in random locations on the window.

运行结果:

The Windows GDI—which is the system that provides you with window handles and device contexts and allows you to draw on windows to build a user interface (or a game that does not use DirectX)—is a step backward, to be blunt坦率.

Keep moving forward, covering only the aspects of Windows coding necessary to provide a footing for DirectX.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/445593.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构课上笔记6

本节课介绍了单链表的操作实现细节&#xff0c;介绍了静态链表。 链表带头的作用&#xff1a;对链表进行操作时&#xff0c;可以对空表、非空表的情况以及 对首元结点进行统一处理&#xff0c;编程更方便。 下面给出带头的单链表实现思路&#xff1a; 按下标查找&#xff1a; …

17校招真题题集(3)11-15

注&#xff1a;本系列题目全是按照通过率降序来排列的&#xff0c;基本保证题目难度递增。 11、 题目名称&#xff1a;买苹果 来源&#xff1a;网易 题目描述 小易去附近的商店买苹果&#xff0c;奸诈的商贩使用了捆绑交易&#xff0c;只提供6个每袋和8个每袋的包装(包装不…

QT5生成.exe文件时,出现缺少QT5core.dll文件解决方法

在 http://qt-project.org/downloads 下载Qt SDK安装需要Qt版本。在QtCreator下&#xff0c;程序可以正常运行&#xff0c;但是当关闭QtCreator后&#xff0c;在DeBug目录下再运行相应的*.exe程序时&#xff0c;会提示缺少Qt5Core.dll错误。解决方法&#xff1a;添加电脑环境变…

《基于Java实现的遗传算法》笔记(7 / 7):个人总结

文章目录为何采用遗传算法哪些问题适合用遗传算法解决遗传算法基本术语一般遗传算法的过程基本遗传算法的伪代码为何采用遗传算法 遗传算法是机器学习的子集。在实践中&#xff0c;遗传算法通常不是用来解决单一的、特定问题的最好算法。对任何一个问题&#xff0c;几乎总有更…

Java设计模式(4 / 23):单例模式

文章目录单例模式的应用场景饿汉式单例模式懒汉式单例模式改进&#xff1a;synchronized改进&#xff1a;双重检查锁改进&#xff1a;静态内部类破坏单例用反射破坏单例用序列化破坏单例解密注册式单例模式枚举式单例模式解密容器式单例线程单例实现ThreadLocal单例模式小结参考…

约瑟夫环-(数组、循环链表、数学)

约瑟夫环&#xff08;约瑟夫问题&#xff09;是一个数学的应用问题&#xff1a;已知n个人&#xff08;以编号1&#xff0c;2&#xff0c;3...n分别表示&#xff09;围坐在一张圆桌周围。从编号为k的人开始报数&#xff0c;数到m的那个人出列&#xff1b;他的下一个人又从1开始报…

链表相交问题

本来想自己写&#xff0c;写了一半发现一篇文章&#xff0c;解释写得简单易懂&#xff0c;我就直接拿过来了。 这个问题值得反复地写&#xff0c;锻炼链表coding能力的好题。 //如果两个链表都不带环 int NotCycleCheckCross(pLinkNode head1,pLinkNode head2) {pLinkNode lis…

双栈

利用栈底位置相对不变的特性&#xff0c;可以让两个顺序栈共享一个空间。 具体实现方法大概有两种&#xff1a; 一种是奇偶栈&#xff0c;就是所有下标为奇数的是一个栈&#xff0c;偶数是另一个栈。但是这样一个栈的最大存储就确定了&#xff0c;并没有起到互补空缺的作用&a…

单调队列优化的背包问题

对于背包问题&#xff0c;经典的背包九讲已经讲的很明白了&#xff0c;本来就不打算写这方面问题了。 但是吧。 我发现&#xff0c;那个最出名的九讲竟然没写队列优化的背包。。。。 那我必须写一下咯嘿嘿&#xff0c;这么好的思想。 我们回顾一下背包问题吧。 01背包问题 …

用Python去除扫描型PDF中的水印

内容概述 含水印扫描型PDF文件&#xff0c;其中某页如下图所示&#xff0c;用Python去除其页顶及页底的水印。 处理思路&#xff1a;PDF中的每一页的水印的相对位置基本相同&#xff0c;将PDF每一页输出成图片&#xff0c;然后进行图片编辑&#xff0c;用白色填充方形覆盖水印…

二阶有源滤波器

滤波器是一种使用信号通过而同时抑制无用频率信号的电子装置, 在信息处理、数据传送和抑制干扰等自动控制、通信及其它电子系统中应用广泛。滤波一般可分为有源滤波和无源滤波, 有源滤波可以使幅频特性比较陡峭, 而无源滤波设计简单易行, 但幅频特性不如滤波器, 而且体积较大。…

用JS写了一个30分钟倒计时器

效果图 额外功能 左键单击计时器数字区&#xff0c;不显示或显示秒钟区。左键双击计时器数字区&#xff0c;暂停或启动计时器。计时完毕&#xff0c;只能刷新页面启动计时器。输入框可输入备注信息&#xff0c;输入框失去焦点或计时完毕后&#xff0c;时间戳附带备注信息会存入…

为什么高手离不了Linux系统?我想这就是理由!

通过本文来记录下我在Linux系统的学习经历&#xff0c;聊聊我为什么离不了Linux系统&#xff0c;同时也为那些想要尝试Linux而又有所顾忌的用户答疑解惑&#xff0c;下面将为你介绍我所喜欢的Linux系统&#xff0c;这里有一些你应该知道并为之自豪的事实。 这里你应该首先抛开W…

用JS写一个电影《黑客帝国》显示屏黑底绿字雨风格的唐诗欣赏器

效果图 放码过来 <!DOCTYPE HTML> <html><head><meta http-equiv"Content-Type" content"text/html;charsetutf-8"/><title>Black Screen And Green Characters</title><style type"text/css">table…

元器件封装大全:图解+文字详述

先图解如下&#xff1a; 元器件封装类型&#xff1a; A.Axial  轴状的封装&#xff08;电阻的封装&#xff09;AGP &#xff08;Accelerate raphical Port&#xff09; 加速图形接口 AMR(Audio/MODEM Riser) 声音/调制解调器插卡BBGA&#xff08;Ball Grid Array&#xff09;…

用JS写一个丐版《2048》小游戏

效果图 放马过来 <!DOCTYPE HTML> <html><head><meta http-equiv"Content-Type" content"text/html;charsetutf-8"/><title>2048</title><style type"text/css">.basic{height:80px;width:80px;back…

如何有效申请TI的免费样片

转自如何有效申请TI的免费样片 TI公司愿意为支持中国大学的师生们的教学、实验、创新实践、竞赛和科研项目&#xff0c;提供有限数量的免费样片。首先需要指出的是&#xff1a;所有的样片申请应该是诚实正当的&#xff0c;所有不恰当的申请&#xff08;包括不必要或多余的&…

用Python批量生成字幕图片用于视频剪辑

说明 视频剪辑时需要为视频添加字幕&#xff0c;添加字幕方法之一&#xff1a;根据字幕文本文件批量生成透明底只有字幕内容的图片文件&#xff0c;如下图&#xff0c;然后将这些图片文件添加到视频剪辑软件轨道中。 于是用pillow这Python图片工具库执行本次批量生成工作。 …

关于接地:数字地、模拟地、信号地、交流地、直流地、屏蔽地、浮

除了正确进行接地设计、安装,还要正确进行各种不同信号的接地处理。控制系统中&#xff0c;大致有以下几种地线&#xff1a; &#xff08;1&#xff09;数字地&#xff1a;也叫逻辑地&#xff0c;是各种开关量&#xff08;数字量&#xff09;信号的零电位。 &#xff08;2&…

AltiumDesigner中PCB如何添加 Logo

AltiumDesigner中PCB如何添加 Logo 转载2015-10-29 00:07:55标签&#xff1a;it文化教育首先用到的画图软件&#xff0c;当然是大家熟悉的Altium Designer了&#xff0c;呵呵&#xff0c;相信很多人都用过这款画图软件吧&#xff08;现在电路设计一直在用&#xff09;&#xff…