DG-Caradチュートリアル(1)

まず何か画面に出してみましょう

はじまりはじまり

 どうやってDG-Caradを使ったらいいのか、ダウンロードしたばかりでは全く見当も付かない、というのが普通でしょう。

 順を追ってゆっくり説明していきますのでご安心を。

 このチュートリアルでは、画面に画像を表示して、簡単に動かしてみるプログラムを作成します。

 まずはDelphiを起動して、新しいプロジェクトを作ってください。作ったら適当な名前を付けて保存しましょう。

 説明に使用するDelphiは、Delphi6 Professionalです。お持ちのDelphiのバージョンに合わせて適宜読み替えが必要になるかもしれませんので、その点はあらかじめご了承くださいね。

 

プロジェクトを作ったら

 まっさらなフォームに、コンポーネントパレットの[QuadrupleD]タブからDGCaradコンポーネントと、[Additional]タブからApplicationEventsコンポーネントを貼り付けます。

 DGCaradコンポーネントの名前は、DGとでもしておきましょうか。

 ApplicationEventsコンホーネントの名前はデフォルトのまま、ApplicationEvents1とでもしておきましょう。

 

 次に、uses節に使用するユニットを追加します。

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, AppEvnts, DGCarad9, SXLib9, d3d9, s_mathpack;

 3D描画ユニット「SXlib9」、DirectGraphicsユニット「d3d9」、演算ルーチン集「s_mathpack」をuses節に追加してください。

 

 チュートリアルで用いる変数やオブジェクトを追加します。

 水色になっている部分が追加するコードです。

type
TForm1 = class(TForm)
  DG: TDGCarad;
  ApplicationEvents1: TApplicationEvents;
  procedure FormCreate(Sender: TObject);
  procedure ApplicationEvents1Idle(Sender: TObject; var Done: Boolean);
  procedure FormResize(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    Scene:TSXScene;     //描画の面倒を見るオブジェクト
    Texture:TDGTexture; //画像入れ(テクスチャオブジェクト)
    Tick:Integer;       //画像を動かすためのカウンタ
end;

 

初期化と解放処理

 次に、解像度の設定やテクスチャの読み込みを行います。

 TForm.OnCreateイベントハンドラを書き換えます。

procedure TForm1.FormCreate(Sender: TObject);
begin
  //とりあえずクライアント領域のサイズに合わせて画面を初期化
  DG.WindowMode(ClientWidth, ClientHeight, []);
end;

 まず、DG.WindowModeメソッドを呼び、画面についての設定をします。

 WindowModeメソッドには多くのパラメータを指定できますが、今回のチュートリアルでは最初の3つ以外はデフォルトでかまいません。最初の2つの引数が解像度で、3つ目がデプスステンシルサーフェスの指定です。このチュートリアルでは使いませんので、空の配列を渡しておきます。

 

procedure TForm1.FormCreate(Sender: TObject);
begin
  //とりあえずクライアント領域のサイズに合わせて画面を初期化
  DG.WindowMode(ClientWidth, ClientHeight, []);

 //シーンオブジェクトの作成
  Scene:=TSXScene.Create(DG);
end;

 次に、シーンオブジェクトを作成します。TSXScene.Createメソッドで生成。引数にはDirectGraphicsを管理するTDGCaradオブジェクトを渡します

 シーンオブジェクトとは、画面に描かれる内容を管理するオブジェクトです。ポリゴンで描かれる物体や、その親子関係などはもとより、今回のチュートリアルで扱う、一枚の画像までについて、それぞれがどのように描画されるのかを記憶・管理します。

 

procedure TForm1.FormCreate(Sender: TObject);
begin
  //とりあえずクライアント領域のサイズに合わせて画面を初期化
  DG.WindowMode(ClientWidth, ClientHeight, []);

  //シーンオブジェクトの作成
  Scene:=TSXScene.Create(DG);

  //テクスチャの作成
  Texture:=TDGTexture.Create(DG, DGFMT_ARGB); //できればαチャンネル付きで
  Texture.BorderColor:=$00FFFFFF;             //ボーダー色は透明度0、白
  Texture.LoadFromFile('dg.bmp');             //画像読み込み

  //カウンタをリセット
  Tick:=0;
end;

 次に、テクスチャを作成します。

 テクスチャの作成は、TDGTexture.Createメソッドで行います。引数にはDirectGraphicsを管理するTDGCaradオブジェクトと、もう一つ、テクスチャのピクセルフォーマットを指定します。

 ここでピクセルフォーマットについて説明しましょう。一個のピクセルはR(赤) G(緑) B(青)の輝度と、不透明度を示すA(α)値で表されますが、このうちRが何ビットで、Gが何ビット…といった割り当てられ方は何通りかあり、その組み合わせのうちどれをサポートするかは環境によって異なります。ですので、

 今回のチュートリアルでは、DGFMT_ARGBを指定しました。これは配列定数で、「第一希望:D3DFMT_A8R8G8B8 (RGBA全部8ビットずつ)、第二希望:D3DFMT_A4R4G4B4(ARGB全部4ビットずつ)、第三希望:D3DFMT_A1R5G5B5(Aだけ1ビット、RGB5ビットずつ)」の順で格納されています。

 テクスチャオブジェクト自体を作成したら、中に画像を読み込みます。

 画像を読み込む前に、ボーダー色を指定します。 ほとんどの環境で、テクスチャの一辺のピクセル数は、内部的に2の整数冪(2,4,8,16,32,64, ... )に制限されるのですが、読み込み元の画像の一辺のピクセル数は必ずしも2の整数冪ではないので、テクスチャには隙間ができることがあります。その隙間を、ボーダー色で埋めてやるのです。表示には直接影響が無いことが多いので指定しなくても良いのですが、知っておきたい事の一つとしてあげておきました(^^;)

 ボーダー色も指定したら、実際に画像を読み込みます。LoadFromFileメソッド一発でラクラク。

 また、チュートリアルで使用する変数を初期化しましょう。今回は、カウンタとして使うTickだけですね。

 

 オブジェクトを作ったら、終了時にきちんと解放できるようなコードも追加しておきましょう。

 Form.OnCloseQueryイベントに、以下をくわえます。

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  //解放
  Texture.Free;
  Scene.Free;
end;

 特にヒネった部分はないですね。

 実際にはDG-Caradの解放とともに、TDGTexture, TSXSceneなど、Create時の引数にTDGCaradオブジェクトが必要になるようなオブジェクトは、DG-Caradオブジェクトの解放とともに、一緒にDG-Caradが解放してくれるのですが、自分で確実に解放しておいたほうが気持ちがよいという方も多いでしょうから、ここに付け加えて起きます。

 

描画ループの作成

 ApplicationEvents.OnIdleイベントハンドラを書き換えて、画像を動き回らせましょう。

 ApplicationEvents.OnIdleイベントは、ウィンドウメッセージの処理が行われていないときに呼ばれるイベントであり、ゲームのメインループのように繰り返し行われる処理を書くのに適しています。

 さて、いざ描画!と行きたい所なんですが、Windowsはご存知の通りマルチタスクOSでして、一度に色々なプログラムが同時に走っています。このことはDG-Caradを使って書かれるプログラムにも少なからず影響を与えます。

 例えば、貴方の作ったDG-Caradアプリをアイコン化してる間に他のDirectGraphicsアプリを起動したとしましょう。すると、今まで使っていたDirectGraphics関係のオブジェクト(テクスチャ用のメモリを管理するオブジェクトなど)はWindowsによって没収される事があるのです。「なんじゃそりゃあ!」という感じかもしれませんが、リソースの有効活用などのため、仕方ないことのようです(^^;)

 ともあれ、面倒な話ですが、DirectGraphics関係のオブジェクトをWindowsによって没収されてないかどうかをチェックした上で描画にとりかかる必要があるのです。そのために、DG.D3DDevice.TestCooperativeLevel メソッドを呼びます。

 TestCooperativeLevelメソッドの返り値は以下の3つのうちいずれかですので、場合分けをしてやればOKと。

D3D_OK OK、普通に使えます
D3DERR_DEVICELOST 没収されました。帰ってくるまで待ちましょう
D3DERR_DEVICENOTRESET 没収された状態から帰ってましたけど、一旦DirectGraphics関係のオブジェクトをリセットしなければなりません

 さて、D3D_OKなら描画ができ、D3DERR_DEVICELOSTの場合は待てば良いのですが、最後のD3DERR_DEVICENOTRESETの場合はどうしましょう。ここでは通常、画像の読み直しなどの処理を書くのですが、DG-Caradが画像の読み直しなどは半自動的に行うため、DG.Resetメソッドを呼ぶだけでこの処理は終了します。

 以上を踏まえて、場合分けをするところまでコードを書くと、こうなります。

procedure TForm1.ApplicationEvents1Idle(Sender: TObject;
  var Done: Boolean);
begin
  //Direct3Dデバイスが生きてるかテストしてから描画
  Case DG.D3DDevice.TestCooperativeLevel of
    //OK、生きてます
    D3D_OK: begin
      // ここに後で、描画ルーチンなんかを書き入れます
    end;

    //デバイスは生きてるけど、ロスト後でリセットされてない
    D3DERR_DEVICENOTRESET: begin
      DG.Reset;
    end;
  end;

  //これを設定すると、他のイベントが実行されなくてもまたこのイベントが呼ばれる
  Done:=False;
end;

 これに実際に描画を行うコードを書きましょう。TestCooperativeLevelがD3D_OKで分岐する先に書けばいいわけです。

 ただし、描画を行うには、TCanvasのCopyRectのように一発で終わるわけではないです。今回のチュートリアルでは、以下のステップで描画ルーチンを作ります。

  1. DirectGraphicsに描画開始を伝える.

  2.  画面に今まで描かれていた内容を消去

  3.  シーンオブジェクトに画面に描画する内容(今回は、画像を一枚)を記憶させる

  4.  シーンオブジェクトが記憶している内容を画面に出させる

  5. DirectGraphicsに描画終了を伝える

  6. DirectGraphicsに、画面を更新させる

 あとは、画像を動かすためのカウンタを増加させるくらいです。

 コードにするとこんな感じ。

procedure TForm1.ApplicationEvents1Idle(Sender: TObject;
  var Done: Boolean);
begin
  //Direct3Dデバイスが生きてるかテストしてから描画
  Case DG.D3DDevice.TestCooperativeLevel of
    //OK、生きてます
    D3D_OK: begin

      //描画開始
      DG.D3DDevice.BeginScene;

      //画面のクリア、色は水色で(色は上位バイトからARGBの順で指定します)
      Scene.Clear(D3DCLEAR_TARGET, $FF40C0FF, 1.0, 0);

      //画像を一個置け
      Scene.PushSprite(
        Vector2D(Tick,50),
        [

          SXVertexSP(0,0, $FFFFFFFF, 0.0,0.0),
          SXVertexSP(Texture.Width, 0, $FFFFFFFF, Texture.U, 0.0),
          SXVertexSP(0,Texture.Height, $FFFFFFFF, 0.0,Texture.V),
          SXVertexSP(Texture.Width,Texture.Height, $FFFFFFFF, Texture.U,Texture.V)
        ],
        Texture,
        sxbAlpha,
        False
      );

      //シーンの内容を全部描く
      Scene.Render(Nil);

      //描画終り
      DG.D3DDevice.EndScene;

      //表示
      DG.D3DDevice.Present(Nil, Nil, 0, Nil);
    end;

    //デバイスは生きてるけど、ロスト後でリセットされてない
    D3DERR_DEVICENOTRESET: begin
      DG.Reset;
    end;
  end;

  //カウンタ増やす
  Inc(Tick);

  //右端まで言ったら左端から出てくるようにする
  if Tick > ClientWidth then
    Tick:=-Texture.Width;

  //これを設定すると、他のイベントが実行されなくてもまたこのイベントが呼ばれる
  Done:=False;
end;

 このうち問題になりそうなScene.PushSpriteメソッドについて補足しておきましょう。

 まず第1引数は、画像を表示する際の原点を、画面上の座標で指定します。この例では(Tick, 50)を指定する事で、Tickの増加に伴って画像は右に動くことになります。

 第2引数は、画像の4隅の頂点についての情報を表した配列です。四隅の頂点を、左上、右上、左下、右下の順で格納します。SXVertexSP関数で、頂点データを作ります。

 尚、SXVertexSP関数への引数は、順番に( X座標、Y座標、 色、 テクスチャ座標U、テクスチャ座標V) となっています。テクスチャ座標(U,V)というのは、テクスチャのどこから画像を持ってくるかという情報で、テクスチャの左上隅を(0,0)、右下隅を(1,1)とした、小数で表される座標です。

 PushSpriteメソッドの引数に話を戻しましょう。

 第3引数は画像として貼り付けるテクスチャオブジェクトです。

 第4引数はアルファブレンディング(元から画面に描かれていた内容と、これから描かれる内容をどう合成するか)についての指定です。sxbAlphaを指定すると、画像のα値に従って背景と画像を混ぜ合わせた色が描かれます。例えば、α=127ならばちょうど中間の色、α=255ならば画像の色となり、0ならば背景の色(つまりも何も描かれない)になります。

 第5引数は、拡大・縮小時のフィルタリングの指定です。フィルタリングなし(False)で画像を拡大すると画像のドットのギサギザが目立つようになりますが、フィルタリングあり(True)では、それが目立たなくなります。

 やたら面倒ですが色々指定が出来るので、回転拡大縮小など、このメソッドだけで自由に行うことが可能です。

 ちなみに、PushSpriteLiteを使うと引数を簡略化できます。回転とか要らないからもっとサクサク使わせろよという場合にどうぞ。

 

できたかな?

 

 画像が左から右へゆっくり進んでいけば成功。

 画像一枚表示させるのでもかなり大変だった気がしますが、汎用性重視なので、実際のゲームなどを作成される場合はそれほど大変とも感じないのではないでしょうか。