Skip to main content

Market night in Oakford

On market night the village square fills with players, far more than the actor pool should draw as real actors. So the Oakford map draws every other player as a row of a crowd. This page builds that, step by step. You switch the map, give the crowd a look, add a sitting pose, a wave and a cheer, promote the players standing close, dim and brighten a torch, and read the health of the raiders on the north road from the server.

Each step shows the values and the order. Draw a crowd with Mass and the pages linked from each step explain why. Every player is still a real actor on their own machine. A row is how another player's machine draws them.

What you start from​

The player is ALanternPlayer, a character class with a Torch point light and a ULanternPlayerExecutor. The executor sends FLanternPlayerState, which holds Location, Rotation and bTorchLit. Build it as Continuous State does, not as the Quickstart leaves it. The crowd reads Location and Rotation to place a row. See Make a character draw as a crowd row.

BP_LanternPlayer is your own Blueprint subclass of ALanternPlayer. It needs a skeletal mesh and the movement and input you already have, not a bare Character. Its Crowdy Entity Component is set to Dynamic, Player Derived and Local Client. You also need an idle, walk, run, sitting, fallen, wave and cheer clip for its skeleton.

Two parts need C++: the Build.cs lines, and the torch data under Brighten the torch, which is a fragment, a trait, a processor and a field registration. Every other step has a Blueprint half and a C++ half that do the same thing.

The rules of the night:

  • Players walk, run and stand at the stalls. One key (X) sits a player down and the same key stands them up again. A seated player's torch burns low.
  • A player who trips on the cobbles lies on the ground until they are helped up.
  • A wave of the torch (V) greets everyone in range. The inspect key (I) marks the player you are looking at, and the cheer key (C) sends a cheer at them.
  • The players standing close to you get a real actor with the full animation graph. The rest of the square is rows.
  • You can read how bright another player's torch is.
  • The raiders camped on the north road are drawn as rows too. The health key (H) shows you how hurt the one you marked is.

Add the module​

The game module lists CrowdyMass in its Build.cs. This example declares fragments, a trait and a processor of its own, so it lists the engine's Mass modules too.

PublicDependencyModuleNames.AddRange(new string[]
{
"CrowdySDK", "CrowdyNet", "CrowdyReplication", "CrowdyServices",
"CrowdyMass", "MassCore", "MassEntity", "MassSpawner"
});

There is no plugin to enable. See Turn Mass on for a map.

Switch the Oakford map to Mass​

  1. In the Content Browser, click Add, then Miscellaneous, then Data Asset, and pick Crowdy Map Profile. Name it DA_OakfordProfile.
  2. Open it. Under Actor Management, set Backend Class to Crowdy Mass Rendering Backend.
  3. Leave Enable Networking ticked and Backend Config empty. The backend then reads Project Settings, Plugins, Crowdy Mass.
  4. In Project Settings, Plugins, Crowdy SDK, open Map Profiles and add an entry that maps the Oakford map to DA_OakfordProfile. See Assign the profile.

You could also start from the shipped profile /CrowdySDK/Mass/Data/DA_CrowdyMassDefaultProfile, which already selects the Mass backend. Every other map keeps the actor pool.

From now on, on the Oakford map, a player you see on your screen is a crowd row, not a spawned BP_LanternPlayer. Your own pawn is still a real actor. See Turn Mass on for a map.

Flag the material​

  1. Open the parent material of the lantern player's body, M_LanternPlayer. Open the parent, not a material instance.
  2. Under Usage, tick Used with Instanced Skinned Mesh.
  3. Save the material.

Skip this and the whole square is grey. Fix a crowd that does not look right has the checklist.

The engine's animation pass​

There is nothing to do here. The SDK hands each lantern player's animation to the renderer itself, and it turns the engine's own animation pass off for you when the game starts. Oakford needs no DefaultMass.ini for it. See The engine's animation pass.

Check the class with crowdy.mass.derive​

Before you run anything, in the editor console:

crowdy.mass.derive BP_LanternPlayer

It prints the mesh it found on BP_LanternPlayer, which of the three routes supplied the mesh, the trace collision object type, and whether every material slot is flagged. It warns if the mesh is missing, if a material is not flagged, or if an authored config would win. The editor world has no Mass subsystem, so the anim set and the config answer appear only when you run the same command in Play In Editor, after the anim set below.

Nothing here needs an entity config of your own: Mass works one out from the class. See Make a character draw as a crowd row.

Look at the square​

On one machine you see no crowd, because the backend does not draw your own entity as a row. Pick one of these:

  • Give DA_OakfordProfile a Backend Config of Crowdy Mass Backend Config and tick Render Local Owner under Mass Backend, Debug. Your pawn then also draws as a row, overlapping the real one. Turn it off when you are done.
  • Or place a Crowdy Mass Area Spawner in the square, with Entity Class BP_LanternPlayer and Entity Count 200.

See Turn Mass on for a map for both. With two Play In Editor windows you see the real thing: each window shows the other player as a row, moving.

Give the crowd legs​

  1. In the Content Browser, click Add, then Miscellaneous, then Data Asset, and pick Crowdy Mass Anim Set. Name it DA_LanternPlayerAnims.

  2. Create the bank TP_LanternPlayer, the asset the crowd renders through, and add the idle, walk, run, sitting and fallen clips to it, each in its own slot. Set up the animation set shows how. Then set TP_LanternPlayer as DA_LanternPlayerAnims's Bank. The config the SDK builds from BP_LanternPlayer draws its mesh through that bank, so nothing else needs authoring.

  3. Under Gaits, add three rows, slowest first. Speeds are in Unreal units per second.

    Gait NameSequenceEnter SpeedExit Speed
    Idlethe idle clip00
    Walkthe walk clip10060
    Runthe run clip400330
  4. Add a Crowdy Mass Anim Set component to BP_LanternPlayer and set its Anim Set to DA_LanternPlayerAnims.

Which gait a row plays comes from how fast it moves on your screen. Nothing about gaits is sent. See Set up the animation set.

Sit and fall​

A pose is held state, so something must say which one. On ALanternPlayer that is the Stance property of the enum ELanternStance : uint8, with the values Standing, Sitting and Fallen. The property is marked CrowdyState and CrowdyHeartbeat. A dropped change would otherwise leave a player sitting on every other screen until something else changed.

// LanternPlayer.h
UENUM(BlueprintType)
enum class ELanternStance : uint8
{
Standing,
Sitting,
Fallen
};

UPROPERTY(BlueprintReadOnly, Category = "Lantern", meta = (CrowdyState, CrowdyHeartbeat))
ELanternStance Stance = ELanternStance::Standing;

In DA_LanternPlayerAnims, under Pose State, leave Pose State Enum Is on Native By Name and pick ELanternStance as the Pose State Enum. Add two Poses rows:

Pose NameSequenceRankHold Last Frame
Sittingthe sitting clipBelow Actionsoff
Fallenthe fallen clipAbove Actionson

Standing is value 0 and has no row. A value with no row clears the pose and the row walks or idles again. Sitting is ranked Below Actions, so a wave plays over a player who is sitting. Fallen is ranked Above Actions, so nobody waves from the ground.

The sit key toggles Stance. It also sets TorchBrightness to 0.3 while the player sits and 1.0 while they stand, and sets the intensity of the local Torch light to match. TorchBrightness is the property you add under Brighten the torch. The sit key is the only thing that sets it.

// LanternPlayer.cpp
#include "LanternPlayer.h"

void ALanternPlayer::OnSit()
{
Stance = Stance == ELanternStance::Sitting ? ELanternStance::Standing : ELanternStance::Sitting;
// A seated player's torch burns low, here and on every crowd row that draws this player.
TorchBrightness = Stance == ELanternStance::Sitting ? 0.3f : 1.f;
Torch->SetIntensity(TorchBrightness * 2000.f);
}

Bind OnSit to the sit key.

Press the key in one window. In the other, the row sits within a moment. Press it again and the row stands and walks. Set Stance to Fallen from your own knock-down code in the same way. See Play poses and one-shot actions.

Each pose needs its own slot in TP_LanternPlayer. If a pose shares a slot with a gait, a player who leaves the pose stays frozen in it, and the anim set's validation says so when the set has a Bank.

Wave the torch and cheer​

An action is a one-shot that plays once and returns to walking. The anim set gets two rows under Actions, in this order:

Action NameSequence
Wavethe wave clip
Cheerthe cheer clip

Press Sync Actions Into Bank. It adds each clip to TP_LanternPlayer twice and fills in Primary Index and Alternate Index for both rows. Action ids start at 1 and follow the row order, so Wave is 1 and Cheer is 2.

The wave: an event marked an action​

WaveTorch is a Crowdy event on ALanternPlayer. It goes to everyone in range, it is marked an action, and it takes ActionId, Variant and PlayRate. Marked like that, a machine that holds the player as a row starts the matching action row with no code written for it.

// LanternPlayer.h
#include "Replication/RPC/CrowdyEvent.h"

UFUNCTION(meta = (CrowdyEvent, CrowdyRecipient = "SpatialMulticast", CrowdyAction))
void WaveTorch_Implementation(int32 ActionId, int32 Variant, float PlayRate);
CROWDY_EVENT(WaveTorch)

The body plays the same action on a machine that holds the player as a real actor. The wave key calls WaveTorch with the id of Wave:

// LanternPlayer.cpp
#include "LanternPlayer.h"
#include "Mass/CrowdyMassActionLibrary.h"

void ALanternPlayer::WaveTorch_Implementation(int32 ActionId, int32 Variant, float PlayRate)
{
UCrowdyMassActionLibrary::PlayCrowdyMassAction(GetMesh(), ActionId, Variant, PlayRate);
}

void ALanternPlayer::OnWave()
{
// Action ids are one-based rows of the anim set, and Wave is its first action.
WaveTorch(1, 0, 1.f);
}

Bind OnWave to the wave key.

One call, three outcomes. On a machine that holds the player as a real actor, the body plays the clip. On a machine that holds a crowd row, or a near tier actor, the body runs on a stand-in and the declared action plays on the row. The near tier actor shows the same action through its animation Blueprint.

The mesh's animation graph needs a Slot node named DefaultSlot, which the stock mannequin graph already has. Play poses and one-shot actions owns the declaration and the body, and Run your code on a crowd row explains the stand-in.

The cheer: send an action by name​

A cheer needs no event of its own. The inspect key traces from the camera and keeps the hit's Target when it hit a Mass entity. The cheer key sends the action by name to the player you kept.

// LanternPlayer.h
#include "Target/CrowdyMassEntityTarget.h"

FCrowdyMassEntityTarget TracedTarget;

// LanternPlayer.cpp
#include "LanternPlayer.h"
#include "Engine/EngineTypes.h"
#include "GameFramework/Controller.h"
#include "Trace/CrowdyMassTraceBlueprintLibrary.h"

void ALanternPlayer::OnInspect()
{
const AController* ViewController = GetController();
if (!ViewController)
{
return;
}
FVector Eye;
FRotator Look;
ViewController->GetPlayerViewPoint(Eye, Look);
FCrowdyMassTraceHit Hit;
UCrowdyMassTraceBlueprintLibrary::CrowdyMassLineTraceSingle(this, Eye, Eye + Look.Vector() * 3000.f,
UEngineTypes::ConvertToTraceType(ECC_Visibility), false, TArray<AActor*>(), EDrawDebugTrace::ForDuration, Hit);
if (!Hit.bHitMassEntity)
{
return;
}
TracedTarget = Hit.Target;
}
// LanternPlayer.cpp
#include "LanternPlayer.h"
#include "Target/CrowdyMassTargetLibrary.h"

void ALanternPlayer::OnCheer()
{
if (!UCrowdyMassTargetLibrary::IsTargetLive(TracedTarget))
{
return;
}
UCrowdyMassTargetLibrary::SendEntityAction(TracedTarget, TEXT("Cheer"), 0, 1.0f);
}

Bind OnInspect to the inspect key and OnCheer to the cheer key.

The name is resolved against the anim set of the class the target stands for, and only the id travels. Everyone who draws that player as a row or a near tier actor sees the cheer, you included. The cheered player holds themselves as a real actor, so they do not see it. The action is a visual, so any client may send it. See Target an entity, from Blueprint and Target an entity, from C++, then Call an event on it, from Blueprint and Call an event on it, from C++.

Give the players close by a real actor​

A row has no animation graph, no bones to attach to and no real components. The players standing close to you should have them. That is the near tier. Promote close entities to actors explains each step.

  1. Create a Blueprint class from Crowdy Mass Near Tier Actor and name it BP_LanternPlayer_Shell.
  2. Create an Animation Blueprint for the character's skeleton and name it ABP_LanternPlayer_Shell. It is its own graph and not a copy of the character's, because Get Owning Actor returns the near tier actor and a cast to the character fails. Build it like this:
    • In Event Blueprint Initialize Animation, call Get Owning Actor, then Get Component By Class with Crowdy Mass Near Tier, and keep the result in a variable named NearTier.
    • In Blueprint Thread Safe Update Animation, set a Speed float from Get Smoothed Speed on NearTier, and drive the locomotion blend space from Speed.
    • Choose the sitting and fallen states from Get Pose Name, comparing against Sitting and Fallen.
    • Route the output through a Slot node named DefaultSlot, then to Output Pose. Wave and Cheer play over whatever the graph shows.
    • Add a Blend Poses by bool before the Slot node, driven by Has Pose and Get Pose Name equal to Fallen, so the graph skips the slot while the player is down. The near tier does not apply Above Actions for you.
  3. On the shell's Mesh component, set the same Skeletal Mesh as BP_LanternPlayer and set Anim Class to ABP_LanternPlayer_Shell. Author nothing else. An empty event graph is the normal shape.
  4. In BP_LanternPlayer, add a Crowdy Mass Near Tier Shell component and set Near Tier Actor Class to BP_LanternPlayer_Shell.
  5. In Project Settings, Plugins, Crowdy Mass, under Near Tier, keep Near Tier Distance at 1000 and Near Tier Visible Distance at 2000. Set Max Near Tier Actors to 12, lower than the default of 50, so you can see the ceiling. The twelve nearest players win a real actor, and the rest of the square stays rows.

A C++ animation instance can read the same getters instead of a Blueprint graph.

Do not add a movement component, a colliding capsule or a Crowdy Entity component to the shell. Turn on crowdy.mass.neartier.trace 1 and walk up to another player: the log says when the player gains a near tier actor and whether it came from the pool.

Brighten the torch​

Every player has a torch, and the crowd has to know how bright it is. That is crowd data of the game's own. The recipe is a replicated property, a fragment, a trait, a processor and a registered field.

On ALanternPlayer, TorchBrightness is a float property marked CrowdyState and CrowdyHeartbeat. It is declared in the snippet further down. In a Blueprint class, add a float variable of that name, set Replication to Replicated and leave Keyframe heartbeat ticked. The sit key is the only thing that sets it. A row has no actor to write it to, so the value is decoded and dropped unless a field claims it.

The fragment holds TargetBrightness, the value that arrived, and Brightness, the value the processor eases toward it, widest member first:

// LanternCrowd.h
#include "CoreMinimal.h"
#include "Mass/EntityElementTypes.h"
#include "LanternCrowd.generated.h"

// Wider members come first: this fragment is read for every crowd row every frame.
USTRUCT()
struct FLanternTorchFragment : public FMassFragment
{
GENERATED_BODY()

UPROPERTY()
float TargetBrightness = 1.f;

UPROPERTY()
float Brightness = 1.f;
};

static_assert(sizeof(FLanternTorchFragment) == sizeof(float) + sizeof(float),
"FLanternTorchFragment has gained padding. Declare wider members before narrower ones.");

The trait adds the fragment to the entity's template:

// LanternCrowd.h
#include "CoreMinimal.h"
#include "MassEntityTraitBase.h"
#include "LanternCrowd.generated.h"

UCLASS(EditInlineNew, CollapseCategories, meta = (DisplayName = "Lantern Torch"))
class ULanternTorchTrait : public UMassEntityTraitBase
{
GENERATED_BODY()

public:
virtual void BuildTemplate(FMassEntityTemplateBuildContext& BuildContext, const UWorld& World) const override;

// Copied onto every entity built from this trait.
UPROPERTY(EditAnywhere, Category = "Lantern")
float StartingBrightness = 1.f;
};

// LanternCrowd.cpp
#include "LanternCrowd.h"
#include "MassEntityTemplateRegistry.h"

void ULanternTorchTrait::BuildTemplate(FMassEntityTemplateBuildContext& BuildContext, const UWorld& World) const
{
FLanternTorchFragment& Torch = BuildContext.AddFragment_GetRef<FLanternTorchFragment>();
Torch.TargetBrightness = StartingBrightness;
Torch.Brightness = StartingBrightness;
}

Add ULanternTorchTrait to the Traits of a Crowdy Mass Entity Traits component on BP_LanternPlayer. The processor eases Brightness toward TargetBrightness each frame:

// LanternCrowd.h
#include "CoreMinimal.h"
#include "MassEntityQuery.h"
#include "MassProcessor.h"
#include "LanternCrowd.generated.h"

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

public:
ULanternTorchFadeProcessor();

protected:
virtual void ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager) override;
virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override;

private:
FMassEntityQuery EntityQuery;
};

// LanternCrowd.cpp
#include "LanternCrowd.h"
#include "MassExecutionContext.h"

ULanternTorchFadeProcessor::ULanternTorchFadeProcessor()
: EntityQuery(*this)
{
// A dedicated server has nothing to draw.
ExecutionFlags = static_cast<int32>(EProcessorExecutionFlags::Client | EProcessorExecutionFlags::Standalone);
ProcessingPhase = EMassProcessingPhase::PrePhysics;
bRequiresGameThreadExecution = false;
}

void ULanternTorchFadeProcessor::ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager)
{
EntityQuery.AddRequirement<FLanternTorchFragment>(EMassFragmentAccess::ReadWrite);
}

void ULanternTorchFadeProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
EntityQuery.ForEachEntityChunk(Context, [](FMassExecutionContext& Context)
{
const TArrayView<FLanternTorchFragment> Torches = Context.GetMutableFragmentView<FLanternTorchFragment>();
const float DeltaTimeSeconds = Context.GetDeltaTimeSeconds();
for (FMassExecutionContext::FEntityIterator EntityIt = Context.CreateEntityIterator(); EntityIt; ++EntityIt)
{
FLanternTorchFragment& Torch = Torches[EntityIt];
Torch.Brightness = FMath::FInterpConstantTo(Torch.Brightness, Torch.TargetBrightness, DeltaTimeSeconds, 2.f);
}
});
}

ULanternCrowdSubsystem is a world subsystem. When the world's subsystems initialize, it registers the state field, which lands an arriving TorchBrightness on TargetBrightness, and the Blueprint field, which makes the same name readable and writable from a graph. Both registrations point at TargetBrightness, so a Blueprint read returns the value that arrived, and a Blueprint write sets a new target the crowd eases toward until the next replicated value replaces it.

// LanternCrowd.h
#include "CoreMinimal.h"
#include "Subsystems/WorldSubsystem.h"
#include "LanternCrowd.generated.h"

UCLASS()
class ULanternCrowdSubsystem : public UWorldSubsystem
{
GENERATED_BODY()

public:
virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void OnWorldBeginPlay(UWorld& InWorld) override;
};

// LanternPlayer.h
UPROPERTY(BlueprintReadOnly, Category = "Lantern", meta = (CrowdyState, CrowdyHeartbeat))
float TorchBrightness = 1.f;

// LanternCrowd.cpp
#include "LanternCrowd.h"
#include "Subsystems/CrowdyMassEntitySubsystem.h"
#include "Target/CrowdyMassFieldAccess.h"

bool ULanternCrowdSubsystem::ShouldCreateSubsystem(UObject* Outer) const
{
// Only where the Mass subsystem is created: a play world whose map profile selects the Mass backend.
return Super::ShouldCreateSubsystem(Outer) && GetDefault<UCrowdyMassEntitySubsystem>()->ShouldCreateSubsystem(Outer);
}

void ULanternCrowdSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
// The transient world at engine start has no game instance yet.
if (!GetWorld()->GetGameInstance())
{
return;
}
UCrowdyMassEntitySubsystem* Mass = Collection.InitializeDependency<UCrowdyMassEntitySubsystem>();
if (!Mass || Mass->IsStateFragmentFieldRegistered(TEXT("TorchBrightness")))
{
return;
}
Mass->RegisterStateFragmentField(TEXT("TorchBrightness"), ECrowdyMassStateFieldType::Float,
[](const void* PropertyValuePtr, const FCrowdyMassViewStateContext& Context)
{
FLanternTorchFragment* Torch = Context.GetFragment<FLanternTorchFragment>();
if (!Torch || !PropertyValuePtr)
{
return;
}
Torch->TargetBrightness = *static_cast<const float*>(PropertyValuePtr);
});
CrowdyMassFields::RegisterField<FLanternTorchFragment>(TEXT("TorchBrightness"), TEXT("TargetBrightness"));
}

The class declaration in that snippet lists both overrides. The next section defines OnWorldBeginPlay.

Flare the torch when a wave arrives​

This step is optional. It shows what a registered handler does. A registered C++ handler for WaveTorch replaces both the event's body and the automatic action write on a crowd row. The row still waves only if the handler writes the action itself. The Blueprint node replaces the body only, so the row still waves.

The handler flares the torch and writes the wave action itself. It is the OnWorldBeginPlay that ULanternCrowdSubsystem declares above.

// LanternCrowd.h
#include "CoreMinimal.h"
#include "Subsystems/WorldSubsystem.h"
#include "LanternCrowd.generated.h"

// LanternCrowd.cpp
#include "LanternCrowd.h"
#include "Fragments/CrowdyMassAnimFragments.h"
#include "LanternPlayer.h"
#include "Mass/CrowdyMassActionEvents.h"
#include "Subsystems/CrowdyMassEntitySubsystem.h"

void ULanternCrowdSubsystem::OnWorldBeginPlay(UWorld& InWorld)
{
Super::OnWorldBeginPlay(InWorld);
UCrowdyMassEntitySubsystem* Mass = InWorld.GetSubsystem<UCrowdyMassEntitySubsystem>();
const UFunction* WaveFunction = ALanternPlayer::StaticClass()->FindFunctionByName(TEXT("WaveTorch_Implementation"));
if (!Mass || !WaveFunction)
{
return;
}
// A registered handler replaces the event body and the automatic action write on a crowd row, so it writes the action itself.
Mass->RegisterEventHandler(WaveFunction,
[](const FCrowdyEventParams&, const FCrowdyMassViewStateContext& Context)
{
if (FLanternTorchFragment* Torch = Context.GetFragment<FLanternTorchFragment>())
{
Torch->TargetBrightness = 1.f;
}
FCrowdyMassActionFragment* Action = Context.GetFragment<FCrowdyMassActionFragment>();
if (!Action)
{
return;
}
CrowdyMassWriteAction(*Action, 1, 0, 1.f);
});
}

Read the brightness​

Landing a value makes it available, so a graph reads it by name. Look at a player with the inspect key. Each frame the HUD checks that the traced target is still live, reads the field and puts the value into Set Percent on a Progress Bar named TorchMeter.

Call UCrowdyMassTargetLibrary::IsTargetLive(TracedTarget). When it is true, call UCrowdyMassTargetLibrary::GetFieldFloat(TracedTarget, TEXT("TorchBrightness"), Brightness) and pass Brightness to the meter.

When the other player sits, the meter drops to 30 percent. When they stand it returns to 100. Run crowdy.mass.state.verdict if the meter never moves. A TorchBrightness that no field claims shows in its dropped list. See Add your own crowd data, Read and write its fields, from Blueprint and Read and write its fields, from C++.

The raiders on the north road​

The warband from Raiders on the north road is far more than a village can draw as actors, and each raider already keeps a record on the server. On the Oakford map BP_Raider is drawn as a row too, and a row reads its raider's record.

BP_Raider is a Crowdy entity with a Crowdy Server Object component whose Definition is CSO_Raider and whose Instance Mode is This Actor's Area, as the actors section of that page sets up. For a row, BP_Raider needs the same treatment as the player in the steps above: a flagged material and a crowdy.mass.derive check.

A client that never spawns a BP_Raider itself cannot resolve the class from the network. Add BP_Raider to Project Settings, Plugins, Crowdy SDK, Replication, Preloaded Entity Classes. See Entities and Spawning.

With This Actor's Area, the game follows the areas around the local player's pawn or camera, as many as Areas Followed Around a Player says, which is 1 on CSO_Raider. A raider outside those areas has no values to read.

The raider's Health is visible to Everyone, so every row can read it. Its Loot is Owner Only and its Hideout Team Only, so a row yields them only for a player who may see them. Mark a raider with the inspect key (I). The health key (H) reads its Health and turns your own torch red when the raider is down to 10 or less, and white again otherwise.

// LanternPlayer.cpp
#include "LanternPlayer.h"
#include "Target/CrowdyMassServerValuesLibrary.h"

void ALanternPlayer::OnCheckHealth()
{
int32 Health = 0;
if (!UCrowdyMassServerValuesLibrary::GetServerValueAs(TracedTarget, TEXT("Health"), Health))
{
return;
}
// A raider on its last legs turns the torch red.
Torch->SetLightColor(Health <= 10 ? FLinearColor::Red : FLinearColor::White);
}

Bind OnCheckHealth to the health key.

Get Server Value returns true only for a value the server sent that fits the type of Value. The pose, the wave and the torch are view state: a modified client can lie about them, and the worst it can do is change what you see. Health is a server value, so it is the truth. Decide what is drawn from view state, and what is true from server values. See Read its server values, from Blueprint, Read its server values, from C++ and Crowd rows.

A record can take a moment to arrive for a raider that has just come into view, and Owner Only values arrive later than Everyone values. The first press can answer false.

Test with two players in Play In Editor​

  1. Play the Oakford map. In Advanced Settings under Multiplayer Options, set Number of Players to 2 and click Play. The map you play must be the one mapped to DA_OakfordProfile. Sign both windows in.
  2. In each window, the other player stands in the square as a row of BP_LanternPlayer, in its own colours and not grey. Walk one: the other window shows Walk at a walk and Run at a run.
  3. Press the sit key (X) in the first window. The other window shows the player sit. Press it again and they stand.
  4. Press the wave key (V). The other window shows the wave, even while the player sits. Press it twice quickly: the wave restarts.
  5. In the second window, look at the first player, press the inspect key (I), then the cheer key (C). The second window shows the cheer on that row. The first window does not: it holds its own player as a real actor, and Send Crowdy Action only reaches machines that draw the player as a row or a near tier actor. A third window shows the cheer too.
  6. Walk the first player to within 1000 cm (10 metres) of the second. The second window now shows a near tier actor with the full animation graph. Walk away and it goes back to a row.
  7. With the first player marked in the second window, press the sit key (X) in the first window. The TorchMeter drops to 30 percent. Stand up and it returns to 100.
  8. In the second window, look at a raider, press the inspect key (I), then the health key (H). The raider starts at 40 health and loses 5 every 5 seconds, so your torch turns red once it is down to 10 or less. At 0 health it retreats and returns at full health.

If one of these does not happen, Fix a crowd that does not look right has a checklist for it. A good first order is crowdy.mass.derive, then one client with Render Local Owner, then two clients.