この記事では UEFN (Unreal Editor for Fortnite) という無料の3Dゲーム開発環境を使って、NPCの振る舞いをコーディングするサンプルをご紹介いたします。なんか UEFNのコミュニティに書いたほうがいいような気もするけれども
今回は NPCが目標地点に移動しつつ、進行方向ベースでジャンプ演出をランダムで繰り返す NPC behavior を実装します。
★この NPC Behavior のポイント
- 配列に任意数の経路を定義(歩く/走る/ダッシュの3挙動IDE選択可能)
- 移動・ジャンプは並列処理でお互い邪魔しない挙動
- ジャンプ演出は三角計算で現実?っぽい放物線
- ジャンプ頻度、ジャンプ距離を用途に応じて調整可
- 移動スピード調整可能
- 旗はクマでも動きます(?)
この記事について
この記事のライターは趣味で子供に遊ばせるような簡単なゲームを作る大人のソフトウェアエンジニアです。興味があって取り組んでいる読者であれば内容の理解に壁はないものと思います。ただ完全に再現するには技術的な背景がいくらか必要です。
記事は 1.開発環境の概要、2.実装内容、3.サンプルコード で構成しています
UEFN (Unreal Editor for Fortnite)
UEFN は Epic Games, Inc. 社製のゲームエンジン Unreal Engine 向けのマップエディターつき統合環境です。カジュアルなプラットフォームを目指しておなじくプレイ無料のバトルロイヤルゲーム Fortnite と機能統合されており、本格的なプロツールよりいくぶん扱い易いものになっています
しかしながら内実は本物の Unreal Engine そのもので、性能は劣らず、機能的なシンラッパーとして実装されており、現在も発展を続けています。
開発はエディターと verse言語でおこない、テストプレイはふつうの Fortniteゲームクライアントをエディターが制御して起動・実行します
私が使っている動作バージョンは Windows PC版です。実行のためにそれほど高価なPCはいりません。ただすくなくとも Fornite がむりなく動く程度のスペックは必要です
※ 参考スペック;このくらいあれば簡単には使えます
ハードウェア | 性能 |
---|---|
CPU | i5-10400 2GHz 6コア おさがりのビジネス用スリムPC |
GPU | NVIDIA GeForce RTX 3050 6GB LP (ロープロファイル。スリムPCに差せる細いやつ) |
メモリー | 16GB |
モニター | 25インチ~。144Hz 出るやつ1枚。+補助モニター1枚あればツールダイアログを主モニターから追い出せるので便利 |
ゲーム開発を始めるに際し、開発元 Epic Games, Inc. 社へ本人確認と納税者登録が必要です。だからごめん、ある程度大人にやってもらわないとだめかも。確定申告した経験くらいがあればなんとかできます。ひきかえにアカウントを発行してもらいます
掲載・収益化には別の登録も必要ですが、それはやってないのでよく調べていません。登録・設定系は本やネット記事に丁寧なものがみつかります
verse言語
verseはUnreal Editor でゲームをプログラムするための独自言語です。エディター同梱の専用プラグイン入り VSCode を起動してコーディングします
※ エディター同梱VSCode まぁこれは普通にVSCodeです。ふつうのVimキーバインドプラグインをいれて使っています(つまり普通のプラグインも使えるっちゃ使える)(いつかIntelliJでエディットできるようになるとうれしいのだけど)
verseはクラスや非同期、例外といったモダンな概念を持っています。よもやゲームエンジンなので基本的には 60 fps といった速度のゲームループと非同期処理のかたまりなのです
あとひとつ覚えること、それは python ライクにインデントでコードブロックを表現します。たとえば、
if (IsLogic = true):
#ここから if文の処理内容を書いたコードブロック
set A = 0
# ここから別のブロック
set A = 1
とかなんとか詳しいことは AIチャットの Epic Developer Assistant
にお尋ねください。
https://dev.epicgames.com/community/assistant/fortnite
Epic Assistant、すごく丁寧に教えてくれます。たまに間違うこともあるので AIチャットを使うときの裏取り練習にもなります。じつのところ verseはまだ生まれたての環境なので、API が行き届かないこともままあります。そのため開拓者精神が重要になってきます。チュートリアルとリファレンスもおなじURLの周辺にインデックスされています
NPC behavior
UEFNでマップのことは「レベル」と呼びます(慣れます)
レベルの座標系はエディター上で「Up-Forward-Left」です。ただこれは最近の仕様変更でそれに替わりました。ながらく「X-Y-Z」だったので、スクリプトのAPI はベクトルを XYZ で指示することがまだ多いです(慣れます)
レベル上にプレイヤーじゃないキャラクター Non Player Character / NPC を配置したいときは NPC Spawner を使います。レベル上に置いてなにかのゲーム機能を負わせる部品のことを「デバイス」と呼びます(慣れます)
余談; そこに気持ち悪いフィッシュがいますね。NPC Spawner は外見を後付けすることもできます。UEFNのすごいところはこういった再利用できる画像(アセット、プロップ)を大量に無料開放しているところです。プロップはエディターウィンドウのコンテンツブラウザから探せます。見ているだけで時間をつぶせます。通常のUEのように自分でつくったSTLをメッシュにインポート等ももちろんできます
で、この NPC Spawner でスポーン(出現)させる NPC には振る舞いをコーディング付加できます。その振る舞いを表現するカスタムクラスの基底クラスが npc_behavior
です。ようやくプログラミングっぽくなりましたね。私は今回その派生クラスassault_target_npc_behavior
クラスを作成しました
assault_target_npc_behavior := class(npc_behavior):
ここに天から指示を与えるごとく操り人形 NPC behavior を操るコードを実装します
自分のverseコードがコンパイルできると、これもコンテンツブラウザから参照できるようになります。NPC Spawnerデバイスの Details で NPC Bebehavior Script Override へ設定することができるようになります
レベル上のアセットを NPC behavior のプロパティとして参照する
今回はレベル上に旗を立てておいて、NPCにその間を行ったり来たり、歩いたり走ったりしてもらいます。旗はcreative_prop
クラスのインスタンスです。振る舞いクラスに参照を追加します
実装は簡単で自分のカスタムクラスにアノーテーションつきのプロパティを定義するだけです。エディターのピッカーや値のバリデーション、配列の管理などはIDEがやってくれます
ここでは旗のアセットとそこまでの移動方法をプロパティにもつクラスmovement_target
をつくり、振る舞いクラスにはその配列を定義しました
enum_movement_type := enum:
Walking
Running
Sprinting
movement_target := class:
@editable MoveTarget :creative_prop = creative_prop{}
@editable MovementType :enum_movement_type = enum_movement_type.Running
assault_target_npc_behavior := class(npc_behavior):
@editable MovementTargets :[]movement_target = array{}
あとは配列を順番に参照してなんやかやしてから Navigatable.NavigateTo(NavTarget, ~)
と呪文を唱えると NPC がそちらに向かって歩き出します。ちょっとスクリプトするだけでなにもかもやってくれるゲームエンジン、ほんとにすごいですね
たまにジャンプする
EPIC Assistant が教えてくれるには、NPCのAI には「ジャンプせよ」という命令が実装されていないそうです。Fortnite 特有の挙動がUEFNに対して公開APIとしてverse実装されていないケースがままあります。そのうち実装されるのかもしれません
対症療法として Assistantが教えてくれることには、 Z 軸を 220 UU (220cm) 上にテレポートさせたらいいよ、ということでした。浮かせてテレポートしたら着地動作はNPCのもとある挙動でめんどうみてくれるそうです。なおNPCが自発的にジャンプすることは(すでに)あるそうです。
いきなり空中に放り出すのも漫画みたいなので、0.01秒づつループしながら、いい感じに放物線っぽくキャラクターを持ち上げてみることにしました。
Jump(Character: fort_character)<suspends>:void=
Zs : []float = array { 80.0, 40.0, 40.0, 20.0, 0.0, 0.0, 20.0, 20.0, 0.0, 0.0, 11.0, 10.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0 }
for (OfsZ : Zs):
if:
Transform := Character.GetTransform()
Rotation := Transform.Rotation
Pos := Transform.Translation
YawPitchRoll := Rotation.GetYawPitchRollDegrees()
YawDegrees := YawPitchRoll[0]
then:
YawRadians := DegreesToRadians(YawDegrees)
OfsX := Cos(YawRadians) * DoJumpAdd
OfsY := Sin(YawRadians) * DoJumpAdd
JumpPos := vector3{X := Pos.X + OfsX, Y := Pos.Y + OfsY, Z := Pos.Z + OfsZ}
if (Character.TeleportTo[JumpPos, Rotation]):
Sleep(0.01)
どうもまだカクカクしますが対症療法なのでよしとします。
サンプルコード
ということで実際のコードです。
このVerseコードは、NPCが目標地点に移動しつつ、進行方向ベースでジャンプ演出をランダムで繰り返す NPC behavior を実装します
Assistant に質問しながら書いた悩みの跡がみえますが、私の趣味マップでは動いています
★ポイント
- MovementTargets配列に任意数の creative_prop で経路定義
- 移動・ジャンプを並列処理し、自然なNPC挙動
- ジャンプ演出はYawの三角関数計算で現実的?な挙動
- DoJumpRate でジャンプ頻度調整可
- BaseSpeedMultiplier/MovementType でダッシュ(1.2倍速)の実現
補足:
- ジャンプ高さやジャンプ追加距離 (DoJumpAdd) はマップ/障害物に応じ調整
- 旗は creative_propであればクマでも動きます(Hidden in Game False設定でゲーム中隠しても動きます)
using { /Fortnite.com/AI }
using { /Fortnite.com/Characters}
using { /Fortnite.com/Devices}
using { /Verse.org/Simulation }
using { /Verse.org/Random }
using { /UnrealEngine.com/Temporary/SpatialMath}
enum_movement_type := enum:
Walking
Running
Sprinting
movement_target := class:
@editable MoveTarget :creative_prop = creative_prop{}
@editable MovementType :enum_movement_type = enum_movement_type.Running
assault_target_npc_behavior := class(npc_behavior):
@editable MovementTargets :[]movement_target = array{}
@editable BaseSpeedMultiplier :float = 1.0
@editable DoJumpRate :float = 0.3
@editable DoJumpAdd :float = 8.0
Jump(Character: fort_character)<suspends>:void=
Zs : []float = array { 80.0, 40.0, 40.0, 20.0, 0.0, 0.0, 20.0, 20.0, 0.0, 0.0, 11.0, 10.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0 }
for (OfsZ : Zs):
if:
Transform := Character.GetTransform()
Rotation := Transform.Rotation
Pos := Transform.Translation
YawPitchRoll := Rotation.GetYawPitchRollDegrees()
YawDegrees := YawPitchRoll[0]
then:
YawRadians := DegreesToRadians(YawDegrees)
OfsX := Cos(YawRadians) * DoJumpAdd
OfsY := Sin(YawRadians) * DoJumpAdd
JumpPos := vector3{X := Pos.X + OfsX, Y := Pos.Y + OfsY, Z := Pos.Z + OfsZ}
if (Character.TeleportTo[JumpPos, Rotation]):
Sleep(0.01)
SometimesInstractJumping()<suspends>:void=
if:
Agent := GetAgent[]
Character := Agent.GetFortCharacter[]
then:
loop:
if:
Character.IsOnGround[]
GetRandomFloat(0.0, 1.0) < DoJumpRate
then:
Jump(Character)
Sleep(1.0)
OnBegin<override>()<suspends>:void=
if (MovementTargets.Length < 1):
return
spawn{ SometimesInstractJumping() }
if:
Agent := GetAgent[]
Character := Agent.GetFortCharacter[]
Navigatable := Character.GetNavigatable[]
then:
loop:
for:
MovementTarget :MovementTargets
Transform := MovementTarget.MoveTarget.GetTransform()
Type := MovementTarget.MovementType
do:
NavTarget := MakeNavigationTarget(Transform.Translation)
NavType := case(Type):
enum_movement_type.Sprinting => movement_types.Running
enum_movement_type.Running => movement_types.Running
_ => movement_types.Walking
if (Type = enum_movement_type.Sprinting):
Navigatable.SetMovementSpeedMultiplier(BaseSpeedMultiplier * 1.2)
else:
Navigatable.SetMovementSpeedMultiplier(BaseSpeedMultiplier * 1.0)
Navigatable.NavigateTo(NavTarget, ?MovementType:=NavType)
実装手順まとめ(UEFN内)
-
Verseファイル作成
- エディターメニューで Verse → Verse Explorer
- プロジェクト名下で右クリック → Create New Verse File
- → ファイル名 assault_target_npc_behavior
- Create Empty を押して開く
-
コード貼付・保存
- 今のVerseコードをファイルへ貼り付けて保存
-
Verseコードをビルド
- UEFNで Verse → Build Verse Code (Ctrl + Shift + B)
- "Build Succeeded" 表示後、新しい NPC Character Definition を Content Browser で確認
-
旗の設置
- レベルに一つ以上のプロップを任意配置
-
NPC Character Definition のVerse Behaviorに割り当て
- レベルに NPC Spawner デバイス配置
- NPC Spawner をレベル上で選択して Details 表示
- Behavior Script Override へ assault_target_npc_behavior を設定
- MovementTargets の各 MoveTarget に進行目標となるプロップを割当
- ほかパラメータ値は任意で調整
- BaseSpeedMultiplier, DoJumpRate, DoJumpAdd など
-
テスト&調整
- Launch Session で動作検証
- NPCが目標地点へ進みつつ、たまに進行方向へジャンプ
- 種々速度で移動
- Launch Session で動作検証
以上です。ご清聴ありがとうございました。
P.S.
なにかを保証はいたしませんが、コードは自由にご利用ください
補足; UEFNメモ レベル内の距離 UU について
-
Unreal Units (UU) が Unreal Engine の基本単位です
-
UE の 1メートルは 100 (UU)です
-
UE / Fortnite の1マスが具体的に何メートルかはグリッド設定によります
-
ただし Fortnite の標準的なグリッドサイズは 512 UU(5.12メートル)です
-
また Fortnite の標準的な壁の高さは 386 UU(3.86メートル)です
-
マップの1マスは 250m です
-
バトルロイヤルにおける射撃距離:
- 近距離武器(SG): 数メートルから十数メートル
- 中距離武器(AR): 数十メートルから百メートル程度
- 遠距離武器(砂): 百メートル以上
-
各射程をUEFNのマス数(5.12メートル/マスと仮定)に換算すると以下になります
- 近距離武器(SG)は1~3マス(5m→0.98、15m→2.93)
- 中距離武器(AR)は 10 ~ 20マス(50m→9.77、100m→19.53)
- 遠距離武器(砂)は 40マス(200m→39.06)
たぶん。