游戏开发论坛

 找回密码
 立即注册
搜索
查看: 1168|回复: 0

游戏制作心得(三)

[复制链接]

89

主题

822

帖子

847

积分

高级会员

Rank: 4

积分
847
发表于 2004-12-17 15:36:00 | 显示全部楼层 |阅读模式
[原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;

大家看看有没有什么问题,欢迎指教
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2025-12-23 18:00

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表