«Танчики» на html5
Отображение карты уровня
Следующим этапом сделаем систему отображения тайлов на игровом поле. Мы предполагаем, что у нас есть тайлы разных типов: кирпичи там, бетон, вода, лёд, кусты.
И чтобы эти самые блоки рисовать на игровом поле, нужно ввести сущность карты и самих блоков.
Сущность карты:
class GameMap {
constructor() {
// Сетка 26x26 субтайлов
this.tileGrid = [];
for (let y = 0; y < GRID_SIZE; y++) {
this.tileGrid[y] = new Array(GRID_SIZE).fill(TILE_EMPTY);
}
// Карта для хранения состояния повреждений разрушаемых тайлов
this.damageMask = new Map();
// Флаг необходимости перерисовки фона
this.dirty = true;
}
}
Ну и в нашем основном классе надо карту создать:
/** @type {GameMap} */
let gameMap = null;
...
// в методе init
// Создаём и загружаем карту
gameMap = new GameMap();
gameMap.loadLevel(TEST_LEVEL_1);
// Рисуем фон (карта + рамка)
renderBackground(gameMap);
gameMap.dirty = false;
При это тестовый уровень сейчас выглядит вот так:
export const TEST_LEVEL_1 = [
'..........................', // 0 верхний ряд пустой (под спавн врагов)
'..........................', // 1
'..BB..BB..BB..BB..BB..BB..', // 2 кирпичные стены
'..BB..BB..BB..BB..BB..BB..', // 3
'..BB..BB..BB..BB..BB..BB..', // 4
'..BB..BB..SS..BB..BB..BB..', // 5 сталь/бетон
'..BB..FF..SS..FF..BB..BB..', // 6 лес
'..BB..FF......FF..BB..BB..', // 7
'..BB..BB..BB..BB..BB..BB..', // 8
'..BB..BB..BB..BB..BB..BB..', // 9
'..........BB..BB..........', // 10
'..........BB..BB..........', // 11
'..BB..BB..........BB..BB..', // 12
'..BB..BB..........BB..BB..', // 13
'..BB..BB..BB..BB..BB..BB..', // 14
'..BB..BB..BB..BB..BB..BB..', // 15
'......WW..BB..BB..WW......', // 16 вода
'......WW..BB..BB..WW......', // 17
'..BB..BB..II..II..BB..BB..', // 18 лёд
'..BB..BB..II..II..BB..BB..', // 19
'..BB..BB..BB..BB..BB..BB..', // 20
'..BB..BB..BB..BB..BB..BB..', // 21
'..........BBBBBB..........', // 22 защита базы
'..........BBBBBB..........', // 23
'..........BBEEBB..........', // 24 база
'..........BBEEBB..........', // 25
];
Не знаю, пока, на сколько такой формат хранения данных корректен и будет удобен в использовании в дальнейшем, но сейчас он по крайне мере очень нагляден и удобен для ручного левел дизайна.
Рисование блоков достаточно просто:
export function renderBackground(gameMap) {
const ctx = backgroundCtx;
// Очищаем весь фон
clearBackground();
// Рамка игрового поля
renderGameFieldBorder();
// Рисуем все непустые тайлы
for (let y = 0; y < GRID_SIZE; y++) {
for (let x = 0; x < GRID_SIZE; x++) {
const tileId = gameMap.getTile(x, y);
if (tileId === TILE_EMPTY) continue;
const tileDef = TILE_DEFS[tileId];
if (!tileDef) continue;
// Логические координаты → физические координаты на canvas
const px = GAME_FIELD_X + x * TILE_SIZE * GAME_SCALE;
const py = GAME_FIELD_Y + y * TILE_SIZE * GAME_SCALE;
const size = TILE_SIZE * GAME_SCALE;
ctx.fillStyle = tileDef.color; // цвет в самих тайлах пока храню
ctx.fillRect(px, py, size, size); // потом буду спрайты рисовать
}
}
}
Хранятся тайлы у меня вот таким образом:
TILE_DEFS = {
[TILE_EMPTY]: {
id: TILE_EMPTY,
name: 'empty',
blocksTank: false,
blocksBullet: false,
destructible: false,
overlay: false,
color: COLOR_EMPTY // эта вся фигня в константах забита
},
...
У сущности карты есть метод для загрузки из вот того текстового бреда, который чуть выше скинут
loadLevel(levelData) {
for (let y = 0; y < GRID_SIZE; y++) {
for (let x = 0; x < GRID_SIZE; x++) {
const char = levelData[y]?.[x] || '.';
this.tileGrid[y][x] = charToTile(char);
}
}
// Инициализируем damageMask для всех разрушаемых тайлов
this.damageMask.clear();
for (let y = 0; y < GRID_SIZE; y++) {
for (let x = 0; x < GRID_SIZE; x++) {
const tileDef = TILE_DEFS[this.tileGrid[y][x]];
if (tileDef && tileDef.destructible) {
this.damageMask.set(`${x},${y}`, DAMAGE_FULL);
}
}
}
this.dirty = true;
console.log(`Level loaded. Destructible tiles: ${this.damageMask.size}`);
}
function charToTile(char) {
switch (char) {
case 'B': return TILE_BRICK;
case 'S': return TILE_STEEL;
case 'W': return TILE_WATER;
case 'F': return TILE_FOREST;
case 'I': return TILE_ICE;
case 'E': return TILE_BASE;
default: return TILE_EMPTY;
}
}
И вот такой вот получается итог
В целом, уже похоже на правду и с этим можно работать.
план на следующий этап:
вдруг они насквозь...
А у тебя такие слова есть, которые ты вообще не правильно трактовала?
Каюсь, грешна.