Appearance
第03回 入力と移動
前回: 第02回 ゲームループと描画 / 次回: 第04回 構造体と矩形
今回の授業内容
- DXライブラリのキー入力でプレイヤーを動かす
- 押しっぱなしと押した瞬間の違いを、前フレームの入力と比較して判定する
- 速度を使って毎フレームの移動量を管理する
- 画面外へ出ないように座標を制限する
- 入力処理を
Inputクラスとして分ける
今回の開始状態
第02回終了時点では、プレイヤーの仮図形が画面下部に表示されています
今回の終了時点では、矢印キーまたはWASDで上下左右に動けるようにします
変更するファイル
| ファイル | 変更内容 |
|---|---|
Input.h | 入力管理クラスを追加する |
Input.cpp | 現在と前フレームのキー状態を記録する |
Game.h | Inputと移動速度を追加する |
Game.cpp | 入力更新、移動、画面内制限を追加する |
Inputクラスを追加する
SourceにInput.hとInput.cppを追加します
入力の判定には2種類あります
1つはキーを押している間ずっと反応する判定で、移動のように毎フレーム値を変えたい処理に使います
もう1つはキーを押した瞬間だけ反応する判定で、ショットやメニュー選択のように1回だけ処理したい場面に使います
この授業では前者をIsDown()、後者をIsPressed()という名前で区別します
cpp
class Input
{
public:
void Update();
bool IsDown(int key) const;
bool IsPressed(int key) const;
private:
char current[256] = {};
char previous[256] = {};
};currentとpreviousという2つの配列を持つのは、「今押しているか」と「1フレーム前も押していたか」を比較しないと、押した瞬間を判定できないためです
押した瞬間とは、「今回は押している」かつ「前回は押していなかった」という状態のことです
Update()では、前回の入力を保存してから現在の入力を読みます
cpp
void Input::Update()
{
for (int i = 0; i < 256; ++i)
previous[i] = current[i];
GetHitKeyStateAll(current);
}GetHitKeyStateAll()は、256個のキーそれぞれの状態を1つのchar配列へまとめて書き込むDXライブラリの関数です
押されているキーには0以外の値、押されていないキーには0が入りますpreviousへの保存を先に行うのは、currentを新しい値で上書きする前に、直前の状態を確保しておく必要があるためです
この順序を逆にすると、previousとcurrentが常に同じ値になり、押した瞬間を判定できなくなります
判定関数は、この2つの配列を比較するだけです
cpp
bool Input::IsDown(int key) const
{
return current[key] != 0;
}
bool Input::IsPressed(int key) const
{
return current[key] != 0 && previous[key] == 0;
}入力で座標を変える
Game.hでInputと速度を持ちます
cpp
Input input;
float playerSpeed = 4.0f;MainLoop()では、Update()の前に入力を更新します
cpp
input.Update();
Update();
Draw();input.Update()を先に呼ぶのは、Game::Update()の中で今フレームの入力を使うためです
これが逆順だと、Game::Update()は1フレーム古い入力を参照してしまいます
Game::Update()では、まず上下左右の入力から移動方向を作り、最後に速度を掛けます
cpp
float moveX = 0.0f;
float moveY = 0.0f;
if (input.IsDown(KEY_INPUT_LEFT) || input.IsDown(KEY_INPUT_A))
moveX -= 1.0f;
if (input.IsDown(KEY_INPUT_RIGHT) || input.IsDown(KEY_INPUT_D))
moveX += 1.0f;
if (input.IsDown(KEY_INPUT_UP) || input.IsDown(KEY_INPUT_W))
moveY -= 1.0f;
if (input.IsDown(KEY_INPUT_DOWN) || input.IsDown(KEY_INPUT_S))
moveY += 1.0f;
playerX += static_cast<int>(moveX * playerSpeed);
playerY += static_cast<int>(moveY * playerSpeed);moveXとmoveYは「-1、0、1のどちらへ動きたいか」だけを表す値です
実際の移動量は、この値にplayerSpeedを掛けて求めます
移動方向の計算と移動量の計算を分けておくと、後で速度だけを変えたいときにこの部分を変更せずに済みます
playerXとplayerYはintなので、moveX * playerSpeedの結果をstatic_cast<int>で整数へ変換してから加算します
この変換は小数点以下を切り捨てるため、速度が遅いとキャラクターがまったく動かなくなる場合があります
座標をfloatで持てばこの問題は起きませんが、ここでは第02回から続くintの座標をそのまま使い、float座標への移行は第04回で扱います
画面外へ出ないようにする
移動後に、座標を画面内へ戻します
プレイヤーの見た目の半分を余白として使います
cpp
if (playerX < 18) playerX = 18;
if (playerX > screenWidth - 18) playerX = screenWidth - 18;
if (playerY < 32) playerY = 32;
if (playerY > screenHeight - 24) playerY = screenHeight - 24;この処理は、移動を計算したあとに必ず実行します
移動前に制限をかけると、画面端に到達する直前のフレームで移動量が中途半端に打ち切られ、キャラクターの動きが不自然に感じられます
動作確認
- 矢印キーとWASDの両方で移動できることを確認する
- 画面端でプレイヤーが止まることを確認する
playerSpeedを2.0fや8.0fへ変えて操作感を比較するIsPressed()を使うと押した瞬間だけ反応することを短い表示で確認する- 斜め方向へ移動したときの速さを、上下左右への移動と見比べる
よくある問題
- 入力が反応しない場合は、
input.Update()をUpdate()より前に呼んでいるか確認する Input.cppを作っただけでプロジェクトへ追加していないと、リンクエラーになるplayerSpeedを小さくしすぎると、static_cast<int>による切り捨てで座標が変化しなくなる- 斜め移動が上下左右への移動より速く感じる場合がある。これは
moveXとmoveYをどちらも1.0fのまま速度倍しているためで、平面上の実際の移動距離が約1.41倍になるのが原因である。この補正は次回以降でベクトルの正規化として扱う
今回の完成条件
Inputクラスで押しっぱなしと押した瞬間を区別できる- 押しっぱなしの入力でプレイヤーを移動できる
- プレイヤーを画面内に制限できる
currentとpreviousを比較して押した瞬間を判定する仕組みを説明できる
まとめ
今回は、入力を読み取って座標を変化させました
次回は、位置や範囲を構造体でまとめ、当たり判定の準備をします