It’s a Word Wrap!

Can’t recall where we found this script – probably the Unity forums – but we want to share it on the blog, because it’s been a nifty little script to have. It’s an auto word-wrapper.

So rather than manually typing the /n’s into your long text descriptions, just dump this little script onto your GUIText object, and presto! Word wrapping. You can chose the maximum width and height of the wrap, line length and co-ords. And you never have to type /n again! This is also great if you have localisation on long text. Nothing worse than hand-formatting every – single – translation!

It’s in UnityScript, have yet to convert to C#:


var lineLength = 400;
private var words : String[];
private var wordsList : ArrayList;
private var result = "";
var TextSize: Rect;
private var numberOfLines = 1;

function FormatString ( text : String )
{
	words = text.Split(" "[0]); //Split the string into seperate words
	result = "";

	for( var index = 0; index < words.length; index++)
	{
	    var word = words[index].Trim();

	    if (index == 0)
	    {
                result = words[0];
                guiText.text = result;
	    }

    	    if (index > 0 )
    	    {
		result += " " + word;
		guiText.text = result;
	    }

    	    TextSize = guiText.GetScreenRect();

            if (TextSize.width > lineLength)
            {
        	//remover
	        result = result.Substring(0,result.Length-(word.Length));
    	        result += "\n" + word;
	        numberOfLines += 1;
    	        guiText.text = result;
            }
      }
}

(And apologies too for WordPress’ god-awful code writing tools that creates really ugly code presentation)

Localising the Unity Project

Localising your text in Unity once the previous steps are done, is even easier than the last post.

Where ever you have text to localise, simply replace:


 //C#
public GUIText myText;
myText.text = "This is my text to localise";

//UnityScript
var myText : GUIText;
myText.text = "This is my text to localise";

with……


public String textID;
public String defaultText;
myText.text = EtceteraBinding.getLocalizedString( textID, defaultText );

Where…

  • textID is the stringID that you have in Column B of your Data Entry tab in the Localisation Register
  • defaultText is the text that should appear should your string reference not work – makes it easy for you to see during QAing. e.g. put in here “NOSTRING” as an example.

And that’s it!

We have made a little script that we literally throw onto any GUIText instances in the scene for quick localisation. It exposes the above textID and defaultText, so that’s all we need to enter in the Inspector once it’s attached:

UnityScript Version

#pragma strict

var textLabel : String;
var defaultText : String;
var thisObject : GUIText;

function Start () {
	LocaliseStrings();
}

function OnEnable () {
	LocaliseStrings();
}

function LocaliseStrings()
{
	thisObject.text = EtceteraBinding.getLocalizedString( textLabel, defaultText );
}

C# Version

using UnityEngine;
using System.Collections;

public class LocaliseMe : MonoBehaviour {

	public string textLabel;
	public string defaultText;
	public GUIText thisObject;

	void Start()
	{
		LocaliseStrings();
	}

	void OnEnable()
	{
		LocaliseStrings();
	}

	void LocaliseStrings()
	{
		thisObject.text = EtceteraBinding.getLocalizedString( textLabel, defaultText );
	}
}

When you’ve added strings for all text, simply make an APPEND BUILD (Command/Control B) and your App is localised.
You can also “localise” images, as well, by using getCurrentLanguage(); to grab the language and load an image accordingly.

So that’s all there is folks! Localisation is so easy when you have the right tools.

In the word’s of Google Translate, “Viel Glück!”

From Localisation Register to Xcode

Now that the .strings files for each language are in Xcode, we can start moving our strings from the Localisation Register into them.

This process is simple — very simple. In fact, I don’t know why we have an entire post dedicated to it.

  1. In the Localisation Register, select the entire COLUMN from your EXPORT tab
  2. Copy (Command/Control C).
  3. Navigate to the corresponding .string file in Xcode. e.g. Localizable.strings (English)
  4. Make sure you click inside the file’s Editor screen
  5. Paste (Command/Control V)
  6. Save (Command/Control S)
  7. Repeat steps 1 to 6  for each language

That’s it.

Now, I’m sure there’s a clever person out there who could automate this process somehow. But how can it be easier than copying and pasting?!

Time to head back to Unity for Localising the Unity Project

Setting Up Xcode for Localisation

Now we move on to getting Xcode set up ready to read your localised strings from Unity.

Build your project from Unity so that you have latest. When Xcode has finished its build process, follow these steps to set up your build for localisation. Follow each step and see how easy it is!

Step 1

With your Unity Project selected, create a new File

Create a New File

Step 2

Select String type file…

Select String File Type

Step 3

Name your file “Localizable.strings”. You MUST use this file name exactly as written.

Name File Localizable.strings

Step 4

Press Save and notice that it now appears here…

Save It

Step 5

Now look to the right of the Xcode Screen. You should see this button…

5_LocaliseIt

With the Localizable.strings file still selected as in Step 4, Press “Localize” now.

Step 6

Select the main language you are localising to, in this case, English…

5b_ConfirmLocalise

..and press the Localize button.

Step 7

Now where the “Localize” button once appeared from Step 4, the following now displays…

6_TickLanguages

Tick the boxes you wish to localise to. Don’t worry if a language isn’t listed… that’s coming up soon.

Step 8

If you look back to your original Localizable.strings file, you’ll now see that the other languages’ .string files have been created as children of the original file.

(NB: “English” = US English)

6b_LanguageFilesCreated

You can now go ahead and insert additional languages, by following these next steps.

Step 9

Make sure you have the project selected…

Add More Languages

Step 10

Navigate to the “INFO” tab of the project. You’ll see a “Localizations” section per below.

Add More Languages

Click on the “+” sign to add other languages. A drop down list of all available languages are shown. When you click on one, it will be added to the list in the “Resources” column.

To remove a language, simply press the “-” sign.

Step 11

Once step 10 is complete, you’ll notice that the languages you added have now had .strings files created for them.

Add More Languages

And that’s it! How easy is that?!

Now we can add our strings created in our Localisation Register. So let’s move on to From Localisation Register to Xcode.

Localisation and Localisation Register

Introduction

There’s nothing worse (we think) for a user of your App, to open it up only to find it isn’t using your native language. We get a little twitchy if we see “color” instead of “colour”, as an example. What’s more, localising your App into various languages will no doubt help push sales. People love that you cater for them and we believe it shows respect.

The game industry “standard” for minimal localisation are the following languages:

  • uk: UK English
  • en: US English
  • fr: French
  • it: Italian
  • de: German
  • es: Spanish

You should most definitely also consider localising into Chinese (Simple), Japanese, and other languages that are becoming more popular to include, such as Dutch and Portugese. We also include au: Australian English into ours, however, we’ve yet been able to set this up on our devices, which is odd. The reason we may want this is that some words we use to describe items, are different to what’s used in the UK or US.  Overall, when it comes to localisation and who to cater for, you’re only limited by your localisation budget.

Budget? Yes, please PAY someone to localise your text for you. Don’t rely on Google Translate – it doesn’t always get it right, particularly when it doesn’t understand the context of the words or sentences that you are plugging into it. Context can dramatically change translation. There are loads of Freelance Translators online these days who charge very reasonable prices for localisation, based on the number of words you require translations for. And the majority of these people’s language that you require are natives to that language, so the translations should be far more reliable. It’s always a good idea to submit text with context/descriptions next to those whose context might be difficult to interpret on paper alone.  For example, “Close”. Is it “close that book”, or “I’m close to the target”?

Don’t under-estimate professional translation services… Bad translation = “We don’t respect your language” in our eyes.

The Localisation Register

Before doing any work in X-Code, you should always have a Localisation Register. This is a spreadsheet (we use Excel) of all text that requires localisation. From front end, to native pop-ups, to your App’s name and every where that text is used.

Don’t just write it straight up into the Loclization.strings file in X-code, for these reasons:

  1. You might accidently build over the X-code project, thereby losing all of your Localisation strings.
  2. It’s a terrible way to track localised strings – what is localised and what’s not – thereby opening up room for ghastly errors
  3. It forms a part of ‘best practice’ in professional game/app development. The Localisation Register should be an Appendix to your App’s “Design Document” or as we call it, “App Blueprint”. Planning properly from the beginning will save you so much time in the future.

What is the Register?

The Localisation Register is a spreadsheet that contains the following headers:

  • Class (where we stipulate whether this particular row is a Comment or Variable)
  • Key (the String type key/legend for each line that is referenced in our code in Unity)
  • Column for each language (contains the text to display for that Key or String e.g. each column is named US, UK, AU, FR, DE, IT, ES)

 

Our Register also has TWO (2) Tabs, named:

  • Data Entry
  • Export for .strings

Data Entry Tab

The Data Entry tab is where we enter in all of our text. Where we don’t have translations yet, we put “TODO”. That way, in-game during testing on other languages, if we see “TODO” we know that there is a translation missing for that line of text.

Here’s a snapshot of our spreadsheet:

The data entry tab in our Localisation Register

The data entry tab in our Localisation Register

As you can see, for the class we use either “Comment” or “Variable” to describe what each row is. A “Comment” row is just that – a comment you want to be inserted into your final Srings file in X-code. A Variable row is the actual string reference you want in your Strings file.  When we enter either “Comment” or “Variable”, our spreadsheet automatically detects it and colours the row to make things easy to read. This is an Excel function, and we won’t be going into that here (Google for Excel tutorials on this).

For the Key in column B, use only one word (no spaces). We use underscores, as well, to help make things easy to reference. e.g. Loading_ for loading screens, Menu_ for menu items, and so on.

Disclaimer: For the sake of this How-To we DID use Google Translate for the above translations 😛 Don’t do this for your final App!!! (We’re not sure if they’re right or wrong lol)

Export for .Strings Tab

The Second tab, called Export for .strings is a direct copy of the above tab, except it contains a formula that reads each row Class type, and formats / spits out the correct variable for each string as it needs to be written in the .strings file.

A string in the .strings file must use the following format:

“myStringID”=”This is the text for my string.”

Obviously, having to put your strings into the format above by hand is a pain in the posterior. Hence, why we created the following formula to do it for us. This then frees us up to change text willy-nilly without having to worry about the format output.

This is a snapshot of our Export tab:

Where the magic happens These columns are copied and pasted into each Localizable.strings file for each language

Where the magic happens
These columns are copied and pasted into each Localizable.strings file for each language

Note that each time you insert or delete rows, or change the order of rows in the Data Entry tab, you will need to Refresh or recopy and paste the formula down the columns in the Export tab in order for it to update correctly.

The Formulas

In row ONE (1) ONLY, from Column C ONWARDS we have the following formula in all cells from C1:F1 (note that B is just empty for us – you don’t need it. And there is no formula in cell A1)

&amp;lt;br /&amp;gt;="/*"&amp;amp;amp;'Data Entry'!D1&amp;amp;amp;"   Localisation Copyright YOURNAMEHERE 2013"&amp;amp;amp;"*/"&amp;lt;br /&amp;gt;

Of course, replace “YOURNAMEHERE” with your Copyright name if you wish. You can remove “Copyright” as well, and insert with any text you want at the top of your .strings file.

Note that in the above formula, and those below, ‘Data Entry’ is the name of the TAB. If you don’t call your first tab “Data Entry”, then you will have to change all formulas we give on this page to use the name you use for your data entry tab.

In Column A (all rows) we simply have the basic formula as follows, just to copy the Class name:

&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;=+'Data Entry'!A2&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;

Finally, the most important, formula we use is the one that creates the final OUTPUT text for the .strings. Be sure that you have the same number of columns in your EXPORT tab as appears on your DATA entry tab. From cells C2:I5 (or how ever many languages and rows you have – essentially all rows and columns that are left over), copy the following formula:

&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;=IF($A2="Variable", (""""&amp;amp;amp;'Data Entry'!$B2&amp;amp;amp;""""&amp;amp;amp;" = "&amp;amp;amp;""""&amp;amp;amp;'Data Entry'!C2&amp;amp;amp;""""&amp;amp;amp;";"), "/*"&amp;amp;amp;'Data Entry'!$B2&amp;amp;amp;"*/")&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;

This formula will automatically format your data from the DATA tab into the correct format, ready for you to copy over to your .strings files. (It’s quite a long function, so make sure you copy it all from above.)

Your Export tab should look like the one above. If you notice any errors, it’s likely you have incorrectly copied a formula or put a wrong entry into the Data tab. Note that in the above example, we’d be deleting row 6 – it’s showing that there is no data and the row above it is the final data entry, so there’s no need to have row 6.

Next, we’ll move to Setting Up Xcode for Localisation.

Accessing and Posting to Game Centre

1. Return to your  Game Centre “Button” script we asked you to open at the start of this How-To, as we’re about to launch Game Centre!

2. In your script, add the following:

//OPEN GAME CENTRE
GameCenterBinding.showAchievements()

3. Now when your Game Centre button is clicked, Game Centre will open. But don’t do that just yet, let’s move on….

4. For the sake of testing, let’s set up unlocking your first Achievement when the user clicks on another button. Select a quick-to-navigate-to button, that won’t load up a new scene, such as a Help button that you may have in your title menu. If you have no buttons, just make a fake one for now for the sake of testing Achievement unlocks

5. In the script that you use to activate the button in step 4, add the following line of code:

GCManager.ReportAchievement("YOURACHIEVEID");

Note that “YOURACHIEVEID” must be EXACTLY what you entered for the Achievement’s ID in iTunes Connect.

Alternatively, if you have an achievement that requires progress or a collection in order to unlock it (e.g. collect x items, and it shows the progress of those items collected, call the following:

GCManager.ReportAchievementProgressive("YOURACHIEVEID", 25.0f);
//Note the float number provided must have "f" next to it
//Where "25.0f is the percentage of this particular instance - i.e. it's 25% complete.
//e.g. If you have 100 items to collect in order to gain the achievement,
//in each instance when the player collects that item, you would call the above
//and insert the number "1.0". 

6.The above script will call on the method from the GCManager.cs script. Don’t test just yet… we want to set a score to the Leaderboard.

7. Now we’ll add in the call to update the Leaderboard with a new high score. If you don’t already have end-game functionality set up for your scores yet, then just choose some other random button that you can fire this score posting from. Add the following line to your end-game or button script:

GCManager.UpdateLeaderWithScore(thescore);

Of course “thescore” should be a LONG integer type. You can either just enter in any random number for now for the sake of testing, like “9999999”, or add the string variable that your score posts to. We’ll let you sort that bit out…

So that’s it! You’re all set-up and ready to go. That wasn’t so painful was it? 😛
Time to Test Unlocking Achievements and Leaderboard!

Prime31 Game Center Plug-in Set-up

A reminder that these plug-ins only work for Unity3D Pro. Also, please excuse the formatting of the code provided. WordPress doesn’t like code 😉

If you still haven’t purchased it, head over to their website now.

(DISCLAIMER! Note that we are in no way affiliated with Prime31, we just use nearly all of their plug-ins! This guide is only intended for instructional purposes. If you have any technical issues, please contact the plug-in owner or Unity.)

From here on we won’t be showing screen grabs of what to do. First, because we want to get this process down on e-paper first, and we may or may not return later to add pretty pictures. We’re big believers in visual aids such as photos, when demonstrating something, so if you really, really need them, then say so! Secondly, we’re assuming that you know Unity, live and breathe Unity, and if you’re at the stage of setting up Game Centre for your app, should know your way around. If you don’t, we suggest you visit the Unity3D website for their wonderful tutorials. Otherwise, read on…

We’ll keep these steps as basic as possible…

Step-by-Step Set-Up

1. Import the Prime31 Game Center Package to your project.

2. We recommend (out of habit) getting latest on the plug-in by navigating to the Prime31 Menu item at the top of screen and selecting “Update”.

3. Perform a NEW FRESH BUILD to implement the system into X-code. Do NOT do an append build (e.g. Control/Command B). Yes, this will mean if you have localisation strings in your X-code build you will have to insert them again after this step. So BACK UP your localisation strings. This is another area fit for another How-To page that we may set up later, but you should always have  a “Localisation Register” for your App in spreadsheet format…. We have a special one we use with sweet formulas that format our text exactly how it needs to appear in the String files… more on that another day.

4. When the build is done, navigate to the Scene where you want Game Centre to appear. Usually a user will have to click on a button/link to open Game Centre. Open up the script you use for this button/link’s functionality. We’ll call this your “Button Script”.

5. In your project, we highly recommend setting up (if you haven’t already) a blank scene that is the FIRST TO LOAD. You will need a small script in this blank scene that navigates to the opening scene of your app (e.g. the title) straightaway.  This opening blank scene will contain your Game Centre scripts we’re about to make. In our project, it also contains prefabs for our enemy pooling system we use in-game, so that they are loaded from app log-in. Of course, these and your game centre scripts, will need to call the method/function: DontDestroyOnLoad.

6. We created two scripts in C#: “GCManager.cs” and “GameCenterEventListeners.cs”. The Listener script we grabbed from the Prime31 demo folder. You’ll find this folder in Plug-ins > GameCenter.  Save your GCManager.cs and Listener.cs scripts to the Plug-ins > GameCenter folder.

7. Open up GameCenterEventListeners.cs and add the following code adjustments to the script:

void playerLoggedOut()
{
     GameCenterManager.achievementsLoaded -= achievementsLoaded;
     Debug.Log( "playerLoggedOut" );
}
void reportAchievementFinished( string identifier )
{
     Debug.Log( "reportAchievementFinished: " + identifier );
     //Update the list on completion of the successful achievement....
     GameCenterBinding.getAchievements();
}

The above adjustments will ensure that upon the event of a player logging out of Game Centre (via the Game Centre app on the device), that their achievement data is removed. When another player logs IN to Game Centre, the event will trigger an update to the achievement list according to what that player has unlocked. Note that getAchievements() only grabs those achievements that have been unlocked by the player.

8. Add this GameCenterEventListeners.cs script to a gameObject in the scene.

9. Create a new CS script and name it whatever you like, but for clarity we suggest something like “GCManager.cs”. Save this script to the Plug-ins > GameCenter folder.

10. Open the script and enter in the following

(Note: for C# users, don’t forget that your CLASS name should be the same as the script name – Unity will yell at you anyway if you don’t do that):

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Prime31;

public class GCManager : MonoBehaviour {
	
	#if UNITY_IPHONE
	public static string playerName;
	public static long playerHighScoreGC;
	public long _score;
	public static bool idFound;
	public static List<GameCenterAchievement> _achievements;
	
//	Initialise Game Centre
	void Awake () 
	{
		GameCenterBinding.isGameCenterAvailable();	
	}
	
	void Start()
	{
		if (GameCenterBinding.isGameCenterAvailable() )
		{			
			GameCenterBinding.authenticateLocalPlayer(); //Must Have
			GameCenterBinding.showCompletionBannerForAchievements(); //Display banners on Achievement Unlocks...
			GameCenterBinding.loadReceivedChallenges();
		}	
		
		//Listener
		GameCenterManager.achievementsLoaded += achievementStatus =>
		{
			_achievements = achievementStatus;
		};	
	}
	
	
//	Update the GC Leaderboard. The leaderboard will determine if the score is higher than what's already been previously posted and will only post a new high score automatically, thereby stopping the achievement banner from reappearing if subsequent repeats of achievement parameters are fulfilled.
	
	public static void UpdateLeaderWithScore (long myScore)
	{
		GameCenterBinding.reportScore(myScore, "YOURLEADERBOARDID");	
		Debug.Log ("New Score reported to GC");
	}
	
	
//	Update the GameCentre with a single parameter new achievement
	public static void ReportAchievement(string id)
        {		
		if (_achievements !=null)
		{
    		foreach (GameCenterAchievement i in _achievements)
    		{		
				idFound = false; 
				
				//search for achievement id in the list
				if (!idFound) 
				{
				
    				     if ( i.identifier == id)
    				     {
					Debug.Log ("Achievement already unlocked "+i.identifier);
					idFound = true;
					break;
				     }
				}
			}
		}
		
		//First achievemeent ever to be unlocked or achievement id not in the list of unlocked achievements	
   		if (_achievements == null || !idFound) 
		{
		     Debug.Log("Unlocked Achievement: "+id);
       	 	     GameCenterBinding.reportAchievement( id, 100.0f );
		}
	}	
	
	
	// Called for achievements where progress to completion occurs over time (e.g. collection of x items)
	public static void ReportAchievementProgressive(string id, float progress)
        {		
		if (_achievements !=null)
		{
    		     foreach (GameCenterAchievement i in _achievements)
    		     {		
				idFound = false; 
				//search for achievement id in the list
				if (!idFound) 
				{
				
    				     if ( i.identifier == id && i.percentComplete == 100.0f)
    				     {
						Debug.Log ("Achievement already unlocked "+i.identifier);
						idFound = true;
						break;
				     }
					
				     else if (i.identifier == id && i.percentComplete < 100.0f)
				     {
						float idProgress = i.percentComplete;
						float updatedProgress = 0.0f;
						idFound = true;
						
						if (idProgress+progress > 100)
						{
							updatedProgress = 100.0f;
							GameCenterBinding.reportAchievement(id, updatedProgress);
							Debug.Log ("Progressive Achievement is now Complete! Check: "+(updatedProgress)+" percent");
							break;
						}
						else
						{
							updatedProgress = idProgress + progress;
							GameCenterBinding.reportAchievement(id, updatedProgress);
							Debug.Log ("Progressive Achievement is now: "+(updatedProgress)+" percent complete!");
							break;
						}					
					}
				}
			}
		}
			
   		if (_achievements == null || !idFound) //First achievemeent ever to be unlocked or achievement id not in the list of unlocked achievements
		{
		     Debug.Log("Unlocked Achievement: "+id);
       	 	     GameCenterBinding.reportAchievement( id, progress );
		}
	}
	
	//Reset this profile's Achievements. Throw a button in your scene if you want your player's to have the ability to reset all of their Achievements.
        //Also handy to have for testing purposes.

	public static void ResetAchievements()
	{		
		GameCenterBinding.resetAchievements();
	}
	#endif
}


11. In the script above, replace : “YOURLEADERBOARDID”.. with the ID of the Leaderboard that you created in iTunes Connect.

12. Add the GCManager.cs script to your Game Centre gameObject in the scene where the Listener script is attached.

13. Remember to add the DontDestroyOnLoad() function to one of the above scripts, or a separate script if you prefer, and attach it to your Game Centre gameObject in the scene

14. Now check your PlayerSettings in Unity to ensure that you have the EXACT Bundle Identifier as in iTunes Connect for your App, and the correct Bundle Version Number as submitted in iTunes Connect. Not doing this is guaranteed to cause you grief!

Note that the above GCManager script will ensure that once the Achievement Banner is displayed for the INITIAL unlock of an Achievement, it will NOT display again if the parameters of that Achievement are met again.

This concludes the initial Set-Up of the Game Centre Plug-in. Now we’re on to Accessing and Posting to Game Centre, then finally, Testing!