mcpField
U
ue-mass-entity

ue-mass-entity

github.com/quodsoler開発4191.1k 回インストール4 週間前に更新
リポジトリ

ue-mass-entity について

大規模なエージェントシミュレーション向け ECS である Mass Entity を扱う、UE 5.8 開発者向けリファレンススキルです。データ型、プロセッサ、クエリ、エンティティ管理、安全な反復処理を説明します。

主な機能

  1. 01フラグメント、タグ、アーキタイプ
  2. 02エンティティマネージャーと構築
  3. 03プロセッサとクエリの設定
  4. 04チャンク反復、並列処理、処理量制限
  5. 05反復中の構造変更の遅延

活用シーン

  1. 01多数のエージェント向けシミュレーション構築
  2. 02フラグメントでエンティティを絞るプロセッサ作成
  3. 03反復中にタグを安全に追加・削除
  4. 04チャンク並列処理やフレーム間制限の設定

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/quodsoler/unreal-engine-skills --skill ue-mass-entity

インストール後、Claude Code で/ue-mass-entityを入力して読み込まれたか確認します。

同梱ファイルreferences/mass-entity-patterns.mdreferences/mass-fragment-reference.md

詳細

UE Mass Entity

対象エンジン:UE 5.8。以下の API は 5.8 のヘッダーで検証済みです。古い形式は「非推奨 — 使用しないでください」に記載しています。

Mass は、キャッシュフレンドリーなデータレイアウトとマルチスレッドプロセッサーを使用して数千のエージェントをシミュレートする、アーキタイプベースの Entity Component System です。コア機能は常に利用可能なランタイムモジュール MassEntity と MassCore(および MassSignals、MassEngine)にあります。ゲームプレイ向けの機能(スポーン、表現、移動、LOD、レプリケーション、アクター連携)は MassGameplay(5.8 では Experimental) プラグインから、ナビゲーションとビヘイビアは MassAI(5.8 では Experimental) および MassCrowd(5.8 では Experimental) から提供されます。

コンテキスト

.agents/ue-project-context.md が存在する場合は読み込んでください(モジュール名、規約、有効なプラグイン、GAS/ネットワーキングの設定)。ファイルがなくても作業を中断しないでください。

リクエストとコードベースから該当する領域を特定してください。異なるコードにつながる解釈が複数あり得る場合に限り、確認してください。

リクエストの内容…参照先
Build.cs、インクルード、「プラグインを有効にする」モジュールと Build.cs
フラグメント、タグ、共有/チャンクデータの宣言フラグメント、タグ、アーキタイプ
エンティティの作成/破棄、直接変更FMassEntityManager
プロセッサーの作成、スケジューリング、フェーズUMassProcessor
エンティティの選択、要件、フィルタリングFMassEntityQuery
ループ内でのフラグメントデータの読み取り/書き込みイテレーションと FMassExecutionContext
イテレーション中のフラグメントの追加/削除遅延コマンド
フラグメント/タグの追加または削除への反応UMassObserverProcessor
プロセッサー外での単一エンティティ、フルーエントビルド単一エンティティへのアクセスと FEntityBuilder
親子リンク、アーキタイプのグループ化リレーションとアーキタイプグループ
トレイト、設定アセット、AMassSpawnerトレイト、設定アセット、スポーン
ISM レンダリング、LOD、アクターへの昇格表現と LOD
群衆、レーン、エンティティ AI群衆、ナビゲーション、ビヘイビア

より詳しいテンプレートは mass-entity-patterns.md に、組み込みの型とフィールドは mass-fragment-reference.md にあります。

モジュールと Build.cs

有効化する MassEntity プラグインはありません。MassEntity と MassCore はエンジンに含まれるランタイムモジュールです。Build.cs に追加すればリンクされます。プラグインなのはゲームプレイ層のみで、これらは .uproject で有効にする必要があります。

PublicDependencyModuleNames.AddRange(new string[] {
    "Core", "CoreUObject", "Engine",
    "MassEntity",          // manager, processors, queries, commands, relations
    "MassCore",            // FMassFragment/FMassTag bases, FTransformFragment, traits
    "MassSignals",         // UMassSignalSubsystem, UMassSignalProcessorBase
    "MassCommon",          // UE::Mass::ProcessorGroupNames      (MassGameplay)
    "MassSpawner",         // UMassEntityTraitBase, AMassSpawner  (MassGameplay)
    "MassMovement",        // FMassVelocityFragment               (MassGameplay)
    "MassRepresentation",  // ISM visualization + LOD             (MassGameplay)
    "MassActors",          // UMassAgentComponent, actor bridging (MassGameplay)
    "MassNavigation",      // FMassMoveTargetFragment             (MassAI)
});

MassCore に移動したインクルードパスには、Mass/ 名前空間が付いています:Mass/EntityElementTypes.h、Mass/EntityFragments.h、Mass/EntityHandle.h、Mass/ExternalSubsystemTraits.h、Mass/ArchetypeGroup.h。MassEntityTypes.h は引き続き要素の基底クラスを推移的に取り込みますが、明示的な Mass/ パスを優先してください。

フラグメント、タグ、アーキタイプ

概念基底型目的
エンティティFMassEntityHandleインデックスと SerialNumber による識別ハンドル
フラグメントFMassFragmentエンティティごとの可変データ
スパースフラグメントFMassSparseFragmentアーキタイプの外部に保存されるエンティティごとのデータ(追加時にエンティティの移動なし)
タグFMassTagフィルタリングに使用するサイズゼロのマーカー
スパースタグFMassSparseTagアーキタイプを変更せずに追加・削除できるマーカー
チャンクフラグメントFMassChunkFragmentメモリチャンクごとに1つのインスタンス
共有フラグメントFMassSharedFragment参照するエンティティ間で共有される可変値
定数共有フラグメントFMassConstSharedFragment不変の共有設定
アーキタイプFMassArchetypeHandle一意なフラグメント/タグの構成

これらはすべて FMassElement を継承し、Mass/EntityElementTypes.h で宣言されています。

// MyMassTypes.h
#pragma once
#include "Mass/EntityElementTypes.h"
#include "MyMassTypes.generated.h"

USTRUCT()
struct FMyHealthFragment : public FMassFragment
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, Category = "Mass")
    float Current = 100.f;

    UPROPERTY(EditAnywhere, Category = "Mass")
    float Max = 100.f;
};

USTRUCT()
struct FMyAliveTag : public FMassTag { GENERATED_BODY() };

USTRUCT()
struct FMyDeadTag : public FMassTag { GENERATED_BODY() };

USTRUCT()
struct FMySquadSharedFragment : public FMassSharedFragment
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, Category = "Mass")
    int32 SquadID = 0;
};

同じ構成のエンティティは同じアーキタイプを共有し、各フラグメント型はチャンクごとに連続して格納されます。フラグメントまたはタグを追加・削除すると、エンティティは別のアーキタイプに移動します。そのため、イテレーション中の構造変更は遅延されます。スパースフラグメントとスパースタグは、まさにこの移動を避けるために存在します。

FMassEntityManager

FMassEntityManager は UObject ではありません。TSharedFromThis<FMassEntityManager> と FGCObject を継承する構造体です。UMassEntitySubsystem(UMassSubsystemBase の派生クラスであり、それ自体は UWorldSubsystem)または UE::Mass::Utils 経由でアクセスします。

#include "MassEntitySubsystem.h"
#include "MassEntityManager.h"

UMassEntitySubsystem* MassSubsystem = GetWorld()->GetSubsystem<UMassEntitySubsystem>();
FMassEntityManager& EntityManager = MassSubsystem->GetMutableEntityManager();
// const access: MassSubsystem->GetEntityManager()
// free function: UE::Mass::Utils::GetEntityManagerChecked(*GetWorld())
FMassEntityHandle Entity = EntityManager.CreateEntity(ArchetypeHandle);

FMassEntityHandle Reserved = EntityManager.ReserveEntity();
EntityManager.BuildEntity(Reserved, ArchetypeHandle);

// Batch creation: hold the returned context until observers should fire.
TArray<FMassEntityHandle> Entities;
TSharedRef<FMassEntityManager::FEntityCreationContext> CreationContext =
    EntityManager.BatchCreateEntities(ArchetypeHandle, 5000, Entities);

EntityManager.DestroyEntity(Entity);
EntityManager.BatchDestroyEntities(Entities);

FMassEntityHandle::IsSet()(およびその別名 IsValid())が示すのは、Index と SerialNumber がゼロ以外であることだけです。エンティティが実際に存在するかを確認するには、マネージャーを使用します。

EntityManager.IsEntityValid(Handle);   // handle refers to a live entity
EntityManager.IsEntityBuilt(Handle);   // reserved handle has been built
EntityManager.IsEntityActive(Handle);  // valid and built

プロセッサーのイテレーション外では、構造を直接変更できます。追加/タグ付け/交換の呼び出しには TNotNull<const UScriptStruct*> が必要です(RemoveFragmentFromEntity は引き続き通常の const UScriptStruct* を受け取ります。MassEntityManager.h:430)。そのため、T::StaticStruct() を渡し、null の可能性があるポインターは決して渡さないでください。

EntityManager.AddFragmentToEntity(Handle, FMyHealthFragment::StaticStruct());
EntityManager.RemoveFragmentFromEntity(Handle, FMyHealthFragment::StaticStruct());
EntityManager.AddTagToEntity(Handle, FMyDeadTag::StaticStruct());
EntityManager.RemoveTagFromEntity(Handle, FMyDeadTag::StaticStruct());
EntityManager.SwapTagsForEntity(Handle, FMyAliveTag::StaticStruct(), FMyDeadTag::StaticStruct());

エンティティのストレージは並行処理に対応しています。FMassEntityManager::Initialize(const FMassEntityManagerStorageInitParams&) は FMassEntityManager_InitParams_Concurrent(MaxEntityCount、MaxEntitiesPerPage)を受け取り、FConcurrentEntityStorage を構築します。サブシステムがこれを行うため、呼び出しが必要なのはスタンドアロンのマネージャーだけです。

UMassProcessor

UMassProcessor をサブクラス化し、クエリの構築時に *this を渡して自身を登録し、宣言どおりに2つの仮想関数をオーバーライドします。

// MyMovementProcessor.h
#pragma once
#include "MassProcessor.h"
#include "MassEntityQuery.h"
#include "MyMovementProcessor.generated.h"

UCLASS()
class MYGAME_API UMyMovementProcessor : public UMassProcessor
{
    GENERATED_BODY()
public:
    UMyMovementProcessor();
protected:
    virtual void ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager) override;
    virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override;
private:
    FMassEntityQuery MovementQuery;
};
// MyMovementProcessor.cpp
#include "MyMovementProcessor.h"
#include "MassCommonTypes.h"        // UE::Mass::ProcessorGroupNames
#include "MassExecutionContext.h"
#include "MassMovementFragments.h"   // FMassVelocityFragment, FMassMovementParameters
#include "MassSignalSubsystem.h"
#include "Mass/EntityFragments.h"    // FTransformFragment
#include "MyMassTypes.h"             // FMyHealthFragment, FMyDeadTag, FMySquadSharedFragment

UMyMovementProcessor::UMyMovementProcessor()
    : MovementQuery(*this)
{
    ProcessingPhase = EMassProcessingPhase::PrePhysics;
    ExecutionFlags = static_cast<int32>(EProcessorExecutionFlags::AllNetModes);
    ExecutionOrder.ExecuteInGroup = UE::Mass::ProcessorGroupNames::Movement;
    ExecutionOrder.ExecuteAfter.Add(TEXT("MassApplyMovementProcessor")); // class FName: no U prefix
    bRequiresGameThreadExecution = false;
}

EMassProcessingPhase:PrePhysics、StartPhysics、DuringPhysics、EndPhysics、PostPhysics、FrameEnd、MAX。

EProcessorExecutionFlags:None、Standalone、Server、Client、Editor、EditorWorld、AllNetModes(Standalone|Server|Client)、AllWorldModes(AllNetModes|EditorWorld)、All。

FMassProcessorExecutionOrder は、ExecuteInGroup(FName)と、プロセッサー名またはグループ名の配列である ExecuteBefore および ExecuteAfter を持ちます。プロセッサー名は、U プレフィックスを除いたクラスの FName です(MassProcessorDependencySolver.cpp:560)。UOther::StaticClass()->GetFName() を使用してください。名前を間違えると、ログの詳細度が Log のメッセージとともにダミーノードになります(:705)。その結果、順序付けは通知されないまま失われます。名前付きグループは UE::Mass::ProcessorGroupNames にあります:UpdateWorldFromMass、SyncWorldToMass、Behavior、Tasks、Avoidance、ApplyForces、Movement。

FMassEntityQuery

要件はConfigureQueriesで宣言してください。クエリはConfigureQueriesの実行直前にエンティティマネージャーにバインドされるため、コンストラクターでは宣言しないでください。FMassEntityQuery(UMassProcessor& Owner)で構築したクエリはすでに登録されています。デフォルト構築したメンバーの場合は、引き続きRegisterQuery(Query)が必要です。

void UMyMovementProcessor::ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager)
{
    MovementQuery.AddRequirement<FTransformFragment>(
        EMassFragmentAccess::ReadWrite, EMassFragmentPresence::All);
    MovementQuery.AddRequirement<FMassVelocityFragment>(
        EMassFragmentAccess::ReadOnly, EMassFragmentPresence::All);
    MovementQuery.AddRequirement<FMyHealthFragment>(
        EMassFragmentAccess::ReadOnly, EMassFragmentPresence::Optional);
    MovementQuery.AddTagRequirement<FMyDeadTag>(EMassFragmentPresence::None);
    MovementQuery.AddSharedRequirement<FMySquadSharedFragment>(
        EMassFragmentAccess::ReadOnly, EMassFragmentPresence::All);
    MovementQuery.AddConstSharedRequirement<FMassMovementParameters>(
        EMassFragmentPresence::All);
    MovementQuery.AddSubsystemRequirement<UMassSignalSubsystem>(
        EMassFragmentAccess::ReadWrite);
}
EMassFragmentAccess意味
Noneフィルターのみ。データバインディングはありません
ReadOnlyGetFragmentView<T>()(TConstArrayView)を通じて読み取ります
ReadWriteGetMutableFragmentView<T>()(TArrayView)を通じて読み書きします
EMassFragmentPresence意味
Allリストに含まれるすべての要素が存在する必要があります
AnyAnyが指定された要素のうち、少なくとも1つが存在する必要があります
None要素が存在してはいけません
Optional存在する場合はバインドされ、存在しない場合はスキップされます

AddChunkRequirement<T>(Access, Presence)はFMassChunkFragmentをバインドします。SetChunkFilter(const FMassChunkConditionFunction&)は、チャンクごとに1回評価されるTFunction<bool(const FMassExecutionContext&)>を受け取ります。そのため、述語からチャンクフラグメント、共有フラグメント、定数データは読み取れますが、エンティティごとのフラグメントは読み取れません。

イテレーションとFMassExecutionContext

void UMyMovementProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
    MovementQuery.ForEachEntityChunk(Context, [](FMassExecutionContext& Context)
    {
        const int32 NumEntities = Context.GetNumEntities();
        const TArrayView<FTransformFragment> Transforms = Context.GetMutableFragmentView<FTransformFragment>();
        const TConstArrayView<FMassVelocityFragment> Velocities = Context.GetFragmentView<FMassVelocityFragment>();
        const float DeltaTime = Context.GetDeltaTimeSeconds();

        for (int32 Index = 0; Index < NumEntities; ++Index)
        {
            Transforms[Index].GetMutableTransform().AddToTranslation(Velocities[Index].Value * DeltaTime);
        }
    });
}

ラムダが受け取るのはFMassExecutionContext&のみです。エンティティマネージャーのパラメーターはありません。その他のアクセサーは、GetEntities()(TConstArrayView<FMassEntityHandle>)、GetFragmentView<T>()、GetMutableFragmentView<T>()、GetChunkFragment<T>()、GetConstSharedFragment<T>()、GetMutableSharedFragment<T>()、GetSubsystem<T>()、GetMutableSubsystem<T>()、Defer()です。

並列処理: ParallelForEachEntityChunk(Context, Lambda, FMassEntityQuery::EParallelExecutionFlags::Default)を使用します。ネストされた列挙型のフラグはDefault、Force、AutoBalanceです(MassEntityQuery.h:67)。

時間分割: ForEachEntityChunk(Context, Limiter, Lambda)は、エンティティ数から構築したUE::Mass::FExecutionLimiter&を受け取ります。指定した数のエンティティを訪問すると停止し(処理中のチャンクは必ず最後まで処理します)、次回の呼び出しでは保存された位置から再開します。リミッターはプロセッサーのメンバーとしてフレームをまたいで保持してください。新しいリミッターは先頭から再開し、リミッターは呼び出し間に作成または破棄されたエンティティを認識しません。

遅延コマンド

ForEachEntityChunkの中では、エンティティマネージャーを通じてコンポジションを変更しないでください。エンティティが別のアーキタイプに移動し、保持中のビューがダングリング状態になります。変更はContext.Defer()にキューイングしてください。

MovementQuery.ForEachEntityChunk(Context, [](FMassExecutionContext& Context)
{
    const TConstArrayView<FMassEntityHandle> Entities = Context.GetEntities();
    const TConstArrayView<FMyHealthFragment> Healths = Context.GetFragmentView<FMyHealthFragment>();

    for (int32 Index = 0; Index < Context.GetNumEntities(); ++Index)
    {
        if (Healths[Index].Current <= 0.f)
        {
            Context.Defer().SwapTags<FMyAliveTag, FMyDeadTag>(Entities[Index]);
            Context.Defer().RemoveFragment<FMyHealthFragment>(Entities[Index]);
        }
    }
});

FMassCommandBufferの便利な呼び出しには、AddFragment<T>、RemoveFragment<T>、AddTag<T>、RemoveTag<T>、SwapTags<TOld, TNew>、AddElements<T...>、RemoveElements<T...>、DestroyEntity、DestroyEntitiesがあります。追加・削除ヘルパーはFMassCommandAddElements<T...>/FMassCommandRemoveElements<T...>に処理を委譲し、1回のエンティティ移動でフラグメントとタグを任意に組み合わせて処理します(SwapTagsはFMassCommandSwapTagsInternalを使用します。MassCommandBuffer.h:264)。

フラグメントとその値の両方を追加するには、コマンド型を直接プッシュします。

Context.Defer().PushCommand<FMassCommandAddFragmentInstances<FMyHealthFragment>>(
    Entities[Index], FMyHealthFragment{});

任意の遅延処理を実行するには、FMassDeferredCommand<OpType>を通じてラムダをプッシュします。例:Context.Defer().PushCommand<FMassDeferredSetCommand>([Entity](FMassEntityManager& Manager) { ... });(エイリアスはFMassDeferredCreate/Add/Remove/ChangeComposition/Set/DestroyCommandです。MassCommands.h:1686-1744)。再利用可能な型付きコマンドを作成する場合は、FMassBatchedCommandをサブクラス化し、Run(FMassEntityManager&)をオーバーライドして(constのExecuteはUE_DEPRECATED(5.7)です。MassCommands.h:202)、PushUniqueCommand(TUniquePtr<FMassBatchedCommand>&&)を呼び出します。

コマンドは列挙型の順序ではなく、EMassCommandOperationTypeのバケット順にフラッシュされます。順序はCreate → Add → ChangeComposition → Set → RemoveとDestroy(1つの共通バケット内でプッシュ順)→ 最後に分類されないNoneです(MassCommandBuffer.cpp:110-119)。この順序により、エンティティが変更前に存在し、値の書き込み前にフラグメントが存在することが保証されます。

UMassObserverProcessor

監視対象のフラグメントまたはタグが追加または削除されると、オブザーバーが実行されます。ObservedTypes(配列です。1つのオブザーバーで複数の型を監視できます)と ObservedOperations を設定します。

// MyHealthObserver.cpp
UMyHealthObserver::UMyHealthObserver()
    : ObserverQuery(*this)
{
    ObservedTypes.Add(FMyHealthFragment::StaticStruct());
    ObservedOperations = EMassObservedOperationFlags::Add;
}

EMassObservedOperationFlags:None、AddElement、RemoveElement、CreateEntity、DestroyEntityに加え、複合フラグのAdd(=AddElement | CreateEntity)、Remove(=RemoveElement | DestroyEntity)、およびAllがあります。エンティティの生成を意図的に無視する場合を除き、Add/Removeを使用してください。AddElementだけでは、フラグメントとともに生成されるエンティティに対して実行されません。

登録する型をカスタマイズするにはRegister()をオーバーライドし、GetObservedTypes()またはGetSingleObservedTypeChecked()で型を取得します。オブザーバーのテンプレート全体はmass-entity-patterns.mdを参照してください。

単一エンティティへのアクセスとFEntityBuilder

FMassEntityViewを使うと、1つのエンティティに型付きでアクセスできます。これは一時的なものです。アーキタイプのメモリは再配置されるため、毎回新しいビューを作成し、キャッシュしないでください。

if (EntityManager.IsEntityValid(Handle))
{
    FMassEntityView View(EntityManager, Handle);
    if (FMyHealthFragment* Health = View.GetFragmentDataPtr<FMyHealthFragment>())
    {
        Health->Current -= Damage;
    }
    const bool bDead = View.HasTag<FMyDeadTag>();
}

UE::Mass::FEntityBuilder(MassEntityBuilder.h)を使うと、エンティティを流れるように組み立てて一度にコミットできます。これはAddFragmentToEntityを繰り返し呼び出すより効率的です。

#include "MassEntityBuilder.h"

UE::Mass::FEntityBuilder Builder(EntityManager);
Builder.Add<FTransformFragment>(SpawnTransform);
Builder.Add<FMyHealthFragment>();
Builder.Add<FMyAliveTag>();
FMyHealthFragment& Health = Builder.GetOrCreate<FMyHealthFragment>();
Health.Current = 50.f;
const FMassEntityHandle NewEntity = Builder.Commit();

SetForceDeferredCommit(true)を設定すると、コミットはコマンドバッファー経由で行われます。CommitAndReprepare()を呼び出すと、次のエンティティにビルダーを再利用できます。

リレーションとアーキタイプグループ

リレーションは、エンティティ間の型付きリンクをモデル化します。リレーション型はFMassRelation(それ自体がFMassTag)を継承します。エンジンにはRelations/MassChildOf.hにあるFMassChildOfFragmentを伴うFMassChildOfRelationが用意されています。インスタンスの作成とクエリにはUE::Mass::FRelationManagerを使用し、EntityManager.GetRelationManager()で取得します。

#include "MassRelationManager.h"
#include "Relations/MassChildOf.h"

UE::Mass::FRelationManager& Relations = EntityManager.GetRelationManager();
Relations.CreateRelationInstance<FMassChildOfRelation>(ChildEntity, ParentEntity);
TArray<FMassEntityHandle> Children = Relations.GetRelationSubjects(
    FMassChildOfRelation::StaticStruct(), ParentEntity);

遅延処理での同等の書き方は次のとおりです。Context.Defer().PushCommand<FMassCommandMakeRelation<FMassChildOfRelation>>(ChildEntity, ParentEntity)。ビルダーではAddRelation<T>(OtherEntity)を使用します。

アーキタイプグループを使うと、クエリで処理するアーキタイプの順序を制御できます。EntityManager.FindOrAddArchetypeGroupType(GroupName)はUE::Mass::FArchetypeGroupTypeを返します。EntityManager.BatchGroupEntities(UE::Mass::FArchetypeGroupHandle(GroupType, UE::Mass::FArchetypeGroupID(N)), Entities)でエンティティを割り当てます(Mass/ArchetypeGroup.h:100)。Query.GroupBy(GroupType)(必要に応じてソート述語も指定)を使うと、イテレーションでグループごとにまとめて処理されます。

トレイト、設定アセット、スポーン

UMassEntityConfigAsset(UDataAsset)には、エンティティテンプレートを構築するトレイトのリストが格納されます。トレイトはUMassEntityTraitBaseを継承し、BuildTemplateを実装します。どちらの仮想関数もconstであり、ValidateTemplateは3つのパラメーターを受け取ります。

// MyHealthTrait.h
#pragma once
#include "MassEntityTraitBase.h"
#include "MyHealthTrait.generated.h"

UCLASS(meta = (DisplayName = "My Health"))
class MYGAME_API UMyHealthTrait : public UMassEntityTraitBase
{
    GENERATED_BODY()
public:
    UPROPERTY(EditAnywhere, Category = "Health")
    float DefaultHealth = 100.f;

protected:
    virtual void BuildTemplate(FMassEntityTemplateBuildContext& BuildContext,
                               const UWorld& World) const override;
    virtual bool ValidateTemplate(const FMassEntityTemplateBuildContext& BuildContext,
                                  const UWorld& World,
                                  FAdditionalTraitRequirements& OutTraitRequirements) const override;
};

FMassEntityTemplateBuildContext(MassEntityTemplateRegistry.hで宣言)では、AddFragment<T>()、AddFragment_GetRef<T>()、AddTag<T>()、AddChunkFragment<T>()、AddSharedFragment(const FSharedStruct&)、AddConstSharedFragment(const FConstSharedStruct&)、およびAddTranslator<T>()を使用できます。

UMassAssortedFragmentsTrait(MassAssortedFragmentsTrait.h)を使うと、C++を記述せずに、エディターで設定したフラグメントとタグのリストを追加できます。AMassSpawnerはEntityTypesを通じて設定アセットを参照し、Count、DoSpawning()、DoDespawning()を実行します。

表現と LOD

UMassRepresentationSubsystem は、Instanced Static Mesh コンポーネントとスポーンされたアクターをプールすることで、各エンティティに 1 つずつアクターを用意することなく、数千のエンティティを描画します。

EMassRepresentationType用途
HighResSpawnedActor最も近いエンティティ用の完全なアクター
LowResSpawnedActor中距離用の軽量なアクター
SkinnedMeshInstanceスキニングされたメッシュのインスタンス
StaticMeshInstance群衆の大部分に使われる ISM
None描画されません

EMassLOD::Type は High、Medium、Low、Off、Max で、TEnumAsByte<EMassLOD::Type> として格納されます。可視性は EMassVisibility(CanBeSeen、CulledByFrustum、CulledByDistance、Max)です。UMassMovableVisualizationTrait と UMassStationaryVisualizationTrait は、メッシュ、アクタークラス、LOD 距離を設定します。

クエリがアクセスするもののスレッドセーフ性は、Mass/ExternalSubsystemTraits.h のトレイトで宣言します。TMassExternalSubsystemTraits のデフォルトは GameThreadOnly = true、TMassSharedFragmentTraits のデフォルトは GameThreadOnly = false です。TMassFragmentTraits にはスレッド関連のフラグはなく、AuthorAcceptsItsNotTriviallyCopyable = false のみです(Mass/ExternalSubsystemTraits.h:57-66)。デフォルトが適切でない場合にのみ特殊化してください。

template<>
struct TMassExternalSubsystemTraits<UMyMassSubsystem> final
{
    enum
    {
        GameThreadOnly = false,
        ThreadSafeWrite = false
    };
};

群衆、ナビゲーション、ビヘイビア

UMassCrowdSubsystem(MassCrowd、5.8 では Experimental)は、ZoneGraph のレーン占有状況、密度、待機スロットを追跡します。エンティティは FMassCrowdTag と FMassCrowdLaneTrackingFragment を持ち、UMassCrowdMemberTrait がこれらを設定します。このサブシステムは TMassExternalSubsystemTraits<UMassCrowdSubsystem> を GameThreadOnly = false で特殊化しているため、並列プロセッサは AddSubsystemRequirement を使ってこれを宣言できます。

移動ターゲットは FMassMoveTargetFragment(MassNavigation モジュールの MassNavigationFragments.h)に格納されます。ZoneGraph 自体と Smart Objects については、ue-ai-navigation で説明しています。

エンティティのビヘイビアは State Tree 上で実行されます。UMassStateTreeTrait はアセットをアタッチし、UMassStateTreeProcessor は UMassSignalProcessorBase を継承して、シグナルが送られたエンティティのみをティックします。また、FMassStateTreeSharedFragment は const の共有フラグメントです。UMassSignalSubsystem::SignalEntity(FName SignalName, FMassEntityHandle Entity) でエンティティにシグナルを送信します。シグナル名が先です。タスクと評価器の作成については、ue-state-trees で説明しています。

非推奨 — 使用しないでください

使用しない5.8 で使用ソース
ObservedType = T::StaticStruct();ObservedTypes.Add(T::StaticStruct());MassObserverProcessor.h の UE_DEPRECATED(5.8)
Operation = EMassObservedOperation::AddElement;ObservedOperations = EMassObservedOperationFlags::Add;MassObserverProcessor.h の UE_DEPRECATED(5.7)
GetObservedTypeChecked()GetSingleObservedTypeChecked()MassObserverProcessor.h の UE_DEPRECATED(5.8)
virtual void ConfigureQueries()ConfigureQueries(const TSharedRef<FMassEntityManager>&)MassProcessor.h の UE_DEPRECATED(5.6)
virtual void Initialize(UObject&)InitializeInternal(UObject&, const TSharedRef<FMassEntityManager>&)MassProcessor.h の UE_DEPRECATED(5.6)
ForEachEntityChunk(EntityManager, Context, Fn)ForEachEntityChunk(Context, Fn)MassEntityQuery.h の UE_DEPRECATED(5.6)
ParallelForEachEntityChunk(EntityManager, …), EParallelForModeParallelForEachEntityChunk(Context, Fn, EParallelExecutionFlags)MassEntityQuery.h の UE_DEPRECATED(5.6)
FMassEntityQuery Query{ FMyFragment::StaticStruct() };FMassEntityQuery Query{*this}; + ConfigureQueries で要件を指定MassEntityQuery.h の UE_DEPRECATED(5.6)
Defer().AddTag_RuntimeCheck<T>() およびその他の *_RuntimeCheck 呼び出しDefer().AddTag<T>()、AddFragment<T>()、RemoveFragment<T>()、RemoveTag<T>()、SwapTags<A,B>()MassCommandBuffer.h の UE_DEPRECATED(5.8)
FMassCommandAddTagsInternal、FMassCommandAddFragmentsInternalFMassCommandAddElements<T...>MassCommands.h の UE_DEPRECATED(5.8)
FMassCommandRemoveTagsInternal、FMassCommandRemoveFragmentsInternalFMassCommandRemoveElements<T...>MassCommands.h の UE_DEPRECATED(5.8)
#include "MassExternalSubsystemTraits.h"、"MassEntityElementTypes.h"、"MassEntityFragments.h"、"MassEntityHandle.h"Mass/ExternalSubsystemTraits.h、Mass/EntityElementTypes.h、Mass/EntityFragments.h、Mass/EntityHandle.h各 shim の UE_DEPRECATED_HEADER(5.8)
FMassEntityManager::Initialize()FMassEntityManager_InitParams_Concurrent を指定した Initialize(FMassEntityManagerStorageInitParams)MassEntityManager.h の UE_DEPRECATED(5.8)
UMassRepresentationSubsystem::GetOrSpawnActorFromTemplateGetOrRequestSpawnActorFromTemplateMassRepresentationSubsystem.h:246 の UE_DEPRECATED(5.8)
FMassLookAtTargetTagFMassLookAtTargetFragmentMassLookAtFragments.h の UE_DEPRECATED(5.6)

よくある間違い

Massをプラグインとして扱う: MassEntity と MassCore は .uplugin のないランタイムモジュールです。Build.cs に "MassEntity" を追加すれば十分です。.uproject で有効化する必要があるのは、MassGameplay、MassAI、MassCrowd だけです。

FTransformFragment のヘッダーを間違える: FTransformFragment は Mass/EntityFragments.h(モジュール MassCore)にあります。MassCommonFragments.h はこれをインクルードしているだけです。MassCommon と MassEntity は MassCore をパブリック依存関係として列挙しているため推移的に解決されますが、それに頼るのではなく、Mass/EntityFragments.h をインクルードし、MassCore を明示的に列挙してください。

イテレーション中に構造を変更する: ForEachEntityChunk 内で EntityManager.AddFragmentToEntity(...) を呼び出すと、エンティティが移動し、処理中のすべてのビューが無効になります。Context.Defer().AddFragment<T>(Entity) を使用してください。

コンストラクターで要件を宣言する: クエリは ConfigureQueries の実行直前にエンティティマネージャーにバインドされるため、コンストラクター内で AddRequirement を呼び出すと、「Modifying requirements before initialization」チェックに抵触します。要件は ConfigureQueries 内でのみ宣言してください。

デフォルト構築したクエリの登録忘れ: FMassEntityQuery MyQuery; は、RegisterQuery(MyQuery) が実行されるまで何も一致させません。コンストラクターの初期化リストで MyQuery(*this) を指定することを推奨します。

FMassEntityView をキャッシュする: ビューは移動するアーキタイプメモリを参照します。使用するたびにハンドルからビューを作り直してください。

存在確認に IsSet() を使う: 破棄されたエンティティへのハンドルでも、IsSet()/IsValid() は true を返します。EntityManager.IsEntityValid(Handle) を確認してください。

アクセス指定の不一致: ReadOnly を要求した後に GetMutableFragmentView<T>() を呼び出すとアサートが発生します。ビューを、宣言した EMassFragmentAccess に合わせてください。

AddElement だけを監視する: フラグメントがすでに存在する状態で作成されたエンティティは、AddElement ではなく CreateEntity を発生させます。EMassObservedOperationFlags::Add を使用してください。

並列プロセッサーから UObject にアクセスする: bRequiresGameThreadExecution = true を設定するか、AddSubsystemRequirement でサブシステムを宣言し、実情を示す TMassExternalSubsystemTraits の特殊化を指定してください。

関連スキル

  • ue-state-trees — State Tree アセット、タスク、評価器、Mass State Tree スキーマ
  • ue-ai-navigation — ZoneGraph、Smart Objects、NavMesh、知覚
  • ue-async-threading — タスクグラフ、ParallelFor、スレッドセーフの基礎
  • ue-actor-component-architecture — UMassAgentComponent の反対側にあるアクターとコンポーネント
  • ue-cpp-foundations — USTRUCT/UCLASS のリフレクション、サブシステム、モジュール構成
  • ue-animation-system — アクターまたはスキン付きインスタンスに昇格したエンティティのアニメーション
  • ue-procedural-generation — Mass のスポーンに利用する PCG と ISM の作成
  • ue-mover — Mover プラグイン:移動モード、レイヤー化された移動、ロールバックネットワーキング

コメント