・衝突判定について理解できました
・当たり判定の仕組みを学べて面白かったです。
何よりです。
・開発演習では考えることが増えてきて苦戦しますが面白いです!
この後もいろいろなテクニックが登場しますので、お楽しみに♪
for trainees of Hirano's class only
・衝突判定について理解できました
・当たり判定の仕組みを学べて面白かったです。
何よりです。
・開発演習では考えることが増えてきて苦戦しますが面白いです!
この後もいろいろなテクニックが登場しますので、お楽しみに♪
テキスト編次回予告:p.362 練習問題2(演算子のオーバーロードの複数化) から
ゲーム開発演習次回予告:自機と敵機の衝突判定とゲームオーバー画面 他
講義メモ:memo20240204.zip
:敵機の出現・移動のフォロー、自弾と敵機の衝突判定
演習32 敵機の出現と移動(再掲載)
・一定間隔ごとに敵機が自機の直上の上端範囲外に出現し、下方向に移動するようにしよう ・敵機の最大数は20機とし、これ以上は出現させない ・出現間隔はタイマーインターバルの50倍とする ・下方向移動速は5とする ・自弾と同様に画面下部から先で見えなくなったら出現前の状態に戻すこと ・敵機の画像は下記を利用可能enemy.gif
手順(再掲載+α)
① 敵機画像の読み込みを追記。Image enemyi 等 ② 敵機の最大数を初期化。int maxenemy 等 ③ 敵機の構造体オブジェクト配列を生成 Item[] enemya 等 ④ 敵機の出現待ち時間を初期化 waitenemy 等 ⑤ 敵機の出現間隔を初期化 enemyint 等 ⑥ 描画処理:全敵機について、表示状態であるものを表示する処理 ⑦ タイマーイベント処理:敵機出現待ち時間がゼロであれば、最初に見つけた非表示である敵機要素を表示状態にし、 自機と同じX座標、上端の直上のY座標などを設定する また、敵機の出現待ち時間に敵機の出現間隔をセット ※1機を出現させたら抜ける ⑧ タイマーイベント処理:出現中の全敵機について、下へ移動し、下端より下に出たら非表示にする ⑨ タイマーイベント処理:敵機出現待ち時間がセットされていたらカウントダウン
作成例
//演習32 敵機の出現と移動
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; //スコア
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"); //【追加】敵機画像を読込む
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; //【追加】敵機の出現間隔
//中央座標を用いる画像描画処理
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);
}
//描画処理のオーバライド
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 if (gamemode == 1) { //プレイ画面?
string s = String.Format("SCORE:{0:000,000}", score); //スコア文字列を作る
e.Graphics.DrawString(s, font1, brushs, 400, 10); //スコア表示
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); //敵機を描画
}
}
}
}
//キー入力時処理
void OnKeyDown(object o, KeyEventArgs e) {
if (e.KeyCode.ToString() == "Escape") { //Escキーが押されていたら
Close(); //フォーム終了
}
//タイトル画面でEnterキーが押されていたら
if (gamemode == 0 && e.KeyCode.ToString() == "Return") {
gamemode = 1; //プレイ動画に遷移
timer.Start(); //タイマー開始
}
Invalidate(); //画面再描画を依頼
}
//タイマーイベント処理
void Play(object o, EventArgs e) {
backy = (backy + 1) % backi.Height; //1枚目の背景描画開始Y座標を下げる
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; //右向き
}
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 = player.x; //X座標は自機と同じ
enemya[i].y = -enemyi.Height; //Y座標は上端の直上
enemya[i].vv = 5; //下移動速度
waitenemy = enemyint; //自弾発射待ち時間をセット
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--; //カウントダウンする
}
Invalidate(); //画面再描画を依頼
}
//コンストラクタ
Program() {
DoubleBuffered = true; //ダブルバッファリングを有効化
KeyDown += new KeyEventHandler(OnKeyDown); //キー入力イベント登録
timer.Tick += new EventHandler(Play); //タイマーイベント登録
timer.Interval = 10; //タイマーインターバル(ミリ秒)
player.i = playeri; //自機の画像
player.x = 320; //自機の中心X座標
player.y = 410; //自機の中心Y座標
player.hv = 0; //自機の左右方向の速度
}
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); //フォームを現出
}
}
テーマ30 衝突判定
・矩形(四角形)の衝突判定は、重なっている部分の有無でできる。 ・しかし、これを矩形以外に用いると角にあたる部分で過度の判定になってしまう。 ・このような場合は、楕円の衝突判定を行えば良い・双方の中心間の距離を得て、双方の内径の和と比較すれば良い。 ・大小比較なので、平方根を取る必要はなく、両座標差の2乗の和と内径の和の2乗を比較しよう ・なお、楕円に近い場合を考慮して、内径には、高さと幅の小さい方の半分を用いよう
演習33 自弾と敵機の衝突判定
・タイマーで呼ばれる処理において、出現中の全ての敵機と全ての自弾の組み合わせにおいて、衝突判定を行い、 衝突していたら、その敵機と自弾の両方を非表示にしよう
手順
① 衝突判定用のメソッド bool isHit(Item a, Item b)を作る ・aとbのX座標差の2乗とY座標差の2乗の和を得て距離の2乗とする ・aの幅が高さ未満なら幅の半分を、でなければ高さの半分を内径aとする ・bの幅が高さ未満なら幅の半分を、でなければ高さの半分を内径bとする ・距離の2乗が、内径aと内径bの和の2乗未満ならtrue、でなければfalseを返す ② タイマーイベント処理において存在中の全敵機について以下を繰り返す ・存在中の全自弾について、その敵機と衝突中かチェックし、衝突中なら双方を非表示にする ・そして、次の敵機に進む
作成例
//演習33 自弾と敵機の衝突判定
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; //スコア
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"); //【追加】敵機画像を読込む
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; //敵機の出現間隔
//中央座標を用いる画像描画処理
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 if (gamemode == 1) { //プレイ画面?
string s = String.Format("SCORE:{0:000,000}", score); //スコア文字列を作る
e.Graphics.DrawString(s, font1, brushs, 400, 10); //スコア表示
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); //敵機を描画
}
}
}
}
//キー入力時処理
void OnKeyDown(object o, KeyEventArgs e) {
if (e.KeyCode.ToString() == "Escape") { //Escキーが押されていたら
Close(); //フォーム終了
}
//タイトル画面でEnterキーが押されていたら
if (gamemode == 0 && e.KeyCode.ToString() == "Return") {
gamemode = 1; //プレイ動画に遷移
timer.Start(); //タイマー開始
}
Invalidate(); //画面再描画を依頼
}
//タイマーイベント処理
void Play(object o, EventArgs e) {
backy = (backy + 1) % backi.Height; //1枚目の背景描画開始Y座標を下げる
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) { //敵機が存在?
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; //この自弾を消す
break; //次の敵機へ進む
}
}
}
}
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 = player.x; //X座標は自機と同じ
enemya[i].y = -enemyi.Height; //Y座標は上端の直上
enemya[i].vv = 5; //下移動速度
waitenemy = enemyint; //自弾発射待ち時間をセット
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--; //カウントダウンする
}
Invalidate(); //画面再描画を依頼
}
//コンストラクタ
Program() {
DoubleBuffered = true; //ダブルバッファリングを有効化
KeyDown += new KeyEventHandler(OnKeyDown); //キー入力イベント登録
timer.Tick += new EventHandler(Play); //タイマーイベント登録
timer.Interval = 10; //タイマーインターバル(ミリ秒)
player.i = playeri; //自機の画像
player.x = 320; //自機の中心X座標
player.y = 410; //自機の中心Y座標
player.hv = 0; //自機の左右方向の速度
}
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); //フォームを現出
}
}
演習34 自機と敵機の衝突判定とゲームオーバー画面
・自機と敵機が衝突したら自機とその敵機を消そう ・そして、ゲームオーバーとし、ゲームオーバー画面に遷移しよう ・ゲームオーバー画面でも背景のスクロールや敵機の出現は続くものとする ・しかし、自機の移動や自弾の発射はできないようにする ・Enterキーが押されたら、リプレイできるようにしよう
提出:演習33(未完成でもOK)
テキスト編:p.345 演算子のオーバーロードとは から
ゲーム開発演習:敵機の出現・移動のフォロー、自弾と敵機の衝突判定
p.345 演算子のオーバーロードとは
・2項+演算子のように、オペランドの型によって作用が異なるものがあり、これを演算子のオーバーロードという ・組み込み型(p.38)に対する演算子のオーバーロードはシステムが定義しており、変更できない ・しかし、ユーザ定義型に対する演算子のオーバーロードは、ユーザ定義型を表すクラスの中で定義することが可能 ・例えば、X座標とY座標を持つ座標クラスのオブジェクトどうしを2項+演算子で演算できるように定義しておけば、 座標 a(3, 2)、b(1, 5) において、a + b を(3 + 2, 2 + 5) = (5, 7) とすることが可能 ・テキストでは複素数を表すクラスを例にとって、複素数の演算公式を演算子のオーバーロードで実装している ・しかし、数学的知識がある前提になっているので、X座標とY座標を持つ座標クラスに置き換えて理解しよう
p.347(X座標とY座標を持つ座標クラス)map01.cs
・テキストp.347のcomplex01.csの仕様を下記のように変更して、演算子のオーバーロードを実装する基にしよう ・クラス名:Map ・インスタンス変数:int x, y ・プロパティ:int X(x用), int Y(y用) ・引数のないコンストラクタ: MAP() ⇒ x←0、y←0 ・int型2引数を持つコンストラクタ: MAP(x, y) ・上記の動作を確認するMain()メソッド
作成例
//p.347 map01.cs(complex01.cs改)
using System;
class Map { //座標クラス
int x; //X座標のデータメンバ
int y; //Y座標のデータメンバ
public int X { //X座標用のプロパティ
get { return x; }
set { x = value; }
}
public int Y { //Y座標用のプロパティ
get { return y; }
set { y = value; }
}
public Map() { //デフォルトコンストラクタ
x = y = 0;
}
public Map(int x, int y) { //X座標、Y座標を指定するコンストラクタ
this.x = x;
this.y = y;
}
}
class Map01 {
public static void Main() {
Map A = new Map(); //座標A(0,0)を生成
A.X = 10;
A.Y = 5; //座標(10, 5)になる
Map B = new Map(2, 3); //座標B(2,3)を生成
Console.WriteLine("座標Aは({0}, {1})です。", A.X, A.Y);
Console.WriteLine("座標Bは({0}, {1})です。", B.X, B.Y);
}
}
p.349 単項演算子のオーバーロード
・クラス内でoperatorキーワードを用いてメソッドに近い表記をすることで、演算子のオーバーロードが可能
・単項演算子のオーバーロードでは、public staticで「戻り値型 operator 演算子(引数型 仮引数)」を定義し、
内部で演算結果をreturnすれば良い
・演算子のオーバーロードの基本ルールとして、元の演算子の意味とかけ離れた定義を行わないことがある
・よって、単項-演算子のオーバーロードでは、戻り値型と引数型をクラスに合わせると良い。よって:
public static クラス名 operator-(クラス名 仮引数) { return 仮引数の符号反転結果; }
・これを座標クラスMapに当てはめると下記のようになり、X座標とY座標の符号反転になる
public static Map operator-(Map w) { return new Map(-w.x, -w.y); }
※ テキストのopov01.csでは継承したクラスにおいて単項演算子のオーバーロードを記述しているが、継承する必要はない
p.349 Map02.cs:単項-演算子のオーバーロード
・Map01.csに単項-演算子のオーバーロードを追記して動作を確認しよう。 ・単項-演算子により、X座標とY座標の符号反転を行うとする。 ・よって、MapクラスのオブジェクトA(x, y)について、-Aは(-x, -y)となる。
作成例
//p.347 map02.cs(opov01.cs改)
using System;
class Map { //座標クラス
int x; //X座標のデータメンバ
int y; //Y座標のデータメンバ
public int X { //X座標用のプロパティ
get { return x; }
set { x = value; }
}
public int Y { //Y座標用のプロパティ
get { return y; }
set { y = value; }
}
public Map() { //デフォルトコンストラクタ
x = y = 0;
}
public Map(int x, int y) { //X座標、Y座標を指定するコンストラクタ
this.x = x;
this.y = y;
}
public static Map operator-(Map w) { //単項-演算子のオーバーロード
return new Map(-w.x, -w.y); //X座標とY座標の符号を反転した結果を返す
}
}
class Map01 {
public static void Main() {
Map A = new Map(2, 3); //座標B(2,3)を生成
Map B = -A; //単項-演算子のオーバーロードを用いる
Console.WriteLine("座標Aは({0}, {1})です。", A.X, A.Y);
Console.WriteLine("座標Bは({0}, {1})です。", B.X, B.Y);
}
}
p.349 Map03.cs:単項+演算子のオーバーロード
・Map02.csに単項+演算子のオーバーロードを追記して動作を確認しよう。 ・単項+演算子により、座標(0,0)から座標までの距離を実数で返すとする。 ・例えば、MapクラスのオブジェクトA(-3, -4)について、+Aは5.0となる。 ・距離は3平方の定理で(X座標の2乗+Y座標の2乗)の平方根で得られる ・平方根はdouble Math.Sqrt(double)メソッドで得ると良い
作成例
//p.347 map03.cs(単項*演算子のオーバーロード)
using System;
class Map { //座標クラス
int x; //X座標のデータメンバ
int y; //Y座標のデータメンバ
public int X { //X座標用のプロパティ
get { return x; }
set { x = value; }
}
public int Y { //Y座標用のプロパティ
get { return y; }
set { y = value; }
}
public Map() { //デフォルトコンストラクタ
x = y = 0;
}
public Map(int x, int y) { //X座標、Y座標を指定するコンストラクタ
this.x = x;
this.y = y;
}
public static Map operator -(Map w) { //単項-演算子のオーバーロード
return new Map(-w.x, -w.y); //X座標とY座標の符号を反転した結果を返す
}
public static double operator +(Map w) { //単項+演算子のオーバーロード
return Math.Sqrt(w.x * w.x + w.y * w.y); //各座標の2乗の和の平方根を返す
}
}
class Map01 {
public static void Main() {
Map A = new Map(-3, -4); //座標Aを生成
Console.WriteLine("座標Aは({0}, {1})で、距離は{2}", A.X, A.Y, +A);
}
}
p.353 2項演算子のオーバーロード
・2項演算子のオーバーロードでは、public staticで「戻り値型 operator 演算子(引数型① 仮引数①, 引数型② 仮引数②)」 を定義し、内部で演算結果をreturnすれば良い ・左オペランドが仮引数①、右オペランドが仮引数②となる
p.349 Map04.cs:2項+演算子のオーバーロード (opov02.cs改)
・Map02.csに2項+演算子のオーバーロードを追記して動作を確認しよう。 ・2項+演算子により、A + B は各座標の和をもつMapオブジェクトを返すとする。 ・例えば、MapクラスのオブジェクトA(-3, -4)、B(4, 2)について、A + Bは(1, -2)となる。
作成例
//p.349 Map04.cs:2項+演算子のオーバーロード (opov02.cs改)
using System;
class Map { //座標クラス
int x; //X座標のデータメンバ
int y; //Y座標のデータメンバ
public int X { //X座標用のプロパティ
get { return x; }
set { x = value; }
}
public int Y { //Y座標用のプロパティ
get { return y; }
set { y = value; }
}
public Map() { //デフォルトコンストラクタ
x = y = 0;
}
public Map(int x, int y) { //X座標、Y座標を指定するコンストラクタ
this.x = x;
this.y = y;
}
public static Map operator -(Map w) { //単項-演算子のオーバーロード
return new Map(-w.x, -w.y); //X座標とY座標の符号を反転した結果を返す
}
public static double operator +(Map w) { //単項+演算子のオーバーロード
return Math.Sqrt(w.x * w.x + w.y * w.y); //各座標の2乗の和の平方根を返す
}
public static Map operator +(Map a, Map b) { //2項+演算子のオーバーロード
return new Map(a.x + b.x, a.y + b.y); //各座標の和をもつオブジェクトを返す
}
}
class Map01 {
public static void Main() {
Map A = new Map(-3, -4); //座標Aを生成
Map B = new Map(4, 2); //座標Bを生成
Console.WriteLine("A + Bは({0}, {1})", (A + B).X, (A + B).Y);
}
}
p.356(2項==演算子のオーバーロード )
・2項==演算子は特別扱いの演算子で、オーバーロードする場合、bool型を返す必要がある ・また、同時に2項!=演算子もオーバーロードする必要がある ・なお、内容は「return !(仮引数① == 仮引数②);」固定で良い ※ テキストにはEqualsメソッドとGetHashCodeメソッドのオーバーライドも必要とあるが、必須ではない
p.358 Map05.cs:2項==演算子、2項!=演算子のオーバーロード (opov03.cs改)
・Map04.csに2項==演算子、2項!=演算子のオーバーロードを追記して動作を確認しよう。 ・2項==演算子により、A == B は各座標が共に等しければtrueを、でなければfalseを返すとする。 ・2項!=演算子により、A != B は各座標が共に等しければfalseを、でなければtrueを返すとする。
作成例
//p.349 Map04.cs:2項+演算子のオーバーロード (opov02.cs改)
using System;
class Map { //座標クラス
int x; //X座標のデータメンバ
int y; //Y座標のデータメンバ
public int X { //X座標用のプロパティ
get { return x; }
set { x = value; }
}
public int Y { //Y座標用のプロパティ
get { return y; }
set { y = value; }
}
public Map() { //デフォルトコンストラクタ
x = y = 0;
}
public Map(int x, int y) { //X座標、Y座標を指定するコンストラクタ
this.x = x;
this.y = y;
}
public static Map operator -(Map w) { //単項-演算子のオーバーロード
return new Map(-w.x, -w.y); //X座標とY座標の符号を反転した結果を返す
}
public static double operator +(Map w) { //単項+演算子のオーバーロード
return Math.Sqrt(w.x * w.x + w.y * w.y); //各座標の2乗の和の平方根を返す
}
public static Map operator +(Map a, Map b) { //2項+演算子のオーバーロード
return new Map(a.x + b.x, a.y + b.y); //各座標の和をもつオブジェクトを返す
}
public static bool operator ==(Map a, Map b) { //2項==演算子のオーバーロード
return a.x == b.x && a.y == b.y; //各座標の和をもつオブジェクトを返す
}
public static bool operator !=(Map a, Map b) { //2項!=演算子のオーバーロード
return !(a == b); //2項==演算子のオーバーロードの結果を反転して返す
}
}
class Map01 {
public static void Main() {
Map A = new Map(-3, -4); //座標Aを生成
Map B = new Map(4, 2); //座標Bを生成
Console.WriteLine("A == Bは{0}、A != Bは{1}", A == B, A != B);
}
}
販売本数ランキング 今回トップは「龍が如く8(PS5)」GO!
『アトリエ』シリーズ好調でコーエーテクモが2割増収―“月商20億円のスマートフォンゲーム”を生み出すことに成功【ゲーム企業の決算を読む】GO!
業績好調でも脱・中古ゲーム販売を進めるゲオが向かう先【ゲーム企業の決算を読む】GO!
『原神』が40カ月で売上50億ドルを達成―『クラッシュ・オブ・クラン』を抜き、モバイルゲーム最速記録 GO!
CESA、「CEDEC2024」開催を発表ーセッション講演者の公募を開始 GO!
マイクロソフトが2024年第2四半期決算を発表―アクティビジョン買収でゲーム部門が躍進 GO!
『いけにえと雪のセツナ』『鬼ノ哭ク邦』のTokyo RPG Factoryがスクエニに吸収合併、解散へ―権利義務一切はスクエニ側に引き継ぎ GO!
【決算】カプコンの3Q連結業績は4割超増益で着地―『スト6』などデジタル販売強化が奏功 GO!
【決算】コーエーテクモHDの3Q決算、経常利益100%増の大幅増益 GO!
AppleがEU圏でアプリのサイドローディングを許可するも大手アプリは反発―Xboxサラ・ボンド氏「間違った方向に進んでいる」GO!
・例外についてさらに理解することができました!
何よりです。
・だんだんとゲームらしくなっていって楽しいです。
この後も、いろいろなテクニックをご紹介しますので、いろいろと試してみてください。
テキスト編次回予告:p.345 演算子のオーバーロードとは から
ゲーム開発演習次回予告:敵機の出現・移動のフォロー、自機と敵機の衝突判定、ゲームオーバー処理
講義メモ:memo20240128.zip
・敵機の出現、敵機の移動、衝突判定
演習32 敵機の出現と移動
・一定間隔ごとに敵機が自機の直上の上端範囲外に出現し、下方向に移動するようにしよう ・敵機の最大数は20機とし、これ以上は出現させない ・出現間隔はタイマーインターバルの50倍とする ・下方向移動速は5とする ・自弾と同様に画面下部から先で見えなくなったら出現前の状態に戻すこと ・敵機の画像は下記を利用可能enemy.gif
手順
① 敵機画像の読み込みを追記。Image enemyi 等 ② 敵機の最大数を初期化。int maxenemy 等 ③ 敵機の構造体オブジェクト配列を生成 Item[] enemya 等 ④ 敵機の出現待ち時間を初期化 waitenemy 等 ⑤ 敵機の出現間隔を初期化 enemyint 等 ⑥ 描画処理:全敵機について、表示状態であるものを表示する処理 ⑦ タイマーイベント処理:敵機出現待ち時間がゼロであれば、非表示である敵機要素を表示状態にし、 自機と同じX座標、上端の直上のY座標などを設定 また、敵機の出現待ち時間に敵機の出現間隔をセット ⑧ タイマーイベント処理:全敵機について、下へ移動し、下端より下に出たら非表示にする ⑨ タイマーイベント処理:敵機出現待ち時間がセットされていたらカウントダウン
提出:演習32 敵機の出現と移動(未完成OK)
テキスト編:p.327「exception03.cs」から
ゲーム開発演習:敵機の出現、敵機の移動、衝突判定
p.326 例外クラスの主なメンバ(再掲載)
・catchブロックに引数として例外クラスと引数名を指定することで得られる例外オブジェクトが持つ情報は、 例外クラスに共通なメンバ(メソッドとプロパティ)を用いて得られる ・詳細は公式ドキュメントで閲覧可能 https://learn.microsoft.com/ja-jp/dotnet/api/system.exception?view=net-8.0 ・なお、テキストに記載のToString()メソッドは、例外クラスに限ったものではなく、すべてのクラスにおいて、 その情報を返すメソッドとして記述する(オーバーライドする)ことが推奨されている。
p.326 System.Objectクラス(再掲載)
・Object型はInt32などと同様の.NETフレームワーク型であり、.NETフレームワーク型にはすべて対応するクラスまたは構造体が提供されている ・int型などと同様に、object型を用いると、.NETフレームワークのObject型として扱われる ・object/Object型に対応するクラスがSystem.Objectクラスで、全てのクラスの暗黙の基本クラス(ルートクラス)として定義されており、 継承を指定しなくても、自動的に用いられる ・このSystem.Objectクラスの持っているメンバの一つがToString()メソッドなので、すべてのクラスにおいてToString()メソッドの オーバーライドが可能。 ・このことを利用して、オブジェクト名(参照変数)をConsole.Writeなどに指定すると、自動的に「参照変数.ToString()」が 実行されるようになっている。 例: p.327 exception03.csで「Console.Write(io)」と「Console.Write(io.ToString())」を実行しているが 同じ結果になる
p.327 exception03.cs
//p.327 exception03.cs
using System;
class exception03 {
public static void Main() {
int[] arr = new int[5];
try { //例外処理範囲
arr[5] = 10; //配列範囲外例外が発生
} catch (IndexOutOfRangeException io) { //配列範囲外例外処理
Console.WriteLine(io); //io.ToString()の結果文字列を表示
Console.WriteLine("[io]---------");
Console.WriteLine(io.Source); //アプリケーション名
Console.WriteLine("[io.Source]---------");
Console.WriteLine(io.Message); //例外メッセージ
Console.WriteLine("[io.Message]---------");
Console.WriteLine(io.ToString()); //例外内容を示す文字列
Console.WriteLine("[io.ToString()]---------");
Console.WriteLine(io.TargetSite); //メソッド名
Console.WriteLine("[io.TargetSite]---------");
}
}
}
p.328(catchブロックの書式)
・p.324の通り「catch{…}」のみで引数のない形式があり、全例外を無条件で捕捉する
・「catch(例外クラス名 引数){…}」とすることで、捕捉する例外(例外の基本クラス)を指定し、そのメンバを引数経由で利用できる
・メンバを用いる必要がなければ「catch(例外クラス名){…}」と引数を省略することも可能
・「if~else if」構造と同様に、1つのtryに対して複数のcatchを記述できる
・ただし、「if~else if」構造と同様に、上から順に評価されることに注意
・継承関係のある例外クラスを指定する場合は、派生クラスを上にしないと、基本クラスでcatchしてしまうことになり
「絶対に実行されないソース」となるためエラーになる。
p.329 exception04.cs
//p.329 exception04.cs
using System;
class exception04 {
public static void Main() {
int x = 10, y = 0;
try { //例外処理範囲
Console.WriteLine("{0} / {1} = {2}", x, y, x / y); //ゼロ除算例外発生
} catch (IndexOutOfRangeException io) { //配列範囲外例外処理(実行対象外)
Console.WriteLine(io.Message);
} catch (DivideByZeroException) { //ゼロ除算例外処理
Console.WriteLine("0で割っちゃだめだよ"); //これ以降のcatchは無効
} catch (Exception ex) { //例外処理(無視)
Console.WriteLine(ex.Message);
}
}
}
アレンジ演習:p.329 exception04.cs
・p.330のように書き換えるとエラーになることを確認しよう
作成例
//p.329 exception04.cs
using System;
class exception04 {
public static void Main() {
int x = 10, y = 0;
try { //例外処理範囲
Console.WriteLine("{0} / {1} = {2}", x, y, x / y); //ゼロ除算例外発生
} catch (Exception ex) { //例外処理(無視)
Console.WriteLine(ex.Message);
} catch (IndexOutOfRangeException io) { //配列範囲外例外処理←エラー
Console.WriteLine(io.Message);
} catch (DivideByZeroException) { //ゼロ除算例外処理←エラー
Console.WriteLine("0で割っちゃだめだよ");
}
}
}
アレンジ演習:p.329 exception04.cs
・基に戻してから、tryブロックの中を工夫して、3つの例外処理を全て試せるようにしよう ・コンソールから入力した値によって、配列範囲外例外、ゼロ除算例外、オーバフロー例外が起こるようにしよう ・オーバフロー例外はExceptionクラスで捕捉すれば良い
作成例
//アレンジ演習:p.329 exception04.cs
using System;
class exception04 {
public static void Main() {
int[] x = new int[2];
try { //例外処理範囲
Console.Write("添字:");
int i = int.Parse(Console.ReadLine()); //オーバーフロー、形式例外発生可能性
Console.Write("値:");
x[i] = int.Parse(Console.ReadLine()); //配列範囲外、オーバーフロー、形式例外発生可能性
Console.WriteLine("10 / {0} = {1}", x[i], 10 / x[i]); //ゼロ除算例外発生可能性
} catch (IndexOutOfRangeException io) { //配列範囲外例外処理
Console.WriteLine(io.Message);
} catch (DivideByZeroException) { //ゼロ除算例外処理
Console.WriteLine("0で割っちゃだめだよ");
} catch (Exception ex) { //例外処理(オーバーフロー、形式例外はここで)
Console.WriteLine("その他の例外:" + ex.Message);
}
}
}
p.330 finallyブロック
・例外処理において例外発生の有無にかかわらず実行したい処理がある場合、finallyブロックを用いて記述する ・利用例としては、データベースや通信などの外部リソースからの切断や、実行者への通知などのような後始末が多い。 ・例外が発生しなければ、tryブロックの末尾の次にfinallyブロックの内容が実行され処理続行 ・例外が発生しcatchされた場合は、catchブロックの末尾の次にfinallyブロックの内容が実行され処理続行 ・例外が発生しcatchされなかった場合は、finallyブロックの内容が実行され、その末尾の後で異常終了
p.331 exception05.cs
//p.331 exception05.cs
using System;
class exception05 {
public static void Main() {
string strWarusu; //入力用
int x; //変換結果
bool bEnd = false; //終了フラグ(初期値:オフ)
while (true) { //無限ループ
Console.Write("割る数--- ");
strWarusu = Console.ReadLine();
try { //例外処理対象
x = int.Parse(strWarusu); //形式例外、オーバーフロー例外発生可能性
Console.WriteLine("10 / {0} = {1}", x, 10 / x); //ゼロ除算例外可能性
} catch (DivideByZeroException d) { //ゼロ除算例外処理
Console.WriteLine(d.Message);
} catch (Exception e) { //その他の例外処理
Console.WriteLine(e.Message);
} finally { //例外の有無にかかわらず実行すること
Console.Write("続けますか(Y/N)---");
if (Console.ReadLine()[0] == 'N') {
bEnd = true; //終了フラグをオンに
}
}
if (bEnd) { //終了フラグがオン?
break; //繰返し終了=処理終了
}
}
}
}
p.333 throw文
・プログラマが自ら例外を投げることができる ・例えば、例外と同様に扱いたいような状態(例:身長が負の数)の時に便利 ・また、catchブロックの中で例外処理を呼び出し元に依頼したい場合にも有効 ※ 通常、呼ばれているメソッドの中の例外処理では、対処が決められない場合や、対処が重複してしまうことが多いので、 呼んでいる側に任せる方が良い ・書式: throw 例外オブジェクト; ※ catchの引数で受け取った例外オブジェクトを用いることができるが、必要であれば、下記の書式で例外オブジェクトを生成して投げると良い throw new 例外クラス();
p.334 exception06.cs
//p.334 exception06.cs
using System;
class MyClassA {
public void Calc() {
int x = 10, y = 0;
int[] arr = new int[5] { 1, 2, 3, 4, 5 };
try { //例外処理範囲
Console.WriteLine("{0}, {1}", arr[x], x / y); //配列範囲外例外発生
} catch (IndexOutOfRangeException i) { //配列範囲外例外処理
Console.WriteLine(i.Message);
DivideByZeroException d = new DivideByZeroException(); //ゼロ除算例外オブジェクト生成
Console.WriteLine("外側にthrowします");
throw d; //ゼロ除算例外オブジェクトを投げる
}
}
}
class MyClassB {
public void Calc() {
MyClassA a = new MyClassA();
try { //例外処理範囲
a.Calc(); //ゼロ除算例外発生(投げられてくる)
} catch (DivideByZeroException d) { //ゼロ除算例外処理
Console.WriteLine("外側のcatch節です");
Console.WriteLine(d.Message);
}
}
}
class exception06 {
public static void Main() {
MyClassB b = new MyClassB();
b.Calc(); //結果的に例外が捕捉されるので正常終了する
}
}
p.336(単独のthrow文)
・catchブロックの中で「throw;」と単独で実行すると、発生中の例外のオブジェクトを投げる
p.336 exception07.cs
//p.336 exception07.cs
using System;
class MyClass {
int x = 5, y = 0;
public void Calc() {
try { //例外処理対象
Console.WriteLine("{0}", x / y); //ゼロ除算例外発生
} catch (DivideByZeroException d) { //ゼロ除算例外処理
Console.WriteLine(d.Message);
throw; //呼出し元へゼロ除算例外を投げる
}
}
}
class MyClassB {
public static void Main() {
MyClass mc = new MyClass();
try { //例外処理対象
mc.Calc(); //ゼロ除算例外発生(投げられてくる)
} catch (DivideByZeroException d) { //ゼロ除算例外処理
Console.WriteLine(d.TargetSite); //メソッド名を表示
}
}
}
p.337 tryのネスト
・tryブロックの中に、さらにtry-catch構造を記述できる ・これにより、複数のtry-catch構造で重複している記述があれば、外側のtryブロックのcatchでまとめて扱うこと可能
p.337 exception08.cs
//p.337 exception08.cs
using System;
class exception08 {
public static void Main() {
int x = 10, y = 0;
try { //外側の例外処理対象(この中に複数のゼロ除算例外を投げる処理を記述可)
try { //内側の例外処理対象
Console.WriteLine("{0},{1}", x / y); //ゼロ除算例外発生
} catch (IndexOutOfRangeException i) { //配列範囲外例外処理(対象外)
Console.WriteLine(i.Message);
} //ここで例外発生状態のままになる
} catch (DivideByZeroException d) { //ゼロ除算例外処理(対象)
Console.WriteLine(d.Message); //実行される
}
}
}
p.338 独自の例外を作る
・全ての例外クラスの基本クラスであるExcptionクラスは、プログラマが継承することを許可しており、 自前の派生クラスもまた例外処理に利用可能 ・加えて、一部を除く、既存の派生例外クラスも継承が可能 ・よって、Excptionクラスまたは、特性の近い派生例外クラスを継承して、独自の例外を作ると良い 例: 形式が定めてある入力でルール不一致の入力があれば形式例外の派生クラスのオブジェクトを投げると良い ・独自の例外においては、ToString()メソッドやMessageプロパティをオーバライドして、説明文を持たせると良い (必須の場合もある)
p.339 exception09.cs
//p.339 exception09.cs
using System;
class MyEx : DivideByZeroException { //ゼロ除算例外クラスを継承した自前例外クラス
public new string Message = "0で割るエラーです"; //メッセージを上書き
public new string HelpLink = "http://www.kumei.ne.jp/c_lang/"; //リンクを上書き
public override string ToString() { //メソッドをオーバーライド
return "0で割ってはいけません!!";
}
}
class exception09 {
public static void Main() {
int x;
Console.Write("割る数(整数)--- ");
string strWaru = Console.ReadLine();
try { //例外処理範囲
x = int.Parse(strWaru); //形式例外、オーバーフロー例外の可能性
if (x == 0) {
throw new MyEx(); //自前の例外を投げる
}
Console.WriteLine("12 / {0} = {1}", x, 12 / x); //ゼロ除算例外は発生しない
} catch (MyEx me) { //自前の例外の処理
Console.WriteLine(me.ToString()); //オーバーライドメソッドを呼ぶ
Console.WriteLine(me.Message); //上書きしたメッセージ
Console.WriteLine(me.HelpLink + "を参照"); //上書きしたリンク
} catch (Exception e) { //形式例外、オーバーフロー例外処理
Console.WriteLine(e.Message);
}
}
}
p.341 checkedとunchecked
・int型などの数値型変数への代入の時点で、その型の範囲を超えているとオーバーフロー例外が発生する ・しかし、計算途中における型の範囲の超過では発生せず、誤った結果になる ・これを阻止したい場合は、checkedブロックの中に置けばよい ・よって、例外処理も必要であれば、tryブロックの中にcheckedブロックを記述すると良い ・テキストでは「checked()」形式も紹介されているが、推奨されない場合がある ・なお、checkedブロックの中の一部分について、その効果を無効化したい場合は、その範囲をuncheckedブロックにすればよい
p.343 checked02.cs
//p.343 checked02.cs
using System;
class checked02 {
public static void Main() {
int x, y, z;
try { //例外処理対象
checked { //計算途中のオーバーフローを例外とする範囲
x = int.MaxValue;
y = 1;
z = x + y; //計算途中のオーバーフロー発生⇒例外を投げる
Console.WriteLine(z); //実行されない
}
} catch (OverflowException o) { //オーバーフロー例外処理
Console.WriteLine(o.Message);
Console.WriteLine(o.StackTrace);
}
}
}
p.344 練習問題 ヒント
・「forループで」とあるが、終了条件の指定がないので「for (byte b = 1; ; b++)」という無限ループになる ・動作がわかるように、for文の中で値を表示すると良い ・「catch」でオーバーフロー例外の処理を記述し、発生した旨を表示したら、breakで無限ループを抜けること
作成例
//p.344 練習問題
using System;
class ex13 {
public static void Main() {
byte x = 1;
for (byte b = 1; ; b++) { //無限ループ
try { //例外処理対象
checked { //計算途中のオーバーフローを例外とする範囲
x *= b; ////計算途中のオーバーフロー発生⇒例外を投げる
Console.WriteLine("途中:" + x);
}
} catch (OverflowException o) { //オーバーフロー例外処理
Console.WriteLine(o.Message);
break;
}
}
Console.WriteLine("最終:" + x);
}
}
販売本数ランキング 今回トップは「Marvel’s Spider-Man 2(PS5)」GO!
業績回復鮮明のシリコンスタジオ、ゲームエンジンの需要高まる自動車業界でも存在感を発揮【ゲーム企業の決算を読む】GO!
Apple、「GeForce NOW」などゲームストリーミングサービスの制限緩和―単一アプリでフル機能が提供可能に GO!
ブロックチェーンゲーム『キャプテン翼 -RIVALS-』、スマホアプリ版のサービス開始 事前登録は10万人突破 GO!
『FF16』が日本PS5部門でトップに!2023年のPS Store年間ダウンロードランキング公開 GO!
3DS/Wii Uのオンラインプレイサービスが2024年4月9日に終了へ GO!
『パルワールド』の“ポケモン”Modがコミュニティで波紋呼ぶ―動画等は任天堂により権利者削除、モデルの出処も怪しい GO!
ポケモン社が類似の他社ゲームに対して「いかなる利用も許諾しておりません」とコメントを発表。『パルワールド』を示唆か。知的財産権の侵害については「調査を行った上で、適切な対応を取っていく所存」GO!
マイクロソフト約1,900人解雇―アクティビジョン買収でゲーム部門全体の約8%にあたる人員整理 GO!
開発期間は6年以上…Blizzardレイオフで開発中止の未発表サバイバルゲームは社内で好評だった模様 GO!
リリースからたった4ヶ月での終了告知…ネクソンの無料大規模対戦アクション『Warhaven』4月にサービス終了へ GO!