teaching machines

Custom Resources in a Unity Android Project

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.

Comments

  1. Cristina says:

    Hi, I have several LinearLayout one in other. What do I do for get work it?

    Thank you.

    Sorry for my bad english.

    1. Chris Johnson says:

      You should be able to get the layout ID with something like this:

      int layoutID = getResources().getIdentifier("my_linear_layout", "layout", getPackageName());

      At this point, you can use the ID like normal, passing it to setContentView, inflating it, and so on.

      1. Cristina says:

        I need a example:

        How can I get the ID and how add at addcontentview?

        1. Chris Johnson says:

          I told you how to get the ID and the example is above.

          1. Cristina says:

            I need know if I have declarate the layout with other layout . and if I have that do several addcontentview for each layout.

  2. Cristina says:

    viewerID = mActivity.getResources().getIdentifier("viewer", "id", mActivity.getPackageName());
    bsubrayarID = mActivity.getResources().getIdentifier("bsubrayar", "id", mActivity.getPackageName());

    //linearID = mActivity.getResources().getIdentifier("linear", "layout", mActivity.getPackageName());
    relativeID = mActivity.getResources().getIdentifier("main", "layout", mActivity.getPackageName());
    rel1ID=mActivity.getResources().getIdentifier("rel1", "layout", mActivity.getPackageName());
    lfooterID=mActivity.getResources().getIdentifier("footer", "layout", mActivity.getPackageName());

    // botonID = mActivity.getResources().getIdentifier("cerrar", "id", mActivity.getPackageName());
    RelativeLayout lout = (RelativeLayout)LayoutInflater.from(mActivity.getApplicationContext()).inflate(relativeID, null);
    RelativeLayout rel1 = (RelativeLayout)LayoutInflater.from(mActivity.getApplicationContext()).inflate(rel1ID, null);
    LinearLayout lfooter = (LinearLayout)LayoutInflater.from(mActivity.getApplicationContext()).inflate(lfooterID, null);

    pdView = (ContentView)lout.findViewById(viewerID);
    //subrayar = (Button) lout.findViewById(bsubrayarID);
    // cerrar = (Button) lout.findViewById(botonID);

    //cerrar.setClickable(true);

    mActivity.addContentView(lout, new RelativeLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
    mActivity.addContentView(rel1, new RelativeLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
    mActivity.addContentView(lfooter, new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));

    Doesn’t work.

  3. Suneet says:

    This was an extremely useful blog post for me.. creating my first Unity android plugin. Thanks a lot, really useful!

  4. Suneet says:

    Any idea if there is a big/noticeable performance hit to the Activity in having to lookup the resources manually rather than using the R class?

    1. Chris Johnson says:

      Lookup only needs to happen once, in onCreate.

  5. pradeep says:

    How do I use Drawable crossDrawable = getContext().getResources().getDrawable(R.drawable.com_facebook_close);

    I keep getting 03-27 14:08:25.803: E/AndroidRuntime(1667): Caused by: android.content.res.Resources$NotFoundException: Resource ID #0x7f020006

    1. Chris Johnson says:

      It appears that you didn’t read the article above. R.drawable.com_facebook_close isn’t going to work. Unity will generate different IDs when it merges the resources.

  6. Christa says:

    I tried copying my /res folder from the Android project into the Assets/Plugins/Android folder of the Unity project, but even using identifier lookup as suggested (via getResources().getIdentifier()), I got this:

    W/ResourceType(16657): No package identifier when getting value for resource number 0x00000000
    W/System.err(16657): android.content.res.Resources$NotFoundException: String resource ID #0x0

    Any suggestions?

    I’m wondering if the use of UnityPlayerActivity makes the difference, since I’m not using an Activity from Android, just accessing classes via AndroidJNIHelper.

    1. Chris Johnson says:

      Christa, sounds painful, doing all that work outside the context of your Activity. I can only guess that the invoking object is not what it should be, or the package isn’t what it should be, or… I dunno.

      A debugging step could be to use Java’s reflection stuff to see what fields are declared in class R.id.

      1. Christa says:

        Yeah, I’m trying to convert an Android Library Project to Unity plugin, and it is pretty crazy-making.

        I’ll try reflection to get a look at R.

  7. Jack says:

    Thanks a lot for sharing this, saves me a lot of time. GL

  8. Igor says:

    Man, you save my life)) Thanks you

Leave a Reply

Your email address will not be published. Required fields are marked *