пятница, 19 декабря 2014 г.

Пишем игру "Пятнашки". Часть 3

И так, настало время запустить наши "Пятнашки" на операционной системе Андроид! Наши любимые телефоны и планшеты должны запустить эту замечательную игру и позволить нам насладиться головоломкой где бы мы ни находились.
Но сначала нам нужно проделать небольшую работу по переводу нашей игры на платформу Adobe Air, подкорректировать управление под мультитач, а так же попробовать заставить игру корректно отображаться экранах различных пропорций и разрешений.


Перед началом работы удостоверьтесь, что у вас установлен AIR SDK. Если нет, то скачать его можно здесь. В своём уроке я использую старую версию Air 3.1. Для нашей задачи его вполне достаточно. Для перевода нашей игры под Андроид мы создадим новый проект и подключим к нему нашу библиотеку SWC и созданные в прошлом уроке файлы Game.as и Tile.as. 

Итак, создаём во FlashDevelop новый Air проект для мобильных устройств: 


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


Для начала нужно создать сертификат для нашего приложения. Это необходимое условие для запуска андроид-приложений. Щелкаем правой кнопкой файл CreateCertificate.bat и выбираем пункт "Execute". Откроется консоль и автоматически будет создан сертификат.

Далее двойным щелчком открываем application.xml. Ищем в начале строчку

<application xmlns="http://ns.adobe.com/air/application/3.1"> 

и изменяем число в конце в соответствии с той версией AIR, коротая у вас установлена. Сохраняем файл. У меня версия AIR 3.1, поэтому я поставил 3.1.
Если вы не знаете какая версия AIR у вас, узнать можно так:
Меню Project -> Properites -> SDK. В пункте "Installed SDK" вы увидете, что у вас установлено.


Так же не забудьте в этом же окне, во вкладке "Output" проверить соответствие версии AIR которая используется в проекте, с той версией что у вас установлена.


Кроме того нам нужно посетить меню  Project -> Air App Properites. В этом окне можно настраивать различные параметры будущего приложения. Например во вкладке Details можно установить имя приложения, копирайты, версию и прочее. Я не заполнял эти поля, так как мы делаем игру в ознакомительных целях. Нас больше интересует вкладка Initial Window. Здесь в подвкладаке Non-Windowed Platforms нужно настроить следующие параметры, которые говорят сами за себя. Убрать автоориентацию, убрать фулскрин (растягивать будем программно), рендер через CPU, а ориентация портретная.


С настройками закончили.
Идём далее. Перекидываем в папку lib нашу библиотеку SWC (ту которую мы создали в первом уроке и подключали к проекту во втором) и подключаем её к проекту. Так же перекидываем в папку src файлы Game.as и Tile.as, которые мы создали на прошлом уроке.

В листинг файла Game.as были внесены некоторые изменения. Полностью переписана функция для обработки ходов. Теперь игра реагирует не на клики мышью, а на касания мультитача (в частности на жест "провел пальцем по экрану"). Считываются жесты пальцем вверх, вниз, вправо и влево. В зависимости от жеста ищется подходящая для перемещения плитка. Слушатель теперь всего один на весь экран (раньше было по одному на каждую плитку), что есть хорошо.

package  
{
 import flash.display.Sprite;
 import flash.events.MouseEvent;
  import flash.events.TransformGestureEvent;
  import flash.ui.MultitouchInputMode;
  import flash.ui.Multitouch;
 /**
  * ...
  * @author RablV
  */
 public class Game extends Sprite
 {
  // матрица плиток
  private  var matrix:Vector.<Vector.<Tile>> = new <Vector.<Tile>>[ new <Tile> [null, null, null, null],
                    new <Tile> [null, null, null, null],
                    new <Tile> [null, null, null, null],
                    new <Tile> [null, null, null, null] ];
  // эталонная матрица для проверки победы
  private  var checkMatrix:Array = new Array( [1,  2,  3,  4],
             [5,  6,  7,  8],
             [9,  10, 11, 12],
             [13, 14, 15, 0] );                  
                    
  //массив для хранения рандомной последовательности
  private var randomMas:Array = new Array();
  
  // счетчик ходов
  private var countMc:Count_mc;
  // переменная для хранения колличества ходов
  private var moveCount:int = 0;
  // флаг победы
  private var flagVictory:Boolean = false;
  
  private var flag:Boolean = false;   // флаг для определения завершения хода
  
  public function Game() {
   
   Multitouch.inputMode = MultitouchInputMode.GESTURE;  // мультитач поддерживаем жесты. ВНИМАНИЕ! Проверка на поддержку 
             // утсройством жестов не реализована! 
   
   
   setFon();  // устанавливаем фон
   setRestartBtn(); // устанавливаем кнопку Рестарт        
   setCountMc();  // устанавливаем счетчик ходов
   setTiles();   // устанавливаем плитки
   
   addEventListener(TransformGestureEvent.GESTURE_SWIPE , move2);  // слушатель жестов пальцев по экрану
    
  }
  
  // устанавливаем фон
  private function setFon():void {
   var fon:Fon_mc = new Fon_mc(); // берем фон из библиотеки SWC
   addChild(fon);
   
   fon.height = Main.newHeight; // растянем фон полностью на всю высоту экрану (допустимо так как фон однородный)
  }  
       
  // установка кнопки Рестарт
  private function setRestartBtn():void {
   var restartBtn:Restart_btn = new Restart_btn();
   addChild(restartBtn);
   restartBtn.x =  44, 7;
   restartBtn.y = 326, 5;
   restartBtn.addEventListener(MouseEvent.CLICK, newRandGame);
  }
  
  // установка счетчика
  private function setCountMc():void {
   countMc = new Count_mc();
   addChild(countMc);
   countMc.x =  192,55;
   countMc.y = 326,5;
  }
  
  // устанавливаем плитки
  private function setTiles():void {
   
   
   for (var j:int = 0; j < 4; j++ ) {  // в двойном цикле ставим плитки по вертикали и горизонтали
    for (var i:int = 0; i < 4; i++ ) {
     matrix[i][j] = new Tile();
     matrix[i][j].i = i;     // присваемвам плиткам их координаты в матрице
     matrix[i][j].j = j;
     matrix[i][j].x = 4 + i * 64 + 8*i;  // расставляем плитки на игровом поле
     matrix[i][j].y = 4 + j * 64 + 8*j;
     matrix[i][j].tileMc.stop();   // каждую плитку нужно остановить, чтоб не мерцала (т.к. в ней 2 кадра)
     addChild(matrix[i][j]);   
    }
   }
   
   newRandGame();
   
  }
  
  // обработка клика на плику, перемещение плит
  
  private function move2(e:TransformGestureEvent):void {
   
   var currNumber:int;  // для хранения номера той плитки, которую будем двигать
   var numberStr:String; // для хранения строки номера той плитки, которую будем двигать
   var j:int;   // вспомогательные для циклов
   var i:int;
   flag = false;  // сбрасываем флаг движения. Т.е. двигать плитки разрешено.
   
   if (e.offsetY == 1) {  // если провели пальцем вниз
     for (j = 0; j < 4; j++ ) { // в двойном цикле перебираем плитки и ищем ту, которую можно сдвинуть
     for (i = 0; i < 4; i++ ) {
      if (j + 1 < 4 && matrix[i][j + 1].number == 0 && flag!=true) { // если такую плитку нашли
       currNumber = matrix[i][j].number;  // сохраняем номер текущей плитки
       numberStr = String(currNumber); // номер текущей плитки запишем в строку      
       matrix[i][j + 1].tileMc.gotoAndStop(1);
       matrix[i][j+1].tileMc.Number_field.text = numberStr;
       matrix[i][j + 1].number = currNumber;
       endMove(i,j);
      }; 
     }}
    
   }
   
   if (e.offsetY == -1) {   // если провели пальцем вверх
     for (j = 0; j < 4; j++ ) { // в двойном цикле перебираем плитки и ищем ту, которую можно сдвинуть
     for (i = 0; i < 4; i++ ) {
      if (j - 1 > -1 && matrix[i][j - 1].number == 0 && flag!=true) { // если такую плитку нашли
       currNumber = matrix[i][j].number;  // сохраняем номер текущей плитки
       numberStr = String(currNumber); // номер текущей плитки запишем в строку
       matrix[i][j - 1].tileMc.gotoAndStop(1);
       matrix[i][j - 1].tileMc.Number_field.text = numberStr;
       matrix[i][j - 1].number = currNumber;
       endMove(i,j);
      };
     }}
   }
   
   if (e.offsetX == 1) {  // если провели пальцем вправо
     for (j = 0; j < 4; j++ ) { // в двойном цикле перебираем плитки и ищем ту, которую можно сдвинуть
     for (i = 0; i < 4; i++ ) {
      if (i + 1 < 4 && matrix[i + 1][j].number == 0 && flag!=true) { // если такую плитку нашли
       currNumber = matrix[i][j].number;  // сохраняем номер текущей плитки
       numberStr = String(currNumber); // номер текущей плитки запишем в строку
       matrix[i + 1][j].tileMc.gotoAndStop(1);   // пустое поле превращаем в плитку
       matrix[i + 1][j].tileMc.Number_field.text = numberStr; // задаем новой плитке строку с числом
       matrix[i + 1][j].number = currNumber;  // теперь новая плитка превратилась в ту, на которую мы нажали
       endMove(i,j);
      };
     }}
   }
   
   if (e.offsetX == -1) {   // если провели пальцем влево
     for (j = 0; j < 4; j++ ) { // в двойном цикле перебираем плитки и ищем ту, которую можно сдвинуть
     for (i = 0; i < 4; i++ ) {
      if (i - 1 > -1 && matrix[i - 1][j].number == 0 && flag!=true) { // если такую плитку нашли
       currNumber = matrix[i][j].number;  // сохраняем номер текущей плитки
       numberStr = String(currNumber); // номер текущей плитки запишем в строку
       matrix[i - 1][j].tileMc.gotoAndStop(1);
       matrix[i - 1][j].tileMc.Number_field.text = numberStr;
       matrix[i - 1][j].number = currNumber;
       endMove(i,j);
      }  
     }}
   }
   }
   
  // завершение хода
  private function endMove(i:int, j:int):void {
   
      matrix[i][j].number = 0;  // текущую плитку делаем пустым полем
      matrix[i][j].tileMc.gotoAndStop(2);
      moveCount++;   // счетчик ходов
      flag = true;
      countMc.Count_field.text = String(moveCount); // увеличиваем счетчик
      if(check()) flagVictory=true; // проверка на победу 
  } 
       
  // функция создания новой игры
  private function newRandGame(e:MouseEvent = null):void {
   flagVictory = false;  // сброс флага победы
   moveCount=0;  // сброс счетчика ходов
   countMc.Count_field.text = String(moveCount); 
   
   var tempcount:int = 0;  // временная переменная для присвоения занчений в массиве
   random();    // генерируем рандомную последовательность от 1 до 15 в массив
   for (var j:int = 0; j < 4; j++ ) { // в двойном цикле пишем рандомную последовательность в нашу матрицу
    for (var i:int = 0; i < 4; i++ ) {
     matrix[i][j].tileMc.gotoAndStop(1);
     matrix[i][j].tileMc.Number_field.text = String(randomMas[tempcount]);
     matrix[i][j].number = randomMas[tempcount];
     tempcount++;
    }
  }
  

  matrix[3][3].tileMc.gotoAndStop(2); // плитку в правом нижнем углу делаем пустым полем
  matrix[3][3].number = 0;
  }
  
  // создание масива с рандомной последовательностью от 1 до 15
  private function random():void {
   var flag:Boolean = true;
   
   var temp:int = 0;
   var count:int = 0;
   for (var k:int = 0; k < 16; k++ ) randomMas[k]=0 // обнуляем массив
    
   
   for (var j:int = 0; j <15; j++ ) { // в цикле создаем и записываем в массив 15 чисел
    while (flag) {    // этот цикл работает до тех пор, пока не сгенерируется число, которого еще нет в массиве
     if (count == 15) break;
     temp = 15 * Math.random() + 1;
     flag = false;
     for (var i:int = 0; i < 16; i++ ) {  // цикл проверяет сгенерированное число, на его наличие в массиве
      if (randomMas[i] == temp) { flag = true; break; }
     }
    }
    
    randomMas[j] = temp;
    count++;
    flag = true;
   }
  }
   
  // проверка победы
  private function check():Boolean {

   // сравниваем текущую матрицу плиток с эталонной матрицей
   for (var j:int = 0; j < 4; j++ ) {
    for (var i:int = 0; i < 4; i++ ) {
     if (matrix[i][j].number != checkMatrix[j][i]) {
      return false;
      }
    }}
   return true;
   }
  }

}

В классе Main.as мы помимо создания объекта класса Game,  так же напишем небольшой код, который будет подгонять размер нашей игры под размер экрана устройства. Но не тупо растягивать, а увеличивать пропорционально.

package 
{
 import flash.desktop.NativeApplication;
 import flash.events.Event;
 import flash.display.Sprite;
 import flash.display.StageAlign;
 import flash.display.StageScaleMode;
 import flash.ui.Multitouch;
 import flash.ui.MultitouchInputMode;
 import flash.system.Capabilities;
 
 import flash.events.TransformGestureEvent;
 import flash.text.TextField;
 import flash.events.GesturePhase;
 import flash.events.TouchEvent;
 
 /**
  * ...
  * @author RablV
  */
 public class Main extends Sprite 
 {
  
  public static const newHeight:Number=Capabilities.screenResolutionY;
  
  public function Main():void 
  {
   stage.scaleMode = StageScaleMode.NO_SCALE;
   stage.align = StageAlign.TOP_LEFT;
   stage.addEventListener(Event.DEACTIVATE, deactivate);
   
   // touch or gesture?
   Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
   
   // entry point
   var game:Game = new Game(); // создаём новую игру
   addChild(game);

   var currW:int = game.width;  // сохраним текущую ширину игры

   stage.stageWidth = Capabilities.screenResolutionX;  // подгоним игру под разреешение экрана
   stage.stageHeight = Capabilities.screenResolutionY;

   game.width = stage.stageWidth;  // растянем всю графику под ширину экрана
   game.height = game.height / currW * game.width; // пропорционально растянем игру по вертикали
   
   
  
  
   // new to AIR? please read *carefully* the readme.txt files!
  }
  
  private function deactivate(e:Event):void 
  {
   // make sure the app behaves well (or exits) when in background
   //NativeApplication.nativeApplication.exit();
  }
  
 }
 
}

Итак почти всё готово. Осталось скомпилировать игру и упаковать в файл с расширением .apk. Если сейчас попробовать запустить игру на компьютере, мы получим что-то вроде этого:


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

В общем приступим к упаковке игры. Окне "Project" щелкаем правой кнопкой по файлу PackageApp.bat и выбираем "Execute". В появившемся окне нажимаем клавишу "1", затем нажимаем "Enter". Через несколько секунд файл .apk появится в папке dist.


Закидываем этот файл на наше Andriod устройство, устанавливаем. Но игра скорее всего не запуститься. Почему?! Потому что наша игра выполняется в среде Adobe AIR, а это виртуальная машина которую сначала нужно установить на устройство. Скачать Adobe AIR можно бесплатно скачать с Play Marketa и потом спокойно запускать любые Air приложения.

Установив Adobe AIR, мы наконец можем запустить нашу игру


Ну вот мы и добились своего. Создали игру "Пятнашки" и играем на Андроиде =)

Скачать исходники можно здесь.


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

Отправить комментарий