原文链接: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应用程序的核心。
一个典型的消息循环如下:
MSGMessage;
Message.message=(~WM_QUIT);
//LoopuntilaWM_QUITmessageisreceived
while(Message.message!=WM_QUIT)
{
if(PeekMessage(&Message,NULL,0,0,PM_REMOVE))
{
//Ifamessagewaswaitinginthemessagequeue,processit
TranslateMessage(&Message);
DispatchMessage(&Message);
}
else
{//空闲时
//Doprocessingstuffhere
}
}
TranslateMessage函数的目的是为了将虚键消息(WM_KEYDOWN和WM_KEYUP)为字符消息(WM_CHAR).最后DispatchMessage会将消息定向到正确的窗口处理程序。
//Theapplicationclass,whichsimplywrapsthemessagequeueandprocess
//thecommandline.
classCApplication
{
public:
CApplication(){}
CApplication(HINSTANCEhInstance);
~CApplication();
//Parsesthecommandlinetoseeiftheapplication
//shouldbeinfullscreenmode.
voidParseCmdLine(LPSTRlpCmdLine);
//Createsthemainwindowandstartthemessageloop.
voidRun();
voidSetHInst(HINSTANCEhInstance);
private:
HINSTANCEm_hInstance;
//Specifiesiftheapplicationhastobestartedinfullscreen
//mode.Thisoptionissuppliedthroughthecommandline
//("-fullscreen"option).
boolm_bFullScreen;
};
ParseCmdLine函数功能非常直观:仅仅简单地检查命令行中是否有参数"-fullscreen",如果有,则设置m_bFullScreen为true,表示窗口模式为全屏模式。
voidCApplication::Run()
{
//Createthemainwindowfirst
CMainWindowmainWindow(800,600,m_bFullScreen);
MSGMessage;
Message.message=~WM_QUIT;
DWORDdwNextDeadLine=GetTickCount()+30;
DWORDdwSleep=30;
boolbUpdate=false;
//LoopuntilaWM_QUITmessageisreceived
while(Message.message!=WM_QUIT)
{
//Waituntilamessagecomesinoruntilthetimeoutexpires.The
//timeoutisrecalculatedsothatthisfunctionwillreturnat
//leastevery30msec.
DWORDdwResult=MsgWaitForMultipleObjectsEx(0,NULL,dwSleep,QS_ALLEVENTS,0);
if(dwResult!=WAIT_TIMEOUT)
{
//Ifthefunctionreturnedwithnotimeout,itmeansthata
//messagehasbeenreceived,soprocessit.
if(PeekMessage(&Message,NULL,0,0,PM_REMOVE))
{
//Ifamessagewaswaitinginthemessagequeue,processit
TranslateMessage(&Message);
DispatchMessage(&Message);
}
//Ifthecurrenttimeisclose(orpast)tothe
//deadline,theapplicationshouldbeprocessed.
if(GetTickCount()>=dwNextDeadLine-1)
bUpdate=true;
}
else
//Onatimeout,theapplicationshouldbeprocessed.
bUpdate=true;
//Checkiftheapplicationshouldbeprocessed
if(bUpdate)
{
DWORDdwCurrentTime=GetTickCount();
//Updatethemainwindow
mainWindow.Update(dwCurrentTime);
//Drawthemainwindow
mainWindow.Draw();
dwNextDeadLine=dwNextDeadLine+30;
dwSleep=30;
}
else
dwSleep=dwNextDeadLine-GetCurrentTime();
}
}
函数第一行创建主窗口。和常见的消息循环不同,由于在2D游戏中并不需要很快地刷新屏幕,以固定地速率(这里是30毫秒)刷新,对于绘制动画和其他处理已经足够了。
作者在这里使用的技巧就是出于这个目的,他使用了MsgWaitForMultipleObjectsEx函数来等待任何事件的发生,再判断是30毫秒的时限已到,还是有事件要处理,若是后者,则先处理事件,在处理完后,若此时也已经接近30毫秒的时限的话,就重绘界面,若是超时发生了,则说明30毫秒时限已到,需要去刷新界面了。若这两种情况都没有的话,这个函数不会消耗 CPU周期,线程只是被挂起,不参与调度。
主窗口
创建窗口
CMainWindow::CMainWindow(intiWidth,intiHeight,boolbFullScreen)
:m_hWindow(NULL),m_hDeviceContext(NULL),m_hGLContext(NULL),
m_bFullScreen(bFullScreen)
{
RegisterWindowClass();
RECTWindowRect;
WindowRect.top=WindowRect.left=0;
WindowRect.right=iWidth;
WindowRect.bottom=iHeight;
//WindowExtendedStyle
DWORDdwExStyle=0;
//WindowsStyle
DWORDdwStyle=0;
if(m_bFullScreen)
{
DEVMODEdmScreenSettings;
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;
//Changethedisplaysettingstofullscreen.Onerror,throw
//anexception.
if(ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)
!=DISP_CHANGE_SUCCESSFUL)
{
throwCException("Unabletoswithtofullscreenmode");
}
dwExStyle=WS_EX_APPWINDOW;
dwStyle=WS_POPUP;
//Infullscreenmode,wehidethecursor.
ShowCursor(FALSE);
}
else
{
dwExStyle=WS_EX_APPWINDOW|WS_EX_WINDOWEDGE;
dwStyle=WS_OVERLAPPEDWINDOW;
}
//Adjustthewindowtothetruerequestedsize
AdjustWindowRectEx(&WindowRect,dwStyle,FALSE,dwExStyle);
//Nowcreatethemainwindow
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)
throwCException("Cannotcreatethemainwindow");
CreateContext();
InitGL();
ShowWindow(m_hWindow,SW_SHOW);
//CallOnSizemanuallybecauseinfullscreenmodeitwillbe
//calledonlywhenthewindowiscreated(whichistooearly
//becauseOpenGLisnotinitializedyet).
OnSize(iWidth,iHeight);
}
在构造函数中,检查完是否需要进入全屏模式后,通过调用ChangeDisplaySettings来切换到全屏模式,然后调用AdjustWindowRectEx来调整矩形的大小,但这个函数在全屏模式下没什么作用,最后CreateContext和InitGL对OpenGL进行初始化。
LRESULTCMainWindow::OnEvent(HWNDHandle,UINTMessage,WPARAMwParam,LPARAMlParam)
{
if(Message==WM_CREATE)
{
//Getthecreationparameters.
CREATESTRUCT*pCreateStruct=reinterpret_cast<CREATESTRUCT*>(lParam);
//Setasthe"userdata"parameterofthewindow
SetWindowLongPtr(Handle,GWLP_USERDATA,
reinterpret_cast<long>(pCreateStruct->lpCreateParams));
}
//GettheCMainWindowinstancecorrespondingtothewindowhandle
CMainWindow*pWindow=reinterpret_cast<CMainWindow*>
(GetWindowLongPtr(Handle,GWLP_USERDATA));
if(pWindow)
pWindow->ProcessEvent(Message,wParam,lParam);
returnDefWindowProc(Handle,Message,wParam,lParam);
}
由于OnEvent函数是静态的,因此就没法访问非静态成员,为了解决这个问题,在调用CreateWindowEx创建窗口时最后一个参数传的是this指针。传给窗口处理过程的第一个消息是WM_CREATE,当接收到这个消息时,wParam参数中包含一个CREATESTRUCT指针,它里面包含了额外的数据,在这里是指向CMainWindow的指针。但遗憾的是,不是每个消息都有这个结构,所以要保存起来供后面使用。因此调用SetWindowLongPtr,它的目的是为一个特定的窗口保存一些用户数据(GWLP_USERDATA)。在这里,作者保存了到类实例的指针。当接收到其他消息时,我们只是简单地通过GetWindowLongPtr来取回这个指针,然后访问非静态函数ProcessEvent,而这个函数负责具体的消息处理。
voidCMainWindow::ProcessEvent(UINTMessage,WPARAMwParam,LPARAMlParam)
{
switch(Message)
{
//Quitwhenweclosethemainwindow
caseWM_CLOSE:
PostQuitMessage(0);
break;
caseWM_SIZE:
OnSize(LOWORD(lParam),HIWORD(lParam));
break;
caseWM_KEYDOWN:
break;
caseWM_KEYUP:
break;
}
}
异常处理
classCException:publicstd::exception
{
public:
constchar*what()const{returnm_strMessage.c_str();}
CException(conststd::string&strMessage=""):m_strMessage(strMessage){}
virtual~CException(){}
std::stringm_strMessage;
};
使用的实例:
CApplicationtheApp;
intWINAPIWinMain(HINSTANCEInstance,HINSTANCEhPrevInstance,LPSTRlpCmdLine,INT)
{
try
{
//Createtheapplicationclass,
//parsethecommandlineand
//starttheapp.
theApp.SetHInst(Instance);
theApp.ParseCmdLine(lpCmdLine);
theApp.Run();
}
catch(CException&e)
{
MessageBox(NULL,e.what(),"Error",MB_OK|MB_ICONEXCLAMATION);
}
return0;
}
初始化OpenGL
CreateContext用来初始化绘制上下文,使得OpenGL基本元素可以在窗口上绘制:
voidCMainWindow::CreateContext()
{
//Describesthepixelformatofthedrawingsurface
PIXELFORMATDESCRIPTORpfd;
memset(&pfd,0,sizeof(PIXELFORMATDESCRIPTOR));
pfd.nSize=sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion=1;//VersionNumber
pfd.dwFlags=PFD_DRAW_TO_WINDOW|//Drawstoawindow
PFD_SUPPORT_OPENGL|//TheformatmustsupportOpenGL
PFD_DOUBLEBUFFER;//Supportfordoublebuffering
pfd.iPixelType=PFD_TYPE_RGBA;//UsesanRGBApixelformat
pfd.cColorBits=32;//32bitscolors
if(!(m_hDeviceContext=GetDC(m_hWindow)))
throwCException("Unabletocreaterenderingcontext");
intPixelFormat;
//DoWindowsfindamatchingpixelformat?
if(!(PixelFormat=ChoosePixelFormat(m_hDeviceContext,&pfd)))
throwCException("Unabletocreaterenderingcontext");
//Setthenewpixelformat
if(!SetPixelFormat(m_hDeviceContext,PixelFormat,&pfd))
throwCException("Unabletocreaterenderingcontext");
//CreatetheOpenGLrenderingcontext
if(!(m_hGLContext=wglCreateContext(m_hDeviceContext)))
throwCException("Unabletocreaterenderingcontext");
//Activatetherenderingcontext
if(!wglMakeCurrent(m_hDeviceContext,m_hGLContext))
throwCException("Unabletocreaterenderingcontext");
}
函数的第一部分填充PIXELFORMATDESCRIPTOR结构体:缓冲区用来绘制到窗口上,支持OpenGL,使用双缓存(为了避免闪烁)。然后调用ChoosePixelFormat来检查像素格式是否支持,这个函数返回一个像素格式索引(若没有找到匹配的,则返回0)。一旦存在这样的像素格式索引,则通过SetPixelFormat来设置新像素格式。然后调用wglCreateContext创建OpenGL绘制上下文,最后,调用wglMakeCurrent,这指明线程接下来的OpenGL调用会绘制在这个设备上下文上。
InitGL函数很简单:
voidCMainWindow::InitGL()
{
//Enable2Dtexturing
glEnable(GL_TEXTURE_2D);
//Chooseasmoothshadingmodel
glShadeModel(GL_SMOOTH);
//Settheclearcolortoblack
glClearColor(0.0,0.0,0.0,0.0);
}
再看OnSize函数
voidCMainWindow::OnSize(GLsizeiwidth,GLsizeiheight)
{
//SetsthesizeoftheOpenGLviewport
glViewport(0,0,width,height);
//Selecttheprojectionstackandapply
//anorthographicprojection
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0,width,height,0.0,-1.0,1.0);
glMatrixMode(GL_MODELVIEW);
}
正交投影
透视投影
绘制简单图形
voidCMainWindow::Draw()
{
//Clearthebuffer
glClear(GL_COLOR_BUFFER_BIT);
//Heregoesthedrawingcode
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);
}
分享到:
相关推荐
OpenGL ES Tutorial for Android – Part I – Setting up the view OpenGL ES Tutorial for Android – Part II – Building a polygon OpenGL ES Tutorial for Android – Part III – Transformations OpenGL ES ...
使用C++代码封装的win32操作类, 与MFC相似,对于学习SDK与C++是巨好的参考 Tutorials Menu of tutorials Tutorial 1: The Simplest Window Tutorial 2: Using Classes and Inheritance Tutorial 3: Using ...
张正友标定法基础上的深入理解与实现
<br>Introduction <br>This tutorial will start with the very basis of File I/O (Input/Output) in C++. After that, I will look into aspects that are more advanced, showing you some tricks, and ...
讲解如何入门PyTorch,包括基础原理知识、numpy与PyTorch的区别以及案例研究实例
从github下载的UVM入门教程。 网页版教程 http://cluelogic.com/2011/07/uvm-tutorial-for-candy-lovers-overview/ 原始地址 https://github.com/cluelogic/uvm-tutorial-for-candy-lovers
This tutorial attempts to get you started developing with the Win32 API as quickly and clearly as possible.
A-tutorial-on-learning-with-Bayesian-networks,A-tutorial-on-learning-with-Bayesian-networksA-tutorial-on-learning-with-Bayesian-networks。
These tutorials will contain comprehensive coverage on Game Programming in OpenGL. These articles are going to assume some familiarty with OpenGL, and C/C++, but thats about it. Not only will these ...
After you have created a new Win32 Application (NOT a console application) in Visual C++, you will need to link the OpenGL libraries. In Visual C++ go to Project, Settings, and then click on the LINK ...
A-Tutorial-on-Joint-Radar-and-Communication-Transmission-for-Veh
A Tutorial on Reed-Solomon Coding for Fault-Tolerance in RAID-like Systems
https://www.pyimagesearch.com/2018/07/19/opencv-tutorial-a-guide-to-learn-opencv/ 代码
forgers win32 asm tutorial
Designed as a guidebook for those who want to become a Java developer, Java 7: A Comprehensive Tutorial discusses the essential Java programming topics that you need to master in order teach other ...
一个基于PyOpenGL的Python opengl-tutorial。 所有内容均遵循 您还可以在这里找到C实现: : 表中的内容 tu_00_glfw_window_sample:GLFW版本彩色立方体。 原始教程=> tu_01_color_cube:GLUT版本彩色立方体。 原始...
He efficiently presents the complex C++ language in this well-designed tutorial/reference that both students and seasoned programmers will appreciate. The book is ideal as a primary text for ...
Getting Started with C++ Audio Programming for Game Developers covers a broad range of topics – from loading and playing audio files to simulating sounds within a virtual environment and implementing...
OpenGL 3.3教程翻译 OpenGL 3.3 Tutorial Translation ======== 项目简介 Introduction -------- 此项目致力于将Arnaud Masserann等所著在线课程《Tutorials for OpenGL 3.3 and later》翻译为简体中文版。原版英文...
Storyboard是一项令人兴奋的功能,在iOS5中首次推出,在开发...http://www.raywenderlich.com/50308/storyboards-tutorial-in-ios-7-part-1 http://www.raywenderlich.com/50310/storyboards-tutorial-in-ios-7-part-2