3D避けゲー
3D避けゲームを作ってみよう
体験入学用教材(30~40分)
今回は、3本のレーンを左右に移動しながら、前から来る敵を避ける3Dゲームを作ります。
完成しているプログラムに、少しずつ穴埋めをしていきます。
1か所直したら、必ずビルドして実行し、ゲーム画面の変化を確認しましょう。
今日の目標
今日の体験では、次のことを学びます。
- プログラムから3Dモデルを読み込む方法
- 数学の式を使って、移動の速さを求める方法
- 速さ × 時間で、そのフレームの移動距離を求める方法
- キー入力でキャラクターを動かす方法
- 時間に応じてスコアを増やす方法
- 敵に当たったときにゲームオーバーにする方法
今日作るゲーム
このゲームでは、次のようなことが起こります。
- プレイヤーが3Dで表示される
- 道路が前へ流れる
- 敵が前から来る
- 左右キーで3本のレーンを移動できる
- 時間がたつとスコアが増える
- 敵に当たるとゲームオーバーになる
【画像挿入①】完成したゲーム画面のスクリーンショットを入れる
例:プレイヤー、道路、敵、スコアが見えている画面
プログラムの中心は PlayScene
今回いちばん注目するのは PlayScene です。
PlayScene は、ゲーム全体の進行を書く台本の役目です。
たとえば、次のように命令を出します。
player_->SetRunState();
stage_->StartScroll();
enemies_->SetSpawning(true);
つまり PlayScene を読むと、
- いつゲームが始まるのか
- プレイヤーがどう動くのか
- スコアがどう増えるのか
- いつゲームオーバーになるのか
が分かります。
今日の進め方
今日は、次の順番で進めます。
- プレイヤーのモデルを表示する
- 移動の速さを計算する
- 1フレームで進む距離を計算する
- 左右キーでレーン移動させる
- スコアを増やす
- 敵に当たったらゲームオーバーにする
大事
1問終わるごとに、ビルド → 実行 → 動作確認をしてください。
課題01 プレイヤーを表示しよう
学ぶこと
- ファイルパス
- 文字列
- 3Dモデルの読み込み
説明
ゲームの中の3Dモデルは、プログラムの中に直接入っているわけではありません。
プログラムから、ファイルの場所を指定して読み込みます。
たとえば、プレイヤーのモデルは次のファイルです。
Assets/run.mv1
穴埋め問題
次の空欄に、プレイヤーのファイル名を入れてください。
constexpr const char* PLAYER_MODEL_PATH =
"Assets/______.mv1";
ヒント
run
想定解答
constexpr const char* PLAYER_MODEL_PATH =
"Assets/run.mv1";
確認
- ビルドして実行する
- プレイヤーの3Dモデルが表示されるか確認する
【画像挿入②】プレイヤーが表示されたゲーム画面
課題02 1レーン分の移動速度を求めよう
学ぶこと
- 距離
- 時間
- 速さ
- 定数
- 割り算
説明
このゲームでは、プレイヤーは3本のレーンを移動します。
レーンの位置は次のようになっています。
$$ 左 = -12, \quad 中央 = 0, \quad 右 = 12 $$
つまり、中央レーンから右レーンまでの距離は 12 です。
今回は、0.20秒でちょうど1レーン分移動させたいとします。
ここで使う式は次です。
$$ 距離 = 速さ \times 時間 $$
この式を、速さについて変形すると
$$ 速さ = \frac{距離}{時間} $$
になります。
今回の数値を代入すると、
$$ 速さ = \frac{12.0}{0.20} = 60.0 $$
となります。
つまり、1秒間に60進む速さにすれば、0.20秒で1レーン分移動できます。
【画像挿入③】レーンと距離の図を入れる
推奨画像ファイル:
lane_speed_diagram.png
穴埋め問題
constexpr float LANE_DISTANCE = 12.0f;
constexpr float LANE_MOVE_TIME = 0.20f;
// 速さ = 距離 ÷ 時間
constexpr float PLAYER_MOVE_SPEED =
______ / ______;
ヒント
- 距離は
LANE_DISTANCE - 時間は
LANE_MOVE_TIME
想定解答
constexpr float PLAYER_MOVE_SPEED =
LANE_DISTANCE / LANE_MOVE_TIME;
確認
- ビルドして実行する
- まだ左右キーは未完成なので、この時点では見た目の変化がなくてもよい
- ただし、ビルドが通ることを確認する
課題03 1フレームで進む距離を求めよう
学ぶこと
deltaTime- 掛け算
- 毎フレームの移動距離
説明
ゲームは、1回だけ画面を描いて終わりではありません。
1秒間に何十回も、次の処理をくり返しています。
入力を調べる
↓
ゲームの状態を更新する
↓
画面を描画する
この1回分を1フレームと呼びます。
フレームごとに経過した時間を deltaTime といいます。
1フレームで進む距離は、次の式で求めます。
$$ 移動距離 = 速さ \times 時間 $$
プログラムでは、
$$ moveDistance = playerMoveSpeed_ \times deltaTime $$
です。
たとえば、
$$ playerMoveSpeed_ = 60.0, \quad deltaTime = 0.016 $$
なら、
$$ moveDistance = 60.0 \times 0.016 = 0.96 $$
となります。
つまり、そのフレームでは 0.96だけ進む ことになります。
【画像挿入④】1フレームごとの移動距離の図を入れる
推奨画像ファイル:
frame_move_diagram.png
穴埋め問題
UpdatePlayerLaneMove(
______ * ______
);
ヒント
- 速さは
playerMoveSpeed_ - 時間は
deltaTime
想定解答
UpdatePlayerLaneMove(
playerMoveSpeed_ * deltaTime
);
補足
この中の詳しい内部処理、たとえば
- レーンの中央でぴったり止まる処理
- 2回素早く押すと2レーン移動する処理
は、今回は完成済みです。
今日は「速さ × 時間で距離を出している」という考え方だけ押さえましょう。
確認
- ビルドして実行する
- この時点ではまだ左右入力が未完成なので、見た目の変化がなくてもよい
- ただし、移動距離の計算式が書けたことを確認する
課題04 左右キーでプレイヤーを動かそう
学ぶこと
- キー入力
if- 関数呼び出し
説明
今度は、左右キーが押されたときに、レーン移動の命令を出します。
今回使う命令は次の2つです。
MovePlayerLeft();
MovePlayerRight();
穴埋め問題
if (Input::IsKeyDown(KEY_INPUT_LEFT))
{
______();
}
else if (Input::IsKeyDown(KEY_INPUT_RIGHT))
{
______();
}
ヒント
- 左へ動かす:
MovePlayerLeft - 右へ動かす:
MovePlayerRight
想定解答
if (Input::IsKeyDown(KEY_INPUT_LEFT))
{
MovePlayerLeft();
}
else if (Input::IsKeyDown(KEY_INPUT_RIGHT))
{
MovePlayerRight();
}
確認
- ビルドして実行する
- 左右キーでプレイヤーがレーン移動するか確認する
- 素早く2回押すと、2レーン移動できるか確認する
【画像挿入⑤】左右移動しているゲーム画面
課題05 スコアを増やそう
学ぶこと
- 変数
- 足し算
- 時間経過
説明
ゲーム中は、時間がたつほどスコアが増えます。
今回は、1秒あたり100点ずつ増えるようにしています。
$$ 1秒あたりの得点 = 100 $$
1フレームで増える点数は、
$$ 今回増える得点 = 1秒あたりの得点 \times deltaTime $$
です。
そのため、スコアの更新式は次のようになります。
$$ score_ = score_ + SCORE_PER_SECOND \times deltaTime $$
穴埋め問題
score_ = score_ + ______ * ______;
ヒント
- 1秒あたりの得点:
SCORE_PER_SECOND - 今回経過した時間:
deltaTime
想定解答
score_ = score_ + SCORE_PER_SECOND * deltaTime;
確認
- ビルドして実行する
- プレイ中、スコアが少しずつ増えていくか確認する
【画像挿入⑥】スコアが増えているゲーム画面
課題06 敵に当たったらゲームオーバーにしよう
学ぶこと
- 条件分岐
- 真偽値
- 当たり判定
説明
敵とプレイヤーが当たったかどうかは、IsHit() という関数で調べます。
- 当たっているとき:
true - 当たっていないとき:
false
つまり、
if (IsHit())
は、**「もし敵に当たっていたら」**という意味です。
穴埋め問題
if (______)
{
LoadDeadMotion();
ChangeToGameOver();
return;
}
ヒント
IsHit()
想定解答
if (IsHit())
{
LoadDeadMotion();
ChangeToGameOver();
return;
}
確認
- ビルドして実行する
- 敵に当たるとゲームオーバーになるか確認する
【画像挿入⑦】ゲームオーバー直前、またはゲームオーバー画面
まとめ
今回の体験では、完成済みのゲームに穴埋めをしながら、次のことを学びました。
- 3Dモデルの読み込み
- 数式を使った移動速度の計算
deltaTimeを使った1フレームの移動距離- キー入力によるレーン移動
- スコアの更新
- 当たり判定によるゲームオーバー
今日の特に大切な式は、次の2つです。
1. 速さを求める式
$$ 速さ = \frac{距離}{時間} $$
2. 1フレームで進む距離を求める式
$$ 移動距離 = 速さ \times 時間 $$
ゲームプログラムでは、こうした数学の式が、そのままキャラクターの動きになります。
最後の自由調整
時間に余裕があれば、次の値を変えて遊んでみましょう。
constexpr float LANE_MOVE_TIME = 0.20f;
constexpr float SCORE_PER_SECOND = 100.0f;
試してみること
LANE_MOVE_TIMEを小さくすると、レーン移動はどう変わるかLANE_MOVE_TIMEを大きくすると、操作感はどう変わるかSCORE_PER_SECONDを大きくすると、スコアはどう変わるか
教員用メモ
この教材で完成済みにしているもの
- 背景画像の読み込み
- カウントダウン
- ゲーム開始命令
- 道路と敵の移動
- 位置補正
- 2レーン移動の内部処理
- ゲームオーバー遷移の内部処理
画像ファイル案
- 画像① 完成ゲーム画面
- 画像② プレイヤー表示画面
- 画像③
lane_speed_diagram.png - 画像④
frame_move_diagram.png - 画像⑤ 左右移動画面
- 画像⑥ スコア増加画面
- 画像⑦ ゲームオーバー画面