목차

Unity 플러그인

문서 출처 : 플러그인 (프로/모바일만 지원) Plugins (Pro/Mobile-only feature)

기능

제한

요구사항

Native C 를 사용한 경우

간단한 예제

C 파일로 된, 최소 플러그인 (너무 최소다..)

float FooPluginFunction() { return 0.5f }

플러그인 사용을 위한 C# 스크립트

using UnityEngine;
using System.Runtime.InteropServices;
 
class FooInterface : MonoBehaviour
{
#if UNITY_IPHONE || UNITY_XBOX360
  // iOS나 Xbox360에서 플러그인은 정적으로
  // 실행파일들에 연결되어 있기 때문에
  // __Internal 을 라이브러리 이름으로 사용해야 합니다.
 
  // __Internal이 파일 이름인가? "__Internal" 문자열 그대로 쓰라는 것인가?
 
  [DllImport ("__Internal")]
#else
  // 다른 플랫폼들은 플러그인을 여러가지 방법으로 로드하기 때문에
  // 플러그인의 다이나믹 라이브러리의 이름을 사용합니다.
 
 
  // <PluginName>이 파일 이름인가? "PluginName" 문자열 그대로 쓰라는 것인가?
 
  [DllImport ("<PluginName>")]
#endif
 
  private static extern float FooPluginFunction();
 
  void Awake () {
    // 플러그인안에 FooPluginFunction 을 호출하고
    // 콘솔에 5를 출력합니다
    print (FooPluginFunction ());
  }
}

안드로이드 플러그인

요구사항

주의사항

extern "C" {
  float FooPluginFunction ();
}

C#에서 플러그인 사용

사용할 라이브러리를 Assets \ Plugins \ Android 폴더에 복사.

아래 형태로 함수를 선언해 두면 유니티에서는 이름으로 라이브러릴를 찾는다.

[DllImport("<PluginName")]
private static extern float FooPluginFunction();
* %%<PluginName>%% : "lib" 같은 라이브러리 접두사, "so" 같은 확장자를 넣지 않는다.
* **Application.platform**으로 플랫폼을 확인 하고 각 실제 디바이스에서 실행 되어야 한다.
* 에디터상에서 실행되는 경우 더미 값을 리턴하도록 작성

안드로이드 라이브러리

배치

자바플러그인

빌드

방법1 : JDK로 “.java” 파일을 “javac”로 컴파일 하는 방법. 컴파일된 “.class” 파일을 “jar” 커맨드툴로 묶는 것.

방법2 : 이클립스에서 ADT로 컴파일하는 방법

NativeCode에서 Java 코드 사용하기

만들어둔 Java plugin (.jar) 파일을 Assets \ Plugins \ Android 폴더에 복사.

Java 코드를 찾기 위해서 JavaVM에 접근해야 하는데, C/C++ 코드로는 아래 처럼 사용한다.

// 그런데 이거 어디에 두는거지..
jint JIN_OnLoad( JavaVM* vmPtr_, void* reserved_) {
  JNIEnv* jni_env_ptr = 0;
  vmPtr_->AttachCurrentThread(&jni_env_ptr, 0);
}

JNI 상세. (별로 알고 싶지 않지만, Java Native Interface Spec)

// 클래스와 클래스 생성자를 찾고(얻고)
// 인스턴스를 생성하고 --> 이렇게 쓰는 모양
jobject createJavaObject( JNIEnv* jni_env ) {
  // find class definition
  jclass cls_JavaClass = jni_env->FindClass("com/your/java/Class");
 
  // find constructor method
  jmethodID mid_JavaClass = jni_env->GetMethodID (cls_JavaClass, "<init>",  "()V");
 
  // create object instance
  jobject obj_JavaClass = jni_env->NewObject(cls_JavaClass, mid_JavaClass);
 
  // return object with a global reference
  return jni_env->NewGlobalRef(obj_JavaClass);
} 

Using Your Java Plugin with helper classes

  1. UnityEngine.AndroidJNIHelper, UnityEngine.AndroidJNI : 쓰기 어렵지만(?) Raw JNI 사용
  2. UnityEngine.AndroidJavaObject, UnityEngine.AndroidJavaClass : 귀찮은 일을 대신 해주고 좀 더 java 콜을 빨리 해준다.
    • UnityEngine.AndroidJNIHelper, UnityEngine.AndroidJNI 기반의 자동화 클래스.
    • 내부에 자동화를 위한 로직이 포함되어 있고 (뭐 어때?)
    • 자바클래스의 static 멤버를 사용하기 위해 static 버젼도 제공된다.(?)

컴파일 할때 있어야할 것들

Windows

환경변수

환경변수:옵션

환경변수:Path

설치프로그램

설치프로그램:옵션

따라가기

신규 프로젝트 설정

PackageName 은 나중에 유니티에 이것과 동일하게 (앱자체의 패키지 이름이라면야)
Create custom launcher icon : 체크 끄기
Blank Activity 선택. 나중에 바꾸려면 골치아므니..
Activity 이름 설정. (메인 함수 같은 느낌인데)
Navigation Type : None

유니티의 안드로이드용 라이브러리(External JAR)추가

파일명 변경

ic_launcher.png 파일명 변경

icon 이름 변경

ic_launcher.png 파일명을 app_icon.png 로 변경해야함 (이유는 모르겟네, 인터넷 검색하면 나오려나. 이 이름이 안맞으면 안된다네)

AndroidManifest.xml 파일에서 ic_launcher.png 파일명 남아 있는지 확인

프로젝트의 AndroidManifest.xml 파일 수정

코드 수정

최초의 생성된 코드, 아래와 같은,를 수정해서 필요한 부분만 남긴다.

// com.test.apiinterface --> com.test.jvplugin 으로 이름 변경
package com.test.apiinterface;
 
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
 
public class APIInterfaceUnityPluginActivity extends Activity
{
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_apiinterface_unity_plugin);
  }
 
  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.apiinterface_unity_plugin, menu);
    return true;
  }
}

삭제 이후

// 아래 부분 삭제
//
setContentView(R.layout.activity_apiinterface_unity_plugin);
//...
public boolean onCreateOptionsMenu(Menu menu) 
{
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.apiinterface_unity_plugin, menu);
  return true;
}	
package com.test.apiinterface;
 
import android.os.Bundle;
 
import com.unity3d.player.UnityPlayerActivity;
 
public class APIInterfaceUnityPluginActivity extends UnityPlayerActivity 
{
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
  }
}

참고

Java에 테스트 코드 추가

APIInterfaceUnityPluginActivity.java에 테스트 코드를 추가.

테스트 함수 추가.

// com.test.apiinterface --> com.test.jvplugin 으로 이름 변경
package com.test.jvplugin;
import android.os.Bundle;
import com.unity3d.player.UnityPlayer;
import com.unity3d.player.UnityPlayerActivity;
 
public class MainActivity extends UnityPlayerActivity 
{
    @Override
    protected void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
    }
 
    float GetPluginNumber( int left_, int right_ )
    {
      return left_ + right_ / 1.23f;
    }
 
    void SendMessageToUnity( String unityObjectName_, String callFunctionName, String param_)
    {
    	UnityPlayer.UnitySendMessage(unityObjectName_, callFunctionName, param_);
    }
}

Java 빌드

최소 빌드 규격을 결정

Clean (메뉴 : Project → Clean…) 화면에서, 빌드 순서가 제대로 조정되지 않으면 빌드 에러가 날 수 있으니 Start a build immediately는 비추라네.

자 이젠 빌드

에러가 나는 경우 : “unity3d plugin error:Error retrieving parent for item: No resource found that matches”

Bin 폴더에 JAR 파일이 생겼는지 확인!

유니티 작업

Jar 파일 복사

유니티의 Assets \ Plugins \ Android 폴더에

<note>플러그인을 Assets \ Plugins \ Android 폴더의 서브폴더에 넣을 수는 없는 걸까..</note>

코드 추가

유니티에 추가하는 플러그인 사용 코드
using UnityEngine;
using System.Collections;
 
public class AndroidPluginInterface : MonoBehaviour
{
#if UNITY_ANDROID
 
  AndroidJavaObject curActivity;
 
  void Awake()
  {
    AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
    curActivity = jc.GetStatic<AndroidJavaObject>("currentActivity");
  }
 
  void Destroy()
  {
    if(curActivity != null) curActivity.Dispose();
  }
 
  public void CallFunctionName(string msg_)
  {
    strLabelReceiveMessage = msg_;
    Debug.Log(msg_);
  }
 
  int init()
  {
    Debug.Log("init()");
    AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
    if(jc != null)
    {
      curActivity = jc.GetStatic<AndroidJavaObject>("currentActivity");
      jc.Dispose();
      if(curActivity != null)
      {
        Debug.Log("init success");
        return 1;
      }
    }
    Debug.Log("init failed");
    return 0;
  }
 
  float CallIntValue()
  {
    Debug.Log("CallIntValue()");
    if(curActivity != null)
    {
      int _left = Random.Range(1, 100);
      int _right = Random.Range(10, 90);
      float n_val = curActivity.Call<float>("GetPluginNumber", _left, _right);
      return n_val;
    }
    else
    {
      Debug.Log("CallIntValue(): androidPlugin is null");
    }
    return 0;
  }
 
  void requestCheckPluginMessage()
  {
    Debug.Log("requestCheckPluginMessage()");
 
    string[] msg_list = new string[] 
    {
      "abcde",
      "hello",
      "세번째 메시지",
      "랜덤메시지"
    };
 
    if(curActivity != null)
    {
      int _n = Random.Range(0, msg_list.Length - 1);
      curActivity.Call("SendMessageToUnity", "AndroidManager", "CallFunctionName", msg_list[_n]);
    }
    else
    {
      Debug.Log("requestCheckPluginMessage(): androidPlugin is null");
    }
  }
 
  string strLabelReturnInt = "";
  string strLabelReceiveMessage = "";
 
  void OnGUI()
  {
    GUILayout.BeginHorizontal();
    {
      if(GUILayout.Button("Called IntValue", GUILayout.Width(200), GUILayout.Height(100)))
      {
        float _fv = CallIntValue();
        strLabelReturnInt = _fv.ToString();
      }
      GUILayout.Label(string.Format("Return Int : {0}", strLabelReturnInt));
    }
    GUILayout.EndHorizontal();
 
    GUILayout.BeginHorizontal();
    {
      if(GUILayout.Button("Called Request Message", GUILayout.Width(200), GUILayout.Height(100)))
      {
        requestCheckPluginMessage();
      }
      GUILayout.Label(string.Format("RecvMsg : {0}", strLabelReceiveMessage));
    }
    GUILayout.EndHorizontal();
  }
 
#endif
}

<note important>실행은 폰에서!! 실제 디바이스가 아니면 실행되지 않는다고 하네. 실제로 그러함.</note>

에러처리

Exception: JNI: Init'd AndroidJavaClass with null ptr!

Exception: JNI: Init'd AndroidJavaClass with null ptr!
UnityEngine.AndroidJavaClass..ctor (IntPtr jclass) (at C:/BuildAgent/work/d3d49558e4d408f4/Runtime/Export/AndroidJavaImpl.cs:539)
UnityEngine.AndroidJavaObject.get_JavaLangClass () (at C:/BuildAgent/work/d3d49558e4d408f4/Runtime/Export/AndroidJavaImpl.cs:517)
UnityEngine.AndroidJavaObject.FindClass (System.String name) (at C:/BuildAgent/work/d3d49558e4d408f4/Runtime/Export/AndroidJavaImpl.cs:50)
UnityEngine.AndroidJavaClass._AndroidJavaClass (System.String className) (at C:/BuildAgent/work/d3d49558e4d408f4/Runtime/Export/AndroidJavaImpl.cs:528)
UnityEngine.AndroidJavaClass..ctor (System.String className) (at C:/BuildAgent/work/d3d49558e4d408f4/artifacts/EditorGenerated/AndroidJava.cs:92)
AndroidPluginInterface.Awake () (at Assets/AppJavaPlugin/AndroidPluginInterface.cs:13)

Unable to find unity activity in manifest

Unable to find unity activity in manifest. You need to make sure orientation attribut is set to portrait manually.
UnityEditor.BuildPlayerWindow:BuildPlayerAndRun()

앱이 제대로 설치되지 않는다

앱이 제대로 설치되지 않거나 실행 안되는 경우

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.test.jvplugin"
  android:versionCode="1"
  android:versionName="1.0" >
 
  <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="19" />
 
  <application android:allowBackup="true"
    android:icon="@drawable/app_icon" 
    android:label="@string/app_name">
      <activity android:name="com.test.jvplugin.MainActivity">
        <intent-filter>
          <action android:name="android.intent.action.MAIN" />
 
          <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
      </activity>
  </application>
</manifest>

첨부파일

plugin_android_140305.7z