برنامه نویسی

جاوا اسکریپت پرتوهای سه بعدی: کروم کارآمدترین مرورگر است

پیوندها:

صحنه بازیصحنه بازیصحنه بازی

معرفی

به‌عنوان یک توسعه‌دهنده فول استک، من دائماً به دنبال کارها و پروژه‌هایی می‌گردم تا مهارت‌هایم را واضح نگه دارم و کنجکاویم را برآورده کنم. آخرین ماجراجویی من مرا به دهه 90، به دنیای ریخته گری پرتوهای سه بعدی برد. با الهام از آثار کلاسیک مانند Wolfenstein 3D، من تصمیم گرفتم این تکنیک را در جاوا اسکریپت خالص، بدون تکیه بر توابع سه بعدی بافت بوم پیاده کنم. این پروژه به سفری جذاب از طریق پیچیدگی های توسعه بازی و عملکرد مرورگر تبدیل شد.

پخش پرتو

چالش

ریخته‌گری پرتو در سبک بازی‌های اوایل دهه 90 شامل پرتاب کردن پرتوها از دید بازیکن، یکی برای هر پیکسل به صورت افقی در سراسر صفحه است. پرتوها در فواصل یک کاشی حرکت می کنند و جلوه ای سه بعدی ایجاد می کنند. علیرغم اینکه هیچ پیشینه ای در توسعه بازی نداشتم، مشتاق بودم با چالش هایی مانند رندر اسپرایت، حرکت بازیکن و تعامل با محیط و اجسام متحرک مقابله کنم.

پخش پرتوپخش پرتو

چرا جاوا اسکریپت؟

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

    const ctx = canvas.getContext('2d', {
      alpha: true,
      willReadFrequently: true
    });
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

سفر

کاری که به عنوان یک پروژه ساده با کنجکاوی آغاز شد، به سرعت به یک تلاش تمام عیار تبدیل شد و شش ماه از وقت من را گرفت. چالش بهینه سازی عملکرد به ویژه جذاب بود. من مکرراً کدم را بازبینی می‌کردم، عملیات اضافی را حذف می‌کردم و محاسبات را بهینه می‌کردم، به‌ویژه آنهایی که شامل توابع مثلثاتی هستند. به عنوان مثال، من به جای ضرب یا تقسیم بر 2 از شیفت بیتی و به جای گرد کردن یا استفاده از Math.floor() از |0 برای سرعت بخشیدن به محاسبات استفاده کردم.

کنترل سطح پیکسل

برای دستکاری پیکسل‌ها، از Uint32Array استفاده کردم که به من اجازه می‌دهد حالت‌های پیکسل را مستقیماً بر اساس شاخص بنویسم. این رویکرد موثر بود زیرا سه بیت رنگ را نشان می‌دهند و بیت چهارم آلفا است که تنظیمات روشنایی پیکسل را امکان‌پذیر می‌کند و مناطق صفحه را به عنوان اشغال شده علامت‌گذاری می‌کند. این سطح از کنترل برای دستیابی به جلوه های بصری و عملکرد مورد نظر بسیار مهم بود.

const buf = new ArrayBuffer(height * width * 4);
const buf8 = new Uint8ClampedArray(buf);
const data = new Uint32Array(buf);

...

const alphaMask = 0x00ffffff | (light << 24);
const pixel = textureImageData[textureIndex];
data[dataIndex] = pixel & alphaMask;
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

غلبه بر موانع ریاضی

یکی از دلهره آورترین جنبه ها یافتن راه حل ها و فرمول هایی برای رندرینگ سطوح افقی مانند کف، سقف و تیرهای مختلف بود. بدون پیش زمینه ای در ریاضیات یا توسعه بازی، این بخش از پروژه به خصوص زمان بر بود. با این حال، نتیجه یک تصویر بصری جالب و قابل قبول بود که اهداف عملکردی من را برآورده کرد.

//// This's just an example. The actual code has been improved to reduce the number of calculations.
public getTileSpriteDataIndexBySideX_positive(
    ray: Ray,
    offset: number,
    textureData: TextureData
  ): number {
    const { width, height } = textureData;
    const fixSinAbs = Math.abs(Math.sin(ray.angle)) / ray.fixDistance;
    const factY = textureData.height * rayAngle.fixSinAbs
    const diff = Math.abs(ray.fixedDistance - ray.distance);
    const fixedCos = Math.cos(ray.angle) / ray.fixDistance;
    const fixedCosDiff = fixedCos * diff;
    const offsetX = offset - fixedCosDiff + (fixedCosDiff | 0) + 1;
    const spriteOffsetX = ((offsetX - (offsetX | 0)) * width) | 0;
    const spriteOffsetY = (diff * factY) | 0;
    const fixedX = height - mod(spriteOffsetY, height) - 1;
    return Math.imul(fixedX, width) + spriteOffsetX;
  }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نه فقط جاوا اسکریپت

اگرچه در ابتدا قصد داشتم از جاوا اسکریپت خالص استفاده کنم، اما در نهایت TypeScript را برای مدیریت موثرتر ساختارهای کد پیچیده وارد کردم. علاوه بر این، من از چارچوب Vue استفاده کردم تا همه چیز را با هم جمع کنم و از روند توسعه نرم‌تر و پایگاه کد قابل نگهداری‌تر اطمینان حاصل کنم.

export type Tile = {
  bottom: number;
  texture?: Texture;
  name?: string;
};

export type Wall = {
  public top: number;
  public bottom: number;
  public texture?: Texture;
  public name?: string;
};

export type MapItem = {
  walls: Wall[];
  tiles: Tile[];  
  mirror?: boolean;
  stopRay: boolean;
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بینش عملکرد

این پروژه همچنین به عنوان یک تست عملکرد مرورگر روشنگر عمل کرد. در لپ تاپ من با پردازنده i7–10510U، کروم به عنوان کارآمدترین مرورگر ظاهر شد و پس از آن Edge، Firefox و Safari قرار گرفتند. این یافته‌ها قابلیت‌های مختلف مرورگرهای مختلف را هنگام انجام محاسبات فشرده جاوا اسکریپت برجسته می‌کند.

FPS 1920×1080. کروم
کروم

FPS 1920×1080. حاشیه، غیرمتمرکز
حاشیه، غیرمتمرکز

FPS 1920×1080. فایرفاکس
فایرفاکس

چشم انداز آینده

در حالی که نسخه فعلی بازی اثبات مفهومی است، من برنامه هایی برای افزایش قابلیت پخش آن دارم. به روز رسانی های آینده ممکن است شامل افزودن اهداف بازیکن، جلوه های صوتی و حتی قابلیت های چند نفره باشد. این پروژه پایه محکمی برای بررسی این احتمالات فراهم کرده است.

نتیجه

این سفر به ریخته‌گری پرتوهای سه بعدی دهه 90 هم چالش‌برانگیز و هم ارزشمند بوده است. این پروژه به عنوان یک پروژه حیوان خانگی با کنجکاوی آغاز شد و به یک فرو رفتن عمیق در تکنیک های برنامه نویسی بازی های کلاسیک و عملکرد وب مدرن تبدیل شد. از شما دعوت می کنم که بازی را ببینید و کد منبع آن را بررسی کنید. از نظرات و مشارکت های شما استقبال می شود زیرا من به اصلاح و گسترش این پروژه ادامه می دهم.

پیوندها:

با تشکر از شما برای خواندن، و کد نویسی مبارک!

https://www.youtube.com/watch?v=4Bktj-XoUHs

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا