演習44 敵弾と爆散円の衝突判定と誘爆
・敵弾が爆散円に入ったら10点加算して爆散するようにしよう ・スコアの上昇に合わせて、敵弾発射間隔が短くなるようにしよう(15まで) ・ゲームオーバー後は、スコア加算をしないようにしよう
手順と仕様
① 2.データメンバ:敵弾の出現間隔を定数から変数に変更 ② 3.初期化処理:敵弾の出現間隔を75に戻す処理を追加 ③ 9.3.敵機と自機・自弾の衝突処理:スコアアップはプレイ画面中のみにする ④ 9.4.敵弾と自機・自弾、爆散中敵弾と敵機の衝突処理:同上 ⑤ 9.4.敵弾と自機・自弾、爆散中敵弾と敵機の衝突処理: 敵弾と爆散円の中央距離が(爆散円の半径+敵弾幅÷2)未満なら、敵弾が爆散円に入ったとして、 その敵弾を爆散にし、プレイ画面中ならスコア10点加算 ⑥ 9.11.カウントダウンと画面再描画:敵弾発射間隔が15超なら、スコア÷100を差し引く
作成例
//演習44 敵弾と爆散円の衝突判定と誘爆
using System; //汎用的に利用
using System.Windows.Forms; //フォームアプリケーションに必須
using System.Drawing; //Size、Image用
//《0.アイテムを表す構造体
struct Item {
public Image i; //画像
public int x; //中心X座標
public int y; //中心Y座標
public int hv; //左右方向の速度(左向きは負の数、右向きは正の数)
public int vv; //上下方向の速度(上向きは負の数、下向きは正の数)
public int v; //表示状態(0:非表示、1~100:表示、101~200:爆散中)
}
class Program : Form { //Formクラスの派生クラス
//《1.DLLインポートと外部定義指定
[System.Runtime.InteropServices.DllImport("user32.dll")] //DLLインポート
private static extern short GetKeyState(int nVirtKey); //外部定義指定
//《2.データメンバ
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"); //敵弾画像を読込む
Image meteoi = Image.FromFile("meteo3.gif"); //破壊画像を読込む
static Image burni = Image.FromFile("burn.gif"); //炎画像を読込む
Pen burnp = new Pen(new TextureBrush(burni), 15); //炎のテクスチャペン
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クラスのインスタンスを生成
int enemybint = 75; //【変数】敵弾の出現間隔
int waitenemyb = 200; //敵弾の出現待ち時間
const int maxenemyb = 20; //敵弾の最大数
Item[] enemyba = new Item[maxenemyb]; //敵弾の構造体配列
const int enemyvv = 5; //敵機移動速度
//《3.初期化処理:各データをゲーム開始時の値にする
void initData() {
player.i = playeri; //自機の画像
player.x = 320; //自機の中心X座標
player.y = 410; //自機の中心Y座標
player.hv = 0; //自機の左右方向の速度
player.v = 100; //自機を100%表示
waitpb = 0; //自弾発射の待ち時間
waitpb = 0; //自弾発射の待ち時間
score = 0; //スコアクリア
waitenemyb = 200; //敵弾の出現待ち時間
enemyint = 50; //敵機の出現間隔
enemybint = 75; //【追加】敵弾の出現間隔
}
//《4.中央座標を用いる画像描画処理
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);
}
//《5.中央座標を用いる画像描画処理(倍率指定)
private void DrawItem(PaintEventArgs e, Item it, int rate) {
if (rate == 100) { //等倍?
DrawItem(e, it); //通常描画を呼ぶ
} else {
int xx = it.x - it.i.Width * rate / 100 / 2; //左上X座標を得る
int yy = it.y - it.i.Height * rate / 100 / 2; //左上Y座標を得る
e.Graphics.DrawImage(it.i, xx, yy, it.i.Width * rate / 100, it.i.Height * rate / 100);
}
}
//《6.楕円形の衝突判定(アイテム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乗を比較
}
//《7.描画処理(オーバライド)
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, player.v); //自機を倍率描画
}
foreach (var pb in pba) { //全自弾について繰返す
if (pb.v == 1) { //自弾がある?
DrawItem(e, pb); //自弾を描画
}
}
foreach (var enemy in enemya) { //全敵機について繰返す
if (enemy.v != 0) { //敵機がある?
DrawItem(e, enemy, enemy.v); //敵機を倍率描画
}
}
foreach (var enemyb in enemyba) { //全敵弾について繰返す
if (enemyb.v == 1) { //敵弾がある?
DrawItem(e, enemyb); //敵弾を描画
} else if (enemyb.v >= 101) { //爆散中?
int rr = (enemyb.v - 100) * 2; //直径を計算
e.Graphics.DrawEllipse(burnp, enemyb.x - (enemyb.v - 100),
enemyb.y - (enemyb.v - 100), rr, rr); //爆散円描画
}
}
}
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); //メッセージ表示
}
}
//《8.キー入力時処理
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(); //画面再描画を依頼
}
//《9.タイマーイベント処理
void Play(object o, EventArgs e) {
//《9.1.背景画像スクロール
backy = (backy + 1) % backi.Height; //1枚目の背景描画開始Y座標を下げる
//《9.2.自機の向きのセット
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; //右向き
}
}
//《9.3.敵機と自機・自弾の衝突処理
for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
if (enemya[i].v == 100) { //敵機が100%存在?
if (player.v == 100 && isHit(enemya[i], player)) { //自機が100%存在し衝突?
GameOver(); //ゲームオーバー
enemya[i].v = 99; //敵機落下開始
enemya[i].i = meteoi; //破壊画像に
break; //抜ける
}
for (int j = 0; j < maxpb; j++) { //全自弾について繰返す
if (pba[j].v != 0 && isHit(enemya[i], pba[j])) { //自弾が存在し衝突?
enemya[i].v = 99; //敵機落下開始
enemya[i].i = meteoi; //破壊画像に
pba[j].v = 0; //この自弾を消す
if (gamemode == 1) { //【追加】プレイ画面なら
score += 10; //スコアアップ
}
break; //次の敵機へ進む
}
}
}
}
//《9.4.敵弾と自機・自弾、爆散中敵弾と敵機の衝突処理
for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
if (enemyba[i].v == 1) { //敵弾が存在?
if (player.v == 100 && isHit(enemyba[i], player)) { //敵弾と自機が衝突?
GameOver(); //ゲームオーバー
enemyba[i].v = 0; //敵弾を消す
break; //繰返しを抜ける
}
for (int j = 0; j < maxpb; j++) { //全自弾について繰返す
if (pba[j].v != 0 && isHit(enemyba[i], pba[j])) { //敵弾と自弾が衝突?
enemyba[i].v = 101; //敵弾を爆散状態へ
pba[j].v = 0; //自弾を消す
if (gamemode == 1) { //【追加】プレイ画面なら
score += 100; //スコアアップ
}
break; //次の敵弾へ
}
}
} else if (enemyba[i].v >= 101) { //敵弾が爆散中?
for (int j = 0; j < maxenemy; j++) { //全敵機について繰返す
if (enemya[j].v == 100) { //敵機が100%存在?
//敵機と爆散円の中央の距離の2乗
double d1 = Math.Pow(enemya[j].x - enemyba[i].x, 2)
+ Math.Pow(enemya[j].y - enemyba[i].y, 2);
//敵機半径と爆散円の半径の和の2乗
double d2 = Math.Pow(enemyi.Width / 2 + enemyba[i].v - 100, 2);
if (d1 < d2) { //敵機が爆散円の中?
enemya[j].v = 99; //敵機落下開始
enemya[j].i = meteoi; //破壊画像に
if (gamemode == 1) { //【追加】プレイ画面なら
score += 10; //スコアアップ
}
break; //次の敵機へ進む
}
}
}
for (int j = 0; j < maxenemyb; j++) { //【以下追加】全敵弾について繰返す
if (enemyba[j].v == 1) { //敵弾が存在?
//敵弾と爆散円の中央の距離の2乗
double d1 = Math.Pow(enemyba[j].x - enemyba[i].x, 2)
+ Math.Pow(enemyba[j].y - enemyba[i].y, 2);
//敵弾半径と爆散円の半径の和の2乗
double d2 = Math.Pow(enemybi.Width / 2 + enemyba[i].v - 100, 2);
if (d1 < d2) { //敵弾が爆散円の中?
enemyba[j].v = 101; //爆散状態に
if (gamemode == 1) { //プレイ画面なら
score += 10; //スコアアップ
}
break; //次の敵弾へ進む
}
}
}
}
}
//《9.5.自弾の発射
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; //自弾発射待ち時間をゼロにして発射可能にする
}
}
//《9.6.敵機出現
if (waitenemy <= 0) { //敵機出現待ち時間がゼロ?
for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
if (enemya[i].v == 0) { //敵機が非表示?
enemya[i].v = 100; //100%表示にする
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 = enemyvv; //下移動速度
waitenemy = enemyint; //敵機出現待ち時間をセット
break; //1機出現できればOK
}
}
}
//《9.7.敵弾発射
if (waitenemyb <= 0) { //敵弾出現待ち時間がゼロ?
int enemyi = -1; //発射敵機を-1(未確定)にしておく
for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
if (enemya[i].v == 100 && 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座標は発射敵機中央の上
double rad = Math.Atan2(player.y - enemyba[i].y, player.x - enemyba[i].x); //角度を得る
enemyba[i].hv = (int)(Math.Cos(rad) * enemyvv * 1.5); //X移動量
enemyba[i].vv = (int)(Math.Sin(rad) * enemyvv * 1.5); //Y移動量
waitenemyb = enemybint; //敵弾発射待ち時間をセット
break; //1弾出現できればOK
}
}
}
}
//《9.8.自弾移動
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; //自弾を消す
}
}
}
//《9.9.敵機移動
for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
if (enemya[i].v != 0) { //敵機が存在?
if (enemya[i].v <= 99) { //落下中?
enemya[i].v--; //落下する
}
enemya[i].y += enemya[i].vv; //下へ移動
if (enemya[i].y - enemya[i].i.Height / 2 > backi.Height) { //画面下端より下に出たら
enemya[i].v = 0; //敵機を消す
}
}
}
//《9.10.敵弾移動
for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
if (enemyba[i].v == 1) { //敵弾が存在?
enemyba[i].x += enemyba[i].hv; //X移動量をX座標に加算
enemyba[i].y += enemyba[i].vv; //Y移動量をY座標に加算
if (enemyba[i].x < -enemybi.Width / 2 ||
enemyba[i].x - enemybi.Width / 2 > backi.Width ||
enemyba[i].y < -enemybi.Height / 2 ||
enemyba[i].y - enemybi.Height / 2 > backi.Height) {
enemyba[i].v = 0; //上下左右から外に出たら消す
}
} else if (enemyba[i].v >= 101) { //爆散中?
enemyba[i].v = (enemyba[i].v <= 200) ? enemyba[i].v + 1 : 0; //200までカウントアップ
}
}
//《9.11.カウントダウンと画面再描画
if (waitpb > 0) { //自弾発射待ち時間がセットされていたら
waitpb--; //カウントダウンする
}
if (waitenemy > 0) { //敵機出現待ち時間がセットされていたら
waitenemy--; //カウントダウンする
}
if (waitenemyb > 0) { //敵弾出現待ち時間がセットされていたら
waitenemyb--; //カウントダウンする
}
if (gamemode == 9 && player.v > 0 && player.v < 100) { //ゲームオーバーで自機が縮小中?
player.v--; //表示倍率をダウンする
}
if (enemyint > 10) { //敵機出現間隔が下限より大きければ
enemyint = 50 - score / 100; //スコア÷100だけ出現間隔を短縮
}
if (enemybint > 15) { //【以下追加】敵弾出現間隔が下限より大きければ
enemybint = 75 - score / 100; //スコア÷100だけ出現間隔を短縮
}
Invalidate(); //画面再描画を依頼
}
//《10.ゲームオーバー処理
private void GameOver() {
player.v = 99; //自機の落下(縮小)を開始
gamemode = 9; //ゲームオーバーにする
if (score > hiscore) { //ハイスコア更新?
hiscore = score; //ハイスコア更新
hiscoremsg = "HiSCORE Update !"; //メッセージ
} else {
hiscoremsg = String.Format("HiSCORE is {0:000,000}", hiscore); //スコア文字列を作る
}
}
//《11.コンストラクタ
Program() {
DoubleBuffered = true; //ダブルバッファリングを有効化
KeyDown += new KeyEventHandler(OnKeyDown); //キー入力イベント登録
timer.Tick += new EventHandler(Play); //タイマーイベント登録
timer.Interval = 10; //タイマーインターバル(ミリ秒)
}
//《12.Main
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); //フォームを現出
}
}
テーマ35 サウンド
・フォームアプリケーションで音を出力する方法は複数ある
① Console.Beep静的メソッドに周波数と時間を与えてブザー音を鳴らす
例: Console.Beep(800, 500); //800MHzの音を500ミリ秒
② System.Media.SoundPlayerクラスを用いてWAVファイルを再生する(詳細は後述)
③ NAudioライブラリを利用する(複雑な処理が可能だが、追加インストールが必要)
・今回は②で効果音を追加する
・利用には「using System.Media;」を記述すると便利
・SoundPlayerクラスのインスタンスを生成し、コンストラクタにWAVファイル名を渡すことで、その参照変数による
再生が可能になる
・書式: SoundPlayer 参照変数 = new SoundPlayer(WAVファイル名);
例: SoundPlayer sound1 = new SoundPlayer("mysound1.wav");
・このままでも再生できることが多いが、再生準備をさせるために、SoundPlayerインスタンスのLoadメソッドで
メモリロードを明示しておくと良い
・書式: 参照変数.Load();
・サイズの大きなWAVファイルを用いる場合は、メモリロードが完了する前に再生できないことがあるので、
bool IsLoadCompletedプロパティでロード待ちを行うことができる
・音を再生するには、SoundPlayerインスタンスのPlayメソッドを用いる
・WAVファイルは画像ファイルなどと同じフォルダ(開発中はdebugフォルダ)に置くと良い
演習45 効果音
・ゲーム開始のEnterキー入力時、ゲームオーバー時、爆散時、爆散円の誘爆時に効果音を鳴らそう ・下記を右クリックし「名前をつけてリンク先保存」でDebugフォルダにダウンロードする。 開始音 終了音 爆散音 誘爆音 ※効果音著作者:MaouDamashii(https://maou.audio/)
手順と仕様
① 冒頭:「using System.Media;」を記述 ② 2.データメンバ:4つの効果音のインスタンスを生成 ③ 11.コンストラクタ:4つの効果音をロード ④ 8.キー入力時処理:タイトル画面でEnterキーが押されたら開始音を再生。 ⑤ 8.キー入力時処理:ゲームオーバー画面でEnterキーが押されたら開始音を再生。 ⑥ 9.4.敵弾と自機・自弾、爆散中敵弾と敵機の衝突処理:敵弾と自弾が衝突したら爆散音を再生。 ⑦ 9.4.敵弾と自機・自弾、爆散中敵弾と敵機の衝突処理:敵弾と爆散円が衝突したら誘爆音を再生。 ⑧ 10.ゲームオーバー処理:終了音を再生。
作成例
//演習45 効果音
using System; //汎用的に利用
using System.Windows.Forms; //フォームアプリケーションに必須
using System.Drawing; //Size、Image用
using System.Media; //【追加】SoundPlayer用
//《0.アイテムを表す構造体
struct Item {
public Image i; //画像
public int x; //中心X座標
public int y; //中心Y座標
public int hv; //左右方向の速度(左向きは負の数、右向きは正の数)
public int vv; //上下方向の速度(上向きは負の数、下向きは正の数)
public int v; //表示状態(0:非表示、1~100:表示、101~200:爆散中)
}
class Program : Form { //Formクラスの派生クラス
//《1.DLLインポートと外部定義指定
[System.Runtime.InteropServices.DllImport("user32.dll")] //DLLインポート
private static extern short GetKeyState(int nVirtKey); //外部定義指定
//《2.データメンバ
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"); //敵弾画像を読込む
Image meteoi = Image.FromFile("meteo3.gif"); //破壊画像を読込む
static Image burni = Image.FromFile("burn.gif"); //炎画像を読込む
Pen burnp = new Pen(new TextureBrush(burni), 15); //炎のテクスチャペン
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クラスのインスタンスを生成
int enemybint = 75; //敵弾の出現間隔
int waitenemyb = 200; //敵弾の出現待ち時間
const int maxenemyb = 20; //敵弾の最大数
Item[] enemyba = new Item[maxenemyb]; //敵弾の構造体配列
const int enemyvv = 5; //敵機移動速度
SoundPlayer starts = new SoundPlayer("maou_se_onepoint30.wav"); //【追加】開始音
SoundPlayer ends = new SoundPlayer("maou_se_onepoint29.wav"); //【追加】終了音
SoundPlayer hits = new SoundPlayer("maou_se_battle12.wav"); //【追加】爆散音
SoundPlayer rhits = new SoundPlayer("maou_se_battle18.wav"); //【追加】誘爆音
//《3.初期化処理:各データをゲーム開始時の値にする
void initData() {
player.i = playeri; //自機の画像
player.x = 320; //自機の中心X座標
player.y = 410; //自機の中心Y座標
player.hv = 0; //自機の左右方向の速度
player.v = 100; //自機を100%表示
waitpb = 0; //自弾発射の待ち時間
waitpb = 0; //自弾発射の待ち時間
score = 0; //スコアクリア
waitenemyb = 200; //敵弾の出現待ち時間
enemyint = 50; //敵機の出現間隔
enemybint = 75; //敵弾の出現間隔
}
//《4.中央座標を用いる画像描画処理
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);
}
//《5.中央座標を用いる画像描画処理(倍率指定)
private void DrawItem(PaintEventArgs e, Item it, int rate) {
if (rate == 100) { //等倍?
DrawItem(e, it); //通常描画を呼ぶ
} else {
int xx = it.x - it.i.Width * rate / 100 / 2; //左上X座標を得る
int yy = it.y - it.i.Height * rate / 100 / 2; //左上Y座標を得る
e.Graphics.DrawImage(it.i, xx, yy, it.i.Width * rate / 100, it.i.Height * rate / 100);
}
}
//《6.楕円形の衝突判定(アイテム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乗を比較
}
//《7.描画処理(オーバライド)
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, player.v); //自機を倍率描画
}
foreach (var pb in pba) { //全自弾について繰返す
if (pb.v == 1) { //自弾がある?
DrawItem(e, pb); //自弾を描画
}
}
foreach (var enemy in enemya) { //全敵機について繰返す
if (enemy.v != 0) { //敵機がある?
DrawItem(e, enemy, enemy.v); //敵機を倍率描画
}
}
foreach (var enemyb in enemyba) { //全敵弾について繰返す
if (enemyb.v == 1) { //敵弾がある?
DrawItem(e, enemyb); //敵弾を描画
} else if (enemyb.v >= 101) { //爆散中?
int rr = (enemyb.v - 100) * 2; //直径を計算
e.Graphics.DrawEllipse(burnp, enemyb.x - (enemyb.v - 100),
enemyb.y - (enemyb.v - 100), rr, rr); //爆散円描画
}
}
}
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); //メッセージ表示
}
}
//《8.キー入力時処理
void OnKeyDown(object o, KeyEventArgs e) {
if (e.KeyCode.ToString() == "Escape") { //Escキーが押されていたら
Close(); //フォーム終了
}
//タイトル画面でEnterキーが押されていたら
if (gamemode == 0 && e.KeyCode.ToString() == "Return") {
starts.Play(); //【追加】開始音を再生
initData(); //各データをゲーム開始時の値にする
gamemode = 1; //プレイ動画に遷移
timer.Start(); //タイマー開始
}
//ゲームオーバー画面でEnterキーが押されていたら
else if (gamemode == 9 && e.KeyCode.ToString() == "Return") {
starts.Play(); //【追加】開始音を再生
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(); //画面再描画を依頼
}
//《9.タイマーイベント処理
void Play(object o, EventArgs e) {
//《9.1.背景画像スクロール
backy = (backy + 1) % backi.Height; //1枚目の背景描画開始Y座標を下げる
//《9.2.自機の向きのセット
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; //右向き
}
}
//《9.3.敵機と自機・自弾の衝突処理
for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
if (enemya[i].v == 100) { //敵機が100%存在?
if (player.v == 100 && isHit(enemya[i], player)) { //自機が100%存在し衝突?
GameOver(); //ゲームオーバー
enemya[i].v = 99; //敵機落下開始
enemya[i].i = meteoi; //破壊画像に
break; //抜ける
}
for (int j = 0; j < maxpb; j++) { //全自弾について繰返す
if (pba[j].v != 0 && isHit(enemya[i], pba[j])) { //自弾が存在し衝突?
enemya[i].v = 99; //敵機落下開始
enemya[i].i = meteoi; //破壊画像に
pba[j].v = 0; //この自弾を消す
if (gamemode == 1) { //プレイ画面なら
score += 10; //スコアアップ
}
break; //次の敵機へ進む
}
}
}
}
//《9.4.敵弾と自機・自弾、爆散中敵弾と敵機の衝突処理
for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
if (enemyba[i].v == 1) { //敵弾が存在?
if (player.v == 100 && isHit(enemyba[i], player)) { //敵弾と自機が衝突?
GameOver(); //ゲームオーバー
enemyba[i].v = 0; //敵弾を消す
break; //繰返しを抜ける
}
for (int j = 0; j < maxpb; j++) { //全自弾について繰返す
if (pba[j].v != 0 && isHit(enemyba[i], pba[j])) { //敵弾と自弾が衝突?
hits.Play(); //【追加】爆散音を再生
enemyba[i].v = 101; //敵弾を爆散状態へ
pba[j].v = 0; //自弾を消す
if (gamemode == 1) { //プレイ画面なら
score += 100; //スコアアップ
}
break; //次の敵弾へ
}
}
} else if (enemyba[i].v >= 101) { //敵弾が爆散中?
for (int j = 0; j < maxenemy; j++) { //全敵機について繰返す
if (enemya[j].v == 100) { //敵機が100%存在?
//敵機と爆散円の中央の距離の2乗
double d1 = Math.Pow(enemya[j].x - enemyba[i].x, 2)
+ Math.Pow(enemya[j].y - enemyba[i].y, 2);
//敵機半径と爆散円の半径の和の2乗
double d2 = Math.Pow(enemyi.Width / 2 + enemyba[i].v - 100, 2);
if (d1 < d2) { //敵機が爆散円の中?
enemya[j].v = 99; //敵機落下開始
enemya[j].i = meteoi; //破壊画像に
if (gamemode == 1) { //プレイ画面なら
score += 10; //スコアアップ
}
break; //次の敵機へ進む
}
}
}
for (int j = 0; j < maxenemyb; j++) { //全敵弾について繰返す
if (enemyba[j].v == 1) { //敵弾が存在?
//敵弾と爆散円の中央の距離の2乗
double d1 = Math.Pow(enemyba[j].x - enemyba[i].x, 2)
+ Math.Pow(enemyba[j].y - enemyba[i].y, 2);
//敵弾半径と爆散円の半径の和の2乗
double d2 = Math.Pow(enemybi.Width / 2 + enemyba[i].v - 100, 2);
if (d1 < d2) { //敵弾が爆散円の中?
rhits.Play(); //【追加】誘爆音を再生
enemyba[j].v = 101; //爆散状態に
if (gamemode == 1) { //プレイ画面なら
score += 10; //スコアアップ
}
break; //次の敵弾へ進む
}
}
}
}
}
//《9.5.自弾の発射
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; //自弾発射待ち時間をゼロにして発射可能にする
}
}
//《9.6.敵機出現
if (waitenemy <= 0) { //敵機出現待ち時間がゼロ?
for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
if (enemya[i].v == 0) { //敵機が非表示?
enemya[i].v = 100; //100%表示にする
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 = enemyvv; //下移動速度
waitenemy = enemyint; //敵機出現待ち時間をセット
break; //1機出現できればOK
}
}
}
//《9.7.敵弾発射
if (waitenemyb <= 0) { //敵弾出現待ち時間がゼロ?
int enemyi = -1; //発射敵機を-1(未確定)にしておく
for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
if (enemya[i].v == 100 && 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座標は発射敵機中央の上
double rad = Math.Atan2(player.y - enemyba[i].y, player.x - enemyba[i].x); //角度を得る
enemyba[i].hv = (int)(Math.Cos(rad) * enemyvv * 1.5); //X移動量
enemyba[i].vv = (int)(Math.Sin(rad) * enemyvv * 1.5); //Y移動量
waitenemyb = enemybint; //敵弾発射待ち時間をセット
break; //1弾出現できればOK
}
}
}
}
//《9.8.自弾移動
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; //自弾を消す
}
}
}
//《9.9.敵機移動
for (int i = 0; i < maxenemy; i++) { //全敵機について繰返す
if (enemya[i].v != 0) { //敵機が存在?
if (enemya[i].v <= 99) { //落下中?
enemya[i].v--; //落下する
}
enemya[i].y += enemya[i].vv; //下へ移動
if (enemya[i].y - enemya[i].i.Height / 2 > backi.Height) { //画面下端より下に出たら
enemya[i].v = 0; //敵機を消す
}
}
}
//《9.10.敵弾移動
for (int i = 0; i < maxenemyb; i++) { //全敵弾について繰返す
if (enemyba[i].v == 1) { //敵弾が存在?
enemyba[i].x += enemyba[i].hv; //X移動量をX座標に加算
enemyba[i].y += enemyba[i].vv; //Y移動量をY座標に加算
if (enemyba[i].x < -enemybi.Width / 2 ||
enemyba[i].x - enemybi.Width / 2 > backi.Width ||
enemyba[i].y < -enemybi.Height / 2 ||
enemyba[i].y - enemybi.Height / 2 > backi.Height) {
enemyba[i].v = 0; //上下左右から外に出たら消す
}
} else if (enemyba[i].v >= 101) { //爆散中?
enemyba[i].v = (enemyba[i].v <= 200) ? enemyba[i].v + 1 : 0; //200までカウントアップ
}
}
//《9.11.カウントダウンと画面再描画
if (waitpb > 0) { //自弾発射待ち時間がセットされていたら
waitpb--; //カウントダウンする
}
if (waitenemy > 0) { //敵機出現待ち時間がセットされていたら
waitenemy--; //カウントダウンする
}
if (waitenemyb > 0) { //敵弾出現待ち時間がセットされていたら
waitenemyb--; //カウントダウンする
}
if (gamemode == 9 && player.v > 0 && player.v < 100) { //ゲームオーバーで自機が縮小中?
player.v--; //表示倍率をダウンする
}
if (enemyint > 10) { //敵機出現間隔が下限より大きければ
enemyint = 50 - score / 100; //スコア÷100だけ出現間隔を短縮
}
if (enemybint > 15) { //敵弾出現間隔が下限より大きければ
enemybint = 75 - score / 100; //スコア÷100だけ出現間隔を短縮
}
Invalidate(); //画面再描画を依頼
}
//《10.ゲームオーバー処理
private void GameOver() {
ends.Play(); //【追加】終了音を再生
player.v = 99; //自機の落下(縮小)を開始
gamemode = 9; //ゲームオーバーにする
if (score > hiscore) { //ハイスコア更新?
hiscore = score; //ハイスコア更新
hiscoremsg = "HiSCORE Update !"; //メッセージ
} else {
hiscoremsg = String.Format("HiSCORE is {0:000,000}", hiscore); //スコア文字列を作る
}
}
//《11.コンストラクタ
Program() {
DoubleBuffered = true; //ダブルバッファリングを有効化
KeyDown += new KeyEventHandler(OnKeyDown); //キー入力イベント登録
timer.Tick += new EventHandler(Play); //タイマーイベント登録
timer.Interval = 10; //タイマーインターバル(ミリ秒)
starts.Load(); //【追加】開始音をロード
ends.Load(); //【追加】終了音をロード
hits.Load(); //【追加】爆散音をロード
rhits.Load(); //【追加】誘爆音をロード
}
//《12.Main
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); //フォームを現出
}
}
ゲーム開発編は以上です。