Unity csharp architecture and ecs
Skill hamzabellouch/agent-skills/Game Development and Interactive 3D/unity-csharp-architecture-and-ecs
Comprehensive collection of 380+ production-ready Agent Skills (26 domains) conforming to the Agent Skills Standard, featuring native auto-discovery for Antigravity, Gemini CLI, Claude Code, Cursor, and Codex.
npx -y skills add hamzabellouch/agent-skills --skill unity-csharp-architecture-and-ecsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 21 days oldThe repository was created 21 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Architectural patterns, Data-Oriented Technology Stack (DOTS), Unity ECS, Burst Compiler, C# Job System, memory management, zero-allocation C# patterns, and frame-budget optimization for high-performance Unity game development.
SKILL.md
12.1 KB, ~2.5k tokens by cl100k_base, as published. Nobody here has run it
Unity C# Architecture & Data-Oriented ECS
This skill guide provides production-grade architectural patterns, memory management protocols, frame budget optimization techniques, and data-oriented design patterns for Unity applications using C#, Unity DOTS (Entities, Burst, C# Job System), and ScriptableObject-driven systems.
1. Architectural Patterns
1.1 Data-Oriented Design (DOTS / ECS) vs Object-Oriented Component Design
High-performance Unity engines split responsibilities based on memory access patterns:
- Object-Oriented (MonoBehaviour): Used for high-level lifecycle control, UI binding, scene bootstrapping, and editor tooling.
- Data-Oriented (ECS / Entities): Used for mass data processing (particles, projectiles, crowd simulation, terrain processing, spatial queries) where cache layout determines performance.
+-------------------------------------------------------------------+
| MonoBehaviour Layer |
| (Bootstrapping, UI Binding, Input Mapping, Authoring Components) |
+-------------------------------------------------------------------+
|
Baker System / Conversion World
v
+-------------------------------------------------------------------+
| Unity DOTS ECS World |
| Entity (ID) + IComponentData (Blittable) + ISystem (Burst-compiled) |
+-------------------------------------------------------------------+
1.2 ScriptableObject-Driven Architecture (Event Channels & Runtime Sets)
Decouple game systems without singletons using ScriptableObject event channels and runtime sets:
- Event Channel: ScriptableObjects acting as broadcast conduits. Subscribed listeners execute without direct reference to publishers.
- Runtime Set: ScriptableObjects holding dynamic lists of active game instances (e.g., active enemies, spatial triggers) avoiding
Object.FindObjectsOfType.
1.3 Service Locator vs Dependency Injection
For MonoBehaviour-based systems:
- Use VContainer or Zenject for explicit compile-time dependency injection.
- Avoid generic untyped Service Locators that introduce hidden temporal dependencies during scene loads.
2. Memory Management & Cache Alignment
2.1 Native Container Lifecycle & Safety Handles
All Native collections (NativeArray, NativeParallelHashMap, NativeList, NativeQueue) require explicit allocation lifetime management:
| Allocator | Duration | Use Case |
|---|---|---|
Allocator.Temp | Single frame (1 frame max) | Short-lived job inputs/outputs within a single method. |
Allocator.TempJob | 4 frames max | Jobs spanning multiple frames; must be disposed upon completion. |
Allocator.Persistent | Indefinite | Long-lived game state, spatial partition grids, resource pools. |
Rule: Always pair
Allocator.Persistentallocations withDispose()inOnDestroy()orOnStopRunning().
2.2 Cache Line Efficiency & Struct Packing
Cache lines on modern modern CPUs are 64 bytes. Layout ECS structs sequentially ([StructLayout(LayoutKind.Sequential)]) and align data types from largest to smallest to eliminate padding bytes:
// BAD: 24 bytes due to padding gaps
public struct UnalignedComponent : IComponentData
{
public bool IsActive; // 1 byte (+ 3 bytes padding)
public double Priority; // 8 bytes
public byte Category; // 1 byte (+ 3 bytes padding)
public int Health; // 4 bytes
}
// GOOD: 16 bytes tightly aligned
[StructLayout(LayoutKind.Sequential)]
public struct AlignedComponent : IComponentData
{
public double Priority; // 8 bytes
public int Health; // 4 bytes
public bool IsActive; // 1 byte
public byte Category; // 1 byte
// 2 bytes automatic trailing padding to 8-byte boundary
}
2.3 Zero-Allocation C# Allocation Rules
To achieve zero Garbage Collection (GC) spikes in Update():
- No LINQ: Replaces all
System.Linqqueries with explicit loops orNativeArrayoperations. - Struct Enumerators: Implement custom
GetEnumerator()returning value types for custom collections. - ArrayPool<T>: Utilize
System.Buffers.ArrayPool<T>.Sharedfor transient array allocations. - Lambda Pre-allocation: Cache delegates/closures into static fields instead of passing inline lambdas.
3. Frame Budget & Job System Optimizations
3.1 Frame Time Budget Allocation (60 FPS = 16.66ms / 120 FPS = 8.33ms)
Total Frame Budget (16.66ms @ 60 FPS)
├── Input & Game Logic (Update): 3.0ms
├── Physics Simulation (FixedUpdate): 4.0ms
├── ECS Job Execution (Parallel Worker Threads): 6.0ms
└── Rendering Prep & Command Buffers (LateUpdate): 3.66ms
3.2 Burst Compiler Guidelines
To ensure [BurstCompile] compatibility:
- Use only blittable primitives, value types (
struct), andNativeContainerpointers. - No managed object references (
string,class,UnityEngine.Object). - Set
CompileSynchronously = trueon critical hot paths to avoid fallback un-burstified execution during domain reloads.
3.3 Structural Change Avoidance via EntityCommandBuffer (ECB)
Executing structural changes (creating entities, destroying entities, adding/removing components) invalidates archetype chunks and forces a Sync Point, stalling all job worker threads.
Solution: Record structural commands into
EntityCommandBuffer.ParallelWriterwithin jobs, then playback deferred commands on the main thread during designated system groups (EndSimulationEntityCommandBufferSystem).
4. Production Code Examples
4.1 Production-Grade Burst-Compiled ECS System (ISystem)
A complete high-performance spatial translation update system for a projectile population:
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
namespace GameEngine.Architecture.ECS
{
public struct ProjectileData : IComponentData
{
public float3 Velocity;
public float LifetimeRemaining;
public float Damage;
}
[BurstCompile]
public partial struct ProjectileUpdateSystem : ISystem
{
private EntityQuery _query;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
_query = state.GetEntityQuery(
ComponentType.ReadWrite<LocalTransform>(),
ComponentType.ReadWrite<ProjectileData>()
);
state.RequireForUpdate(_query);
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
float deltaTime = state.WorldUnmanaged.Time.DeltaTime;
var ecbSingleton = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>();
EntityCommandBuffer ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);
var job = new ProjectileMovementJob
{
DeltaTime = deltaTime,
Ecb = ecb.AsParallelWriter()
};
state.Dependency = job.ScheduleParallel(state.Dependency);
}
[BurstCompile]
private partial struct ProjectileMovementJob : IJobEntity
{
public float DeltaTime;
public EntityCommandBuffer.ParallelWriter Ecb;
// Executed in parallel across worker threads per chunk
private void Execute(
Entity entity,
[EntityIndexInQuery] int sortIndex,
ref LocalTransform transform,
ref ProjectileData projectile)
{
transform.Position += projectile.Velocity * DeltaTime;
projectile.LifetimeRemaining -= DeltaTime;
if (projectile.LifetimeRemaining <= 0f)
{
// Defer structural modification to ECB without stalling worker threads
Ecb.DestroyEntity(sortIndex, entity);
}
}
}
}
}
4.2 ScriptableObject Event Channel Pattern
A zero-allocation type-safe event broadcasting channel:
using System;
using UnityEngine;
namespace GameEngine.Architecture.Events
{
[CreateAssetMenu(fileName = "FloatEventChannel", menuName = "Architecture/Events/Float Event Channel")]
public class FloatEventChannelSO : ScriptableObject
{
private Action<float> _onEventRaised;
public void RaiseEvent(float value)
{
_onEventRaised?.Invoke(value);
}
public void Subscribe(Action<float> action)
{
_onEventRaised += action;
}
public void Unsubscribe(Action<float> action)
{
_onEventRaised -= action;
}
}
}
4.3 High-Performance Zero-GC Array Pool & Job Processing
Processing non-ECS arrays without allocations using NativeArray and IJobParallelFor:
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
namespace GameEngine.Architecture.Jobs
{
public class NativeBatchProcessor : MonoBehaviour
{
[SerializeField] private int elementCount = 100000;
private NativeArray<Vector3> _positions;
private NativeArray<Vector3> _velocities;
private void Awake()
{
_positions = new NativeArray<Vector3>(elementCount, Allocator.Persistent);
_velocities = new NativeArray<Vector3>(elementCount, Allocator.Persistent);
}
private void Update()
{
var job = new BatchPositionJob
{
DeltaTime = Time.deltaTime,
Positions = _positions,
Velocities = _velocities
};
JobHandle handle = job.Schedule(elementCount, 64);
handle.Complete(); // Complete synchronously when required by rendering pipeline
}
private void OnDestroy()
{
if (_positions.IsCreated) _positions.Dispose();
if (_velocities.IsCreated) _velocities.Dispose();
}
[BurstCompile]
private struct BatchPositionJob : IJobParallelFor
{
public float DeltaTime;
public NativeArray<Vector3> Positions;
[ReadOnly] public NativeArray<Vector3> Velocities;
public void Execute(int index)
{
Positions[index] += Velocities[index] * DeltaTime;
}
}
}
}
5. Anti-Patterns & Critical Pitfalls
❌ Anti-Pattern 1: Managed References in IComponentData
// WRONG: Adding managed types causes compiler errors or GC tracking overhead in ECS
public struct BadComponent : IComponentData
{
public string TargetName; // Managed reference! Breaks Burst and memory layout
public GameObject PrefabReference; // Managed reference!
}
// RIGHT: Use fixed-size byte buffers or Entity references
public struct GoodComponent : IComponentData
{
public FixedString64Bytes TargetName;
public Entity PrefabEntity;
}
❌ Anti-Pattern 2: Direct Structural Changes in Parallel Jobs
Calling EntityManager.AddComponent() or EntityManager.DestroyEntity() inside a parallel job causes dynamic archetype re-allocation, corrupting memory pointers read by concurrent threads. Always pass an EntityCommandBuffer.ParallelWriter.
❌ Anti-Pattern 3: Mid-Frame GC Allocs via String Concatenation & LINQ
// WRONG: Allocates string objects and closures every frame in Update
void Update()
{
var activeEnemies = _enemies.Where(e => e.IsAlive).ToList();
uiText.text = "Enemies: " + activeEnemies.Count;
}
// RIGHT: Reuse non-allocating string buffers or FixedString
private readonly char[] _scoreBuffer = new char[32];
void Update()
{
int count = GetActiveEnemyCount();
// Use StringBuilder or direct custom number formatters without heap allocation
}
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.