آنچه در این مقاله میخوانید [پنهانسازی]
معماری ECS در بازی سازی روشی داده محور برای ساخت بازی است که اشیا را به سه بخش «Entity» (شناسه)، «Component» (داده خالص) و «System» (منطق پردازش) تقسیم می کند. اگر با هزاران شیء همزمان سروکار دارید یا کارایی و مقیاس پذیری برایتان حیاتی است، ECS انتخاب درستی است؛ اما برای نمونه سازی سریع، پروژه های کوچک یا رفتارهای بسیار یکتا، مدل های ساده تر مثل اسکریپت نویسی شیءگرا معمولا به صرفه ترند.
ECS دقیقا چیست و چگونه کار می کند؟
پاسخ کوتاه: ECS داده و منطق را از هم جدا می کند؛ Entity فقط یک شناسه است، Component فقط داده دارد، و System منطق را روی مجموعه ای از Entity هایی اجرا می کند که کامپوننت های لازم را دارند.
جزئیات مهم:
- Entity: تنها یک عدد یا شناسه بدون رفتار. مثلا 42.
- Component: ساختار داده ساده مثل Position{x,y} یا Health{value}. هیچ تابع عضوی ندارد.
- System: کدی که روی تمام Entity هایی که مجموعه مشخصی از کامپوننت ها را دارند اجرا می شود. مثلا MovementSystem روی همه Entity های دارای Position و Velocity کار می کند.
مزیت کلیدی ECS «چینش داده» است. به جای نگهداری هر شیء به صورت مجموعه ای از فیلدها (AoS)، داده های یک نوع کامپوننت در آرایه های پیوسته (SoA) ذخیره می شوند. این کار حافظه کش CPU را بهتر پر می کند و در پیمایش های بزرگ (مثل آپدیت حرکت هزاران گلوله) جهش کارایی ایجاد می کند.
چرا ECS محبوب شد؟ مزایا به زبان کاربردی
- کارایی بالا: داده های هم نوع کنار هم می نشینند و حلقه های پردازشی سریع تر می شوند.
- ترکیب پذیری رفتار: با افزودن یا حذف یک کامپوننت، رفتار جدید بدون ارث بری پیچیده شکل می گیرد.
- موازی سازی ساده تر: سیستم ها را می توان بر اساس عدم تعارض داده ها همزمان اجرا کرد.
- آزمایش و نگهداری: چون سیستم ها خالص ترند و داده جداست، تست و ردیابی باگ منطقی تر می شود.
چه زمانی از ECS استفاده کنیم؟
پاسخ کوتاه: وقتی حجم موجودیت ها و پردازش ها زیاد است و رفتارها از کنار هم چیدن داده های ساده ساخته می شوند.
- تعداد بالای Entity: ذرات، گلوله ها، جمعیت NPC، یونیت های RTS یا دشمنان با منطق مشابه.
- حلقه های داغ و تکرارشونده: سیستم هایی که هر فریم روی تعداد زیادی آیتم می چرخند (Physics، AI ساده، Path Following).
- نیاز به مقیاس پذیری: می خواهید بعدا بدون بازنویسی ساختاری، ده ها هزار شیء اضافه کنید.
- ترکیب رفتار پویا: اضافه و حذف آنی قابلیت ها (مثلا Stun، Burn، Shield) با افزودن یا برداشتن کامپوننت.
- هدف گیری چند پلتفرم با محدودیت منابع: مخصوصا کنسول های دستی و موبایل های میان رده که کش کوچکتر دارند.
چه زمانی ECS مناسب نیست؟
پاسخ کوتاه: وقتی بازی کوچک است، سرعت ساخت مهم تر از کارایی است، یا هر شیء رفتار خاص و یکتای زیادی دارد.
- پروتوتایپ های کوچک و سریع: اضافه کردن لایه های ECS ممکن است سرعت توسعه را کم کند.
- رفتارهای به شدت یکتا: اگر بیشتر اشیا منطق اختصاصی دارند، جداسازی به کامپوننت های ریز سود کمی می دهد.
- تیم تازه کار با ECS: منحنی یادگیری مفاهیم داده محور، سفارش گذاری سیستم ها و دیباگ ممکن است ریسک زمان تحویل را بالا ببرد.
- وابستگی زیاد به درخت های شیءگرا و ارث بری پیچیده موجود: مهاجرت وسط پروژه می تواند هزینه زا باشد.
یک مثال عملی کوچک: سیستم حرکت با ECS
هدف: نشان دادن جداسازی داده از منطق. سه Entity داریم؛ هر کدام ممکن است Position و Velocity داشته باشد. سیستم حرکت، فقط روی Entity های دارای هر دو کامپوننت اعمال می شود.
using System;
struct Position { public float x, y; }
struct Velocity { public float dx, dy; }
class Program
{
// برای سادگی، هر Entity را با اندیس آرایه نشان می دهیم.
static void Main()
{
int entityCount = 3;
// وجود یا عدم وجود کامپوننت ها را با آرایه های موازی مدل می کنیم.
bool[] hasPosition = new bool[entityCount];
bool[] hasVelocity = new bool[entityCount];
Position[] positions = new Position[entityCount];
Velocity[] velocities = new Velocity[entityCount];
// Entity 0: Position + Velocity
hasPosition[0] = true; hasVelocity[0] = true;
positions[0] = new Position { x = 0, y = 0 };
velocities[0] = new Velocity { dx = 1, dy = 0.5f };
// Entity 1: فقط Position
hasPosition[1] = true;
positions[1] = new Position { x = 10, y = -2 };
// Entity 2: Position + Velocity
hasPosition[2] = true; hasVelocity[2] = true;
positions[2] = new Position { x = -3, y = 4 };
velocities[2] = new Velocity { dx = -0.2f, dy = 0.0f };
float dt = 0.016f; // حدودا یک فریم در 60FPS
// MovementSystem: روی همه Entity های دارای Position و Velocity اجرا می شود.
for (int i = 0; i < entityCount; i++)
{
if (hasPosition[i] && hasVelocity[i])
{
positions[i].x += velocities[i].dx * dt;
positions[i].y += velocities[i].dy * dt;
}
}
// بررسی نتیجه
for (int i = 0; i < entityCount; i++)
{
if (hasPosition[i])
{
Console.WriteLine($"Entity {i} pos=({positions[i].x:F3}, {positions[i].y:F3})");
}
}
}
}
روش بررسی: کد را اجرا کنید. فقط Entity های 0 و 2 جابجا می شوند. Entity 1 چون Velocity ندارد ثابت می ماند. این همان «اجرای منطق بر اساس وجود/عدم وجود داده» در ECS است.
اشتباهات رایج و نکات اجرایی
- منطق داخل کامپوننت: کامپوننت باید فقط داده باشد. توابع سنگین را به سیستم ها منتقل کنید.
- ریزکردن افراطی: شکستن هر ویژگی به ده ها کامپوننت ریز، تعداد «ترکیب ها» را منفجر می کند و هزینه مدیریت را بالا می برد.
- ترتیب اجرای سیستم ها: وابستگی ها را شفاف کنید (مثلا Physics قبل از Movement، سپس Rendering). یک گراف وابستگی ساده بنویسید.
- آرک تایپ های پراکنده: اگر ترکیب های کامپوننت زیاد و کم مصرفند، داده ها در حافظه پراکنده می شوند. ترکیب های پرتکرار را تثبیت کنید.
- رویدادهای پرحجم: Event های رشته ای یا شیءگرای سنگین در حلقه های داغ، کش را می شکنند. از صف های سبک و ساختارهای داده ساده استفاده کنید.
مقایسه کوتاه با مدل های دیگر
در برابر شیءگرایی سنتی (OOP)
OOP رفتار و داده را در کلاس ها ادغام می کند و ارث بری برای اشتراک رفتار به کار می رود. این مدل برای تعداد کم شیء با رفتارهای متمایز خوب است. ECS داده و منطق را جدا می کند، ارث بری را حذف و ترکیب پذیری را با افزودن/حذف کامپوننت ممکن می کند؛ در عوض نیاز به تفکر داده محور و مدیریت سفارش سیستم ها دارد.
در برابر مدل کامپوننت مبنا رایج (GameObject + Script)
مدل کامپوننت مبنا معمولی همچنان روی شیء متمرکز است و هر شیء چند اسکریپت دارد؛ دسترسی های تصادفی و فراخوانی های مجزا می تواند کش را خراب کند. ECS داده های یکسان را کنار هم می چیند و منطق را به صورت Batch اجرا می کند که برای حجم بالا سریع تر است.
الگوی پیاده سازی پیشنهادی
- دامنه های پرهزینه را شناسایی کنید: ذرات، پرتابه ها، پیمایش ساده AI.
- کامپوننت ها را به عنوان «ساختار داده ساده» تعریف کنید: بدون منطق.
- سیستم ها را بر اساس «چه ترکیب داده ای» و «به چه ترتیبی» اجرا می شوند طراحی کنید.
- چینش داده را SoA نگه دارید: آرایه های پیوسته برای هر فیلد پرتکرار.
- اندازه گام ها را ثابت و قابل برداشتن کنید: هر سیستم یک کار مشخص کند.
- مسیر دیباگ داشته باشید: ابزار نمایش ترکیب کامپوننت هر Entity و ترتیب سیستم ها.
چک لیست تصمیم گیری سریع
- بیش از چند هزار Entity همزمان دارید؟ بله ← به ECS فکر کنید.
- منطق یکنواخت روی تعداد زیادی شیء اجرا می شود؟ بله ← ECS مناسب است.
- پروتوتایپ کوچک یا ددلاین فشرده دارید؟ بله ← از مدل ساده تر شروع کنید.
- رفتارها عمدتا یکتا و پر استثنا هستند؟ بله ← ECS احتمالا ارزشش کم است.
- تیم با داده محوری و سفارش سیستم ها آشناست؟ بله ← ریسک کمتر.
- قرار است بعدا مقیاس را زیاد کنید؟ بله ← ECS مزیت بلندمدت می دهد.
گام بعدی چیست؟
به جای بازنویسی کل بازی، یک زیرسیستم داغ را به ECS منتقل کنید (مثلا مدیریت پرتابه ها). معیارهای ساده بسازید: زمان هر فریم، نرخ کش میس، و تعداد Entity پردازش شده. اگر سود روشن بود، دامنه استفاده از ECS را آرام آرام گسترش دهید.





