rdInst Tutorial 11.7 – Mass Entity Movement


The Movement system in the rdECS routines uses a MovementClass which gets called for each Entity. All processing is done in the “move” function – it passes in data such as the delta time and entity grid reference for you to use.

Note: These classes are C++ only, there is no Blueprint support.

There are a number of Base Classes (all derived from a single Base Movement Class) for handling the various types of movement, hordes, swarms, traffic systems, pedestrians, wildlife etc.

You can find these in /rdInst/Source/rdInst/Public/rdEntityMovement.h and /rdInst/Source/rdInst/Private/rdBaseActor/rdEntityMovement.cpp.

You can use these classes directly, or you can sub-class any one and extend it from there.


The Base class looks like this:

// EntityMovement BaseClass
UCLASS()
class UrdEntityMovementBase : public UObject {
	GENERATED_BODY()
public:
	virtual bool hasMovement() { return false; }

	// Our main move routine
	virtual void move(FrdMovementGridArray& grid,FrdMovementInstanceGridItem& item,TArray<TTuple<int32,FTransform>>& moveArray,TArray<FrdUpdateCustomData>& customDataArray,TArray<TTuple<FrdMovementInstanceGridItem*,FTransform>>& moveGridArray,const FVector& closestPlayerLoc,const float closestPlayerDist,float deltaSeconds,FVector& predictedMovement) {}

	// returns the rdNavCell under the 2D position
	FORCEINLINE rdNavCell* getCell(const FVector2f& v) { return &((*navMap)[ FMath::Max(FMath::Min((int32)((v.Y-navOrigin.Y)/navCellSize),(navSizeY-1)),0)*navSizeX  +  FMath::Max(FMath::Min((int32)((v.X-navOrigin.X)/navCellSize),(navSizeX-1)),0) ]); }

 	TArray<rdNavCell>* navMap=nullptr; // pointer to our global NavMap (in rdInstBaseActor)

	FVector2f navOrigin;	// Information about the NavMap
	int32 navSizeX=0;
	int32 navSizeY=0;
	float navCellSize=100.0f;
	float lastMove=0.0f;

	rdNavCell* currCell=nullptr;	// Current cell we're under
	rdNavCell* nextCell1=nullptr;	// These 3 cells are surrounding current cell in the direction we're moving
	rdNavCell* nextCell2=nullptr;
	rdNavCell* nextCell3=nullptr;

	int32 cellUpdateCountdown=0; // Used for spreading out the reading and testing of NavCells between frames

	TArray<float> playerChaseWeights; // This is passed in from rdInstBaseActor - each player has a function returning it's weight - useful for following specific players

	UrdEntityDescriptionDataAsset* descriptor=nullptr; // The DataAsset this entity is created from
};

The routine to focus on is the “move” function:

move(	FrdMovementGridArray& grid,			// This Entities MovementGridArray - the parent container for the item 
		FrdMovementInstanceGridItem& item,	// This Entities MovementInstanceGridItem - the data pertaining to this Entity instance
		TArray>& moveArray,					// Reference to the systems ISM move queue (movement gets pushed onto this to be processed later)
		TArray& customDataArray,			// Reference to the systems ISM CustomData queue (custom data changes gets pushed onto this to be processed later)
		TArray>& moveGridArray,				// Reference to the systems Grid Movement queue (when an Entity moves into another Spatial Grid)
		const FVector& closestPlayerLoc,	// Location of the closest player (weighted)
		const float closestPlayerDist,		// Distance of the closest player (weighted)
		float deltaSeconds,					// Amount of time since the last call to this entities move
		FVector& predictedMovement			// reference to a Location we set the next frames predicted velocity
	 )

This gives us all the information needed for each type of movement.

The FrdMovementInstanceGridItem struct has all the information needed to maintain the entity:

USTRUCT(BlueprintType)
struct RDINST_PLUGIN_API FrdMovementInstanceGridItem {
	GENERATED_BODY()
public:
	FrdMovementInstanceGridItem() : hasProxy(false),keepAsProxy(false),forImpact(false),removed(false) {}
	FrdMovementInstanceGridItem(const FrdMovementInstanceGridItem& o) : index(o.index),velocity(o.velocity),transform(o.transform),lastRenderedLoc(o.lastRenderedLoc),lastCell(o.lastCell),speedVariation(o.speedVariation),randAnimSets(o.randAnimSets),hasProxy(o.hasProxy),keepAsProxy(o.keepAsProxy),forImpact(o.forImpact),removed(o.removed),currentAnim(o.currentAnim),animStartTime(o.animStartTime),totalLifeTime(o.totalLifeTime),entityProxy(o.entityProxy) {}
	FrdMovementInstanceGridItem(const FTransform& t,const FrdMovementInstanceGridItem& o) : index(o.index),velocity(o.velocity),transform(t),lastRenderedLoc(o.lastRenderedLoc),lastCell(o.lastCell),speedVariation(o.speedVariation),randAnimSets(o.randAnimSets),hasProxy(o.hasProxy),keepAsProxy(o.keepAsProxy),currentAnim(o.currentAnim),animStartTime(o.animStartTime),totalLifeTime(o.totalLifeTime),entityProxy(o.entityProxy)  {}
	FrdMovementInstanceGridItem(const FTransform& t,int32 i,const FVector& vel,rdNavCell* lc) : index(i),velocity(vel),transform(t),lastCell(lc) {}
	FrdMovementInstanceGridItem(const FTransform& t,int32 i,const FVector& vel,rdNavCell* lc,float sv,TArray<uint8>& rs) : index(i),velocity(vel),transform(t),lastCell(lc),speedVariation(sv),randAnimSets(rs) {}

	FORCEINLINE bool operator ==(const FrdMovementInstanceGridItem& o) const { return index==o.index; }

	UPROPERTY(EditAnywhere,Category=rdMovementInstanceGridItem)
	int32 index=-1;

	UPROPERTY(EditAnywhere,Category=rdGridArray)
	FVector velocity=FVector(0,0,0);

	UPROPERTY(EditDefaultsOnly,Category=rdMovementInstanceGridItem)
	FTransform transform;

	UPROPERTY(EditDefaultsOnly,Category=rdMovementInstanceGridItem)
	FVector lastRenderedLoc=FVector(WORLD_MAX);

	rdNavCell*  lastCell=nullptr;
	float		speedVariation=0.0f;
	TArray<uint8> randAnimSets;
	uint8		hasProxy:1;
	uint8		keepAsProxy:1;
	uint8		forImpact:1;
	uint8		removed:1;
	uint8		currentAnim=0;
	float		animStartTime=0.0f;
	float		totalLifeTime=0.0f;
	TWeakObjectPtr<AActor>  entityProxy=nullptr;
};

The other useful function there is the rdNavCell* getCell(Vector2D) function – this finds the NavCell under the location.

NavCells are 64bit structs that contain information such as the z height of the landscape at that point, whether anything is occupying the cell already – the direction of movement of any entities occupying the cell.

They also have a “blocked” flag when the UE NavMesh shows an object is blocking the world. In these blocked cells, there are flow hints for each side of the cell to instruct moving entities which way to go when they hit the obstruction. There are also flags for determining if the blocked cell can be climbed or fallen from.