8.2 自機と敵の衝突
そろそろ、このネタも終盤に入ってきました。自機が無敵だとどうしても気が緩みがち(笑)なので、敵と衝突したときは爆発するようにします。もちろん作るのは超簡単で、今までに紹介した方法を少しアレンジすればすぐできますよっ。

なお、撃墜は画面上の物体同士の衝突判定が基本です。エクセルマクロTips〜ゲーム篇〜5.3 衝突を判定するに衝突に関する詳しいことが書いてあるので、この章と合わせてどうぞ。
Sheet1(ゲーム)のマクロ「(General)(Declarations)」への変数追加
(General)(Declarations)へ、自機の爆発パターンを保存する変数rngMyShipHit(Range型)、自機の爆発パターン描画時間(=速度)を管理する変数lngMyShipHitS(Long型)とlngMyShipHitB(Long型)、自機の残機を管理するための変数lngMyShipLeft(Long型)、自機が爆発中かを管理する変数をblnMyShipLock(Boolean型)、爆発パターンをアニメーションするときの描画パターン番号を管理する変数lngMyShipHitCを追加します。

ちなみに、rngMyShipHitは5個の配列として定義。爆発パターン描画時間は「1コマを描画する時間」です。残機は、もちろんゼロになったらゲームオーバー(笑)。他の変数の詳しい使い道は後ほど…。


 ★Sheet1(ゲーム)のマクロ「(General)(Declarations)」
Option Explicit

'エクセルスマイル★シューティングゲームを作ろう
'記述場所:ワークシート「ゲーム」
'記述箇所:(General)(Declarations)

Dim lngGameStatus as Long 'ゲームの状態

Dim lngMyShipX As Long  '自機の横位置
Dim rngMyShip  As Range '自機のパターン
Dim blnBeamF   As Boolean 'ビーム砲発射フラグ
Dim lngBeamX   As Long    'ビーム砲横位置
Dim lngBeamY   As Long    'ビーム砲縦位置
Dim rngBeam    As Range   'ビーム砲のパターン

Dim rngMyShipHit(1 To 5) As Range '自機の爆発パターン
Dim lngMyShipLeft    As Long '自機の残機
Dim blnMyShipLock As Boolean '自機爆発中フラグ

Dim rngClear8  As Range   '8x8の消去用パターン
Dim rngClear16 As Range   '16x8の消去用パターン

Dim lngEnemyX(1 To 5)    As Long  '敵の横位置
Dim lngEnemyY(1 To 5)    As Long  '敵の縦位置
Dim lngEnemyPtn(1 To 5)  As Long  '敵待機飛行のパターン番号
Dim lngEnemySt(1 To 5)   As Long  '敵の状態
Dim rngEnemyWalk(1 To 4) As Range '敵待機飛行のパターン
Dim rngEnemyHit          As Range '敵爆発のパターン

Dim blnEnemyAttackSt     As Boolean '攻撃飛行中の敵有無
Dim lngEnemyAttackX      As Long    '攻撃飛行中の縦位置
Dim lngEnemyAttackY      As Long    '攻撃飛行中の横位置
Dim lngEnemyAttackDir    As Long    '攻撃飛行中の移動方向
Dim rngEnemyAttack       As Range   '攻撃飛行中のパターン

Dim lngStage      As Long 'ステージ数
Dim lngScore      As Long '得点
Dim lngEnemyCount As Long '敵撃墜数

Dim rngEnemyHit          As Range '敵爆発のパターン

Dim lngMyShipS As Long    '自機の移動速度
Dim lngMyShipB As Long    '自機の前回処理時刻
Dim lngBeamS   As Long    'ビーム砲の移動速度
Dim lngBeamB   As Long    'ビーム砲の前回処理時刻

Dim lngMyShipHitS As Long '自機の爆発描画速度
Dim lngMyShipHitB As Long '自機の爆発描画前回時刻
Dim lngMyShipHitC As Long '自機の爆発パターンの描画回数

Dim lngEnemyWalkS   As Long '敵待機飛行の移動速度
Dim lngEnemyWalkB   As Long '敵待機飛行の前回処理時刻
Dim lngEnemyAttackS As Long '敵攻撃飛行の移動速度
Dim lngEnemyAttackB As Long '敵攻撃飛行の前回処理時刻
Dim lngEnemyHitS    As Long '敵爆発パターン描画時間
Dim lngEnemyHitB(1 To 5) As Long '敵爆発パターンの前回処理時刻
Dim lngEnemyHitC(1 To 5) As Long '敵爆発パターンの描画回数

Const cstFKeyLeft    As Long = 37 '左ボタン
Const cstFKeyRight   As Long = 39 '右ボタン
Const cstFKeyV     As Long = 86 'Vボタン

Const cstPlay        As Long = 1 'ゲーム中
Const cstStageClear  As Long = 2 'ステージクリア
Const cstGameOver    As Long = 4 ' ゲームオーバー
Const cstDemo        As Long = 8 ' デモ画面

Const cstEnemyWalk   As Long = 1 '敵待機飛行
Const cstEnemyAttack As Long = 2 '敵攻撃飛行
Const cstEnemyHit    As Long = 8 '敵爆発中
Const cstEnemyDown   As Long = 16 '敵撃墜
 ※このマクロは、ワークシート「ゲーム」へ記述してください。
  灰色は既に作成済みの部分です。黒の部分を追加してください。
Sheet1(ゲーム)のマクロ「prcAllInit」への処理追加
全体の初期設定(起動時の初期設定)では、自機の爆発パターンの格納と自機の爆発パターン描画時間の設定を行います。これらの設定はアプリケーション起動時に一度だけ行っておけば良いため、この処理で初期化してます。

ちなみに、爆発パターンは4種類の画でアニメーションするのが基本ですが、rngMyShipHitは5個の配列にして5番目に「消し用パターン」を入れておきました。理由は後ほど…。



 ★Sheet1(ゲーム)のマクロ「prcAllInit」
Sub prcAllInit()

    'エクセルスマイル★シューティングゲームを作ろう
    '処理概要:アプリケーション全体の初期設定を行う
    '記述場所:ワークシート「ゲーム」

    '乱数を初期化
    Randomize

    'ゲームの状態を設定
    lngGameStatus = cstPlay

    '自機のパターンを設定
    Set rngMyShip = Range(Cells(201, 1), Cells(208, 16))

    '自機の爆発パターンを設定
    Set rngMyShipHit(1) = Range(Cells(201, 17), Cells(208, 32))
    Set rngMyShipHit(2) = Range(Cells(201, 33), Cells(208, 48))
    Set rngMyShipHit(3) = Range(Cells(201, 49), Cells(208, 64))
    Set rngMyShipHit(4) = Range(Cells(201, 65), Cells(208, 80))
    Set rngMyShipHit(5) = Range(Cells(217, 33), Cells(224, 48))

    'ビーム砲のパターンを設定
    Set rngBeam = Range(Cells(217, 1), Cells(224, 8))

    '8×8のパターン消去用
    Set rngClear8 = Range(Cells(217, 33), Cells(224, 40))

    '16x8のパターン消去用
    Set rngClear16 = Range(Cells(217, 33), Cells(224, 48))

    '敵待機飛行のパターン
    Set rngEnemyWalk(1) = Range(Cells(209, 1), Cells(216, 16))
    Set rngEnemyWalk(2) = Range(Cells(209, 17), Cells(216, 32))
    Set rngEnemyWalk(3) = Range(Cells(209, 33), Cells(216, 48))
    Set rngEnemyWalk(4) = Range(Cells(209, 49), Cells(216, 64))

    '敵攻撃飛行のパターン
    Set rngEnemyAttack = Range(Cells(209, 65), Cells(216, 80))

    '敵の爆発パターン
    Set rngEnemyHit = Range(Cells(209, 81), Cells(216, 96))

    '移動速度取得
    lngMyShipS = Worksheets("コンフィグ").Range("B3") '自機
    lngBeamS = Worksheets("コンフィグ").Range("B5") 'ビーム砲
    lngEnemyAttackS = Worksheets("コンフィグ").Range("B7") '敵攻撃飛行
    lngEnemyHitS = Worksheets("コンフィグ").Range("B7") '敵爆発時間
    lngMyShipHitS = Worksheets("コンフィグ").Range("B4") '自機爆破時間

    'ゲーム画面の初期化
    Call prcScreenInit

End Sub
 ※このマクロは、ワークシート「ゲーム」へ記述してください。
  灰色は既に作成済みの部分です。黒の部分を追加してください。
Sheet1(ゲーム)のマクロ「prcGameInit」への処理追加
ステージ毎の初期設定処理には、自機爆発中フラグ(blnMyShipLock)をFalse(爆発していない)にする処理を追加します。ゲーム開始時は爆発していないのであたりまえですね(笑)

なお、自機の爆発パターン前回処理時刻(lngMyShipHitB)は、敵との衝突時に都度初期化するので、この処理では特に意識しなくてOKです。


 ★Sheet1(ゲーム)のマクロ「prcGameInit」
Sub prcGameInit ()

    'エクセルスマイル★シューティングゲームを作ろう
    '処理概要:ステージ毎の初期設定を行う
    '記述場所:ワークシート「ゲーム」

    Dim i As Long '汎用変数
    Dim lngTmpY As Long ' 敵の縦位置調整用

    '自機の横位置設定
    lngMyShipX = 1

    'ビーム砲フラグをOFF
    blnBeamF = False

    '自機の爆発中フラグをOFF
    blnMyShipLock = False

    '敵の基準縦位置設定
    'ステージ数を元に基準位置を算出
    '104を超えたときはそれ以上、下へ表示しない
    lngTmpY = lngStage * 8
    If lngTmpY > 104 Then
       lngTmpY = 104
    End If

    '敵の待機飛行の移動速度再設定
    lngEnemyWalkS = Worksheets("コンフィグ").Range("B6")

    '敵の位置設定
    For i = 1 To 5
        lngEnemyX(i) = Worksheets("コンフィグ").Cells(11, i + 1) '敵の横位置
        lngEnemyY(i) = lngTmpY '敵の縦位置
        lngEnemyPtn(i) = Worksheets("コンフィグ").Cells(13, i + 1) '敵待機のパターン番号
        lngEnemySt(i) = cstEnemyWalk '敵の状態
    Next

    '攻撃飛行中の敵はナシ
    blnEnemyAttackSt = False

    '前回処理時刻初期化
    lngMyShipB = GetTickCount() '自機
    lngBeamB = GetTickCount()   'ビーム砲
    lngEnemyWalkB = GetTickCount()   '敵待機飛行
    lngEnemyAttackB = GetTickCount() '敵攻撃飛行

    '敵の撃墜数
    lngEnemyCount = 0

    'ゲーム画面の初期化
    Call prcScreenInit

End Sub
 ※このマクロは、ワークシート「ゲーム」へ記述してください。
  灰色は既に作成済みの部分です。黒の部分を追加してください。
Sheet1(ゲーム)のマクロ「prcEnemyMove」への処理追加
敵の移動処理の「攻撃飛行処理」(サブルーチンEnemyAttack)の所へ、自機と敵の衝突判定処理を追加します。ただし、判定処理の呼び出しは自機が爆発していないとき(blnMyShipLockFalse)のときだけにしましょう。なお、待機飛行中は自機と衝突することが100%無いので追加不要です。

ついでに、攻撃飛行に移る条件(通常と残り1匹の両方)に「自機が爆発していないとき」という条件を加えました。敵は、自機が生きているときだけ攻撃飛行に移るのです。


 ★Sheet1(ゲーム)のマクロ「prcEnemyMove」
 長くなったので別ウィンドウで表示するようにしました。ここをクリックしてくださいな。
Sheet1(ゲーム)のマクロ「prcMyShipHitCheck」の作成
自機と敵の衝突判定処理です。これは、新たに追加します。

衝突しているかは、同じように自機と敵の座標の差から求めます。自機と敵の衝突範囲は図の通りですが、あまりシビアすぎても面白くないので、少々甘めに…。衝突しているときは、自機の爆発中フラグ(blnMyShipLock)をON(True)にして、あとは爆発パターン描画の為の初期設定などを行います。というわけで、もう特にムズカシイ箇所はないでしょう。



 ★Sheet1(ゲーム)のマクロ「prcMyShipHitCheck」
Sub prcMyShipHitCheck()

    'エクセルスマイル★シューティングゲームを作ろう
    '処理概要:自機と敵の衝突を判定する
    '記述場所:ワークシート「ゲーム」

    '自機と敵が衝突しているかを判定、135は自機の縦位置
    If lngMyShipX - lngEnemyAttackX <= 12 And _
       lngMyShipX - lngEnemyAttackX >= -11 And _
       135 - lngEnemyAttackY <= 2 And _
       135 - lngEnemyAttackY >= -6 Then

       blnMyShipLock = True '爆破中は自機をロック
       lngMyShipHitB = GetTickCount() '爆発パターンの描画時刻
       lngMyShipHitC = 1 '爆発パターンの番号

    End If

End Sub
 ※このマクロは、ワークシート「ゲーム」へ記述してください。
Sheet1(ゲーム)のマクロ「prcMyShipDown」の作成
自機の爆発処理です。爆発パターンの描画が中心なので特に難しい処理はありません。この処理は、新たに追加します。

lngMyShipHitCは爆発パターンの番号ですが、6未満なら爆発パターンの描画を行い、それ以降は20以上かつ敵の攻撃飛行が終了するまでlngMyShipHitCのカウント以外は何もしません。

なぜ、6未満なら描画…なんてことをしているかというと、爆発した後少しの間マッタリする時間を作るためです。爆発パターンの最後の画(5番目)には「消し用パターン」が入っているので、lngMyShipHitCが5のときに画面から自機が完全に消えます。しかし、その後もlngMyShipHitC20になるまではこの処理は呼び出されますが、カウントするだけで何もしません。この6〜20以上までの少しの間がマッタリした時間になります。

それと、「敵の攻撃飛行が終了するまで…」という条件にしているのは、自機が爆発する原因は敵との接触の他に敵が落とす糞との接触もあるからです。糞に接触して爆発し、マッタリせずにすぐに自機が復活した場合もしかするとすぐ目の前に敵がいるかもしれません…ということは逃げられませんね(汗)。なので、いったん全てが落ち着いてから(=マッタリしてから)自機を復活するようにしています。

マッタリが終わったら、とりあえず自機を通常(blnMyShipLockFalse)に戻して、位置も戻して、ついでに残機も引いちゃいましょう。ちなみに、残機が0になるとゲームオーバーですが、ゲームオーバー処理はまた後ほど…。

ちなみに「Call GetAsyncKeyState(cstFKeyV)」が何を意味するかというと、コメントの通り(笑)キー入力を溜めないようにする(クリアする)ためです。キー入力は、いったんどこかにその情報が記録され、その記録された情報は「GetAsyncKeyState(???)」が実行されたときに取り出されます。もし取り出しを行わないと、爆発中に押したVボタンの情報が次にGetAsyncKeyState(???)を実行したときまで残るので、自機が復活した瞬間ビーム砲が発射されてしまいます。これじゃカッコ悪いので、自機が爆発している間もキー入力(特にVキー)情報を取り出し、Vボタンの情報が残らないようにしました。

今後作成するプロシジャでも、このダミー呼び出しをしている所が何ヶ所か出てくるので「要チェック!」です。


 ★Sheet1(ゲーム)のマクロ「prcMyShipDown」
Sub prcMyShipDown()

    'エクセルスマイル★シューティングゲームを作ろう
    '処理概要:自機の爆発
    '記述場所:ワークシート「ゲーム」

    'lngMyShipHitCが6未満のときは爆発パターンの描画
    If lngMyShipHitC < 6 Then
       rngMyShipHit(lngMyShipHitC).Copy _
           Destination:=Range(Cells(135, lngMyShipX), _
                              Cells(142, lngMyShipX + 15))
    End If

    'lngMyShipHitCが20以上で攻撃飛行中の敵がいないときは
    '自機の残数を減らし、自機の位置を初期位置へ戻し、自機を復活
    If lngMyShipHitC >= 20 And _
       blnEnemyAttackSt = False Then
       lngMyShipLeft = lngMyShipLeft - 1 '自機の残数を減らす
       lngMyShipX = 1 '自機の初期表示位置
       blnMyShipLock = False '自機の復活
    End If

    'キー入力を溜め込まないためのダミー呼び出し
    Call GetAsyncKeyState(cstFKeyV)

    '爆発制御カウンタのカウントアップ
    lngMyShipHitC = lngMyShipHitC + 1

End Sub
 ※このマクロは、ワークシート「ゲーム」へ記述してください。
Sheet1(ゲーム)のマクロ「prcGameMain」への処理追加
ゲームの主制御処理には、自機の爆発ステータス(blnMyShipLock)の内容から、自機の移動処理(プロシジャprcMyShipMove)を呼び出すか、自機の爆発処理(プロシジャprcMyShipDown)を呼び出すかの制御を追加します。

プロシジャprcMyShipDown(自機の爆発処理)は今回新たに追加したプロシジャで、自機の爆発ステータス(blnMyShipLock)がON(True)のとき一定時間経過ごとに呼び出します。


 ★Sheet1(ゲーム)のマクロ「prcGameMain」
Sub prcGameMain()

    'エクセルスマイル★シューティングゲームを作ろう
    '処理概要:ゲームの主制御処理
    '記述場所:ワークシート「ゲーム」

    'ゲームの初期設定
    Call prcGameInit

    '主処理(ステージクリアかゲームオーバーになるまで実行)
    '強制終了はEscキーを押す
    Do Until lngGameStatus = cstStageClear or lngGameStatus = cstGameOver

       '自機の移動。自機が爆発していないとき、かつ
       '前回処理時刻から一定の時間が過ぎたとき処理を行う
       If
blnMyShipLock = False And _
          GetTickCount() - lngMyShipB > lngMyShipS Then

          '自機の移動処理を呼び出す
          Call prcMyShipMove

          '自機の移動処理を行った時刻を保存(次回処理のため)
          lngMyShipB = GetTickCount()

       End If

       '自機の爆発処理。敵と衝突するとblnMyShipLockがTrueになる。
       '前回処理時刻から一定の時間が過ぎたときだけ処理
       If blnMyShipLock = True And _
          GetTickCount() - lngMyShipHitB > lngMyShipHitS Then
          Call prcMyShipDown
          lngMyShipHitB = GetTickCount()
       End If

       'ビーム砲/前回処理時刻から一定の時間が過ぎたときだけ処理
       If GetTickCount() - lngBeamB > lngBeamS Then

          'ビーム砲フラグが発射状態(ture)のときはビーム砲処理を呼び出す
          If blnBeamF Then
             Call prcBeamMove
          End If

          'ビーム砲の移動処理を行った時刻を保存(次回処理のため)
          lngBeamB = GetTickCount()

       End If

       '敵の移動処理を呼び出す
       Call prcEnemyMove

    Loop

End Sub
 ※このマクロは、ワークシート「ゲーム」へ記述してください。
  灰色は既に作成済みの部分です。黒の部分を追加してください。
Sheet1(ゲーム)のマクロ「prcGame」への処理追加
全体を制御するための処理には、自機の残数設定を追加します。残数はシート「コンフィグ」に設定しておいた値を取得します。

ところで、昔からゲームスタート時は3機って場合が多いですよね。でも、なぜ3機なのでしょう…。


 ★Sheet1(ゲーム)のマクロ「prcGame」
Sub prcGame()

    'エクセルスマイル★シューティングゲームを作ろう
    '処理概要:全体の主制御処理
    '記述場所:ワークシート「ゲーム」

    '変数の初期設定
    Call prcAllInit

    '主処理(永久にprcGameMainを呼び続ける)
    '終了はEscキーを押す
    Do Until False

       'ステージ数の初期設定
       lngStage = 1

       '得点の初期化
       lngScore = 0

       '自機の残数
       lngMyShipLeft = Worksheets("コンフィグ").Range("B1")

       'ゲームの主制御処理の呼び出し
       Call prcGameMain

    Loop

End Sub
 ※このマクロは、ワークシート「ゲーム」へ記述してください。
  灰色は既に作成済みの部分です。黒の部分を追加してください。
というわけで実験!
サンプルのコードを全て定義したら、Sheet1のプロシジャ「prcGame」を実行しましょう。実行する際は、VBエディターからの実行ではなく、エクセルのツールバーにある(マクロの実行)ボタンを押し、マクロの一覧で「Sheet1.prcGame」を選択しましょう。

今度は敵と当たらないように注意!うっかり当たってしまうと爆発します。でも、自機だけです。いったい、敵はどんな素材で作られているのでしょうか(汗)。

なお、実行すると永久ループと同じような状態になるのでタダでは停止できません。(マクロの停止)ボタンが押せないのです。

でも、安心してください。Escキーを押すことで処理を止めることができます。
Copyright(C) 1999-2006 結城圭介。 All rights reserved