はじめに
前回の記事(前編)に引き続き、ガードAIの行動を制御する guard_actions_component について調べます
✅ Revive(蘇生)
❓ Attack(ターゲット攻撃:挙動確認中)
✅ Tether (Position)(地点への係留)
✅ Tether (Entity)(エンティティへの係留・追従)
✅ Untether(係留の解除)
環境:
UEFN v39.10(UE 5.8)
7. Revive
# Revive the specified target.
Revive<native><public>(Target:entity)<suspends>:result(void, ai_action_error_type)
# 指定されたターゲットを蘇生します。
-
検証結果:
- Revive() を実行すると、 NPCが対象へ歩み寄り、蘇生モーションを開始します
- NPCを味方チームに変更してから Revive() を実行しました
検証用コード・設定
プレイヤーをダウン状態にするための準備:
- ダウンの仕掛けを配置します
- 2人プレイでゲームを開始し、NPCを1体スポーンします
- 全員を同じチームにします(コード内で実行しています)
# 復活のテスト
Revive_Test(Agent:agent)<suspends>:void=
Print("========== Revive_Test ==========")
AllFortCharacters := GetAllFortCharacters()
Print(" - AllFortCharacters.Length: {AllFortCharacters.Length}")
TC := GetPlayspace().GetTeamCollection()
for(I -> FC : AllFortCharacters, FCAgent := FC.GetAgent[]):
if(FCEntity := FC.GetEntity[]):
PrintAgentName(FCEntity)
# NPCを含む全員が、Agentと同じチームになるように変更
if(TC.ChangeTeam[FCAgent, Agent, team_attitude.Friendly]):
else:
Print(" - Error: ChangeTeam[] failed. {FCAgent}" + ToDiagnostic(team_attitude.Friendly))
# Agent を ダウン状態にする
DBNODevice.Down(Agent)
# NPC が ダウン状態のプレイヤーを復活させる
if(SimE := GetSimulationEntity[]):
GuardActionsComps := for(Comp : SimE.FindDescendantComponents(guard_actions_component)){Comp}
if(GuardActionsComp := GuardActionsComps[0]):
GuardActionsComp.Revive(Agent)
Print("===============================================")
8. Attack
# Attack the target. The target must have been detected.
Attack<native><public>(Target:entity)<suspends>:result(void, ai_action_error_type)
# ターゲットを攻撃します。ターゲットは検知されている必要があります。
-
検証結果:
- NPCを敵チームに変更後、Attack() を実行すると、プレイヤーに向かって攻撃アクションを開始しました
しかし、Attack() を呼び出さなくても、敵チームであればNPCは自律的に攻撃を開始します。そのため、このメソッドによって攻撃がトリガーされたのか、標準AIの検知によって開始されたのかの区別が困難でした - 処理終了タイミング: NPCが攻撃しているプレイヤーが撃破した直後。また、NPCが撃破されてリスポーンした直後 に処理が終了しました
- NPCを敵チームに変更後、Attack() を実行すると、プレイヤーに向かって攻撃アクションを開始しました
検証用コード
GetAgentFromEntity[]、ChangeTeam[] は自作関数です。 おまけ: 自作関数
Attack_Test1(Agent:agent)<suspends>:void=
if(SimE := GetSimulationEntity[]):
GuardActionsComps := for(Comp : SimE.FindDescendantComponents(guard_actions_component)){Comp}
TC := GetPlayspace().GetTeamCollection()
if:
GuardActionsComp := GuardActionsComps[0]
AgentE := Agent.GetFortCharacter[].GetEntity[]
NPCAgent := GetAgentFromEntity[GuardActionsComp.Entity]
NPCAwarenessComp := GuardActionsComp.Entity.GetComponent[npc_awareness_component]
then:
NPCAwarenessComp.DetectTargetEvent.Subscribe(OnDetectTarget)
NPCAwarenessComp.ForgetTargetEvent.Subscribe(OnForgetTarget)
Print("========== Attack_Test1 ==========")
# NPC を Agentと敵チームになるように変更
if(TC.ChangeTeam[NPCAgent, Agent, team_attitude.Hostile]):
Print(" - ChangeTeam[] Success: Hostile")
else:
Print(" - Error: ChangeTeam[] failed. Hostile")
GuardActionsComp.Attack(AgentE)
Print("===============================================")
OnDetectTarget(TaretInfo : npc_target_info):void=
Print("OnDetectTarget: Target を検知されました")
OnForgetTarget(TargetEntity : entity):void=
Print("OnForgetTarget: Target を忘れられました")
9. Tether
# Tether the NPC to a position.
# 'Radius' is in centimeters.
Tether<native><public>(Location:(/Verse.org/SpatialMath:)vector3, Radius:float):void
# NPCを位置にテザー(係留)します。
# 'Radius'はセンチメートル単位です。
-
検証結果:
- RoamAround() を実行すると、 この指定位置にNPCが位置に係留します
特定の拠点を守る歩哨のような動きが作れます
- RoamAround() を実行すると、 この指定位置にNPCが位置に係留します
10. Tether
# Tether the NPC to an entity.
# 'Radius' is in centimeters.
Tether<native><public>(Target:entity, Radius:float):void
# NPCをエンティティにテザー(係留)します。
# 'Radius'はセンチメートル単位です。
-
検証結果:
- プレイヤーに Tether して RoamAround() を実行すると、 プレイヤーの移動に合わせてNPCも付いてくる(追従する) ようになります。護衛対象を守るガードの実装ができます
検証用コード
Tether_Test(Agent:agent)<suspends>:void=
Print("========== Tether_Test ==========")
if(SimE := GetSimulationEntity[]):
GuardActionsComps := for(Comp : SimE.FindDescendantComponents(guard_actions_component)){Comp}
if(GuardActionsComp := GuardActionsComps[0]):
# # 特定の位置にテザーする場合
# TetherCenter := (/Verse.org/SpatialMath:)vector3{Left:=8456.0, Up:=0.0, Forward:=3340.0}
# TetherRadius := 512.0
# GuardActionsComp.Tether(TetherCenter, TetherRadius)
# Agent にテザーする場合
TetherRadius := 256.0
GuardActionsComp.Tether(Agent, TetherRadius)
Print(" - Tether to Agent. Radius: {TetherRadius}")
GuardActionsComp.RoamAround()
Print("===============================================")
11. Untether
# Untether the NPC.
Untether<native><public>():void
# NPCのテザー(係留)を解除します。
-
検証結果:
- Tether を設定して、 RoamAround() 実行中に Untether() することで、 係留を解除します
検証用コード
Untether_Test(Agent:agent)<suspends>:void=
Print("========== Untether_Test ==========")
if(SimE := GetSimulationEntity[]):
GuardActionsComps := for(Comp : SimE.FindDescendantComponents(guard_actions_component)){Comp}
if(GuardActionsComp := GuardActionsComps[0]):
# Tether(RoamAround) と Untether を繰り返す
loop:
race:
block:
TetherRadius := 256.0
GuardActionsComp.Tether(Agent, TetherRadius)
Print(" - Tether to Agent. Radius: {TetherRadius}")
GuardActionsComp.RoamAround()
Print(" - RoamAround: End.")
block:
Sleep(3.0)
GuardActionsComp.Untether()
Print(" - Untether")
Sleep(2.0)
Print("===============================================")
まとめ
前後編にわたって guard_actions_component を検証してきました。
このコンポーネントの登場により、「プレイヤーに付いてくるガード」や「ダウンした仲間を助けるNPC」が、Verseからシンプルに実装できるようになりそうですね
おまけ:自作関数
Agent と予想される Entity から Agent を取得する
# Agent と予想される Entity から agent オブジェクトを取得する。取得できない場合、失敗する
GetAgentFromEntity<public>(AgentEntity:entity)<transacts><decides>:agent=
var ReturnValue:?agent = false
for(Comp : AgentEntity.GetComponents()):
if:
FC := fort_character[Comp]
Agent := FC.GetAgent[]
then:
set ReturnValue = option{ Agent }
return ReturnValue?
すべての FortCharacter を取得する
# すべての FortCharacter を取得する
(Entity:entity).GetAllFortCharacters<public>()<transacts>:[]fort_character=
var ReturnValue:[]fort_character = array{}
if(SimE := Entity.GetSimulationEntity[]):
for(ChildEntity : SimE.GetEntities()):
for(Comp : ChildEntity.GetComponents()):
if(FC := fort_character[Comp]):
set ReturnValue += array{ FC }
return ReturnValue
Agent1 を Agent2 の味方 or 敵 にする
# Agent1 を Agent2 の味方 or 敵 にする
# - Friendly(味方): Agent1 を Agent2 と同じチームにする
# - Hostile(敵): Agent1 を Agent2 と異なるチームにする
# - Neutral(中立): 実装なし。失敗扱い
# agent に team_attitude を直接設定することはできないため、 チームを変更することで関係性を設定する
(TeamCollection:fort_team_collection).ChangeTeam<public>(Agent1:agent, Agent2:agent, TeamAttitude:team_attitude)<transacts><decides>:void=
case(TeamAttitude):
team_attitude.Friendly =>
Agent2Team := TeamCollection.GetTeam[Agent2]
TeamCollection.AddToTeam[Agent1, Agent2Team]
team_attitude.Hostile =>
Teams := TeamCollection.GetTeams()
# Agent2 が所属していない最初のチームを取得
NewTeamQ := option:
for(I -> Team : Teams, not TeamCollection.IsOnTeam[Agent2, Team]){ Team }[0]
NewTeam := NewTeamQ?
TeamCollection.AddToTeam[Agent1, NewTeam]
team_attitude.Neutral =>
Print(" - Neutral はチーム変更の実装なし. 失敗扱い")
false?
関連記事
