避けゲー
体験実習 実習課題 避けゲー
準備目的(これを体験で学ぶ)
ゲームエンジニアリングの基礎を知るGame.zip- C++
を解凍し、と Visual Studio2022で実際に動くゲームを作る流れを体験する - ソースコード
で付属→ ビルド → 実行ファイル という開発のソリューショ基本パイプライン/を理解する - プロ
ジェクトグラミングの基本(変数/順次・分岐・反復の3つの構造)を開く。使えるようになる imageフォルダエラーが出たときの「原因を調べて直す」デバッグの流れを身にタつける- DxLib(ゲーム用ライ
トル用・ブラリザルト用・猫用・蛇用)を使って画面に描画し、ゲームの画像構成要素を配置す組み立てる。
ゲーム構成(例:title.png, result.png, cat.png, snake.pngこの体験で作る「避けゲー」の要素)
課題01:実行してみてみよう
- タイトル画面
の背景を(スペースキーでスタート) - プレイヤー(左右キーで移動)
- 敵(上からランダムに落ちてくる)
- 衝突判定(ぶつかったらゲームオーバー)
- スコア(何秒生き残ったか)
- リザルト画面(結果表示)
実習課題(ゲームをうごかしてみよう)
ファイル:ここから先は、実習ステップ(やってみよう)PlayScene.cpp
void TitleScene::Init()
{
isFinished = false;
// ToDo: 背景課題01:画像を一度だけロ読み込んで表示させよ
- 説明: プレイヤー
ドするコードを追加しよう
if ( bgImage == -1 )
{
bgImage = LoadGraph( "______" ); // ← と敵の画像ファイル名を入力
}
}
穴埋め:正しく読み込まないと表示されない。に "image/cat.png"、"______"playerImageに "image/snake.png" を"image/title.png"enemyImage入れて F5 で確認読み込む作業を行う。
課題02:修正前(元のコード) 36~41行目辺り
if( playerImage == -1 )
//#課題01 LoadGraph関数を使って画像を読み込む
playerImage = LoadGraph( "image/xxx.XXX.png" );
if( enemyImage == -1 )
//#課題01 LoadGraph関数を使って画像を読み込む
enemyImage = LoadGraph( "image/xxxx.XXXX.png" );
注意点
- 画像ファイルがプロジェクト内に存在することを確認する。パスのスペルミスに注意。
やること
Init()内のLoadGraph呼び出しを上記のように書き換える。- ビルド & 実行してプレイヤーと敵の画像が表示されるか確認。
- 表示されなければファイル名やパスをチェック。
確認ポイント
- プレイヤーと敵の画像が画面に表示されているか?
- 画像が表示されない場合、ファイルパスの間違いやファイルの存在を確認したか?
課題02:プレイヤーの移動速度を設定しよう
スピードを表す変数に値を入れよう
23行目辺り
C++(他のどの言語でもそうだけど)プログラミングでは、いろいろなものを変数として表現します。
プレイヤーの位置は、初期位置と、スピード、経過時間などの変数から毎フレーム計算されます。
//課題02 プレイヤーの移動速度を設定
playerSpeed = _____f; // 例: 150.0f- 左右キーを押したときに動く量が変わる。値を変えても動きを試してみよう。
用語と基礎説明
- プログラミングの変数と数学の変数の違い:数学で習う や は「値がわからないもの(代入される記号)」として出てくるけど、プログラムの変数は「値を入れておける箱」で、中の値を変えたり取り出したりできる。
例:playerSpeedという箱に150.0fを入れておいて、使うときにその中身を取り出して移動に使う。 - 座標軸:画面の左上が (0,0) で、右に行くほど X が増え、下に行くほど Y が増える。
(0,0) ┌─────────────> X │ │ v Y - ピクセル:画面を構成する最小の点。位置やサイズはピクセル単位で考える。
例:画像が 64x64 ピクセルなら、横幅が64ピクセル、高さが64ピクセル。 - 画像サイズ:キャラクター画像の幅と高さ。描画位置は中心を基準にしているので、
player.x - CHARACTER_SIZE/2などで左上を計算している。 - 移動速度(playerSpeed):1秒あたり何ピクセル動くかを表す。
player.x += playerSpeed * deltaTime;で実際の移動距離になる。
例:playerSpeed = 150.0f;なら 1秒で 150 ピクセル移動する(0.5秒なら 75 ピクセル)。
//課題02 プレイヤーの移動速度を設定
playerSpeed = 0.0f;
//課題03 敵の落下速度を設定
enemySpeed = 0.0f;
//課題04 敵の生成タイマーを加算
enemySpawnTimer = enemySpawnTimer + deltaTime;
//課題05 スコアの値を更新
score += deltaTime;
課題03:プレイ画面の初期設定をしよう
ファイル:PlayScene.cpp
void PlayScene::Init()
{
isFinished = false;
enemies.clear();
// ToDo: プレイヤー初期位置を画面中央にセット(Y座標は地面から半分上)
player.x = WINDOW_WIDTH / 2;
player.y = GROUND_Y - CHARACTER_SIZE / 2;
// ToDo: プレイヤーの移動速度を設定しよう
playerSpeed = _____f; // 例:150.0f
// ToDo: 敵の落下速度を設定しようプレイヤーは動き始めたけど、今度は敵が落ちてきません。
敵のスピードも、(わざとらしく?)0になってるので、スピードを設定してあげてください。
敵の落下速度を設定しよう
26行目辺り?
//課題03 敵の落下速度を設定
enemySpeed = _____f; // 例:: 150.0f
- 敵がどれくらい速く落ちてくるかを調整する。難易度に関係する。
- 今の画面のサイズは横✕縦=960x640、640ピクセルを2秒で落ちるにはスピードは?みたいに決める
課題04:敵の生成(出現)タイマーを増やそう
敵がまだ出現しない。。。
敵は、一定時間ごとに設定されたタイマーによって、定期的に現れる仕組みになっている。
現状では、タイマーが動いていないのでタイマーを動かして敵を出現させよう!
ここでは、ゲームの進行管理で重要なフレームについて学ぶよ!
// ToDo:課題04 敵の出現間隔を設定しよう
enemySpawnInterval = _____f; // 例:0.5f
// ToDo: 敵生成タイマー初期化を加算
enemySpawnTimer = 0.0f;enemySpawnTimer + deltaTime; // ToDo: スコア初ここを書いて敵が定期化
score = 0.0;
// ToDo: 画像とフォントを一度だけロード的に出るようにするコードを追加しよう
if ( playerImage == -1 ) playerImage = LoadGraph( "______" ); // cat.png
if ( enemyImage == -1 ) enemyImage = LoadGraph( "______" ); // snake.png
if ( scoreFont == -1 ) scoreFont = CreateFontToHandle( "メイリオ", 64, 0, DX_FONTTYPE_NORMAL );
}
enemySpawnInterval を超えたら CreateEnemy() が呼ばれる。
仕組みの補足(フレームごとの動きとタイマーの関係)
- ゲームは
穴埋め例「フレーム」という単位で動いている。1フレームごとに画面を更新する。普通は1秒に60回くらい(60FPS)呼ばれる。 - 毎フレーム、次の順番で処理が行われる:
playerSpeed = 150.0f;Update()(状態を変える:敵を落とす、タイマーを増やす、入力を読む)enemySpeedDraw()(画面に今の状態を描く)
deltaTime は前のフレームからの時間(たとえば 1/60 秒 = 150.0f;約0.0167)。これを enemySpawnTimer に足して「時間を数える」。enemySpawnTimer が設定された間隔(enemySpawnInterval)を超えたら、その時点で新しい敵が出現する。つまり「何秒たったら出すか」をフレームをまたいで数えている。- 例:
enemySpawnInterval = 0.5f;5f - なら、0.5秒ごとに敵が出る。60FPS なら約30フレームごとに
LoadGraph("image/cat.png"CreateEnemy() /が呼ばれる。
式 LoadGraph("image/snake.png")enemySpawnTimer = enemySpawnTimer + deltaTime について(数学とプログラムの違い)
- 数学の式だと左辺と右辺に同じ記号が出てくると変に見えるが、プログラムでは「今の値に新しい分を足して更新する」操作として普通に使う。
これは「箱の中身を取り出して、そこに時間分を足して、また箱に戻す」処理と考えられる。
例:今のタイマーが0.3秒で、deltaTime が0.0167なら、次のフレームでは 0.3167 秒になる。
課題04:プ05:スコアの更新を有効にしよう
フレイヤーム間時間(deltaTime)を左右に動かそ使ってスコアの変数を変化させよう
ファイル:PlayScene.cpp
93行目辺り
void//課題05 PlayScene::Update(floatスコアの値を更新
deltaTime)score {= score + ?????; // ToDo: 左右キーで player.x を更新しよう
if ( CheckHitKey( KEY_INPUT_LEFT ) )
{
player.x -= _____f * deltaTime; // playerSpeed を使おう
}
else if ( CheckHitKey( KEY_INPUT_RIGHT ) )
{
player.x += _____f * deltaTime;
}
// (以降は生き残った時間がスコアや敵生成になど…)
}る
仕組みの補足(敵の出現タイマーと同じ考え方)
穴埋め:スコアも playerSpeedenemySpawnTimer と同じで、毎フレーム deltaTime を入足して時間を数えている。score += deltaTime; は「生き残った時間」がそのまま点数になる仕組み。60FPSなら1フレームごとに約0.0167ずつ増える。
画面表示の豆知識
- このスコア表示や文字の描画は
DxLib が簡単にしてくれて動いるから少ないコードで書ける。
もし DirectX 単体で文字を表示しようとすると、フォントを読み込んでテクスチャを作確認り、描画用の頂点バッファやシェーダを用意する必要があり、かなりコードが長くなる。
DxLib はそういう面倒な下準備を隠してくれていて、DrawFormatStringToHandle などを呼ぶだけで表示できる。
課題05:敵を定期的に生成しよう
ファイル:PlayScene.cpp (同じ Update 内)
// 一定時間後、敵生成
// ToDo: タイマーに経過時間を加算
enemySpawnTimer += Scene::DeltaTime(); // または deltaTime
// ToDo: タイマーが間隔を超えたら CreateEnemy() を呼び出し、タイマーをリセット
if ( enemySpawnTimer > enemySpawnInterval )
{
CreateEnemy();
}
確認:F5 で蛇(enemy)が落ちてくるかチェック。
課題06:当たり判定をつけよう
当たり判定が働いたら敵を消す/終了にする(条件分岐、関数呼び出し)
86行目辺り
当たり判定の処理を呼び出してみよう。当たり判定の処理は、別のところにIsHit()として記述されています。
それを必要なところで呼び出すと、すべての敵と、プレイヤーがぶつかっているかどうかを判定し、その結果を真偽値(true, false)で、知らせてくれます。
//課題06 当たり判定
if( 0 ) //ここで当たり判定の処理を呼び出してみよう
{
ClearEnemies(); // 敵を消してみる(実装済み関数を使う)
// 課題07 リザルト画面へ遷移しようファイル:PlayScene.cpp
bool PlayScene::IsHit()
{
for ( auto& e : enemies )
{
float dx = player.x - e.x;
float dy = player.y - e.y;
// ToDo: 衝突判定を完成させよう行(距離の2乗 <= 半径の2乗下につなげる)
if ( (dx*dx) + (dy*dy) <= _____ )
{
return true;
}
}
return false;
}
説明:ついに当たり判定です
ゲームにおける当たり判定とは、画面上のもの同士が「ぶつかったかどうか」を調べて、それに応じた反応(ダメージを受ける/ゲームオーバーになる/アイテムを取るなど)を起こす仕組みです。
このプログラムでは、キャラクター(プレイヤー)と敵のぶつかり判定に「円と円の距離」を使っています。
プレイヤーと敵の中心の距離を計算して、それが一定以内(キャラクターのサイズの2乗)ならぶつかったと判断します。これは簡単で速い衝突チェックの方法です。
![]()
条件分岐(if文)と真偽値
- 当たり判定の結果は「ぶつかっているか」「ぶつかっていないか」の 真(true)/偽(false) で表される値(真偽値)として返される。
voidIsHit() PlayScene::Update(floatはこれを返す関数で、ぶつかっていれば deltaTime)true {を、そうでなければ //false …(敵更新・描画の直前)
if (を返す。 if( IsHit() ) { isFinished = true; // リザルトシーンへ... } }
- のように書くと、「ぶつかったときだけ中の処理をする」ことができる。これが
穴埋め条件分岐: で、プログラミングの3つの基本構造(順次・分岐・反復)のひとつで非常に重要。処理を状況に応じて切り替えるための仕組み。 - ここでは「当たっていたら敵を消す/ゲームオーバーにする」といった反応を
CHARACTER_SIZE * CHARACTER_SIZEif を入れ使って確認実現している。
サブルーチン(関数)としての IsHit() の役割
IsHit() という関数が別に作られていて、毎フレームの中で呼ばれています。
ゲームの処理は1フレームごとに Update() → Draw() の順に動いていて、その Update() の中で状態チェック(衝突の有無など)を行うために IsHit() を呼び出します。- このように「特定の処理をまとめて名前をつけ、必要なときに呼び出す仕組み」を サブルーチン(関数、ルーチン)と呼びます。
サブルーチンを使うと、同じ処理を何度も書かずにすみ、プログラムが整理されて読みやすく、直しやすくなります。
###課
課題07:リザルト画面の背景へ移行する処理を表示追加しよう
ファイル:ResultScene.cpp
89行目辺り
void ResultScene::Init()
{
isFinished = false; //課題06 ToDo:当たり判定
背景画像をロードif( if ( bgImage == -10 )
{
bgImage = LoadGraph( "______" ClearEnemies();
//課題07 たとえばリザルト画面へ移行
"image/result.png"
}//->ここに移行処理を追加
}
- 衝突後にリザルト用のシーンに変えるコードを追加。
穴埋め補足:"image/result.png"GoToResultScene() という関数が用意されている
画面遷移と Scene(状態遷移)の考え方
- 多くのゲームは「Scene(シーン)」という単位で画面ごとの役割を
指定分けて管理し、スペースキーでている。たとえばタイトルに戻画面、プレイ画面、リザルト画面がそれぞれ別の Scene。 - ある条件が満たされると Scene を切り替える。これを 状態遷移 と
を確認呼ぶ。たとえば当たり判定で衝突したら「プレイ中」から「リザルト」へ移る、といった具合。
発展課題
BGMを追加GoToResultScene() や changeScene(...) は状態を変える関数で、現在の Scene の状態を終了してみ新しい Scene に移る(遷移する)処理をまとめている。ハイスコアをファイルに書き出してみるプレイヤ状態遷移は画面遷移だけでなく、キャラクターの状態管理(立っている・走っている・ジャンプ中など)やアニメーションの切り替え、敵のAIの振る舞いの切り替えなど、ゲームのあらゆる部分で使われている。- 状態遷移を整理しておくと、「何が起きたら次に何を表示するか/どう振る舞うか」が明確になり、ゲーム全体の流れや作りやすさが向上する。
発展課題
ゲームの中で気になるところない?
- プレイヤーが画面の外に出るまで移動可能
- どうやって、移動を制限するかな?
- 時間があったら試してみよう
- 敵の見た目の変更
- 敵の出現間隔の変更
- 敵の落下位置をプレイヤーキャラクター付近に変更
- 敵の落下スピードを徐々に上げる




