インデックスに戻る ←前のチャプター →次のチャプター

3. 避けゲーを創る on Delphi

3-0.スケルトンを作る

避けゲーとはSTGと比べ「攻撃する」要素が無い、あるいは軽視されているゲームの事です。おたまがこれに当たります。自機と敵弾があればゲームとして成り立つので、本格的にSTGを創る前にとりあえずやってみるには良いジャンルです、と、ここらへんは、猫又刃さんのおっしゃっていることそのままです。

今回のチャプターの内容に沿って作ったソースとバイナリを用意しました。ダウンロードはこちらから。このファイルを解凍してそのまま使用してかまいません。


以下各部説明

まずQuadruple Dを用いたスケルトンを作る。

ここでは、

1.60FPSで描画されるウインドウモード表示
2.簡単にスプライトを表示できるようにする
3.キー入力を有効にする(キー入力さえ有効にすれば、ジョイパットからもJoytoKey等を使って入力できます)

以上3点を目標として、Quadruple DのDG-Caradチュートリアルのサンプルを改造してスケルトンを作りました。
その概要を簡単に記しておきます。

・Form1のOnCreateイベントで必要な各オブジェクトの生成、各変数の初期化を行ってプログラム開始
 ↓
・CPUに余裕がある限り、何度でもApplicationEvents1Idle手続きを呼んで、時間を計る。
 そして、17ms(ミリ秒)に1回、mainloop()手続きを呼ぶ。
 ↓
・mainloop()手続きが呼ばれたら、画面の表示を更新する処理を行う。
 (この際、makegraphic()手続きは、キャラクターの描画を行う)
 ↓
・プログラム終了時にForm1のOnDestroyイベントで各オブジェクトの開放を行う。

以下、Quadruple Dのサンプルから変更した部分について説明します。

Formとコンポーネント

formとオブジェクトインスペクタ
form1には、Quadruple Dのサンプルで既に使用されているApplecationEventsコンポーネントとDGCARADコンポーネントに加えてキーからの入力を可能とするために、DDIDEXコンポーネントを加えています。又、オブジェクトインスペクタで、ウインドウの大きさを640x480に固定するために、Form1のClientHeightを480、ClientWidthを640、更にForm1のBorderStyleをbsDialogとしています。

60FPSの実装

FPSの実装方法については、宗教論争といわれるほど、いろんな意見のあるところです。今回は、一番簡単で頭が悪そうな方法で約60FPSを実装します。

今回は、60FPSを実現するために、TimeGetTime関数を使用します。TimeGetTimeは、Windowsが起動してからの時間を単位ms(ミリ秒)で返します。これを用いて、1秒に60回画面を書き換えるプログラムを作ります。
TimeGetTimeを使用する際、下記のように、uses説にMMSystemを書き加える必要があります。


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

Quadruple Dのサンプルでは最初の説明ということもあって、FPSについて考慮してありません。
まず、60FPSを実装するには、ApplicationEvents1コンポーネントのOnIdleイベントを改造する必要があります。

CPUが暇になると、何回でも、ApplicationEvents1コンポーネントのOnidleイベントが呼び出されます。
これを利用して下記の様に、17ms(ミリ秒)ごとにmainloop()手続きが呼び出されるようにします。
このmainlloop()に画面の描画処理を入れていくことで、1秒間に約60回画面を書き換えるようにします。
ApplicationEvents1コンポーネントをクリックして、オブジェクトインスペクタのOnIdleをダブルクリックしてください。
今回改造する部分が表示されます。


////////////////////
//
//CPUが暇になったら真っ先に呼ばれる。
//
/////////////////////
procedure TForm1.ApplicationEvents1Idle(Sender: TObject;var Done: Boolean);
var
  m:integer;//mは次のTGTまでの時間をいれとく
begin
  //TimeGetTimeはWindowsが起動してからの時間をミリ秒で返す関数
            if TimeGetTime>=steptime+17 then//1000/60=16.7だいたい17ということで
            begin
            //メインループ
            mainloop();

            //次タイミングを決定
            steptime:=steptime+17;

          //次のタイミングまで残り3msまでは一気にとばす。
            m:=steptime-TimeGetTime;
            if m>3 then
              begin
//下の行の処理は、この間に他のアプリにCPU時間を分ける。じゃないと、OSがとても重くなる。
                Sleep(m-3);//このアプリは(m-3)ms処理を休む
              end;
            end
          else
            begin
              Sleep(0);//ここらへんの処理は、結構いろんな意見がある。
            end;
//以上メインループ

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

次に画面等の初期化と終了処理を作ります。

Form1のOnCreateイベントは、アプリが起動する際に呼び出されます。
同様にOnDestroyイベントはアプリ終了時に呼ばれます。
これを利用して初期化と終了処理を作ります。

TimeBeginPeriod()とTimeEndPeriod()は()内を同じ数字にした物をセットで記述します。
これにより、TimeGetTimeの精度をミリセカンドで指定することができます。
経験上、TimeBeginPeriod(1)とTimeEndPeriod(1)を記述しないと、まともに60FPSが出ません。
以上で、1秒に約60回mainloop()が実行されるようになります。


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

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

  TimeBeginPeriod(1);//TimeGetTimeの精度を上げる
end;

先ほど1秒に60回実行されるようにしたmainloop()の中身です。
Quadruple D3(以下QD3)付属マニュアルのDG-Carad第一章サンプルのOnIdleイベント手続きを改造しています。
QD3のサンプルから、見た目は少し変わっています。
余裕があるようなら、なぜこのように改造したのか考えてみて下さい。


////////////////////
//
//アプリケーションが終了時呼び出される
//
/////////////////////
procedure TForm1.FormDestroy(Sender: TObject);//Form1のOnDestroyイベント
begin
  TimeEndPeriod(1);//TimeBeginPeriod(1)とセットで記述する。
  //Sceneオブジェクトの解放。
  //Scene:=TSXScene.Create(DG);とセットだと思って下さい。
  //createしたものは、あとでfreeする必要があると覚えておいて下さい。    
  Scene.Free;
end;

////////////////////
//
//メインループの中身。1秒に60回呼ばれる。つまり1フレーム分の処理
//
/////////////////////
procedure TForm1.mainloop;
var
//DG.D3DDevice.TestcooperativeLevelの返り値を一時的に格納させる
  tmpHRESULT:HRESULT;
begin
  tmpHRESULT:=DG.D3DDevice.TestcooperativeLevel;
    
  if tmpHRESULT<>D3D_OK then
    begin
    //アプリをフル画面で起動したとき、Alt+Tabとかするとこっちにくる。普通はこっちにきません。
      Application.Terminate;//まあ、システムに何かがあったのは間違いないので、トラブルを避けるために今回は、単純にアプリケーション終了しちゃう。
      exit;
    end;
    
    
    //ここまでくるってことは、D3D_OKを得たと言うこと。生きてます。
      //描画開始
      DG.D3DDevice.BeginScene;


/////////ここでスプライトを置く
      makegraphic();
///////

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

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

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

簡単にスプライト表示できるようにする

2つ前のチャプターで言ったようにQuadruple Dのマニュアルのサンプルを読んだ方なら解ると思いますが、普通にスプライトを画面に表示しようとすると、その都度、毎回10行近いコードを書く必要があります。
単にスプライトを表示させるのに毎回面倒なことをしたくないので、楽をできるようにputspriteという名前の手続きを作りました。
ここには書いていませんが、ソースの最初の方(Interface部)のprivateの所にこの手続きの宣言も加えておいて下さい。(加えなければコンパイルが通らないのでわかると思いますが)
こうすることで、以降は、
putsprite(テクスチャ,x座標,y座標)
と書くだけで簡単にスプライトを表示させられます。


////////////////////
//
//スプライトを置く作業を簡単にするラッピングメソッド
//
/////////////////////
procedure TForm1.putsprite(ture:TDGtexture; x, y: integer);
//単純にスプライトを置く際に楽をできるようにします。
//毎回、下のような記述をするのはイヤでしょ?
begin

//Quadruplle DのマニュアルDG-Carad第一章の解説参照
      Scene.PushSprite(
        Vector2D(x,y),
        [

          SXVertexSP(0,0, $FFFFFFFF, 0.0,0.0),
          SXVertexSP(ture.Width, 0, $FFFFFFFF, ture.U, 0.0),
          SXVertexSP(0,ture.Height, $FFFFFFFF, 0.0,ture.V),
          SXVertexSP(ture.Width,ture.Height, $FFFFFFFF, ture.U,ture.V)
        ],
        ture,
        sxbAlpha,
        false,
        true//後に描画された物ほど手前に書かれるようにする
      );
end;

キー入力の実装

実は、DDIDEXコンポーネントをForm1に乗っけた時点で実装は終了しています。


次のチャプターから、自機を動かしていきます。
基本的には、TForm1.makegraphic に、画像をかけ、という命令を加えるだけです。
(どういうデータが必要か、ということも書き加えなければなりませんが。)

今の状態でコンパイルと実行をしてみてください。
真っ黒い画面が出れば成功です。
真っ黒である


インデックスに戻る ←前のチャプター →次のチャプター