【译】TetroGL: An OpenGL Game Tutorial in C++ for Win32 Platforms - Part 1

简介:
原文链接:TetroGL: An OpenGL Game Tutorial in C++ for Win32 Platforms - Part 1 
      
这个系列专注于使用C++和OpenGL在windows平台上开发2D游戏,项目目标是在系列结束后能开发出一个类似俄罗斯方块的游戏。本系列分为3篇文章:

第一部分:涉及win32消息循环,窗口创建和OpenGL的搭建,并且你将会学习如何绘制一些简单的图形。

第二部分:涉及资源处理和简单动画的显示

第三部分:将前面的内容包含进来,并且讨论游戏逻辑。

项目设置

作者做了两项设置:

1) LinkeràInput中,在” Addition Dependencies”中加入”opengl32.lib”,


2) 禁掉UNICODE,“c/c++”à” Preprocessor”,在Preprocessor Definitions中将"Inherit from parent or project defaults"不选中。


消息循环

      Windows系统为每个应用程序创建一个消息队列,当指定应用程序的窗口上发生一个事件时,系统会把消息推入到这个队列中。你的应用程序应当检索并处理这些消息。这就是所谓的“消息循环“,是win32应用程序的核心。

      一个典型的消息循环如下:

复制代码
    MSG Message;
    Message.message = (~WM_QUIT);
    // Loop until a WM_QUIT message is received
    while (Message.message != WM_QUIT)
    {
        if (PeekMessage(&Message, NULL, 0, 0, PM_REMOVE))
        {
            // If a message was waiting in the message queue, process it
            TranslateMessage(&Message);
            DispatchMessage(&Message);
        }
        else
        {//空闲时
            // Do processing stuff here
        }
}

复制代码
  TranslateMessage函数的目的是为了将虚键消息(WM_KEYDOWN和WM_KEYUP)为字符消息(WM_CHAR).最后DispatchMessage会将消息定向到正确的窗口处理程序。

复制代码
// The application class, which simply wraps the message queue and process
// the command line.
class CApplication
{
public:
    CApplication(){}
    CApplication(HINSTANCE hInstance);
    ~CApplication();

    // Parses the command line to see if the application
    // should be in fullscreen mode.
    void ParseCmdLine(LPSTR lpCmdLine);
    // Creates the main window and start the message loop.
    void Run();
    void SetHInst(HINSTANCE hInstance);

private:
    HINSTANCE m_hInstance;
    // Specifies if the application has to be started in fullscreen
    // mode. This option is supplied through the command line
    // ("-fullscreen" option).
    bool m_bFullScreen;
};

复制代码
  ParseCmdLine函数功能非常直观:仅仅简单地检查命令行中是否有参数"-fullscreen",如果有,则设置m_bFullScreen为true,表示窗口模式为全屏模式。

复制代码
void CApplication::Run()
{
    // Create the main window first
    CMainWindow mainWindow(800,600,m_bFullScreen);

    MSG Message;
    Message.message = ~WM_QUIT;
    DWORD dwNextDeadLine = GetTickCount() + 30;
    DWORD dwSleep = 30;
    bool bUpdate = false;
    // Loop until a WM_QUIT message is received
    while (Message.message != WM_QUIT)
    {
        // Wait until a message comes in or until the timeout expires. The
        // timeout is recalculated so that this function will return at
        // least every 30 msec.
        DWORD dwResult = MsgWaitForMultipleObjectsEx(0,NULL,dwSleep,QS_ALLEVENTS,0);
        if (dwResult != WAIT_TIMEOUT)
        {
            // If the function returned with no timeout, it means that a 
            // message has been received, so process it.
            if (PeekMessage(&Message, NULL, 0, 0, PM_REMOVE))
            {
                // If a message was waiting in the message queue, process it
                TranslateMessage(&Message);
                DispatchMessage(&Message);
            }

            // If the current time is close (or past) to the 
            // deadline, the application should be processed.
            if (GetTickCount() >= dwNextDeadLine-1)
                bUpdate = true;
        }
        else
            // On a timeout, the application should be processed.
            bUpdate = true;

        // Check if the application should be processed
        if (bUpdate)
        {
            DWORD dwCurrentTime = GetTickCount();
            // Update the main window
            mainWindow.Update(dwCurrentTime);
            // Draw the main window
            mainWindow.Draw();

            dwNextDeadLine = dwNextDeadLine + 30;
            dwSleep = 30;
        }
        else
            dwSleep =  dwNextDeadLine - GetCurrentTime();
    }
}

复制代码
  函数第一行创建主窗口。和常见的消息循环不同,由于在2D游戏中并不需要很快地刷新屏幕,以固定地速率(这里是30毫秒)刷新,对于绘制动画和其他处理已经足够了。

作者在这里使用的技巧就是出于这个目的,他使用了MsgWaitForMultipleObjectsEx函数来等待任何事件的发生,再判断是30毫秒的时限已到,还是有事件要处理,若是后者,则先处理事件,在处理完后,若此时也已经接近30毫秒的时限的话,就重绘界面,若是超时发生了,则说明30毫秒时限已到,需要去刷新界面了。若这两种情况都没有的话,这个函数不会消耗 CPU周期,线程只是被挂起,不参与调度。

主窗口

创建窗口

复制代码
    CMainWindow::CMainWindow(int iWidth, int iHeight, bool bFullScreen) 
  :  m_hWindow(NULL), m_hDeviceContext(NULL), m_hGLContext(NULL), 
     m_bFullScreen(bFullScreen)
{
    RegisterWindowClass();

    RECT WindowRect;
    WindowRect.top = WindowRect.left = 0;
    WindowRect.right = iWidth;
    WindowRect.bottom = iHeight;

    // Window Extended Style
    DWORD dwExStyle = 0;    
    // Windows Style
    DWORD dwStyle = 0;        

    if (m_bFullScreen)
    {
        DEVMODE dmScreenSettings;
        memset(&dmScreenSettings,0,sizeof(dmScreenSettings));    
        dmScreenSettings.dmSize = sizeof(dmScreenSettings);        
        dmScreenSettings.dmPelsWidth    = iWidth;            
        dmScreenSettings.dmPelsHeight    = iHeight;        
        dmScreenSettings.dmBitsPerPel    = 32;        
        dmScreenSettings.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;

        // Change the display settings to fullscreen. On error, throw 
        // an exception.
        if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)
                != DISP_CHANGE_SUCCESSFUL)
        {
            throw CException("Unable to swith to fullscreen mode");
        }

        dwExStyle = WS_EX_APPWINDOW;    
        dwStyle = WS_POPUP;        
        // In fullscreen mode, we hide the cursor.
        ShowCursor(FALSE);
    }
    else
    {
        dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
        dwStyle = WS_OVERLAPPEDWINDOW;    
    }

    // Adjust the window to the true requested size
    AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);        
    // Now create the main window
    m_hWindow = CreateWindowEx(dwExStyle,TEXT(WINDOW_CLASSNAME), 
                             TEXT("Tutorial1"), 
                             WS_CLIPSIBLINGS | WS_CLIPCHILDREN | dwStyle,
                             0, 0, WindowRect.right-WindowRect.left, 
                             WindowRect.bottom-WindowRect.top, 
                             NULL, NULL, 
                             GetModuleHandle(NULL), 
                             this);
    if (m_hWindow==NULL)
        throw CException("Cannot create the main window");

    CreateContext();
    InitGL();
    ShowWindow(m_hWindow,SW_SHOW);
    // Call OnSize manually because in fullscreen mode it will be 
    // called only when the window is created (which is too early
    // because OpenGL is not initialized yet).
    OnSize(iWidth,iHeight);
}

复制代码
    在构造函数中,检查完是否需要进入全屏模式后,通过调用ChangeDisplaySettings来切换到全屏模式,然后调用AdjustWindowRectEx来调整矩形的大小,但这个函数在全屏模式下没什么作用,最后CreateContext和InitGL对OpenGL进行初始化。

复制代码
LRESULT CMainWindow::OnEvent(HWND Handle, UINT Message, WPARAM wParam, LPARAM lParam)
{
    if (Message == WM_CREATE)
    {
        // Get the creation parameters.
        CREATESTRUCT* pCreateStruct = reinterpret_cast<CREATESTRUCT*>(lParam);

        // Set as the "user data" parameter of the window
        SetWindowLongPtr(Handle, GWLP_USERDATA, 
                         reinterpret_cast<long>(pCreateStruct->lpCreateParams));
    }

    // Get the CMainWindow instance corresponding to the window handle
    CMainWindow* pWindow = reinterpret_cast<CMainWindow*>
        (GetWindowLongPtr(Handle, GWLP_USERDATA));
    if (pWindow)
        pWindow->ProcessEvent(Message,wParam,lParam);

    return DefWindowProc(Handle, Message, wParam, lParam);
}

复制代码
由于OnEvent函数是静态的,因此就没法访问非静态成员,为了解决这个问题,在调用CreateWindowEx创建窗口时最后一个参数传的是this指针。传给窗口处理过程的第一个消息是WM_CREATE,当接收到这个消息时,wParam参数中包含一个CREATESTRUCT指针,它里面包含了额外的数据,在这里是指向CMainWindow的指针。但遗憾的是,不是每个消息都有这个结构,所以要保存起来供后面使用。因此调用SetWindowLongPtr,它的目的是为一个特定的窗口保存一些用户数据(GWLP_USERDATA)。在这里,作者保存了到类实例的指针。当接收到其他消息时,我们只是简单地通过GetWindowLongPtr来取回这个指针,然后访问非静态函数ProcessEvent,而这个函数负责具体的消息处理。

复制代码
void CMainWindow::ProcessEvent(UINT Message, WPARAM wParam, LPARAM lParam)
{
    switch (Message)
    {
        // Quit when we close the main window
        case WM_CLOSE :
            PostQuitMessage(0);
            break;
        case WM_SIZE:
            OnSize(LOWORD(lParam),HIWORD(lParam));
            break;
        case WM_KEYDOWN :
            break;
        case WM_KEYUP :
            break;
    }
}

复制代码
  异常处理

复制代码
    class CException : public std::exception
{
public:
    const char* what() const  { return m_strMessage.c_str(); }

    CException(const std::string& strMessage="") : m_strMessage(strMessage)  { }
    virtual ~CException()  { }

    std::string m_strMessage;
};

复制代码
使用的实例:

复制代码
CApplication theApp;

int WINAPI WinMain(HINSTANCE Instance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT)
{
    try
    {
        // Create the application class, 
        // parse the command line and
        // start the app.
        theApp.SetHInst(Instance);
        theApp.ParseCmdLine(lpCmdLine);
        theApp.Run();
    }
    catch(CException& e)
    {
        MessageBox(NULL,e.what(),"Error",MB_OK|MB_ICONEXCLAMATION);
    }

    return 0;
}

复制代码
初始化OpenGL
      CreateContext用来初始化绘制上下文,使得OpenGL基本元素可以在窗口上绘制:

复制代码
void CMainWindow::CreateContext()
{
    // Describes the pixel format of the drawing surface
    PIXELFORMATDESCRIPTOR pfd;
    memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
    pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
    pfd.nVersion = 1;    // Version Number
    pfd.dwFlags = PFD_DRAW_TO_WINDOW |    // Draws to a window
                  PFD_SUPPORT_OPENGL |    // The format must support OpenGL
                  PFD_DOUBLEBUFFER;        // Support for double buffering
    pfd.iPixelType = PFD_TYPE_RGBA;        // Uses an RGBA pixel format
    pfd.cColorBits = 32;                // 32 bits colors

    if (!(m_hDeviceContext=GetDC(m_hWindow)))    
        throw CException("Unable to create rendering context");

    int PixelFormat;
    // Do Windows find a matching pixel format ?
    if (!(PixelFormat=ChoosePixelFormat(m_hDeviceContext,&pfd)))                
        throw CException("Unable to create rendering context");
    // Set the new pixel format
    if(!SetPixelFormat(m_hDeviceContext,PixelFormat,&pfd))            
        throw CException("Unable to create rendering context");
    // Create the OpenGL rendering context
    if (!(m_hGLContext=wglCreateContext(m_hDeviceContext)))    
        throw CException("Unable to create rendering context");
    // Activate the rendering context
    if(!wglMakeCurrent(m_hDeviceContext,m_hGLContext))
        throw CException("Unable to create rendering context");                        
}

复制代码
    函数的第一部分填充PIXELFORMATDESCRIPTOR结构体:缓冲区用来绘制到窗口上,支持OpenGL,使用双缓存(为了避免闪烁)。然后调用ChoosePixelFormat来检查像素格式是否支持,这个函数返回一个像素格式索引(若没有找到匹配的,则返回0)。一旦存在这样的像素格式索引,则通过SetPixelFormat来设置新像素格式。然后调用wglCreateContext创建OpenGL绘制上下文,最后,调用wglMakeCurrent,这指明线程接下来的OpenGL调用会绘制在这个设备上下文上。

      InitGL函数很简单:

复制代码
void CMainWindow::InitGL()
{
    // Enable 2D texturing
    glEnable(GL_TEXTURE_2D);
    // Choose a smooth shading model
    glShadeModel(GL_SMOOTH);
    // Set the clear color to black
    glClearColor(0.0, 0.0, 0.0, 0.0);
}

复制代码
再看OnSize函数

复制代码
void CMainWindow::OnSize(GLsizei width, GLsizei height)
{
    // Sets the size of the OpenGL viewport
    glViewport(0,0,width,height);

    // Select the projection stack and apply 
    // an orthographic projection
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0.0,width,height,0.0,-1.0,1.0);
    glMatrixMode(GL_MODELVIEW);
}

复制代码
正交投影
 

透视投影


 
  绘制简单图形
 复制代码
void CMainWindow::Draw()
{
    // Clear the buffer
    glClear(GL_COLOR_BUFFER_BIT);

    // Here goes the drawing code
    glBegin(GL_QUADS);
        glColor3f(1.0,0.0,0.0);        glVertex3i(50,200,0);
        glColor3f(0.0,1.0,0.0);        glVertex3i(250,200,0);
        glColor3f(0.0,0.0,1.0);        glVertex3i(250,350,0);
        glColor3f(1.0,1.0,1.0);        glVertex3i(50,350,0);
    glEnd();

    glBegin(GL_TRIANGLES);
        glColor3f(1.0,0.0,0.0);  glVertex3i(400,350,0);
        glColor3f(0.0,1.0,0.0);  glVertex3i(500,200,0);
        glColor3f(0.0,0.0,1.0);  glVertex3i(600,350,0);
    glEnd();

    SwapBuffers(m_hDeviceContext);
}

复制代码



本文转自Phinecos(洞庭散人)博客园博客,原文链接:http://www.cnblogs.com/phinecos/archive/2008/06/26/1230692.html,如需转载请自行联系原作者
目录
相关文章
|
7月前
|
存储 编解码 监控
C++与OpenGL结合实现的屏幕监控软件:高性能屏幕录制解决方案
使用C++和OpenGL结合,本文展示了如何创建高性能屏幕录制软件。通过OpenGL的`glReadPixels`获取屏幕图像,存储为视频文件,再利用C++的网络编程(如libcurl)将监控数据提交到网站。示例代码包括了屏幕捕获和数据上传的简单实现。
302 2
|
C++ 计算机视觉 Python
C++ VS OpenGL绘制教室三维立体旋转图像
C++ VS OpenGL绘制教室三维立体旋转图像
180 0
C++ VS OpenGL绘制教室三维立体旋转图像
|
C++ Windows
【OpenGL】一、Visual Studio 2019 创建 Windows 桌面程序 ( Visual Studio Installer 安装 C++ 桌面开发库 | 创建桌面程序 )
【OpenGL】一、Visual Studio 2019 创建 Windows 桌面程序 ( Visual Studio Installer 安装 C++ 桌面开发库 | 创建桌面程序 )
612 0
【OpenGL】一、Visual Studio 2019 创建 Windows 桌面程序 ( Visual Studio Installer 安装 C++ 桌面开发库 | 创建桌面程序 )
|
传感器 C++ Go
Draw the RGB data from kinect C++ via opengl
<p>In order to improve my English writing skills,I am going to  write the blogs in English form now!</p> <p>---------------------------------------------------------------------------------------
1769 0
|
C++ Windows
visual c++6.0安装与配置opengl
一、经典问题 没有配置opengl的vc++工程,直接调用这些相关头文件就会报错。 Cannot open include file: 'gl/glut.h': No such file or directory   二、解决过程   1、下载并安装glut库      opengl的glut库 GLUT不是OpenGL所必须的,但它会给我们的学习带来一定的方便,推荐安装。
1330 0
|
1月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
51 2