Sunday, March 1, 2015

Annihilate, puzzle game made with Phaser engine, is finshed!


 Finally, we just finshed our HTML5 game called Annihilate. Game was made using great Phaser engine. Annihilate is puzzle game in which you are matching pairs o atoms. In 48 levels you have to avoid various pitfalls like lasers, black holes, ...

 Now, we are looking for sponsors. If you are interested, conntact me at my gmail address: tomas[dot]rychnovsky[at]gmail[dot]com

 Game features:
  • 48 puzzle levels,
  • nice simple and clear graphics and music,
  • scales well for different screen resolutions (no black belts on iPhone nor iPad),
  • ready for easy translation



 And you can try the game right here!:



 Screenshots:







Tuesday, February 10, 2015

Phaser tutorial: How to wrap bitmap text

 In past month I started exploring world of HTML5 and Javascript games. Finally I ended with Phaser game engine by Photonstorm and Typescript (in Visual Studio 2013).

 During making our first games I was really missing ability to wrap bitmap text. So, I created helper class TextWraper, that is doing text wrap for me.

 Why you need such a class? You can choose between bitmap and true type fonts. While true type fonts are more flexible, bitmap fonts are more visually appaeling. Unfortunately, Phaser's bitmapText cannot wrap text. If you tune your text screen with "hard coded" new lines and then you change few words or translate it into different language, you have to tune it again...
 
 The final TextWrapper helper not only can wrap the text, but it also splits it into multiple pages returning string[], where every single element is one page of text. You just tell to it what is requested width and height of your text area.

 In example below you can see the TextWrapper class in action (press buttons to change screens). All original new line characters are preserved and other that are needed to wrap long paragraphs are added. Font for the demo was created in Littera.



 Using the class is as simple as this:

        var text: string = 'WRAPPED BITMAP TEXT\n' +
            '-------------------------------------------\n\n' +
            ' This example demonstrates TextWrapper helper class for Phaser game engine, that allows you to easily wrap bitmap text.\n' +
            ' Not only it can wrap long paragraphs into lines but it also splits whole text into multiple pages - ' +
            'touch buttons bellow to go to next or previous page.\n' +
            ' The TextWrapper preserves all original new line characters and ads others, that are necessary to wrap text correctly. ' +
            'After calling wrapText() method string array is returned where each array element is single text page with all necessary new line characters.\n\n' +
            ' By the way, if you like background image it is from our game Shards - the brickbreaker. ' +
            'It is available for Android as well as for iOS. Give it a try! Not a bad way how to create long text and make publicity to our game at once :-)';

        var pages: string[] = Helper.TextWrapper.wrapText(text, 520, 260, 'Font', 25);
        var currentPage: number = 0;
        var bitmapText = this.add.bitmapText(60, 30, 'Font', pages[currentPage], 25);

 As can be seen from the code, the class takes these parameters:
  • text - text to wrap,
  • width - width of target text area,
  • height - height of target text area,
  • fontName - name of the font,
  • size (optional) - to calculate font scale. If omitted then scale = 1;

 The TextWrapper class itself is rather long as it has to handle lot of wrapping issues. For example, it is splitting by words, but in case some word is too wide and would not fit into requested width then this word has to be split in the middle.

 Whole listing for the TextWrapper class is here:

module Helper {
    enum eCharType {
        UNDEFINED = -1,
        SPACE = 1,
        NEWLINE = 2,
        CHARACTER = 3,
        //SPECIAL = 4 // for future
    }

    export class TextWrapper {
        static mText: string;
        static mTextPosition: number;
        static mFontData: any;

        // -------------------------------------------------------------------------
        private static hasNext(): boolean {
            return TextWrapper.mTextPosition < TextWrapper.mText.length;
        }

        // -------------------------------------------------------------------------
        private static getChar(): string {
            return TextWrapper.mText.charAt(TextWrapper.mTextPosition++);
        }

        // -------------------------------------------------------------------------
        private static peekChar(): string {
            return TextWrapper.mText.charAt(TextWrapper.mTextPosition);
        }

        // -------------------------------------------------------------------------
        private static getPosition(): number {
            return TextWrapper.mTextPosition;
        }

        // -------------------------------------------------------------------------
        private static setPosition(aPosition: number): void {
            TextWrapper.mTextPosition = aPosition;
        }

        // -------------------------------------------------------------------------
        private static getCharAdvance(aCharCode: number, aPrevCharCode: number): number {
            var charData = TextWrapper.mFontData.chars[aCharCode];
            
            // width
            var advance: number = charData.xAdvance;
            
            // kerning
            if (aPrevCharCode > 0 && charData.kerning[aPrevCharCode])
                advance += charData.kerning[aPrevCharCode];

            return advance;
        }

        // -------------------------------------------------------------------------
        private static getCharType(aChar: string): eCharType {
            if (aChar === ' ')
                return eCharType.SPACE;
            else if (/(?:\r\n|\r|\n)/.test(aChar))
                return eCharType.NEWLINE;
            else
                return eCharType.CHARACTER;
        }

        // -------------------------------------------------------------------------
        static wrapText(aText: string, aWidth: number, aHeight:number, aFontName: string, aSize? : number): string[] {
            // set vars for text processing
            TextWrapper.mText = aText;
            TextWrapper.setPosition(0);
            // font data
            TextWrapper.mFontData = PIXI.BitmapText.fonts[aFontName];

            // if size not defined then take default size
            if (aSize === undefined)
                aSize = TextWrapper.mFontData.size;

            var scale: number = aSize / TextWrapper.mFontData.size;
            // height of line scaled
            var lineHeight: number = TextWrapper.mFontData.lineHeight * scale;
            // instead of scaling every single character we will scale line in opposite direction
            var lineWidth: number = aWidth / scale;

            // result
            var mLineStart: number[] = [];
            var mLineChars: number[] = [];
            var mPageStart: number[] = [];
            var mMaxLine: number = 0;
            var firstLineOnPage: boolean = true;
            var pageCounter: number = 0;

            // char position in text
            var currentPosition: number = 0;
            // first line position
            mLineStart[mMaxLine] = currentPosition;
            // first page
            mPageStart[pageCounter++] = 0;
            // remaining height of current page
            var remainingHeight: number = aHeight;

            
            // whole text
            while (TextWrapper.hasNext()) {
                var charCount: number = 0;
                // saves number of chars before last space
                var saveSpaceCharCount:number = 0;
                var saveCharPosition:number = -1;
                // (previous) type of character
                var type: eCharType = eCharType.UNDEFINED;
                var previousType: eCharType = eCharType.UNDEFINED;
                // remaining width will decrease with words read
                var remainingWidth: number = lineWidth;
                // previous char code
                var prevCharCode: number = -1;

                // single line
                while (TextWrapper.hasNext()) {
                    currentPosition = TextWrapper.getPosition();
                    // read char and move in text by 1 character forward
                    var char: string = TextWrapper.getChar();
                    // get type and code
                    type = TextWrapper.getCharType(char);
                    var charCode: number = char.charCodeAt(0);

                    // process based on type
                    if (type === eCharType.SPACE) {
                        if (previousType != eCharType.SPACE)
                            saveSpaceCharCount = charCount;

                        ++charCount;
                        remainingWidth -= TextWrapper.getCharAdvance(charCode, prevCharCode);
                    }
                    else if (type === eCharType.CHARACTER) {
                        if (previousType !== eCharType.CHARACTER)
                            saveCharPosition = currentPosition;

                        remainingWidth -= TextWrapper.getCharAdvance(charCode, prevCharCode);

                        if (remainingWidth < 0)
                            break;

                        ++charCount;
                    }
                    else if (type === eCharType.NEWLINE) {
                        var breakLoop: boolean = false;

                         // if there is no more text then ignore new line
                        if (TextWrapper.hasNext()) {
                            breakLoop = true;
                            saveSpaceCharCount = charCount;
                            saveCharPosition = TextWrapper.getPosition();
                            currentPosition = saveCharPosition;
                            // simulate normal width overflow
                            remainingWidth = -1;
                            type = eCharType.CHARACTER;
                        }

                        if (breakLoop)
                            break;
                    }

                    previousType = type;
                    prevCharCode = charCode;
                }


                // lines / pages
                remainingHeight -= lineHeight;
                // set new page if not enough remaining height
                if (remainingHeight < 0)
                    mPageStart[pageCounter++] = mMaxLine;

                if (remainingWidth < 0 && type === eCharType.CHARACTER) {
                    if (saveSpaceCharCount != 0)
                        mLineChars[mMaxLine] = saveSpaceCharCount;
                    else // for too long words that do not fit into one line (and Chinese texts)
                        mLineChars[mMaxLine] = charCount;

                    // does new line still fits into current page?
                    firstLineOnPage = false;

                    // set new page
                    if (remainingHeight < 0) {
                        firstLineOnPage = true;
                        remainingHeight = aHeight - lineHeight;
                    }

                    if (saveSpaceCharCount != 0) {
                        mLineStart[++mMaxLine] = saveCharPosition;
                        TextWrapper.setPosition(saveCharPosition);
                    } else {
                        mLineStart[++mMaxLine] = currentPosition;
                        TextWrapper.setPosition(currentPosition);
                    }
                } else if (!TextWrapper.hasNext()) {
                    if (type === eCharType.CHARACTER) {
                        mLineChars[mMaxLine] = charCount;
                    } else if (type === eCharType.SPACE) {
                        mLineChars[mMaxLine] = saveSpaceCharCount;
                    }
                }
            }

            mPageStart[pageCounter] = mMaxLine + 1;


            // lines into string[]
            var result: string[] = [];

            for (var i = 1; i <= pageCounter; i++) {
                var firstLine: number = mPageStart[i - 1];
                var lastLine: number = mPageStart[i];

                var pageText: string[] = [];
                for (var l = firstLine; l < lastLine; l++) {
                    pageText.push(TextWrapper.mText.substr(mLineStart[l], mLineChars[l]));
                }

                result.push(pageText.join("\n"));
            }

            return result;
        }
    }
}

 You can download the example with Typescript source files here.

 By the way, in last days we released our new mobile game "Flat Jewels Match 3" for iOS and Android - give it try!





Friday, January 30, 2015

Mobile Income Report #7 - December 2014 (monthly income report from Android, iOS, Tizen, ... games)





previous parts
  Mobile Income Report #6 - November 2014
  Mobile Income Report #5 - October 2014

  Mobile Income Report #4 - September 2014 
  Mobile Income Report #3 - August 2014
  Mobile Income Report #2 - July 2014
  Mobile Income Report #1 - June 2014
  Apps page - Portfolio (what are my assets?)


 If you do not want to miss any of my Income Reports you can follow me on Twitter. Just click the button above.

 Under Apps page you can see my portfolio. It is list of the games that are then reported in Income Reports. The portfolio is regularly updated with new information (releases for new platforms, new updates, ...)


What I did in December

  • in December I continued work on our games, mainly Flat Jewels Match 3. We finished the game itself and mainly worked on additional features like various "game services" and so on. Now, when writing this post, the game is released on Android (at Google Play and at Amazon) and it is in approval process for iOS 
Flat Jewels Match 3 - menuFlat Jewels Match 3 - gameplay

  • I also started learning HTML5 and Javascript as it is whole new field of opportunities for simple games. First I used Netbeans IDE, which I really like, for my first attempts. But then I found there is Typescript language that can help with type checks and you can write code in more convenient way, so I switched to Microsoft Visual Studio Community 2013, which is free for individual developers and has Typescript support. Typescript is superset of Javascript, so everything you write is compiled into Javascript,
  • you can start to write your HTML5 game from scratch or save some time and use some engine. There is lot of them. But finally I ended with Phaser by PhotonStorm as I like their approach and it is obvious that the engine is constantly being improved,
  • On 31st December there was promotion event on Shards by AppGratis. It brought more than 17k downloads in one day and more importantly it helped us to gain some positive user ratings.

Report

 Here are December figures for paid apps:


 While income from iOS games is more or less stable (from $27 in November to $24,3), there is decrease in Android versions (from $38,1 to $22,4). Total amount $46,7 means decrease from $65,1 in November.


 Shards with 60% share is still most profitable from our games.

 Stats for free versions of our games are these:


 There is really big decrease from $220,7 in November to $107,4 in December. In November there was good result with Chartboost (close to $150), but it did not lasted long time. Also income from other networks was lower in december.


 Total income for December was $46,7 + $107,4 = $154,1 which is -46% decrease compared to November and it is really very bad result...


Next?

 In January I am continuing in work on our games - Flat Jewels Match 3 game was released on Android and is in approvel process on iOS. I hope it will help me little bit. I am also continuing in my Typescript / Phaser experiments and I hope there will be some results soon.
 

Thursday, December 11, 2014

Mobile Income Report #6 - November 2014 (monthly income report from Android, iOS, Tizen, ... games)





previous parts
  Mobile Income Report #5 - October 2014

  Mobile Income Report #4 - September 2014 
  Mobile Income Report #3 - August 2014
  Mobile Income Report #2 - July 2014
  Mobile Income Report #1 - June 2014
  Apps page - Portfolio (what are my assets?)


 If you do not want to miss any of my Income Reports you can follow me on Twitter. Just click the button above.

 Under Apps page you can see my portfolio. It is list of the games that are then reported in Income Reports. The portfolio is regularly updated with new information (releases for new platforms, new updates, ...)


What I did in November

  •  in November there were several promotion events for our previous games. All of them were in this way: we will promote your app, but you will set it free during promotion period. So, there is not direct impact into income, but it is still worth as it brings some publicity. The events were these:
    • 7th November - Fruit Dating was featured as "Free app of the day" at Amazon Brazil. It brought 854 downloads. It is obvious, that Amazon in US, UK or Germany is more popular (as we experienced with Shards promotion) than in Brazil, but it is still great feeling if your game is featured somewhere,
    • 11th-14th November - Shards was featured at Amazon China. It brought close to 950 downloads. Here is part of main screen:

    • 9th and 12th November - Fruit Dating was promoted through AppGratis. AppGratis is app discovery service. Users have it installed in their phones and are offered one free app a day. The promotion was for both Android (9th) and iOS (12th). Total number of downloads exceeded 13000. AppGratis was most effective from all November promotions,
    • 28th November - Fruit Dating was promoted through Appgratuita service. It looks that this service is smaller and focused mainly on Italy. Again, both versions, Android and iOS were promoted with around 680 downloads.
  • we (me and Tomáš Kopecký) started to work on new game - classic match 3. But we moved it into world of flat design. We do not want to compete with other match 3 games with shiny graphical effects. We want to make neat match 3 game with small install size, that will not push players into IAPs. Game has two modes - levels and time attack. Currently we are testing all 150 levels. This is our first game we put Google Analytics into. The events sent from game help us balance levels difficulty. Here is arranged screenshot (used Free iPhone 6, 4.7-inch Template from Dribbble.com):

  • I still continue in work on big Unity project. The progress is slow as I am also still learning Unity as I go. Anyway, I have small but flexible system for moving platforms and other objects. It is combined with flexible events and I already made some applications of this system in the game. I also started writing tutorials on these Unity topics. You can read first three here:
  And here is how result can look - object is following defined path (yellow squares), is performing different kinds of move (linear, quadratic), can wait in path nodes, is emitting event (blue circle) on impact (dust). Other features are described in tutorials.


Report


 Now, to November figures. Here is table for paid apps:


 There is big decrease, compared to October. From $146,9 to $65,1. It is because effect of big promotion events for Shards and Deadly Abyss 2 in September is definitely over.


 Promotion events in November were related mainly to Fruit Dating. For this reason its share increased from 5% to 15%. The most profitable is still Shards.

 In table and chart below are stats for versions supported with ads. I also moved here downloads during promotion events as these downloads were for free. Leaving it in paid table would spoil stats.




 All income channels are more or less the same as in October except for Chartboost. November was the best month with Chartboost during this year (its share raised from 47% to 67%). Thanks to it the overall income remained close to income in October:

   total income for SBC team in November = $65,1 + $220,7 = $285,8 (+ 3,9% compared to October report). It is still far away from my mid term target $750 / month.



Next?


 In December I am continuing work on my projects. I hope we will finish and release our match-3 game. I will also continue with Unity game and currently I have at least one topic I want to write tutorial on (how to keep colliders visible in part of object hierarchy in scene editor when editing single collider)








Monday, December 8, 2014

Unity 2D: Simple system for moving platforms (not only!) - improving events






 Other Unity articles
  Simple system for moving platforms (not only!)
  Traversing GameObjects hierarchy



 In article "Simple system for moving platforms (not only!)" we created simple system that allowed us to move platforms or other objects along path defined with path nodes. This path had visual representation in editor and the system had some parameters that allowed us easy parametrization (waiting in nodes of our choice, setting speed and speed modifiers, moving in ping-pong way, loops or only once, setting movement type like linear, cos, quadratic ...).

 One of the features was ability to send events when platform or any other object following path arrived to destination path node or when leaving current path node. Originally this event was defined like stupid enum - just listing of some generalized event names:

public enum ePathNodeEventType
    {
        Nothing,
        Hit
    }

 In this tutorial we will change this enum to regular event object. The result animated above shows small dust animation when platform is hit. This animation is initiated with event. Each event will have also some parameters as pre-delay and post-delay and also it will have possibility to point to next event. In this way we can initiate chains of events. Here is how our inspector for PathNode will change:


 You can download final source files here. We first change two public variables holding enum values in PathNode.cs to this:

    public PathNodeEvent mOnArriveEvent;
    public PathNodeEvent mOnLeaveEvent;

 This will cause small change in our existing PathFollower class where we have to change OnLeaveEvent and OnArriveEvent like this:

    // -------------------------------------------------------------------------
    public virtual void OnLeaveEvent(PathNodeEvent aEvent)
    {
        if (aEvent != null)
            aEvent.ProcessEvent(gameObject);
    }

    // -------------------------------------------------------------------------
    public virtual void OnArriveEvent(PathNodeEvent aEvent)
    {
        if (aEvent != null)
            aEvent.ProcessEvent(gameObject);
    }

 Now we need to create new class. This class will name PathNodeEvent. Its properties will be delay before event is processed and time after the event is processed. The delay after event may seem unnecessary, but there is one more property - next event (again PathNodeEvent or derived class). The delay after processing current event says how much time to wait before processing next event. The event chain is stopped when next event is null.

 Here is listing of the beginning of PathNodeEvent class:

using UnityEngine;
using System.Collections;

public class PathNodeEvent : MonoBehaviour
{
    public float mDelayBefore = 0.0f;
    public float mDelayAfter = 0.0f;
    public PathNodeEvent mNextEvent = null;

 And here is rest of this small, but powerful class:

    // -------------------------------------------------------------------------
    public virtual void ProcessEvent(GameObject aGameObject)
    {
        StartCoroutine(Process(aGameObject));
    }

    // -------------------------------------------------------------------------
    protected virtual void DoProcess(GameObject aGameObject)
    {
        Debug.Log(gameObject.name + ": processing event");
    }

    // -------------------------------------------------------------------------
    protected IEnumerator Process(GameObject aGameObject)
    {
        if (mDelayBefore > 0)
            yield return new WaitForSeconds(mDelayBefore);

        DoProcess(aGameObject);

        if (mDelayAfter > 0)
            yield return new WaitForSeconds(mDelayAfter);

        if (mNextEvent != null)
            mNextEvent.ProcessEvent(aGameObject);
    }
}

 From the PathFollower class you see that ProcessEvent is called. The method is virtual so we can override it in subclasses. The implementation here is default. What it does is, that coroutine (Process) is started. There is first processed delay before event, then event itself - DoProcess. If some delay after event is entered, it is processed next and finally we check if there is any next event.

 This class does not do anything interesting except it prints message to debug console. Most of the real event will be subclasses of this class. You can override ProcessEvent if you are not interesting in delays or in chaining (for some simple event) or you can override DoProcess and you will have all the features described above.

 So, let's subclass it with simple SpawnEvent. The class is note derived from MonoBehaviour, but from our PathNodeEvent:


using UnityEngine;
using System.Collections;

public class SpawnEvent : PathNodeEvent
{
    public Transform mSpawnPoint;
    public GameObject mSpawnObject;

    // -------------------------------------------------------------------------
    public override void ProcessEvent(GameObject aGameObject)
    {
        // check for undefined spawn object or point
        if (mSpawnObject == null)
        {
            Debug.LogError("Spwan object is not defined");
            return;
        }
        else if (mSpawnPoint == null)
        {
            Debug.LogError("Spawn point is note defined");
            return;
        }

        // all ok, all defined
        /*GameObject spawnObject = (GameObject) */ Instantiate(mSpawnObject,
            mSpawnPoint.position, mSpawnPoint.rotation);
    }
}

 This type of event is very simple, so we override directly ProcessEvent and are not interested in delays. It has two additional public properties - Spawn Point and Spawn Object. Spawn object is "something" that will be spawned at "point". the inspector view looks like this:


 It means, you have to create event object somewhere in the scene. Spawn point can be any other Transform in the scene. SpawnObject can be prefab, which is very useful. To save one one GameObject in scene, the event here has its own Transform as spawn point - it is pointing to itself. Object in scene are arranged like this:


 The yellow squares are PathNodes from previous tutorial. The blue circle is event. The spawn object I am pointing to is short dust animation. When running it. you will get the result already shown in the beginning:


 The event spawns the dust when second PathNode is reached. But you can also play sound in the same event or chain sound event and animation event.

 Download (4 kb) here

 As you can imagine, the original PathFollowing system in connection with events can have lot of usages. I think it can also be used for some character animation scenes. Good thing is that both, the path system and events are easily extensible.

Unity: Traversing GameObjects hierarchy






 Other Unity articles
Simple system for moving platforms (not only!)


 Traversing gameobjects in scene one by one is very common task when working with Unity. It usually includes two things: you want to traverse scene hierarchy and you want to do something with each hierarchy node.

 In this article I will build a class that solves this once and for all. You can use this class for any processing on the particular gameobjects as the traversing is divided from processing.

 Lets name our class HierarchyUtils. It will hold some static methods and it also defines delegate. We will also put it into separate namespace, so we can add other useful utility classes into it in future:

1
2
3
4
5
6
7
using UnityEngine;

namespace SBC.Unity.Utils
{
    public class HierarchyUtils
    {
        public delegate void GameObjectHandler(GameObject aGameObject, int aDepthLevel);

 This delegate method is key in splitting traversing and processing part. The traversing part will call any method that meets delegate definition - any method returning void and taking two parameters: GameObject and int.

 So, let's say we have some GameObject and we want to traverse all its child gameobjects. We will add these two methods to our class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
        // -------------------------------------------------------------------------
        public static void IterateGameObject(GameObject aGameObject,
                                   GameObjectHandler aHandler, bool aRecursive)
        {
            DoIterate(aGameObject, aHandler, aRecursive, 0);
        }

        // -------------------------------------------------------------------------
        public static void DoIterate(GameObject aGameObject,
                                   GameObjectHandler aHandler, bool aRecursive, int aDepthLevel)
        {
            aHandler(aGameObject, aDepthLevel);

            foreach (Transform child in aGameObject.transform)
            {
                if (aRecursive)
                {
                    DoIterate(child.gameObject, aHandler, aRecursive, aDepthLevel + 1);
                }
            }
        }

 Now, we can use our class. Put the following methods into any class that needs to traverse hierarchy (these are not part of HierarchyUtils):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
    // -------------------------------------------------------------------------
    public void Traverse(GameObject aGameObject)
    {
        if (mGameObject == null)
            return;

        handler = new HierarchyUtils.GameObjectHandler(ProcessNode);

        HierarchyUtils.IterateGameObject(mGameObject, handler, true);
    }

    // -------------------------------------------------------------------------
    private void ProcessNode(GameObject aGameObject, int aDepthLevel)
    {
        Debug.Log("GameObject: " + aGameObject.name + ", depth level: " + aDepthLevel);
    }

 Call the first method with root gameobject you want to start from. It creates handler, which is ProcessNode method. This method complies delegate definition, so we can handle it to IterateGameObject. The handler here is only simple one - it prints name of each gameobject and also its depth within hierarchy. But you can do here whatever you want.

 Our solution is working. It is up to you which handler you pass to IterateGameObject method, but you do not have to write traversing every time.

 Now, we will add one more method to HierarchyUtils. We can call this method instead of IterateGameObject. The signature will be:

public static void IterateScene(GameObjectHandler aHandler, string[] aFilterNames)

 This method will traverse whole scene. And more, it will dive only into gameobjects whose names begin with names in aFilterNames. In Unity you do not have easy access to root gameobjects. You have to find gameobjects that do not have parent. Let's say your hierarchy is like this:


 There is 5 root objects. You want to traverse scene, but only for root objects that are in foreground or background tree. Your aFilterNames parameter will then look like {"fore", "back"}; (remember, that names in our filter says: "stars with").

 Here is whole listing for the method:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
        // -------------------------------------------------------------------------
        public static void IterateScene(GameObjectHandler aHandler, string[] aFilterNames)
        {
            GameObject[] gameObjects = GameObject.FindObjectsOfType<GameObject>();

            foreach (GameObject gameObject in gameObjects)
            {
                // not top node
                if (gameObject.transform.parent != null)
                    continue;

                // is current top node in filterNames
                bool isNameInFilter = false;
                foreach(string filter in aFilterNames)
                {
                    if (gameObject.name.StartsWith(filter))
                    {
                        isNameInFilter = true;
                        break;
                    }
                }

                //Debug.Log("Name " + gameObject.name + " in filter " + isNameInFilter);
                                
                // if in filternames
                if (isNameInFilter)
                {
                    IterateGameObject(gameObject, aHandler, true);
                }
            }
        }

 We first take all gameobjects in scene. Then we check one by one. If has parent then it is not root gameobject. If root node, but its name does not start like any filter in passed filters list then we do not iterate on this gameobject. If in list we again call our IterateGameObject method with root gameobject.

 Here is example of usage somewhere in your code, which will print indented gameobject names:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    // -------------------------------------------------------------------------
    private void TraverseScene()
    {
        // method to be called on every single gameobject traversed
        HierarchyUtils.GameObjectHandler handler = new HierarchyUtils.GameObjectHandler(PrintName);
        // filter top nodes in hierarchy
        string[] filterNames = {"back", "fore"};

        // traverse
        HierarchyUtils.IterateScene(handler, filterNames);
    }

    // -------------------------------------------------------------------------
    public void PrintName(GameObject aGameObject, int aDepthLevel)
    {
        string indent = new string(' ', aDepthLevel * 2);
        Debug.Log(indent + aGameObject.name);
    }

 With this simple class you do not have to spend time on traversing again and again. You just write method for processing gameobjects and handle it to HierarchyUtils methods.






Friday, December 5, 2014

Unity 2D: Simple system for moving platforms (not only!)





 In this post I will describe my simple system for moving platforms and other objects in Unity. What is great is, that it is simple, flexible and easily extensible.

Here are see some results achieved with it:

  • moves between two nodes in "ping-pong" way,
  • stops little in upper node for defined time,
  • falls fast with some gravity-like effect ,
  • waits very little time,
  • slowly returns up











  • moves in "loop" way,
  • stops at first node,
  • moves in gravity way from second to third node,
  • moves in "cos" way from third to fourth node


1. Timer class


 First we will create small helper class called Timer. If you often need to track time in your Update method you are probably used to create float variable holding some amount of time and adjust it with Time.delta or Time.smoothDeltaTime. Doing this again and again is boring so we will create class that can:
  • hold current time,
  • increase time from zero to specified time or decrease time from initial time to zero,
  • return current time,
  • return current time as percent from initial or maximum time,
  • say if timer expired

Here is listing for Timer.cs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
using UnityEngine;

public class Timer
{
    private float mTime;
    private float mCurrentTime;
    private bool mRunning;
    private bool mReversed;

    // -------------------------------------------------------------------------
    public void Set(float aInitialTime, bool aReversed = false)
    {
        mTime = aInitialTime;
        mCurrentTime = aReversed ? aInitialTime : 0.0f;

        mReversed = aReversed;

        mRunning = (aInitialTime > 0.0f);
    }

    // -------------------------------------------------------------------------
    public float Update(float aDeltaTime)
    {
        if (!mRunning)
            return GetActual();

        if (mReversed)
            mCurrentTime -= aDeltaTime;
        else
            mCurrentTime += aDeltaTime;

        if (mCurrentTime < 0 || mCurrentTime > mTime)
        {
            mCurrentTime = Mathf.Clamp(mCurrentTime, 0.0f, mTime);
            mRunning = false;
        }

        return mCurrentTime;
    }

    // -------------------------------------------------------------------------
    public float GetActual()
    {
        return mCurrentTime;
    }

    // -------------------------------------------------------------------------
    public float GetActualRatio()
    {
        float ratio = mCurrentTime / mTime;

        if (ratio < 0.0f)
            ratio = 0.0f;
        else if (ratio > 1.0f)
            ratio = 1.0f;

        return ratio;
    }

    // -------------------------------------------------------------------------
    public bool IsRunning()
    {
        return mRunning;
    }

    // -------------------------------------------------------------------------
    public bool IsReversed()
    {
        return mReversed;
    }
}

 To use this class create Timer object and first call Set on it. You can call it like this:
  • Set(5.0f); ... timer will start with 0 and increase until it reaches 5 seconds,
  • Set(5.0f, false); ... timer will has its initial value 5 seconds and will decrease towards zero
 In Update method of parent object then call Update(Time.deltaTime) on Timer object to adjust it. To check if timer is finnished simply check if IsRunning is false.



2. Path node, path definition and path follower


 Now we can focus on real subject. Our path following system will have three parts:
  • PathNode - class that defines single node in path,
  • PathDefinition - is object managing two or more path nodes. As having only two nodes is very common we will make PathDefinition class abstract and build two implementations: SimplePathDefinition for two nodes only and LongPathDefinition for unlimited number of nodes. In fact, LongPathDefinition can also handle two nodes without problem. But the main reason is that SimplePathDefinition component has easier input interface for user (two fields for node 1 and 2 instead of nodes array),
  • PathFollower - is object that will hold PathDefinition and will move object from node to node. This is script is then added in component view to platform or log or any other object that shell follow the path.



2.1. PathNode class


 Path node is single node the path consists of. The position of node is given with its Transform component. Beside position the node holds other information:
  • delay in seconds - how long the moving object will pause its move in this node,
  • movement type - type of movement from this node to next. Supported moves are: linear, cos, quadratic,
  • speed modifier - is in % and affects speed of moving object for move from current node to next one. For example, if set to 0,5 (50%) then distance to next move is overcome in half of the time. So, numbers below zero means faster move.
  • OnArriveEvent, OnLeaveEvent - two events fired when leaving current node and when arrived to next node.
 Currently the events are little bit confusing, because in OnArriveEvent node says which event will be triggered when next node is reached. The order of processing for single node is like this:

   ----- wait (delay) ---- trigger OnLeaveEvent ----- move to next node ----- trigger OnArriveEvent -----

 For future improvements it would be better to bind the OnArriveEvent event to node that defines it and trigger it as first when object reaches node like this:

   ----- trigger OnArriveEvent ----- wait (delay) ----- trigger OnLeaveEvent ----- move to next node -----

 Currently events are implemented only as some enum values. Update: read next part of this tutorial here - enum values are replaced with event objects that can be chained to create series of events, have delays and are easily extensible (also source download is available there).

 Here is listing for core of PathNode class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using UnityEngine;

public class PathNode : MonoBehaviour
{
    public enum ePathNodeEventType
    {
        Nothing,
        Hit
    }

    public enum eMovementType
    {
        Linear,
        Cos,
        Qudratic
    }

    public eMovementType mMovementType = eMovementType.Linear;

    // delay in this point in seconds
    public float mDelay = 0.0f;

    public ePathNodeEventType mOnArriveEvent;
    public ePathNodeEventType mOnLeaveEvent;

    public float mSpeedModifier = 1.0f;

 Using enums is convenient for users as they can select from it in editor then. Here is picture how the component looks like in editor. Notice that PathNode has no visual representation. It is just point in space with some additional parameters. So to visualise it add some visual tag to it (see small yellow square in top left of image):



 Rest of the class are only get properties, but to make it complete here is the listing:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
    // -------------------------------------------------------------------------
    public float Delay
    {
        get
        {
            return mDelay;
        }
    }

    // -------------------------------------------------------------------------
    public eMovementType MovementType
    {
        get
        {
            return mMovementType;
        }
    }

    // -------------------------------------------------------------------------
    public ePathNodeEventType ArriveEvent
    {
        get
        {
            return mOnArriveEvent;
        }
    }

    // -------------------------------------------------------------------------
    public ePathNodeEventType LeaveEvent
    {
        get
        {
            return mOnLeaveEvent;
        }
    }

    // -------------------------------------------------------------------------
    public float SpeedModifier
    {
        get
        {
            return mSpeedModifier;
        }
    }
}


2.2 PathDefinition class


 Path definition will manage PathNodes and it will be abstract class. The abstaract method will be method:

      public abstract IEnumerator<PathNode> GetPathEnumerator();

 This method is:
  • abstract, so some other class will have to define implementation for this method,
  • returns IEnumerator, which means that implementation will manage set of PathNodes and on request it will return correct PathNode or report that there are no more nodes.
 PathDefinition class looks simple like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public abstract class PathDefinition : MonoBehaviour
{
    public enum ePathRepeatType
    {
        Once,
        PingPong,
        Loop
    }

    public ePathRepeatType mMovementType;

    // -------------------------------------------------------------------------
    public abstract IEnumerator<PathNode> GetPathEnumerator();

 Notice the ePathRepeatType enum. This enum says what type of path it is. It can be traveled by the object only once or back and forth forever (ping-pong) or in loop when from last node the object will continue to the first node again.

 As it would be very pleasant to visuailise the path between nodes, we will implment OnDrawGizmos method:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    // -------------------------------------------------------------------------
    public void OnDrawGizmos()
    {
        IEnumerator<PathNode> nodes = GetPathEnumerator();

        if (!nodes.MoveNext())
            return;

        Gizmos.color = Color.grey;
        PathNode firstNode = nodes.Current;

        PathNode node1 = firstNode;
        PathNode node2 = null;

        while (nodes.MoveNext())
        {
            node2 = nodes.Current;

            Gizmos.DrawLine(node1.transform.position, node2.transform.position);

            if (node2 == firstNode)
                break;

            node1 = node2;
        }
    }
}

 As you can see we are asking for enumerator which is provided by the abstract method. We draw debug lines between nodes until enumerator is giving us nodes. In case the enumerator is returning nodes in "loop" mode, we check if loop is closed and exit the method.

 Now we can provide concrete implementation in class that will be able to hold only two PathNodes. Let's call it SimplePathDefinition:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
using UnityEngine;
using System.Collections.Generic;

public class SimplePathDefinition : PathDefinition
{
    public PathNode mPathNode1;
    public PathNode mPathNode2;

    // -------------------------------------------------------------------------
    public override IEnumerator<PathNode> GetPathEnumerator()
    {
        // check if both path nodes entered
        if (mPathNode1 == null || mPathNode2 == null)
            yield break;


        // else set pointer to current node
        PathNode currentNode = mPathNode1;

        // return points based on movement type
        // in case of simple path loop and ping pong are the same
        while (true)
        {
            yield return currentNode;

            // swap to next node
            if (currentNode == mPathNode1)
                currentNode = mPathNode2;
            else
                currentNode = mPathNode1;

            // if move only once mode and returned to node1 then exit
            if (mMovementType == ePathRepeatType.Once && currentNode == mPathNode1)
                yield break;
        }
    }
}

 We are not inheriting directly form MonoBehaviour here but from PathDefinition. It means in editor our parent's OnDrawGizmos will be called and only thing we have to do is provide GetPathEnumerator method. This class will be the component you can add to GameObject in editor and you will also have to give it two PathNodes:


 In case of "ping-pong" or "loop" modes, the iterator never runs out of nodes. It just switches between nodes and return first or second in turn. In case of "once" move mode the iterator check if reached second node and if yes it terminates (yield break;)

 Now you can go to editor and create three empty objects. Add PathNode component to two of them and add SimplePathDefinition to the last one. Then drag PathNode objects to SimplePathDefinition object as Path Node 1 and Path Node 2 and you should see line between them (if you see nothing, check if the position of PathNodes is different).


 Now, we will create another class - LongPathDefinition. This class will have different GetPathEnumerator implementation and also different way how to store PathNodes. PathNodes will be stored in array and we will also add one additional feature that had no sense for only two nodes. This feature is ReverseDirection and it says whether the move should be from first to last node or from last to first node. Here stars LongPathDefinition listing:

1
2
3
4
5
6
7
using UnityEngine;
using System.Collections.Generic;

public class LongPathDefinition : PathDefinition
{
    public bool mReverseDirection;
    public PathNode[] mPathNodes;

 GetPathEnumerator is only last thing we have to write:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
    // -------------------------------------------------------------------------
    public override IEnumerator<PathNode> GetPathEnumerator()
    {
        // check if nodes array is valid entered and longer than 2 nodes (1 node = no path)
        if (mPathNodes == null || mPathNodes.Length < 2)
            yield break;
        
        // set index to start
        int currentNodeIndex = 0;
        int directionAdjustment = mReverseDirection ? -1 : 1;
        // if move type is only once and direction is reversed, start from last node
        if (mReverseDirection && mMovementType == ePathRepeatType.Once)
            currentNodeIndex = mPathNodes.Length - 1;
        
        // return points based on movement type
        // in case of simple path loop and ping pong are the same
        while (true)
        {
            yield return mPathNodes[currentNodeIndex];

            // adjust in move direction
            currentNodeIndex += directionAdjustment;

            if (currentNodeIndex < 0 || currentNodeIndex >= mPathNodes.Length)
            {
                if (mMovementType == ePathRepeatType.Once)
                {
                    yield break;
                }
                else if (mMovementType == ePathRepeatType.PingPong)
                {
                    directionAdjustment *= -1;
                    // adjust twice - first return to current node, second move in new direction
                    currentNodeIndex += directionAdjustment * 2;
                }
                else if (mMovementType == ePathRepeatType.Loop)
                {
                    if (currentNodeIndex < 0)
                        currentNodeIndex = mPathNodes.Length - 1;
                    else
                        currentNodeIndex = 0;
                }
            }
        }
    }
}

 In case of "once" move we have to set the last node as first one. In case of "ping-pong" the reversing has no sense and in case of "loop" the first node is still the first one, but we start to move to last one instead to second one if direction is reversed.

 The rest is just infinite enumerator loop (except for "once", which terminates when last (first if reversed) node is reached).

 As with SimplePathDefinition, you can now go to editor and create new GameObject, add LongPathDefinition script component to it and create and add several PathNodes to it:




2.3 PathFollwer class


 PathFollower is last piece into mosaic. This script will be the component that you add to object you want to move. This component will hold some path definition (Simple or Long) which in turn holds set of path nodes.

 This class is the longest one so I will go step by step. First, listing shows variables. The public ones are those you can set in editor. Beside PathDefinition (Simple/Long) it has general speed in Unity units. 1 = 1 Unity unit in second. Next public variable - mStopped - says whether the move is stopped.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using UnityEngine;
using System.Collections.Generic;

public class PathFollower : MonoBehaviour
{
    // path definition
    public PathDefinition mPathDefinition;
    // speed in units per second
    public float mSpeed = 1.0f;
    // stopped or in action?
    public bool mStopped = false;

    private enum ePathFollowerState
    {
        Waiting,
        Moving
    }

    // enumerator
    private IEnumerator<PathNode> mPathNodes;

    // inner state of follower
    private ePathFollowerState mState = ePathFollowerState.Waiting;
    // inner waiting time
    private Timer mTimer = new Timer();

    // curren path node, object is in (or going from)
    private PathNode mCurrentPathNode;
    // target path node (or node object is going to)
    private PathNode mTargetPathNode;

    // finished
    private bool mFinished;

 Private variables then holds inner state of the PathFollower. If it is currently moving on waiting in actual node. It also stores concrete IEnumerator implementation. Here we also finally utilize the Timer class to simplify all timings.
 Next we store current and target node and also whether the path following is finished (in case of "once" movetype).

 In Start method we initialize enumerator and also we ask it for current and next node. We read delay from path node (can be 0) and set inner state into "waiting".

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
    // -------------------------------------------------------------------------
    public void Start()
    {
        if (mSpeed == 0.0f)
            Debug.LogError("Speed cannot be zero!");

        Reset();
    }

    // -------------------------------------------------------------------------
    public void Reset()
    {
        if (mPathDefinition == null)
        {
            Debug.LogError(gameObject + ": path definition cannot be null");
            return;
        }

        mPathNodes = mPathDefinition.GetPathEnumerator();
        mPathNodes.MoveNext();

        mCurrentPathNode = mPathNodes.Current;

        mPathNodes.MoveNext();
        mTargetPathNode = mPathNodes.Current;

        mTimer.Set(mCurrentPathNode.Delay);
        mState = ePathFollowerState.Waiting;

        mFinished = false;
    }

 Here is Update method where most of the things take place:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
    // -------------------------------------------------------------------------
    public void Update()
    {
        if (mStopped || mFinished)
            return;


        // adjust timer (regardless it is for waiting or moving)
        mTimer.Update(Time.smoothDeltaTime);

        // if not waiting (moving) then adjust position of object
        if (mState == ePathFollowerState.Moving)
        {
            AdjustPosition(mTimer.GetActualRatio());
        }

        // if timer finished its running - do next steps
        if (!mTimer.IsRunning())
        {
            if (mState == ePathFollowerState.Waiting)
            {
                // any event on start of move from current position?
                OnLeaveEvent(mCurrentPathNode.LeaveEvent);
                MoveToTargetNode();

            }
            else if (mState == ePathFollowerState.Moving)
            {
                // any event in the end of move?
                OnArriveEvent(mCurrentPathNode.ArriveEvent);

                // if no new nodes we are finished
                if (!PrepareNextTargetNode())
                {
                    mFinished = true;
                    return;
                }

                // set waiting state (if any)
                float waitingTime = mCurrentPathNode.Delay;
                if (waitingTime > 0.0f)
                {
                    mTimer.Set(waitingTime);
                    mState = ePathFollowerState.Waiting;
                }
                else
                {
                    OnLeaveEvent(mCurrentPathNode.LeaveEvent);
                    MoveToTargetNode();
                }
            }
        }
    }

 We first check if still running and not paused. If running, we update timer - we are not interested what it is timing now - it is either moving or waiting. If moving, we adjust position of object.
 Then we check if timer expired and if yes we change inner state and get next node if needed.

 Now I will list explain how moving works. When two nodes are read (current and target), distance between then is calculated in Unity units. This is divided with speed in Unity units and you get time how long it should take to the object to travel from current to target node. This time is then affected by PathNode's speed modifier (0,5 = 50% ... time should be only half). this is all calculated in CalculateMoveTime method.
 Timer is then set to calculated value (MoveToTargetNode). As the timer has GetActualRatio method, we can ask it in which distance between two nodes the moving object should be. As the result is between 0 and 1 it is ideal entry for LERP.
 Look into AdjustPosition method and you will see that it is called with value returned from Timer.GetActualRatio. If movement type is linear we just calculate new position as Vector3.Lerp between current and target nodes positions. If movement type is quadratic, we power the ratio by 2. In case of cos we do this:
  • cos (ratio * PI) => gives 1 to -1 values,
  • -cos (ratio * PI) => gives -1 to 1,
  • -cos (ratio * PI) + 1 => gives 0 to 2,
  • (-cos (ratio * PI) + ) / 2 => gives 0 to 1 ... correct entry for lerp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
    // -------------------------------------------------------------------------
    private void MoveToTargetNode()
    {
        // calculate time to move based on nodes positions and speed per unit
        mTimer.Set(CalculateMoveTime());
        // set object as moving
        mState = ePathFollowerState.Moving;
    }

    // -------------------------------------------------------------------------
    private bool PrepareNextTargetNode()
    {
        // set target node as current node
        mCurrentPathNode = mTargetPathNode;

        // read new target node - if no nodes we are finished (only resetting can start it again
        if (!mPathNodes.MoveNext())
        {
            mCurrentPathNode = null;
            mTargetPathNode = null;
            return false;
        }
        
        mTargetPathNode = mPathNodes.Current;
        return true;
    }
    
    // -------------------------------------------------------------------------
    private void AdjustPosition(float aRatio)
    {
        PathNode.eMovementType movementType = mCurrentPathNode.MovementType;

        // adjust ratio based on movement type
        if (movementType == PathNode.eMovementType.Cos)
        {
            // return cos from 0 to 1 in PI range (1...-1 => 0...1)
            aRatio = (-Mathf.Cos(Mathf.PI * aRatio) + 1) / 2.0f;
        }
        else if (movementType == PathNode.eMovementType.Qudratic)
        {
            aRatio *= aRatio;
        }

        transform.position = Vector3.Lerp(mCurrentPathNode.transform.position,
                                          mTargetPathNode.transform.position,
                                          aRatio);
    }

    // -------------------------------------------------------------------------
    private float CalculateMoveTime()
    {
        Vector3 distance = mTargetPathNode.transform.position -
            mCurrentPathNode.transform.position;

        return (distance.magnitude / mSpeed) * mCurrentPathNode.SpeedModifier;
    }

 And finally, here are two last methods for handling events:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    // -------------------------------------------------------------------------
    public virtual void OnLeaveEvent(PathNode.ePathNodeEventType aEvent)
    {
        if (aEvent != PathNode.ePathNodeEventType.Nothing)
            Debug.Log("Leave event: " + aEvent);
    }

    // -------------------------------------------------------------------------
    public virtual void OnArriveEvent(PathNode.ePathNodeEventType aEvent)
    {
        if (aEvent != PathNode.ePathNodeEventType.Nothing)
            Debug.Log("Arrive event: " + aEvent);
    }

 These methods are only "default implementation" and you can change them in derived classes.

 We are finished. You can go to editor and add new game object. Give it some appearance through SpriteRenderer, add PathFollower component to it and attach some PathDefinition. then you can pres Play and see how the object is moving along defined path!



Conclusion


 This is end of my first Unity tutorial. I hope you made it here and now you have also this small system working.