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

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

プログラミングについて
ホームページについて
キャドについて
電子カタログについて
書籍・雑誌
イベント
リンク集
20:簡単ゲームソフトを作ってみよう
 
最後に、簡単ゲームソフト、ここでは、落ちてくる敵を撃って得点を数えるという単純なシューティングを作ってみます。簡略化のため、自機や敵は文字(Labelコンポーネント)を使っています。グラフィックキャラクタを使いたい場合には、イメージ Image コンポーネントを使うと良いでしょう。
 
ゲームといっても様々な種類があります。シューティング、アクション、アドベンチャー、ロールプレイング、シミュレーション、パズル、等々。どういうゲームを作りたいのかを決め、シナリオ、キャラクターや背景図、サウンド、そしてプログラミングを進めていきますが、ゲームクリア条件、ゲームオーバー条件、コンティニューの可否、そしてゲームバランスをちゃんと行う必要がありますが、プレイヤーに楽しんでもらえるような工夫も必要でしょう。本格的なゲームソフトを作るのは大変ですが、最初は、簡単なものから始めてみて下さい。アイデア次第では面白いゲームが作れるかもしれません。
 
 
まずは、フォルダの準備を行います。プログラムを作成して保存・作業を行うフォルダを予めエクスプローラで作成しておきます。Windowsに添付されているファイル管理ツール「エクスプローラ」を動かします。Windowsの「スタート」メニューから、「プログラム」メニューの中、或いは、「プログラム」メニューの中の「アクセサリ」内に「エクスプローラ」はあります。
 
画面左側のツリー表示の中の「マイ コンピュータ」内にある、「ローカル ディスク(C:)」内の「DelphiProgram」フォルダ内の「test」フォルダをクリックして反転表示(選択状態)にします。
 
メニュー「ファイル」→「新規作成」→「フォルダ」をクリックして選択します。

初期状態が「新しいフォルダ」という名前でキー入力編集状態となっています。フォルダ名を「chap20」と決めて入力します。最後に[Enter]キー(改行キー)を押すか、どこかでマウス左クリックして下さい。
 
画面左側のツリー表示の中に、
「ローカル ディスク(C:)」
 →「DelphiProgram」
  →「test」
   →「chap20」
というフォルダが新しく作成されるのを確認して下さい。

Delphi6を起動し、
Delphi6のメニュー「プロジェクト」内の「オプション」をクリックします。

「プロジェクト オプション」画面が表示されます。
「アプリケーション」ページをクリックし、タイトルを
「GAME1」と入力します。

「ディレクトリ/条件」ページをクリックし、
「ディレクトリ」内の設定
 「出力ディレクトリ」
 「ユニット出力ディレクトリ」
 「検索パス」
 「デバッグ用ソースパス」
 「BPL出力ディレクトリ」
 「DCP出力ディレクトリ」
の各設定を
「C:\DelphiProgram\test」
から
「C:\DelphiProgram\test\chap20」
に変更して[OK]ボタンをクリックして下さい。

プログラミング作業は何も行っていませんが、最初にまず保存を行っておきます。
 
メニュー「ファイル」→「名前を付けて保存」をクリックして下さい。

「Unit1に名前を付けて保存」画面が表示されます。「保存する場所」には、先に作成したフォルダ「C:\DelphiProgram\test\chap20」を選択します。
ファイル名は「Unit1.pas」のまま[保存]ボタンをクリックして下さい。

メニュー「ファイル」→「プロジェクトに名前を付けて保存」をクリックして下さい。

「Project1 に名前を付けて保存」画面が表示されます。
「保存する場所」は、先で既に「C:\DelphiProgram\test\chap20」を選択していますので、そのままで構いません。
ファイル名は「Game1」と変更入力して、[保存]ボタンをクリックして下さい。
 
これで最初の準備が出来ました。
これから簡単ゲームソフトの作成を行っていきます。
 
オブジェクトインスペクタ画面でフォーム Form1 の BorderStyle プロパティ欄を選択すると右横に[▼]ボタンがありますのでこのボタンをクリックし、「bsSingle」に変更します。
BorderIcons プロパティで、最大化・最小化を出来ないよう False に指定しておきます。
Caption プロパティ欄を選択し、「Game1」と入力します。
Color プロパティ欄を選択し、黒色(clBlack)に指定します。

オブジェクトインスペクタ画面でフォーム Form1 の Font プロパティ欄を選択して指定します。
Name を「MS ゴシック」
Size を「24」
にしています。

コンポーネントパレット[Standard]の中にある「Panel」コンポーネントをクリックして選択します。

フォーム Form1 画面上でクリックして Panel1 オブジェクトを配置します。

オブジェクトインスペクタ画面でPanel1 オブジェクトの Align プロパティ欄を選択すると右横に[▼]ボタンがありますのでこのボタンをクリックし、「alBottm」にします。パネル Panel1 は画面の下の部分に貼り付いた状態となります。

オブジェクトインスペクタ画面でPanel1 オブジェクトの Caption プロパティ欄を選択し、「Panel1」という文字を消して何も入力していない状態にします。

Panel1 を選択した状態で Label コンポーネントを選択して Label1 オブジェクトを Panel1 オブジェクトの上に配置します。

Label1 オブジェクトの Font プロパティで Size を小さくして左側の方へ移動します。
更に、Form1 の上に Label2、Label3 オブジェクトを配置し、それぞれの Caption プロパティを
「n▲n」「|」として Font プロパティ内の Color プロパティで色を指定します。
Label2 が自機、Label3 が 発射するビームとします。

オブジェクトインスペクタ画面で Form1 の OnShow イベント欄を選択してダブルクリックします。
ここには、本ソフトを起動した時の初期設定等を行うようコーディングします。

オブジェクトインスペクタ画面で Form1 の OnClose イベント欄を選択してダブルクリックします。
ここには、本ソフトを終了した時の最終処理等を行うようコーディングします。

オブジェクトインスペクタ画面で Form1 の OnKeyDown イベント欄を選択してダブルクリックします。
ここには、Form1 上でキーボードを押した時の処理を行います。
自機は、[←]又はテンキーの[4]を押した時、左側へ移動、[→]又はテンキーの[6]を押した時、右側へ移動、空白キー(スペースキー)を押した時、ビーム発射、としますのでそのキーを押した時の処理をここで行います。
 
コードエディタ画面に
・・・
type
 TForm1 = class(TForm)
  ・・・
 private
  { Private 宣言 }
 public
  { Public 宣言 }
 end;
 
var
 Form1: TForm1;
 
implementation
 
{$R *.dfm}
 
procedure TForm1.FormShow(Sender: TObject);
begin
 
end;
 
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
 
end;
 
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
 Shift: TShiftState);
begin
 
end;
 
end.
というプログラムコードが追加されます。これを下記のように記述します。
・・・
type
 TForm1 = class(TForm)
  ・・・
 private
  { Private 宣言 }
  pnts : integer ; // 得点
  scx,scy : integer ; // 画面大きさ
  myx, myy, myw, myh : integer ; // 自機
 public
  { Public 宣言 }
 end;
 
var
 Form1: TForm1;
 
implementation
 
{$R *.dfm}
 
// 起動時
procedure TForm1.FormShow(Sender: TObject);
begin
 pnts := 0 ;
 scx := Form1.ClientWidth ;
 scy := Form1.ClientHeight - Panel1.Height ;
 myx := Form1.ClientWidth div 2 ;
 myy := Label2.Top ;
 myw := Label2.Width div 2 ;
 myh := Label2.Height ;
 
 Label1.Caption := '得点:0' ;
 Label2.Left := myx - myw ;
 Label3.Visible := False ;
end;
 
// 終了時
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
 ;  // 現在はなにも行わない
end;
 
// キーを押した場合の処理
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
 Shift: TShiftState);
begin
 if (Key = VK_LEFT)or(Key = VK_NUMPAD4) then begin
  myx := myx - 8 ;
  if (myx < myw) then myx := myw ;
  Label2.Left := myx - myw ;
 end
 else if (Key = VK_RIGHT)or(Key = VK_NUMPAD6) then begin
  myx := myx + 8 ;
  if (myx > scx-myw) then myx := scx-myw ;
  Label2.Left := myx - myw ;
 end ;
 Key := 0 ;
end;
 
end.
ビームや敵、自機の移動範囲などは画面サイズ内で行いますので、Form1 のクライアント領域の大きさ(pix.) ClientWidth、ClientHeight を参照しますが、何度も Form1.Client〜と書くのは面倒なので変数 scx,scy に代入しています。自機の位置(pix.)は myx,myy に入れています。myxは水平中央位置、myyは垂直上端位置です。myw,myh は自機の大きさ(pix.)です(処理の都合上、mywは半分の大きさです)。左右への移動は8pix.ずつ動かすようにしています。もっと速く(粗く)移動したい場合には数値を大きく、もっと遅く(細かく)移動したい場合には数値を小さくしてください。OnKeyDown イベント内でのキー Key は仮想キーコード(VK_〜)となりますので注意して下さい。画面の外に出ないよう if文で判定しています。
 
メニュー「ファイル」→「すべて保存」をクリックしてプログラムの保存を行い、メニュー「プロジェクト」→「Game1 を再構築」をクリックしてプログラムのコンパイル(再構築)を行い、正常終了したら、メニュー「実行」→「実行」をクリックして下さい。どういう動きをするのか確認して下さい。
 
さて、次にビームですが、スペースキーを押した時にビームを表示させるのは良いとして、ビーム移動をどこで処理をさせるのかが問題となります。イベントドリブン形式のプログラミングは、何かをした場合、それに応じたイベントハンドラを動かす事になりますが、逆に、何もしていない場合には、何も動きません。こういう場合には、タイマー割り込みを可能にする タイマー Timer コンポーネントを利用します。何をしていなくても、指定した時間の後にイベントハンドラ OnTimer が実行されます。その中で、割り込み禁止をさせ、行いたい処理の後、再度、タイマーをリセットして割り込み許可を行い再び指定時間後にイベントハンドラが動くようにします。こうして、指定時間毎に同じ処理を何度も行わせる事によって、何もしていない場合にも何らかの処理を行わせる、という事が出来るようになります。
 
コンポーネントパレット[System]の中にある「Timer」コンポーネントをクリックして選択します。

フォーム Form1 画面上でクリックして Timer1 オブジェクトを配置します。

オブジェクトインスペクタ画面でTimer1 オブジェクトの Intervalプロパティ欄を選択し、「100」と入力します。この数値はミリ秒単位で指定します。100ミリ秒後にタイマーイベントが発生する、という事になります。
この数値は最初のタイマーイベント発生の際の数値で、2回目以降はプログラム内で指定します。

オブジェクトインスペクタ画面で Timer1 の OnTimer イベント欄を選択してダブルクリックします。
 
コードエディタ画面で下記のようにプログラムを追加記述します。
・・・
type
 TForm1 = class(TForm)
  ・・・
 private
  { Private 宣言 }
  pnts : integer ; // 得点
  scx,scy : integer ; // 画面大きさ
  myx, myy, myw, myh : integer ; // 自機
  beamf : Boolean ; // ビームONOFF
  bex, bey, bew, beh : integer ; // ビーム
 public
  { Public 宣言 }
 end;
 
var
 Form1: TForm1;
 
implementation
 
{$R *.dfm}
 
// 起動時
procedure TForm1.FormShow(Sender: TObject);
begin
 pnts := 0 ;
 scx := Form1.ClientWidth ;
 scy := Form1.ClientHeight - Panel1.Height ;
 myx := Form1.ClientWidth div 2 ;
 myy := Label2.Top ;
 myw := Label2.Width div 2 ;
 myh := Label2.Height ;
 beamf:= False ;
 bew := Label3.Width div 2 ;
 beh := Label3.Height ;
 
 Label1.Caption := '得点:0' ;
 Label2.Left := myx - myw ;
 Label3.Visible := False ;
end;
 
// 終了時
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
 Timer1.Enabled := False ;
end;
 
// キーを押した場合の処理
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
 Shift: TShiftState);
begin
 if (Key = VK_LEFT)or(Key = VK_NUMPAD4) then begin
  myx := myx - 8 ;
  if (myx < myw) then myx := myw ;
  Label2.Left := myx - myw ;
 end
 else if (Key = VK_RIGHT)or(Key = VK_NUMPAD6) then begin
  myx := myx + 8 ;
  if (myx > scx-myw) then myx := scx-myw ;
  Label2.Left := myx - myw ;
 end
 else if (Key = VK_SPACE) then begin
  if not(beamf) then begin
   beamf := True ;
   bex := myx ;
   bey := myy ;
   Label3.Left := bex - bew ;
   Label3.Top := bey ;
   Label3.Visible := True ;
  end ;
 end ;
 Key := 0 ;
end;
 
// タイマー割り込み
procedure TForm1.Timer1Timer(Sender: TObject);
begin
 Timer1.Enabled := False ; // 割り込み禁止
 
 // ビームの動き
 if (beamf) then begin
  bey := bey - 8 ;
  Label3.Top := bey ;
  if (bey < 0) then begin
   beamf := False ;
   Label3.Visible := False ; // 隠す
  end;
 end;
 
 Timer1.Interval := 100 ; // 割り込み再開
 Timer1.Enabled := True ;
end;
 
end.
ビームは最初は表示されておらず、空白キーを押すと発射・表示されて移動され、画面上まで行くと消えます。ですので非表示・表示の状態がありますのでこれを変数 beamf で管理します。ビームの位置(pix.)は変数 bex,bey、ビームの大きさ(pix.)は変数 bew,beh(処理の都合上、bewは半分の大きさ)です。ビームは自機の真上方向へ8pix.ずつ移動させています。一番上まで行くと消します。タイマ割り込みのイベントハンドラの最後で、再度イベントが発生するようリセットします。
 
メニュー「ファイル」→「すべて保存」をクリックしてプログラムの保存を行い、メニュー「プロジェクト」→「Game1 を再構築」をクリックしてプログラムのコンパイル(再構築)を行い、正常終了したら、メニュー「実行」→「実行」をクリックして下さい。どういう動きをするのか確認して下さい。
 
次に、敵を考えます。
 
コンポーネントパレット[Standard]の中にある「Label」コンポーネントをクリックして選択し、フォーム Form1 画面上でクリックして Label4 オブジェクトを配置します。
 
オブジェクトインスペクタ画面でLabel4 オブジェクトの Caption プロパティ欄を選択し「◎」と入力します。敵の図柄ですが別に何でも構いません。

オブジェクトインスペクタ画面でLabel4 オブジェクトの Font プロパティ内の Color プロパティを茶色(clMaroon)に、Size プロパティを「32」にします。敵の色と大きさです。別に何でも構いません。

オブジェクトインスペクタ画面で Label4 オブジェクトの Transparent プロパティを「True」にします。同じく、Label3 オブジェクトも行います。キャラクタが重なり合った場合に Label文字背景を透明にしておいて、背景部分で相手側を消さないようにします。
 
コードエディタ画面で下記のようにプログラムを追加記述します。
・・・
type
 TForm1 = class(TForm)
  ・・・
 private
  { Private 宣言 }
  pnts : integer ; // 得点
  scx,scy : integer ; // 画面大きさ
  myx, myy, myw, myh : integer ; // 自機
  beamf : Boolean ; // ビームONOFF
  bex, bey, bew, beh : integer ; // ビーム
  tekif : Boolean ; // 敵ONOFF
  tex, tey, tew, teh : integer ; // 敵
 public
  { Public 宣言 }
 end;
 
var
 Form1: TForm1;
 
implementation
 
{$R *.dfm}
 
// 起動時
procedure TForm1.FormShow(Sender: TObject);
begin
 pnts := 0 ;
 scx := Form1.ClientWidth ;
 scy := Form1.ClientHeight - Panel1.Height ;
 myx := Form1.ClientWidth div 2 ;
 myy := Label2.Top ;
 myw := Label2.Width div 2 ;
 myh := Label2.Height ;
 beamf:= False ;
 bew := Label3.Width div 2 ;
 beh := Label3.Height ;
 tekif:= False ;
 tew := Label4.Width div 2 ;
 teh := Label4.Height ;
 tex := Random(scx - tew*2) ;
 tey := 0 ;
 
 Label1.Caption := '得点:0' ;
 Label2.Left := myx - myw ;
 Label3.Visible := False ;
 Label4.Visible := False ;
end;
 
// 終了時
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
 Timer1.Enabled := False ;
end;
 
// キーを押した場合の処理
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
 Shift: TShiftState);
begin
 if (Key = VK_LEFT)or(Key = VK_NUMPAD4) then begin
  myx := myx - 8 ;
  if (myx < myw) then myx := myw ;
  Label2.Left := myx - myw ;
 end
 else if (Key = VK_RIGHT)or(Key = VK_NUMPAD6) then begin
  myx := myx + 8 ;
  if (myx > scx-myw) then myx := scx-myw ;
  Label2.Left := myx - myw ;
 end
 else if (Key = VK_SPACE) then begin
  if not(beamf) then begin
   beamf := True ;
   bex := myx ;
   bey := myy ;
   Label3.Left := bex - bew ;
   Label3.Top := bey ;
   Label3.Visible := True ;
  end ;
 end ;
 Key := 0 ;
end;
 
// タイマー割り込み
procedure TForm1.Timer1Timer(Sender: TObject);
begin
 Timer1.Enabled := False ; // 割り込み禁止
 
 // ビームの動き
 if (beamf) then begin
  bey := bey - 8 ;
  Label3.Top := bey ;
  if (bey < 0) then begin
   beamf := False ;
   Label3.Visible := False ; // 隠す
  end;
 end;
 
 // 敵の動き
 if (tekif) then begin
  tey := tey + 4 ;
  Label4.Top := tey ;
  if (tey > scy) then begin
   tekif := False ;
   Label4.Visible := False ; // 隠す
  end ;
 end
 else begin
  tex := Random(scx - tew*2) ;
  tey := 0 ;
  tekif:= True ;
  Label4.Left := tex - tew ;
  Label4.Top := tey ;
  Label4.Visible := True ; // 敵出現
 end;
 
 Timer1.Interval := 100 ; // 割り込み再開
 Timer1.Enabled := True ;
end;
 
end.
敵(Label4)は最初は表示されておらず、タイマーイベント内で、表示されていない時には出現させ、敵が表示されている場合には敵を移動させています。出現位置は、水平位置はランダム・垂直位置は一番上、下方向へ真っ直ぐ移動し、画面下まで行くと消えます。敵もビーム同様に非表示・表示の状態がありますので変数 tekif で管理します。敵の位置(pix.)は変数 tex,tey、敵の大きさ(pix.)は変数 tew,teh(処理の都合上、tewは半分の大きさ)です。敵は下方向へ4pix.ずつ移動させています。
 
メニュー「ファイル」→「すべて保存」をクリックしてプログラムの保存を行い、メニュー「プロジェクト」→「Game1 を再構築」をクリックしてプログラムのコンパイル(再構築)を行い、正常終了したら、メニュー「実行」→「実行」をクリックして下さい。どういう動きをするのか確認して下さい。
 
次に、ビームと敵の当たり判定を考えます。
 
ここでは、ビームの表示座標範囲と、敵の表示座標範囲が重なっていれば「当たり」と判定します。ビームの表示座標範囲は、
bex-bew,bey  bex+bew,bey
bex-bew,bey+beh  bex+bew,bey+beh
ですが、「|」は細いので、bewの半分の値を変数 bw に入れ、
bex-bw,bey  bex+bw,bey
bex-bw,bey+beh  bex+bw,bey+beh
としています。
敵の表示範囲は、
tex-tew,tey  tex+tew,tey
tex-tew,tey+teh  tex+tew,tey+teh
となります。
 
ビームが移動した直後に敵と当たったかどうか、及び、敵が移動した直後に敵の方からビームに当たったかどうか、の2回、判断させます。
 
// タイマー割り込み
procedure TForm1.Timer1Timer(Sender: TObject);
var
 bw : integer ;
 i,c : integer ;
begin
 bw := bew div 2 ; // ビーム絵の幅
 Timer1.Enabled := False ; // 割り込み禁止
 
 // ビームの動き
 if (beamf) then begin
  bey := bey - 8 ;
  Label3.Top := bey ;
  if (bey < 0) then begin
   beamf := False ;
   Label3.Visible := False ; // 隠す
  end;
 end;
 
 // ビームと敵の当たり判定1
 if (beamf)and(tekif) then begin
  if ((bex+bw) >= (tex-tew))and((bex-bw) <= (tex+tew)) then begin
   if ((bey+beh) >= tey)and(bey <= (tey+teh)) then begin
    // 当たり!
    c := Label4.Font.Color ;
    for i:=1 to 1000 do begin
     Label4.Font.Color := RGB(Random(256),Random(256),Random(256));
     Label4.Update ;
    end;
    Label4.Font.Color := c ;
    tekif := False ; Label4.Visible := False ;
    beamf := False ; Label3.Visible := False ;
    pnts := pnts + 1 ;
    Label1.Caption := '得点:' + IntToStr(pnts) ;
   end;
  end;
 end;
 
 // 敵の動き
 if (tekif) then begin
  tey := tey + 4 ;
  Label4.Top := tey ;
  if (tey > scy) then begin
   tekif := False ;
   Label4.Visible := False ; // 隠す
   // 地面に激突!
   for i:=1 to 1000 do begin
    Panel1.Color := RGB(Random(256),Random(256),Random(256));
    Panel1.Update ;
   end;
   Panel1.Color := clBtnFace ;
   pnts := pnts - 10 ;
   Label1.Caption := '得点:' + IntToStr(pnts) ;
  end ;
 end
 else begin
  tex := Random(scx - tew*2) ;
  tey := 0 ;
  tekif:= True ;
  Label4.Left := tex - tew ;
  Label4.Top := tey ;
  Label4.Visible := True ; // 敵出現
 end;
 
 // ビームと敵の当たり判定2
 if (beamf)and(tekif) then begin
  if ((bex+bw) >= (tex-tew))and((bex-bw) <= (tex+tew)) then begin
   if ((bey+beh) >= tey)and(bey <= (tey+teh)) then begin
    // 当たり!
    c := Label4.Font.Color ;
    for i:=1 to 1000 do begin
     Label4.Font.Color := RGB(Random(256),Random(256),Random(256));
     Label4.Update ;
    end;
    Label4.Font.Color := c ;
    tekif := False ; Label4.Visible := False ;
    beamf := False ; Label3.Visible := False ;
    pnts := pnts + 1 ;
    Label1.Caption := '得点:' + IntToStr(pnts) ;
   end;
  end;
 end;
 
 Timer1.Interval := 100 ; // 割り込み再開
 Timer1.Enabled := True ;
end;
 
end.
ビームと敵の当たり判定1・2は同じ内容です。ビームと敵の双方が表示されていて、表示位置が重なる場合、当たりとして、色をランダムに1000回変えて表示させ(当たって爆発しました、という効果のつもり)ビームと敵を非表示にして、得点を1点プラスします。
また、敵が地面に激突した場合、ペナルティとして、得点を -10しています。
 
ビームと敵の当たり判定1・2で同じ事を2回書くのは大変ですし、後で修正した際に、一方だけ修正してもう一方を修正し忘れたという事のないように、この部分を手続きにしてしまいます。
 
・・・
type
 TForm1 = class(TForm)
  ・・・
 private
  { Private 宣言 }
  pnts : integer ; // 得点
  scx,scy : integer ; // 画面大きさ
  myx, myy, myw, myh : integer ; // 自機
  beamf : Boolean ; // ビームONOFF
  bex, bey, bew, beh : integer ; // ビーム
  tekif : Boolean ; // 敵ONOFF
  tex, tey, tew, teh : integer ; // 敵
  procedure ATARI1 ;
 public
  { Public 宣言 }
 end;
 
・・・
 
// タイマー割り込み
procedure TForm1.Timer1Timer(Sender: TObject);
var
 i : integer ;
begin
 Timer1.Enabled := False ; // 割り込み禁止
 
 // ビームの動き
 if (beamf) then begin
  bey := bey - 8 ;
  Label3.Top := bey ;
  if (bey < 0) then begin
   beamf := False ;
   Label3.Visible := False ; // 隠す
  end;
 end;
 
 // ビームと敵の当たり判定1
 ATARI1 ;
 
 // 敵の動き
 if (tekif) then begin
  tey := tey + 4 ;
  Label4.Top := tey ;
  if (tey > scy) then begin
   tekif := False ;
   Label4.Visible := False ; // 隠す
   // 地面に激突!
   for i:=1 to 1000 do begin
    Panel1.Color := RGB(Random(256),Random(256),Random(256));
    Panel1.Update ;
   end;
   Panel1.Color := clBtnFace ;
   pnts := pnts - 10 ;
   Label1.Caption := '得点:' + IntToStr(pnts) ;
  end ;
 end
 else begin
  tex := Random(scx - tew*2) ;
  tey := 0 ;
  tekif:= True ;
  Label4.Left := tex - tew ;
  Label4.Top := tey ;
  Label4.Visible := True ; // 敵出現
 end;
 
 // ビームと敵の当たり判定2
 ATARI1 ;
 
 Timer1.Interval := 100 ; // 割り込み再開
 Timer1.Enabled := True ;
end;
 
// ビームと敵の当たり判定
procedure TForm1.ATARI1 ;
var
 bw : integer ;
 i,c : integer ;
begin
 if (beamf)and(tekif) then begin
  if ((bex+bw) >= (tex-tew))and((bex-bw) <= (tex+tew)) then begin
   if ((bey+beh) >= tey)and(bey <= (tey+teh)) then begin
    // 当たり!
    c := Label4.Font.Color ;
    for i:=1 to 1000 do begin
     Label4.Font.Color := RGB(Random(256),Random(256),Random(256));
     Label4.Update ;
    end;
    Label4.Font.Color := c ;
    tekif := False ; Label4.Visible := False ;
    beamf := False ; Label3.Visible := False ;
    pnts := pnts + 1 ;
    Label1.Caption := '得点:' + IntToStr(pnts) ;
   end;
  end;
 end;
end;
 
end.
 
次に、敵と自機が当たった場合について考えます。
 
自機の表示座標範囲は、
myx-myw,myy  myx+myw,myy
n▲n
myx-myw,myy+myh  myx+myw,myy+myh
ですのでビームと敵の場合と同様に処理を行います。
敵が移動してきて自機に当たった場合と、自機が移動して敵に当たった場合の2パターンがあります。
 
・・・
type
 TForm1 = class(TForm)
  ・・・
 private
  { Private 宣言 }
  pnts : integer ; // 得点
  scx,scy : integer ; // 画面大きさ
  myx, myy, myw, myh : integer ; // 自機
  beamf : Boolean ; // ビームONOFF
  bex, bey, bew, beh : integer ; // ビーム
  tekif : Boolean ; // 敵ONOFF
  tex, tey, tew, teh : integer ; // 敵
  procedure ATARI1 ;
  procedure ATARI2 ;
 public
  { Public 宣言 }
 end;
 
・・・
 
// キーを押した場合の処理
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
 Shift: TShiftState);
begin
 if (Key = VK_LEFT)or(Key = VK_NUMPAD4) then begin
  myx := myx - 8 ;
  if (myx < myw) then myx := myw ;
  Label2.Left := myx - myw ;
  // 自機と敵の当たり判定
  ATARI2 ;
 end
 else if (Key = VK_RIGHT)or(Key = VK_NUMPAD6) then begin
  myx := myx + 8 ;
  if (myx > scx-myw) then myx := scx-myw ;
  Label2.Left := myx - myw ;
  // 自機と敵の当たり判定
  ATARI2 ;
 end
 else if (Key = VK_SPACE) then begin
  if not(beamf) then begin
   beamf := True ;
   bex := myx ;
   bey := myy ;
   Label3.Left := bex - bew ;
   Label3.Top := bey ;
   Label3.Visible := True ;
  end ;
 end ;
 Key := 0 ;
end;
 
// タイマー割り込み
procedure TForm1.Timer1Timer(Sender: TObject);
var
 bw : integer ;
 i,c : integer ;
begin
 bw := bew div 2 ; // ビーム絵の幅
 Timer1.Enabled := False ; // 割り込み禁止
 
 // ビームの動き
 if (beamf) then begin
  bey := bey - 8 ;
  Label3.Top := bey ;
  if (bey < 0) then begin
   beamf := False ;
   Label3.Visible := False ; // 隠す
  end;
 end;
 
 // ビームと敵の当たり判定1
 ATARI1 ;
 
 // 敵の動き
 if (tekif) then begin
  tey := tey + 4 ;
  Label4.Top := tey ;
  if (tey > scy) then begin
   tekif := False ;
   Label4.Visible := False ; // 隠す
   // 地面に激突!
   for i:=1 to 1000 do begin
    Panel1.Color := RGB(Random(256),Random(256),Random(256));
    Panel1.Update ;
   end;
   Panel1.Color := clBtnFace ;
   pnts := pnts - 10 ;
   Label1.Caption := '得点:' + IntToStr(pnts) ;
  end ;
 end
 else begin
  tex := Random(scx - tew*2) ;
  tey := 0 ;
  tekif:= True ;
  Label4.Left := tex - tew ;
  Label4.Top := tey ;
  Label4.Visible := True ; // 敵出現
 end;
 
 // ビームと敵の当たり判定2
 ATARI1 ;
 
 // 自機と敵の当たり判定
 ATARI2 ;
 
 Timer1.Interval := 100 ; // 割り込み再開
 Timer1.Enabled := True ;
end;
 
// ビームと敵の当たり判定
procedure TForm1.ATARI1 ;
・・・
end;
 
// 自機と敵の当たり判定
procedure TForm1.ATARI2 ;
var
 i,c : integer ;
begin
 if (tekif) then begin
  if ((myx+myw) >= (tex-tew))and((myx-myw) <= (tex+tew)) then begin
   if ((myy+myh) >= tey)and(myy <= (tey+teh)) then begin
    // 当たり!
    c := Label2.Font.Color ;
    for i:=1 to 1000 do begin
     Label2.Font.Color := RGB(Random(256),Random(256),Random(256));
     Label2.Update ;
    end;
    Label2.Font.Color := c ;
    tekif := False ; Label4.Visible := False ;
    pnts := pnts - 5 ;
    Label1.Caption := '得点:' + IntToStr(pnts) ;
   end;
  end;
 end;
end;
 
end.
 
敵が表示されていて、敵と自機の表示位置が重なる場合、当たりとして、自機の色をランダムに1000回変えて表示させ(当たって爆発しました、という効果のつもり)敵を非表示にして、ペナルティとして、得点を -5 しています。
 
メニュー「ファイル」→「すべて保存」をクリックしてプログラムの保存を行い、メニュー「プロジェクト」→「Game1 を再構築」をクリックしてプログラムのコンパイル(再構築)を行い、正常終了したら、メニュー「実行」→「実行」をクリックして下さい。
 
これで、おおよそのプログラミングが終了です。
タイマー割り込みのイベントハンドラ内にあるビームの動き部分と敵の動き部分を別々に手続きにした方が、後から見た場合、分かり易いかもしれません。
 
敵が1機しか無いと退屈で余り面白くありませんね。やはり、敵は複数居た方が面白いでしょう。また、ゲームオーバー条件がありません。敵が地面に激突した場合、占領されたものとしてゲームオーバーにしましょう。自機が敵と当たった場合は、一応阻止したとしてマイナスポイントではなくプラス1とし、ビームで倒した場合にはプラス3とします。Panel1 の上にボタン Button1 を配置し、Caption プロパティを「ReStart」として、ゲームオーバーの後、最初から再度スタートさせるようにします。
 
さて、敵を2機にした場合、変数 tekif,tex,tey,tew,teh としていましたから、変数 tekif1,tex1,tey1,tew1,teh1 を追加し、Label5 を作って、敵の移動、当たり判定を tekif1,tex1,tey1,tew1,teh1 の分も追加するとします。単純に、その部分のプログラム入力は2倍の手間が掛かってしまいます。3機になった場合は、変数 tekif2,tex2,tey2,tew2,teh2 を作って・・・、4機、5機・・・と考えて行くと嫌になってきます。
そういった場合には、配列変数を使い、繰り返し for文等を組み合わせて何十・何百行も書かなくて済ませる事が出来ます。
 
配列変数の宣言は、
  var
   a : array[1..100] of integer ;
とすると、
整数の変数 a[1]、a[2]、a[3]、・・・、a[99]、a[100]、が利用出来るようになります。
そして、
  for i:=1 to 100 do
   a[i] := 0 ;
のようにすると、
a[1]、a[2]、a[3]、・・・、a[99]、a[100] に数値 0 を代入する事が出来ます。
 
この場合は、
  tekif : Boolean ; // 敵ONOFF
  tex, tey, tew, teh : integer ; // 敵
としていた箇所を
  tekif : array[1..100] of Boolean ; // 敵ONOFF
  tex, tey, tew, teh : array[1..100] of integer ; // 敵
として最大、100機まで扱えるようにします。
現在の敵数を 変数 tekiN に入れるものとし、タイマー割り込みのイベントハンドラが起きる度に1ずつカウントさせる変数 cnts を用意し、この数値によって敵数をどんどん増やしていくようにします。
ゲームオーバーかどうかを管理するための変数 endflag を用意します。ゲームオーバー状態では、FormKeyDownイベントハンドラ、Timer1Timerイベントハンドラ内の処理は行わないようにします。
そして問題になってくるのは、敵を表現するための Label オブジェクトです。最大100個の Label、つまり、画面上に Label4 〜 Label103を配置させるのも結構大変な作業になってきますし、この Label4 〜 Label103 をどのように配列のように扱うのか?という問題もあります(FindComponent等を使う手法はあります)。
ここでは、Labelオブジェクトを動的に生成して扱うようにしています。つまり、画面デザイン時にはオブジェクトを配置せず、プログラム内でオブジェクトを自動生成し、画面表示させます。入門編としては少し難しい話かもしれませんが、最後ですので記述しておきます。
これまでのオブジェクトの生成、INIファイルやレジストリの場合と同様に、
  var
   Lab : TLabel ;
と宣言しておき
  Lab := TLabel.Create(SELF) ;
  Lab.Visible := False ;
  Lab.Parent := Form1 ;
  Lab.Caption := 〜 ;
  Lab.Font := 〜 ;
  Lab.Left := 〜 ;
  Lab.Top := 〜 ;
  Lab.Visible := True ;
のようにすれば、指定した位置に指定した内容の文字を表示することが出来ます。Parent つまり、どのフォーム/パネル/等の上に配置するのかに注意して下さい。Visible を最初に False にして最後に True にしているのは、そうしておかないと作業中の状態が画面に見えてしまって余り美しくないからです。そして、必要が無くなったら必ず、
  Lab.Free ;
を行うのを絶対に忘れないで下さい。
 
以上、ラベルオブジェクトが1つの場合ですが、これを配列にする事が出来ます。
  var
   Lab : array[1..100] of TLabel ;
  ・・・
  for i:=1 to 100 do begin
   Lab[i] := TLabel.Create(SELF) ;
   Lab[i].Visible := False ;
   Lab[i].Parent := Form1 ;
   Lab[i].Caption := 〜 ;
   Lab[i].Font := 〜 ;
   Lab[i].Left := 〜 ;
   Lab[i].Top := 〜 ;
   Lab[i].Visible := True ;
  end;
  ・・・
  for i:=1 to 100 do
   Lab[i].Free ;
という具合です。
 
・・・
type
 TForm1 = class(TForm)
  ・・・
 private
  { Private 宣言 }
  pnts : integer ;
  cnts : integer ;
  scx,scy : integer ; // 画面大きさ
  myx, myy, myw, myh : integer ; // 自機
  beamf : Boolean ; // ビームONOFF
  bex, bey, bew, beh : integer ; // ビーム
  tekiN : integer ;
  tekif : array[1..100] of Boolean ; // 敵ONOFF
  tex, tey, tew, teh : array[1..100] of integer ; // 敵
  Lab : array[1..100] of TLabel ;
  endflag : Boolean ;
  procedure ATARI1 ;
  procedure ATARI2 ;
 public
  { Public 宣言 }
 end;
 
var
 Form1: TForm1;
 
implementation
 
{$R *.dfm}
 
// 起動時
procedure TForm1.FormShow(Sender: TObject);
var
 i : integer ;
begin
 endflag := False ;
 pnts := 0 ;
 cnts := 0 ;
 scx := Form1.ClientWidth ;
 scy := Form1.ClientHeight - Panel1.Height ;
 myx := Form1.ClientWidth div 2 ;
 myy := Label2.Top ;
 myw := Label2.Width div 2 ;
 myh := Label2.Height ;
 beamf:= False ;
 bew := Label3.Width div 2 ;
 beh := Label3.Height ;
 tekiN:= 1 ;
 for i:=1 to 100 do begin
  tekif[i] := False ;
  tew[i] := Label4.Width div 2 ;
  teh[i] := Label4.Height ;
  tex[i] := Random(scx-tew[i]*2) + tew[i] ;
  tey[i] := 0 ;
  Lab[i] := TLabel.Create(SELF) ;
  Lab[i].Visible := False ;
  Lab[i].Parent := Form1 ;
  Lab[i].Caption := Label4.Caption ;
  Lab[i].Font := Label4.Font ;
  Lab[i].Transparent := True ;
  Lab[i].Left := tex[i] - tew[i] ;
  Lab[i].Top := tey[i] ;
 end;
 Label1.Caption := '得点:0' ;
 Label2.Left := myx - myw ;
 Label3.Visible := False ;
 Label4.Visible := False ;
 Button1.Enabled := False ;
end;
 
// 終了時
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var
 i : integer ;
begin
 Timer1.Enabled := False ;
 
 for i:=1 to 100 do
  Lab[i].Free ;
end;
 
// キーを押した場合の処理
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
 if (endflag) then exit ;
 
 if (Key = VK_LEFT)or(Key = VK_NUMPAD4) then begin
  myx := myx - 8 ;
  if (myx < myw) then myx := myw ;
  Label2.Left := myx - myw ;
  // 自機と敵の当たり判定
  ATARI2 ;
 end
 else if (Key = VK_RIGHT)or(Key = VK_NUMPAD6) then begin
  myx := myx + 8 ;
  if (myx > scx-myw) then myx := scx-myw ;
  Label2.Left := myx - myw ;
  // 自機と敵の当たり判定
  ATARI2 ;
 end
 else if (Key = VK_SPACE) then begin
  if not(beamf) then begin
   beamf := True ;
   bex := myx ;
   bey := myy ;
   Label3.Left := bex - bew ;
   Label3.Top := bey ;
   Label3.Visible := True ;
  end;
 end;
 Key := 0 ;
end;
 
// タイマー割り込み
procedure TForm1.Timer1Timer(Sender: TObject);
var
 i,j : integer ;
begin
 if (endflag) then exit ;
 Timer1.Enabled := False ; // 割り込み開始
 
 // ビームの動き
 if (beamf) then begin
  bey := bey - 16 ;
  Label3.Top := bey ;
  if (bey < 0) then begin
   beamf := False ;
   Label3.Visible := False ; // 隠す
  end;
 end;
 
 // ビームと敵の当たり判定1
 ATARI1 ;
 
 // 敵の動き
 for j:=1 to tekiN do begin
  if (tekif[j]) then begin
   tey[j] := tey[j] + 4 ;
   Lab[j].Top := tey[j] ;
   if (tey[j] > scy) then begin
    tekif[j] := False ;
    Lab[j].Visible := False ; // 隠す
    // 地面に激突!
    for i:=1 to 1000 do begin
     Panel1.Color := RGB(Random(256),Random(256),Random(256));
     Panel1.Update ;
    end;
    Panel1.Color := clBtnFace ;
    endflag := True ;
    break ; // for文抜け出し
   end;
  end
  else begin
   tex[j] := Random(scx-tew[j]*2) + tew[j] ;
   tey[j] := 0 ;
   tekif[j] := True ;
   Lab[j].Left := tex[j] - tew[j] ;
   Lab[j].Top := tey[j] ;
   Lab[j].Visible := True ; // 敵出現
  end;
 end;
 
 if (endflag) then begin
  MessageDlg('ゲームオーバー', mtInformation, [mbOk], 0);
  Button1.Enabled := True ;
  exit ;
 end;
 
 // ビームと敵の当たり判定2
 ATARI1 ;
 
 // 自機と敵の当たり判定
 ATARI2 ;
 
 Inc(cnts) ;
 tekiN := (cnts div 100) + 1 ;
 if (tekiN > 100) then tekiN := 100 ;
 
 Timer1.Interval := 100 ; // 割り込み再開
 Timer1.Enabled := True ;
end;
 
// ビームと敵の当たり判定
procedure TForm1.ATARI1 ;
var
 bw : integer ;
 i,j,c : integer ;
begin
 bw := bew div 2 ; // ビーム絵の幅
 for j:=1 to tekiN do begin
  if (beamf)and(tekif[j]) then begin
   if ((bex+bw) >= (tex[j]-tew[j]))and((bex-bw) <= (tex[j]+tew[j])) then begin
    if ((bey+beh) >= tey[j])and(bey <= (tey[j]+teh[j])) then begin
     // 当たり!
     c := Lab[j].Font.Color ;
     for i:=1 to 1000 do begin
      Lab[j].Font.Color := RGB(Random(256),Random(256),Random(256));
      Lab[j].Update ;
     end;
     Lab[j].Font.Color := c ;
     tekif[j] := False ; Lab[j].Visible := False ;
     beamf := False ; Label3.Visible := False ;
     pnts := pnts + 3 ;
     Label1.Caption := '得点:' + IntToStr(pnts) ;
    end;
   end;
  end;
 end;
end;
 
// 自機と敵の当たり判定
procedure TForm1.ATARI2 ;
var
 i,j,c : integer ;
begin
 for j:=1 to tekiN do begin
  if (tekif[j]) then begin
   if ((myx+myw) >= (tex[j]-tew[j]))and((myx-myw) <= (tex[j]+tew[j])) then begin
    if ((myy+myh) >= tey[j])and(myy <= (tey[j]+teh[j])) then begin
     // 当たり!
     c := Label2.Font.Color ;
     for i:=1 to 1000 do begin
      Label2.Font.Color := RGB(Random(256),Random(256),Random(256));
      Label2.Update ;
     end;
     Label2.Font.Color := c ;
     tekif[j] := False ; Lab[j].Visible := False ;
     pnts := pnts + 1 ;
     Label1.Caption := '得点:' + IntToStr(pnts) ;
    end;
   end;
  end;
 end;
end;
 
// Restart
procedure TForm1.Button1Click(Sender: TObject);
var
 i : integer ;
begin
 Label1.Caption := '得点:0' ;
 Label3.Visible := False ;
 Button1.Enabled := False ;
 
 endflag := False ;
 cnts := 0 ;
 pnts := 0 ;
 beamf:= False ;
 tekiN:= 1 ;
 
 for i:=1 to 100 do begin
  tekif[i] := False ;
  tex[i] := Random(scx-tew[i]*2) + tew[i] ;
  tey[i] := 0 ;
  Lab[i].Visible := False ;
 end;
 
 Timer1.Interval := 100 ; // 割り込み再開
 Timer1.Enabled := True ;
end;
 
end.
Label4は必要無いのですが、敵デザインの基本形として利用しています。
 
メニュー「ファイル」→「すべて保存」をクリックしてプログラムの保存を行い、メニュー「プロジェクト」→「Game1 を再構築」をクリックしてプログラムのコンパイル(再構築)を行い、正常終了したら、メニュー「実行」→「実行」をクリックして下さい。
 
更に、ハイスコアの登録・表示、敵をいろんな形状(文字内容)にしたり、移動速度や動き方等に変化を付ける等を行えば面白いかもしれません。
バッチファイル
BASIC
C言語のお勉強
拡張子な話
DOSプログラム
Delphi
>Delphi入門編
 01 02 03 04
 05 06 07 08
 09 10 11 12
 13 14 15 16
 17 18 19 20
シェアウェア
Script!World
データベース
 
お問い合わせ 
本サイトはリンクフリーです
リンクバナー
(C)Copyright 1999-2015. By AFsoft All Rights Reserved.