Membuat Dunia Isometrik: Hal Dasar untuk Pengembang Game
() translation by (you can also view the original English article)
Dalam tutorial ini, saya akan memberi Anda gambaran luas tentang apa yang perlu Anda ketahui untuk menciptakan dunia isometrik. Anda akan belajar apa proyeksi isometrik, dan bagaimana merepresentasikan level isometrik sebagai array 2D. Kami akan merumuskan hubungan antara tampilan dan logika, sehingga kami dapat dengan mudah memanipulasi objek di layar dan menangani deteksi tabrakan berbasis-ubin. Kami juga akan melihat pengurutan kedalaman dan animasi karakter.
Ingin tips lebih lanjut tentang menciptakan dunia isometrik? Periksa posting tindak lanjut, Membuat Dunia Isometrik: A Primer untuk Gamedevs, Lanjutan, dan buku Juwal, Starling Game Development Essentials.
1. Dunia Isometrik
Tampilan isometrik adalah metode tampilan yang digunakan untuk menciptakan ilusi 3D untuk game 2D yang lain - kadang-kadang disebut sebagai pseudo 3D atau 2.5D. Gambar-gambar ini (diambil dari Diablo 2 dan Age of Empires) menggambarkan apa yang saya maksud:






Menerapkan pandangan isometrik dapat dilakukan dengan banyak cara, tetapi demi kesederhanaan saya akan fokus pada pendekatan berbasis ubin, yang merupakan metode yang paling efisien dan banyak digunakan. Saya telah menumpangkan setiap tangkapan layar di atas dengan kotak berlian yang menunjukkan bagaimana dataran dibagi menjadi ubin.
2. Game Berbasis Ubin
Dalam pendekatan berbasis ubin, setiap elemen visual dipecah menjadi bagian-bagian yang lebih kecil, yang disebut ubin, dengan ukuran standar. Ubin-ubin ini akan disusun untuk membentuk dunia permainan sesuai dengan data level yang ditentukan sebelumnya - biasanya sebuah array 2D.
Sebagai contoh, marilah kita mempertimbangkan tampilan 2D top-down standar dengan dua ubin - ubin rumput dan ubin dinding - seperti yang ditunjukkan di sini:

Ubin ini masing-masing memiliki ukuran yang sama satu sama lain, dan masing-masing persegi, sehingga tinggi ubin dan lebar ubin adalah sama.
Untuk tingkat dengan padang rumput tertutup di semua sisi oleh dinding, tingkat 2D array data akan terlihat seperti ini:
1 |
[[1,1,1,1,1,1], |
2 |
[1,0,0,0,0,1], |
3 |
[1,0,0,0,0,1], |
4 |
[1,0,0,0,0,1], |
5 |
[1,0,0,0,0,1], |
6 |
[1,1,1,1,1,1]] |
Di sini, 0
menunjukkan ubin rumput dan 1
menunjukkan ubin dinding. Mengatur ubin sesuai dengan data level akan menghasilkan gambar tingkat di bawah ini:

Kami dapat meningkatkan ini dengan menambahkan ubin sudut dan ubin dinding vertikal dan horizontal terpisah, membutuhkan lima ubin tambahan:
1 |
[[3,1,1,1,1,4], |
2 |
[2,0,0,0,0,2], |
3 |
[2,0,0,0,0,2], |
4 |
[2,0,0,0,0,2], |
5 |
[2,0,0,0,0,2], |
6 |
[6,1,1,1,1,5]] |

Saya harap konsep pendekatan berbasis ubin sekarang sudah jelas. Ini adalah implementasi grid 2D langsung, yang bisa kami kode seperti ini:
1 |
for (i, loop through rows) |
2 |
for (j, loop through columns) |
3 |
x = j * tile width |
4 |
y = i * tile height |
5 |
tileType = levelData[i][j] |
6 |
placetile(tileType, x, y) |
Di sini kita mengasumsikan bahwa lebar petak dan tinggi petak adalah sama (dan sama untuk semua ubin), dan cocok dengan dimensi gambar petak. Jadi, lebar ubin dan tinggi ubin untuk contoh ini adalah 50px, yang merupakan total ukuran total 300x300px - yaitu, enam baris dan enam kolom ubin berukuran masing-masing 50x50px.
Dalam pendekatan berbasis ubin yang normal, kami juga menerapkan tampilan top-down atau tampilan samping; untuk pandangan isometrik kita perlu menerapkan proyeksi isometrik.
3. Proyeksi Isometrik
Penjelasan teknis terbaik tentang "proyeksi isometrik" berarti, sejauh yang saya tahu, adalah dari artikel ini oleh Clint Bellanger:
Kami mengarahkan kamera kami sepanjang dua sumbu (ayunkan kamera 45 derajat ke satu sisi, lalu 30 derajat ke bawah). Ini menciptakan grid berbentuk berlian (belah ketupat) di mana ruang grid dua kali lebar karena mereka tinggi. Gaya ini dipopulerkan oleh game strategi dan aksi RPG. Jika kita melihat kubus dalam pandangan ini, tiga sisi terlihat (atas dan dua sisi yang menghadap).
Meskipun terdengar agak rumit, sebenarnya menerapkan pandangan ini sangat mudah. Yang perlu kita pahami adalah hubungan antara ruang 2D dan ruang isometrik - yaitu hubungan antara data tingkat dan tampilan; transformasi dari koordinat "Cartesian" top-down ke koordinat isometrik.



(Kami tidak mempertimbangkan teknik berbasis hexagonal tile, yang merupakan cara lain untuk menerapkan dunia isometrik.)
Menempatkan Ubin Isometrik
Biarkan saya mencoba menyederhanakan hubungan antara data level yang disimpan sebagai array 2D dan tampilan isometrik - yaitu, bagaimana kita mengubah koordinat Cartesian menjadi koordinat isometrik.
Kami akan mencoba untuk membuat tampilan isometrik untuk data tingkat padang rumput di dinding tertutup:
1 |
[[1,1,1,1,1,1], |
2 |
[1,0,0,0,0,1], |
3 |
[1,0,0,0,0,1], |
4 |
[1,0,0,0,0,1], |
5 |
[1,0,0,0,0,1], |
6 |
[1,1,1,1,1,1]] |
Dalam skenario ini kita dapat menentukan area berjalan dengan memeriksa apakah elemen array adalah 0
pada koordinat itu, dengan demikian menunjukkan rumput. Implementasi tampilan 2D dari tingkat di atas adalah iterasi langsung dengan dua loop, menempatkan ubin persegi mengimbangi masing-masing dengan tinggi ubin tetap dan nilai lebar ubin.
1 |
for (i, loop through rows) |
2 |
for (j, loop through columns) |
3 |
x = j * tile width |
4 |
y = i * tile height |
5 |
tileType = levelData[i][j] |
6 |
placetile(tileType, x, y) |
Untuk tampilan isometrik, kode tetap sama, tetapi fungsi placeTile()
berubah.
Untuk tampilan isometrik kita perlu menghitung koordinat isometrik yang sesuai di dalam loop.
Persamaan untuk melakukan ini adalah sebagai berikut, di mana isoX
dan isoY
mewakili isometrik koordinat x dan y, dan cartX
dan cartY
mewakili koordinat x dan y Cartesian:
1 |
//Cartesian to isometric:
|
2 |
|
3 |
isoX = cartX - cartY; |
4 |
isoY = (cartX + cartY) / 2; |
1 |
//Isometric to Cartesian:
|
2 |
|
3 |
cartX = (2 * isoY + isoX) / 2; |
4 |
cartY = (2 * isoY - isoX) / 2; |
Fungsi-fungsi ini menunjukkan bagaimana Anda dapat mengkonversi dari satu sistem ke sistem lainnya:
1 |
function isoTo2D(pt:Point):Point{ |
2 |
var tempPt:Point = new Point(0, 0); |
3 |
tempPt.x = (2 * pt.y + pt.x) / 2; |
4 |
tempPt.y = (2 * pt.y - pt.x) / 2; |
5 |
return(tempPt); |
6 |
}
|
1 |
function twoDToIso(pt:Point):Point{ |
2 |
var tempPt:Point = new Point(0,0); |
3 |
tempPt.x = pt.x - pt.y; |
4 |
tempPt.y = (pt.x + pt.y) / 2; |
5 |
return(tempPt); |
6 |
}
|
Pseudocode untuk loop kemudian terlihat seperti ini:
1 |
for(i, loop through rows) |
2 |
for(j, loop through columns) |
3 |
x = j * tile width |
4 |
y = i * tile height |
5 |
tileType = levelData[i][j] |
6 |
placetile(tileType, twoDToIso(new Point(x, y))) |



Sebagai contoh, mari kita lihat bagaimana posisi 2D biasa dikonversi ke posisi isometrik:
1 |
2D point = [100, 100]; |
2 |
// twoDToIso(2D point) will be calculated as below |
3 |
isoX = 100 - 100; // = 0 |
4 |
isoY = (100 + 100) / 2; // = 100 |
5 |
Iso point == [0, 100]; |
Demikian pula, masukan [0, 0]
akan menghasilkan [0, 0]
, dan [10, 5]
akan memberikan [5, 7.5]
.
Metode di atas memungkinkan kami untuk membuat korelasi langsung antara data level 2D dan koordinat isometrik. Kita dapat menemukan koordinat ubin dalam data level dari koordinat Cartesiannya menggunakan fungsi ini:
1 |
function getTileCoordinates(pt:Point, tileHeight:Number):Point{ |
2 |
var tempPt:Point = new Point(0, 0); |
3 |
tempPt.x = Math.floor(pt.x / tileHeight); |
4 |
tempPt.y = Math.floor(pt.y / tileHeight); |
5 |
return(tempPt); |
6 |
}
|
(Di sini, kita pada dasarnya mengasumsikan bahwa tinggi dan lebar petak ubin adalah sama, seperti dalam banyak kasus.)
Oleh karena itu, dari sepasang koordinat layar (isometrik), kita dapat menemukan koordinat ubin dengan memanggil:
1 |
getTileCoordinates(isoTo2D(screen point), tile height); |
Titik layar ini bisa berupa, katakanlah, posisi klik mouse atau posisi pick-up.
Tips: Metode penempatan lainnya adalah model Zigzag, yang mengambil pendekatan yang berbeda sama sekali.
Bergerak dalam Koordinat Isometrik
Gerakannya sangat mudah: Anda memanipulasi data dunia game Anda dalam koordinat Cartesian dan cukup gunakan fungsi di atas untuk memperbaruinya di layar. Misalnya, jika Anda ingin memindahkan karakter ke depan dalam arah y positif, Anda bisa langsung menaikkan properti y
dan kemudian mengonversi posisinya menjadi koordinat isometrik:
1 |
y = y + speed; |
2 |
placetile(twoDToIso(new Point(x, y))) |
Depth Sorting
Selain penempatan normal, kita perlu memperhatikan penyortiran mendalam untuk menggambar dunia isometrik. Ini memastikan bahwa item yang lebih dekat ke pemain ditarik di atas item yang lebih jauh.
Metode pengurutan kedalaman yang paling sederhana adalah hanya menggunakan nilai koordinat-Cartesian y, sebagaimana disebutkan dalam Tip Singkat ini: semakin jauh ke atas layar objek, maka sebelumnya harus ditarik. Ini berfungsi dengan baik selama kita tidak memiliki sprite yang menempati lebih dari satu ruang ubin.
Cara penyortiran kedalaman yang paling efisien untuk dunia isometrik adalah memecah semua ubin menjadi dimensi satu-ubin standar dan tidak memungkinkan gambar yang lebih besar. Misalnya, di sini adalah ubin yang tidak sesuai dengan ukuran ubin standar - lihat bagaimana kita dapat membaginya menjadi beberapa ubin yang masing-masing sesuai dengan dimensi ubin:



4. Menciptakan Seni
Seni isometrik dapat berupa seni piksel, tetapi tidak harus demikian. Ketika berhadapan dengan seni piksel isometrik, panduan RhysD memberi tahu Anda hampir semua hal yang perlu Anda ketahui. Beberapa teori dapat ditemukan di Wikipedia juga.
Saat membuat seni isometrik, aturan umumnya adalah
- Mulai dengan grid isometrik kosong dan patuhi presisi sempurna piksel.
- Coba pecah seni menjadi gambar ubin isometrik tunggal.
- Cobalah untuk memastikan bahwa masing-masing ubin bisa berjalan kaki atau tidak bisa dijelajahi. Akan rumit jika kita perlu mengakomodasi satu petak yang berisi area yang dapat dijelajahi dan tidak dapat dijelajahi.
- Kebanyakan ubin perlu berplatform mulus dalam satu atau lebih arah.
- Bayangan bisa rumit untuk diterapkan, kecuali kita menggunakan pendekatan berlapis di mana kita menggambar bayangan di lapisan tanah dan kemudian menggambar pahlawan (atau pohon, atau objek lain) di lapisan atas. Jika pendekatan yang Anda gunakan tidak berlapis-lapis, pastikan bayangan jatuh ke depan sehingga mereka tidak akan jatuh, katakanlah, pahlawan ketika dia berdiri di belakang pohon.
- Jika Anda perlu menggunakan gambar ubin yang lebih besar dari ukuran ubin isometrik standar, coba gunakan dimensi yang merupakan kelipatan dari ukuran ubin iso. Lebih baik memiliki pendekatan berlapis dalam kasus-kasus seperti itu, di mana kita dapat membagi seni menjadi bagian-bagian berbeda berdasarkan tingginya. Sebagai contoh, sebuah pohon dapat dibagi menjadi tiga bagian: akar, batang, dan dedaunan. Ini membuatnya lebih mudah untuk menyortir kedalaman karena kita dapat menggambar potongan di lapisan yang sesuai yang sesuai dengan ketinggian mereka.
Ubin isometrik yang lebih besar dari dimensi ubin tunggal akan menciptakan masalah dengan penyortiran kedalaman. Beberapa masalah dibahas dalam tautan ini:
5. Karakter Isometrik
Menerapkan karakter dalam tampilan isometrik tidaklah rumit karena mungkin terdengar. Seni karakter perlu dibuat sesuai dengan standar tertentu. Pertama kita harus memperbaiki berapa banyak gerakan yang diizinkan dalam permainan kita - biasanya game akan menyediakan gerakan empat arah atau gerakan delapan arah.



Untuk tampilan top-down, kita bisa membuat satu set animasi karakter yang menghadap ke satu arah, dan cukup memutar untuk semua yang lain. Untuk seni karakter isometrik, kita perlu membuat ulang setiap animasi di setiap arah yang diizinkan - jadi untuk gerakan delapan arah kita perlu membuat delapan animasi untuk setiap tindakan. Untuk memudahkan pemahaman kita biasanya menunjukkan arah sebagai Utara, Utara-Barat, Barat, Selatan-Barat, Selatan, Tenggara, Timur, dan Utara-Timur, berlawanan arah jarum jam, dalam urutan itu.



Kami menempatkan karakter dengan cara yang sama seperti yang kami letakkan ubin. Pergerakan karakter dilakukan dengan menghitung gerakan dalam koordinat Cartesian dan kemudian mengkonversi ke koordinat isometrik. Mari kita asumsikan kita menggunakan keyboard untuk mengontrol karakter.
Kami akan menetapkan dua variabel, dX
dan dY
, berdasarkan tombol arah ditekan. Secara default, variabel ini akan menjadi 0
, dan akan diperbarui sesuai grafik di bawah ini, di mana U
, D
, R
dan L
menunjukkan tombol panah Atas, Bawah, Kanan dan Kiri, masing-masing. Nilai 1
bawah kunci mewakili tombol yang ditekan; 0
menyiratkan bahwa kuncinya tidak ditekan.
1 |
Key Pos |
2 |
U D R L dX dY |
3 |
================ |
4 |
0 0 0 0 0 0 |
5 |
1 0 0 0 0 1 |
6 |
0 1 0 0 0 -1 |
7 |
0 0 1 0 1 0 |
8 |
0 0 0 1 -1 0 |
9 |
1 0 1 0 1 1 |
10 |
1 0 0 1 -1 1 |
11 |
0 1 1 0 1 -1 |
12 |
0 1 0 1 -1 -1 |
Sekarang, dengan menggunakan nilai-nilai dX
dan dY
, kita dapat memperbarui koordinat Cartesian seperti:
1 |
newX = currentX + (dX * speed); |
2 |
newY = currentY + (dY * speed); |
Jadi dX
dan dY
mewakili perubahan posisi x dan y karakter, berdasarkan tombol yang ditekan.
Kita dapat dengan mudah menghitung koordinat isometrik baru, seperti yang telah kita bahas:
1 |
Iso = twoDToIso(new Point(newX, newY)) |
Begitu kita memiliki posisi isometrik yang baru, kita perlu memindahkan karakter ke posisi ini. Berdasarkan nilai-nilai yang kita miliki untuk dX
dan dY
, kita dapat memutuskan arah mana yang dihadapi karakter dan menggunakan seni karakter yang sesuai.
Deteksi Tabrakan
Deteksi tabrakan dilakukan dengan memeriksa apakah ubin pada posisi baru yang dihitung adalah ubin yang tidak dapat dijelajahi. Jadi, setelah kami menemukan posisi baru, kami tidak segera memindahkan karakter di sana, tetapi periksa dulu untuk melihat apa ubin menempati ruang itu.
1 |
tile coordinate = getTileCoordinates(isoTo2D(iso point), tile height); |
2 |
if (isWalkable(tile coordinate)) { |
3 |
moveCharacter(); |
4 |
} else { |
5 |
//do nothing; |
6 |
} |
Dalam fungsi isWalkable()
, kita memeriksa apakah nilai array data level pada koordinat yang diberikan adalah ubin walkable atau tidak. Kita harus berhati-hati untuk memperbarui arah di mana karakter itu menghadapinya - bahkan jika dia tidak bergerak, seperti dalam kasusnya memukul ubin yang tidak bisa dijelajahi.
Depth Sorting Dengan Karakter
Pertimbangkan karakter dan ubin pohon di dunia isometrik.
Untuk memahami dengan benar pemilahan kedalaman, kita harus memahami bahwa bilamana x-dan y-koordinat karakter kurang dari pohon, pohon tumpang tindih dengan karakter. Setiap kali x-dan y-koordinat karakter lebih besar dari pohon, karakter tumpang tindih dengan pohon.
Ketika mereka memiliki x-koordinat yang sama, maka kami memutuskan berdasarkan y-koordinat saja: mana yang memiliki koordinat y tinggi tumpang tindih dengan yang lain. Ketika mereka memiliki koordinat y yang sama maka kami memutuskan berdasarkan x-koordinat saja: mana yang memiliki koordinat x lebih tinggi tumpang tindih dengan yang lain.
Versi yang disederhanakan dari ini adalah hanya secara berurutan menarik level mulai dari ubin terjauh - yaitu, tile[0][0]
- kemudian tarik semua ubin di setiap baris satu per satu. Jika karakter menempati ubin, kami menggambar ubin tanah terlebih dahulu dan kemudian membuat ubin karakter. Ini akan berfungsi dengan baik, karena karakter tidak dapat menempati ubin dinding.
Penyortiran kedalaman harus dilakukan setiap kali ada petak yang berubah posisi. Misalnya, kita perlu melakukannya setiap kali karakter bergerak. Kami kemudian memperbarui adegan yang ditampilkan, setelah melakukan semacam kedalaman, untuk mencerminkan perubahan kedalaman.
6. Memiliki Go!
Sekarang, letakkan pengetahuan baru Anda untuk penggunaan yang baik dengan menciptakan prototipe kerja, dengan kontrol keyboard dan penyortiran kedalaman yang tepat dan deteksi tabrakan. Inilah demo saya:
Klik untuk memberi fokus SWF, lalu gunakan tombol panah. Klik di sini untuk versi ukuran penuh.
Anda mungkin menemukan kelas utilitas ini berguna (saya sudah menulisnya di AS3, tetapi Anda harus bisa memahaminya dalam bahasa pemrograman lainnya):
1 |
package com.csharks.juwalbose |
2 |
{
|
3 |
import flash.display.Sprite; |
4 |
import flash.geom.Point; |
5 |
|
6 |
public class IsoHelper |
7 |
{
|
8 |
/**
|
9 |
* convert an isometric point to 2D
|
10 |
* */
|
11 |
public static function isoTo2D(pt:Point):Point{ |
12 |
//gx=(2*isoy+isox)/2;
|
13 |
//gy=(2*isoy-isox)/2
|
14 |
var tempPt:Point=new Point(0,0); |
15 |
tempPt.x=(2*pt.y+pt.x)/2; |
16 |
tempPt.y=(2*pt.y-pt.x)/2; |
17 |
return(tempPt); |
18 |
}
|
19 |
/**
|
20 |
* convert a 2d point to isometric
|
21 |
* */
|
22 |
public static function twoDToIso(pt:Point):Point{ |
23 |
//gx=(isox-isoxy;
|
24 |
//gy=(isoy+isox)/2
|
25 |
var tempPt:Point=new Point(0,0); |
26 |
tempPt.x=pt.x-pt.y; |
27 |
tempPt.y=(pt.x+pt.y)/2; |
28 |
return(tempPt); |
29 |
}
|
30 |
|
31 |
/**
|
32 |
* convert a 2d point to specific tile row/column
|
33 |
* */
|
34 |
public static function getTileCoordinates(pt:Point, tileHeight:Number):Point{ |
35 |
var tempPt:Point=new Point(0,0); |
36 |
tempPt.x=Math.floor(pt.x/tileHeight); |
37 |
tempPt.y=Math.floor(pt.y/tileHeight); |
38 |
|
39 |
return(tempPt); |
40 |
}
|
41 |
|
42 |
/**
|
43 |
* convert specific tile row/column to 2d point
|
44 |
* */
|
45 |
public static function get2dFromTileCoordinates(pt:Point, tileHeight:Number):Point{ |
46 |
var tempPt:Point=new Point(0,0); |
47 |
tempPt.x=pt.x*tileHeight; |
48 |
tempPt.y=pt.y*tileHeight; |
49 |
|
50 |
return(tempPt); |
51 |
}
|
52 |
|
53 |
}
|
54 |
}
|
Jika Anda benar-benar terjebak, inilah kode lengkap dari demo saya (dalam bentuk kode waktu Flash dan AS3):
1 |
// Uses senocular's KeyObject class
|
2 |
// http://www.senocular.com/flash/actionscript/?file=ActionScript_3.0/com/senocular/utils/KeyObject.as
|
3 |
|
4 |
import flash.display.Sprite; |
5 |
import com.csharks.juwalbose.IsoHelper; |
6 |
import flash.display.MovieClip; |
7 |
import flash.geom.Point; |
8 |
import flash.filters.GlowFilter; |
9 |
import flash.events.Event; |
10 |
import com.senocular.utils.KeyObject; |
11 |
import flash.ui.Keyboard; |
12 |
import flash.display.Bitmap; |
13 |
import flash.display.BitmapData; |
14 |
import flash.geom.Matrix; |
15 |
import flash.geom.Rectangle; |
16 |
|
17 |
var levelData=[[1,1,1,1,1,1], |
18 |
[1,0,0,2,0,1], |
19 |
[1,0,1,0,0,1], |
20 |
[1,0,0,0,0,1], |
21 |
[1,0,0,0,0,1], |
22 |
[1,1,1,1,1,1]]; |
23 |
|
24 |
var tileWidth:uint = 50; |
25 |
var borderOffsetY:uint = 70; |
26 |
var borderOffsetX:uint = 275; |
27 |
|
28 |
var facing:String = "south"; |
29 |
var currentFacing:String = "south"; |
30 |
var hero:MovieClip=new herotile(); |
31 |
hero.clip.gotoAndStop(facing); |
32 |
var heroPointer:Sprite; |
33 |
var key:KeyObject = new KeyObject(stage);//Senocular KeyObject Class |
34 |
var heroHalfSize:uint=20; |
35 |
|
36 |
//the tiles
|
37 |
var grassTile:MovieClip=new TileMc(); |
38 |
grassTile.gotoAndStop(1); |
39 |
var wallTile:MovieClip=new TileMc(); |
40 |
wallTile.gotoAndStop(2); |
41 |
|
42 |
//the canvas
|
43 |
var bg:Bitmap = new Bitmap(new BitmapData(650,450)); |
44 |
addChild(bg); |
45 |
var rect:Rectangle=bg.bitmapData.rect; |
46 |
|
47 |
//to handle depth
|
48 |
var overlayContainer:Sprite=new Sprite(); |
49 |
addChild(overlayContainer); |
50 |
|
51 |
//to handle direction movement
|
52 |
var dX:Number = 0; |
53 |
var dY:Number = 0; |
54 |
var idle:Boolean = true; |
55 |
var speed:uint = 5; |
56 |
var heroCartPos:Point=new Point(); |
57 |
var heroTile:Point=new Point(); |
58 |
|
59 |
//add items to start level, add game loop
|
60 |
function createLevel() |
61 |
{
|
62 |
var tileType:uint; |
63 |
for (var i:uint=0; i<levelData.length; i++) |
64 |
{
|
65 |
for (var j:uint=0; j<levelData[0].length; j++) |
66 |
{
|
67 |
tileType = levelData[i][j]; |
68 |
placeTile(tileType,i,j); |
69 |
if (tileType == 2) |
70 |
{
|
71 |
levelData[i][j] = 0; |
72 |
}
|
73 |
}
|
74 |
}
|
75 |
overlayContainer.addChild(heroPointer); |
76 |
overlayContainer.alpha=0.5; |
77 |
overlayContainer.scaleX=overlayContainer.scaleY=0.5; |
78 |
overlayContainer.y=290; |
79 |
overlayContainer.x=10; |
80 |
depthSort(); |
81 |
addEventListener(Event.ENTER_FRAME,loop); |
82 |
}
|
83 |
|
84 |
//place the tile based on coordinates
|
85 |
function placeTile(id:uint,i:uint,j:uint) |
86 |
{
|
87 |
var pos:Point=new Point(); |
88 |
if (id == 2) |
89 |
{
|
90 |
|
91 |
id = 0; |
92 |
pos.x = j * tileWidth; |
93 |
pos.y = i * tileWidth; |
94 |
pos = IsoHelper.twoDToIso(pos); |
95 |
hero.x = borderOffsetX + pos.x; |
96 |
hero.y = borderOffsetY + pos.y; |
97 |
//overlayContainer.addChild(hero);
|
98 |
heroCartPos.x = j * tileWidth; |
99 |
heroCartPos.y = i * tileWidth; |
100 |
heroTile.x=j; |
101 |
heroTile.y=i; |
102 |
heroPointer=new herodot(); |
103 |
heroPointer.x=heroCartPos.x; |
104 |
heroPointer.y=heroCartPos.y; |
105 |
|
106 |
}
|
107 |
var tile:MovieClip=new cartTile(); |
108 |
tile.gotoAndStop(id+1); |
109 |
tile.x = j * tileWidth; |
110 |
tile.y = i * tileWidth; |
111 |
overlayContainer.addChild(tile); |
112 |
}
|
113 |
|
114 |
//the game loop
|
115 |
function loop(e:Event) |
116 |
{
|
117 |
if (key.isDown(Keyboard.UP)) |
118 |
{
|
119 |
dY = -1; |
120 |
}
|
121 |
else if (key.isDown(Keyboard.DOWN)) |
122 |
{
|
123 |
dY = 1; |
124 |
}
|
125 |
else
|
126 |
{
|
127 |
dY = 0; |
128 |
}
|
129 |
if (key.isDown(Keyboard.RIGHT)) |
130 |
{
|
131 |
dX = 1; |
132 |
if (dY == 0) |
133 |
{
|
134 |
facing = "east"; |
135 |
}
|
136 |
else if (dY==1) |
137 |
{
|
138 |
facing = "southeast"; |
139 |
dX = dY=0.5; |
140 |
}
|
141 |
else
|
142 |
{
|
143 |
facing = "northeast"; |
144 |
dX=0.5; |
145 |
dY=-0.5; |
146 |
}
|
147 |
}
|
148 |
else if (key.isDown(Keyboard.LEFT)) |
149 |
{
|
150 |
dX = -1; |
151 |
if (dY == 0) |
152 |
{
|
153 |
facing = "west"; |
154 |
}
|
155 |
else if (dY==1) |
156 |
{
|
157 |
facing = "southwest"; |
158 |
dY=0.5; |
159 |
dX=-0.5; |
160 |
}
|
161 |
else
|
162 |
{
|
163 |
facing = "northwest"; |
164 |
dX = dY=-0.5; |
165 |
}
|
166 |
}
|
167 |
else
|
168 |
{
|
169 |
dX = 0; |
170 |
if (dY == 0) |
171 |
{
|
172 |
//facing="west";
|
173 |
}
|
174 |
else if (dY==1) |
175 |
{
|
176 |
facing = "south"; |
177 |
}
|
178 |
else
|
179 |
{
|
180 |
facing = "north"; |
181 |
}
|
182 |
}
|
183 |
if (dY == 0 && dX == 0) |
184 |
{
|
185 |
hero.clip.gotoAndStop(facing); |
186 |
idle = true; |
187 |
}
|
188 |
else if (idle||currentFacing!=facing) |
189 |
{
|
190 |
idle = false; |
191 |
currentFacing = facing; |
192 |
hero.clip.gotoAndPlay(facing); |
193 |
}
|
194 |
if (! idle && isWalkable()) |
195 |
{
|
196 |
heroCartPos.x += speed * dX; |
197 |
heroCartPos.y += speed * dY; |
198 |
heroPointer.x=heroCartPos.x; |
199 |
heroPointer.y=heroCartPos.y; |
200 |
|
201 |
var newPos:Point = IsoHelper.twoDToIso(heroCartPos); |
202 |
//collision check
|
203 |
hero.x = borderOffsetX + newPos.x; |
204 |
hero.y = borderOffsetY + newPos.y; |
205 |
heroTile=IsoHelper.getTileCoordinates(heroCartPos,tileWidth); |
206 |
depthSort(); |
207 |
//trace(heroTile);
|
208 |
}
|
209 |
tileTxt.text="Hero is on x: "+heroTile.x +" & y: "+heroTile.y; |
210 |
}
|
211 |
|
212 |
//check for collision tile
|
213 |
function isWalkable():Boolean{ |
214 |
var able:Boolean=true; |
215 |
var newPos:Point =new Point(); |
216 |
newPos.x=heroCartPos.x + (speed * dX); |
217 |
newPos.y=heroCartPos.y + (speed * dY); |
218 |
switch (facing){ |
219 |
case "north": |
220 |
newPos.y-=heroHalfSize; |
221 |
break; |
222 |
case "south": |
223 |
newPos.y+=heroHalfSize; |
224 |
break; |
225 |
case "east": |
226 |
newPos.x+=heroHalfSize; |
227 |
break; |
228 |
case "west": |
229 |
newPos.x-=heroHalfSize; |
230 |
break; |
231 |
case "northeast": |
232 |
newPos.y-=heroHalfSize; |
233 |
newPos.x+=heroHalfSize; |
234 |
break; |
235 |
case "southeast": |
236 |
newPos.y+=heroHalfSize; |
237 |
newPos.x+=heroHalfSize; |
238 |
break; |
239 |
case "northwest": |
240 |
newPos.y-=heroHalfSize; |
241 |
newPos.x-=heroHalfSize; |
242 |
break; |
243 |
case "southwest": |
244 |
newPos.y+=heroHalfSize; |
245 |
newPos.x-=heroHalfSize; |
246 |
break; |
247 |
}
|
248 |
newPos=IsoHelper.getTileCoordinates(newPos,tileWidth); |
249 |
if(levelData[newPos.y][newPos.x]==1){ |
250 |
able=false; |
251 |
}else{ |
252 |
//trace("new",newPos);
|
253 |
}
|
254 |
return able; |
255 |
}
|
256 |
|
257 |
//sort depth & draw to canvas
|
258 |
function depthSort() |
259 |
{
|
260 |
bg.bitmapData.lock(); |
261 |
bg.bitmapData.fillRect(rect,0xffffff); |
262 |
var tileType:uint; |
263 |
var mat:Matrix=new Matrix(); |
264 |
var pos:Point=new Point(); |
265 |
for (var i:uint=0; i<levelData.length; i++) |
266 |
{
|
267 |
for (var j:uint=0; j<levelData[0].length; j++) |
268 |
{
|
269 |
tileType = levelData[i][j]; |
270 |
//placeTile(tileType,i,j);
|
271 |
|
272 |
pos.x = j * tileWidth; |
273 |
pos.y = i * tileWidth; |
274 |
pos = IsoHelper.twoDToIso(pos); |
275 |
mat.tx = borderOffsetX + pos.x; |
276 |
mat.ty = borderOffsetY + pos.y; |
277 |
if(tileType==0){ |
278 |
bg.bitmapData.draw(grassTile,mat); |
279 |
}else{ |
280 |
bg.bitmapData.draw(wallTile,mat); |
281 |
}
|
282 |
if(heroTile.x==j&&heroTile.y==i){ |
283 |
mat.tx=hero.x; |
284 |
mat.ty=hero.y; |
285 |
bg.bitmapData.draw(hero,mat); |
286 |
}
|
287 |
|
288 |
}
|
289 |
}
|
290 |
bg.bitmapData.unlock(); |
291 |
//add character rectangle
|
292 |
}
|
293 |
createLevel(); |
Poin Pendaftaran
Berikan pertimbangan khusus untuk poin pendaftaran ubin dan pahlawan. (Titik registrasi dapat dianggap sebagai titik asal untuk setiap sprite tertentu.) Ini umumnya tidak akan jatuh di dalam gambar, melainkan akan menjadi sudut kiri atas kotak pembatas sprite.
Kami harus mengubah kode gambar kami untuk memperbaiki titik pendaftaran dengan benar, terutama untuk pahlawan.
Deteksi Tabrakan
Hal lain yang menarik untuk dicatat adalah kami menghitung deteksi tabrakan berdasarkan titik di mana pahlawan berada.
Tapi pahlawan memiliki volume, dan tidak dapat secara akurat diwakili oleh satu titik, jadi kita perlu mewakili pahlawan sebagai persegi panjang dan memeriksa tabrakan terhadap setiap sudut persegi panjang ini sehingga tidak ada tumpang tindih dengan ubin lainnya dan karenanya tidak ada artefak yang mendalam.
Pintasan
Dalam demo, saya hanya menggambar ulang adegan lagi setiap frame berdasarkan posisi baru sang pahlawan. Kami menemukan ubin yang ditempati pahlawan dan menggambar pahlawan di atas ubin tanah ketika loop render mencapai ubin tersebut.
Tetapi jika kita melihat lebih dekat, kita akan menemukan bahwa tidak perlu mengulang melalui semua ubin dalam kasus ini. Ubin rumput dan ubin dinding atas dan kiri selalu diambil sebelum pahlawan ditarik, jadi kita tidak perlu menggambar ulang sama sekali. Juga, ubin dinding bawah dan kanan selalu di depan pahlawan dan karenanya ditarik setelah pahlawan ditarik.
Pada dasarnya, kita hanya perlu melakukan pemilahan kedalaman antara dinding di dalam area aktif dan pahlawan - yaitu, dua ubin. Memperhatikan pintas semacam ini akan membantu Anda menghemat banyak waktu pemrosesan, yang dapat menjadi sangat penting untuk kinerja.
Kesimpulan
Sekarang, Anda harus memiliki dasar yang hebat untuk membangun permainan isometrik Anda sendiri: Anda dapat membuat dunia dan benda-benda di dalamnya, mewakili data tingkat dalam array 2D sederhana, mengkonversi antara Cartesian dan coordiates isometrik, dan menangani konsep-konsep seperti pengurutan kedalaman dan animasi karakter. Nikmati menciptakan dunia isometrik!