【発展・希望者向け】WinFormsでキー入力と押しっぱなしを扱う

広告

この記事は 授業の範囲外 です。興味のある人・余力のある人が読む発展用の内容です。 無理に読む必要はありません。


前提

  • Timer の使い方が分かる
  • 変数・イベントの基本が分かる
  • 電話帳課題を理解しているとスムーズです

はじめに

これまでの学習では、

  • ボタンをクリックする
  • クリックイベントが呼ばれる

という形で入力を受け取ってきました。

しかし、ゲームなどではキーボードを押しっぱなしでキャラを動かしたいことがあります。

今回は、矢印キーでキャラがスムーズに動くプログラムを作ります。押しっぱなしの検出方法を学びます。


今日作るもの

矢印キーを押すと、画面上の■が上下左右に動きます。

  • 押している間、ずっと動き続ける
  • 離すと止まる
  • 複数のキーを同時押しすると斜めに動く(例:↑と→で右上)

キーリピートでは不向きな理由

KeyDown イベントは、キーを押し続けるとリピートで何度も発生します。しかしゲームには向きません。

問題説明
初回遅延押し始めてから最初のリピートまで、数百ミリ秒の待ち時間がある
一定間隔リピート間隔が OS 設定に依存し、ゲームのフレームレートと合わない
区別しづらい1回だけ押したのか、押しっぱなしなのか、イベントだけでは判断しづらい

そのため、KeyDown + KeyUp + フラグ + Timer のパターンを使います。


ソリューションとプロジェクトを作る

Visual Studio で新しいプロジェクトを作ります。

  • テンプレート: Windows Forms アプリ (.NET Framework または .NET 6/8/10)
  • ソリューション名: KeyInputSample
  • プロジェクト名: KeyMove

作成すると Form1 が表示されます。


フォームに配置するコントロール

フォームに次の2つを配置します。

コントロール名前
LabellabelPlayer
Timertimer1
  • labelPlayer:Text を「■」に、Font を大きく(例:24pt)に、AutoSize を false にして適度なサイズに
  • timer1:Interval を 30(約30ミリ秒ごとに更新 = 滑らかに動く)
  • Timer は画面には表示されません。フォーム下のコンポーネントトレイに配置されます

フォームの設定(重要)

フォームがキー入力を受け取るため、次の設定をします。

  1. Form1 をクリックして選択
  2. プロパティウィンドウで KeyPreview を true に変更

KeyPreview = true にすると、フォーム上のどのコントロールにフォーカスがあっても、フォームが先にキーイベントを受け取るようになります。


手順1:キー押下状態のフラグを追加する

Form1.cs のクラス内に、次のフィールドを追加します。

bool keyUpPressed = false;
bool keyDownPressed = false;
bool keyLeftPressed = false;
bool keyRightPressed = false;

手順2:KeyDown と KeyUp イベントを登録する

  1. Form1 をクリックして選択
  2. プロパティウィンドウの ⚡ 雷マーク(イベント) をクリック
  3. KeyDown をダブルクリック → Form1_KeyDown が作成される
  4. KeyUp をダブルクリック → Form1_KeyUp が作成される

手順3:KeyDown でフラグを true にする

Form1_KeyDown に次のコードを書きます。

switch (e.KeyCode)
{
    case Keys.Up:    keyUpPressed = true; break;
    case Keys.Down:  keyDownPressed = true; break;
    case Keys.Left:  keyLeftPressed = true; break;
    case Keys.Right: keyRightPressed = true; break;
}

手順4:KeyUp でフラグを false にする

Form1_KeyUp に次のコードを書きます。

switch (e.KeyCode)
{
    case Keys.Up:    keyUpPressed = false; break;
    case Keys.Down:  keyDownPressed = false; break;
    case Keys.Left:  keyLeftPressed = false; break;
    case Keys.Right: keyRightPressed = false; break;
}

手順5:Timer の Tick で移動処理を書く

Timer をダブルクリックして timer1_Tick を作成し、次のコードを書きます。

int speed = 5;

if (keyUpPressed)    labelPlayer.Top -= speed;
if (keyDownPressed)  labelPlayer.Top += speed;
if (keyLeftPressed)  labelPlayer.Left -= speed;
if (keyRightPressed) labelPlayer.Left += speed;

手順6:Form1 の Load で Timer を開始する

フォームをダブルクリックして Form1_Load を作成し、次のコードを書きます。

timer1.Start();

完成コード

using System;
using System.Windows.Forms;

namespace KeyMove
{
    public partial class Form1 : Form
    {
        bool keyUpPressed = false;
        bool keyDownPressed = false;
        bool keyLeftPressed = false;
        bool keyRightPressed = false;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            timer1.Start();
        }

        private void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.Up:    keyUpPressed = true; break;
                case Keys.Down:  keyDownPressed = true; break;
                case Keys.Left:  keyLeftPressed = true; break;
                case Keys.Right: keyRightPressed = true; break;
            }
        }

        private void Form1_KeyUp(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.Up:    keyUpPressed = false; break;
                case Keys.Down:  keyDownPressed = false; break;
                case Keys.Left:  keyLeftPressed = false; break;
                case Keys.Right: keyRightPressed = false; break;
            }
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            int speed = 5;

            if (keyUpPressed)    labelPlayer.Top -= speed;
            if (keyDownPressed)  labelPlayer.Top += speed;
            if (keyLeftPressed)  labelPlayer.Left -= speed;
            if (keyRightPressed) labelPlayer.Left += speed;
        }
    }
}

プログラムの流れ

キーを押す
        ↓
KeyDown が呼ばれる → フラグを true
        ↓
Timer の Tick が一定間隔で呼ばれる
        ↓
フラグが true の間、毎回移動処理
        ↓
キーを離す
        ↓
KeyUp が呼ばれる → フラグを false
        ↓
移動停止

重要ポイント

押しっぱなしの検出は「KeyDown + KeyUp + フラグ + Timer」

  • KeyDown:押した瞬間にフラグを true
  • KeyUp:離した瞬間にフラグを false
  • Timer:フラグが true の間、毎フレーム移動処理

キーリピートに頼らず、自分でフラグを管理することで、ゲーム向きのスムーズな動きになります。

  • KeyPreview = true を忘れない(フォームがキーを受け取るため)
  • 複数キー同時押しも、フラグを分けておけば自然に対応できる

補足:初回の遅延について

KeyDown から次の Timer Tick まで、最大で Interval 分(例:30ms)の遅延があり得ます。体感しにくいことが多いですが、気になる場合は Interval を 16 にすると滑らかになります。KeyDown の時点で移動処理を 1 回だけ呼べば、初回の遅延はほぼなくなります。


発展アイデア

キー入力が使えるようになると、次のようなものも作れます。

  • 座標と物理の記事と組み合わせて、重力付きのジャンプゲーム
  • 当たり判定を入れて、障害物を避けるゲーム
  • サウンドの記事と組み合わせて、移動時に効果音を鳴らす
訪問数 2 回, 今日の訪問数 2回

広告