WinfowsFormアプリでキャラクタをスムーズに移動させる

2021年1月19日

通常、キー入力はリピート間隔が設定されているため、キーを押しっぱなしの場合、1回目の入力と2回目以降の入力の間、若干のウェイトがあります。タイプ入力の場合、必要な機能なのですがゲームではゲーム性を損なうためこの機能は使いたくありません。WindowsOSの設定変更、レジストリの変更等では、他のアプリへの影響が考えられるため、プログラムで対処します。

基本のサンプル

Form画面にLabelコンポーネント(label1)をD&Dしておきます。

using System;
using System.Windows.Forms;

namespace RealInputSample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            label1.Text = "☆彡";
            //timerイベント
            var update = new Timer();
            // Updateイベントハンドラを呼び出す(「Update」は独自名)
            update.Tick += new EventHandler(Update);
            // 16ms毎
            update.Interval = 16;
            // タイマースタート
            update.Start();
        }
        private void Update(object sender, EventArgs e)
        {
            MoveChar();
        }

        private void MoveChar()
        {
            if (Input.KeyPress(Keys.Right)) label1.Left += 1;
            if (Input.KeyPress(Keys.Left)) label1.Left -= 1;
            if (Input.KeyPress(Keys.Up)) label1.Top -= 1;
            if (Input.KeyPress(Keys.Down)) label1.Top += 1;
        }
    }
    // キー入力クラス
    static class Input
    {
        // DLLのインポート
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        // externで宣言が必要
        private static extern short GetKeyState(int nVirtKey);

        // 最上位Bitが1で KeyDownを取得
        public static bool KeyPress(Keys keyCode) => (GetKeyState((int)keyCode) & 0x8000) > 0;
    }
}

WindowsFormアプリサンプル

実行結果

Form1クラス(プログラム作成)

using System;
using System.Drawing;
using System.Windows.Forms;

namespace RealInputSample
{
    public partial class Form1 : Form
    {
        // プレイヤー変数の宣言
        Player player;

        // 猫のイメージ
        readonly Image CatImage = Properties.Resources.Cat;
        // 犬のイメージ
        readonly Image DogImage = Properties.Resources.Dog;

        public Form1()
        {
            InitializeComponent();

            //timerイベント
            var update = new Timer();
            // Updateイベントハンドラを呼び出す(「Update」は独自名)
            update.Tick += new EventHandler(Update);
            // 16ms毎
            update.Interval = 16;
            // タイマースタート
            update.Start();
        }

        // 60fps(16ms毎)でコールされるタイマーイベント
        void Update(object sender, EventArgs e)
        {
            // 移動
            if (Input.KeyPress(Keys.Right)) player.Left += 1;
            if (Input.KeyPress(Keys.Left)) player.Left -= 1;
            if (Input.KeyPress(Keys.Up)) player.Top -= 1;
            if (Input.KeyPress(Keys.Down)) player.Top += 1;

            // 猫画像に切り替え
            if (Input.KeyDown(Keys.C))
            {
                player.Image = CatImage;
            }

            // 犬画像に切り替え
            if (Input.KeyDown(Keys.D))
            {
                player.Image = DogImage;
            }
        }
        // フォームがロードされた時、一度だけ呼び出される
        void Form1_Load(object sender, EventArgs e)
        {
            // プレイヤーのインスタンス化 
            // インスタンス作成の時にリソースに登録したDogイメージをセット
            player = new Player(DogImage);
            // 現在のフォームコントロールに追加
            Controls.Add(player);
        }
    }
}

VisualStudioによって作成押されたForm1クラス(自動作成のため更新しないこと)
確認用

namespace RealInputSample
{
    partial class Form1
    {
        /// <summary>
        /// 必要なデザイナー変数です。
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// 使用中のリソースをすべてクリーンアップします。
        /// </summary>
        /// <param name="disposing">マネージド リソースを破棄する場合は true を指定し、その他の場合は false を指定します。</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows フォーム デザイナーで生成されたコード

        /// <summary>
        /// デザイナー サポートに必要なメソッドです。このメソッドの内容を
        /// コード エディターで変更しないでください。
        /// </summary>
        private void InitializeComponent()
        {
            this.SuspendLayout();
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(13F, 24F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(800, 450);
            this.Name = "Form1";
            this.Text = "Form1";
            this.Load += new System.EventHandler(this.Form1_Load);
            this.ResumeLayout(false);

        }

        #endregion
    }
}

Inputクラス

using System.Windows.Forms;

namespace RealInputSample
{
    /// <summary>
    /// キー入力クラス
    /// </summary>
    static class Input
    {
        // DLLのインポート
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        // externで宣言が必要
        static extern short GetKeyState(int nVirtKey);
        // 最上位Bitが1で KeyDownを取得
        /// <summary>
        /// キーが押されているかを判定
        /// </summary>
        /// <param name="keyCode">入力キーコード</param>
        /// <returns>キーが押されているとtrue</returns>
        public static bool KeyPress(Keys keyCode)
        {
            return (GetKeyState((int)keyCode) & 0x8000) > 0;
        }

        public static bool IsKeyTrigger { get; private set; }
        /// <summary>
        /// キーが押された時の判定
        /// </summary>
        /// <param name="keyCode">入力キーコード</param>
        /// <returns>キーが押された瞬間一度だけtrue</returns>
        public static bool KeyDown(Keys keyCode)
        {
            if (!((GetKeyState((int)keyCode) & 0x8000) > 0))
            {
                return IsKeyTrigger = false;
            }
            if (!IsKeyTrigger)
            {
                return IsKeyTrigger = true;
            }
            return false;
        }
        /// <summary>
        /// キーが押されて、離されたか判定
        /// </summary>
        /// <param name="keyCode">入力キーコード</param>
        /// <returns>キーが離された瞬間一度だけtrue</returns>
        public static bool KeyUp(Keys keyCode)
        {
            if ((GetKeyState((int)keyCode) & 0x8000) > 0)
                return false;

            if (IsKeyTrigger)
            {
                return !(IsKeyTrigger = false);
            }
            return false;
        }
    }
}

Playerクラス

using System.Drawing;
using System.Windows.Forms;

namespace RealInputSample
{
    /// <summary>
    /// イメージを含むプレイヤークラス
    /// PictureBoxを使えるように継承している
    /// </summary>
    class Player : PictureBox
    {
        /// <summary>
        /// ダミー
        /// </summary>
        public int HP { get; set; }
        /// <summary>
        /// ダミー
        /// </summary>
        public int MP { get; set; }

        /// <summary>
        /// プレイヤーインスタンスの作成
        /// </summary>
        /// <param name="image">PictureBoxにセットするイメージ</param>
        public Player(Image image)
        {
            // 位置
            Location = new Point(100, 100);
            // 大きさ
            Size = new Size(50, 50);
            // イメージ
            Image = image;
            // ストレッチ設定
            SizeMode = PictureBoxSizeMode.StretchImage;
        }
    }
}

リソース

C#,VisualStudio

Posted by hidepon