Importing a Unity3D scene into Android Fragment

I’ve searched online for similar questions, and while I’ve found several similar questions, none had reliable answers, which is why I’m posting here.

When the Unity3D project is compiled for Android, it basically just gives the scene as an Activity that you can start/end/etc. I want to change this Activity into a Fragment in order to display it as a tab inside a navigation drawer, and as a sub-view inside another fragment/activity.

So basically I had a MainActivity with an Open button, and a UnityPlayerNativeActivity which is the actual Unity3D project.

I searched how to change a general activity to a fragment, and changed the UnityPlayerNativeActivity to match. For example, in the newly-titled UnityPlayerNativeFragment below (with comments reflecting what exactly was changed from before):

import com.unity3d.player.UnityPlayer;
// Other imports available in full code linked to below

public class UnityPlayerNativeFragment extends Fragment
{
    // Changes in this class:
    // 1- 'this' references changed to "getActivity()"
    // 2- onCreate -> onCreateView
    // 3- protected -> public in function names
    // 4- @Override added before function calls
    // 5- newInstance and onAttach added

	protected UnityPlayer mUnityPlayer;		// don't change the name of this variable; referenced from native code
    private static final String ARG_SECTION_NUMBER = "section_number";

    public static UnityPlayerNativeFragment newInstance(int sectionNumber) {
        UnityPlayerNativeFragment fragment = new UnityPlayerNativeFragment();
        Bundle args = new Bundle();
        args.putInt(ARG_SECTION_NUMBER, sectionNumber);
        fragment.setArguments(args);
        return fragment;
    }


    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        ((HomeActivity) activity).onSectionAttached(
                getArguments().getInt(ARG_SECTION_NUMBER));
    }

	// UnityPlayer.init() should be called before attaching the view to a layout - it will load the native code.
	// UnityPlayer.quit() should be the last thing called - it will unload the native code.
	
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState)
	{
        //below line removed because it was causing errors
		//getActivity().requestWindowFeature(Window.FEATURE_NO_TITLE);

		getActivity().getWindow().takeSurface(null);
		getActivity().setTheme(android.R.style.Theme_NoTitleBar_Fullscreen);
		getActivity().getWindow().setFormat(PixelFormat.RGB_565);

		mUnityPlayer = new UnityPlayer(getActivity());
		if (mUnityPlayer.getSettings ().getBoolean ("hide_status_bar", true))
			getActivity().getWindow ().setFlags (WindowManager.LayoutParams.FLAG_FULLSCREEN,
			                       WindowManager.LayoutParams.FLAG_FULLSCREEN);

		int glesMode = mUnityPlayer.getSettings().getInt("gles_mode", 1);
		boolean trueColor8888 = false;
		mUnityPlayer.init(glesMode, trueColor8888);

		View playerView = mUnityPlayer.getView();
		return playerView;
	}

    @Override
	public void onDestroy ()
	{
		mUnityPlayer.quit();
		super.onDestroy();
	}

	// onPause()/onResume() must be sent to UnityPlayer to enable pause and resource recreation on resume.
	@Override
    public void onPause()
	{
		super.onPause();
		mUnityPlayer.pause();
	}

    @Override
	public void onResume()
	{
		super.onResume();
		mUnityPlayer.resume();
	}

    @Override
	public void onConfigurationChanged(Configuration newConfig)
	{
		super.onConfigurationChanged(newConfig);
		mUnityPlayer.configurationChanged(newConfig);
	}
}

The AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.SamerBekhazi.Test" android:versionName="1.0" android:versionCode="1" android:installLocation="preferExternal">
  <supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" android:anyDensity="true" />
  <application android:theme="@style/AppTheme" android:icon="@drawable/app_icon" android:label="@string/app_name">
    <activity android:launchMode="singleTask" android:label="@string/app_name" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen" android:screenOrientation="portrait" android:name=".HomeActivity">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
      <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="false" />
    </activity>
  </application>
  <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="21" />
  <uses-feature android:glEsVersion="0x00020000" />
</manifest>

I only included those 2 files here because I figure the problem is from one or the other. The full code is downloadable here. Extract/Import into Android Studio - you may need to press Sync Project with Gradle Files once for it to work.

The rest of the project is based on the Navigation Drawer Activity project that Android Studio auto-creates for you when creating a new project. I basically just call a newInstance of the above UnityPlayerNativeFragment when its corresponding icon is pressed in the NavigationDrawer.

What results when I press the tab for the scene: A black screen, with the Action Bar showing still, but nothing else. The top-right “Settings” button is still press-able, as is the Navigation Drawer button, but when one presses another tab on the Navigation Drawer, the entire app freezes and you have to force exit. I think the latter issue is because you can’t close a UnityPlayer that hasn’t opened properly, so the main issue is in actually making the UnityPlayer open up properly inside the Fragment. The rest of the app (the other tabs) work fine. I tried several variations (everything I could think of) in the above code and AndroidManifest.xml file, but nothing worked.

Logcat doesn’t show any errors, it just says:

11-11 21:22:19.681  29280-29280/com.Bekhazi.Bouncy_Ball W/linker﹕ libmain.so has text relocations. This is wasting memory and is a security risk. Please fix.
11-11 21:22:19.681  29280-29280/com.Bekhazi.Bouncy_Ball D/dalvikvm﹕ Added shared lib /data/app-lib/com.Bekhazi.Bouncy_Ball-2/libmain.so 0x42d64cd8
11-11 21:22:19.691  29280-29280/com.Bekhazi.Bouncy_Ball W/linker﹕ libmono.so has text relocations. This is wasting memory and is a security risk. Please fix.
11-11 21:22:19.691  29280-29280/com.Bekhazi.Bouncy_Ball W/linker﹕ libunity.so has text relocations. This is wasting memory and is a security risk. Please fix.

What exactly is wrong here? The target build is for Android 5.0 and I’m using Android Studio 0.8.14.
Worth noting: I was able to successfully load the scene onto my Nexus 5 when it was an Activity.

Hey @GeodeLX , @gjcope

I fix that with one line of code. So I put code snippet here
Checkout last lines of onCreateView

My Fragment:

public class UnityFragment extends Fragment {

    protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code
    //Declare a FrameLayout object
    FrameLayout fl_forUnity;
    //Test Button
    Button button;

    public UnityFragment() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        mUnityPlayer = new UnityPlayer(getActivity());
        View view = inflater.inflate(R.layout.fragment_unity, container, false);
    
        //Inflate the frame layout from XML
        this.fl_forUnity = (FrameLayout) view.findViewById(R.id.fl_forUnity);

        //Add the mUnityPlayer view to the FrameLayout, and set it to fill all the area available
        this.fl_forUnity.addView(mUnityPlayer.getView(),
                FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);

        button = (Button) view.findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(view.getContext(), "BOOM!", Toast.LENGTH_SHORT).show();
            }
        });

        //Requesting the Focus
        mUnityPlayer.requestFocus();

        //The main fix of resolving BLACK SCREEN PLAYER ISSUE
         mUnityPlayer.windowFocusChanged(true);//First fix Line
        // Yes, it's "static" way and should to be more dynamic, anyway, it works well
        return view;
    }

    // Quit Unity
    @Override
    public void onDestroy() {
        mUnityPlayer.quit();
        super.onDestroy();
    }

    // Pause Unity
    @Override
    public void onPause() {
        super.onPause();
        mUnityPlayer.pause();
    }

    // Resume Unity
    @Override
    public void onResume() {
        super.onResume();
        mUnityPlayer.resume();
    }

// This ensures the layout will be correct.
//    @Override
//    public void onConfigurationChanged(Configuration newConfig) {
//        super.onConfigurationChanged(newConfig);
//        mUnityPlayer.configurationChanged(newConfig);
//    }
}

and Xml layout of fragment

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.marsstudio.project.grand.UnityFragment">

    <FrameLayout
        android:id="@+id/fl_forUnity"
        android:layout_width="match_parent"
        android:layout_height="250dip"
        android:background="#ff90ff15"/>

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|center_horizontal"
        android:text="Button" />
</FrameLayout>

Hi @iRYO400 ! Thanks for your post, it helped me.

However I have one issue and my app often crashes when I try to start my Unity fragment.
The problem comes from the line mUnityPlayer.windowFocusChanged(true); which leads me to this error when called (and then crashes) :

Attempt to invoke virtual method 'boolean android.os.Handler.sendMessage(android.os.Message)' on a null object reference at android.os.Message.sendToTarget(Message.java:416) at com.unity3d.player.UnityPlayer$b.a(Unknown Source) at com.unity3d.player.UnityPlayer$b.a(Unknown Source) at com.unity3d.player.UnityPlayer.windowFocusChanged(Unknown Source) at fr.artefacto.testfragments.UnityManagerFragment.onCreateView(UnityManagerFragment.java:40) at android.support.v4.app.Fragment.performCreateView(Fragment.java:2192) at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1299) at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1528) at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1595) at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:758) at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2363) at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2149) at android.support.v4.app.FragmentManagerImpl.optimizeAndExecuteOps(FragmentManager.java:2103) at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2013) at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:710) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:145) at android.app.ActivityThread.main(ActivityThread.java:6873) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1404) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1199)

To avoid this, I always need to completely shut down my app then relaunch it. And if the error doesn’t disappear, I need to clean my project then reinstall my app.
Did you get into this error before ?

Thanks !

PS: I don’t know why but today I can no longer leave comments so I had to comment as an answer. Sorry for that.

Hey, I am having the same issue did you solve this problem?