Pages

getteammates.com

Tuesday, 10 February 2015

Integrating Android UI controls in Unity3D applications by creating a Java plug-in




Let me show you how to add android controls on top of unity activity in the unity application.

The steps to achieve the same is given below,
1. layout xml file
2. jar library with a java class which adds the control on top of unity activity, this would also pass the event back to unity
3. Manifest file which forwards the events to java

The sample code

We will create a simple unity application with an android button and checkbox. Button will be used to change the background color
and checkbiox will be used to lock or unlock the background color chaning functionality.

The Layout file

Let me create a simple layout xml file with a button and a checkbox.
Button and checkbox will be displayed at the center of the screen.
Let me name the xml file as android_layout.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="center_horizontal|center_vertical"
    android:gravity="center_horizontal|center_vertical"
    android:orientation="vertical" >
    
    <Button android:id="@+id/button_id"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Change Background"/>

    <CheckBox
        android:id="@+id/checkbox_id"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Lock Background" />

</LinearLayout>

The java class

Java class does two thing.
1. Adds the above layout  in the activity
2. Receves the event from button and checkbox and then pass it back to unity.

The complete source code for the java class is show below,


package com.test.androidcontrols;

import android.app.Activity;
import android.content.res.Resources;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.FrameLayout;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;

public class MyControls {
 
 public static interface Listner {
  public void onButtonClicked();
  public void onCheckBoxChanged(boolean state);
 }
 
 private Listner mListner;
 private Activity mActivity;
 
 public MyControls(Activity activity, Listner listner) {
  mActivity = activity;
  mListner = listner;
  
  mActivity.runOnUiThread(new Runnable() {
   @Override
   public void run() {
    LayoutInflater inflater = mActivity.getLayoutInflater();
    
    Resources resources =  mActivity.getResources();
    String pkgName = mActivity.getPackageName();
    
    // get the resource id for the layout xml file android_layout.xml
    int id = resources.getIdentifier("android_layout", "layout", pkgName);

    // inflate the layout xml file
    View view = inflater.inflate(id, null);
    
    LayoutParams param = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
                                                            FrameLayout.LayoutParams.MATCH_PARENT);
    
    // add the view hierarchy in to the unity activity
    mActivity.addContentView(view, param);
    
    Button button = (Button)mActivity.findViewById(resources.getIdentifier("button_id", "id", pkgName));
    button.setOnClickListener(new OnClickListener() {
     @Override
     public void onClick(View v) {
      if (mListner != null) {
       mListner.onButtonClicked();
      }
     }
    });
    
    final CheckBox checkBox = (CheckBox)mActivity.findViewById(resources.getIdentifier("checkbox_id", "id", pkgName));
    checkBox.setOnClickListener(new OnClickListener() {
     @Override
     public void onClick(View v) {
      if (mListner != null) {
       mListner.onCheckBoxChanged(checkBox.isChecked());
      }
     }
    });
   }
  });
 }
}

Manifest file

Updated manifest file is shown below.
Please note the change for meta data unityplayer.ForwardNativeEventsToDalvik, it is set to true.


<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.test.androidcontrols"
 android:installLocation="preferExternal"
 android:theme="@android:style/Theme.NoTitleBar"
    android:versionCode="1"
    android:versionName="1.0">
    <supports-screens
        android:smallScreens="true"
        android:normalScreens="true"
        android:largeScreens="true"
        android:xlargeScreens="true"
        android:anyDensity="true"/>

    <application
  android:icon="@drawable/app_icon"
        android:label="@string/app_name"
        android:debuggable="true">
        <activity android:name="com.unity3d.player.UnityPlayerNativeActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
            <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="true" />
        </activity>
    </application>
</manifest>


Integrating the android part in unity

The steps to integrate android specific artifacts is show below,
  1. Create plugin folder for android, Assets\Plugins\Android. 
  2. Copy the layout xml file, android_layout.xml, in to  Assets\Plugins\Android\res\layout folder.
  3. Create a jar file for our java class and copy it to Assets\Plugins\Android
  4. Copy the manifest file to Assets\Plugins\Android

Using the android ui from unity

The source code to display and receive event from the android ui is shown below.

using UnityEngine;
using System.Collections;

public class AndroidUIControls : MonoBehaviour {

 private bool mLockBackgroundColor = false;

 void Start () {
  init();
 }
 
 void Update () {
 }

 void onButtonClicked() {
  if (!mLockBackgroundColor) {
   float red = Random.value;
   float green = Random.value;
   float blue = Random.value;
   float alpha = 1.0f;

   Camera.main.backgroundColor = new Color(red, green, blue, alpha);
  }
 }

 void onCheckBoxChanged(bool state) {
  mLockBackgroundColor  = state;
 }

#if UNITY_ANDROID
 AndroidJavaObject mUI;

 // create the listner implementation
 // To derive from a java class use AndroidJavaProxy and pass the class name to base class as done below
 private sealed class EventListner: AndroidJavaProxy {
  private AndroidUIControls mReceiver;

  public EventListner(AndroidUIControls receiver)
   : base("com.test.androidcontrols.MyControls$Listner") {
   mReceiver = receiver;
  }

  public void onButtonClicked() {
   mReceiver.onButtonClicked();
  }

  public void onCheckBoxChanged(bool state) {
   mReceiver.onCheckBoxChanged(state);
  }
 }

 void init() {
     // obtain the android activity of the unity application
  AndroidJavaClass unityPlayer = new AndroidJavaClass ("com.unity3d.player.UnityPlayer");
  AndroidJavaObject activity = unityPlayer.GetStatic<AndroidJavaObject> ("currentActivity");
  
  // Create an instance of the java class com.test.androidcontrols.MyControls
  // pass the activity and the listner as argument to its constructor
  mUI = new AndroidJavaObject("com.test.androidcontrols.MyControls", activity, new EventListner(this));
 }
#else
 void init() {}
#endif

}


7 comments:

  1. Can you tell me how can I use this C# source do I need to apply this on some object?

    ReplyDelete
    Replies
    1. If you want to use the script as it is then you can add this to a gameobject, though it is not necessary. You can call the methods which loads the android plug-in from any other script as well.

      Delete
    2. Thank You chikku techie, I have made a function call from unity which make the Android layout inside unity but can you guide me how can I destroy the layout after getting values from it ? Thanks once again your approch really helped me :) the function on native side is which I am calling from unity is given below:

      public void inflateLayout() {

      getActivity().runOnUiThread(new Runnable() {
      @Override
      public void run() {
      LayoutInflater inflater = getActivity().getLayoutInflater();

      Resources resources = getActivity().getResources();
      String pkgName = getActivity().getPackageName();

      // get the resource id for the layout xml file android_layout.xml
      int id = resources.getIdentifier("android_layout", "layout", pkgName);

      // inflate the layout xml file
      View view = inflater.inflate(id, null);

      LayoutParams param = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
      FrameLayout.LayoutParams.MATCH_PARENT);

      // add the view hierarchy in to the unity activity
      getActivity().addContentView(view, param);

      EditText eTxt1 = (EditText) getActivity().findViewById(resources.getIdentifier("editText","id",pkgName));

      EditText pTxt1 = (EditText) getActivity().findViewById(resources.getIdentifier("editText2","id",pkgName));

      Button button = (Button) getActivity().findViewById(resources.getIdentifier("button_id", "id", pkgName));
      button.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
      Log.i("ButtonLayout", "inflateLayoutButtonClicked");

      }
      });
      final CheckBox checkBox = (CheckBox)getActivity().findViewById(resources.getIdentifier("checkbox_id", "id", pkgName));
      checkBox.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
      Log.i("CheckBoxLayout","Chalgaya");
      }
      });
      }

      });

      }

      Delete
    3. Simple approach could be to hide your view by setting the visibility to View.GONE.

      Delete
  2. This comment has been removed by the author.

    ReplyDelete
  3. hi, great tutorial :) i have some question, how to back to unity scene when i press back button in android jar? Because when i try "finish() or System.exit(0);" always close the apps not the current activity. thank you

    ReplyDelete
    Replies
    1. Do you have any other activity other than the unity player activity?
      If you have then finish or onbackpressed will take you to the other activity, if it started the unity player activity

      Delete