C#でUPnPを使った ポートマッピング

ゲームを始めオンラインでの通信機能が必要になる事は多いと思います。

そんな時立ちはだかる難関の1つ。それはNAT越えです。

NAT越えをする手段はいくつか考えられますが、今回は一番簡単(?)なUPnPを使ってルーターにポートフォアリングを設定して通信通路を確保したいと思います。 この記事はその時の記録です。

実行・テスト環境

テスト環境:Unity 2018 2.9f1

今回のプログラムはUnity+C#で作成。

パケット送信などの仕組み・ソースコードは 前に書いた記事「UnityでUDP通信を行うには」 などを参考にしてください。

ルーターにポートフォアリング(ポート転送)をリクエストする

ネットワーク内のルーターを探索する

探索の為に「M-Search」リクエストを送信先「 239.255.255.250 : 1900 」(ブロードキャスト)にUDPで送信します。

「M-SEARCH」送信の様子

その後ルーターなどUPnP対応機器からのリスポンス「NOTIFY」が同じアドレスで返ってくるので受信します。

UPnP対応機器からのリスポンス「NOTIFY」

ルーターの情報を取得

先ほどの情報を元にルーターに「HTTP / GET」で詳細情報をリクエストします。

リクエストは以下のようなプログラムで実装しました。


私の環境では「192.168.0.12」のPCから「192.168.0.1」のルーターにリクエストを送信している

なお、リクエストに成功すると「HTTP 200 OK」が返され、XML形式の情報を入手することができます。

そのXMLのうち今回は <controlURL> の情報を元にリクエスト先アドレスを入手します。

リクエストをルーターに送信

先ほどの情報を元に対象のアドレスに「HTTP / POST」でリクエスト内容(SOAPプロトコル)を送信します。

今回テストに送信したリクエストは以下の通りです。

このリクエストではポート「12345」のUDP通信を「192.168.0.12」に転送するリクエストをしています。

ExternalPortとInternalPortは転送元と転送先なので通常同じにします。

リスポンスを受信し成功確認

上のSOAPリクエストを送信するとルーターからリスポンスを受け取ります。

この時、「HTTP 200 OK」なら成功です。

HTTP/1.1 200 OK で成功だとわかる
ルーターの管理者ページでも確認できる

成功すると上図のように管理者ページにも追加されているのがわかる。

なお、失敗だと「 HTTP/1.1 500 Internal Server Error 」が返されます。失敗原因としては「既にポートが使用中」「UPnP設定の上限まで達している」などが挙げられます。

そもそも何も返ってこない場合、そもそもどこかが間違えているはずですので再確認してみてくださいね。

外部からの送信テスト

ポートフォアリングの設定などがしっかり出来ていることを確認するために外からUDPでパケットを送信してみます。

パケット送信アプリで送信テスト

スマートフォンからDocomo(4G)のキャリア回線で自宅のパソコンへとメッセージを送信。

パケット(メッセージ)が届いているのが分かる

受信できていれば完璧です。

オンラインゲーム作りの第一歩ですかね^^

今度は「UDPホールパンチング」という方法でも挑戦してみたいと思います。
またその時は記事を書きますね


ここまで読んでくれて本当にありがとうございました~^

ホームページでは他にも

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

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

また、「このアプリの作り方を知りたい。この部分どうなってるの?」「身の回りのこんなもの作れるの?」などなどご意見何でも受け付けていますので是非連絡くださいね!

Unityでカメラ(PC/スマホ)を使う方法

Unity上で「カメラを使って~をしたい!」「写真を撮りたい!」という方、多いのではないでしょうか?そこで今回はUnityでカメラを使う方法について書きたいと思います。

カメラにアクセスするには?

「WebCamTexture」といったクラスを使います。

このクラスを使うと、カメラ(Webカメラ・スマートフォンのカメラ)で映し出された映像をテクスチャとして扱うことができまるようになります。

※なお、スマートフォンの場合フロント・リアカメラどちらにでもアクセスすることが可能です。

とりあえず映してみる

まずは「どうやってプログラムを書けばカメラの映像を出せるかだけが知りたいんだよ」という方も多いと思うのでとりあえずサクッとカメラの映像を表示させてみようと思います。


もし、PCにカメラがついていない環境で開発する人(デスクトップPCなど)は

このような安いものでも探してみるといいかもしれません~👼


1.まずプロジェクトを作成し、カメラの映像を表示させるための「RawImage」を設置します。

※「Create」→「UI」→「Raw Image」から作成可能です。

2.スクリプトを作成します。

ファイル名:WebCameraTest.cs

3.スクリプトをシーン上のオブジェクト(今回はEventSystemにしているが何でも良い)にアタッチし、その後スクリプト内RawImageにもアタッチする

4.実行してみる。PCの場合はカメラを接続してから実行してください。

しかし!環境によって次の問題が発生する場合があります。

・スマホで実行したとき、求めているカメラの方向ではない(リア・フロント)
・PCでやったがそもそも映らない
・移された画像の向きが反対

これらには理由がありますので次項で解説していきたいと思います。

WebCamTextureをいじってみる

使用するカメラを選択・変更する

アプリの実行環境によってはカメラがいくつか複数個、接続されている可能性があります。この場合、表示させるカメラを選ばなくてはいけません。

もし前項で実行したが表示されなかったという人は、表示できないカメラにアクセスしていたのかもしれないのでここで正しい表示させたいカメラを選ぶことが大切です。

カメラを選択する

先ほどのソースコードに追加し、ボタンをクリックするとカメラを変更できるようにしたいとおもいます。

スクリプトを上のように変更し、シーン上に変更用ボタンを生成します。その後、ボタンのOnClickにpublic void ChangeCamera()を割り当てれば完了です。

ボタンを押せばカメラが切り替わると思います。(複数個接続時)

※また、スマホのリア・フロントカメラは別々のカメラとして扱われます。

取得する画質・フレームレートを変更する

WebCamTextureのインスタンス生成時に引数として画質やフレームレートを指定すると変更することができます。

カメラによっては対応していない場合もあります。
(例:HDカメラでFull HDをリクエスト等)

カメラをミラー表示にする

この映像を、

こうする。

やり方はRawImageのスケール(Scale)のXを-1にするだけです。

上下反転したい場合はYを-1にすればOKです。

3Dオブジェクトに表示する

このように3Dのオブジェクトにカメラの映像を張り付けることもできます。

GameObjectのテクスチャをWebCamTextureに変える事で実装可能です。

まとめ

ここではUnityでカメラを使う方法について説明しました。カメラを使えば写真を撮る以外にも様々な使い方ができるようになるのではないでしょうか?

Unityでカメラを使った例:
Webカメラを使って顔認識 モーションキャプチャーへの道(Part 1)

これからもカメラを用いた様々な使い道を見つけていきたいですね!

読んでくれて本当にありがとうございました^^

もしよろしければ他の記事も観ていってくださるとうれしい限りです!

Unity上でUDP/IPを用いた通信機能(マルチプレイ)を実装する

こんちゃ!洋梨🍐です。

unityでマルチプレイをはじめとする通信機能を実装したい方、多いのではないでしょうか?

そこでUNET(廃止予定の様ですが…)、Socket.ioなどを使おうと考えた・考えている方も多いと思うのですが私は勉強の為やライセンスの問題、使いやすさの点から1から作ってみたのでそれまでの過程及びソースコードなどを記事にしました。

今回作るもの

今回はUnityの動く端末間で、オブジェクトの位置情報を共有できるようにしていきたいと思います。


PC・スマホ間でステージ内Cubeの位置情報を共有している図

応用次第ではクライアントサーバー形式・p2p形式でのマルチプレイ機能実装など様々なことに使えるのではないでしょうか?

仕様

今回はゲームオブジェクトの位置共有、つまりTransform.positionのVector3 情報を受信及び反映、送信したいとおもいます。

なお、今回行う通信はアクションゲームで使うことを推定し、高速化を図るためUDPで行います。

Tips 「TCPとUDPの違い」
TCP : 安定性は高いが低速。

>ゲームではチャットやマッチメイキングに使われる。
UDP : 不安定だが高速。

>FPSなど速さが大切なゲームの通信で使われている。
(※RUDP : 二つを組み合わせ安定性、高速を兼ねそろえたもの。実装はアプリ側。)

プロジェクト

今回作ったプロジェクト

今回はステージ内に配置したCubeの座標を共有します。椅子とかは距離感をつかみやすい様になんとなくおいた飾りです。Textには現在のCubeの座標を表示しようと思います。

(プロジェクトファイルのダウンロードは下にあります。)

スクリプト

main.cs メイン。主な動作はここに書く。
UdpSystem.cs UDP通信の動作をまとめてあるものです
MyNetwork.cs IPアドレス取得等、ネットワーク関連の基本動作を担当

(ソースファイルのダウンロードは下にあります。)

ポート番号

同じでもいいのですが今回はホスト端末Aには5001、クライアント端末Bには5002としてあります。なお、送信に使うポートは6001としました。

パケット構造

今回は位置情報の共有のみなのでVector3(x,y,z)の情報が入るパケットを作って送ります。パケット情報を管理するクラスは以下の通りです。

DATAクラスはインスタンス生成時の引数(Vector3もしくはbyte配列)に応じてfloat x,y,zの値に変換します。インスタンス生成後、ToVector3(),ToByte()で双方に変換できるようにしています。

Vector3のそれぞれの値(x,y,z)はfloat型なので4byteです。そのためパケットサイズは12Byteになります。パケットの構造は以下の通りです。

今回の送信パケットのデーター構造

※今回は受信側・送信側と1対1での通信で一つのオブジェクトの位置座標共有しかしていないためパケットに識別番号や送信元IPなど情報を含んでいません。なお、TCPなら送信元IP等がなくてもコネクションでわかりますがUDPはそうもいかないため含まないと判別できません。

ソースコード

※なお、今回紹介するソースコードは以前私が作ったマルチプレイ仕様ゲームの一部を切り取り、説明用に編集したものになります。

Main.cs

UdpSystem.cs

ライセンスなんて書いちゃってますけどこの Younaship.com をどっかに紹介してほしいなーって思ってるだけなんであまり気にしなくて大丈夫です。

ソースファイル説明

UdpSystem.cs の説明です。

Class : UDPSystem()

コントラクタ
UDPSystem(Callback)

インスタンス生成時、コールバック関数を引数としてとります。このコールバックは受信時発生するため送信用として生成するときはnullでも大丈夫です。

関数

Set(hostIP,hostPort,clientIP,clientPort)
自分及び相手のIP、ポートをセットします。
Receive()
受信を開始します。受信するとコールバックが呼ばれます。
Send(byte[])
byte[]を「clientIP:Port」へ送信します。
Send_NonAsync(byte[])
byte[]を同期送信します。※同期送信なので送信中はほかの動作は停止します。
Stop()
WebSocket を閉じます。

Class : ScanIPAddr()

自分のIPアドレス取得用のクラスです。
var myIpAddr = ScanIPAddr.IP[n] といった感じで使います。
nには識別番号を入れます。

~NOTE~
例えばスマートフォンの場合、Wi-fi接続時はキャリア回線(4G/3G)でのIPアドレス・Wi-fi上でのIPアドレスが存在してしまうため選ばなくてはいけません。また、PCの場合仮想PCのネットワークアダプタがあるとそのIPアドレスが存在してしまいます。どちらにおいても優先順位が高いほう(接続中)が0になっているはずなのでデフォルトでは0でいいと思います。状況に合わせて変えてください。

Class : ScanDevice()

特に現段階であまり意味ないです。気にしないでください。

ソースファイル・動作説明

・まず通信を開始するためにUdpClientクラスのインスタンスを生成します。

ホスト側 (ipAddr)
udpSystem = new UDPSystem(null);
udpSystem.Set(ipAddr, 5001, ipAddr2, 5002);

クライアント側(ipAddr2)
udpSystem = new UDPSystem((x) => Receive(x));
udpSystem.Set(ipAddr2, 5002, ipAddr, 5001);
udpSystem.Receive();

クライアント側には受信時の動作としてReceive(byte[])を指定しています。
これで指定ポートでの受信時にReceive(byte[])関数が呼ばれるようになります。(引数は受信したByte配列)

・Update()内で繰り返し行う部分を書き込みます

ホスト側
vector3 = gameObject.transform.position;
DATA sendData = new DATA(vector3);
udpSystem.Send(sendData.ToByte(),99);

ホスト側はゲームオブジェクトの座標を取得・送信を繰り返します。
SendData.ToByte()でVector3の座標情報をByte配列に変換し、その内容をudpSystem.Send()で送信しています。

またSend()で送信する際に引数99をとっているのは理由がありまして、私の作ったSend関数は送信に失敗した場合(送信処理中などで)に10回までコンテニューをするようになっているためにループで呼ぶ際は失敗するとどんどん送信待機中のデータが溜まってしまうので引数に10以上の数値を入れることで失敗しても1度しか送らないようにし、回避しています。この辺は各自でカスタマイズしてください。

クライアント側
gameObject.transform.position = vector3;

vector3に保存されている値をゲームオブジェクトに反映させています。

void Receive(byte[] bytes)
{
DATA getData = new DATA(bytes);
vector3 = getData.ToVector3();
}

クライアント側のみサーバーからデータを受信時この関数が呼び出されます。受信したByte配列のデータを使える形に変換及び読み取り用の変数vector3に代入しています。

共通部分
text.text = “(” + vector3.x + “,” + vector3.y + “,” + vector3.z + “)”;

現在のゲームオブジェクトの位置を数値として画面に表示しているだけです。


ダウンロード

「ソースコード見ただけじゃ分からんわ👼」
「いや、動かんやけど♡」

という方などに向けてプロジェクトやソースコードの配布を行います。
※なお、全ての環境における動作保証はしていません事をご理解ください。

当方が確認した動作環境

・「デスクトップPC」- (有線LAN) – (wi-fi) -「スマホ」
・「ノートPC」- (wi-fi) – 「スマホ」
・ 「デスクトップPC」- (有線LAN) -「ノートパソコン」

での通信ができることを確認しました。なお、スマホは「Android(Xperia-Z5及びX)、iphone 6」を用いて確認しました。

ソースファイル ( main.cs、UdpClient.cs )

(2019/01/18 追記) 一応これでも動きますが変な部分・書き直すべき部分があったので一部書き直しています。いつか新しいソースコードに変えておきます。

Unity プロジェクトファイルのダウンロード (zip形式)


ここまで読んでくれてありがとうございました!
沢山の人でゲーム業界・アプリ業界など盛り上げていけるといいですね!

ホームページでは他にも

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

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

また、「このアプリの作り方を知りたい。この部分どうなってるの?」「身の回りのこんなもの作れるの?」などなどご意見何でも受け付けていますので是非連絡くださいね!