スクリプト言語の HSP については、公式ホームページの「HSPTV!」をどうぞ。
なお、このページはシューティング・ゲームのミニ講座シリーズの1つです。
今回は第9回「完成ソース」について説明します。(戻る)
スタート画面
スタート画面の役割は、次の項目を選択するのが一般的でしょう。
- ゲームのタイトル表示
- ゲームの開始
- ゲームの設定
- ゲームの説明
- ゲームの終了
今までゲームを起動すると直ぐにゲームが開始されました。
心の準備もなしにゲームがスタートするのは、ちょっとビックリしますね。
そこで、簡単ながらゲームのスタート画面を付けて完成させましょう。
なお、今回がシューティング・ゲームのミニ講座シリーズの最終回になります。
画面デザイン
今回は簡単なスタート画面を目指してます。
そこで、ゲーム・タイトル、ゲーム開始、ゲーム終了の3要素を付けたいと思います。
さらに[SPC]キーが押されると「ゲームの開始」、[ESC]キーが押されると「ゲームの終了」の処理も行います。
このスタート画面は、次のようになると思います。
- スタート画面を表示する(ゲーム・タイトル、ゲーム開始、ゲーム終了)
- キー入力のループ待ちで(a)~(c)を行う
- stick 命令を実行
- [SPC]キーが押されてたら break 命令を実行して、ループを抜ける
- [ESC]キーが押されてたら end 命令を実行して、ゲーム終了
- 繰り返しを抜ける
- ゲームのメインに進む
上記のフローチャートをもとにスタート画面を作成すると次のようになると思います。
*Start screen 0,600,400,SCREEN_FIXEDSIZE title "シューティング・ゲームのミニ講座" redraw 0 color:boxf color $00,$FF,$FF:font "HG明朝E",50:pos (600-50*11)/2,100:mes "シューティング・ゲーム" color $FF,$FF,$00:font "HG明朝E",30:pos (600-30* 6)/2,180:mes "~ミニ講座~" color $FF,$FF,$FF:font "HG明朝E",16:pos (600- 8*15)/2,250:mes "[SPC]キーで開始" color $FF,$FF,$FF:font "HG明朝E",16:pos (600- 8*15)/2,282:mes "[ESC]キーで終了" redraw 1 repeat stick key if(key & $10):break if(key & $80):end await 20 loop *Main
どうですか?
すごくシンプルなスタート画面ですが、よりゲームらしくなりましたね。
上記の (key & $10) が[SPC]キーが押されてるかのチェックです。
押されていたら break 命令を実行して繰り返しを抜けます。
その後は *Main ラベルに進んでゲームが開始されます。
上記の (key & $20) は[ESC]キーが押されてるかのチェックです。
押されていたら end 命令を実行してウインドウが閉じます。
つまり、ゲームの終了を選択したことになります。
完成ソース
それでは第9回目の「完成ソース」を紹介します。
//------------------------------------------------------------------------------ // シューティング・ゲームのミニ講座 for HSP(Ver.3.3.2) //============================================================================== // 第9回「完成ソース」by 科学太郎 //------------------------------------------------------------------------------ //-------------------------------------- // メイン部 //-------------------------------------- *Init ;スコアの管理 dim Score ;現在スコア dim HiScore ;最高スコア ;自機の管理 dim x ;自機の横軸 dim y ;自機の縦軸 dim fight ;自機の残機数 dim blast ;自機の爆発カウンタ ;弾丸の管理 dim tamaF,10 ;有無フラグ dim tamaX,10 ;弾丸の横軸 dim tamaY,10 ;弾丸の縦軸 ;敵機の管理 dim enemyF,20 ;有無フラグ dim enemyX,20 ;敵機の横軸 dim enemyY,20 ;敵機の縦軸 dim enemyZ,20 ;敵機の爆発カウンタ ;流星の管理 dim starF,50 ;有無フラグ dim starX,50 ;流星の横軸 dim starY,50 ;流星の縦軸 ;スコアの初期化 Score=0 HiScore=9900 ;自機の初期化 x=(600-50)/2 y=(400-50)-16 fight=3 blast=0 *Start screen 0,600,400,SCREEN_FIXEDSIZE title "シューティング・ゲームのミニ講座" redraw 0 color:boxf color $00,$FF,$FF:font "HG明朝E",50:pos (600-50*11)/2,100:mes "シューティング・ゲーム" color $FF,$FF,$00:font "HG明朝E",30:pos (600-30* 6)/2,180:mes "~ミニ講座~" color $FF,$FF,$FF:font "HG明朝E",16:pos (600- 8*15)/2,250:mes "[SPC]キーで開始" color $FF,$FF,$FF:font "HG明朝E",16:pos (600- 8*15)/2,282:mes "[ESC]キーで終了" redraw 1 repeat stick key if(key & $10):break if(key & $80):end await 20 loop *Main font MSGOTHIC,50 randomize repeat redraw 0 stick key,%11111 gosub *EnemyBirth gosub *StarBirth gosub *StarDraw gosub *FightDraw gosub *EnemyDraw gosub *TamaDraw gosub *TelopDraw redraw 1 await (1000/60) loop stop //-------------------------------------- // 自機の描画 //-------------------------------------- *FightDraw if(blast==0){ if(key&1):x-=5:if(x<0):x=0 if(key&2):y-=5:if(y<0):y=0 if(key&4):x+=5:if(x>550):x=550 if(key&8):y+=5:if(y>350):y=350 if(key&16):gosub *TamaBirth color $00,$FF,$00:pos x,y:mes "山" } else:if(blast>1){ blast-- color $FF,$00,$00:pos x,y if(blast\10<5):mes "※":else:mes "*" } else:if(fight){ fight-- blast=0 x=(600-50)/2 y=(400-50)-16 } else{ dialog "もう一度、ゲームを行いますか?",3,"ゲームオーバー" if(stat==7):end ;スコアの初期化 Score=0 ;自機の初期化 x=(600-50)/2 y=(400-50)-16 fight=3 blast=0 ;配列の初期化 dim starF,50 dim tamaF,10 dim enemyF,20 } return //-------------------------------------- // 流星の発生 //-------------------------------------- *StarBirth if(starCycle):starCycle--:return foreach starF if(starF(cnt)==0){ starF(cnt)=1 starX(cnt)=rnd(600) starY(cnt)=0 break } loop starCycle=3 return //-------------------------------------- // 流星の描画 //-------------------------------------- *StarDraw color $00,$00,$00:boxf foreach starF if starF(cnt){ starY(cnt)+=2:if(starY(cnt)>=400):starF(cnt)=0:continue color $FF,$FF,$00 pset starX(cnt),starY(cnt) } loop return //-------------------------------------- // 弾丸の発生 //-------------------------------------- *TamaBirth if(tamaTrigg):tamaTrigg--:return foreach tamaF if(tamaF(cnt)==0){ tamaF(cnt)=1 tamaX(cnt)=x tamaY(cnt)=y break } loop tamaTrigg=8 return //-------------------------------------- // 弾丸の描画 //-------------------------------------- *TamaDraw foreach tamaF if tamaF(cnt){ tamaY(cnt)-=8:if(tamaY(cnt)<-50):tamaF(cnt)=0:continue pos tamaX(cnt),tamaY(cnt) color $FF,$FF,$00:mes ":" } loop return //-------------------------------------- // 敵機の発生 //-------------------------------------- *EnemyBirth if(enemyCycle):enemyCycle--:return foreach enemyF if(enemyF(cnt)==0){ enemyF(cnt)=1 enemyX(cnt)=rnd(600/50)*50 enemyY(cnt)=-50 enemyZ(cnt)=0 break } loop enemyCycle=30 return //-------------------------------------- // 敵機の描画 //-------------------------------------- *EnemyDraw foreach enemyF if enemyF(cnt){ if(enemyZ(cnt)==0){ enemyY(cnt)+=3:if(enemyY(cnt)>400) :enemyF(cnt)=0:continue if FightCrash(enemyX(cnt),enemyY(cnt)) :enemyZ(cnt)=60:ScoreCalc 100:continue if TamaCrash(enemyX(cnt),enemyY(cnt)) :enemyZ(cnt)=60:ScoreCalc 100:continue pos enemyX(cnt),enemyY(cnt) color $00,$FF,$FF:mes "Ж" } else:if(enemyZ(cnt)>1){ enemyZ(cnt)-- color $FF,$00,$00:pos enemyX(cnt),enemyY(cnt) if(enemyZ(cnt)\10<5):mes "※":else:mes "*" } else{ enemyF(cnt)=0 } } loop return //-------------------------------------- // テロップの描画 //-------------------------------------- *TelopDraw msg="" repeat fight msg+="山" loop y(1)=0:x(1)=(20) y(2)=0:x(2)=(600-20)-(16*11) x(3)=0:y(3)=(400-20) font MSGOTHIC,20,1 color $FF,$FF,$00:pos x(1),y(1):mes strf("Score:%08d",Score) color $FF,$FF,$00:pos x(2),y(2):mes strf("HiScore:%08d",HiScore) color $00,$FF,$00:pos x(3),y(3):mes msg font MSGOTHIC,50 return //-------------------------------------- // スコアの加算 //-------------------------------------- #deffunc ScoreCalc int _score_ Score+=_score_ if(Score>HiScore):HiScore=Score return //-------------------------------------- // 自機の衝突判定 //-------------------------------------- #defcfunc FightCrash int _x_,int _y_ if(blast==0)and(abs(x-_x_)<50)and(abs(y-_y_)<50){ blast=60 return 1 } return 0 //-------------------------------------- // 自機弾の衝突判定 //-------------------------------------- #defcfunc TamaCrash int _x_,int _y_ n=0 foreach tamaF if tamaF(cnt){ if(abs(tamaX(cnt)-_x_)<50)and(abs(tamaY(cnt)-_y_)<50){ tamaF(cnt)=0 n=1 break } } loop return n //------------------------------------------------------------------------------ // End of lesson-9.hsp //------------------------------------------------------------------------------
どうですか?
全部で 257 行になりました。
コメント行が多いので実際には 202 行ですが…。
変数・配列の一覧
以下にミニ講座で使用してる変数と配列の一覧を表示します。
変数が 12 個、配列が 10 個の合計で 22 個です。
簡単なシューティング・ゲームですが 22 個も変数を使ってますね。
これは、多い方なのでしょうか、それとも少ない方なのでしょうか。
2次元配列にすれば、配列の個数を減らせるという噂もあります。
この方法は、また別の機会にでも紹介しましょう。
名前 | 意味 |
---|---|
スコアの管理 | |
dim Score | 現在スコア |
dim HiScore | 最高スコア |
自機の管理 | |
dim x | 自機の横軸 |
dim y | 自機の縦軸 |
dim fight | 自機の残機数 |
dim blast | 自機の爆発カウンタ |
弾丸の管理 | |
dim tamaF,10 | 有無フラグ |
dim tamaX,10 | 弾丸の横軸 |
dim tamaY,10 | 弾丸の縦軸 |
dim tamaTrigg | 弾丸の発生間隔 |
敵機の管理 | |
dim enemyF,20 | 有無フラグ |
dim enemyX,20 | 敵機の横軸 |
dim enemyY,20 | 敵機の縦軸 |
dim enemyZ,20 | 敵機の爆発カウンタ |
dim enemyCycle | 敵機の発生間隔 |
流星の管理 | |
dim starF,50 | 有無フラグ |
dim starX,50 | 流星の横軸 |
dim starY,50 | 流星の縦軸 |
dim starCycle | 流星の発生間隔 |
作業用の変数 | |
dim key | キー入力 |
dim n | 整数型の一時変数 |
sdim msg | 文字型の一時変数 |
サブルーチン(命令,関数)の一覧
続いてミニ講座で使用してるサブルーチン(命令,関数)の一覧を表示します。
サブルーチンが 11 個、ユーザ定義命令が 1 個、ユーザ定義関数が 2 個の合計 14 個です。
簡単なシューティング・ゲームですが 14 個もサブルーチンなどを使ってます。
これは、多い方なのでしょうか、それとも少ない方なのでしょうか。
今回、移動と描画を一体化してるので、サブルーチンの数は少ない方だと思います。
なお、サブルーチンの個数は、分かりやすい程度に分割して決めます。
多くても少なくてもシューティング・ゲームのソース・コードを管理しづらくなります。
また、ミニ講座なので1つの巨大なファイルにしました。
本格的なシューティング・ゲーム(パワーアップ型)にする場合は、複数のファイルに分割して1本のゲームを作成します。
この方法は、また別の機会にでも紹介しましょう。
名前 | 処理 |
---|---|
メイン部 | |
*Init | 配列の確保、スコアの初期化、自機の初期化 |
*Start | スタート画面の処理 |
*Main | ゲームループの処理 |
自機の処理 | |
*FightDraw | 自機の移動、描画、爆発、ゲームオーバー、コンティニュー処理 |
FightCrash(x,y) | 自機の衝突判定 |
流星の処理 | |
*StarBirth | 流星の発生 |
*StarDraw | 流星の移動、描画 |
弾丸の処理 | |
*TamaBirth | 弾丸の発生 |
*TamaDraw | 弾丸の移動、描画 |
TamaCrash(x,y) | 弾丸の衝突判定 |
敵機の処理 | |
*EnemyBirth | 敵機の発生 |
*EnemyDraw | 敵機の移動、描画、爆発、当たり判定 |
その他 | |
*TelopDraw | スコア、最高スコア、残機の描画 |
ScoreCalc score | スコアの加算 |
上記のサブルーチン(命令,関数)の見分け方は、次のようになります。
- 「*」マークがあるのがサブルーチン
- 引数が付いてるのがユーザ定義命令(ScoreCalc)
- カッコがあるのがユーザ定義関数(FightCrash、TamaCrash)
おわりに
今回は、シューティング・ゲームのミニ講座なので、敵機弾、アイテムは登場しませんでした。
また、文字ベースで自機、弾丸、敵機、爆風などを描いてるため見栄えは良くありませんでしたね。
しかし、シューティング・ゲームの全体的な仕組みは理解できたと思います。
最初から見た目のデザイン、ゲームの操作性、ボス・キャラ、ステージ・クリアを考えると「頭」がパンクすると思います。
そこでワザと文字ベースで自機、弾丸、敵機、爆風だけを登場させました。
今後の予定
パワーアップ型の「シューティング・ゲームの基礎講座」を書こうと思ってます。
こちらは、電子書籍(有料)とブログ(無料)の両方でネット上に紹介するつもりです。
ただし、内容は異なり、ブログ版では、配列を使った方法で、約50回ほどで紹介する予定です。
そして、電子書籍では、ファイル分割、モジュール利用、モジュール型変数を使った方法で紹介する予定です。
あと電子書籍は、1冊200円で前編・中編・後編の三部作を考えてます。
それでは、これでシューティング・ゲームのミニ講座は終わります。
ありがとうございます。