`
dato0123
  • 浏览: 911192 次
文章分类
社区版块
存档分类
最新评论

【译】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++OpenGLwindows平台上开发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_KEYDOWNWM_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_bFullScreentrue,表示窗口模式为全屏模式。

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来调整矩形的大小,但这个函数在全屏模式下没什么作用,最后CreateContextInitGLOpenGL进行初始化。

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);
}

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics