В первой части данной статьи "Игра на QuickTiGame2d в Appcelerator Titanium. Часть 1" я рассказывала о некоторых моментах работы с движком, которые, на мой взгляд, будут полезны тем, кто собирается ознакомиться с этой публикацией. Сейчас я предлагаю рассмотреть создание простейшей игры на Appcelerator Titanium.
Суть игры заключается в том, что из появляющихся букв нужно собрать слово «TITANUIM», после чего будет выведено соответствующее сообщение о победе и игра будет закончена.
Для начала создадим файл pathsToFiles.js, в котором присвоим пути к имеющимся ресурсам:
//menuScene
var pngMenuBackground = "pictures/menu/background.png";
var pngButtonNewGame = "pictures/menu/newGame.png";
var pngButtonHelp = "pictures/menu/help.png";
//helpScene
var pngHelp = "pictures/help/help.png";
var pngBackFromHelp = "pictures/help/back.png";
//mainLoadingScene
var pngMainBackground = "pictures/game/background.png";
var pngBackToMenu = "pictures/game/back.png";
var pngVictory = "pictures/game/victory.png";
var xmlCharactersPosition = "pictures/game/charactersPosition.xml";
//sound
var soundClick = "sound/click.wav";
Перво-наперво обратимся к файлу app.js. Создадим экран игры, подключим файлы js, с которыми будем работать, а также опишем расположение экрана для Android и iOS:
var window = Ti.UI.createWindow({
backgroundColor : 'black'
});
var quicktigame2d = require('com.googlecode.quicktigame2d');
var game = quicktigame2d.createGameView();
if (Titanium.Platform.osname === 'android') {
game.orientation = Ti.UI.LANDSCAPE_LEFT;
window.orientationModes = [Titanium.UI.LANDSCAPE_LEFT];
} else {
game.orientation = Ti.UI.LANDSCAPE_RIGHT;
window.orientationModes = [Titanium.UI.LANDSCAPE_RIGHT];
}
Ti.include('pathsToFiles.js');
Ti.include('helpScene.js');
Ti.include('menuScene.js');
Ti.include('mainScene.js');
Далее рассчитаем необходимый масштаб игры для устройства, проинициализируем подгружаемые сцены, которые будут описаны ниже, добавим игровой экран в окно приложения, откроем его и запустим MenuScene.
var WINDOW_SCALE_FACTOR_X = 1;
var WINDOW_SCALE_FACTOR_Y = 1;
game.addEventListener('onload', function(e) {
var screenScale = game.size.height / 640;
game.screen = {
width : game.size.width / screenScale,
height : game.size.height / screenScale
};
WINDOW_SCALE_FACTOR_X = game.screen.width / game.size.width;
WINDOW_SCALE_FACTOR_Y = game.screen.height / game.size.height;
MenuScene.init();
HelpScene.init();
MainScene.init();
game.pushScene(MenuScene.scene);
game.start();
});
window.add(game);
window.open({
fullscreen : true,
navBarHidden : true
});
Теперь займемся созданием MenuScene для игры. Для начала непосредственно создадим её:
var buttonMargin = 20;
var clickSound = null;
var MenuScene = {
scene : null,
background : null,
buttonNewGame : null,
buttonHelp : null,
init : function() {
this.scene = quicktigame2d.createScene();
...
},
};
Здесь buttonMargin — это отступ, который будет использоваться далее, а clickSound — переменная для хранения звука.
Теперь стоит добавить некоторые действия. Запускаем текущую сцену при загрузке спрайтов:
var onloadsprite = (function(self) {
return function(e) {
if (e.name == pngButtonHelp) {
game.startCurrentScene();
}
};
})(this);
Опишем события нажатия на кпопки «New game» и «Help». При нажатии и при отпускании кнопки будет изменяться её картинкаю Также, при нажатии будет издаваться короткий звук, а при «отжатии» — сцена игры будет изменена на выбранную:
var touchstart = (function(self) {
return function(e) {
var x = e.x * WINDOW_SCALE_FACTOR_X;
var y = e.y * WINDOW_SCALE_FACTOR_Y;
if (self.buttonNewGame.contains(x, y)) {
self.buttonNewGame.frame = 0;
clickSound.play();
}
if (self.buttonHelp.contains(x, y)) {
self.buttonHelp.frame = 0;
clickSound.play();
}
}
})(this);
var touchend = (function(self) {
return function(e) {
var x = e.x * WINDOW_SCALE_FACTOR_X;
var y = e.y * WINDOW_SCALE_FACTOR_Y;
self.buttonNewGame.frame = 1;
self.buttonHelp.frame = 1;
if (self.buttonNewGame.contains(x, y)) {
game.pushScene(MainScene.scene);
}
if (self.buttonHelp.contains(x, y)) {
game.pushScene(HelpScene.scene);
}
};
})(this);
Теперь проинициализируем переменные и зададим координаты расположения кнопок и фона меню игры. Добавим на сцену соответствующие элементы, а так же слушателей событий:
var activated = (function(self) {
return function(e) {
if (clickSound == null) {
clickSound = Ti.Media.createSound({
url : soundClick
});
}
if (self.background == null) {
self.background = quicktigame2d.createSprite({
image : pngMenuBackground
});
}
if (self.buttonNewGame == null) {
self.buttonNewGame = quicktigame2d.createSpriteSheet({
image : pngButtonNewGame,
width : 275,
height : 100
});
}
if (self.buttonHelp == null) {
self.buttonHelp = quicktigame2d.createSpriteSheet({
image : pngButtonHelp,
width : 275,
height : 100
});
}
self.background.x = (game.screen.width * 0.5) - (self.background.width * 0.5);
self.background.y = (game.screen.height * 0.5) - (self.background.height * 0.5);
self.buttonNewGame.x = (game.screen.width - self.buttonNewGame.width) / 4;
self.buttonNewGame.y = (game.screen.height * 0.8);
self.buttonHelp.x = (game.screen.width - self.buttonNewGame.width) / 4*3;
self.buttonHelp.y = self.buttonNewGame.y;
self.buttonNewGame.frame = 1;
self.buttonHelp.frame = 1;
self.scene.add(self.background);
self.scene.add(self.buttonNewGame);
self.scene.add(self.buttonHelp);
game.addEventListener('touchstart', touchstart);
game.addEventListener('touchend', touchend);
self.scene.addEventListener('enterframe', enterframe);
};
})(this);
Опишем функцию удаления всех созданных элементов (выгрузка текстур, удаление слушателей событий):
var deactivated = (function dea(self) {
return function(e) {
self.scene.remove(self.background);
self.background = null;
self.scene.remove(self.buttonNewGame);
self.scene.remove(self.buttonHelp);
self.buttonNewGame = null;
self.buttonHelp = null;
game.unloadTexture(pngButtonNewGame);
game.unloadTexture(pngButtonHelp);
game.removeEventListener('touchstart', touchstart);
game.removeEventListener('touchend', touchend);
};
})(this);
Осталось только добавить слушателей событий активации/деактивации сцен и загрузки спрайтов:
this.scene.addEventListener('activated', activated);
this.scene.addEventListener('deactivated', deactivated);
this.scene.addEventListener('onloadsprite', onloadsprite);
Сцена меню готова. Перейдем к сцене Help. В принципе, все процессы загрузки и выгрузки элементов очень похожи, поэтому не будем повторяться и рассмотрим наиболее интересующие нас элементы. Одним из них является создание фона help:
if (self.background == null) {
self.background = quicktigame2d.createSprite({
image : pngMenuBackground
});
}
if (self.menuLayer == null) {
self.menuLayer = quicktigame2d.createSprite({
width : self.background.width,
height : self.background.height
});
self.menuLayer.color(0.5, 0.5, 0.5);
self.menuLayer.alpha = 0.78;
}
if (self.foreground == null) {
self.foreground = quicktigame2d.createSpriteSheet({
image : pngHelp,
width : 960,
height : 640
});
}
Здесь мы подгружаем 3 слоя фона. Один из них — это слой в меню, он будет нижним. Второй фон мы устанавливаем поверх (в нем присутствует цвет и прозрачность). Третий — это слой с текстом. Определяем его как SpriteSheet, чтобы использовать для него несколько изображений.
Опишем расположение кнопки в углу экрана относительно его размеров:
if (game.screen.width > self.background.width) {
self.okButton.x = self.background.x + self.background.width - self.okButton.width - buttonMargin;
self.okButton.y = self.background.y + self.background.height - self.okButton.height - buttonMargin;
} else {
self.okButton.x = game.screen.width - self.okButton.width - buttonMargin;
self.okButton.y = game.screen.height - self.okButton.height - buttonMargin;
}
И добавим анимацию текстового фона:
self.foreground.animate(0, 2, 2000, -1);
Теперь перейдем к созданию сцены для процесса игры. Стоит создать несколько переменых:
var COUNT_OF_CHARACTERS = 9;
var word = "";
var wordTitanium = "titanium";
Где COUNT_OF_CHARACTERS — это количество мест на поле для появления букв, word — собранное слово, wordTitanium — слово которое нужно собрать.
В функцию activated добавим процесс создания массива элементов с изображениями букв:
var xCoef = self.background.width / 4;
var yCoef = self.background.height / 4;
var xParam = 1;
var yParam = 1;
for (var i = 0; i < COUNT_OF_CHARACTERS; i++) {
self.characters[i] = quicktigame2d.createSpriteSheet({
image : xmlCharactersPosition
});
self.characters[i].x = self.background.x + xCoef * xParam;
self.characters[i].y = self.background.x + yCoef * yParam;
xParam++;
if (xParam > 3) {
xParam = 1;
yParam++;
}
self.characters[i].z = 3;
self.characters[i].hide();
self.characters[i].index = i;
self.characters[i].status = "waiting";
self.characters[i].selectFrame("character0");
self.scene.add(self.characters[i]);
if (self.charactersTransforms[i] == null) {
self.charactersTransforms[i] = quicktigame2d.createTransform();
self.charactersTransforms[i].addEventListener('complete', oncharactersCompleted);
self.charactersTransforms[i].index = i;
}
}
Здесь мы устанавливаем местоположение для каждого элемента, скрываем его, устанавливаем статус, текущее изображение. Здесь же устанавливается свой слушатель событий для каждого элемента. Слушатель для событий будет выглядить таким образом:
var oncharactersCompleted = (function(self) {
return function(e) {
var transform = e.source;
var choosenCharacter = self.characters[transform.index];
if (choosenCharacter != null) {
if (choosenCharacter.status == "moving") {
choosenCharacter.show();
choosenCharacter = changeStatus(choosenCharacter, transform, "living");
} else if (choosenCharacter.status == "living") {
choosenCharacter = changeStatus(choosenCharacter, transform, "dying");
} else if (choosenCharacter.status == "dying") {
choosenCharacter = changeStatus(choosenCharacter, transform, "hiding");
} else if (choosenCharacter.status == "hiding") {
transform.scale(0, 0);
choosenCharacter = changeStatus(choosenCharacter, transform, "queue_waiting");
} else if (choosenCharacter.status == "queue_waiting") {
choosenCharacter.hide();
choosenCharacter.status = "waiting";
transform.duration = 1000;
choosenCharacter.transform(transform);
} else if (choosenCharacter.status == "killed") {
transform.rotate(-360);
transform.scale(0, 0);
choosenCharacter = changeStatus(choosenCharacter, transform, "queue_waiting");
}
}
};
})(this);
function changeStatus(choosenCharacter, transform, statuscharacter) {
transform.duration = 2000;
choosenCharacter.status = statuscharacter;
choosenCharacter.transform(transform);
return choosenCharacter;
}
Здесь в зависимости от статуса элемента с ним будут происходить разного рода действия: вращение, изменение масштаба, скрытие и появление на экране.
Добавим метод для завершения игры, в котором мы убираем текст в уже набранном слове, а также добавляем на сцену информацию о том, что игрок выиграл, что в свою очередь вызовет слушателя событий victoryText:
function finishActivity(self) {
word = "";
self.scene.add(self.victoryText);
self.victoryTextTransform.duration = 1000;
self.victoryText.transform(self.victoryTextTransform);
}
var onVictoryTextCompleted = (function(self) {
return function(e) {
if (self.victoryTextTransform.completed) {
self.scene.remove(self.victoryText);
closeGame(self);
} else {
self.victoryTextTransform.y = game.screen.height;
self.victoryTextTransform.completed = true;
self.victoryText.transform(self.victoryTextTransform);
}
};
})(this);
В enterframe следует добавить процесс смены букв в соответствующих элементах. Это будет происходить при статусе waiting: элементы будут появляться на экране со случайно созданной для них буквой.
self.charactersTransforms[i].show();
self.charactersTransforms[i].scale(1, 1);
self.charactersTransforms[i].duration = 500;
self.charactersTransforms[i].delay = 0;
self.charactersTransforms[i].easing = quicktigame2d.ANIMATION_CURVE_CUBIC_IN;
var face = Math.floor((randomNumber * 100) % 8);
for (var count = 0; count < wordTitanium.length; count++) {
if (face == count) {
self.characters[i] = addDataToWord(self.characters[i], self.charactersTransforms[i], "character"+count, wordTitanium[count]);
self.characters[i].transform(self.charactersTransforms[i]);
break;
}
}
где функция addDataToWord:
function addDataToWord(choosenCharacter, choosenCharacterTransform, frame, symbol) {
choosenCharacter.isMoving = false;
choosenCharacter.faceName = frame;
choosenCharacter.symbol = symbol;
choosenCharacter.status = "moving";
choosenCharacter.selectFrame(frame);
choosenCharacterTransform.duration = 0;
choosenCharacter.transform(choosenCharacterTransform);
return choosenCharacter;
}
Последнее, что осталось сделать, — это прописать функцию клика по элементу:
var dblclick = (function(self) {
return function(e) {
var x = e.x * WINDOW_SCALE_FACTOR_X;
var y = e.y * WINDOW_SCALE_FACTOR_Y;
if (!self.loaded)
return;
if (!self.started)
return;
for (var i = 0; i < COUNT_OF_CHARACTERS; i++) {
if (self.characters[i].status != "killed") {
if (self.characters[i].contains(x, y)) {
clickSound.play();
self.charactersTransforms[i].rotate(360);
self.charactersTransforms[i].duration = 2000;
self.characters[i].transform(self.charactersTransforms[i]);
self.characters[i].status = "killed";
word += self.characters[i].symbol;
Ti.API.info(word);
if (wordTitanium.indexOf(word) == 0) {
Ti.API.info(word + " " + wordTitanium);
if(wordTitanium.length == word.length){
finishActivity(self);
}
} else {
word = "";
}
}
}
}
};
})(this);
При этом мы изменяем его статус, добавляем анимацию и делаем проверку вхождения этой буквы в порядок букв в соответствующем слове.
Получившийся результат:
Автор: mkolenchukova