사용자 도구

사이트 도구


language:unity:plugin:android

차이

문서의 선택한 두 판 사이의 차이를 보여줍니다.


이전 판
language:unity:plugin:android [2024/04/23 22:45] (현재) – 바깥 편집 127.0.0.1
줄 1: 줄 1:
 +====== Unity 플러그인 ======
  
 +문서 출처 : [[http://unitykoreawiki.com/index.php?n=KrMain.Plugins|플러그인 (프로/모바일만 지원) Plugins (Pro/Mobile-only feature)]]
 +
 +===== 기능 =====
 +  * 다른 navtive 라이브러리, %%C/C++%% 코드와 통합
 +
 +===== 제한 =====
 + * Pro, Mobile 지원
 + * 웹은 지원하지 않음
 +
 +===== 요구사항 =====
 + * C 기반 언어로 플러그인을 작성하고 라이브러리로 컴파일
 + * 라이브러리의 함수를 호출하기 위한 C# 스크립트 작성
 +
 +===== Native C 를 사용한 경우 =====
 +==== 간단한 예제 ====
 +
 +C 파일로 된, 최소 플러그인 (너무 최소다..)
 +
 +<code c>
 +float FooPluginFunction() { return 0.5f }
 +</code>
 +
 +==== 플러그인 사용을 위한 C# 스크립트 ====
 +
 +<code csharp>
 +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 ());
 +  }
 +}
 +</code>
 +
 +====== 안드로이드 플러그인 ======
 +
 +===== 요구사항 =====
 + * [[http://developer.android.com/sdk/ndk|Android NDK]]
 + * NDK로 공유 라이브러리를 만드는 방법
 +
 +===== 주의사항 =====
 + * C++ 로 작성한다면 C linkage 형태의 %%extern "C"%%를 선언해서 name mangling issule를 피할 것
 +<code csharp>
 +extern "C" {
 +  float FooPluginFunction ();
 +}
 +</code>
 +===== C#에서 플러그인 사용 =====
 +사용할 라이브러리를 **Assets \ Plugins \ Android** 폴더에 복사. 
 +
 +아래 형태로 함수를 선언해 두면 유니티에서는 이름으로 라이브러릴를 찾는다.
 +
 + * %%<PluginName>%% : 라이브러리 이름
 +
 +<code csharp>
 +[DllImport("<PluginName")]
 +private static extern float FooPluginFunction();
 +</code>
 +
 + * %%<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++ 코드로는 아래 처럼 사용한다.
 +
 +<code java>
 +// 그런데 이거 어디에 두는거지..
 +jint JIN_OnLoad( JavaVM* vmPtr_, void* reserved_) {
 +  JNIEnv* jni_env_ptr = 0;
 +  vmPtr_->AttachCurrentThread(&jni_env_ptr, 0);
 +}
 +</code>
 +
 +JNI 상세. (별로 알고 싶지 않지만, [[http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html|Java Native Interface Spec]])
 +
 +<code java>
 +// 클래스와 클래스 생성자를 찾고(얻고)
 +// 인스턴스를 생성하고 --> 이렇게 쓰는 모양
 +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);
 +
 +</code>
 +
 +==== Using Your Java Plugin with helper classes ====
 +
 + - **UnityEngine.AndroidJNIHelper**, **UnityEngine.AndroidJNI** : 쓰기 어렵지만(?) Raw JNI 사용
 + - **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
 +
 +====== 따라가기 ======
 +
 + * [[http://blog.naver.com/PostView.nhn?blogId=saram95&logNo=90176732890&parentCategoryNo=&categoryNo=&viewDate=&isShowPopularPosts=false&from=postView|따라하기1, 안드로이드플러그인만들기]]
 +  * [[http://westwoodforever.blogspot.kr/2013/05/unity3d-android-jar-lib.html|따라하기2,Unity3D Android Jar Lib 만들어 연동하기]]
 +
 +===== 신규 프로젝트 설정 =====
 +
 +| {{:language:unity:plugin:make_project_1.png|}} |
 +| {{:language:unity:plugin:make_project_2.png|}} |
 +| PackageName 은 나중에 유니티에 이것과 동일하게 (앱자체의 패키지 이름이라면야) |
 +| {{:language:unity:plugin:make_project_3.png|}} |
 +| **Create custom launcher icon** : 체크 끄기 |
 +| {{:language:unity:plugin:make_project_4.png|}} |
 +| **Blank Activity** 선택. 나중에 바꾸려면 골치아므니..  |
 +| {{:language:unity:plugin:make_project_5.png|}} |
 +| Activity 이름 설정. (메인 함수 같은 느낌인데) \\ Navigation Type : None |
 +
 +===== 유니티의 안드로이드용 라이브러리(External JAR)추가 =====
 +
 +| {{:language:unity:plugin:make_project_6.png|}} |
 +| {{:language:unity:plugin:make_project_7.png|}} |
 +| {{:language:unity:plugin:make_project_8.png|}} |
 +| {{:language:unity:plugin:make_project_9.png|}} |
 +
 +===== 파일명 변경 =====
 +
 +==== ic_launcher.png 파일명 변경 ====
 +
 +icon 이름 변경
 +
 +ic_launcher.png 파일명을 app_icon.png 로 변경해야함 (이유는 모르겟네, 인터넷 검색하면 나오려나. 이 이름이 안맞으면 안된다네)
 +
 +  * 프로젝트의 **res** 폴더의 **drawable-???** 폴더를 연다.
 + * 일괄 변경을 위해서 Rename 기능 활용. 
 + * 바뀌지 않은게 있는지 수동 확인.
 +
 +| {{:language:unity:plugin:make_project_10.png|}} |
 +| {{:language:unity:plugin:make_project_11.png|}} |
 +
 +==== AndroidManifest.xml 파일에서 ic_launcher.png 파일명 남아 있는지 확인 ====
 +
 +프로젝트의 AndroidManifest.xml 파일 수정
 +
 +  * **Applicaiont 탭**에서 **Icon** 항목이 **@drawable/app_icon**이 아니면 바꿔줄 것. ( 난 해보니까 되어 있는데?? )
 +
 +| {{:language:unity:plugin:make_project_12.png|}} |
 +| {{:language:unity:plugin:make_project_13.png|}} |
 +
 +===== 코드 수정 =====
 +
 +최초의 생성된 코드, 아래와 같은,를 수정해서 필요한 부분만 남긴다.
 +
 +  * **extends Activity** -> **extends UnityPlayerActivity**
 + * **UnityPlayerActivity** 마우스 우클릭, \\ 자동 교정 메뉴로 **import com.unity3d.player.UnityPlayerActivity;** 가 포함되도록 교정
 + * 아래 부분 삭제 - 미사용 부분
 +
 +<code java>
 +// 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;
 +  }
 +}
 +</code>
 +
 +
 +삭제 이후
 +
 +<code java>
 +// 아래 부분 삭제
 +//
 +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;
 +}
 +</code>
 +
 + * 정리 후에는 (Ctrl + Shift + O, 뭐하는 단축키?) : 패키지와 import 상태 정리. -> 필요한 패키지가 import 되어 있다.
 +
 +<code java>
 +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);
 +  }
 +}
 +</code>
 +
 +==== 참고 ====
 +
 + * 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()
 +
 +테스트 함수 추가.
 +
 +<code 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_);
 +    }
 +}
 +</code>
 +
 +===== Java 빌드 =====
 +
 +최소 빌드 규격을 결정
 +
 +{{:language:unity:plugin:make_project_14.png|}}
 +
 +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** 항목을 비울 것 \\ {{:language:unity:plugin:make_project_15.png|}}
 +
 +  Bin 폴더에 JAR 파일이 생겼는지 확인!
 +
 +===== 유니티 작업 =====
 +
 +==== Jar 파일 복사 ====
 +
 +유니티의 **Assets \ Plugins \ Android** 폴더에 
 +  * AndroidManifest.xml
 +  * %%<새로 만든 jar>%%파일을 복사한다.
 +
 +<note>플러그인을 **Assets \ Plugins \ Android** 폴더의 서브폴더에 넣을 수는 없는 걸까..</note>
 +
 +==== 코드 추가 ====
 +
 +^ 유니티에 추가하는 플러그인 사용 코드 ^
 +| <code csharp>
 +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
 +}
 +</code> |
 +
 +<note important>실행은 폰에서!! 실제 디바이스가 아니면 실행되지 않는다고 하네. 실제로 그러함.</note>
 +
 +
 +====== 에러처리 ======
 +
 +===== Exception: JNI: Init'd AndroidJavaClass with null ptr! =====
 +
 +<code>
 +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)
 +</code>
 +
 +  * 에디터상에서, 안드로이드 플러그인이 동작하지 않는다.
 +  * 적어도 에러를 감추는 방법은 없을까?
 +    * 인터넷으로 찾아보니 디파인으로 처리하랜다.
 +
 +===== Unable to find unity activity in manifest =====
 +
 +<code>
 +Unable to find unity activity in manifest. You need to make sure orientation attribut is set to portrait manually.
 +UnityEditor.BuildPlayerWindow:BuildPlayerAndRun()
 +</code>
 +
 +  * 액티비트 클래스가 유니티에서 정한 것이 아니라서 뜨는 워닝이라 무시해도 된다는데, 
 +  * 감출 수는 없을까?
 +
 +===== 앱이 제대로 설치되지 않는다 =====
 +
 +  * 용량(심카드가 없는)문제로 설치 안되는 경우도 있으니
 +
 +===== 앱이 제대로 설치되지 않거나 실행 안되는 경우 =====
 +
 +  * 용량 문제가 아니라면 **AndroidManifest.xml** 파일 설정에 패키지 이름이 맞는지 확인
 +
 +<code 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>
 +</code>
 +
 +====== 첨부파일 ======
 +
 +{{:language:unity:plugin:plugin_android_140305.7z|}}