Керування текстовими картами в двовимірному масиві, щоб бути намальованим на HTML5 Canvas


46

Отже, я роблю HTML5 RPG просто для розваги. Карта - це <canvas>(ширина 512 пікселів, висота 352 пікселів | 16 плиток поперек, 11 плиток зверху вниз). Я хочу знати, чи існує більш ефективний спосіб фарбування <canvas>.

Ось як у мене це зараз.

Як завантажуються та малюються плитки на карті

Карта намальована плиткою (32х32) за допомогою Image()шматка. Файли зображень завантажуються через простий forцикл і поміщаються в масив, покликаний tiles[]бути PAINTED при використанні drawImage().

Спочатку завантажуємо плитку ...

введіть тут опис зображення

і ось як це робиться:

// 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);
}

Природно, коли гравець починає гру, він завантажує карту, яку він остаточно залишив. Але для цього це карта з усіма травами.

Зараз карти використовують 2D-масиви. Ось приклад карти.

[[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]];

Я отримую різні карти, використовуючи просту ifструктуру. Як тільки 2d масив вище return, відповідне число у кожному масиві буде пофарбовано відповідно до Image()збереженого всередині tile[]. Тоді drawImage()відбудеться і пофарбувати відповідно до xта yі час, коли 32намалювати правильну x-yкоординату.

Як відбувається багаторазова комутація карти

З моєї гри, карти є п'ять речей , щоб стежити за: currentID, leftID, rightID, upID, і bottomID.

  • currentID: поточний ідентифікатор карти, на якій ви знаходитесь.
  • leftID: Який ідентифікатор currentIDзавантажувати, коли ви виходите зліва від поточної карти.
  • rightID: Ідентифікатор, який currentIDпотрібно завантажити, коли ви виходите праворуч від поточної карти.
  • downID: ідентифікатор, який currentIDпотрібно завантажити, коли ви виходите внизу поточної карти.
  • upID: Який ідентифікатор currentIDпотрібно завантажити, коли ви виходите вгорі поточної карти.

Що - то Примітка: Якщо якась або leftID, rightID, upIDабо bottomIDне є специфічними, це означає , що вони є 0. Це означає, що вони не можуть залишити цю сторону карти. Це просто невидима блокада.

Отже, як тільки людина вийде на бік карти, залежно від місця виходу ... наприклад, якщо вона вийшла на дні, bottomIDбуде номер mapзавантажуваного і таким чином буде намальований на карті.

Ось представницький .GIF, який допоможе вам краще візуалізувати:

введіть тут опис зображення

Як бачите, рано чи пізно з багатьма картами я матиму справу з багатьма посвідченнями. І це, можливо, може стати трохи заплутаним і неспокійним.

Очевидним плюсом є те, що він завантажує одночасно 176 плиток, оновлює невелике полотно 512x352 та обробляє одну карту за один раз. Суть полягає в тому, що ідентифікатори MAP при роботі з багатьма картами часом можуть заплутатися.

Моє запитання

  • Це ефективний спосіб зберігання карт (враховуючи використання плиток), чи є кращий спосіб поводження з картами?

Я думав по лінії гігантської карти. Розмір карти великий, і все це один двовимірний масив. Проте огляд перегляду все ще має 512x352 пікселів.

Ось ще один .gif, який я зробив (для цього питання), щоб допомогти візуалізувати:

введіть тут опис зображення

Вибачте, якщо ви не можете зрозуміти мою англійську. Будь ласка, запитайте все, що у вас є проблеми з розумінням. Сподіваюся, я дав зрозуміти. Дякую.


16
Зусилля, поставлені в цьому питанні з графікою, і все заслуговує на надбавку. :)
Тапіо

Відповіді:


5

Редагувати: Щойно я побачив, що моя відповідь заснована на вашому коді, але насправді не відповіла на ваше запитання. Я зберігав стару відповідь, якщо ви можете використовувати цю інформацію.

Редагувати 2: Я виправив 2 питання з оригінальним кодом: - Додавання +1 при обчисленні кінця x і y було помилково всередині дужок, але його потрібно додати після поділу. - Я забув перевірити значення x і y.

Ви можете просто зберегти поточну карту в пам'яті та завантажити навколишні карти. Уявіть собі світ, який складається з 2d масиву одиничних рівнів розміром 5x5. Гравець починається в полі 1. Оскільки верхня та ліва межі рівня знаходяться на краю світу, їх не потрібно завантажувати. Тож у цьому випадку рівень 1/1 активний, а рівень 1/2 та 2/1 завантажуються ... Якщо гравець зараз рухається вправо, усі рівні (крім того, на який ви переїжджаєте), вивантажуються, а нове оточення - завантажений. Засоби рівня 2/1 зараз активні, 1/1, 2/2 та 3/1 зараз завантажені.

Я сподіваюся, що це дає вам уявлення про те, як це можна зробити. Але такий підхід не буде працювати дуже добре, коли кожен рівень повинен бути імітований під час гри. Але якщо ви можете заморозити невикористані рівні, це повинно працювати добре.

Стара відповідь:

Що я роблю при візуалізації рівня (також на основі плитки) - це обчислити, які елементи з масиву плиток перетинаються з вікном перегляду, а потім я виводжу лише ці плитки. Начебто у вас можуть бути великі карти, але потрібно лише відобразити частину на екрані.

//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++)
          [...]

Примітка: наведений вище код не перевіряється, але він повинен дати уявлення, що робити.

Наступний основний приклад подання представницького огляду:

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

Представлення javascript буде виглядати так

<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>

Якщо ви берете приклад мого коду, вам просто потрібно замінити декларування int та float на var. Також переконайтеся, що ви використовуєте Math.floor для тих обчислень, які присвоєні значенням на основі int, щоб отримати таку саму поведінку.

Я б врахував (хоча я не впевнений, що це має значення в JavaScript) - це поставити всі плитки (або якомога більше) в одну велику текстуру, а не використовувати багато одиничних файлів.

Ось посилання, що пояснює, як це зробити: http://thiscouldbebetter.wordpress.com/2012/02/25/slicing-an-image-into-tiles-in-javascript/


Просто для уточнення ... viewport.x буде визначатися як "531", а viewport.y буде "352", а розмір плитки = "32". А такі ..?
тест

Якщо ви маєте на увазі, що положення камери 531, то так. Я додав кілька коментарів до коду вище.
tom van green

Я використовую JavaScript ... Я Java майже ідентичний цьому.
тест

Так, вам просто потрібно створити клас перегляду на основі js (або ви можете просто створити 4 змінні x, y, w і h). Крім того, ви повинні переконатися, що підлоги (Math.floor) ті обчислення, які присвоєні значенням int. Я додав приклад коду, як виглядатиме клас перегляду. Просто переконайтеся, що ви поставили декларацію перед використанням.
tom van green

Просто кажучи, що ти кладеш 640, 480.. але це може працювати з 512, 352правильним?
тест

3

Зберігання карт у вигляді плиток корисно для дозволу повторного використання активів та перероблення карти. Реально, хоча це не дуже корисно для гравця. Крім того, ви кожного разу перемальовуєте кожну плитку. Ось що я б робив:

Зберігайте всю карту як один великий масив. Немає сенсу мати карти та підмапи, а також потенційно під-карти (якщо, наприклад, ви входите до будівель). Ви все одно завантажуєте це, і це зберігає все добре і просто.

Візуалізуйте карту все одночасно. Майте позаекранне полотно, на яке ви будете відображати карту, а потім використовувати це у своїй грі. На цій сторінці показано, як це зробити за допомогою простої функції javascript:

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

Це за умови, що ваша карта не зміниться сильно. Якщо ви хотіли візуалізувати підкарти на місці (наприклад, внутрішню частину будівлі), ви просто виведете це на полотно, а потім намалюйте його зверху. Ось приклад того, як ви можете використовувати це для візуалізації карти:

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);
}

Це дозволить намалювати невелику частину карти, зосередженої навколо mapCentreX, mapCentreY. Це дозволить вам плавно прокручувати всю карту, а також рух під плиткою - ви можете зберігати позицію гравця як 1/32 частини плитки, щоб ви могли плавно рухатись по карті (зрозуміло, якщо ви хочете переміщення плитки за плиткою як стилістичний вибір, ви можете просто збільшити в шматки по 32).

Ви все ще можете згорнути свою карту, якщо вам подобається, або якщо ваш світ величезний і не впишеться в пам’ять у ваших цільових системах (майте на увазі, що навіть на 1 байт на піксель карта 512x352 становить 176 КБ), але попередньо -надавши карту таким чином, ви побачите значне збільшення продуктивності в більшості браузерів.

Це дає вам гнучкість повторного використання плиток у всьому світі без головного болю від редагування одного великого зображення, а також дозволяє вам швидко та легко запускати цю програму і розглядати лише одну «карту» (або стільки «карт», скільки вам подобається) .


2

У всіх реалізаціях я бачив, як карти плитки часто генеруються як одне велике зображення, а потім "збиваються" на більш дрібні шматочки.

https://gamedev.stackexchange.com/a/25035/10055

Ці шматки зазвичай мають потужність 2, тому вони оптимально вписуються в пам'ять.


1

Найпростішим способом відстеження ідентифікаторів було б просто використовувати інший 2d масив з ними: лише підтримуючи x, y на цьому "мета-масиві", ви можете легко шукати інші ідентифікатори.

Однак, Google взяв участь у 2D полотнох іграх (ну, власне, багатокористувацький HTML5, але також охоплюється двигун плитки) з недавнього I / O 2012: http://www.youtube.com/watch?v=Prkyd5n0P7k Це також доступний вихідний код.


1

Ваша ідея спочатку завантажити всю карту в масив - це ефективний спосіб, але отримати JavaScript Injection'd буде легко, будь-хто зможе дізнатися карту і вирушає в пригоду.

Я також працюю над веб-іграми RPG, як завантажую свою карту через Ajax зі сторінки PHP, яка завантажує карту з бази даних, де написано X, Y, Z-позицію та vlaue плитки, намалюйте її, і нарешті гравець рухається.

Я все ще працюю над цим, щоб зробити його більш ефективним, але карта завантажується досить швидко, щоб зберегти це таким чином.


1
Чи можу я побачити демонстрацію?
тест

1

Ні , масив - це найефективніший спосіб зберігання карти плитки .

Можливо, існує кілька структур, для яких потрібно трохи менше пам’яті, але лише за рахунок значно більших витрат на завантаження та доступ до даних.


Однак слід враховувати, коли ви збираєтеся завантажити наступну карту. Зважаючи на те, що карти не особливо великі, річ, яка насправді займе найдовше, чекає, коли сервер доставить карту. Фактичне завантаження займе лише кілька мілісекунд. Але це очікування можна зробити асинхронним, воно не повинно блокувати потік гри. Тому найкраще не завантажувати дані, коли потрібно, але раніше. Гравець знаходиться на одній карті, тепер завантажте всі сусідні карти. Гравець рухається на наступній карті, знову завантажує всі сусідні карти тощо, тощо. Гравець навіть не помітить, що ваша гра завантажується у фоновому режимі.

Таким чином, ви також можете створити ілюзію менш прикордонного світу, також намалювавши суміжні карти, але в цьому випадку вам потрібно завантажити не лише сусідні карти, але й ті, що примикають до них.


0

Я не впевнений, чому ви думаєте: Як ви бачите, рано чи пізно з багатьма картами я матиму справу з багатьма посвідченнями. І це, можливо, може стати трохи заплутаним і неспокійним.

LeftID завжди буде -1 поточногоID, правийID завжди буде +1 поточногоID.

UpID завжди буде - (загальна ширина карти) поточного ідентифікатора, а downID завжди буде + (загальна ширина карти) поточного ідентифікатора, за винятком нульового значення, що ви потрапили на край.

Одну карту можна розбити на безліч карт і зберігати послідовно за допомогою mapIDXXXX, де XXXX - це ідентифікатор. Таким чином, нічого не можна плутати. Я був на вашому сайті, і, здається, я можу помилятися, що проблема полягає в тому, щоб ваш редактор карт наклав обмеження розміру, що перешкоджає вашій автоматизації розбивати велику карту на кілька маленьких при збереженні, і це обмеження є ймовірно, обмежує технічне рішення.

Я написав редактор карт Javascript, який прокручується в усі сторони, плитки 1000 x 1000 (я б не рекомендував цього розміру), і він все одно рухається так само, як 50 x 50 карти, і це з 3 шарами, включаючи прокрутку паралакса. Він зберігає та читає у форматі json. Я надішлю вам копію, якщо ви скажете, що не заперечуєте проти електронної пошти приблизно в 2 мега. Це покликане бути на github, але я не маю жодного пристойного (законного) набору плиток, тому я ще не намагався розмістити його там.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.