Ctrl+K

نمایش اولین چهره (Sprite) شما

در فصل قبل، پروژهٔ توت فرنگی را راه‌اندازی کردیم و یک پنجرهٔ خالی با رنگ آبی کورن‌فلاور باز کردیم. آن پنجره همان بوم نقاشی است — حالا وقت آن رسیده که واقعاً چیزی روی آن بکشیم. در این فصل، اولین چهره (Sprite) خود را رندر می‌کنیم: یک تصویر کوچک توت‌فرنگی که در مرکز صفحه قرار می‌گیرد.

در پایان این راهنما، بازی شما یک تصویر توت‌فرنگی را روی پس‌زمینهٔ آبی نمایش خواهد داد. در طول این مسیر، یاد می‌گیرید که چهره‌ها (Sprites) چه هستند، چرا توت فرنگی به جای فایل‌های خام PNG از اطلس‌های تکسچر (Texture Atlases) استفاده می‌کند، چگونه با ابزار Atlas Generator اطلس بسازید، و چگونه یک چهره را از کد بارگذاری و رسم کنید.


۱. چهره (Sprite) چیست؟

یک چهره (Sprite) یک تصویر دوبعدی است که بازی شما برای نمایش یک عنصر بصری روی صفحه از آن استفاده می‌کند. شخصیت بازیکن در یک بازی پلتفرمر، یک دشمن، یک گلوله، یک درخت در پس‌زمینه، یک آیکون رابط کاربری — همهٔ این‌ها معمولاً چهره هستند. از نظر مفهومی، یک چهره فقط یک ناحیهٔ مستطیلی از پیکسل‌هاست که موتور هر فریم آن را در یک موقعیت، مقیاس و چرخش مشخص روی صفحه کپی می‌کند.

چیزی که یک چهره را فراتر از یک فایل تصویری ساده می‌کند این است که موتور آن را به عنوان یک شیء اصیل (first-class object) درمان می‌کند. یک چهره در توت فرنگی همراه با خود پیکسل‌ها، فراداده (metadata) را نیز حمل می‌کند:

  • یک نام (تا بتوانید از کد آن را پیدا کنید، مثلاً "Strawberry")
  • یک موقعیت درون تکسچر مبدأ (جایی که چهره آغاز می‌شود)
  • یک اندازه (عرض و ارتفاع به پیکسل)
  • یک نقشهٔ فریم (frame map) اختیاری برای چهره‌های متحرک (فهرستی از زیرمستطیل‌ها که با هم یک چرخهٔ راه رفتن، یک انفجار و ... را تشکیل می‌دهند)

وقتی می‌نویسید sprite.Sprite = Sprites["Strawberry"]، در آن لحظه فایلی را از دیسک بارگذاری نمی‌کنید — بلکه یک توصیف‌گر چهرهٔ strongly-typed را می‌گیرید که هنگام شروع بازی قبلاً parse و index شده است. تکسچر GPU قبلاً آپلود شده، نقشهٔ فریم قبلاً محاسبه شده، و تنها کاری که برای رندرر باقی می‌ماند رسم مستطیل درست روی صفحه است.

به همین دلیل چهره‌ها در توت فرنگی فقط «فایل‌های PNG بارگذاری‌شده با Texture.FromFile()» نیستند. آن‌ها اشیاء سطح‌بالاتری هستند که داده‌های پیکسلی، اطلاعات چیدمان و داده‌های انیمیشن اختیاری را در یک دارایی واحد برای موتور می‌بندند.


۲. چرا نمی‌توانیم مستقیماً از فایل PNG استفاده کنیم

اگر تا به حال یک بازی کوچک نوشته‌اید یا یک آموزش مقدماتی را دنبال کرده‌اید، احتمالاً کدی مثل Texture2D tex = LoadPNG("player.png") دیده‌اید. این روش کار می‌کند، ساده است، و برای یک تصویر واحد کاملاً مناسب است. پس چرا توت فرنگی این الگو را ممنوع می‌کند و به یک قالب واسط به نام اطلس (atlas) نیاز دارد؟

پاسخ به نحوهٔ کار واقعی GPUهای مدرن برمی‌گردد.

۲.۱ مسئلهٔ تعویض تکسچر در GPU

هر بار که از GPU خواسته می‌شود از یک تکسچر متفاوت نمونه‌برداری کند، خط لولهٔ گرافیکی باید یک عملیات اتصال تکسچر (texture binding) انجام دهد. اتصال رایگان نیست — بخشی از خط لوله را flush می‌کند، ممکن است cacheها را نامعتبر کند، و روی برخی بک‌اندهای موبایل و WebAssembly به‌مراتب گران‌تر از GPUهای دسکتاپ است. اگر صحنهٔ شما ۲۰۰ چهرهٔ متفاوت داشته باشد و هر کدام از فایل PNG خودشان بیایند، رندرر باید در هر فریم ۲۰۰ اتصال تکسچر انجام دهد. این مقدار زیادی زمان تلف‌شده است.

یک اطلس تکسچر (texture atlas) با بسته‌بندی چهره‌های متعدد در یک تکسچر بزرگ‌تر، این مسئله را حل می‌کند. رندرر اطلس را یک‌بار متصل می‌کند و سپس چهره پس از چهره را از زیرمستطیل‌های متفاوتِ همان تکسچر رسم می‌کند. تعداد اتصال‌ها از «یکی برای هر چهره» به «یکی برای هر اطلس» کاهش می‌یابد، که می‌تواند به‌راحتی روی صحنه‌هایی با چهره‌های کوچک متعدد، ۱۰ تا ۱۰۰ برابر سرعت ببخشد.

۲.۲ محدودیت‌های حافظه و توان‌های دو

GPUها از نظر تاریخی نیاز دارند که ابعاد تکسچر توان‌های دو باشند (۶۴×۶۴، ۱۲۸×۱۲۸، ۲۵۶×۲۵۶، ...). حتی روی سخت‌افزار مدرن که از تکسچرهای غیرتوان‌دو پشتیبانی می‌کند، درایور به‌طور داخلی آن‌ها را pad و align می‌کند، که به معنای هدررفتن حافظه و به‌هم‌خوردن ترازبندی هنگام آپلود PNGهای کوچک و با اندازه‌های عجیب است. اطلس‌ها از ابتدا با ابعاد توان‌دوی مناسب ساخته می‌شوند، پس هیچ هزینهٔ پدینگ پنهانی وجود ندارد.

۲.۳ پهنای باند و زمان بارگذاری

بارگذاری هزار فایل PNG کوچک از دیسک بسیار کندتر از بارگذاری یک فایل اطلس بزرگ است. هر باز کردن فایل، خواندن و decode کردن PNG هزینه‌ای دارد. با یک اطلس، یک فایل را باز می‌کنید، یک تصویر را decode می‌کنید، و فوراً به هر چهره‌ای که درون آن است دسترسی دارید. این به‌ویژه روی اندروید و WebAssembly، که در آن‌ها I/O فایل به‌مراتب کندتر از دسکتاپ است، اهمیت دارد.

۲.۴ قالب بومی توت فرنگی

توت فرنگی ایدهٔ اطلس را یک گام جلوتر می‌برد و آن را به جای تکیه بر اطلس‌های خام PNG، در قالب بومی خود ذخیره می‌کند. یک اطلس در توت فرنگی از دو فایل تشکیل شده است:

فایل پسوند کاربرد
فایل تکسچر atlas.sbTex یک تکسچر از پیش decode شده و آماده برای GPU در قالب داخلی توت فرنگی. هیچ decode ی PNG در زمان اجرا انجام نمی‌شود.
فایل فهرست چهره‌ها atlas.sprList یک manifest متنی/باینری که هر چهره را درون تکسچر فهرست می‌کند: نام، موقعیت، اندازه و چیدمان فریم.

وقتی فراخوانی می‌کنید Sprites.LoadSprites(this, "Assets/atlas.sbTex", "Assets/atlas.sprList")، موتور manifest را می‌خواند، هر چهره را بر اساس نام index می‌کند، و تکسچر را دقیقاً یک‌بار به GPU آپلود می‌کند. از آن پس، جستجوی یک چهره بر اساس نام عملاً رایگان است.

به طور خلاصه: PNGها برای ویرایش در ابزارهایی مثل Aseprite یا Photoshop عالی هستند، اما برای رندر در زمان اجرا افتضاح‌اند. قالب اطلس توت فرنگی پل میان «ویرایش آسان» و «رندر سریع» است.


۳. ابزار Atlas Generator

ساختن اطلس‌ها به‌صورت دستی خسته‌کننده است. باید چهره‌ها را بسته‌بندی کنید، زیرمستطیل‌هایشان را محاسبه کنید، فایل manifest را بنویسید و یک تکسچر export کنید — برای هر تغییر. توت فرنگی یک ابزار اختصاصی کوچک به نام Atlas Generator عرضه می‌کند که همهٔ این کارها را برایتان انجام می‌دهد.

لینک دانلود: Atlas Generator در صفحهٔ ابزارهای توت فرنگی در دسترس است: https://sb-engine.ir/tools

Atlas Generator یک اپلیکیشن دسکتاپ مستقل با رابط کاربری تیره و سه‌پنلی است:

  • **پنل چپ — فهرست چهره‌ها (Sprites List)😗* فهرست فایل‌های PNG که به اطلس اضافه کرده‌اید.
  • **پنل وسط — بوم (Canvas)😗* پیش‌نمایش زندهٔ اطلس بسته‌بندی‌شده، با کنترل‌های زوم.
  • **پنل راست — خواص (Properties)😗* اندازهٔ اطلس، درصد بهره‌وری، تعداد چهره‌ها، عرض حاشیه و اطلاعاتی دربارهٔ چهرهٔ انتخاب‌شدهٔ کنونی.

Atlas Generator — حالت خالی

تصویر بالا ابزار را بلافاصله پس از اجرا نشان می‌دهد، وقتی هنوز هیچ چهره‌ای وارد نشده است. بوم وسط شما را دعوت می‌کند که فایل‌های PNG را بکشید و رها کنید، و پنل راست اندازهٔ اطلس را ۶۴ × ۶۴ با بهره‌وری ۰٫۰٪ گزارش می‌کند.

۳.۱ افزودن چهره‌ها

روی دکمهٔ بنفش Add Sprites در نوار ابزار بالایی کلیک کنید (یا ساده‌طور فایل‌های PNG را روی بوم بکشید و رها کنید). ابزار هر تعداد فایل PNG را در یک batch واحد می‌پذیرد. پس از افزودن، هر چهره با نام، تعداد فریم‌ها و ابعاد پیکسلی‌اش در پنل چپ ظاهر می‌شود.

Atlas Generator — چهرهٔ توت‌فرنگی اضافه شد

در این تصویر دوم، یک فایل PNG به نام Strawberry.png اضافه کرده‌ایم. توجه کنید که پنل راست فوراً به‌روز می‌شود:

  • Atlas Size به‌طور خودکار از ۶۴ × ۶۴ به ۱۲۸ × ۱۲۸ رشد کرد (ابزار کوچک‌ترین اندازهٔ توان‌دویی را که همه چیز را جا بدهد، انتخاب می‌کند).
  • Utilization اکنون ۲۸٫۲٪ است و به ما می‌گوید چه مقدار از تکسچر اطلس واقعاً با پیکسل‌های چهره پر شده است.
  • Sprite Count برابر ۱ است.
  • ورودی چهره در پنل چپ نام Strawberry و 1 frames را نشان می‌دهد، یعنی این چهره فقط یک فریم دارد (یک تصویر ایستا است، نه انیمیشن).

۳.۲ شیت‌های چهره و نقشه‌های فریم خودکار

بازی‌های واقعی به‌ندرت از چهره‌های ایستا استفاده می‌کنند — شخصیت‌ها راه می‌روند، دشمنان منفجر می‌شوند، پرتابه‌ها می‌چرخند. این انیمیشن‌ها معمولاً به شکل شیت‌های چهره (sprite sheets) ساخته می‌شوند: یک فایل PNG واحد که چندین فریم را به‌صورت افقی (یا در یک شبکه) در خود جای داده است.

Atlas Generator بر اساس نام فایل، شیت‌های چهره را به‌طور خودکار تشخیص می‌دهد. اگر فایل PNG شما با الگوی زیر نام‌گذاری شده باشد

<name>_strip#.png

که در آن # تعداد فریم‌هاست، ابزار کارهای زیر را انجام می‌دهد:

۱. PNG را بارگذاری می‌کند. ۲. آن را به‌صورت افقی به # فریم با اندازهٔ مساوی تقسیم می‌کند. ۳. یک نقشهٔ فریم داخلی برای آن چهره می‌سازد. ۴. هر فریم را به‌گونه‌ای در اطلس بسته‌بندی می‌کند که گویی چهره‌های جداگانه هستند، اما همه را تحت همان نام چهره گروه‌بندی می‌کند.

برای مثال، فایلی به نام player_walk_strip8.png به‌عنوان یک انیمیشن راه‌رفتن ۸ فریمی تفسیر می‌شود. وقتی بعداً Sprites["player_walk"] را درخواست کنید، توت فرنگی یک شیء چهره به شما برمی‌گرداند که همهٔ ۸ فریم را می‌شناسد، و می‌توانید با ایندکس فریم میان آن‌ها جابه‌جا شوید.

اگر فایل شما با الگوی _strip# مطابقت ندارد (مثل Strawberry.png ما)، ابزار آن را به‌عنوان یک چهرهٔ تک‌فریمی در نظر می‌گیرد، که دقیقاً همان چیزی است که برای این راهنما می‌خواهیم.

۳.۳ پیکربندی اطلس

پیش از export، می‌توانید چند گزینه را در پنل راست تغییر دهید:

  • Border Width — تعداد پیکسل‌های شفاف که میان چهره‌های مجاور در اطلس باقی گذاشته می‌شود. این از texture bleeding (جایی که پیکسل‌های یک چهره به دلیل نمونه‌برداری اعشاری به چهرهٔ همسایه نشت می‌کنند) جلوگیری می‌کند. مقدار پیش‌فرض 2 نقطهٔ شروع خوبی است.
  • Atlas Size — معمولاً روی «auto» گذاشته می‌شود، اما اگر می‌خواهید برای چهره‌های آینده فضا رزرو کنید، می‌توانید یک اندازهٔ توان‌دوی خاص را اجبار کنید.

برای اولین چهرهٔ ما عموماً نیازی به تغییر این موارد ندارید — پیش‌فرض‌ها مناسب‌اند.

۳.۴ خروجی گرفتن

روی دکمهٔ قرمز Export در نوار ابزار بالایی کلیک کنید. ابزار دو فایل را در دایرکتوری انتخابی شما می‌نویسد:

atlas.sbTex       # تکسچر آماده برای GPU (قالب بومی توت فرنگی)
atlas.sprList     # manifest چهره‌ها (موقعیت‌ها، اندازه‌ها، فریم‌ها)

هر دو فایل باید با هم باشند — تکسچر بدون manifest بی‌معناست، و manifest به تکسچری ارجاع می‌دهد که باید روی دیسک وجود داشته باشد. در بخش بعد، هر دوی آن‌ها را از بازی‌مان بارگذاری می‌کنیم.


۴. دارایی‌های موردنیاز

پیش از نوشتن کد، به دو چیز نیاز دارید:

۱. ابزار Atlas Generator، قابل دانلود از https://sb-engine.ir/tools. ۲. تصویر Strawberry.png برای وارد کردن به ابزار.

برای ساده‌سازی، فایل Strawberry.png (یک PNG شفاف ۶۴ × ۶۴ از یک توت‌فرنگی کوچک) را در یک آرشیو zip قرار داده‌ایم که می‌توانید مستقیماً دانلود و استخراج کنید:

دانلود دارایی‌ها: Strawberry.zip — شامل Strawberry.png (۶۴ × ۶۴، RGBA، پس‌زمینهٔ شفاف).

وقتی هر دو را داشتید:

۱. Atlas Generator را اجرا کنید. ۲. Strawberry.png را روی بوم بکشید (یا روی Add Sprites کلیک کنید). ۳. بررسی کنید که پنل چپ Strawberry را با 1 frames نشان دهد و پنل راست 128 × 128 را با بهره‌وری ~۲۸٪ نشان دهد. ۴. روی Export کلیک کنید و دو فایل خروجی (atlas.sbTex و atlas.sprList) را در پوشه‌ای به نام Assets/ درون پروژهٔ MyFirstGame.Desktop خود ذخیره کنید. مطمئن شوید هر دو فایل هنگام build به دایرکتوری خروجی کپی می‌شوند (ویژگی «Copy to Output Directory» آن‌ها را در IDE خود روی «Copy if newer» تنظیم کنید، یا موارد زیر را به MyFirstGame.Desktop.csproj اضافه کنید):

<ItemGroup>
  <None Update="Assets\atlas.sbTex">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </None>
  <None Update="Assets\atlas.sprList">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </None>
</ItemGroup>

۵. بارگذاری اطلس و رندر چهره

اکنون MyGameContextای که در فصل قبل نوشتیم را گسترش می‌دهیم. MyGameContext.cs را باز کنید و به شکل زیر تغییرش دهید. خطوط جدید با // NEW نظر داده شده‌اند تا در نگاه اول آن‌ها را پیدا کنید.

using Strawberry.Components;
using Strawberry.Core;
using Strawberry.Graphics;
using Strawberry.Graphics.Layers;          // NEW — for SpriteLayer
using Strawberry.Graphics.ParticleSystem;
using Strawberry.Graphics.Text;
using Strawberry.Math;
using Strawberry.Sound;
using Color = Strawberry.Graphics.Color;

namespace Strawberry.Test
{
    public class MyGameContext : StdGameContext
    {
        public const float ppm = 32f;

        public AtlasHelper Sprites;          // NEW — holds our loaded atlas

        public MyGameContext()
            : base(1280, 720)
        {
        }

        public override void OnInitialize(IGameLauncher laucnher)
        {
            base.OnInitialize(laucnher);

            var size = GraphicsContext.GetScreenSize();
            Viewport viewport = new Viewport(
                "Default",
                new Vector2(),
                size,
                new Vector2(),
                new Vector2(1280, 720));

            Scene scene = new Scene("Main", 1280, 720);
            scene.ClearColor = Color.CornflowerBlue;

            
            AddScene(scene);
            SetScene("Main");

            scene.Viewports[0] = viewport;

            // NEW — create a sprite layer and register it with the scene
            var spriteLayer = new SpriteLayer();
            scene.AddLayer("Sprite1", spriteLayer);

            // NEW — load the atlas (texture + sprite list) from disk
            Sprites = new AtlasHelper();
            Sprites.LoadSprites(this, "Assets/atlas.sbTex", "Assets/atlas.sprList");

            // NEW — create an entity, give it a transform and a sprite,
            //       and bind the sprite to the layer we just created
            Entity entity = new Entity();
            entity.Initialize("first_entity", scene);

            var transform = entity.AddComponent<TransformComponent>();
            transform.Position = new Vector2(Scene.Width / 2, Scene.Height / 2);
            transform.Origin = Sprites["Strawberry"].Size / 2;

            var sprite = entity.AddComponent<SpriteComponent>();
            sprite.Sprite = Sprites["Strawberry"];
            sprite.Layer = spriteLayer;
        }
    }
}

۵.۱ مرور کد

بیایید هر بلوک جدید را مرور کنیم و توضیح دهیم که چه کاری انجام می‌دهد.

۵.۱.۱ دستور using برای لایه‌ها

using Strawberry.Graphics.Layers;

کلاس SpriteLayer در namespace به نام Strawberry.Graphics.Layers قرار دارد. بدون این using، کامپایلر نمی‌تواند بعداً در فایل SpriteLayer را تفکیک کند.

۵.۱.۲ فیلد Sprites

public AtlasHelper Sprites;

AtlasHelper رابط سطح‌بالای موتور با یک اطلس بارگذاری‌شده است. پس از فراخوانی LoadSprites، این شیء مانند یک دیکشنری رفتار می‌کند: می‌توانید با نام در آن index کنید (Sprites["Strawberry"]) و یک شیء Sprite کاملاً تفکیک‌شده برگردانید. آن را به‌عنوان یک فیلد نگه می‌داریم (نه یک متغیر محلی)، زیرا بخش‌های دیگر بازی — مثلاً کدی که بعداً موجودهای (Entities) جدید تولید می‌کند — هم می‌خواهند چهره‌ها را بر اساس نام جستجو کنند.

۵.۱.۳ ساخت و ثبت یک لایهٔ چهره

var spriteLayer = new SpriteLayer();
scene.AddLayer("Sprite1", spriteLayer);

یک لایه (layer) در توت فرنگی یک گروه رندر است. هر فریم، موتور از لایه‌های صحنه به ترتیبی که اضافه شده‌اند عبور می‌کند و هر چهره‌ای که به هر لایه تعلق دارد را رسم می‌کند. این به شما اجازه می‌دهد ترتیب رسم را مستقل از ترتیب ساخت موجودها کنترل کنید.

مهم است بدانید که نوع لایه ترتیب رسم آن را تعیین نمی‌کند. یک BackgroundLayer، یک SpriteLayer و یک ParticleLayer نه به‌خاطر جایی که در صف رندر ظاهر می‌شوند، بلکه به‌خاطر نحوهٔ رندر و آنچه درون‌ساختانه مدیریت می‌کنند متفاوت‌اند — مثلاً یک SpriteLayer چهره‌ها را مرتب و batch می‌کند، یک ParticleLayer یک سیستم ذره را به‌روز و رندر می‌کند، یک BackgroundLayer برای پس‌زمینه‌های کاشی‌شده یا تکرارشونده بهینه شده است. اینکه کدام روی کدام قرار می‌گیرد، صرفاً موضوع ترتیب درج است. اگر یک BackgroundLayer را بعد از یک SpriteLayer اضافه کنید، روی آن رسم می‌شود — فقط در حال انجام این کار مثل یک پس‌زمینه رفتار می‌کند. لایه‌ها همچنین می‌توانند در زمان اجرا جابه‌جا شوند اگر نیاز به مرتب‌سازی پویا داشته باشید.

اینجا یک SpriteLayer می‌سازیم، نامش را "Sprite1" می‌گذاریم، و آن را به صحنه اضافه می‌کنیم. هر چهره‌ای که ویژگی Layer آن روی spriteLayer تنظیم شود، از طریق این لایه رسم خواهد شد.

۵.۱.۴ بارگذاری اطلس

Sprites = new AtlasHelper();
Sprites.LoadSprites(this, "Assets/atlas.sbTex", "Assets/atlas.sprList");

LoadSprites سه آرگومان می‌گیرد:

آرگومان مقدار معنی
this game context به atlas helper اجازه می‌دهد به graphics context و انبار دارایی دسترسی پیدا کند.
"atlas.sbTex" فایل تکسچر تکسچر آماده برای GPU که توسط Atlas Generator تولید شده است.
"atlas.sprList" فایل فهرست چهره‌ها manifest که به موتور می‌گوید هر چهره درون تکسچر کجا قرار دارد.

پس از بازگشت این فراخوانی، Sprites["Strawberry"] فوراً قابل استفاده است. تکسچر به GPU آپلود شده، و هر چهره‌ای که در manifest تعریف شده، بر اساس نام index شده است.

۵.۱.۵ ساخت موجود

Entity entity = new Entity();
entity.Initialize("first_entity", scene);

در توت فرنگی، یک موجود (Entity) یک ظرف عمومی برای جزءها (Components) است. خودش هیچ رفتاری ندارد — فقط یک کیسه از جزءهاست. فراخوانی Initialize موجود را با صحنهٔ داده‌شده ثبت می‌کند و نامی ("first_entity") به آن اختصاص می‌دهد که بعداً می‌توانید برای دیباگ یا جستجو از آن استفاده کنید.

۵.۱.۶ جزء Transform

var transform = entity.AddComponent<TransformComponent>();
transform.Position = new Vector2(Scene.Width / 2, Scene.Height / 2);
transform.Origin = Sprites["Strawberry"].Size / 2;

TransformComponent جزءی است که تعیین می‌کند موجود در فضای دوبعدی کجا قرار دارد. سه ویژگی اهمیت بیشتری دارند:

  • Position — مختصات فضای جهانی موجود. آن را روی مرکز صحنه (Scene.Width / 2، Scene.Height / 2) تنظیم می‌کنیم، یعنی وسط رزولوشن مجازی ۱۲۸۰ × ۷۲۰ ما.
  • Origin — نقطهٔ محور موجود، به پیکسل از گوشهٔ بالا-چپ چهره اندازه‌گیری شده. با نصف کردن اندازهٔ چهره، محور را در مرکز تصویر قرار می‌دهیم، که به این معناست چهره روی Position خود مرکز خواهد شد، نه اینکه از گوشهٔ بالا-چپش به بیرون رسم شود.

بدون تنظیم origin، توت‌فرنگی در ربع پایین-راست صفحه ظاهر می‌شد، زیرا گوشهٔ بالا-چپش در مرکز صحنه قرار می‌گرفت. تنظیم origin روی Size / 2 ترفند استاندارد برای «این چهره را روی موقعیتش مرکز کن» است.

۵.۱.۷ جزء Sprite

var sprite = entity.AddComponent<SpriteComponent>();
sprite.Sprite = Sprites["Strawberry"];
sprite.Layer = spriteLayer;

SpriteComponent چیزی است که واقعاً موجود را مرئی می‌کند. سه اتفاق اینجا می‌افتد:

۱. جزء را به موجود اضافه می‌کنیم (به همان روشی که TransformComponent را اضافه کردیم). ۲. شیء چهره‌ای که می‌خواهیم رسم کنیم را انتساب می‌دهیم — در این مورد، آن چی که در اطلس تازه بارگذاری‌شده نامش "Strawberry" است. ۳. چهره را به spriteLayerای که قبلاً ساختیم متصل می‌کنیم. از این پس، هر بار که لایه رسم شود، این چهره رندر خواهد شد.

توجه کنید که ما خودمان هیچ draw callای ننوشتیم. رندرر توت فرنگی هر فریم به‌طور خودکار از لایه‌های صحنه عبور می‌کند و از هر لایه می‌خواهد چهره‌هایش را رسم کند. تنها کاری ما باید بکنیم این بود که چه چیزی بکشیم و کجا را تعریف کنیم؛ موتور بقیه را مدیریت می‌کند.


۶. اجرای بازی

پروژه را دقیقاً مثل قبل اجرا کنید:

dotnet run --project MyFirstGame.Desktop

اگر همه چیز درست وصل شده باشد، باید پنجرهٔ آبی کورن‌فلاور فصل قبل را ببینید — اما این بار، یک تصویر کوچک توت‌فرنگی دقیقاً در مرکز صفحه رندر شده است.

اگر بازی compile می‌شود اما توت‌فرنگی ظاهر نمی‌شود، شایع‌ترین مقصرها این‌ها هستند:

  • فایل‌های اطلس در دایرکتوری خروجی نیستند. مطمئن شوید atlas.sbTex و atlas.sprList به خروجی build کپی می‌شوند (به snippet مربوط به .csproj در بخش ۴ نگاه کنید).
  • نام چهره مطابقت ندارد. Sprites["Strawberry"] نسبت به کوچکی و بزرگی حروف حساس است و باید دقیقاً با نام چهره مطابقت داشته باشد، همان‌طور که در پنل چپ Atlas Generator نشان داده می‌شود (که به‌طور پیش‌فرض نام فایل PNG بدون پسوند است).
  • لایهٔ چهره پیش از ساخت موجود اضافه نشده است. لایه‌ها متعلق به صحنه‌اند، و صحنه باید پیش از آنکه بتوان چهره‌هایی را به لایه‌ای انتساب داد، آن لایه را بشناسد.

۷. گام بعدی

اکنون یک خط لولهٔ کارا از فایل خام PNG → اطلس → موتور → صفحه دارید. همین گردش‌کار برای صدها چهره مقیاس می‌شود: همه را در Atlas Generator بریزید، یک (یا چند) اطلس export کنید، در شروع بارگذاری کنید، و هر چهره را با نام ارجاع دهید.

در فصل بعد، روی این پایه می‌سازیم و اولین جزء (Component) سفارشی خود را می‌سازیم — با نوشتن کلاسی که به سیستم جزءهای توت فرنگی متصل می‌شود، رفتار خودتان را به یک موجود اضافه می‌کنید.