ExcelVBAゲームプログラミング?

初心者でもきっとできる!
Excelさえ持っていれば特別なソフトは不要!
すぐにでも始められる簡単ゲームプログラミング!
今すぐ始めよう!

サンプルやゲームのダウンロードができる別館も好評運営中です。
ご意見やご質問、ゲームの感想等は掲示板までお気軽に。是非、皆さんの声を聞かせてください。運営、開発の励みになります。



各種ダウンロードはコチラ ↓ 意見・感想・質問はコチラ ↓
影倉庫 Shadow warehouse サポート掲示板
ブログの全体像はコチラ ↓ リンクのページはコチラ ↓
サイトマップ 自分本位なリンク


スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。






Chapter.52 [ シューティングゲーム12:敵の攻撃 ]

■継ぎ足し継ぎ足し

前回の講座で、プレイヤーの放ったショットと敵キャラクターの衝突判定が完成しました。だいぶゲームらしくなりましたね。

今回は、敵キャラクターが攻撃してくるようにしてみたいと思います。
実は今回解説する内容は、今までやってきたことの復習みたいな感じになります。なぜなら、今まで紹介してきたノウハウがあれば、敵キャラクターが攻撃してくる仕組みを作るのは、けして難しくないからです。

プレイヤーキャラクターがショットを撃てるようにしたときと同じように、プロシージャを部品化しながら、落ち着いて書き足していけば大丈夫です。もしかしたら、もう既に作ってしまったという人も、中にはいるかもしれませんね。
コードの意味や考え方など、細かな部分は既に説明している内容がほとんどです。今回は簡単な説明を添えながら、一気に作ってしまいましょう。


■材料を準備する

それでは、まずユーザーフォームに、敵ショット用のイメージコントロールを準備するところから始めましょう。

プレイヤーキャラクターの時と同様に、ユーザーフォーム上にコントロールを10個配置します。画像は、下の小さい青弾を使いましょう。この青い弾は8×8ピクセルのgif画像です。

Shot02.gif

これをユーザーフォーム上に配置したイメージコントロールにロードしておきます。8ピクセル四方の画像ですから、イメージコントロールの大きさ( Width・Height )は6ポイントになります。これはもう説明しなくても大丈夫ですね。

290.gif

ここで配置した10個のイメージコントロールは、名前を変更するのを忘れずに。
今回は、敵キャラクター用のショットなので、『E_Shot1 ~ E_Shot10』までの連番で名前をつけておきます。

これで、画面の準備はOKです。
イメージコントロールの数からもわかるように、今回のゲームでは、敵キャラクターのショットの最大数を10個にします。それを踏まえて次に進みましょう。


■コードの記述

敵が撃てるショットの数は10個です。
そこで、この敵用ショットを管理する変数を宣言します。

Public E_Shot_Data(9) As Shot

変数の型に使用している『Shot』は、プレイヤーキャラクターのショットで使用した構造体と同じものです。そのまま使いまわしできます。10個のデータを管理するので、要素数10個の配列として宣言しています。

いつものパターンだと、ここで定数なども宣言しますね。でも今回は全てプレイヤー用のショットで宣言したものを流用できますから、下準備はこれで完了です。簡単ですね。

さて、テンポよくいきましょう。次はプロシージャの準備です。

プレイヤーキャラクターがショットを撃てるようにしたとき、どのように処理していたか覚えているでしょうか。
ショットの『生成』と『移動』を分けて管理していたんでしたね。

ショット生成プロシージャを準備する
    ▼
ショットの移動管理プロシージャを準備する
    ▼
任意の箇所で、座標やタイプを決めて、ショット生成プロシージャを呼び出す

ショットの生成と移動を分けて管理することで、あとあと非常に楽になります。
敵キャラクターの処理を行っている最中に、一定の規則にのっとって『ショット生成プロシージャ』を呼び出せば、あとは勝手にショットが移動していってくれます。プログラマーである我々は、どのタイミングで、どのタイプのショットを発射させるか、これだけを考えればいいわけですね。

もちろん、ショットの軌道を凝ったものにしようと思ったら、ショットの移動プロシージャを修正しなくてはいけません。ですが、そのような場合でも、軌道の計算だけに集中すればいいですから、気持ち的にも管理が楽になります。


それでは早速コードを見ていきましょう。

まずは『ショット生成プロシージャ』からです。これは、構造的にはほとんどプレイヤー用のものと同じです。

Sub E_Shot_Begin(X As Single, Y As Single, Typ As Long)
    
    Dim L As Long
    
    For L = 0 To 9
        With E_Shot_Data(L)
            If Not .Vis Then '①
                .X = X '②
                .Y = Y
                .Typ = Typ
                .Vis = True
                With UserForm1.Controls("E_Shot" & L + 1) '③
                    .Visible = True
                End With
                Exit For '④
            End If
        End With
    Next
    
End Sub

このプロシージャは引数を3つ取ります。
ひとつめは『 X ……横位置 』。ふたつめは『 Y ……縦位置 』。そしてみっつめが『 Typ ……ショットのタイプ 』の3つですね。

まずはプロシージャが開始されると、For文による繰り返し処理が始まります。ショットを管理する変数E_Shot_Dataの、要素Visの状態を調べて、内容がFalseだった場合(ショットが見えていない=まだ撃たれていない)は次に進みます。ここが①ですね。
最初に引数として受け取ったデータを、②の部分で変数に格納します。ここで格納したデータを元に、この次に作成する『ショットの移動管理プロシージャ』が処理を行いますので、ここでは③で示すように、イメージコントロールのVisibleプロパティをTrueにしておけば大丈夫ですね。

そして何気に大切なのが④の部分。ここで『Exit For』を使って繰り返し処理を抜けていますね。こうしておかないと、非表示のショットが全て一度に発射されてしまいますので注意しましょう。


次は『ショットの移動管理プロシージャ』を準備します。
こちらは、ちょっとコードが長くなりますが、落ち着いて見ていけば大丈夫なはずです。知っておくべきことは既にすべて解説しています。

Sub E_Shot_Action()

    Dim L As Long
    Dim LL As Long
    
    For L = 0 To 9
        With E_Shot_Data(L)
            If .Vis Then '①
                Select Case .Typ
                    Case 1
                        .Y = .Y + 4.5
                    Case 2
                        .X = .X + 0.5
                        .Y = .Y + 3.5
                    Case 3
                        .X = .X - 0.5
                        .Y = .Y + 3.5
                End Select
                If .X > 205 Then .Vis = 0 '②
                If .Y > 205 Then .Vis = 0
                If .X < -5 Then .Vis = 0
                If .Y < -5 Then .Vis = 0
                If .X - 2 < Player_Data.X + Player_Data.W Then '③
                    If .X + 2 > Player_Data.X - Player_Data.W Then
                        If .Y - 2 < Player_Data.Y + Player_Data.H Then
                            If .Y + 2 > Player_Data.Y - Player_Data.H Then
                                .Vis = False
                                MsgBox "Hit!" '④
                            End If
                        End If
                    End If
                End If
                With UserForm1.Controls("E_Shot" & L + 1) '⑤
                    If E_Shot_Data(L).Vis Then
                        .Left = E_Shot_Data(L).X - Shot_Size
                        .Top = E_Shot_Data(L).Y - Shot_Size
                    Else
                        .Visible = False
                    End If
                End With
            End If
        End With
    Next
                
End Sub

さぁどうでしょうか。なんとなくやっていることが想像できるでしょうか。

敵キャラクター用のショットは10個ありますから、このプロシージャでもまずはFor文の繰り返し処理が始まります。
そして、ショットが見えているかどうか、①で判断していますね。見えていない場合は何もせずに素通りします。見えている場合は、その後の処理に進みます。
もしもショットが現在見えている状態だった場合には、そのショットを動かさなくてはいけません。
今回は、敵用のショットを3種類用意してみましょう。
ショットのタイプによって、移動の仕方を3種類に分別します。
タイプ1は『まっすぐ進むショット』。タイプ2は『右斜め下に向かって進むショット』で、タイプ3は『左斜め下に向かって進むショット』になります。横位置や縦位置をどのように変化させているかをよく見れば、おのずとわかると思います。

次に②の部分で、ショットが画面の外に出てしまっていないかチェックしています。上下左右全ての方向でチェックします。

③ではプレイヤーキャラクターとの『衝突判定』を行っています。
一見すると複雑に見えるかもしれませんが、単に文字数が少し多いだけですから、落ち着いて計算しましょうね。必ずわかるはずです。
衝突判定を行った結果、ショットとプレイヤーキャラクターが接触していた場合には、④の部分に処理が進みます。
今回は、暫定的な処理として、メッセージを表示するようにしておきます。最終的には、ここで爆発のエフェクトなどを入れて、キャラクターが撃墜されてしまったような演出にする予定です。今はテスト段階ということで、メッセージを表示するようにしておきましょう。

そして⑤、ここは画面上のイメージコントロールを処理している部分ですね。
今まで上のほうで計算してきたのは、あくまでもデータ上での計算です。この部分で、実際にイメージコントロールを移動したり、消したりするわけです。

細かな部分でわからないことがある場合は、面倒でも、以前のChapterに戻って復習してみてくださいね。わからないまま進めてしまうと、さらにわからなくなっていってしまいますので。


■敵キャラクターの処理を修正

さて、ショットの『生成』と『移動』に関する処理が完成しました。
あとは、ショットを実際に発射する敵キャラクターの処理を変更します。

とは言っても、たった1行、処理を追加するだけです。
次のコードで緑文字になっている部分、この1行が肝ですよ。

Sub Enemy_Action()

    Dim L As Long
    Dim LL As Long
    Dim TCMOD
    
    TCMOD = Total_Count Mod 30
    
    For L = 0 To 9
        With Enemy_Data(L)
            If .Lif > 0 Then
                Select Case .Typ
                    Case 1
                        .Y = .Y + 1.5
                    Case 2
                        .X = .X + 0.5
                        .Y = .Y + 1
                    Case 3
                        .X = .X - 0.5
                        .Y = .Y + 1
                End Select
                If .X > 220 Then .Lif = 0
                If .Y > 220 Then .Lif = 0
                If .X < -20 Then .Lif = 0
                If .Y < -20 Then .Lif = 0
                If TCMOD = 0 Then
                    Select Case .Typ
                        Case 2
                            .Typ = 3
                        Case 3
                            .Typ = 2
                    End Select
                    Call E_Shot_Begin(.X, .Y + 2, .Typ)
                End If
            End If
        End With
        With UserForm1.Controls("Enemy" & L + 1)
            If Enemy_Data(L).Lif > 0 Then
                .Left = Enemy_Data(L).X - Enemy_Size
                .Top = Enemy_Data(L).Y - Enemy_Size
            Else
                .Visible = False
            End If
        End With
    Next
    
End Sub

このプロシージャの詳しい解説は、以前の講座で行っています。もしわからないという人は、そちらをご覧ください(Chapter49を参照)。

今回は緑の文字で表示されている部分だけ、簡単に解説します。
ここでは、先ほど作成した『ショット生成プロシージャ』を呼び出しています。ただそれだけです。実際。

今現在の敵キャラクターの座標、そして、敵キャラクターのタイプをそのままショットのタイプとして、引数に渡しています。
敵キャラクターが今現在いる座標上に、タイプに応じてショットが生成されるわけです。
敵のタイプによって、ショットの移動も連動して変化するのですね。

まっすぐ進むタイプの敵は、まっすぐ進むショットを撃ちます。斜めに移動する敵は、斜め方向に進むショットを撃ちます。


■まとめ、全コード掲載

それでは最後に、ここまでの全てのコードを掲載します。
いつもどおり、追加した部分は色が変わっています。講座を見ながら作成している人は、色の変化している部分に注意しながら、適宜コードを追加してみましょう。

ユーザーフォームの方の準備、各種変数の初期化を追加することも忘れずに。

'標準モジュール先頭部分

'◆API 宣言

Declare Function GetTickCount Lib "kernel32" () As Long
Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMillsecounds As Long)

'■キャラクター 構造体宣言
Type Chara
    X As Single
    Y As Single
    W As Single
    H As Single
    Lif As Long
    Typ As Long
    Par As Long
End Type

'■ショット 構造体宣言
Type Shot
    X As Single
    Y As Single
    Vis As Boolean
    Typ As Long
End Type

'●キャラクター 変数宣言
Public Player_Data As Chara
Public Enemy_Data(9) As Chara

'●ショット 変数宣言
Public P_Shot_Data(4) As Shot
Public E_Shot_Data(9) As Shot

'◎その他 変数宣言
Public Shot_Interval As Long
Public Total_Count As Long 'ゲーム中のカウンタ

'▲定数宣言
Public Const Left_Key As Long = 37 'カーソル左
Public Const Up_Key As Long = 38 'カーソル上
Public Const Right_Key As Long = 39 'カーソル右
Public Const Down_Key As Long = 40 'カーソル下
Public Const Esc_Key As Long = 27 'Escキー
Public Const Shot_Key As Long = 90 'Zキー
Public Const Player_Size As Single = 9
'プレイヤーキャラクターの実際の大きさ
Public Const Shot_Size As Single = 3
'弾各種の実際の大きさ
Public Const Enemy_Size As Single = 9
'敵キャラクターの実際の大きさ



'初期化を担当するプロシージャ

Sub Init()
    With Player_Data
        .X = 100
        .Y = 180
        .W = 3
        .H = 3 'プレイヤーの内部的な大きさ
        .Lif = 1
        .Typ = 0
        .Par = 0
    End With
    Erase P_Shot_Data
    Erase E_Shot_Data
    Erase Enemy_Data
    Shot_Interval = 0
    Total_Count = 0
End Sub

'メインプロセスを担当するプロシージャ

Sub Main()

    Dim Flg As Boolean
    Dim Stm As Long
    
    Call Init
    
    Flg = False
    
    Do Until Flg
        Stm = GetTickCount
        Total_Count = Total_Count + 1
        Call Stage_Count
        Call Player_Action
        Call P_Shot_Action
        Call Enemy_Action
        Call E_Shot_Action
        DoEvents
        Do
            Call Sleep(1)
        Loop Until GetTickCount - Stm > 30
        If GetAsyncKeyState(Esc_Key) < 0 Then Flg = True
    Loop
    
End Sub


'敵キャラクターの出現管理プロシージャ

Sub Stage_Count()
    Randomize
    Select Case Total_Count Mod 200
        Case 0
            Call Enemy_Begin(Int(Rnd * 120) + 40, -10, 1)
        Case 50
            Call Enemy_Begin(Int(Rnd * 120) + 40, -10, 2)
        Case 100
            Call Enemy_Begin(Int(Rnd * 120) + 40, -10, 3)
    End Select
End Sub


'プレイヤーキャラクターを担当するプロシージャ

Sub Player_Action()

    With Player_Data

        'キー入力判定と移動処理 
        If GetAsyncKeyState(Left_Key) < 0 Then .X = .X - 3
        If GetAsyncKeyState(Up_Key) < 0 Then .Y = .Y - 3
        If GetAsyncKeyState(Right_Key) < 0 Then .X = .X + 3
        If GetAsyncKeyState(Down_Key) < 0 Then .Y = .Y + 3

        'はみだし防止処理 
        If .X - Player_Size < 0 Then .X = Player_Size
        If .X + Player_Size > 200 Then .X = 200 - Player_Size
        If .Y - Player_Size < 0 Then .Y = Player_Size
        If .Y + Player_Size > 200 Then .Y = 200 - Player_Size

        'ショット生成
        If Shot_Interval = 0 Then
            If GetAsyncKeyState(Shot_Key) < 0 Then
                Call P_Shot_Begin(.X, .Y, 0)
                Shot_Interval = 1
            End If
        Else
            Shot_Interval = Shot_Interval + 1
            If Shot_Interval > 7 Then Shot_Interval = 0
        End If

    End With

    'フォーム上の『Player』を実際に移動する処理 
    With UserForm1.Player
        .Left = Player_Data.X - Player_Size
        .Top = Player_Data.Y - Player_Size
    End With

End Sub


'ショット生成プロシージャ

Sub P_Shot_Begin(X As Single, Y As Single, Typ As Long)

    Dim L As Long

    For L = 0 To 4
        With P_Shot_Data(L)
            If Not .Vis Then
                .X = X
                .Y = Y
                .Typ = Typ
                .Vis = True
                UserForm1.Controls("P_Shot" & L + 1).Visible = True
                Exit For
            End If
        End With
    Next

End Sub


'ショット移動管理プロシージャ

Sub P_Shot_Action()

    Dim L As Long
    Dim LL As Long

    For L = 0 To 4
        With P_Shot_Data(L)
            If .Vis Then
                .Y = .Y - 6
                If .Y < -5 Then .Vis = False
                For LL = 0 To 9
                    If Enemy_Data(LL).Lif > 0 Then
                        If .X - 2 < Enemy_Data(LL).X + Enemy_Data(LL).W Then
                            If .X + 2 > Enemy_Data(LL).X - Enemy_Data(LL).W Then
                                If .Y - 2 < Enemy_Data(LL).Y + Enemy_Data(LL).H Then
                                    If .Y + 2 > Enemy_Data(LL).Y - Enemy_Data(LL).H Then
                                        .Vis = False
                                        With Enemy_Data(LL)
                                            .Lif = 0
                                        End With
                                        Exit For
                                    End If
                                End If
                            End If
                        End If
                    End If
                Next

                With UserForm1.Controls("P_Shot" & L + 1)
                    If P_Shot_Data(L).Vis Then
                        .Left = P_Shot_Data(L).X - Shot_Size
                        .Top = P_Shot_Data(L).Y - Shot_Size
                    Else
                        .Visible = False
                    End If
                End With
            End If
        End With
    Next

End Sub


'敵キャラクター生成プロシージャ

Sub Enemy_Begin(X As Single, Y As Single, Typ As Long)

    Dim L As Long
    
    For L = 0 To 9
        With Enemy_Data(L)
            If .Lif = 0 Then
                .X = X
                .Y = Y
                .W = 5
                .H = 5 '敵キャラクターの内部的な大きさ
                .Typ = Typ
                .Lif = 1
                .Par = 0
                With UserForm1.Controls("Enemy" & L + 1)
                    .Visible = True
                    Select Case Typ
                        Case 1
                            .Picture = UserForm1.Enemy_s1.Picture
                        Case 2, 3
                            .Picture = UserForm1.Enemy_s2.Picture
                    End Select
                End With
                Exit For
            End If
        End With
    Next
    
End Sub


'敵キャラクターを担当するプロシージャ

Sub Enemy_Action()

    Dim L As Long
    Dim TCMOD
    
    TCMOD = Total_Count Mod 30
    
    For L = 0 To 9
        With Enemy_Data(L)
            If .Lif > 0 Then
                Select Case .Typ
                    Case 1
                        .Y = .Y + 1.5
                    Case 2
                        .X = .X + 0.5
                        .Y = .Y + 1
                    Case 3
                        .X = .X - 0.5
                        .Y = .Y + 1
                End Select
                If .X > 220 Then .Lif = 0
                If .Y > 220 Then .Lif = 0
                If .X < -20 Then .Lif = 0
                If .Y < -20 Then .Lif = 0
                If TCMOD = 0 Then
                    Select Case .Typ
                        Case 2
                            .Typ = 3
                        Case 3
                            .Typ = 2
                    End Select
                    Call E_Shot_Begin(.X, .Y + 2, .Typ)
                End If
            End If
        End With
        With UserForm1.Controls("Enemy" & L + 1)
            If Enemy_Data(L).Lif > 0 Then
                .Left = Enemy_Data(L).X - Enemy_Size
                .Top = Enemy_Data(L).Y - Enemy_Size
            Else
                .Visible = False
            End If
        End With
    Next
    
End Sub


'敵キャラクターショット生成プロシージャ

Sub E_Shot_Begin(X As Single, Y As Single, Typ As Long)
    
    Dim L As Long
    
    For L = 0 To 9
        With E_Shot_Data(L)
            If Not .Vis Then
                .X = X
                .Y = Y
                .Typ = Typ
                .Vis = True
                With UserForm1.Controls("E_Shot" & L + 1)
                    .Visible = True
                End With
                Exit For
            End If
        End With
    Next
    
End Sub



'敵キャラクターショット移動管理プロシージャ

Sub E_Shot_Action()

    Dim L As Long
    Dim LL As Long
    
    For L = 0 To 9
        With E_Shot_Data(L)
            If .Vis Then
                Select Case .Typ
                    Case 1
                        .Y = .Y + 4.5
                    Case 2
                        .X = .X + 0.5
                        .Y = .Y + 3.5
                    Case 3
                        .X = .X - 0.5
                        .Y = .Y + 3.5
                End Select
                If .X > 205 Then .Vis = 0
                If .Y > 205 Then .Vis = 0
                If .X < -5 Then .Vis = 0
                If .Y < -5 Then .Vis = 0
                If .X - 2 < Player_Data.X + Player_Data.W Then
                    If .X + 2 > Player_Data.X - Player_Data.W Then
                        If .Y - 2 < Player_Data.Y + Player_Data.H Then
                            If .Y + 2 > Player_Data.Y - Player_Data.H Then
                                .Vis = False
                                MsgBox "Hit!"
                            End If
                        End If
                    End If
                End If
                With UserForm1.Controls("E_Shot" & L + 1)
                    If E_Shot_Data(L).Vis Then
                        .Left = E_Shot_Data(L).X - Shot_Size
                        .Top = E_Shot_Data(L).Y - Shot_Size
                    Else
                        .Visible = False
                    End If
                End With
            End If
        End With
    Next
                
End Sub




'標準モジュールはここまで
'ここから下はフォームモジュールに記述


'コマンドボタンのクリックイベント

Private Sub Com_Start_Click()
    Com_Start.Enabled = False
    Call Main
    Unload UserForm1
End Sub


'ユーザーフォームのクエリクロースイベント

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    End
End Sub


'ユーザーフォームのイニシャライズイベント
Private Sub UserForm_Initialize()
    UserForm1.Width = 220
End Sub

いやぁ……これは長いですね。

でも、修正している箇所に絞って考えれば、それほどでもないはずです。
初期化処理や、変数の宣言箇所などは忘れやすいですから注意しましょう。

それから、敵キャラクターとプレイヤーキャラクターに、あたり判定部分を設定しなければならないので、キャラクター構造体の、要素 W と要素 H に値を設定するのも忘れずにやっておきましょう。
これをやっておかないと、キャラクターとショットの衝突が正しく判定されませんのでね。


さて、どうですか? 皆さんうまくいったでしょうか。
今回の講座の内容を実装させたら、見た目上はほとんど完璧にシューティングゲームになったはずです。
敵が攻撃してくる、それを避けながらプレイヤーがショットを撃つ……。これはもうシューティングゲームの基盤を実装できている状態、と言っていいでしょう。

実践講座1として、全19回にわたってシューティングゲームを作成してきました。実践講座1は、これにて修了とします。ここまでがんばってきた皆さん、お疲れ様でした。
そして、当初の約束では、もう少し凝った内容のシューティングゲームを作成することになっていました。それらの発展系技術は、実践講座2として、引き続きお送りしていく予定です。

実践講座2では、演出などの技術を中心に、さらに上を目指して取り組んでいきます。
実践講座1の内容をしっかりと理解し、とりあえず、ここまでを完璧に作ってみてください。ちゃんと遊べる状態になりましたので、それなりの達成感も味わえるはずです。

講座内で何度も言っていますが、プログラミングに限らず、全ての学習は『諦めず』そして『自分のレベルに合ったものから取り組む』ことが非常に大切です。
より良いものを完成させるためには、焦りは禁物です。じっくりゆっくりで構いません。自分にとって最良のレベルの内容をしっかり見極めて、突き進んでいってくださいね。

とりあえず、ここまで長いこと、本当にお疲れ様でした。


サンプルダウンロード ⇒ コチラよりダウンロードできます。(別館)


■格言

必要に応じて過去の講座を復習
焦らず、そして諦めず!


結局最後はこんなありきたりな格言になってしまいました……。
実践講座2もお楽しみに。
スポンサーサイト






実践講座2:演出を考える

実践講座1では、シューティングゲームを実際に作成してきました。まだ完成にはもう少しですが、ほとんど基本的な部分はできあがっています。

そして、この実践講座2では、総合的なテーマとして『演出を考える』という題を掲げています。
業務用のアプリケーションソフトには、必ずしも演出は必要ではありません。むしろ、このようなソフトに必要とされるのは、『軽快な動作』であったり『見やすく扱い易いインターフェイス』であったりすることが多いですよね。

これに対して、ゲームは全く逆の性質を持っています。
演出の仕方によって、総合的なゲームの完成度は大きく変わってきます。これはシューティングゲームに限った話ではありません。
ゲームが人(自分も含めて)を楽しませるためのものである以上、凝った演出でプレイヤーを喜ばせる、あるいは、思わず驚くような演出でプレイヤーをハラハラさせる、といった要素は、できる限り追及したほうがいいでしょう。

開発者のスタンスとして、シンプルイズベスト、を目指してプログラミングすることもあるでしょう。
ですが、実践講座2では、むしろ凝った演出をしたい場合に参考になりそうなプログラムを紹介していくつもりです。



    Chapter.53 [ シューティングゲーム13:爆発エフェクト ]

    Chapter.54 [ シューティングゲーム14:残機数表示① ]

    Chapter.55 [ シューティングゲーム15:残機数表示② ]

    Chapter.56 [ シューティングゲーム16:スコアの表示 ]

    Chapter.57 [ シューティングゲーム17:タイトル画面 ]

    Chapter.58 [ シューティングゲーム18:ボスキャラクター ]

    Chapter.59 [ シューティングゲーム19:最後の仕上げへ ]

    Chapter.60 [ シューティングゲーム20:いよいよ完成STG ]

    Chapter.61 [ カードゲームで使えるめくり効果 ]

    Chapter.62 [ ラジアンと角度 ]

    Chapter.63 [ ラジアンの活用:円運動 ]

    Chapter.64 [ ラジアンの活用:任意の角度へ移動する ]

    Chapter.65 [ APIによるサウンド再生:基礎 ]

    Chapter.66 [ APIによるサウンド再生:MIDIと多重再生 ]

    Chapter.67 [ APIによるサウンド再生:MCIコマンドとループ再生 ]









Chapter.53 [ シューティングゲーム13:爆発エフェクト ]

■エフェクト

エフェクトとひとくちに言っても、実際には色々なものがあります。シューティングゲームだけで考えても、ショットを撃った瞬間の閃光や、敵キャラクターを倒したときの爆風などが思い浮かびますね。
それ以外にも、アイテムを取った瞬間にキャラクターが光るようにしたりすれば、それも立派なエフェクトです。ゲームの開始時に、タイトルロゴがカットインしてくるようにすればこれもエフェクトですね。

今回は、シューティングゲームに、爆発のエフェクトを追加してみたいと思います。
これが実装できると、敵キャラクターが爆発しながら消えるので、ショットが当たった感触が強くなりますね。プレイヤーが被弾した際も、おもわず『しまった!』と言いたくなるでしょう。
爆発のエフェクトなんてほんの小さなことですが、あるとないとではゲームの完成度が違ってきます。見た目もかなり良くなります。

考え方としては、キャラクターをアニメーションさせたときのように、爆発のエフェクト用画像を切り替えながら連続して表示します。今回は以下の5枚の画像を使って、爆発のエフェクトを実装させてみることにしましょう。画像の大きさは40×40ピクセルです。

画像1画像2画像3画像4画像5
bom01.gif
bom02.gif
bom03.gif
bom04.gif
bom05.gif


これが連続して表示されると、まるで爆発しているように見えます。多分……。


■ユーザーフォームの準備

まずは、前回までに作成しているシューティングゲームに、爆発のエフェクト用画像をロードしましょう。
先ほどの5枚の画像をユーザーフォーム上のイメージコントロールにロードして、名前を変更します。

300.gif

画像をキチンとロードして、名前を変更します。Burst_s1~Burst_s5までの連番にしておきましょう。はじめから順番に講座を進めてきている方ならわかると思いますが、ここで配置したイメージコントロールは『原画の役割』を担うコントロールです。ですから、エフェクトを表示するときに、このコントロールがそのまま表示されるわけではありません。

実際に画面上でエフェクトとして使われるイメージコントロールは、フレームの中に配置します。

301.gif

ここで配置した3つのイメージコントロールは名前をBurst1~Burst3としておきます。実際に表示されることになるわけですから、透過処理が行われるように、キチンとBackStyleプロパティや、BorderStyleプロパティを設定しておきましょう。詳しくは過去の講座をご覧ください(Chapter25を参照)。
そして忘れてはならないのが、Visibleプロパティです。
ゲームを開始したときに、始めから爆発用のエフェクトが表示されていてはおかしいですから、必ずVisibleプロパティにはFalseを設定しておきましょう。

ここまでできたら、ユーザーフォームの準備はOKです。次はコードを記述していきます。


■爆発エフェクトの考え方

爆発のエフェクトは、ゲームのキャラクターというわけではありません。あくまでも演出のひとつに過ぎません。ですが、プログラムから見た場合にはキャラクターとの違いはほとんどありません。
例えば『敵キャラクター』は、画面の外から勝手に出てきて、放っておけば勝手に画面外へ消えていきます。爆発エフェクトだって同じです。ショットがヒットしたときに勝手に発生して、一定時間表示されたら勝手に消えます。ですから、敵キャラクターの処理と同じような感じでプログラムを組めば、それで爆発のエフェクトを作成することができます。

爆発エフェクト生成プロシージャをつくる
    ▼
爆発エフェクトの管理プロシージャがアニメーションさせる
    ▼
一定のコマ数アニメーションしたら消す

こんな流れでやればいいわけですね。

それではまず、爆発エフェクト用に、構造体と変数を準備します。
構造体の要素は、キャラクター用の構造体とかぶっている部分もありますが、先々を考えて一応別のものを用意しておきます。

'■汎用オブジェクト 構造体宣言
Type Obj
    X As Single '横位置
    Y As Single '縦位置
    W As Single '横幅
    H As Single '縦幅
    Par As Long 'パラメータ
End Type

'●汎用オブジェクト 変数宣言
Public Burst_Data(2) As Obj

爆発のエフェクトは同時にいくつも発生する可能性があります。そこで先ほどもフレーム内に3つのイメージコントロールを配置したわけです。同じように、爆発エフェクト用の配列変数も、『Burst_Data(2)』としておき、要素数が3つになるようにしておきます。

さらに、キャラクターなどと同じように定数を使って爆発エフェクトの大きさを先に定義しておきます。

Public Const Burst_Size As Single = 15

爆発エフェクトの元となる画像は、40×40ピクセルでしたね。ということは、ユーザーフォーム上に配置されているイメージコントロールの大きさは、30×30ポイントになります。これはポイントとピクセルという単位の違いから発生する大きさの差でしたね。
コードの中で扱いやすくするために30ポイントをあらかじめ半分にしておき、それを定数化したのが、このBurst_Sizeという定数になります。
画面上にエフェクトを配置する段階になると、この定数が非常に役に立ちます。あとで使いますので、定数の宣言だけはしっかりやっておいてくださいね。


■エフェクト生成、管理まで

それでは早速『爆発エフェクト生成プロシージャ』を作りましょう。
敵キャラクターやショットのときと同じように、エフェクトを生成する座標は、引数として受け取るようにしておきます。
こうすることで、どの場所でショットがヒットしても、任意の場所にエフェクトを生成することができるようになるからですね。

Sub Burst_Begin(X As Single, Y As Single)

    Dim L As Long
    
    For L = 0 To 2
        With Burst_Data(L)
            If .Par = 0 Then
                .X = X
                .Y = Y
                .W = Burst_Size
                .H = Burst_Size
                .Par = 1
                UserForm1.Controls("Burst" & L + 1).Visible = True
                Exit For
            End If
        End With
    Next
    
End Sub

ここまで順番に進めてきていれば、この内容は問題ないと思います。
やっていることは至極単純です。

爆発用のエフェクトの数だけ繰り返し処理しますので、まずはFor文での繰り返し処理から始まります。そしてまず、要素Par(パラメータ)の値を調べます。このパラメータという要素は、爆発のエフェクトが今現在どのような状態かを表しています。

要素Parの値とエフェクトの関係
    Par = 0 …… エフェクトは非表示
    Par = 1 …… 1枚目の画像を表示中
    Par = 2 …… 2枚目の画像を表示中
    Par = 3 …… 以下同様に続く

最初に要素Parの値を調べて、0だったときだけその先に進みます。それ以外の場合は、既に表示されているということになるからです。

要素Parが0だったときは、引数として受け取った要素を、変数に格納していきます。ここで、先ほど定義した定数Burst_Sizeが出てきていますね。
さらに、ここで要素Parに1を代入しておきます。これで、非表示から表示中に切り替わるわけですね。
そして、ひとつの爆発エフェクトを生成したら、Exit Forで繰り返し処理を抜けておきます。こうしておかないと、非表示のエフェクトが全て一度に生成されてしまいますから変なことになっちゃいます。

さて、ここまではついてこれていますか?
次は、上のプロシージャで生成した爆発エフェクトを、管理するためのプロシージャを作りましょう。

Sub Burst_Action()

    Dim L As Long
    Dim Pict As Long '①
    
    For L = 0 To 2
        With Burst_Data(L)
            If .Par > 0 Then
                Select Case .Par '②
                    Case 1
                        Pict = 1
                    Case 2, 3
                        Pict = 2
                    Case 4, 5
                        Pict = 3
                    Case 6, 7
                        Pict = 4
                    Case 8
                        Pict = 5
                    Case 9
                        .Par = -1
                End Select
                .Par = .Par + 1 '③
                With UserForm1.Controls("Burst" & L + 1)
                    If Burst_Data(L).Par > 0 Then '④
                        .Left = Burst_Data(L).X - Burst_Size
                        .Top = Burst_Data(L).Y - Burst_Size
                        .Picture = UserForm1.Controls("Burst_s" & Pict).Picture
                    Else
                        .Visible = False
                    End If
                End With
            End If
        End With
    Next
                
End Sub

少し長いですが、ひとつひとつ見ていきましょう。

まずは①です。
今回はここで宣言している変数Pictが重要な役割を果たします。PictPictureを短くしただけです。要するに、いまどの画像を表示するかをこの変数が管理します。

繰り返し処理で、表示されているエフェクト全てを処理していきます。
まずはIf文を使って要素Parの値を調べます。0よりも大きい(1以上)なら、エフェクトが表示されているということなので、その先の処理に進みます。
そして、②で示すように、要素Parの現在の値に応じて、Select Case文で処理を分岐させます。

ここで、要素Parの値によって、最初に宣言した変数Pictに代入する数値を変化させます。ここで変数Pictに1を代入しているなら、最終的には1枚目のエフェクト画像が使われます。3を代入しているなら3枚目の画像が使われますね。
もしも、要素Parが9だった場合には、今度はエフェクトを消してしまうようにしますので、直接Parに『 -1 』を代入してしまいます。

Select Case文での分岐処理が完了したら、要素Parの値をプラス1します。これをやっておかないと、エフェクトがアニメーションされません。要素Parの値の応じて表示される画像を切り替えているので、ループするごとに毎回プラス1するわけですね。これが③です。

そして次に④。
ここではユーザーフォーム上のイメージコントロールの処理をしています。先ほどまではあくまでもデータ上での処理でしたが、ここで実際にコントロールの画像の切り替えなどをしているわけです。


■まとめ、全コード掲載

さて、前回までに作成しているコードに、上記のコードを追加します。
ただし、緑文字で表示されている部分をよく見てもらえればわかりますが、変数の初期化なども追加されていますので、注意してくださいね。

'標準モジュール先頭部分

'◆API 宣言

Declare Function GetTickCount Lib "kernel32" () As Long
Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMillsecounds As Long)

'■キャラクター 構造体宣言
Type Chara
    X As Single
    Y As Single
    W As Single
    H As Single
    Lif As Long
    Typ As Long
    Par As Long
End Type

'■ショット 構造体宣言
Type Shot
    X As Single
    Y As Single
    Vis As Boolean
    Typ As Long
End Type

'■汎用オブジェクト 構造体宣言
Type Obj
    X As Single
    Y As Single
    W As Single
    H As Single
    Par As Long
End Type


'●キャラクター 変数宣言
Public Player_Data As Chara
Public Enemy_Data(9) As Chara

'●ショット 変数宣言
Public P_Shot_Data(4) As Shot
Public E_Shot_Data(9) As Shot

'●汎用オブジェクト 変数宣言
Public Burst_Data(2) As Obj

'◎その他 変数宣言
Public Shot_Interval As Long
Public Total_Count As Long 'ゲーム中のカウンタ

'▲定数宣言
Public Const Left_Key As Long = 37 'カーソル左
Public Const Up_Key As Long = 38 'カーソル上
Public Const Right_Key As Long = 39 'カーソル右
Public Const Down_Key As Long = 40 'カーソル下
Public Const Esc_Key As Long = 27 'Escキー
Public Const Shot_Key As Long = 90 'Zキー
Public Const Player_Size As Single = 9
'プレイヤーキャラクターの実際の大きさ
Public Const Shot_Size As Single = 3
'弾各種の実際の大きさ
Public Const Enemy_Size As Single = 9
'敵キャラクターの実際の大きさ
Public Const Burst_Size As Single = 15
'爆発エフェクトの実際の大きさ


'初期化を担当するプロシージャ

Sub Init()
    With Player_Data
        .X = 100
        .Y = 180
        .W = 3
        .H = 3
        .Lif = 1
        .Typ = 0
        .Par = 0
    End With
    Erase P_Shot_Data
    Erase E_Shot_Data
    Erase Enemy_Data
    Erase Burst_Data
    Shot_Interval = 0
    Total_Count = 0
End Sub

'メインプロセスを担当するプロシージャ

Sub Main()

    Dim Flg As Boolean
    Dim Stm As Long
    
    Call Init
    
    Flg = False
    
    Do Until Flg
        Stm = GetTickCount
        Total_Count = Total_Count + 1
        Call Stage_Count
        Call Player_Action
        Call P_Shot_Action
        Call Enemy_Action
        Call E_Shot_Action
        Call Burst_Action
        DoEvents
        Do
            Call Sleep(1)
        Loop Until GetTickCount - Stm > 30
        If GetAsyncKeyState(Esc_Key) < 0 Then Flg = True
    Loop
    
End Sub


'敵キャラクターの出現管理プロシージャ

Sub Stage_Count()
    Randomize
    Select Case Total_Count Mod 200
        Case 0
            Call Enemy_Begin(Int(Rnd * 120) + 40, -10, 1)
        Case 50
            Call Enemy_Begin(Int(Rnd * 120) + 40, -10, 2)
        Case 100
            Call Enemy_Begin(Int(Rnd * 120) + 40, -10, 3)
    End Select
End Sub


'プレイヤーキャラクターを担当するプロシージャ

Sub Player_Action()

    With Player_Data

        'キー入力判定と移動処理 
        If GetAsyncKeyState(Left_Key) < 0 Then .X = .X - 3
        If GetAsyncKeyState(Up_Key) < 0 Then .Y = .Y - 3
        If GetAsyncKeyState(Right_Key) < 0 Then .X = .X + 3
        If GetAsyncKeyState(Down_Key) < 0 Then .Y = .Y + 3

        'はみだし防止処理 
        If .X - Player_Size < 0 Then .X = Player_Size
        If .X + Player_Size > 200 Then .X = 200 - Player_Size
        If .Y - Player_Size < 0 Then .Y = Player_Size
        If .Y + Player_Size > 200 Then .Y = 200 - Player_Size

        'ショット生成
        If Shot_Interval = 0 Then
            If GetAsyncKeyState(Shot_Key) < 0 Then
                Call P_Shot_Begin(.X, .Y, 0)
                Shot_Interval = 1
            End If
        Else
            Shot_Interval = Shot_Interval + 1
            If Shot_Interval > 7 Then Shot_Interval = 0
        End If

    End With

    'フォーム上の『Player』を実際に移動する処理 
    With UserForm1.Player
        .Left = Player_Data.X - Player_Size
        .Top = Player_Data.Y - Player_Size
    End With

End Sub


'ショット生成プロシージャ

Sub P_Shot_Begin(X As Single, Y As Single, Typ As Long)

    Dim L As Long

    For L = 0 To 4
        With P_Shot_Data(L)
            If Not .Vis Then
                .X = X
                .Y = Y
                .Typ = Typ
                .Vis = True
                UserForm1.Controls("P_Shot" & L + 1).Visible = True
                Exit For
            End If
        End With
    Next

End Sub


'ショット移動管理プロシージャ

Sub P_Shot_Action()

    Dim L As Long
    Dim LL As Long

    For L = 0 To 4
        With P_Shot_Data(L)
            If .Vis Then
                .Y = .Y - 6
                If .Y < -5 Then .Vis = False
                For LL = 0 To 9
                    If Enemy_Data(LL).Lif > 0 Then
                        If .X - 2 < Enemy_Data(LL).X + Enemy_Data(LL).W Then
                            If .X + 2 > Enemy_Data(LL).X - Enemy_Data(LL).W Then
                                If .Y - 2 < Enemy_Data(LL).Y + Enemy_Data(LL).H Then
                                    If .Y + 2 > Enemy_Data(LL).Y - Enemy_Data(LL).H Then
                                        .Vis = False
                                        With Enemy_Data(LL)
                                            Call Burst_Begin(.X, .Y)
                                            .Lif = 0
                                        End With
                                        Exit For
                                    End If
                                End If
                            End If
                        End If
                    End If
                Next
                With UserForm1.Controls("P_Shot" & L + 1)
                    If P_Shot_Data(L).Vis Then
                        .Left = P_Shot_Data(L).X - Shot_Size
                        .Top = P_Shot_Data(L).Y - Shot_Size
                    Else
                        .Visible = False
                    End If
                End With
            End If
        End With
    Next

End Sub


'敵キャラクター生成プロシージャ

Sub Enemy_Begin(X As Single, Y As Single, Typ As Long)

    Dim L As Long
    
    For L = 0 To 9
        With Enemy_Data(L)
            If .Lif = 0 Then
                .X = X
                .Y = Y
                .W = 5
                .H = 5
                .Typ = Typ
                .Lif = 1
                .Par = 0
                With UserForm1.Controls("Enemy" & L + 1)
                    .Visible = True
                    Select Case Typ
                        Case 1
                            .Picture = UserForm1.Enemy_s1.Picture
                        Case 2, 3
                            .Picture = UserForm1.Enemy_s2.Picture
                    End Select
                End With
                Exit For
            End If
        End With
    Next
    
End Sub


'敵キャラクターを担当するプロシージャ

Sub Enemy_Action()

    Dim L As Long
    Dim TCMOD
    
    TCMOD = Total_Count Mod 30
    
    For L = 0 To 9
        With Enemy_Data(L)
            If .Lif > 0 Then
                Select Case .Typ
                    Case 1
                        .Y = .Y + 1.5
                    Case 2
                        .X = .X + 0.5
                        .Y = .Y + 1
                    Case 3
                        .X = .X - 0.5
                        .Y = .Y + 1
                End Select
                If .X > 220 Then .Lif = 0
                If .Y > 220 Then .Lif = 0
                If .X < -20 Then .Lif = 0
                If .Y < -20 Then .Lif = 0
                If TCMOD = 0 Then
                    Select Case .Typ
                        Case 2
                            .Typ = 3
                        Case 3
                            .Typ = 2
                    End Select
                    Call E_Shot_Begin(.X, .Y + 2, .Typ)
                End If
            End If
        End With
        With UserForm1.Controls("Enemy" & L + 1)
            If Enemy_Data(L).Lif > 0 Then
                .Left = Enemy_Data(L).X - Enemy_Size
                .Top = Enemy_Data(L).Y - Enemy_Size
            Else
                .Visible = False
            End If
        End With
    Next
    
End Sub


'敵キャラクターショット生成プロシージャ

Sub E_Shot_Begin(X As Single, Y As Single, Typ As Long)
    
    Dim L As Long
    
    For L = 0 To 9
        With E_Shot_Data(L)
            If Not .Vis Then
                .X = X
                .Y = Y
                .Typ = Typ
                .Vis = True
                With UserForm1.Controls("E_Shot" & L + 1)
                    .Visible = True
                End With
                Exit For
            End If
        End With
    Next
    
End Sub


'敵キャラクターショット移動管理プロシージャ

Sub E_Shot_Action()

    Dim L As Long
    Dim LL As Long
    
    For L = 0 To 9
        With E_Shot_Data(L)
            If .Vis Then
                Select Case .Typ
                    Case 1
                        .Y = .Y + 4.5
                    Case 2
                        .X = .X + 0.5
                        .Y = .Y + 3.5
                    Case 3
                        .X = .X - 0.5
                        .Y = .Y + 3.5
                End Select
                If .X > 205 Then .Vis = 0
                If .Y > 205 Then .Vis = 0
                If .X < -5 Then .Vis = 0
                If .Y < -5 Then .Vis = 0
                If .X - 2 < Player_Data.X + Player_Data.W Then
                    If .X + 2 > Player_Data.X - Player_Data.W Then
                        If .Y - 2 < Player_Data.Y + Player_Data.H Then
                            If .Y + 2 > Player_Data.Y - Player_Data.H Then
                                .Vis = False
                                With Player_Data
                                    Call Burst_Begin(.X, .Y)
                                    .Lif = 0
                                End With

                            End If
                        End If
                    End If
                End If
                With UserForm1.Controls("E_Shot" & L + 1)
                    If E_Shot_Data(L).Vis Then
                        .Left = E_Shot_Data(L).X - Shot_Size
                        .Top = E_Shot_Data(L).Y - Shot_Size
                    Else
                        .Visible = False
                    End If
                End With
            End If
        End With
    Next
                
End Sub


'爆発エフェクト生成プロシージャ

Sub Burst_Begin(X As Single, Y As Single)

    Dim L As Long
    
    For L = 0 To 2
        With Burst_Data(L)
            If .Par = 0 Then
                .X = X
                .Y = Y
                .W = Burst_Size
                .H = Burst_Size
                .Par = 1
                UserForm1.Controls("Burst" & L + 1).Visible = True
                Exit For
            End If
        End With
    Next
    
End Sub



'爆発エフェクト管理プロシージャ

Sub Burst_Action()

    Dim L As Long
    Dim Pict As Long
    
    For L = 0 To 2
        With Burst_Data(L)
            If .Par > 0 Then
                Select Case .Par
                    Case 1
                        Pict = 1
                    Case 2, 3
                        Pict = 2
                    Case 4, 5
                        Pict = 3
                    Case 6, 7
                        Pict = 4
                    Case 8
                        Pict = 5
                    Case 9
                        .Par = -1
                End Select
                .Par = .Par + 1
                With UserForm1.Controls("Burst" & L + 1)
                    If Burst_Data(L).Par > 0 Then
                        .Left = Burst_Data(L).X - Burst_Size
                        .Top = Burst_Data(L).Y - Burst_Size
                        .Picture = UserForm1.Controls("Burst_s" & Pict).Picture
                    Else
                        .Visible = False
                    End If
                End With
            End If
        End With
    Next
                
End Sub



'標準モジュールはここまで
'ここから下はフォームモジュールに記述


'コマンドボタンのクリックイベント

Private Sub Com_Start_Click()
    Com_Start.Enabled = False
    Call Main
    Unload UserForm1
End Sub


'ユーザーフォームのクエリクロースイベント

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    End
End Sub


'ユーザーフォームのイニシャライズイベント
Private Sub UserForm_Initialize()
    UserForm1.Width = 220
End Sub

さて、今回のポイントとなるのは、黄色い文字で表示されている部分です。

今回作成した爆発エフェクトの生成プロシージャを、衝突判定の中から呼び出すようにします。
前回までは、プレイヤーキャラクターがショットに被弾したときにはメッセージを表示していました。今回は爆発のエフェクトを生成するように変更したので、敵のショットに当たってしまうと、そこに爆発のエフェクトが発生するようになったはずです。
敵キャラクターにプレイヤーのショットがヒットした場合も同様です。敵が死んだときに、そこに爆発のエフェクトが発生します。

これで、かなりリアルになったのではないでしょうか。
演出の大切さがわかると思います。

大切なのは、エフェクトであっても、その考え方はキャラクターと同じという点です。
キャラクターであれ、ショットであれ、また、エフェクトであっても、プロシージャを部品化して『生成』と『移動や管理』を別々に処理することには変わりありません。

こうして部品化された構造になっていれば、うまくいかなかったときに、どこが原因かわかりやすいです。だんだん全体の構造が複雑になってきてはいますが、細かく区切って考えればそれほど難しくはありません。考え方も似ていますしね。

諦めずに、がんばってみてください。


サンプルダウンロード ⇒ コチラよりダウンロードできます。(別館)


■格言

エフェクトもキャラクターも中身はほとんど同じ
パラメータをうまく使ってアニメーションさせる


かっこいいエフェクトを実装させましょう。






Chapter.54 [ シューティングゲーム14:残機数表示① ]

■生存と死亡

前回の講座では、爆発のエフェクトを実装させました。
毎回少しずつですが、確実に完成度が高まっていますね。最終的にはそれなりのものが出来上がるはずです。がんばってくださいね。

さて、今回は、シューティングゲームに残機数という概念を実装させたいと思います。
今のところ、敵が放ったショットに当たってしまっても、プレイヤーキャラクターはへっちゃらで動き続けてしまっています。
ここに『残機数』という概念を導入することで、『3回死んだらゲームオーバー』という仕様にすることができます。

今回は考え方さえ理解できれば、それほど難しい内容ではありません。落ち着いて、ゲームを修正していきましょう。


まず、プレイヤーキャラクターの残機数を表す、専用の変数を用意します。

Public Player_Stock As Long

この変数Player_Stockは、キャラクターの残機数を管理するのに使います。ですから、ゲームの開始時に、初期化処理の中で変数Player_Stockに2を代入しておき、1回死亡するたびに、マイナス1ずつ減らしていきます。
この変数Player_Stockが0のときに死んでしまったら、それがゲームオーバーの合図というわけです。

これを実装するだけでは非常に簡単で、なんとなく物足りないので、プレイヤーキャラクターの登場と合わせて、少し演出を考えてみましょう。


■登場の仕方

プレイヤーキャラクターは、ゲームの開始前から画面上に表示されています。
これを少し変更して、画面の外から登場するようにしてみましょう。

これを実現するために、構造体の要素である『 LifPar 』を有効に活用します。

'■キャラクター 構造体宣言
Type Chara
    X As Single    '横位置
    Y As Single    '縦位置
    W As Single    '横幅
    H As Single    '縦幅
    Lif As Long    'ライフ
    Typ As Long    'タイプ
    Par As Long    '汎用パラメーター
End Type

キャラクターを管理するための構造体Charaは上のような感じで定義されていましたね。
このうち、『 Lif・Typ・Par 』の3つは、今のところ何の用途にも使われていません。これを今回の演出のために使うことにしましょう。

要素 Lif …… 生きているか死んでいるか
要素 Par …… 登場演出中かどうか

要素Lifの値が0のとき、これはプレイヤーキャラクターが死んでいます。画面に表示されているかどうかは関係ありません。とにかく、操作できない状態ということです。
そして要素Parですが、こちらは『演出中かどうか』を管理します。
この要素が0のときは、演出中ではないということにします。

それを踏まえて考えると、ゲームの流れは次のようになります。

ゲームの流れ
ゲーム中のシーンLifの状態Parの状態
ゲームの開始時 1 1 死んではいない、出現の演出をする
ゲームプレイ中 1 0 死んではいない、演出はしていない
被弾(死亡) 0 1 死んだ、死亡時の演出をする
死亡演出から出現へ 1 1 生き返らす、出現の演出をする
ゲームプレイ中 1 0 死んではいない、演出はなし

どうでしょうか、なんとなくイメージできましたか?

大事なことは、死んでいるかどうか、あるいは演出中かどうか、これです。


■それでは具体的に何をする?

さて、それでは具体的には何をすればいいのでしょうか?
先ほど説明したように、死亡 ⇒ 復活 までの演出を行うためには、2段階に分けて判断をしなくてはいけません。
まずは、『生きているか、死んでいるか』、そして『演出中か、そうではないか』の2段階ですね。この2つをチェックしながら処理すれば、復活時の演出が可能になります。
これは条件によって処理を分岐させればいいわけですから、If文を使えばできそうですね。

全体を把握しやすくするために、プレイヤーキャラクターの処理を行っている、プロシージャPlayer_Actionが、どのような構造になっているか考えましょう。
現状では、以下のようになっています。

Player_Actionの構造

キー入力判定処理
はみだし防止処理
ショットを撃つかどうかの判断
ユーザーフォーム上の処理

特に条件判断などは行っておらず、大まかに分けて4つの処理を素直に行っている状態です。

次は、これに先ほどの2段階の条件判断を含めてみます。

あたらしいPlayer_Actionの構造

If 生きているか = 生きている Then
    If 演出中かどうか = 演出中 Then
        復活時の演出
    Else
        キー入力判定処理
        はみだし防止処理
        ショットを撃つかどうかの判断
    End If
Else
    死亡時の演出
End If
ユーザーフォーム上の処理

先ほどの表と合わせて見ていくとわかりやすいのではないかと思います。
プレイヤーキャラクターを操作できるときというのは、『生きていて、かつ、演出中ではない』というタイミングの場合だけなのですね。死んでいるときは、当然操作できるわけがありませんし、演出を行っている間に、勝手に動いてしまうのも困ります。2つの条件分岐にかけることによって、適切な場合だけ、プレイヤーはキャラクターを操作することができるようになるのです。

ポイントは、『ユーザーフォーム上の処理』を条件分岐の外に出しておくということでしょう。
ユーザーフォーム上のキャラクターは、演出をしていようが、あるいはキャラクターが死んでいようが、どのような場合であっても何かしらのアクションをとります。表示したり消したり、あるいは動かしたりしますものね。ユーザーフォーム上の処理は、特定の場合に限ったことではなく、常に行わなければならないことなのです。

ユーザーフォーム上の処理を条件分岐の中に含めてしまうと、特定の場合にしか画面上のキャラクターが更新されなくなってしまいます。これでは死んだあと画面の外から登場するという、復活時の演出ができなくなってしまうので本末転倒です。演出中であっても死亡時であっても、プログラムからはキャラクターを操作できるようにしておかないと困るわけです。

なんとなく考え方はイメージできたでしょうか。構造をよく考えて、落ち着いてイメージしてみてくださいね。


■ユーザーフォームの準備をする

さて、考え方がなんとなくわかったところで、残機数表示という本来のテーマに戻りましょう。

復活時の演出をすることも含め、今回変更する部分を実装するための準備をします。

まずは、ユーザーフォーム上に配置されているイメージコントロールPlayerに、ひとつ変更を加えます。

310.gif

今現在は、ゲームの開始時には、すでに画面上にプレイヤーキャラクターが表示されている状態です。
今回は、登場するときに画面の外から登場するようにしたいので、ゲームの開始時には非表示の状態になっているようにしておきましょう。

イメージコントロールPlayerの、Visibleプロパティを、現在のTrueからFalseに変更しておきます。

311.gif

    ▼

312.gif

さらに、イメージコントロールを2つ、追加することにします。
この新たに追加するイメージコントロールは、残機数を視覚的に確認するための、いわばアイコンの役割を果たします。名前を変更して『 Stock1・Stock2 』としておきます。

312+.gif

実は、このアイコン用イメージコントロールに使用している画像は、先ほどのPlayerに使われている画像と全く同じものです。小さいアイコン用の画像を新たに用意したわけではありません。
なぜ同じ画像を使用しているのに、小さく縮小した画像が表示されているのでしょうか。
ここでポイントとなるのが、PictureSizeModeプロパティです。このプロパティを変更することで、全く同じ画像を使っていながら、縮小させて表示することが可能になるのです。

以下のように、このプロパティには3種類の設定を行うことができます。

PictureSizeModeプロパティ

画像の表示形式を管理するプロパティ

・fmPictureSizeModeClip(規定値)
    元々の画像の倍率で表示する

・fmPictureSizeModeStretch
    画像を引き伸ばす
    イメージコントロールの大きさに合わせて
    縦横の比率を伸縮させる

・fmPictureSizeModeZoom
    画像を引き伸ばす
    ただし、元画像の縦横の比率を保持する

規定値として設定されているのは、1番上のfmPictureSizeModeClipです。この設定になっているときは、イメージが伸縮したりせず、元々の画像の倍率で表示されます。
2番目と3番目は、いずれも画像を引き伸ばしたり縮小したりしますが、ちょっと動作が異なります。

fmPictureSizeModeStretchは、元々の画像が20×20ピクセルなどの正方形なら、イメージコントロールが正方形でなくても、表示されるイメージは正方形の形で表示されます。
これに対しfmPictureSizeModeZoomは、イメージコントロールの大きさに合わせて、縦横の比率も変化します。

313.gif

見比べてみるとよくわかるのではないでしょうか。
今回は、対象となる画像が正方形なので、厳密にはどちらを使用しても問題ありません。
ただ、汎用性を考えて、fmPictureSizeModeZoomを設定しておくことにしましょう。


プレイヤーキャラクター用のイメージと、アイコン用イメージの準備ができたら、とりあえずユーザーフォーム側の準備は完了です。
あとは、コードを修正すればいいわけですが、長くなったので次回に続きます。今回の講座で解説した考え方をしっかり理解しておきましょう。


■格言

条件分岐で演出を可能にする
PictureSizeModeプロパティを理解する


PictureSizeModeは何かと演出に使えるので、覚えておいて損はありません。






Chapter.55 [ シューティングゲーム15:残機数表示② ]

■つづきです

さて、今回は前回の続きです。
講座を通して作成しているシューティングゲームに、残機数を表示するべく準備をした前回。ここからは実際のコードを記述していきます。

まずは、コードをすんなり理解するためにも、前回の講座で載せた新しいプロシージャの構造をもう一度見てみましょう。

If 生きているか = 生きている Then
    If 演出中かどうか = 演出中 Then
        復活時の演出
    Else
        キー入力判定処理
        はみだし防止処理
        ショットを撃つかどうかの判断
    End If
Else
    死亡時の演出
End If
ユーザーフォーム上の処理

色の着いている部分を今回修正します。
重要なのは、キャラクターの生死の状態や、演出中かどうかによって、処理が分岐するという仕組みです。

上の構造図をよく見ながら、ゲーム中の実際の流れをイメージしてみてください。

プレイヤーキャラクターが、今現在、生きているのかいないのか、それによって赤い字で書かれたIf文の部分で処理が分かれます。
もし、プレイヤーキャラクターが生きているとすると、『復活時の演出』か『プレイヤーの処理』のいずれかが実行されます。逆に死んでいるとすると、『死亡時の演出』に処理が移りますね。

ただし、プレイヤーキャラクターが生きている場合には、さらにもう一段階の分岐があります。それが青い字If文で表されている部分にあたります。
ここでは、今現在、演出をしている最中かどうかによって処理が分岐します。もしも演出中であれば、それに応じた演出処理が行われますし、演出中でなければ、これは通常のプレイヤーキャラクターを操作できる状態ということになります。

ざっくりとした流れは、なんとなく理解できますか?
キャラクターが生きているのかどうか、そして、生きていたとしたら演出中なのかどうか。まずはそれを条件分岐によって判断します。カーソルキーでキャラクターを操作できるときは『生きていて演出していないとき』ということですね。この考え方をよく理解したうえで、実際のコードを見てみましょう。

Sub Player_Action()

    Dim S As Single
    Dim SS As Single
    
    Randomize

    
    With Player_Data
        If .Lif > 0 Then
            If .Par > 0 Then
                If .Y > 180 Then
                    .Y = .Y - 1
                Else
                    .Par = 0
                End If

            Else
                If GetAsyncKeyState(Left_Key) < 0 Then .X = .X - 3
                If GetAsyncKeyState(Up_Key) < 0 Then .Y = .Y - 3
                If GetAsyncKeyState(Right_Key) < 0 Then .X = .X + 3
                If GetAsyncKeyState(Down_Key) < 0 Then .Y = .Y + 3

                If .X - Player_Size < 0 Then .X = Player_Size
                If .X + Player_Size > 200 Then .X = 200 - Player_Size
                If .Y - Player_Size < 0 Then .Y = Player_Size
                If .Y + Player_Size > 200 Then .Y = 200 - Player_Size

                If Shot_Interval = 0 Then
                    If GetAsyncKeyState(Shot_Key) < 0 Then
                        Call P_Shot_Begin(.X, .Y, 1)
                        Shot_Interval = 1
                    End If
                Else
                    Shot_Interval = Shot_Interval + 1
                    If Shot_Interval > 7 Then Shot_Interval = 0
                End If
            End If
        Else
            .Par = .Par + 1
            If .Par > 50 Then
                .X = 100
                .Y = 210
                .Lif = 1
                .Par = 1
                Player_Stock = Player_Stock - 1
                Select Case Player_Stock
                    Case Is < 0
                        MsgBox "Game Over"
                    Case 0
                        UserForm1.Stock1.Visible = False
                    Case 1
                        UserForm1.Stock2.Visible = False
                End Select
            Else
                If .Par Mod 9 = 1 Then
                    S = Int(Rnd * 20) - 9
                    SS = Int(Rnd * 20) - 9
                    Call Burst_Begin(.X + S, .Y + SS)
                End If
            End If

        End If
        With UserForm1.Player
            .Left = Player_Data.X - Player_Size
            .Top = Player_Data.Y - Player_Size
        End With
    End With
    
End Sub

さて、なんだかカラフルなコードになっちゃいました。
色の着いている部分が今回変更になった部分です。先ほどの構造図と、同じ要領で色が着いていますので、よく見比べてみるとなんとなく構造がわかると思います。

まずは緑色の文字部分。ここは単純に追加された部分です。コードを追加するにあたり、新たに必要となる変数の宣言をしています。さらに、乱数を使用するのでRandomizeを使って、乱数ジェネレータを初期化しています。
Randomizeについては、以前の講座で詳しく解説していますので、わからない人はそちらも併せてどうぞ(Chapter12を参照)。

さて、その次は赤い字If文で、キャラクターが生きているか死んでいるかをチェックして処理を分岐します。
要素Lifがプレイヤーキャラクターの生存状態を表しており、この部分が0より大きければ、キャラクターは生きています。逆に0以下だった場合には、キャラクターは死んでいるということになりますね。

そして青い字で書かれているIf文では、演出を行っている最中かどうかを判定します。演出を行っているかどうかは、要素Parが管理します。この要素の値が0以外の時には、何かしらの演出を行っている最中として考えます。

上のほうで出てくる黄色い字の処理では、『復活時の演出』を行っています。ここでは、キャラクターが画面の下のほうから、ゆっくりと登場するように処理しています。
キャラクターの縦位置が、一定の座標に達するまで、すこしずつキャラクターを移動しているわけです。指定された座標まで到達したら、演出を終わらせるために要素Parに0を代入します。ここで0を代入したことによって、次回のループからはキャラクターを通常通りカーソルキーで操作できるようになります。

ここまでが、キャラクターが生きている場合の処理になります。
キャラクターが生きている場合には、復活時の演出か、あるいはカーソルキーで操作できる状態かのいずれかが実行されることになりますね。


■それじゃ死亡時の処理は?

さて次はキャラクターが死んでいる場合の処理です。
下のほうにある黄色い字の部分がこれにあたります。

ここは少し複雑なので、抜粋したコードを載せます。

.Par = .Par + 1 '①
If .Par > 50 Then '②
    .X = 100
    .Y = 210
    .Lif = 1
    .Par = 1
    Player_Stock = Player_Stock - 1
    Select Case Player_Stock
        Case Is < 0
            MsgBox "Game Over" '③
        Case 0
            UserForm1.Stock1.Visible = False '③
        Case 1
            UserForm1.Stock2.Visible = False '③
    End Select
Else
    If .Par Mod 9 = 1 Then '④
        S = Int(Rnd * 20) - 9
        SS = Int(Rnd * 20) - 9
        Call Burst_Begin(.X + S, .Y + SS)
    End If
End If

まず①の部分です。
ここでは要素Parの値をプラス1していますね。これは何のためかといいますと、要素Parをカウンタの役割として使うためです。
ループするたびにプラス1ずつ増やしているわけですから、演出が始まってから、どのくらいの時間が経っているのかは、要素Parの値を調べることでわかります。たくさんカウントされたあとであれば、大きな値が入っているはずですし、逆にまだあまり時間が経っていなければ、小さな値が入っているはずです。

それを踏まえて②の部分を見てください。
ここでは、要素Parが50よりも大きな値になっているかどうかチェックしていますね。もしも50よりも大きな数値になるまで時間が経っている場合には、ここで死亡時の演出を終わりにして、復活時の処理へ移行させます。
キャラクターの横位置や縦位置を設定して、さらに生きている状態に戻すため、要素Lifに1を代入しています。また、復活時の演出を行わせるために、要素Parにも1を代入しています。

そして、残機数を表す変数Player_Stockをひとつ減らします。これで残機数がひとつ少なくなるわけですね。

その後、変数Player_Stockの値に応じてSelect Case文で処理を分岐しています。これが③です。
変数Player_Stockの値は規定値で2が入っています。
変数Player_Stockの値が、1だったときは1回死んでしまったことになり、0だった場合には、すでに2回死んでいることになります。もしもここがマイナスだった場合には、3回死んだことになりますから、メッセージを表示して、ゲームオーバーの状態になったことを通知しています。

ゲームオーバーにならない場合には、残機数を表すイメージコントロール『 Stock1・Stock2 』を適宜非表示にしています。

続いて④の部分。多分コードを見ただけで、これが何をしているコードなのかわかる人は少ないと思います。実際にはこんなことしなくてもいいんですが、『演出』という意味では結構重要な部分だと個人的には思います。

実はこの④の部分は、被弾したプレイヤーキャラクターが爆発しているシーンを演出しています。
敵キャラクターならまだしも、プレイヤーキャラクターが被弾してしまったときに、単発の爆発で終わってしまうのはなんだか寂しい気がします。これはかなり個人的な意見ですが。

そこで、プレイヤーキャラクターの死亡時の演出として、復活するまでの間9ループごとに爆発のエフェクトを複数回表示するようにしています。Mod演算子を使って、要素Parの値が50よりも大きくなるまでの間、何度か爆発のエフェクトが発生するようになっています。
爆発エフェクトの発生する座標もランダムに決めているので、キャラクターが崩れ落ちるように何度も爆発しているような感じになっていると思います。
多分これは実際に動くのを見てもらったほうがわかりやすいのではないかな。言葉で説明するのが難しい……。講座の最後に、サンプルへのリンクがありますので、よかったらそちらからサンプルを落として、実際に見てみてください。そのほうがわかりやすいと思います。

とりあえず、ここまでの全コードを掲載しますので、やる気のある方は自力で修正してみるといいでしょう。

'標準モジュール先頭部分

'◆API 宣言

Declare Function GetTickCount Lib "kernel32" () As Long
Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMillsecounds As Long)

'■キャラクター 構造体宣言
Type Chara
    X As Single
    Y As Single
    W As Single
    H As Single
    Lif As Long
    Typ As Long
    Par As Long
End Type

'■ショット 構造体宣言
Type Shot
    X As Single
    Y As Single
    Vis As Boolean
    Typ As Long
End Type

'■汎用オブジェクト 構造体宣言
Type Obj
    X As Single
    Y As Single
    W As Single
    H As Single
    Par As Long
End Type

'●キャラクター 変数宣言
Public Player_Data As Chara
Public Enemy_Data(9) As Chara

'●ショット 変数宣言
Public P_Shot_Data(4) As Shot
Public E_Shot_Data(9) As Shot

'●汎用オブジェクト 変数宣言
Public Burst_Data(2) As Obj

'◎その他 変数宣言
Public Shot_Interval As Long
Public Total_Count As Long 'ゲーム中のカウンタ
Public Player_Stock As Long 'プレイヤーの残機数

'▲定数宣言
Public Const Left_Key As Long = 37 'カーソル左
Public Const Up_Key As Long = 38 'カーソル上
Public Const Right_Key As Long = 39 'カーソル右
Public Const Down_Key As Long = 40 'カーソル下
Public Const Esc_Key As Long = 27 'Escキー
Public Const Shot_Key As Long = 90 'Zキー
Public Const Player_Size As Single = 9
'プレイヤーキャラクターの実際の大きさ
Public Const Shot_Size As Single = 3
'弾各種の実際の大きさ
Public Const Enemy_Size As Single = 9
'敵キャラクターの実際の大きさ
Public Const Burst_Size As Single = 15
'爆発エフェクトの実際の大きさ


'初期化を担当するプロシージャ

Sub Init()
    With Player_Data
        .X = 100
        .Y = 210
        .W = 3
        .H = 3
        .Lif = 1
        .Typ = 0
        .Par = 1
    End With
    Erase P_Shot_Data
    Erase E_Shot_Data
    Erase Enemy_Data
    Erase Burst_Data
    Shot_Interval = 0
    Total_Count = 0
    Player_Stock = 2
    With UserForm1.Player
        .Visible = True
        .Top = 200
    End With

End Sub

'メインプロセスを担当するプロシージャ

Sub Main()

    Dim Flg As Boolean
    Dim Stm As Long
    
    Call Init
    
    Flg = False
    
    Do Until Flg
        Stm = GetTickCount
        Total_Count = Total_Count + 1
        Call Stage_Count
        Call Player_Action
        Call P_Shot_Action
        Call Enemy_Action
        Call E_Shot_Action
        Call Burst_Action
        DoEvents
        Do
            Call Sleep(1)
        Loop Until GetTickCount - Stm > 30
        If GetAsyncKeyState(Esc_Key) < 0 Then Flg = True
    Loop
    
End Sub


'敵キャラクターの出現管理プロシージャ

Sub Stage_Count()
    Randomize
    Select Case Total_Count Mod 200
        Case 0
            Call Enemy_Begin(Int(Rnd * 120) + 40, -10, 1)
        Case 50
            Call Enemy_Begin(Int(Rnd * 120) + 40, -10, 2)
        Case 100
            Call Enemy_Begin(Int(Rnd * 120) + 40, -10, 3)
    End Select
End Sub


'プレイヤーキャラクターを担当するプロシージャ

Sub Player_Action()

    Dim S As Single
    Dim SS As Single
    
    Randomize
    
    With Player_Data
        If .Lif > 0 Then
            If .Par > 0 Then
                If .Y > 180 Then
                    .Y = .Y - 1
                Else
                    .Par = 0
                End If
            Else
                If GetAsyncKeyState(Left_Key) < 0 Then .X = .X - 3
                If GetAsyncKeyState(Up_Key) < 0 Then .Y = .Y - 3
                If GetAsyncKeyState(Right_Key) < 0 Then .X = .X + 3
                If GetAsyncKeyState(Down_Key) < 0 Then .Y = .Y + 3

                If .X - Player_Size < 0 Then .X = Player_Size
                If .X + Player_Size > 200 Then .X = 200 - Player_Size
                If .Y - Player_Size < 0 Then .Y = Player_Size
                If .Y + Player_Size > 200 Then .Y = 200 - Player_Size

                If Shot_Interval = 0 Then
                    If GetAsyncKeyState(Shot_Key) < 0 Then
                        Call P_Shot_Begin(.X, .Y, 1)
                        Shot_Interval = 1
                    End If
                Else
                    Shot_Interval = Shot_Interval + 1
                    If Shot_Interval > 7 Then Shot_Interval = 0
                End If
            End If
        Else
            .Par = .Par + 1
            If .Par > 50 Then
                .X = 100
                .Y = 210
                .Lif = 1
                .Par = 1
                Player_Stock = Player_Stock - 1
                Select Case Player_Stock
                    Case Is < 0
                        MsgBox "Game Over"
                    Case 0
                        UserForm1.Stock1.Visible = False
                    Case 1
                        UserForm1.Stock2.Visible = False
                End Select
            Else
                If .Par Mod 9 = 1 Then
                    S = Int(Rnd * 20) - 9
                    SS = Int(Rnd * 20) - 9
                    Call Burst_Begin(.X + S, .Y + SS)
                End If
            End If
        End If
        With UserForm1.Player
            .Left = Player_Data.X - Player_Size
            .Top = Player_Data.Y - Player_Size
        End With
    End With
    
End Sub



'ショット生成プロシージャ

Sub P_Shot_Begin(X As Single, Y As Single, Typ As Long)

    Dim L As Long

    For L = 0 To 4
        With P_Shot_Data(L)
            If Not .Vis Then
                .X = X
                .Y = Y
                .Typ = Typ
                .Vis = True
                UserForm1.Controls("P_Shot" & L + 1).Visible = True
                Exit For
            End If
        End With
    Next

End Sub


'ショット移動管理プロシージャ

Sub P_Shot_Action()

    Dim L As Long
    Dim LL As Long

    For L = 0 To 4
        With P_Shot_Data(L)
            If .Vis Then
                .Y = .Y - 6
                If .Y < -5 Then .Vis = False
                For LL = 0 To 9
                    If Enemy_Data(LL).Lif > 0 Then
                        If .X - 2 < Enemy_Data(LL).X + Enemy_Data(LL).W Then
                            If .X + 2 > Enemy_Data(LL).X - Enemy_Data(LL).W Then
                                If .Y - 2 < Enemy_Data(LL).Y + Enemy_Data(LL).H Then
                                    If .Y + 2 > Enemy_Data(LL).Y - Enemy_Data(LL).H Then
                                        .Vis = False
                                        With Enemy_Data(LL)
                                            Call Burst_Begin(.X, .Y)
                                            .Lif = 0
                                        End With
                                        Exit For
                                    End If
                                End If
                            End If
                        End If
                    End If
                Next
                With UserForm1.Controls("P_Shot" & L + 1)
                    If P_Shot_Data(L).Vis Then
                        .Left = P_Shot_Data(L).X - Shot_Size
                        .Top = P_Shot_Data(L).Y - Shot_Size
                    Else
                        .Visible = False
                    End If
                End With
            End If
        End With
    Next

End Sub


'敵キャラクター生成プロシージャ

Sub Enemy_Begin(X As Single, Y As Single, Typ As Long)

    Dim L As Long
    
    For L = 0 To 9
        With Enemy_Data(L)
            If .Lif = 0 Then
                .X = X
                .Y = Y
                .W = 5
                .H = 5
                .Typ = Typ
                .Lif = 1
                .Par = 0
                With UserForm1.Controls("Enemy" & L + 1)
                    .Visible = True
                    Select Case Typ
                        Case 1
                            .Picture = UserForm1.Enemy_s1.Picture
                        Case 2, 3
                            .Picture = UserForm1.Enemy_s2.Picture
                    End Select
                End With
                Exit For
            End If
        End With
    Next
    
End Sub


'敵キャラクターを担当するプロシージャ

Sub Enemy_Action()

    Dim L As Long
    Dim TCMOD
    
    TCMOD = Total_Count Mod 30
    
    For L = 0 To 9
        With Enemy_Data(L)
            If .Lif > 0 Then
                Select Case .Typ
                    Case 1
                        .Y = .Y + 1.5
                    Case 2
                        .X = .X + 0.5
                        .Y = .Y + 1
                    Case 3
                        .X = .X - 0.5
                        .Y = .Y + 1
                End Select
                If .X > 220 Then .Lif = 0
                If .Y > 220 Then .Lif = 0
                If .X < -20 Then .Lif = 0
                If .Y < -20 Then .Lif = 0
                If TCMOD = 0 Then
                    Select Case .Typ
                        Case 2
                            .Typ = 3
                        Case 3
                            .Typ = 2
                    End Select
                    Call E_Shot_Begin(.X, .Y + 2, .Typ)
                End If
            End If
        End With
        With UserForm1.Controls("Enemy" & L + 1)
            If Enemy_Data(L).Lif > 0 Then
                .Left = Enemy_Data(L).X - Enemy_Size
                .Top = Enemy_Data(L).Y - Enemy_Size
            Else
                .Visible = False
            End If
        End With
    Next
    
End Sub


'敵キャラクターショット生成プロシージャ

Sub E_Shot_Begin(X As Single, Y As Single, Typ As Long)
    
    Dim L As Long
    
    For L = 0 To 9
        With E_Shot_Data(L)
            If Not .Vis Then
                .X = X
                .Y = Y
                .Typ = Typ
                .Vis = True
                With UserForm1.Controls("E_Shot" & L + 1)
                    .Visible = True
                End With
                Exit For
            End If
        End With
    Next
    
End Sub


'敵キャラクターショット移動管理プロシージャ

Sub E_Shot_Action()

    Dim L As Long
    Dim LL As Long
    
    For L = 0 To 9
        With E_Shot_Data(L)
            If .Vis Then
                Select Case .Typ
                    Case 1
                        .Y = .Y + 4.5
                    Case 2
                        .X = .X + 0.5
                        .Y = .Y + 3.5
                    Case 3
                        .X = .X - 0.5
                        .Y = .Y + 3.5
                End Select
                If .X > 205 Then .Vis = 0
                If .Y > 205 Then .Vis = 0
                If .X < -5 Then .Vis = 0
                If .Y < -5 Then .Vis = 0
                If .X - 2 < Player_Data.X + Player_Data.W Then
                    If .X + 2 > Player_Data.X - Player_Data.W Then
                        If .Y - 2 < Player_Data.Y + Player_Data.H Then
                            If .Y + 2 > Player_Data.Y - Player_Data.H Then
                                .Vis = False
                                With Player_Data
                                    Call Burst_Begin(.X, .Y)
                                    .Lif = 0
                                End With
                            End If
                        End If
                    End If
                End If
                With UserForm1.Controls("E_Shot" & L + 1)
                    If E_Shot_Data(L).Vis Then
                        .Left = E_Shot_Data(L).X - Shot_Size
                        .Top = E_Shot_Data(L).Y - Shot_Size
                    Else
                        .Visible = False
                    End If
                End With
            End If
        End With
    Next
                
End Sub


'爆発エフェクト生成プロシージャ

Sub Burst_Begin(X As Single, Y As Single)

    Dim L As Long
    
    For L = 0 To 2
        With Burst_Data(L)
            If .Par = 0 Then
                .X = X
                .Y = Y
                .W = Burst_Size
                .H = Burst_Size
                .Par = 1
                UserForm1.Controls("Burst" & L + 1).Visible = True
                Exit For
            End If
        End With
    Next
    
End Sub


'爆発エフェクト管理プロシージャ

Sub Burst_Action()

    Dim L As Long
    Dim Pict As Long
    
    For L = 0 To 2
        With Burst_Data(L)
            If .Par > 0 Then
                Select Case .Par
                    Case 1
                        Pict = 1
                    Case 2, 3
                        Pict = 2
                    Case 4, 5
                        Pict = 3
                    Case 6, 7
                        Pict = 4
                    Case 8
                        Pict = 5
                    Case 9
                        .Par = -1
                End Select
                .Par = .Par + 1
                With UserForm1.Controls("Burst" & L + 1)
                    If Burst_Data(L).Par > 0 Then
                        .Left = Burst_Data(L).X - Burst_Size
                        .Top = Burst_Data(L).Y - Burst_Size
                        .Picture = UserForm1.Controls("Burst_s" & Pict).Picture
                    Else
                        .Visible = False
                    End If
                End With
            End If
        End With
    Next
                
End Sub


'標準モジュールはここまで
'ここから下はフォームモジュールに記述


'コマンドボタンのクリックイベント

Private Sub Com_Start_Click()
    Com_Start.Enabled = False
    Call Main
    Unload UserForm1
End Sub


'ユーザーフォームのクエリクロースイベント

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    End
End Sub


'ユーザーフォームのイニシャライズイベント
Private Sub UserForm_Initialize()
    UserForm1.Width = 220
End Sub



ちなみに、ここまでの状態だと、一度ゲームオーバーになってしまっても、おかまいなしでキャラクターが登場し続けます。つまり、まだ未完成ということです。
将来的には、ゲームオーバーになったらゲームが終わってしまうようにしなければなりませんが、それはまた次回以降、がんばってみることにしましょう。

まずは、サンプルをご覧頂くなり、コードを自力で調整するなりしていただいて、今回までの内容を実装させてみてください。



サンプルダウンロード ⇒ コチラよりダウンロードできます。(別館)


■格言

順番に条件に沿って考える
全体の構図をよく理解する


やや複雑ですが、ひとつひとつ順番に理解することが大切です。






Chapter.56 [ シューティングゲーム16:スコアの表示 ]

■いまさら基本的な部分

シューティングゲームの講座も、そろそろ終わりが見えてきました。

基本的な部分がだいぶ完成して、あとはスコアとタイトルの実装、そして、ボスの登場にてフィニッシュです。あと少し、根気を出してがんばってほしいと思います。

さて、今回のテーマは『スコア』です。
『スコア』の実装は非常に簡単です。さっくり実装させてしまいましょう。
スコアの表示には、変数をひとつ加える必要があります。この変数にスコアの状態を保持しておき、ループするごとに画面を更新すればいいのです。

Public Score As Long 'ゲームのスコア

この変数Scoreに、現在のスコアの状態を保持します。そして、当然のことながら敵キャラクターを破壊したときに、この変数に得点をプラスしていきます。ということは、どのプロシージャを修正すればいいのかわかりますね?

敵キャラクターが破壊されるかどうか、それを判定しているのは『プレイヤーのショット移動管理プロシージャ』です。プレイヤーキャラクターが放ったショットが、敵キャラクターにヒットしていた場合には、その時点で敵キャラクターが破壊されます。このとき、同時にスコアをプラスすれば、それだけでスコアの実装はほとんど完成したといってもいいでしょう。

'ショット移動管理プロシージャ

Sub P_Shot_Action()

    Dim L As Long
    Dim LL As Long

    For L = 0 To 4
        With P_Shot_Data(L)
            If .Vis Then
                .Y = .Y - 6
                If .Y < -5 Then .Vis = False
                For LL = 0 To 9
                    If Enemy_Data(LL).Lif > 0 Then
                        If .X - 2 < Enemy_Data(LL).X + Enemy_Data(LL).W Then
                            If .X + 2 > Enemy_Data(LL).X - Enemy_Data(LL).W Then
                                If .Y - 2 < Enemy_Data(LL).Y + Enemy_Data(LL).H Then
                                    If .Y + 2 > Enemy_Data(LL).Y - Enemy_Data(LL).H Then
                                        .Vis = False
                                        With Enemy_Data(LL)
                                            Call Burst_Begin(.X, .Y)
                                            .Lif = 0
                                        End With
                                        Score = Score + 100
                                        Exit For
                                    End If
                                End If
                            End If
                        End If
                    End If
                Next
                With UserForm1.Controls("P_Shot" & L + 1)
                    If P_Shot_Data(L).Vis Then
                        .Left = P_Shot_Data(L).X - Shot_Size
                        .Top = P_Shot_Data(L).Y - Shot_Size
                    Else
                        .Visible = False
                    End If
                End With
            End If
        End With
    Next

End Sub

はい、こんだけ。簡単ですね。
今回は敵キャラクターを1体破壊するごとに、100点入るようにしました。これは自由に変更すればいいでしょう。

ショット移動管理プロシージャに関しては、本当にこれだけです。敵キャラクターとの衝突判定を行い、衝突していた場合には敵キャラクターを破壊しますが、それと同時に変数Scoreを加算しているだけです。

さて、まさかこれで完了ではありません。
油断せずに次に行きましょう。


■初期化処理を修正

スコアは、ゲームが開始されるときにはリセットされていないとおかしいですね。前回のプレイ時に稼いだスコアが、そのまま次に残ってしまっていては変です。
そこで、初期化処理はしっかりやっておきましょう。

初期化処理を担当するプロシージャ、『Init』を変更します。

'初期化を担当するプロシージャ

Sub Init()
    With Player_Data
        .X = 100
        .Y = 210
        .W = 3
        .H = 3
        .Lif = 1
        .Typ = 0
        .Par = 1
    End With
    Erase P_Shot_Data
    Erase E_Shot_Data
    Erase Enemy_Data
    Erase Burst_Data
    Shot_Interval = 0
    Total_Count = 0
    Player_Stock = 2
    Score = 0
    With UserForm1.Player
        .Visible = True
        .Top = 200
    End With
    UserForm1.Lab_Score.Caption = "00000000"
End Sub

変数Scoreには0を設定しておきます。これは説明不要ですね。

そして、注目すべきはその下です。
ユーザーフォームの上段に配置している、スコア表示のためのラベルが『Lab_Score』です。一番最初の頃に配置したものなので、忘れてしまった方もいるかもしれませんね。

このラベルは、ゲーム中にスコアを表示するために使います。そして、ラベルに表示される文字列を管理するプロパティが『Captionプロパティ』です。このプロパティは既に何度か出てきていますね。ここにも初期値となる値を設定しておきます。
ポイントは、Captionプロパティには文字列を設定する、ということです。
上のコードを見てもらえればわかると思いますが、ダブルクォーテーション( コレ ⇒ " )で囲んでいますよね。つまり、数値のデータとしての0ではなく、文字列の『 00000000 』を設定しているわけですね。

補足コラム:Captionプロパティ

Captionプロパティは、ラベルなどのコントロールに文字列を表示するプロパティです。このプロパティに何かしらの文字列を設定していると、その内容がコントロール上に文字として表示されます。
ちなみに、ラベル、及びコマンドボタンなどでは、Captionプロパティに設定された文字列がコントロール上にそのまま表示されますが、コントロールの種類によっては、文字列が表示される位置がそれぞれに異なります。

ラベルなど
フレーム
マルチページなど
320.gif
321.gif
322.gif

これらの違いをよく理解しておきましょう。


■ゲーム中の更新処理

ゲームが実際に始まったら、スコアはリアルタイムに更新する必要があります。そこで、ここではメインプロセスを管理しているプロシージャを修正することになります。

ここも、実際のコードを載せますのでまずは見てみてください。

'メインプロセスを担当するプロシージャ

Sub Main()

    Dim Flg As Boolean
    Dim Stm As Long
    
    Call Init
    
    Flg = False
    
    Do Until Flg
        Stm = GetTickCount
        Total_Count = Total_Count + 1
        Call Stage_Count
        Call Player_Action
        Call P_Shot_Action
        Call Enemy_Action
        Call E_Shot_Action
        Call Burst_Action
        UserForm1.Lab_Score.Caption = Format(Score, "00000000")
        DoEvents
        Do
            Call Sleep(1)
        Loop Until GetTickCount - Stm > 30
        If GetAsyncKeyState(Esc_Key) < 0 Then Flg = True
    Loop
    
End Sub

さて、ここでポイントとなるのがFormat関数です。

この関数は、文字列を整形してくれる関数で、主に日付や時刻の表示に関する機能が充実しています。同じ日付でも『2008/03/16』と表記したい場合と『Mar 16 2008』と表記したい場合などがあると思います。このような表示形式の違いなどを自由に整形することができるわけです。
さらに、この関数では日付や時刻以外にも、文字列を様々な形式に整形することができます。
細かい使い方を全部説明してしまうと、それだけでひとつの講座ができてしまうぐらいのボリュームになってしまうので、興味のある方は自力でヘルプを見てみるなり、ネットで検索するなりして調べてください。

今回は、スコアの数値を整形するためにFormat関数を利用しています。ここを少し詳しく解説しましょう。

まず、先ほどのコードから、Format関数を使っている部分を抜き出してもう一度見てみましょう。

UserForm1.Lab_Score.Caption = Format(Score, "00000000")

スコア表示用のラベルに、整形した文字列を設定している部分ですね。
Format関数は、第一引数として与えられたデータを、第二引数の書式で整形して、結果を返してくれます。

今回は、第一引数に、スコアの値を保持している変数Scoreを指定しています。そして、第二引数には、『 "00000000" 』と指定されています。
仮に、変数Scoreの値が『 1000 』だった場合には、Format関数は『 00001000 』という文字列を返してきます。変数Scoreが『 543210 』だったら『 00543210 』を返してきます。

もうおわかりですね?
Format関数の第二引数で8桁の数値という書式を指定しているので、変数Scoreの値が何桁の数値であっても、必ず8桁の文字列に整形されているわけです。

シューティングゲームなどのスコア表示では、どんなにスコアが少ないときでも、足りない桁数の部分は0で埋まっていることが多いですよね。
Format関数をうまく使うことによって、簡単にこれが実現できるというわけです。この関数は、戻り値として内部的な文字列型を返してくるので、Captionプロパティとの相性もいいのです。


先ほども書きましたが、Format関数は実に奥が深くて、そんな使い方があるのか! とつい言いたくなるようなことができたりします。一度調べてみることをオススメします。わからないことはとりあえず調べてみる、これはプログラマーには欠かせない習性のひとつと言っていいでしょう。
この講座では解説しませんが、調べるといろんなことがわかると思いますので、興味のあるかたはがんばって調べてみましょうね。

スコアについては簡単ですがこれで完了です。全体のコードは載せませんが、色つきの部分を追加してやれば、それだけでうまくいくようになっているはずです。
次回はタイトル画面を作ります。お楽しみに。


■格言

Captionには文字列を指定する
Format関数を有効に利用


スコアだけでなく、タイムを表示したい場合などにも応用できます。






Chapter.57 [ シューティングゲーム17:タイトル画面 ]

■タイトル

ゲームを開始してから、ユーザーが最初に目にするのはタイトル画面です。
今回は、このタイトル画面について考えてみましょう。

タイトル画面は、用意していなくても、それはそれでどうにかなってしまいます。ですが、カッコいいタイトル画面がどーんと表示されると、ゲームの第一印象がぐっとよくなります。
タイトル画面にちからを入れすぎて、肝心の中身が無いというのも考え物ですが、できる限りカッコいい演出をしておいて損は無いでしょう。今回は、ユーザーフォームのラベルコントロールを使って、ちょっとしたタイトル画面の演出を行ってみたいと思います。


■ユーザーフォームの準備

まずは、ユーザーフォーム上に、タイトルロゴの役割を果たすラベルを配置します。

330.gif

このラベルは、次のような感じでプロパティを変更しています。

BackStyleプロパティ ⇒ fmBackStyleTransparent(透明にする)
TextAlign ⇒ fmTextAlignCenter(中央揃え)
Font ⇒ MS UI Gothic 20ポイント 太字斜体
331+.gif

ForeColor ⇒ パレットタブより赤を選択
332.gif

字が大きくなっており、文字色が赤になっています。そして、背景を透過するようにして、文字を中央揃えにしているのです。ひとつひとつ焦らず設定しましょう。

設定がうまくいったら、このラベルの名前を『 Lab_Cap1 』に変更します。

そして、次はこのラベルの上で右クリックして、コピーを選択してください。

333.gif

次に、フレームの中で右クリックして、貼り付けを選択します。すると、先ほどのラベルがフレームの中に貼り付けされます。全く同じ場所に貼り付けされるので、一見すると何も変わっていないように見えますが、ラベルを動かしてみると、貼り付けされているのがわかるはずです。

334.gif

    ▼

335.gif

この、新しく貼り付けられたラベルの、文字色を変えて、さらにTopプロパティとLeftプロパティをうまく調整すると、次のような感じになります。

336.gif

どうですか? 影つきの文字みたくなりましたね。ちょっとロゴっぽくなりました。
これは、ラベルをふたつ重ねて表示することで、あたかも影がついているかのように見せているという、演出のしかたの一例です。
あとから貼り付けたほうのラベルも、名前を変更して『 Lab_Cap2 』としておきます。

ここまでできたら、ユーザーフォームの準備は完了です。
あとは、これをゲーム中にどのように処理するか、それを見ていきましょう。


■タイトル画面の実装

さて、タイトル画面を考えるときには、今まで書いてきたコードを、大幅に修正する必要があります。それはなぜでしょうか。

今までは、ゲームの開始と同時にプレイヤーキャラクターが登場し、あとは勝手に敵キャラクターがエンドレスで登場するようになっていました。
しかし、今回タイトル画面を実装するとなると、まずはゲームの開始時には、タイトル画面を表示しておくようにしなくてはいけませんよね。

それに、ゲームをクリアしたときや、ゲームオーバーになってしまったときのことも、最終的には考えていかなくてはいけません。

そこで、ゲームが今現在どのような進行状況なのか、それを管理できるようにしなくてはいけません。ゲームの開始前なのか、それともゲームをプレイしている最中なのか、はたまたゲームオーバーとなり、進行が止まってしまった状態なのか……。これはちょっとめんどくさそうですね。

これを簡単に実装させるには、やはり変数をうまく使う必要があります。今現在、ゲームがどのような進行状況なのかを、ある変数に確保しておき、その内容に応じて処理を分岐させるようにします。

ゲームの進行状況

ゲーム開始前なら ・・・ タイトル画面を表示

ゲームを開始したなら ・・・ タイトルを終了しキャラクターを登場させる

ゲームプレイ中なら ・・・ ゲーム中の処理

ゲームクリアしたら ・・・ クリア時の演出を行う

ゲームオーバーになったら ・・・ ゲームオーバーの演出を行う

このように、現在のシーンに応じて、処理が分岐するようにしておけば、どのような状態であっても、適切に処理を行うことができるようになります。

そのために、まずは変数をひとつ追加しましょう。

Public Game_Mode As Long '現在のゲームモード
'0=タイトル
'1=ゲーム中
'2=ゲームオーバー
'3=ゲームクリア

この変数Game_Modeに入っている値に応じて、処理を分岐させるようにすればいいわけです。

あとは、この変数Game_Modeの値をチェックしながら処理を分岐するように、メインプロセスを変更してやればいいのです。

Sub Main() 'メインループとなるプロシージャ

    Dim Flg As Boolean
    Dim Stm As Long
    
    Init
    
    Flg = False
    
    Do Until Flg
        Stm = GetTickCount
        Total_Count = Total_Count + 1
        Select Case Game_Mode
            Case 0
                Call
Tytle_Action
            Case 1
                Call Stage_Count
                Call Player_Action
                Call Enemy_Action
                Call P_Shot_Action
                Call E_Shot_Action
                Call Burst_Action
                UserForm1.Lab_Score.Caption = Format(Score, "00000000")
            Case 2
                MsgBox "Game Over!!"
                Flg = True
            Case 3
                MsgBox "Clear!!"
                Flg = True
        End Select

        DoEvents
        Do
            Call Sleep(1)
        Loop Until GetTickCount - Stm > 30
        If GetAsyncKeyState(27) < 0 Then Flg = True
    Loop

End Sub

色つきになっている部分が、追加された部分です。
今までは、直接ゲームプレイ中の処理を呼び出しているだけでした。しかし、今回の変更に伴って、ゲームの各進行状態に応じた処理が呼び出されるようになりました。

黄色い字で表示されている、Tytle_Actionというプロシージャは、まだ作成していないプロシージャです。このプロシージャは、タイトル画面から通常のゲームプレイ中の処理に移行する際に、タイトルロゴを消す役割を果たすプロシージャです。
このプロシージャを作成すれば、タイトル画面の実装は完了です。


■タイトルロゴの処理

それでは、タイトルのアクションを行うプロシージャ『Tytle_Action』を作成しましょう。

このプロシージャは、ゲームの開始と同時に画面上に表示されているタイトルロゴを、画面上から消すときのアクションを行うプロシージャです。
ただパッと消えてしまうのではつまらないので、タイトルロゴが横にスライドして画面外へと消えていくようにしてみます。

Sub Tytle_Action()

    With UserForm1
        .Lab_Cap1.Left = .Lab_Cap1.Left + 3 '①
        .Lab_Cap2.Left = .Lab_Cap2.Left - 3 '①
        If .Lab_Cap1.Left > 200 Then
            .Lab_Cap1.Visible = False
            .Lab_Cap2.Visible = False
            Game_Mode = 1 '②
        End If
    End With
    
End Sub

このプロシージャは、呼び出されるたびにラベルを移動するだけのプロシージャです。
①の部分で示すように、毎回ラベルの位置を3ポイントずつ移動しているだけです。ただし、Lab_Cap1が画面の外に出た段階で、ゲームの進行状態を切り替えています。それが②の部分。ゲームのモードを、タイトル画面のアクションから、ゲームのプレイ中に変更しているわけですね。


■まとめ

今回は、メインプロセスの部分を変更することになるので、ちょっと複雑に感じるかもしれません。ですが、順を追ってひとつひとつこなしていけば、きっと大丈夫なはずです。

ポイントとなるのは、ゲームの進行状況によって、処理を分岐するという仕組みです。
全てのコードを掲載しますので、色が付いている部分を中心に、全体像をうまく把握してください。


'標準モジュール先頭部分

'◆API 宣言

Declare Function GetTickCount Lib "kernel32" () As Long
Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMillsecounds As Long)

'■キャラクター 構造体宣言
Type Chara
    X As Single
    Y As Single
    W As Single
    H As Single
    Lif As Long
    Typ As Long
    Par As Long
End Type

'■ショット 構造体宣言
Type Shot
    X As Single
    Y As Single
    Vis As Boolean
    Typ As Long
End Type

'■汎用オブジェクト 構造体宣言
Type Obj
    X As Single
    Y As Single
    W As Single
    H As Single
    Par As Long
End Type

'●キャラクター 変数宣言
Public Player_Data As Chara
Public Enemy_Data(9) As Chara

'●ショット 変数宣言
Public P_Shot_Data(4) As Shot
Public E_Shot_Data(9) As Shot

'●汎用オブジェクト 変数宣言
Public Burst_Data(2) As Obj

'◎その他 変数宣言
Public Shot_Interval As Long
Public Total_Count As Long 'ゲーム中のカウンタ
Public Player_Stock As Long 'プレイヤーの残機数
Public Score As Long 'ゲームのスコア
Public Game_Mode As Long '現在のゲームモード
'0=タイトル
'1=ゲーム中
'2=ゲームオーバー
'3=ゲームクリア

'▲定数宣言
Public Const Left_Key As Long = 37 'カーソル左
Public Const Up_Key As Long = 38 'カーソル上
Public Const Right_Key As Long = 39 'カーソル右
Public Const Down_Key As Long = 40 'カーソル下
Public Const Esc_Key As Long = 27 'Escキー
Public Const Shot_Key As Long = 90 'Zキー
Public Const Player_Size As Single = 9
'プレイヤーキャラクターの実際の大きさ
Public Const Shot_Size As Single = 3
'弾各種の実際の大きさ
Public Const Enemy_Size As Single = 9
'敵キャラクターの実際の大きさ
Public Const Burst_Size As Single = 15
'爆発エフェクトの実際の大きさ


'初期化を担当するプロシージャ

Sub Init()
    With Player_Data
        .X = 100
        .Y = 210
        .W = 3
        .H = 3
        .Lif = 1
        .Typ = 0
        .Par = 1
    End With
    Erase P_Shot_Data
    Erase E_Shot_Data
    Erase Enemy_Data
    Erase Burst_Data
    Shot_Interval = 0
    Total_Count = 0
    Player_Stock = 2
    Score = 0
    Game_Mode = 0
    With UserForm1.Player
        .Visible = True
        .Top = 200
    End With
    UserForm1.Lab_Score.Caption = "00000000"
End Sub

'メインプロセスを担当するプロシージャ

Sub Main()

    Dim Flg As Boolean
    Dim Stm As Long
    
    Init
    
    Flg = False
    
    Do Until Flg
        Stm = GetTickCount
        Total_Count = Total_Count + 1
        Select Case Game_Mode
            Case 0
                Call
Tytle_Action
            Case 1
                Call Stage_Count
                Call Player_Action
                Call Enemy_Action
                Call P_Shot_Action
                Call E_Shot_Action
                Call Burst_Action
                UserForm1.Lab_Score.Caption = Format(Score, "00000000")
            Case 2
                MsgBox "Game Over!!"
                Flg = True
            Case 3
                MsgBox "Clear!!"
                Flg = True
        End Select

        DoEvents
        Do
            Call Sleep(1)
        Loop Until GetTickCount - Stm > 30
        If GetAsyncKeyState(27) < 0 Then Flg = True
    Loop

End Sub


'タイトルのアクションを管理するプロシージャ

Sub Tytle_Action()

    With UserForm1
        .Lab_Cap1.Left = .Lab_Cap1.Left + 3
        .Lab_Cap2.Left = .Lab_Cap2.Left - 3
        If .Lab_Cap1.Left > 200 Then
            .Lab_Cap1.Visible = False
            .Lab_Cap2.Visible = False
            Game_Mode = 1
        End If
    End With
    
End Sub



'敵キャラクターの出現管理プロシージャ

Sub Stage_Count()
    Randomize
    Select Case Total_Count Mod 200
        Case 0
            Call Enemy_Begin(Int(Rnd * 120) + 40, -10, 1)
        Case 50
            Call Enemy_Begin(Int(Rnd * 120) + 40, -10, 2)
        Case 100
            Call Enemy_Begin(Int(Rnd * 120) + 40, -10, 3)
    End Select
End Sub


'プレイヤーキャラクターを担当するプロシージャ

Sub Player_Action()

    Dim S As Single
    Dim SS As Single
    
    Randomize
    
    With Player_Data
        If .Lif > 0 Then
            If .Par > 0 Then
                If .Y > 180 Then
                    .Y = .Y - 1
                Else
                    .Par = 0
                End If
            Else
                If GetAsyncKeyState(Left_Key) < 0 Then .X = .X - 3
                If GetAsyncKeyState(Up_Key) < 0 Then .Y = .Y - 3
                If GetAsyncKeyState(Right_Key) < 0 Then .X = .X + 3
                If GetAsyncKeyState(Down_Key) < 0 Then .Y = .Y + 3

                If .X - Player_Size < 0 Then .X = Player_Size
                If .X + Player_Size > 200 Then .X = 200 - Player_Size
                If .Y - Player_Size < 0 Then .Y = Player_Size
                If .Y + Player_Size > 200 Then .Y = 200 - Player_Size

                If Shot_Interval = 0 Then
                    If GetAsyncKeyState(Shot_Key) < 0 Then
                        Call P_Shot_Begin(.X, .Y, 1)
                        Shot_Interval = 1
                    End If
                Else
                    Shot_Interval = Shot_Interval + 1
                    If Shot_Interval > 7 Then Shot_Interval = 0
                End If
            End If
        Else
            .Par = .Par + 1
            If .Par > 50 Then
                .X = 100
                .Y = 210
                .Lif = 1
                .Par = 1
                Player_Stock = Player_Stock - 1
                Select Case Player_Stock
                    Case Is < 0
                        Game_Mode = 2
                    Case 0
                        UserForm1.Stock1.Visible = False
                    Case 1
                        UserForm1.Stock2.Visible = False
                End Select
            Else
                If .Par Mod 9 = 1 Then
                    S = Int(Rnd * 20) - 9
                    SS = Int(Rnd * 20) - 9
                    Call Burst_Begin(.X + S, .Y + SS)
                End If
            End If
        End If
        With UserForm1.Player
            .Left = Player_Data.X - Player_Size
            .Top = Player_Data.Y - Player_Size
        End With
    End With
    
End Sub


'ショット生成プロシージャ

Sub P_Shot_Begin(X As Single, Y As Single, Typ As Long)

    Dim L As Long

    For L = 0 To 4
        With P_Shot_Data(L)
            If Not .Vis Then
                .X = X
                .Y = Y
                .Typ = Typ
                .Vis = True
                UserForm1.Controls("P_Shot" & L + 1).Visible = True
                Exit For
            End If
        End With
    Next

End Sub


'ショット移動管理プロシージャ

Sub P_Shot_Action()

    Dim L As Long
    Dim LL As Long

    For L = 0 To 4
        With P_Shot_Data(L)
            If .Vis Then
                .Y = .Y - 6
                If .Y < -5 Then .Vis = False
                For LL = 0 To 9
                    If Enemy_Data(LL).Lif > 0 Then
                        If .X - 2 < Enemy_Data(LL).X + Enemy_Data(LL).W Then
                            If .X + 2 > Enemy_Data(LL).X - Enemy_Data(LL).W Then
                                If .Y - 2 < Enemy_Data(LL).Y + Enemy_Data(LL).H Then
                                    If .Y + 2 > Enemy_Data(LL).Y - Enemy_Data(LL).H Then
                                        .Vis = False
                                        With Enemy_Data(LL)
                                            Call Burst_Begin(.X, .Y)
                                            .Lif = 0
                                        End With
                                        Score = Score + 100
                                        Exit For
                                    End If
                                End If
                            End If
                        End If
                    End If
                Next
                With UserForm1.Controls("P_Shot" & L + 1)
                    If P_Shot_Data(L).Vis Then
                        .Left = P_Shot_Data(L).X - Shot_Size
                        .Top = P_Shot_Data(L).Y - Shot_Size
                    Else
                        .Visible = False
                    End If
                End With
            End If
        End With
    Next

End Sub


'敵キャラクター生成プロシージャ

Sub Enemy_Begin(X As Single, Y As Single, Typ As Long)

    Dim L As Long
    
    For L = 0 To 9
        With Enemy_Data(L)
            If .Lif = 0 Then
                .X = X
                .Y = Y
                .W = 5
                .H = 5
                .Typ = Typ
                .Lif = 1
                .Par = 0
                With UserForm1.Controls("Enemy" & L + 1)
                    .Visible = True
                    Select Case Typ
                        Case 1
                            .Picture = UserForm1.Enemy_s1.Picture
                        Case 2, 3
                            .Picture = UserForm1.Enemy_s2.Picture
                    End Select
                End With
                Exit For
            End If
        End With
    Next
    
End Sub


'敵キャラクターを担当するプロシージャ

Sub Enemy_Action()

    Dim L As Long
    Dim TCMOD
    
    TCMOD = Total_Count Mod 30
    
    For L = 0 To 9
        With Enemy_Data(L)
            If .Lif > 0 Then
                Select Case .Typ
                    Case 1
                        .Y = .Y + 1.5
                    Case 2
                        .X = .X + 0.5
                        .Y = .Y + 1
                    Case 3
                        .X = .X - 0.5
                        .Y = .Y + 1
                End Select
                If .X > 220 Then .Lif = 0
                If .Y > 220 Then .Lif = 0
                If .X < -20 Then .Lif = 0
                If .Y < -20 Then .Lif = 0
                If TCMOD = 0 Then
                    Select Case .Typ
                        Case 2
                            .Typ = 3
                        Case 3
                            .Typ = 2
                    End Select
                    Call E_Shot_Begin(.X, .Y + 2, .Typ)
                End If
            End If
        End With
        With UserForm1.Controls("Enemy" & L + 1)
            If Enemy_Data(L).Lif > 0 Then
                .Left = Enemy_Data(L).X - Enemy_Size
                .Top = Enemy_Data(L).Y - Enemy_Size
            Else
                .Visible = False
            End If
        End With
    Next
    
End Sub


'敵キャラクターショット生成プロシージャ

Sub E_Shot_Begin(X As Single, Y As Single, Typ As Long)
    
    Dim L As Long
    
    For L = 0 To 9
        With E_Shot_Data(L)
            If Not .Vis Then
                .X = X
                .Y = Y
                .Typ = Typ
                .Vis = True
                With UserForm1.Controls("E_Shot" & L + 1)
                    .Visible = True
                End With
                Exit For
            End If
        End With
    Next
    
End Sub


'敵キャラクターショット移動管理プロシージャ

Sub E_Shot_Action()

    Dim L As Long
    Dim LL As Long
    
    For L = 0 To 9
        With E_Shot_Data(L)
            If .Vis Then
                Select Case .Typ
                    Case 1
                        .Y = .Y + 4.5
                    Case 2
                        .X = .X + 0.5
                        .Y = .Y + 3.5
                    Case 3
                        .X = .X - 0.5
                        .Y = .Y + 3.5
                End Select
                If .X > 205 Then .Vis = 0
                If .Y > 205 Then .Vis = 0
                If .X < -5 Then .Vis = 0
                If .Y < -5 Then .Vis = 0
                If .X - 2 < Player_Data.X + Player_Data.W Then
                    If .X + 2 > Player_Data.X - Player_Data.W Then
                        If .Y - 2 < Player_Data.Y + Player_Data.H Then
                            If .Y + 2 > Player_Data.Y - Player_Data.H Then
                                .Vis = False
                                With Player_Data
                                    Call Burst_Begin(.X, .Y)
                                    .Lif = 0
                                End With
                            End If
                        End If
                    End If
                End If
                With UserForm1.Controls("E_Shot" & L + 1)
                    If E_Shot_Data(L).Vis Then
                        .Left = E_Shot_Data(L).X - Shot_Size
                        .Top = E_Shot_Data(L).Y - Shot_Size
                    Else
                        .Visible = False
                    End If
                End With
            End If
        End With
    Next
                
End Sub


'爆発エフェクト生成プロシージャ

Sub Burst_Begin(X As Single, Y As Single)

    Dim L As Long
    
    For L = 0 To 2
        With Burst_Data(L)
            If .Par = 0 Then
                .X = X
                .Y = Y
                .W = Burst_Size
                .H = Burst_Size
                .Par = 1
                UserForm1.Controls("Burst" & L + 1).Visible = True
                Exit For
            End If
        End With
    Next
    
End Sub


'爆発エフェクト管理プロシージャ

Sub Burst_Action()

    Dim L As Long
    Dim Pict As Long
    
    For L = 0 To 2
        With Burst_Data(L)
            If .Par > 0 Then
                Select Case .Par
                    Case 1
                        Pict = 1
                    Case 2, 3
                        Pict = 2
                    Case 4, 5
                        Pict = 3
                    Case 6, 7
                        Pict = 4
                    Case 8
                        Pict = 5
                    Case 9
                        .Par = -1
                End Select
                .Par = .Par + 1
                With UserForm1.Controls("Burst" & L + 1)
                    If Burst_Data(L).Par > 0 Then
                        .Left = Burst_Data(L).X - Burst_Size
                        .Top = Burst_Data(L).Y - Burst_Size
                        .Picture = UserForm1.Controls("Burst_s" & Pict).Picture
                    Else
                        .Visible = False
                    End If
                End With
            End If
        End With
    Next
                
End Sub


'標準モジュールはここまで
'ここから下はフォームモジュールに記述


'コマンドボタンのクリックイベント

Private Sub Com_Start_Click()
    Com_Start.Enabled = False
    Call Main
    Unload UserForm1
End Sub


'ユーザーフォームのクエリクロースイベント

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    End
End Sub


'ユーザーフォームのイニシャライズイベント
Private Sub UserForm_Initialize()
    UserForm1.Width = 220
End Sub

ゲームの進行状況を表す変数Game_Modeが、どのようなタイミングで変更されているか、これを理解することが大切です。
プレイヤーキャラクターが3回死んでしまったときには、これはゲームオーバーに相当しますので、ここでも変数Game_Modeが変更されていますよね。
ちなみに、今回の変更を行った時点で、ゲームのクリアにも対応できる仕組みになっています。最終的にゲームのクリアと判断される状況になった場合には、変数Game_Modeに3を設定すればいいわけです。

メインプロセスとなるプロシージャMainでは、Game_Modeに入っている値によって、タイトルのアクションを行ったり、ゲームのプレイ中の処理を行ったり、あるいはゲームオーバーのメッセージを出したりと、分岐しながら処理します。

これがわかっていれば、今回の講座内容はほぼ完璧に理解できたと言っていいでしょう。

今回もサンプルを用意しましたので、実際に動かしてみるとわかりやすいのではないかと思います。全体の流れをよく理解して、ゆっくりで構いませんから、がんばって修正してみてください。
シューティングゲームもここらでいよいよ大詰めです。次回からは、最後の難関、ボスキャラクターを取り上げます。

ボスキャラクターを実装できれば、晴れてゲームは完成です。


サンプルダウンロード ⇒ コチラよりダウンロードできます。(別館)


■格言

タイトルはゲームの顔
進行状況に合わせて処理を分岐する


状況に合わせ柔軟に処理できる仕組みを構築しましょう。






Chapter.58 [ シューティングゲーム18:ボスキャラクター ]

■最後の難関

さて、長いこと続けてきたシューティングゲームの作成講座ですが、そろそろ終わりも近づいてきました。
ゲームの基本的な部分はほぼ完成し、あとはボスキャラクターを残すのみです。ここまで順に進めてきた方は本当に大変だったことでしょう。覚えなければならないこともたくさんあったでしょうし、難解な考え方をしている部分もあったかもしれません。

しかし、諦めずに少しずつでも前に進むことが、ゲームの作成では一番大事です。気分が乗らないときは無理に進める必要はありません。できることから、自分のペースで作るというスタンスが大事です。
また、最終的には作品を必ず完成させる、ということも大事です。
どうしても途中でモチベーションが下がってしまったり、他のものに興味が移ったりしますから、最後までキチンと完成させるというのは結構大変なのです。

シューティングゲームの作成講座もあと少し。最後で気を抜かず、きっちり作品を仕上げてしまいましょう。

さて、ボスキャラクターを作るとは言ったものの、具体的にどのようにしてボスキャラクターを実装させるか、なんとなく皆さんは想像がつくでしょうか。

ボスキャラクターの実装については、今までこの講座でシューティングゲームを作成してきたノウハウがあれば、新しい技術とかは必要ありません。応用と、ひらめきがあれば、結構簡単にボスキャラクターを実装することができるはずです。

とりあえず、私の考えるボスキャラクターの実装方法を、今回は解説してみることにします。


■ボス? それともザコ?

では、ボスキャラクターとザコキャラクターの違いって何でしょうか。
プレイヤー側から見たボスとザコの違いは、結構簡単に思いつきますね。強い、硬い、でかい、とかでしょうか。でも、プログラムから見た場合には、それほど大きな違いはありません。違う点といえば、つぎのような感じでしょうか。

1:倒すとゲームクリア(ステージクリア)になる
2:一発食らったぐらいじゃ死なない
3:攻撃パターンが多彩

1番は、前回の講座の内容をしっかり理解していれば、難なく解決できます。前回の講座で、ゲームがどのような進行状態なのか、それによって処理を分岐できるようにしたからです。ゲームをクリアしたなら、進行状況を管理している変数の値を変更してやればいいわけですね。
ボスキャラクターを撃破したら、ゲームの進行状況を管理している変数Game_Modeに3を設定してやればいいのです。これでゲームのクリア処理が行われます。

次に2番ですが、これも実は簡単です。
キャラクターを定義する構造体には、『ライフ』という項目があったのを覚えていますか。
ザコキャラクターは、ショットに1回当たると死んでしまいますので、このライフという要素を『生きているか・死んでいるか』の2択でしか使っていませんでした。
でも、あらかじめ敵のライフを設定しておき、ショットに1回当たるごとに引き算していくようにすれば、耐久力を調節することができるようになります。ライフが10の敵キャラクターなら、10回ショットを当てないと倒せないようになるというわけです。

ボスキャラクターは、まさにこれです。適当なライフをあらかじめ与えておき、ショットが1回当たるごとに、ライフをマイナス1ずつしていけばいいのです。ライフが0になったら、ボスを撃破したと判断するわけですね。

そして、3番目、多分ここが一番面倒です。
敵の攻撃パターンを変化させるわけですから、ちょっと難しそうに思えるかもしれませんね。でも、実は今までに解説した内容を応用するだけで、これも実装させることができてしまいます。

以前、Mod演算子をうまく活用して、敵キャラクターの出現するタイミングを調節する方法を解説しました(Chapter48を参照)。
このときは、Mod演算子の計算結果によって、一定のタイミングで敵キャラクターが登場するようにしたんでしたね。これと同じ方法を使って、ボスキャラクターに色んな種類の攻撃パターンを実装させることができます。

細かい実装については、あとで実際のコードを見ながら考えてみることにします。


■用意するべきもの

さて、それではいつものように、ユーザーフォームの準備から始めましょう。
今回のボスキャラクターとして使用する画像には、次のものを用意しました。

Boss.gif

この画像の大きさは40×40ピクセルです。ユーザーフォーム上に、イメージコントロールを配置して、この画像をロードしましょう。

340.gif

例によって、背景の透過処理や大きさの調節をしっかりやっておきましょう。さらに、このボスキャラクター用イメージコントロールは、ゲームの開始時には画面上に映っていてはおかしいので、VisibleプロパティをFalseにしておきます。
それと、名前の変更も忘れずに。名前はそのものズバリ、『Boss』としておきます。

さて、今回は、ボスキャラクターということで、撃ってくるショットも追加してみます。
それがこちら。

Shot03.gif

こちらは8×8ピクセルです。かなり小さいのでちょっとわかりにくいですね。
他の画像同様、右クリックから『対象をファイルに保存』でダウンロードできます。ダウンロードできたら、次のような感じでユーザーフォーム上にイメージコントロールを配置し、画像をロードしておきましょう。

341.gif

今回追加したショットの画像は、『原画用』のイメージコントロールです。ショットが生成されると同時に、E_Shot_s1 の画像を使うのか、それとも、E_Shot_s2の画像を使うのか、ショットの種類に応じて切り替えるようにするわけです。
ここで追加したコントロールは、名前を変更して『E_Shot_s1E_Shot_s2』としておきましょう。

ここまでできたら、ユーザーフォームの準備は完了です。


次は、ボスキャラクターを管理する変数などを準備します。

Public Boss_Data As Chara ・・・ ボスキャラクター管理用 構造体変数
Public Boss_Begin As Boolean ・・・ ボスキャラクターが出現しているかどうか
Public Const Boss_size As Single = 15 ・・・ ボスキャラクターの実際の大きさ

これら3つを追加する必要があります。
このなかで、うえから2番目の『 Boss_Begin 』はちょっと特殊な役割をします。

ゲームの処理が行われている間、敵キャラクターや、ショットなど、様々なものが同時に処理されていきます。ボスキャラクターも同様に、それを管理するプロシージャをこれから用意するわけですが、ボスキャラクターが登場していないときに、毎回ボスキャラクター用のプロシージャを呼び出すのは無意味です。
そこで、この変数Boss_Beginを使って、ボスキャラクターが登場しているかどうかを管理するようにします。

ゲームの様々な処理のなかで、ボスキャラクターの存在が関係する部分では、この変数Boss_Beginの値を調べることで、処理を行う必要があるのかないのか、その都度判断することができるというわけです。

これで、最低限追加するべき変数などの準備はOKです。
追加した変数などは、初期化処理できっちり初期化するようにしておきます。

Sub Init()
    With Player_Data
        .X = 100
        .Y = 210
        .W = 3
        .H = 3
        .Lif = 1
        .Typ = 0
        .Par = 1
    End With
    With Boss_Data
        .X = 0
        .Y = 0
        .W = 0
        .H = 0
        .Lif = 0
        .Typ = 0
        .Par = 0
    End With

    Erase P_Shot_Data
    Erase E_Shot_Data
    Erase Enemy_Data
    Erase Burst_Data
    Shot_Interval = 0
    Total_Count = 0
    Player_Stock = 2
    Score = 0
    Game_Mode = 0
    Boss_Begin = False
    With UserForm1.Player
        .Visible = True
        .Top = 200
    End With
    UserForm1.Lab_Score.Caption = "00000000"
End Sub

これで初期化はOKです。

注意するべきは、構造体を使って宣言している『Boss_Data』の初期化です。
敵キャラクターなどを管理している構造体変数は、Erase を使って初期化しています。しかし、上のコードを見ると、ボスキャラクター用の構造体変数はEraseを使わずに直接0を代入して初期化していますね。これは、Erase配列の初期化のみを行うという仕様になっているためです。
ボスキャラクターが1体だけなので、この構造体変数は配列にはなっていません。ですからEraseを使用することができないのですね。

さて、その他のコードですが……、これは次回以降の講座で載せることにします。参考までに、どのようなコードを追加するのか、その概要だけ記載しておきます。

敵出現処理の追加
ボスを制御するプロシージャの追加
敵ショット生成プロシージャの修正
敵ショット移動管理プロシージャの修正
ショットと敵との衝突判定を追加
メインプロセスの修正

結構もりだくさんですね。がんばりましょう。


■格言

プログラミングは応用とセンス
そして、ひらめきがモノを言う
Eraseは配列専用


あと少しで完成です。がんばってください。







Chapter.59 [ シューティングゲーム19:最後の仕上げへ ]

■コードの修正と追加

前回に引き続き、ボスキャラクターの処理に関してやっていきます。
修正する箇所が多くなりますので、ちょっと大変ですが頑張っていってみましょう。

まずは、敵キャラクターの出現を管理しているプロシージャ、Stage_Countの修正からです。

今の状態では、ゲームを止めるまで、永遠に敵キャラクターが出現し続ける仕様になっています。これを、ボスキャラクターが登場する仕様に修正して変更します。

Sub Stage_Count()

    Dim TCMOD As Long
    
    Randomize
    
    TCMOD = Total_Count Mod 1000 '①
    
    If Total_Count < 2000 Then
        Select Case TCMOD
            Case 100, 150, 600, 650, 700, 750
                Call Enemy_Begin(Int(Rnd * 120) + 40, -10, 1)
            Case 200, 350, 450, 500, 800, 950
                Call Enemy_Begin(Int(Rnd * 120) + 40, -10, 2)
            Case 250, 300, 400, 550, 850, 900
                Call Enemy_Begin(Int(Rnd * 120) + 40, -10, 3)
        End Select
    Else
        If Total_Count = 2000 Then '②
            With Boss_Data
                .X = 100
                .Y = -20
                .W = 8
                .H = 8
                .Lif = 50
                .Par = 0
                .Typ = 0
            End With
            Boss_Begin = True '③
            UserForm1.Boss.Visible = True
        End If
    End If
    
End Sub

上から順番に説明します。

まず①の部分。ゲームが開始されてから、1回ループするごとに変数Total_Countはプラス1ずつ増えていきます。そして、この変数の値を元にMod演算した結果が、変数TCMODに入ります。

ここでは、『TCMOD = Total_Count Mod 1000』という代入を行っていますので、変数TCMODに入る値は常に0~999の範囲に制限されます。ここまでは大丈夫ですか?
Mod演算子を使った計算では、『除算の余り』を得ることができます。つまり、ループのたびに無制限に増え続ける変数Total_Countを1000で割ると、その余りは『割り切れた場合の0』から『割り切れなかった場合の余り999』までの間におさまるというわけですね。
この余りをうまく利用して、Select Caseでの分岐を行いながら処理を行っているのです。

その次は変数Total_Countの値に応じて処理が分かれます。
もしも、変数Total_Countが2000よりも小さい数だった場合には、ザコ敵キャラクターの出現処理に移ります。ここでは、先ほど登場した変数TCMODの値を元にザコ敵キャラクターを出現させています。

そして、もし変数Total_Countが2000以上の数値になった場合、ここからがボスキャラクターの出現処理です。これが②の部分ですね。
ここでは、変数Total_Countが2000ちょうどだった場合にだけ、ボスキャラクターの出現処理を行っています。構造体の各要素に、値を設定します。

そして最後に③の部分で、変数Boss_BeginTrueにしています。これ以降は、この変数を参照することによって、ボスが出現しているということを判断することができるようになりました。


■ボスキャラクターの管理

さて、次はボスキャラクターを管理するプロシージャの作成です。こちらは結構長いプロシージャですが、今までやってきたことを応用したものに過ぎません。よく見てひとつずつ理解していってください。

Sub Boss_Action()

    Dim L As Long
    Dim LL As Long
    Dim S As Single
    Dim SS As Single
    
    Dim TCMOD
    
    Randomize
    
    TCMOD = Total_Count Mod 500
    
    With Boss_Data
        If .Lif > 0 Then
            If .Y < 30 Then '①
                .Y = .Y + 1
            Else
                If .Par > 0 Then
                    .X = .X + 2
                    If .X > 160 Then
                        .X = 160
                        .Par = 0
                    End If
                Else
                    .X = .X - 2
                    If .X < 40 Then
                        .X = 40
                        .Par = 1
                    End If
                End If
            End If
            Select Case TCMOD '②
                Case 50, 70, 90, 110, 130, 150
                    .Typ = 1
                Case 60, 80, 100, 140
                    .Typ = 2
                Case 80, 120, 160
                    .Typ = 3
                Case 200, 230, 260, 290, 320
                    .Typ = 4
                Case 350, 360, 370, 380, 390, 400, 410, 420, 430, 440, 450
                    .Typ = 5
                Case Else
                    .Typ = 0
            End Select
            If .Typ <> 0 Then Call E_Shot_Begin(.X, .Y + 5, .Typ)
        Else
            .Par = .Par + 1 '③
            If .Par < 100 Then
                If .Par Mod 10 = 0 Then
                    S = Int(Rnd * 25) - 13
                    SS = Int(Rnd * 25) - 13
                    Call Burst_Begin(.X + S, .Y + SS)
                End If
            Else
                Game_Mode = 3 '④
                Boss_Begin = False
            End If
        End If
    End With
    With UserForm1.Boss
        If Boss_Begin Then
            .Left = Boss_Data.X - Boss_size
            .Top = Boss_Data.Y - Boss_size
        Else
            .Visible = False
        End If
    End With

End Sub

さて、①から見ていきましょう。
①の部分から、次の②までの間の部分では、ボスキャラクターの移動処理を行っています。今回のコードでは、単純に左右に往復するだけの動きになっています。

画面の外からボスキャラクターが登場し、縦位置が30を超えると左右往復の動きへと移行します。そして左右往復の動作に移ったあとは、構造体の要素Parの状態によって、今現在、左に向かって動いているのか、それとも右に向かって動いているのかを決めています。

構造体の要素Parの値が、0より大きい(つまり1以上)のときは、ボスキャラクターが右に向かって移動します。逆に、0以下の数値が設定されているときは左に向かって移動します。

②の部分では、敵キャラクターの出現管理と同じ手法を使って、ボスキャラクターの攻撃パターンを作っています。
Mod演算をうまく活用しているのがわかりますね。
変数TCMODの値によって、全部で5種類のショットを発射するようになっています。ちなみに、今はまだショットの種類が3種類しか定義されていません。4番目と5番目のショットについては、次の項でやります。

ショットについてはちょっと置いといて、次の③を見てみましょう。③以降の部分では、ボスキャラクターが破壊された場合の処理を行っています。これは、プレイヤーキャラクターの爆発エフェクトのときに、詳しく仕組みを解説しましたね。(Chapter55を参照)あれと同様の手法です。
ここでは、ループが100回まわるまで、10ループごとに爆発エフェクトを発生させています。ボスキャラクターなので、たくさん爆発が起こったほうが、それっぽい感じがするでしょ? 多分……。

そして最後に④の部分。ここでは、ボスキャラクターが完全に消えたところで、ゲームの進行状況を『ゲームのクリア』を表す状態に変更しています。『Game_Mode = 3』の部分ですね。

これをやっておかないと、ボスキャラクターを破壊したのに、ゲームがクリアできないという状態になってしまいます。忘れずに設定しておきましょう。


さて、これで残すはショット関連の処理だけです。
ボスキャラクターを含めた敵キャラクターが放つショットの修正、そしてプレイヤーキャラクターが放つショットの修正。このふたつを行えば、晴れてシューティングゲームが完成します。

内容的にかなり縦長になってしまいますので、これは次回です。ちょっとまどろっこしい気もしますが、今回の内容をまずはしっかり理解してください。

目新しい技術は特にありません。全てが今までの応用になっています。一見するとわかりにくい部分でも、ゆっくり順番に条件分岐などを踏まえて考えていけば、おのずと何をやろうとしているのかわかるはずです。ここまで地道に進めてきてくださっていれば尚更です。

いきなりこのページから見始めている方にとっては、ちょっと難しい内容になっているかもしれません。わからないことがあれば、必要に応じて過去の講座に戻ってやってみるといいでしょう。なんにしても焦りは禁物です。自分のレベルに合わせて、わかるところからじっくり取り組みましょう。


次回はいよいよシューティングゲーム講座の最終回。あと少しだけ、頑張ってほしいと思います。


■格言

Mod演算子を活用する
わかることからコツコツと


次回はいよいよ完成させます。
自分で言うのもなんですが、長かったなぁ……。







Chapter.60 [ シューティングゲーム20:いよいよ完成STG ]

■ショット関連の処理

さぁ、いよいよ今回の講座で、長かったシューティングゲームの講座も最後です。気合を入れて頑張りましょう。

それでは、前回宣言したとおり、今回はショット関連の処理を一気にやってしまいます。まずはプレイヤーキャラクターが放ったショットの処理から見ていきましょう。

Sub P_Shot_Action()

    Dim L As Long
    Dim LL As Long

    For L = 0 To 4
        With P_Shot_Data(L)
            If .Vis Then
                .Y = .Y - 6
                If .Y < -5 Then .Vis = False
                For LL = 0 To 9
                    If Enemy_Data(LL).Lif > 0 Then
                        If .X - 2 < Enemy_Data(LL).X + Enemy_Data(LL).W Then
                            If .X + 2 > Enemy_Data(LL).X - Enemy_Data(LL).W Then
                                If .Y - 2 < Enemy_Data(LL).Y + Enemy_Data(LL).H Then
                                    If .Y + 2 > Enemy_Data(LL).Y - Enemy_Data(LL).H Then
                                        .Vis = False
                                        With Enemy_Data(LL)
                                            Call Burst_Begin(.X, .Y)
                                            .Lif = 0
                                        End With
                                        Score = Score + 100
                                        Exit For
                                    End If
                                End If
                            End If
                        End If
                    End If
                Next
                If Boss_Data.Lif > 0 Then
                    If .X - 2 < Boss_Data.X + Boss_Data.W Then
                        If .X + 2 > Boss_Data.X - Boss_Data.W Then
                            If .Y - 2 < Boss_Data.Y + Boss_Data.H Then
                                If .Y + 2 > Boss_Data.Y - Boss_Data.H Then
                                    .Vis = False
                                    With Boss_Data
                                        Call Burst_Begin(.X, .Y)
                                        .Lif = .Lif - 1
                                    End With
                                    Score = Score + 50
                                End If
                            End If
                        End If
                    End If
                End If

                With UserForm1.Controls("P_Shot" & L + 1)
                    If P_Shot_Data(L).Vis Then
                        .Left = P_Shot_Data(L).X - Shot_Size
                        .Top = P_Shot_Data(L).Y - Shot_Size
                    Else
                        .Visible = False
                    End If
                End With
            End If
        End With
    Next

End Sub

ボスキャラクターは、通常のザコ敵キャラクターとは違う構造体変数を使っています。ですから、ショットとの衝突判定も独自に実装する必要があります。追加されているのは緑色で表示されている部分だけです。

実際にやっていることは、通常の衝突判定と全く同じことをしているだけです。ボスキャラクターの位置と大きさ、これを元に衝突しているかどうか計算しているのですね。

ポイントは、緑色の文字の一番最初の行の部分。ここで、ボスキャラクターが存在しているかどうか調べています。存在していないときに衝突判定を行う必要は無いので、ここでそれを判断してから衝突判定の処理に移っているわけです。


■敵キャラクターのショット

さて、次に敵キャラクターのショットまわりを修正しましょう。
前回の講座で載せたコードの中に、ボスキャラクターが放つショットの処理がありました。そこでは、ボスキャラクターが5種類のショットを撃ってくるように設計されていましたが、今現在は3種類のショットしか定義されていません。

ここでは、ショットの種類に応じて処理が分かれるように、適宜、コードの修正及び追加を行いましょう。
まずは、ショットを生成するプロシージャの修正からです。

ショットの生成段階で、ショットの種類に応じて、画像が変化するようにしてみます。

Sub E_Shot_Begin(X As Single, Y As Single, Typ As Long)
    
    Dim L As Long
    
    For L = 0 To 9
        With E_Shot_Data(L)
            If Not .Vis Then
                .X = X
                .Y = Y
                .Typ = Typ
                .Vis = True
                With UserForm1.Controls("E_Shot" & L + 1)
                    .Visible = True
                    Select Case Typ
                        Case 1, 2, 3
                            .Picture = UserForm1.E_Shot_s1.Picture
                        Case Else
                            .Picture = UserForm1.E_Shot_s2.Picture
                    End Select

                End With
                Exit For
            End If
        End With
    Next
    
End Sub

さて、緑色になっている部分が今回追加された部分ですが、何をしようとしているか、なんとなくわかりますか?

ここでは、ショットのタイプによって、画像が切り替わるように処理しています。
前々回の講座で、ユーザーフォーム上にショット用の原画となるイメージコントロールを配置しました。『E_Shot_s1E_Shot_s2』がこれにあたります。

ショットのタイプが、1か2か3の場合には、青いショットのほうを使います。青いショットの画像はE_Shot_s1の画像を使います。
そして、それ以外の場合にはE_Shot_s2の画像を使います。今回は4番目と5番目のショットを追加しますので、そのふたつのショットはE_Shot_s2を原画として使用して表示するようになります。

このプロシージャで修正しているのはここだけです。簡単ですね。

さて問題は次です。
ショットの4番目と5番目、これがどのような動きをするのかを定義しておかなくてはいけません。それが次のコードになります。ちょっと長いですが、色つきの部分に絞って考えれば大丈夫だと思います。

Sub E_Shot_Action()

    Dim L As Long
    Dim LL As Long
    
    For L = 0 To 9
        With E_Shot_Data(L)
            If .Vis Then
                Select Case .Typ
                    Case 1
                        .Y = .Y + 4.5
                    Case 2
                        .X = .X + 0.5
                        .Y = .Y + 3.5
                    Case 3
                        .X = .X - 0.5
                        .Y = .Y + 3.5
                    Case 4
                        If .X < Player_Data.X Then
                            .X = .X + 1.5
                        Else
                            .X = .X - 1.5
                        End If
                        .Y = .Y + 2
                    Case 5
                        .Y = .Y + 6

                End Select
                If .X > 205 Then .Vis = 0
                If .Y > 205 Then .Vis = 0
                If .X < -5 Then .Vis = 0
                If .Y < -5 Then .Vis = 0
                If .X - 2 < Player_Data.X + Player_Data.W Then
                    If .X + 2 > Player_Data.X - Player_Data.W Then
                        If .Y - 2 < Player_Data.Y + Player_Data.H Then
                            If .Y + 2 > Player_Data.Y - Player_Data.H Then
                                .Vis = False
                                With Player_Data
                                    Call Burst_Begin(.X, .Y)
                                    .Lif = 0
                                End With
                            End If
                        End If
                    End If
                End If
                With UserForm1.Controls("E_Shot" & L + 1)
                    If E_Shot_Data(L).Vis Then
                        .Left = E_Shot_Data(L).X - Shot_Size
                        .Top = E_Shot_Data(L).Y - Shot_Size
                    Else
                        .Visible = False
                    End If
                End With
            End If
        End With
    Next
                
End Sub

ショットの4番目は、簡易的な追尾弾です。
プレイヤーキャラクターの現在の横位置を調べて、その方向へ移動していくようになっています。ショットの位置よりプレイヤーが左にいれば、ショットも左へ向かうという具合ですね。
追尾弾というのは、本物をやろうとすると結構大変なので、今回は簡易的な実装に留めています。横位置を調べてその方角へ移動させるだけなら、結構簡単ですからね。

5番目のショットは、単に下方向へまっすぐ進むだけです。ただし、1回のループで移動する距離が少々大きめに設定されています。これはつまり高速移動するショットということになります。

このプロシージャに追加するのはこれだけでOKです。
そして、これで修正のほぼ全てが完了したことになります。


■まとめ

いやぁ、長かった。本当に長かった。
ひとつの作品を作り上げるということは、本当に大変な労力を使います。手間も、根気も、並大抵ではありません。それだけに、大抵のプログラムというのは、途中で開発が止まってしまい、未完成に終わることが多いのです。

しかし、以前にも言いましたが、ゲームを作成する上でとても大切なことのひとつが、とりあえず完成させるということなのです。
御託をいくら並べても、ひとつの作品が出来上がらなかったら意味がありません。えらそうなことを言って、知識ばっかりたくさん持っていても、作品を仕上げられないうちは一人前とは言えません。

多少簡素なものであっても、それが完成品であるならば、それはあなたにとってとても意味のあるもののはずです。簡単なコードしか書けなくても、難しいことはわからなくても、努力の末に作品を仕上げたときには、あなたは立派なプログラマーです。
私が全てのコードを公開しているという都合上、コピーするだけでも動くゲームは出来上がります。でも、ここにどのようなエッセンスを加えていくかはあなた次第。どのように応用して生かしていくのかもあなた次第です。

何度も言いますが、私ができるのは、皆さんのお手伝い、道案内だけです。
この講座を通して得た知識で、皆さんが独自の世界を表現していくことを切に願います。

とにかく、ここまで根気強く進めてきてくださった皆さん。本当にお疲れ様でした。


最後にコードを全て掲載しておきます。講座の最後には、サンプルのページへのリンクも貼っておきます。これらを参考に、頑張ってみてください。


シューティングゲーム講座 全コード掲載
'標準モジュール先頭部分

'◆API 宣言

Declare Function GetTickCount Lib "kernel32" () As Long
Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMillsecounds As Long)

'■キャラクター 構造体宣言
Type Chara
    X As Single
    Y As Single
    W As Single
    H As Single
    Lif As Long
    Typ As Long
    Par As Long
End Type

'■ショット 構造体宣言
Type Shot
    X As Single
    Y As Single
    Vis As Boolean
    Typ As Long
End Type

'■汎用オブジェクト 構造体宣言
Type Obj
    X As Single
    Y As Single
    W As Single
    H As Single
    Par As Long
End Type

'●キャラクター 変数宣言
Public Player_Data As Chara
Public Enemy_Data(9) As Chara
Public Boss_Data As Chara

'●ショット 変数宣言
Public P_Shot_Data(4) As Shot
Public E_Shot_Data(9) As Shot

'●汎用オブジェクト 変数宣言
Public Burst_Data(2) As Obj

'◎その他 変数宣言
Public Shot_Interval As Long
Public Total_Count As Long 'ゲーム中のカウンタ
Public Player_Stock As Long 'プレイヤーの残機数
Public Score As Long 'ゲームのスコア
Public Boss_Begin As Boolean 'ボスが出現しているかどうか
Public Game_Mode As Long '現在のゲームモード
'0=タイトル
'1=ゲーム中
'2=ゲームオーバー
'3=ゲームクリア

'▲定数宣言
Public Const Left_Key As Long = 37 'カーソル左
Public Const Up_Key As Long = 38 'カーソル上
Public Const Right_Key As Long = 39 'カーソル右
Public Const Down_Key As Long = 40 'カーソル下
Public Const Esc_Key As Long = 27 'Escキー
Public Const Shot_Key As Long = 90 'Zキー
Public Const Player_Size As Single = 9
'プレイヤーキャラクターの実際の大きさ
Public Const Shot_Size As Single = 3
'弾各種の実際の大きさ
Public Const Enemy_Size As Single = 9
'敵キャラクターの実際の大きさ
Public Const Burst_Size As Single = 15
'爆発エフェクトの実際の大きさ
Public Const Boss_size As Single = 15
'ボスの実際の大きさ


'初期化を担当するプロシージャ

Sub Init()
    With Player_Data
        .X = 100
        .Y = 210
        .W = 3
        .H = 3
        .Lif = 1
        .Typ = 0
        .Par = 1
    End With
    With Boss_Data
        .X = 0
        .Y = 0
        .W = 0
        .H = 0
        .Lif = 0
        .Typ = 0
        .Par = 0
    End With

    Erase P_Shot_Data
    Erase E_Shot_Data
    Erase Enemy_Data
    Erase Burst_Data
    Shot_Interval = 0
    Total_Count = 0
    Player_Stock = 2
    Score = 0
    Game_Mode = 0
    Boss_Begin = False
    With UserForm1.Player
        .Visible = True
        .Top = 200
    End With
    UserForm1.Lab_Score.Caption = "00000000"
End Sub


'メインプロセスを担当するプロシージャ

Sub Main()

    Dim Flg As Boolean
    Dim Stm As Long
    
    Init
    
    Flg = False
    
    Do Until Flg
        Stm = GetTickCount
        Total_Count = Total_Count + 1
        Select Case Game_Mode
            Case 0
                Call Tytle_Action
            Case 1
                Call Stage_Count
                Call Player_Action
                Call Enemy_Action
                Call P_Shot_Action
                Call E_Shot_Action
                Call Burst_Action
                If Boss_Begin Then Boss_Action
                UserForm1.Lab_Score.Caption = Format(Score, "00000000")
            Case 2
                MsgBox "Game Over!!"
                Flg = True
            Case 3
                MsgBox "Clear!!"
                Flg = True
        End Select
        DoEvents
        Do
            Call Sleep(1)
        Loop Until GetTickCount - Stm > 30
        If GetAsyncKeyState(27) < 0 Then Flg = True
    Loop

End Sub


'タイトルのアクションを管理するプロシージャ

Sub Tytle_Action()

    With UserForm1
        .Lab_Cap1.Left = .Lab_Cap1.Left + 3
        .Lab_Cap2.Left = .Lab_Cap2.Left - 3
        If .Lab_Cap1.Left > 200 Then
            .Lab_Cap1.Visible = False
            .Lab_Cap2.Visible = False
            Game_Mode = 1
        End If
    End With
    
End Sub


'敵キャラクターの出現管理プロシージャ

Sub Stage_Count()

    Dim TCMOD As Long
    
    Randomize
    
    TCMOD = Total_Count Mod 1000
    
    If Total_Count < 2000 Then
        Select Case TCMOD
            Case 100, 150, 600, 650, 700, 750
                Call Enemy_Begin(Int(Rnd * 120) + 40, -10, 1)
            Case 200, 350, 450, 500, 800, 950
                Call Enemy_Begin(Int(Rnd * 120) + 40, -10, 2)
            Case 250, 300, 400, 550, 850, 900
                Call Enemy_Begin(Int(Rnd * 120) + 40, -10, 3)
        End Select
    Else
        If Total_Count = 2000 Then
            With Boss_Data
                .X = 100
                .Y = -20
                .W = 8
                .H = 8
                .Lif = 50
                .Par = 0
                .Typ = 0
            End With
            Boss_Begin = True
            UserForm1.Boss.Visible = True
        End If
    End If
    
End Sub



'プレイヤーキャラクターを担当するプロシージャ

Sub Player_Action()

    Dim S As Single
    Dim SS As Single
    
    Randomize
    
    With Player_Data
        If .Lif > 0 Then
            If .Par > 0 Then
                If .Y > 180 Then
                    .Y = .Y - 1
                Else
                    .Par = 0
                End If
            Else
                If GetAsyncKeyState(Left_Key) < 0 Then .X = .X - 3
                If GetAsyncKeyState(Up_Key) < 0 Then .Y = .Y - 3
                If GetAsyncKeyState(Right_Key) < 0 Then .X = .X + 3
                If GetAsyncKeyState(Down_Key) < 0 Then .Y = .Y + 3

                If .X - Player_Size < 0 Then .X = Player_Size
                If .X + Player_Size > 200 Then .X = 200 - Player_Size
                If .Y - Player_Size < 0 Then .Y = Player_Size
                If .Y + Player_Size > 200 Then .Y = 200 - Player_Size

                If Shot_Interval = 0 Then
                    If GetAsyncKeyState(Shot_Key) < 0 Then
                        Call P_Shot_Begin(.X, .Y, 1)
                        Shot_Interval = 1
                    End If
                Else
                    Shot_Interval = Shot_Interval + 1
                    If Shot_Interval > 7 Then Shot_Interval = 0
                End If
            End If
        Else
            .Par = .Par + 1
            If .Par > 50 Then
                .X = 100
                .Y = 210
                .Lif = 1
                .Par = 1
                Player_Stock = Player_Stock - 1
                Select Case Player_Stock
                    Case Is < 0
                        Game_Mode = 2
                    Case 0
                        UserForm1.Stock1.Visible = False
                    Case 1
                        UserForm1.Stock2.Visible = False
                End Select
            Else
                If .Par Mod 9 = 1 Then
                    S = Int(Rnd * 20) - 9
                    SS = Int(Rnd * 20) - 9
                    Call Burst_Begin(.X + S, .Y + SS)
                End If
            End If
        End If
        With UserForm1.Player
            .Left = Player_Data.X - Player_Size
            .Top = Player_Data.Y - Player_Size
        End With
    End With
    
End Sub


'ショット生成プロシージャ

Sub P_Shot_Begin(X As Single, Y As Single, Typ As Long)

    Dim L As Long

    For L = 0 To 4
        With P_Shot_Data(L)
            If Not .Vis Then
                .X = X
                .Y = Y
                .Typ = Typ
                .Vis = True
                UserForm1.Controls("P_Shot" & L + 1).Visible = True
                Exit For
            End If
        End With
    Next

End Sub


'ショット移動管理プロシージャ

Sub P_Shot_Action()

    Dim L As Long
    Dim LL As Long

    For L = 0 To 4
        With P_Shot_Data(L)
            If .Vis Then
                .Y = .Y - 6
                If .Y < -5 Then .Vis = False
                For LL = 0 To 9
                    If Enemy_Data(LL).Lif > 0 Then
                        If .X - 2 < Enemy_Data(LL).X + Enemy_Data(LL).W Then
                            If .X + 2 > Enemy_Data(LL).X - Enemy_Data(LL).W Then
                                If .Y - 2 < Enemy_Data(LL).Y + Enemy_Data(LL).H Then
                                    If .Y + 2 > Enemy_Data(LL).Y - Enemy_Data(LL).H Then
                                        .Vis = False
                                        With Enemy_Data(LL)
                                            Call Burst_Begin(.X, .Y)
                                            .Lif = 0
                                        End With
                                        Score = Score + 100
                                        Exit For
                                    End If
                                End If
                            End If
                        End If
                    End If
                Next
                If Boss_Data.Lif > 0 Then
                    If .X - 2 < Boss_Data.X + Boss_Data.W Then
                        If .X + 2 > Boss_Data.X - Boss_Data.W Then
                            If .Y - 2 < Boss_Data.Y + Boss_Data.H Then
                                If .Y + 2 > Boss_Data.Y - Boss_Data.H Then
                                    .Vis = False
                                    With Boss_Data
                                        Call Burst_Begin(.X, .Y)
                                        .Lif = .Lif - 1
                                    End With
                                    Score = Score + 50
                                End If
                            End If
                        End If
                    End If
                End If

                With UserForm1.Controls("P_Shot" & L + 1)
                    If P_Shot_Data(L).Vis Then
                        .Left = P_Shot_Data(L).X - Shot_Size
                        .Top = P_Shot_Data(L).Y - Shot_Size
                    Else
                        .Visible = False
                    End If
                End With
            End If
        End With
    Next

End Sub


'敵キャラクター生成プロシージャ

Sub Enemy_Begin(X As Single, Y As Single, Typ As Long)

    Dim L As Long
    
    For L = 0 To 9
        With Enemy_Data(L)
            If .Lif = 0 Then
                .X = X
                .Y = Y
                .W = 5
                .H = 5
                .Typ = Typ
                .Lif = 1
                .Par = 0
                With UserForm1.Controls("Enemy" & L + 1)
                    .Visible = True
                    Select Case Typ
                        Case 1
                            .Picture = UserForm1.Enemy_s1.Picture
                        Case 2, 3
                            .Picture = UserForm1.Enemy_s2.Picture
                    End Select
                End With
                Exit For
            End If
        End With
    Next
    
End Sub


'敵キャラクターを担当するプロシージャ

Sub Enemy_Action()

    Dim L As Long
    Dim TCMOD
    
    TCMOD = Total_Count Mod 30
    
    For L = 0 To 9
        With Enemy_Data(L)
            If .Lif > 0 Then
                Select Case .Typ
                    Case 1
                        .Y = .Y + 1.5
                    Case 2
                        .X = .X + 0.5
                        .Y = .Y + 1
                    Case 3
                        .X = .X - 0.5
                        .Y = .Y + 1
                End Select
                If .X > 220 Then .Lif = 0
                If .Y > 220 Then .Lif = 0
                If .X < -20 Then .Lif = 0
                If .Y < -20 Then .Lif = 0
                If TCMOD = 0 Then
                    Select Case .Typ
                        Case 2
                            .Typ = 3
                        Case 3
                            .Typ = 2
                    End Select
                    Call E_Shot_Begin(.X, .Y + 2, .Typ)
                End If
            End If
        End With
        With UserForm1.Controls("Enemy" & L + 1)
            If Enemy_Data(L).Lif > 0 Then
                .Left = Enemy_Data(L).X - Enemy_Size
                .Top = Enemy_Data(L).Y - Enemy_Size
            Else
                .Visible = False
            End If
        End With
    Next
    
End Sub


'敵キャラクターショット生成プロシージャ

Sub E_Shot_Begin(X As Single, Y As Single, Typ As Long)
    
    Dim L As Long
    
    For L = 0 To 9
        With E_Shot_Data(L)
            If Not .Vis Then
                .X = X
                .Y = Y
                .Typ = Typ
                .Vis = True
                With UserForm1.Controls("E_Shot" & L + 1)
                    .Visible = True
                    Select Case Typ
                        Case 1, 2, 3
                            .Picture = UserForm1.E_Shot_s1.Picture
                        Case Else
                            .Picture = UserForm1.E_Shot_s2.Picture
                    End Select

                End With
                Exit For
            End If
        End With
    Next
    
End Sub


'敵キャラクターショット移動管理プロシージャ

Sub E_Shot_Action()

    Dim L As Long
    Dim LL As Long
    
    For L = 0 To 9
        With E_Shot_Data(L)
            If .Vis Then
                Select Case .Typ
                    Case 1
                        .Y = .Y + 4.5
                    Case 2
                        .X = .X + 0.5
                        .Y = .Y + 3.5
                    Case 3
                        .X = .X - 0.5
                        .Y = .Y + 3.5
                    Case 4
                        If .X < Player_Data.X Then
                            .X = .X + 1.5
                        Else
                            .X = .X - 1.5
                        End If
                        .Y = .Y + 2
                    Case 5
                        .Y = .Y + 6

                End Select
                If .X > 205 Then .Vis = 0
                If .Y > 205 Then .Vis = 0
                If .X < -5 Then .Vis = 0
                If .Y < -5 Then .Vis = 0
                If .X - 2 < Player_Data.X + Player_Data.W Then
                    If .X + 2 > Player_Data.X - Player_Data.W Then
                        If .Y - 2 < Player_Data.Y + Player_Data.H Then
                            If .Y + 2 > Player_Data.Y - Player_Data.H Then
                                .Vis = False
                                With Player_Data
                                    Call Burst_Begin(.X, .Y)
                                    .Lif = 0
                                End With
                            End If
                        End If
                    End If
                End If
                With UserForm1.Controls("E_Shot" & L + 1)
                    If E_Shot_Data(L).Vis Then
                        .Left = E_Shot_Data(L).X - Shot_Size
                        .Top = E_Shot_Data(L).Y - Shot_Size
                    Else
                        .Visible = False
                    End If
                End With
            End If
        End With
    Next
                
End Sub


'ボスキャラクターの移動管理プロシージャ

Sub Boss_Action()

    Dim S As Single
    Dim SS As Single
    
    Dim TCMOD
    
    Randomize
    
    TCMOD = Total_Count Mod 500
    
    With Boss_Data
        If .Lif > 0 Then
            If .Y < 30 Then
                .Y = .Y + 1
            Else
                If .Par > 0 Then
                    .X = .X + 2
                    If .X > 160 Then
                        .X = 160
                        .Par = 0
                    End If
                Else
                    .X = .X - 2
                    If .X < 40 Then
                        .X = 40
                        .Par = 1
                    End If
                End If
            End If
            Select Case TCMOD
                Case 50, 70, 90, 110, 130, 150
                    .Typ = 1
                Case 60, 80, 100, 140
                    .Typ = 2
                Case 80, 120, 160
                    .Typ = 3
                Case 200, 230, 260, 290, 320
                    .Typ = 4
                Case 350, 360, 370, 380, 390, 400, 410, 420, 430, 440, 450
                    .Typ = 5
                Case Else
                    .Typ = 0
            End Select
            If .Typ <> 0 Then Call E_Shot_Begin(.X, .Y + 5, .Typ)
        Else
            .Par = .Par + 1
            If .Par < 100 Then
                If .Par Mod 10 = 0 Then
                    S = Int(Rnd * 25) - 13
                    SS = Int(Rnd * 25) - 13
                    Call Burst_Begin(.X + S, .Y + SS)
                End If
            Else
                Game_Mode = 3
                Boss_Begin = False
            End If
        End If
    End With
    With UserForm1.Boss
        If Boss_Begin Then
            .Left = Boss_Data.X - Boss_size
            .Top = Boss_Data.Y - Boss_size
        Else
            .Visible = False
        End If
    End With

End Sub



'爆発エフェクト生成プロシージャ

Sub Burst_Begin(X As Single, Y As Single)

    Dim L As Long
    
    For L = 0 To 2
        With Burst_Data(L)
            If .Par = 0 Then
                .X = X
                .Y = Y
                .W = Burst_Size
                .H = Burst_Size
                .Par = 1
                UserForm1.Controls("Burst" & L + 1).Visible = True
                Exit For
            End If
        End With
    Next
    
End Sub


'爆発エフェクト管理プロシージャ

Sub Burst_Action()

    Dim L As Long
    Dim Pict As Long
    
    For L = 0 To 2
        With Burst_Data(L)
            If .Par > 0 Then
                Select Case .Par
                    Case 1
                        Pict = 1
                    Case 2, 3
                        Pict = 2
                    Case 4, 5
                        Pict = 3
                    Case 6, 7
                        Pict = 4
                    Case 8
                        Pict = 5
                    Case 9
                        .Par = -1
                End Select
                .Par = .Par + 1
                With UserForm1.Controls("Burst" & L + 1)
                    If Burst_Data(L).Par > 0 Then
                        .Left = Burst_Data(L).X - Burst_Size
                        .Top = Burst_Data(L).Y - Burst_Size
                        .Picture = UserForm1.Controls("Burst_s" & Pict).Picture
                    Else
                        .Visible = False
                    End If
                End With
            End If
        End With
    Next
                
End Sub


'標準モジュールはここまで
'ここから下はフォームモジュールに記述


'コマンドボタンのクリックイベント

Private Sub Com_Start_Click()
    Com_Start.Enabled = False
    Call Main
    Unload UserForm1
End Sub


'ユーザーフォームのクエリクロースイベント

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    End
End Sub


'ユーザーフォームのイニシャライズイベント
Private Sub UserForm_Initialize()
    UserForm1.Width = 220
End Sub


サンプルダウンロード ⇒ コチラよりダウンロードできます。(別館)


■格言

まずは完成させることが大切
あとは努力と工夫次第


ボスキャラクターの動きなど、サンプルと併用してみるとわかりやすいと思います。







Chapter.61 [ カードゲームで使えるめくり効果 ]

■プロパティを活用した演出

ユーザーフォーム上に配置できる様々なコントロールたち。それらの多くは、非常に多彩なプロパティを持っています。これらのプロパティを変化させることで、ユーザーにとって扱いやすいインターフェイスを作ったり、安定したプログラムを作成することができるようになります。

ですが、プロパティを活用することで、ゲームの演出面が強化される場合もあります。

今まで扱ってきた、アニメーション処理やシューティングゲームの処理でも、イメージコントロールのBackStyleプロパティをうまく利用したキャラクターの表示などを行ってきましたね。
これも、プロパティの成せる技です。

今回は、イメージコントロールが持っているプロパティのうち、PictureSizeModeというプロパティにフォーカスを当ててみたいと思います。


■PictureSizeModeプロパティ

コントロールに画像を表示する際、その表示方法にPictureSizeModeが影響を与えます。
PictureSizeModeは、画像の大きさとコントロールの大きさが一致していない場合に、画像を伸縮させて表示させるか、あるいはそのままの大きさを維持して表示させるか、それを管理します。
シューティングゲームの中でも、このプロパティを使った部分がありましたね(Chapter54を参照)。

このプロパティをうまく活用することで、カードがめくれるような演出を行うことができます。早速、その詳細を見てみましょう。

まずは、ユーザーフォーム上にイメージコントロールを3つ配置します。このイメージコントロールがカードの役割を果たします。
このうち、1つはカードそのものを表し名前が『card』です。残りの2つは原画の役割を果たします。名前は『back』と『front』としておきます。

350.gif

カードの表面と裏面の両方に相当する画像をロードしておき、適宜名前を変更します。
カードそのものを表すcardPictureSizeModeプロパティを変更してfmPictureSizeModeStretchに設定しておきましょう。
コマンドボタンを追加して、これでユーザーフォームの準備は完了です。

あとでコードが記述しやすいように、各コントロールの名前を変更しておくのを忘れずにやっておきましょうね。


■考え方とコードの記述

カードがめくれるような演出を行うためには、どのようにコードを記述すればいいのでしょうか。人間の視点に立って考えてみましょう。

カードが視線に対して直角に存在する場合には、次のようになりますね。

351.gif

カードが視点に対して直角になっているので、カードがそのまま見えるだけです。
そして、カードがめくれていく途中の段階では、こんな風になりますね。

352.gif

視点から見たカードの幅は、めくれていく中でだんだん狭くなっていきます。
完全に視線と水平になったとき、カードはほとんど見えなくなり、今度はだんだん幅が広がっていきます。
完全にめくり終わると、カードは再び視線に対して直角に戻ります。

ここで重要なのは、めくれていく中で、はじめは『 幅が狭くなっていき 』裏返ると『 幅が広くなっていく 』ということです。
イメージコントロールの幅が、狭くなる⇒広くなる、という具合に連続して処理できれば、カードのめくれる動作を実現できるのです。

それでは実際にコードを見てみます。

'API宣言と変数及び定数の宣言
Public Declare Function GetTickCount Lib "kernel32" () As Long
Public Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMillsecounds As Long)

Public Stime As Long
Public Fronts As Boolean

Public Const Card_Left As Single = 72
Public Const Card_Width As Single = 90


Sub Turn()

    Dim Before As Boolean
    Dim Flg As Boolean
    
    Before = True
    
    With UserForm1.card
        Do Until Flg
            Stime = GetTickCount
            If Before Then
                .Width = .Width - 4
                If .Width < 5 Then
                    Before = False
                    If Fronts Then
                        .Picture = UserForm1.back.Picture
                        Fronts = False
                    Else
                        .Picture = UserForm1.front.Picture
                        Fronts = True
                    End If
                End If
            Else
                .Width = .Width + 4
                If .Width > Card_Width Then
                    .Width = Card_Width
                    Flg = True
                End If
            End If
            .Left = Card_Left + (Card_Width - .Width) / 2 '①
            DoEvents
            Do
                Sleep 1
            Loop Until GetTickCount - Stime > 10
        Loop
    End With
        
End Sub


'標準モジュールここまで
'ここからはフォームモジュール

Private Sub CommandButton1_Click()

    CommandButton1.Enabled = False
    
    Turn
    
    CommandButton1.Enabled = True

End Sub

Private Sub UserForm_Initialize()

    Me.Height = 215

    With card
        .Top = 18
        .Left = Card_Left
    End With

    Fronts = False

End Sub

それでは最初から見ていきます。

まずは、APIの宣言と変数及び定数の宣言です。
APIはSleep関数とGetTickCount関数の2種類です。これはループ処理中の同期を取るために使います。今までのゲーム作成などでも使ってきたAPIなので、ここでは詳しい説明はしません。

次に変数ですが、これも2つ、Publicを使って宣言しています。
Stimeは同期処理のために使います。一方、Frontsはカードが今現在表向きなのか、それとも裏向きなのか、それを管理するために使います。

定数も2つです。こちらは必ずしも定義しなくてもいいのですが、後からコードを修正するときの手間を考えて宣言しています。カードの横位置(Left)と幅(Width)の規定値を設定しています。

その下、Turnという名前のプロシージャが、実際にカードのめくれる動作を処理するプロシージャです。
このプロシージャがユーザーフォーム上のコマンドボタンから呼び出されると、ユーザーフォーム上のカードが裏返ります。

そして、このプロシージャの中でも変数を宣言していますね。こちらも2つ宣言されています。Beforeという変数は、今現在の状態が、めくり作業の前半か後半かを管理します。しかしなぜ、前半と後半を分けて処理しなければいけないのでしょう。
先ほども書いたように、カードがめくれていく過程では、始めはカードの幅が狭くなっていきます。しかし後半はカードの幅が広がっていくようになります。今現在カードの幅を狭めるべきなのか、もしくは広げるべきなのか、これを変数Beforeで判断して処理するわけです。

もうひとつの変数Flgは、ループ処理から抜けるためのフラグの役割を担います。


さて、実際の処理の中身です。
変数Beforeが、今カードがどのような状態かを管理するのでしたね。まずはプロシージャの開始と同時に、この変数にTrueを代入しておきます。変数BeforeTrueのときには、めくり動作の前半だというわけです。

もし、めくり動作の前半ならば、カードを表すイメージコントロールの幅、つまりWidthをマイナスして狭くしていきます。一定の幅まで狭まったら、今度は変数BeforeFalseを設定して、カードがめくれたあとの動作に移ります。このときにカードの画像を同時に切り替えているのがポイントです。
カードがめくれたあとは、逆に幅を広げていきます。ここでも、一定の幅まで広がったら、ループ処理を抜けるようにしておきます。

今回は①で示す部分が最も難解な部分になると思います。
ここでは、カードの横位置(Left)を設定しています。定数をうまく利用することで、できる限り簡単に処理できるようにしています。

本来のカードの横位置がCard_Leftです。止まっているときのカードの横位置ですね。そしてCard_Widthが本来のカードの幅です。
めくり動作中には、カードの幅が常に変更され続けますよね。これをただ単に幅の変更だけにしてしまうと、横位置が変更されないので左に寄っていってしまいます。

353.gif

    ▼

354.gif

    ▼

355.gif

どうですか? ただ横幅(Width)を変更するだけでは、幅が狭くなっていくだけなので左に寄っていってしまうのです。

カードが中心を軸にして回転しているように見せるためには、常にカードの中心を求めて横位置を修正しなければいけません。①の部分のコードでは、それをやっているのです。

.Left = Card_Left + (Card_Width - .Width) / 2

本来の幅から、現在の幅を引けば、今どれくらい幅が狭まったかがわかります。もし幅が10ポイント狭まっていたなら、中心を軸に考えるので、半分の5ポイント移動させればいいことになります。少しわかりにくいかもしれませんが、よーく式を見て考えてみてくださいね。


■まとめ

カードゲームは、激しく動くアクションゲームなどと比べると、どうしても地味な印象になりがちです。カードがめくれるという単純な演出でも、その効果は非常に大きいものとなります。

今回の処理で注意しなければならないのは、コントロールのWidthプロパティを設定するときです。
コントロールの幅を表すWidthプロパティや、高さを表すHeightプロパティなど、幅を定義するプロパティにはマイナスの数値を設定できないので注意してください。
もしマイナスの数値を設定しようとすると、エラーが起きて処理が止まってしまいます。これはどんなコントロールであっても共通する仕様です。幅を表すプロパティにはマイナスの数値を設定できないと覚えておきましょう。

カードをめくる処理自体は、中心を軸にして横位置を調整する、というところさえ理解できれば、それほど難しいものではないと思います。サンプルを作成しておきましたのでそちらも参考に。


サンプルダウンロード ⇒ コチラよりダウンロードできます。(別館)



■格言

プロパティを有効に利用する
カードめくりでは中心を考えて横位置を調整


PictureSizeModeをキチンと設定しておかないとうまくいきません。






Chapter.62 [ ラジアンと角度 ]

■角度を扱う

ゲームのプログラミングでは角度計算を行うことが頻繁にあります。

シューティングゲームでの弾道計算。ブロック崩しなどでのボールの角度計算。アクションゲームでのキャラクターの移動する軌道計算……などなど。

プログラムの中では、分度器を使って角度を測るわけにはいきません。今回は『角度計算』について考えてみましょう。


■度数法と弧度法

皆さんは、円が1周で何度ですか、と聞かれたらなんと答えますか? 普通は『360度』と答えるはずです。円が1周で360度というのは小学生でもわかることでしょう。

実は、このような角度の考え方を度数法と呼びます。算数や数学で頻繁に登場するこの度数法。皆さんも散々慣れしたしんできたことでしょう。
度数法は円の内側を360個に分割して、それぞれを1度とすることで角度を表す単位の考え方です。これは噂によると、一年が365日であることが由来だそうです。

そして、角度の考え方の別の方法として、弧度法というものがあります。
しかも、プログラミングで角度を扱う場合には、度数法ではなくこの弧度法が使われるのが一般的です。弧度法と言われてもなんのことだかサッパリ……という方も多いと思いますので、これはちょっと困った問題ですね。

弧度法では、度数法とは全く違う方法で角度を表します。
細かく説明すると非常に長くなってしまうので、ここで弧度法の数学的な詳細までは解説しませんが、簡単に説明します。


まずは簡単に数学のおさらいから。

円周の求め方は……
    半径×2(つまり直径)×3.14
でしたね。ということは、半円の円周はこれの半分なので……
    半径×2×3.14÷2
ですね。これはよく見ると2をかけて2で割っていますから、次のように書くことができますね。
    半径×3.14

内角が180度の半円では、
    弧の長さ = 半径×3.14
となるわけです。ここまではいいですね。

弧度法では、弧の長さと半径を用いて角度を表します。上の式に当てはめると、半径が大きくなるほど、弧の長さも大きくなりますよね。半径が10なら、弧の長さは31.4になります。半径が100なら弧の長さは314になります。でも比率は変わっていませんよね? 必ず1対3.14になっています。

弧度法とは、この性質を逆に利用して、弧の長さと半径の比率から角度を求めてしまおうという考え方なのです。そして、その公式は次のように表します。

    ラジアン = 度数×3.14÷180

ここで登場した『ラジアン』こそが、弧度法で角度を表すための単位になります。ちょっとわかりにくいかもしれませんが、ラジアンは度数とは全く違う単位です。こういうものだとなんとなくイメージしておけば、とりあえずはOKです。
そして、上で紹介した式こそが重要です。この式さえ覚えておけば、ラジアンがよくわかんなくても何とかなります。本当です。

任意の度数法の角度から、ラジアンに変換することさえできれば、後はどうにかなります。変換公式をよく覚えておきましょう。

度数⇒ラジアン の変換公式

    rad = ang * 3.14 / 180

    rad……ラジアン
    ang……度数



■計算してみる

度数からラジアンを求める方法はわかりました。ではどのような場面でラジアンが必要になるのでしょうか。

実は座標の計算などに欠かせない、サイン(sin)、コサイン(cos)を求めるために、このラジアンが必要になります。VBAには、サインを求める関数として、Sin関数が、コサインを求める関数として、Cos関数があります。他にもタンジェントを求めるTan関数とかもあります。

これらの数学系関数では、角度を扱う際にラジアンでの角度を引数に必要とします。

×……Sin(度数)
○……Sin(ラジアン)

例えば90度のサイン・コサインを得たい場合には、次のように記述します。

Sub Radian()

    Dim rad As Single 'ラジアン
    Dim ang As Long '度数
    Dim pi As Single 'π つまり円周率
    
    pi = 3.14159
    
    ang = 90
    rad = ang * pi / 180
    
    MsgBox "度数" & ang & "は、ラジアンでは" & rad & "です" & vbCr & _
            "Sin = " & Format(Sin(rad), "0.0000000000") & vbCr & _
            "Cos = " & Format(Cos(rad), "0.0000000000")

End Sub

このコードを実行すると、度数法の90度をもとに、ラジアンとサインとコサインを求めた結果がメッセージとして表示されます。ちなみに、コードの中で出てきている『vbCr』というのは改行を表す定数です。この定数を記述した部分では文字列が改行されます。

変数angに代入される数値を変更すると、それぞれに応じたラジアンとサイン・コサインを表示することができますので、色々変更してやってみるといいでしょう。
しかし、これがゲームの作成にどう役立つのか、それがいまいちわからない人もいるでしょう。

次回、ラジアンがその本当の実力を見せてくれます。とりあえず今回は、ラジアンを求めるための式だけでも、理解しておいてください。演出や計算に必ず役立ちますのでね。


■格言

度数法と弧度法の違いを理解する
ラジアンの求め方を理解する


どのようなプログラミング言語でも角度計算にラジアンは必須です。
がんばって覚えましょう。







メールフォーム

影斬に物申すという方はこちら

名前 :
メール:
件名 :
本文 :

可能な限り要望には応えますが、必ず返信や回答ができることを、保障するものではありません。
ご了承ください。

Chapters

コンテンツ一覧


■Chapter 一覧■
    全てのChapterの一覧です。
    直接アクセスしたい方はこちらをご利用下さい。

    Chapter.1 [ 知っておくべき心得 ]
    Chapter.2 [ Excelってなんだろう ]
    Chapter.3 [ Excelの基本画面 ]
    Chapter.4 [ VBAとは? ]
    Chapter.5 [ モジュールについて ]
    Chapter.6 [ 変数 ]
    Chapter.7 [ 変数の型と宣言 ]
    Chapter.8 [ プロシージャとスコープ ]
    Chapter.9 [ ゲームつくる様々な手法 ]
    Chapter.10 [ ユーザーフォーム ]
    Chapter.11 [ プロパティウィンドウ ]
    Chapter.12 [ 乱数 ]
    Chapter.13 [ 条件分岐 ]
    Chapter.14 [ ゲーム画面のデザイン ]
    Chapter.15 [ コード記述の基本作法 ]
    Chapter.16 [ じゃんけんゲーム:1 名前をつける ]
    Chapter.17 [ じゃんけんゲーム:2 フォームの起動 ]
    Chapter.18 [ じゃんけんゲーム:3 乱数の種 ]
    Chapter.19 [ じゃんけんゲーム:4 イベント ]
    Chapter.20 [ じゃんけんゲーム:5 引数 ]
    Chapter.21 [ じゃんけんゲーム:6 役判定 ]
    Chapter.22 [ じゃんけんゲーム:7 予測と制限 ]
    Chapter.23 [ Withステートメント ]
    Chapter.24 [ 画像を表示させる ]
    Chapter.25 [ 画像表示の発展形 ]
    Chapter.26 [ 繰り返し処理 For文 ]
    Chapter.27 [ 繰り返し処理 Do~Loop文 ]
    Chapter.28 [ Exitステートメント ]
    Chapter.29 [ フォーム上の位置情報 ]
    Chapter.30 [ API基礎知識 ]
    Chapter.31 [ API補足知識 ]
    Chapter.32 [ メインループを考える ]
    Chapter.33 [ 同期処理の概念 ]
    Chapter.34 [ 移動処理その1:画面設定と考え方 ]
    Chapter.35 [ 移動処理その2:DoEvents ]
    Chapter.36 [ 移動処理その3:キー入力判定API ]
    Chapter.37 [ 条件分岐のさらなる探求 Select Case ]
    Chapter.38 [ アニメーション ]
    Chapter.39 [ 配列変数 ]
    Chapter.40 [ ゲームの初期化 ]
    Chapter.41 [ シューティングゲーム1:ゲーム設計 ]
    Chapter.42 [ シューティングゲーム2:メインプロセス ]
    Chapter.43 [ シューティングゲーム3:構造体 ]
    Chapter.44 [ シューティングゲーム4:定数 ]
    Chapter.45 [ シューティングゲーム5:プレイヤーキャラクター ]
    Chapter.46 [ シューティングゲーム6:ショットを撃つ① ]
    Chapter.47 [ シューティングゲーム7:ショットを撃つ② ]
    Chapter.48 [ シューティングゲーム8:Mod演算子の活用 ]
    Chapter.49 [ シューティングゲーム9:敵キャラクター登場 ]
    Chapter.50 [ シューティングゲーム10:衝突判定 ]
    Chapter.51 [ シューティングゲーム11:衝突の実体 ]
    Chapter.52 [ シューティングゲーム12:敵の攻撃 ]
    Chapter.53 [ シューティングゲーム13:爆発エフェクト ]
    Chapter.54 [ シューティングゲーム14:残機数表示① ]
    Chapter.55 [ シューティングゲーム15:残機数表示② ]
    Chapter.56 [ シューティングゲーム16:スコアの表示 ]
    Chapter.57 [ シューティングゲーム17:タイトル画面 ]
    Chapter.58 [ シューティングゲーム18:ボスキャラクター ]
    Chapter.59 [ シューティングゲーム19:最後の仕上げへ ]
    Chapter.60 [ シューティングゲーム20:いよいよ完成STG ]
    Chapter.61 [ カードゲームで使えるめくり効果 ]
    Chapter.62 [ ラジアンと角度 ]
    Chapter.63 [ ラジアンの活用:円運動 ]
    Chapter.64 [ ラジアンの活用:任意の角度へ移動する ]
    Chapter.65 [ APIによるサウンド再生:基礎 ]
    Chapter.66 [ APIによるサウンド再生:MIDIと多重再生 ]
    Chapter.67 [ APIによるサウンド再生:MCIコマンドとループ再生 ]
    Chapter.68 [ Function プロシージャ ]
    Chapter.69 [ 値渡しと参照渡し ]
    Chapter.70 [ デバッグ1:イミディエイトウィンドウ ]
    Chapter.71 [ デバッグ2:ローカルウィンドウ ]
    Chapter.72 [ デバッグ3:コード実行の中断 ]
    Chapter.73 [ オブジェクトってなんだ ]
    Chapter.74 [ プロパティ・メソッド・イベント ]
    Chapter.75 [ オブジェクト変数 ]
    Chapter.76 [ オブジェクトとコレクション ]
    Chapter.77 [ 特殊な繰り返し:For Each ]
    Chapter.78 [ エラー処理 ]
    Chapter.79 [ On Error と GoTo文 ]
    Chapter.80 [ Resumeステートメント ]
    Chapter.81 [ バイトとビット ]
    Chapter.82 [ ウィンドウメッセージとイベント ]
    Chapter.83 [ 文字列の基礎 ]
    Chapter.84 [ 文字列操作① ]
    Chapter.85 [ 文字列操作② ]
    Chapter.86 [ タイピングゲーム1:仕様を決める ]
    Chapter.87 [ タイピングゲーム2:キー入力検知 ]
    Chapter.88 [ タイピングゲーム3:文字列照合 ]
    Chapter.89 [ タイピングゲーム4:判定関数 ]
    Chapter.90 [ タイピングゲーム5:ゲーム画面設計 ]
    Chapter.91 [ タイピングゲーム6:問題文のソート ]
    Chapter.92 [ タイピングゲーム7:動的配列 ]
    Chapter.93 [ タイピングゲーム8:キーダウンイベント ]
    Chapter.94 [ タイピングゲーム9:正打数の表示 ]
    Chapter.95 [ タイピングゲーム10:タイムの表示 ]
    Chapter.96 [ クリックゲーム1:イベントの種類 ]
    Chapter.97 [ クリックゲーム2:画面設計 ]
    Chapter.98 [ クリックゲーム3:クリック座標検知 ]
    Chapter.99 [ クリックゲーム4:キャラクター準備 ]
    Chapter.100 [ クリックゲーム5:キャラクターの配置 ]
    Chapter.101 [ クリックゲーム6:キャラクター移動とNot演算子 ]
    Chapter.102 [ クリックゲーム7:クリックのヒット判定 ]
    Chapter.103 [ クリックゲーム8:ヒットマークエフェクト ]
    Chapter.104 [ クリックゲーム9:サウンド処理の実装 ]
    Chapter.105 [ クリックゲーム10:マウスカーソルの変更 ]
    Chapter.106 [ ブロック崩しゲーム1:仕様と概要を決める ]
    Chapter.107 [ ブロック崩しゲーム2:基本概念の確認 ]
    Chapter.108 [ ブロック崩しゲーム3:ベクトルとは ]
    Chapter.109 [ ブロック崩しゲーム4:変数や定数の宣言 ]
    Chapter.110 [ ブロック崩しゲーム5:初期化処理の実装 ]
    Chapter.111 [ ブロック崩しゲーム6:ブロックの配置 ]
    Chapter.112 [ ブロック崩しゲーム7:根幹処理とバーの処理 ]
    Chapter.113 [ ブロック崩しゲーム8:線分と線分の交差を判定 ]
    Chapter.114 [ ブロック崩しゲーム9:線分同士の交点 ]
    Chapter.115 [ ブロック崩しゲーム10:ボールの処理 ]
    Chapter.116 [ ブロック崩しゲーム11:最終調整して完成へ ]
    Chapter.117 [ テキストファイル操作基礎 ]
    Chapter.118 [ テキストファイル操作:読み込み編 ]
    Chapter.119 [ テキストファイル操作:CSV読み込み編 ]
    Chapter.120 [ テキストファイル操作:様々な読込編 ]
    Chapter.121 [ テキストファイル操作:バイナリ編 ]
    Chapter.122 [ テキストファイル操作:暗号化編 ]
    Chapter.123 [ テキストファイル操作:復号化編 ]
    Chapter.124 [ クラスモジュールとは ]
    Chapter.125 [ クラスモジュール:メソッド編 ]
    Chapter.126 [ クラスモジュール:プロパティ編 ]
    Chapter.127 [ クラスモジュール:イベント拡張編 ]
    Chapter.128 [ クラスモジュール:イベント自作編 ]
    Chapter.129 [ APIによる描画処理1:ハンドル ]
    Chapter.130 [ APIによる描画処理2:デバイスコンテキスト ]
    Chapter.131 [ APIによる描画処理3:ペン オブジェクト ]
    Chapter.132 [ APIによる描画処理4:ブラシ オブジェクト ]
    Chapter.133 [ APIによる描画処理5:図形描画準備編 ]
    Chapter.134 [ APIによる描画処理6:図形描画実践編 ]
    Chapter.135 [ APIによる描画処理7:画像描画の仕組み編 ]
    Chapter.136 [ APIによる描画処理8:ビットブロック転送編 ]
    Chapter.137 [ APIによる描画処理9:ラスタオペレーション ]
    Chapter.138 [ APIによる描画処理10:マスク描画 概念編 ]
    Chapter.139 [ APIによる描画処理11:マスク描画 実践編 ]


    コードやVBAに関する質問などはサポート掲示板(別館)までお気軽にどうぞ。




fc2 seotool Excel VBA ゲーム プログラミング 講座

Counter

twitter


Shadow BBS - 影掲示板

VBA 関連書籍



上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。