Game.Simulation.CitizenBehaviorSystem
Assembly:
{{ This class is part of the game's runtime simulation assembly (Cities: Skylines 2 game code). }}
Namespace: Game.Simulation
Type: class
Base: GameSystemBase
Summary:
{{ CitizenBehaviorSystem is the ECS system that drives high-level citizen AI decisions each simulation tick. It evaluates citizens' states (sleep, work/study, leisure, shopping, meetings, moving away, traveling outside connections, mail collection, car reservation/release, etc.), schedules and runs several Burst-compiled jobs (AI tick, car reservation, mail collection, sleep updates), and issues commands through an EndFrameBarrier command buffer. The system queries various parameter singletons (economy, leisure, time, population) and many component lookups/buffers required by citizen logic. It batches AI work into parallel jobs to scale for large populations. }}
Fields
-
public static readonly float kMaxPathfindCost
{{ Max cost used by pathfinding/heuristics for some citizen decisions. Value: 17000f. }} -
public static readonly float kMaxMovingAwayCost
{{ Derived constant for "moving away" cost checks (kMaxPathfindCost * 10). }} -
public static readonly int kMinLeisurePossibility
{{ Tunable minimum leisure chance constant used by the leisure decision logic. Value: 80. }} -
private JobHandle m_CarReserveWriters
{{ Accumulated JobHandle dependencies for jobs that produce writes to the car-reserve queue. Used to combine dependencies before scheduling the car-reserve consumer job. }} -
private EntityQuery m_CitizenQuery
{{ EntityQuery used to select citizens to process (reads/writes Citizen, CurrentBuilding, HouseholdMember, UpdateFrame and excludes TravelPurpose / ResourceBuyer / Deleted / Temp). }} -
private EntityQuery m_OutsideConnectionQuery
{{ Query that gathers outside connection entities (used to pick random outside connection targets). }} -
private EntityQuery m_EconomyParameterQuery
{{ Query for fetching the EconomyParameterData singleton. }} -
private EntityQuery m_LeisureParameterQuery
{{ Query for fetching the LeisureParametersData singleton. }} -
private EntityQuery m_TimeDataQuery
{{ Query for fetching the TimeData singleton. }} -
private EntityQuery m_PopulationQuery
{{ Query for fetching Population singleton/entity used to read current population. }} -
private SimulationSystem m_SimulationSystem
{{ Cached reference to SimulationSystem (frame index etc.). }} -
private TimeSystem m_TimeSystem
{{ Cached reference to TimeSystem (normalized day time). }} -
private EndFrameBarrier m_EndFrameBarrier
{{ Command buffer barrier used to record structural/component changes from jobs (producer). }} -
private EntityArchetype m_HouseholdArchetype
{{ Archetype used when creating household entities for malformed citizens that lacked a household. }} -
private NativeQueue<Entity> m_CarReserveQueue
{{ Persistent native queue (single-producer) used to enqueue citizens that want to reserve a household car. Consumers run on the main-thread job to assign vehicles. }} -
private NativeQueue<Entity>.ParallelWriter m_ParallelCarReserveQueue
{{ ParallelWriter view of m_CarReserveQueue used by parallel jobs to enqueue car reservation requests. }} -
private TypeHandle __TypeHandle
{{ Internal struct instance containing ComponentTypeHandle/Lookup/Buffer handles used by jobs. It is assigned during OnCreateForCompiler. }}
Properties
{{ This system does not expose public properties. Core accessors are exposed via methods (GetCarReserveQueue, AddCarReserveWriter). }}
Constructors
public CitizenBehaviorSystem()
{{ Default constructor. The real initialization happens in OnCreate; constructor is preserved for ECS system instantiation. }}
Methods
-
public override int GetUpdateInterval(SystemUpdatePhase phase)
{{ Returns the update interval in frames for this system. This system uses an interval of 16. }} -
public override int GetUpdateOffset(SystemUpdatePhase phase)
{{ Returns the update offset. This system uses offset 11 to stagger updates. }} -
public static float2 GetSleepTime(Entity entity, Citizen citizen, ref EconomyParameterData economyParameters, ref ComponentLookup<Worker> workers, ref ComponentLookup<Game.Citizens.Student> students)
{{ Computes the normalized time interval [start, end) during which the given citizen should be sleeping. Takes into account age, per-citizen pseudo-random offset, and worker/student schedules (shifts/school). Returns a wrapped float2 in normalized day-time coordinates (0..1). Useful for mods that need to determine a citizen's sleep window. }} -
public static bool IsSleepTime(Entity entity, Citizen citizen, ref EconomyParameterData economyParameters, float normalizedTime, ref ComponentLookup<Worker> workers, ref ComponentLookup<Game.Citizens.Student> students)
{{ Convenience check: returns true if the provided normalizedTime falls inside the citizen's sleep interval computed by GetSleepTime. }} -
public NativeQueue<Entity>.ParallelWriter GetCarReserveQueue(out JobHandle deps)
{{ Returns a ParallelWriter to the internal car-reserve queue that jobs can use to enqueue reservation requests. The out parameter 'deps' is the JobHandle representing current writers' dependencies; callers should chain or combine dependencies correctly when scheduling writers. }} -
public void AddCarReserveWriter(JobHandle writer)
{{ Allows external code to add a writer JobHandle dependency to the system's internal m_CarReserveWriters. This prevents the car assignment job from running until all writers have completed. Mods scheduling jobs that write to the car-reserve queue should call this to register their writer dependency. }} -
[Preserve] protected override void OnCreate()
{{ System initialization: caches references to SimulationSystem/TimeSystem/EndFrameBarrier, creates persistent NativeQueue for car reservations and its ParallelWriter, prepares EntityQuery instances, creates the Household archetype, and calls RequireForUpdate on required queries. Also sets up handles used by compiled jobs in OnCreateForCompiler. }} -
[Preserve] protected override void OnDestroy()
{{ Disposes the persistent m_CarReserveQueue and performs any superclass cleanup. Must be called to avoid leaks. }} -
[Preserve] protected override void OnUpdate()
{{ The main scheduler: computes the current update frame index, creates temporary queues for mail and sleep updates, constructs and schedules several jobs: - CitizenAITickJob (IJobChunk, Burst) — the main per-citizen AI logic (leisure, work/study checks, meetings, travel/GoHome/GoShopping, car reserve enqueueing, etc.). Runs in parallel with many ComponentLookup/BufferLookup usages.
- CitizenReserveHouseholdCarJob (IJob) — processes the car-reserve queue, assigns household cars to citizens (sets CarKeeper component), updates PersonalCar.m_Keeper.
- CitizenTryCollectMailJob (IJob) — tries to collect mail from the citizen's current building into MailSender component.
-
CitizeSleepJob (IJob) — updates building CitizenPresence when citizens go to sleep. The method wires up job dependencies, registers producer jobs with m_EndFrameBarrier, disposes temporary queues when done, and updates base.Dependency. }}
-
private void __AssignQueries(ref SystemState state)
{{ Internal helper used by compiler-generated code to assign queries; not intended for modders to call. }} -
protected override void OnCreateForCompiler()
{{ Internal initialization used by the generated code path for assigning handles and queries at compile time. }} -
[MethodImpl(MethodImplOptions.AggressiveInlining)] private void __AssignHandles(ref SystemState state)
(inside TypeHandle)
{{ Internal: assigns component/buffer/lookup handles used by jobs. }} -
private struct TypeHandle
{{ Compiler-generated container storing all ComponentTypeHandle, ComponentLookup and BufferLookup handles used by the system's jobs. It's assigned during system initialization. }} -
Nested job types (Burst-compiled):
-
CitizenBehaviorSystem.CitizenReserveHouseholdCarJob : IJob
{{ Consumes the car reserve queue. For each enqueued citizen, if the citizen belongs to a household and is eligible (not a child, doesn't already have CarKeeper enabled), attempts to assign a free household car by checking OwnedVehicle buffers and PersonalCar components. If successful, enables and sets CarKeeper on the citizen and updates PersonalCar.m_Keeper. Runs as a single IJob (not chunked). }} -
CitizenBehaviorSystem.CitizenTryCollectMailJob : IJob
{{ Reads citizen current building and building mail producer data: if the building has mail to send and the building/prefab requires collection, transfers up to capacity into the citizen's MailSender component and enables it. Uses PrefabRef/SpawnableBuildingData/MailAccumulationData/ServiceObjectData to determine if the building's mail must be collected. }} -
CitizenBehaviorSystem.CitizeSleepJob : IJob
{{ Decrements CitizenPresence.m_Delta for the building when citizens go to sleep (queued by CitizenAITickJob). Helps track presence changes for building behavior/visuals. }} -
CitizenBehaviorSystem.CitizenAITickJob : IJobChunk
{{ The core per-citizen AI chunked job. Responsibilities include:- Skipping citizens not matching the current UpdateFrame shared component.
- Handling missing or invalid household/citizen situations (creating households for malformed citizens, marking deleted).
- Processing moving-away households (GoToOutsideConnection / mark structures removed).
- Attending coordinated meetings (AttendMeeting).
- Evaluating work/study schedules, leisure decisions (DoLeisure), sleeping (CheckSleep / GoHome / ReleaseCar), shopping (GoShopping), traveling to outside connections (GoToOutsideConnection).
- Enqueuing car reservation, mail collection, and sleep queue entries via parallel writers.
- Adding TravelPurpose, ResourceBuyer, Leisure components and other command buffer operations via EndFrameBarrier parallel writer. The job uses many ComponentLookup / BufferLookup handles, singleton parameters (economy, leisure), time data, and random seeds. It schedules sub-actions by adding TripNeeded buffer elements or adding components to citizens via the command buffer. }}
Usage Example
// Example: how another system/job can get the car-reserve queue and register job dependencies.
protected override void OnUpdate()
{
// Get the running CitizenBehaviorSystem instance
var citizenBehavior = World.GetExistingSystemManaged<Game.Simulation.CitizenBehaviorSystem>();
// Retrieve the ParallelWriter for the car-reserve queue and the current writer dependencies
JobHandle currentWriterDeps;
NativeQueue<Entity>.ParallelWriter carReserveWriter = citizenBehavior.GetCarReserveQueue(out currentWriterDeps);
// If scheduling a job that will enqueue to the car-reserve queue, combine dependencies and register the writer:
var enqueueJob = new EnqueueCarReserveJob { /* ... fields ... */, m_Queue = carReserveWriter };
JobHandle enqueueHandle = enqueueJob.Schedule(currentWriterDeps);
// Inform the CitizenBehaviorSystem about our writer so its consumer won't run before our job finishes
citizenBehavior.AddCarReserveWriter(enqueueHandle);
// Ensure this system's dependency chain includes our enqueue job
Dependency = JobHandle.CombineDependencies(Dependency, enqueueHandle);
}
{{ Notes: - This system is conservative about structural changes: all structural changes from jobs are performed via m_EndFrameBarrier command buffers and scheduled as producers. - Many types (Citizen, Household, TripNeeded, CarKeeper, PersonalCar, Worker, Student, MailSender, MailProducer, etc.) are read/updated by the jobs — consult their component definitions when modifying or extending behavior. - Use GetSleepTime / IsSleepTime helpers when making logic that needs to align with citizen sleep windows. - When writing jobs that enqueue to the car-reserve queue, always call AddCarReserveWriter with the job handle so the system can combine dependencies safely. }}