:敵弾の発射、移動、衝突判定 他
演習37 敵弾の発射(修正)
・一定時間(200カウント)が経過する都度、出現中の敵機のいずれかが敵弾を発射するようにしよう ・最初の1発目は開始後75カウント経過してからにしよう ・よって、敵弾は同時に複数出現可能なので最大20個とする ・発射位置は敵機中央の15ドット上にある砲口とする ・敵弾の画像は以下を利用可能ebullet.gif (20x20) ・敵弾の移動と衝突判定は後回し
手順と仕様
① データメンバ:敵弾の画像を読み込んでImage型変数enemybiで扱う ② データメンバ:敵弾の出現間隔をint型定数enemybintで定義し値を75とする ③ データメンバ:敵弾の出現待ち時間をint型変数waitenemybで定義し初期値を200とする ④ データメンバ:敵弾の最大数をint型定数maxenemybで定義し値を20とする ⑤ データメンバ:敵弾の構造体Item型配列enemybaを敵弾の最大数の分定義 ⑥ 初期化処理:敵弾の出現待ち時間waitenemybを200に戻す ⑦ 描画処理:プレイ画面かゲームオーバー画面なら、全敵弾について、敵弾の配列enemybaの表示状態vが0以外なものを描画 ⑧ キー入力時処理:ゲームオーバー画面でEnterキーが押されていたら、全敵弾の表示状態vを0にする ⑨ タイマーイベント処理:敵弾出現待ち時間がゼロであれば: ・発射敵機を-1(未確定)にしておく ・全敵機の中で出現中でY座標が画面上半分にいるものがあれば、発射敵機とする ・発射敵機確定していたら、全敵弾の中で未出現のものがあれば、出現させ、X座標は発射敵機と同じに、Y座標は発射敵機-15とする また、敵弾出現待ち時間を敵弾の出現間隔にする ⑩ タイマーイベント処理:敵弾出現待ち時間がセットされていたらカウントダウンする
作成例
//演習37 敵弾の発射
using System; //汎用的に利用
using System.Windows.Forms; //フォームアプリケーションに必須
using System.Drawing; //Size、Image用
struct Item { //アイテムを表す構造体
public Image i; //画像
public int x; //中心X座標
public int y; //中心Y座標
public int hv; //左右方向の速度(左向きは負の数、右向きは正の数)
public int vv; //上下方向の速度(上向きは負の数、下向きは正の数)
public int v; //表示状態(0:非表示、1以上:表示)
}
class Program : Form { //Formクラスの派生クラス
[System.Runtime.InteropServices.DllImport("user32.dll")] //DLLインポート
private static extern short GetKeyState(int nVirtKey); //外部定義指定
int gamemode = 0; //モード(0:タイトル画面,1:プレイ画面,9:終了画面)
int score = 0; //スコア
int hiscore = 0; //ハイスコア
Image backi = Image.FromFile("backb.bmp"); //背景画像を読込む
Image playeri = Image.FromFile("player.gif"); //自機通常画像を読込む
Image playerl = Image.FromFile("playerl.gif"); //自機左寄画像を読込む
Image playerr = Image.FromFile("playerr.gif"); //自機右寄画像を読込む
Image bulleti = Image.FromFile("bullet.gif"); //自弾画像を読込む
Image bullet2i = Image.FromFile("bullet2.gif"); //自弾画像2を読込む
Image enemyi = Image.FromFile("enemy.gif"); //敵機画像を読込む
Image enemybi = Image.FromFile("ebullet.gif"); //【追加】敵弾画像を読込む
Pen pen1 = new Pen(Color.Red, 2); //赤色太さ2のペン
Brush brush1 = new SolidBrush(Color.FromArgb(63, 255, 0, 0)); //透明赤いブラシ
Font font1 = new Font("メイリオ", 20, FontStyle.Bold); //フォントを生成
Font fontt = new Font("メイリオ", 80, FontStyle.Bold); //フォントを生成
Font fontm = new Font("メイリオ", 25, FontStyle.Bold); //フォントを生成
Brush brushs = new SolidBrush(Color.Yellow); //黄色のブラシ
Timer timer = new Timer(); //タイマーの生成
int backy = 0; //1枚目の背景描画開始Y座標
Item player; //自機の構造体オブジェクト
const int maxpb = 10; //自弾の最大数
Item[] pba = new Item[maxpb]; //自弾の構造体オブジェクト配列
const int cold = 10; //自弾発射の冷却時間
int waitpb = 0; //自弾発射の待ち時間
const int maxenemy = 20; //敵機の最大数
Item[] enemya = new Item[maxenemy]; //敵機の構造体オブジェクト配列
int waitenemy = 0; //敵機の出現待ち時間
int enemyint = 50; //敵機の出現間隔
string hiscoremsg = ""; //ハイスコアメッセージ
Random rnd = new Random(); //乱数用のRandomクラスのインスタンスを生成
const int enemybint = 75; //【追加】敵弾の出現間隔
int waitenemyb = 200; //【追加】敵弾の出現待ち時間
const int maxenemyb = 20; //【追加】敵弾の最大数
Item[] enemyba = new Item[maxenemyb]; //【追加】敵弾の構造体配列
//各データをゲーム開始時の値にする
void initData() {
player.i = playeri; //自機の画像
player.x = 320; //自機の中心X座標
player.y = 410; //自機の中心Y座標
player.hv = 0; //自機の左右方向の速度
player.v = 1; //自機を表示
waitpb = 0; //自弾発射の待ち時間
waitpb = 0; //自弾発射の待ち時間
score = 0; //スコアクリア
waitenemyb = 200; //【追加】敵弾の出現待ち時間
}
//中央座標を用いる画像描画処理
private void DrawItem(PaintEventArgs e, Item it) {
int xx = it.x - it.i.Width / 2; //左上X座標を得る
int yy = it.y - it.i.Height / 2; //左上Y座標を得る
e.Graphics.DrawImage(it.i, xx, yy);
}
//楕円形の衝突判定(アイテムaとアイテムbが衝突しているかどうかを返す)
private bool isHit(Item a, Item b) {
int dest = (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y); //距離2乗
double ra = (a.i.Width < a.i.Height) ? a.i.Width / 2.0 : a.i.Height / 2.0; //内径a
double rb = (b.i.Width < b.i.Height) ? b.i.Width / 2.0 : b.i.Height / 2.0; //内径b
return dest < (ra + rb) * (ra + rb); //距離2乗と内径の和の2乗を比較
}
//描画処理のオーバライド
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e); //基本クラスの描画処理を呼ぶ
e.Graphics.DrawImage(backi, 0, backy); //背景画像を描画
e.Graphics.DrawImage(backi, 0, backy - backi.Height); //背景画像を描画
if (gamemode == 0) { //スタート画面?
e.Graphics.DrawString("GAME1", fontt, brushs, 100, 150); //タイトル表示
e.Graphics.DrawString("Hit Enter Key", fontm, brushs, 200, 300); //メッセージ表示
} else { //プレイ画面かゲームオーバー画面?
string s = String.Format("SCORE:{0:000,000}", score); //スコア文字列を作る
e.Graphics.DrawString(s, font1, brushs, 400, 10); //スコア表示
if (player.v != 0) { //自機が表示中?
switch (player.hv) { //自機の向きによって分岐
case 0: player.i = playeri; break; //通常画像にする
case -1: player.i = playerl; break; //左寄画像にする
case 1: player.i = playerr; break; //右寄画像にする
}
DrawItem(e, player); //自機を描画
}
foreach (var pb in pba) { //全自弾について繰返す
if (pb.v == 1) { //自弾がある?
DrawItem(e, pb); //自弾を描画
}
}
foreach (var enemy in enemya) { //全敵機について繰返す
if (enemy.v == 1) { //敵機がある?
DrawItem(e, enemy); //敵機を描画
}
}
foreach (var enemyb in enemyba) { //【以下追加】全敵弾について繰返す
if (enemyb.v == 1) { //敵弾がある?
DrawItem(e, enemyb); //敵弾を描画
}
}
}
if (gamemode == 9) { //ゲームオーバー画面?
e.Graphics.DrawString("GAME OVER", fontm, brushs, 210, 150); //メッセージ表示
e.Graphics.DrawString(hiscoremsg, fontm, brushs, 150, 200); //ハイスコアメッセージ表示
e.Graphics.DrawString("Hit Enter Key", fontm, brushs, 200, 300); //メッセージ表示
}
}
//キー入力時処理
void OnKeyDown(object o, KeyEventArgs e) {
if (e.KeyCode.ToString() == "Escape") { //Escキーが押されていたら
Close(); //フォーム終了
}
//タイトル画面でEnterキーが押されていたら
if (gamemode == 0 && e.KeyCode.ToString() == "Return") {
initData(); //各データをゲーム開始時の値にする
gamemode = 1; //プレイ動画に遷移
timer.Start(); //タイマー開始
}
//ゲームオーバー画面でEnterキーが押されていたら
else if (gamemode == 9 && e.KeyCode.ToString() == "Return") {
initData(); //各データをゲーム開始時の値にする
for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
enemya[i].v = 0; //無しにする
}
for (int i = 0; i < maxpb; i++) { //全自弾について繰返す
pba[i].v = 0; //無しにする
}
for (int i = 0; i < maxenemyb; i++) { //【以下追加】全敵弾について繰返す
enemyba[i].v = 0; //無しにする
}
gamemode = 1; //プレイ動画に遷移
}
Invalidate(); //画面再描画を依頼
}
//タイマーイベント処理
void Play(object o, EventArgs e) {
backy = (backy + 1) % backi.Height; //1枚目の背景描画開始Y座標を下げる
if (gamemode == 1) { //プレイ画面?
player.hv = 0; //自機の向きを無しにしておく
if (player.x > playeri.Width / 2 && GetKeyState((int)Keys.Left) < 0) { //範囲内で←キーが押されている?
player.x -= 10; //自機を左へ
player.hv = -1; //左向き
}
if (player.x < backi.Width - playeri.Width / 2 && GetKeyState((int)Keys.Right) < 0) { //範囲内で→キーが押されている?
player.x += 10; //自機を右へ
player.hv = 1; //右向き
}
}
for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
if (enemya[i].v != 0) { //敵機が存在?
if (player.v != 0 && isHit(enemya[i], player)) { //自機が存在し衝突?
player.v = 0; //自機を消す
gamemode = 9; //ゲームオーバーにする
if (score > hiscore) { //ハイスコア更新?
hiscore = score; //ハイスコア更新
hiscoremsg = "HiSCORE Update !"; //メッセージ
} else {
hiscoremsg = String.Format("HiSCORE is {0:000,000}", hiscore); //スコア文字列を作る
}
break; //抜ける
}
for (int j = 0; j < maxpb; j++) { //全自弾について繰返す
if (pba[j].v != 0 && isHit(enemya[i], pba[j])) { //自弾が存在し衝突?
enemya[i].v = 0; //この敵機を消す
pba[j].v = 0; //この自弾を消す
score += 10; //スコアアップ
break; //次の敵機へ進む
}
}
}
}
if (gamemode == 1) { //プレイ画面?
if (GetKeyState((int)Keys.Space) < 0) { //スペースキーが押されている?
if (waitpb <= 0) { //自弾発射待ち時間がゼロ?
for (int i = 0; i < maxpb; i++) { //全自弾について繰返す
if (pba[i].v == 0) { //自弾が非表示?
pba[i].v = 1; //表示にする
pba[i].i = bulleti; //画像
pba[i].x = player.x; //X座標は自機と同じ
pba[i].y = player.y - player.i.Height / 2 - pba[i].i.Height / 2; //Y座標は自機の直上
pba[i].vv = -5; //上移動速度
waitpb = cold; //自弾発射待ち時間をセット
break; //1発発射できればOK
}
}
}
} else { //スペースキーが押されていない?
waitpb = 0; //自弾発射待ち時間をゼロにして発射可能にする
}
}
if (waitenemy <= 0) { //敵機出現待ち時間がゼロ?
for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
if (enemya[i].v == 0) { //敵機が非表示?
enemya[i].v = 1; //表示にする
enemya[i].i = enemyi; //画像
enemya[i].x = enemyi.Width / 2 + rnd.Next(backi.Width - enemyi.Width);
enemya[i].y = -enemyi.Height; //Y座標は上端の直上
enemya[i].vv = 5; //下移動速度
waitenemy = enemyint; //敵機出現待ち時間をセット
break; //1機出現できればOK
}
}
}
if (waitenemyb <= 0) { //【以下追加】敵弾出現待ち時間がゼロ?
int enemyi = -1; //発射敵機を-1(未確定)にしておく
for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
if (enemya[i].v == 1 && enemya[i].y > 0 && enemya[i].y < backi.Height / 2) { //該当する敵機?
enemyi = i; //発射敵機が確定
break;
}
}
if (enemyi != -1) { //発射敵機が未確定ではない?
for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
if (enemyba[i].v == 0) { //敵弾が非表示?
enemyba[i].v = 1; //表示にする
enemyba[i].i = enemybi; //画像
enemyba[i].x = enemya[enemyi].x; //X座標は発射敵機と同じ
enemyba[i].y = enemya[enemyi].y - 15; //Y座標は発射敵機中央の上
waitenemyb = enemybint; //敵弾発射待ち時間をセット
break; //1弾出現できればOK
}
}
}
}
for (int i = 0; i < maxpb; i++) { //全自弾について繰返す
if (pba[i].v != 0) { //自弾が存在?
pba[i].i = (pba[i].i == bulleti) ? bullet2i : bulleti; //画像を交互変更
pba[i].y += pba[i].vv; //上へ移動
if (pba[i].y + pba[i].i.Height / 2 < 0) { //画面上端より上に出たら
pba[i].v = 0; //自弾を消す
}
}
}
for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
if (enemya[i].v != 0) { //敵機が存在?
enemya[i].y += enemya[i].vv; //下へ移動
if (enemya[i].y - enemya[i].i.Height / 2 > backi.Height) { //画面下端より下に出たら
enemya[i].v = 0; //敵機を消す
}
}
}
if (waitpb > 0) { //自弾発射待ち時間がセットされていたら
waitpb--; //カウントダウンする
}
if (waitenemy > 0) { //敵機出現待ち時間がセットされていたら
waitenemy--; //カウントダウンする
}
if (waitenemyb > 0) { //【以下追加】敵弾出現待ち時間がセットされていたら
waitenemyb--; //カウントダウンする
}
Invalidate(); //画面再描画を依頼
}
//コンストラクタ
Program() {
DoubleBuffered = true; //ダブルバッファリングを有効化
KeyDown += new KeyEventHandler(OnKeyDown); //キー入力イベント登録
timer.Tick += new EventHandler(Play); //タイマーイベント登録
timer.Interval = 10; //タイマーインターバル(ミリ秒)
}
public static void Main() {
Program f = new Program(); //自分のオブジェクトを生成
f.Size = new Size(660, 520); //フォームのサイズを設定
f.Text = "Game"; //フォーム名を設定
f.ControlBox = false; //コントロールボックスを非表示に
f.FormBorderStyle = FormBorderStyle.Fixed3D; //サイズ変更を抑止
Application.Run(f); //フォームを現出
}
}
テーマ32 2座標の角度から移動量を得る
・座標(x0,y0)への(x1,y1)からの角度はMath.Atan2関数にそれぞれの差を渡すことでラジアン値として得られる。 double rad = Math.Atan2(y0 - y1, x0 - x1); ・すると、物体の移動先の座標は、Cos(角度)、Sin(角度)、移動速度で得ることができる x座標移動量=Math.Cos(rad)×移動速度 y座標移動量=Math.Sin(rad)×移動速度
演習38 敵弾の移動・消去・自機との衝突判定
・敵弾は敵機の砲口からその時点の自機中央に向かって斜めに移動するようにしよう ・移動速度は敵機速度の1.5倍とする ・敵弾が画面端より外に出たら消そう ・全敵弾と自機との衝突判定を行い、衝突していたらゲームオーバーに遷移しよう ・衝突していたら、その敵弾も消そう
手順と仕様
① データメンバ:敵機移動速度をint型定数enemyvvで定義し値を5とする ② タイマーイベント処理:全敵機について繰返す中で自機が存在し衝突したら、ゲームオーバーのメソッドGameOver()を 呼ぶようにする これに伴い、繰返しを抜けるbreakを除く各処理をメソッドGameOver()の中に移動する ③ タイマーイベント処理:全敵弾について敵弾と自機が存在し衝突していたら、メソッドGameOver()を呼び、 その敵弾を消してから繰返しを抜ける処理を追加 ④ タイマーイベント処理:敵機を出現させるときに与える下移動速度を敵機移動速度定数enemyvvで与えるように変更 ⑤ タイマーイベント処理:敵弾を出現させるときに自機と敵弾の座標で角度を得て、X移動量とY移動量を設定する処理を追加 ⑥ タイマーイベント処理:全敵弾について、敵弾が存在したらXY座標にXY移動量を加算し、画面端より外に出たら敵弾を消す処理を追加
提出:演習37または38(未完成でもOK)
ebullet.gif (20x20)
・敵弾の移動と衝突判定は後回し