HTML5 Canvas'ta boyanacak 2B dizisindeki metin haritalarını yönetme


46

Bu yüzden sadece eğlence için bir HTML5 RPG yapıyorum. Harita bir <canvas>(512px genişlik, 352px yükseklik | 16 döşeme, 11 döşeme yukarıdan aşağıya). Boyamak için daha etkili bir yol olup olmadığını bilmek istiyorum <canvas>.

İşte şimdi nasıl var.

Karolar haritada nasıl yüklenir ve boyanır?

Harita, Image()parça kullanılarak çinilerle (32x32) boyanıyor . Görüntü dosyaları basit bir fordöngüden yüklenir ve tiles[]kullanımda PAINTED olarak adlandırılan bir diziye konur drawImage().

İlk önce, fayansları yüklüyoruz ...

görüntü tanımını buraya girin

ve işte nasıl yapıldığı:

// SET UP THE & DRAW THE MAP TILES
tiles = [];
var loadedImagesCount = 0;
for (x = 0; x <= NUM_OF_TILES; x++) {
  var imageObj = new Image(); // new instance for each image
  imageObj.src = "js/tiles/t" + x + ".png";
  imageObj.onload = function () {
    console.log("Added tile ... " + loadedImagesCount);
    loadedImagesCount++;
    if (loadedImagesCount == NUM_OF_TILES) {
      // Onces all tiles are loaded ...
      // We paint the map
      for (y = 0; y <= 15; y++) {
        for (x = 0; x <= 10; x++) {
          theX = x * 32;
          theY = y * 32;
          context.drawImage(tiles[5], theY, theX, 32, 32);
        }
      }
    }
  };
  tiles.push(imageObj);
}

Doğal olarak, bir oyuncu bir oyuna başladığında en son bıraktıkları haritayı yükler. Ama burada, bütünüyle çim biçimli bir harita.

Şu anda, haritalar 2D dizileri kullanıyor. İşte örnek bir harita.

[[4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 1, 1, 1, 1], 
[1, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 1, 1, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 1, 1, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 13, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 13, 11, 11, 11, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 1, 1, 1, 1, 1, 1, 1, 13, 13, 13, 13, 13, 1], 
[1, 1, 1, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 1, 1, 1]];

Basit bir ifyapı kullanarak farklı haritalar alıyorum . Yukarıdaki 2d dizisi olduğunda return, her dizideki karşılık gelen sayı, Image()içeride depolanana göre boyanacaktır tile[]. Sonra doğru koordinat üzerinde boyamak drawImage()için xve yve zamana göre boyamak olacaktır .32x-y

Çoklu harita değiştirme nasıl gerçekleşir?

Oyunum ile, haritalar izlemek için beş şey vardır: currentID, leftID, rightID, upID, ve bottomID.

  • currentID: Bulunduğunuz haritanın geçerli kimliği.
  • leftID:currentID Geçerli haritanın solundan çıktığınızda hangi ID'nin yükleneceği.
  • rightID:currentID Geçerli haritanın sağından çıktığınızda hangi ID'nin yükleneceği.
  • downID:currentID Geçerli haritanın altından çıkarken hangi ID'nin yükleneceği.
  • upID:currentID Geçerli haritanın tepesinden çıktığınızda yüklenecek kimlik numarası .

Nota şey: ya varsa leftID, rightID, upIDveya bottomID, belirli DEĞİLDİR aracının onlar vardır 0. Bu, haritanın bu tarafını terk edemeyecekleri anlamına gelir. Bu sadece görünmez bir abluka.

Böylece, bir kişi haritanın bir tarafından çıktıktan sonra, nereye çıktıklarına bağlı olarak ... örneğin alttan çıkmışlarsa, bottomIDyükleneceklerin sayısı mapve dolayısıyla harita üzerinde boyanacak.

İşte size daha iyi görselleştirmenize yardımcı olacak temsili bir .GIF:

görüntü tanımını buraya girin

Gördüğünüz gibi, er ya da geç, pek çok harita ile pek çok kimlikle uğraşıyorum . Ve bu muhtemelen biraz kafa karıştırıcı ve telaşlı olabilir.

Bariz artıları, bir seferde 176 kiremit yüklemesi, küçük bir 512x352 kanvas yenilemesi ve bir haritayı idare etmesidir. Sonuç olarak MAP kimlikleri birçok harita ile uğraşırken zaman zaman kafa karıştırıcı olabiliyor.

Benim sorum

  • Bu, haritaları saklamak için etkili bir yol mu (fayans kullanımı verilen) veya haritaları kullanmanın daha iyi bir yolu var mı?

Dev bir haritanın çizgileri boyunca düşünüyordum. Harita boyutu büyük ve hepsi bir 2B dizi. Ancak görünüm alanı hala 512x352 pikseldir.

İşte görselleştirmek için (bu soru için) yaptığım başka bir .gif:

görüntü tanımını buraya girin

İngilizcemi anlayamıyorsanız üzgünüm. Lütfen anlamakta zorlandığınız herhangi bir şeyi sorun. Umarım açıklığa kavuşturdum. Teşekkürler.


16
Bu soruyu grafiklerle ortaya koyma çabası ve tümü bir artığı hak ediyor. :)
Tapio

Yanıtlar:


5

Düzenleme: Sadece cevabımın koduna dayandığını ama soruna cevap vermediğini gördüm. Bu bilgiyi kullanabilmen için eski cevabı sakladım.

Düzenleme 2: Orijinal kodla ilgili 2 sorunu düzelttim: - x ve y sonunun hesaplanmasında +1 eki, parantez içinde yanlışlıkla yapıldı, ancak bölümden sonra eklenmesi gerekiyor. - x ve y değerlerini doğrulamayı unuttum.

Geçerli haritayı bellekte saklayabilir ve çevresindeki haritaları yükleyebilirsiniz. 5x5 boyutunda 2d tek seviyeli bir diziden oluşan bir dünya düşünün. Oyuncu 1. alanda başlar. Seviyenin üst ve sol sınırları dünyanın kenarında olduğundan, yüklenmeleri gerekmez. Bu durumda, 1/1 seviyesi aktif ve seviye 1/2 ve 2/1 yüklenir ... Eğer oyuncu şimdi sağa hareket ederse, tüm seviyeler (hareket ettiğinize kadar) boşaltılır ve yeni ortam yüklendi. Ortalama seviye 2/1 şimdi aktif, 1/1, 2/2 ve 3/1 şimdi yüklendi.

Umarım bu size nasıl yapılabileceği hakkında bir fikir verir. Ancak bu yaklaşım, oyun sırasında her seviyenin simüle edilmesi gerektiğinde çok işe yaramaz. Ancak, kullanılmayan seviyeleri dondurabiliyorsanız, bunun iyi çalışması gerekir.

Eski Cevap:

Seviye oluştururken yaptığım şey (aynı zamanda döşeme tabanlı), döşeme dizisindeki hangi öğelerin görünüm alanıyla kesiştiğini hesaplamak ve ardından yalnızca bu döşemeleri oluşturuyorum. Bunun gibi büyük haritalara sahip olabilirsiniz, ancak sadece kısmı ekranda göstermek zorundasınız.

//Mock values
//camera position x
//viewport.x = 233f;
//camera position y
//viewport.y = 100f;
//viewport.w = 640
//viewport.h = 480
//levelWidth = 10
//levelHeight = 15
//tilesize = 32;

//startX defines the starting index for the x axis.
// 7 = 233 / 32
int startX = viewport.x / tilesize;

//startY defines the starting index
// 3 = 100 / 32
int startY = viewport.y / tilesize;

//End index y
// 28 = (233 + 640) / 32 + 1
int endX = ((viewport.x + viewport.w) / tilesize) + 1;

//End index y
// 19 = (100 + 480) / 32 + 1
int endX = ((viewport.x + viewport.w) / tilesize) + 1;

//Validation
if(startX < 0) startX = 0;
if(startY < 0) startY = 0;
//endX is set to 9 here
if(endX >= levelWidth) endX = levelWidth - 1;
//endX is set to 14 here
if(endY >= levelHeight) endY = levelHeight - 1;

for(int y = startY; y < yEnd; y++)
    for(int x = startX; x < xEnd; x++)
          [...]

Not: Yukarıdaki kod test edilmemiştir, ancak ne yapılması gerektiği konusunda bir fikir vermelidir.

Bir görünüm alanı gösterimi için temel bir örneği takip ederek:

public class Viewport{
    public float x;
    public float y;
    public float w;
    public float h;
}

Bir javascript gösterimi şöyle görünür

<script type="text/javascript">
//Declaration
//Constructor
var Viewport = function(xVal, yVal, wVal, hVal){
    this.x = xVal;
    this.y = yVal;
    this.w = wVal;
    this.h = hVal;
}
//Prototypes
Viewport.prototype.x = null;
Viewport.prototype.y = null;
Viewport.prototype.w = null;
Viewport.prototype.h = null;
Viewport.prototype.toString = function(){
    return ["Position: (", this.x, "/" + this.y + "), Size: (", this.w, "/", this.h, ")"].join("");
}

//Usage
var viewport = new Viewport(23, 111, 640, 480);
//Alerts "Position: (23/111), Size: (640/480)
alert(viewport.toString());
</script>

Kod örneğimi alırsanız, int ve float bildirimlerini var ile değiştirmeniz gerekir. Ayrıca, aynı davranışı elde etmek için, int tabanlı değerlere atanan bu hesaplamalarda Math.floor kullandığınızdan emin olun.

Düşüneceğim bir şey (javascript'te bunun önemli olup olmadığından emin olmasam da), tüm döşemeleri (veya mümkün olduğunca çok) tek bir dosya kullanmak yerine tek bir büyük dokuya koymak.

İşte nasıl yapılacağını açıklayan bir link: http://thiscouldbebetter.wordpress.com/2012/02/25/slicing-an-image-into-tiles-in-javascript/


Sadece açıklığa kavuşturmak için ... viewport.x "531" olarak ve önceden viewport.y "352" ve döşemeler = "32" olur. Ve benzeri..?
Test

Kamera pozisyonunun 531 olduğunu kastediyorsanız, o zaman evet. Yukarıdaki koda bazı yorumlar ekledim.
tom van green

JavaScript kullanıyorum ... Java bununla aynı.
Test

Evet, sadece js tabanlı bir viewport sınıfı yapmalısınız (veya sadece 4, x, y, w ve h değişkenleri oluşturabilirsiniz). Ayrıca int değerlerine atanmış bu hesaplamaları (Math.floor) da yatırdığınızdan emin olmalısınız. Viewport sınıfının nasıl göründüğünü gösteren bir kod örneği ekledim. Sadece kullanımdan önce bildirimi koyduğunuzdan emin olun.
tom van green

Sadece koyduğunu söylüyorsun 640, 480.. ama doğru olabilir 512, 352mi?
test edin

3

Haritaları döşeme olarak saklamak, varlıkları yeniden kullanmanıza ve haritayı yeniden tasarlamanıza izin verir. Gerçekçi olmakla birlikte, oyuncuya gerçekten fazla bir fayda sağlamaz. Artı, her seferinde her bir taşı yeniden çiziyorsun. İşte ne yapardım:

Tüm haritayı büyük bir dizi olarak saklayın. Haritalara, alt haritalara ve potansiyel olarak alt alt haritalara sahip olmanın anlamı yoktur (örneğin, binalara giriyorsanız). Yine de hepsini yüklüyorsun ve bu her şeyi güzel ve basit tutar.

Haritayı bir kerede oluşturun. Haritayı oluşturduğunuz ekran dışı bir tuvale sahip olun ve bunu oyunda kullanın. Bu sayfa basit bir javascript fonksiyonu ile nasıl yapılacağını gösterir:

var renderToCanvas = function (width, height, renderFunction) {
  var buffer = document.createElement('canvas');
  buffer.width = width;
  buffer.height = height;
  renderFunction(buffer.getContext('2d'));
  return buffer;
};

Bu, haritanızın fazla değişmeyeceğini varsayıyor. Alt haritaları yerinde (örneğin, bir binanın içi) yapmak istiyorsanız, onu da bir tuval üzerinde hazırlar ve sonra üstüne çekersiniz. İşte haritanızı oluşturmak için bunu nasıl kullanabileceğinize dair bir örnek:

var map = renderToCanvas( map_width*32, map_height*32, renderMap );

var MapSizeX = 512;
var MapSizeY = 352;

var draw = function(canvas, mapCentreX, mapCentreY) {
  var context = canvas.getContext('2d');

  var minX = playerX - MapSizeX/2;
  var minY = playerY - MapSizeY/2;

  context.drawImage(map,minX,minY,MapSizeX,MapSizeY,0,0,MapSizeX,MapSizeY);
}

Bu, haritanın küçük bir bölümünü ortalanmış olarak çizecektir mapCentreX, mapCentreY. Bu, alt karo hareketinin yanı sıra, haritanın tamamında yumuşak kaydırma yapmayı da sağlar - oynatıcı konumunu bir karonun 1 / 32'si kadar saklayabilir, böylece harita üzerinde yumuşak bir hareket elde edebilirsiniz (İsterseniz açıkça kiremit döşemeyi stilistik bir seçenek olarak taşıyın, sadece 32'lik parçalarla artırın).

İsterseniz veya dünyanız çok büyükse ve hedef sistemlerinizdeki belleğe sığmıyorsa haritanızı kesebilirsiniz (piksel başına 1 bayt için bile, 512x352 bir haritanın 176 KB olduğunu unutmayın) Haritanızı bu şekilde işleyerek, çoğu tarayıcıda büyük bir performans artışı göreceksiniz .

Bunun size, büyük bir görüntüyü düzenlemenin başağrısı olmadan dünyadaki döşemeleri yeniden kullanma esnekliği, aynı zamanda bunu hızlı ve kolay bir şekilde çalıştırmanıza ve sadece bir 'harita' (ya da istediğiniz kadar 'harita') göz önüne almanıza izin vermesi esnekliğidir. .



1

ID'leri takip etmenin kolay bir yolu, onlarla birlikte başka bir 2d dizisini kullanmak olacaktır: sadece "x" yi "meta dizisi" nde tutarak diğer kimlikleri kolayca arayabilirsiniz.

Bu varlık burada / O 2012 onların son I'den 2D kiremitli tuval oyun (iyi, aslında çok oyunculu fakat karo motoru da kaplıdır HTML5) Google'ın almak olduğunu söyledi: http://www.youtube.com/watch?v=Prkyd5n0P7k It ayrıca kaynak kodu mevcuttur.


1

Önce tüm diziyi bir diziye yerleştirme fikriniz verimli bir yoldur, ancak JavaScript Injection'ı kullanmak kolay olacaktır, herkes haritayı tanıyabilecek ve macera başlayacak.

Ben de web tabanlı bir RPG oyunu üzerinde çalışıyorum, haritamı yükleme biçimim, haritayı veri tabanından yükleyen bir PHP sayfasındaki Ajax üzerinden, X, Y, Z konumu ve vlaue yazan yere çini, çizin ve son olarak oyuncu hareket ettirin.

Daha verimli hale getirmek için hala üzerinde çalışıyorum, ancak harita bu şekilde tutmak için yeterince hızlı bir şekilde yükleniyor.


1
Bir demo görebilir miyim?
Test

1

Hayır , dizi, kiremit haritasını depolamanın en etkili yoludur .

Biraz daha az bellek gerektiren birkaç yapı olabilir, ancak yalnızca verileri yüklemek ve erişmek için çok daha yüksek maliyetler pahasına.


Bununla birlikte, dikkate alınması gereken şey, bir sonraki haritayı yükleyeceğiniz zamandır. Haritaların özellikle büyük olmadığı göz önüne alındığında, aslında en uzun sürecek olan şey sunucunun haritayı yayınlamasını beklemektir. Gerçek yükleme sadece birkaç milisaniye alacaktır. Ancak bu bekleme zaman uyumsuz yapılabilir, oyunun akışını engellemesi gerekmez. Yani en iyi şey, gerektiğinde değil, verileri gerektiğinde yüklemektir. Müzikçalar bir harita üzerinde, şimdi tüm bitişik haritaları yükleyin. Oynatıcı bir sonraki haritaya hareket eder, bitişik tüm haritaları tekrar vb. Yükler.

Bu sayede, bitişik haritaları da çizerek sınırsız bir dünya illüzyonu yaratabilirsiniz, bu durumda sadece bitişik haritaları değil, aynı zamanda bunlara bitişik olanları da yüklemeniz gerekir.


0

Neden düşündüğünüzden emin değilim: Gördüğünüz gibi, er ya da geç, birçok harita ile pek çok kimlikle uğraşacağım. Ve bu muhtemelen biraz kafa karıştırıcı ve telaşlı olabilir.

LeftID her zaman geçerli ID'nin -1'i, sağID her zaman geçerliID'nin +1'i olur.

UpID her zaman geçerli ID'nin - (toplam harita Genişliği) ve downID daima, isabet ettiğiniz sıfıra sıfır hariç, geçerli ID'nin + (toplam harita genişliği) olacaktır.

Tek bir harita birçok haritaya bölünebilir ve sırayla XXXX'in id olduğu mapIDXXXX ile kaydedilir. Bu şekilde, kafanız karışacak bir şey yok. Sitenizde bulundum ve görünen o ki, yanlış olabilir, sorunun harita editörünüze bağlı olması, otomasyonunuzu büyük bir haritayı kaydetmeden birden çok küçük parçaya bölmesini engelleyen bir boyut sınırlaması getiriyor ve bu sınırlama muhtemelen teknik bir çözümü kısıtlıyor.

Her yöne kaydırılan bir Javascript harita editörü yazdım, 1000 x 1000 (bu boyutu tavsiye etmem) döşer ve hala 50 x 50 harita kadar iyi hareket eder ve paralaks kaydırmayı da içeren 3 katlıdır. Json formatında kaydeder ve okunur. Bir e-postayı yaklaşık 2meg sakıncası olmazsa size bir kopyasını gönderirim. Bu github üzerinde olması gerekiyordu ama herhangi bir makul (yasal) fayans setim yok, bu yüzden henüz oraya koymak için uğraşmadım.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.