Language Localization in Unity

One of the main ways you interact with the player in your games is through language. This post is focusing on written text, but could easily be adapted to spoken words and audio files.

If you are writing a game, for a jam or just for fun, you may not be all that concerned with language localization. I know I never was. Then, I realized just how easy it is implement. I don’t think I’ll ever write a game without it again.

The way I’ve chosen to implement localization, and there are many ways, also gives me some added benefits that I find extremely useful. I create the base for the whole system, a localization class that is instantiated as a singleton. Immediatly, this gives a huge benefit, in that I can access the entire localization system from anywhere in my code statically, without any references to it. So there are no variables to pass and track, no searching for components or game objects to find it. It’s just there an ready for use.

There is a sneaky side benefit to this as well. In the instantiation code for the singleton class, I can do a single Unity search for the object that does the actual feedback to the user, and store it. So now, I can access the localization system and the feedback system from the same singleton. One function call can get localized text and display it to the user.

The basis for how this all works, is that I generate multiple language files that pair keys to text. I’m currently using JSON formatting on the files, because it’s easy to deserialize in C#. You could just as easily use CSV, or XML if you are more comfortable.

They keys can be anything that is comparable. I typically use strings, as I find it easy to make them meaningful for context. I also tend to make a static class structures to utilize as a reference in the code, so I don’t have to memorize the keys, or keep looking back at the language files. You could just as easily use integers or an enum for keys.

My typical language file would look something like the following. I identify the locale as a key. There are plenty of ways to do this, I’ve chosen to use the IETF language tag. I’ve also added a localeName field to represent a plane text name for the language, that would, of course, be in the language of the file. Finally, there is a list of key/value pairs that link the (in this case) string keys to the actual text values to be displayed to the user.

{
"locale":"en-us",
"localeName":"US English",
"items":
[
{"key":"game.title","value":"My Test Game"},
{"key":"object.interact","value":"You interacted with an object"}
]
}

Deserializing (loading) JSON into a class structure in C# is fairly easy for this simple of a structure. There are a couple classes needed. The first class is the base that is going to hold all data from the file. It has fields that correspond to the keys from the JSON file (local, localName, and items). The second class is the one that will hold each item from the list of items in the JSON file. It needs to have corresponding fields (key and value). There’s nothing special about these field names. They simply need to match what you use in the JSON file. The key here is the “[System.Serializable]” attribute applied to each class. This lets the C# serialization code do the work for you.

[System.Serializable]
public class LocalizationData
{
    public string locale;
    public string localeName;
    public LocalizationItem[] items;
}

[System.Serializable]
public class LocalizationItem
{
    public string key;
    public string value;
}

The next thing we’ll need is some code to read the file and build the objects. There are a few tricks here that I’ll walk through. The first highlighted line has a couple things going on. First, I’m using Path.Combine to to concatenate path elements into a valid path for the operating system to use. The first argument is also interesting. Application.streamingAssetsPath is a special path in your project. Create a folder in your Assets folder named StreamingAssets. Spell it exactly like that, capitalization included. Now, when your game is executing Application.streamingAssetsPath will always refer to the full path of that folder. You don’t need to worry about where it actually is on the operating system. In the highlighted line below, I’m also appending a folder named Languages where all the language files are at.

The second highlighted line is what actually converts the JSON formatted text data that is held in fileData into a LocalizationData object. This converts contents of the JSON text file into the object structure we created earlier. The items field is an array of type LocalizationItem. That’s not the best format for the data, and the way we want to use it, so we’ll convert it to a Dictionary<string, string>.

   private Dictionary<string, string> stringSet = new Dictionary<string, string>();
 
   private void LoadLanguage(string filename)
    {
        string fullPath = Path.Combine(Application.streamingAssetsPath, "Languages", filename);

        if (File.Exists(fullPath))
        {
            string fileData = File.ReadAllText(fullPath);
            LocalizationData data = JsonUtility.FromJson<LocalizationData>(fileData);

            locale = data.locale;
            language = data.localeName;

            stringSet.Clear();

            for (int i = 0; i < data.items.Length; i++)
            {
                stringSet.Add(data.items[i].key, data.items[i].value);
            }
        }
        else
        {
            Debug.LogError("File doesn't Exist: " + fullPath);
        }
    }

Now that the language file is loaded into a dictionary, getting language specific versions of strings is simple.

    public string GetLocalizedString(string key)
    {
        string retVal;
        if(stringSet.TryGetValue(key, out retVal))
        {
            return retVal;
        }
        return "";
    }

Now, this works just fine the way it is, but as I said, I like to create an object tree to make using the localization easier. No need to remember the keys, they are all tied to objects.

public static class TextKey 
{
    
    public static class Game
    {
        public static readonly string Title = "game.title";
    }

    public static class Object
    {
        public static readonly string Interact = "object.interact";
    }
}

Pop it all together in a singleton, and you can get language localized strings quick and easy, from anywhere in your code. The following will print text associated with the game.title key to the debug log. In my case, it will print, “My Test Game”.

Debug.Log(Localization.instance.GetLocalizedString(TextKey.Game.Title));

One added benefit… All displayed text is in one place, not scattered all over the code. If some piece of texts needs to change, I don’t need to scour the code looking for it. It’s a simple change in one file per language. Couldn’t be simpler.

What is a SINGLETON?

By definition, a singleton, at least in terms of object oriented programing, is a class that can have only one instance. That one instance is stored and referenced statically.

Here is the code necessary to create a simple singleton.

public class GameData
{
    #region Singleton

    private static GameData s_Instance;

    public static GameData instance
    {
        get
        {
            if (GameData.s_Instance == null)
                GameData.s_Instance = new GameData();
            return GameData.s_Instance;
        }
    }

    private GameData()
    {

    }

    #endregion

    // Your code here...

    public int hitPoints;

}

The code above creates a simple singleton class. Of course, if you are using this in Unity, you may want to use some of the standard Unity functions, such as FindObjectOfType. In this case, you will need to inherit from one of the standard Unity classes, such as ScriptableObject. These classes don’t like to be instantiated with new. Instead, they rely on function calls to create instances, so the code needs to be modified to accommodate that.

public class Localization : ScriptableObject
{
    #region Singleton

    private static Localization s_Instance;

    public static Localization instance
    {
        get
        {
            if (Localization.s_Instance == null)
                Localization.s_Instance = ScriptableObject.CreateInstance<Localization>();
            return Localization.s_Instance;
        }
    }

    #endregion

    // Your code here...

    public string language;
}

The region/endregion statements are not necessary. They are there simply to allow the whole region to be collapsed in the IDE. Add whatever other code you want to be part of the class, and you are ready to go.

In this example, you can refer to the string language from anywhere in your code by accessing the static instance of the Localization class as in the following example:

Localization.instance.language = "en-us";

Debug.Log(Localization.instance.language);

This code can be called from anywhere in your program, without the need to instantiate an object. The first time access Localization.instance, it is instantiated.

Global Game Jam 2019

Last weekend, I participated in the 2019 Global Game Jam. I’ve participated in Ludum Dare several times in the past, but always as an individual. This jam is a little different. You go to a jam event location to participate. Everyone at the event location watches a keynote video that announces the theme for the jam. You are then encouraged to form teams and come up with ideas.

I went to to the jam location not knowing anyone there. I ended up getting paired with another lone jammer, who did know a couple folks there. We started brainstorming ideas, and as the night went on, several other lone jammers came to the event, and ended up on our team. In the end, we had four people on our team who had never met each other before.

We came up with some rather ambitions ideas for a game, and we were able to implement most of them during the jam. We made a first-person puzzle game, where you had to roam around rooms on an abandoned spacecraft trying to escape. The twist is that you are a disembodied AI (Artificial Intelligence). You don’t have a body to move around, so you have to interact with the environment by taking control of other technology.

You start off controlling a security camera, and move on to three other robots that have varying skills. There is a garbage robot who can carry small things. A loader robot that can move heavy things, but can’t manipulate delicate things. A very complicated repair robot, that can, well, not do much because only its head is left. He’s so damaged, all he can do is shine a light. Now, a light is very important for levels where there is no outside illumination.

You need to work your way through the ship to get to the core, where you can physically take your consciousness and move it to an escape pod. The final puzzle never got fully implemented, as there was to be a moral dilemma at the end. But, no spoilers here. In case the game ever gets completed.

Overall, the experience was a good one. I met some very smart, talented people who also like to create video games. We created a good base of a game, that has potential to be expanded and completed. We survived a pretty big snowstorm that impeded our ability to meet in person on the final day. I learned a few things, and created some new standards for myself for future game designs.

Most importantly, I had fun. I’d definitely do it again. Thanks to Nate, Elliott, and Brian for making the weekend the fun event that it was. Alone, I never could have completed what we did as a team.

Check out our game at our GGJ 2019 submission.

I Suck at Drawing

I’ve never been good at drawing. Art has never been my forte.

This has been a problem for my game development. Having an amazing game can make up for not so great art. Having great art can make up for a not so great game. Unfortunately for me, my mediocre games don’t benefit at all from my lousy game art.

I’ve tried to do better. Since I really can’t draw, I’ve attempted to do more pixelated style game art. All of the games I’ve developed thus far, have had really bad art. Just take a look at the examples littered around this page.

Now, I’m not one for resolutions. I don’t see why there is one time of year for making changes. I just happen to be doing this at the beginning of the year. I’ve decided I want to improve my drawing skills. I’m watching videos, and following tutorials. What I’m really hoping will help the most is that I’m trying to practice for a bit EVERY day. Just drawing, and accumulating some experience.

I’m looking for resources that help with understanding elements that make up different art styles. I don’t expect to ever be drawing photo realistic portraits. I’ll never be van Gogh. I’m OK with that. I just want to make some artwork for my games that doesn’t make me cringe to look at. I mean, if I don’t want to look at it, why would anyone else?

My first goal is to learn some techniques for drawing simple art styles. I plan on doing some research about different art styles used in games. I’ll try to learn some techniques for drawing this art. I’ll attempt to practice daily.

Something else I plan to do is utilize my practice art to make simple game-like objects. Basically, I’ll drop some of this art into Unity and practice making animations, and interactions. This way, I’ll be exercising my art skills, and getting in some Unity coding practice as well.

Now, I’m a gearhead. No doubt about that. So I am using some fancy tools to try to help my progress. I got myself a Wacom smart stylus, and an inexpensive drawing app for my iPad. Now, the rest is up to me…

How to Ping from Python

Here’s a quick tip on how to Ping from Python!

Today, I had a need to do a quick ping test from a Python script. It took me a bit of searching to figure out how to do it properly. Most of the examples I saw in the wild ended up displaying results to the screen (or stdout), which is not at all what I was looking for.

Here is what I ended up with:

#!/usr/bin/python -tt
import platform
import subprocess
import sys  # This import is only needed to get command line arguments.

def ping_test(host, ping_count=1):
  
  #
  # Some systems use different parameters for ping count
  # Linux and MacOS use -n, Windows uses -c
  # Adjust this as necessary for other systems
  #
  count_param = '-n' if platform.system().lower() == 'windows' else '-c'
    
  #
  # subprocess.Popen takes a list of parameters, starting with the command to run
  #
  command = ['ping', count_param, str(ping_count), host]
  
  #
  # When calling subprocess.Popen, we are redirecting stdout and stderr to PIPE
  #
  # This will cause the proces to return a tuple of (stdout, stderr) when communicate
  #   is called
  #
  # We do this even if we don't want or need the results, so it doesn't display to
  #   screen while executing
  #
  process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  result = process.communicate()
  
  # Use the Popen variable (i.e. process) to get the return code.  The output from the
  #   command is held in result, as described above.
  #
  # In this example, we are only looking at stdout.  We could also get stderr as result[1]
  #   if that were interesting to us.
  #
  return (process.returncode, result[0])
  


#
# This function is just to get command line arguments, and demonstrate how to call
#   The ping_test function.
#
def main():
  args = sys.argv[1:]
  
  if len(args) < 1:
  	print 'Usage: %s host-or-ip [count]' % sys.argv[0]
  	return
  
  return_code, result = ping_test(*args[:2])
  
  print 'Ping Successful' if return_code == 0 else 'Ping Failure'
  print result

#
# Standard boilerplate to call the main() function.
#
if __name__ == '__main__':
  main()

2018 Holiday Break

Every year, in December, I end up taking some much needed vacation time. During this time, I often work on projects and spend time with the family. 2018 was no different. This time around, I had a few objectives.

I decided it’s finally time to learn Python. I took a look at it a few years ago, and pretty much just shrugged it off. I’ve been programming in C/C#/Java/Swift and similar languages for a LONG time. It’s been pretty easy to switch between them, as they all have a basic structure. Blocks are defined by curly braces {}. Declare strongly typed variables. You know, the basics.

Then, along comes Python. Where are my curly braces? No variable declarations? WHITESPACE MATTERS?!?!? What gives?

Well, I finally got some time to give it a try, and I have to say, I’m liking it. I’m still not sold on the structure, using indenting instead of curly braces for blocks. I also struggle with not declaring variables, as I often can’t figure out what variable types are. Then I learned about the slice. Such a simple, elegant thing. How much Java code would it take to print the last 6 letters of a string reversed? I’m sold!

I started dabbling in machine learning. It’s a fascinating topic with many real-world applications. I’m just beginning to learn the fundamentals. I have a long way to go. I do have several ideas that I want to purse, so I’m sure you’ll see more here in the future.

I also launched this website. I’ve been wanting a place where I can share ideas, projects, nonsense, etc. I finally decided to just get this thing going. So, here it is!

I’ve got to go read up some more on multiple linear regression, and K nearest neighbors. Wish me luck!

Hello!

#!/usr/bin/python -tt

def main():
print('Hello World')

#Standard boilerplate to call the main() function.
if __name__ == '__main__':
main()