テラリウム徹底攻略ガイドActualDirectionを使ったコミュニケーション大石 晃裕+デジタルアドバンテージ |
まずは、移動角度であるActualDirectionを使ったコミュニケーションを考えてみよう。
ActualDirectionは、あるターゲットが現在移動している方向を示す情報であり、0〜359度の範囲で取得できる。これはちなみに、単位がdegreeのint値だ。
取りあえず、簡単なサンプルを作成してみた。この生物はひたすらコミュニケーションを行い続けるだけの生物なので、あくまで実験用である。データを受信したり、発信したりするときにWriteTraceメソッドを呼び出すので、トレース・ウィンドウで確認すれば、データがやりとりできていることを確認できるはずだ。
ちなみに、コミュニケーションの機能はSpeakクラスとListenクラスにまとめている。
→コミュニケーションを実装したクラス(communication1.cs)のダウンロード
→サンプル動物(animal1.cs)のダウンロード
いうまでもなく、テラリウムはコンピュータ上で動くソフトであり、生物はプログラムである。従ってそれらがコミュニケーションするということは、「データ」のやりとり、つまり「ビット列」の送受信を可能にすることである。そこで、ActualDirectionを使ったコミュニケーションでは、byte型のデータを相手に送ることを目指す。
次は、そのデータをどういう手順で相手とやりとりをするかということを決定しなくてはならない。いい換えれば通信プロトコルである。
まずは以下の図を見てもらいたい。ActualDirectionを利用したコミュニケーションで筆者が規定したプロトコルの図である。
ActualDirectionを利用したコミュニケーションのプロトコル |
3段階に移動時の角度を変化させ、1byteのデータを送受信する。ここに示した順序の場合には、先頭のビット8は0となるが、送信する順序が、、の場合は、これを1とする。 |
8bitのデータを送るために若干複雑になってしまった。図から分かるとおり、ここでは、3段階に移動時の角度を変化させることによって、1byteのデータを送受信するようにしている。
では、詳しく説明していこう。
まず筆者は、ActualDirectionの値を観察してみたのだが、この値はかなり変動が激しいことが分かった。同じDestinationへ移動している場合でも、かなり変動する場合がある。だが、その中で安定しているケースがいくつか見付かったので、これをキーとして識別するようにした。具体的には、
- 0、90、180、270度
- 48、131、228、311度
のときである。前者はそれぞれ左、真上、右、真下の場合であり、後者は前者それぞれの中間を取ったものだ(普通に考えれば45、135、225、315度となるはずなのだが、なぜかこのように若干ずれた値になるようだ)。
ここで重要なのは、通信とは無関係な本来の移動と、通信のための移動を区別することである。これがなければ、データをデータであると認識することができない。この目的のために、安定して値を得られる上記の2つの場合を利用する。前者、後者の順で、または後者、前者の順で移動しなければ、それをデータとして認識しないようにしている。
前者は4通り、後者も4通り存在するので、これを利用してデータを送ることが可能である。4通りであるので、bitに直せば、それぞれ2bit送ることができる。
そしてそのチェックを行ったあとで、先ほどの前者、後者以外の8方向でデータを送っている。これは、前述したとおりActualDirectionの値が安定していないので、かなり広い範囲を許容するようにしている。これで3bit送ることが可能だ。
さて、ここまでのビット数を合計してみると、2+2+3=7となり、7bitである。これではbyte型は表せない。実はこれが、前者(0、90、180、270度)と後者(48、131、228、311度)を入れ替えられるようにした理由だ。これで1bitを表現し、合計で1byte(8bit)を表現することにしたわけだ。
また、もう1つ気を付けねばならない点がある。これは後述するCurrentMoveToActionを使ったコミュニケーションでも同じなのだが、コミュニケーションの形態はブロードキャストとなる。つまり、データを発信すると、その発信側の生物を視界に収めることができる生物すべてにデータが発信される方式だ。これは、なるべくコミュニケーションの効率を上げることを考えたためである。イーサネットなどがそうであるように、基本的にブロードキャスト型の通信メディアであっても、上位プロトコルを既定すれば、特定の生物同士だけで通信を行うことができる。
さて、説明はこれくらいにして、以上の通信メカニズムを実装してみよう。以下、受信側と送信側に分けてそれぞれ実装してみる。
受信側の実装
受信側の実装はそれほど難しくない。送信側生物のActualDirectionを直接取得できるからだ。このそれぞれの角度に、それが指し示すbitを割り当てるだけでよい。ただし3段階の処理が必要である。
実装が必要な手順をまとめると、次のようになる。
- ActualDirectionを調査し、0、48、90、131、180、228、270、311度を検出する。
- 始めに検出したのが「0、90、180、270度」なら、次に「48、131、228、311度」を検出する。逆に「48、131、228、311度」なら「0、90、180、270度」を検出する。
- 最後に「0、48、90、131、180、228、270、311度」の間の角度を検出する。
- そして、それぞれのデータを合成し、発信されたデータを復元する。もちろん、途中でこれらの手順から外れた移動を検出した場合には、それはデータではないと見なす。
発信側の実装
発信側の場合は、直接自分のActualDirectionが指定できないので、若干工夫する必要がある。サンプルでは、以下のようにした。
まず、自分を中心とした4×4の正方形を考える。X方向、Y方向は画面座標と同じ方向で、現在自分がいる位置を(0、0)として考えると、ActualDirectionと、移動目標の座標は次のように対応付けることができる。
ActualDirection | 移動目標の座標 |
0 | (-2, 0) |
0〜48 | (-2, -1) |
48 | (-2, -2) |
48〜90 | (-1, -2) |
90 | (0, -2) |
90〜131 | (1, -2) |
131 | (2, -2) |
131〜180 | (2, -1) |
180 | (2, 0) |
180〜228 | (2, 1) |
228 | (2, 2) |
228〜270 | (1, 2) |
270 | (0, 2) |
270〜311 | (-1, 2) |
311 | (-2, 2) |
311〜 | (-2, 1) |
これは、先ほどの図において、矢印が指している位置を座標として表したものだ(もちろん、ActualDirectionがどのような方向のときにどのような値を返すのかは直接観察して調べる必要があるが、これはその結果である)。
この表ができれば、あとは自分の中心座標とこの表を足し合わせて移動する目標を決定すればよい。ただし、実際の実装ではいくらかの余裕を持たせる意味で、一辺の長さが4の倍数の正方形を利用するようにしている。
ここまでで、実装の概要を説明した。では最後にサンプルに使われているコミュニケーション用クラス群の利用法を説明しよう。まず、全体の構造だが、主に2つのクラスで構成した。SpeakクラスとListenクラスがそれである。それぞれについて解説していく。
受信を行うListenクラス
まず初期化だが、
Listen Listener = new Listen(this); |
のようにして行う。引数で渡しているのは、Listenクラスが利用されるAnimalクラスのオブジェクトへの参照である。これは、相手のActualDirectionを取得するときにLookForメソッドを利用するために必要となる。普通はthisを渡せばよい。
次にイベントの登録である。Listenクラスでは、データを取得したときにイベントとしてCatchDataイベントが発生するようになっている。このメンバにデリゲートを登録すればよい。それが以下のコードである。テラリウムでは、コンストラクタに初期化処理を記述することは推奨されていないので、Initializeメソッドでこの初期化を行えばよいだろう。
protected override void Initialize() |
Listenerは、Listenクラスのフィールド・メンバであり、OnCatchDataは、イベントを処理するために、このクラスに用意されているメソッドである。
そして、ある生物からメッセージを聞きたい(受信したい)場合には、以下のように指定する。
Listener.BeginListen(OrganismState target); |
ここで指定したtargetは、これからデータを取得したい相手だ。このメソッドを呼び出したときから、(もし可能なら)データの取得が開始される。
ちなみに、1つのListenオブジェクトは一度に1つの相手からしかデータを取得できない。複数から取得したい場合は、このオブジェクトを複数作成する必要がある。
これ以外の機能についても、いくつか補足しよう。
コミュニケーション中にエラーが発生したときには、ListenErrorイベントが発生するようになっている。これを取得したければ、同じように、
Listener.ListenError |
とすればよい。OnListenErrorは、エラーにより発生したイベントを処理するハンドラである。
Listen中かどうかを判定するためのプロパティとして、IsListeningも用意した。
if(Listener.IsListening) |
また、受信を終了したいときはStopListenメソッドを呼び出せばよい。
Listener.StopListen(); |
送信を行うSpeakクラス
次はSpeakクラスである。まず初期化だが、これはListenクラスとまったく同じだ。
Speak Speaker = new Speak(this); |
このように、引数としてはthisを渡せばよい。thisを渡すのは、BeginMovingメソッドをSpeakクラスが利用するためである。
次はイベントの登録だ。Speakクラスには、SpeakCompletedイベントが実装されている。登録は次のようにする。
Speaker.SpeakCompleted |
あとは、
Speaker.BeginSpeaking(Data); |
とすれば、データを発信(ブロードキャスト)することができる。このBeginSpeakingメソッドは、引数としてbyte型のデータを取るようになっていて、これが相手に送られるデータとなる。
ただし注意点として、Speak中はBeginMovingメソッドを呼び出してはいけない。Speakクラスのメソッドは、移動を利用している関係で、BeginMovingメソッドとは排他的な関係にある。
これにはSpeakクラスが用意している、IsSpeakingプロパティを使って、
if(Speaker.IsSpeaking) |
などとすればよいだろう。
最後に少しほかのパブリック・メンバについて補足していこう。
public int UseSpeed=2; |
前者の変数UseSpeedは、データ発信に使う移動のスピードである。Speakクラスはこの値を使ってBeginMovingメソッドを呼び出す。
次の変数MovingSquareSideLengthは、データ発信のときに、BeginMovingメソッドに指定する座標に影響を及ぼす。先ほど説明したように、Speakクラスは自分を中心とした正方形を考え、その上で指定するのだが、その正方形の一辺の長さに相当するのがこの値だ。これは4の倍数でなければならない。詳しくはプログラム・コードを見てもらいたい。
void StopN(); |
このメソッドは、強制的にデータの発信をやめる場合に呼び出す。これを呼び出せば、直ちにSpeakの処理は停止する。
INDEX | ||
[連載]テラリウム徹底攻略ガイド | ||
第7回 ダンスによる動物間コミュニケーション | ||
コミュニケーションの基本 | ||
ActualDirectionを使ったコミュニケーション | ||
Destinationを使ったコミュニケーション | ||
コミュニケーションの利用 | ||
「連載 テラリウム徹底攻略ガイド」 |
- 第2回 簡潔なコーディングのために (2017/7/26)
ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている - 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう - 第1回 明瞭なコーディングのために (2017/7/19)
C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える - Presentation Translator (2017/7/18)
Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
|
|