|
|
[原http://bbs.chaosstars.com/dispbbs.asp?boardid=61&id=244]
上次说了一点关于动画的,下面就讲点关于界面的吧,对于界面,我没有实际操作过,这里就对一些分析发表点自己的观点
Windows的界面是目前键盘鼠标输入设备发展的产物,不仅玩家看起来舒服,用起来习惯,而且也是PC人机交互的最好方式,所以,在游戏中,我们也常常参照Windows来设计UI
仿造微软的MFC,我们可以设计一个CWnd基类,具有一些基本的属性和方法:
class CWnd : public CObj
{
public:
HWND m_hParentWnd;
// 属性,比如编号,标题,位置,大小,类型,背景等等
int m_nWndID;
string m_strText;
RECT m_rcPos;
CImgObj * m_pImgBack;
int m_nIsShow; // 可见
int m_nZOrder; // 这个属性非常重要,关系到窗口互相重叠的问题
...
public:
CWnd( int nID, string& strText );
CWnd( int nID );
~CWnd( );
void SetText( string& strText );
void SetPos( LPRECT lpRect );
void SetImage( CImgObj * pImg );
void Render( )
{
m_pImgBack->Render( ... );
}
};
与属性相关的就是一些方法,比如改变标题,改变窗体位置,改变背景等,要注意的是,窗体的这些属性都是显示相关的,那么怎么才能改变以后就立即显示呢,其实只要调用Render函数绘制就可以了,怎么调用等会再说
窗体除了属性和方法,最重要的就是事件,各种事件的响应,比如鼠标,键盘,还可能有网络事件(以后专讲网络部分)
事件怎么办我们可以通过虚函数来处理,定义一些常用的事件
class CWnd : public CObj
{
...
public:
// 这个函数可以有也可以不要
virtual LRESULT CallWndProc( UINT uMsg, WPARAM wParam, LPARAM lParam ) { }
virtual void OnKeyDown( WPARAM wParam, LPARAM lParam ) { }
virtual void OnKeyUp( WPARAM wParam, LPARAM lParam ) { }
virtual void OnLButtonDown( WPARAM wParam, LPARAM lParam ) { }
virtual void OnRButtonUp( WPARAM wParam, LPARAM lParam ) { }
virtual void OnRButtonDown( WPARAM wParam, LPARAM lParam ) { }
virtual void OnLButtonUp( WPARAM wParam, LPARAM lParam ) { }
virtual void OnMouseMove( WPARAM wParam, LPARAM lParam ) { }
};
消息从哪里来?从系统Windows来,实际上,你可以把自己定义的所有CWnd及其派生类都看作是游戏主窗体的子窗体,什么是游戏主窗体?就是最开始生成的系统窗体,回忆一下我们游戏的框架
[游戏制作心得(一)http://bbs.chaosstars.com/dispbbs.asp?boardID=61&ID=232&page=1]
LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM );
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
wc.lpfnWndProc = (WNDPROC)WndProc;
RegisterClassEx( &wc );
hWnd = CreateWindow( ... ); // 游戏主窗体,所有你自己定义窗体的父窗体
Game_Init( );
while ( true )
{
if ( PeekMessage ( ... ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
{
Update( );
Render( );
}
}
}
// 消息环
LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM )
{
// 处理消息
}
消息来了怎么传递给窗体呢,当然,我们同样要对各种窗体来个集合,注意,这里的窗体不是游戏中所有窗体的集合(不像像图片集和,图片集合是字典),这里窗体集合是当前所用到的窗体集合,没用的别放进去(大家看的时候小心,集合类CCWnd要比元素类CWnd多一个C,我这里为了方便才这么写的,大家写的时候怕出错可以改改)
class CCWnd : public CObj
{
// 这里我要实现一个函数,可以看到CWnd::m_nZOrder的用处了
void OrderWnd( ); // 按照m_nZOrder的顺序排序
void Render( );
{
map<ObjKey, ObjPtr>::iterator it = m_lstObjs.begin( );
for ( ; it != m_lstObjs.end( ); ++it )
{
CWndPtr p = (CWndPtr)(*it);
if ( p && p->m_nIsShow ) // 可见的窗体
{
p->Render( ... );
}
}
}
};
这里有个小技巧,每当我们new一个窗体的时候,就自动把它加入到集合中去,下面是CPP文件
CWnd::CWnd( int nID, string& strText );
{
m_nID = nID;
m_strText = strText;
CCWnd::GetInst( )->Add( this );
}
由于集合CCWnd不是字典,是动态的,所以其中的元素要手工释放,不能完全等集合CCWnd析构的时候释放。释放的方法,是调用CCWnd::GetInst( )->Remove( "窗体1" );
上次我没讲这个Remove函数的实现,我强调一下,Remove函数里一定要把元素delete掉,然后再erase元素指针
这样集合就建好了,用的时候如下:
#define ID_WIN1 1000 // 窗体编号1000
CWnd * pWnd1 = new CWnd( ID_WIN1, "选择角色窗口" );
pWnd1.SetPos( ... ); // 设置属性调用方法
...
Render( ) // 还是那个框架的渲染函数
{
CCWnd::GetInst( )->Render( );
}
...
// 最后用完了,不用这个窗体了(不是隐藏),比如我们进入游戏不用再选择角色了
CCWnd::GetInst( )->Remove( "选择角色窗口" );
说了这么多,现在回到消息那个话题,我们需要将消息一级一级的传递给集合CCWnd中的每一个元素,传递的方式是上面窗体最先接受到消息,下面的窗体不接受消息。不同的消息是不同的,比如,WM_KEYDOWN,只要最上面的窗体响应就可以了,但是WM_LBUTTONDOWN,不一定是嘴上面的窗体响应,而是鼠标所在位置的最上面的窗体响应,我就以WM_LBUTTONDOWN为例来说明
// 消息环
LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
// 处理消息
CCWnd::GetInst( )->CallWndProc( uMsg, wParam, lParam );
DefWindowProc( hWnd, uMsg, wParam, lParam );
}
class CCWnd : public CObj
{
...
// 不是虚函数
LRESULT CallWndProc( UINT uMsg, WPARAM wParam, LPARAM lParam )
{
switch ( uMsg )
{
case WM_LBUTTONDOWN:
{
OnLButtonDown( wParam, lParam )
}
}
}
void OnLButtonDown( WPARAM wParam, LPARAM lParam )
{
int xPos = GET_X_LPARAM(lParam);
int yPos = GET_Y_LPARAM(lParam);
map<ObjKey, ObjPtr>::iterator it = m_lstObjs.begin( );
for ( ; it != m_lstObjs.end( ); ++it )
{
CWndPtr p = (CWndPtr)(*it);
if ( p && p->m_nIsShow ) // 可见的窗体
{
if ( p->InWnd( xPos, yPos ) // 在窗体范围内
p->OnLButtonDown( wParam, lParam );
return; // 下面都不用处理了
}
}
}
};
好了消息处理完了,剩下的都好办了,我们可以以此为借鉴写一个控件的基类
class CCtrl : public CWnd
{
public:
CWnd * pParent;
public:
CCtrl( CWnd * pParent );
};
其实CWnd * pParent就是CCtrl的集合类,当pParent被释放的时候,将其上面所有的控件都释放
有了这2个基类(CWnd和CCtrl),下面都好办了
class CDialog : public CWnd;
class CEdit : public CCtrl;
class CButton : public CCtrl;
class CList : public CCtrl;
大家看看有没有什么问题,欢迎指教
|
|