В этом уроке мы будем работать с холстом HTML5 и Javascript, чтобы создать динамическую игру с обменом плитками. Результатом будет головоломка, которая работает с любым изображением и имеет гибкие уровни сложности, которые легко настраиваются.
Полная HTML5 головоломка на холсте
Вот краткий снимок головоломки, которую мы будем строить:
Пара заметок:
-
Кросс-браузерная совместимость: эта головоломка была протестирована и работает во всех версиях Safari, Firefox и Chrome, которые поддерживают элемент
canvas
. - Мобильный: приведенный здесь код работает в вышеупомянутом браузере для настольных компьютеров и не оптимизирован для мобильных устройств. Пазл будет загружаться и рендериться очень хорошо, но из-за поведения касания и перетаскивания в мобильных браузерах, для его правильной работы требуется оптимизация. Оптимизация этой головоломки для мобильных устройств будет рассмотрена в следующем уроке.
- Настраиваемая сложность: код содержит константу
PUZZLE_DIFFICULTY
, которая определяет количество частей. В приведенной выше демонстрации это значение равно4
, что дает головоломку 4х4. Мы можем легко изменить это — например, эта версия имеетPUZZLE_DIFFICULTY
10
.
Начиная
Для начала создайте каталог для проекта. Поместите изображение в каталог, который вы хотите использовать в качестве головоломки. Подойдет любое изображение, подходящее для Интернета, и оно может быть любого размера, которого пожелает ваше сердце — просто убедитесь, что оно вписывается в окно браузера.
Шаг 1. Создание шаблона HTML
Откройте новый файл, используя ваш любимый текстовый редактор, и сохраните его в каталоге вашего проекта, рядом с вашим изображением. Затем заполните этот базовый шаблон HTML
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
<!DOCTYPE html>
<html>
<head>
<title>HTML5 Puzzle</title>
<script>
</script>
</head>
<body onload=»init();»>
<canvas id=»canvas»></canvas>
</body>
</html>
|
Все, что нам нужно сделать здесь, это создать стандартный шаблон HTML5
содержащий один тег canvas
с id
«canvas». Мы напишем прослушиватель onload
в теге body
который при запуске вызовет нашу функцию init()
.
Теперь начните с размещения курсора внутри тега script
. С этого момента все это Javascript. За исключением начальных переменных, я буду организовывать разделы по функциям. Сначала покажу вам код, а затем объясню логику.
Готов? Давайте получим право на это!
Шаг 2: Настройка наших переменных
Давайте настроим наши переменные и посмотрим на каждую.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
const PUZZLE_DIFFICULTY = 4;
const PUZZLE_HOVER_TINT = ‘#009900’;
var _canvas;
var _stage;
var _img;
var _pieces;
var _puzzleWidth;
var _puzzleHeight;
var _pieceWidth;
var _pieceHeight;
var _currentPiece;
var _currentDropPiece;
var _mouse;
|
Сначала у нас есть пара констант: PUZZLE_DIFFICULTY
и PUZZLE_HOVER_TINT
. Константа PUZZLE_DIFFICULTY
содержит количество частей в нашей головоломке. В этом приложении строки и столбцы всегда совпадают, поэтому, установив PUZZLE_DIFFICULTY
в 4
, мы получим в общей сложности 16 частей головоломки. Увеличение этого увеличивает сложность головоломки.
Далее приведен ряд переменных:
-
_canvas
и_stage
будут содержать ссылку на холст и его контекст рисования соответственно. Мы делаем это, поэтому нам не нужно выписывать весь запрос каждый раз, когда мы их используем. И мы будем их много использовать! -
_img
будет ссылкой на загруженное изображение, из которого мы будем копировать пиксели из всего приложения. -
_puzzleWidth
,_puzzleHeight
,_pieceWidth
и_pieceHeight
будут использоваться для хранения размеров как всей головоломки, так и каждого отдельного фрагмента головоломки. Мы устанавливаем их один раз, чтобы не рассчитывать их снова и снова каждый раз, когда они нам нужны. -
_currentPiece
содержит ссылку на фрагмент, который в данный момент перетаскивается. -
_currentDropPiece
содержит ссылку на часть, в данный момент_currentDropPiece
в позиции, на которую нужно сбросить. (В демоверсии этот фрагмент выделен зеленым цветом.) -
_mouse
— это ссылка, которая будет содержать текущую позицию мышиx
иy
. Это обновляется, когда головоломка нажимается, чтобы определить, к какой части она прикоснулась, и когда часть перетаскивается, чтобы определить, над какой частью она зависла.
Теперь перейдем к нашим функциям.
Шаг 3: Функция init()
1
2
3
4
5
|
function init(){
_img = new Image();
_img.addEventListener(‘load’,onImage,false);
_img.src = «mke.jpg»;
}
|
Первое, что мы хотим сделать в нашем приложении, это загрузить изображение для головоломки. Объект изображения сначала _img
и устанавливается в нашу переменную _img
. Затем мы прослушиваем событие load
которое затем запустит нашу onImage()
когда изображение завершит загрузку. Наконец, мы устанавливаем источник изображения, который запускает загрузку.
Шаг 4: Функция onImage()
1
2
3
4
5
6
7
8
|
function onImage(e){
_pieceWidth = Math.floor(_img.width / PUZZLE_DIFFICULTY)
_pieceHeight = Math.floor(_img.height / PUZZLE_DIFFICULTY)
_puzzleWidth = _pieceWidth * PUZZLE_DIFFICULTY;
_puzzleHeight = _pieceHeight * PUZZLE_DIFFICULTY;
setCanvas();
initPuzzle();
}
|
Теперь, когда изображение успешно загружено, мы можем установить большинство переменных, объявленных ранее. Мы делаем это здесь, потому что теперь у нас есть информация об изображении и мы можем соответствующим образом установить наши значения.
Первое, что мы делаем, это вычисляем размер каждого кусочка головоломки. Мы делаем это путем деления значения PUZZLE_DIFFICULTY
на ширину и высоту загруженного изображения. Мы также убираем жир с краев, чтобы дать нам хорошие четные числа для работы и гарантируем, что каждая часть может соответствующим образом поменяться местами с другими.
Далее мы используем наши новые значения фрагмента головоломки, чтобы определить общий размер головоломки, и устанавливаем эти значения _puzzleWidth
и _puzzleHeight
.
Наконец, мы вызываем несколько функций — setCanvas()
и initPuzzle()
.
Шаг 5: Функция setCanvas()
1
2
3
4
5
6
7
|
function setCanvas(){
_canvas = document.getElementById(‘canvas’);
_stage = _canvas.getContext(‘2d’);
_canvas.width = _puzzleWidth;
_canvas.height = _puzzleHeight;
_canvas.style.border = «1px solid black»;
}
|
Теперь, когда наши значения головоломки завершены, мы хотим настроить наш элемент canvas
. Сначала мы устанавливаем переменную _canvas
для ссылки на наш элемент canvas
и _stage
для ссылки на его context
.
Теперь мы устанавливаем width
и height
нашего canvas
чтобы соответствовать размеру нашего обрезанного изображения, затем применяем несколько простых стилей, чтобы создать черную рамку вокруг нашего canvas
чтобы отобразить границы нашей головоломки.
Шаг 6: initPuzzle()
1
2
3
4
5
6
7
8
9
|
function initPuzzle(){
_pieces = [];
_mouse = {x:0,y:0};
_currentPiece = null;
_currentDropPiece = null;
_stage.drawImage(_img, 0, 0, _puzzleWidth, _puzzleHeight, 0, 0, _puzzleWidth, _puzzleHeight);
createTitle(«Click to Start Puzzle»);
buildPieces();
}
|
Здесь мы инициализируем головоломку. Мы настроили эту функцию таким образом, чтобы мы могли вызывать ее позже, когда захотим воспроизвести головоломку. Все остальное, что нужно было установить перед игрой, больше не нужно устанавливать.
Сначала мы устанавливаем _pieces
как пустой array
и создаем объект _mouse
, который будет удерживать нашу позицию мыши по всему приложению. Затем мы устанавливаем для _currentPiece
и _currentPieceDrop
значение null
. (При первой игре эти значения уже будут null
, но мы хотим убедиться, что они сбрасываются при повторном воспроизведении головоломки.)
Наконец пришло время рисовать! Сначала мы рисуем все изображение, чтобы показать игроку, что они будут создавать. После этого мы создаем несколько простых инструкций, вызывая нашу createTitle()
.
Шаг 7: createTitle()
01
02
03
04
05
06
07
08
09
10
11
|
function createTitle(msg){
_stage.fillStyle = «#000000»;
_stage.globalAlpha = .4;
_stage.fillRect(100,_puzzleHeight — 40,_puzzleWidth — 200,40);
_stage.fillStyle = «#FFFFFF»;
_stage.globalAlpha = 1;
_stage.textAlign = «center»;
_stage.textBaseline = «middle»;
_stage.font = «20px Arial»;
_stage.fillText(msg,_puzzleWidth / 2,_puzzleHeight — 20);
}
|
Здесь мы создаем довольно простое сообщение, которое инструктирует пользователя нажать на головоломку, чтобы начать.
Наше сообщение будет полупрозрачным прямоугольником, который будет служить фоном нашего текста. Это позволяет пользователю видеть изображение позади него, а также гарантирует, что наш белый текст будет разборчивым на любом изображении.
Мы просто устанавливаем fillStyle
на black и globalAlpha
на .4
, прежде чем заполнить короткий черный прямоугольник внизу изображения.
Так как globalAlpha
влияет на весь холст, нам нужно установить его обратно в 1
(непрозрачный) перед рисованием текста. Чтобы установить наш заголовок, мы устанавливаем textAlign
в ‘center’ и textBaseline
в 'middle'
. Мы также можем применить некоторые свойства font
.
Чтобы нарисовать текст, мы используем метод fillText()
. Мы передаем переменную msg
и поместим ее в горизонтальный центр canvas
и вертикальный центр прямоугольника.
Шаг 8: buildPieces()
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
function buildPieces(){
var i;
var piece;
var xPos = 0;
var yPos = 0;
for(i = 0;i < PUZZLE_DIFFICULTY * PUZZLE_DIFFICULTY;i++){
piece = {};
piece.sx = xPos;
piece.sy = yPos;
_pieces.push(piece);
xPos += _pieceWidth;
if(xPos >= _puzzleWidth){
xPos = 0;
yPos += _pieceHeight;
}
}
document.onmousedown = shufflePuzzle;
}
|
Наконец пришло время построить головоломку!
Мы делаем это путем создания object
для каждой части. Эти объекты не будут отвечать за рендеринг на холст, а будут просто содержать ссылки на то, что рисовать и где. Как говорится, давайте к этому.
Прежде всего, давайте объявим несколько переменных, которые мы будем повторно использовать в цикле. Мы хотим настроить цикл, чтобы перебирать количество частей головоломки, которые нам нужны. Мы получаем это значение, умножая PUZZLE_DIFFICULTY
на себя — так что в этом случае мы получаем 16.
В петле:
01
02
03
04
05
06
07
08
09
10
11
|
for(i = 0;i < PUZZLE_DIFFICULTY * PUZZLE_DIFFICULTY;i++){
piece = {};
piece.sx = xPos;
piece.sy = yPos;
_pieces.push(piece);
xPos += _pieceWidth;
if(xPos >= _puzzleWidth){
xPos = 0;
yPos += _pieceHeight;
}
}
|
Начните с создания пустого объекта. Затем добавьте свойства sx
и sy
к объекту. На первой итерации эти значения равны 0
и представляют точку на нашем изображении, откуда мы начнем рисовать. Теперь _pieces[]
массив _pieces[]
. Этот объект также будет содержать свойства xPos
и yPos
, которые yPos
нам текущую позицию в пазле, где должна быть нарисована фигура. Мы будем перетасовывать объекты до того, как они будут воспроизведены, поэтому эти значения пока не нужно устанавливать.
Последнее, что мы делаем в каждом цикле, это увеличиваем локальную переменную xPos
на _pieceWidth
. Прежде чем продолжить цикл, мы определяем, нужно ли нам xPos
к следующему ряду частей, проверяя, находится ли xPos
за пределами ширины головоломки. Если это так, мы сбрасываем xPos
обратно на 0 и увеличиваем yPos
на _pieceHeight
.
Теперь у нас есть все кусочки головоломки, которые хранятся в нашем массиве _pieces
. На этом этапе код, наконец, перестает выполняться и ожидает взаимодействия с пользователем. Мы установили прослушиватель щелчка на document
для shufflePuzzle()
функции shufflePuzzle()
при shufflePuzzle()
, которая начнет игру.
Шаг 9: shufflePuzzle()
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
function shufflePuzzle(){
_pieces = shuffleArray(_pieces);
_stage.clearRect(0,0,_puzzleWidth,_puzzleHeight);
var i;
var piece;
var xPos = 0;
var yPos = 0;
for(i = 0;i < _pieces.length;i++){
piece = _pieces[i];
piece.xPos = xPos;
piece.yPos = yPos;
_stage.drawImage(_img, piece.sx, piece.sy, _pieceWidth, _pieceHeight, xPos, yPos, _pieceWidth, _pieceHeight);
_stage.strokeRect(xPos, yPos, _pieceWidth,_pieceHeight);
xPos += _pieceWidth;
if(xPos >= _puzzleWidth){
xPos = 0;
yPos += _pieceHeight;
}
}
document.onmousedown = onPuzzleClick;
}
|
1
2
3
4
|
function shuffleArray(o){
for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[—i], o[i] = o[j], o[j] = x);
return o;
}
|
Перво- _pieces[]
: _pieces[]
массив _pieces[]
. Здесь я использую полезную служебную функцию, которая будет перемешивать индексы переданного в нее массива. Объяснение этой функции выходит за рамки темы этого урока, поэтому мы продолжим, зная, что мы успешно перетасовали наши фрагменты. (Для базового введения в перетасовку посмотрите этот учебник .)
Давайте сначала очистим всю графику, нарисованную на canvas
чтобы освободить место для рисования наших частей. Затем настройте массив так же, как мы это делали при первом создании наших кусочных объектов.
В петле:
01
02
03
04
05
06
07
08
09
10
11
12
|
for(i = 0;i < _pieces.length;i++){
piece = _pieces[i];
piece.xPos = xPos;
piece.yPos = yPos;
_stage.drawImage(_img, piece.sx, piece.sy, _pieceWidth, _pieceHeight, xPos, yPos, _pieceWidth, _pieceHeight);
_stage.strokeRect(xPos, yPos, _pieceWidth,_pieceHeight);
xPos += _pieceWidth;
if(xPos >= _puzzleWidth){
xPos = 0;
yPos += _pieceHeight;
}
}
|
Прежде всего, используйте переменную i
чтобы установить нашу ссылку на текущий объект фигуры в цикле. Теперь мы заполняем свойства xPos
и yPos
я упоминал ранее, которые будут yPos
0
на нашей первой итерации.
Теперь, наконец, мы рисуем наши фигуры.
Первый параметр drawImage()
назначает источник изображения, из которого мы хотим рисовать. Затем используйте объекты sx
и sy
объектов piece, а также _pieceWidth
и _pieceHeight
, чтобы заполнить параметры, которые объявляют область изображения, из которой нужно рисовать. Последние четыре параметра задают область canvas
где мы хотим рисовать. Мы используем значения xPos
и yPos
которые мы одновременно строим в цикле и присваиваем объекту.
Сразу после этого мы рисуем быстрый ход вокруг фигуры, чтобы придать ей границу, которая будет красиво отделять ее от других фигур.
Теперь мы ждем, пока пользователь захватит кусок, установив еще один прослушиватель щелчков. На этот раз он запустит функцию onPuzzleClick()
.
Шаг 10: onPuzzleClick()
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
function onPuzzleClick(e){
if(e.layerX || e.layerX == 0){
_mouse.x = e.layerX — _canvas.offsetLeft;
_mouse.y = e.layerY — _canvas.offsetTop;
}
else if(e.offsetX || e.offsetX == 0){
_mouse.x = e.offsetX — _canvas.offsetLeft;
_mouse.y = e.offsetY — _canvas.offsetTop;
}
_currentPiece = checkPieceClicked();
if(_currentPiece != null){
_stage.clearRect(_currentPiece.xPos,_currentPiece.yPos,_pieceWidth,_pieceHeight);
_stage.save();
_stage.globalAlpha = .9;
_stage.drawImage(_img, _currentPiece.sx, _currentPiece.sy, _pieceWidth, _pieceHeight, _mouse.x — (_pieceWidth / 2), _mouse.y — (_pieceHeight / 2), _pieceWidth, _pieceHeight);
_stage.restore();
document.onmousemove = updatePuzzle;
document.onmouseup = pieceDropped;
}
}
|
Мы знаем, что головоломка была нажата; Теперь нам нужно определить, на какой части щелкнули. Это простое условие позволит нам позиционировать мышку во всех современных настольных браузерах, которые поддерживают canvas
, используя либо e.layerX
и e.layerY
либо e.offsetX
и e.offsetY
. Используйте эти значения, чтобы обновить наш объект _mouse
, назначив ему свойства x
и y
для хранения текущей позиции мыши — в данном случае, позиции, в которой он был нажат.
В строке 112 мы немедленно устанавливаем _currentPiece
на возвращаемое значение из нашей функции checkPieceClicked()
. Мы разделяем этот код, потому что хотим использовать его позже при перетаскивании фрагмента головоломки. Я объясню эту функцию на следующем шаге.
Если возвращаемое значение было null
, мы просто ничего не делаем, так как это означает, что пользователь фактически не щелкал мышью по части головоломки. Однако, если мы действительно получим кусок головоломки, мы хотим прикрепить его к мышке и немного потушить, чтобы показать кусочки внизу. Итак, как мы это сделаем?
Сначала мы canvas
область canvas
где сидел кусок, прежде чем щелкнуть по нему. Мы clearRect()
используем clearRect()
, но в этом случае мы передаем только область, полученную из объекта _currentPiece
. Прежде чем перерисовать его, мы хотим save()
контекст холста, прежде чем продолжить. Это гарантирует, что все, что мы рисуем после сохранения, не будет просто рисовать поверх чего-либо. Мы делаем это, потому что мы немного потушим потянувшуюся часть и захотим увидеть части под ней. Если бы мы не вызывали save()
, мы бы просто рисовали любую графику — блеклая или нет.
Теперь мы рисуем изображение так, чтобы его центр располагался у указателя мыши. Первые 5 параметров drawImage
всегда будут одинаковыми во всем приложении. При щелчке следующие два параметра будут обновлены, чтобы центрироваться по указателю мыши. Последние два параметра, width
и height
для рисования, также никогда не изменятся.
Наконец, мы вызываем метод restore()
. По сути это означает, что мы закончили с использованием нового альфа-значения и хотим восстановить все свойства туда, где они были. Чтобы завершить эту функцию, мы добавим еще двух слушателей. Один для того, когда мы перемещаем мышь (перетаскивая часть головоломки), и один для того, когда мы отпускаем (опускаем часть головоломки).
Шаг 11: checkPieceClicked()
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
function checkPieceClicked(){
var i;
var piece;
for(i = 0;i < _pieces.length;i++){
piece = _pieces[i];
if(_mouse.x < piece.xPos || _mouse.x > (piece.xPos + _pieceWidth) || _mouse.y < piece.yPos || _mouse.y > (piece.yPos + _pieceHeight)){
//PIECE NOT HIT
}
else{
return piece;
}
}
return null;
}
|
Теперь нам нужно немного откатиться назад. Мы смогли определить, какая часть была нажата, но как мы это сделали? Это довольно просто на самом деле. Что нам нужно сделать, так это пройтись по всем частям головоломки и определить, был ли щелчок в пределах какого-либо из наших объектов. Если мы найдем его, мы вернем соответствующий объект и завершим функцию. Если мы ничего не находим, мы возвращаем null
.
Шаг 12: updatePuzzle()
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
function updatePuzzle(e){
_currentDropPiece = null;
if(e.layerX || e.layerX == 0){
_mouse.x = e.layerX — _canvas.offsetLeft;
_mouse.y = e.layerY — _canvas.offsetTop;
}
else if(e.offsetX || e.offsetX == 0){
_mouse.x = e.offsetX — _canvas.offsetLeft;
_mouse.y = e.offsetY — _canvas.offsetTop;
}
_stage.clearRect(0,0,_puzzleWidth,_puzzleHeight);
var i;
var piece;
for(i = 0;i < _pieces.length;i++){
piece = _pieces[i];
if(piece == _currentPiece){
continue;
}
_stage.drawImage(_img, piece.sx, piece.sy, _pieceWidth, _pieceHeight, piece.xPos, piece.yPos, _pieceWidth, _pieceHeight);
_stage.strokeRect(piece.xPos, piece.yPos, _pieceWidth,_pieceHeight);
if(_currentDropPiece == null){
if(_mouse.x < piece.xPos || _mouse.x > (piece.xPos + _pieceWidth) || _mouse.y < piece.yPos || _mouse.y > (piece.yPos + _pieceHeight)){
//NOT OVER
}
else{
_currentDropPiece = piece;
_stage.save();
_stage.globalAlpha = .4;
_stage.fillStyle = PUZZLE_HOVER_TINT;
_stage.fillRect(_currentDropPiece.xPos,_currentDropPiece.yPos,_pieceWidth, _pieceHeight);
_stage.restore();
}
}
}
_stage.save();
_stage.globalAlpha = .6;
_stage.drawImage(_img, _currentPiece.sx, _currentPiece.sy, _pieceWidth, _pieceHeight, _mouse.x — (_pieceWidth / 2), _mouse.y — (_pieceHeight / 2), _pieceWidth, _pieceHeight);
_stage.restore();
_stage.strokeRect( _mouse.x — (_pieceWidth / 2), _mouse.y — (_pieceHeight / 2), _pieceWidth,_pieceHeight);
}
|
Теперь вернемся к перетаскиванию. Мы вызываем эту функцию, когда пользователь перемещает мышь. Это самая большая функция приложения, поскольку оно выполняет несколько вещей. Давайте начнем. Я сломаю это, когда мы пойдем.
1
2
3
4
5
6
7
8
9
|
_currentDropPiece = null;
if(e.layerX || e.layerX == 0){
_mouse.x = e.layerX — _canvas.offsetLeft;
_mouse.y = e.layerY — _canvas.offsetTop;
}
else if(e.offsetX || e.offsetX == 0){
_mouse.x = e.offsetX — _canvas.offsetLeft;
_mouse.y = e.offsetY — _canvas.offsetTop;
}
|
Начните с установки _currentDropPiece
в _currentDropPiece
. Нам нужно сбросить это значение на null
при обновлении из-за вероятности, что наш кусок был перетащен обратно к себе домой. Мы не хотим, чтобы предыдущее значение _currentDropPiece
. Затем мы устанавливаем объект _mouse
же, как мы делали это при нажатии.
1
|
_stage.clearRect(0,0,_puzzleWidth,_puzzleHeight);
|
Здесь нам нужно очистить всю графику на холсте. По сути, нам нужно перерисовать кусочки головоломки, потому что перетаскиваемый сверху объект будет влиять на их внешний вид. Если бы мы этого не сделали, мы бы увидели некоторые очень странные результаты, следуя пути нашей перетаскиваемой части головоломки.
1
2
3
|
var i;
var piece;
for(i = 0;i < _pieces.length;i++){
|
Начните с настройки нашего обычного цикла штук.
В петле:
1
2
3
4
|
piece = _pieces[i];
if(piece == _currentPiece){
continue;
}
|
Создайте ссылку на наш piece
как обычно. Затем проверьте, совпадает ли часть, на которую мы сейчас ссылаемся, с частью, которую мы перетаскиваем. Если это так, продолжайте цикл. Это сохранит пустую ячейку перетаскиваемой части.
1
2
|
_stage.drawImage(_img, piece.sx, piece.sy, _pieceWidth, _pieceHeight, piece.xPos, piece.yPos, _pieceWidth, _pieceHeight);
_stage.strokeRect(piece.xPos, piece.yPos, _pieceWidth,_pieceHeight);
|
Двигаясь дальше, перерисовываем кусок головоломки, используя его свойства точно так же, как мы делали это при первом рисовании. Вам также нужно будет нарисовать границу.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
if(_currentDropPiece == null){
if(_mouse.x < piece.xPos || _mouse.x > (piece.xPos + _pieceWidth) || _mouse.y < piece.yPos || _mouse.y > (piece.yPos + _pieceHeight)){
//NOT OVER
}
else{
_currentDropPiece = piece;
_stage.save();
_stage.globalAlpha = .4;
_stage.fillStyle = PUZZLE_HOVER_TINT;
_stage.fillRect(_currentDropPiece.xPos,_currentDropPiece.yPos,_pieceWidth, _pieceHeight);
_stage.restore();
}
}
|
Поскольку у нас есть ссылка на каждый объект в цикле, мы также можем использовать эту возможность, чтобы проверить, находится ли перетаскиваемый фрагмент поверх него. Мы делаем это потому, что хотим дать пользователю обратную связь о том, на какую часть он может быть добавлен. Давайте углубимся в этот код сейчас.
Во-первых, мы хотим посмотреть, не выполнил ли этот цикл цель удаления. Если это так, нам не нужно беспокоиться, так как возможна только одна цель падения и любое движение мыши. Если нет, _currentDropPiece
будет null
и мы можем перейти к логике. Поскольку наша мышь находится в середине перетаскиваемой части, все, что нам действительно нужно сделать, это определить, над какой другой частью находится наша мышь.
Далее, воспользуйтесь нашей удобной checkPieceClicked()
чтобы определить, находится ли мышь над текущим объектом фигуры в цикле. Если это так, мы устанавливаем переменную _currentDropPiece
и рисуем закрашенный прямоугольник над частью головоломки, указывая, что теперь она является целью отбрасывания.
Не забудьте save()
и restore()
. В противном случае вы получите тонированную коробку, а не изображение внизу.
Вне цикла:
1
2
3
4
5
|
_stage.save();
_stage.globalAlpha = .6;
_stage.drawImage(_img, _currentPiece.sx, _currentPiece.sy, _pieceWidth, _pieceHeight, _mouse.x — (_pieceWidth / 2), _mouse.y — (_pieceHeight / 2), _pieceWidth, _pieceHeight);
_stage.restore();
_stage.strokeRect( _mouse.x — (_pieceWidth / 2), _mouse.y — (_pieceHeight / 2), _pieceWidth,_pieceHeight);
|
И последнее, но не менее важное: нам нужно перерисовать перетянутый кусок. Код такой же, как и при первом щелчке по нему, но мышь переместилась, поэтому его положение будет обновлено.
Шаг 13: pieceDropped()
01
02
03
04
05
06
07
08
09
10
11
12
|
function pieceDropped(e){
document.onmousemove = null;
document.onmouseup = null;
if(_currentDropPiece != null){
var tmp = {xPos:_currentPiece.xPos,yPos:_currentPiece.yPos};
_currentPiece.xPos = _currentDropPiece.xPos;
_currentPiece.yPos = _currentDropPiece.yPos;
_currentDropPiece.xPos = tmp.xPos;
_currentDropPiece.yPos = tmp.yPos;
}
resetPuzzleAndCheckWin();
}
|
ОК, худшее позади. Теперь мы успешно перетаскиваем кусок головоломки и даже получаем визуальную обратную связь о том, куда он будет брошен. Теперь все, что осталось, это бросить кусок. Давайте сначала сразу удалим слушателей, так как ничего не перетаскиваем.
Затем проверьте, что _currentDropPiece
не является null
. Если это так, это означает, что мы перетащили его обратно в домашнюю область фигуры, а не через другой слот. Если это не null
, мы продолжим с функцией.
То, что мы делаем сейчас, это просто поменяйте местами xPos
и yPos
каждого элемента. Мы создаем быстрый временный объект в качестве буфера для хранения одного из значений объекта в процессе обмена. На этом этапе обе части имеют новые значения xPos
и yPos
и будут yPos
к своим новым домам на следующем розыгрыше. Это то, что мы будем делать сейчас, одновременно проверяя, была ли игра выиграна.
Шаг 14: resetPuzzleAndCheckWin()
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
function resetPuzzleAndCheckWin(){
_stage.clearRect(0,0,_puzzleWidth,_puzzleHeight);
var gameWin = true;
var i;
var piece;
for(i = 0;i < _pieces.length;i++){
piece = _pieces[i];
_stage.drawImage(_img, piece.sx, piece.sy, _pieceWidth, _pieceHeight, piece.xPos, piece.yPos, _pieceWidth, _pieceHeight);
_stage.strokeRect(piece.xPos, piece.yPos, _pieceWidth,_pieceHeight);
if(piece.xPos != piece.sx || piece.yPos != piece.sy){
gameWin = false;
}
}
if(gameWin){
setTimeout(gameOver,500);
}
}
|
Еще раз очистите canvas
и установите переменную gameWin
, установив для нее значение по умолчанию. Теперь перейдем к нашему слишком знакомому циклу пьес.
Код здесь должен выглядеть знакомым, поэтому мы не будем его рассматривать. Он просто рисует части обратно в их оригинальные или новые слоты. В этом цикле мы хотим посмотреть, разыгрывается ли каждая фигура в своей выигрышной позиции. Это просто: мы проверяем, совпадают ли наши свойства sx
и sy
с xPos
и yPos
. Если нет, то мы знаем, что не сможем победить в пазле, и установим для gameWin
значение false
. Если мы прошли через цикл, в котором все занимали призовые места, мы настроили быстрый timeout
для вызова нашего gameOver()
. (Мы устанавливаем тайм-аут, чтобы экран не менялся так сильно при падении части головоломки.)
Шаг 15: gameOver()
1
2
3
4
5
6
|
function gameOver(){
document.onmousedown = null;
document.onmousemove = null;
document.onmouseup = null;
initPuzzle();
}
|
Это наша последняя функция! Здесь мы просто удаляем всех слушателей и вызываем initPuzzle()
, который сбрасывает все необходимые значения и ждет повторного воспроизведения пользователем.
Вывод
Нажмите здесь, чтобы увидеть окончательный результат.
Как видите, вы можете создавать множество новых творческих вещей в HTML5, используя выбранные растровые области загруженных изображений и рисунков. Вы можете легко расширить это приложение, добавив выигрыш и, возможно, даже таймер, чтобы дать ему больше игрового процесса. Другая идея заключается в увеличении сложности и выборе другого изображения в функции gameOver()
, дающей игровые уровни.