AFsoft WebSite(エーエフソフト・ウェブサイト)
 

オペレーティング・システムについて

プログラミングについて
ホームページについて
キャドについて
電子カタログについて
書籍・雑誌
イベント
リンク集
DelphiXE3 [3D-FMX] 3Dアプリケーションテスト(2) 2014/04/30
 
前回に続き今回も少しだけテストプログラムとして簡単なゲームプログラムを作ってみます。
 
昔は2Dスクロールゲームで「リバーゲーム」という川下りをしてどれだけ進んだか?を競う単純なゲームがありましたが、これは、画面最下行に1行の文字を書くと勝手にコンソールがスクロールされる、という特性と、画面上の文字を読み取って自機が障害物にぶつかったかどうか?を判定させる、というものでしたが、3D、というより、GUI な Windowsプログラムでは、自動的にコンソールをスクロールさせる、という事は出来ません。画面スクロールは、画面スクロールをさせるプログラムを作る必要があります。衝突判定も、キャラクタ単位での判定は、画面上の1文字を読み取って、という事も出来ませんから、ドット単位で画面情報を読み取って判断させたり、各キャラの座標値から判定を行なったり、という事が必要になってきます。また、タイミングによっては、キャラのすり抜けが発生してしまい、それがまずい場合には、キャラ移動軌跡同士の交点計算を行なって交点が発生すれば衝突した、というような判定を行う必要があったりします。
 
というわけで、まずは、3D FireMonkey アプリケーションのプロジェクトを新規作成し、自機のキャラを円錐(Cone)とし、ライト、及び、ライト材質ソースを配置して、プロパティを設定します。画面の色は宇宙空間を想定して黒色にしておきます。自機をちゃんとした絵にしたい場合は、3D-CGソフトで自機データを作ってCOLLADA(.dae)として保存し、3Dモデル(Model3D)を使って表現すれば良いでしょう。
円錐を画面の向こう側に向けたいので、RotationAngle.X を -90 にします(X軸回転 -90°)。円錐の大きさは、Width=1、Height=2、Depth=1、にします。但し回転をしていますから、X=1,Y=1,Z=2、という感じです。
円錐の位置は、(0,0,-5)とし、Z位置は固定、X・Y値は -5〜5 の範囲で動ける事とします。X座標は左がマイナスで右がプラス、Y座標は上がマイナスで下がプラス、Z座標は手前側がマイナスで向こう側がプラス、です。

 
自機の移動は取り敢えず単純に、OnKeyDownイベントハンドラで記述します。
・・・
 type
  TForm1 = class(TForm3D)
   ・・・
  public
   { public 宣言 }
   my_x,my_y,my_z : single ;
  end;
 
const
 _MIN_X = -5.0 ;
 _MIN_Y = -5.0 ;
 _MAX_X = 5.0 ;
 _MAX_Y = 5.0 ;
 
var
 Form1: TForm1;
 
implementation
 
{$R *.fmx}
 
procedure TForm1.Form3DShow(Sender: TObject);
begin
 my_x := Cone1.Position.X ;
 my_y := Cone1.Position.Y ;
 my_z := Cone1.Position.Z ;
end;
 
procedure TForm1.Form3DKeyDown(Sender: TObject; var Key: Word;
 var KeyChar: Char; Shift: TShiftState);
var
 mv : Integer ;
begin
 mv := 0 ;
 Case(Key)of
 vkLEFT, vkNUMPAD4:  mv := 4 ;
 vkRIGHT, vkNUMPAD6: mv := 6 ;
 vkUP, vkNUMPAD8:   mv := 8 ;
 vkDOWN, vkNUMPAD2:  mv := 2 ;
 End;
 if (mv = 0) then begin
  Case(KeyChar)of
  '4': mv := 4 ;
  '6': mv := 6 ;
  '8': mv := 8 ;
  '2': mv := 2 ;
  end;
 end;
 //
 if (mv > 0) then begin
  Case(mv)of
  4: begin
    my_x := my_x - 0.5 ;
    if (my_x < _MIN_X) then
     my_x := _MIN_X ;
   end;
  6: begin
    my_x := my_x + 0.5 ;
    if (my_x > _MAX_X) then
     my_x := _MAX_X ;
   end;
  8: begin
    my_y := my_y - 0.5 ;
    if (my_y < _MIN_Y) then
     my_y := _MIN_Y ;
   end;
  2: begin
    my_y := my_y + 0.5 ;
    if (my_y > _MAX_Y) then
     my_y := _MAX_Y ;
   end;
  End;
  Cone1.Position.X := my_x ;
  Cone1.Position.Y := my_y ;
  Key := 0 ;
 end;
end;
仮想キーコード vkNUMPAD〜 が有効になっていない様子?なので、文字としてキー判定させるようにもしています。
 
さて、次に障害物です。
障害物は、単純に、球(Sphere)を表示することにします。障害物は、奥の方から手前に向かって移動してくるものとし、動的に生成され、X,Y座標は乱数で決めるものとします。
 type
  TForm1 = class(TForm3D)
   ・・・
  public
   { public 宣言 }
   my_x,my_y,my_z : single ;
   bl : array of TSphere ;
   bn : integer ;
  end;
・・・
procedure TForm1.Form3DShow(Sender: TObject);
begin
 ・・・
 bl := nil ;
 bn := 0 ;
end;
 
procedure TForm1.Form3DClose(Sender:TObject;var Action:TCloseAction);
var
 i : integer ;
begin
 for i := 0 to bn-1 do
  if (bl[i] <> nil) then
   bl[i].Free ;
 bl := nil ;
end;
 
// 障害物発生
procedure TForm1.BlockMake(n:integer) ;
var
 i,j : integer ;
begin
 bn := bn + n + 1;
 SetLength(bl, bn);
 
 for i := 0 to n do begin
  j := bn-n+i-1 ;
  bl[j] := nil ;
  try
   bl[j] := TSphere.Create(Self);
   bl[j].Visible := False ;
   bl[j].Name := 'Shpere' + IntToStr(j) ;
   bl[j].MaterialSource := LightMaterialSource2 ;
   bl[j].Parent := Form1 ;
   bl[j].Position.X := Random*10.0 - 5.0 ;
   bl[j].Position.Y := Random*10.0 - 5.0 ;
   bl[j].Position.Z := 5.0 ;
   bl[j].Width := 1 ;
   bl[j].Height:= 1 ;
   bl[j].Depth := 1 ;
   bl[j].Visible := True ;
  except
   ;
  end;
 end;
end;
障害物は動的配列の変数 bl 、個数を変数 bn で管理します。障害物用の色を示すライト材質ソースを配置しておきます。大きさは1×1×1 にしておきます。ある程度の乱数幅を持たせてもいいと思います。
 
障害物を移動させるには、タイマー割り込みで行います。自機の進行距離=合計時間によって、障害物を生成する数をコントロールし、障害物の移動速度を決めておきます。ゲームバランスを考えて、実行しながら調整すれば良いと思います。
procedure TForm1.Form3DShow(Sender: TObject);
begin
 ・・・
 Timer1.Interval := 100 ;
 Timer1.Enabled := True ;
end;
・・・
procedure TForm1.Timer1Timer(Sender: TObject);
begin
 Timer1.Enabled := False ;
 
 Inc(dist) ;
 Form1.Caption := '距離:'+IntToStr(dist);
 
 BlockMove ; // 障害物移動
 
 if ((dist mod 10) = 0) then
  BlockMake(dist div 100) ; // 障害物発生
 
 Timer1.Interval := 50 ;
 Timer1.Enabled := True ;
end;
・・・
// 障害物移動
procedure TForm1.BlockMove ;
var
 i : integer ;
begin
 for i := 0 to bn-1 do begin
  if (bl[i] <> nil) then begin
   bl[i].Position.Z := bl[i].Position.Z - 0.5 ;
   if (bl[i].Position.Z < -10.0) then begin
    bl[i].Free ;
    bl[i] := nil ;
   end;
  end;
 end;
end;
障害物は 0.05秒毎にタイマー割り込みで Z座標を 0.5 ずつ手前に移動させ、障害物が手前の端の方に来た=Z座標が -10 より小さくなった場合、その障害物は消えた=メモリ解放を行うようにします。メモリ解放をしないとすぐにメモリ不足になるかもしれません。メモリ解放済のメンバについては処理は行いません。
 
そして、当たり判定を行います。
ここでは簡単に、自機を立方体であると想定し、障害物も立方体であると想定して、X・Y・Z座標の範囲が重なっていないかどうか、だけで判断するものとします。当たり判定は、自機が移動した時と、障害物が移動した時に行うものとします。
procedure TForm1.Form3DKeyDown(Sender: TObject; var Key: Word;
var KeyChar: Char; Shift: TShiftState);
var
 mv : Integer ;
begin
 ・・・
  Cone1.Position.X := my_x ;
  Cone1.Position.Y := my_y ;
  
  if not(BlockConflict) then begin
   Timer1.Enabled := False ;
  end;
  
  Key := 0 ;
 end;
end;
・・・
procedure TForm1.Timer1Timer(Sender: TObject);
begin
 ・・・
 BlockMove ; // 障害物移動
 
 if not(BlockConflict) then begin
  exit ;
 end;
 
 ・・・
end;
・・・
// 障害物との当たり判定
// ret : True : セーフ False : 当たり
function TForm1.BlockConflict : Boolean ;
var
 i : integer ;
 x1,x2,y1,y2,z1,z2 : single ;
begin
 Result := True ;
 x1 := my_x - Cone1.Width / 2.0 ;
 x2 := my_x + Cone1.Width / 2.0 ;
 y1 := my_y - Cone1.Depth / 2.0 ;
 y2 := my_y + Cone1.Depth / 2.0 ;
 z1 := my_z - Cone1.Height/ 2.0 ;
 z2 := my_z + Cone1.Height/ 2.0 ;
 
 for i := 0 to bn-1 do begin
  if (bl[i] <> nil) then begin
   if ((bl[i].Position.X + bl[i].Width /2.0)<x1) then continue;
   if ((bl[i].Position.X - bl[i].Width /2.0)>x2) then continue;
   if ((bl[i].Position.Y + bl[i].Height/2.0)<y1) then continue;
   if ((bl[i].Position.Y - bl[i].Height/2.0)>y2) then continue;
   if ((bl[i].Position.Z + bl[i].Depth /2.0)<z1) then continue;
   if ((bl[i].Position.Z - bl[i].Depth /2.0)>z2) then continue;
   Result := False ;
   break ;
  end;
 end;
 
 // 爆発処理
 if not(Result) then begin
  LightMaterialSource1.Diffuse := $FFFF0000 ;
  LightMaterialSource1.Emissive := $FFFF0000 ;
  for i := 0 to 10 do begin
   Cone1.Width := Cone1.Width + 1.0 ;
   Cone1.Depth := Cone1.Depth + 1.0 ;
   Application.ProcessMessages ;
  end;
 end;
end;
当たった時、爆発したように見せる処理は、かなりいい加減です。赤色にして円錐を大きくしているだけです。
 
最後に、終わった事を示す変数 EndFlag を設定して、この変数が True になった場合には、キー操作もタイマー割り込みもしない、という風にしておきます。
 
ここでは行なっていませんが、何かキーを押したら次のゲーム、という風にするのも良いでしょうし、最高点を保存するようにするのも良いでしょう。
 
実行すると分かりますが、影がないと距離感が分からないかもしれません。しかし影を付けるのは相当に難しいと思います。地面を想定するのでしたら、地面の平面を作図し、平面上に、影となる黒い円を描画する、という事になるでしょう。けれども、各オブジェクト毎に外オブジェクトからの影を付ける、というのはおそらく相当に難しいでしょうし、処理速度もかなり掛かってしまう事になると思われますから、余り現実的ではないでしょう。
 

実行中
 

障害物に当たった=爆発
 
ゲームの再実行は、ここでは何もしていませんので、プログラムを終了し、再度、実行を行なって下さい。
 
 
ソース・実行プログラムのダウンロード(ZIP圧縮;3,229KB)
 
 
バッチファイル
BASIC
C言語のお勉強
拡張子な話
DOSプログラム
Delphi
>Dehi入門編
>Delphi2010
>DelphiXE3
▲2014/04/20
 2014/04/30
▼2014/05/01
 
シェアウェア
Script!World
データベース
 
お問い合わせ 
本サイトはリンクフリーです
リンクバナー
(C)Copyright 1999-2015. By AFsoft All Rights Reserved.