Начинаем реализовывать перемещение плиток.
2.1. Выбор плитки для перемещения
Чтобы выбрать плитку, нужно на неё нажать. Значит, плитки должны быть интерактивными. Добавим интерактивность при их создании в классе Board:
createTile(field) {
// ...
tile.sprite.interactive = true;
tile.sprite.on("pointerdown", () => {
this.container.emit('tile-touch-start', tile);
});
}
Отправим событие tile-touch-start, используя возможности PIXI.Container. Затем отследим и обработаем его в классе Game:
// ...
export class Game {
constructor() {
// ...
this.board.container.on('tile-touch-start', this.onTileClick.bind(this));
}
// ...
При событии tile-touch-start вызовем onTileClick. В этом методе обработаем три возможных сценария:
:one: выбрать новую плитку, если ни одна ещё не выбрана
:two: поменять плитки местами, если другая уже выбрана и находится рядом с текущей
:three: выбрать новую плитку, если другая уже выбрана, но не соседствует с текущей
Сейчас реализуем первый пункт. Сейчас реализуем первый пункт. При выборе новой плитки нужно сделать две вещи:
- запомнить выбранную плитку
- визуально выделить выбранную клетку
export class Game {
onTileClick(tile) {
if (this.selectedTile) {
// select new tile or make swap
} else {
this.selectTile(tile);
}
}
selectTile(tile) {
this.selectedTile = tile;
this.selectedTile.field.select();
}
Выделим клетку с выбранной плиткой дополнительной рамкой. Реализуем код в классе Field:
export class Field {
constructor(row, col) {
// ...
this.selected = App.sprite("field-selected");
this.sprite.addChild(this.selected);
this.selected.visible = false;
this.selected.anchor.set(0.5);
}
unselect() {
this.selected.visible = false;
}
select() {
this.selected.visible = true;
}
2.2. Обмен плиток
В onTileClick класса Game добавьте вызов swap, реализующего перемещение плиток:
onTileClick(tile) {
if (this.selectedTile) {
this.swap(this.selectedTile, tile);
} else {
this.selectTile(tile);
}
}
Определим действия, необходимые для перемещения плиток:
:one: Сбросить связи с клетками у перемещаемых плиток
:two: Сбросить связи с плитками у клеток поля
:three: Разместить перемещённые плитки в позициях новых клеток на экране
Для перемещения плиток создадим твин-анимацию с gsap. На время анимации стоит заблокировать взаимодействие с полем дополнительным флагом.
Реализуем эти действия в коде:
swap(selectedTile, tile) {
this.disabled = true; // lock the board to prevent tiles from moving again while the animation is already running
this.clearSelection(); // hide the "field-selected" frame from the field of the selectedTile object
selectedTile.sprite.zIndex = 2; // place the selectedTile sprite one layer higher than the tile sprite
selectedTile.moveTo(tile.field.position, 0.2); // move selectedTile to tile position
// move tile to electedTile position
tile.moveTo(selectedTile.field.position, 0.2).then(() => {
// after motion animations complete:
// change the values of the field properties in the tile objects
// change the values of the tile properties in the field objects
this.board.swap(selectedTile, tile);
this.disabled = false; // unlock the board
});
}
Сбросим выделение клетки в методе clearSelection:
clearSelection() {
if (this.selectedTile) {
this.selectedTile.field.unselect();
this.selectedTile = null;
}
}
Реализуем moveTo в классе Tile с твин-анимацией gsap:
moveTo(position, duration) {
return new Promise(resolve => {
gsap.to(this.sprite, {
duration,
pixi: {
x: position.x,
y: position.y
},
onComplete: () => {
resolve()
}
});
});
}
Добавим swap в класс Board: он будет менять значения свойств перемещаемых объектов:
swap(tile1, tile2) {
const tile1Field = tile1.field;
const tile2Field = tile2.field;
tile1Field.tile = tile2;
tile2.field = tile1Field;
tile2Field.tile = tile1;
tile1.field = tile2Field;
}
Сейчас можно поменять местами любые плитки поля, а не только соседние. Но обмениваться должны только соседние. Если игрок выбрал несоседнюю плитку, нужно полностью сбросить первое выделение и выбрать новую плитку.
Обновим условие в методе onTileClick класса Game:
onTileClick(tile) {
if (this.disabled) {
return;
}
if (this.selectedTile) {
if (!this.selectedTile.isNeighbour(tile)) {
this.clearSelection(tile);
this.selectTile(tile);
} else {
this.swap(this.selectedTile, tile);
}
} else {
this.selectTile(tile);
}
}
Примечание. Мы также добавили проверку флага disabled, установленного в прошлом пункте. Так мы блокируем onTileClick во время перемещения плиток, чтобы избежать возможных ошибок.
Реализуем isNeighbour в классе Tile. Соседняя плитка находится либо в смежном столбце, либо в смежной строке. Следовательно, модуль разности строк или столбцов проверяемой и текущей плитки должен равняться единице:
isNeighbour(tile) {
return Math.abs(this.field.row - tile.field.row) + Math.abs(this.field.col - tile.field.col) === 1
}