C#で楽しいゲーム作り講座　入力編

C#にはFormがあってそれに入力操作を任せることができます。しかしそれだけではゲームには不向きです。キーンコンフィグが必要だったり、あとマウスの座標をゲーム内座標にするのがちょっと面倒です。

マウス、キーを一緒くたに扱いコンフィグも割とできちゃう方法が……

#こちら！


```C#:inputin.cs
  public enum itype 
    {
        down,ing,up//押した、押している、離したの三種類を定義
    }
    public class inputin 
    {
        List<Keys> k = new List<Keys>();
        List<MouseButtons> m = new List<MouseButtons>();
        //一フレーム前の入力
        List<Keys> pk = new List<Keys>();
        List<MouseButtons> pm = new List<MouseButtons>();
        public float x=0, y=0;
       
       
        public void down(Keys i,List<IPC>converts) 
        {
        //入力したキーを変換してからリストに加える
            foreach (var b in converts)
            {
                if (b.getin(i))
                {
                    if (b.KO != Keys.None)
                    {
                        if (!k.Contains(b.KO))
                        {
                            k.Add(b.KO);
                        }
                        return;
                    }
                    if (b.MO != MouseButtons.None)
                    {
                        if (!m.Contains(b.MO))
                        {
                            m.Add(b.MO);
                        }
                        return;
                    }
                }
            }
            if (!k.Contains(i))
            {
                k.Add(i);
            }
        }
       //上と同様
        public void down(MouseButtons i, List<IPC> converts)
        {
            foreach (var b in converts)
            {
                if (b.getin(i))
                {
                    if (b.KO != Keys.None)
                    {
                        if (!k.Contains(b.KO))
                        {
                            k.Add(b.KO);
                        }
                        return;
                    }
                    if (b.MO != MouseButtons.None)
                    {
                        if (!m.Contains(b.MO))
                        {
                            m.Add(b.MO);
                        }
                        return;
                    }
                }
            }
            if (!m.Contains(i))
            {
                m.Add(i);
            }
        }
        //上の離したしたときバージョン
        public void up(Keys i, List<IPC> converts)
        {
            foreach (var b in converts)
            {
                if (b.getin(i))
                {
                    if (b.KO != Keys.None)
                    {
                        k.Remove(b.KO);
                        return;
                    }
                    if (b.MO != MouseButtons.None)
                    {
                        m.Remove(b.MO);
                        return;
                    }
                }
            }
            k.Remove(i);
        }
        //おなじく
        public void up(MouseButtons i, List<IPC> converts)
        {
            foreach (var b in converts)
            {
                if (b.getin(i))
                {
                    if (b.KO != Keys.None)
                    {
                        k.Remove(b.KO);
                        return;
                    }
                    if (b.MO != MouseButtons.None)
                    {
                        m.Remove(b.MO);
                        return;
                    }
                }
            }
            m.Remove(i);
        }
        //一フレーム前の入力を設定
        public void topre() 
        {
            pk = new List<Keys>(k);
            pm = new List<MouseButtons>(m);
        }
        //マウスの位置を取得し、ゲーム内の座標に変換する
        //hyojimanは画面の描画をするためのクラス。カメラ位置camx,camy、ゲーム内の大きさww,whをもつ。
        public void setpointer(hyojiman hyojiman,Form f) 
        {
            var cu = f.PointToClient(Cursor.Position);//クライアントにおける位置を取得

            //クライアント座標をゲーム内座標に変換
            x = cu.X * (hyojiman.ww / f.ClientRectangle.Width);
            y = cu.Y * (hyojiman.wh / f.ClientRectangle.Height);
       
            //画面外は指定できなくしておく
            if (x < 0) x = 0;
            if (x > hyojiman.ww) x = hyojiman.ww;
            if (y < 0) y = 0;
            if (y > hyojiman.wh) y = hyojiman.wh;

            //カメラの位置を足す
            x += +hyojiman.camx;
            y += +hyojiman.camy;
        }
        
        public bool ok(Keys i,itype t) 
        {
            switch (t)
            {
                case itype.down://一フレーム前には存在しなければこのフレームで押されたとみなす
                    return k.Contains(i) && !pk.Contains(i);
                case itype.ing://どちらかないあればOK
                    return k.Contains(i) ;
                case itype.up://一フレーム前のみにあれば離されたとみなす
                    return !k.Contains(i) && pk.Contains(i);
                default:
                    return false;
            }
        }
        //同じ
        public bool ok(MouseButtons i, itype t)
        {
            switch (t)
            {
                case itype.down:

                    return m.Contains(i) && !pm.Contains(i);

                case itype.ing:
                    return m.Contains(i);
                case itype.up:
                    return !m.Contains(i) && pm.Contains(i);
                default:
                    return false;
                    
            }

        }
    }
//キーコンフィグのためのクラス
    public class IPC
    {
        MouseButtons m = MouseButtons.None, mo = MouseButtons.None;
        Keys k = Keys.None, ko = Keys.None;

        public Keys KO { get { return ko; } }
        public MouseButtons MO { get { return mo; } }
        public Keys KI { get { return k; } }
        public MouseButtons MI { get { return m; } }
        public IPC(Keys i, Keys o)
        {
            k = i;
            ko = o;
        }
       
        public IPC(Keys i, MouseButtons o)
        {
            k = i;
            mo = o;
        }
        public IPC(MouseButtons i, Keys o)
        {
            m = i;
            ko = o;
        }
        public IPC(MouseButtons i, MouseButtons o)
        {
            m = i;
            mo = o;
        }

        public bool getin(Keys i)
        {
            return k == i;
        }
        public bool getin(MouseButtons i)
        {
            return m == i;
        }
        public bool getout(Keys i)
        {
            return ko == i;
        }
        public bool getout(MouseButtons i)
        {
            return mo == i;
        }
    }
```
:::note :
キーコンフィグの仕様
今のままでは変換が何もない場合はそのままの入力と認識します。つまりA->Bと変換を定義しただけでは、AもBもBと認識します。
:::
で、実際使う感じはこうです

```C#:Form.cs
        List<IPC> converts = new List<IPC> { new IPC(Keys.I, MouseButtons.Left), new IPC(MouseButtons.Left, Keys.I) };
        inputin i = new inputin();
        hyojiman hyojiman;
        //Formのイベントね
        private void mousedown(object sender, MouseEventArgs e)
        {
            i.down(e.Button, converts);
        }
        private void mouseup(object sender, MouseEventArgs e)
        {
            inputin.raw.up(e.Button);
        }
        private void keydown(object sender, KeyEventArgs e)
        {
            i.down(e.KeyCode, SD.S.converts);
        }
        private void keyup(object sender, KeyEventArgs e)
        {
            i.up(e.KeyCode, SD.S.converts);
        }

        //tickのイベントね
        private void ticked(object sender, EventArgs e)
        {
            i.setpointer(hyojiman, this);//マウスのゲーム内座標をセット
            if(i.ok(Keys.I, itype.down))
            {
                hyojiman.addpicture(new picture(i.x, i.y, 0, 100, 100, 50, 50, 0, false, 1, "def", new Dictionary<string,string> { { "def", "Cursor" } }));
                //Iが押されたらマウスポインタの位置に絵を一枚描く
                //しかしIPCによってI<->左クリックとなってるぞ
            }
            hyojiman.hyoji();//画面を描画
            i.topre();//一フレーム前の入力をセット
        }

```

実際にゲーム内でキーコンフィグをつける場合は変換なしに入力を捉える必要があるのでちょっとめんどいです。設定のテキストファイルを用意しといてそこからIPCを読み込む形式にすると楽だと思います。

そしてこの仕組みで行くとゲームをやってる時にフォーカスが通知か何かで外れた時、押し続けていたボタンを解除するのにもう一度押してから離す必要があるあの現象が発生してくれます。