2016-02-22 5 views
0

Я работаю над изометрическим движком игры для своей собственной игры. В настоящее время это большой открытый мир, в котором данные карты извлекаются динамически с сервера Node.js.Как оптимизировать рендеринг холста для динамической загрузки игрового мира HTML5?

Чтобы понять, что я делаю ... по большей части это мир, основанный на плитки. Таким образом, каждая карта имеет максимальное количество столбцов, строк (19), и каждый мир имеет максимальное количество карт по col, row (6). Таким образом, это карта мира 6x6, состоящая из 19x19 плиток на карту. Всякий раз, когда игроки перемещаются на новую карту/регион, клиент запрашивает матрицу 3x3 окружающих карт, а карта центра - это карта, на которой в данный момент находится игрок. Эта часть довольно хорошо оптимизирована.

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

В основном, как я работаю прямо сейчас, когда данные отправляются с сервера, он добавляет каждую карту и все изображения плитки для каждого столбца/строки, которую он должен отображать в буфер. Каждый цикл игрового цикла будет в основном отображать небольшую часть из 25 фрагментов на скрытом холсте конкретной карты. Когда все запрошенные карты сделаны рендерингом (после нескольких игровых циклов), камера будет двигаться вперед и объединить эти скрытые карты в один большой холст карты матрицы 3x3 (путем нарезки частей из скрытых холстов и слияния их с новыми холст).

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

Спасибо!

+0

Лучший "оптимизации" это сделать, как несколько вызовов отрисовки, насколько это возможно. Просто потому, что у вас есть данные с сервера, это не значит, что вам нужно рисовать его, когда он выключен. – user2867288

+0

Насколько велики плитки (в пикселях) ??? – Blindman67

+0

@ Blindman67 128x64 изображение для каждой плитки – Ryan

ответ

0

Вот пример рендеринга сетки 19x19 в каждом кадре. Новая случайная плитка добавляется справа налево сверху вниз в каждом кадре. Сетка отображается в том же порядке, и вы можете видеть, что это работает для перекрытия плит.

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

update Я предоставил функцию для устранения переустановки и переключения. Это может повысить производительность для некоторых людей. Он рисует снизу вверх сверху слева направо. Это сначала накладывает элементы наложения, а с помощью globalCompositeOperation «distination-over» говорит, что холст оставляет только отдельные пиксели при добавлении нового контента. Это должно означать меньшую работу, которую нужно делать при размещении пикселей на холсте, поскольку это не рисование неиспользуемых пикселей.

var cols = 19; 
 
var tile_width = 32; 
 
var rows = 19; 
 
var tile_height = 16; 
 
var y_offset = 64; 
 

 
var h_tw = tile_width/2; 
 
var h_th = tile_height/2; 
 

 
var frames = 0; 
 
var fps = "- fps"; 
 

 
setInterval(function(){ 
 
    fps = frames + " fps"; 
 
    frames = 0; 
 
}, 1000); 
 

 
var can = document.getElementById('tile'); 
 
var ctx = can.getContext('2d'); 
 

 
var wcan = document.getElementById('world'); 
 
var wctx = wcan.getContext('2d'); 
 
wcan.width = cols * tile_width; 
 
wcan.height = rows * tile_height + y_offset; 
 

 
var tiles = initTiles(); 
 

 

 
document.getElementById('toggle').addEventListener('click', function() { 
 
    if (this.innerHTML == 'renderWorld') { 
 
    renderFn = renderWorldNoOverdraw; 
 
    this.innerHTML = "renderWorldNoOverdraw"; 
 
    } else { 
 
    renderFn = renderWorld; 
 
    this.innerHTML = "renderWorld"; 
 
    } 
 
}); 
 

 
//renderWorld(); 
 
var ani_x = cols; 
 
var ani_y = 0; 
 
var renderFn = renderWorld; 
 
ani(); 
 

 

 
function initTiles() { 
 
    var tiles = []; 
 
    
 
    for (var y = 0; y < rows; y++) { 
 
    var row = []; 
 
    for (var x = 0; x < cols; x++) { 
 
     var can = document.createElement('canvas'); 
 
     can.width=tile_width; 
 
     can.height=tile_height+y_offset; 
 
     row[x]=can; 
 
    } 
 
    tiles[y] = row; 
 
    } 
 
    return tiles; 
 
} 
 

 
function ani() { 
 
    var can = tiles[ani_y][--ani_x] 
 
    
 
    if (ani_x == 0) ani_x = cols, ani_y++; 
 
    ani_y %= rows; 
 
    var ctx = can.getContext('2d'); 
 
    randTile(can, ctx); 
 
    
 
    renderFn(); 
 
    requestAnimationFrame(ani); 
 
} 
 

 
// renders from bottom left to right and skips 
 
// drawing over pixels already present. 
 
function renderWorldNoOverdraw() { 
 
    frames++; 
 
    wctx.clearRect(0,0,wcan.width,wcan.height); 
 
    wctx.save(); 
 
    wctx.globalCompositeOperation = "destination-over"; 
 
    wctx.translate(0, y_offset); 
 
    var x_off = 0; 
 
    var y_off = 0; 
 
    var y_off2 = 0; 
 
    for (var y = rows; y--;) { 
 
    x_off = (cols * h_tw)- ((rows-y) * h_tw); 
 
    y_off = y * h_th + tile_height; 
 
    y_off2 = y_off; 
 
    for (var x = 0; x < cols; x++) { 
 
     var can = tiles[y][x]; 
 
     wctx.drawImage(can, x_off, y_off2 + y_offset); 
 
     y_off2 -= h_th; 
 
     x_off += h_tw; 
 
    } 
 
    } 
 
    wctx.translate(0,-y_offset); 
 
    wctx.fillStyle = "#ddaadd"; 
 
    wctx.fillRect(0,0,wcan.width, wcan.height); 
 
    wctx.restore(); 
 
    wctx.fillStyle= "black"; 
 
    wctx.fillText(fps, 10, 10); 
 
} 
 

 
function renderWorld() { 
 
    frames++; 
 
    wctx.fillStyle = "#CCEEFF"; 
 
    wctx.fillRect(0, 0, wcan.width, wcan.height); 
 
    wctx.save(); 
 
    wctx.translate(0, y_offset); 
 
    var x_off = 0; 
 
    var y_off = 0; 
 
    var y_off2 = 0; 
 
    for (var y = 0; y < rows; y++) { 
 
    x_off = (cols * h_tw) + (y * h_tw) - h_tw; 
 
    y_off = y * h_th; 
 
    y_off2 = y_off; 
 
    for (var x = cols; x--;) { 
 
     var can = tiles[y][x]; 
 
     wctx.drawImage(can, x_off, y_off2 - 64); 
 
     y_off2 += h_th; 
 
     x_off -= h_tw; 
 
    } 
 
    y_off += h_th; 
 
    x_off -= h_tw; 
 
    } 
 
    wctx.restore(); 
 
    wctx.fillStyle= "black"; 
 
    wctx.fillText(fps, 10, 10); 
 
} 
 

 
function randTile(can, ctx) { 
 
    var maxH = can.height - 24; 
 
    var ranH = Math.floor(Math.random() * maxH); 
 
    var h = Math.max(ranH, 1); 
 
    ctx.clearRect(0, 0, can.width, can.height); 
 
    ctx.beginPath(); 
 
    ctx.save(); 
 
    ctx.translate(0, can.height - 16); 
 

 
    ctx.moveTo(0, 8); 
 
    ctx.lineTo(16, 0); 
 
    ctx.lineTo(32, 8); 
 
    ctx.lineTo(16, 16); 
 
    ctx.lineTo(0, 8); 
 
    ctx.strokeStyle = "#333333"; 
 
    ctx.stroke(); 
 

 
    // random floor color 
 
    var colors = ["#dd9933", "#22aa00", "#66cccc", "#996600"]; 
 
    ctx.fillStyle = colors[Math.floor(Math.random() * 4)]; 
 
    ctx.fill(); 
 

 
    // random building 
 
    if (Math.floor(Math.random() * 8) == 0) { 
 
    ctx.beginPath(); 
 
    ctx.moveTo(8, 8); 
 
    ctx.lineTo(8, -h - 4); 
 
    ctx.lineTo(16, -h); 
 
    ctx.lineTo(16, 12); 
 
    ctx.lineTo(8, 8); 
 
    ctx.stroke(); 
 
    ctx.fillStyle = "#333333"; 
 
    ctx.fill(); 
 

 
    ctx.beginPath(); 
 
    ctx.moveTo(16, 12); 
 
    ctx.lineTo(16, -h); 
 
    ctx.lineTo(24, -h - 4); 
 
    ctx.lineTo(24, 8); 
 
    ctx.lineTo(16, 12); 
 
    ctx.stroke(); 
 
    ctx.fillStyle = "#999999"; 
 
    ctx.fill() 
 

 
    ctx.beginPath(); 
 
    ctx.moveTo(16, -h); 
 
    ctx.lineTo(24, -h - 4); 
 
    ctx.lineTo(16, -h - 8); 
 
    ctx.lineTo(8, -h - 4); 
 
    ctx.moveTo(16, -h); 
 
    ctx.stroke(); 
 
    ctx.fillStyle = "#CCCCCC"; 
 
    ctx.fill() 
 
    } 
 
    ctx.restore(); 
 
}
body { 
 
    background-color: #444444; 
 
}
<button id="toggle">renderWorld</button><br/> 
 
<canvas id='tile' width="32" height="32" style="display:none"></canvas> 
 
<canvas id="world" width="608" height="368"> 
 
</canvas>

+0

интересно, я всегда думал об этом, но ... если вы сохраните каждый спрайт или графику, чтобы его собственный отдельный холст не будет слишком большим? Или вы заметили изменение в производительности, сделав это? У меня всегда было впечатление, да, лучше всего использовать несколько полос для наложения/рендеринга, но слишком многие могут замедлить работу вашего браузера и потенциально могут быть сотнями холстов. @wolfhammer – Ryan

+0

@ Ryan, вы правы, что для этого требуется больше ресурсов. Я вижу, что у вас большие плитки, поэтому это может привести к зависанию ваших ресурсов. Вы делаете своего рода двойной буферный подход, когда вы рисуете все за пределами экрана, а затем добавляете его на дисплей после его завершения. Для этого требуется больше ресурсов для большого холста с экрана, но он делает так, чтобы карты отображались до конца и не материализовались на основном холсте. Если вы не заботитесь о том, чтобы люди увидели загрузку деталей, тогда можно опираться на основной холст и отбрасывать скрытые холсты. – wolfhammer

+0

Спасибо Wolfhammer за вашу помощь! Это потрясающая информация! :) – Ryan

Смежные вопросы