
[UEFN&Verse]オリジナルの敵を出す
今回はオリジナルの敵キャラクターを作ってみる
近づくと寄ってきて攻撃してくるモンスターだ
プロジェクトの準備
UEFNを起動してプロジェクトを作成する
テンプレートは何でも良いし、新規に作らなくても既にあるプロジェクト上でも良い
今回はTestProject003という名前で島テンプレートのBlankを指定している

モデルを登録する
まずは敵のデータを用意する必要がある
大したものではないが今回使うデータを用意した
内訳はこのようになっている
slime.fbx 敵モデル
slime.png 敵モデルテクスチャ
idle.fbx アイドル時のアニメーション
walk.fbx 移動時のアニメーション
コンテンツブラウザを開き All > TestProject003コンテンツ の下にslimeというフォルダ作成、その中に解凍したslime.fbxをドラッグする

こんな画面が開くので

Import Animatinsのチェックだけ外して[インポート]する

こんな感じでいろいろインポートされる
マテリアル、ボーン、テクスチャ、物理?、スケルタルメッシュだ

引き続きアニメーションデータのidle.fbx walk.fbxも[インポート]する

ブループリントを作成する
Slimeのブループリントを作成する
ブループリントとはモデルやコリジョンやライティングなどを組み合わせて一つの構造として扱うための仕組みだ
ブループリントを構築してシーンに配置しVerseで制御することでいろいろな挙動をさせることができる
コンテンツブラウザから All>TestProject003コンテンツ>slimeを右クリックしてブループリントクラスを選択する

生成されたブループリントはslimeBPとリネームしておく

ダブルクリックするとこんなエディタが開く

まずはslimeのスケルタルメッシュをブループリントに登録する
コンポーネントタブにある[+追加]を押してSkeltal Meshを追加

スケルタルメッシュはSlimeとリネームしておく

詳細タブの Skeltal MeshにSlimeスケルタルメッシュをセットする
ちなみにスケルタルメッシュとは内部に含まれたジョイントを使ってアニメーションをさせることができるメッシュのことだ

次に Anim to Playにidleのアニメーションシーケンスをセットする

これでアイドリングするスライムのブループリントができる

今のままでもslimeBPはシーンに配置することができるがまだコリジョンがないので素通りする

コリジョンを設定する
次にSlimeBPにコリジョンを設定する
スケルタルメッシュにはコリジョン情報は含まれないのでコリジョン情報を持ったスタティックメッシュをSlimeBPに追加する必要がある
スタティックメッシュとはジョイントを含まないポリゴン情報だけのメッシュだ
スタティックメッシュはスケルタルメッシュから生成できる
slimeのスケルタルメッシュをダブルクリック

エディタが開くのでスタティックメッシュを作成を選択

slimeフォルダを指定して名前はslimeStaticMeshとし[保存]

今度は生成されたslimeStaticMeshを開く

コリジョンを選び

18DPO単純化コリジョンを追加
URは様々なコリジョン形状を自動生成してくれるのでモデルにあったデータをその都度選択する
今回はシンプルなものを選んだ

これでコリジョン情報を含んだスタティックメッシュを作ることができた
ビューポートにある表示から単純なコリジョンをチェックすると生成されたコリジョンを視認できる

このコリジョン付きのスタティックメッシュをslimeBPに追加することでコリジョンのあるslimeBPを作る
その前にもう一つ用意しておくものがある
コリジョン用のスタティックメッシュを生成したわけだが、必要なのはコリジョン情報だけなので、メッシュ本体を隠す方法が欲しい
そのための透明なマテリアルを作成してメッシュに適応する
コンテンツブラウザ上でAll > TestProject003 > slimeで右クリックしてマテリアルを選択する

生成されたマテリアルはTransparentMatとリネームしておく
マテリアルをダブルクリックしてマテリアルエディターを開く

まずは詳細タブのBlend ModeをTranslucentに変更する

このあたりを右クリックして

Constantを追加する
続けてConstant3Vectorも追加する

コンスタント3Vectorは白に変更し
コンスタントは0.0のままにして
それぞれをベースカラーとオパシティに接続する

これで必要なマテリアルは完成したのでマテリアルエディタはクローズする
ではSlimeBPにコリジョンのついたスタティックメッシュを登録する
再びコンテンツブラウザからSlimeBPをダブルクリックしてブループリントエディターを開く
コンポーネントタブでslimeを選択した状態で [+追加]

スタティックメッシュコンポーネントを選択する

slimeの下にStaticMesh1が追加される
StaticMesh1のスタティックメッシュにコリジョンがついたslimeStaticMeshを指定する
このままだと2体のスライムが重なったような見た目になるのでみにくい、とはいえ最終的に利用する透明のマテリアルを今適応してしまうと見えなくて作業がしにくい、ということでマテリアルには適当にM_Barrier_BoundsSolid_01を指定しておく

次にスタティックメッシュのすぐ上にある親ソケットを設定する
これでスケルタルメッシュの内部に含まれるジョイントに対して親子関係をつけることができる

slimeスケルタルメッシュに含まれているジョイントがリストされるのでjoint1を選択する

するとコリジョンモデルの軸がおかしくなるので

もとに戻しておく

joint1にコリジョンをアタッチするとスライムをwalkモーションに切り替えたときよりモデルの動きにコリジョンがフィットしてくれる
アニメーションに合わせてこまかくコリジョンを指定する方法だ

これでコリジョンの調整は完了したのでコリジョンモデルのマテリアルを先ほど作ったTransparentMatに変更し見えなくしてしまう

動作確認すると上にも乗れるようになっている

攻撃できるようにする
次はスライムを攻撃して破壊できるようにしたい
とりあえず何か武器がないと試せないので
All > Fortnite > Deviceの下にあるアイテムスポナーをシーンに配置する

アイテムスポナーを機能させるにはスポーンさせたいアイテムのリストを登録する必要がある
シーンに配置したアイテムスポナーの詳細タブからアイテムリストの右にある+マークを押してリストを1つ追加する
追加したリストに含まれるスポーンするピックアップで武器を適当に選ぶ

ついでに、最初のスポーンまでの時間を0.0にしておくと良い

攻撃能力は手に入れた
まだスライムに弾は当たるが倒せない

再びslimeBPのブループリントエディター画面を開く
slimeBPを選択した状態で詳細タブの検索欄にattrといれると属性を指定する場所がピックアップされる
この設定でslimeBPの基本ステータスを指定することができる

とりあえず敵っぽいカテゴリを選ぶ
インデックス[0]の初期化カテゴリをSoldierにセット

初期化サブカテゴリにSoldier_Ability_R_T1を選ぶ

インデックス[1]にも全く同じ設定をいれる

なぜ2つあるのか不明もう少し調べる必要がある
設定するとでSoldier_Ability_R_T1の属性がslimeBPに付与され攻撃が当たるようになる

触るとダメージを受けるようにする
All > Fortnite > Deviceの下にある ダメージボリューム を使う

ダメージボリュームを図のようにスライムの足元に配置し
ユーザーオプションからボリュームの大きさやダメージ量などを調整する

スライムの移動にあわせてダメージボリュームも移動させる必要があるのでダメージボリュームをSlimeBPの子供にしておく
アウトライナーからダメージボリュームを右クリック→親子付け→slimeBPを選ぶ

親子関係ができる

触るとダメージを受けるようになる

歩きアニメーションを登録する
敵キャラクターを動かすためには単にモーションを切り替えて座標を変更させればよいわけではなく、タイミング良くSEを鳴らしたりエフェクトを追加したりいろいろ調整が必要になる
それらを各モーションごとに設定するのがレベルシーケンスという機能だ
コンテンツブラウザからAll > TestProject003 > slimeフォルダを右クリックしシネマティックスの下のレベルシーケンスを選択する

slimeフォルダにこんなのが追加されるのでwalkSeqとリネームしておく

で、これをダブルクリックするとシーケンサータブが表示される

シーケンサーにSlimeBPのためのトラックを追加してwalkモーションを登録する
最初にタイムスライダの位置が0になっていることを確認してからシーケンサーの [+トラックを]押してシーケンサへのアクタからslimeBPを見つけて選択する

シーケンサーにslimeBPトラックが追加される

トラックの右にある+ボタンを押してアニメーションからwalkを選択する

ここで左下にある再生ボタンを押すとシーン上でアニメーションを確認することができる

Walkモーションはちょうど1秒、30FPSだと30フレームの長さで作られているのでトラックの幅をそれに合わせる必要がある
右側に見える赤い 】を移動させて30に調整する

あ、動かすまえにスナップボタンをONにしたほうがよい

ほかにも様々な機能があるレベルシーケンスだが今回はこれだけで完成だ
作成したレベルシーケンスは他の仕掛けと同様にシーンに配置して利用するのだが直接シーンには配置できずムービーシーケンスの仕掛けを利用する
All > Fortnite > Deviceから探し出してシーンに配置
SlimeWalkSeqとリネームしておく

配置したムービーシーケンスの仕掛けのアウトライナー/詳細から
シーケンスにwalkSeqをセットする

歩かせる
スライムの移動プログラムを組む
まずVerseコードを追加、名前はslime_ai_deviceとする

Verseデバイスも忘れないうちにシーンに置いておこう

slime_ai_device.verseファイルを編集する
全コードがこんな感じ
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Fortnite.com/Characters }
using { /UnrealEngine.com/Temporary/SpatialMath}
slime_ai_device := class(creative_device):
@editable WalkCinema : cinematic_sequence_device = cinematic_sequence_device{}
@editable Slime : creative_prop = creative_prop{}
MoveSpeed : float = 0.02
OnBegin<override>()<suspends>:void=
loop:
MoveAnimation()
Sleep(0.1)
MoveAnimation()<suspends>:void=
AllPlayers := GetPlayspace().GetPlayers()
if (Player := AllPlayers[0], Agent := agent[Player], Fort := Agent.GetFortCharacter[]):
PropLocation := Slime.GetTransform().Translation
PlayerLocation := Fort.GetTransform().Translation
var Dist : float := Distance(PlayerLocation , PropLocation)
if (Dist < 1000.0):
WalkCinema.Play()
if (LookDirection := (PlayerLocation - PropLocation).MakeUnitVector[]):
Yaw := RadiansToDegrees(ArcTan(LookDirection.Y, LookDirection.X)) - 90.0
Pitch := 0.0
Roll := 0.0
NewRotation := MakeRotationFromYawPitchRollDegrees(Yaw, Pitch, Roll)
LerpLocation := Lerp(PropLocation, PlayerLocation, MoveSpeed)
FinalLocation := vector3 {X:= LerpLocation.X, Y:= LerpLocation.Y, Z:=0.0}
if:
Slime.TeleportTo[FinalLocation,NewRotation]
#Slime.MoveTo(PlayerLocation, NewRotation, 1.0)
ざっくりとコードを説明
cinematic_sequence_deviceはムービーシーケンスの仕掛け
creative_propはブループリント
シーンに配置したslimeWalkSetとslimeBPにアクセスするものだ
@editable WalkCinema : cinematic_sequence_device = cinematic_sequence_device{}
@editable Slime : creative_prop = creative_prop{}
OnBeginは初期化用のイベントハンドラだ
今回はこのなかに直接スライムの移動ルーチンが組み込んである
この手の初期化ハンドラはどんなシステムにもあるけど必要な処理を終えたら速やかにリターンさせるものだと思っていた
VerseのOnBeginはSleep()さえコールすればリターンしなくても良いらしい
OnBegin<override>()<suspends>:void=
loop:
MoveAnimation()
Sleep(0.1)
プレイヤーキャラのインスタンスを取得している
この処理は失敗する可能性があるのでif文の中で行う
Verseのプログラムで最初に不思議に思う部分だ
if文のくせに条件文が存在しないことがある
条件が成立するかしないか以外に処理が成功するかしないかでも分岐が発生する
AllPlayers := GetPlayspace().GetPlayers()
if (Player := AllPlayers[0], Agent := agent[Player], Fort := Agent.GetFortCharacter[]):
スライムとプレイヤーキャラ両方の位置を取得してその距離を計算している
距離が1000.0以下になると追いかけてくるようにしている
PropLocation := Slime.GetTransform().Translation
PlayerLocation := Fort.GetTransform().Translation
var Dist : float := Distance(PlayerLocation , PropLocation)
if (Dist < 1000.0):
歩きモーションを再生
WalkCinema.Play()
スライムからみたプレイヤーキャラの方向を法線ベクトルを計算しそれをオイラー角に変換しさらにRotationというフォーマットに変換しているRotationはクォータニオンなのかな
if (LookDirection := (PlayerLocation - PropLocation).MakeUnitVector[]):
Yaw := RadiansToDegrees(ArcTan(LookDirection.Y, LookDirection.X)) - 90.0
Pitch := 0.0
Roll := 0.0
NewRotation := MakeRotationFromYawPitchRollDegrees(Yaw, Pitch, Roll)
Leapは線形補間の関数だ
スライムがプレイヤーに方向にちょっとだけ進んだ位置を計算している
で計算した新しい位置と新しい向きをSlimeに設定して移動させている
LerpLocation := Lerp(PropLocation, PlayerLocation, MoveSpeed)
FinalLocation := vector3 {X:= LerpLocation.X, Y:= LerpLocation.Y, Z:=0.0}
if:
Slime.TeleportTo[FinalLocation,NewRotation]
コードをの編集が終わったらVersesコードをビルドする
ビルドが成功すればslime ai deviceに以下のパラメータが追加されるので
WalkCinemaにSlimeWalkSeq、SlimeにslimeBPを設定する

動作確認すると、スライムが動き出すようになった

ひとまずやりたいことはできたが大量の敵をこの方法で出すのは大変だ
クリーチャースポナーも利用できないブループリントをVerseで動的に生成することもできない
今後はもう少し改善されるのだろうか?