Skip to content

Instantly share code, notes, and snippets.

@jeserodz
Last active June 7, 2019 05:11
Show Gist options
  • Select an option

  • Save jeserodz/7ae2b590e959ce9e6adc1250bdef6e37 to your computer and use it in GitHub Desktop.

Select an option

Save jeserodz/7ae2b590e959ce9e6adc1250bdef6e37 to your computer and use it in GitHub Desktop.
Integrate Unity/Vuforia in React Native

Export Unity project as AAR and import in React Native app

  1. Export Unity project as Gradle in AAR format (Guide)
  2. Marge build.gradle configurations into /android/app/build.gradle
    • android.defaultConfig.multiDexEnabled = true
    • android.lintOptions (copy from Unity Android project)
    • android.aaptOptions (copy from Unity Android project)
    • android.packagingOptions(copy from Unity Android project)
    • dependecies.implementation fileTree(dir: "libs", include: ["*.jar"])
  3. Install react-native-android-navigation to launch UnityPlayerActivity from React Native

If need to launch Unity in a sub view (to render UI on top of Unity):

  1. Create a new activity layout (xml) and class (java)
  2. Copy all the method overrides from the UnityPlayerActivity to the new activity
  3. Follow this guide to complete process
  4. See the files below for actual example

If using Vuforia (AR):

  1. copy any files from /libs to /android/app/libs that appears in the build.gradle file of the Unity project.

  2. add them as project dependencies in /android/app/build.gradle (NOTE: Remove if getting duplicate depedencies issue):

android {
  ...
  dependencies {
    ...
    implementation(name: 'NatCamRenderPipeline', ext:'aar')
    implementation(name: 'NatCorder', ext:'aar')
    implementation(name: 'NatShare', ext:'aar')
    implementation(name: 'Sharing', ext:'aar')
    implementation(name: 'core-1.5.0', ext:'aar')
    implementation(name: 'VuforiaWrapper', ext:'aar')
  }
}
  1. allow to use libs directory as dependency repository in /android/build.gradle
allprojects {
  repositories {
    ...
    flatDir {
      dirs 'libs'
    }
  }
}
<!--
This is an Android Activity layout used to draw
invisible controls over Unity to exit the experience.
The Unity view is added to a FrameLayer and the transparent
UI is rendered on top of it.
Location: android/app/src/main/res/layout/activity_start_unity.xml
-->
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".StartUnity">
<FrameLayout
android:id="@+id/fl_forUnity"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_row="0"
android:layout_column="4"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:background="#000">
</FrameLayout>
<Button
android:id="@+id/backButton"
android:layout_width="158dp"
android:layout_height="124dp"
android:background="@android:color/transparent"
tools:layout_editor_absoluteX="0dp"
tools:layout_editor_absoluteY="0dp" />
</android.support.constraint.ConstraintLayout>
/**
* A React Native custom module to start
* an Android activity from JavaScript side
*
* Location android/app/src/main/java/<app_dir>/ActivityStarterModule.java
*/
package <APP_BUNDLE_ID>;
import android.content.Intent;
import android.content.Context;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.OUE.Augmently.UnityPlayerActivity;
import com.unity3d.player.UnityPlayer;
import android.app.Activity;
import android.view.View;
class ActivityStarterModule extends ReactContextBaseJavaModule {
protected UnityPlayer mUnityPlayer;
Context mContext;
public ActivityStarterModule(ReactApplicationContext reactContext) {
super(reactContext);
mContext = reactContext;
}
@Override
public String getName() {
return "ActivityStarter";
}
@ReactMethod
public void launchUnityAr() {
// Intent intent = new Intent(mContext, UnityPlayerActivity.class);
Intent intent = new Intent(mContext, StartUnity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
public void ReturnToMainActivity ()
{
mUnityPlayer.quit();
UnityPlayer.currentActivity.finish();
}
}
/**
* A React Native custom module to start
* an Android activity from JavaScript side
*
* Location android/app/src/main/java/<app_dir>/ActivityStarterReactPackage.java
*/
package <APP_BUNDLE_ID>;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ActivityStarterReactPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new ActivityStarterModule(reactContext));
return modules;
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
/**
* Add the ActivityStarter custom native module to the app.
*
* Location: android/app/src/main/java/<APP_DIR>/MainApplication.java
*/
package <APP_BUNDLE_ID>;
import android.app.Application;
import android.content.Intent;
import android.content.Context;
[...]
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
[...]
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
[...]
new ActivityStarterReactPackage(), // <-- Add the custom native module here
);
}
}
/* Launch the Unity view from anywhere in React Native */
import React from 'react';
import { NativeModules } from 'react-native';
export default class ArScreen extends React.Component {
componentDidMount() {
NativeModules.ActivityStarter.launchUnityAr();
}
render() {
return <View />;
}
}
/**
* A custom Android Activity that renders Unity
* as a FrameLayer. This is needed to exit Unity
* by pressing an area of the screen.
* Note: This needs and Activity Layout, see activity_start_unity.xml
*
* Location: android/app/src/main/java/<app_dir>/StartUnity.java
*/
package <APP_BUNDLE_ID>;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactMethod;
import com.unity3d.player.*;
import android.content.Intent;
import android.content.Context;
import <UNITY_PROJ_BUNDLE_ID>.UnityPlayerActivity;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import android.app.Activity;
import com.facebook.react.bridge.ReactApplicationContext;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.LinearLayout.LayoutParams;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import android.os.Debug;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
public class StartUnity extends AppCompatActivity {
protected UnityPlayer mUnityPlayer;
Context mContext;
//Declare a FrameLayout object
FrameLayout fl_forUnity;
//Declare the buttons
Button backButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
// requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
// Hold a reference to this activity
final AppCompatActivity activityRef = this;
getWindow().setFormat(PixelFormat.RGBX_8888); // <--- This makes xperia play happy
// Create the UnityPlayer
mUnityPlayer = new UnityPlayer(this);
int glesMode = mUnityPlayer.getSettings().getInt("gles_mode", 1);
boolean trueColor8888 = false;
if (mUnityPlayer.getSettings ().getBoolean ("hide_status_bar", true))
{
setTheme(android.R.style.Theme_NoTitleBar_Fullscreen);
getWindow ().setFlags (
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
);
}
//setContentView(mUnityPlayer);
//Set the content to main
setContentView(R.layout.activity_start_unity);
//Inflate the frame layout from XML
this.fl_forUnity = (FrameLayout)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
);
//Initialize the buttons from the XML file
this.backButton = (Button) findViewById(R.id.backButton);
this.backButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
UnityPlayer.UnitySendMessage("Main Camera", "ReceiveRotDir", " ");
activityRef.finish();
}
});
mUnityPlayer.init(glesMode, trueColor8888);
mUnityPlayer.start();
mUnityPlayer.requestFocus();
}
@Override protected void onNewIntent(Intent intent)
{
// To support deep linking, we need to make sure that the client can get access to
// the last sent intent. The clients access this through a JNI api that allows them
// to get the intent set on launch. To update that after launch we have to manually
// replace the intent with the one caught here.
setIntent(intent);
}
// Quit Unity
@Override protected void onDestroy ()
{
mUnityPlayer.quit();
super.onDestroy();
}
// Pause Unity
@Override protected void onPause()
{
super.onPause();
mUnityPlayer.pause();
}
// Resume Unity
@Override protected void onResume()
{
super.onResume();
mUnityPlayer.resume();
}
@Override protected void onStart()
{
super.onStart();
mUnityPlayer.start();
}
@Override protected void onStop()
{
super.onStop();
mUnityPlayer.stop();
}
// Low Memory Unity
@Override public void onLowMemory()
{
super.onLowMemory();
mUnityPlayer.lowMemory();
}
// Trim Memory Unity
@Override public void onTrimMemory(int level)
{
super.onTrimMemory(level);
if (level == TRIM_MEMORY_RUNNING_CRITICAL)
{
mUnityPlayer.lowMemory();
}
}
// This ensures the layout will be correct.
@Override public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
mUnityPlayer.configurationChanged(newConfig);
}
// Notify Unity of the focus change.
@Override public void onWindowFocusChanged(boolean hasFocus)
{
super.onWindowFocusChanged(hasFocus);
mUnityPlayer.windowFocusChanged(hasFocus);
}
// For some reason the multiple keyevent type is not supported by the ndk.
// Force event injection by overriding dispatchKeyEvent().
@Override public boolean dispatchKeyEvent(KeyEvent event)
{
if (event.getAction() == KeyEvent.ACTION_MULTIPLE)
return mUnityPlayer.injectEvent(event);
return super.dispatchKeyEvent(event);
}
// Pass any events not handled by (unfocused) views straight to UnityPlayer
@Override public boolean onKeyUp(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); }
@Override public boolean onKeyDown(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); }
@Override public boolean onTouchEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); }
/*API12*/ public boolean onGenericMotionEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); }
// Quit Unity
public void closeActivity() {
mUnityPlayer = new UnityPlayer(this);
this.mUnityPlayer.quit();
UnityPlayer.currentActivity.finish();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment