画面にキャラクターデータの描画

こんにちは。



プログラムで2Dアニメーションを実装する方法は色々あると思いますが、
今回はSDLのライブラリを使ったアニメーションを、図解で説明したいと
思います。


今回のソースです。
http://www.geocities.jp/finnissy/data/AlphaBlitTest.zip


予めアニメーションの背景となるピクセルデータと、アニメーション
させるキャラクターのピクセルデータを用意しておきます。

この画像を一定の間隔で連続して描画する事で、
アニメーションさせる事が出来ます。
普通の2Dゲームだとフレームレートが60FPSぐらいです。
(1秒間に約60回ぐらい画面を更新します)

毎フレーム、アニメーションさせる画像の座標軸を少しづつ変える事で、
画像が動いて見えます。

今回は3つの実装方法を紹介したいと思います。


1:毎フレーム全画面のバッファを交換する

SDLは低レベルなライブラリですから、ビデオメモリが貧弱 or システム
メモリを使う場合は、めんどくさがって毎フレーム全画面リフレッシュ
すると、かなりのマシンパワーを必要とします。




冗長なロジックで実装

SDL_ttf



2:キャラクターデータのみ描画する



この方法だと、1に比べれば処理がだいぶ軽くなりますが、
前フレームで描画した内容がそのまま残ってますので、
このままでは使えません。
つまり前フレームで描画した情報を消去する必要があります。


中途半端なロジックで実装

SDL_ttf



毎フレーム、描画した内容を消さない

SDL_ttf





3:描画した箇所のみ裏画面・更新処理を行う



最後にキャラクターを描画した箇所を読み込み、
描画した箇所に合わせて背景を描画する。


必要のあるところだけ更新

SDL_ttf



毎フレーム裏画面を描画

SDL_ttf





main.h






#ifndef MAIN_H
#define MAIN_H

#include <SDL/SDL.h>
#include <stdio.h>

/* ライブラリのインポート   */
#pragma comment(lib,"SDL.lib")
#pragma comment(lib,"SDLmain.lib")

//  0と1でゲームモードを変更
//  0 : 冗長なロジックで処理
//  1 : 最適なロジックで処理
#define GAMEMODE    1
#define BACKDRAW    1

#define FRAME_WIDTH     600
#define FRAME_HEIGHT    600
#define FRAME_BPP       32
#define UPDATE_MAX  (100)

#define INIT_FLAG_SUBSYSTEM (SDL_INIT_VIDEO | SDL_INIT_JOYSTICK)
#define INIT_FLAG_VIDEO (SDL_SWSURFACE)

typedef SDL_bool Bool;

#define FALSE   (SDL_FALSE)
#define TRUE   (SDL_TRUE)

#define BACKSCREEN_PATH ("./data/backpic.bmp")
#define CURSOR_ICON_PATH ("./data/cursor.bmp")
#define CURSOR_ICON_PATH2 ("./data/cursor2.bmp")

#endif





main.c






#include "main.h"

static Bool init();
static void run();
static void quit();
static void set_cursor(SDL_Surface *img);
static void blit_cursor(void);
static void push(SDL_Rect *src);
static void update(void);
static
SDL_Surface *load_image(const char *path, int a_flg, Uint32 a_color);
static void blit(SDL_Surface *s,int x,int y);
static void blitR(SDL_Surface *s,SDL_Rect *r);
static SDL_Rect *get_oldUpdate(void);
static void set_oldUpdate(SDL_Surface *s, int x,int y);

SDL_Surface *screen;
SDL_Surface *backScreen;
SDL_Surface *backBuffer;
SDL_Surface *cursor;
SDL_Surface *cursor2;
SDL_Surface *now_cursor;

int         totalRect;
SDL_Rect    rects[UPDATE_MAX];
SDL_Rect    oldUpdate;

static int mouseX,mouseY;

int main(int argc, char *argv[])
{
    if( init() )
        run();

    quit();
    
    return 0;
}

static
Bool init(void)
{
    /*  ライブラリを初期化する  */
    if(SDL_Init(INIT_FLAG_SUBSYSTEM) < 0) {
        printf("初期化に失敗");
        return FALSE;
    }

    /*    ビデオモードを設定する    */
    screen = SDL_SetVideoMode(
        FRAME_WIDTH,    FRAME_HEIGHT,
        FRAME_BPP,      INIT_FLAG_VIDEO
    );

    if( screen == NULL) {
        printf("VideoInitialize Error");
        return FALSE;
    }

    /*  背景画像の読み込み  */
    backScreen = SDL_LoadBMP( BACKSCREEN_PATH );
    if( backScreen == NULL ){
        printf("error BackImage Load \n");
        return FALSE;
    }

#if GAMEMODE >= 1
    /*  空のサーフェースを確保  */
    backBuffer = SDL_AllocSurface(
        screen->flags,                  //元のビデオフラグ
        screen->w,                      //幅
        screen->h,                      //高さ
        screen->format->BitsPerPixel,   //ピクセルのビット深度
        screen->format->Rmask,          //赤のマスク
        screen->format->Gmask,          //緑のマスク
        screen->format->Bmask,          //青のマスク
        screen->format->Amask           //透過色のマスク
        );

    /*  バックバッファーに転送  */
    SDL_BlitSurface(backScreen,NULL,backBuffer,NULL);
#endif

    cursor  = load_image(CURSOR_ICON_PATH,1,0x000000);
    if(cursor == NULL)
        return FALSE;

    cursor2 = load_image(CURSOR_ICON_PATH2,1,0x000000);
    if(cursor2 == NULL)
        return FALSE;

    /*  カーソルのセット    */
    set_cursor( cursor );

    /*  マウスカーソルを非表示  */
    SDL_ShowCursor( 0 );

    return TRUE;
}


static
void run(void)
{
    SDL_Event ev;

    blit(backScreen,0,0);

    ev.type = SDL_MOUSEMOTION;
    SDL_PushEvent( &ev );

    /****************************************/
    /*  次のイベントが来るまで無限に待機    */
    /****************************************/
    while( SDL_WaitEvent(&ev) )
    {
        switch(ev.type)
        {
        /*ウィンドウの×ボタン*/
        case SDL_QUIT:
            printf("QUIT\n");
            return;
        /*キーダウン*/
        case SDL_KEYDOWN:
            printf("KeyDonw\n");
            if(ev.key.keysym.sym == SDLK_ESCAPE)
                return;
            break;
        /*  マウスアクションイベント    */
        case SDL_MOUSEBUTTONDOWN:
            printf("MouseDonw\n");
            set_cursor( cursor2 );  // mouse cursor change
            blit_cursor();
            update();
            break;
        case SDL_MOUSEBUTTONUP:
            printf("MouseUp\n");
            set_cursor( cursor );   // mouse cursor change
            blit_cursor();
            update();
            break;
        case SDL_MOUSEMOTION:
            printf("mouse move\n");
            blit_cursor();
            update();
            break;
        default:
            break;
        }
    }
}

static
void quit(void)
{
    if(screen != NULL)
        SDL_FreeSurface( screen );
    
    if(backScreen != NULL)
        SDL_FreeSurface( backScreen );

    if(cursor != NULL)
        SDL_FreeSurface( cursor );

    if(cursor2 != NULL)
        SDL_FreeSurface( cursor2 );

    SDL_Quit();
}

static
void set_cursor(SDL_Surface *img)
{
    now_cursor = img;
}

static
void blit_cursor(void)
{
#if GAMEMODE >= 1
#else
    SDL_Rect rect;
#endif

    /*  カーソル画像がセットされてない  */
    if(now_cursor == NULL)
            return;

#if GAMEMODE >= 1

#if BACKDRAW > 0
    SDL_SetAlpha(backBuffer,0,0);
    blitR( backBuffer, get_oldUpdate() );
#endif
    /*  マウスを描画    */
    SDL_GetMouseState( &mouseX, &mouseY );
    blit(now_cursor,mouseX,mouseY);
    set_oldUpdate(now_cursor,mouseX,mouseY);

#else

    /*  背景を描画  */
    SDL_BlitSurface(backScreen,NULL,screen,NULL);

    /*  マウスを描画    */
    SDL_GetMouseState( &mouseX, &mouseY );

    rect.x = mouseX;
    rect.y = mouseY;
    rect.w = 0; //使わない
    rect.h = 0; //使わない

    SDL_BlitSurface(now_cursor,NULL,screen,&rect);
#endif
}

static
void push(SDL_Rect *src)
{
    if(totalRect < UPDATE_MAX)
    {
        int i = totalRect;

        rects[i] = (*src);
        
        printf("push Rect X:%d\tY:%d\tW:%d\tH:%d\n",src->x,src->y,src->w,src->h);

        /*      更新する矩形の数をインクリメント    */
        totalRect++;
    }
}

static
void update(void)
{
#if GAMEMODE >= 1
    /********************************************/
    /*  与えられた矩形情報を元に画面の更新      */
    /********************************************/
    SDL_UpdateRects(screen, totalRect, rects);

    /********************************************/
    /*          矩形情報をクリアする            */
    /********************************************/
    memset(rects,0,sizeof(rects));
    totalRect = 0;
#else
    //全体を更新する
    SDL_UpdateRect(screen, 0, 0, 0, 0);
#endif
}

static
SDL_Surface * load_image(const char *path, int a_flg, Uint32 a_color)
{
    /*  カーソル画像の読み込み  */
    SDL_Surface *r;
    SDL_Surface *t = SDL_LoadBMP( path );

    if( t == NULL ){
        printf("Image Load Error! \n");
        return NULL;
    }

    if( a_flg )
    {
        /*  黒を透過色としてセット  */
        SDL_SetColorKey(t,SDL_SRCCOLORKEY,a_color);
        r = SDL_DisplayFormat(t);

        SDL_FreeSurface(t);
        return r;
    }

    return t;
}

static void blit(SDL_Surface *s,int x,int y)
{
    SDL_Rect rect,t;
    int r;

    if(s == NULL)
        return;

    rect.w = s->w;
    rect.h = s->h;
    rect.x = x;
    rect.y = y;

    t = rect;

    printf("blit Rect X:%d\tY:%d\tW:%d\tH:%d\n",rect.x,rect.y,rect.w,rect.h);

    r = SDL_BlitSurface(s,NULL,screen,&t);

    if(r < 0)
    {
        printf("error BlitSurface\n");
    } else {
        printf("BlitSurface Ok\n");
        push( &rect );
    }
}

static void blitR(SDL_Surface *s,SDL_Rect *r)
{
    SDL_Rect rect,t;
    int ret;

    if(s == NULL)
        return;

    rect = (*r);

    t = rect;

    printf("blit Rect X:%d\tY:%d\tW:%d\tH:%d\n",rect.x,rect.y,rect.w,rect.h);

    ret = SDL_BlitSurface(s,&t,screen,&t);

    if(ret < 0)
    {
        printf("error BlitSurface\n");
    } else {
        printf("BlitSurface Ok\n");
        push( &rect );
    }
}

static SDL_Rect *get_oldUpdate(void)
{
    return &oldUpdate;
}

static void set_oldUpdate(SDL_Surface *s, int x,int y)
{
    oldUpdate.w = (s->w+1) % FRAME_WIDTH;
    oldUpdate.h = (s->h+1) % FRAME_HEIGHT;
    oldUpdate.x = x < 0 ? 0 : x;
    oldUpdate.y = y < 0 ? 0 : y;
}