rdInst Tutorial 11.7 – Mass Entity Movement
Last Updated: 25th April 2026
Tutorial created using rdInst version 1.53
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.