456 lines
16 KiB
C#
456 lines
16 KiB
C#
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.IO;
|
||
|
using System.Linq;
|
||
|
using System.Reflection;
|
||
|
using System.Xml.Linq;
|
||
|
using UnityEditor;
|
||
|
using UnityEditor.Build.Reporting;
|
||
|
using UnityEditor.SceneManagement;
|
||
|
using UnityEngine.SceneManagement;
|
||
|
using UnityEngine;
|
||
|
using game;
|
||
|
|
||
|
#if RND_FACEBOOK
|
||
|
using Facebook.Unity.Settings;
|
||
|
#endif
|
||
|
#if UNITY_IOS
|
||
|
using UnityEditor.iOS.Xcode;
|
||
|
using UnityEngine.iOS;
|
||
|
#endif
|
||
|
#if PURCHASING_IS_CONNECT
|
||
|
using UnityEngine.Purchasing;
|
||
|
using UnityEditor.Purchasing;
|
||
|
#endif
|
||
|
|
||
|
[InitializeOnLoad]
|
||
|
public static class BuildUtils
|
||
|
{
|
||
|
private static readonly string[] _primaryLevels = { "Assets/Scenes/loader.unity", "Assets/Scenes/game.unity" };
|
||
|
private const string BUILD_LOCATION_PATH = "LOCATION_PATH";
|
||
|
|
||
|
[MenuItem("Game/SetUpBuiltInLevels (Editor)", false, 20)]
|
||
|
private static void SetUpBuiltInLevels()
|
||
|
{
|
||
|
string[] levels = GetBuiltInLevels();
|
||
|
var scenes = new List<EditorBuildSettingsScene>(levels.Length);
|
||
|
|
||
|
foreach (string lvl in levels)
|
||
|
scenes.Add(new EditorBuildSettingsScene(lvl, true));
|
||
|
|
||
|
EditorBuildSettings.scenes = scenes.ToArray();
|
||
|
}
|
||
|
|
||
|
private static string[] GetBuiltInLevels()
|
||
|
{
|
||
|
const string dir = "Assets/Scenes/Levels";
|
||
|
|
||
|
if (Directory.Exists(dir) == false)
|
||
|
{
|
||
|
Log.Warn($"Levels directory not found from path {dir}");
|
||
|
return _primaryLevels;
|
||
|
}
|
||
|
|
||
|
string[] files = Directory.GetFiles(dir).Where(file => file.EndsWith(".unity")).ToArray();
|
||
|
Log.Info($"Founded game levels count: {files?.Length}");
|
||
|
files.ForEach(lvl => Log.Info($"Level add to built in {lvl}"));
|
||
|
|
||
|
var levels = new List<string>(_primaryLevels);
|
||
|
levels.AddRange(files);
|
||
|
return levels.ToArray();
|
||
|
}
|
||
|
|
||
|
[MenuItem("Game/BuildAndPlay (Editor)", false, 20)]
|
||
|
public static void BuildAndPlay()
|
||
|
{
|
||
|
if (Build())
|
||
|
PlayGameScene();
|
||
|
}
|
||
|
|
||
|
private static void Recompile()
|
||
|
{
|
||
|
var editorAssembly = Assembly.GetAssembly(typeof(Editor));
|
||
|
Type editorCompilationInterfaceType =
|
||
|
editorAssembly.GetType("UnityEditor.Scripting.ScriptCompilation.EditorCompilationInterface");
|
||
|
MethodInfo dirtyAllScriptsMethod =
|
||
|
editorCompilationInterfaceType.GetMethod("DirtyAllScripts", BindingFlags.Static | BindingFlags.Public);
|
||
|
dirtyAllScriptsMethod.Invoke(editorCompilationInterfaceType, null);
|
||
|
}
|
||
|
|
||
|
public static void BuildAndPlayRecompile()
|
||
|
{
|
||
|
Recompile();
|
||
|
BuildAndPlay();
|
||
|
}
|
||
|
|
||
|
public static void RecompileAndPlay()
|
||
|
{
|
||
|
Recompile();
|
||
|
PlayGameScene();
|
||
|
}
|
||
|
|
||
|
private static void PlayGameScene()
|
||
|
{
|
||
|
OpenGameScene();
|
||
|
EditorApplication.isPlaying = true;
|
||
|
}
|
||
|
|
||
|
private static void OpenGameScene()
|
||
|
{
|
||
|
Scene scene = SceneManager.GetActiveScene();
|
||
|
|
||
|
if (scene.name != "game")
|
||
|
EditorSceneManager.OpenScene("Assets/Scenes/game.unity");
|
||
|
}
|
||
|
|
||
|
private static BuildOptions GetBuildOptions()
|
||
|
{
|
||
|
//var arr = Settings.BUILD_CLIENT_OPTS.Split(',');
|
||
|
const BuildOptions opts = new BuildOptions();
|
||
|
|
||
|
//foreach(var name in arr)
|
||
|
// opts |= name.ParseEnum<BuildOptions>();
|
||
|
|
||
|
return opts;
|
||
|
}
|
||
|
|
||
|
[MenuItem("Game/SetupCommonPlayerSettings (Editor)", false, 20)]
|
||
|
private static void SetupCommonPlayerSettings()
|
||
|
{
|
||
|
PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.Android ,Settings.PACKAGE_ID_FULL);
|
||
|
PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.iOS ,Settings.PACKAGE_ID_FULL);
|
||
|
|
||
|
PlayerSettings.bundleVersion = Settings.VERSION;
|
||
|
PlayerSettings.productName = Settings.CLIENT_APP_NAME;
|
||
|
PlayerSettings.companyName = Settings.GAME_COMPANY_NAME;
|
||
|
PlayerSettings.stripUnusedMeshComponents = true;
|
||
|
|
||
|
#if RND_FACEBOOK
|
||
|
FacebookSettings.AppLabels = new List<string>(){Settings.CLIENT_APP_NAME};
|
||
|
FacebookSettings.AppIds = new List<string>(){Settings.FACEBOOK_APP_ID};
|
||
|
FacebookSettings.AndroidKeystorePath = "user-key-store.keystore";
|
||
|
#endif
|
||
|
|
||
|
#if PURCHASING_IS_CONNECT
|
||
|
PurchasingSettings.enabled = Settings.UNITY_CONNECT_PURCHASING;
|
||
|
#endif
|
||
|
UpdateIconsForBuildTarget(BuildTargetGroup.Android, Settings.CLIENT_APP_ICON);
|
||
|
UpdateIconsForBuildTarget(BuildTargetGroup.iOS, Settings.CLIENT_APP_ICON);
|
||
|
}
|
||
|
|
||
|
static void UpdateIconsForBuildTarget(BuildTargetGroup target, string icon)
|
||
|
{
|
||
|
Log.Info($"UpdateIconsForBuildTarget: {target} - {icon}");
|
||
|
|
||
|
int[] iconSizes = PlayerSettings.GetIconSizesForTargetGroup(target);
|
||
|
var icons = new Texture2D[iconSizes.Length];
|
||
|
|
||
|
for (var i = 0; i < iconSizes.Length; i++)
|
||
|
icons[i] = AssetDatabase.LoadAssetAtPath<Texture2D>(icon);
|
||
|
|
||
|
PlayerSettings.SetIconsForTargetGroup(target, icons);
|
||
|
}
|
||
|
|
||
|
private static void EnableIL2CPP(BuildTargetGroup targetGroup, bool flag)
|
||
|
{
|
||
|
ScriptingImplementation backend = flag ? ScriptingImplementation.IL2CPP : ScriptingImplementation.Mono2x;
|
||
|
PlayerSettings.SetScriptingBackend(targetGroup, backend);
|
||
|
}
|
||
|
|
||
|
//[MenuItem("Game/Build/Android APK + Run %R", false, 21)]
|
||
|
public static void BuildAPKAndRun()
|
||
|
{
|
||
|
BuildAPK(opts => opts.Add(BuildOptions.AutoRunPlayer));
|
||
|
}
|
||
|
|
||
|
[MenuItem("Game/Build/Android APK %D", false, 22)]
|
||
|
public static void BuildAPKOnly()
|
||
|
{
|
||
|
BuildAPK(opts => opts.Remove(BuildOptions.AutoRunPlayer));
|
||
|
}
|
||
|
|
||
|
private static void BuildAPK(BuildOptionsTweak tweak)
|
||
|
{
|
||
|
BuildAPK(PrepareBuild(tweak));
|
||
|
}
|
||
|
|
||
|
public static void ExportAndroidProject()
|
||
|
{
|
||
|
ExportAndroidProject(opts => opts.Remove(BuildOptions.AutoRunPlayer));
|
||
|
}
|
||
|
|
||
|
private static void ExportAndroidProject(BuildOptionsTweak tweak)
|
||
|
{
|
||
|
ExportAndroidProject(PrepareBuild(tweak));
|
||
|
}
|
||
|
|
||
|
[MenuItem("Game/Build/iOS Project (Clean)", false, 24)]
|
||
|
public static void BuildXCodeProjectClean()
|
||
|
{
|
||
|
BuildXCode(opts => opts
|
||
|
.Remove(BuildOptions.AutoRunPlayer)
|
||
|
.Remove(BuildOptions.AcceptExternalModificationsToPlayer)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
private static void BuildXCode(BuildOptionsTweak tweak)
|
||
|
{
|
||
|
BuildXCode(PrepareBuild(tweak));
|
||
|
}
|
||
|
|
||
|
private static void BuildXCode(BuildOptions buildOpts)
|
||
|
{
|
||
|
PlayerSettings.iOS.allowHTTPDownload = true;
|
||
|
PlayerSettings.iOS.targetOSVersionString = "10.0";
|
||
|
PlayerSettings.iOS.sdkVersion = iOSSdkVersion.DeviceSDK; //TODO: simulator build support?
|
||
|
|
||
|
#if UNITY_IOS
|
||
|
PlayerSettings.iOS.deferSystemGesturesMode = SystemGestureDeferMode.TopEdge | SystemGestureDeferMode.BottomEdge;
|
||
|
#endif
|
||
|
PlayerSettings.iOS.appleEnableAutomaticSigning = true;
|
||
|
|
||
|
const BuildTarget target = BuildTarget.iOS;
|
||
|
PlayerSettings.SetUseDefaultGraphicsAPIs(target, automatic: true);
|
||
|
|
||
|
PlayerSettings.iOS.appleDeveloperTeamID = Settings.IOS_TEAM_ID;
|
||
|
PlayerSettings.iOS.buildNumber = $"{PlayerSettings.bundleVersion}.{Settings.VERSION_REV_NUMBER}";
|
||
|
|
||
|
#if UNITY_IOS
|
||
|
//IOSCredentialsPostProcess.ReplaceFirebasePlistAccordingToBundleID(); //TODO: Delete?
|
||
|
#endif
|
||
|
|
||
|
EnableIL2CPP(BuildTargetGroup.iOS, true);
|
||
|
string locationPath = Environment.GetEnvironmentVariable(BUILD_LOCATION_PATH); //TODO: To method?
|
||
|
|
||
|
if (string.IsNullOrEmpty(locationPath))
|
||
|
throw new InvalidDataException($"Environment variable {BUILD_LOCATION_PATH} is NULL"); //TODO: With this
|
||
|
|
||
|
BuildPipeline.BuildPlayer(GetBuiltInLevels(), locationPath, target, buildOpts);
|
||
|
}
|
||
|
|
||
|
private static BuildReport BuildStandalone(BuildOptionsTweak tweak)
|
||
|
{
|
||
|
return BuildStandalone(PrepareBuild(tweak));
|
||
|
}
|
||
|
|
||
|
private static void ExportAndroidProject(BuildOptions buildOpts)
|
||
|
{
|
||
|
var ver = (int)Settings.VERSION_CODE;
|
||
|
AndroidArchitecture arch = ParseArch();
|
||
|
int bundleCode = MakeAndroidBundleVersionCode();
|
||
|
|
||
|
Log.Info($"==== GAME VERSION CODE: {ver}, BUNDLE VERSION CODE: {bundleCode} ({arch}) ====");
|
||
|
SetupAndroidBuildSettings(bundleCode, arch);
|
||
|
EnableIL2CPP(BuildTargetGroup.Android, Settings.ANDROID_USE_IL2CPP);
|
||
|
|
||
|
string locationPath = Environment.GetEnvironmentVariable(BUILD_LOCATION_PATH);
|
||
|
|
||
|
if (string.IsNullOrEmpty(locationPath))
|
||
|
throw new InvalidDataException($"Environment variable {BUILD_LOCATION_PATH} is NULL");
|
||
|
|
||
|
BuildPipeline.BuildPlayer(GetBuiltInLevels(), string.Concat(locationPath, ".proj"), BuildTarget.Android, buildOpts);
|
||
|
AndroidRepackNativeDebugSymbols();
|
||
|
}
|
||
|
|
||
|
private static void BuildAPK(BuildOptions buildOpts)
|
||
|
{
|
||
|
var ver = (int)Settings.VERSION_CODE;
|
||
|
AndroidArchitecture arch = ParseArch();
|
||
|
int bundleCode = MakeAndroidBundleVersionCode();
|
||
|
|
||
|
Log.Info($"==== GAME VERSION CODE: {ver}, BUNDLE VERSION CODE: {bundleCode} ({arch}) ====");
|
||
|
SetupAndroidBuildSettings(bundleCode, arch);
|
||
|
EnableIL2CPP(BuildTargetGroup.Android, Settings.ANDROID_USE_IL2CPP);
|
||
|
|
||
|
string locationPath = Environment.GetEnvironmentVariable(BUILD_LOCATION_PATH);
|
||
|
|
||
|
if (string.IsNullOrEmpty(locationPath))
|
||
|
throw new InvalidDataException($"Environment variable {BUILD_LOCATION_PATH} is NULL");
|
||
|
|
||
|
if ((buildOpts & BuildOptions.AcceptExternalModificationsToPlayer) != 0)
|
||
|
locationPath += ".proj";
|
||
|
|
||
|
//EditorUserBuildSettings.androidCreateSymbolsZip = false; //TODO: ???
|
||
|
BuildPipeline.BuildPlayer(GetBuiltInLevels(), locationPath, BuildTarget.Android, buildOpts);
|
||
|
AndroidRepackNativeDebugSymbols();
|
||
|
}
|
||
|
|
||
|
[MenuItem("Game/SetupAndroidResolverDependencies (Editor)", false, 20)]
|
||
|
private static void SetupAndroidResolverDependencies()
|
||
|
{
|
||
|
const string ardPath = "ProjectSettings/AndroidResolverDependencies.xml";
|
||
|
|
||
|
if (File.Exists(ardPath))
|
||
|
{
|
||
|
Log.Info($"Android Resolver Dependencies loading existing file {ardPath}");
|
||
|
XDocument ardDoc = XDocument.Load(ardPath);
|
||
|
|
||
|
Log.Info("ARD_doc " +
|
||
|
ardDoc
|
||
|
.Element("dependencies")
|
||
|
.Element("settings")
|
||
|
.Elements("setting")
|
||
|
.Single(e => e.Attribute("name").Value == "bundleId").Attribute("value").Value
|
||
|
);
|
||
|
|
||
|
XElement bundleId = ardDoc
|
||
|
.Element("dependencies")
|
||
|
.Element("settings")
|
||
|
.Elements("setting")
|
||
|
.Single(e => e.Attribute("name").Value == "bundleId");
|
||
|
|
||
|
Log.Info($"bundleId {bundleId.Attribute("value").Value}");
|
||
|
bundleId.Attribute("value").Value = Settings.PACKAGE_ID;
|
||
|
ardDoc.Save(ardPath);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Log.Info($"Android Resolver Dependencies not found {ardPath} file");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void SetupAndroidBuildSettings(int bundleCode, AndroidArchitecture arch)
|
||
|
{
|
||
|
PlayerSettings.Android.bundleVersionCode = bundleCode;
|
||
|
PlayerSettings.Android.targetArchitectures = arch;
|
||
|
PlayerSettings.Android.keyaliasName = "android-build";
|
||
|
PlayerSettings.Android.keyaliasPass = "WV7fx6pRykCk";
|
||
|
PlayerSettings.Android.keystorePass = "WV7fx6pRykCk";
|
||
|
|
||
|
PlayerSettings.Android.minSdkVersion = AndroidSdkVersions.AndroidApiLevel23;
|
||
|
PlayerSettings.Android.targetSdkVersion = AndroidSdkVersions.AndroidApiLevelAuto;
|
||
|
|
||
|
EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Gradle;
|
||
|
EditorUserBuildSettings.exportAsGoogleAndroidProject = false;
|
||
|
EditorUserBuildSettings.buildAppBundle = true;
|
||
|
|
||
|
SetupAndroidResolverDependencies();
|
||
|
|
||
|
//TODO: ?
|
||
|
//EditorUserBuildSettings.androidReleaseMinification = AndroidMinification.Gradle;
|
||
|
//EditorUserBuildSettings.androidDebugMinification = AndroidMinification.Gradle;
|
||
|
|
||
|
//EditorUserBuildSettings.androidCreateSymbolsZip = Settings.ANDROID_NATIVE_DEBUG_SYMBOLS_ZIP &&
|
||
|
// Settings.ANDROID_USE_IL2CPP;
|
||
|
|
||
|
AndroidSelectTargetStore();
|
||
|
}
|
||
|
|
||
|
private static void AndroidSelectTargetStore()
|
||
|
{
|
||
|
#if PURCHASING_IS_CONNECT
|
||
|
switch(Settings.ANDROID_TARGET_STORE)
|
||
|
{
|
||
|
case EnumAndroidStore.AMAZON:
|
||
|
UnityPurchasingEditor.TargetAndroidStore(AppStore.AmazonAppStore);
|
||
|
break;
|
||
|
case EnumAndroidStore.GPLAY:
|
||
|
UnityPurchasingEditor.TargetAndroidStore(AppStore.GooglePlay);
|
||
|
break;
|
||
|
default:
|
||
|
throw new ArgumentNullException($"Unknown Android store flavor: {Settings.ANDROID_TARGET_STORE}");
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
private static string AndroidFlavorFilenameSuffix()
|
||
|
{
|
||
|
switch(Settings.ANDROID_TARGET_STORE)
|
||
|
{
|
||
|
case EnumAndroidStore.GPLAY:
|
||
|
return "google_play";
|
||
|
default:
|
||
|
throw new ArgumentNullException($"Unknown Android store flavor: {Settings.ANDROID_TARGET_STORE}");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void AndroidRepackNativeDebugSymbols()
|
||
|
{
|
||
|
if (!EditorUserBuildSettings.androidCreateSymbolsZip)
|
||
|
return;
|
||
|
|
||
|
string ndkPath = EditorPrefs.GetString("AndroidNdkRootR16b", "");
|
||
|
|
||
|
if (string.IsNullOrEmpty(ndkPath))
|
||
|
{
|
||
|
Log.Error("Android NDK path not specified in Unity Editor Preferences - can't repack Android debug symbols");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var prefix = $"{Settings.GAME_PROJECT_NAME}_{AndroidFlavorFilenameSuffix()}";
|
||
|
|
||
|
//E.g.: game_google_play-24.0.0-v3917800.symbols.zip
|
||
|
var zipName =
|
||
|
$"{prefix}-{PlayerSettings.bundleVersion}-v{PlayerSettings.Android.bundleVersionCode}.symbols.zip";
|
||
|
var outName = $"{prefix}.symbols.zip";
|
||
|
|
||
|
//EditorDevUtils.ProcessGamectlTask($"android_native_debug_symbols_repack {ndk_path} {zip_name} {out_name}"); //TODO: mem
|
||
|
}
|
||
|
|
||
|
private static BuildReport BuildStandalone(BuildOptions buildOpts)
|
||
|
{
|
||
|
PlayerSettings.fullScreenMode = FullScreenMode.Windowed;
|
||
|
var binName = $"{Settings.GAME_PROJECT_NAME}";
|
||
|
return BuildPipeline.BuildPlayer(GetBuiltInLevels(), binName, BuildTarget.StandaloneOSX, buildOpts);
|
||
|
}
|
||
|
|
||
|
private static AndroidArchitecture ParseArch()
|
||
|
{
|
||
|
var architecture = AndroidArchitecture.None;
|
||
|
|
||
|
//TODO: mem
|
||
|
//string[] arch_strs = Settings.ANDROID_TARGET_ARCHITECTURES.Trim().Split(',');
|
||
|
//for(int i = 0; i < arch_strs.Length; i++)
|
||
|
// architecture |= arch_strs[i].ParseEnum<AndroidArchitecture>();
|
||
|
|
||
|
architecture |= AndroidArchitecture.ARM64;
|
||
|
architecture |= AndroidArchitecture.ARMv7;
|
||
|
|
||
|
return architecture;
|
||
|
}
|
||
|
|
||
|
private static int MakeAndroidBundleVersionCode()
|
||
|
{
|
||
|
return (int)(Settings.VERSION_REV_NUMBER * 100); //Last two digits reserved for possible suffixes
|
||
|
}
|
||
|
|
||
|
private static bool Build()
|
||
|
{
|
||
|
if (ShouldAskToSaveScene() && !EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
|
||
|
return false;
|
||
|
|
||
|
Log.Info("=== Building Game ===");
|
||
|
return Urun();
|
||
|
}
|
||
|
|
||
|
public static bool Urun()
|
||
|
{
|
||
|
return EditorDevUtils.ProcessGamectlTask("urun");
|
||
|
}
|
||
|
|
||
|
private static bool ShouldAskToSaveScene()
|
||
|
{
|
||
|
return !Application.isPlaying &&
|
||
|
SceneManager.GetActiveScene().name.IndexOf("game.unity", StringComparison.Ordinal) == -1;
|
||
|
}
|
||
|
|
||
|
private static BuildOptions Add(this BuildOptions opts, BuildOptions newOpt)
|
||
|
{
|
||
|
return opts |= newOpt;
|
||
|
}
|
||
|
|
||
|
private static BuildOptions Remove(this BuildOptions opts, BuildOptions uselessOpt)
|
||
|
{
|
||
|
return opts &= ~uselessOpt;
|
||
|
}
|
||
|
|
||
|
private static BuildOptions PrepareBuild(BuildOptionsTweak tweak)
|
||
|
{
|
||
|
Build();
|
||
|
SetupCommonPlayerSettings();
|
||
|
return tweak(GetBuildOptions());
|
||
|
}
|
||
|
|
||
|
private delegate BuildOptions BuildOptionsTweak(BuildOptions rawOpts);
|
||
|
}
|