ریاضیات در نمای ایزومتریک [قسمت اول]
#1
Brick 
سلام.
من چند وقتی هست مشغول ساختن یک بازی ایزومتریک در کانستراکت هستم و همونجور که میدونید پیاده کردن این نما در انجین های دوبعدی کمی سخت و پیچیده است. تصمیم گرفتم روشی که خودم از اون استفاده کردم در این کار رو با شما به اشتراک بذارم تا دوستانی که به این نما علاقه دارن بتونن به راحتی اون رو در کانستراکت پیاده کنن. امیدوارم این آموزش به کار دوستان بیاد. سورس آموزش رو میتونید از آخر پست دانلود کنید.

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

مقدمه
خب همونطور که گفتم کار کردن با کاشی های ایزومتریک کمی سخت تر از کاشی های مربعی معمولیه. همینجور که با هم پیش میریم و فرمول های جذابی رو پیدا میکنیم بهتون میگم که هر کدوم چه کاری میکنن و کجا باید استفاده بشن.
راه های زیادی برای پیاده کردن نمای ایزومتریک وجود داره اما من تو این آموزش فقط از یک متود استفاده میکنم که اتفاقا پراستفاده ترین متود بین برنامه نویسا هست. این روش حتی در ادیتور Tiled هم اسفاده میشه و این نشون میده که روش شش دانگی هست.

نمای ارتوگرافیک
اگر نمیدونید نمای ارتوگرافیک چیه میتونید به این صفحه ویکیپدیا رجوع کنید، یا بطور خلاصه :

نقل قول: "تصویر اُرتوگرافیک نوعی تصویرسازی از اجسام سه‌بعدی در فضای دوبعدی است که در آن دید ناظر نسبت به جسم به صورت خطوط موازی‌ای است که همگی بر صفحهٔ رسم عمود هستند."
برای اینکه بهتر متوجه بشید به تصویر زیر نگاه کنید :


[تصویر:  index1.png]

حتی زمانی که در نمای ایزومتریک کار میکنیم، جدول یا نقشه ی ما در واقع یک آرایه دوبعدی از کاشی ها با نمای ارتوگرافیک هست ( اصطلاحا top-view ) :
[تصویر:  map_coordinates.png]
       تصویر 2) خانه های ما اینجوری نمایش داده میشن. مقدارها رو mapX و mapY فرض میکنیم.

اگر قبلا با مختصات ساده ی مربعی کار کرده باشید ( اگر نکردید پیشنهاد میکنم حتما کار کنید تا درک خوبی از این آموزش پیدا کنید ) میدونید که ریاضیات ساده ای نیاز داره. برای کشیدن یه مربع کافیه مختصات نقطه ای از نقشه که میخواید کاشی اونجا باشه رو بگیرید و در اندازه کاشی ضرب کنید :
کد:
tile.x = map.x * TILE_WIDTH;
tile.y = map.y * TILE_HEIGHT;

اگر فرض کنیم که سایز کاشی هامون 64x64 باشه و بخوایم که یک کاشی در مختصات (2،1) نقشه داشته باشیم، با جایگذاری عددها تو فرمول خیلی راحت به جواب میرسیم :
کد:
tile.x = 2 * 64; // x is 128 px
tile.y = 1 * 64; // y is 64 px
پس کاشی باید در نقطه ی (128،64) ساخته بشه :
[تصویر:  orthographic_projection.png]
نمای ایزومتریک
ما میخوایم که نقشه مون به شکل ایزومتریک نمایش داده بشه پس مختصات نقشه در نمای ایزومتریک اینجوری میشه :
[تصویر:  screen_coordinates.png]
اندازه ی کاشی های ما تو این حالت 128x64 پیکسله.
خب بیاید مختصات کاشی (2،1) رو اینبار در نمای ایزومتریک پیدا کنیم :
[تصویر:  iso_map_to_screen.png]
       تصویر 5) مختصات کاشی (2،1) در صفحه میشه 64x96

اینجا جاییه که کار میتونه پیچیده بشه. میتونیم برای انجام دادن اینکار میزان چرخش و مقیاس تغییر ایگرگ ( y-scale ) رو محاسبه کنیم اما راه آسونتری هم برای انجام دادنش وجود داره و اون اینه که به x و y بصورت جداگونه فکر کنیم :
وقتی توی مختصات نقشه به x یکی اضافه کنیم ( در نمای ارتوگرافیک به سمت "راست" بریم )، هر دو مختصات x و y رو در صفحه تغییر میدیم ( در نمای ایزومتریک به سمت "راست" + "پایین" میریم ). تو مثال ما اینجوری میشه که 64 پیکسل ( نصف عرض کاشی ) به راست میریم و 32 پیکسل ( نصف ارتفاع کاشی ) به سمت پایین :
[تصویر:  tracing_axis.png]
به همین ترتیب اگر به y یکی اضافه بشه ( در نمای ارتوگرافیک به سمت "پایین" بریم ) یعنی داریم از مختصات x صفحه کم و به مختصات y صفحه اضافه میکنیم ( در نمای ایزومتریک به سمت "چپ" + "پایین" ) میریم.
خب اگر بخواین این حرفارو بصورت کد دربیاریم همچین چیزی میشه :
کد:
tile.x = map.x * TILE_WIDTH_HALF - map.y * TILE_WIDTH_HALF;
tile.y = map.x * TILE_HEIGHT_HALF + map.y * TILE_HEIGHT_HALF;

و با ساده سازی به کد پایه تبدیل مختصات ایزومتریک میرسیم :
کد:
tile.x = (map.x - map.y) * TILE_WIDTH_HALF;
tile.y = (map.x + map.y) * TILE_HEIGHT_HALF;

حالا بیاید این کد رو با کاشی (2،1) تست کنیم و ببینیم چطور کار میکنه. جواب باید بشه 64x96 :
کد:
tile.x = (2 - 1) * 64; // x == 64
tile.y = (2 + 1) * 32; // y == 96
یوهوووو !! درست کار میکنه ! حالا وقت پیاده کردن فرمولمون تو کانستراکته...

پیاده سازی در کانستراکت
من پروژه حودم رو در کانستراکت با سایز 640x360 ساختم.
برای شروع ما 5 تا آبجکت نیاز داریم :
  1. اسپرایت کاشی به اسم oTile : [تصویر:  tilable_img_0044_grey_0.png]
  2. تکست باکس txbX برای انتخاب x کاشی.
  3. تکست باکس tbxY برای انتخاب y کاشی.
  4. دکمه btnTileGen برای تولید کاشی ها.
  5. تکست txtMapPos برای نمایش مختصات کاشی در نقشه.
[تصویر:  1111.png]

نکته 1 ) آبجکت کاشی رو یه جایی بیرون از لایوت بذارید ولی حذفش نکنید !
نکته 2 ) روی تکست باکس ها کلیک کنید و از قسمت properties، گزینه type رو روی number بذارید تا کاربر فقط بتونه عدد وارد کنه.

خب، حالا وقت اضافه کردن متغیر هاست. ما 4 تا متغیر نیاز داریم :
  1. orgX : مقدار ایکس مبنا. کاشی ها بر اساس این نقطه ساخته میشن.
  2. orgY : مقدار ایگرگ مبنا. کاشی ها بر اساس این نقطه ساخته میشن.
  3. mapX : مقدار ایکس خانه ی موردنظر در جدول.
  4. mapY : مقدار ایگرگ خانه ی موردنظر در جدول.
[تصویر:  2222.png]

بریم سراغ ایونت نویسی ...
اول از همه باید orgX و orgY رو مقداردهی کنیم تا کاشی ها به کمک این نقطه ساخته بشن. من میخوام orgX مقدارش به اندازه نصف لایوت باشه و مقدار orgY هم یک پنجم ارتفاع لایوت :

[تصویر:  3333.png]

حالا میخوایم کاری کنیم که با کلیک روی دکمه، کاشی موردنظر با توجه به اعداد واردشده در تکست باکس در نقطه موردنظر ساخته بشه :

[تصویر:  4444.png]

مقدار x در اکشن create object :
کد:
(mapX - mapY) * (oTile.Width / 2) + orgX

مقدار y در اکشن create object :
کد:
(mapX + mapY) * (oTile.Height / 2) + orgY

نکته : به نسبت فرمول های خام یه تغییراتی داشتیم و اونم orgX و orgY هستن. به این خاطر که باید مختصات اولیه رو به سیستم بشناسونیم تا بر مبنای اون کاشی هارو بسازه.

حالا اگر بازی رو تست کنیم میبینیم که به خوبی کار میکنه :
[تصویر:  5555.png]

تبدیل مختصات پیکسلی به مختصات نقشه
کارکردن با مختصات نقشه ( مربعی ) خیلی آسونه و اکثر محاسبات داخل بازی ( مثلا برخوردها ) تو این مختصات انجام میشه. فقط زمانی که میخوایم چیزی رو در صفحه رسم کنیم از مختصات ایزومتریک استفاده میکنیم.
بعضی مواقع نیاز داریم که مختصات ایزومتریک رو به مربعی تبدیل کنیم. مثلا فرض کنید بخوایم بفهمیم کاربر الان رو کدوم یکی کاشی کلیک کرده. خب اینجا هم از مبحث شیرین ریاضی ( در واقع جبر ) استفاده میکنیم :

کد:
// میدونیم که مختصات ایزومتریک اینجوری بدست میومدن
tile.x = (map.x - map.y) * TILE_WIDTH_HALF;
tile.y = (map.x + map.y) * TILE_HEIGHT_HALF;

// معادله اول رو جوری تغییر میدیم که مقدار ایکس نقشه رو برگردونه
tile.x == (map.x - map.y) * TILE_WIDTH_HALF
tile.x / TILE_WIDTH_HALF == map.x - map.y
map.x == tile.x / TILE_WIDTH_HALF + map.y

// معادله دوم هم به همین صورت
tile.y == (map.x + map.y) * TILE_HEIGHT_HALF
tile.y / TILE_HEIGHT_HALF == map.x + map.y
map.y == tile.y / TILE_HEIGHT_HALF - map.x

// مقدار ایگرگ نقشه رو تو ایکس نقشه جاگذاری میکنیم
map.x == tile.x / TILE_WIDTH_HALF + map.y
map.x == tile.x / TILE_WIDTH_HALF + tile.y / TILE_HEIGHT_HALF - map.x
2(map.x) == tile.x / TILE_WIDTH_HALF + tile.y / TILE_HEIGHT_HALF
map.x == (tile.x / TILE_WIDTH_HALF + tile.y / TILE_HEIGHT_HALF) /2

// و همینکار رو برای ایگرگ نقشه هم انجام میدیم
map.y == tile.y / TILE_HEIGHT_HALF - (tile.x / TILE_WIDTH_HALF + map.y)
map.y == tile.y / TILE_HEIGHT_HALF - (tile.x / TILE_WIDTH_HALF) - map.y
2(map.y) == tile.y / TILE_HEIGHT_HALF - (tile.x / TILE_WIDTH_HALF)
map.y == (tile.y / TILE_HEIGHT_HALF - (tile.x / TILE_WIDTH_HALF)) /2

// پس فرمول های نهایی اینجوری میشن
map.x = (tile.x / TILE_WIDTH_HALF + tile.y / TILE_HEIGHT_HALF) /2;
map.y = (tile.y / TILE_HEIGHT_HALF - (tile.x / TILE_WIDTH_HALF)) /2;

خب بیاید تستش کنیم. اگر بهش مختصات 64x96 رو بدیم باید آدرس (2،1) رو برگردونه :
کد:
map.x = (tile.x / TILE_WIDTH_HALF + tile.y / TILE_HEIGHT_HALF) /2;
map.x = (64 / 64 + 96 / 32) /2;
map.x = (1 + 3) /2;
map.x = 2;

map.y = (tile.y / TILE_HEIGHT_HALF - (tile.x / TILE_WIDTH_HALF)) /2;
map.y = (96 / 32 - (64 / 64)) /2;
map.y = (3 - 1) /2;
map.y = 1;

به به ! کار میکنه ! بالاخره این ریاضی یجایی تو زندگیمون خودشو نشون داد ! 

پیاده سازی در کانستراکت
میخوایم کاری کنیم وقتی رو کاشی کلیک شد آدرس کاشی رو بهمون تحویل بده :
[تصویر:  6666.png]

مقدار mapX :
کد:
((oTile.X - orgX) / (oTile.Width / 2) + (oTile.Y - orgY) / (oTile.Height / 2)) / 2

مقدار mapY :
کد:
((oTile.Y - orgY) / (oTile.Height / 2) - (oTile.X - orgX) / (oTile.Width / 2)) / 2

حالا اگر بازی رو تست کنیم میبینیم که به خوبی کار میکنه و مختصات به درستی تبدیل میشن :
[تصویر:  7777.png]
خب، فکر کنم برای قسمت اول کافی باشه. تو این قسمت ما بصورت دستی کاشی هارو ساختیم تا بتونیم خیلی خوب الگوریتم کار رو بفهمیم. تو قسمت بعد با استفاده از حلقه ها کاری میکنیم که کاشی ها بصورت اتوماتیک ساخته بشن.
  • شما میتونید از فرمول هایی که تو آموزش گفته شد بصورت فانکشن استفاده کنید تا توی وقت و حجم کدها صرفه جویی بشه.
  • سورس آموزش رو از اینجا میتونید دانلود کنید.
  • اگر وقت خوندنش رو ندارید بهم بگید تا آموزش رو با فرمت pdf بزارم.
  • اگر وقت کنم این آموزش رو برای گیم میکر هم آماده میکنم. ( فکر کنم این آموزش رو بخونید راحت بتونید پیاده اش کنید خودتون )
امیدوارم این آموزش بدردبخور باشه.
موفق باشید.

  پاسخ




موضوع‌های مشابه…
موضوع نویسنده پاسخ بازدید آخرین ارسال
  آموزش پروژه محور ساخت بازی مار و پله mohsen_nasri 6 3,807 1403/10/24، 09:38 عصر
آخرین ارسال: Tggi
Star مهم آموزش خروجی اندروید روی سیستم شخصی rezamms 128 86,462 1403/10/23، 04:58 عصر
آخرین ارسال: Tggi
  مهم آموزش تصویری خروجی مستقیم - یکبار برای همیشه! rezamms 33 20,911 1401/2/13، 09:39 عصر
آخرین ارسال: kamran_cn
  خروجی اندرید davinmstr1 2 2,601 1400/8/4، 10:23 عصر
آخرین ارسال: ᔕinaᗪehghani
  AAB (بسته برنامه اندروید) چيست؟ + نحوه خروجي گرفتن در كرودوا ᔕinaᗪehghani 15 8,600 1400/6/21، 01:55 صبح
آخرین ارسال: mehdi1100

پرش به انجمن: