====== 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|}}