How-To: Make an Actor a Team Agent
Goal: give an actor team membership the right way for your project - and, for AI, make sure perception can actually read its attitude.
1. Add the component (pick one path)
| Path | When to use |
|---|---|
| Default subobject on a base class | Your own characters/NPCs that must always have a team - guaranteed present, no setup |
| `UGFA_AddTeams` (Game Feature) (CrimsonCore - requires all Crimson plugins) | Data-driven assignment of teams/tags to actor classes from a mode or faction Game Feature, no base-class edit. Ships in CrimsonCore; without it, use the default-subobject or Add Component path above |
| Add Component in a Blueprint | One-off or designer-placed actors |
UGFA_AddTeams (CrimsonCore - requires all Crimson plugins) find-or-configures: if the actor already has a component (e.g. a default subobject) it applies the configured tags to it instead of adding a duplicate, and on deactivation removes only the tags it added. So a base-class subobject and a GFA can coexist.2. Set its tags
Set Team Tags / Hostile To Tags in the Details panel for static actors, or mutate at runtime on the server. The full mutation set:
| Team tags | Hostile-to tags |
|---|---|
JoinTeam(tag) / LeaveTeam(tag) | AddHostileTags(c) / RemoveHostileTags(c) |
AddTeamTags(c) / RemoveTeamTags(c) | SetHostileTags(c) |
SetTeamTags(c) | - |
// NPC faction setup, server-side (Team is this NPC's UCrimsonTeamAgentComponent):if (HasAuthority()){const FGameplayTag Bandit = FGameplayTag::RequestGameplayTag(FName("Team.Bandit"));const FGameplayTag Player = FGameplayTag::RequestGameplayTag(FName("Team.Player"));Team->JoinTeam(Bandit);Team->AddHostileTags(FGameplayTagContainer(Player));}
3. Expose attitude to the engine
Steps 1 and 2 are enough for everything in CrimsonTeams itself - Get Team Attitude Between, Should Cause Damage To Target and the rest are Blueprint nodes that read the component directly. This step is only needed for systems that ask the engine for attitude rather than asking CrimsonTeams.
Those systems call FGenericTeamId::GetAttitude, which casts the asking actor to IGenericTeamAgentInterface and returns Neutral if that fails - it never looks at the component. Two things do this:
- AI Perception - affiliation-filtered senses on the perceiving AI's controller or pawn.
- CrimsonHitDetection's affiliation filter - the attacking actor in a hit query. See CrimsonHitDetection -> How-To: Filter By Team.
For either, the asking actor must implement the interface and forward to the tag logic:
// On your AI controller (or pawn):virtual ETeamAttitude::Type GetTeamAttitudeTowards(const AActor& Other) const override{// Resolve via the controlled pawn, where the team component usually lives.return UCrimsonTeamStatics::GetTeamAttitudeBetween(GetPawn(), &Other);}
// On a Character or Pawn that attacks - the case CrimsonHitDetection needs.// Add "AIModule" and "CrimsonTeams" to your module's dependencies.#include "CrimsonTeamStatics.h"#include "GenericTeamAgentInterface.h"UCLASS()class AMyCharacter : public ACharacter, public IGenericTeamAgentInterface{GENERATED_BODY()public:virtual ETeamAttitude::Type GetTeamAttitudeTowards(const AActor& Other) const override{// Forwards to the tag logic, which resolves BOTH sides through their components.return UCrimsonTeamStatics::GetTeamAttitudeBetween(this, &Other);}};
UGenericTeamAgentInterface is not marked Blueprintable, so a Blueprint class cannot implement it and there is no node that substitutes. This is the one part of CrimsonTeams that needs C++ when you are not using CrimsonCore.It is one small class, written once - reparent your Blueprint to it and everything else stays Blueprint. Steps 1 and 2, and every CrimsonTeams query node, remain fully Blueprint either way.
GetTeamAttitudeBetween, which resolves both sides through FindTeamAgentComponent, the actors being looked at need nothing but the component. Only the perceiver, or the attacker, needs the interface.That is a real advantage over plain engine teams, where the default implementation casts the target actor directly and so requires the interface on both sides.
ACrimsonCharacter, ACrimsonNPCBase, the AI controllers, and ACrimsonPlayerState already implement this forward, so perception reads Crimson attitudes out of the box. The component ships too, as a default subobject - on ACrimsonPlayerState for players, and on ACrimsonNPCBase / ACrimsonCompanionBase / ACrimsonSummonBase / ACrimsonPawn for the rest - so a GFA_AddTeams only needs to set tags. You only need the manual steps above when you write your own classes.Using CrimsonHitDetection too? Swing at an ally with
Crimson.HitDetection.Debug 1 on: they should draw red, meaning the shape reached them and affiliation rejected them. Green means the forward above is missing.See also
- Concept: Dynamic Attitude & Multiplayer - why the interface-on-actor rule exists, and how changes propagate
- API Reference - the full component surface
- CrimsonHitDetection -> How-To: Filter By Team - the same bridge from the hit-detection side, including what happens when it is missing