Site navigation

Social networks

Contact us

Simple recipe for incredible creativity and efficiency with Titanium. Part 2

app development

In the first part of this article "Simple recipe for incredible creativity and efficiency with Titanium. Part 1" I wrote about some peculiar aspects of working with the engine. So if you are going to read this part, it will be really helpful to begin by first checking out part one.

This second part describes the process of creating a simple game in Appcelerator Titanium, with a detailed step-by-step explanation.

The aim of the game is to build the word "TITANIUM" using letters that appear. Once the word is built, a user is shown a winning notification and the game is over.

To start with let's create pathsToFiles.js and use it to indicate the paths to the assets we have:

//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";
Now let's work with the app.js file. Create a game window, include the .js files that you are going to use, and describe window orientation modes for 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');
Our next step is to define the scale necessary for the device, initialize the scenes that will be described later, add a game screen to the game window, open it and run 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
});
Now let's switch to MenuScene and create it:
var buttonMargin = 20;
var clickSound = null;
var MenuScene = {
    scene : null,
    background : null,
    buttonNewGame : null,
    buttonHelp : null,
    init : function() {
        this.scene = quicktigame2d.createScene();
...
    },
};
Here, 'buttonMargin' is a margin that will be used further on and 'clickSound' is a variable for storing the sound.

We can now add some events. Start the current scene while the sprites are loaded:

 var{
            return function(e) {
                if (e.name == pngButtonHelp) {
                    game.startCurrentScene();
                }
            };
        })(this);
Now we should describe the events correlated with [New Game] and [Help] buttons. When a button is pressed and released its image will change. A user will also hear a short sound when pressing a button and as soon as the button is released, the game scene will be changed for a chosen one:
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);
Now we should initialize variables and fix the coordinates of the locations of the buttons and menu background. Add the relevant elements to the scene and to the event listeners:
        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);
Using the following method we can describe the function for the removal of all the created elements (texture unloading, removal of event listeners):
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);
There is just one thing remaining to deal with; we need to add event listeners for activation/deactivation of the scenes and loading of the sprites:
      this.scene.addEventListener('activated', activated);
      this.scene.addEventListener('deactivated', deactivated);
      this.scene.addEventListener('onloadsprite', onloadsprite);
The Menu scene is now complete.

Let's move on to the Help scene. Generally, all the processes of loading and unloading of elements are very similar, so let's not waste any time and look at the most peculiar elements; one of these is creation of the background for Help:

We will load 3 background layers:

  1. Is the menu layer; it will be a bottom layer.
  2. Overlays the first one with color and transparency.
  3. The top layer contains the text. We define this layer as a SpriteSheet in order to use several images for it.
 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
                    });
                }
We can describe the location of the button in the corner of the screen relative to the screen size as follows:
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;                   
                }
Then we can add animation to the text layer:
self.foreground.animate(0, 2, 2000, -1);
We will now start to create a scene for the gameplay. We need several variables created:
var COUNT_OF_CHARACTERS = 9;
var word = "";
var wordTitanium = "titanium";
Where 'COUNT_OF_CHARACTERS' is a number of cells of the field for the letters to appear in; 'word' - is the word built, 'wordTitanium' - is the word to be built.

Next is the addition of a process that results in the creation of an array of elements with letter images to 'activated' function:

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;
                    }
                }
Here we set the location for each element, hide it, set the status and the current image. A separate event listener should be added for each element. The event listener will look like this:
  var{
            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;
        }
Depending on the element status, different actions will take place such as rotation, scaling, hiding and on-screen appearance.

Then add the method to finalize the game by removing the text of the built word and showing a winning notification on the scene; we will call this the 'victoryText' event listener:

function finishActivity(self) {
            word = "";
            self.scene.add(self.victoryText);
            self.victoryTextTransform.duration = 1000;
            self.victoryText.transform(self.victoryTextTransform);
        }
var{
            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);
It is necessary to add to 'enterframe' the process of changing letters in the corresponding elements. This will take place while the status is "Waiting." The elements will appear on the screen with a randomly chosen letter.
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;
                                    }
                                } 
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;
        }
To complete the task we must describe the function of clicking on an element:
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);
At the same time we changed its status, added animation and checked the presence of the letter in the sequence of letters in a corresponding word.

Here is the result:

new game

collect

titanium

app development

victory

Andrey Kudievskiy

ABOUT

WEEZLABS

Not just another web and mobile apps development company!

WeezLabs is about dreaming big and helping our clients reach their full potential.

LEARN MORE

VISIT OUR OFFICE:

1848 Lincoln Blvd, Suite #100,
Santa Monica, CA 90404

TELL US HOW

WE CAN HELP

Call us 310.776.6234 or complete the form below.