第7回 逆引き:プログラムを分解解説 「4.自機の移動」

7−4 自機の移動

 さて、次は自機の表示と移動です。
自機(1007)は15x15ピクセルの画像ですが、スクロールエリアの高さは120ピクセルでしたので、ちょうど、120÷15=8段分の表示が可能になります。
ピクセル単位で移動させることも出来ますが、今回のゲームでは「段」単位で移動させています。
つまり、自機は 15ピクセル単位で移動しているわけですが、この表示用の座標に使っている、Yiというグローバル変数には、その段の値が代入されています。
段の値Yi=0〜7の8段階です。そして、実際の表示は、この段数に15を掛ければピクセル寸法になりますので、
    DrawBitmap 1007,5,Yi*15+25
としています。スクロールエリアの左端の座標が(5,25)ですので、横方向は 5、縦方向は Yi*15+25 ですね。
Yi=0,1,2,3,4,5,・・・ですから、Yi*15+25=25,40,55,70,85,・・・となります。これで、変数Yiの値を変化させれば自機が上下に移動するようになります。
ただし、移動時に前の位置に描いた自機を消す必要はありません。スクロール画面が全てを消してくれますので、楽チンですね。

次に自機の移動ですが、今回は「ボタンを押している時は上昇、そうでない時は下降」するように設定しました。
まずは、簡単な下降から見ていきましょう。
下降させるには、Yiを1加えれば良いわけですから、
    Yi=Yi+1
ですね。これが、Do〜Loopの中に入っていますので、1スクロール毎に1つずつ下に下がります。
一方、上昇は、何かのキーが押された時に処理します。なので、まずは、ループから抜け出す必要がありますねが、これは、Do〜Loopの条件に、SysEventAvailable()を使えば良いですね。
だたし、常にループが回っていますので、SysEventAvailable()を使って抜け出す時点は、すでに、下降の処理が行われた後ですね。

    

図で表すと分かりやすいですが、NS Basicでは、Doの方にしか条件がつけられませんので、イベントの判定が下降処理の後になります。
したがって、上昇ルーチンに入る時には、既に下降しちゃってますので、変数Yiは+1されています。そのため、上昇したい場合は−1ではなく−2をしなければなりません。
[After]
    Do Until SysEventAvailable()=1

        Yi=Yi+1
 
    Loop

[events]
    Dim theKey as String
    
    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
とまぁ、こうすればよさそうです。
これだと、4つのハードキーの何れかを押すと上昇しますね。
ちなみに、それ以外のキーを使うと、SetEventHandledコマンドを処理しませんので、そのキーに割り当てられた処理が行われます。
しかし、この時点で機能を持ったキーと言えば[ホーム]と[電源]くらいしか残っていませんね。それらのキーは、押されると、割り当てられた機能が働くことになりますが、それ以外(と言っても、ハードキーの上下くらいですが)の場合、処理すべき処理もなく、また、Redrawコマンドも実行しませんので、結果的に「ゲームの一時停止」になります。
そして、操縦用の4つのキーの何れかを押すと、ちゃんと再開します。

さて、NS BasicのDo〜Loopでは、Do側にしか条件が置けないため、更に問題を発生します。
先ほどの図に戻ってみますと、Do〜Loop内でキー入力があれば、イベントが発生してループを抜けて、上昇処理をして、再びDo〜Loopに戻ってきますね。
しかし、キーを押しっぱなしにしている状態ではどうでしょうか?

    

キーを押しっぱなしにしていると、上昇処理が終わった時点で、すぐにSysEventAvailable()が 1になってしまいます。
したがって、Doループの入り口に差し掛かった時点で、すでに条件が成立し、また上昇処理を行ってしまいます。そのため、押しっぱなしの状態では、下降処理どころか、画面の処理も更新されなくなってしまいます。
そこで、そのような状態を防止するため、Flgというグローバル変数を用意しました。
Flgは、上昇処理が行われた直後に 1 となり、一度Do〜Loopが処理された時に 0 になります。そのため、Doの条件にFlgの値をチェックするようにすれば、押しっぱなし対策を取ることが可能です。
[After]
    Do Until (SysEventAvailable()=1 And Flg=1) 

        Yi=Yi+1
        Flg=1
    Loop

[events]
    
    Flg=0
これで、Flg=0の時は、キーが押されていても、ループから抜けることはありませんので、必ず、画面更新と下降処理が行われます。
また、実際に実行してみて分かったのですが、このまま実行すると、上昇と下降の処理で著しく速度が変わってしまいましたので、
[After]
        If Flg=1 Then
            Delay 0.15
        End If
という時間稼ぎをDo〜Loopの中にいれておきました。この値は、VISOR Platinumでの値ですので、他の機種では適当に変化させると良いでしょう。
(とはいえ、0.15秒が正確かどうかは意外と怪しいですので、この値のままでも問題はないと思われます。)

さて、キーの押しっぱなしに対応するために、Flgなどという変数を持ち出してきましたが、これらはすべてイベントの判定を先頭で行っているから発生する問題でしたね。したがって、このイベント判定を途中や最後に行えば、もう少しスッキリするのではないでしょうか?
[After]
    Do 

        If SysEventAvailable()=1 Then
            Delay 0.15
            Exit Do
        else
            Yi=Yi+1
        End if

    Loop
例えば、途中でイベントを判定するこの方法であれば、変数Flgは不要になるのは明らかでしょう。
BASICプログラムでは、1つの問題を回避することが比較的容易で、それは、他の部分に大きな影響を与えることなく回避することが可能です。
例えば、先ほどの例では、変数Flgを用意して、問題を回避しましたが、その他の部分にはあまり影響を与えていませんね。
ここが、BASICの良いところでもあり、悪いところでもあります。問題解決が容易なのは良いですが、気がつくと「なんだ、この無駄な処理は?」という部分が多く出てきます。
何も考えずにプログラムを作れるBASIC、こんな欠点があることを覚えておきましょう。



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回 逆引き:プログラムを分解解説 「4.自機の移動」