迷走テクノロギア

妄言に限りなく近い何か

【ミドルウェア?】スマートフォンをゲームコントローラー化する

お久しぶりですSANAです。

突然ですが、

僕ってWiiとかSwitchのコントローラー振ったりするゲーム好きなんですよね!

まぁ、WiiもSwitchも持ってないですけど👐

ハードを買うのも起動するのも開発するのもハードルが高くてVeryHard(ハードだけに)

ハードや専用コントローラー買いたくないけど
コントローラー振ったりして遊びたいんだあああ

...

あ、スマホ振ればええやん

とまぁそんな訳で作りました

適当な設計

f:id:sanaftg:20210626162300p:plain
設計書()

開発者向けにするかプレイヤー向けにするか

ちょっと悩んだんですけど、普通に世の中に出てるPCゲームでも使えたほうが絶対良い。

→中継アプリが必要

懐かしのWindowsForm

PC側の中継アプリはタスクトレイに入れて常駐させたかったので一番手軽なWindowsFormを選択

昔はよく使ってましたねw

[:]
μ'sの曲が数秒流れて、どのメンバーが歌っているか当てる謎のゲーム
f:id:sanaftg:20210626164546p:plain
実行ファイルの名前がHageなの何

スマホアプリ側

脳死Unityを選択

JoyStickは有名なJoyStickPackを使用致しました。 assetstore.unity.com

コントローラー画面
中継アプリとの接続画面

スマホ → 中継アプリ

UDP通信を使用しました。

参考サイト

[Unity]非同期処理(Task)の利用例(UDP送受信,SerialPortのRead) - Qiita

送る値は大した量ないので

通信種別 / イベントの種類(ダウン、アップ)/ キーの種類

みたいな文字列送ってます

※キーコードはUnityとWindowsで値が異なる為、WindowsのKeyCode(System.Windows.Forms.Keys)に合わせてます

   private void InputDown(InputData data)
    {
        StringBuilder sb = new StringBuilder();
        switch(data.type){
            case Type.KeyCode:
                sb.Append("k/");
                sb.Append(WM_KEYDOWN).Append("/");
                int keyInt = (int)data.keyCode;
                sb.Append(keyInt);
                break;
            case Type.Mouse:
                sb.Append("m/");
                if(data.mouse == MouseType.LeftButton){
                    sb.Append(WM.WM_LBUTTONDOWN);
                }else if(data.mouse == MouseType.RightButton){
                    sb.Append(WM.WM_RBUTTONDOWN);
                }
                sb.Append("/");
                sb.Append(0);
                break;
        }
        sendEvent.Invoke(sb.ToString());
    }

中継アプリ → ゲーム

調査した結果SendKeysかSendMessage、PostMessageという選択肢がありました。

しかし、SendKeysは押した情報しか送らず、離したという情報を送る方法がなかったので却下 docs.microsoft.com

       /// <summary>
        /// 受診時イベント
        /// </summary>
        /// <param name="text"></param>
        private void ReceiveEvent(string text)
        {
            if (window == IntPtr.Zero)
            {
                return;
            }
            Invoke(new Action(() =>
            {
                AddLog(text);
            }));
            string[] args = text.Split('/');
            switch (args[0])
            {
                case "s":
                    Invoke(new Action(() => { DeviceName.Text = args[1]; }));
                    break;

                case "k":
                    KeyEvent(args[1], args[2]);
                    break;

                case "g":
                    GyroEvent(args[1]);
                    break;

                case "m":
                    MouseEvent(args[1], args[2]);
                    break;

                case "e":
                    Invoke(new Action(() => { DeviceName.Text = "None"; }));
                    break;
            }

        }

        private void KeyEvent(string typeStr, string keyStr)
        {
            int type = int.Parse(typeStr);
            int key = int.Parse(keyStr);
            uint repeatCount = 0;
            uint scanCode = 0x2D;
            uint extended = 0;
            uint context = 0;
            uint previousState = 0;
            uint transition = 0;
            uint lParam = 0;
            // combine the parameters above according to the bit
            // fields described in the MSDN page for WM_KEYDOWN
            if (type == 256)
            {
                lParam = repeatCount
                    | (scanCode << 16)
                    | (extended << 24)
                    | (context << 29)
                    | (previousState << 30)
                    | (transition << 31);
            }
            if (type == 257)
            {
                previousState = 1;
                transition = 1;

                lParam = repeatCount
                    | (scanCode << 16)
                    | (extended << 24)
                    | (context << 29)
                    | (previousState << 30)
                    | (transition << 31);
            }

            PostMessage(window, type, key, lParam);
        }

参考サイト キーストロークをウインドウに送信 | C#

このlParamってあるじゃないですか

キーボード入力の概要 - Win32 apps | Microsoft Docs

キーストロークメッセージフラグっていう奴らしいんですけど、押したばっかりかどうか?とか色々送ってるみたいなんですよね

ここが一番難所でしたがコピペで解決しました()

参考サイト c# - Setting WM_KEYDOWN lParam parameters - Stack Overflow

送信先のゲーム選択

最初はアクティブな画面に送っていたのですが、送信先が選べないのが不便なの選択式にしました。 f:id:sanaftg:20210626171306p:plain

       private void LoadWindow()
        {
            window = GetForegroundWindow();
            windowList = new List<IntPtr>();
            target.Items.Clear();
            foreach (Process p in Process.GetProcesses())
            {
                if (p.MainWindowTitle.Length == 0) continue;
                if (p.MainWindowHandle == IntPtr.Zero) continue;
                windowList.Add(p.MainWindowHandle);
                target.Items.Add($"{p.MainWindowTitle}");
                if (window == p.MainWindowHandle)
                {
                    target.SelectedIndex = target.Items.Count - 1;
                }
            }
        }

イクラでテスト

案外あっさり動きました。作業時間は6時間弱?

サブのプロジェクトとしてちょっとづつ進めようかなと思ってます~!

アプリ側でキー設定や配置の変更ができるようになったらまだまだ夢広がりそうですね。

では今回は長くなってしまいましたが最後までお付き合い頂きありがとうございました~!