Статьи

Создайте HTML5-головоломку для замены холста

В этом уроке мы будем работать с холстом HTML5 и Javascript, чтобы создать динамическую игру с обменом плитками. Результатом будет головоломка, которая работает с любым изображением и имеет гибкие уровни сложности, которые легко настраиваются.


Вот краткий снимок головоломки, которую мы будем строить:

Финальная HTML5 головоломка

Нажмите, чтобы играть

Пара заметок:

  • Кросс-браузерная совместимость: эта головоломка была протестирована и работает во всех версиях Safari, Firefox и Chrome, которые поддерживают элемент canvas .
  • Мобильный: приведенный здесь код работает в вышеупомянутом браузере для настольных компьютеров и не оптимизирован для мобильных устройств. Пазл будет загружаться и рендериться очень хорошо, но из-за поведения касания и перетаскивания в мобильных браузерах, для его правильной работы требуется оптимизация. Оптимизация этой головоломки для мобильных устройств будет рассмотрена в следующем уроке.
  • Настраиваемая сложность: код содержит константу PUZZLE_DIFFICULTY , которая определяет количество частей. В приведенной выше демонстрации это значение равно 4 , что дает головоломку 4х4. Мы можем легко изменить это — например, эта версия имеет PUZZLE_DIFFICULTY 10 .

Для начала создайте каталог для проекта. Поместите изображение в каталог, который вы хотите использовать в качестве головоломки. Подойдет любое изображение, подходящее для Интернета, и оно может быть любого размера, которого пожелает ваше сердце — просто убедитесь, что оно вписывается в окно браузера.


Откройте новый файл, используя ваш любимый текстовый редактор, и сохраните его в каталоге вашего проекта, рядом с вашим изображением. Затем заполните этот базовый шаблон 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. За исключением начальных переменных, я буду организовывать разделы по функциям. Сначала покажу вам код, а затем объясню логику.

Готов? Давайте получим право на это!


Давайте настроим наши переменные и посмотрим на каждую.

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 . Это обновляется, когда головоломка нажимается, чтобы определить, к какой части она прикоснулась, и когда часть перетаскивается, чтобы определить, над какой частью она зависла.

Теперь перейдем к нашим функциям.


1
2
3
4
5
function init(){
    _img = new Image();
    _img.addEventListener(‘load’,onImage,false);
    _img.src = «mke.jpg»;
}

Первое, что мы хотим сделать в нашем приложении, это загрузить изображение для головоломки. Объект изображения сначала _img и устанавливается в нашу переменную _img . Затем мы прослушиваем событие load которое затем запустит нашу 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() .


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 чтобы отобразить границы нашей головоломки.


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() .


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 и вертикальный центр прямоугольника.


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() , которая начнет игру.


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() .

Финальная HTML5 головоломка

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() . По сути это означает, что мы закончили с использованием нового альфа-значения и хотим восстановить все свойства туда, где они были. Чтобы завершить эту функцию, мы добавим еще двух слушателей. Один для того, когда мы перемещаем мышь (перетаскивая часть головоломки), и один для того, когда мы отпускаем (опускаем часть головоломки).


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 .


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);

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


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 к своим новым домам на следующем розыгрыше. Это то, что мы будем делать сейчас, одновременно проверяя, была ли игра выиграна.


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() . (Мы устанавливаем тайм-аут, чтобы экран не менялся так сильно при падении части головоломки.)


1
2
3
4
5
6
function gameOver(){
    document.onmousedown = null;
    document.onmousemove = null;
    document.onmouseup = null;
    initPuzzle();
}

Это наша последняя функция! Здесь мы просто удаляем всех слушателей и вызываем initPuzzle() , который сбрасывает все необходимые значения и ждет повторного воспроизведения пользователем.


Нажмите здесь, чтобы увидеть окончательный результат.

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