unity遊戲框架學習-一鍵打包

概述:http://www.javashuo.com/article/p-nggymcxb-bw.htmlhtml

注意事項:java

1.python如何解析命令行參數python

2.python如何調用unity命令進行打包android

3.unity如何解析命令行參數,例如命令行傳過來的:ios

'"%s" -batchmode -projectPath %s -executeMethod ExportProject.Build name:"%s" output:"%s" id:"%s" symbols:"%s" development:%s release:%s language:%s checkupdate:%s expansion:%s version:"%s" compatibility:%s -quit -logFile ./log/%s'git

其中name是bundleid,識別平臺、是不是測試包等,release是不是正式包等等參數。json

4.如何更新gitc#

5.unity一鍵導出圖集、ab包名字,生個成功md5文件windows

6.配製參數導出到兩端平臺api

ios:修改info.plist文件

android:將配置保存成java能夠解析的本地文件,例如json\.properties文件,在導出結束後拷貝到android工程目錄下。

 

詳細步驟:

1.python調用unity命令,通知unity打包AssetBundle.

1-1.更新git

1-2.一鍵設置圖集

1-3.一鍵設置ab包

1-4.生成ab包

1-5.壓縮ab包(可選)

1-6.生成md5文件

2.python調用unity命令,導出工程。(正常打包)

2-1.導出工程

2-2.導出配置文件,如app id,app key。

3.python調用兩端命令進行打包。(正常打包)

3-1.python拷貝文件,組合unity導出項目和兩端備份工程。

3-2.android端調用:./gradlew assembleNormal

3-3.ios端先調用pod repo update(可選,看sdk是否從遠端拉取),在調用cmdBuild = 'xcodebuild archive  -workspace Unity-iPhone.xcworkspace  -scheme Unity-iPhone -archivePath %s || exit '%(archivePath)生成archive 包,最後調用cmdIpa = 'xcodebuild -exportArchive -archivePath %s -exportPath %s -exportOptionsPlist \"%s/config/ad-hoc.plist\"' % (archivePath, ipaDir, self.CurrPath)生成ipa。

4.python將變動的ab包以及md5文件上傳網站(熱更)

 

下面對一些關鍵步驟進行詳細分析

1、python如何解析命令行參數

api使用:https://docs.python.org/zh-cn/3/howto/argparse.html

API:https://docs.python.org/zh-cn/3/library/argparse.html#action。看完這兩篇,解析參數其實很簡單~

import argparse
import configparser

def Run(self):
        parser = argparse.ArgumentParser()
        
        group = parser.add_mutually_exclusive_group()
        group.add_argument("-f", action="store_true", help="打完整包")
     args
= parser.parse_args()

如上所示,使用python的庫便可解析命令行的參數

1.建立一個解析器:parser = argparse.ArgumentParser()

2.添加解析器要解析的參數add_argument

ArgumentParser.add_argument(name or flags...[, action][, nargs][, const][, default][, type][, choices][, required][, help][, metavar][, dest])

name or flags:參數的flag(名字),例如:foo 或 -f, —foo。注意不帶-的參數是必須輸入的,帶-的是可選參數,能夠再也不命令行輸入。

action:獲取的參數該採起何種提取方式。例如action="store_true",那麼命令行有輸入參數時,該參數爲true,不然爲false。action="store_const",那麼參數等於const的值

const: 被一些 action 和 nargs 選擇所需求的常數

default: 當參數未在命令行中出現時使用的值。

type: 命令行參數應當被轉換成的類型。默認是string型

choice: 參數容許的值。例如choice = [0,1,2]那麼參數值只能是這三個中的一個

help: 對該參數的簡要說明。

3.ArgumentParser 經過 parse_args() 方法解析參數。它將檢查命令行,把每一個參數轉換爲適當的類型而後調用相應的操做。

2、python如何調用unity打包

這邊應該是命令行如何調用。

API說明:https://docs.unity3d.com/Manual/CommandLineArguments.html

fumExport = "\"%s\" -batchmode -projectPath %s -executeMethod ExportAssetBundle.Build symbols:\"%s\" checkupdate:%s -quit -logFile ./log/%s"
cmdExport = fumExport%(self.UnityPath, self.ProjectPath,self.BaseSymbols, checkupdate, logFileName)

主要關注如下幾個參數,參數間以空格隔開

1.-batchmode:打開unity,但不會彈出unity界面。須要確保要打開的項目沒有被unity打開,否則會報錯,一次只能運行一個Unity項目實例。

Run Unity in batch mode. You should always use this in conjunction with the other command line arguments, because it ensures no pop-up windows appear and eliminates the need for any human intervention. 
When an exception occurs during execution of the script code, the Asset server updates fail, or other operations fail, Unity immediately exits with return code 1. Note that in batch mode, Unity sends a minimal version of its log output to the console. However, the Log Files still contain the full log information.
You cannot open a project in batch mode while the Editor has the same project open; only a single instance of Unity can run at a time. Tip: To check whether you are running the Editor or Standalone Player in batch mode, use the Application.isBatchMode operator. If the project has not yet been imported when using -batchmode, the target platform is the default one. To force a different platform when using -batchmode, use the -buildTarget option.

2.-projectPath:要打開的unity工程路徑

3.-executeMethod:要觸發的unity方法,該方法必須是靜態方法且放在Editor目錄下

Execute the static method as soon as Unity opens the project, and after the optional Asset server update is complete.
You can use this to do tasks such as continous integration, performing Unit Tests, making builds or preparing data.
To return an error from the command line process, either throw an exception which causes Unity to exit with return code 1, or call EditorApplication.Exit with a non-zero return code.
To pass parameters, add them to the command line and retrieve them inside the function using System.Environment.GetCommandLineArgs.
To use -executeMethod, you need to place the enclosing script in an Editor folder. The method you execute must be defined as static.

4.-quit:退出unity

5.-logFile:log的輸出路徑,方便在命令失敗時查看日誌

6.-buildTarget:指定目標平臺。建議每一個平臺拷貝一份unity項目而不是經過這個參數指定平臺

3、unity如何解析命令行參數

使用c# 的Environment.GetCommandLineArgs方法能夠獲取到命令行的參數列表

//Returns a string array containing the command-line arguments for the current process.
public
static string[] GetCommandLineArgs ();

建議使用鍵值對的方式定義參數,方便解析和理解。例如symbols:"1" checkupdate:0。解析以下,key對應參數名字如symbols

static string GetValueFromArgs(string key, string defaultValue)
    {
        string ret = defaultValue;
        foreach (string arg in System.Environment.GetCommandLineArgs())
        {
            if (arg.StartsWith(key))
            {
                ret = arg.Split(":"[0])[1];
                break;
            }
        }
        return ret;
    }

4、如何同步git代碼?

git:定位到你的git目錄(cd),而後調用'git pull'命令

svn:定位到你的svn目錄(cd),而後調用'TortoiseProc.exe /command:update /path ' + dist + ' /notempfile' + configs.setting['closeOption']

能夠參考http://www.javashuo.com/article/p-mjhhgqnr-bm.html

那麼在unity能夠調用命令行嗎?  能夠的

public static void ProcessCommand(string command, string argument, string workingdir)
    {
        ProcessStartInfo start = new ProcessStartInfo(command)
        {
            Arguments = argument,
            CreateNoWindow = false,
            ErrorDialog = true,
            UseShellExecute = true,
            WorkingDirectory = workingdir
        };

        Process p = Process.Start (start);

        p.WaitForExit ();
        p.Close ();
    }

調用:

ShellHelper.ProcessCommand("TortoiseProc.exe", "/command:update /path:" + svnPath + " /closeonend:1", null);
ShellHelper.ProcessCommand ("git", "pull", loadDir);

5、如何統一設置圖集

private static void SetSingleAtlasName(string strFile)
{
    string strPath = Path.GetDirectoryName (strFile);
    string strSPName = GetSpriteNameFromPath (strPath);

    TextureImporter importer = TextureImporter.GetAtPath (strFile) as TextureImporter;
    if(importer==null)
    {
        Debug.LogErrorFormat ("Import atlas:{0}, is not a texture!", strFile);
        return;
    }
    Debug.LogFormat ("TextureImport:{0}-->spname:{1}", strFile, strSPName);
    if(importer.textureType != TextureImporterType.Sprite)
    {
         return;
    }

    if(importer.spritePackingTag!=strSPName)
    {
        importer.spritePackingTag = strSPName;
        importer.SaveAndReimport ();
     }
}

private static string GetSpriteNameFromPath(string strSpritePath)
{
   strSpritePath = strSpritePath.Replace ("/", "_");
   string strAtlasName = strSpritePath.Replace ("Assets_Art_", "").Replace ("Assets_Data_", "").ToLower();
   return strAtlasName;
}

如上,核心是GetSpriteNameFromPath和importer.spritePackingTag。GetSpriteNameFromPath經過圖片的目錄獲取圖片的圖集名字,importer.spritePackingTag設置圖片的名字。使用時須要將每一個要打圖集的.pngg文件調用此方法。獲取某個目錄下的.png文件方法以下

string[] arrFiles = FileHelper.GetAllChildFiles (strPath, ".png", SearchOption.AllDirectories);
int len = arrFiles.Length;
for(int i=0; i<len; i++)
{
    string strFile = arrFiles [i];
    SetSingleAtlasName (strFile);
}

6、如何統一設置ab包並生成md5

這邊須要注意的是,業務傳入文件名,框架如何加載到對應的ab包並加載出asset。

一個可行的方法是把文件的文件名和文件的ab包名字做一個映射並保存到本地,框架加載ab包時先加載該配置並獲取對應的ab包名字。

核心的方法是unity設置ab包的方法

    //
        // 摘要:
        //     Set the AssetBundle name and variant.
        //
        // 參數:
        //   assetBundleName:
        //     AssetBundle name.
        //
        //   assetBundleVariant:
        //     AssetBundle variant.
        [GeneratedByOldBindingsGenerator]
        public void SetAssetBundleNameAndVariant(string assetBundleName, string assetBundleVariant);

詳細代碼以下

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

public class ImporterAssetBundle
{
    static Dictionary<string, string> ms_uiBundles = new Dictionary<string, string>();
    public static void Reimport(bool buildLua = false)
    {
        ClearBundleName();
        ms_uiBundles.Clear();

        ImportAll();

        AssetDatabase.RemoveUnusedAssetBundleNames();
        AssetDatabase.Refresh();
    }

    // 清除AssetBundle設置
    public static void ClearBundleName()
    {
        var names = AssetDatabase.GetAllAssetBundleNames();
        foreach (string name in names)
        {
            AssetDatabase.RemoveAssetBundleName(name, true);
        }
    }

    private static void ImportAll()
    {
        ImportSubPath("Assets/Data/city/sky", "city/sky/", true);
        ImportFilesInPath("Assets/Data/city/buildingpart", "city/buildingpart/", true);
        ImportSingleFile("Assets/Data/city/other", "city/other", true);

        SaveBundleList(ms_uiBundles, "BundleDefine");
    }

    public static bool CheckStringInArray(string[] arrStr, string value)
    {
        if (arrStr == null)
        {
            return false;
        }
        for (int i = 0; i < arrStr.Length; i++)
        {
            if (arrStr[i] == value)
            {
                return true;
            }
        }
        return false;
    }

    //獲取全部子文件
    public static string[] GetAllChildFiles(string path, string suffix = "", SearchOption option = SearchOption.AllDirectories)
    {
        string strPattner = "*";
        if (suffix.Length > 0 && suffix[0] != '.')
        {
            strPattner += "." + suffix;
        }
        else
        {
            strPattner += suffix;
        }

        string[] files = Directory.GetFiles(path, strPattner, option);

        return files;
    }

    #region 導入功能函數
    // 將路徑下的子路徑,按目錄設置Bundle
    private static void ImportSubPath(string strPath, string abHead, bool bAddDefine, string[] excludes = null)
    {
        if (!Directory.Exists(strPath))
        {
            return;
        }

        string[] allChilds = Directory.GetDirectories(strPath);
        for (int i = 0; i < allChilds.Length; i++)
        {
            string childPath = allChilds[i];
            if (excludes != null && CheckStringInArray(excludes, childPath))
            {
                continue;
            }
            string strPathName = childPath.Replace('\\', '/');
            strPathName = strPathName.Substring(strPathName.LastIndexOf('/') + 1);
            string strAbName = abHead + strPathName;

            ImportSingleFile(childPath, strAbName, bAddDefine);
        }
    }

    // 將路徑下的每一個文件導出成獨立的Bundle 只處理一級文件
    private static void ImportFilesInPath(string path, string abHead, bool bAddDefine, string suffix = "", string[] excludes = null, SearchOption option = SearchOption.TopDirectoryOnly)
    {
        if (!Directory.Exists(path))
        {
            return;
        }

        string[] allFiles = GetAllChildFiles(path, suffix, option);
        for (int i = 0; i < allFiles.Length; i++)
        {
            string file = allFiles[i];
            if (file.EndsWith(".meta") || file.EndsWith("txt"))
            {
                continue;
            }

            string strFileName = Path.GetFileNameWithoutExtension(file);
            if (excludes != null && CheckStringInArray(excludes, strFileName))
            {
                continue;
            }

            string abName = abHead + strFileName;
            ImportSingleFile(file, abName, bAddDefine);
        }
    }

    // 設置單個文件(或目錄)的ABName
    private static void ImportSingleFile(string Path, string abName, bool bAddDefine)
    {
        AssetImporter importer = AssetImporter.GetAtPath(Path);
        if (bAddDefine)
        {
            AddBundleDefine(Path, abName);
        }

        if (importer == null)
        {
            return;
        }
        abName = abName.Replace('\\', '_').Replace('/', '_');
        importer.SetAssetBundleNameAndVariant(abName, "unity3d");
    }

    #endregion

    private static void AddBundleDefine(string strPath, string strABName)
    {
        //若是是一個路徑 才須要添加到字典
        if (Directory.Exists(strPath))
        {
            string[] files = Directory.GetFiles(strPath, "*", SearchOption.AllDirectories);
            for (int i = 0; i < files.Length; i++)
            {
                string filename = files[i];
                if (filename.EndsWith(".meta"))
                {
                    continue;
                }

                AddToBundleDic(filename, strABName);
            }
        }
        else if (File.Exists(strPath))
        {
            AddToBundleDic(strPath, strABName);
        }
    }

    private static void AddToBundleDic(string strFileName, string strABName)
    {
        string strExtension = Path.GetExtension(strFileName);
        string fullName = strFileName.Replace("Assets/Data/", "").Replace(strExtension, "").Replace("\\", "/");

        if (ms_uiBundles.ContainsKey(fullName))
        {
            Debug.LogWarningFormat("文件名字重複:{0}, strABName old:{1}, new:{2}", strFileName, ms_uiBundles[fullName], strABName);
        }

        ms_uiBundles[fullName] = strABName;
    }

    static void SaveBundleList(Dictionary<string, string> dicAB, string strFileName)
    {
        string strSaveFile = string.Format("Assets/Lua/game/define/{0}.lua", strFileName);
        // 保存到文件
        StringBuilder builder = new StringBuilder();
        builder.AppendLine("-- Create by Tool, don`t modify");
        builder.AppendLine();

        string strHeadLine = strFileName + " = {";
        builder.AppendLine(strHeadLine);
        {
            var iter = dicAB.GetEnumerator();
            while (iter.MoveNext())
            {
                string prefabName = iter.Current.Key.Replace("\\", "/").ToLower();
                string abName = iter.Current.Value.Replace("\\", "/").ToLower();
                builder.AppendFormat("\t[\"{0}\"] = \"{1}\",\n", prefabName, abName);
            }
        }

        builder.AppendLine("}");
        builder.AppendLine();
        builder.AppendLine("--EOF");

        FileHelper.SaveTextToFile(builder.ToString(), strSaveFile);
        EditorHelper.EditorSaveFile(strSaveFile);

        ms_uiBundles.Clear();
    }
}

生成ab包:ab包的壓縮模式選擇:https://docs.unity3d.com/Manual/AssetBundles-Building.html

  public static void BuildAssetBundle()
 {
    string pathDst = AssetBundlePath;
    FileHelper.CreateDirectory (pathDst);

    BuildAssetBundleOptions options = BuildAssetBundleOptions.DeterministicAssetBundle;
    options |= BuildAssetBundleOptions.ChunkBasedCompression;

    BuildPipeline.BuildAssetBundles (pathDst, options, EditorUserBuildSettings.activeBuildTarget);
    AssetDatabase.Refresh ();
 }

md5生成:md5用於熱更時判斷一個文件是否須要更新(只有變化的文件md5纔會變動改)

FileStream fs = File.OpenRead(filePath)
MD5 md5 = MD5.Create();
byte[] fileMd5Bytes=md5.ComputeHash(fs);//計算FileStream 對象的哈希值
fileMd5 = System.BitConverter.ToString(fileMd5Bytes).Replace("-", "").ToLower();

7、如何導出包給兩端使用

獲取當前項目的平臺:BuildTarget target = EditorUserBuildSettings.activeBuildTarget;

若是使用xcode\as等進行打包,options須要設置成BuildOptions options = BuildOptions.AcceptExternalModificationsToPlayer;

On iOS, this setting will append an existing Xcode project. Existing Xcode project setting changes will be preserved. With the IL2CPP scripting backend, this setting will also allow incremental builds of the generated C++ code to work in Xcode.

On Android, this setting will create a new Eclipse project. Existing Eclipse project setting changes will be discarded.

固然你也能夠拼接多個option

if (CommandHelper.IsDevelopment) {
    options |= BuildOptions.Development;
    options |= BuildOptions.AllowDebugging;
}

8、如何調用as命令生成apk,如何調用xcode生成ipa

ipa生成參考文章:https://www.jianshu.com/p/3f43370437d2

主要就是下面兩行命令

cmdBuild = 'xcodebuild archive  -workspace Unity-iPhone.xcworkspace  -scheme Unity-iPhone -archivePath %s || exit '%(archivePath)
cmdIpa = 'xcodebuild -exportArchive -archivePath %s -exportPath %s -exportOptionsPlist \"%s/config/ad-hoc.plist\"' % (archivePath, ipaDir, self.CurrPath)

archive參數以下,若是使用CocoaPod,workspace必須傳,scheme不傳則默認爲項目第一個Target。archivePath爲archive包存儲路徑。其他參數可不傳

exportArchive ,導出ipa,參數以下:archivePath上面的archivePath路徑。exportPath :ipa導出路徑。exportOptionsPlist :list文件

 

android 端直接調用:./gradlew assembleNormal就行了

 

9、如何導出配置給兩個端使用

1.ios直接修改info.plist文件和項目設置項

首先咱們須要瞭解OnPostprocessBuild方法。就是說unity每次build完後都會回調這個方法,該方法有兩個參數,一個是xcode的Target,一個是導出的工程目錄。咱們能夠在這個回調裏設置證書、權限、白名單等信息

Implement this function to receive a callback after the build is complete.

示例以下,具體能夠參考API:https://docs.unity3d.com/ScriptReference/iOS.Xcode.PBXProject.html。主要是PBXProject這個API

  [PostProcessBuild]
    static void OnPostprocessBuild(BuildTarget target, string pathToBuildProject){
        //pathToBuildProject unity export路徑
        EditProject(pathToBuildProject);//添加framework、設置證書
        EditPlist (pathToBuildProject);//配置appid等、設置白名單、權限
    }

  static void EditProject(string path)
    {
        string projPath = PBXProject.GetPBXProjectPath(path);
        PBXProject pbxProj = new PBXProject();
        pbxProj.ReadFromString(File.ReadAllText(projPath));

        string target = pbxProj.TargetGuidByName("Unity-iPhone");
        if (!string.IsNullOrEmpty(target))
        {
            pbxProj.RemoveFrameworkFromProject(target, "libiconv.2.dylib");
            pbxProj.AddFrameworkToProject(target, "AdSupport.framework", true); //用於firebase推送

            pbxProj.SetBuildProperty(target, "ENABLE_BITCODE", "false");
            pbxProj.SetBuildProperty(target, "DEVELOPMENT_TEAM", GetTeamId());
            pbxProj.SetBuildProperty(target, "CODE_SIGN_IDENTITY", GetCertificate()); //證書
            pbxProj.SetBuildProperty(target, "PROVISIONING_PROFILE", GetProfiles());//描述文件

            string googleServicedir = ConfigHelper.IsDeveloper ? "develop" : "release";
            string rootGoogleServicepath = string.Format("{0}/../Config/GoogleService/", Application.dataPath) + googleServicedir;
            FileCopy(rootGoogleServicepath, path);
            pbxProj.AddFileToBuild(target, pbxProj.AddFile(path + "/GoogleService-Info.plist", "GoogleService-Info.plist", PBXSourceTree.Build));
            pbxProj.AddFileToBuild(target, pbxProj.AddFile(path + "/OMTService-Info.plist", "OMTService-Info.plist", PBXSourceTree.Build));
        }

        File.WriteAllText(projPath, pbxProj.WriteToString());
    }

    /// <summary>
    /// 這是plist
    /// </summary>
    /// <param name="pathToBuildProject">Path to build project.</param>
    static void EditPlist(string pathToBuildProject)
    {
        string _plistPath = pathToBuildProject + "/Info.plist";
        PlistDocument _plist = new PlistDocument();

        _plist.ReadFromString(File.ReadAllText(_plistPath));
        PlistElementDict _rootDic = _plist.root;

        //添加Scheme白名單
        PlistElementArray _array2 = _rootDic.CreateArray("LSApplicationQueriesSchemes");
        _array2.AddString("fbapi");
        _array2.AddString("fb-messenger-api");
        _array2.AddString("fbauth2");
        _array2.AddString("fbshareextension");
        _array2.AddString("gamcoios");
        _array2.AddString("whatsapp");
        _array2.AddString("twitter");
        _array2.AddString("twitterauth");
        _array2.AddString("instagram");

        //配置權限
        _rootDic.SetString("NSPhotoLibraryUsageDescription", SDKConfig.PhotoDesc);
        _rootDic.SetString("NSMicrophoneUsageDescription", SDKConfig.MicrophoneDes);
        _rootDic.SetString("NSPhotoLibraryAddUsageDescription", SDKConfig.PhotoAddDesc);
        _rootDic.SetString("NSCameraUsageDescription", SDKConfig.CameraDes);
        _rootDic.SetString("NSLocationWhenInUseUsageDescription", SDKConfig.LocationDes);

        //正式版/測試版
        _rootDic.SetBoolean("DebugMode", ConfigHelper.IsDeveloper);

        File.WriteAllText(_plistPath, _plist.WriteToString());
    }

 

2.Android端:經過json或.properties文件存儲,而後拷貝到android目錄下就行了。如下以.properties文件做爲實例

c#端存儲:FileHelper.SaveTextToFile只是調用file.write方法,把字符串寫進文件裏。

System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.AppendFormat("GAME_NAME={0}\n", ConfigHelper.GameName);
sb.AppendFormat("VERSION_NAME={0}\n", ConfigHelper.Version);
sb.AppendFormat("VERSION_CODE={0}\n", ConfigHelper.BuildNo);
sb.AppendFormat("BUNDLE_IDENTIFIER={0}\n", CommandHelper.BundleIdentifier);
sb.AppendFormat("IS_DEVELOPER={0}\n", ConfigHelper.IsDeveloper);
sb.AppendFormat("IABKEY={0}\n", SDKConfig.IABKey);

FileHelper.SaveTextToFile(sb.ToString(), string.Format("{0}/{1}/version.properties", CommandHelper.OutputPath, CommandHelper.ProjectName));

android端讀取:

  def verName = "1.0.0"
    def verCode = 8
    def bundleId = "and.onemt.sf.ar"
    def isDeveloper = false
    def iabKey = ""
    def name = "@string/app_name"

    def versionPropsFile = file('version.properties')

    if (versionPropsFile.canRead()) {
        def Properties versionProps = new Properties()
        versionProps.load(new FileInputStream(versionPropsFile))
        verName = versionProps['VERSION_NAME']
        verCode = versionProps['VERSION_CODE'].toInteger()
        bundleId = versionProps['BUNDLE_IDENTIFIER']
        isDeveloper = versionProps['IS_DEVELOPER']

        iabKey = versionProps['IABKEY']
        if ("True".equalsIgnoreCase(isDeveloper)) {
            name = "@string/app_name_test"
        }
    }

 

詳細導圖以下,其實一鍵打包無非就是命令行調用python,python調用各個引擎(as\xcode\unity)命令進行打包。

相關文章
相關標籤/搜索