Sunday 29 May 2016

Porting Unity Mobile Game To tvOS

I am back from my hibernation. Life and my full-time job kept me busy for 2 years and so much had happened since my last post back in April 2014. Including Apple's new toy: Apple TV 4 with its brand new tvOS.

I finally had some free time these days so I decided to port Bot Trigger, one of my mobile games, to this new and shiny OS.

Having build Bot Trigger with Unity made porting much easier. 
However, it was not free from challenges which I will highlight in this post.

Game Controls

Porting game controls from touch input to any other form of inputs requires bit of rethinking the game.

In the case of Bot Trigger, the game controls are relying on dual pad input. one for movement and one for aiming and firing and this brought a challenge as Apple (at the moment of writing this post) requires games to be fully functional using Siri Remote even though they do support a game controller.

This meant that the dual pad controls were not an option anymore and instead I had to make the game as single pad game with auto firing and no aiming for now. This in itself might not be a bad thing as most of the feedback I got was the two pad option was bit tough to master on touch screen. But for most games this might proof a challenge to make more console like games.

I looked into the accelerometer/ gyroscope options.  However, it seemed bit fiddly

UI

When I built Bot Trigger back in 2013 old Unity UI wasn't a good option as the  out of the box GUI was bit limited with performance issues. 

One of the most popular options at that time was NGUI. NGUI  (at the moment of writing this post) doesnt support tvOS remote input yet. 

This meant that I need to port my UI to the new Unity GUI and support the non touch input which is well documented in Unity's website.

3rd Party Libraries

Need to keep in mind that tvOS supports a subset of iOS frameworks. 3rd party plugins/ libs need to be built for tvOS. 


Bugs :-)

It hasn't been that long since Unity published tvOS support. some glitches I encountered were mainly with Leaderboard /Social API. Unity 5.3.5 seems to have fixed most. 

Still, I needed to manually add leaderboard assets in XCode rather than Unity's Editor to be able to display the Leaderboard (to be fair Unity mentioned it in the docs but I didn't notice it at first)

Also it seems that there is a memory leak if I keep debugger attached to my game while running on device.

It will be also nice if Unity support input from tvOS simulator (or provide their own simulation) as at the moment its difficult to start porting without having the physical device.


The port is almost finished. I will follow up with post once I have a "release ready" version of the game

--UPDATE--

tvOS submission was rejected due to the menu button in game (after searching it seems alot of devs like me were not clear on how it behave). 

In case you are planning to port/make a game on tvOS make sure that the menu button is used to show and hide pause menu (confusing as you would think play/pause buttons should be used for that).

Monday 7 April 2014

"Infinite Scrolling" Using Unity3d and NGUI's UIScrollview Part 2

Previously I posted a demo of an early version of NGUI UIScrollview that uses a pool of objects to display large amount of data. e.g. contacts list, achievements...etc

Since then I updated the component with more features such as:
  • Better scroll handling which allowed me to use spring physics without issues so far
  • Sections support (not as sophisticated as the indexpath in iOS but did the job for now)
  • Basic scroll indicator (i.e. not interactive)
  • Made for on item per row for now...(uses UITable... should work with UIGrid)
  • Only fixed row height is supported.
 Here is a demo of the result:

How does the infinite scrolling work?
1. Each visible item will notify the main controller when it becomes invisible using NGUI's panel function... here is an example of how I've done it:
private bool isVisible;
void CheckVisibilty() 
{
 bool currentVisibilty = panel.IsVisible(backgroundSprite);
 if(currentVisibilty != isVisible)
 {
  isVisible = currentVisibilty;
  thisCollider.enabled = isVisible;
   
  if(!isVisible)
  {
   // notify main controller. itemNumber is the index of the pool item (not the data)
   StartCoroutine(listPopulator.ItemIsInvisible(itemNumber));
  }
 }
}
2. On receiving this notification the main controller do the following:
  • Retrieve the data index from the pool item
  • Check the visiblity of the next or previous item to determine scroll direction. (I didn't use the currentMomentum of UIScrollview... )
  • Based on the direction we start moving items of the pool from the top to the bottom (or vice versa) and change it to an item or section...
public IEnumerator ItemIsInvisible(int itemNumber)
{
    if(isUpdatingList) yield return null;
    isUpdatingList = true;
    if(dataList.Count > poolSize)// we need to do something "smart"... 
    {
 Transform item = itemsPool[itemNumber];
 int itemDataIndex = 0;
 if(item.tag.Equals(listItemTag))
            itemDataIndex = item.GetComponent().itemDataIndex;
 if(item.tag.Equals(listSectionTag))
     itemDataIndex = item.GetComponent().itemDataIndex;

 int indexToCheck=0;
 InfiniteItemBehavior infItem = null;
 InfiniteSectionBehavior infSection = null;
 if(dataTracker.ContainsKey(itemDataIndex+1))
 {
     infItem = itemsPool[(int)(dataTracker[itemDataIndex+1])].GetComponent();
     infSection = itemsPool[(int)(dataTracker[itemDataIndex+1])].GetComponent();

     if((infItem != null && infItem.verifyVisibility()) || (infSection != null && infSection.verifyVisibility()))
     {
  // dragging upwards (scrolling down)
  indexToCheck = itemDataIndex -(extraBuffer/2);
  if(dataTracker.ContainsKey(indexToCheck))
  {
      //do we have an extra item(s) as well?
      for(int i = indexToCheck; i>=0; i--)
      {
          if(dataTracker.ContainsKey(i))
   {
       infItem = itemsPool[(int)(dataTracker[i])].GetComponent();
       infSection = itemsPool[(int)(dataTracker[i])].GetComponent();
       if((infItem != null && !infItem.verifyVisibility()) || (infSection != null && !infSection.verifyVisibility()))
       {
    item = itemsPool[(int)(dataTracker[i])];
    if((i)+poolSize < dataList.Count && i>-1)
    {
        // is it a section index?
        if(sectionsIndices.Contains(i+poolSize))
        {
     // change item to section
     ChangeItemToSection(item,i+poolSize,i);
        }
        else if(item.tag.Equals(listSectionTag))
        {
     // change section to item
     ChangeSectionToItem(item,i+poolSize,i);
        }
        else
        {
     PrepareListItemWithIndex(item,i+poolSize,i);
        }
    }
       }
   }
   else
   {
     scrollCursor = itemDataIndex-1;
     break;
   }
      }
        }
           }
       }
       if(dataTracker.ContainsKey(itemDataIndex-1))
       {
    infItem = itemsPool[(int)(dataTracker[itemDataIndex-1])].GetComponent();
    infSection = itemsPool[(int)(dataTracker[itemDataIndex-1])].GetComponent();

    if((infItem != null && infItem.verifyVisibility()) || (infSection != null && infSection.verifyVisibility()))
    {
        //dragging downwards check the item below
        indexToCheck = itemDataIndex +(extraBuffer/2);
    
        if(dataTracker.ContainsKey(indexToCheck))
        {
            // if we have an extra item
     for(int i = indexToCheck; i();
      infSection = itemsPool[(int)(dataTracker[i])].GetComponent();
      if((infItem != null && !infItem.verifyVisibility()) || (infSection != null && !infSection.verifyVisibility()))
      {
          item = itemsPool[(int)(dataTracker[i])];
          if((i)-poolSize > -1 && (i) < dataList.Count)
          {
       // is it a section index?
       if(sectionsIndices.Contains(i-poolSize))
       {
           // change item to section
           ChangeItemToSection(item,i-poolSize,i);
       }
       else if(item.tag.Equals(listSectionTag))
       {
           // change section to item
           ChangeSectionToItem(item,i-poolSize,i);
       }
       else
       {
           PrepareListItemWithIndex(item,i-poolSize,i);
       }
          }
             }
                }
                else
                {
             scrollCursor = itemDataIndex+1;
             break;
                }
            }
               }
           }
        }
     }
     isUpdatingList = false;
}
The full source with sample and documentation is available on Github under MIT License :

Free Android app called Avatar Messenger is using this component for the phone book list: https://play.google.com/store/apps/details?id=com.orange.labs.avachat

Monday 31 March 2014

Avatar Messenger: Free App For Android

One of the apps I've been developing for Orange Labs UK. An updated version was released last week

Demo:


The app allows users to create their avatar and see their friends' ones as well as sending animated messages.

The app is made with Unity3D and utilizes the Infinite scrolling component I made for the phone book screen. (I am working on making the scripts of this component available as open source)

If anyone would like to try the app its available for free on Google Play

This version of the app uses SMS for messaging. Next step is to make an IM version for both iOS and Android.