Processing of Crowds is City Sample project

Published on

The structure analysis of Crowds in City Sample project. Before studying the principes below, be sure you understand the Mass Framework concept.

Platforma

Sample Video

Crowd Simulation sample form CitySample project

Crowds in CitySample project are MetaHuman based characters. They are managed through assembled individual parts into a body. This allows to achieve a high variability in the number of individually looking characters when using a limited number of assets.

Crowds in CitySample project are controlled through the MassFramework, especially through ZoneGraph navigation in its MassAI plugin.

Crowd Assets

CitySample project comes with assets at Crowds folder that contain following:

  • Character folder with:
    • Female and Male folder. Each is with:
      • 6 heads with differ faces and then Static Mesh for head (and mustache for male) in 4 LODs. Faces are for various nationalities.
      • 3 Body types based on weight: Normal, Over, Under. Each contain Rig and Skeletal meshes for various outfit parts.
    • Accessories folder with Static Meshes for human accessories, namely:
      • 3 briefcases
      • 3 purses
      • 1 Smartphone
      • 1 Coffee cap
      • 2 backpacks in various variants
    • Shared folder with shared Rig, Materials and other data and settings
    • Anims folder with animations
    • AnimSets folder with Data Asset files for animating Accessories from Accessories folder
  • VAT folder with:
    • 164 Static Meshes with baked animation based on animation sequence (see data folder):
      • 9 Hair meshes in LOD 5
      • Woman-based meshes:
        • 18 head faces
        • 1 full body (1 Color Nat combined) for the worst LOD
        • 1 nrw + 1 ovw + 1 unw body (hands)
        • 27 top parts
        • 15 bottom parts
        • 9 pairs of shoes
      • Man-based meshes:
        • 18 head faces
        • 1 full body (1 Color Nat combined) for the worst LOD
        • 1nrw + 1 ovw + 1 unw body (hands)
        • 36 top parts
        • 18 bottom parts
        • 6 pairs of shoes
    • 155 Data Asssets files (AnimToTexture type) for animated static mashes

      The purpose of each such file is to link Skeletal Mesh with Static Mesh, Animation Sequence file and other related data, see more at AnimToTexture plugin.

      CitySample uses Static Meshes with Bone animations. See example of one of the file (DA_f_tal_nrw_scoopneck_croppedJacket) below:

      Crowd Character

      Notice, that:

      • Precision of Eight Bits, Bone mode and texture files (Bone Position Texture, Bone Rotation Texture, Bone Weight Texture) of all 155 Data Asset files refer to the same texture files located at Crowd/Vat.
      • Empty Anim Sequences on the screenshot above are correct in this phase and applay for all the 155 Data Asset configuration files.

    Assets list

    There's an asset list that refer to the all the assets mentioned above - it's named CrowdCharacterDataAsset DataAsset file with location at Crowd/Character/Shared/Data. This file is then used as a list of available assets we can work for indicidual characters - specifically for building them.

    Crowd Character Definition
    Click at the image to open it in full size

    Notice, that:

    • The DataAsset file contain list of all assets binded to certain skeleton type (A/B + Weight)
    • There's displayed only assets for Skeleton A of Normal Weight. Skeleton B and other Weights have the same options.
    • The DataAsset file contain shared assets for LODs, differention, binding etc.
  • Blueprints folder with:
    • BP_Crowd_Character Blueprint class used for Crowd Characters. The class has a reference to Assets List based on which its visual is created.
    • BPI_CrowdCharacter Blueprint Interface
    • VFXWarpState Enumeration file with 4 defined states

Two Types of Humans

CitySample project works with 2 techniques ("types") of humans that are switched each other based on the distance (LODs) from the player's camera. As both types share metadata (visual, transform and so on) they keep the same look that only differ in details while switching between them.

  • Crowd Characters

    CitySample project uses Characters for the nearest humans to the player camera. These characters have an extra details as well as functionality, such as holding an extra accessory, see below:

    Crowd Character

    Character Class Inheritance

    • ACharacter
      • ACitySampleCharacter
        • ACitySampleCrowdCharacter
          • BP_CrowdCharacter

    Characters use Skeletal Meshes and as for the animations, they use Animation Blueprint Animation Mode and rely on ABP_Crowd_C file located at Character/Anims.

  • Crowd Members

    Crowd members are represented with static meshes with baked bone animations. There's no actor representation of each in the world, instead, they are instanced, displayed through the Mass Subsystem.

Building characters

In the CitySample, there are not created characters that would be just placed into the scene, instead, every character is dynamically assembled from crowd parts in the application start time.

Character's visual definition - parts the character is being assembled from:

Character's visual (and individual parts) choosed from the Assets List (UCrowdCharacterDataAsset class) is holded in FCrowdCharacterDefinition struct that contain other substructs, see below:

USTRUCT(BlueprintType)
struct CITYSAMPLE_API FCrowdCharacterDefinition
{
	GENERATED_BODY()

	FCrowdCharacterDefinition()
	{
		HairDefinitions.SetNum(static_cast<uint8>(ECrowdHairSlots::MAX));
	}

	const FCrowdHairDefinition& GetHairDefinitionForSlot(const ECrowdHairSlots HairSlot) const
	{
		uint8 SlotIdx = static_cast<uint8>(HairSlot);

		// Should always succeed as the array is sized to the enum
		check(HairDefinitions.IsValidIndex(SlotIdx));

		return HairDefinitions[SlotIdx];
	}

	// Meshes
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	FCrowd<mark>BodyDefinition BodyDefinition;</mark>

	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	TSoftObjectPtr<mark>&lt;USkeletalMesh&gt; Head;</mark>
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	TSoftObjectPtr&lt;UAnimToTextureDataAsset&gt; HeadData;

	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	FCrowd<mark>OutfitDefinition OutfitDefinition;</mark>

	// Fixed size array where the index in the array corresponds to a value in ECrowdHairSlots
	// i.e. HairDefinitions[0] is the hair definition for ECrowdHairSlots::Hair and so on
	UPROPERTY(EditAnywhere, BlueprintReadOnly, EditFixedSize)
	TArray<mark>&lt;FCrowdHairDefinition&gt; HairDefinitions;</mark>

	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	FCrowd<mark>AccessoryDefinition AccessoryDefinition;</mark>

	// Material & Textures

	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	FCrowd<mark>HairColorDefinition HairColorDefinition;</mark>

	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	FCrowd<mark>OutfitMaterialDefinition OutfitMaterialDefinition;</mark>

	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	FCrowd<mark>SkinTextureDefinition SkinTextureDefinition;</mark>

	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	FCrowd<mark>SkinTextureModifierDefinition SkinTextureModifierDefinition;</mark>

	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	float <mark>ScaleFactor = 1.0f;</mark>

	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	uint8 <mark>PatternColorIndex = 0;</mark>

	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	uint8 <mark>PatternOptionIndex = 0;</mark>

	// Optionnally override the min. ray tracing LOD set on the skeleton mesh. Default: -1, use the skeleton mesh value
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	int32 <mark>RayTracingMinLOD = -1;</mark>

	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	TSoftObjectPtr<mark>&lt;UDataAsset&gt; LocomotionAnimSet;</mark>

	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	TSoftObjectPtr<mark>&lt;UMassCrowdContextualAnimationDataAsset&gt; ContextualAnimDataAsset;</mark>

	TArray&lt;FSoftObjectPath&gt; GetSoftPathsToLoad() const;
};

Character Meshes

USTRUCT(BlueprintType)
struct CITYSAMPLE_API FCrowd<mark>BodyDefinition</mark>
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	TSoftObjectPtr&lt;USkeletalMesh&gt; Base;

	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	TSoftObjectPtr&lt;UAnimToTextureDataAsset&gt; BodyData;

	//UPROPERTY(EditAnywhere, BlueprintReadOnly)
		//TSoftObjectPtr&lt;USkeletalMesh&gt; Head;
};

Character Materials & Textures

USTRUCT(BlueprintType)
struct CITYSAMPLE_API FCrowd<mark>HairColorDefinition</mark>
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	float HairMelanin = 0.161905f;
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	float HairRedness = 0.25f;

	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	float HairRoughness = 0.37f;
};

The FCrowdCharacterDefinition structure is created for each crowd member (including character) and defines its visual. See the process of filling the Struct with data in Building Characters section.

Filing FCrowdCharacterDefinition with data from UCrowdCharacterDataAsset

To summarize the important, we have 2 following structs that we use for characters creation:

  • UCrowdCharacterDataAsset that keeps a list of all available assets we can use for the crowds
  • FCrowdCharacterDefinition struct that is attached to each member and contain used parts (and other data) for the member from UCrowdCharacterDataAsset It's used also when shitching between Crowd member (Bone Animated Static Mesh) and Crowd Character member (detailed skeleton with animations) to keep the same look.

Both UCrowdCharacterDataAsset and FCrowdCharacterDefinition are attached to ACitySampleCrowdCharacter class that represents our BP_CrowdCharacter located at Crowd/Blueprints and representing Crowd Character Members (nearest members to player's camera). See the code and image below:

Crowd Character
Click at the image to see it in full size

Then, we have 2 Custom MassProcessors in the project:

  • UCitySampleCrowdVisualizationFragmentInitializer - takes care about filling FCrowdCharacterDefinition with data from UCrowdCharacterDataAsset and displaying nearest Characters
  • UMassProcessor_CrowdVisualizationCustomData - takes care about MassEntities (Instanced Static Meshes) visualization (Crowd in larger distance)

UCitySampleCrowdVisualizationFragmentInitializer Mass Observer Processor

As Crowd members (Mass Entities) and Mass Crowd Characters (Mass Driven Actors) both are controlled and processed through Mass Framework, FCrowdCharacterDefinition is defined and filled with data for both Crowd types right in this processor, using following logic of filling the Struct with data from Data Asset:

  1. Access ACitySampleCrowdCharacter class and get its referred CrowdCharacterDataAsset (Assets list)
  2. Randomize the CrowdCharacterDataAsset - doing that the characters will get random combination of assets
  3. Use randomized CrowdCharacterDataAsset to build teh character, Fill selected parts to FCrowdCharacterDefinition
void UCitySampleCrowdVisualizationFragmentInitializer::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
	...
	
	EntityQuery.ForEachEntityChunk(EntityManager, Context, [&, this, RepresentationSubsystem, CachedCrowdScalabilityValues, RandomStream](FMassExecutionContext& Context)
	{
		const TArrayView&lt;FCitySampleCrowdVisualizationFragment&gt; VisualizationList = Context.GetMutableFragmentView&lt;FCitySampleCrowdVisualizationFragment&gt;();
		const TArrayView&lt;FMassRepresentationFragment&gt; RepresentationList = Context.GetMutableFragmentView&lt;FMassRepresentationFragment&gt;();
		const TArrayView&lt;FCrowdAnimationFragment&gt; AnimationDataList = Context.GetMutableFragmentView&lt;FCrowdAnimationFragment&gt;();
		
		const int32 NumEntities = Context.GetNumEntities();
		for (int32 i = 0; i < NumEntities; ++i)
		{
			// <mark>1 - Get the data asset file with list of assets from the crowd character</mark>
			ACitySampleCrowdCharacter* CitySampleCrowdCharacterCDO = nullptr;
			<mark>UCrowdCharacterDataAsset* CrowdCharacterDataAsset</mark> = nullptr;
			TSubclassOf&lt;AActor&gt; TemplateActorClass = RepresentationSubsystem->GetTemplateActorClass(RepresentationList[i].HighResTemplateActorIndex);
			if (TemplateActorClass)
			{
				CitySampleCrowdCharacterCDO = Cast&lt;ACitySampleCrowdCharacter&gt;(TemplateActorClass->GetDefaultObject());
				if (CitySampleCrowdCharacterCDO)
				{
					<mark>CrowdCharacterDataAsset = CitySampleCrowdCharacterCDO->CrowdCharacterData.Get();</mark>
				}
			}
			...
			if (CrowdCharacterDataAsset) // Based on CrowdCharacterDataAsset
			{
				// <mark><strong>2 - Randomize Data of CrowdCharacterDataAsset</strong></mark>
				<mark>CharacterOptions.Randomize(*CrowdCharacterDataAsset, RandomStream);</mark>
				ClampCharacterOptions(CharacterOptions, CachedCrowdScalabilityValues);
				VisualizationList[i].VisualizationID = FCrowdVisualizationID(CharacterOptions);
			}
			...
			if (CVarUseMetahumanISMParts.GetValueOnGameThread() == true && RepresentationSubsystem)
			{
				if (CrowdCharacterDataAsset)
				{
					// <mark>3 - GenerateCharacterDefinition (CharacterDefinition) based on FCrowdCharacterDefinition</mark>
					<a href="#FCrowdCharacterDefinition">FCrowdCharacterDefinition</a> CharacterDefinition;
					CharacterOptions.<a href="#GenerateCharacterDefinition">GenerateCharacterDefinition(<strong>CrowdCharacterDataAsset</strong>, CharacterDefinition);</a>

					...

					<mark>4 - Attach additional</mark>
					// Order of the static meshes is important here
					// UMassProcessor_CrowdVisualizationCustomData::UpdateCrowdCustomData assumes
					
					<mark>// 0 - Head</mark>
					if (UAnimToTextureDataAsset* ATTDA = <strong>GetAnimToTextureDataAsset(CharacterDefinition.HeadData)</strong>)
					{
						ensureMsgf(ATTDA->GetStaticMesh(), TEXT("%s is missing static mesh %s"), *ATTDA->GetName(), *ATTDA->StaticMesh.ToString());

						StaticMeshDesc.Mesh = ATTDA->GetStaticMesh();
						StaticMeshInstanceDesc.Meshes.Add(StaticMeshDesc);
					}

					<mark>// 1 - Body</mark>
					if (UAnimToTextureDataAsset* ATTDA = <strong>GetAnimToTextureDataAsset(CharacterDefinition.BodyDefinition.BodyData)</strong>)
					{
						ensureMsgf(ATTDA->GetStaticMesh(), TEXT("%s is missing static mesh %s"), *ATTDA->GetName(), *ATTDA->StaticMesh.ToString());

						StaticMeshDesc.Mesh = ATTDA->GetStaticMesh();
						StaticMeshInstanceDesc.Meshes.Add(StaticMeshDesc);
						AnimationDataList[i].AnimToTextureData = ATTDA;
					}

					<mark>// 2 - Top</mark>
					if (UAnimToTextureDataAsset* ATTDA = <strong>GetAnimToTextureDataAsset(CharacterDefinition.OutfitDefinition.TopData)</strong>)
					{
						ensureMsgf(ATTDA->GetStaticMesh(), TEXT("%s is missing static mesh %s"), *ATTDA->GetName(), *ATTDA->StaticMesh.ToString());
						ensureMsgf(ATTDA->GetSkeletalMesh(), TEXT("%s is missing skeletal mesh %s"), *ATTDA->GetName(), *ATTDA->SkeletalMesh.ToString());

						StaticMeshDesc.Mesh = ATTDA->GetStaticMesh();
						StaticMeshInstanceDesc.Meshes.Add(StaticMeshDesc);

						VisualizationList[i].TopColor = FindColorOverride(CharacterDefinition, ATTDA->GetSkeletalMesh());
					}

					<mark>// 3 - Bottom</mark>
					if (UAnimToTextureDataAsset* ATTDA = <strong>GetAnimToTextureDataAsset(CharacterDefinition.OutfitDefinition.BottomData)</strong>)
					{
						ensureMsgf(ATTDA->GetStaticMesh(), TEXT("%s is missing static mesh %s"), *ATTDA->GetName(), *ATTDA->StaticMesh.ToString());
						ensureMsgf(ATTDA->GetSkeletalMesh(), TEXT("%s is missing skeletal mesh %s"), *ATTDA->GetName(), *ATTDA->SkeletalMesh.ToString());

						StaticMeshDesc.Mesh = ATTDA->GetStaticMesh();
						StaticMeshInstanceDesc.Meshes.Add(StaticMeshDesc);

						VisualizationList[i].BottomColor = FindColorOverride(CharacterDefinition, ATTDA->GetSkeletalMesh());
					}

					<mark>// 4 - Shoes</mark>
					if (UAnimToTextureDataAsset* ATTDA = <strong>GetAnimToTextureDataAsset(CharacterDefinition.OutfitDefinition.ShoesData)</strong>)
					{
						ensureMsgf(ATTDA->GetStaticMesh(), TEXT("%s is missing static mesh %s"), *ATTDA->GetName(), *ATTDA->StaticMesh.ToString());
						ensureMsgf(ATTDA->GetSkeletalMesh(), TEXT("%s is missing skeletal mesh %s"), *ATTDA->GetName(), *ATTDA->SkeletalMesh.ToString());

						StaticMeshDesc.Mesh = ATTDA->GetStaticMesh();
						StaticMeshInstanceDesc.Meshes.Add(StaticMeshDesc);

						VisualizationList[i].ShoesColor = FindColorOverride(CharacterDefinition, ATTDA->GetSkeletalMesh());
					}

					<mark>// 5 - Hair</mark>
					const FCrowdHairDefinition& HairDefinition = <strong>CharacterDefinition.GetHairDefinitionForSlot(ECrowdHairSlots::Hair)</strong>;
					if (UStaticMesh* HairStaticMesh = HairDefinition.GetGroomStaticMesh())
					{
						StaticMeshDesc.Mesh = HairStaticMesh;
						StaticMeshInstanceDesc.Meshes.Add(StaticMeshDesc);
					}

					...

					RepresentationList[i].StaticMeshDescIndex = RepresentationSubsystem->FindOrAddStaticMeshDesc(StaticMeshInstanceDesc);
				}
			}

			...
		}
	});
};

Building Character Members on Spawn based on FCrowdCharacterDefinition

Character members are built by CrowdCharacterActor.cpp file (ACitySampleCrowdCharacter class) based on FCrowdCharacterDefinition, usually on spawning to the world, see:

void ACitySampleCrowdCharacter::OnGetOrSpawn(FMassEntityManager* EntitySubsystem, const FMassEntityHandle MassAgent)
{
	if (EntitySubsystem && EntitySubsystem->IsEntityActive(MassAgent))
	{
		...

		FCitySampleCrowdVisualizationFragment* CitySampleVisualizationFragment = EntitySubsystem->GetFragmentDataPtr&lt;<a href="#FCitySampleCrowdVisualizationFragment">FCitySampleCrowdVisualizationFragment</a>&gt;(MassAgent);
		if (CitySampleVisualizationFragment)
		{
			<mark>BuildCharacterFromID(CitySampleVisualizationFragment->VisualizationID);</mark>
		}
	}
}

The function above takes VisualizationID that represents visual characteristics (defined for Crowd member) and build the Crowd Character based on it.

LODs management

UMassProcessor_CrowdVisualizationCustomData Mass Processor

UMassProcessor_CrowdVisualizationCustomData takes care about visuals of Mass Entities (Lover LODs)

...
FMassInstancedStaticMeshInfo& ISMInfo = ISMInfos[Representation.StaticMeshDescIndex];

const float PrevLODSignificance = UE::CitySampleCrowd::bAllowKeepISMExtraFrameBetweenISM ? Representation.PrevLODSignificance : -1.0f;

// Update Transform
UMassUpdateISMProcessor::UpdateISMTransform(GetTypeHash(Context.GetEntity(EntityIdx)), ISMInfo, TransformFragment.GetTransform(), Representation.PrevTransform, RepresentationLOD.LODSignificance, PrevLODSignificance);

// Custom data layout is 0-4 are anim data, 5-7 are color variations, 5 is an atlas index on some meshes
// Need 3 floats of padding after anim data
const int32 CustomDataPaddingAmount = 3;

// Add Vertex animation custom floats
UMassCrowdUpdateISMVertexAnimationProcessor::UpdateISMVertexAnimation(ISMInfo, AnimationData, RepresentationLOD.LODSignificance, PrevLODSignificance, CustomDataPaddingAmount);

// Add color custom floats
R = (CitySampleCrowdVisualization.TopColor >> 24) / 255.0f;
G = ((CitySampleCrowdVisualization.TopColor >> 16) & 0xff) / 255.0f;
B = ((CitySampleCrowdVisualization.TopColor >> 8) & 0xff) / 255.0f;
ISMInfo.WriteCustomDataFloatsAtStartIndex(TopIdx, CustomFloats, RepresentationLOD.LODSignificance, NumCustomFloatsPerISM, ColorVariationIndex, PrevLODSignificance);
R = (CitySampleCrowdVisualization.BottomColor >> 24) / 255.0f;
G = ((CitySampleCrowdVisualization.BottomColor >> 16) & 0xff) / 255.0f;
B = ((CitySampleCrowdVisualization.BottomColor >> 8) & 0xff) / 255.0f;
ISMInfo.WriteCustomDataFloatsAtStartIndex(BottomIdx, CustomFloats, RepresentationLOD.LODSignificance, NumCustomFloatsPerISM, ColorVariationIndex, PrevLODSignificance);
R = (CitySampleCrowdVisualization.ShoesColor >> 24) / 255.0f;
G = ((CitySampleCrowdVisualization.ShoesColor >> 16) & 0xff) / 255.0f;
B = ((CitySampleCrowdVisualization.ShoesColor >> 8) & 0xff) / 255.0f;
ISMInfo.WriteCustomDataFloatsAtStartIndex(ShoesIdx, CustomFloats, RepresentationLOD.LODSignificance, NumCustomFloatsPerISM, ColorVariationIndex, PrevLODSignificance);

// Add skin atlas custom floats
TArray&lt;float, TInlineAllocator&lt;1&gt;&gt; SkinAtlasIndex({ float(CitySampleCrowdVisualization.SkinAtlasIndex) });
ISMInfo.WriteCustomDataFloatsAtStartIndex(HeadIdx, SkinAtlasIndex, RepresentationLOD.LODSignificance, NumCustomFloatsPerISM, AtlasVariationIndex, PrevLODSignificance);
ISMInfo.WriteCustomDataFloatsAtStartIndex(BodyIdx, SkinAtlasIndex, RepresentationLOD.LODSignificance, NumCustomFloatsPerISM, AtlasVariationIndex, PrevLODSignificance);
			
...

Animating Crowd Humans

  • Animating Human Actors

    Characters animations are of Use Animation Blueprint Animation Mode and rely on ABP_Crowd_C animation file located at Character/Anims.

Source Files structure - Files list and purpose

  • Project Files (Source/CitySample)

    • /Anim
      • CitySampleAnimInstance_Accessory
      • CitySampleAnimInstance_Crowd_Head
      • CitySampleAnimInstance_Crowd
      • CitySampleAnimNode_CopyPoseRotations
      • CitySampleAnimNode_CrowdSlopeWarping
      • CitySampleAnimNode_RequestInertialization
      • CitySampleAnimNotifyState_PlayMontageOnFace
      • CitySampleAnimSet_Accessory
      • CitySampleAnimSet_Locomotion
      • RootMotionModifier_CitySampleSimpleWarp - Custom version of the built-in Simple Warp with a few new options
    • /Character
      • CitySampleCharacter - Character class used for BP_CitySamplePlayerCharacter Inheritance
      • CitySampleCharacterMovementComponent
    • /Crowd
      • CitySampleCrowdSettings - settings file
      • CitySampleMassContextualAnimTask - Same as MassContextualAnimTask but it picks the animation to play from the character definition's ContextualAnimDataAsset.
      • CrowdBlueprintLibrary - Set of utilities for dealing with the Crowd Definition Objects.
      • CrowdCharacterActor - Character class used for BP_CrowdCharacter Inheritance
      • CrowdCharacterDataAsset - DataAsset of character visual
      • CrowdCharacterDefinition - character visual definition
      • CrowdCharacterEnums - character visual enum
      • CrowdCharacterLineupActor
      • CrowdPresetDataAsset
      • CrowdVisualizationCustomDataProcessor
      • CrowdVisualizationFragment
  • Plugins

Used Crowd character inheritance

  • ACharacter, ICitySampleTraversalInterface, ICitySampleUIComponentInterface, ICitySampleControlsOverlayInterface, ICitySampleInputInterface
    • ACitySampleCharacter (Source/CitySample/Character/CitySampleCharacter.h)
    • IMassCrowdActorInterface (Plugins/CitySampleMassCrowd/Source/CitySampleMassCrowd/Public/IMassCrowdActor.h)
    • IMassActorPoolableInterface (Massgameplay plugin)
      • ACitySampleCrowdCharacter (/Source/CitySample/Crowd/CrowdCharacterActor.h)
        • BP_CrowdCharacter (Content/Crowd/Blueprints/BP_CrowdCharacter)

Mass Traits

MassCrowdAgentConfig_Metahuman Data Asset file

Masscrowdagentconfigmetahuman data asset

MassCrowdAgentConfig Data Asset file

Masscrowdagentconfig data asset

UMassMovementTrait

A System taking care about Crowd movement.

  • UMassApplyMovementProcessor (MassGameplay/Movement)
    • UMassMovementTrait (MassGameplay/Movement)
      • FMassMovementParameters shared fragment (MassGameplay)

Fragments

Assorted fragments

  • FCitySampleCrowdVisualizationFragment

    Located at Source/CitySample/Crowd/CrowdVisualizationFragment.h

    FCitySampleCrowdVisualizationFragment keeps visualization characteristic of the entity (human)

    USTRUCT()
    struct CITYSAMPLE_API FCitySampleCrowdVisualizationFragment : public FMassFragment
    {
    	GENERATED_BODY()
    
    	UPROPERTY(EditAnywhere, Category = "")
    	FCrowdVisualizationID VisualizationID;
    
    	UPROPERTY(EditAnywhere, Category = "")
    	uint32 TopColor = 0;
    
    	UPROPERTY(EditAnywhere, Category = "")
    	uint32 BottomColor = 0;
    
    	UPROPERTY(EditAnywhere, Category = "")
    	uint32 ShoesColor = 0;
    
    	UPROPERTY(EditAnywhere, Category = "")
    	uint8 SkinAtlasIndex = 0;
    };
  • FCrowdAnimationFragment

    Located at Plugins/CitySampleMassCrowd/Source/CitySampleMassCrowd/Public/MassAnimationTypes.h

    USTRUCT()
    struct CITYSAMPLEMASSCROWD_API FCrowdAnimationFragment : public FMassFragment
    {
    	GENERATED_BODY()
    
    	TWeakObjectPtr&lt;UAnimToTextureDataAsset&gt; AnimToTextureData;
    	float GlobalStartTime = 0.0f; // filled by <a href="#UMassFragmentInitializer_Animation">UMassFragmentInitializer_Animation</a>
    	float PlayRate = 1.0f;
    	int32 AnimationStateIndex = 0;
    	bool bSwappedThisFrame = false;
    };

Other fragments

FMassMontageFragment

Located at Plugins/CitySampleMassCrowd/Source/CitySampleMassCrowd/Public/MassAnimationTypes.h

USTRUCT()
struct CITYSAMPLEMASSCROWD_API FMassMontageFragment : public FMassFragment
{
	GENERATED_BODY()

	UE::VertexAnimation::FLightweightMontageInstance MontageInstance = UE::VertexAnimation::FLightweightMontageInstance();
	UE::CrowdInteractionAnim::FRequest InteractionRequest = UE::CrowdInteractionAnim::FRequest();
	UE::CrowdInteractionAnim::FMotionWarpingScratch MotionWarpingScratch = UE::CrowdInteractionAnim::FMotionWarpingScratch();
	FRootMotionMovementParams RootMotionParams = FRootMotionMovementParams();
	float SkippedTime = 0.0f;
	
	void Request(const UE::CrowdInteractionAnim::FRequest& InRequest);
	void Clear();
};

UCitySampleCrowdVisualizationFragmentInitializer

Located at Source/CitySample/Crowd/CrowdVisualizationCustomDataProcessor.h

A UMassProcessor that ...

#pragma once

#include "MassTranslator.h"
#include "MassRepresentationTypes.h"
#include "MassLODSubsystem.h"
#include "CrowdVisualizationCustomDataProcessor.generated.h"

class UMassCrowdRepresentationSubsystem;

UCLASS()
class UMassProcessor_CrowdVisualizationCustomData : public UMassProcessor
{
	GENERATED_BODY()

public:
	UMassProcessor_CrowdVisualizationCustomData();

protected:
	virtual void ConfigureQueries(const TSharedRef&lt;FMassEntityManager&gt;& EntityManager) override;
	virtual void InitializeInternal(UObject& Owner, const TSharedRef&lt;FMassEntityManager&gt;& EntityManager) override;
	virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override;

	void UpdateCrowdCustomData(FMassExecutionContext& Context);

	FMassEntityQuery EntityQuery_Conditional;

	UPROPERTY(Transient)
	UMassLODSubsystem* LODSubsystem;

	UPROPERTY(Transient)
	UWorld* World = nullptr;
};

CrowdCharacterActor.cpp

UMassCrowdUpdateISMVertexAnimationProcessor

Located at Plugins/CitySampleMassCrowd/Source/CitySampleMassCrowd/Public/MassCrowdUpdateISMVertexAnimationProcessor.h

A UMassUpdateISMProcessor that ...

#include "MassUpdateISMProcessor.h"

#include "MassCrowdUpdateISMVertexAnimationProcessor.generated.h"

struct FMassInstancedStaticMeshInfo;
struct FCrowdAnimationFragment;

UCLASS()
class CITYSAMPLEMASSCROWD_API UMassCrowdUpdateISMVertexAnimationProcessor : public UMassUpdateISMProcessor
{
	GENERATED_BODY()
public:
	UMassCrowdUpdateISMVertexAnimationProcessor();

	static void UpdateISMVertexAnimation(FMassInstancedStaticMeshInfo& ISMInfo, FCrowdAnimationFragment& AnimationData, const float LODSignificance, const float PrevLODSignificance, const int32 NumFloatsToPad = 0);

protected:

	/** Configure the owned FMassEntityQuery instances to express processor's requirements */
	virtual void ConfigureQueries() override;

	/**
	 * Execution method for this processor
	 * @param EntitySubsystem is the system to execute the lambdas on each entity chunk
	 * @param Context is the execution context to be passed when executing the lambdas */
	virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override;
};

MassCrowdAnimationProcessor

Located at Plugins/CitySampleMassCrowd/Source/CitySampleMassCrowd/Public/MassCrowdAnimationProcessor.h

MassCrowdAnimationProcessor source code file contain 2 processors:

  • UMassFragmentInitializer_Animation (UMassObserverProcessor)

    It sets GlobalStartTime value at FCrowdAnimationFragment fragment.

    UCLASS()
    class CITYSAMPLEMASSCROWD_API UMassFragmentInitializer_Animation : public UMassObserverProcessor
    {
    	GENERATED_BODY()
    public:
    	UMassFragmentInitializer_Animation();
    protected:
    	virtual void ConfigureQueries() override;
    	virtual void Initialize(UObject& Owner) override;
    	virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override;
    
    	UPROPERTY()
    	UWorld* World = nullptr;
    
    	FMassEntityQuery EntityQuery;
    };
  • UMassProcessor_Animation (UMassProcessor)

    It updates animations for all entity types (entities as well as actors)

    #include "MassObserverProcessor.h"
    #include "MassRepresentationTypes.h"
    #include "MassCrowdAnimationProcessor.generated.h"
    
    class UAnimToTextureDataAsset;
    struct FMassActorFragment;
    
    // UMassFragmentInitializer_Animation .h source code
    
    UCLASS()
    class CITYSAMPLEMASSCROWD_API UMassProcessor_Animation : public UMassProcessor 
    {
    	GENERATED_BODY()
    
    public:
    	UMassProcessor_Animation();
    
    	UPROPERTY(EditAnywhere, Category="Animation", meta=(ClampMin=0.0, UIMin=0.0))
    	float MoveThresholdSq = 750.0f;
    
    private:
    	void UpdateAnimationFragmentData(FMassEntityManager& EntityManager, FMassExecutionContext& Context, float GlobalTime, TArray&lt;FMassEntityHandle, TInlineAllocator&lt;32&gt;&gt;& ActorEntities);
    	void UpdateVertexAnimationState(FMassEntityManager& EntityManager, FMassExecutionContext& Context, float GlobalTime);
    	void UpdateSkeletalAnimation(FMassEntityManager& EntityManager, float GlobalTime, TArrayView&lt;FMassEntityHandle&gt; ActorEntities);
    
    protected:
    
    	/** Configure the owned FMassEntityQuery instances to express processor's requirements */
    	virtual void ConfigureQueries() override;
    	virtual void Initialize(UObject& Owner) override;
    	virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override;
    
    	static class UAnimInstance* GetAnimInstanceFromActor(const class AActor* Actor);
    
    	UPROPERTY(Transient)
    	UWorld* World = nullptr;
    
    	FMassEntityQuery AnimationEntityQuery_Conditional;
    	FMassEntityQuery MontageEntityQuery;
    	FMassEntityQuery MontageEntityQuery_Conditional;
    };

Look at Animation

Animations are processed by MassCrowdAnimationProcessor located at Plugins/CitySampleMassCrowd/Source/CitySampleMassCrowd/Private/MassCrowdAnimationProcessor.cpp that contain variable AnimInstanceData.LookAtDirection.

By default, the Look at Direction is driven by the presence of LookAtFragment, see AnimInstanceData.LookAtDirection = LookAtFragment ? LookAtFragment->Direction : FVector::ForwardVector;. If such fragment is not available, occupants look in their forward direction.

FMassLookAtFragment is defined by MassAI.

Walking / Standing

MassCrowdAnimationProcessor also setting movement states through AnimationData.AnimationStateIndex parameter. 0 belongs to standing, 1 belongs to walking.

Vrealmatic consulting

Anything unclear?

Let us know!

Contact Us