사용자 도구

사이트 도구


사이드바

카테고리

language:unity:plugin:android

Unity 플러그인

기능

  • 다른 navtive 라이브러리, C/C++ 코드와 통합

제한

  • Pro, Mobile 지원
  • 웹은 지원하지 않음

요구사항

  • C 기반 언어로 플러그인을 작성하고 라이브러리로 컴파일
  • 라이브러리의 함수를 호출하기 위한 C# 스크립트 작성

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 ());
  }
}

안드로이드 플러그인

요구사항

  • NDK로 공유 라이브러리를 만드는 방법

주의사항

  • C++ 로 작성한다면 C linkage 형태의 extern "C"를 선언해서 name mangling issule를 피할 것
extern "C" {
  float FooPluginFunction ();
}

C#에서 플러그인 사용

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

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

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

안드로이드 라이브러리

  • jar 형태로 만들어진 안드로이드 라이브러리를 Assets \ Plugins \ Android에 복사한다. “.java” 파일이 아니라, “.jar”파일이다.

배치

  • 크로스 플랫폼을 지원하려면, 각 플랫폼에 맞는 플러그인을 배치해야 한다.
    • libPlugin.so : for Android
    • Plugin.bundle : for MAC
    • Plugin.dll : for Windows
  • 플랫폼에 맞는 라이브러리는 자동선택된다.

자바플러그인

빌드

  • 빌드 방법에 상관 없이 .class 파일을 포함하는 “.jar” 파일이 최종 골

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

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

NativeCode에서 Java 코드 사용하기

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

  • JNI (Java Native Code) 에 의해 Java 코드에 접근할 수 있다.
  • JNI 는 Java ↔ NativeCode , 접근을 위해 사용된다.

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

환경변수

  • ANDROID_SDK_ROOT : ADK 경로
  • JDK_ROOT : JDK 경로
  • NDKROOT : Android NDK 경로
  • JAVA_HOME : Java RLE 경로. JDK를 설치하면 필요없지만, 참조하는 경우가 있을까봐

환경변수:옵션

  • ADT_ROOT : 언제 생긴걸까?

환경변수:Path

  • "%JDK_ROOT%\bin;" : 경로를 Path 환경 변수에 추가

설치프로그램

  • ADK : Android SDK
  • NDK : Android NDK
  • JDK : Java SDK
  • JRE : Java Runtime
  • keytool.exe : JDK, JRE에 있다.
  • openssl.exe : 별도 설치.

설치프로그램:옵션

  • bash

따라가기

신규 프로젝트 설정

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

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

파일명 변경

ic_launcher.png 파일명 변경

icon 이름 변경

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

  • 프로젝트의 res 폴더의 drawable-??? 폴더를 연다.
  • 일괄 변경을 위해서 Rename 기능 활용.
  • 바뀌지 않은게 있는지 수동 확인.

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

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

  • Applicaiont 탭에서 Icon 항목이 @drawable/app_icon이 아니면 바꿔줄 것. ( 난 해보니까 되어 있는데?? )

코드 수정

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

  • extends Activityextends UnityPlayerActivity
  • UnityPlayerActivity 마우스 우클릭,
    자동 교정 메뉴로 import com.unity3d.player.UnityPlayerActivity; 가 포함되도록 교정
  • 아래 부분 삭제 - 미사용 부분
// 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;
}	
  • 정리 후에는 (Ctrl + Shift + O, 뭐하는 단축키?) : 패키지와 import 상태 정리. → 필요한 패키지가 import 되어 있다.
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);
  }
}

참고

  • UnityPlayerActivity 항목을 상속 받는 대신, Activity 상속 받고 UnityPlayerActivity 를 직접 구현해도 된다.(는데)
  • 지금의 UnityPlayerActivity 가 구현된 상태를 참고하는 것은
    • <유니티설치폴더>\Editor\Data\PlaybackEngines\androiddevelopmentplayer\src\com\unity3d\player\UnityPlayerActivity.java
  • 같은 폴더의 파일들은
    • UnityPlayerProxyActivity 가
    • Android Version >= 2.3 : UnityPlayerNativeActivity 사용
    • Android Version < 2.3 : UnityPlayerActivity 사용
    • 하도록 한다는데 (그냥 넘어가자)
  • AndroidMenifest.xml 에서 MainActivity 에 정해진, 플러스, 이 MainActivity가 UnityPlayerActivity를 상속 받았다면 해당 Activity를 사용한다.

Java에 테스트 코드 추가

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

  • GetPluginNumber()
  • PluginToUnitySendMessage()

테스트 함수 추가.

// 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”

  • 테마가 없다고 에러가 나는 경우,
  • 다시 컴파일 해보기.
  • Project → Clean 메뉴에서 자동 빌드 옵션 끄고, 다시 리빌드.
  • 프로젝트의 AndroidManifest.xml 파일에서 Theme 항목을 비울 것
Bin 폴더에 JAR 파일이 생겼는지 확인!

유니티 작업

Jar 파일 복사

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

  • AndroidManifest.xml
  • <새로 만든 jar>파일을 복사한다.
플러그인을 Assets \ Plugins \ Android 폴더의 서브폴더에 넣을 수는 없는 걸까..

코드 추가

유니티에 추가하는 플러그인 사용 코드
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
}
실행은 폰에서!! 실제 디바이스가 아니면 실행되지 않는다고 하네. 실제로 그러함.

에러처리

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()
  • 액티비트 클래스가 유니티에서 정한 것이 아니라서 뜨는 워닝이라 무시해도 된다는데,
  • 감출 수는 없을까?

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

  • 용량(심카드가 없는)문제로 설치 안되는 경우도 있으니

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

  • 용량 문제가 아니라면 AndroidManifest.xml 파일 설정에 패키지 이름이 맞는지 확인
<?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>

첨부파일

language/unity/plugin/android.txt · 마지막으로 수정됨: 2022/12/07 22:30 저자 kieuns