/// <summary>
/// Unity - Android) Plugin 제작하기 (part. 1-1)
/// @author : CloudD
/// @last update : 2023. 05. 26
/// @update
/// - 2023. 05. 26 : 최초 작성.
/// AAR 파일 생성 및 함수 호출 방법 정리
/// </summary>
오랜만에 글을 쓰게 되었네요.
회사에서 2021년 부터 진행 중인 과제의 마무리를 한다고 한동안 바쁜 시간을 보내고 왔습니다... ㅠ
오늘부터 몇 번에 걸쳐서 Unity 에서 사용할 Android Plugin 을 만드는 방법을 정리하려고 합니다.
Unity Document ( 참고 링크 )를 보면,
Unity 에서 Android Plugin 을 사용하는 방법 5가지를 소개하고 있습니다.
* AAR 플러그인과 Android 라이브러리
* JAR 플러그인
* UnityPlayerActivity Code 확장
* 네이티브 (C++) 플러그인
* Java 및 Kotlin 소스 파일을 플러그인으로 사용
이 중에서 오늘 다뤄볼 내용은 AAR 플러그인 입니다.
지극히 개인적인 경험이지만, 제가 경험한 바로는
간단한 기능 또는 피드백을 받지 않아도 되는 기능 (예를 들어, Log 또는 Toast 같은) 을 사용할 때
편하게 사용할 수 있었습니다.
그래서 제가 오늘 정리할 부분들도 아주 간단하지만 중요한,
Log 남기기와 Toast 출력하기 입니다.
우선 이 작업을 하기 위한 프로젝트를 하나 생성하겠습니다.
2D Mobile Core Template 를 선택하고 프로젝트를 생성했습니다.
![]() |
프로젝트 이름은 매우 직관적으로 보이게끔, 'plugin_test' 라고 지었습니다. |
Log 와 Toast 를 출력하기 위한 용도에 맞게,
InputField 하나와 Button 이 두 개 있는 아주 간단한 UI 를 구성해 주었습니다.
![]() |
Simple is best... |
이 위에서 동작할 스크립트도 하나 추가합니다. PluginTest.cs 라는 이름으로 파일을 하나 생성합니다.
using UnityEngine; | |
using UnityEngine.UI; | |
using TMPro; | |
#if UNITY_ANDROID && !UNITY_EDITOR | |
using UnityEngine.Android; | |
#endif | |
public class PluginTest : MonoBehaviour | |
{ | |
[SerializeField] private TMP_InputField _inputText; | |
[SerializeField] private Button _btnLog; | |
[SerializeField] private Button _btnToast; | |
#if UNITY_ANDROID && !UNITY_EDITOR | |
// const string | |
private const string PackageName = "kr.blogspot.incentive_code.plugin_test.Bridge"; | |
private const string UnityDefaultJavaClassName = "com.unity3d.player.UnityPlayer"; | |
private const string UnityDefaultActivityName = "currentActivity"; | |
private const string PluginSetActivity = "setActivity"; | |
private const string PluginAndroidLog = "androidLog"; | |
private const string PluginAndroidToast = "androidToast"; | |
// Android Plugin | |
private AndroidJavaClass unityClass; | |
private AndroidJavaObject unityActivity; | |
private AndroidJavaObject pluginObject; | |
#endif | |
#region Unity function | |
private void Awake() | |
{ | |
// set button listener | |
_btnLog.onClick.AddListener(() => AndroidLog(_inputText.text)); | |
_btnToast.onClick.AddListener(() => AndroidToast(_inputText.text)); | |
#if UNITY_ANDROID && !UNITY_EDITOR | |
// #1. UnityActivity 를 확보하고, Plugin 으로 전달합니다. | |
unityClass = new AndroidJavaClass(UnityDefaultJavaClassName); | |
unityActivity = unityClass.GetStatic<AndroidJavaObject>(UnityDefaultActivityName); | |
pluginObject = new AndroidJavaObject(PackageName); | |
pluginObject.Call(PluginSetActivity, unityActivity); | |
#else | |
Debug.Log(SystemInfo.operatingSystem); | |
#endif | |
} | |
#endregion // Unity function | |
#region Simple functions | |
public void AndroidLog(string msg) | |
{ | |
#if UNITY_ANDROID && !UNITY_EDITOR | |
pluginObject.Call(PluginAndroidLog, msg); | |
#else | |
Debug.Log($"AndroidLog() : {msg}"); | |
#endif | |
} | |
public void AndroidToast(string msg) | |
{ | |
#if UNITY_ANDROID && !UNITY_EDITOR | |
pluginObject.Call(PluginAndroidToast, msg); | |
#else | |
Debug.Log($"AndroidToast() : {msg}"); | |
#endif | |
} | |
#endregion // Simple functions | |
} |
위 코드에서 중요한 파트는 const string 부분과 Awake() 입니다.
PackageName 은 이후에 추가할 Android plugin project 의 패키지 이름입니다.
UnityDefaultJavaClassName 과 UnityDefaultActivityName 은 이 프로젝트가 android app 으로 빌드했을 때 갖게되는 기본 이름 이라고 보시면 되겠습니다.
그 아래 있는 setActivity, androidLog, androidToast 는 kotlin 으로 작성할 함수 이름입니다. plugin 이 가지고 있는 함수를 쉽게 호출하기 위해 따로 적어두었습니다.
그럼, 이번엔 Android Studio 를 이용해서 플러그인 프로젝트를 생성해보겠습니다.
Android Studio 를 실행 후 New Project 메뉴를 선택합니다.
![]() |
어차피 화면을 그릴 것이 아니니, No Activity 를 선택합니다. |
![]() |
패키지 명은 Unity 프로젝트의 패키지 명과 동일하게 가져갑니다. |
프로젝트가 생성되면 Bridge.kt 파일을 생성합니다.
package kr.blogspot.incentive_code.plugin_test | |
import android.annotation.SuppressLint | |
import android.app.Activity | |
import android.app.Application | |
import android.content.Context | |
import android.util.Log | |
import android.widget.Toast | |
class Bridge : Application() { | |
companion object { | |
private const val TAG = "Bridge" | |
@SuppressLint("StaticFieldLeak") | |
var mActivity: Activity? = null | |
@SuppressLint("StaticFieldLeak") | |
var mContext: Context? = null | |
} | |
override fun onCreate() { | |
super.onCreate() | |
mContext = applicationContext | |
} | |
// ----- ----- ----- | |
// Unity 에서 호출할 함수들 | |
fun setActivity(activity: Activity?) { | |
if (activity != null) { | |
mActivity = activity | |
Log.d(TAG, "call setActivity()") | |
} | |
} | |
// #1. simple function (Log.d() / Toast()) | |
fun androidLog(msg: String) { | |
Log.d(TAG, msg); | |
} | |
fun androidToast(msg: String) { | |
Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show() | |
} | |
} |
그 아래 있는 applicationId 값은 주석 처리 하거나 삭제합니다.
![]() |
이런 모양으로 값을 수정하시면 됩니다. |
![]() |
Android plugin 을 만들다보면 자주 만나는 ActionFacade 그리고 AAPT |
마지막으로 Run configuration 을 수정합니다.
![]() |
저 선명한 X 표를 없애야 합니다. |
저 영역을 클릭 후, Edit Configuration 이라는 메뉴를 클릭합니다.
![]() |
이렇게 생성이 되었다면 성공입니다. |
여기까지 오셨으면 이제 aar 파일을 생성할 준비가 완료되었습니다.
![]() |
build / outputs / aar 아래에 app-debug.aar 이 생성됩니다. |
이렇게 생성된 aar 파일을 Unity 의 Assets / Plugins / Android / 아래에 배치합니다.
![]() |
폴더 경로가 이렇게 됩니다. |
![]() |
NoClassDefFoundError ... 필요한 클래스를 runtime 에 찾지 못했다는 의미입니다. |
이 부분에 대해 정확하게 이해하진 못해서 더 자세한 설명을 여기에 적지는 못하지만, 어쨌든 해결 방법은 찾았습니다.
![]() |
저는 주석도 한 줄 넣었습니다. |
이제 다시 Unity 에서 빌드를 하면, 정상적으로 Log 와 Toast 가 찍히는 것을 확인하실 수 있습니다.
하지만 누군가에게는 도움이 될 수 있다면 기쁠 것 같습니다.