シューティング
ゲームは「重要な要素のたくさん詰まったシューティングゲーム」が作り初めに適しているということで、
サイドビューのシューティングを紹介したいと思います。
といっても「自分vs敵」の一機打ち、弾はお互いに連射不可、移動は上下のみで画面スクロールなし。
今回は初ゲームということでかなりショボめ。
プログラムの書く量がいきなり多すぎても大変なので必要最低限の、
自機と敵機の上下移動、弾の発射、当たり判定だけを行います。
ボスを含む、複数の敵が存在したり、敵が様々な動きをする、複数発射できる等の
もう少し本格的なシューティングゲームをお望みの方はシューティング2をご覧ください。
ただ、ココでやるようなものと違って単純なスクリプトではないので、はじめは当ページで基礎を学びましょう。
初めにこの章で使用する画像をダウンロードしてください。
#define SP 5 ; 自機の移動量
#define MX 50 ; 自機X座標
#define SX 80 ; 機体横幅
#define SY 60 ; 機体高さ
buffer 2
picload "shoot1.bmp"
gsel 0
title "[↑]上へ [↓]下へ [Esc]終了"
gmode 2
my = 100 ; 自機Y座標
*main
gosub *myaction
gosub *draw
await 30
goto *main
*myaction
stick key, 2 + 8 + 128
if key & 2 ! 0 & (my - SP >= 0) : my -= SP ; ↑キー
if key & 8 ! 0 & (my + SP + SY <= winy) : my += SP ; ↓キー
if key & 128 : end ; ESCキーで終了
return
*draw
redraw 0
color 50, 150, 250
boxf , , winx, winy
pos MX, my : gcopy 2, , SY * 2, SX, SY
redraw 1
return
とりあえず自機部分だけを対象にしてみました。
#defineは脱講座に入って初めて出てきましたね。
全部変数で宣言してもいいのですがこんなあまりにも単純すぎるものを変数で宣言するのもなんですので
メモリを確保しない定義(定数による置き換え)にしたわけです。
前半の初期化を終えた後の処理はメインループ、キャラクタの移動、描画の3つに分けました。
メインルーチンは各処理への遷移と、ウェイトによる制御を行っています。
移動ルーチンは冒頭でも書いたように機体の上下移動を監視すると共に、
タイトルバーにも表示されますが、Escキーでプログラム終了を行う処理となっています。
キー判定にstickを使用していますが、getkeyでも構いません。
stickは1度に複数キーの押下判定を行えますが、
getkeyのように文字キーなどのチェックは出来ません。
場所によって使い分けるようにしましょう。
尚、以前に掲示板でキャラクタの移動についてキーによって同時押しで問題があるとの情報をいただきました。
全てのキーボードで当てはまるわけではないのですが、多くのボードで当てはまると思われることなので、
以下の説明も考慮して開発していただければ、と思います。
掲示板で投稿していただいた情報(シューティングでの話)によりますと、
・右、下(上)、スペースキーでは動作しないキーボードあり(ノートパソコン(機種不明)で発生確認)
・スペースはOKだが「右、下、Tabキー」では動作しないキーボードあり(VAIO付属キーボードで発生確認)
・Ctrl、Tab、EnterはOKだが「左、上、スペース」では動作しないキーボードあり
・CtrlはOKだが「斜め4方向(2キー)、スペース(Tab若しくはEnterも)」では動作しないキーボードあり
・左右両方のShiftキーを押すと、何種類かのキー同時押しが動作しないキーボードあり
と、様々でどのキーボードでどのキーの組み合わせがダメなのかわかりません。
ゲームを作るときの対処法として、キーの設定を出来るようにするのが一番妥当ではないでしょうか?
マウスとの同時押しは可能なのでマウスも併用するなど工夫しましょう。
描画ルーチンは、背景の描画と期待の描画を行っています。
尚、機体の描画部「gcopy 2, , SY * 2, SX, SY」の「SY * 2」は、
画像ファイルを直接開いてもらえればわかりますが、
上から120ピクセルまでは傾いた機体の画像となっており今回は使いません。
同様に180ピクセルから下も機体が逆方向に傾いた画像は使いませんが、
最下部にある小さな横長楕円はレーザー弾として後で使います。
この弾の処理を入れる前に、目標物となる敵機を表示する処理を先にいれておきましょう。
#define SP 5
#define EX 500 ; 敵機X座標
#define SX 80
#define SY 60
buffer 2
picload "shoot1.bmp"
pos SX * 2 - 1 : gzoom -SX, SY * 5, 2, SX, , SX, SY * 5 ; 反転表示
gsel 0
title "脱講座3章サンプル"
gmode 2
ey = 350 ; 敵機Y座標
randomize
*main
gosub *enmaction
gosub *draw
await 30
goto *main
*enmaction
if eact {
eact--
} else {
rnd eact.0, 10 ; 行動数
rnd eact.1, 2 ; 移動方向(0:上 1:下)
}
if eact.1 = 0 & (ey - SP >= 0) : ey -= SP
if eact.1 = 1 & (ey + SP + SY <= winy) : ey += SP
return
*draw
redraw 0
color 50, 150, 250
boxf , , winx, winy
pos EX, ey : gcopy 2, SX, SY * 2, SX, SY
redraw 1
return
自機の処理とほとんど同じで、やることはメインループから敵機の移動と描画ルーチンへ遷移する処理。
自機の処理と異なっているのは、右向きの機体画像を自機と向き合うために左向きに反転させているのと、
ユーザーが操作する自機の移動を自動的に動かすように変更している点だけですね。
画像の反転は、gzoom命令で変倍サイズまたはコピーサイズをマイナスにすることで反転できます。
便利な機能なので知らなかった人は覚えておきましょう。
自動的に機体を移動させるやり方は単純なものから複雑なものまで色々ありますが、
何の制限も加えなければ予期せぬ問題に発展することもありますので、
移動範囲や移動速度等はユーザーが制御できる自機と同じように制限を設けた上で自動化させてください。
ココでは、画面をはみ出ない範囲で上下にフラフラと動かすようにしています。
尚、自機と同様に移動ターン毎に上下のいずれかに動かすようなやり方だと、
ほぼ同じ位置を小刻みに震える状態を繰り返すだけで面白くないので、
移動方向と共にターン数を設定し、指定ターン数だけ上または下に動くというやり方を採用しました。
ソレでは、自機と敵機を同時に描画した上で弾を撃つ処理を入れてみましょう。
#define SP 5
#define TSP 20 ; 弾の移動量
#define MX 50
#define EX 500
#define SX 80
#define SY 60
#define TX 16 ; 弾幅
#define TY 3 ; 弾高
buffer 2
picload "shoot1.bmp"
pos SX * 2 - 1 : gzoom -SX, SY * 5, 2, SX, , SX, SY * 5
gsel 0
title "脱講座3章サンプル"
gmode 2
my = 100
ey = 350
randomize
*main
gosub *myaction
gosub *enmaction
gosub *shoot
gosub *draw
await 30
goto *main
*myaction
stick key, 2 + 8 + 128
if key & 2 ! 0 & (my - SP >= 0) : my -= SP
if key & 8 ! 0 & (my + SP + SY <= winy) : my += SP
if key & 16 ! 0 & (tama = 0) : tama = 1, MX + SX, my + 30 ; 弾発射
if key & 128 : end
return
*enmaction
if eact {
eact--
} else {
rnd eact.0, 10
rnd eact.1, 3 ; 行動(0:上移動 1:下移動 2:弾発射)
}
if eact.1 = 0 & (ey - SP >= 0) : ey -= SP
if eact.1 = 1 & (ey + SP + SY <= winy) : ey += SP
if eact.1 = 2 : eact = 0 : if tama.3 = 0 : tama.3 = 1, EX - TX, ey + 30 ; 弾発射
return
*shoot
if tama.0 ! 0 & (tama.1 < winx) : tama.1 += TSP : else : tama.0 = 0 ; 自機弾が画面外に出たら弾消滅
if tama.3 ! 0 & (tama.4 - TX > 0) : tama.4 -= TSP : else : tama.3 = 0 ; 敵機弾が画面外に出たら弾消滅
return
*draw
redraw 0
color 50, 150, 250
boxf , , winx, winy
pos MX, my : gcopy 2, , SY * 2, SX, SY
pos EX, ey : gcopy 2, SX, SY * 2, SX, SY
if tama.0 : pos tama.1, tama.2 : gcopy 2, , SY * 5, TX, TY
if tama.3 : pos tama.4, tama.5 : gcopy 2, TX, SY * 5, TX, TY
redraw 1
return
こうすることでスペースキーで発射できるようになりました。
配列変数tamaが弾を管理しており、要素0が現在画面上に弾が存在するか否かを示すフラグ、
要素1が現在の弾X座標、要素2が弾Y座標を保持しています。
画面外に出たらtamaの要素0に0をセットするようにすることで、再度打てるようになります。
また、tamaが0ではない=弾が画面上にあるならば*drawラベル内で弾を描画しています。
弾情報を保持している変数が1つしかないので弾は1発しか発射できません。
コレを複数弾同時に扱えるようにするには、発射できる数分の保持領域(変数)が必要となります。
シューティング2で行いますがここでは行いません。
弾を消滅させる順番は必ずしも先頭からの順番ではなく、
障害物に当たったりする等様々な条件により変わってきますので、プログラムはもっと難しくなるからです。
敵機の移動方向を制御していた変数eactを拡張し、値が2なら敵機も弾を発射できるようにしました。
tamaの要素3が自機同様に現在画面上に弾が存在するか否かを示すフラグ、
要素4が現在の弾X座標、要素5が弾Y座標の保持変数としています。
行動パターンとして既に弾を発射して画面上に存在する時、発射ターンが複数回とっても意味がないので、
弾を発射する時はターン数を0(複数回繰り返さないよう)にしています。
さて、コレで互いに弾を発射しあえるようになったわけですが、肝心の当たり判定がありません。
「物体Aが物体Bに当たる」というのは、Aの右下座標がBの左上座標よりも右下に来て、かつ、
Aの左上座標がBの右下座標よりも左上にあるときに衝突していると言えるので、
コレを機体と弾に置き換えて衝突判定を入れるには*shootラベルを以下のように変えてみてください。
*shoot
if tama.0 {
if tama.1 + TX > EX & (EX + SX > tama.1) & (tama.2 + TY - 15 > ey) & (ey + SY - TY - 15 > tama.2) {
dialog "自弾が敵機に衝突", , "You win !"
end
}
if tama.1 < winx : tama.1 += TSP : else : tama.0 = 0
}
if tama.3 {
if tama.4 - TX > MX & (MX + SX > tama.4) & (tama.5 + TY - 15 > my) & (my + SY - TY - 15 > tama.5) {
dialog "敵弾が自機に衝突", , "You lose !"
end
}
if tama.4 - TX > 0 : tama.4 -= TSP : else : tama.3 = 0
}
return
こんな感じでOK。
条件式に「●● - 15」と入ってるのは、機体画像(80×60ピクセル)のうち空白部分を除くためです。
衝突判定を甘くするなら値を大きく、キツくするなら値を小さくしてください。
それではこの弾の衝突時に爆発アニメを付け加えてみましょう。
爆発画像はコチラからダウンロードしてください。
以下のサンプルでは配列変数tamaの発射フラグ(要素0と要素3)を拡張して、
「0:未発射」「1:発射済」「2〜10:爆破アニメ」「11:消滅(0に戻す)」としました。
#define SP 5
#define TSP 20
#define MX 50
#define EX 500
#define SX 80
#define SY 60
#define TX 16
#define TY 3
#define BS 32 ; 爆発サイズ
buffer 2
picload "shoot1.bmp"
pos SX * 2 - 1 : gzoom -SX, SY * 5, 2, SX, , SX, SY * 5
buffer 3
picload "bomb.bmp"
gsel 0
title "脱講座3章サンプル"
gmode 2
my = 100
ey = 350
randomize
*main
gosub *myaction
gosub *enmaction
gosub *shoot
gosub *draw
await 30
goto *main
*myaction
stick key, 2 + 8 + 128
if key & 2 ! 0 & (my - SP >= 0) : my -= SP
if key & 8 ! 0 & (my + SP + SY <= winy) : my += SP
if key & 16 ! 0 & (tama = 0) : tama = 1, MX + SX, my + 30
if key & 128 : end
return
*enmaction
if eact {
eact--
} else {
rnd eact.0, 10
rnd eact.1, 3
}
if eact.1 = 0 & (ey - SP >= 0) : ey -= SP
if eact.1 = 1 & (ey + SP + SY <= winy) : ey += SP
if eact.1 = 2 : eact = 0 : if tama.3 = 0 : tama.3 = 1, EX - TX, ey + 30
return
*shoot
if tama.0 {
if tama = 1 {
if tama.1 + TX > EX & (EX + SX > tama.1) & (tama.2 + TY - 15 > ey) & (ey + SY - TY - 15 > tama.2) : tama.0++
if tama.1 < winx : tama.1 += TSP : else : tama.0 = 0
} else {
tama++ ; 爆発アニメを1コマ進める
if tama.0 > 10 : tama.0 = 0 ; 爆発アニメ終了して弾消滅
}
}
if tama.3 {
if tama.3 = 1 {
if tama.4 - TX > MX & (MX + SX > tama.4) & (tama.5 + TY - 15 > my) & (my + SY - TY - 15 > tama.5) : tama.3++
if tama.4 - TX > 0 : tama.4 -= TSP : else : tama.3 = 0
} else {
tama.3++ ; 爆発アニメを1コマ進める
if tama.3 > 10 : tama.3 = 0 ; 爆発アニメ終了して弾消滅
}
}
return
*draw
redraw 0
color 50, 150, 250
boxf , , winx, winy
pos MX, my : gcopy 2, , SY * 2, SX, SY
pos EX, ey : gcopy 2, SX, SY * 2, SX, SY
if tama {
if tama.0 = 1 {
pos tama.1, tama.2 : gcopy 2, , SY * 5, TX, TY
} else {
pos BS / -2 + tama.1, BS / -2 + tama.2 : gcopy 3, tama.0 - 1 * BS, , BS, BS
}
}
if tama.3 {
if tama.3 = 1 {
pos tama.4, tama.5 : gcopy 2, TX, SY * 5, TX, TY
} else {
pos BS / -2 + tama.4, BS / -2 + tama.5 : gcopy 3, tama.3 - 1 * BS, , BS, BS
}
}
redraw 1
return
この章はココまでとします。
コチラのスクリプトは上記スクリプトに被弾数を互いにチェックして
「先に3発当たった方が負け」というルールを設けてみました。
ショボいですが立派なシューティングゲームとして成立しましたね。