ActionScript 3: Создание игры «три-в-ряд» Bejeweled (вторая часть)

Выбор драгоценного камня при помощи курсора мыши

Теперь игрок должен иметь возможность выбирать драгоценные камни, так как нужно менять их местами для формирования линий-комбинаций.

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

Разработка: Вы уже нарисовали селектор, поэтому это только вопрос его установки в соответствующий тайл при нажатии кнопки мыши игроком.

Сначала нам нужна новая переменная класса selector_mc, чтобы создать экземпляр selector_mc.

private var jewels:Array=new Array();
private var gemsContainer:Sprite=new Sprite();
private var gem:gem_mc;
private var selector:selector_mc=new selector_mc();

Теперь в функции-конструкторе Main нужно добавить переменную selector в список отображения (Display List) и добавить прослушиватель, чтобы запускать его, как только игрок нажмет кнопку мыши.

public function Main() {
  jewelsInit();
  addChild(selector);
  selector.visible=false;
  stage.addEventListener(MouseEvent.CLICK,onClick);
}

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

Существует много способов, чтобы сделать объект отображения (DisplayObject) видимым. Например, мы могли бы установить его свойство alpha на ноль, или переместить его за пределы области видимости, изменив свойства x и y.

На этот раз мы будем использовать новый способ, чтобы сделать объект невидимым, установив его свойство visible на значение false.

В чем разница между установкой свойства visible на значение false и установкой свойства alpha на ноль? Объекты отображения (DisplayObjects), которые не видимы, считаются отключенными, это значит, что если вы установите кнопку как невидимую, то на нее нельзя будет нажать кнопкой мыши.

Когда игрок нажимает кнопку мыши, мы должны быть уверены, что курсор мыши находится внутри игрового поля. Игровое поле состоит из сетки размером 8×8 тайлов, и каждый тайл — это квадрат со сторонами размером 60 пикселей, поэтому все игровое поле — это квадрат размером 60×8=480 пикселей.

Как только мы знаем, что игрок нажал кнопку мыши внутри игрового поля, мы можем поместить селектор и сделать его видимым.

Это функция onClick:

private function onClick(e:MouseEvent):void {
  if (mouseX<480&&mouseX>0&&mouseY<480&&mouseY>0) {
    var selRow:uint=Math.floor(mouseY/60);
    var selCol:uint=Math.floor(mouseX/60);
    selector.x=60*selCol;
    selector.y=60*selRow;
    selector.visible=true;
  }
}

Выражение if проверяет, находится ли курсор мыши внутри игрового поля, затем создается пара переменных:

var selRow:uint=Math.floor(mouseY/60);

Переменная selRow представляет ряд выбранного драгоценного камня и его округленное значение координаты мыши y деленную на 60 (размер тайла).

Таким же образом, selCol — это столбец выбранного драгоценного камня.

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

selector.x=60*selCol;
selector.y=60*selRow;

И наконец, пришло время показать его:

selector.visible=true;

Установка свойства visible на значение true сделает его видимым в правильном месте.

Проверьте работу приложения и нажмите на драгоценный камень. Селектор будет установлен вокруг такого драгоценного камня.

Выделение драгоценного камня при помощи символа селектора

Но в игре «Bejeweled» игрок не просто выбирает драгоценные камни. Он должен менять их местами.

Подготовка к свопингу драгоценных камней

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

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

Разработка: Все зависит от концепции «быть смежным», поэтому нам нужна функция для определения смежности двух драгоценных камней.

Давайте выражать эту концепцию простыми словами: два драгоценных камня смежные, если они в одном столбце и первый из них находится справа над или под вторым, или они в одном ряду и находятся рядом друг с другом.

Если перевести на язык кода AS3, то это будет выглядеть так:

private function isAdjacent (row1:int,col1:int,row2:int,col2:int):
Boolean {
  if ((row1==row2+1||row1==row2-1)&&col1==col2) {
    return true;
  }
  return (col1==col2+1||col1==col2-1)&&row1==row2;
}

Для функции isAdjacent необходимо четыре аргумента: ряд и столбец обоих драгоценных камней, и возвращаемое значение true, если они смежные, иначе возвращается значение false.

Обратите внимание, как я расположил return true внутри выражения if. Когда функция выполняет команду return, то она завершается. Поэтому, если код входит в выражение if и находит первый return, то он никогда не выполнится второй раз, как это было бы, если скрипт продолжил бы свое выполнение.

Еще один более оптимизированный способ функции isAdjacent будет следующим:

isAdjacent(row1:int,col1:int,row2:int,col2:int):Boolean {
  return Math.abs(row1-row2)+Math.abs(col1-col2)==1
}

Теперь мы должны отслеживать выбранный драгоценный камень, если таковые имеются, чтобы знать, что делать, когда игрок нажимает на драгоценный камень. Две переменные класса уровня с именами pickedRow и pickedCol будут сохранять ряд и столбец выделенного драгоценного камня.

private var jewels:Array=new Array();
private var gemsContainer:Sprite=new Sprite();
private var gem:gem_mc;
private var selector:selector_mc=new selector_mc();
private var pickedRow:int=-10;
private var pickedCol:int=-10;

Посмотрите, что их начальные значения -10. Поскольку в начале игры нет выбранного драгоценного камня, я назначил pickedRow и pickedCol нестандартные значения по выбору.

Начиная с этого момента, когда две переменные установлены на -10, это означает, что нет никакого выбранного драгоценного камня.

Вы также можете определить константу, например, с таким именем NONE_PICKED, и установить ей значение -10, чтобы сделать код более читаемым.

Теперь, когда игрок будет нажимать кнопку мыши, код будет вести себя следующим образом:

  • Если нет никакого выбранного драгоценного камня, поместить селектор на драгоценный камень, по которому нажали кнопкой мыши, и установить как выбранный.
  • Если уже есть выбранный драгоценный камень, то проверить является ли драгоценный камень, по которому щелкнули мышью, соседним.
  • Если они являются смежными, то приготовить их к обмену местами, убрать селектор и не устанавливать его на другой драгоценный камень.
  • Если драгоценные камни не являются смежными, то переместить селектор на драгоценный камень, который выбрали мышью и установить как выбранный.

Измените функцию onClick следующим образом:

private function onClick(e:MouseEvent):void {
  if (mouseX<480&&mouseX>0&&mouseY<480&&mouseY>0) {
    var selRow:uint=Math.floor(mouseY/60);
    var selCol:uint=Math.floor(mouseX/60);
    if (! isAdjacent(selRow,selCol,pickedRow,pickedCol)) {
      pickedRow=selRow;
      pickedCol=selCol;
      selector.x=60*pickedCol;
      selector.y=60*pickedRow;
      selector.visible=true;
    } else {
      trace("going to swap gems");
      pickedRow=-10;
      pickedCol=-10;
      selector.visible=false;
    }
  }
}

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

При нажатии курсором на соседний с выбранным драгоценный камень появляется сообщение, иначе выделяется другой драгоценный камень

Слева направо, оранжевый драгоценный камень выделен, и игрок нажимает на зеленый драгоценный камень, который не соседний. Поэтому зеленый драгоценный камень становится выделенным, и когда игрок нажимает мышью на желтый камень, который соседний, то в окне Output вы увидите следующее.

going to swap gems

Давайте теперь посмотрим как работает код:

if (! isAdjacent(selRow,selCol,pickedRow,pickedCol)) { ... }

Оператор if является основой функции: он проверяет, чтобы выбранные мышью и выделенные драгоценные камни не были смежными. Если нет никакого выбранного драгоценного камня, то pickedRow и pickedCol устанавливаются на -10, поэтому функция isAdjacent будет возвращать значение false. Если есть драгоценный камень, на который нажимаем мышью, то isAdjacent сообщит, является ли он смежным к выделенному драгоценному камню.

pickedRow=selRow;
pickedCol=selCol;

Если они не являются смежными, или нет никаких выбранных драгоценных камней, то установить pickedRow и pickedCol соответственно на selRow и selCol. Это означает, что драгоценный камень, на который только что нажали мышью, становится выделенным. Также, на этот драгоценный камень помещается селектор.

else {
  trace("going to swap gems");
  pickedRow=-10;
  pickedCol=-10;
  selector.visible=false;
}

Это код, который будет выполнен, при условии, если драгоценный камень, на который нажали мышью, и выделенный драгоценный камень являются смежными: строка отладки, показанная ранее, будет отображаться в окне Output; pickedRow и pickedCol устанавливаются на -10, поскольку нет никаких выбранных драгоценных камней, а селектор делается видимым.

Теперь все готово, чтобы поменять местами драгоценные камни.

Свопинг драгоценных камней

Свопинг драгоценных камней — это ключевой момент игры, поскольку это позволяет игроку создавать линии-комбинации.

Идея: Для того, чтобы поменять местами драгоценные камни нужно два шага:

  • Поменять местами их значения в массиве.
  • Поменять местами позиции их экземпляров типа DisplayObject.

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

Разработка: Как обычно, мы делаем набор функций для выполнения грязной работы и сохраняем главные функции понятными и легко читаемыми.

Во-первых, нам нужна функция для свопинга двух элементов в массиве jewels.

private function swapJewelsArray (row1:uint,col1:uint,row2:uint,col2:uint):void {
  var tmp:uint=jewels[row1][col1];
  jewels[row1][col1]=jewels[row2][col2];
  jewels[row2][col2]=tmp;
}

Функция настолько проста, что нет необходимости объяснять ее.

Другая, более интересная функция, которую мы собираемся создать, будет обменивать местами драгоценные камни типа DisplayObject.

private function swapJewelsObject(row1:uint,col1:uint,row2:uint,col2:
uint):void {
   with (gemsContainer.getChildByName(row1+"_"+col1)) {
     x=col2*60;
     y=row2*60;
     name="tmp";
   }
   with (gemsContainer.getChildByName(row2+"_"+col2)) {
     x=col1*60;
     y=row1*60;
     name=row1+"_"+col1;
   }
   gemsContainer.getChildByName("tmp").name=row2+"_"+col2;
}

В основном, это та же старая концепция, которая используется для свопинга объектов любого типа, с временным объектом для сохранения первого объекта, который мы перезапишем, так же, как это делали с кодированием функции swapJewelsArray.

Еще одна интересная вещь в функции swapJewelsObject это оператор with.

Оператор with устанавливает свой параметр в качестве объекта по умолчанию, который будет использоваться во время всего блока с кодом, уменьшая количество кода, который нужно написать, и, следовательно, предотвращая ошибки.

Это означает, что этот код:

with (gemsContainer.getChildByName(row1+"_"+col1)) {
   x=col2*60;
   y=row2*60;
   name="tmp";
}

такой же, как и этот:

gemsContainer.getChildByName(row1+"_"+col1).x=col2*60;
gemsContainer.getChildByName(row1+"_"+col1).y=row2*60;
gemsContainer.getChildByName(row1+"_"+col1).name="tmp";

но, намного легче читается.

С помощью этих двух совершенно новых функций, свопинг драгоценных камней — очень легкий. Просто используйте их для свопинга, где раньше записывали «going to swap gems» :

private function onClick(e:MouseEvent):void {
  if (mouseX<480&&mouseX>0&&mouseY<480&&mouseY>0) {
    var selRow:uint=Math.floor(mouseY/60);
    var selCol:uint=Math.floor(mouseX/60);
    if (! isAdjacent(selRow,selCol,pickedRow,pickedCol)) {
      pickedRow=selRow;
      pickedCol=selCol;
      selector.x=60*pickedCol;
      selector.y=60*pickedRow;
      selector.visible=true;
    } else {
      swapJewelsArray(pickedRow,pickedCol,selRow,selCol);
      swapJewelsObject(pickedRow,pickedCol,selRow,selCol);
      pickedRow=-10;
      pickedCol=-10;
      selector.visible=false;
    }
  }
}

Проверьте приложение, выберите драгоценный камень и нажмите мышью на соседний, как показано на картинке:

Свопинг драгоценных камней

Драгоценные камни будут меняться местами, а селектор будет исчезать. Теперь вы можете сделать это для всех соседних драгоценных камней.

В этот раз, как правило, я бы написал о плавном движении, но многое еще предстоит сделать, а вы должны быть способны сделать это сами, так как вы имеете дело с плавными движениями с первых глав.

Лучше сконцентрируемся на проблеме, то есть, в игре «Bejeweled» вы не можете поменять местами любые соседние драгоценные камни, потому что обязательное условие для этого — по крайней мере, один из драгоценных камней должен формировать линию-комбинацию.

Свопинг драгоценных камней «по-настоящему»

У нас есть все, что нужно для свопинга драгоценных камней только тогда, когда создается комбинация-линия, поэтому это пятиминутная прогулка. Сделайте перерыв, так как самое сложное еще впереди.

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

Разработка: Создание кода для прогнозирования, который проверяет, будут ли поменянные местами драгоценные камни формировать линию-комбинацию, будет немного сложным, поскольку потребуется больше временных переменных, поэтому мы будем придерживаться более простого подхода.

Когда игрок меняет местами два соседних драгоценных камня, мы будет обновлять массив драгоценных камней. Затем мы будем проверять линии-комбинации. Если они найдены, то будем обновлять драгоценные камни типа DisplayObject, чтобы игрок увидел поменянные местами драгоценные камни, в противном случае будем восстанавливать массив драгоценных камней до его первоначального статуса.

Но как мы сможем восстановить массив jewels, если не сохраняли его нигде? Это делается простым свопингом его элементов снова, чтобы вернуть их в начальное положение.

Измените функцию onClick вот так:

private function onClick(e:MouseEvent):void {
  if (mouseX<480&&mouseX>0&&mouseY<480&&mouseY>0) {
    var selRow:uint=Math.floor(mouseY/60);
    var selCol:uint=Math.floor(mouseX/60);
    if (! isAdjacent(selRow,selCol,pickedRow,pickedCol)) {
      ...
    } else {
       swapJewelsArray(pickedRow,pickedCol,selRow,selCol);
       if (isStreak(pickedRow,pickedCol)||isStreak(selRow,selCol)) {
          swapJewelsObject(pickedRow,pickedCol,selRow,selCol);
       } else {
          swapJewelsArray(pickedRow,pickedCol,selRow,selCol);
       }
       pickedRow=-10;
       pickedCol=-10;
       selector.visible=false;
    }
  }
}

Как только элементы массива jewels были поменяны местами, проверяем линии-комбинации:

if (isStreak(pickedRow,pickedCol)||isStreak(selRow,selCol)) { ... }

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

Если у нас есть такая комбинация, то меняем местами, также меняем местами объекты типа DisplayObjects, иначе делаем другое

swapJewelsArray(pickedRow,pickedCol,selRow,selCol);

чтобы отменить предыдущее движение.

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

Свопинг драгоценных камней с проверкой создания линии-комбинации

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

Это вторая часть урока по созданию игры «три-в-ряд» Bejeweled на ActionScript 3 из книги «Flash game development by example» Emanuele Feronato. Если вам понравился урок, то добавьте его в закладки социальных сетей и сохраните (значки внизу).

Автор перевода: Jean Winters

Комментировать

Почта не публикуется.Обязательные поля отмечены *