Задался я вопросом, как вообще быстро можно разработать игру для Android. На скорую руку, абы как, но, чтобы запустилась и начала играть.
Про конструкторы подумал, но решил их не трогать, ибо нельзя по условиям. Для чего мне такие жесткие условия? Для конкурса. Хочу себя поднатаскать.
Основные требования к разрабатываемой игре:
— Запускаемость в эмуляторе Android
— Запускаемость на конкретном Android устройстве (обязательно!!!)
— Высокая скорость разработки
Какую игру буду делать: простая игрушка, три игровых состояния: Стартовое меню, Игра, Меню GameOver.
На поле есть айфон, который ловит падающие яблочки от Эппл.
В качестве инструмента был взят JavaScript, J2ds (2D движок) и Intel XDK.
Подготовить проект оказалось проще простого: распаковать архив с движком, запустить текстовые редактор и браузер. Вот и все. Заодно и отладка в виде консоли браузера.
Что ж, теперь к реализации задуманного. У меня есть всего час на разработку, с допущениями и поблажками, т.к. это первый опыт такой скоростной разработки, я приступил. Изучить J2ds проще простого, т.к. нет ничего сложного.
Далее я буду приводить куски кода, с некоторыми пояснениями.
Подготовка игровой сцены.
Так как я выбрал 2D движок в HTML5, то подготовка сцены — это создание специального формата HTML странички.
index.html
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="j2ds/engineMath.js"></script>
<script type="text/javascript" src="j2ds/engineKey.js"></script>
<script type="text/javascript" src="j2ds/engineDOM.js"></script>
<script type="text/javascript" src="j2ds/engine2D.js"></script>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width,user-scalable=no" />
<title></title>
</head>
<body id="gameBody">
<img id="buttons" src="img/buttons.png" alt="">
<canvas id="iCanvas" width="100" height="100"></canvas>
<br> <div id="hint"></div>
<script type="text/javascript">
initKeyBoard('gameBody');
scene= createScene('iCanvas', '#ceeeee');
startGame();
</script>
</body>
</html>
Т.к. в нашей игре будут не только примитивы, но и красивые картинки, загрузим одну большую картинку этой строчкой:
<img id="buttons" src="img/buttons.png" alt="">
Выглядит это так:
Основной файл, в котором я подглядывал функции: namespaces.js, там все функции разобраны по разделам и с комментариями.
В игре будет производиться работа с тачскрином, поэтому нам нужно инициализировать устройство ввода. В J2ds это делается командой «initKeyBoard()». Кроме того, нужно создать игровую сцену, чтобы движок ее обрабатывал и а так же создать из нашего изображения «buttons» спрайт-карту. Делается следующими командами:
initKeyBoard('game');
buttons= CreateImageMap('buttons'); // id объекта HTML
scene= createScene('canvas', // Первый агрумент - id объекта Сanvas
'rgb(250, 250, 200)'); // Второй агрумент - цвет фона
Как только мы инициализировали игровую сцену, развернем ее на весь экран:
Далее нам нужно создать анимации. В J2ds анимация — это один и более кадров одного и того же изображения. Кадров может быть сколько угодно. Кроме того, спайт-карт так же может быть сколько угодно. Мы загрузили только одну, пришло время вытащить из нее необходимые кадры:имые кадры:
// Кнопочки управления платформой
anim= buttons.CreateAnimation(
0, 0, // Начальная позция первого кадра
300, 300, // Размер кадра
2 // Количество кадров (по горизонтали)
);
// Анимация яблочка
apples= buttons.CreateAnimation(
8, 325, // Начальная позция первого кадра
81, 89, // Размер кадра
2 // Количество кадров (по горизонтали)
);
// Айфон
iPhone= buttons.CreateAnimation(
210, 345, // Начальная позция первого кадра
312, 56, // Размер кадра
2 // Количество кадров (по горизонтали)
);
Пришло время поговорить об игровых состояниях.
При запуске игры запускается первое игровое состояние — Стартовое меню. Давайте с ним и поработаем.
Разработка стартового меню
Выглядит стартовое меню так:
Важно понять, что Menu() - это функция и одновременно игровое состояние. Их (состояний) может быть сколько угодно, хоть 1000.
Ниже приведен код функции «Menu()» с комментариями:
// Создание зеленого прямоугольника
newGame= CreateRect(
90, 20, // Положение
150, 100, // Размеры
'green' // Цвет
);
// Создание красного прямоугольника
exitGame= CreateRect(
90, 20, // Положение
150, 100, // Размеры
'red' // Цвет
);
dY= -100; // Переменная для смещения меню (для плавного появления сверху вниз)
// А теперь опишем саму функцию меню
function Menu() {
Mouse.updPosition(scene); // Обновляем позицию курсора/тач-касания относительно сцены
dY+= dY > 0 ? 0 : 1; // смещаем меню вниз до -100px
// Если произведен клик/тач по зеленому прямоугольнику - переключаемся на игровой процесс
// а так же окращиваем зеленый прямоугольник белым цветом, символизируя реакцию игры на действия пользователя
if (Mouse.Click && Mouse.onNode(newGame)) {
newGame.color= 'white';
SetActivEngine(Game);
}
// Аналогично и для красного прямоугольника, только с выходом из игры
if (Mouse.Click && Mouse.onNode(exitGame)) {
exitGame.color= 'white';
ExitGame();
}
// Выводим текст со смещением dY
scene.DrawTextOpt(
170, 100+dY, // Позиция
'Rect Game', // Текст
'bold 30px sans-serif', // Шрифт (аналогично CSS)
'white', // Цвет текста
'green', // Цвед обводки
6 // Толщина обводки
);
// Позиционируем зеленый прямоугольник со смещением
newGame.setPosition(120, 200+dY);
newGame.Draw(scene); // рисуем его
// То же самое для красного прямоугольника
exitGame.setPosition(370, 200+dY);
exitGame.Draw(scene);
// Рисуем надпись поверх зеленого прямоугольника
scene.DrawText(90, 190+dY, // Позиция
'Новая игра'); // Текст
// Аналогично для красного
scene.DrawText(350, 190+dY, // Позиция
'Bыход'); // Текст
}
В этой функции все просто: выводим нужные объекты, и ждем, пока юзер нажмет на тот или другой прямоугольники.
Аналогично этой функции есть функция GameOver, которая отличается только выводимым текстом «Game Over» вместо названия «Rect Game»:
function GameOver() {
Mouse.updPosition(scene);
dY+= dY > 0 ? 0 : 1;
if (Mouse.Click && Mouse.onNode(newGame)) {
newGame.color= 'white';
SetActivEngine(Game);
}
if (Mouse.Click && Mouse.onNode(exitGame)) {
exitGame.color= 'white';
ExitGame();
}
scene.DrawTextOpt(
170, 100+dY, // Позиция
'Game Over', // Текст
'bold 30px sans-serif', // Шрифт (аналогично CSS)
'white', // Цвет текста
'green', // Цвед обводки
6 // Толщина обводки
);
newGame.setPosition(120, 200+dY);
newGame.Draw(scene);
exitGame.setPosition(370, 200+dY);
exitGame.Draw(scene);
scene.DrawText(90, 190+dY, // Позиция
'Новая игра'); // Текст
scene.DrawText(350, 190+dY, // Позиция
'Bыход'); // Текст
}
Уже хорошо. Осталось самое главное: игровой процесс. Тут все гораздо интереснее!
Помните, мы ранее загрузили спрайт-карту с кнопками? Пришло время ею воспользоваться! Создадим две кнопки для управления айфоном:
move= []; // массив с двумя кнопками
move[0]= CreateSprite(
0, 200, // Позиция в игре
100, 100, // Размеры в игре
anim // Анимация
);
move[1]= CreateSprite(
400, 200, // Позиция в игре
100, 100, // Размеры в игре
anim // Анимация
);
Немного поясню: anim — это объект анимации, который мы создавали вначале, прокрутите вверх, посмотрите, чтобы лучше понять. amin хранит у нас два кадра: первый — стрелка влево, второй — стрелка вправо. Идем дальше.
А дальше нам нужно создать сам айфон, который будет ловить яблочки от эппл:
me= CreateSprite(
250, 180, // Позиция в игре
100, 20, // Размеры в игре
iPhone // Анимация
);
Вроде всё. А, не все! Самих яблочек то нет! Даавй тоже их создадим:
arr= []; count= 5; // Объявим массив и количество яблок
// Затем циклом создадим их
for (var i=0; i<count; i+=1) {
arr[i]= CreateSprite(
Random(0, 450), -50, // Позиция в игре (по оси Х они рандомятся, чтобы падали по всей ширине)
20, 20, // Размеры в игре
apples // Анимация (хранит в себе два кадра анимации)
);
arr[i].speed= Random(1, 3); // Устанавливаем скорость падения яблочка рандомно
}
Объявим еще три переменных:
speed= 4; score= 0; gameOverScore= 0;
Они нужны для подсчета пойманных и упущенных яблочек, а так же скорости перемещения айфона.
Теперь пришло время написать самую главную функцию: саму игру!
function Game() {
// Обновляем позицию курсора/тач-касания
Mouse.updPosition(scene);
// Если игрок пропустил более 10 яблок, то Гейм Овер
if (gameOverScore > 10) {
dY= -100;
SetActivEngine(GameOver);
gameOverScore= 0;
}
// Если кликает ли жмет на экран влево, двигаем айфон влево, пока не упрется в стену
if (Mouse.Click && Mouse.onNode(move[0])) {
if (me.posX > 0) me.Move(-speed, 0);
}
// Аналогично и вправо
if (Mouse.Click && Mouse.onNode(move[1])) {
if (me.posX+me.sizeX < scene.Canvas.width) me.Move(speed, 0);
}
// Начинаем цикл для проверки яблок
for (var i=0; i<count; i+=1) {
// Если яблоко коснулось айфона, мы перекидываем его за пределы видимости вверх (оно снова падает)
// А игрок получает очко (игровое) и прибавляет в скорости
if (arr[i].Collision(me)) {
score+=1; speed+=0.2;
arrNewPos(i); // фнукция для перекидывания яблочка наверх
}
// Если пользователь пропустил яблочко, он приближается к своему концу
// А яблочко снова перемещается и падает
if (arr[i].posY > me.posY) {
arrNewPos(i); gameOverScore+=1;
}
// двигаем яблочко вниз с его скоростью
arr[i].Move(0, arr[i].speed);
// рисуем яблочко, анимируя его (в этом случае скорость анимации: 10)
arr[i].DrawAnimate(scene, 10);
}
// После всех яблочек рисуем айфон
me.Draw(scene);
// Выводим счет игрока вверху экрана
scene.DrawTextOpt(
5, 5, // Позиция
'Игровой счет: '+score, // Текст
'bold 25px sans-serif', // Шрифт (аналогично CSS)
'white', // Цвет текста
'green', // Цвед обводки
3 // Толщина обводки
);
// рисуем наши кнопки управления, где второй параметр функции Draw() - это номер кадра.
// Если не забыли, 1 кадр - стрелка влево, 2 вправо
move[0].Draw(scene, 1); move[1].Draw(scene, 2);
// дебаг, выводит количество упущенных яблочек
dbg(gameOverScore);
}
// функция, перемещающая яблочко за пределы видимости камеры вверх
// рандомно устанавливает позицию и рандомно присваивает скорость
function arrNewPos(_i) {
arr[_i].setPosition(Random(0, scene.Canvas.width-arr[_i].sizeY),
-Random(50, 300));
arr[_i].speed= Random(2, 5);
}
Так же, как и Menu() и GamwOver() - Game() - это функция и одновременно игровое состояние.
Вот и весь игровой цикл, выглядит это вот так:
И остался последний штрих, игру нужно запустить:
startGame(Menu, 30); // запускает игровое состояние Menu() и устанавливает сцене ограничение в 30 fps
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="j2ds/engineMath.js"></script>
<script type="text/javascript" src="j2ds/engineKey.js"></script>
<script type="text/javascript" src="j2ds/engineDOM.js"></script>
<script type="text/javascript" src="j2ds/engine2D.js"></script>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width,user-scalable=no" />
<title>Rect Game</title>
</head>
<body id="game">
<img id="buttons" src="img/buttons.png" alt="">
<canvas id="canvas" width="500" height="300"></canvas>
<br> <div id="hint"></div>
<script type="text/javascript">
initKeyBoard('game');
buttons= CreateImageMap('buttons'); // id объекта HTML
scene= createScene('canvas', // Первый агрумент - id объекта Сanvas
'rgb(250, 250, 200)'); // Второй агрумент - цвет фона
scene.fullScreen(true);
anim= buttons.CreateAnimation(
0, 0, // Начальная позция первого кадра
300, 300, // Размер кадра
2 // Количество кадров (по горизонтали)
);
apples= buttons.CreateAnimation(
8, 325, // Начальная позция первого кадра
81, 89, // Размер кадра
2 // Количество кадров (по горизонтали)
);
iPhone= buttons.CreateAnimation(
210, 345, // Начальная позция первого кадра
312, 56, // Размер кадра
2 // Количество кадров (по горизонтали)
);
newGame= CreateRect(
90, 20, // Положение
150, 100, // Размеры
'green' // Цвет
);
exitGame= CreateRect(
90, 20, // Положение
150, 100, // Размеры
'red' // Цвет
);
dY= -100;
function Menu() {
Mouse.updPosition(scene);
dY+= dY > 0 ? 0 : 1;
if (Mouse.Click && Mouse.onNode(newGame)) {
newGame.color= 'white';
SetActivEngine(Game);
}
if (Mouse.Click && Mouse.onNode(exitGame)) {
exitGame.color= 'white';
ExitGame();
}
scene.DrawTextOpt(
170, 100+dY, // Позиция
'Rect Game', // Текст
'bold 30px sans-serif', // Шрифт (аналогично CSS)
'white', // Цвет текста
'green', // Цвед обводки
6 // Толщина обводки
);
newGame.setPosition(120, 200+dY);
newGame.Draw(scene);
exitGame.setPosition(370, 200+dY);
exitGame.Draw(scene);
scene.DrawText(90, 190+dY, // Позиция
'Новая игра'); // Текст
scene.DrawText(350, 190+dY, // Позиция
'Bыход'); // Текст
}
function GameOver() {
Mouse.updPosition(scene);
dY+= dY > 0 ? 0 : 1;
if (Mouse.Click && Mouse.onNode(newGame)) {
newGame.color= 'white';
SetActivEngine(Game);
}
if (Mouse.Click && Mouse.onNode(exitGame)) {
exitGame.color= 'white';
ExitGame();
}
scene.DrawTextOpt(
170, 100+dY, // Позиция
'Game Over', // Текст
'bold 30px sans-serif', // Шрифт (аналогично CSS)
'white', // Цвет текста
'green', // Цвед обводки
6 // Толщина обводки
);
newGame.setPosition(120, 200+dY);
newGame.Draw(scene);
exitGame.setPosition(370, 200+dY);
exitGame.Draw(scene);
scene.DrawText(90, 190+dY, // Позиция
'Новая игра'); // Текст
scene.DrawText(350, 190+dY, // Позиция
'Bыход'); // Текст
}
move= [];
move[0]= CreateSprite(
0, 200, // Позиция в игре
100, 100, // Размеры в игре
anim // Анимация
);
move[1]= CreateSprite(
400, 200, // Позиция в игре
100, 100, // Размеры в игре
anim // Анимация
);
me= CreateSprite(
250, 180, // Позиция в игре
100, 20, // Размеры в игре
iPhone // Анимация
);
arr= []; count= 5;
for (var i=0; i<count; i+=1) {
arr[i]= CreateSprite(
Random(0, 450), -50, // Позиция в игре
20, 20, // Размеры в игре
apples // Анимация
);
arr[i].speed= Random(1, 3);
}
speed= 4; score= 0; gameOverScore= 0;
function Game() {
Mouse.updPosition(scene);
if (gameOverScore > 10) {
dY= -100;
SetActivEngine(GameOver);
gameOverScore= 0;
}
if (Mouse.Click && Mouse.onNode(move[0])) {
if (me.posX > 0) me.Move(-speed, 0);
}
if (Mouse.Click && Mouse.onNode(move[1])) {
if (me.posX+me.sizeX < scene.Canvas.width) me.Move(speed, 0);
}
for (var i=0; i<count; i+=1) {
if (arr[i].Collision(me)) {
score+=1; speed+=0.2;
arrNewPos(i);
}
if (arr[i].posY > me.posY) {
arrNewPos(i); gameOverScore+=1;
}
arr[i].Move(0, arr[i].speed);
arr[i].DrawAnimate(scene, 10);
}
me.Draw(scene);
scene.DrawTextOpt(
5, 5, // Позиция
'Игровой счет: '+score, // Текст
'bold 25px sans-serif', // Шрифт (аналогично CSS)
'white', // Цвет текста
'green', // Цвед обводки
3 // Толщина обводки
);
move[0].Draw(scene, 1); move[1].Draw(scene, 2);
dbg(gameOverScore);
}
function arrNewPos(_i) {
arr[_i].setPosition(Random(0, scene.Canvas.width-arr[_i].sizeY),
-Random(50, 300));
arr[_i].speed= Random(2, 5);
}
startGame(Menu, 30);
</script>
</body>
</html>
Разработка игры окончена. Осталось запустить ее на Android. Для этого я скачал и установил Intel XDK, прошел регистрацию, верифицировал данные, и создал новый проект. Передо мной открылось окно нового проекта.
Выбрал я пустой (blank) HTML5 документ, и сохранил проект.
После чего открыл его в файловом менеджере, и заменил все файлы в папке проекта WWW на свои, а там у меня всего одна папка с картиной, папка с движком и файл index.html, Intel XDK тут же подхватила этот файл и предложила мне открыть его для редактирования. Я его открыл и перешел в режим эмуляции:
Все работает как надо, ничего не глючит.
Далее открыл вкладку с проектом и ввел необходимые данные, такие как версия программы, название, аписание, иконка и т.д. Заполнив все необходимые данные — я прошел с вежливого приглашения XDK во вкладку Build, и выбрал Build for Android, после чего мой проект выгрузился на сервер, откомпилировался, а система предоставела мне удобную ссылку для загруки уже готового apk файла.
С замиранием сердца я скинул его на гугл.Диск и открыл с телефона, и Чудо! Пошла установка приложения. Через пару секунд я уже во всю ловил яблочки айфоном в новоиспеченной игре.
Кстати, таким же образом я собрал еще одну игрушку (на том же J2ds)
Любой желающий так же может скачать APK файл и протестировать ловлю яблочек айфоном:
Скачать APK файл
А так же попрыгать в небольшой 2D игрушке по платформам и понаблюдать красивый задний фон:
Скачать APK или
Запустить и поиграть в браузере
Если запускаете в браузере, то работает опять же — и на компах и на планшетах/смарфонах.