3 / 3 画面端での移動制限
キー入力で変わった座標を最小値と最大値で制限し、画像が画面外へ出ないようにします。入力、座標の更新、境界補正、描画をどの順序で行うかを学びます。
境界判定(クランプ処理)の実装
方向キーを押し続けると座標が増減し、キャラクターが画面外へ出てしまいます。第02回で求めた範囲を使って、移動できる位置を制限してください。画像全体が画面に収まるのは、xが0〜592、yが0〜432の範囲です。
座標を範囲内へ戻すコードの追加
キー入力の四つのif文の直後、ClearDrawScreen();の前に、次のコードを追加してください。
if (x < 0)
x = 0;
if (x > 640 - 48)
x = 640 - 48;
if (y < 0)
y = 0;
if (y > 480 - 48)
y = 480 - 48;
ClearDrawScreen();比較演算子(<, >)とクランプ処理のロジック
<と>は、二つの数値の大小を比べる比較演算子です。数学では不等号と呼ぶ記号を使います。
例えば、右端を超えたかどうかは、次の条件式で判定します。
x > 640 - 48x:左上のx座標>:より大きい640 - 48:上限592
条件に当てはまると、代入演算子=で座標を画面端の値に戻します。
| 条件 | 戻す値 | 止まる位置 |
|---|---|---|
x < 0 | x = 0 | 左端 |
x > 640 - 48 | x = 592 | 右端 |
y < 0 | y = 0 | 上端 |
y > 480 - 48 | y = 432 | 下端 |
値が範囲を超えたときに最小値や最大値へ戻す処理を、クランプ処理と呼びます。英語ではclampです。範囲内なら条件に当てはまらないため、代入は行いません。キー入力で更新した座標を、そのまま使います。
フレーム内処理順序と境界値の推移
座標の更新・補正・描画の順序
1フレームの処理は、必ず次の順序で行います。この処理の流れをパイプラインと呼びます。
図を準備しています…
境界付近での数値の変化例
右端付近でx = 591のときに右キーを押した場合を考えてください。移動量は3ピクセルなので、まず591 + 3 = 594になります。続いてx > 592を満たすため、xを592に戻します。最後に、補正後の座標592で画像を描きます。
境界補正を描画の後に置くと、補正前の594で描いてしまいます。そのフレームでは、画像の一部が画面外に出たままです。右キーを押し続けると、次のフレームも592に3を足してから描くため、はみ出した表示が続きます。画像を画面内に収めるには、座標の更新、境界補正、描画の順序を守ってください。
四辺および四隅での動作検証
四辺と四隅での停止確認
ビルドして実行し、上・下・左・右のキーをそれぞれ押し続けてください。各画面端で、画像全体が見える位置に止まることを確認します。
次は右キーと下キーなどを同時に押し、斜めに移動してください。xとyの両方を制限できていれば、画像全体が見える位置で角に止まります。
課題:左端のマージン設定
左端で止まる位置を、画面端から20ピクセル離したい場合を考えてください。止まるx座標を0から20に変えるには、どのコードを変更しますか。
条件式をif (x < 20)、代入する値をx = 20;に変えて実行してください。左端に20ピクセルの余白を残して止まることを確認します。確認後は、条件と代入する値を元の0に戻してください。
- 画面サイズから画像サイズを引いて最大許容座標を求める理由を説明できる
- 「入力 → 座標更新 → 境界補正 → 描画」という実行順序の理由を説明できる
- 上下左右の端および角において、画像が見切れることなく停止することを確認した
参考実装
ここまでの手順を終えたmain.cppのコード全体です。
main.cppの参考実装
#include <DxLib.h>
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
ChangeWindowMode(TRUE);
SetGraphMode(640, 480, 32);
SetMainWindowText(L"C++ Basics");
if (DxLib_Init() == -1)
return -1;
SetDrawScreen(DX_SCREEN_BACK);
SetBackgroundColor(245, 248, 252);
int image = LoadGraph(L"Assets/Images/Robot.png");
if (image == -1)
{
MessageBoxW(nullptr, L"Assets/Images/Robot.png を読み込めません。", L"画像の確認", MB_OK);
DxLib_End();
return -1;
}
int x = 160;
int y = 120;
while (ProcessMessage() == 0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0)
{
if (CheckHitKey(KEY_INPUT_LEFT) != 0)
x = x - 3;
if (CheckHitKey(KEY_INPUT_RIGHT) != 0)
x = x + 3;
if (CheckHitKey(KEY_INPUT_UP) != 0)
y = y - 3;
if (CheckHitKey(KEY_INPUT_DOWN) != 0)
y = y + 3;
if (x < 0)
x = 0;
if (x > 640 - 48)
x = 640 - 48;
if (y < 0)
y = 0;
if (y > 480 - 48)
y = 480 - 48;
ClearDrawScreen();
DrawGraph(x, y, image, TRUE);
ScreenFlip();
WaitTimer(16);
}
DeleteGraph(image);
DxLib_End();
return 0;
}