2 / 3 入力処理と描画処理の関数化
キー入力から移動量を算出する関数と、キャラクターを描画する関数を作成します。計算結果を返す関数(戻り値あり)と、描画などの処理を実行する関数(void 型・戻り値なし)の役割の違い、および変数のスコープを理解します。
移動量計算の関数化(ReadMove)
矢印キーによる入力処理を観察すると、水平方向(左右キー)も垂直方向(上下キー)も、「マイナス方向のキーが押されていれば -3、プラス方向のキーが押されていれば +3」という全く同一の計算構造を持っています。
この処理を関数 ReadMove として抽象化します。調べる2つのキーコードを引数として受け取り、そのフレームで進むべき移動量(変位)を計算して返す設計にします。
KeepInside の直後に、次の関数定義を追加してください。
return value;
}
int ReadMove(int minusKey, int plusKey)
{
int move = 0;
if (CheckHitKey(minusKey) != 0)
move = move - 3;
if (CheckHitKey(plusKey) != 0)
move = move + 3;
return move;
}
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)判定するキーと移動量の受け渡し
- 仮引数(
int minusKey, int plusKey): 判定したい2つのキーコード(例:KEY_INPUT_LEFTとKEY_INPUT_RIGHT)を受け取ります。キーが押されているかどうかの状態ではなく、「調べる対象のキーの識別番号」を引数に渡すことで、横方向にも縦方向にも同じ関数を再利用できます。 - ローカル変数
int move = 0;: そのフレームにおける移動量を一時保持する変数です。キーを離しているときは移動量0を返したいため、呼び出されるたびに0から計算を開始します。
メインループへの適用
ループ内の4つのキー入力 if 文を削除し、次のコードの先頭2行に置き換えます。前のステップで追加した KeepInside の2行はその直後に残し、「移動量の加算 → 座標の制限」という順序を保ちます。
x = x + ReadMove(KEY_INPUT_LEFT, KEY_INPUT_RIGHT);
y = y + ReadMove(KEY_INPUT_UP, KEY_INPUT_DOWN);
x = KeepInside(x, 640 - 48);
y = KeepInside(y, 480 - 48);1行目では、左キーと右キーを渡して横方向の移動量(-3、0、+3)を取得し、現在の x に加算しています。両方のキーを同時押しした場合は -3 + 3 = 0 が返るため、静止状態が維持されます。縦方向(y)についても同様に、上キー(マイナス方向)と下キー(プラス方向)を渡して移動量を求めています。
描画処理の関数化と void 型(DrawCharacter)
続いて、画面にキャラクターを描画する処理を関数化します。ReadMove の直後に、次の関数定義を追加してください。
return move;
}
void DrawCharacter(int x, int y, int image)
{
DrawGraph(x, y, image, TRUE);
}
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)void 型(戻り値のない関数)
関数名の前の void(ヴォイド)は、「この関数は呼び出し元へ何もデータを返さない」ことを宣言する特別な型として使われます。
- 戻り値のある関数(
intなど):ReadMoveやKeepInsideのように、計算した数値(座標や移動量)を呼び出し元へ返却することが目的です。 - 戻り値のない関数(
void):DrawCharacterのように、画面に絵を描くという「具体的なアクション(処理)」を実行すること自体が目的です。
返す数値がないため、関数本体の末尾に return 0; などの記述は不要であり、閉じ波括弧 } に達した時点で呼び出し元へ自動的に復帰します。
同名の仮引数と変数のスコープ
DrawCharacter の仮引数には int x, int y, int image という名前が付けられています。
ここで、「WinMain の中にも同じ名前の変数 x, y, image があったはずだが、混ざらないのか?」という疑問が生じます。
ここで扱う変数 x は、WinMain と DrawCharacter で別々の変数です。変数名を使える範囲を スコープ(scope) と呼びます。DrawCharacter は、呼び出し時に渡された値を自分の仮引数 x、y、image で受け取って描画します。
引数の順序と描画関数の呼び出し
メインループ内の2行の DrawGraph を、次のように置き換えます。
DrawCharacter(x, y, image);
DrawCharacter(walkerX, walkerY, walker);2行目では walkerX, walkerY, walker を渡していますが、関数 DrawCharacter の側では第1引数が x、第2引数が y、第3引数が image という名前の箱にコピーされて受け取られます。実引数と仮引数の名前が一致している必要はなく、「手渡された引数の順番(第1、第2、第3)」によって正しく値が対応付けられます。
また、DrawCharacter は void 型で値を返さないため、呼び出し時に x = DrawCharacter(...) のような代入を行うことはできません(代入しようとするとコンパイルエラーになります)。
リファクタリング前後の動作同等性検証
プログラムの外部的な振る舞い(ユーザーから見た操作感や表示結果)を変えずに、内部構造を整理・改善する作業を リファクタリング(refactoring) と呼びます。
ビルドして実行し、関数化を行う前と全く同じ挙動が保たれているかを検証しましょう。
- 方向キーを押している間だけ青いロボットが滑らかに動き、離すと停止するか
- 左右のキーを同時押しした際に、横方向へ動かず静止するか
- 青いロボットが画面の四辺で画像を見切れることなく停止するか
- オレンジ色のロボットが左右の画面端で自動的に跳ね返り、往復運動を継続するか
- 2体が重なった際に、オレンジ色のロボットが手前(前面)に描画されるか
コードが機能ごとに小さな関数へ分割されたことで、メインループ内の記述が簡潔になり、プログラム全体の処理の流れが一目で把握しやすくなりました。
-
ReadMoveが座標そのものではなく「移動量(変位)」を返す設計の利点を説明できる -
void型の関数は戻り値を返さないため代入が不要であることを理解している - 仮引数と実引数の名前が一致していなくても、引数の順序で正しく値が渡される仕組みを説明できる