teaching machines

Custom Resources in a Unity Android Project

October 8, 2012 by . Filed under public, reader animated.

The Unity documentation on building custom Android plugins is terse, but it does tell me how to package up my Java classes and manifest and bundled them in to my Unity project:

The resulting .class file(s) should be compressed into a .jar file and placed in the Assets->Plugins->Android folder. Since the manifest dictates which activity to launch it is also necessary to create a new AndroidManifest.xml. The AndroidManifest.xml file should also be placed in the Assets->Plugins->Android folder.

It’s less clear how to package up resources, assets, and libs, but through forum-scrounging and trial and error, I’ve learned that I can simply mirror res, assets, and libs in Assets/Plugins/Android. With those in place, Unity will merge its own resources and other files with mine.

One drawback to this merge is that my R.class file in my *.jar file is not brought over. (Even if I include it, Unity appears to ignore it.) Instead, a brand new R class will be generated. That means my R.*.* identifiers will not work. The workaround is to query for the IDs dynamically, like so:

int textViewID = getResources().getIdentifier("caption", "id", getPackageName());
TextView textView = (TextView) findViewById(textViewID);

// I'd rather do it this way:
// TextView textView = (TextView) findViewById(R.id.caption);

The code above assumes I’m in my custom Activity class.

Custom Layouts

One of the resources I wanted to bring over was a custom overlay that dropped on top of the Unity game screen. I created a simple FrameLayout with a ToggleButton on it:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
  <ToggleButton
    android:id="@+id/toggleButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal|bottom" />
</FrameLayout>

Getting this to appear on top of the Unity game screen turned out to be pretty easy. I inflate the XML (overlay.xml) and addContentView in my onCreate method:

int layoutID = getResources().getIdentifier("overlay", "layout", getPackageName());
int buttonID = getResources().getIdentifier("toggleButton", "id", getPackageName());

FrameLayout layout = (FrameLayout) LayoutInflater.from(this).inflate(layoutID, null);
button = (ToggleButton) layout.findViewById(buttonID);

addContentView(layout, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

The ToggleButton appeared beautifully at the bottom of the screen. It toggled and I rejoiced.

Custom Strings

Next up I wanted to draw custom strings depending on the locale. I added the following to res/values/strings.xml:

<string name="line0">ay</string>
<string name="line1">bee</string>
<string name="line2">cee</string>
<string name="line3">dee</string>

I made Spanish versions in res/values-es/strings.xml:

<string name="line0">ah</string>
<string name="line1">bay</string>
<string name="line2">say</string>
<string name="line3">day</string>

Pulling them out sequentially on toggles was pretty straightforward. Here’s the complete Activity:

public class MainActivity extends UnityPlayerActivity {
  private int n;
  private ToggleButton button;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    int layoutID = getResources().getIdentifier("overlay", "layout", getPackageName());
    int buttonID = getResources().getIdentifier("toggleButton", "id", getPackageName());

    FrameLayout layout = (FrameLayout) LayoutInflater.from(this).inflate(layoutID, null);
    button = (ToggleButton) layout.findViewById(buttonID);

    addContentView(layout, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

    button.setOnCheckedChangeListener(new OnCheckedChangeListener() {
      @Override
      public void onCheckedChanged(CompoundButton buttonView,
                                   boolean isChecked) {
        Toast.makeText(MainActivity.this, "Checked: " + isChecked + " -> " + getNext(), Toast.LENGTH_SHORT).show();
      }
    });

    n = 0;
  }

  public String get(int n) {
    return getString(getResources().getIdentifier("line" + n, "string", getPackageName()));
  }

  public String getNext() {
    String s = get(n);
    n++;
    if (n > 3) {
      n = 0;
    }
    return s;
  }
}

Switching locales dynamically works just fine. I start reading from the other locale’s resource directory on the next toggle after a locale switch.