今回はBlueprint Interfaceを紹介して、その重要性を非プログラマにもできるだけわかりやすいように解説したいと思います。
プログラマであれば重要性を理解しやすいと思うのですが、プログラムはとんとわからないという人には説明しづらい部分でもあります。
ただ、ある程度の規模の開発を行っていく上では知っていると知らないとではかなり開発効率に差が出てくると思います。
なので、是非使っていきましょう。
ではまず、Blueprint Interfaceを使用しない場合にどのような問題が発生するかを実際のプロジェクトで見てみましょう。
Blankプロジェクトを作成したら新規のBlueprintを2つ作成してください。
"BP_A"、"BP_B"という名前を付けて両方とも編集画面を開いてください。
次にそれぞれのBlueprintに関数を作成します。
BP_AにはActorを引数とする関数"Test"を作成し、BP_BにはActorを引数とする関数"Test"と引数も何もない"Test2"を作成します。
ここまで来たら一旦両方ともコンパイルしましょう。
次に関数の内容を作成していきますが、内容は、BP_BのTest関数からBP_AのTest関数を呼び出し、BP_AのTest関数からBP_BのTest2関数を呼び出すという、行ってこいの関数呼び出しを行います。
各Test関数の引数は対になるBPが入ってきているものと仮定するのでキャストを行って成功したら呼び出す、という形をとります。
簡単ではありますが、一応関数の実装を張り付けておきます。
(BP_B.Test)
(BP_A.Test)
(BP_B.Test2)
呼び出す順番に並べてあります。
さて、ここまでできたら両方ともコンパイルしましょう。
BP_Bをコンパイルしたらもう一度BP_Aのウィンドウを見てください。
コンパイルボタンがチェック状態ではなく編集後の状態になっていることがわかるはずです。
コンパイルし忘れたかな?と思ってもう一度BP_Aをコンパイルし、BP_Bのウィンドウを見るとこっちもチェックが外れています。
わかりやすいのはこの状態で両方とも保存し、BP_BをコンパイルすればBP_Aに米印がついて編集された扱いになるでしょう。
なぜこのようなことが起こるのか?
それは、それぞれのBlueprintが相手をキャストして直接使用しているからです。
BP_Bがコンパイルされ変更されたとするなら、それを直接参照しているBP_Aも変更された可能性がある、と考えられてしまうわけです。
このような状態を『依存が強い』などと呼び、オブジェクト指向プログラミングにおいては出来るだけ避けないといけないものになるのです。
UE4ではこの状態でPlayボタンを押すと実行するまでのラグが普通より長くなります。
すべて保存している状態で実行するとわかるのですが、依存の強いものに編集後を示す米印が付きます。
UE4は実行時にある程度コンパイル的なことをしているのか、別のBlueprintに依存しているBlueprintが編集された扱いになってしまうようです。
これが実行に時間がかかる要因で、複雑に依存しまくってると結構実行までに時間がかかるようになります。
…経験者は語る。
と、ここまでが今回の話の前座。
BP間のやり取りは完全になくすことはできません。やはり、何らかのやり取りは行わなければなりません。
C++で実装されているものは固定的なものなので、問題がなければC++に移すのも1つの手です。
しかしそういうわけにはいかない場合に使えるのが、今回紹介するBlueprint Interfaceです。
Blueprint Interfaceはコンテンツブラウザの[New] -> [Miscellaneous] -> [Blueprint Interface] で作成できます。
まずは新規に1つ作成してみましょう。名前は"SampleInterface"としておきます。
ダブルクリックすると編集画面になりますが、できることは関数を追加することだけです。
関数追加ボタンを押して関数を追加してみましょう。
普通のBlueprintの関数と同様に関数の入力と出力を追加することができるのはわかると思いますが、ノードの編集画面はグレーアウトして何も編集できないことが大きな違いとなっています。
Actorを引数とする関数"BPITest"を作成しましたが、中身はなにもありません。
Blueprint Interfaceは関数の入り口は提供しますが、その中身については一切提供を行いません。
中身はBlueprint側で実装することになります。
では、この中身をBlueprint側で実装してみましょう。
まず、BP_Aの編集ウィンドウで、上のボタンの中にある [Blueprint Props] をクリックしましょう。
[Details] タブに [Interfaces] という項目が見つかるはずです。
Addボタンを押して先ほど作成したSampleInterface_Cを選択しましょう。
Blueprint Interfaceはここに複数追加することが可能です。追加すると中身のない関数の中身を作成することができるようになります。
中身の実装はEvent Graphで行います。
右クリックしてBPITestと入力すると3つの命令が検索に引っかかります。
それぞれが別の意味を持っていますが、中身の実装は"Event BPITest"で行います。
この実装ではまだActorをBP_BにキャストしてTest2関数を呼び出すようにします。
(クリックで拡大)
これだけではただInterfaceの中身を実装しただけです。
実際にはこの命令はBP_Bから呼び出されなければなりません。
なので、BP_Aのコンパイル後、BP_Bの編集ウィンドウに移動してください。
BP_BのTest関数内で右クリックをし、BPITestと入力すると"Interface Messages"というカテゴリに1つだけ関数が見つかります。
これを配置し、以下のように設定してください。
(クリックで拡大)
これは、BPITestという関数を呼び出す、というより、Targetに対してBPITestというメッセージを投げる、という方が言い方としては正しいのかもしれません。
つまり、BPITestという関数を実行しろ、というメッセージをTarget(ここではTest関数の引数であるActor A)に送り、その時に必要とされる引数のActorは自分自身(つまりはBP_B)を渡しているというわけです。
この状態でBP_Bをコンパイルすると、BP_Bをキャストして使用しているBP_Aは変更扱いになってしまいます。
しかし、その後にBP_AをコンパイルしてもBP_Bには変化はありません。
これは、BP_BがBP_Aに依存しないようになったからです。
Test関数のActor AがBP_Aかそうでないかは関係なくただメッセージを投げる役割しか果たしていないからです。
そのメッセージを受け取ってどう処理するか、もしくは処理しないかはメッセージのTargetで指定したActorにゆだねられるというわけです。
Targetで指定したActorが [Blueprint Props] でBlueprint Interfaceを追加していなかったとしても問題はありません。
何も処理されない、というだけで終了です。
同じようにしてBP_BのTestとTest2もInterfaceを利用するようにしましょう。
BP_BのTest関数はBP_AのTest関数と同じ形式の入り口と出口を持っているのでそのまま流用が可能です。
Test2関数は入り口が違うので、SampleInterfaceにBPITest2関数を追加しましょう。
そしてBP_BでもBP_Aのように実装を行います。こちらはBPITestとBPITest2のメッセージを処理します。
(クリックで拡大)
BP_AのBPITestメッセージ処理もBP_BをTargetとしたBPITest2を呼び出すように修正します。
(クリックで拡大)
実際にうまく動くのかどうかの確認はLevelBPを用いましょう。
BP_AとBP_Bをレベルに配置し、LevelBPのBeginPlayイベントでBP_BのBPITestを呼び出してやればOKです。
BP_BのBPITest2メッセージの処理命令で指定したデバッグ出力が行われていれば成功です。
もちろん、どちらかをコンパイルしたらもう片方が変更扱いになる、というような状況にはならないはずです。
さて、Blueprint Interfaceについてはもう3点ほど解説を行っておきましょう。
まず、[Blueprint Props] でInterfaceを追加したBP上では、Interface内の関数が3つ存在する点についてです。
Event ~とあるものは、その関数の処理を行うイベントで、すでに前述したとおりです。
Interface Messagesのものは他のBlueprintが実装しているInterfaceを呼び出すもので、Targetで指定したActorに対してメッセージを投げます。
CallFunctionにあるものは自分自身、もしくは型が明確なActorの関数として呼び出すものです。
特にInterface MessagesのものとCallFunctionのものは似てはいますが違う代物で、前者はTargetに何を入れてもかまいませんが、後者は [Blueprint Props] で対象Interfaceを追加しているActorに限られています。
次にInterfaceとして追加した関数がOutputを持っていた場合です。
Outputを持ったInterfaceの場合、イベントメッセージを受けて対処するのではなく、普通に関数として実装する必要があります。
なお、実装が行われていない場合はデフォルトの値が返ってくることになります。
最後にBP Interfaceと関数のどちらで実装すべきか、という話をしましょう。
これは別に難しく考える必要はありません。
基本としては、Blueprint内部からしか呼び出されないものは関数に、外部から呼び出される関数はInterfaceで実装するのがよいと思います。
外部から呼び出す場合はほぼキャストすることが前提となっているので、ここをBP Interfaceで実装しないとだめでしょうね。
というわけで今回はBlueprint Interfaceのこれで終わりとします。
Interfaceがやっぱりよくわからない、という方は何らかの形で連絡していただければ解説をもう少し加えてみようかとは思います。