見出し画像

vvvv gamma入門 - 8 デリゲート

今回はデリゲートの使い方をメモしようと思います。

デリゲートとは

デリゲートとは何ぞや? とかデリゲートを使うと何が良いのか? といった解説は今更自分が書くまでもなくネットの海に沢山漂っていますが、一応簡単に書いてみます。

デリゲートは委譲という意味です。現実世界の仕組みを表現するというオブジェクト指向の概念から生まれたものですから、現実世界にもデリゲートを使う場面が存在するはずです。
例えば次のような状況です。

営業のAさんは来客の予定があるので会社のエントランスホールに常駐している受付係のZさんに次の様なメモを渡します。
「〇〇株式会社の□□さんがいらっしゃったら会議室を案内して下さい。」
また、総務部のBさんはZさんに次の様なお願いを書いたメモを渡すかもしれません。
「宅配便が来たら着払いなのでこちらの封筒のお金で支払って荷物を受け取ってください。」
受付係のZさんはメモの内容を対応したあと、依頼者(AさんやBさん)に内線をかけて対応完了の連絡をします。内線番号はメモに書いています。

このメモの内容を実行することがデリゲートです。デリゲートには入力(封筒に入ったお金)や出力(内線連絡、荷物)があることもあります。

ここで重要なのはメモの内容を実行しているのはZさんであってメモの差出人は誰でも構わないという点です。
「メモにやってほしい内容と内線番号を書いて渡す」(時には封筒に入ったお金も添えたりして)という決まり事さえ守っておけば、依頼者が誰だろうが関係なくZさんは忠実に職務を遂行します。この決まり事をプロトコルといいます。
プロトコルさえ守っておけばZさんは依頼者が誰なのか気にしなくても構いません。
これをプログラムで表現するとZさんクラスは依頼者クラスのことは知らなくていい、つまり参照を持たなくても良いということです。

もしデリゲートが無かったらどうなるでしょうか? Zさんは自分で対応が出来ないので、Aさん達も受付に常駐しなければならないか、来客の度にAさん達を呼んで来なければなりません。

ちょっと妥協してZさんが来客の度にAさん達を呼ぶ対応でも良いとしましょう。その場合、Zさんは社員全員の座席または内線番号を知っておく必要があります。
もし配置換えなどで座席や内線番号の割当が変わってしまったらZさんの持っている情報も更新が必要です。
これをプログラムで表現するとZさんクラスは来客時に必要になる全ての会社スタッフクラスを参照しておかなければなりません。管理がとても大変そうです。

デリゲートを活用すると上記の様な状況をスマートに処理する事が出来ます。

MVCフレームワーク

今回はデリゲートを使ってMVCフレームワークを実装してみます。MVCフレームワークについても今更自分が書くまでもなく沢山資料が見つかると思いますが、こちらも一応ざっくりと解説します。

MVCフレームワークはGUIがありユーザーインタラクションが発生するソフトウェア開発で良く使用されるフレームワークです。
ユーザーの操作を受け付け、処理し、画面描画を更新するということをどうやって実装すれば開発しやすいのか? ただ機能要件を実現するだけでなく、保守しやすく、変更や拡張にも強くしたい、ということを頭の良い先人達がよ~~く考えて作った人類の英知なのです。

今回はマウスでクリックした場所に3Dオブジェクトを置くという簡単なアプリを作ります。

MVCフレームワークを検索するとこんな感じの図が見つかると思います。

MVC

ネットの記事によってはバリエーションの違いというか若干矢印の関係が違ったり、そもそも間違っていたりするので注意が必要なのですが、今回この記事で実装するMVCフレームワークにおいてはこの図をイメージして実装します。

ではまず登場人物であるModel、View、Controllerを定義してみます。

登場人物たち

それぞれMyModel、MyView、MyControllerとしました。

MyModel

MVCにおいてModelはデータの管理やビジネスロジックを持ちます。
MyModelはシーンに配置する3Dオブジェクトの位置情報とそれらの管理オペレーションを持ちます。毎フレーム処理する物ではないのでClassとして定義しています。
3Dオブジェクトの位置情報を保持するので、そのパラメーターは変更が必要なため、RecordではなくClassを使っています。

MyView

Viewは画面の描画を担当します。つまりStrideのレンダリング画面(SceneWindow)やマウス入力を処理するため毎フレーム実行される必要があるのでProcessとして定義しています。

MyController

ControllerはModelとViewの手綱を握ってそれらを管理する役割です。実際の処理は極力行わず、Viewからのイベントを受け取ったり、Modelに命令だけします。
Classでも(恐らくRecordでも)やりようはあると思うのですが、ProcessであるMyViewと密にやり取りする関係でProcessにした方が実装しやすかったのでProcessにしています。

MyModel

それぞれの実装の詳細を見ていきます。まずはMyModelです。

MyModel

MyModelは3DオブジェクトのデータであるTransformationをSpreadで保持します。Transformationsというパラメーターです。型はSpread<Matrix>です。
Transformationsのセッター(追加)とゲッターがあるだけのシンプルなClassです。

MyView

MyViewの全体像はこちらです。

MyView

ちょっと量が多いですがエリアごとに説明していきます。

Create

3Dオブジェクトのデータを取得するため、MyModelへの参照が必要なのでCreateでMyModelを渡しています。

Stride scene

ベースとなるStrideのシーンです。OrbitCameraとSkyboxLightだけ置いています。

Get click position

StrideのSceneWindowをクリックした時、マウスカーソルの位置からレイを飛ばして地面にヒットした場所を特定しています。
地面としてPlaneノードを使用して見えない板を置いています。

Mouse pointer

マウスポインターとして、レイがヒットした場所に赤いスフィアを表示しています。ぶっちゃけ無くてもよいです…。

Mouse left click

マウスの左クリックを検出する処理です。
TogEdgeノードはトグルに変更があった1フレームのみ反応するというノードです。一瞬だけ反応させたい時等に重宝する超便利ノードなので覚えておくとよいと思います。
トグルのOn->Offの時とOff->Onの時のどちらに反応させるか選ぶことができます。

Instancing 3D objects

MyModelからTransformationsを取得して、その数だけBoxを生成しています。
Help BrowserからStrideのInstancing系のヘルプパッチが参考になります。

Invoke delegate

ようやく今回のメイントピックであるデリゲートに関連する部分が登場しました。

Invoke delegate

OnLeftClickというInputでデリゲートを受け取って、Invokeノードで実行します。
マウスを左クリックした時だけ実行するので、Ifで囲っています。

Delegate/Invokeでちょっと分かりづらいのは引数と返り値(プロトコル)の定義です。
Invokeを検索するとずらっと数字が違うInvokeノードが並んでいます。

Invokeノード

これらの数字は何を表しているかというと、引数(Input)の数と返り値(Output)の数です。
今回は、1つの引数としてMatrixをとり、返り値は何も返さないDelegateを実行するためInvoke(1->0)を使用します。

Invokeノードの1つ目のピンにはDelegateを渡しますが、この入力の定義も最初は戸惑うかもしれません。

Delegate(1->0)<Matrix>
Delegate(1->0)<Matrix>

Configureで定義する場合に上記の様に書いても良いですが、以下の様に簡単に書くこともできます。(自動でに上記の形式に変換されます。)

(Matrix)->()

MyController

最後はControllerです。

MyController

CreateでMyModelを作成しています。
MyViewノードも配置します。

Delegate

いよいよDelegateの登場です。

Delegate

DelegateではMatrixを入力で受け取って、MyModelのAddTransformationsオペレーションを呼び出しています。

Delegateの作り方

Delegateを作成するには、ノードブラウザーでDelegateを検索して配置しますが、最初はこの様な見た目です。

Delegate

Delegateと書かれている部分は名前の変更ができません。また、Delegateの出力ピンをMyViewのOnLeftClickピンに繋げようとしても受け付けません。
これはDelegateの引数/返り値の定義がOnLeftClickピンの定義と違うからです。

Delegateの中にInputを置き、型をMatrixに指定すると、OnLeftClickピンにつなげるようになります。
ピンにつなぐと、Delegateの名前がピンの名前に変わります。

型が一致した

完成

ここまで実装できたらMainパッチにMyControllerノードを配置してみます。

実行!

期待通りに動けばStrideのウィンドウが立ち上がります。
クリックするとBoxを配置できます。

クリックでBoxを配置

拡張性を体験する

せっかく実装したMVCフレームワークなので変更のしやすさを体験してみましょう。
今度はマウスの右クリックでBoxを削除できるようにしてみましょう。

MVCフレームワークではビジネスロジックはModelに書きます。Controllerに書いてしまうのはアンチパターンです。
この様にフレームワークを使っていると、どこに何を書くのかが大体わかるため他の開発者と作業するときも意思疎通がしやすいと思います。

ということで、MyModelにTransformationsからSliceを削除するオペレーションRemoveTransformationを実装します。
右クリックした座標を受け取る想定でInputにはVector3型を指定します。
TransformationsをForEachで一つずつクリックした座標との距離を計算します。
距離が最小のインデックスを判定し、そのインデックスのSliceを削除しています。

RemoveTransformation

次にMyViewに右クリックした時の処理を追加します。
マウスの右クリック判定を左クリックの時と同様に実装します。
OnRightClickというDelegateを受け取るInputを作り、OnLeftClickと同様にIfで囲ったInvokeノードへつなぎます。Invokeの引数はVector3が欲しいのでRayIntersectsPlaneのOutputを繋ぎます。

MyViewの修正

最後にMyControllerの修正です。
OnLeftClickと同様にDelegateを作って、今度はMyModelのRemoveTransformationオペレーションを呼びます。それをMyViewに渡します。

MyControllerの修正

今までの実装を崩すことなく追加機能が実装できました!
Controllerはちゃんと司令塔の役割だけ担い、Modelは順調に太っています。

いかがだったでしょうか。Model、View、Controllerが協調して動作している様を楽しめたでしょうか?
自分は初めてMVCフレームワークを意識して実装できたときちょっとした感動を覚えました。
それがvvvvでも実装できるのです。VLは素晴らしいと思います。

それではまた次の記事で。

もしこの記事があなたのお役に立てたなら幸いです。 よろしければサポートをお願いします。今後の制作資金にさせていただきます!