SDL入门教程(十三):1、多线程,从动画说起

[复制链接]

该用户从未签到

2380

主题

2433

帖子

9139

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
9139
QQ
跳转到指定楼层
楼主
发表于 2017-12-19 09:40:30 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

想要查看内容赶紧注册登陆吧!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
                        作者:龙飞

1.1:简单动画

        游戏离不开动画。我们考虑最简单的情况:将一个角色从一个位置移动到另外一个位置。这个行为表述给电脑就是,将一个surface不断的blit(),从起始位置的坐标,移动到结束位置的坐标。移动速度取决于每次blit()的坐标差和blit()的时间间隔(v = ds/dt )。
        我们来设计一个函数实现这个简单的动画。我们需要的数据有:起始坐标(int beginX, int beginY),结束坐标(int endX, int endY),以及作为SDL基础的ScreenSurface窗口(const ScreenSurface& screen)。一般的考虑是,将这5个数据以参数的方式传入函数;但是一种更加通用一点的方式是,将这5种数据合并成一个结构,这样函数的参数形式会更加的统一,这正是触发多线程的函数所需要的。在SDL中,我们通过函数:

SDL_Thread *SDL_CreateThread(int (*fn)(void *), void *data);
触发多线程,其中所需要的函数指针形式为:

typedef int (*fn)(void*);
而void*类型的data就是函数(*fn)()需要的的数据。我们可以将任意的结构体指针,转化为void*,作为这个函数的第二个参数需要。
因此,我们可以为我们需要的动画函数定义一个结构作为传递所有数据的载体:

struct AmnArg
{
   
int beginX;
   
int beginY;
   
int endX;
   
int endY;
   
const ScreenSurface& screen;
    AmnArg(
int begin_x, int begin_y, int end_x, int end_y, const ScreenSurface& _screen): beginX(begin_x), beginY(begin_y), endX(end_x), endY(end_y), screen(_screen){}
};

这样,我们可以将AmnArg对象的指针传递给动画函数——考虑到多线程函数的需要,我们再曲折一点:先将AmnArg*转换成void*传递给函数,在函数内部再将其转换回来以供调用。

int amn(void* data)
{
    AmnArg
* pData = (AmnArg*)data;
    PictureSurface stand(
"./images/am01.png", pData->screen);
    stand.colorKey();
    PictureSurface bg(
"./images/background.png", pData->screen);

   
const int SPEED_CTRL = 300;
   
int speedX = (pData->endX - pData->beginX) / SPEED_CTRL;
   
int speedY = (pData->endY - pData->beginY) / SPEED_CTRL;

   
for ( int i = 0; i < SPEED_CTRL; i++ ){
        pData
->beginX += speedX;
        pData
->beginY += speedY;
        bg.blit(pData
->beginX, pData->beginY, pData->beginX, pData->beginY, stand.point()->w, stand.point()->h, 2, 2);
        stand.blit(pData
->beginX, pData->beginY);
        pData
->screen.flip();
    }

   
return 0;
}

注意:我们这里仅仅设定了每次blit()的位移差(ds)而没有设定时间差(dt)。这并不意味着dt == 0,事实上,电脑处理数据是需要时间的,包括运算和显示。我们这里事实上将dt的设定交给了电脑,也就是说,让电脑以其最快的速度来完成。为什么要这么做呢?这是为了演示多线程的一个现象,卖个关子,后面解释。

1.2:动画函数在主程序中的调用

#include "SurfaceClass.hpp"
#include
"amn.hpp"

int main(int argc ,char* argv[])
{
   
//Create a SDL screen.
    const int SCREEN_WIDTH = 640;
   
const int SCREEN_HEIGHT = 480;
   
const Uint32 SCREEN_FLAGS = 0; //SDL_FULLSCREEN | SDL_DOUBLEBUF | SDL_HWSURFACE
    const std::string WINDOW_NAME = "Amn Test";
    ScreenSurface screen(SCREEN_WIDTH, SCREEN_HEIGHT, WINDOW_NAME,
0, SCREEN_FLAGS);

    PictureSurface bg(
"./images/background.png", screen);
    bg.blit(
0);
    screen.flip();

    AmnArg test1(
0, 250, 600, 250, screen);
    amn((
void*)&test1);

    SDL_Event gameEvent;
   
bool gameOver = false;
   
while ( gameOver == false ){
        
while ( SDL_PollEvent(&gameEvent) != 0 ){
            
if ( gameEvent.type == SDL_QUIT ){
                gameOver
= true;
            }
            
if ( gameEvent.type == SDL_KEYDOWN ){
               
if ( gameEvent.key.keysym.sym == SDLK_ESCAPE ){
                    gameOver
= true;
                }
            }
            screen.flip();
        }
    }

   
return 0;
}

当这个程序运行的时候,我们会发现一些很明显的问题:
1、图片移动的时候,界面不接受任何信息。这是因为必须把amn()执行完毕才会运行到有事件响应的事件轮询循环。
2、如果我们需要另外一张图片移动起来,我们唯一能做的事情,是修改amn()函数,而不是把amn()以不同的参数调用两次——如果以不同的参数调用两次,那么移动总是有先后的——是不可能完成“同时”移动的。

1.3:创建线程

        如果要将这个程序从主线程(主进程)调用函数修改为通过新创建的线程调用函数,只需要做很小的修改,即将amn((void*)&test1);修改为:

SDL_Thread* thread1 = SDL_CreateThread(amn, (void*)&test1);
然后在return 0;之前加入清理线程的语句:

SDL_KillThread(thread1);
这样,程序在执行动画的同时,事件轮询就已经开始,我们可以随时结束程序,SDL界面也不会出现不响应的情况。
                    

分享到:  QQ好友和群QQ好友和群
收藏收藏
回复

使用道具 举报

快速回复高级模式
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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