第7回 逆引き:プログラムを分解解説 「5.衝突判定と燃料補給」

7−5 衝突判定と燃料補給

 次は、隕石との衝突判定を説明します。
NS Basicのコマンドでは、「そこに隕石を描く」事はできるのですが、「そこに隕石があるか」ということが分かりません。
通常は、その座標に何か描かれているか?というような関数があり、例えば、VBではPoint(X,Y)という関数で、座標(X,Y)の位置に表示されている色コードを返します。
しかし、NS Basicの標準の関数ではそのような機能を持ったものがありませんので、多少の工夫が必要です。

さて、隕石画像は、先ほどもありました通り、

1008  1009  1010  1011  1012  1013  1014  1015  1016

となっています。
実は、お気づきの方もあるかもしれませんが、隕石も自機の移動量と同じ15ピクセル毎、の8段に描き分けられています。

   

赤い線は15ピクセル毎の線ですが、このように、隕石の位置も「段」の概念を取り入れて作りました。
この尺度で考えると、自機の位置を表す変数Yiが0〜7でしたから、画像番号から1007を引いた値が、このYiと同じ尺度になります。
また、この画像番号は、スクロール用の配列変数Scrn()に入っていましたが、このうち、自機が表示されているのは、Scrn(1)の位置になりますので、
    If Scrn(1)-1007=Yi Then
とすれば、この条件が成立する時に、衝突したことになります。
ただし、ブランク画像や燃料画像もありますので、
    If Scrn(1)<1014 Then
        If Scrn(1)-1007=Yi Then
            Call Crush
            Redraw
        End If
    End If
と、Scrn(1)<1014の条件を先にチェックしておけば、隕石の時だけ判定することが可能です。

さて、この中にCall Crushという部分があります。
Callコマンドで別のサブルーチンを呼ぶ方法は初登場ではありませんが、今回のCrush()というサブルーチンは、共通モジュールではなく、FormのAfter部分に記述されています。
NS Basicでは、共通モジュールを用意しなくても、同一のルーチン中(コード編集画面で表示されている範囲内)でいくつでもサブルーチンや関数を作ることが可能です。
ただし、そこで定義したサブルーチンは、同一のルーチン内からしか利用することが出来ません。

    

例えば、今回の衝突処理を行うCrush()というサブルーチンは、Form1014のAfter内からしか使いませんので、この場所に定義しています。
一方、初期設定を行うInit_Screen()はスタートアップでも利用しますから、共通モジュールとして定義しているわけです。

さて、隕石に衝突したら、Callコマンドによって、Crushサブルーチンを呼び出して衝突処理をしています。
Crushサブルーチンの最初の、
    For i=0 to 10
        DrawBitmap 1018,5,Yi*15+25
        Delay 0.1
        DrawBitmap 1019,5,Yi*15+25
        Delay 0.1
    Next
この部分は、爆発の表示です。1018と1019の2つのビットマップを交互に表示して、爆発の雰囲気(?)を出しています。
次のFillRectangleコマンドは、自機の残数処理ですが、この部分を説明する前に、自機の残りを表示している部分を説明しましょう。

自機の残数表示は、10x10ピクセルのビットマップ1020を使って、次のように表示しています。
        For i=1 to MyShip 
            DrawBitmap 1020,90+i*11,148
        Next
10ピクセル四方の画像の座標を、i*11と、11倍していますので、お互いの画像に1ピクセルの隙間をおいて並んでいる訳です。
そして、これは、[After]のDo〜Loop内で、ReStartという共通変数が 1 の時だけ処理される位置に入っています。(ゲーム中では「Ready!」の表示をしている部分です)
        If ReStart=1 Then
            DrawChars "Ready!",65,75
            For i=1 to MyShip 
                DrawBitmap 1020,90+i*11,148
            Next
            DrawLine 5,147,155,147
            Delay 1.5
            ReStart=0
        End If
この部分では、Ready!、の表示をするとともに、自機の残りを表示し、エネルギー残量の初期値を表示し、Delayコマンドで、1.5秒待ったあと、ReStart変数の値を 0 にして処理から抜けます。
Do〜Loopの外に置いてもよいのですが、一度、自機や背景が描画された後に位置していますので、画面表示のルーチンをあえて作らなくても済み、プログラム的に楽なため、ここに入れてみました。

さて、Crush()サブルーチンに戻りましょう。
表示ルーチンでも分かるように、自機の残りと表示の関係は、([残り数]×11+90,148) になります。
したがって、衝突した時は、自機の残り表示の一番右端、つまり、残り数MyShipの位置に、空白の四角描いて、自機を消しています。
    FillRectangle 90+MyShip*11,148,10,10,0,nsbInverted
    MyShip=MyShip-1
表示したら1機減らせばい良いので、その後にMyShip変数から 1 を引いています。
そして、自機の残りがある間は「Ready!」に戻りたいですし、自機がなくなれば「ゲームオーバー」ですね。
    If MyShip<0 Then
        DrawChars "GAME OVER",55,75
        Flg3=1
    Else
        Delay 1.5
        Call Init_Screen
    End If
この部分で、MyShip変数が 0より小さくなったら「GAME OVER」表示をしてゲームオーバー用の共通変数 Flg3 に 1 を代入しています。
Flg3変数が 1 になると、ゲームオーバーです。したがって、もう、Do〜Loopを行う必要がありませんので、Do〜Loopの条件にFlg3の条件を追加します。
    Do Until (SysEventAvailable()=1 And Flg=1) Or Flg3=1
これで、ゲーム中にキーを押したか、ゲームオーバー時だけDo〜Loopから抜けることが出来ます。
一方、まだ、残機がある場合、Callコマンドで画面の初期化ルーチン Init_Screen()を呼び出しています。
Init_Screen()は、
  • Scrn()(背景)の初期化
  • 押しっぱなし防止フラグFlgの初期化
  • 隕石表示用のカウントFlg2の初期化
  • ゲームオーバー用フラグの初期化
  • 自機の位置初期化
  • ReStart変数のセット
  • 燃料の初期化
を行っていますので、ReStart変数が 1 となり、衝突後、再び「Ready!」に戻ってくるわけですね。
そして、Callコマンドの後に Redrawコマンドがあるので、ちゃんと[After]の始めから処理されるのですが、そもそも[After]は画面の描画が完了した場合に発生するイベントでしたね?
画面の描画が終わった処理をしている最中に、再度、Redrawで再描画するとは、なんだか、プログラムは休む暇なしですね。

また、自機が壁に衝突した場合、これは、変数Yiが1〜6以外の値になった時(ま、0か7にしかならない筈ですが)ですね。
ついでに、燃料が 0 になった時も衝突扱いとすれば、隕石との衝突の場合と同様に、
    If Yi<1 Or Yi>6 Or FUEL=0 Then
        Call Crush
        Redraw
    End If
とすれば処理できますね。

燃料は、FUELという変数を使っていますが、これはDo〜Loopが1回実行される毎に1ずつ減っていきます。
    FUEL=FUEL-1
初期値は、150で、Init_Screen()ルーチン内で初期化しています。
実際の表示は、1本の線ですね。
初期表示は、
        DrawLine 5,147,155,147
で、消していくのは、自機の残りを表示するのと同じ手法を用いて、
        DrawLine 5+FUEL,147,155,147,nsbInverted
としています。

そして、その逆の燃料が増える方、つまり、燃料補給は衝突処理とまったく同じになります。
自機の位置が Yi=6 で、背景画像が1016の時、燃料画像と重なっていますから、次のように処理します。
    If Scrn(1)=1016 And Yi=6 Then
        FUEL=FUEL+20
        If FUEL>150 Then
            FUEL=150
        End If
        DrawLine 5,147,5+FUEL,147
    End If
一度燃料を補給すると+20されますが、最大は150までとして、If〜Thenを使って調整しています。
そして、増えた分の描画していますが、これら補給に関する処理は、衝突などで一度お目にかかった手法を使っていますので、それほど難しくはないでしょう。

「スクロール」「移動」と続いて「衝突・補給」と3つの処理を説明しましたが、いかがでしょうか?
だんだん難しくなりましたが、ここまでの流れが理解できれば、ほぼゲームの骨格は出来ていますね。わかんなくなったら、もう一度、読み直しましょう。
また、説明がわかりにくいようでしたら、苦情のメールをお送り下さい(TT)



Sub main()

    Global Scrn(10)as Integer   'スクロール表示用(Bmpの番号:1008〜1016)
    Global Yi as Integer        '自機のY座標(段)
    Global Flg as Integer       '連続キー入力防止フラグ
    Global Flg2 as Integer      'カウントフラグ
    Global Flg3 as Integer      'ゲームオーバーフラグ(=1の時、ゲームオーバー)

    Global ReStart as Integer   '再開フラグ
    Global MyShip as Integer    '自機の数
    Global Score as Integer     '得点 
    Global FUEL as Integer      '燃料
    
    Call Init_All
    
End Sub

Sub Form1004_after()

    Dim i as Integer
    
    Do Until (SysEventAvailable()=1 And Flg=1) Or Flg3=1
    
        For i=1 to 10
            DrawBitmap Scrn(i),i*15-10,25
        Next
        DrawBitmap 1007,5,Yi*15+25

        Lbl1021.Label="SCORE:" + Format(Score,"000000")
                
        If ReStart=1 Then
            DrawChars "Ready!",65,75
            For i=1 to MyShip 
                DrawBitmap 1020,90+i*11,148
            Next
            DrawLine 5,147,155,147
            Delay 1.5
            ReStart=0
        End If
        
        FUEL=FUEL-1
        DrawLine 5+FUEL,147,155,147,nsbInverted
        
        Score=Score+1
        
        If Yi<1 Or Yi>6 Or FUEL=0 Then
            Call Crush
            Redraw
        End If
        
        If Scrn(1)<1014 Then
            If Scrn(1)-1007=Yi Then
                Call Crush
                Redraw
            End If
        End If
        
        If Scrn(1)=1016 And Yi=6 Then
            FUEL=FUEL+20
            If FUEL>150 Then
                FUEL=150
            End If
            DrawLine 5,147,5+FUEL,147
        End If
        
        For i=1 to 9
            Scrn(i)=Scrn(i+1)
        Next
        
        If Flg=1 Then
            Delay 0.15
        End If
        
        Flg2=Flg2-1
        If Flg2=0 Then
            Scrn(10)=1008+Int(rand()*9)
            Flg2=5
        Else
            Scrn(10)=1014+Int(rand()*2)
        End If
        Yi=Yi+1
        Flg=1
    Loop
    
End Sub

Sub Crush()

    Dim i as Integer
    
    For i=0 to 10
        DrawBitmap 1018,5,Yi*15+25
        Delay 0.1
        DrawBitmap 1019,5,Yi*15+25
        Delay 0.1
    Next
    
    FillRectangle 90+MyShip*11,148,10,10,0,nsbInverted
    MyShip=MyShip-1
    If MyShip<0 Then
        DrawChars "GAME OVER",55,75
        Flg3=1
    Else
        Delay 1.5
        Call Init_Screen
    End If
    
End Sub

Sub Form1004_events()

    Dim theKey as String
    
    Flg=0
    
    If GetEventType()=nsbKeyOrButton Then
        
        theKey=GetKey()
        If theKey=&h1 Or theKey=&h2 Or theKey=&h3 Or theKey=&h4 Then
            SetEventHandled
            Yi=Yi-2
            Redraw
        End If
    End If
    
    If GetEventType()=nsbPenDown Then
        If flg3=0 Then 
            Stop
        Else
            Call Init_All
            Redraw
        End If
        
    End If
    
End Sub

Sub Init_Screen()

    Dim i as Integer
    
    For i=1 to 10
        Scrn(i)=1014+Rand()*2
    Next
    
    Flg=0
    Flg2=5
    Flg3=0
    Yi=3
    ReStart=1
    FUEL=150

End Sub

Sub Init_All()

    MyShip=2
    Score=0
    Call Init_Screen

End Sub


前へ     目次へ     次へ

第7回 逆引き:プログラムを分解解説 「5.衝突判定と燃料補給」