بین بازی ساز ها یک شوخی هست که میپرسه چرا بازی بسازیم؟ تفریحی؟ برای پول؟ و یا از روی لجبازی یا بخاطر اینکه «هیچکس این بازی خاص که من دوست دارم بازی کنم رو نساخته پس مجبورم خودم بسازم» و این داستان یکی از اونهاست… بازی پاسور مارک
من و مارک و دزفول
من تو دزفول که یه شهر کوچیک استان خوزستانه به دنیا اومدم و بزرگ شدم، توی این شهر شما هر جمع و گروهی برید یکی از تفریحاتشون دور هم جمع شدن و پاسور بازی کردنه، مخصوصا مدل شخصی سازی شده شلم که بهش میگن مارک. من هم مثل بقیه از حدودای ۱۲-۱۳ سالگی پشت دست* نشستم و بازی کردن دیگران رو دیدم تا زمانی که پام به عرصه ادم بزرگها باز شد و بازیکن جدی تری شدم. زمانی که دانش اموز بود گاهی عصر ها و شب ها با دوستام کنار رودخونه دز جمع میشدیم و بازی میکردیم و زمانی هم که برای دانشگاه به تهران نقل مکان کردم همچنان با یسری از دوستام بخشی از زمانمون به مارک بازی میگذشت، حالت کلی این بود که تفریح اکثر گروه های دوستیم کافه و پلی استیشن و گردش بود ولی زمانی که ۴ نفر اهل دزفول دور همدیگه جمع میشدیم شکی نبود که تفریح ما باید مارک باشه.
پشت دست نشستن
فرآیند یادگیری بازی با دیدن بازی کردن بقیه، معمولا پشت سر ینفر که داره بازی میکنه میشینی و سعی میکنی بفهمی چطوری تصمیماتش رو میگیره و سوالاتت رو ازش میپرسی.
اگر یکم راست گو تر باشم و دقیق تر بگم من خیلی طرفدار این بازی نبودم و حتی همیشه هم برای گردش ها و سفر های دوستانه مون شرط میزاشتم که تمام زمان نباید به مارک بازی بگذره، حتی یادمه یبار تو سفر بوشهر وقتی به مقصد رسیدیم بچه ها مارک شروع کردن و تا نصفه شب ادامه دادن و منم دیگه عصبی شده بودم و یه بحث کوتاهی هم شکل گرفت.
اما شرایط همیشه یکسان نیست، چرخ روزگار چرخید و من مهاجرت کردم، بعد از مهاجرت دیگه مارک بازی نکردن یک انتخاب نبود و به یک اجبار تبدیل شد، دوستام زنگ میزدن و صحبت میکردیم و چقدر که دلم تنگ میشد! دوست داشتم همون لحظه برگردم و حتی تحمل کنم که صب تا شب بازی کنن ولی خودمم تو اون جمع و محیط باشم. میدونستم که شدنی نیست و شرایط هم حالا حالا ها جوری نیست که من بتونم برگردم. نهایتا یادم نیست ایده اش کی به سرم خورد، ولی طرفای فروردین ۱۴۰۳ مصمم تر شدم که بخشی از وقتم رو به درست کردن این بازی اختصاص بدم و همونجور که میدونید سخت ترین کار برای این تصمیم ها خود تصمیم گیری نیست بلکه شروع، تعهد و ثبات اون کار و ادامه دادن مسیره.
کار بدون فکر، شکست مزبوحانه
تو همون تب و تاب و هیجان اولیه ایده بودم که به ذهنم اومد یک بازی کامپیوتری ترمینالی به سبک بازی های قدیم درست کنم، راستش الان خندم میگیره که اولین تصمیمم این بوده ولی خوب اون زمان برای ذهن من این خیلی جالب و خفن بنظر میومد و البته که عطش یادگیری این رو هم داشتم پس رفتم سراغ درست کردن بازی ترمینالی با TypeScript، یکمدتی هم باهاش ور رفتم و راستش رو بگم ظاهر اولیه اش هم درومد ولی خوب تهش به این نتیجه رسیدم که حالا بیا اصلا درستشم کردیم، کی میاد اینو بازی کنه؟ همه دوستامو که نمیتونم مجبور کنم برای هربار بازی کردن کامپیوتر رو باز کنن و کار کردن با ترمینال رو بهشون یاد بدم، این شد که پرونده این روش رو همونجا بستم! خلاصه که زیر یک هفته و خیلی سریع شکست خوردم!

کدش رو میتونید اینجا پیدا کنید: GitHub
بیا درست فکر کنیم
بعد از اون ایده احمقانه تصمیم گرفتم درست فکر کنم و مثل همه پروژه هایی که انجام دادم براش برنامه ریزی درست انجام بدم. اولین قدم از نظر من و خیلی های دیگه تو طراحی و توسعه هر نرم افزاری ستون های اون پروژه هستن. شما باید یکسری ستون های اصلی داشته باشید که به اونها پایبند باشید، قانون کلی و محکمی برای اینها نیست و کاملا وابسته به پروژه، اهداف و تمایلات شخصی هستن و حضورشون باعث میشه شما خیلی راحت تر تصمیم گیری های کوچیک و بزرگ رو انجام بدین.
اگر با ستون های طراحی اشنا نیستید بزارید براتون یک مثال بزنم، فرض کنید یکی از ستون های پروژه شما «سریعترین راه حل» باشه و شما در بخشی از طراحی سیستم به سیستم ذخیره سازی موقت داده ها (Caching) نیاز پیدا کنید، طبیعتا برای این مساله پاسخ های متفاوت و ابزار های زیادی وجود داره، از ساده ترینش که درست کردن یک Map در کد پروژه خودتون بگیرید تا بالا اوردن Redis و Memchached و یا کلی ابزار دیگه، بیاین فرض کنیم برنامه نویس ما با هیچکدوم از این ابزار ها آشنایی نداره، پس حالا مسیر روبروش میشه اون Map ساده یا باید وقت بزاره برای یادگیری یکی از این ابزار ها. انتخاب و یادگیری این هم ساده نیست چون یک تصمیم دیگه هم لازمه که اصلا کدوم یکی از این بررسی بشه، اونی که مناسب تره پیدا بشه و نهایتا هم یک گواه اثبات (چه کلمه سختی! همون proof of concept) هم بزنه تا نهایتا راه حل مناسب رو انتخاب و پیاده سازی کنه. دیدین چقدر پیچیده شد دیگه؟ حالا تو این شرایط برمیگرده به ستون ها و میبینه که یکی از ستون ها «سریعترین راه حل» بود پس حالا دیگه بحثی نمیمونه، سریعترین راه حل یعنی همون برنامه نویس خودش یچیزی تو پروژه بزنه و فعلا کار جلو بره تا بعدا اگر به مشکل یا چالشی خورد ارتقاعش بده. حضور ستون ها در پروژه ها سرعت تصمیم گیری و نتیجتا توسعه شمارو بشدت بالا میبره و راحت میکنه. اگر تا حالا روی پروژه بزرگ کار نکرده باشید نمیتونید تصور کنید که در طول پیاده سازی حتی ساده ترین سیستم ها ادم ها به چه مسائلی بر میخورن و در نبودن این ستون ها پروژه به چه شکل های عجیب و غریبی قفل میشه.
خوب حالا که اینهمه حرف زدم، ستون های پروژه من چی بودن؟
- اول: این یک پروژه تفریحیه و مهم نیست چقدر طول بکشه، اگر به موعد تاریخ هایی که برای خودم میزارم نرسیدم اشکال نداره و برای من لذت بردن از ساخت و عرضه این پروژه مهمه -> این باعث میشه برای پروژه استرسی متحمل من نشه و برای تاخیر خوردن عذاب وجدان نداشته باشم.
- دوم: هدف من از درست کردن این بازی یادگیریه، پس هرجا که دیدم فرصتش هست پروژه رو پارک میکنم یه گوشه و میرم اون چیزی که لازمه رو یاد میگیرم -> این باعث میشه من صرفا به چیزهایی که دانششون رو دارم بسنده نکنم و اگر فرصتی برای یادگیری چیزی که نمیدونم و دوست دارم که بدونم و حوصله دارم که وقت بزارم میرم سراغش
- سوم: نیاز نیست تمام نیاز های مخاطب برطرف بشه -> در مسیر پروژه ایده ها و نظرات متفاوتی برای پروژه میاد که ادم اصولا دوست داره همشون رو پوشش بده، ولی این لیست سر درازی داره و باعث میشه ادم خیلی درگیر بشه، بنابراین هرکس که بازخورد داد یادداشت میکنم ولی به این معنی نیست که اصلاح یا پیاده سازی هم میکنم
- نیاز های فعلی باید پوشش داده بشن و نباید اینده نگری زیادی بکنم -> این باعث میشه از همون اول به این فکر نکنم که اگر ۱۰ هزار بازیکن همزمان دارن بازی میکنن سیستم قراره چه بلایی سرش بیاد، بلکه فعلا روی پوشش ۱۰ بازیکن همزمان تمرکز میکنم و اصطلاحا over engineering نمیکنم و بدنبال راه حل برای مسائلی که هنوز وجود ندارن نمیگردم
همونطور که میبینید ستون های خیلی خاص و عجیبی نبودن، صرفا یک نقشه راه به من میدن برای تصمیم گیری راحت تر، شما چه ستون هایی توی پروژه هاتون میزارید (به سبک اینفلوئنسر های اینستاگرامی)؟

منطقی فکر کن
ازونجایی که همچنان اون روحیه انجام این پروژه از بین نرفته بود تصمیمم این شد که بازی رو با React Native درست کنم، چرا RN؟ چون ابزاریه که مدتها باهاش کار کردم و کاملا بهش مسلطم، پس میتونم سریع پیش برم، همچنین مطمئنم همه موبایل تو جیبشون دارن پس کاربر هام نیاز به اموزش برای نصب و استفاده از این بازی رو ندارن. تصمیم گرفتم همین اول کار طراح و گرافیست وارد پروژه نکنم و خودم صفحات و گزینه هارو بدون هیچ طراحی خاصی بزنم و سعی کنم یه نمونه اولیه از بازی داشته باشم. مدتی طول کشید تا این مرحله دلی کد زدن تموم بشه ولی نهایتا ظاهر کلی کار اماده شد و پایه خیلی خوبی برای ادامه کار بود:

حالا پروژه به مرحله ای رسیده بود که باید بخش انلاین و منطق کلی بازی درست میشد، شیوه کار هم اینطوریه که یک اتاق ۴ نفره باید درست بشه، بازیکن ها به همدیگه وصل بشن و کارت ها پخش بشه و بازی، طبیعتا هیچکس نباید بدونه دست شخص دیگه چیه، بازی turn based هست و کسی نمیتونه چنتا کارت پشت سر هم بندازه، کسی نمیتونه کارتی که قبلا انداخته دوباره بندازه و الی اخر، اما Multiplayer رو چطوری پیاده سازی میکنن؟ این اولین تجربه من برای همچین کاری بود و فرصت یادگیری خوبی بهم داد.
بصورت کلی برای یک بازی شبیه به این دو روش اصلی هست (و البته روش های دیگه که معمولا بر پایه همین دو تا هستن):
۱- یک بازیکن بعنوان Host انتخاب میشه، بازیکنان دیگه Peer to peer به Host وصل میشن، وظیفه کامپیوتر Host مدیریت بازی و کنترل هست، یعنی مشخص میکنه نوبت کیه، کارت هارو پخش میکنه، جلوگیری از تقلب، اطلاعات و حرکات هر بازیکن رو به بازیکن های دیگه بده و Server بازی ما حساب میشه.
۲- یک کامپیوتر سرور وجود داشته باشه که همه بازی رو مدیریت کنه و همه بازیکن ها به این سرور وصل بشن و اون کار Host رو انجام بده
سوالات مهم این بخش که باید بهشون جواب داده بشه:
- اطلاعات چطوری بین بازیکنان مختلف گردش میکنن، کی به کی وصل میشه
- کی بازی رو مدیریت میکنه، اگر اهل اتاق فرار باشید به این کار گیم مستر میگن
میتونید حدس بزنید که هر کدوم از این روش ها معایب و مزایای خودشونو دارن و بستگی به هدف و کار شما یکی از اینها میتونه جوابگو هدف شما باشه. برای این پروژه من سراغ روش دوم رفتم، به چند دلیل که مهم ترینش این بود که بازیکن Host ممکنه اینترنتش قطع بشه (مخصوصا در کشور ما) و یا کلا بیخیال بازی بشه (لغو بازی) در این صورت یه عملیات تغیر Host باید انجام بشه که پیاده سازی پیچیده ای داره یا بازی همونجا برای باقی هم قطع بشه که بنظرم کار جذابی نبود مخصوصا که اینترنت ایران پایداری خوبی نداره.
وقتی روش کار انتخاب شد حالا وقت پیاده سازی این سرور نزدیک به بلادرنگ (Real time) بود. راحت ترین و سریعترین راه استفاده از سرویس های Firebase بود ولی این گزینه هم چون برای دوستای من تحریمه نمیشد و عاقلانه نبود که از همین الان کاربر ها به VPN نیاز پیدا کنن هرچند حدس میزنم بزودی خود بازی فیلتر میشه. گزینه بعدی که علاقه شخصیم هم بود نوشتن این قسمت با Go و استفاده از Websocket برای ارتباط Client و Server بود، ولی بیشتر با خودم فکر کردم دیدم توی هر دوی اینها دستی بر آتش دارم ولی حرفه ای نیستم و یادگیری ۲ چیز جدید همزمان میتونه دیگه زیادی چالشی باشه پس سمت سرور رو با Node که تجربه بالایی دارم پیاده سازی میکنم و برای سوکت هم از SocketIO استفاده میکنم. اصلی ترین علت انتخاب SIO پشتیبانی فعال و حجم استفاده بالایی که در پروژه های مختلف از سالهای پیش داشته و طبیعتا جوابشو پس داده، بقول نرم افزاریها battle-tested هست، پس یادگیری این بخش رو صرفا به SIO محدود کردم و نوشتن بکند شروع شد. اما این پروژه نیاز به دیتابیس هم داره، مگه نه؟ اون روز ها روزانه مقدار زیادی با MongoDB دست و پنجه نرم میکردم و میدونستم که جواب نیازهای فعلی من رو میده مخصوصا که من Schema های مشخصی ندارم هنوز و Mongo (اصولا NoSQL ها) این قدرت رو به شما میدن که داده هارو بدون مشخص کردن ساختار مشخصی بریزید همینجوری هل بدین تو دیتابیس، این انعطاف پذیری برای من مهم بود چون هنوز مدل نگهداری اطلاعات تو پایگاه داده برای خودمم شفاف نشده بود و دوست نداشتم پروژه رو اینجا متوقف کنم و مدل های کامل و دقیق رو در بیارم به عبارت دیگه این «پشت وانتی» کار کردن منو جلو مینداخت، از طرف دیگه هم بیشتر کار کردن با MongoDB دانش من رو هم افزایش میداد و برای کارم هم مفید بود پس این تصمیم یک تیر و چند نشون شد.
چون از قبل با سوکت و SIO سمت کلاینت کار کرده بودم یادگیری سوکت و SIO سمت سرور زمان زیادی نگرفت و توسعه backend سریع بود. مرحله بعد هم سیم کشی های بین اپلیکیشن و سرور بود که مجموع این دو مرحله ۵ ماه زمان گرفت. با اینکه این زمان زیادی بنظر میاد ولی برای من که یک روز از اخر هفته و عصر/شب بعضی از روزهای هفته روی پروژه کار میکردم قابل قبول بود. این بین به تفریح و سفر هم نیاز داشتم و نمیتونستم تمام وقت روی پروژه باشم چون خسته و کلافه میشدم و اینم میتونست باعث بشه پروژه رو توقف کنم، بنظرم توی انجام side project ها داشتن همچین موازنه ای مهم و لازمه.
بالاخره همه چیز آماده بود برای تست اولیه بازی توسط دیگران، اما احساس میکردم یچیزی این وسط کمه، خود بازی خوب بود و کار میکرد ولی بخش زیادی از لذت مارک کل کل و کری خوندنه و یک اپلیکیشن که یسری کارت بهت نشون میده و روی صفحه کلیک میکنی حس زنده ای نداره، پس تصمیم گرفتم انتشار این نسخه رو عقب بندازم تا وقتی که تماس صوتی به بازی اضافه کنم مخصوصا که دوست داشتم اولین واکنش باقی رو هم بشنوم. راهکار معقولش استفاده تلگرام یا واتس اپ یا اینستاگرام برای تماس بود ولی خوب تو اون زمان همه اینا فیلتر بودن و البته من دوباره یچیز جدید پیدا کرده بودم که میتونستم یاد بگیرم.
تئوری راه اندازی سیستم تماس گروهی از طریق اینترنت کار آنچنان سختی نیست، تمام کار اینه که buffer صدای میکروفون دیوایس کاربر رو بگیری، ترجیحا فشرده سازی و encrypt کنی، به سرعت برسونیش به کاربر دیگه و decrypt و پخش کنی. حالا مشکل چیه؟ من هیچکدومو بلد نیستم :)
WebRTC
من اسم WebRTC رو زیاد شنیده بودم ولی هیچوقت فرصت و نیاز نشده بود که باهاش کار کنم، وقتی که داشتم دنبال راه حل برای تماس صوتی گروهی با استفاده از WebRTC میگشتم سه تا راه جلوم سبز شد:
۱- استفاده از سرویس های اماده تماس صوتی (call as a service) مثل getstream که SDK خودشونو میدن و تقریبا همه چیو مدیریت کردن برای شما
۲- استفاده از WebRTC و P2P
۳- استفاده از WebRTC Server و ارتباط همه Client ها به یک سرور
ازونجایی که استفاده از ابزار های اماده اینجا بهم حال نمیداد و حالا که وقت و انگیزه هست چرا بجای ابزار های اماده نرم سراغ اینکه درست و حسابی یاد بگیرم انتخابم مورد دوم و سوم شد. این شد که با Janus اشنا شدم، ابزاری که با C نوشته شده و بعنوان WebRTC Server عمل میکنه، کارش اینه که شما یک سرور میزارین وسط که همه Client ها به اون وصل میشن، صدا و تصویر رو میفرستین براش و این با استفاده از ابزار های خودش میتونه صدا و تصویر رو کم حجم و با همدیگه ترکیب کنه و یدونه خروجی نهایی بده، یعنی بجای اینکه هر بازیکن با ۳ بازیکن دیگه P2P بزنه و صدای خودشو برای اونا بفرسته و صدای همه اوناو دریافت کنه کافیه که یک ارتباط به Janus بزنه و بافر صداهای ترکیب (mix) شده رو دریافت کنه، طبیعتا میتونید حدس بزنید برای اینترنت ما این روش بهتر جواب میده چون نیازی نیست صدارو برای ۳ نفر دیگه اپلود کنی. یاد گرفتن و درک WebRTC، یادگیری Janus و راه اندازیش و پیاده سازی های سمت اپلیکیشن از من حدود یکماه و نیم وقت گرفت ولی نتیجه مطلوب بود و من بالاخره تماس درون برنامه گروهی داشتم. امیدوارم یروزی فرصت کنم درباره این تجربه هم بیشتر بنویسم.
اولین بار
بدون اینکه به دوستام بگم این چیزی که فرستادم چی هست اولین نسخه رو بهشون دادم، یدونه قفل صفحه هم که از سمت سرور مدیریت میشد براش گذاشته بودم و اونها هم منتظر بودن ببینن این چی هست، وقتی همشون نصب کردن قفل رو برداشتم و به محض باز شدن خیلی شوکه شدن و اولین بازخورد هاشون بامزه و باحال بود، اونشب یکی دو دست بازی کردیم و یکی از باحال ترین لحظات زندگی من بود، بعد از حدود ۱.۵ سال میتونستم دوباره با دوستانم بازی کنم.
این نسخه اشکالات زیاد داشت و خیلی از قوانین پیاده سازی نشده بود ولی همون بازخورد هایی که اونجا گرفتم برام مشخص کرد که مسیر رو دارم درست میرم و احتمالا ادمهای بیشتری هم از این بازی لذت ببرن. نکته جالب اینکه این ویژگی تماس گروهی الان وجود نداره و فکر هم نمیکنم دیگه اضافه اش کنم، خروجی اون زمان و تلاش تماس صوتی کلای برای استفاده در کمتر از ۱۰ تا بازی شد و حذف شد، چیزی که برای من موند صدای ضبط شده اولین بازی و تجربه کار با WebRTC و Janus هست.
پختن پروژه
زمانی که تعدادی بازی انجام شد برای خودم یک برنامه بلند مدت تر نوشتم از ویژگی هایی که باید اضافه بشن و مشکلاتی که باید حل بشن، تازه اینجا زمانی بود که یک بورد Trello درست کردم و کارهارو مشخص تر و ساختار یافته تر انجام میدادم. از این برنامه ریزی تا پیاده سازی مواردی که بنظرم مهم بودن حدود یکی دو ماه زمان برد و حالا بازی اماده بود، بازی ای که کار میکرد ولی همچنان زشت بود و من احساس کردم الان وقت مناسبیه برای اینکه بدنبال یک طراحی درست و حسابی باشم. پیدا کردن یک طراح و گرافیست خوب برای این پروژه چالشی بود چون این روزها اکثر طراح ها در سبک Minimalist کار میکنن و بنظر من این پروژه طراحی ها و گرافیک ها سنتی تر بیشتر بهش مینسشت و پیدا کردن گرافیستی که اینکاره باشه برای من اسون نبود.
بعد از مدتها گشتن، پیام دادن به ادمهای متفاوت و صحبت ها نهایتا عرفان رو پیدا کردم، عرفان یک Game Artist کاربلد هست و در نمونه کارهاش طراحی هایی شبیه چیزی که من دنبالش بودم داشت، اول لینکدین پیام دادم و یک هفته صبر کردم ولی جوابی نیومد پس ایمیل زدم و خوشبختانه ایمیلم رو دید و نهایتا موافقت کرد که طراحی این کار رو انجام بده، منم یک فایل فیگما از تمام صفحات بازی فعلی و صفحاتی که وجود ندارن درست کردم همچنین یک Moodboard با کمک همدیگه ایجاد کردیم که ذهنیت من از طراحی رو بیشتر درک کنه و نهایتا کار رو شروع کرد و من برای مدتی زمان ازاد داشتم.

هرچند که سمت سرور رو نسبتا ساده و تمیز پیاده سازی کرده بودم ولی کد های پروژه RN بشدت کثیف و غیر قابل نگهداری و توسعه بودن، البته به خودم خورده نمیگیرم چون تا اینجا همه چیز فاز حدس و تست داشت و هدف اصلی صرفا درست کردن نسخه قابل استفاده بود. ازونجایی که همیشه به یادگیری جدی تر موتور های بازی سازی علاقه داشتم تصمیم گرفتم بجای باز نویسی با RN نسخه جدید رو با یکی از Game Engine های روز پیاده سازی کنم. طبیعتا Unreal زیادی بود، Unity هم بخاطر تصمیم های چند سال اخیرش حس خوبی بهم نمیداد، دو گزینه باقی مونده برای من Phaser و Godot بودن که هر کدوم مزایا و معایب خودشونو دارن. با علم به محدودیت پشتیبانی کامل از WebRTC (و طبیعتا اگر نمیتونستم راه حل درستی براش کنم باید بیخیال تماس صوتی میشدم) Godot انتخاب شد و یادگیریش در دستور کار قرار گرفت! بنظرم خوبه که بگم من قبلا تجربه کار با Construct و Unity و کلا بازی سازی رو در حد کم ولی مناسبی داشتم و این آشنایی به دنیای موتور های بازی و ساز و کارشون به من برای یادگیری سریعتر Godot خیلی کمک کرد. تصاویری از پروژه های قدیمی تر من:


پیاده سازی در Godot
یادگیری Godot سر راست بود، زبان برنامه نویسی اسکریپتی خودش به اسم GDScript رو داره که کاملا شبیه Python هست، البته پشتیبانی رسمی از #C هم داره. تصمیم اولم استفاده از #C بخاطر وسعت بیشتر ابزار هاش مثل کتابخونه SocketIO بود ولی تو تست هام مشخص شد که برای Godot بهینه نشده و خود کد های #C هم گاهی برای کامپایل اذیت میکردن و سرعت کارم پایین میومد پس تصمیم گرفتم با GDScript پیش برم و هرچیزی نیاز داشتم خودم درست کنم. ازونجایی که SocketIO برای Godot وجود نداشت خودم یه نسخه حداقلی ازش خودم نوشتم (و اینجا منتشر کردم). روند کار هم این بود که Spec کامل SocketIO رو بخونم و طبق اون و سورس کد Typescript موجود نسخه Godot اون رو پیاده سازی کنم و میتونید حدس بزنید که این بخش چه درک عمیق تری از شیوه کار SIO به من داد.
تنها بخشی که تلاش کردم ولی نتونستم پیاده سازی کنم همون تماس صوتی بود که از قبل براش اماده بودم، با این کتابخونه میشه buffer صدارو در Godot گرفت (البته یسری مشکلات روی اندروید داره که نیاز داره template های اندروید رو خودم کامپایل کنم) اما مشکل ارسالشون به سرور/کاربرهای دیگه بود چون WebRTC فعلی پیاده سازی شده در Godot از Media ها پشتیبانی نمیکنه و صرفا DataChannel داره، اولین تلاشم با MQTT بود که سرعت کافی رو نداشت (طبیعی هم هست بخاطر طبیعت بر پایه TCP بودن این پروتکل)، بعد به این نتیجه رسیدم که از طریق DataChannel دیتاهارو منتقل کنم، خوشبختانه Janus از این پشتیبانی میکرد ولی کتابخونه Janode (که یک پکیج NodeJS برای مدیریت Janus هست) هنوز از Text Room (بر پایه DataChannel) پشتیبانی نمیکرد. چند روز زمان برد تا Text Room رو بنویسم و تونستم صدارو راه بندازم. روش کار اینطوری بود که بجای Audio Room یک Text Room درست میکردم و کاربر ها به اون اتاق وصل میشدن، اینجا هر دستگاه buffer صدای خودش رو base64 میکرد و برای Janus میفرستاد و باقی هم اینو میگرفتن و decode میکردن و پخش میشد، اما پرفرمنس بشدت افت میکرد (مخصوصا روی گوشی های مدل قدیمی تر) و تاثیرش غیر قابل چشم پوشی بود، نهایتا شکست رو فعلا پذیرفتم و تماس صوتی رو کلا حذف کردم تا در زمان مناسب تر سراغش برم. نکته جالب اینکه برنامه داشتم پیاده سازی Text Room رو برای Janode بفرستم ولی درگیر کارهای دیگه شدم که تا چند وقت پیش دیدم بالاخره خودشون پیاده سازی کردن، درسی شد که اگر کدی نوشتم زودتر منتشرش کنم.
کار عرفان نهایتا به بهترین شکل و با رضایت کامل از سمت من تموم شد و حالا وقت پیاده سازی بود. درست کردن تمامی صفحات و طراحی ها در Godot حدود ۲.۵ ماه زمان برد، بعد از یکم تست ظاهری رفتم سراغ وصل کردن بازی به سرور، ویژگی هایی که بکند کم داشت رو اضافه کردم و همه سیم هارو وصل کردم، حالا دیگه میشد بازی کرد و لذت برد.
با اینکه وقت پخش کردن بازی بین alpha tester ها بود ولی میدونستم کور بودن درباره اینکه کاربر هات چطوری از برنامه استفاده میکنن یکی از اشتباه ترین کارهای ممکنه، برای همین تصمیم گرفتم یک سیستم Logging/Tracking و Crash Analytics هم به پروژه اضافه کنم که وقتی کسی به مشکل میخوره بتونم از اطلاعات جمع شده استفاده کنم، مشکلی که وجود داشت این بود هیچکدوم از ابزار های معروف (مثل Sentry و Firebase) از Godot پشتیبانی نمیکردن، سیستم های دیگه هم یا پولی بودن و یا از ساز و کارشون مطمئن نبودم، نهایتا با خودم فکر کردم که چه لحظه خوبی برای تجربه و یادگیریه و این شد که سراغ Go رفتم. یک سرویس جدا به اسم Track با Go پیاده سازی کردم که track event های کلاینت رو در یک دیتابیس SQLite ذخیره کنه، این اطلاعات اونقدرا هم مهم نیستن و هر چند وقت با Cron همشون حذف میشن، هدف اصلی من داشتن یه راه برای دیدن user intraction ها و پیدا کردن مشکلاتی که برای کاربر ها پیش میاد بود. حالا پروژه اماده بود، هم کلی زیباتر شده بود و هم کلی ویژگی های جدید بهش اضافه شده بود. تنها چیزی که کم داشت یک هوش مصنوعی برای بازی کردن با کامپیوتر بود که اون لحظه بنظرم خیلی حیاتی نمیومد، تصمیم گرفتم که بین تعدادی از دوستام بازی رو پخش کنم.
سلام دنیا و فراتر از آن
تو اینستاگرام استوری گذاشتم که کی دوست داره پروژه رو تست کنه و حدود ۳۰ نفر اونروز علاقه نشون دادن، طی یک هفته هم ۳۰ نفر دیگه دوست و اشنا و گاهی دوستان دوستانم پیام دادن و به اونها هم دسترسی دادم، تست بازی رسما شروع شده بود و هر روز پیام و وویس های متفاوتی از اشکالات و نیازمندی ادمها میگرفتم، همه این بازخورد هارو به بورد ترلو اضافه و دسته بندی و اولویت بندی میکردم. دو تا گروه واست اپی هم درست کردم که با گروه های مختلف بتونیم هماهنگ کنیم و بریم بازی کنیم. گروه دوستانم هنوز هم وجود داره و هر از گاهی ینفر نقطه میفرسته، نقطه یعنی من هستم برای بازی و وقتی ۴ نفر پشت سر هم نقطه میفرستن میرن بازی میکنن. از اونروز اول تا همین امروز دیدن این نقطه ها و امار بازی ها برای من لذت بخشه و حس خوبی بسیار خوبی داره.

برای هوش مصنوعی بازی دو نفر از دوستام وقت گذاشتن و یسری خروجی داشتن ولی به پختگی کافی نرسید و نهایتا درگیر مهاجرت و درس و دردسر های زندگی شدن و متاسفانه AI متوقف شد. با کمک ChatGTP و یسری بازی اوپن سورس مشابه (حکم و شلم) یک الگوریتم انتخاب با کلی if/else و جدول ارزش نوشتم که فعلا خوب کار میکنه، با اینکه یسری اوقات خیلی احمق میشه ولی کار راه اندازه.
بعد از حذف کردن تماس صوتی چون دوست نداشتم پروژه بی روح باشه با یک گوینده محلی ارتباط گرفتم و قرار شد برای پیامها و اتفاقات درون بازی یکسری صدا ضبط کنه، چیزی که از بازی Stronghold و اون دوبله خاطره انگیر دارینوس الهام گرفته بودم، این بخش کار هم بسیار خوب درومد و یکی از امکانات مورد علاقه خودمه. اینجوری شد که بعد از یک دوران تست الان به یک نسخه پایدار (تر) رسیده و بنظرم حق مطلب ادا شده.
نهایتا یک VPS گرفتم و Caprover نصب کردم و دیتابیس و کد های سمت سرور همه اونجاس، دیاگرام فعلی این مدلیه:

اینده
الان که این پست رو مینویسم پروژه روی حالت Feature Lock هست و من چیزی اضافه و کم نمیکنم بغیر از یک استثنا که منتظر انتشار یه نسخه پایدار از Sentry Godot هستم که اون پروژه Track رو خاموش کنم و همه لاگ هارو توی Sentry جمع کنم، بعد از اون بازی رو کاملا منتشر میکنم. فعلا هم برنامه ای برای توسعه بیشتر و یا درامد زایی ندارم و بنظرم پرونده این بازی اینجا برای یکسال بسته میشه، تو این مدت میتونم بازخورد هارو جمع اوری کنم و مشکلاتی که خیلی حیاتی و یا روی اعصاب هستن رو اصلاح کنم، بعد از بازه تصمیم میگیرم اینده پروژه چی میشه ولی من به هدف نهایی این پروژه برای خودم رسیدم. حدس من اینه بعد از انتشار این بازی نهایتا ۳۰۰ تا کاربر میگیره و روزانه ۳ الی ۵ بازی انجام میشه، بعدا این بخش رو اپدیت میکنم و میگم پیش بینیم چقدر به واقعیت نزدیک بود.
تشکرات
ممنون از سارا که فضای مورد نیاز برای کار کردن روی این پروژه رو فراهم کرد و اسپانسر معنوی کار بود و هست. ممنون از علی برای ترجمه لری، محمد و حسین و امید برای کمک در انتخاب کلمات درست دزفولی، از دایی علی بابت روزانه چند بار بازی کردن و بازخورد های مهم و تاثیر گذارش، سینا، مجتبی، محمد، علیرضا، مهدی، علی، حسین، پرهام و کلی دوست دیگه که منو تو تست کردن این بازی کمک کردن و بازخورد های خیلی خوبی به من دادن. از همتون ممنونم. بریم بازی کنیم :)
امیدوارم شما هم پروژه هاتون به سرانجام هایی که دوست دارین برسه.
پایان
EOF
تاریخ انتشار: ۲۲ آبان