본문 바로가기

[unity] Build Script class

앤디가이 2022. 5. 20.

Unity에서 프로젝트 세팅으로 하는 빌드가 아닌, 빌드를 스크립트를 통해서 빌드할 수 있는 Build Script Class를 제작해보자.

 

1.  Build Script를 사용해야 되는 이유

  - Unity에서 빌드는 File Build Settting -> Build or Build And Run을 통한 빌드를 하는 것이 일반적이다.

하지만 프로젝트를 열때마다 입력해야 하는 keystore 비밀번호나, apk 경로, apk 이름 등을 간소화하고자 빌드 스크립트를 이용하는 경우도 많아지고 있다.

 - 게임이나 앱 제작 시 빌드 스크립트만 잘 구축해 놓아도 상당히 시간 비용을 줄일 수 있다.

 

2.  Build Script 제작 방법

1. 우선 프로젝트를 열고 Asset/Scripts/Editor 폴더를 만들어 준다.

Build Script 에디터 폴더 구성
Build Script 에디터 폴더 구성

2. 해당 폴더에 BuildScript.cs 스크립트를 하나 생성 후 아래와 같이 코딩해보자!

using UnityEditor;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System;

namespace Container.Build
{
    public class BuildScript
    {
        /* App Info */
        private const string APP_NAME = "APPNAME"; //APK 명칭
        protected const string KEYSTORE_PASSWORD = "********";
        private const string BUILD_BASIC_PATH = "../../build/";
        private const string BUILD_ANDROID_PATH = BUILD_BASIC_PATH + "Android/";
        private const string BUILD_IOS_PATH = BUILD_BASIC_PATH + "Ios/";

        /* IOS 권한 메세지 정보 */
        private const string PHOTO_LIBRARY_USAGE_DESCRIPTION = "앱과 상호 작용하려면 사진 액세스 권한이 필요합니다.";
        private const string PHOTO_LIBRARY_ADDITIONS_USAGE_DESCRIPTION = "이 앱에 미디어를 저장하려면 사진에 액세스할 수 있어야 합니다.";
        private const string MICROPHONE_USAGE_DESCRIPTION = "앱 내 음성 확인 콘텐츠를 활용하려면 마이크 권한이 필요합니다.";
        private const bool DONT_ASK_LIMITED_PHOTOS_PERMISSION_AUTOMATICALLY_ON_IOS14 = true;

        [MenuItem("Builder/Build/BuildForAndroid")]
        public static void BuildForAndroid()
        {
            string fileName = SetPlayerSettingsForAndroid();

            BuildPlayerOptions buildOption = new BuildPlayerOptions();

            buildOption.locationPathName = BUILD_ANDROID_PATH + fileName;
            buildOption.scenes = GetBuildSceneList();
            buildOption.target = BuildTarget.Android;
            buildOption.options = BuildOptions.AutoRunPlayer;
            BuildPipeline.BuildPlayer(buildOption);
        }

        [MenuItem("Builder/Build/BuildForIOS")]
        public static void BuildForIOS()
        {
            BuildPlayerOptions buildOption = new BuildPlayerOptions();
            buildOption.target = BuildTarget.iOS;
            buildOption.scenes = GetBuildSceneList();
            buildOption.locationPathName = BUILD_IOS_PATH;
            BuildPipeline.BuildPlayer(buildOption);
        }

        [MenuItem("Builder/OpenBuildDirectory")]
        public static void OpenBuildDirectory()
        {
            OpenFileBrowser(Path.GetFullPath(BUILD_BASIC_PATH));
        }

        public static void OpenFileBrowser(string path)
        {
            bool openInsidesOfFolder = false;

            if (Directory.Exists(path))
            {
                openInsidesOfFolder = true;
            }

            string arguments = (openInsidesOfFolder ? "" : "-R ") + path;
            try
            {
                System.Diagnostics.Process.Start("open", arguments);
            }
            catch (Exception e)
            {
                Debug.Log("Failed to open path : " + e.ToString());
            }
        }

        /// <summary>
        /// 현재 빌드세팅의 Scene리스트를 받아옴.
        /// Enable이 True인 것만 받아옴.
        /// </summary>
        /// <returns></returns>
        protected static string[] GetBuildSceneList()
        {
            EditorBuildSettingsScene[] scenes = UnityEditor.EditorBuildSettings.scenes;

            List<string> listScenePath = new List<string>();

            for (int i = 0; i < scenes.Length; i++)
            {
                if (scenes[i].enabled)
                    listScenePath.Add(scenes[i].path);
            }

            return listScenePath.ToArray();
        }

        protected static string SetPlayerSettingsForAndroid()
        {
            PlayerSettings.Android.keystorePass = KEYSTORE_PASSWORD;
            PlayerSettings.Android.keyaliasPass = KEYSTORE_PASSWORD;
            PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARM64 | AndroidArchitecture.ARMv7;

            string fileName = string.Format("{0}_{1}.apk", APP_NAME, PlayerSettings.bundleVersion);
            return fileName;
        }

#if UNITY_IOS
#pragma warning disable 0162
    [PostProcessBuild]
    public static void OnPostprocessBuild(BuildTarget target, string buildPath)
    {
        if (target == BuildTarget.iOS)
        {
            string pbxProjectPath = PBXProject.GetPBXProjectPath(buildPath);
            string plistPath = Path.Combine(buildPath, "Info.plist");
 
            PBXProject pbxProject = new PBXProject();
            pbxProject.ReadFromFile(pbxProjectPath);
 
#if UNITY_2019_3_OR_NEWER
                string targetGUID = pbxProject.GetUnityFrameworkTargetGuid();
#else
            string targetGUID = pbxProject.TargetGuidByName(PBXProject.GetUnityTargetName());
#endif
            //필요한 라이브러리 추가//
            //pbxProject.AddBuildProperty(targetGUID, "OTHER_LDFLAGS", "-weak_framework PhotosUI");
            //pbxProject.AddBuildProperty(targetGUID, "OTHER_LDFLAGS", "-framework Photos");
            //pbxProject.AddBuildProperty(targetGUID, "OTHER_LDFLAGS", "-framework MobileCoreServices");
            //pbxProject.AddBuildProperty(targetGUID, "OTHER_LDFLAGS", "-framework ImageIO");
 
            //pbxProject.RemoveFrameworkFromProject(targetGUID, "Photos.framework");
 
            //File.WriteAllText(pbxProjectPath, pbxProject.WriteToString());
 
            PlistDocument plist = new PlistDocument();
            plist.ReadFromString(File.ReadAllText(plistPath));
 
            PlistElementDict rootDict = plist.root;
            //수출 규정 물어보지 않게 하기 위한 옵션.
            rootDict.SetBoolean("ITSAppUsesNonExemptEncryption", false);
            //사진첩 사용권한 설명.
            rootDict.SetString("NSPhotoLibraryUsageDescription", PHOTO_LIBRARY_USAGE_DESCRIPTION);
            //사진추가 사용권한 설명.
            rootDict.SetString("NSPhotoLibraryAddUsageDescription", PHOTO_LIBRARY_ADDITIONS_USAGE_DESCRIPTION);
            //마이크 사용권한 설명.
            rootDict.SetString("NSMicrophoneUsageDescription", MICROPHONE_USAGE_DESCRIPTION);
 
            //if (DONT_ASK_LIMITED_PHOTOS_PERMISSION_AUTOMATICALLY_ON_IOS14)
            //    rootDict.SetBoolean("PHPhotoLibraryPreventAutomaticLimitedAccessAlert", true);
 
            File.WriteAllText(plistPath, plist.WriteToString());
        }
    }
#pragma warning restore 0162
#endif
    }
}

3. 컴파일 후 Unity 상단의 에디터 메뉴에 보면 빌드 메뉴가 생성된걸 확인할 수 있다.

Build Editor Menu 생성
Build Editor Menu 생성

4. Build And Run 항목이 싫다면 아래 코드 처럼 AutoRunPlayer를 주석처리해주면 된다.

        [MenuItem("Builder/Build/BuildForAndroid")]
        public static void BuildForAndroid()
        {
            string fileName = SetPlayerSettingsForAndroid();

            BuildPlayerOptions buildOption = new BuildPlayerOptions();

            buildOption.locationPathName = BUILD_ANDROID_PATH + fileName;
            buildOption.scenes = GetBuildSceneList();
            buildOption.target = BuildTarget.Android;
            //buildOption.options = BuildOptions.AutoRunPlayer;
            BuildPipeline.BuildPlayer(buildOption);
        }

5. BuildForAndroid 를 눌러서 빌드를 해보면 APK가 추출된 걸 볼 수 있다.

App 이름과 키스토어 비밀번호는 각자 앱 환경에 맞게 코드를 바꿔줘야 한다.

private const string APP_NAME = "APPNAME"; //APK 명칭
protected const string KEYSTORE_PASSWORD = "********";

6. IOS의 경우는 빌드 후 OnPostprocessBuild 함수를 통해. 빌드 후에 xcode 에서 해야 될 사항들을 자동화할 수 있다.

위 샘플 코드에서는 빌드 후 xcode의 Info.plist 파일을 편집해주는 코드 및 라이브러리 추가하는 방법이 들어 있다.

개인 앱에 맞게 적절히 활용하면 된다.

 

댓글