Управление текстовыми картами в двумерном массиве для рисования на холсте HTML5

46

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

Вот как у меня это сейчас.

Как плитки загружены и нарисованы на карте

Карта рисуется плиткой (32х32) с использованием Image()фигуры. Файлы изображений загружаются через простой forцикл и помещаются в массив, вызываемый tiles[]для использования при рисовании 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 при работе со многими картами могут иногда сбивать с толку.

Мой вопрос

  • Это эффективный способ хранения карт (с учетом использования листов) или есть лучший способ обработки карт?

Я думал по линии гигантской карты. Размер карты большой, и все это один массив 2D. Однако область просмотра по-прежнему составляет 512x352 пикселей.

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

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

Извините, если вы не понимаете мой английский. Пожалуйста, спросите все, что у вас есть проблемы с пониманием. Надеюсь, я дал понять. Благодарю.

контрольная работа
источник
16
Усилия, вложенные в этот вопрос с графикой и все это заслуживает одобрения. :)
Тапио

Ответы:

5

Изменить: только что увидел, что мой ответ был основан на вашем коде, но на самом деле не ответил на ваш вопрос. Я сохранил старый ответ на случай, если вы сможете использовать эту информацию.

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

Вы можете просто сохранить текущую карту в памяти и загрузить окружающие карты. Представьте себе мир, который состоит из двумерного массива одиночных уровней размером 5х5. Игрок начинает с поля 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" и tileize = "32". И тому подобное..?
тест
Если вы имеете в виду, что положение камеры 531, то да. Я добавил несколько комментариев к коду выше.
Том Ван Грин
Я использую JavaScript ... Я Java почти идентичен этому.
тест
Да, вам просто нужно создать класс окна просмотра на основе js (или вы можете просто создать 4 переменные x, y, w и h). Кроме того, вы должны удостовериться (Math.floor) в тех вычислениях, которые присвоены значениям int. Я добавил пример кода, как будет выглядеть класс области просмотра. Просто убедитесь, что вы положили декларацию перед использованием.
Том Ван Грин
Просто сказать, что вы положили 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 It также имеет доступный исходный код.

Тапио
источник
1

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

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

Я все еще работаю над этим, чтобы сделать его более эффективным, но карта загружается достаточно быстро, чтобы сохранить его таким.

BernaMariano
источник
1
Могу ли я увидеть демо?
тест
1

Нет , массив является наиболее эффективным способом хранения карты тайлов .

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


Однако нужно немного подумать, когда вы собираетесь загрузить следующую карту. Учитывая, что карты не особенно большие, то, что на самом деле займет больше всего времени, ждет сервер, чтобы доставить карту. Фактическая загрузка займет всего несколько миллисекунд. Но это ожидание может быть асинхронным, оно не должно блокировать ход игры. Так что лучше всего загружать данные не по мере необходимости, а раньше. Игрок находится на одной карте, теперь загружаются все соседние карты. Игрок перемещается на следующую карту, снова загружает все смежные карты и т. Д. И т. П. Игрок даже не заметит, что ваша игра загружается в фоновом режиме.

Таким образом, вы также можете создать иллюзию мира без границ, также рисуя смежные карты, в этом случае вам нужно загрузить не только соседние карты, но и те, которые примыкают к ним.

API-Beast
источник
0

Я не уверен, почему вы думаете: как вы видите, рано или поздно со многими картами я буду иметь дело со многими удостоверениями личности. И это может стать немного запутанным и беспокойным.

LeftID всегда будет -1 от currentID, rightID всегда будет +1 от currentID.

UpID всегда будет - (общая ширина карты) текущего идентификатора, а downID всегда будет + (общая ширина карты) текущего идентификатора, за исключением нуля, означающего, что вы достигли края.

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

Я написал редактор карт Javascript, который прокручивает плитки во всех направлениях, 1000 x 1000 (я бы не рекомендовал этот размер), и он все еще перемещается так же хорошо, как карта 50 x 50, и это с 3 слоями, включая прокрутку параллакса. Сохраняет и читает в формате JSON. Я пришлю вам копию, если вы скажете, что не возражаете против электронного письма, приблизительно 2 мг. Он предназначен для работы на github, но у меня нет приличного (легального) набора плиток, поэтому я пока не удосужился поставить его там.

Джон
источник