Образец план разработка игры на андроид. Провал длиною в год, или опыт разработки игры под Android

Приветствую всех.
Этот урок был написан в марте 2012. Сейчас я решил опубликовать его на данном ресурсе. Код решил не менять, прошу сильно к нему не придираться(сам бы придрался). Немного доработан сам текст урока и комментарии в коде. Цель урока - не научить делать игры, что в рамках одного урока само по себе невозможно, а продемонстрировать основы работы с мобильным AIR. Почему именно Android, а не ios? Потому что на момент написания урока под рукой был только он. Под ios всё делается практически так же, но есть некоторые отличия, о которых написано в конце второй части урока.
Буду рад любым комментариям, замечаниям, указанием на ошибки.

Немного теории.

Adobe AIR позволяет создавать на ActionScript 3 и MXML для iOS и Android приложения, которые для пользователя ничем не будут отличать от нативных. Их, как и любые нативные приложения, можно распространять через фирменные магазины приложений Apple AppStore и Google Play Store (бывший Android Market). С версии AIR 3.2 появилась поддержка Stage3D. Для работы AIR приложения на Android нужно установить на устройство AIR Runtime, или же при компиляции в captive-runtime среда выполнения вшивается в apk. При этом установка AIR Runtime на девайс не требуется.

При работе с мобильными устройствами стоит учитывать, что разрешения их дисплеев ниже(уже есть и такие, у которых выше), чем у мониторов компьютеров и их физические размеры тоже значительно меньше. Также есть такие понятия, как «физический размер пикселя» и «плотность пикселей», поэтому нужно уделить внимание размеру различных графических элементов(кнопок, персонажей игры и т.д.). В общем, это целая наука и мы не будем останавливаться на ней подробно.
Метод ввода - сенсорный дисплей. Для обработки сенсорного ввода существует специальное событие TouchEvent , хотя и события мыши обрабатываются корректно. Также есть другие особенности, о которых я расскажу в ходе урока.
Мы будем делать очень простую игру для Android. Запускать её можно будет на смартфонах, планшетах и любых других устройствах.
Для работы среды выполнения AIR есть некоторые аппаратные и программные требования.
Для Android они выглядят следующим образом:
- Android версии 2.2 или выше;
- Процессор с архитектурой ARM7 с частотой минимум 550MHz;
- минимум 256 мегабайт оперативной памяти.

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

Для выполнения урока нам понадобится следующее:
- FlashDevelop 4.2;
- Flex SDK 4.6;
- AIR SDK 3.5;
- библиотека от greensock ;
- устройство на Android. Можно обойтись и эмулятором, но это не так интересно;
- установленный на устройство AIR Mobile .

Версии указаны актуальные на момент публикации урока. Уже есть Flex SDK 4.8, но по сути это тот же самый 4.6. И с 4.8 у меня FlashDevelop начинает непомерно поглощать оперативку, непонятно почему. Лучше всегда использовать последнюю версию AIR SDK, так как с каждым релизом добавляются новые возможности и исправляются ошибки. AIR SDK нужно просто распаковать в папку Flex SDK с заменой файлов и подключить Flex SDK к FlashDevelop.

Что именно будем делать.

Делать мы будем небольшую игру. Она представляем из себя следующее: Сверху экрана падают фигуры. Снизу находится платформа, которой надо эти фигуры ловить или не ловить в зависимости от их вида. Игра состоит из трёх экранов:
- главное меню с фоном, логотипом и двумя кнопками. Одна для начала игры, другая для выхода из приложения;
- игровой экран. Содержит платформу, падающие фигуры а также индикаторы текущего уровня и набранных очков. И кнопка выхода в главное меню;
- экран отображения количества набранных очков с кнопками для выхода в меню и «сыграть ещё раз».

Пара скриншотов:

Приступаем.

Для начала, убедитесь, что на устройство установлен AIR, если не установлен, установите. Также установите на компьютер драйвера для вашего устройства для подключения к с помощью кабеля.

Запускаем FlashDevelop и создаём новый проект AIR AS3 Mobile app. Вот так выглядит созданный проект:

Что мы перед собой видим:

Папка bat содержит несколько пакетных файлов:
- CreateCertificate.bat нужен для генерации self-signed сертификата для Android. Без этого сертификата не получится собрать apk;
- InstallAirRuntime.bat устанавливает AIR runtime на Android устройство из %FLEX_SDK%\runtimes\air\android\device\runtime.apk. Лучше самостоятельно установить последнюю версию из Android Play Store;
- Packager.bat упаковывает флешку в apk(android) или ipa(ios);
- SetupApplication.bat содержит различные параметры приложения (пути к сертификатам, пароли от них, название приложения и т.п.);
- SetupSDK.bat содержит путь к FlexSDK. Определяется автоматически, если не определился, нужно прописать вручную. И также содержит путь к AndroidSDK. На самом деле из него нужны только три файла: adb.exe , AdbWinApi.dll и AdbWinUsbApi.dll . Причём они уже содержатся в дистрибутиве FlashDevelop и путь к ним прописывается также автоматически.

В папку bin помещается скомпилированный swf файл.
В папке cert должны лежать сертификаты.
Папка icons содержит наборы иконок.
Две стандартные папки lib и src для библиотек и классов соответственно.

Корневая папка:
- Стандартный для AIR приложений application.xml с различными параметрами приложения;
- PackageApp.bat позволяет выбрать платформу и тип упаковки приложения. После выбора упаковывает. Появляется папка dist c apk или ipa;
- Run.bat заливает приложение на мобильное устройство и запускает его там;
- Два файла AIR_Android_readme.txt и AIR_iOS_readme.txt с инструкциями.

Настройка проекта.

Открываем настройки проекта Project/Propeties , меняем цвет фона на чёрный. Разрешение значения не имеет. Будем подстраиваться под текущее.
Версию AIR выставляем 3.5. В итоге параметры должны выглядеть так:

Открываем файл application.xml , в нём во второй строке меняем версию AIR на 3.5:

Открываем настройки AIR - Project/AIR App Properties . В открывшемся окне переходим к вкладке Initial window . В этом окне выбираем Non-Windowed Platforms .
Переключаем режим отображения на портретный. И выключаем Auto Orient, так как игра будет только в портретном режиме. Render Mode выставляем CPU или Direct . Настройки должны выглядеть вот так:


Далее прям во FlashDevelop двойным щелчком открываем файл Run.bat и меняем строку goto desktop на goto android-debug . Это нужно для тестирования проекта на устройстве. Если оставить goto desktop , проект будет запускаться на эмуляторе.

Теперь нам нужно сгенерировать сертификат. Без него не получится запаковать приложение в.apk. Если папка cert отсутствует в проекте, создайте её вручную. Это важно, так как bat файл не сможет достучаться до несуществующей папки. Запускаем файл bat/CreateCertificate.bat (правый клик/Execute), в папке cert появляется наш сертификат со стандартным паролем «fd». Стоит сказать, что любое приложение для Android должно быть подписано сертификатом. То, что мы сгенерировали, это так называемый «сертификат для разработки». Его нам на данном этапе достаточно.

Из архива с сайта greensock.com достаём файл greensock.swc , кладём его в папку lib и подключаем к проекту (правый клик/Add To Library).

С настройкой всё. Добавим к проекту нужную графику и иконки. Создайте папку assets и положите туда графику из архива , также замените иконки. Или же можете нарисовать собственные аналоги.

Приступаем к коду.

А теперь самое главное и интересное - пишем код. Классы игры выглядят следующим образом:

ItemType - типы предметов. Тех самых, которые падают сверху экрана
package constants { /** * Статические константы для определния типа игрового объекта. * * @author illuzor */ public class ItemType { /** обычный объект. Прибавляет единицу к очкам */ public static const GOOD:String = "goodItem"; /** "очень хороший" объект. Прибавляет 5 к очкам */ public static const VERY_GOOD:String = "veryGoodItem"; /** "злой" объект. Отнимает единицу от очков */ public static const EVIL:String = "evilitem"; } }
ScreenType - типы экранов. Всего у нас их три, и описаны они выше
package constants { /** * Статические константы для определения типа экрана * * @author illuzor */ public class ScreenType { /** главное меню */ public static const MAIN_MENU:String = "mainMenu"; /** игровой экран */ public static const GAME_SCREEN:String = "gameScreen"; /** экран с отображением результата игры */ public static const SCORE_SCREEN:String = "scoreScreen"; } }
GameEvent - игровые события
package events { import flash.events.Event; /** * Игровые события. * * @author illuzor */ public class GameEvent extends Event { /** выход из игры в главное меню через кнопку menu */ public static const EXIT_GAME:String = "exitGame"; /** игра проиграна */ public static const GAME_OVER:String = "gameOver"; public function GameEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false) { super(type, bubbles, cancelable); } public override function clone():Event { return new GameEvent(type, bubbles, cancelable); } public override function toString():String { return formatToString("GameEvent", "type", "bubbles", "cancelable", "eventPhase"); } } }
Теперь рассмотрим пакет elements . Это различные графические элементы, которые используются в игре.

Button - класс кнопки. Состоит из графического изображения и текста. Имеет два состояния: нажато/не нажато
package elements { import com.greensock.TweenLite; import flash.display.Bitmap; import flash.display.Sprite; import flash.events.Event; import flash.events.TouchEvent; import flash.text.TextField; import tools.Bitmaps; import tools.Tools; /** * Класс кнопки, которая используется в меню и в других местах. * * @author illuzor */ public class Button extends Sprite { /** @private текст для отображения на кнопке */ private var text:String; /** @private битмап для фона кнопки */ private var buttonImage:Bitmap; /** * Конструктор слушает добавления на сцену. * тут stage нам нужен на случай, если произойдёт тап по кнопке и перемещение пальца в сторону от кнопки * * @param text текст для отображения на кнопке */ public function Button(text:String) { this.text = text; addEventListener(Event.ADDED_TO_STAGE, addedToStage); } /** * @private добавление на сцену. * добавляем графику и текстовое поле кнопки. * * @param e событие добавления на сцену */ private function addedToStage(e:Event):void { removeEventListener(Event.ADDED_TO_STAGE, addedToStage); buttonImage = Bitmaps.buttonBitmap; // добавляем битмап buttonImage.smoothing = true; addChild(buttonImage); var textField:TextField = Tools.generateTextField(50, text); // генерируем текстовое поле... textField.x = (buttonImage.width - textField.width) / 2; // ... позиционируем его и добавляем в дисплейЛист textField.y = (buttonImage.height - textField.height) / 2; addChild(textField); this.addEventListener(TouchEvent.TOUCH_BEGIN, touchBegin); // прикосновение к кнопке addEventListener(Event.REMOVED_FROM_STAGE, removedFromStage); // слушатель удаления со stage } /** * анимируем по альфе на половину * * @param e событие прикосновения пальцем к кнопке */ private function touchBegin(e:TouchEvent):void { TweenLite.to(buttonImage, .3, { alpha:.5 }); stage.addEventListener(TouchEvent.TOUCH_END, touchEnd); // убирание пальца от дисплея после прикосновения к кнопке } /** * возвращаем альфу к единице * * @param e событие убирания пальца */ private function touchEnd(e:TouchEvent):void { TweenLite.to(buttonImage, .3, { alpha:1 }); stage.removeEventListener(TouchEvent.TOUCH_END, touchEnd) } /** * при удалении со stage убиваем более не нужные слушатели * * @param e событие удаления со сцены */ private function removedFromStage(e:Event):void { removeEventListener(Event.REMOVED_FROM_STAGE, removedFromStage); this.removeEventListener(TouchEvent.TOUCH_BEGIN, touchBegin); stage.removeEventListener(TouchEvent.TOUCH_END, touchEnd) } } }
Item - предмет, который падает сверху в процессе игры. Может быть трёх типов, они описаны в коде
package elements { import constants.ItemType; import flash.display.Shape; import flash.filters.GlowFilter; /** * Класс айтема (предмета), который падаёт сверху экрана. * * @author illuzor */ public class Item extends Shape { /** тип айтема из constants.ItemType */ public var type:String; /** скорость движения айтема */ public var speed:uint; /** * В конструкторе рисуется графика айтема. * * @param type тип айтема */ public function Item(type:String) { this.type = type; switch (type) { // проверяем, какой тип передан в конструктор // и в зависимости от этого рисуем соответствующую графику. case ItemType.GOOD: // рисуем зелёный квадрат this.graphics.beginFill(0x00A400); this.graphics.drawRect(0, 0, 14, 14); this.graphics.endFill(); break; case ItemType.VERY_GOOD: // рисуем синий квадрат со свечением this.graphics.beginFill(0x01A6FE); this.graphics.drawRect(0, 0, 14, 14); this.graphics.endFill(); this.filters = ; break; case ItemType.EVIL: // рисуем красный круг graphics.beginFill(0xFF0000); graphics.drawCircle(0, 0, 7); graphics.endFill(); break; } } } }
Platform - «главный герой» игры. Небольшая платформа, которой управляет игрок.
package elements { import flash.display.Shape; import flash.filters.GlowFilter; /** * Платформа для ловли объектов, которая находится в нижней части экрана. * Тут всё очень просто, рисуется белый прямоугольник и применяется фильтр свечения. * Отдельный класс для того, чтобы платформа воспринималась, как отдельная игровая единица. * * @author illuzor */ public class Platform extends Shape { public function Platform() { this.graphics.clear(); this.graphics.beginFill(0xFFFFFF); this.graphics.drawRect(0, 0, 110, 24); this.graphics.endFill(); this.filters = ; } } }
Пакет tools с инструментами.

Класс Bitmaps содержит прикреплённую графику и методы её получения извне
package tools { import flash.display.Bitmap; /** * "Генератор" битмапов из прикреплённых файлов * * @author illuzor */ public class Bitmaps { /** @private прикреплённый файл графики фона */ private static var BackgroundBitmap:Class; /** @private прикреплённый файл графики для кнопки */ private static var ButtonBitmap:Class; /** @private прикреплённый файл графики для логотипа */ private static var LogoBitmap:Class; /** Битмап фона */ public static function get backgroundBitmap():Bitmap { return new BackgroundBitmap() as Bitmap; } /** Битмап кнопки */ public static function get buttonBitmap():Bitmap { return new ButtonBitmap() as Bitmap; } /** Битмап логотипа */ public static function get logoBitmap():Bitmap { return new LogoBitmap() as Bitmap; } } }
Класс Tools содержит другие инструменты. Пока что, только генератор текстфилда
package tools { import flash.text.TextField; import flash.text.TextFormat; /** * Класс с небольшими инструментами. * Пока что содержит только генератор текстовового поля. * * @author illuzor */ public class Tools { /** * Генератор текстфилда по заданным параметрам * * @param size размер шрифта * @param text текст для отображения * @param color цвет текста * @return настроенное текстовое поле */ public static function generateTextField(size:uint, text:String = "", color:uint = 0xFFFFFF):TextField { var textFormat:TextFormat = new TextFormat(); textFormat.color = color; textFormat.size = size; var textField:TextField = new TextField(); textField.selectable = false; textField.defaultTextFormat = textFormat; textField.text = text; textField.width = textField.textWidth +4; textField.height = textField.textHeight +4; return textField; } } }
Экраны приложения из пакета screens

MainMenu - главное меню игры. Отображается сразу после запуска. Содержит кнопки, фон и логотип.
package screens { import flash.display.Bitmap; import flash.display.Sprite; import flash.events.Event; import flash.display.DisplayObject; import tools.Bitmaps; import elements.Button; /** * Класс главного меню игры. * Тут отображется фон, логотип и две кнопки. * * @author illuzor */ public class MainMenu extends Sprite { /** кнопка "PLAY" */ public var playButton:Button; /** кнопка "EXIT" */ public var exitButton:Button; /** * В конструкторе просто слушаем добавление на сцену */ public function MainMenu() { addEventListener(Event.ADDED_TO_STAGE, adddedToStage); } /** * создаём и добавляем фон, логотип, кнопки * * @param e событие добавления на сцену */ private function adddedToStage(e:Event):void { removeEventListener(Event.ADDED_TO_STAGE, adddedToStage); // создаём битмап фона и добавляем его на сцену var background:Bitmap = Bitmaps.backgroundBitmap; background.smoothing = true; addChild(background); placeBackground(background); // позиционируем фон // создаём битмап логотипа, задаём размер относительно ширины сцены // задаём положение и добавляем на сцену var logo:Bitmap = Bitmaps.logoBitmap; logo.smoothing = true; logo.width = stage.stageWidth * .7; logo.scaleY = logo.scaleX; logo.x = (stage.stageWidth - logo.width) / 2; logo.y = stage.stageHeight / 5; addChild(logo); // контейнер для кнопок для удобного позиционирования этих кнопок var buttonsContainer:Sprite = new Sprite(); addChild(buttonsContainer); // создание кнопок "PLAY" и "EXIT", подгонка их размеров и добавление в контейнер playButton = new Button("PLAY"); buttonsContainer.addChild(playButton); playButton.width = stage.stageWidth / 2; playButton.scaleY = playButton.scaleX; exitButton = new Button("EXIT"); buttonsContainer.addChild(exitButton); exitButton.y = buttonsContainer.height + 25; exitButton.width = stage.stageWidth / 2; exitButton.scaleY = exitButton.scaleX; // позиционирование контейнера с кнопками buttonsContainer.x = (stage.stageWidth - buttonsContainer.width) / 2; buttonsContainer.y = (stage.stageHeight - buttonsContainer.height) / 2 + stage.stageWidth / 6; } /** * Эта функция делает так, что переданный ей DisplayObject заполняет собой всю сцену * без изменения пропорций. В нашем случае это фоновое изображение * * @param scaledObject DisplayObject для подгонки */ private function placeBackground(scaledObject:DisplayObject):void { scaledObject.scaleX = scaledObject.scaleY = 1; var scale:Number; if (scaledObject.width / scaledObject.height > stage.stageWidth / stage.stageHeight){ scale = stage.stageHeight / scaledObject.height; } else { scale = stage.stageWidth / scaledObject.width; } scaledObject.scaleX = scaledObject.scaleY = scale; scaledObject.x = (stage.stageWidth - scaledObject.width) / 2; scaledObject.y = (stage.stageHeight - scaledObject.height) / 2; } } }
В классе GameScreen проходит игровой процесс. Сверху падают айтемы. В зависимости от их типа нужно ловить их или избегать. Управляет игровым циклом
package screens { import com.greensock.TweenLite; import constants.ItemType; import elements.Button; import elements.Platform; import elements.Item; import events.GameEvent; import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; import flash.events.TimerEvent; import flash.events.TouchEvent; import flash.text.TextField; import flash.utils.Timer; import tools.Tools; /** * Основной класс игры. Что предствляет собой игровой процесс: * Внизу экрана находится платформа, которую можно двигать влево/вправо движением пальца по экрану в любой части экрана. * Сверху падают так называемые айтемы, которые могут быть трёх видов: * - ItemType.GOOD - обычный айтем. при собирании прибавляет единицу к очками, при пропускании(уход за нижнюю границу экрана) * отнимает единицу от очков * - ItemType.VERY_GOOD - "усиленный" айтем. При собирании прибавляет 5 к очками. При пропускании ничего не происходит. * - ItemType.EVIL - "злой" айтем. При собирании отнимает единицу от очков. При пропускании ничего не происходит. * * При значении очков меньше пяти засчитывается проигрыш, а результатом игры считается максимальное набранное количество очков. * С каждым новым уровнем айтемы движутся быстрей, чем в предыдущем. * * Сверху слева находится идикатор набранных очков, справа сверху кнопка выхода в главное меню. * Снизу в центре под платформой находится индикатор текущего уровня. * * * @author illuzor */ public class GameScreen extends Sprite { /** @private контейнер для платформы и айтемов */ private var gameContainer:Sprite; /** @private кнопка для выхода в главное меню */ private var menuButton:Button; /** @private платформа */ private var platform:Platform; /** @private игровой таймер. нужен для добавления нового айтема */ private var gameTimer:Timer; /** @private номер текущего уровня */ private var currentLevel:uint; /** @private текущее количество очков */ private var currentScore:int; /** @private текстовое поле для отображения номера уровня */ private var levelText:TextField; /** @private текстовое поле для отображения очков */ private var scoreText:TextField; /** максимальное количество очков */ public var maxScore:uint; /** * В конструкторе слушаем добавление на сцену. */ public function GameScreen() { addEventListener(Event.ADDED_TO_STAGE, addedtToStage); } /** * создаём элементы экрана * * @param e событие добавления на сцену */ private function addedtToStage(e:Event):void { removeEventListener(Event.ADDED_TO_STAGE, addedtToStage); gameContainer = new Sprite(); // контейнер addChild(gameContainer); platform = new Platform(); // создаём платформу, подгоняем её размер и положение и добавляем на сцену platform.width = stage.stageWidth * .18; platform.scaleY = platform.scaleX; platform.x = (stage.stageWidth - platform.width) / 2; platform.y = stage.stageHeight * .88; addChild(platform); scoreText = Tools.generateTextField(30, "SCORE: 0"); // текстовое поле очков scoreText.width = stage.stageWidth / 2; scoreText.x = scoreText.y = 10; addChild(scoreText); levelText = Tools.generateTextField(30, "LEVEL: 1"); // текстовое поле уровня levelText.width = levelText.textWidth + 4; levelText.x = (stage.stageWidth - levelText.width) / 2; levelText.y = stage.stageHeight - levelText.height - 20; addChild(levelText); menuButton = new Button("MENU");// кнопка для выхода в главное меню. addChild(menuButton); menuButton.width = stage.stageWidth / 3.2; menuButton.scaleY = menuButton.scaleX; menuButton.x = stage.stageWidth - menuButton.width - 10; menuButton.y = 10; startNewLevel(); // запускаем новый уровень menuButton.addEventListener(TouchEvent.TOUCH_TAP, exitGame); // событие нажатия на кнопку выхода в меню stage.addEventListener(MouseEvent.MOUSE_MOVE, moveplatform); // событие движения пальца по экрану stage.addEventListener(MouseEvent.MOUSE_UP, moveplatform); // событие убирания пальца с экрана addEventListener(Event.ENTER_FRAME, updateGame); // обновление состояния игры addEventListener(Event.REMOVED_FROM_STAGE, removedFromStage); } /** * @private двигаем платформу в зависимости от положения пальца на дисплее. * * @param e событие движения пальца по экрану или его убирания с экрана */ private function moveplatform(e:MouseEvent):void { if(mouseY > menuButton.y + menuButton.height)TweenLite.to(platform, .36, { x:mouseX + -platform.width/2 }); } /** * @private запуск нового уровня. каждый уровень состоит из 20 айтемов */ private function startNewLevel():void { var interval:uint = 2300; // интервал вызова таймера if (2300 - currentLevel * 350 < 250) { // чем выше уровень, тем меньше интервал interval = 350; } else { interval = 2300 - currentLevel * 350; } gameTimer = new Timer(interval, 20); // создаём и запускаем таймер. gameTimer.start(); gameTimer.addEventListener(TimerEvent.TIMER, addItem); gameTimer.addEventListener(TimerEvent.TIMER_COMPLETE, cicleEnd); } /** * @private Создаём новый айтем по таймеру * * @param e событие тика таймера */ private function addItem(e:TimerEvent):void { var randomRange:Number = Math.random(); // случайное значене var itemType:String = ItemType.GOOD; // тип нового айтема. по умолчнанию все айтемы обычные if (randomRange > 0.65 && randomRange < .95) { // если случайное значение в заданном диапазоне (30%)... itemType = ItemType.EVIL; // айтем злой } else if (randomRange >= .95){ // 5% айтемов пусть будут "усиленными" itemType = ItemType.VERY_GOOD; } var item:Item = new Item(itemType); // создаём новый айтем со сгенерированным типом item.x = stage.stageWidth * Math.random(); // помещаем на случайны.x item.y = -item.height; // а по.y убираем за пределы сцены item.speed = currentLevel+1; // скорость айтема gameContainer.addChild(item); // и добавлеем его на сцену } /** * @private когда таймер закончил работу, очищаем, что не нужно и запускаем новый уровень * * @param e событие окончания работы таймера */ private function cicleEnd(e:TimerEvent):void { gameTimer.removeEventListener(TimerEvent.TIMER, addItem); gameTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, cicleEnd); gameTimer = null; // удаляем слушаели с таймера и ссылку на таймер currentLevel++; // увеличиваем уровень на единицу levelText.text = "LEVEL: " + String(currentLevel + 1); // обновляем текст уровня levelText.width = levelText.textWidth +4; startNewLevel();// запускаем новый уровень } /** * @private Игровой цикл * Обновляем положения айтемов. если они ушли за пределы сцены, удаляем. * Обратываем их столкновения с платформой * * @param e enterFrame событие */ private function updateGame(e:Event):void { // в цикле проходимся по всему содержимому игрового контейнера, кроме платформы (i = 1) for (var i:int = 1; i < gameContainer.numChildren; i++) { var tempItem:Item = gameContainer.getChildAt(i) as Item; // берём айтем tempItem.y += (tempItem.speed * 3) * .8; // увеличиваем его.y координаты в зависимости от его скорости. if (tempItem.y > stage.stageHeight) { // если он ушёл вниз за сцену... gameContainer.removeChild(tempItem); //... удаляем его... if (tempItem.type == ItemType.GOOD) currentScore--; // .. а если его тип при этом оказался обычным, отнимаем единицу от очков. } if (tempItem.hitTestObject(platform)) { // если айтем пойман платформой // в зависимости от его типа, производим действие. // думаю, тут ничего не надо объяснять switch (tempItem.type) { case ItemType.GOOD: currentScore++; break; case ItemType.VERY_GOOD: currentScore +=5; break; case ItemType.EVIL: currentScore--; break; } // также при попадании на платформу айтем больше не нужен, удаляем его со сцены gameContainer.removeChild(tempItem); } } scoreText.text = "SCORE: " + currentScore; // обновляем тексовое поле с очками if (maxScore < currentScore) maxScore = currentScore; // записываем максимальное количество очков if (currentScore < -5) { // если количество очков меньше, чем -5.. dispatchEvent(new GameEvent(GameEvent.GAME_OVER)); //... генерируем событие проигрыша } } /** * @private генерируем событие выхода из игры * * @param e событие прикосновения к кнопке выхода в меню */ private function exitGame(e:TouchEvent):void { dispatchEvent(new GameEvent(GameEvent.EXIT_GAME)); } /** * @private удаляем все ненужные больше слушатели * * @param e событие удаления со сцены */ private function removedFromStage(e:Event):void { removeEventListener(Event.REMOVED_FROM_STAGE, removedFromStage); removeEventListener(Event.ENTER_FRAME, updateGame); stage.removeEventListener(MouseEvent.MOUSE_MOVE, moveplatform); stage.removeEventListener(MouseEvent.MOUSE_UP, moveplatform); menuButton.removeEventListener(TouchEvent.TOUCH_TAP, exitGame); gameTimer.stop(); gameTimer.removeEventListener(TimerEvent.TIMER, addItem); gameTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, cicleEnd); } } }
ScoreScreen отображается после проигрыша. Показывает текстовое поле с итоговым результатом и кнопки для возврата в меню и повторения игры
package screens { import elements.Button; import flash.display.Sprite; import flash.events.Event; import flash.text.TextField; import tools.Tools; /** * Экран для отображения результата игры в виде количества набранных очков. * Состоит из текста "YOUR SCORE: " и кнопок "MENU" и "AGAIN" * * @author illuzor */ public class ScoreScreen extends Sprite { /** @private количество очков для отображения */ private var score:uint; /** кнопка "MENU" */ public var menuButton:Button; /** кнопка "AGAIN" */ public var againButton:Button; /** * В конструкторе ждём добавления на stage. Тут stage нужен для позиционирования элементов * @param score количество очков для отображения */ public function ScoreScreen(score:uint) { this.score = score; addEventListener(Event.ADDED_TO_STAGE, addedToStage); } /** * создаём текстовое поле для отображения очков и кнопки для повтора игры и возврата в главное меню. * * @param e событие добавления на сцену */ private function addedToStage(e:Event):void { removeEventListener(Event.ADDED_TO_STAGE, addedToStage); // текстовое поле отображет количество набранных очков var scoreText:TextField = Tools.generateTextField(40, "YOUR SCORE: " + score); scoreText.x = (stage.stageWidth - scoreText.width) / 2; scoreText.y = (stage.stageHeight - scoreText.height) / 2 - stage.stageHeight / 6; addChild(scoreText); // кнопка для выхода в главное меню menuButton = new Button("MENU"); addChild(menuButton); menuButton.width = stage.stageWidth / 2; menuButton.scaleY = menuButton.scaleX; menuButton.x = (stage.stageWidth - menuButton.width) / 2; menuButton.y = scoreText.y + scoreText.height + 30; // кнопка "сыграть ещё" againButton = new Button("AGAIN"); addChild(againButton); againButton.width = stage.stageWidth / 2; againButton.scaleY = againButton.scaleX; againButton.x = (stage.stageWidth - againButton.width) / 2; againButton.y = menuButton.y + menuButton.height + 30; } } }
И последнее - класс Main из корня. Основной класс игры. Служит для переключения экранов и очистки при этих переключениях. Слушает кнопки экранов на нажатие и игровые события. В зависимости от них очищает сцену и показывает нужный экран
package { import constants.ScreenType; import events.GameEvent; import flash.desktop.NativeApplication; import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import flash.events.TouchEvent; import flash.ui.Multitouch; import flash.ui.MultitouchInputMode; import screens.GameScreen; import screens.MainMenu; import screens.ScoreScreen; /** * Основной класс игры. Управляет отображением разных экранов игры. * Игра представляет из себя следующее: внизу экрана находится "платформа", которую можно передвигать. * Сверху падают "предметы" трёх типов: красный, зелёный, синий. Нужно ловить их платформой. * * Создано с помощью FlashDevelop 4.0.1 и Flex SDK 4.6 * С использованием библиотеки от greensock - http://www.greensock.com/v11/ * * @author illuzor * @version 0.6 */ public class Main extends Sprite { /** @private экран главного меню */ private var menuScreen:MainMenu; /** @private экран игры */ private var gameScreen:GameScreen; /** @private экран отображения результата игры (набарнных очков) */ private var scoreScreen:ScoreScreen; /** @private эта переменная хранит текстовое значения типа экрана * из constants.ScreenType, который отображается в данный момент * нужна для корретной очистки от слушателей и экранных объектов */ private var currentScreen:String; /** * Главный конструктор */ public function Main():void { stage.scaleMode = StageScaleMode.NO_SCALE; stage.align = StageAlign.TOP_LEFT; // событие деактивации приложения. то есть выхода из него или сворачивания (нажитии кнопки "домой" или "назад") stage.addEventListener(Event.DEACTIVATE, deactivate); // тип ввода. TOUCH_POINT выставлен по умолчанию и нам он подходит Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT; showMenu(); // показываем главное меню. } /** * @private показываем главное меню */ private function showMenu():void { currentScreen = ScreenType.MAIN_MENU; // применяем тип экрана menuScreen = new MainMenu(); // создаём меню и добавляем его на сцену addChild(menuScreen); menuScreen.playButton.addEventListener(TouchEvent.TOUCH_TAP, startGame); // добавляем слушатели к кнопкам меню menuScreen.exitButton.addEventListener(TouchEvent.TOUCH_TAP, deactivate); } /** * @private показываем игровой экран * * @param e событие прикосновения к кнопке playButton главного меню */ private function startGame(e:TouchEvent):void { clear(); // очищаем currentScreen = ScreenType.GAME_SCREEN; // применяем тип экрана gameScreen = new GameScreen(); // создаём игровой экран и добавляем на сцену addChild(gameScreen); gameScreen.addEventListener(GameEvent.EXIT_GAME, exitGame); // событие выхода из игры по кнопке gameScreen.addEventListener(GameEvent.GAME_OVER, gameOver); // событие проигрыша } /** * @private выход из игры по нажатию кнопки * * @param e событие выхода из игры */ private function exitGame(e:GameEvent):void { clear(); // очищаем showMenu(); // показываем главное меню } /** * @private игра проиграна, показ результата * * @param e событие проигрыша */ private function gameOver(e:GameEvent):void { var score:uint = gameScreen.maxScore; // количество очков. достаётся из игрового экрана из переменной maxScore clear(); // очищаем currentScreen = ScreenType.SCORE_SCREEN; // применяем тип экрана scoreScreen = new ScoreScreen(score); // создаём и показываем экран результатов addChild(scoreScreen); scoreScreen.menuButton.addEventListener(TouchEvent.TOUCH_TAP, exitScore); // слушатели кнопок экрана результатов scoreScreen.againButton.addEventListener(TouchEvent.TOUCH_TAP, startGame); } /** * @private выход из экрана результатов по кнопке. * нужно сделать то же самое, что и при нажатии кнопки выхода из игры, поэтому просто вызываем exitGame() * * @param e событие прикосновения к кнопке выхода из экрана результатов */ private function exitScore(e:TouchEvent):void { exitGame(null); } /** * @private очистка от ненужных слушателей и экранных объектов * в зависимости от текущего экрана. */ private function clear():void { switch (currentScreen) { case ScreenType.MAIN_MENU: menuScreen.playButton.removeEventListener(TouchEvent.TOUCH_TAP, startGame); menuScreen.exitButton.removeEventListener(TouchEvent.TOUCH_TAP, deactivate); removeChild(menuScreen); menuScreen = null; break; case ScreenType.GAME_SCREEN: gameScreen.removeEventListener(GameEvent.EXIT_GAME, exitGame); gameScreen.removeEventListener(GameEvent.GAME_OVER, gameOver); removeChild(gameScreen); gameScreen = null; break; case ScreenType.SCORE_SCREEN: scoreScreen.menuButton.removeEventListener(TouchEvent.TOUCH_TAP, exitScore); scoreScreen.againButton.removeEventListener(TouchEvent.TOUCH_TAP, startGame); removeChild(scoreScreen); scoreScreen = null; break; } } /** * выход из приложения через NativeApplication * при нажатии кнопки "домой" или "назад" приложение закрывается * * @param e событие деактивации */ private function deactivate(e:Event):void { NativeApplication.nativeApplication.exit(); } } }

Компиляция и запуск.

Проект настроен, графика нарисована, код написан. Это всё. Можно тестировать игру. Берём смартфон, заходим в Developer Options , включаем USB Debug Mode . Подключаем его кабелем к компьютеру.

Теперь нужно совершить некоторые действия во FlashDevelop.
- компилируем проект Project/BuildProject (F8). В папке bin появится флешка;
- запускаем PackageApp.bat . Вводим «2», нажимаем Enter. Ждём, пока проект запакуется в apk и появится в папке dist ;
- в меню выбираем Debug/Start Remote Session ;
- Запускаем Run.bat - приложение зальётся на устройство, запустится там и подключится к дебаггеру.

Всё, можно тестировать, использовать дебаггер и даже ловить трейсы прям с устройства. Профайлер, к сожалению, не работает.

Все эти 4 действия можно произвести другим способом. Просто выбираем Project/Test Project (F5). Всё выполнится автоматически.

Когда приложение протестировано и закончено, запускаем PackageApp.bat , вводим единицу. В папке dist появится релизный apk.

Конец первой части.

Этот урок начинает серию статей, посвященных написанию аркадных игр для Android. За основу был взят цикл, который написал и опубликовалв своем блоге Tamas Jano . Материал ориентирован, прежде всего, на начинающих программистов, которые хотят попробовать свои силы в создании игр для Android. Я ставлю перед собой цель создать у нас на сайте максимально понятный и доступный учебник для молодой русскоязычной аудитории, поэтому очень надеюсь на вашу критику, вопросы и комментарии. Опубликованный в этом цикле материал будет постоянно перерабатываться с учетом ваших вопросов, пожеланий и замечаний. Надеюсь, что вместе мы сможем успешно осуществить этот небольшой эксперимент.

Александр Ледков

Идея и архитектура

Прежде чем переходить непосредственно к программированию, давайте определимся с нашими задачами и опишем в общих чертах нашу будущую игру. Идея проста: главный герой борется с ордами роботов, которые хотят его уничтожить. У него есть три жизни и лазерное ружье. Роботы не умеют стрелять. Все что они могут - поймать нашего героя и оторвать ему голову... ну или что-нибудь еще. Управление персонажем осуществляется с помощью двух "сенсорных джойстиков" Вы наверняка встречали их в подобных играх. В левом нижнем углу будет размещен джойстик, отвечающий за перемещение героя. В правом нижнем углу - за оружие.


Смоделируем игровую ситуацию. Наш персонаж находится в центре экрана. Роботы каждую 1/10 секунды приближаются к нему. каждую десятую секунду мы проверяем также не произошло ли касание экрана. Если произошло - двигаем наш персонаж в необходимом направлении или делаем выстрел. если выстрел сделан, каждый тик (1/10 секунды) мы проверяем столкновение пули с врагами. Если пуля попала в робота - то и робот и пуля взрываются, если нет - роботы и пуля перемещаются на новые позиции (робот перемещается на 5 пикселей за тик, а пуля - на 50 пикселей). Мы также проверяем не поймал ли робот нашего героя. Если поймал - игра заканчивается.

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

  1. Модуль управления . Здесь считываются координаты касания экрана.
  2. Игровой движок . Здесь мы должны обновить состояние игровых объектов, то есть вычислить их новые координаты, проверить здоровье, столкновения и тому подобное.
  3. Аудио модуль .
  4. Графический модуль . Здесь на основании текущего состояния формируется и выводится на экран новый кадр.

Давайте более детально рассмотрим наши модули.

Модуль управления

В нашей игре сообщения генерируются при касании пользователем двух областей на экране. Программа отслеживает события onTouch и записывает координаты каждого касания. Если координаты находятся внутри управляющей области, мы посылаем соответствующую команду игровому движку. Например, если произошло касание сбоку круга, мы должны двигать нашего персонажа в соответствующую сторону. Если произошло касание круга, управляющего оружием, мы посылаем команду движку обработать событие выстрела.

Игровой движок

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

Давайте рассмотрим взаимодействие модуля управления и игрового движка. На представленном выше рисунке показан круг-контроллер. Светлое зеленое пятно символизирует область касания. Модуль управления сообщает игровому движку координаты касания (dx и dy - расстояния в пикселях от центра круга). На основании этих координат игровой движок вычисляет направление и скорость движения нашего героя. Например, если dx>0, наш персонаж движется вправо, eсли dy>0 - в верх.

Аудио модуль

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

Графический модуль

Этот модуль отвечает за вывод игровой ситуации на экран телефона. В Android существует несколько способов формирования изображения на экране. Можно рисовать прямо на canvas, полученный от View или использовать отдельный графический буффер и вы, а затем передавать его View, а можно воспользоваться возможностями библиотеки OpenGL. Полезно при разработке игры постоянно измерять FPS - число кадров в секунду, которые выдает ваш графический движок. Величина в 30 FPS означает, что за одну секунду наша программа успевает 30 раз обновить экран. Забегая вперед скажу, что для мобильного устройства 30 FPS более чем достойный показатель.

Создаем проект-заготовку для будущей Android игры

Я не буду здесь подробно расписывать процесс установки Android SDK и Eclipse, за рамками повествования я оставлю и элементарные действия по созданию Android проекта. В интернете валяется огромное количество уроков и статей, посвященных этой теме.

Создайте новый проект. В поле Project Name введитеDroidz . В качестве целевой платформы выберите Android 2.2 или выше. В Package Name - "ru.mobilab.gamesample ". Не забудьте поставить галочку около Create Activity. В качестве имени главной activity введитеDroidzActivity .

Откройте файл src/ru.mobilab.gamesample/DroidzActivity.java

import android.app.Activity;
import android.os.Bundle;

public class DroidzActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}

Метод onCreate вызывается при создании activity во время запуска приложения. Этот метод можно рассматривать, как точу входа в программу. Класс R.java автоматически генерируется Eclipse и содержит в себе ссылки на ресурсы. Каждый раз, когда вы изменяете ресурсы в Eclipse класс R пересобирается.

Главный игровой цикл

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

Как вы помните, в Android все происходит внутри Activity. Activity создает View - объект, где происходит все самое интересное. Именно через него мы можем получить информацию о касаниях экрана, здесь же можно вывести картинку на экран.

Давайте откроем файл DroidzActivity.java. В конструкторе класса вы увидите строчку

SetContentView(R.layout.main);

эта строка выбирает текущий объект View для Activity. Давайте создадим новый объект для View. Наиболее простым способом получения View - создать собственный класс на основании SurfaceView. В нашем классе мы реализуем интерфейс SurfaceHolder.Callback, чтобы ускорить доступ к изменениям поверхности, например когда она уничтожается при изменении ориентации устройства.

MainGamePanel.java

package ru.mobilab.gamesample;






SurfaceHolder.Callback {


super(context);
// Добавляем этот класс, как содержащий функцию обратного
// вызова для взаимодействия с событиями
// делаем GamePanel focusable, чтобы она могла обрабатывать сообщения
setFocusable(true);
}

@Override
}

@Override
}

@Override
}

@Override

}

@Override
}
}

В приведенном выше листинге показан шаблон класса, которые нам предстоит реализовать. Давайте более внимательно посмотрим на содержание конструктора. Строка

GetHolder().addCallback(this);

Устанавливает текущий класс (MainGamePanel) как обработчик событий от поверхности.

SetFocusable(true);

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

Давайте создадим поток, внутри которого собственно и будет реализован наш игровой цикл. Разделение игры на несколько параллельно выполняющихся потоков - общепринятая в современном геймдеве практика. Создадим для нашего потока класс MainThread.java

Package ru.mobilab.gamesample;

public class MainThread extends Thread {

//флаг, указывающий на то, что игра запущена.

Private boolean running;
public void setRunning(boolean running) {
this.running = running;
}

@Override
public void run() {
while (running) {
// обновить состояние игровых объектов
// вывести графику на экран
}
}
}

Как видите, этот класс существенно проще предыдущего. Внутри мы переопределили метод run(). Поток выполняется до тех пор, пока выполняется этот метод, поэтому мы организовали внутри него бесконечный цикл. Мы добавили логическую переменную running, которая служит индикатором выхода из цикла. Теперь чтобы завершить поток, нужно просто где-то изменить значение этой переменной на false.

После того, как мы создали класс потока, его нужно запустить. Давайте запускать его при загрузке экрана. Изменим класс MainGamePanel

Package ru.mobilab.gamesample;

import android.content.Context;
import android.graphics.Canvas;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class MainGamePanel extends SurfaceView implements
SurfaceHolder.Callback {

Private MainThread thread;

Public MainGamePanel(Context context) {
super(context);
getHolder().addCallback(this);

// создаем поток для игрового цикла
thread = new MainThread();

SetFocusable(true);
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}

@Override
public void surfaceCreated(SurfaceHolder holder) {
thread.setRunning(true);
thread.start();
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
//посылаем потоку команду на закрытие и дожидаемся,
//пока поток не будет закрыт.
boolean retry = true;
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
// пытаемся снова остановить поток thread
}
}
}

@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}

@Override
protected void onDraw(Canvas canvas) {
}
}

Мы объявили объект thread

Private MainThread thread;

и создали его в конструкторе

Thread = new MainThread();

в методе surfaceCreated мы установили флаг running в значение true и запустили поток. К этому времени объект thread уже благополучно создан и можем без опасений запускать его.

Метод surfaceDestroyed вызывается перед закрытием поверхности. Здесь недостаточно просто снять флаг running. Мы должны убедиться, что поток действительно закрылся. Мы просто блокируем поток и ждем, пока он не умрет.

Добавляем взаимодействие с экраном

Чтобы показать, как в Android обработать касания, напишем небольшой пример. Будем выходить из программы, когда пользователь коснется нижней части экрана. Если касание произошло где-то выше - будем просто выводить в лог соответствующие координаты. Добавим в класс MainThread следующие строки:

Private SurfaceHolder surfaceHolder;
private MainGamePanel gamePanel;

public MainThread(SurfaceHolder surfaceHolder, MainGamePanel gamePanel) {
super();
this.surfaceHolder = surfaceHolder;
this.gamePanel = gamePanel;
}

тем самым мы определили переменные gamePanel и surfaceHolder, взяв соответствующие значения из параметров конструктора. Нам нужно запомнить эти значения, чтобы потом иметь возможность блокировать поверхность на время рисования, а это можно сделать только через surfaceHolder.

Измените строку в классе MainGamePanel, добабвив в конструктор вновь объявленные параметры

Thread = new MainThread(getHolder(), this);

Мы передаем текущий обработчик и панель в новый конструктор. Это позволит нам иметь к ней доступ из потока. В gamePanel мы создадим метод update и будем переключать его из потока, но пока оставим все как есть.

Логирование

Ниже мы напишем вспомогательный код, осуществляющий логирование - запись специальных отладочных строк с текстом, отражающих состояние нашей программы, в специальный файл, который потом можно просмотреть и попытаться понять, что происходило в программе. добавим константу TAG в класс MainThread. Каждый класс будет у нас иметь собственную константу с именем TAG, которая будет содержать название соответствующего класса. Бы будем использовать Android logging framework, чтобы вести логирование, в рамках этой библиотеки каждый лог должен иметь два параметра. Первый параметр определяет место, откуда записан лог. Именно для этих целей мы и создали константу TAG. Второй параметр - собственно сообщение, которое мы хотим записать в лог. Использование имен классов в качестве первого параметра - довольно распространенная в среде java программистов практика.

Чтобы посмотреть записанные в процессе выполнения программы логи нужно выбрать меню
Windows -> Show View -> Other…
а затем в открывшемся диалоге
Android -> LogCat
В открывшемся окне можно не только просматривать логи, но и осуществлять фильтрацию и поиск.

Вернемся к нашему коду. Внесем изменения в MainThread.java

Package ru. mobilab. gamesample; import android. util. Log ; import android. view. SurfaceHolder; public class MainThreadextends Thread{ private static final String TAG= MainThread. class . getSimpleName() ; private SurfaceHolder surfaceHolder; private MainGamePanel gamePanel; private boolean running; public void setRunning(boolean running) { this. running= running; } public MainThread(SurfaceHolder surfaceHolder, MainGamePanel gamePanel) { super() ; this. surfaceHolder= surfaceHolder; this. gamePanel= gamePanel; } @ Override public void run() { long tickCount= 0L; Log . d(TAG, "Starting game loop" ) ; while (running) { tickCount++; // здесь будет обновляться состояние игры // и формироваться кадр для вывода на экран } Log . d(TAG, "Game loop executed " + tickCount+ " times" ) ; } }

Как видите, мы определили TAG и вызвали внутри метода run команду Log, которая делает соответствующую запись в лог файле. Мы выводим в лог значение переменной tickCount, которая фактически является счетчиком игрового цикла и показывает сколько раз успел выполниться игровой цикл за время работы программы

Перейдем к файлу MainGamePanel.java и найдем метод onTouchEvent, который является обработчиком касаний экрана.

public boolean onTouchEvent(MotionEvent event) { if (event. getAction() == MotionEvent. ACTION_DOWN) { if (event. getY() > getHeight() - 50 ) { thread. setRunning(false ) ; ((Activity) getContext() ) . finish() ; } else { Log . d(TAG, "Coords: x=" + event. getX() + ",y=" + event. getY() ) ; } } return super. onTouchEvent(event) ; }

Сначала мы проверяем произошло ли событие касания экрана (MotionEvent.ACTION_DOWN). Если произошло, проверяем координату y и если она находится в нижней части экрана (50 пикселей снизу), мы посылаем потоку команду на завершение (установив переменную running в false), а затем вызываем метод finish() для главной Activity, который закрывает всю нашу программу.

Замечание. Начало системы координат у экрана находится в левом верхнем углу. Ось y направлена вниз, ось x - вправо. Ширину и высоту экрана можно получить с помощью методов getWidth() и getHeight() соответственно.

Изменим DroidzActivity.java, добавив команды записи в лог

Package ru. mobilab. gamesample; import android. app. Activity; import android. os. Bundle; import android. util. Log ; import android. view. Window; import android. view. WindowManager; public class DroidzActivityextends Activity{ /** Вызывается при создании activity. */ private static final String TAG= DroidzActivity. class . getSimpleName() ; @ Override public void onCreate(Bundle savedInstanceState) { super. onCreate(savedInstanceState) ; // запрос на отключение строки заголовка requestWindowFeature(Window. FEATURE_NO_TITLE) ; // перевод приложения в полноэкранный режим getWindow() . setFlags(WindowManager. LayoutParams. FLAG_FULLSCREEN, WindowManager. LayoutParams. FLAG_FULLSCREEN) ; // устанавливаем MainGamePanel как View setContentView(new MainGamePanel(this) ) ; Log . d(TAG, "View added" ) ; } @ Override protected void onDestroy() { Log . d(TAG, "Destroying..." ) ; super. onDestroy() ; } @ Override protected void onStop() { Log . d(TAG, "Stopping..." ) ; super. onStop() ; } }

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

Просмотрев лог вы получите четкое представление о порядке запуска методов. Вы также можете увидеть сколько раз за время работы программы успел выполниться игровой цикл. Эта цифра ни о чем не говорит, в следующий раз мы выведем в лог более полезную информацию: FPS и UPS (обновлений в секунду).

Подведем итог. Мы создали полноэкранное приложение. Написали класс, который выполняется в отдельном потоке и будет содержать игровой движок. Написали простейший метод обработки касаний экрана и грамотно закрыли приложение.

В следующий раз мы перейдем к рисованию. Исходный код этого урока можно скачать .

Перевод и адаптация:Александр Ледков

Как создать игру на андроид?











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

Движок

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

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

Среда программирования

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

Создание проекта

Чтобы написать игру на андроид, как и на любую другую платформу, нужно сначала создать проект. Сделать это несложно. В случае с LibGDX для этого достаточно распаковать движок в папку и найти в нем одноименный файл. Далее понадобится нажать кнопку "Создание проекта", и вы уже окажетесь в шаге от создания проекта. Затем потребуется лишь выбрать платформу для игры и указать название проекта. В конфигурации нужно указать, где будет размещена игра в среде программирования. Движок сам создаст проект и подскажет, если вы что-то сделали не так. Далее можно будет перейти в среду программирования и начать писать игру.

Как видите, создание игры для андроид и любой другой ОС просто невозможно без знаний основ языков программирования. Однако сейчас движки становятся все лучше и удобнее даже для тех, кто только начинает свой путь в этом деле. Примером такого движка также является Game Maker. Об этом движке и о создании игры на нем вы сможете прочитать в статье .

Для начала нужно собрать все инструменты, без которых не обойтись. В данной статье будет рассматриваться создание игры на андроид с Unity 3D.

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

Создать свою игру на андроид нужно пошагово, по основным пунктам:

Загрузить графический редактор Unity 3D для Android и установить на компьютер. Можно скачать программу для создания андроид игр с официального сайта Unity.

Вы должны получить программу обработчика видео Java JDK по ссылке: http://www.oracle.com/technetwork/java/javase/downloads/index.html
В окне загрузок выбираете «загрузить JDK». Вам не нужно загружать движок JRE, он более сложен в работе и рассчитан на профессионалов.

Далее необходим инструмент отладчика Android SDK, его можно найти по следующей ссылке: http://developer.android.com/sdk/index.html
Выбираете в таблице загрузку для вашей установленной операционной системы.
Желательно прочитать пошаговую инструкцию по установке приложения на этом сайте.

В конечном итоге, еще необходимо выполнить следующие установки:
Драйвер для андроид устройства.
Устройство связи с Андроид ADB.
Запустить USB отладку.

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

Добавляйте его через пункт «Other devices» в окне «Device manager».

Если драйвера были уже установлены с вашего андроид телефона их нужно обновить, следуя по пунктам:

Android phone>Android composite ADB interface>Properties>Driver>Update driver.

Устройство связи с Андроид ADB дает возможность перекинуть приложение с компьютера на ваш андроид девайс.

Проходим по пути C:\Program Files\android-sdk-windows\platform-tools\ помечаем галочками файлы: adb.exe, AdbWinApi.dll, AdbWinUsbApi.dll, копируем их и вставляем в папку C:\Program Files\android-sdk-windows\platform-tools\

Открываем командную строку Windows.

Проходим по адресу:


Если установленная утилита отображается, как показано, тогда все сделано правильно.

Иначе повторите пункт обновления драйвера для андроид устройства и копирования файлов ADB.

Запускаем USB отладку.

Настройка происходит на вашем андроид девайсе, нажимаем на вкладки по очереди:

Setting>Applications>Development>USB Debugging

Теперь все готово для запуска графического редактора

Пользовательское меню программы Unity 3D на английском языке.


Создаем новый проект игры на андроид

Даем ему название в окошке с адресом.
Импортируем тип расширения приложения, достаточно поставить галочку в приведенном списке.
Для андроид телефонов — Standard assets (Mobile).unityPackage

Перед созданием новой анимации, требуется настроить программу для андроид устройств.

Нужно в настройках «Build settings» выбрать в списке целевую платформу «Android», дальше отметить «Debug build», и кликнуть «Switch platform».


Нажав в этом же всплывающем окне «Player settings», в боковом меню «Inspector» отобразиться:

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

Теперь о самом инструменте для создания игр на андроид.

Немного о структуре главного окна программы:

В центре экрана представлено видовое окно — рабочая область.
В верхнем меню вкладки:
«Scene» – здесь моделируем анимацию будущей игры.
«Game» — позволяет просмотреть в движении образец игры.
Вкладки в нижней части экрана:
«Hierarchy» — отображает все ваши действия с моделью в порядке иерархии по времени.
«Project» — содержит готовые модели для создания анимации.
Боковое меню «Inspector» позволяет осуществить: контроль над перемещением создаваемого объекта; настройку вида с камеры.

Подпункт «Main camera» вкладки «Hierarchy» включает, в отельном окне, вид с камеры.

Теперь изучайте пункты меню программы для создания игры на андроид Unity 3D.
Используйте готовые материалы во вкладке «Project» и создавайте свои .

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

Честно говоря, данная статья не будет каким-то гайдом или наставлением, скорее всего из этой статьи вы поймёте что гуглить для того чтобы сделать игру что вы задумали и всё. Давайте начнём.

Чем делать игры на Android?

1. Unity

Начнём мы пожалуй с самого лучшего иуниверсального движка для игр, который есть на сегодняшний день и на котором уже вышло очень много годных игр не только на мобильных платформах (Android и IOS), но и в социальных сетях и на компьютеры.

Unity (Юнити) вам подойдёт независимо от того какой сложнисти проект вы задумали сделать. Это движок своего рода аллигатор на рынке универсальных движков. Движок постоянно обновляется и дополняется новыми впечатляющими возможностями. Юнити обладает хорошей 2D и 3D составляющей, причём с версии 4 он обладает полноценной пддержкой 2D и работает с физическим движком

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