シップの動きを Text File で制御する

前田稔(Maeda Minoru)の超初心者のプログラム入門

// シップの座標データ  1000  Frame
// X座標(最初の 1000 個)
(200 +2)0, (200 -1)*, (200)*, (200 +2)*, (200 >0)*,
// Y座標(次の 1000 個)
(100 -2)300, (100 +1)*, (100 +1)*, (200 -2)*, (200 >400)*, (300 >300)*,
(1000)0         //数が足らなくてエラーにならないように余分に定義

>1000 の機能を使った場合。
// シップの座標データ  1000  Frame
// X座標
(200 +2)0, (200 -1)*, (200)*, (200 +2)*, (>1000 >0)*,
// Y座標
(100 -2)300, (100 +1)*, (100 +1)*, (200 -2)*, (200 >400)*, (>2000 >300)*,

動きのデータを Text File にタイプして、これを入力してシップの動きを制御します。
ゲームでは細かな調整が必要ですが、プログラムを修正しなくても調整できるこのような機能は必須です。

ゲームを実行するプログラムを「エンジン」と言いますが、エンジンは同じでも与えるコンテンツ(画像や音声)が 違えば別のゲームかと思われます。
コンテンツに加えて様々な制御データを外部から与えて新しいゲームプログラムを作成すると言う考え方があります。
ゲームツクールはその代表例ですが、そこまで行かなくても多かれ少なかれこのような方法が用いられています。

プログラムの説明

  1. My.lib に登録されている BACKBUF, IMAGE, CSV Object Class を使うので my.h を組み込んで my.lib をリンクします。
    ID_TIMER はシップを動かすタイマのIDです。
    Back は画面のチラツキを抑えるための BACKBUF Object Class の定義です。
    BackImg は背景画像を表示する IMAGE Object Class の定義です。
    IMAGE Class は BMP, JPEG, GIF を表示できる総合的な画像のクラスで Sprite Class の機能も併せ持っています。
    Ship は Text File で動きを制御する Sprite の Object Class です。
    Csv は TEXT File から制御情報を入力する CSV Object Class の定義です。
    tbl[2000] は 1000 フレーム分のシップの座標を格納する領域で、最初の 1000 個がX座標で、次の 1000 個がY座標です。
    pos は、現在参照している tbl[] の位置を記憶する領域です。
        #define     NAME    "Ship Move"
        #include    <windows.h>
        #include    "my.h"
        #pragma     comment(lib,"my.lib")
        #define     SAFE_DELETE(p)  { if (p) { delete (p);     (p)=NULL; } }
        #define     SAFE_RELEASE(p) { if (p) { (p)->Release(); (p)=NULL; } }
        #define     ID_TIMER    (WM_APP + 0)
    
        BACKBUF*    Back= NULL;         //Back Buffer
        IMAGE*      BackImg= NULL;      //Back Image
        IMAGE*      Ship= NULL;         //Ship Image
        CSV         Csv;                //CSV Object Class
        int         tbl[2000];          //座標テーブル
        int         pos= 0;             //現在の座標
        
  2. WM_CREATE: で Object をインスタンス化して、画像を入力します。
    Csv Object Class で、シップの座標を ship.txt から入力して tbl[] に格納します。
    詳しい形式は後ほど説明しますが ship.txt の内容をページの先頭に記述しています。
    Adjust() 関数で、ウインドウのサイズを背景画像の大きさに合わせて設定します。
            case WM_CREATE:
                Back= new BACKBUF(hWnd);
                BackImg= new IMAGE(hWnd);
                BackImg->LoadFile("c:\\data\\星空.jpg");
                Ship= new IMAGE(hWnd);
                Ship->LoadFile("c:\\data\\ship.gif",4,1);
                Csv.SetTable("ship.txt",tbl,2000);
                BackImg->Adjust(FALSE);
                SetTimer(hWnd,ID_TIMER,10,NULL);
                break;
        
  3. WM_TIMER: で pos の値を 0~1000 まで繰り返します。
    WM_PAINT: から描画するとチラツクので、WM_TIMER: から直接 Render() 関数で描画します。
            case WM_TIMER:
                pos= (pos+1)%1000;
                Render();
                break;
        
  4. WM_DESTROY: ではタイマを止めてリソースを開放します。
            case WM_DESTROY:
                KillTimer(hWnd,ID_TIMER);
                SAFE_DELETE(Back);
                SAFE_DELETE(BackImg);
                SAFE_DELETE(Ship);
                PostQuitMessage(0);
                return 0L;
        
  5. WinMain() は何時もと同じで、ウインドウを生成して表示するだけです。
  6. 背景とシップを描画する Render() 関数です。
    画面のチラツキを抑えるためにバックバッファに描画して一括転送します。
    tbl[] は最初の 1000 がX座標で、後の 1000 にY座標が格納されています。
        VOID  Render()
        {
            BackImg->Show(Back->hBackDC);
            Ship->Show(Back->hBackDC,3,tbl[pos],tbl[pos+1000]);
            Back->Blt();
        }
        

TEXT DATA の仕様

アニメーションなどに使用するデータでは個数が数千を超えることもあり、そのままタイプしたのでは膨大な量になるので、 TEXT DATA の記述仕様を定めて変換することにします。
  1. 繰り返し指定
    同じ値の繰り返しを定義します。
    記述形式展開結果
    1, 2 , 3 ,4 , 5 , 1 2 3 4 5 の単純定義
    (500) 0, 0 が 500 回
    (100) 50, (100) 70,50 が 100 回と 70 が 100 回
    (98)5, 6 , 7, 5 が 98 回続いたあとに、6,7
    (>1000)0, 1000 フレームになるまで 0 を繰り返す
    1 , 2, (>5)0, 1 2 0 0 0
  2. カウントアップ(ダウン)指定
    規則的に加算(減算)を繰り返します。
    加算(減算)の符号(+-)は必ず書きます。
    記述形式展開結果
    (5 +1)1, 1,2,3,4,5,
    (5+2) 7, 7,9,11,13,15,
    (4 -5) 50, 50, 45, 40, 35,
  3. 現在値の参照
    最後の値を引き継いで計算して行きます。
    記述形式展開結果
    (5 +1)1, (4 +2) *, 1,2,3,4,5, 7,9,11,13,
    (3 +3)0,(2 +2)*,(2 -3)*,0,3,6,8,10,7,4,1,
    (2 +3)2, (>5)*, 2,5,5,5,5,
    3, (>5 +1)*, 3,4,5,6,7,
  4. 速度調整指定
    数回に一度の割合で計算することで速度を落とします。
    記述形式展開結果
    (10 +2 :3) 20,20,20,20,22,22,22,24,24,24,26,
    (12 +1 :2)0, 0,0,1,1,2,2,3,3,4,4,5,5,
  5. 目標指定
    目標値までの値を等分して設定します。
    変換結果は整数ですが、計算は実数で行って下さい。(例えば 0.2 ずつ5回加えると1になる)
    記述形式展開結果
    (10 >0) 50,50,45,40,35,30, .....
    (5 >9)1, 1,3,5,7,9,
    (6 >1)3, 3,3,2,2,1,1,
    (100 >0) *,現在値から 100 回で 0 に戻す
  6. 範囲繰り返し指定
    繰り返しの範囲を {} で指定します。
    記述形式展開結果
    { 3, 0,1,5 }, 0,1,5,0,1,5,0,1,5,
    { 2, (3)1 , (2)5, } 1,1,1,5,5,1,1,1,5,5,
    { 2,(3 >5)1,(3 >1)5, }1,3,5,5,3,1,1,3,5,5,3,1,
  7. float タイプの仕様は int タイプに準じますが、速度調整(:3)は必要が無いので使えません。
    記述形式展開結果
    (20)0.0, 0.0 を 20 回
    (60 -0.01)1.0, 1.0 から 60 回 -0.01 を加える
    (10 +0.05)0, 0 から 10 回 +0.05 を加える
    (90 >0.0)*, 現在値から 90 回で 0.0 に戻す
    (>500 >2.0)*,500 フレームに合わせて、現在値から 2.0 に戻す

【演習】

超初心者のプログラム入門(Win32API C++)