Menggambar dunia game Isometrik

178

Apa cara yang benar untuk menggambar ubin isometrik dalam game 2D?

Saya telah membaca referensi (seperti yang ini ) yang menyarankan agar ubin diberikan dengan cara yang akan zig-zag setiap kolom dalam representasi array 2D dari peta. Saya membayangkan bahwa mereka harus digambar lebih dalam mode berlian, di mana apa yang ditarik ke layar berhubungan lebih dekat dengan apa yang akan terlihat seperti array 2D, hanya diputar sedikit.

Apakah ada kelebihan atau kekurangan dari kedua metode tersebut?

Benny Hallett
sumber

Jawaban:

505

Pembaruan: Algoritma perenderan peta yang dikoreksi, menambahkan lebih banyak ilustrasi, pemformatan berubah.

Mungkin keuntungan untuk teknik "zig-zag" untuk memetakan ubin ke layar dapat dikatakan bahwa ubin xdan ykoordinat berada pada sumbu vertikal dan horizontal.

Pendekatan "Menggambar berlian":

Dengan menggambar peta isometrik menggunakan "drawing in a diamond", yang saya yakini hanya merujuk pada rendering peta dengan menggunakan nested for-loop pada array dua dimensi, seperti contoh ini:

tile_map[][] = [[...],...]

for (cellY = 0; cellY < tile_map.size; cellY++):
    for (cellX = 0; cellX < tile_map[cellY].size cellX++):
        draw(
            tile_map[cellX][cellY],
            screenX = (cellX * tile_width  / 2) + (cellY * tile_width  / 2)
            screenY = (cellY * tile_height / 2) - (cellX * tile_height / 2)
        )

Keuntungan:

Keuntungan dari pendekatan ini adalah ia bersarang sederhana for sarang-loop dengan logika yang cukup lurus ke depan yang bekerja secara konsisten di semua ubin.

Kerugian:

Satu kelemahan dari pendekatan itu adalah bahwa xdan ykoordinat ubin pada peta akan meningkat dalam garis diagonal, yang mungkin membuatnya lebih sulit untuk secara visual memetakan lokasi di layar ke peta yang direpresentasikan sebagai sebuah array:

Gambar peta ubin

Namun, akan ada kesulitan untuk mengimplementasikan kode contoh di atas - urutan rendering akan menyebabkan ubin yang seharusnya berada di belakang ubin tertentu yang akan ditarik di atas ubin di depan:

Menghasilkan gambar dari urutan render yang salah

Untuk memperbaiki masalah ini, urutan inner- forloop harus dibalik - mulai dari nilai tertinggi, dan merender ke nilai yang lebih rendah:

tile_map[][] = [[...],...]

for (i = 0; i < tile_map.size; i++):
    for (j = tile_map[i].size; j >= 0; j--):  // Changed loop condition here.
        draw(
            tile_map[i][j],
            x = (j * tile_width / 2) + (i * tile_width / 2)
            y = (i * tile_height / 2) - (j * tile_height / 2)
        )

Dengan perbaikan di atas, rendering peta harus diperbaiki:

Menghasilkan gambar dari urutan render yang benar

Pendekatan "Zig-zag":

Keuntungan:

Mungkin keuntungan dari pendekatan "zig-zag" adalah bahwa peta yang diberikan mungkin tampak sedikit lebih kompak secara vertikal daripada pendekatan "berlian":

Pendekatan zig-zag untuk rendering tampaknya kompak

Kerugian:

Dari mencoba menerapkan teknik zig-zag, kerugiannya mungkin sedikit lebih sulit untuk menulis kode rendering karena itu tidak dapat ditulis sesederhana seperti bersarang-di foratas setiap elemen dalam sebuah array:

tile_map[][] = [[...],...]

for (i = 0; i < tile_map.size; i++):
    if i is odd:
        offset_x = tile_width / 2
    else:
        offset_x = 0

    for (j = 0; j < tile_map[i].size; j++):
        draw(
            tile_map[i][j],
            x = (j * tile_width) + offset_x,
            y = i * tile_height / 2
        )

Selain itu, mungkin agak sulit untuk mencoba mencari tahu koordinat dari ubin karena sifat urutan pembuatan yang dibuat:

Mengkoordinasikan rendering pesanan zig-zag

Catatan: Ilustrasi yang disertakan dalam jawaban ini dibuat dengan implementasi Java dari kode render ubin yang disajikan, dengan intlarik berikut sebagai peta:

tileMap = new int[][] {
    {0, 1, 2, 3},
    {3, 2, 1, 0},
    {0, 0, 1, 1},
    {2, 2, 3, 3}
};

Gambar ubin adalah:

  • tileImage[0] -> Sebuah kotak dengan kotak di dalamnya.
  • tileImage[1] -> Kotak hitam.
  • tileImage[2] -> Kotak putih.
  • tileImage[3] -> Sebuah kotak dengan benda abu-abu tinggi di dalamnya.

Catatan tentang Lebar dan Ketinggian Ubin

Variabel tile_widthdan tile_heightyang digunakan dalam contoh kode di atas merujuk pada lebar dan tinggi ubin tanah di gambar yang mewakili ubin:

Gambar menampilkan lebar dan tinggi ubin

Menggunakan dimensi gambar akan berfungsi, selama dimensi gambar dan dimensi ubin cocok. Kalau tidak, peta ubin bisa diberikan dengan celah di antara ubin.

coobird
sumber
136
Anda bahkan menggambar. Itu upaya.
zaratustra
Ceria coobird. Saya menggunakan pendekatan intan untuk game saat ini yang saya kembangkan dan ini berhasil. Terima kasih lagi.
Benny Hallett
3
Bagaimana dengan beberapa lapisan ketinggian? Bisakah saya menggambar mereka smiliary, mulai dari level terendah dan terus sampai mencapai level tertinggi?
NagyI
2
@DomenicDatti: Terima kasih atas kata-kata
baikmu
2
Ini luar biasa. Aku hanya menggunakan pendekatan berlian Anda dan juga, dalam hal kebutuhan seseorang untuk mendapatkan coords grid dari posisi layar, saya melakukan ini: j = (2 * x - 4 * y) / tilewidth * 0.5; i = (p.x * 2 / tilewidth) - j;.
Kyr Dunenkoff
10

Either way menyelesaikan pekerjaan. Saya berasumsi bahwa dengan zigzag Anda berarti sesuatu seperti ini: (angka adalah urutan render)

..  ..  01  ..  ..
  ..  06  02  ..
..  11  07  03  ..
  16  12  08  04
21  17  13  09  05
  22  18  14  10
..  23  19  15  ..
  ..  24  20  ..
..  ..  25  ..  ..

Maksud Anda berlian:

..  ..  ..  ..  ..
  01  02  03  04
..  05  06  07  ..
  08  09  10  11
..  12  13  14  ..
  15  16  17  18
..  19  20  21  ..
  22  23  24  25
..  ..  ..  ..  ..

Metode pertama membutuhkan lebih banyak ubin yang diberikan sehingga layar penuh digambar, tetapi Anda dapat dengan mudah melakukan pemeriksaan batas dan melewati semua ubin sepenuhnya di luar layar. Kedua metode akan memerlukan beberapa angka untuk mengetahui apa lokasi ubin 01. Pada akhirnya, kedua metode kira-kira sama dalam hal matematika yang diperlukan untuk tingkat efisiensi tertentu.

zaratustra
sumber
14
Sebenarnya yang saya maksud adalah sebaliknya. Bentuk berlian (yang membuat tepi peta halus) dan metode zig-zag, membuat ujungnya spikey
Benny Hallett
1

Jika Anda memiliki beberapa ubin yang melebihi batas berlian Anda, saya sarankan menggambar dalam urutan mendalam:

...1...
..234..
.56789.
..abc..
...d...
Wouter Lievens
sumber
1

Jawaban Coobird adalah jawaban yang benar dan lengkap. Namun, saya menggabungkan petunjuknya dengan yang ada di situs lain untuk membuat kode yang berfungsi di aplikasi saya (iOS / Objective-C), yang ingin saya bagikan dengan siapa saja yang datang ke sini mencari hal seperti itu. Tolong, jika Anda suka / pilih suara jawaban ini, lakukan hal yang sama untuk aslinya; yang saya lakukan adalah "berdiri di atas bahu raksasa."

Adapun sort-order, teknik saya adalah algoritma pelukis yang dimodifikasi: setiap objek memiliki (a) ketinggian basis (saya sebut "level") dan (b) X / Y untuk "base" atau "foot" dari gambar (contoh: pangkalan avatar ada di kakinya; pangkal pohon ada di akarnya; pangkalan pesawat terbang adalah gambar tengah, dll.) Lalu saya hanya menyortir terendah ke level tertinggi, lalu terendah (layar tertinggi) ke basis tertinggi- Y, lalu terendah (paling kiri) ke basis-X tertinggi. Ini membuat ubin seperti yang diharapkan.

Kode untuk mengkonversi layar (titik) ke ubin (sel) dan kembali:

typedef struct ASIntCell {  // like CGPoint, but with int-s vice float-s
    int x;
    int y;
} ASIntCell;

// Cell-math helper here:
//      http://gamedevelopment.tutsplus.com/tutorials/creating-isometric-worlds-a-primer-for-game-developers--gamedev-6511
// Although we had to rotate the coordinates because...
// X increases NE (not SE)
// Y increases SE (not SW)
+ (ASIntCell) cellForPoint: (CGPoint) point
{
    const float halfHeight = rfcRowHeight / 2.;

    ASIntCell cell;
    cell.x = ((point.x / rfcColWidth) - ((point.y - halfHeight) / rfcRowHeight));
    cell.y = ((point.x / rfcColWidth) + ((point.y + halfHeight) / rfcRowHeight));

    return cell;
}


// Cell-math helper here:
//      http://stackoverflow.com/questions/892811/drawing-isometric-game-worlds/893063
// X increases NE,
// Y increases SE
+ (CGPoint) centerForCell: (ASIntCell) cell
{
    CGPoint result;

    result.x = (cell.x * rfcColWidth  / 2) + (cell.y * rfcColWidth  / 2);
    result.y = (cell.y * rfcRowHeight / 2) - (cell.x * rfcRowHeight / 2);

    return result;
}
Olie
sumber
1

Anda dapat menggunakan jarak euclidean dari titik tertinggi dan terdekat pemirsa, kecuali itu tidak tepat. Ini menghasilkan urutan semacam bola. Anda bisa meluruskannya dengan melihat dari jauh. Lebih jauh kelengkungan menjadi rata. Jadi tambahkan saja 1000 ke masing-masing komponen x, y dan z untuk memberikan x ', y' dan z '. Sortir pada x '* x' + y '* y' + z '* z'.

Siobhan O'Connor
sumber
0

Masalah sebenarnya adalah ketika Anda perlu menggambar beberapa ubin / sprite memotong / merentang dua atau lebih ubin lainnya.

Setelah 2 (analisis) masalah pribadi saya akhirnya menemukan dan menerapkan "gambar render yang benar" untuk permainan cocos2d-js baru saya. Solusi terdiri dalam pemetaan, untuk setiap ubin (rentan), yang sprite adalah "depan, belakang, atas dan belakang". Setelah melakukan itu, Anda dapat menggambar mereka mengikuti "logika rekursif".

Carlos Lopez
sumber