====== Unity 플러그인 ====== 문서 출처 : [[http://unitykoreawiki.com/index.php?n=KrMain.Plugins|플러그인 (프로/모바일만 지원) Plugins (Pro/Mobile-only feature)]] ===== 기능 ===== * 다른 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" 문자열 그대로 쓰라는 것인가? [DllImport ("")] #endif private static extern float FooPluginFunction(); void Awake () { // 플러그인안에 FooPluginFunction 을 호출하고 // 콘솔에 5를 출력합니다 print (FooPluginFunction ()); } } ====== 안드로이드 플러그인 ====== ===== 요구사항 ===== * [[http://developer.android.com/sdk/ndk|Android NDK]] * NDK로 공유 라이브러리를 만드는 방법 ===== 주의사항 ===== * C++ 로 작성한다면 C linkage 형태의 %%extern "C"%%를 선언해서 name mangling issule를 피할 것 extern "C" { float FooPluginFunction (); } ===== C#에서 플러그인 사용 ===== 사용할 라이브러리를 **Assets \ Plugins \ Android** 폴더에 복사. 아래 형태로 함수를 선언해 두면 유니티에서는 이름으로 라이브러릴를 찾는다. * %%%% : 라이브러리 이름 [DllImport(" * %%%% : "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 상세. (별로 알고 싶지 않지만, [[http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html|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, "", "()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 ==== - **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;** 가 포함되도록 교정 * 아래 부분 삭제 - 미사용 부분 // 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 빌드 ===== 최소 빌드 규격을 결정 {{: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>%%파일을 복사한다. 플러그인을 **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("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("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("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** 파일 설정에 패키지 이름이 맞는지 확인 ====== 첨부파일 ====== {{:language:unity:plugin:plugin_android_140305.7z|}}