一台のSwitch+桃鉄2020でオンライン対戦する方法

こんちゃ!洋梨です。

先日、待望の新作「桃太郎電鉄 ~昭和 平成 令和も定番!」発売されましたね!

桃鉄ファンの自分にはとても嬉しい限りです。

さて、そんな神ゲー「桃鉄2020」ですが、やはり桃鉄は友達とマルチプレイでやるから面白いと思います。

しかし、オンラインで対戦するには人数分のSwitchや桃鉄のカセットを用意する必要があります。

いくら神ゲーでもオンライン対戦する環境を整えるのは難しい、、、

「桃鉄もってない人ともオンライン対戦がしたい…」

という事で今回は、一台のSwitchと、一つのソフトでオンライン対戦する環境を構築してみました☻

動作の様子

まずは動作の様子をご覧ください。

※動画内にも書いてますが外部の人(LAN外)も使えるという事を示すためにキャリア回線(4G)で動作テストを行っています。

※せっかく作ったのに記事だけだとなんかもったいないので今回は動画も作りました(笑)観てね?w

どうでしょうか?

遅延は多少あるにしても桃鉄の様なターン性のゲームならできそうではないでしょうか?

少なくとも自分はこれで友達と何回か桃鉄をやりましたし、やってます(笑)

仕組み

綺麗にわかりやすく図でかくとこんな感じです^^

構築方法

必要なもの

・Switch + やりたいゲーム

・ビデオ通話用端末、操作用端末

・キャプチャーボード


KeeQii HD HDMI キャプチャーボード USB2.0 1080P HDMI ゲームキャプチャー、 ビデオキャプチャカード ゲーム実況生配信、画面共有、録画、ライブ会議に適用 DSLRビデオカメラ Nintendo Switch、Xbox One、OBS Studio XSplit対応 電源不要

Switchの映像をZoomなど通話アプリに映すために使います。

・Arduino (とりあえずATmega32U4が乗ってればおk)


Aideepen 5個セット プロマイクロATmega32U4 3.3V 16MHz 2列ピンヘッダモジュール交換Pro Mini ATmega328ボードArduinoレオナルド用 (3.3V)

・USBシリアルコンバータモジュール


HiLetgo® CP2102 USB 2.0 to TTL UART シリアル コンバータモジュール 5P STC PRGMR ケーブル付き [並行輸入品]

.

コントローラを作る

まずSwitchをPCから操作できるようにする必要があります。

そのためにコントローラを作る必要があります。

今回はこちらのソースを使わせて頂きました。

これでPCからシリアル通信でSwitchを操作できるようになります。

.

操作用アプリを作る

今回はスムーズに公開、入れてもらうためPWA・Webアプリという形で開発しました。

# index.html
<body class="disable_event disable_selecter">
    <p style="position:fixed;top:0;left:0;font-size:small;color:lightgray;margin: 2px;">KeyTouch v0.2.0</p>

    <img id="status" src="loading.gif" class="enable_event" style="padding: 5px;background-color: gray;z-index: 10;"/>

    <div class="remcon">
        <div class="plus enable_event">
            <div key="U" class="btn" style="top:0;left:80px"></div>
            <div key="L" class="btn" style="top:80px;left:0px"></div>
            <div key="R" class="btn" style="top:80px;left:160px"></div>
            <div key="D" class="btn" style="top:160px;left:80px"></div>
        </div>
        <div class="center enable_event">
            <div key="m" class="btn" ></div>
        </div>
        <div class="onetwo enable_event">
            <div key="1" class="btn" style="top:10px;left:80px"></div>
            <div key="B" class="btn" style="top:85px;left:0px"></div>
            <div key="2" class="btn" style="top:160px;left:80px"></div>
            <div key="X" class="btn" style="top:85px;left:160px"></div>
        </div>
    </div>

</body>
# main.js
    var ws = null;
    var isBusy = false;

    function connect(){
        if(isBusy) return;
        if(ws) return $("#status").css({ transform: 'rotate(-90deg)' }, 1000); 
        $("#status").attr("src","loading.gif");

        isBusy = true;
        ws = new WebSocket("ws://{Host IP Addr}:11111/ws"); // 書き換え
        ws.onopen = ()=>{ //接続できたとき
            isBusy = false;
            $("#status").attr("src","niko.jpg");
            alert("接続しました👼");
        }
        
        ws.onerror = (e)=>{ 
            isBusy = false;
            ws = null;
            $("#status").attr("src","pien.png");
            alert("接続失敗しました(´;ω;`)\n"+e)
        }

        ws.onclose = ()=>{
            isBusy = false;
            ws = null;
        }

        ws.addEventListener('close', function (event) {
            $("#status").attr("src","pien.png");
        });
        
        ws.onmessage = (event) => {
            console.log(event)
        }
    }

    function disconnect(){
        ws.close()
    }

    var KEY_SET = {
        37:"L",
        38:"U", //up
        39:"R",
        40:"D",
        65:"2", // a key
        90:"1", // z key
        81:"m" // q key
    }
    var activeKey = {};
    
    
    $(".btn").on("touchstart",function(){
        console.log("pushed")
        const key = $(this).attr("key");
        $(this).addClass("active");
        if(ws) ws.send(JSON.stringify({uid : "none",type:"push",key:key}))
    });

    $(".btn").on("touchend",function(){
        console.log("pulled")
        const key = $(this).attr("key");
        $(this).removeClass("active");
        if(ws) ws.send(JSON.stringify({uid : "none",type:"pull",key:key}))
    });

    $("#status").click(()=>{
        connect();
    })

とこんな感じに簡単にSocket通信するだけのWebアプリを作ります。

.

ホスト用アプリを作る

次に.NET(C#)でホスト用(Switch操作用)アプリを作ります。

# WsHost.cs
   public class WsHost
    {

        public WsHost(string port)
        {
            serialPush = new SerialPush(port);
        }

        SerialPush serialPush;
        List<Connection> connections = new List<Connection>();

        public async void Start()
        {
            Console.WriteLine("Ws>>11111");

            //Httpリスナーを立ち上げ、クライアントからの接続を待つ
            HttpListener s = new HttpListener();
            s.Prefixes.Clear();
            s.Prefixes.Add("http://+:11111/ws/");
            s.Start();

            Main:
            var hc = await s.GetContextAsync();

            //クライアントからのリクエストがWebSocketでない場合は処理を中断
            if (!hc.Request.IsWebSocketRequest)
            {
                hc.Response.StatusCode = 401;
                hc.Response.Close();
                Console.WriteLine("ops...");
                goto Main;
            }

            //WebSocketでレスポンスを返却
            var wsc = await hc.AcceptWebSocketAsync(null);
            var ws = wsc.WebSocket;

            var con = new Connection(ws);
            connections.Add(con);
            con.AddEventListener((type, str) =>
            {
                if (type == Connection.ActionType.DisConnect)
                {
                    connections.Remove(con);
                }
                else
                {
                    try
                    {
                        DataContractJsonSerializer dc = new DataContractJsonSerializer(typeof(Packet));
                        Packet packet = null;
                        using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(str)))
                        {
                            packet = (Packet)dc.ReadObject(ms);
                        }
                        serialPush.PacketToSerial(packet);
                        break;    
                    }
                    catch { }
                }
            });

            goto Main;

        }

        public int GetConnectionCount()
        {
            return connections.Count;
        }

        public class Connection
        {
            public enum ActionType
            {
                DisConnect, Recive
            }

            public bool isActive = true;

            WebSocket Ws;
            Action<ActionType, string> Callback;

            public Connection(WebSocket webSocket)
            {
                Ws = webSocket;
                Main();
            }

            public void AddEventListener(Action<ActionType, string> callback)
            {
                this.Callback = callback;
            }

            private async void Main()
            {
                while (isActive)
                {
                    var result = await Receive();
                    if (result is null)
                    {
                        Callback?.Invoke(ActionType.DisConnect, null);
                        isActive = false;
                    }
                    else Callback?.Invoke(ActionType.Recive, result);
                }
            }

            private async Task<string> Receive()
            {
                try
                {
                    var array = new ArraySegment<byte>(new byte[256]);
                    var result = await Ws.ReceiveAsync(array, CancellationToken.None);

                    var st = result.CloseStatus;
                    if (st.HasValue)
                    {
                        isActive = false;
                        return null;
                    }
                    var res = Encoding.UTF8.GetString(array.Take(result.Count).ToArray());
                    return res;
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                    return null;
                }
            }

            public async Task Send(byte[] data)
            {
                var array = new ArraySegment<byte>(data);
                await Ws.SendAsync(array, WebSocketMessageType.Text, false, CancellationToken.None);
                Console.WriteLine(array);
            }

        }
# SerialPush.cs     
public class SerialPush
    {
        SerialPort serialPort;

        public SerialPush(string port)
        {
            serialPort = new SerialPort();
            serialPort.PortName = port;
            serialPort.BaudRate = 9600;
            serialPort.Encoding = Encoding.UTF8;
            serialPort.Open();
        }

        void Write(string str)
        {
            str = str + "\r\n";
            serialPort.Write(str);
            return;
        }

        public void PacketToSerial(Packet packet)
        {
            Console.WriteLine(packet.key+" "+packet.type);
            if (!serialPort.IsOpen) return;
            if (packet.type == "push")
            {
                switch (packet.key)
                {
                    case "1":
                        Write("Button Y");
                        break;
                    case "2":
                        Write("Button A");
                        break;

                    case "X":
                        Write("Button X");
                        break;

                    case "B":
                        Write("Button B");
                        break;

                    case "p":
                        
                        break;
                    case "m":
                        Write("Button SELECT");
                        break;

                    case "L":
                        Write("HAT BOTTOM");
                        break;
                    case "R":
                        Write("HAT TOP");
                        break;
                    case "U":
                        Write("HAT LEFT");
                        break;
                    case "D":
                        Write("HAT RIGHT");
                        break;

                }
            }
            else
            {
                Write("RELEASE");
            }
        }
    }

とこんな感じで作ります。

WebSocket通信を受け付け送られてきたものに対してシリアル出力(Switch操作)を行うものになります。

.

完成

あとはこれらをビルドして実行。

Switchに作ったコントローラを接続、Zoomなどに映像を映せばあとは友達とやるだけです!

お疲れ様でした^^

最後に

桃鉄神ゲーなので皆さん買いましょうね^^

対戦者募集中です~^(笑)


ここまで読んでくれてありがとう!

ホームページでは他にも

・様々な記事や作った作品および過程
・ソースコード、素材ファイル
・あらゆる”モノ”の作り方

などなど随時、記事や作品を新規公開・更新していますので是非見ていってくださいね!見ていただけると本当に嬉しいです!