HomeSearchRegisterLog in

Share | 
 

 برنامه نویسی با زبان C برای بازی های NES

View previous topic View next topic Go down 
AuthorMessage
DeltaCode
avatar

Posts : 23
Join date : 2015-08-04
Age : 27
Location : Tehran
درجه : سرباز دوم

درجه : سرباز دوم


PostSubject: برنامه نویسی با زبان C برای بازی های NES   2015-10-26, 9:15 am


این مقاله برای آنهایی نوشته شده که دوست دارند برای NES نرم افزار توسعه بدهند، اما هنوز آمادگی آنرا ندارند که پروژه‌های بزرگی با زبان اسمبلی 6502 بسازند، و به دنبال جایگزینهای آسان تر و سطح بالاتری هستند. این مقاله طیف وسیعی از موضوعات مرتبط با بازی نویسی برای 6502  با زبان C  با استفاده از کامپایلر CC65 ، به همراه بازی ساده ی توسعه یافته ی ویژه ای که  برای نمونه تهیه شده را پوشش می دهد.
هدف توسعه‌ی برنامه‌ی بازی نمونه‌ای که به عنوان مثال آورده شده، این است که یک پروژه کامل با کدهای ساده و کوتاه که قابل فهم باشد، ارئه شود. پروژه های قبلی من نسبتا بزرگ بودند و تقریبا هیچ توضیحاتی در آن وجود نداشت، بنا بر این  آنها برای ارائه به عنوان مثال زیاد خوب نیستند. همچنین بازی نمونه، شامل آخرین بازبینی کدهای سطح پایینی هستند که در پروژه های NES قبلی من توسعه یافته اند و بکار رفته اند.
اگر کدهای بازی نمونه ای که ارائه شده هنوز زیاد هستند و برای فهمیدن پیچیده هستند، این برنامه های نمونه ی کوچک را بررسی کنید که همچنین از کدهای سطح پایین استفاده میکنند و نشان میدهد که چطور کارهای ساده ای مانند نشان دادن یک پیغام روی صفحه و یا اسپیرایت انجام میشوند.

چه چیزهایی نیازدارید تا بدانید










برای اینکه یک بازی NES بسازید نیاز دارید تا دانش پیش زمینه ی زیادی در مورد چیزهای زیادی داشته باشید. حتی توضیح دادن مختصر تمام چیزهایی که در ساخت بازی شرکت دارند در این مقاله غیر ممکن است، پس این لیست را بررسی کنید. اگر چیزی را که ذکر شده نمیدانید، ممکن است نیاز باشد که آنرا از جایی یاد بگرید. درباره ی این موارد کتابها و مقاله های زیادی نوشته شده. تمام اطلاعات مربوط به NES از جزئیات فنی و غیره را میتوانید از ویکی و فروم NesDev بدست آورید.
•    دانش کامل و خوب برنامه نویسی با زبان C
o    عملیات های بیتی و شیفت
o    محاسبات ممیز ثابت
o    اشاره گرها
•    دانش پایه ای از پردازنده ی 6502
o    ثباتها
o    آدرس دهی حافظه ی zero page
o    حافظه ی پشته
•    دانش پایه ای سخت افزار NES ، بویژه PPU
o    حافظه ی CHR ROM
o    جدول های NameTable
o    پالت های رنگ
o    زمان VBlank و دسترسی به حافظه ی VRAM
o    نگاشت های حافظه ی CPU و PPU
•    دستورات خط فرمان ویندوز و نوشتن batch فایل

مزایا و معایب










برنامه نویسی برای NES به زبان C نسبت به برنامه نویسی به زبان اسمبلی دارای 2 تا مشکل عمده است. اولا کدهای کامپایل شده نسبت به کدهای دست نویس اسمبلی کند تر هستند. ثانیا همیشه حجم آن بیشتر است. اندازه ی کد حتی میتواند از سرعت هم مهمتر باشد. به عبارت دیگر کد کوچکتر معمولا سریعتر هم هست.
برنامه نویسی با C در مقابل این معایب که به همراه می آورد یک مزیت بزرگ دارد- شما می توانید برنامه هایتان را با سرعت  بسیار بیشتری توسعه دهید. زیرا با نوشتن کد کمتر؛ شما  به نوشتن ، ایرادیابی و نگه داشت کمتری نیاز پیدا خواهید کرد، و همچنین کدهایی که مینویسید خوانا تر خواهند بود، همچنین شما تجرید بیشتری از سخت افزار خواهید یافت ، در نتیجه شاید شما بتوانید حتی بدون دانستن اسمبلی 6502 یک برنامه NES ساده بسازید، گرچه دانستن اسمبلی قطعا میتواند کمک کننده باشد.
این جا یک مثال عملی ارائه میشود که اثبات می کند برنامه نویسی با C بسیار در زمان صرفه جویی می کند. در زیر یک کد به زبان اسمبلی نوشته شده که یک مقداری را از یک آرایه 32x32 تایی ، به واسطه ی 2 عدد 8-بیتی از مختصات (mx, my) بازیابی می کند:
Code:

; assembly version

 lda my        ;multiply my by 32
 sta ptr_h    ;through shifting
 ldy #0        ;a 16-bit var (ptr_h,ptr_l)
 sty ptr_l    ;to the right for three times
dup 3
 lsr ptr_h    ;shift
 ror ptr_l
edup
 lda ptr_l    ;add mx as 16-bit value
 clc
 adc mx
 bcc @1
 inc ptr_h
@1:
 clc
 adc #<map    ;add map offset
 sta ptr_l
 lda ptr_h
 adc #>map
 sta ptr_h
 lda [ptr_l],y    ;read the value


معادل زبان C آنرا مشاهده می کنید:
Code:

// C version

n=map[(my<<5)+mx];


بهینه سازی










بخاطر اینکه NES منابع بسیار محدودی دارد، مانند سرعت کم CPU ، اندازه ی کم حافظه های RAM و ROM ، نوشتن کدهای C تمیز و شسته رفته ، زیاد کارایی ندارد. برای اینکه کدها سریع و کوتاه باشند مجبورید آنرا به روش هایی بهینه سازی کنید، در غیر این صورت کدهای شما ارزشمند و قابل قبول نخواهند بود. این روش ها برخی از مزایای برنامه نویسی با زبان C را از بین می برند و باعث می شوند کدهایی که می نویسید نزدیک به سطح پایین باشند، و ساخت یافتگی کمتری داشته باشند. با این حال و با تمام این محدودیتها هنوز هم در مقایسه با زبان اسمبلی بسیار سطح بالا محسوب می شود.
این توصیه هایی که در زیر آورده شده کدهای شما را کارا تر می کند ولی خوانایی کدها کمتر می شود:
•    از متغیر های محلی تا حد ممکن اجتناب کنید و آنها را static کنید.
•    از پاس دادن پارامترها به توابع خودداری کنید.
•    از توابعی که فقط یک بار فراخوانی می شوند اجتناب کنید.
•    از خصوصیت __fastcall__ برای فراخوانی توابع استفاده کنید.
•    آرایه هایی از structها کند هستند، آرایه های جداگانه سریعتر هستند.
•    سریعترین نوع داده unsigned char است، از آن تا جایی که می توانید استفاده کنید. و فراموش نکنید که در cc65 نوع int  به اندازه ی 16-بیت است.
•    انواع علامت دار کندتر هستند.
•    شما می توانید با استفاده از pragma متغیرها را به قسمت zero page منتقل کنید(مطالب زیر را ببینید) ، این کار دسترسی به آنها را سریعتر میکند.
•    فراموش نکنید که برای قرار دادن آرایه ای از اشاره گر ها در حافظه ی ROM باید آنها را const type* const اعلان کنید.
•    هر جا که امکان دارد از عملیات پیش افزایش استفاده کنید، زیرا هم سریعتر و هم کوتاهتر است.
•    از ضرب و تقسیم تا حد ممکن اجتناب کنید زیرا بسیار کند است، بجای آن از عمل شیفت استفاده کنید.
•    اگر به انجام پردازش روی آرایه ای از اشیا نیاز پیدا کردید، بهتر است ابتدا یک کپی از اطلاعاتی که نیاز دارید از آرایه بگیرید و درمتغیرهایی بریزید. از این متغیر ها در کد استفاده کنید و اطلاعات جدید را دوباره در آرایه کپی کنید. این کار می  تواند کدهای شما را سریعتر و کوچکتر کند. زیرا دسترسی به آیتم های آرایه، کدهای بیشتری نسبت به دسترسی به متغیر میسازد.
•    اعلان متغیرهای جهانی به عنوان static می تواند به پیدا کردن متغیرهای بلااستفاده کمک کند ، زیرا کامپایلر آنها را گزارش می دهد.
اگر برنامه شما محدودیت های CPU را نقض کرد، و شما نیاز به بهینه سازی کدهای زبان C داشتید تا هرچه بیشتر سریع اجرا شود ، شما نیاز به تحلیل برنامه به وسیله ایرادیاب های شبیه سازهای NES دارید.  برای مثال یک نسخه از VirtualNES وجود دارد که زمان نوشتن در یک ثبات خاص مثلا $401e  و $401f را بر مبنای clockهای CPU اندازه گیری می کند و آنرا روی صفحه نشان میدهد. مثلا مینویسد 30000 کلاک از CPU در یک فریم زمان لازم است ، سپس شما می توانید در مورد زمان اجرای یک قسمت از کدهایتان ایده بگیرید و ببینید با بهبود کدهایتان چقدر زمان اجرای برنامه تاثیر پذیرفته است.

آماده سازی و اولین کامپایل










قبل از اینکه توضیحاتی در مورد کدهای بازی نمونه بدهم که چطور کار می کند، بهتر است آماده سازی ابزارها و کامپایل کردن بازی توسط خودتان را آموزش بدهم.
محیط توسعه ی NES من بسیار ابتدایی است.  من از IDE یا چیزخاصی استفاده نمی کنم. فقط از Notepad++ برای ویرایش کدها استفاده می کنم. از cc65 به عنوان کامپایلر خط فرمان ، از تعدادی bat فایل برای اتوماسیون فرآیند کامپایل و از یک شبیه ساز برای آزمایش استفاده می کنم. همچنین از یکسری ابزارهایی برای ساختن منابع مورد نیاز استفاده می کنم.
ابتدا cc65 را دانلود کنید و در یک دایرکتوری آنرا از حالت فشرده خارج کنید، برای مثال در c:\cc65 – پس پوشه های \bin\  ، \include\ و \lib\ و غیره در همان دایرکتوری قرار خواهند گرفت. کدهای منبع بازی نمونه را در همان دایرکتوری cc65 و در یک پوشه جدا از حالت فشرده خارج کنید. اسکریپت ساخت پروژه از مسیرهای نسبی استفاده میکند، پس در این صورت نیازی نیست کار دیگری انجام دهید – فقط کافیست compile.bat را اجرا کنید. بعد از کامپایل صبر می کند تا شما اگر خطایی رخ داده آنرا ببیند ، و بعد از فشردن یک کلید، در صورتی که یک شبیه ساز داشته باشید که به پرونده های *.nes وصل شده باشد، شروع به اجرای بازی در آن شبیه سازمی کند.
شما می توانید فرایند ساخت پروژه را با ویرایش پرونده ی compile.bat تغییر دهید و یا یک IDE را برای این کار تنظیم کنید. به طور مثال شما میتوانید کلید run در یک ویرایشگر را برای این کار اختصاص دهید یا مثلا در Notepad++ از F5 برای این کار استفاده کنید، ولی شاید نیاز داشته باشید اسکریپت ساخت پروژه را ویرایش کنید و مسیرهای مطلق اضافه کنید.  من تمام این جزئیات و حالات ممکن را توضیح نمی دهم، پس خودتان در مورد روش های انجام آن تحقیق کنید.

کدهای سطح پایین و تنظیمات










در کدهای بازی نمونه تعداد کمی کدهای سطح پایین وجود دارد. شما کمتر نیاز خواهید داشت تا مستقیما با آنها سروکار داشته باشید ، اما در شرایط خاصی شاید نیاز پیدا کنید. پس ایده ی خوبی خواهد بود که مختصرا به آنها نگاهی بیندازیم،  و بفهمیم چرا اصلا آنها وجود دارند. این کدها در پرونده های *.s ، *.lib و *.cfg قرار دارند.
crt0.s – کدهای شروع بکار دستگاه است. این کدها سخت افزار را مقدار دهی اولیه می کند و کتابخانه هایی در آن قرار دارد و چیز های دیگر. در ابتدای این پرونده تنظیماتی قرار دارد، در صورتی که نیاز دارید از تنظیمات دیگری استفاده کنید، مانند استفاده از Mirroring متفاوت یا استفاده از Mapper، باید آنرا تغییر دهید.
runtime.lib – کدهای زمان اجرای زبان C . کدهای پایه ای  و مهم برای اجرای زبان C  مثلا شامل کدهای مورد نیاز برای عملیاتهای ریاضی. این کدها شامل کدهای مورد نیاز برای NES نمی باشند و سفارشی کامپایل شده اند. اگر به هر دلیلی نیاز به تغییر این قابلیتها دارید باید کدهای منبع cc65 را بگیرید و کمی با GNU make بازی کنید تا بتوانید آنرا برای خودتان کامپایل کنید.
nes.cfg – این پرونده شامل پیکربندی کلی حافظه ی  NESمی باشد. در این پرونده پروژه به صورت NROM128 پیکربندی شده. اگر نیاز به NROM256 دارید یا نیاز به RAM بیشتری دارید نیاز به ویرایش این پرونده دارید.
برای تجرید بیشتر سخت افزار و سریعتر شدن عملیاتهایی که به سخت افزار وابسته هستند، یکسری پیاده سازی های سطح پایین از کدهای توابع زبان C در این پروژه وجود دارند. شما می توانید از آنها آزادانه در پروژه های خودتان استفاده کنید، و یا اگر تمام نیاز های شما را براورده نمی کند، می توانید از آن به عنوان نمونه ای برای ساختن کدهای خودتان بهره ببرید. می توانید آنرا یک کتابخانه بنامید، اما در حقیقت کدهای آن به صورت اسمبلی ارائه شده است، برای استفاده از آن در پروژه های NES نیاز دارید که کمی کدهای آنرا بهبود دهید، زیرا ممکن است به تمام توابع آن نیاز نداشته باشد و با حذف کردن کدهای بلا استفاده فضای بیشتری کسب کنید.
neslib.s – کتابخانه ی سفارشی که در بالا ذکر شد.
neslib.h- اعلان توابع کتابخانه ای  سفارشی به زبان C. توضیحاتی در این پرونده وجود دارد که توابع اعلان شده را مستند سازی کرده اند.
همچنین کتابخانه ی سفارشی این پروژه شامل کدهای کتابخانه ای مربوط به صداها و موسیقی های ساخته شده توسط ابزارهایی مانند FamiTone2 به طور مجزا ارائه شده است.
famitone.s –  کتابخانه ی نواختن موسیقی و افکتهای صوتی.
sound.s – کتابخانه ی مربوط به افکتهای صوتی که با استفاده از ابزار nsf2data ساخته شده است.
music.s – نواهای موسیقی که با استفاده از ابزار text2data ساخته شده است.

کدهای بازی و منابع










کدهای بازی در پرونده های *.c و *.h قرار دارد. در مورد این مثال کدهای بازی در game.c قرار گرفته.
همچنین منابع زیادی هم وجود دارد. این منابع در قالبهای متنوعی وجود دارند- پرونده های *.h ( به عنوان nametable) ، پرونده های *.s (به عنوان موسیقی) ، پرونده های *.chr (برای گرافیک) . ممکن است پرونده های *.h و *.s برای هر چیز دیگری نیز استفاده شده باشند، یا ممکن است قالبهای باینری متفاوتی در آنها موجود باشد. معمولا این پرونده ها به وسیله ابزارهای اتوماتیک ساخته شده اند، پس تعجب نکنید که چرا درون آنها حجم زیادی اعداد هست که هیچ توضیحی در مورد آنها وجود ندارد.
چیز زیادی در مورد کدهای بازی نمانده که بگویم – تمام توضیحات در این مقاله ی ارائه شده و توضیحات فراوانی که در درون پرونده ی کدها آورده شده میتواند مورد استفاده شما قرار گیرد.
موارد مهم تری که باید ذکر شود نحوه ی ساختن منابعی است که در بازی استفاده می شود. بخاطر اینکه توانایی کامپایل کردن مشتی کد، برای ساختن بازی کافی نیست. نکته ی با اهمیت دیگر این است که به طور کلی چطور چیزهایی، به غیر از کد، که به برنامه نویسی و ساختن پروژه مربوط می شود را اداره کنیم.

گرافیک










در بازی های NROM دو مجموعه برای گرافیک های 8x8 پیکسل وجود دارد، که کاراکتر، الگو یا الگوهای موزائیکی نیز خوانده می شوند. هر مجموعه شامل 256 تا موزائیک 8x8 می شود. شما می توانید از یک مجموعه برای گرافیک تصاویر پس زمینه استفاده کنید و از دیگری برای اسپیرایت ها. یا شما میتوانید تمام تصاویر زمینه و اسپرایت ها را در یک مجموعه قرار دهید و از یک مجموعه برای هر دوی آنها استفاده کنید، و یک کپی با کمی تغییرات در مجموعه ی دیگر استفاده کنید، با سویچ کردن بین این مجموعه ها می توانید یک پویانمایی با 2 فریم بسازید. این روشی است که من از آن برای پویانمایی در بازی نمونه استفاده کرده ام.
تمام گرافیک مورد استفاده در بازی نمونه بوسیله ی NES Screen Tool از صفر ساخته شده. برای مثال از ابزارهای ویرایش CHR داخل این برنامه استفاده شده. خروجی گرافیکی ارائه شده توسط این نرم افزار با قالب .chr می باشد ، در پروژه ی بازی نمونه با نام tileset.chr می باشد. به وسیله ی این ابزار می توانید پالت رنگ و جداول nametable را نیز ویرایش کنید، یا اینکه می توانید در پرونده های جداگانه ذخیره کنید یا حتی آنها را کپی کنید و در داخل کد های منبع قرار دهید.
حروف بزرگ و اعداد نیز با NES Screen Tool کشیده شده اند. الان مجموعه موزائیک ها بسیار بهم ریخته بنظر می رسند، اما از اول اینطوری نکشیده شده اند. بجای آن ، من برای هر سمبل از صفحه ی موزاییکی 2x3 استفاده کرده ام ، در نتیجه آنها شکل درست خودشان را در مجموعه ی موزائیکی داشته اند. این کار باعث می شود تعداد زیاد از موزائیکها تکراری شوند ویا خالی بمانند. برای صرفه جویی در فضا از ابزار بهینه سازی این برنامه استفاده شده است. برای ساده شدن کار، من ابتدا جداول Nametable برای مرحله ها ، صفحه ی Game Over ،و صفحه ی Well Done را با استفاده از مجموعه موزائیکهای بهینه نشده ساختم، و همچنین جداول nametable اضافی برای اعداد بزرگ. سپس هر جدول nametable را با این مجموعه موزائیک های غیر بهینه پر کردم و سپس از بهینه ساز استفاده کردم – بهینه ساز تمام جداول nametable را دوباره چینی می کند تا بهینه شود و آنرا با یک مجموعه موزائیک های بهینه شده جایگزین می کند. این راه حل مرا از حل کردن پازل ساختن صفحه از قطعات درهم ریخته ی موزائیک ها نجات داد.
اگر نیاز دارید با گرافیک های پیچیده تر و بزرگتر از بازی نمونه کار کنید، ابزار NES Screen Tool زیاد مناسب نیست. در این موارد شما می توانید از برنامه های ویرایش گرافیک عمومی تری برای خلق گرافیک با درنظر گرفتن محدودیتهای NES استفاده کنید، سپس آنرا با استفاده از NES Screen Tool تبدیل کنید و بهبود بدهید. برای اطلاعات بیشتر به مستنداتی که همراه این ابزار موجود است مراجعه کنید.
من اغلب از GraphicsGale به عنوان یک ابزار ویرایش pixel art استفاده می کنم ، گاهی هم از GIMP استفاده می کنم. شما هم می توانید از اینها استفاده کنید یا از هر چیز دیگری، فقط مطمئن شوید که ویرایشگر گرافیکی شما ویژگی هایی داشته باشد که کار شما را آسان تر کند. آن ویژگی ها اینها هستند:
•    یک صفحه ی 8x8 یا 16x16 داشته باشد که به شما امکان دهد که به راحتی تعداد رنگهایی که در هر سلول بکار برده اید مشاهده کنید و به راحتی بتوانید گرافیک را تنظیم کنید.
•    جابجایی به صورت Snap در درون شبکه ، یک ویژگی مفید است که به شما امکان می دهد اشیای گرافیکی را بدون از بین رفتن فواصل آنها جابجا کنید.
•    لایه بندی همیشه یک ویژگی مفید محسوب می شود.
•    کنترول پالت رنگ به وسیله ی index ، این توانایی را به شما می دهد که رنگ های پالت را تغییر دهید و گرافیک را برای تبدیل آماده کنید.

اسپیرایت های بزرگ










سخت افزار گرافیکی NES تنها قادر است اسپرایت های کوچک را نمایش دهد، در حد 8x8 یا 8x16 . کتابخانه ی سطح پایین من فقط از 8x8 پشتیبانی می کند. برای داشتن اسپرایت های بزرگتر نیاز دارید آنها را با استفاده از چندتا اسپرایت کوچکتر بسازید. به این شی جدید ابر اسپرایت ( metasprite) می گوییم.  برای ساختن این ابر اسپرایت با استفاده از کتابخانه ی من نیاز دارید آنها را به صورت یک آرایه از شماره ی موزائیک ها و خصوصیت ها و آفست از نقطه ی اتکا معرفی کنید.
اگر ابر اسپرایت شما اساسا از تعدادی موزائیک که به صورت مستطیلی در یک شبکه ی 8x8 قرار گرفته اند ساخته شده، شما می توانید از NES Screen Tool برای تولید توصیفات آن استفاده کنید. فقط کافی است آنرا مانند یک قسمت از جدول nametable بکشید، سپس آن قسمت را انتخاب کنید، و از گزینه ی Nametable/Copy metasprite استفاده کنید. این برنامه، توصیفات را در کلیپ برد قرار می دهد، و شما می توانید آنرا در کدهای منبع قرار دهید. این امکان وجود دارد که یک کپی قرینه ی افقی هم به صورت اتوماتیک ایجاد کنید- در نتیجه از یک سری از مجموعه های گرافیکی برای اسپرایت های رو به راست و رو به چپ استفاده می شود ولی از دوتا توصیف برای ابر اسپرایت استفاده می شود. در کتابخانه ام این فرض شده که در زمان اجرا برعکس کردن ابر اسپرایت قابل قبول نیست ، چرا که کند تر اجرا می شود و این توصیفات حافظه ی زیادی هم نمی گیرند.
برای ابر اسپرایت هایی که در به صورت مستطیلی نیستند و در یک شبکه منظم قرار نمی گیرند مجبورید توصیفات را دستی بنویسید.

مرحله های بازی










در تمام پروژه های بازیهای NES من که با زبان C نوشته شده اند , همچنین این بازی نمونه، من از یک میانبر استفاده کرده ام و از NES Screen Tool برای ویرایش و ساختن مرحله های بازی استفاده کرده ام. این ابزار برای بازی های ساده خوب جواب می دهد ، شما فقط مرحله ی بازی را رسم می کنید و آنرا به عنوان یک جدول nametable ذخیره می کنید. برای بازی های پیچیده تر به ساختن یک ویرایشگر سفارشی ، یا نوشتن یک اسکریپت تبدیل از یک نقشه موزائیکی، نیاز خواهید داشت.
لطفا به یاد داشته باشید که در این بازی نمونه ای، آرایه ای از نقشه بازی  در حافظه ، دو برابر فضای کمتری نسبت به جدول nametable برای مرحله می گیرد. مراحل بازی با قالب RLE-packek nametable ذخیره می شوند و مستقیما در VRAM از حالت فشرده خارج می شوند، سپس دوباره ردیف به ردیف از VRAM خوانده می شوند تا نقشه بازی ساخته شود. ایجاد اشیای تکراری و قراردادن آنها در نقشه هم در داخل جدولهای nametable قرارداده شده، این مرحله از پردازش حذف شده، و ردیف ها در VRAM نوشته می شوند. این روش زیاد سرراست نیست ، ولی به اندازه ی کافی خوب کار می کند، و نهایتا باعث می شود همه چیز ساده تر شود – نیازی به یک ویرایشگر خاص نقشه های بازی که نقشه ها را دریک قالب بهینه شده خروجی دهد ، و بازسازی نقشه های بازی از روی این قالب بهینه شده در هنگام اجرای بازی را مرتفع می کند.

افکتهای صوتی










تمام افکت های صوتی با ابزار FamiTracker برای فراهم کردن نیازمندی های FamiTone2 ساخته شده اند. نیازمنده های ساخت افکت های صوتی و موسیقی تماما در مستنداتFamiTone2  با جزئیات آورده شده است. به طور خلاصه فرایند ساخت موسیقی و صوت بدین صورت است: شما صداها را در FamiTracker  به عنوان یک پرونده با چند موسیقی می سازید، هر افکت یک موسیقی می شود که با دستور C00 به پایان می رسد. سپس شما پرونده ی NSF را به یک پرونده ی زبان اسمبلی تبدیل می کنید:
nsf2data sounds.nsf -ca65

پرونده ی sound.s حاصل در دایرکتوری پروژه قرار می گیرد، این پرونده همیشه توسط crt0.s به پروژه افزوده می شود. اگر نیازی به صدا در پروژه ی خودتان ندارید می توانید تمام آنرا غیر فعال کنید. غیر فعال کردن صدا باعث می شود تمام کدهای مربوط به آن هم از پروژه حذف شوند.
تعداد افکت های صوتی در این بازی نمونه بسیار کم است، در نتیجه اولویت بندی اجرای صدا ها کاملا نشان داده نشده. تعداد 4 تا کانال صوتی مجازی برای افکت های صوتی وجود دارد، از 0 تا 3 شماره گذاری شده اند. کانال های مجازی افکت با موسیقی و دیگر کانال ها  به وسیله ی حجم صدا میکس می شوند ( قسمت های بلند تر اولویت دارند) ، بجز کانال مثلثی که همیشه همپوشانی پیدا می کند با افکت صوتی بالا ترین شماره. اگر یک افکت جدید در حالی که دیگر افکت ها در حال اجرا هستند، شروع به اجرا کند،  افکت قدیمی باز خواهد ایستاد. پس برنامه ریزی برای اینکه کدام افکت اولویت بیشتری برای نواخته شدن دارد و یا باید دیگر افکت ها را از کار باز دارد، صبر کند یا به کانال دیگری انتقال پیدا کند اهمیت پیدا می کند.

موسیقی










موسیقی هم با استفاده از ابزار FamiTracker برای فراهم کردن نیازمندی های FamiTone2 ساخته شده. تمام صدا ها به صورت یک پرونده با چند موسیقی ساخته شده، پس تمام آنها از یک مجموعه سازهای موسیقی یکسان استفاده می کنند. سپس آنها با ابزارهای تبدیل متن FamiTracker به یک پرونده ی اسمبلی متنی تبدیل می شوند.
text2data music.txt -ca65

اگر ویژگی های FamiTone2 برای شما بسیار محدود است، می توانید بجای آن FamiTracker player را گیر بیاورید. کدهای آن با  کدهای ca65 سازگار است، اما نیازمند آن است که دانش اسمبلی 6502   داشته باشد و مقداری با آن کار کنید، مخصوصا زمانی که بخواهید از افکت ها صوتی هم پشتیبانی کنید. استفاده از آن سه برابر بیشتر فضا اشغال می کند و دو برابر کندتر است. پس تصمیم گیری در این مورد به میزان فضای حافظه ی آزاد و زمان آزاد CPU در پروژه شما بستگی دارد.

سیستمهای PAL و NTSC










یکی از مهمترین عواملی که سیستم های کامپیوتری متصل به تلویزیون به عنوان نمایشگر باید همیشه در نظر داشته باشند، شامل NES نیز می شود، این است که تفاوت های بین NTSC و PAL را بشناسند. بخاطر اینکه نرخ تولید فریم ها اصلی ترین منبع همگام سازی در برنامه های این سیستم هاست. این سیستم ها حدود 17 درصد با هم تفاوت دارند ( 50 و 60 هرتز برای به هنگام سازی عمودی) ، این تفاوت باید به طریقی مدیریت شود. به طور کلی سه راه برای این منظور وجود دارد.
اولین و شاید دقیق ترین راه این است که همه جای برنامه از محاسبات ممیز ثابت استفاده شود، و 2 نمونه از برنامه ساخته شود- یکی با مقادیر ثابت محاسبه شده برای سیستم های NTSC ( مثلا سرعت اشیا) ، و دیگری با مقادیر محاسبه شده برای سیستم های PAL . مقادیری ثابت برای سیستم PAL برابر NTSC * 18 / 15 هستند.
نگهداری از دو نسخه از یک برنامه می تواند موجب دردسر شود، پس راه دوم این است که برنامه ای بسازیم که خودش تشخیص دهد که در چه سیستمی مورد استفاده قرار گرفته است، و با توجه به آن یکی از دو سری مقادیر ثابت را مورد استفاده قرار دهد. این روش اجازه می دهد که فقط یک نسخه از برنامه داشته باشیم، ولی همچنان ناخوشایند است. نه تنها مجبورید تمام محاسبات را با ممیز ثابت انجام دهید، بلکه الان دیگر واقعا مقادیر ثابتی هم وجود ندارد، زیرا آنها بسته به سیستم مورد استفاده تغییر می کنند – پس نیاز دارید که از متغیر بجای ثابتها استفاده کنید، یا مجبورید دو نسخه از برخی قسمت های کد داشته باشید.
کتابخانه ی من از راه سومی استفاده می کند، که خیلی دقیق نیست، اما از معایب دو راه قبلی خالی است. این کتابخانه در ابتدای راه اندازی دستگاه، سیستم مورد استفاده را تشخیص می دهد و اگر روی کنسول هایNTSC  در حال اجرا باشد، از ششمین فریم تصویر صرف نظر می کند. در نتیجه شما فقط باید کدهایتان را برای PAL زمانبندی کنید و روی سیستم های NTSC هم با کمی لرزش در هنگام حرکت کار می کند. این روشی است که در تمام بازی های NES من بکار رفته است ، و شما می توانید آنها را بررسی کنید تا ببینید آیا قابل ملاحظه هستند یا نه. اگر می خواهید از کتابخانه ی من استفاده کنید، ولی دوست دارید مشکل PAL/NTSC را از روش دیگری حل کنید، می توانید پریدن از فریم ها را در تنظیمات crt0.s غیر فعال کنید.
در هر صورت کتابخانه ی من تفاوت سرعت موسیقی را کاهش می دهد ولی سرعت و گام افکت های صوتی را تغییر نمی دهد. یک سازش بین بار پردازشی CPU و میزان استفاده از RAM وجود دارد که من فرض کردم قابل قبول است.  شما اگر خواستید می توانید اینها را نیز تغییر دهید، اما نیاز به تغییراتی در قسمتهای سطح پایین کدها خواهید داشت ، مانند افزودن جدول فرکانس نوت های دوم و شاید کپی دوم از اطلاعات افکت های صوتی.
نکته دیگری که باید به خاطر داشته باشید این است که زمان  VBlank در PAL بسیار بیشتر است، و این تنها زمانی است که با فعال بودن نمایشگر، می توان به VRAM دسترسی داشت. این زمان توسط کتابخانه برای به روز رسانی سیستم استفاده می شود و توسط vram_set_update کنترل می شود و می تواند برای چیزهایی مانند نمایش وضعیت بازی یا اسکرول کردن مرحله مورد استفاده قرار گیرد. اگر می خواهید مطمئن باشید برنامه شما به درستی در هر دو سیستم کار می کند باید برنامه را در NTSC ایرادیابی کنید – اگر در آن کار کرد، کار کردن آن در سیستم های PAL قطعی خواهد بود، اما برعکس آن امکان ندارد.

استفاده از حافظه RAM










یکی از مواردی که در زمان طراحی نرم افزار برای NES همیشه باید به یاد داشته باشید این است که میزان RAM محدودیت بسیاری دارد – فقط 2048 بایت. به علاوه اینکه تمام این مقدار هم برای برنامه ی زبان C قابل دسترس نیست.
512 بایت از RAM توسط CPU  به شکل ویژه ای مورد استفاده قرار می گیرد. این مورد برمی گردد به طراحی CPU و نمی توان آنرا تغییر داد. اولین 256 بایت به عنوان zero page مورد استفاده قرار می گیرد، یک محل ویژه در RAM است و 48 بایت از آن توسط کتابخانه قبلا مورد استفاده قرار گرفته. 256 بایت بعدی به عنوان پشته سخت افزاری CPU بکار رفته . خوشبختانه کدهای تولید شده توسط کامپایلر زبان C تقریبا از این پشته استفاده نمی کند – تقریبا حداکثر 32 بایت حتی در برنامه های پیچیده و بزرگ. این به ما اجازه می دهد تا برخی از بافرهای داخلی را در فضای باقی مانده از پشته قرار بدهیم. کتابخانه ی من از آن برای ذخیره کردن متغیرهای اجراکننده ی موسیقی  و صدا و بافر داخلی پالت رنگ استفاده می کند.
علاوه بر اینها یک 256 بایت دیگر برای بافر OAM استفاده شده تا در هر فریم به وسیله ی DMA اطلاعات آن به PPU فرستاده شود. این روش بخاطر نیازمندی های طراحی PPU بوجود آمده و آنرا هم نمی توان تغییر داد.
دست آخر فقط 1280 بایت از RAM برای ذخیره سازی متغیرها، آرایه ها، بافرها و غیره باقی می ماند. برای مقایسه، یک جدول nametable به اندازه ی 1024  بایت است.
برای آنکه متغیرهای محلی سریعتر کار کنند شما مجبورید آنها را static کنید، ولی این کار باعث می شود که متغیرها فضای RAM را اشغال کنند. به همین دلیل تعریف یکسری متغیرهای جهانی همه منظوره که در جاهای مختلف و چندین باره مورد استفاده قرار گیرند توصیه می شود. متغیرهای جهانی کمی از متغیرهای static  محلی سریعتر هستند.
شما می توانید با قرار دادن تعدادی از متغیرها در zero page که نزدیک 200 بایت آزاد دارد، مقداری از RAM را آزاد کنید. این کار می تواند کدهای شما را سریعتر کند و کمی هم اندازه ی کدهای کامپایل شده را کوچکتر کند. قرار دادن متغیرهای مشترک و جهانی هم در این بخش از حافظه ایده ی خوبی است. می توانید با استفاده از pragma قبل از متغیرهایی که باید در zero page  ایجاد شوند آنها را به این بخش از حافظه منتقل کنید:
Code:

#pragma bssseg (push,"ZEROPAGE")
#pragma dataseg(push,"ZEROPAGE")


اگر می خواهید بقیه متغیرها را به جای معمولی برگردانید باید از این pragmaها استفاده کنید:
Code:

#pragma bssseg (push,"BSS")
#pragma dataseg(push,"BSS")


لطفا توجه داشته باشید در انتشار نسخه جدید cc65 ، اکنون یک پیش نمایش درحال توسعه از آن در دسترس است، باید از این pragmaها استفاده کنید:

Code:
#pragma bss-name (push,"ZEROPAGE")
#pragma data-name(push,"ZEROPAGE")


اگر پروژه شما نیاز به بیش از حدود 1500 بایت RAM نیاز دارد، یک گزینه پیش رو دارد و آن استفاده از 8K حافظه RAM خارجی است. اگر بخواهید نسخه فیزیکی بازی را منتشر کنید، تصمیم گیری برای استفاده از این گزینه زیاد آسان نیست، زیرا باید یک تراشه اضافی RAM به هر کارتریج هم اضافه کنید. برای قابل استفاده کردن این حافظه برای cc65 مجبورید پرونده ی nes.cfg را ویرایش کنید. توضیحات آنرا همان جا مشاهده کنید.

اندازه ی ROM










بخاطر اینکه اندازه ی بازی نمونه بسیار کوچک است، با یک NROM128 کامپایل شده است – یک بانک 16K از PRG ROM و یک بانک 8K برای CHR ROM. عدد 128  به میزان PRG ROM به کیلو بیت اشاره دارد ( 16 * 8 ). برای پروژه های بزرگتر ولی همچنان بی نیاز به Mapper، شما باید از NROM256 بهره ببرید، که رایجتر هستند و دارای دو تا بانک 16K از حافظه های PRG ROM است که در مجموع می شود 32K کد و اطلاعات. برای تغییر پیکره بندی پروژه ، باید تنظیمات crt0.s و nes.cfg را ویرایش کنید – می توانید توضیحاتی که با عنوان NROM256 مشخص شده اند را بررسی کنید.
همچنین می توانید پروژه های بزرگتری را با استفاده از Mapperها بسازید. گرچه من نمی توانم جزئیات بیشتری در این باره بدهم، چون من تجربه ای در این مورد ندارم. می توانید یک تابع بنویسید و به راحتی بانکهای CHR را کنترل کنید. فکر نمی کنم کامپایلر قادر باشد کدهای تولید شده را درون بانکهای PRG قرار دهد و به صورت اتوماتیک بانک سویچ کند، پس باید کدهای شما کاملا در یک بانک ثابت قرار گیرد و از بانکهای دیگر برای دسترسی به اطلاعات استفاده کنید. یا اینکه به یک روشی بانک سویچ را انجام دهید. همچنین می توانید موسیقی و کدهای پخش کننده ی صدا را در یک بانک دیگر قرار دهید، و این نیاز به تغییراتی جزئی در کد های کتابخانه دارد.
بدلیل اینکه کامپایلر cc65 گزارشی از میزان مصرف حافظه ی ROM نمی دهد، من یک ابزار کوچک به نام NES Space Checker برای این کار نوشتم ، که میزان مصرف را به صورت گراف نشان می دهد. ممکن است این ابزار برای شما هم مفید باشد. اگر سعی کنید آنرا روی بازی نمونه بکار برید خواهید دید کمتر از یک چهارم از 16K حافظه ی ROM خالی است.

نوشتن توابع به زبان اسمبلی










اگر زمانی هنگام بهینه سازی کدهای زبان C گیر افتادید و بهینه سازی های انجام شده کمکی به بهبود سرعت و اندازه ی کدها نکرد، مجبورید از گزینه ی باز نویسی دستی آن قطعه از کدها به زبان اسمبلی استفاده کنید. برای این کار باید اسمبلی 6502 را بدانید و پیکربندی RAM را درک کنید تا برای متغیرها فضا داشته باشید. سوالی که پیش می آید این است که چطور یک واسط بین کدهای روتین های اسمبلی و زبان C بوجود آوریم. جواب این سوال در جایی بین مستندات و کدهای منبع قرار دارد، ولی برای صرفه جویی در زمان شما من توضیحاتی برای شما اینجا فراهم کردم.
اولا شما به یک جایی برای قرار دادن کدها نیاز دارید. شما می توانید آنها را در یک پرونده ی جدا قرار دهید، اجازه بدهید آنرا myfuncs.s بنامیم. برای این کار نیاز دارید اسکریپت ساختن پروژه را تغییر دهید ( جاهایی که ca65 فراخوانی شده را بیابید و برای فایل جدید یکی مشابه آنها اضافه کنید)، سپس myfuncs.o را به پارامترهای ld65 قبل از game.o اضافه کنید. معمولا فرض بر این است که هر پرونده شامل یک تابع می باشد ولی می توان تمام توابع را در یک پرونده نیز جای داد. اگر تمایل داشتید حتی می توانید پرونده ی جدید نیز درست نکنید و توابعتان را در neslib.s قرار دهید، فقط به خاطر داشته باشید توابعی که به این صورت در پرونده قرار می گیرند به تمام پروژه اضافه می شوند، حتی اگر هیچگاه فراخوانی نشده باشند.
همچنین نیاز دارید اعلان تابع را نیز بسازید. آنرا در یک کد منبع بگذارید یا در یک پرونده ی سرآیند قرار دهید.
ساده ترین تابع ممکن، تابعی است بدون پارامتر که هیچ مقدار بازگشتی هم ندارد:

Code:
void __fastcall__ func(void);

کد اسمبلی مربوطه نیز چیزی شبیه به این خواهد بود:
.export _func ;makes the function available outside of the file

_func:
    ... ;your code
    rts



حالا تابعی که در ورودی یک پارامتر می گیرد و یک مقدار بازگشتی هم دارد:

Code:
unsigned char __fastcall__ func(unsigned char n);

 .export _func

_func:
    ;n is already in the register A
    ...
    ;put your return value into the register A
    rts



برای متغیر های 16-بیتی شما باید از جفت ثباتهای X:A استفاده کنید بطوری که LSB در A و MSB در X قرار دارد.
مواردی که بیش از یک ورودی به عنوان پارامتر به تابع داده می شود، پیچیده گی زیاد دارد. خصوصیت __fastcall__ باعث می شود آخرین پارامتر در جفت ثباتهای X:A قرار گیرد ، ولی تمام پارامتر های قبلی در پشته ی نرم افزاری قرار می گیرند، که کند است. پس در کل هر زمانی که ممکن است باید از هر گونه پارامتری اجتناب کرد.

Code:
unsigned int __fastcall__ func(unsigned int x,unsigned int y,unsigned char n,const unsigned char *ptr);

 .export _func

_func:
    ;ptr is in X:A
    jsr popa
    ;n is in A
    jsr popax
    ;y is in X:A
    jsr popax
    ;x is in X:A
    ...
    ;put your return value into X:A
    rts


   

ایرادیابی










قسمت های سطح پایین پروژه ی من روی تعدادی پروژه ی واقعی آزمایش شده و اطمینان داده شده که روی سخت افزار واقعی کار می کند. با استفاده از آن شانس نوشتن برنامه ای که فقط روی شبیه ساز کار کند، ولی روی کنسول واقعی کار نکند، کاهش می یابد. اگرچه هنوز شما می توانید چیزی بنویسید که روی سخت افزار واقعی کار نکند. پس آزمایش کردن نرم افزارتان روی کنسول با استفاده از PowerPak ، یا به هر روش دیگری، می تواند ایده ی خوبی باشد. البته آزمایش روی شبیه ساز در طی فرایند توسعه می تواید بسیار مفید باشد. چند تا شبیه ساز مختلف را آزمایش کنید، مجموعه ای از شبیه سازهای خوب Nestopia ، Nintendulator ، FCEUX ، و Mednafen  هستند – این کار کمک می کند که مشکلات دیده نشده ای که شما با استفاده کردن از یکی از شبیه سازهای محبوبتان که اکثر اوقات از آن استفاده می کنید ، شناسایی شوند. انجام آزمایش ها در هر دو سیستم NTSC و PAL هم ایده ی خوبی است، برای مثال شبیه ساز FCEUX در سیستم PAL چیزهایی را نشان می دهد که در NTSC پنهان شده اند.
گاهی مشکلاتی بسیار کوچکی رخ می دهند، یا نیاز است که یک عملی با یک زمانبدی دقیق رخ دهد. برای مثال تنظیم کردن یک پالت رنگ نادرست در یک فریم از تصویر، یا نادرست انجام شدن آزمایش برخورد در برخی شرایط. بیشتر شبیه سازها ویژگی هایی دارند که می تواند به شما برای فهمیدن این چیزها کمک کنند – مانند حرکت آهسته ، حرکت فریم به فریم، ضبط کردن ورودی های جوی استیک.
زمانی که شما چیزی می نویسید که در ظاهر باید درست کار کند، ولی در عمل این گونه نباشد، سرکار رفتن واقعی آن موقع شروع می شود. مشکل اینجاست که برای کدهای زبان C که با کامپایلر به زبان اسمبلی 6502  تبدیل شده اند ابزار ایرادیابی مناسبی وجود ندارد (البته هنوز) – یکی برای ایرادیابی درون NESICIDE هست. معمولا شما فقط به کدهای اسمبلی دسترسی دارید آنهم در برخی از ایرادیابهای شبیه سازها، که زیاد در مورد کدهای کامپایل شده سودمند نیست.
یکی از مواردی که می تواند برای فهمیدن اشتباهات کمی مفید باشد، این است که کدهای منبع C را به عنوان توضیحات در کدهای تولید شده ی اسمبلی خروجی بگیریم. این کار قبلا در اسکریپت ساخت پروژه ی بازی نمونه انجام شده. کامپایلر کدهای تولید شده از پرونده ی game.c را در game.s جایگذاری می کند، و شما می توانید این پرونده را بعد از اجرای compile.bat و قبل از فشردن یک کلید، ببینید. این کار می تواند برای دیدن اینکه کدهای کامپایل شده ی زبان C شما چقدر کارایی دارد هم مفید باشد، و شاید دوست داشته باشید ایده های دیگری را برای بهتر کامپایل شدن، آزمایش کنید.
شما می توانید از چیزهایی شبیه while (1); هم به عنوان یک نقطه شکست استفاده کنید، یا یک افکت صوتی را به عنوان تایید رسیدن کدها به یک جای خاص پخش کنید.
اگر نیاز دارید یک مقداری را برای ایرادیابی خروجی دهید، می توانید آنرا در یک ناحیه ی خالی از RAM بنویسید، معمولا انتهای ناحیه ی zero page برای اینکار مناسب است، و آنرا با استفاده از یک نمایشگر حافظه ی شبیه ساز مشاهده کنید. بعضی از شبیه سازها اجازه می دهند تا آدرس مورد نظر به شکل راحت تری دنبال شود. شما می تواند از اشاره گرهای زبان C برای نوشتن در یک آدرس بهره ببرید:
Code:
*(unsigned char*)0x00ff=1; //write 1 into $00ff, it is the last byte of the zero page



جایگزین های دیگر










در این مقاله فقط روش من توضیح داده شد. برای برخی از ابزارها، کدها، و روشهایی که در این مقاله نام برده شد جایگزین های دیگری وجود دارد، پس خود شما باید انتخاب کنید. من توصیه می کنم که وب سایت Kalle’s Kanister را هم بررسی کنید، مخصوصا پروژه های KNES، و MUSE و غیره. یکی از جایگزین های آینده دار، که از این راه هایی که من در این مقاله توضیح دادم ساده تر هم هست، استفاده از NESICIDE می باشد، آنرا هم بررسی کنید.


برگردان از :

دانلود فایل word مقاله از اینجا
.
Back to top Go down
https://github.com/VahidHeidari
 

برنامه نویسی با زبان C برای بازی های NES

View previous topic View next topic Back to top 
Page 1 of 1

Permissions in this forum:You cannot reply to topics in this forum
Microbaz :: برنامه نویسی اسمبلی 6502-