
みなみ
RPGゲームのような俯瞰視点の作り方を教えてください!

りゅう
こんなお悩みを解決します。
本記事の内容
本記事の信頼性
この記事を書いている僕は、クリエイター兼ブロガーです。実際にUEFNを使っており、現在進行形で学んでいます。
今回は、RPGゲームのような俯瞰視点の作り方についてスクショ画像を使って解説します。
なお、RingoGames様のブログを参考にさせていただきました。
私の解釈に誤りがありましたらその際はご了承ください。(o_ _)o))

あすか
RPGゲームのような俯瞰視点の作り方
※音量にご注意ください。
▼ UEFNを開き、画面左上の青枠にあるボタンをクリックします。
※クリックすると拡大表示できます。
▼ 「シネマティック」を選択し、「Cine カメラ アクタ」をクリックします。
▼ 次に、画面左下のコンテンツドロワーを選択します。
▼ CreativePropの作成に入ります。
今回は破壊されない小道具(Indestructible props)を作りましょう。
破壊されない小道具
破壊されない小道具(Indestructible props)の作り方は以下の記事にて詳しく解説していますので、参考にしてください。
>>【UEFN】破壊されない小道具を作る方法【初心者さんでも簡単】
-
-
【UEFN】破壊されない小道具を作る方法【初心者さんでも簡単】
続きを見る
▼ 破壊されない小道具を作成したら、propをシーン上に配置します。
▼ propをシーン上に配置したら、カメラをpropの子供にします。
画面右の「アウトライナー」で、カメラをドラッグし、propにドロップしましょう。
▼ 以下のようにpropの下にカメラがあればOK
▼ カメラの設定をいじります。
以下のとおり。
位置 | 0.0 | 0.0 | 0.0 |
回転 | 0.0 | -45.0 | 0.0 |
拡大・縮小 | 1.0 | 1.0 | 1.0 |
シーケンス
▼ コンテンツドロワーを開き、コンテンツブラウザ上で右クリック。
「シネマティック」→「レベル シーケンス」を選択します。
▼ レベルシーケンスをダブルクリックします。
▼ 画面下にシーケンサーが開いたら、右上の「アウトライナー」からカメラをドラッグし、シーケンサーにドロップします。
▼ 以下のようになればOKです。
▼ 「コンテンツドロワー」→「All」をクリックし、『Cinematic Sequence Device』と検索。
「ムービーシーケンスの仕掛け」をシーン上に配置します。
▼ 配置したら、画面右の詳細で以下のように変更します。
シーケンス | 「なし」→「レベル シーケンス」を選択 |
ループ再生 | チェックを入れる |
オートプレイ | チェックを入れる |
▼ ムービーシーケンスの仕掛けを変更したら、「Verse」→「Verse Explorer」をクリックします。
▼ Verse Explorerに移動し、一番上の箱のようなアイコンをクリック。
「Add new Verse file to project」をクリックしましょう。
▼ 「track_player_manager」を入力し、「作成」をクリック。
▼ Verse Explorerで「track_player_manager.verse」をダブルクリック。
▼ 以下のようにコードを入力します。
コード
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Fortnite.com/Characters }
using { /UnrealEngine.com/Temporary/SpatialMath }
# A Verse-authored creative device that can be placed in a level
track_player_manager := class(creative_device):
# 変数 _Target を creative_prop のインスタンスで初期化
@editable
_Target : creative_prop = creative_prop{}
# _OffsetX、_OffsetY、_OffsetZ という名前の変数を定義し、それぞれの初期値を設定
@editable
_OffsetX : float = -3000.0
@editable
_OffsetY : float = 0.0
@editable
_OffsetZ : float = 3000.0
# Offset という名前の vector3 型の変数を定義し、それに _OffsetX、_OffsetY、_OffsetZ の値を代入。UpdateTargetPosition(Offset) を別スレッドで実行
OnBegin<override>()<suspends>:void=
Offset:vector3 = vector3{X:=_OffsetX, Y:=_OffsetY, Z:=_OffsetZ}
## ループ機能を開始させるための起爆剤
spawn{UpdateTargetPosition(Offset)}
UpdateTargetPosition(Offset:vector3)<suspends>:void=
loop:
## プレイヤーとポジションを取得
Players : []player = GetPlayspace().GetPlayers()
if(Player : player = Players[0]):
if(FortniteCharacter : fort_character = Player.GetFortCharacter[]):
PlayerPosition : vector3 = FortniteCharacter.GetTransform().Translation
TargetPositionX : float = PlayerPosition.X
TargetPositionY : float = PlayerPosition.Y
TargetPositionZ : float = PlayerPosition.Z
TargetPosition:vector3 = vector3{X:=TargetPositionX, Y:=TargetPositionY, Z:=TargetPositionZ} + Offset
# ターゲットをプレイヤーの位置に移動:位置・回転・時間
_Target.MoveTo(TargetPosition, IdentityRotation(), 0.5)
# コメントアウト
# if(_Target.TeleportTo[TargetPosition, IdentityRotation()]):
# void
# 0.5秒間プログラムの実行を一時停止
Sleep(0.5)
# コメントアウト
# Sleep(0.0)
▼ コードを入力したら、コンテンツドロワーを開き、「Creative Devices」をクリック。
▼ 「track_player_manager」をシーン上に配置しましょう。
▼ こんな感じで配置できればOKです。
▼ 「Verse」→「Verseコードをビルド」をクリックしましょう。
▼ Verseコードをビルドすると、Track Player manager欄が表示されます。
以下のように設定しましょう。
ゲーム中に表示 | チェックなし |
ゲーム開始時に有効化 | チェックあり |
_Target | propを選択 |
_OffsetX | -300.0 |
_OffsetY | 0.0 |
_OffsetZ | 300.0 |
▼ 設定が完了したら、いざ試してみましょう。
「セッションを開始」をクリック。
▼ 以下のような画面になっていれば完了です。
▼ さらにゾンビスポナーや武器を配置すると、RPGゲームっぽくなります。

あすか

みなみ

りゅう
リンゴさんのTwitter account 🍎
My Twitter account 🍀
RPGゲームのような俯瞰視点のVerseコードを解説
コード
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Fortnite.com/Characters }
using { /UnrealEngine.com/Temporary/SpatialMath }
# A Verse-authored creative device that can be placed in a level
track_player_manager := class(creative_device):
# 変数 _Target を creative_prop のインスタンスで初期化
@editable
_Target : creative_prop = creative_prop{}
# _OffsetX、_OffsetY、_OffsetZ という名前の変数を定義し、それぞれの初期値を設定
@editable
_OffsetX : float = -3000.0
@editable
_OffsetY : float = 0.0
@editable
_OffsetZ : float = 3000.0
# Offset という名前の vector3 型の変数を定義し、それに _OffsetX、_OffsetY、_OffsetZ の値を代入。UpdateTargetPosition(Offset) を別スレッドで実行
OnBegin<override>()<suspends>:void=
Offset:vector3 = vector3{X:=_OffsetX, Y:=_OffsetY, Z:=_OffsetZ}
## ループ機能を開始させるための起爆剤
spawn{UpdateTargetPosition(Offset)}
UpdateTargetPosition(Offset:vector3)<suspends>:void=
loop:
## プレイヤーとポジションを取得
Players : []player = GetPlayspace().GetPlayers()
if(Player : player = Players[0]):
if(FortniteCharacter : fort_character = Player.GetFortCharacter[]):
PlayerPosition : vector3 = FortniteCharacter.GetTransform().Translation
TargetPositionX : float = PlayerPosition.X
TargetPositionY : float = PlayerPosition.Y
TargetPositionZ : float = PlayerPosition.Z
TargetPosition:vector3 = vector3{X:=TargetPositionX, Y:=TargetPositionY, Z:=TargetPositionZ} + Offset
# ターゲットをプレイヤーの位置に移動:位置・回転・時間
_Target.MoveTo(TargetPosition, IdentityRotation(), 0.5)
# コメントアウト
# if(_Target.TeleportTo[TargetPosition, IdentityRotation()]):
# void
# 0.5秒間プログラムの実行を一時停止
Sleep(0.5)
# コメントアウト
# Sleep(0.0)
注意:以下のコードの解説文はプログラミング初心者の主が解説したものです。

あすか
参考程度にどうぞ。
▼ クラス内にいくつかの変数が定義されています。
_Target
は、ターゲットの位置を表すcreative_prop
オブジェクトです。
_OffsetX
、_OffsetY
、_OffsetZ
は、プレイヤーの位置に対するオフセット値を表す変数です。
※オフセット値とは、基準点からの距離で表した位置のこと。
@editable
_Target : creative_prop = creative_prop{}
@editable
_OffsetX : float = -3000.0
@editable
_OffsetY : float = 0.0
@editable
_OffsetZ : float = 3000.0
クリックすると開きます。
@editable
_Target : creative_prop = creative_prop{}
_Target
はクリエイティブデバイス内のプロパティ(属性)を表します。@editable
は、このプロパティが編集可能であることを示します。
creative_prop
はcreative_prop{}
という初期値で初期化される変数の型です。creative_prop
はVerseで使用される特殊なプロパティの一種です。
@editable
_OffsetX : float = -3000.0
_OffsetX
はクリエイティブデバイス内のもう一つのプロパティです。float
は浮動小数点数のデータ型を表します。-3000.0
は初期値です。
@editable
_OffsetY : float = 0.0
_OffsetY
はもう一つのプロパティで、float
型のデータを保持します。0.0
は初期値です。
@editable
_OffsetZ : float = 3000.0
_OffsetZ
は最後のプロパティで、float
型のデータを保持します。3000.0
は初期値です。
これらのコード行は、クリエイティブデバイスの状態や属性を表しています。
プログラム内でこれらのプロパティを編集することで、デバイスの動作や振る舞いを変更できます。
なお、@editable
はアノテーションと呼ばれるもので、プロパティがエディタ上で編集可能であることを示すために使用されます。
これにより、エディタ上でこれらのプロパティの値を調整できます。
_Targetの意義
クリックすると開きます。
_Target
は、クリエイティブデバイス内で使用されるプロパティであり、creative_prop
というデータ型を持っています。
このプロパティは、クリエイティブデバイスの振る舞いや機能の一部として使用されます。
creative_prop
はVerseにおいて特殊なプロパティを表すデータ型です。このプロパティは、デバイス内の他の要素やオブジェクトとの対話や制御に使用されます。
_Target
プロパティは、デバイスが追跡する対象の位置を表します。
具体的には、プレイヤーの位置に対してオフセットを加えた位置がこの_Target
プロパティの値として設定されます。
このプロパティを利用することで、プレイヤーの位置を基準にした相対的な位置に対象を配置することが可能となります。
例えば、_OffsetX
、_OffsetY
、_OffsetZ
がそれぞれ-3000.0、0.0、3000.0の値を持っている場合、プレイヤーの位置からX軸方向に-3000.0、Y軸方向に0.0、Z軸方向に3000.0のオフセットを加えた位置が_Target
の値となります。
このように_Target
プロパティを使うことで、デバイスはプレイヤーに対して相対的な位置にターゲットを配置することができます。、プレイヤーが移動するたびにターゲットも追従するなど、プレイヤーとの相互作用や位置関係を制御するために使用されます。
エディタ上で_Target
プロパティを編集することで、デバイスが追跡する対象の位置を調整できるようになります。
つまり、_Target
プロパティは対象の位置を設定し、その位置に対象を移動させるために使用されるということです。

あすか

みなみ
_Target
プロパティの値を更新することで、デバイスが追跡する対象の位置を変更することができますね🌸
▼ その後、UpdateTargetPosition
関数が呼び出されます。この関数は、プレイヤーの位置を定期的に更新し、ターゲットの位置を変更するためのループ処理を行います。
Offset:vector3 = vector3{X:=_OffsetX, Y:=_OffsetY, Z:=_OffsetZ}
## start update loop
spawn{UpdateTargetPosition(Offset)}
クリックすると開きます。
このコードの該当部分は、Offset
という名前のvector3
型の変数を作成し、その値を設定しています。
そして、UpdateTargetPosition()
メソッドを開始するためのループを作成しています。
具体的に解説すると以下のようになります:
Offset:vector3 = vector3{X:=_OffsetX, Y:=_OffsetY, Z:=_OffsetZ}
Offset
という名前のvector3
型の変数を作成し、その値を設定しています。vector3
は3次元のベクトルを表すデータ型で、この場合はX軸方向のオフセットを_OffsetX
、Y軸方向のオフセットを_OffsetY
、Z軸方向のオフセットを_OffsetZ
の値として設定しています。
spawn{UpdateTargetPosition(Offset)}
UpdateTargetPosition()
メソッドを実行するためのループを開始します。spawn
キーワードは非同期でメソッドを実行するために使用されます。UpdateTargetPosition()
メソッドは、引数として先ほど定義したOffset
を受け取ります。- このループは継続的に実行され、対象の位置を更新し続ける役割を担います。
このコードの目的は、Offset
を計算し、UpdateTargetPosition()
メソッドを開始するための準備を行うことです。
Offset
は対象の位置を計算する際に使用され、UpdateTargetPosition()
メソッドは対象の位置を更新するためのループを制御します。
Offset:vector3 = vector3{X:=_OffsetX, Y:=_OffsetY, Z:=_OffsetZ}の詳細
クリックすると開きます。
このコードは、Offset
という名前のvector3
型の変数を作成し、その値を設定しています。
具体的に解説すると以下のようになります:
Offset:vector3
は、Offset
という名前の変数を宣言し、その型をvector3
として指定しています。
vector3
は3次元ベクトルを表すデータ型です。
vector3{X:=_OffsetX, Y:=_OffsetY, Z:=_OffsetZ}
は、vector3
型のオブジェクトを作成しています。
中括弧 {}
内にX、Y、Zというプロパティ名と対応する値を指定しています。
X:=_OffsetX
は、X
というプロパティに_OffsetX
という変数の値を代入しています。
同様に、Y:=_OffsetY
ではY
プロパティに_OffsetY
の値を、Z:=_OffsetZ
ではZ
プロパティに_OffsetZ
の値を代入しています。
つまり、この行のコードはOffset
という名前のvector3
型の変数を作成し、そのX座標に_OffsetX
の値、Y座標に_OffsetY
の値、Z座標に_OffsetZ
の値を設定しています。
このようにして、対象の位置に加えるオフセットを表すベクトルを作成しています。
spawn{UpdateTargetPosition(Offset)}の詳細
クリックすると開きます。
このコードは、UpdateTargetPosition()
メソッドを非同期に実行するためのループを開始しています。
具体的に解説すると以下のようになります:
spawn{UpdateTargetPosition(Offset)}
は、UpdateTargetPosition()
メソッドを非同期に実行するためのループを開始します。
spawn
キーワードは非同期な処理を開始するために使用されます。
UpdateTargetPosition(Offset)
は、Offset
を引数としてUpdateTargetPosition()
メソッドを呼び出しています。
このメソッドは、対象の位置を更新するための処理を行います。
このループは継続的に実行されます。
つまり、UpdateTargetPosition()
メソッドが呼び出され、対象の位置が更新されるたびにループが再度開始されます。
このようにすることで、対象の位置を定期的に更新するためのメソッド呼び出しを非同期に実行し続けることができます。
これにより、対象がプレイヤーに追従するなどの動作を実現することができます。
spawnを使う理由
クリックすると開きます。
spawn
キーワードは非同期な処理を開始するために使用されます。
この場合、spawn{UpdateTargetPosition(Offset)}
はUpdateTargetPosition()
メソッドを非同期に実行するためにspawn
キーワードが使用されています。
非同期処理とは、プログラムの実行フローが一時停止することなく、他の処理と並行して実行される処理のことを指します。
なぜ非同期処理が使用されているのかというと、UpdateTargetPosition()
メソッドは継続的に対象の位置を更新するためのループを制御しています。
もし非同期処理を使用しない場合、このループが実行される間は他の処理がブロックされてしまい、プログラム全体の動作が停止してしまいます。
spawn
キーワードを使用することで、UpdateTargetPosition()
メソッドを別のスレッドやタスクで実行し、メインの処理と並行して実行できるようになります。
これにより、対象の位置の更新を待つことなく、他の処理を続けることができます。
つまり、spawn
キーワードは非同期処理を開始するために使用され、メインの処理と並行してUpdateTargetPosition()
メソッドを実行することができるようにしています。
これにより、スムーズな動作や他の処理との同時実行が可能となります。
loopを実現できる理由
クリックすると開きます。
using { /UnrealEngine.com/Temporary/SpatialMath }
文は、スクリプト内でベクトル操作や数学的な計算を行うためにUnreal Engineの一時的な数学ライブラリであるSpatialMath
を使用するためにインポートしています。
具体的に言えば、loop
キーワードはループ処理を行うために使用されますが、このループが可能なのはvector3
型やその他の数学的な操作が提供されるためです。
vector3
は三次元ベクトルを表すため、位置や方向の計算に使用されます。
したがって、using { /UnrealEngine.com/Temporary/SpatialMath }
を使用することで、ベクトルの操作や数学的な計算が可能になり、loop
キーワードを使用して継続的な処理や反復を行うことができます。
これにより、対象の位置の更新をループ内で行うことができます。
つまり、using { /UnrealEngine.com/Temporary/SpatialMath }
を使用することで、ループ処理とベクトル演算が組み合わさり、対象の位置を継続的に更新するための反復処理を実現しています。
詳しくは公式サイトのドキュメントを参照してください。
>> Loop and Breakについて
▼ UpdateTargetPosition
メソッドは、プレイヤーの情報と位置情報を取得して更新します。
具体的には、プレイスペース内のプレイヤーを取得し、そのプレイヤーがFortniteキャラクターを持っている場合にキャラクターの位置を取得します。
ループ構造を持つため、UpdateTargetPosition
メソッドは定期的に実行されます。
これにより、プレイヤーの位置情報を定期的に更新し、ターゲットの位置を適切に追従させることができます。
UpdateTargetPosition(Offset:vector3)<suspends>:void=
loop:
## プレイヤーとポジションを取得
Players : []player = GetPlayspace().GetPlayers()
if(Player : player = Players[0]):
if(FortniteCharacter : fort_character = Player.GetFortCharacter[]):
PlayerPosition : vector3 = FortniteCharacter.GetTransform().Translation
クリックすると開きます。
上記のコードは、UpdateTargetPosition()
というメソッドの定義です。
以下では、コードの行ごとに解説します。
UpdateTargetPosition(Offset: vector3)<suspends>: void:
UpdateTargetPosition
はメソッドの名前です。- このメソッドは
Offset
という名前のvector3
型の引数を受け取ります。
<suspends>
はこのメソッドが一時停止可能であることを示しています。void
はメソッドが何も返さないことを示しています。
loop:
loop
キーワードは無限ループを表します。ループ内のコードは繰り返し実行されます。
Players: []player = GetPlayspace().GetPlayers():
GetPlayspace().GetPlayers()
はプレイスペース内のプレイヤーを取得する関数です。[]player
はplayer
型の配列を表します。Players
はプレイヤーの配列を格納するための変数です。
if(Player: player = Players[0]):
Players[0]
は配列の最初の要素、つまり最初のプレイヤーを取得します。Player: player
はPlayer
という変数をplayer
型として宣言します。if
文は条件式が真である場合にコードを実行します。
if(FortniteCharacter: fort_character = Player.GetFortCharacter[]):
Player.GetFortCharacter[]
はプレイヤーのFortniteキャラクターを取得するメソッドです。FortniteCharacter: fort_character
はFortniteCharacter
という変数をfort_character
型として宣言します。if
文は条件式が真である場合にコードを実行します。
PlayerPosition: vector3 = FortniteCharacter.GetTransform().Translation
FortniteCharacter.GetTransform().Translation
はFortniteキャラクターの現在の位置を取得するメソッドです。PlayerPosition: vector3
はPlayerPosition
という変数をvector3
型として宣言します。PlayerPosition
にはキャラクターの位置が代入されます。
最終的に、このコードはプレイスペース内のプレイヤーとそのFortniteキャラクターを取得し、キャラクターの位置をPlayerPosition
に格納します。
これによって、後続のコードで対象の位置を更新するための情報が取得されます。
各コードの役割
クリックすると開きます。
UpdateTargetPosition(Offset:vector3)<suspends>:void=
loop:
## プレイヤーとポジションを取得
Players : []player = GetPlayspace().GetPlayers()
if(Player : player = Players[0]):
if(FortniteCharacter : fort_character = Player.GetFortCharacter[]):
PlayerPosition : vector3 = FortniteCharacter.GetTransform().Translation
上記のコードは、ゲーム内でプレイヤーキャラクターの位置情報を取得し、それを使用してターゲットの位置を更新するためのメソッドであるUpdateTargetPosition
を定義しています。
以下に詳細な解説を提供します。
このコードの存在理由は、ゲーム内のプレイヤーの動きに応じてターゲットの位置を追従させるために、ターゲットの位置を定期的に更新する必要があるためです。
具体的には、以下の役割を果たします。
UpdateTargetPosition
メソッドの定義:UpdateTargetPosition(Offset:vector3)
というメソッドが定義されています。- このメソッドは一時停止可能であり、何も返さない(void)型です。
- 引数として
Offset
という名前のvector3
型のオブジェクトを受け取ります。- このオフセットはターゲット位置の調整に使用されます。
loop:
loop
キーワードは無限ループを表します。- ループ内のコードは繰り返し実行されます。
- プレイヤーとプレイヤーの位置の取得:
Players : []player = GetPlayspace().GetPlayers()
というコードは、プレイスペース内のプレイヤーを取得します。[]player
はplayer
型の配列を表します。Players
変数にはプレイヤーの配列が格納されます。
- 最初のプレイヤーの取得:
if(Player : player = Players[0])
というコードは、配列Players
の最初の要素を取得し、それをPlayer
という変数に割り当てます。Player
はplayer
型の変数として宣言されます。- このコードは最初のプレイヤーの存在を確認するために使用されます。
- Fortniteキャラクターの取得:
if(FortniteCharacter : fort_character = Player.GetFortCharacter[])
というコードは、Player
オブジェクトからFortniteキャラクターを取得します。FortniteCharacter
はfort_character
型の変数として宣言されます。- このコードはFortniteキャラクターの存在を確認するために使用されます。
- プレイヤーの位置の取得:
PlayerPosition : vector3 = FortniteCharacter.GetTransform().Translation
というコードは、Fortniteキャラクターの位置情報を取得します。FortniteCharacter.GetTransform().Translation
はキャラクターの位置を表す`vector3
関数の呼び出しと定義の違い
クリックすると開きます。
spawn{UpdateTargetPosition(Offset)}
UpdateTargetPosition(Offset:vector3)<suspends>:void=
このデバイスは、プレイヤーの位置を追跡し、指定したオフセット値に基づいてターゲットの位置を更新します。
spawn{UpdateTargetPosition(Offset)}
という行では、UpdateTargetPosition
関数を非同期で実行しています。
この関数には引数としてOffset
が渡されます。
この行では、UpdateTargetPosition
関数が非同期で実行され、他のコードの実行をブロックせずに処理が続行されます。
一方、UpdateTargetPosition(Offset:vector3)<suspends>:void=
という行では、関数の定義が行われています。
ここでは、Offset
引数の型を明示的に指定しています。
Offset:vector3
という記述により、Offset
はvector3
型の引数であることが示されます。
この型指定により、コンパイラは関数を呼び出す際に適切な型の引数を渡すことを要求します。
つまり、spawn{UpdateTargetPosition(Offset)}
は関数の呼び出しであり、引数の型を指定する必要はありません。
一方、UpdateTargetPosition(Offset:vector3)<suspends>:void=
は関数の定義であり、引数の型を指定する必要があるため、Offset:vector3
という型指定が必要となります。
関数が定義されている上で呼び出しているので、わざわざ型を記入する必要がありません。

あすか
loopの詳細
クリックすると開きます。
UpdateTargetPosition(Offset:vector3)<suspends>:void=
loop:
## プレイヤーとポジションを取得
Players : []player = GetPlayspace().GetPlayers()
if(Player : player = Players[0]):
if(FortniteCharacter : fort_character = Player.GetFortCharacter[]):
PlayerPosition : vector3 = FortniteCharacter.GetTransform().Translation
loop:
は無限ループを表すキーワードです。
このloop:キーワードの後に続くコードブロックは繰り返し実行されます。
このコードでは、UpdateTargetPosition
メソッドが呼び出されると、loop:
が開始されます。
ループ内のコードが実行された後、再びループの先頭に戻り、同じコードが繰り返し実行されます。
具体的には、次のようなフローで処理が進行します:
loop:
に到達すると、ループが開始されます。- プレイヤーの配列を取得し、最初のプレイヤーを選択します。
- 選択したプレイヤーがFortniteキャラクターを持っているかを確認します。
- もしFortniteキャラクターを持っている場合、そのキャラクターの位置情報を取得します。
- 取得した位置情報を
PlayerPosition
に代入します。 - ループの先頭に戻り、再びプレイヤーの位置情報の取得とターゲットの位置の更新が行われます。
- 以降、無限に繰り返されます。
このように、loop:
を使用することで、UpdateTargetPosition
メソッドが繰り返し実行されることが保証されます。
これにより、プレイヤーの位置情報を定期的に更新し、ターゲットの位置を適切に追従させることができるわけです。

あすか
if(条件式)を使う理由
クリックすると開きます。
if(Player : player = Players[0]):
条件式の目的は、プレイヤーリストに少なくとも1人以上のプレイヤーが存在するかどうかを確認することです。
Players[0]
にプレイヤーが存在する場合は真となり、条件式内のブロックが実行されます。
一方、プレイヤーリストが空である場合は偽となり、条件式内のブロックはスキップされます。
この条件式を使用することで、最初のプレイヤーが存在するかどうかを確認し、その後のコードの実行を制御しているわけですね。

あすか
spawnとUpdateTargetPositionの関係
クリックすると開きます。
OnBegin<override>()<suspends>:void=
Offset:vector3 = vector3{X:=_OffsetX, Y:=_OffsetY, Z:=_OffsetZ}
## start update loop
spawn{UpdateTargetPosition(Offset)}
UpdateTargetPosition(Offset:vector3)<suspends>:void=
loop:
## プレイヤーとポジションを取得
Players : []player = GetPlayspace().GetPlayers()
if(Player : player = Players[0]):
if(FortniteCharacter : fort_character = Player.GetFortCharacter[]):
PlayerPosition : vector3 = FortniteCharacter.GetTransform().Translation
spawn{UpdateTargetPosition(Offset)}
:spawn
キーワードは、新しいタスク(スレッドやプロセス)を生成して非同期に実行するためのものです。UpdateTargetPosition(Offset)
メソッドをspawn
で非同期に実行しています。- これにより、
UpdateTargetPosition
メソッドがバックグラウンドで繰り返し実行されることになります。
つまり、上記のコードの役割は、OnBegin
イベントが発生した際に実行され、Offset
の値を設定してから、UpdateTargetPosition
メソッドを非同期に実行することです。
このメソッドの実行により、UpdateTargetPosition
メソッドが繰り返し実行され、プレイヤーの位置情報を更新し、ターゲットの位置に適切に追従することが期待されます。
spawn{Upda...~はUpdateTargetPositionのループ機能を開始させるための起爆剤として機能するってわけですね。

あすか
spawn
キーワードを使用することで、UpdateTargetPosition
メソッドは非同期に実行され、繰り返し処理が開始されます。
suspendsを使う意味
クリックすると開きます。
<suspends>
は、メソッドが一時停止可能であることを示すアノテーションです。
このアノテーションは、メソッドが非同期操作や長時間実行される可能性がある場合に使用されます。
UpdateTargetPosition
メソッドがプレイヤーの位置情報を取得し、その位置情報を更新するために使用される場合、これは非同期の操作であり、一時停止が発生する可能性があります。
また、ループ処理が継続的に実行されるため、長時間実行される可能性もあります。
<suspends>
を指定することで、メソッドが一時停止可能であることを明示的に示し、コードの読み手に対してその特性を伝えることができます。
また、一時停止可能なメソッドは非同期操作に適しており、他の処理との並行実行を可能にするため、効率的なプログラムの実行が期待できます。
したがって、<suspends>
を入れることで、UpdateTargetPosition
メソッドが非同期であり、一時停止が発生する可能性があることを明示しているのです。
suspendsと書かなくても機能するの?
UpdateTargetPosition(Offset:vector3):void=と記述しても機能します。
<suspends>
アノテーションは、メソッドが一時停止可能であることを明示するためのものであり、コードの実行には直接的な影響を与えません。
したがって、<suspends>
を省略してUpdateTargetPosition(Offset:vector3):void=
と記述しても、同様の動作をするでしょう。
ただし、<suspends>
を明示的に指定することで、メソッドが非同期操作や一時停止可能な処理を含んでいることを明確に示すことができます。

あすか
()の意味
クリックすると開きます。
GetPlayers
はメソッドであり、()
を使用してメソッドを呼び出すことが一般的な構文です。
GetPlayers
の後ろに()
をつけることで、そのメソッドを実行し、その結果を取得します。
GetPlayspace().GetPlayers()
は、GetPlayspace
メソッドを呼び出してプレイスペースを取得し、そのプレイスペースからGetPlayers
メソッドを呼び出してプレイヤーの配列を取得するという意味です。
()
を使用することで、メソッドの実行を明示し、そのメソッドからの戻り値を受け取ることができます。

あすか
ちなみに、戻り値がない場合でも、()
をつけることでメソッドの呼び出しを示す慣例があります。
PlayerPosition~の詳細
クリックすると開きます。
PlayerPosition : vector3 = FortniteCharacter.GetTransform().Translation
PlayerPosition: vector3 = FortniteCharacter.GetTransform().Translation
という行は、FortniteCharacter
の位置情報を取得し、その値を PlayerPosition
という変数に代入するコードです。
まず、この行は右辺から評価されます。右辺の最初の部分は FortniteCharacter.GetTransform()
です。
これは、FortniteCharacter
オブジェクトの GetTransform()
メソッドを呼び出しています。
GetTransform()メソッドは、FortniteCharacter
の位置や回転、スケールなどの情報を含む変換情報を返します。
次に、右辺の FortniteCharacter.GetTransform()
の結果に対して、さらに Translation
を呼び出しています。
.Translation
は変換情報から位置情報だけを抽出するためのプロパティです。
>> 【詳細】:EpicGames公式ドキュメント
つまり、FortniteCharacter
の位置を表すベクトルを取得することができます。
そして、取得した位置情報を PlayerPosition
という変数に代入しています。
この行の左辺の PlayerPosition
は、変数の宣言を表します。変数名の前に :
を付けることで、その変数の型を示します。
ここでは、PlayerPosition
の型は vector3
と宣言されています。vector3
は3次元のベクトルを表すデータ型です。
最終的に、FortniteCharacter
の位置情報が PlayerPosition
に代入されます。
これにより、PlayerPosition
変数にはプレイヤーの位置が保持されることになります。
このコードの役割は、プレイヤーの位置情報を取得して PlayerPosition
変数に格納することです。
前提:オブジェクトとメソッドの関係
クリックすると開きます。
オブジェクトとメソッドの関係は、人間とその能力の関係に例えることができます。
人間はデータや記憶の集まりであり、メソッドは人間の能力です。
たとえば、人間には名前、年齢、住所などのデータがありますし、能力に関しては歩く、話す、食べるなどできますよね。
これらのデータと能力は、人間の全体像を構成しています。
オブジェクトとメソッドも同じです。
オブジェクトはデータの集まりであり、メソッドはオブジェクトの能力です。
たとえば、オブジェクト「車」には、色、製造年、エンジンの種類などのデータがあります。
また、車を運転する、車を駐車する、車を修理するなどの能力がありますよね。
これらのデータと能力は、オブジェクト「車」の全体像を構成しています。
オブジェクト指向プログラミングでは、プログラムをオブジェクトの組み合わせとして記述します。
オブジェクトはデータとメソッドの集まりであり、オブジェクトの組み合わせによって、複雑なプログラムを作成することができます。
オブジェクトとメソッドの関係は、オブジェクト指向プログラミングの基本的な概念なので把握しておきましょう。

あすか
Translationだけが抽出される理由
クリックすると開きます。
Translation
は、FortniteCharacter
の位置情報のうち、位置座標(X、Y、Z)の部分を取得するために使用されています。
FortniteCharacter
の位置情報には他の情報も含まれており、位置座標以外にも回転やスケールなどが含まれています。
しかし、この今回のコードでは特に位置座標のみを使用しているため、他の情報は必要ありません。
したがって、Translation
を使用して位置座標のみを抽出し、変数PlayerPosition
に代入しているのです。
このように位置座標のみを抽出することで、後続の処理でターゲットの位置を更新する際に便利になります。
PlayerPosition
にはプレイヤーの現在の位置が保持されており、その位置情報にオフセットを加えることでターゲットの位置を計算しています。
要するに、Translation
を使用することで位置座標のみを取得し、プレイヤーの位置情報を利用してターゲットの位置を計算するための処理が行われているわけです。

あすか
GetFortCharacter[]が配列の要素を指定しない理由
クリックすると開きます。
GetFortCharacterメソッドはPlayerオブジェクトのFortniteCharactersプロパティからFortniteCharacterオブジェクトの配列を取得し、それを返すことがわかります。
FortniteCharactersプロパティは、Playerオブジェクトに関連付けられたFortniteCharacterオブジェクトの配列を返します。
そのため、GetFortCharacterメソッドは配列の最初の要素を返します。
Playerオブジェクトに関連づけられているため、そもそも指定しなくても最初のプライヤー(要素)が返ってくるわけです。

あすか
▼ キャラクター情報からプレイヤーの位置を取得し、TargetPositionX
、TargetPositionY
、TargetPositionZ
に代入します。
そして、TargetPosition
という名前のベクトルオブジェクトを作成し、TargetPosition
にOffset
を加えた値を代入します。
これにより、プレイヤーの位置にオフセットを加えた位置がターゲットの新しい位置となります。
TargetPositionX : float = PlayerPosition.X
TargetPositionY : float = PlayerPosition.Y
TargetPositionZ : float = PlayerPosition.Z
TargetPosition:vector3 = vector3{X:=TargetPositionX, Y:=TargetPositionY, Z:=TargetPositionZ} + Offset
クリックすると開きます。
上記のコードは、プレイヤーの現在位置を基に、目標位置を計算しています。
TargetPositionX : float = PlayerPosition.X
- プレイヤーの位置情報からX座標を取得し、
TargetPositionX
という変数に代入しています。 PlayerPosition
はプレイヤーの位置情報を表すvector3
型の変数です。.X
はvector3
オブジェクトのX座標を取得するためのプロパティです。
同様に、TargetPositionY
とTargetPositionZ
もプレイヤーの位置情報からY座標とZ座標を取得し、対応する変数に代入しています。
TargetPosition:vector3 = vector3{X:=TargetPositionX, Y:=TargetPositionY, Z:=TargetPositionZ} + Offset
- 上記で取得したX座標、Y座標、Z座標を使って、最終的な目標位置
TargetPosition
を計算しています。 vector3{X:=TargetPositionX, Y:=TargetPositionY, Z:=TargetPositionZ}
は、vector3
型のオブジェクトを作成しています。Offset
は、先ほど定義されたオフセット値を表すvector3
型の変数です。+
演算子を使って、プレイヤーの位置情報にオフセットを加えて目標位置を計算しています。
要するに、このコードはプレイヤーの現在位置に対してオフセットを加えた目標位置を計算し、TargetPosition
という変数に格納しています。
なお、TargetPositionX
、TargetPositionY
、TargetPositionZ
、TargetPosition
はそれぞれ変数の名前であり、コード中で任意の名前を選んで付けています。
例
クリックすると開きます。
仮にプレイヤーの位置が次のように定義されているとします:
TargetPositionX = 1000.0
TargetPositionY = 2000.0
TargetPositionZ = 500.0
そして、Offset
は次のように定義されています:
_OffsetX = -3000.0
_OffsetY = 0.0
_OffsetZ = 3000.0
この場合、TargetPosition
の計算は以下のようになります:
TargetPosition = vector3{X := TargetPositionX, Y := TargetPositionY, Z := TargetPositionZ} + Offset
具体的な計算手順を示します:
TargetPositionX = 1000.0
TargetPositionY = 2000.0
TargetPositionZ = 500.0
_OffsetX = -3000.0
_OffsetY = 0.0
_OffsetZ = 3000.0
# プレイヤーの位置座標を表すベクトル
PlayerPosition = vector3{X := TargetPositionX, Y := TargetPositionY, Z := TargetPositionZ}
# プレイヤーの位置座標にオフセットを適用した位置を表すベクトル
Offset = vector3{X := _OffsetX, Y := _OffsetY, Z := _OffsetZ}
# プレイヤーの位置に対してオフセットを適用した最終的な位置座標を表すベクトル
TargetPosition = PlayerPosition + Offset
計算結果は以下のようになります:
TargetPositionX = 1000.0
TargetPositionY = 2000.0
TargetPositionZ = 500.0
_OffsetX = -3000.0
_OffsetY = 0.0
_OffsetZ = 3000.0
PlayerPosition = vector3{X := 1000.0, Y := 2000.0, Z := 500.0}
Offset = vector3{X := -3000.0, Y := 0.0, Z := 3000.0}
TargetPosition = vector3{X := 1000.0, Y := 2000.0, Z := 500.0} + vector3{X := -3000.0, Y := 0.0, Z := 3000.0}
= vector3{X := -2000.0, Y := 2000.0, Z := 3500.0}
【わかりやすく計算してみた】
X = 1000.0 + (-3000.0) = -2000.0
Y = 2000.0 + (0.0) = 2000.0
Z = 500.0 + (3000.0) = 3500.0
したがって、最終的なTargetPosition
はvector3{X := -2000.0, Y := 2000.0, Z := 3500.0}
となります。
このように、プレイヤーの位置に対してオフセットを適用した結果の位置座標がTargetPosition
となります。
(ちょっとややこしいですよね、、、🙄)

あすか
つまり、現在地と基準値の差異がTargetPositionってこと?
おっしゃるとおりです。
TargetPosition
は、プレイヤーの現在位置と基準値(オフセット)との差異を表します。
オフセットをプレイヤーの現在位置に加算することで、最終的な移動先の位置座標が得られます。
基準値(オフセット)が負の値を持つ場合、TargetPosition
はプレイヤーの現在位置からマイナス方向に移動した位置になります。
基準値が正の値を持つ場合、TargetPosition
はプレイヤーの現在位置からプラス方向に移動した位置になります。
つまり、TargetPosition
はプレイヤーの位置からの相対的な位置変化を表すベクトルとなります。
▼ このコードは、ターゲットオブジェクトをプレイヤーに追従させるために使用されます。
最初の行では、_Target
を指定した位置に0.5秒かけて移動させます。
2つ目の行では、0.5秒の待機を行います。
これにより、一定の間隔でプレイヤーの位置を取得し、ターゲットオブジェクトをプレイヤーに移動させる動作が実現されます。
# ターゲットをプレイヤーの位置に移動:位置・回転・時間
_Target.MoveTo(TargetPosition, IdentityRotation(), 0.5)
# コメントアウト
# if(_Target.TeleportTo[TargetPosition, IdentityRotation()]):
# void
# 0.5秒間プログラムの実行を一時停止
Sleep(0.5)
# あまりいい動きにならないためコメントアウト
# Sleep(0.0)
クリックすると開きます。
## ターゲットをプレイヤーの位置に移動
_Target.MoveTo(TargetPosition, IdentityRotation(), 0.5)
この部分のコードは、ターゲットオブジェクト(_Target
)をプレイヤーの位置(TargetPosition
)に移動させるための処理です。
MoveTo
メソッドを使用して、ターゲットを指定した位置に移動させます。
また、IdentityRotation()
は回転を表す関数で、ここではデフォルトの回転を指定しています。
最後の引数の0.5
は移動の時間を表しており、この場合は0.5秒かけて移動します。
# Not Good
# if(_Target.TeleportTo[TargetPosition, IdentityRotation()]):
# void
この部分はコメントアウトされていますが、もともとは_Target
を指定した位置に瞬時に移動させるためのコードです。
しかし、コメントアウトされているため、現在のコードでは使用されていません。
Sleep(0.5)
Sleep(0.5)
は、0.5秒間プログラムの実行を一時停止するための処理です。
この場合、次のループの開始まで0.5秒待機します。
つまり、プレイヤーの位置の更新を0.5秒ごとに行います。
# Not Good
# Sleep(0.0)
この部分もコメントアウトされていますが、もともとはSleep(0.0)
というコードがありました。
しかし、これは実行において意味がないため、コメントアウトされています。
このコードは、一定の間隔でプレイヤーの位置を取得し、ターゲットオブジェクトをプレイヤーに追従させるという動作を実現しています。
また、コメントアウトされたコードの部分は一部変更された可能性があり、現在は使用されていないことに注意してください。
回転は適用されるのか?
クリックすると開きます。
正確に言うとIdentityRotation()
は回転を表す関数ですが、デフォルトの回転を指定しています。
IdentityRotation()
関数は、単位回転を表すクォータニオンや行列を返します。単位回転は、オブジェクトを回転させない変換を表します。
したがって、_Target.MoveTo(TargetPosition, IdentityRotation(), 0.5)
のコードでは、_Target
オブジェクトをTargetPosition
に移動させる際に、回転は適用されません。
オブジェクトは移動先の位置にのみ移動し、回転は変わりません。
回転を適用する場合は、IdentityRotation()
の代わりに適切な回転を指定する必要があります。
回転を表すクォータニオンや行列を生成し、それをMoveTo
関数の引数として渡すことで、オブジェクトの移動と回転を同時に行うことができます。
spawnを使うか否か
クリックすると開きます。
spawn
は移動処理が実行されるタイミングによって使用するかどうかが決まります。
一般的に、移動処理が長時間かかる可能性がある場合や他の処理と同時に実行したい場合には、spawn
キーワードを使用します。
これにより、移動処理が非同期的にバックグラウンドで実行され、他の処理が待たされることなく進行できます。
応答性が向上し、プログラム全体のパフォーマンスが改善されます。
一方、移動処理が短時間で完了し、他の処理をブロックすることがない場合には、spawn
キーワードを使用する必要はありません。
同期的な呼び出しで十分な場合は、spawn
キーワードを省略することができます。
つまり、移動処理の実行時間や他の処理との関係性に応じて、適切なタイミングでspawn
キーワードを使うかどうかを選択します。

あすか
Sleepの意義
クリックすると開きます。
Sleep(0.5)
は、プログラムの実行を一時停止するための処理です。
引数の0.5
は秒数を表し、ここでは0.5秒間の一時停止を指定しています。
Sleep
関数を設けない場合、ループが繰り返されるたびに処理がすぐに次に進み、ほぼ瞬時に次のイテレーションが実行されます。
つまり、無制限に処理が繰り返されることになります。
Sleep
関数を使用することで、一時停止が導入されます。
指定した秒数だけプログラムの実行が一時停止し、その後に次の処理が実行されます。
このようにすることで、プログラムの実行を一定の間隔で制御し、無駄な処理やリソースの消費を防ぐことができます。
したがって、Sleep(0.5)
を設けることで、ループが0.5秒ごとに処理されるようになります。
数字の入力だけで移動速度が変更される理由
クリックすると開きます。
MoveTo
関数の第三引数に指定する数字によって移動速度を変更することができます。
通常、移動速度は1より大きい値で指定されます。
例えば、移動速度を2に設定すると、オブジェクトは通常の速度の2倍で目的地に移動します。
逆に、移動速度を0.5に設定すると、オブジェクトは通常の速度の半分の速さで目的地に移動します。
移動速度は、目的地までの距離を1秒あたりに進む距離として解釈されます。
したがって、移動速度が大きいほど、より高速に移動し、移動速度が小さいほど、より遅く移動します。
以上、このコードは、Fortniteのレベル内でプレイヤーの位置を追跡し、ターゲットの位置を更新する機能を持つクリエイティブデバイスを定義しています。
まとめ
今回は、RPGゲームのような俯瞰視点の作り方について解説しました。
完全なスクリプト
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Fortnite.com/Characters }
using { /UnrealEngine.com/Temporary/SpatialMath }
# A Verse-authored creative device that can be placed in a level
track_player_manager := class(creative_device):
# 変数 _Target を creative_prop のインスタンスで初期化
@editable
_Target : creative_prop = creative_prop{}
# _OffsetX、_OffsetY、_OffsetZ という名前の変数を定義し、それぞれの初期値を設定
@editable
_OffsetX : float = -3000.0
@editable
_OffsetY : float = 0.0
@editable
_OffsetZ : float = 3000.0
# Offset という名前の vector3 型の変数を定義し、それに _OffsetX、_OffsetY、_OffsetZ の値を代入。UpdateTargetPosition(Offset) を別スレッドで実行
OnBegin<override>()<suspends>:void=
Offset:vector3 = vector3{X:=_OffsetX, Y:=_OffsetY, Z:=_OffsetZ}
## ループ機能を開始させるための起爆剤
spawn{UpdateTargetPosition(Offset)}
UpdateTargetPosition(Offset:vector3)<suspends>:void=
loop:
## プレイヤーとポジションを取得
Players : []player = GetPlayspace().GetPlayers()
if(Player : player = Players[0]):
if(FortniteCharacter : fort_character = Player.GetFortCharacter[]):
PlayerPosition : vector3 = FortniteCharacter.GetTransform().Translation
TargetPositionX : float = PlayerPosition.X
TargetPositionY : float = PlayerPosition.Y
TargetPositionZ : float = PlayerPosition.Z
TargetPosition:vector3 = vector3{X:=TargetPositionX, Y:=TargetPositionY, Z:=TargetPositionZ} + Offset
# ターゲットをプレイヤーの位置に移動:位置・回転・時間
_Target.MoveTo(TargetPosition, IdentityRotation(), 0.5)
# コメントアウト
# if(_Target.TeleportTo[TargetPosition, IdentityRotation()]):
# void
# 0.5秒間プログラムの実行を一時停止
Sleep(0.5)
# コメントアウト
# Sleep(0.0)
詳細なコメント付き
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Fortnite.com/Characters }
using { /UnrealEngine.com/Temporary/SpatialMath }
# A Verse-authored creative device that can be placed in a level
track_player_manager := class(creative_device):
# 変数 _Target を creative_prop のインスタンスで初期化
@editable
_Target : creative_prop = creative_prop{}
# _OffsetX、_OffsetY、_OffsetZ という名前の変数を定義し、それぞれの初期値を設定
@editable
_OffsetX : float = -3000.0
@editable
_OffsetY : float = 0.0
@editable
_OffsetZ : float = 3000.0
# Offset という名前の vector3 型の変数を定義し、それに _OffsetX、_OffsetY、_OffsetZ の値を代入。UpdateTargetPosition(Offset) を別スレッドで実行
OnBegin<override>()<suspends>:void=
Offset:vector3 = vector3{X:=_OffsetX, Y:=_OffsetY, Z:=_OffsetZ}
# ループ機能を開始させるための起爆剤
spawn{UpdateTargetPosition(Offset)}
# ターゲットの位置の更新:関数の定義
UpdateTargetPosition(Offset:vector3)<suspends>:void=
loop:
# プレイヤーとポジションを取得
Players : []player = GetPlayspace().GetPlayers()
if(Player : player = Players[0]):
# 現在のプレイヤーがフォートナイトキャラクターを制御しているかどうかを確認
# 条件式がTrue(キャラクターオブジェクトが存在する)の場合、次の行でキャラクターの位置情報を取得
if(FortniteCharacter : fort_character = Player.GetFortCharacter[]):
# フォートナイトキャラクターの位置情報のみ取得
PlayerPosition : vector3 = FortniteCharacter.GetTransform().Translation
# PlayerPositionベクトルのX、Y、Zの各成分を個別の変数に格納
TargetPositionX : float = PlayerPosition.X
TargetPositionY : float = PlayerPosition.Y
TargetPositionZ : float = PlayerPosition.Z
# OffsetベクトルをTargetPositionベクトルに加算して、最終的なターゲットの位置を計算
# 計算されたターゲットの位置は、変数"TargetPosition"に代入される
TargetPosition : vector3 = vector3{X:=TargetPositionX, Y:=TargetPositionY, Z:=TargetPositionZ} + Offset
# ターゲットをプレイヤーの位置に移動:位置・回転・時間
_Target.MoveTo(TargetPosition, IdentityRotation(), 0.5)
# コメントアウト
# if(_Target.TeleportTo[TargetPosition, IdentityRotation()]):
# void
# 0.5秒間プログラムの実行を一時停止
Sleep(0.5)
# あまりいい動きにならないためコメントアウト
# Sleep(0.0)
今回もお疲れさまでした。
地道にコツコツと積み上げていきましょう😌