支持HW团队,就支付宝领取下面的红包吧!(2018年3月31前,就几毛,也会几块,可以和其他红包叠加使用),你领取消费,HW有奖励。红包使用无条件限制,有条件请注意是不是有病毒。

小伙伴们,给大家发红包喽!人人可领,领完就能用。祝大家领取的红包金额大大大!#吱口令#长按复制此消息,打开支付宝就能领取!er1OEj73Uj

登入 注册 | 验证
| 搜索
HelloWorld论坛 : > 计算机科学、技术、教学> 编程专题> 开源免费项目> [转]SurfaceClass类的再设计
 
 
 
 
类别:其他 阅读:3173 评论:0 时间:五月 16, 2013, 2:21 p.m. 关键字:

 

 来源:
http://www.cppblog.com/lf426/archive/2008/04/17/47373.html
http://www.cppblog.com/lf426/archive/2008/04/19/47573.html
http://www.cppblog.com/lf426/archive/2008/04/19/47575.html
作者:龙飞

1、SurfaceClass类的再设计
1.1:为了按钮做准备

按钮是鼠标事件响应的象征,在PC游戏中起着十分重要的作用。这一章节,我们开始通过SDL提供的底层函数,自己来设计按钮。
按钮一般有这么几种状态:
out: 鼠标不在按钮上;
over: 鼠标在按钮上;
down: 鼠标按下了;
up: 鼠标松开了;
其中,down和up又可以细分为鼠标是在按钮上按下松开的,还是在按钮外按下松开的。
我们先从按钮的表现形式——图片来分析这些问题。
为了表现不同的状态,我们需要多张图片。至少的,out与over和down中的至少一张图片应该是不一样的,这样才能给人以“按下了”的视觉效果。也就是说,如果我们构建按钮类,这个类将会包含我们之前设计的SurfaceClass类。这样,根据我们之前讨论的一些C++细节问题,我们必须重新小心的设计SurfaceClass类。

1.2:为SurfaceClass设计基类

我们之前使用DisplaySurface本身作为TextSurface的基类,这是因为我们希望通过基类本身引用派生类,从而不需要为基类和派生类分别写函数。同样的,我们希望在ButtonClass中直接使用基类作为成员数据,这样,ButtonClass可以不需要为派生类重新构建。在之前的DisplaySurface中,我们可以看到,大部分方法,比如最为重要的blit()方法,所涉及到的成员数据仅仅就是SDL_Surface* pSurface,这一点,TextSurface类也是一样的。DisplaySurface类通过图片创建pSurface,TextSurface类通过字库创建pSurface,他们的创建条件不尽相同,TextSurface需要打开TTF_Init(),还需要打开字库TTF_OpenFont(),所以,他们的构造函数与析构函数是不一样的。一种更加清晰的关系,是建立起他们的基类,然后把他们的区别点分开处理,而不是之前采用的if来判断处理。
最清晰的办法,是把BaseSurface设计成为抽象基类(ABC),但是ABC的问题在于,无法创建ABC本身的对象,也就是说,我们需要在ButtonClass中包含的BaseSurface对象无法建立,所以,我们不能把BaseSurface设计成为ABC。我想到的办法,是将BaseSurface的默认构造函数仅仅提供给派生类使用,也就是说,因为BaseClass的默认构造函数本身是不能建立起必要的数据的,比如pSurface和pScreen,我们暂时都用空指针代替。这里的思想其实还是ABC的,让基类仅仅提供算法,即使算法所需要的元素还尚不存在。但是,只要这些元素存在了,BaseSurface也就可以完成这些算法,所以,我们把BaseSurface的复制构造函数设计为公共的,这很重要,这样才为我们通过派生类去初始化基类的构造函数初始化列表提供了可能。
我们把基类命名为BaseSurface,之前的DisplaySurface我们用更形象的名称代替,叫PictureSurface,然后TextSurface不改名。

1.3:如何深度复制SDL_Surface ?

因为PictureSurface和TextSurface的构造函数都涉及到了堆操作,也就是说,SDL_Surface是建立在堆上的。我们需要通过基类的复制构造函数复制SDL_Surface,必须找到合适的工具。具体来说,新对象的pSurface必须指像一个新建立起来的SDL_Surface对象,而非被复制的对象本身。
我最先想到的是用new...delete组合,但是失败了。为什么呢?我们研究SDL_Surafce结构本身,发现在之中包含着一些其它的指针。比如SDL_PixelFormat *format和void *pixels。也就是说,SDL_Surface本身的构建也有堆操作,而且,因为SDL_Surafce是C风格的结构而不是类,它不能提供深度复制的操作,所以,即使我们用new为SDL_Surface申请了新空间,SDL_Surface本身也不会为format和pixels申请新的副本。当被释放掉原本的时候,副本将寻找不到原本的format和pixels,从而产生错误。
我们另外能找到的可以深度复制SDL_Surface的函数,是:

SDL_Surface *SDL_DisplayFormat(SDL_Surface *surface);
SDL_Surface *SDL_ConvertSurface(SDL_Surface *src, SDL_PixelFormat *fmt, Uint32 flags);

这两个函数貌似都是可行的。实际上,基本上也都是可行的。但是涉及到文字的时候,SDL_DisplayFormat将不能正确的转换Blended和Shaded的文字,所以,最后只能使用SDL_ConvertSurafce()。
新构建的SurfaceClass源代码见:
http://www.cppblog.com/lf426/archive/2008/04/14/47038.html

2、设计按钮ButtonClass
2.1:设计框架与基类的接口

有了更加完善的SurfaceClass的支持,我们可以进行按钮的设计了。接着上一节的话题,按钮除了要给我们表现出来是否被按下的视觉效果,还要起到实际上的作用。一种最简单的思路,既是鼠标在按钮上一旦按下,程序就马上响应。这种思路很朴素,也很实用。大名鼎鼎的QuakeIII的菜单按钮就是这么设计的,这样我们几乎是可以直接使用SDL的事件响应,即:事件不为空——鼠标事件的左键按下——响应处理。
但是也许我们已经习惯更加人性话的GUI按钮了。比如,如果是不小心点错了,马上响应意味着没有机会改正操作失误。事实上,我们仔细分析当今GUI上的按钮,可以发现按钮的实际效果,是在按下鼠标,并且又松开的时候产生的响应。其实这样说也并不完全准确,更加准确的描述,应该是鼠标既要在按钮上按下,又要在按钮上松开——其实这还是不完整,我想说的是,为了描述这个复杂的状态,我们不得不在ButtonClass中引入几个bool量,以判断按钮是否真正起作用。
按钮的构成与视觉效果,根据我们之前的知识,大概具有这几类:完全由PictureSurface组成,这又分为两类,两张Picture(out和over)或者三张Picture(再加张down);由TextSurface组成;由一张精灵图的PictureSurafce切分出来。他们的接口几乎是一样的:设置位置和按下时的偏移(setup),扣色(colorkey),添加文字(addText),显示(blit),鼠标事件判断(mouse out, over, down, up 甚至是 up outside)和有效点击(effectiveClick)。所以,我们有理由用基类来规定这些接口。或者说,用ABC(抽象基类)的纯虚函数硬性规定这些接口。

2.2:鼠标事件判断与有效点击

移动:SDL_MOUSEMOTION
触发的开关是鼠标发生了移动。我们需要判断鼠标是否移动到了按钮上,或者移动到了不是按钮区域的地方;
点击:SDL_MOUSEBUTTONDOWN
触发条件是鼠标按下了,我们需要进一步判断是不是左键按下了(gameEvent.button.button == SDL_BUTTON_LEFT ),然后判断是不是在按钮区域内按下的。
松开:SDL_MOUSEBUTTONUP
触发条件是鼠标松开了,我们需要进一步判断是不是左键松开了(gameEvent.button.button == SDL_BUTTON_LEFT ),然后判断是不是在按钮区域内松开的。
按钮外松开:SDL_MOUSEBUTTONUP
触发条件是鼠标松开了,我们需要进一步判断是不是左键松开了(gameEvent.button.button == SDL_BUTTON_LEFT ),然后判断是不是在按钮区域内松开的。
我们之所以需要做这些判断,是为了构建我们刚才设想中的按钮效果,即有效点击(effectiveClick)。因为有效点击不是通过一次事件的判断完成的,我们通过三个bool量在整个按钮的生命周期类描述按钮所接收到的鼠标事件:inBox鼠标在按钮区域内;clickDown鼠标在按钮区域类被按下过(并且没有在外面松开);clickUp鼠标在按钮区域内松开。我们来看看这段代码吧……我承认,if得很混乱,但是居然能正常工作,呵呵。

bool BaseButton::effectiveClick(const SDL_Event& game_event)
{
        inBox = this->mouseOver(game_event);
        if ( this->mouseDown(game_event) == true )
        {
                clickDown = true;
                inBox = true;
        }
        if ( this->mouseUp(game_event) == true )
        {
                if ( clickDown == true )
                        clickUp = true;
                inBox = true;
        }
        if ( this->mouseUpOutside(game_event) == true )
                clickDown = false;

        if ( inBox == true && clickDown == false )
        {
                this->blitOver();
                return false;
        }
        else if ( inBox == true && clickDown == true )
        {
                if ( clickUp == true ){
                        clickUp = false;
                        clickDown = false;
                        this->blitOver();
                        return true;
                } else {
                        this->blitDown();
                        return false;
                }
        }
        else
        {
                this->blitOut();
                return false;
        }
}

最有意思的是,我们可以把这个函数构建在基类中——即使blitxxx()这类的函数都是纯虚函数——但是他们已经代表了算法。在用不同的派生类调用基类的这个方法的时候,blitxxx()会被替换成相应派生类的版本,从而减少了重复写代码的工作。

2.3:ButtonClass的源代码
http://www.cppblog.com/lf426/archive/2008/04/15/47156.html

3、做一个对话框
有了按钮类,我们制作对话框就很轻松了。边写程序边说明吧。

bool hand_dialog(const ScreenSurface& screen, const std::string& dialog_text, int size)
{
        const int CENTRE_X = (screen.point()->w) / 2;
        const int CENTRE_Y = (screen.point()->h) / 2;
        const int HALF_SUB_BUTTON_W = 64 / 2;

先找出显示屏的中心点,然后是对话框按钮宽度的一半。这些是为了在屏幕中心的对称位置显示YES和NO

        PictureSurface quitBG("./images/dialog_bg.png", screen);
        quitBG.blit();

载入对话框的背景

        TextSurface quitDlg(dialog_text, screen, 215, 195, 122, size);
        int quitDlg_x = CENTRE_X - ((quitDlg.point()->w)/2);
        int quitDlg_y = CENTRE_Y - 50;
        quitDlg.blit(quitDlg_x, quitDlg_y);

载入对话框的提示文字

        const int YES_X = CENTRE_X - 40 - HALF_SUB_BUTTON_W;
        const int YES_Y = CENTRE_Y + 30;
        Button yesButtonEffect("./images/h3_yes_off.png", "./images/h3_yes_over.png", screen);
        yesButtonEffect.setup(YES_X, YES_Y, 5);
        yesButtonEffect.blitOut();

构建YES按钮

        const int NO_X = CENTRE_X + 40 - HALF_SUB_BUTTON_W;
        const int NO_Y = CENTRE_Y + 30;
        Button noButtonEffect("./images/h3_no_off.png", "./images/h3_no_over.png", screen);
        noButtonEffect.setup(NO_X, NO_Y, 5);
        noButtonEffect.blitOut();

构建NO按钮

        screen.flip();

显示屏幕

        SDL_Event gameEvent;
        while( true ){
                while ( SDL_PollEvent(&gameEvent) != 0 ){
                        if ( gameEvent.type == SDL_KEYDOWN ){
                                if ( gameEvent.key.keysym.sym == SDLK_ESCAPE ){
                                        return false;
                                }
                        }
                        quitBG.blit();
                        quitDlg.blit(quitDlg_x, quitDlg_y);
                        if ( yesButtonEffect.effectiveClick(gameEvent) == true )
                                return true;
                        if ( noButtonEffect.effectiveClick(gameEvent) == true )
                                return false;
                        screen.flip();
                }
        }
}

主循环了。可以通过按下ESC或者按下NO按钮取消对话框,按下YES按钮则表示做出了选择。我们使用effectiveClick()方法,一次性的将接收事件,判断事件,显示按钮的不同状态集成性的完成了。

[挂载人]初学MPEG

个人签名--------------------------------------------------------------------------------

Please Login (or Sign Up) to leave a comment