Unity3D + luaframework如何做到一键打包

阅前提示:

本文讲述内容为如何优化luaframework的打包流程,并不是讲如何使用luaframework配合unity3D进行热更新的基础内容,因此需要读者有unity3d + luaframework的使用经验,以及熟悉assetbundle的热更原理和流程,否则不建议阅读.

1.为什么要做一键打包

公司项目近两年来一直在使用luaframework,热更功能和lua开发的效率让我们受益良多,不过其打包流程的缺陷也很明显,很多流程都需要等待,盯着右下角的小菊花,等待它转完,然后进行下一步操作,当项目开发到中后期,代码量和资源量都上来以后,等的时间也在随着变长,有时候我们会在等的过程中不自觉的做点其它事,开个网页,滑滑手机,并不是每次都可以在菊花刚好转完时注意到,中间间隔的时间就这么浪费掉了.

我们今天就来对luaframework框架进行一个小小的改造,减少一些人工干预,做到一键打包.

首先我们来看看整个打包的流程,整个流程很清晰都是线性执行的,在没有进行修改的情况下,每一步执行以后都需要等待,等待当前这一步执行完毕后再手动执行下一步,非常麻烦.

2.使用OnScriptsReloaded 来串接整个流程

为了不用等待,我们需要使用unity editor自带的一个回调接口 OnScriptsReloaded 来完成功能

当每次editor编译完成后(右下角的菊花转完以后)系统都会回调一次这个函数,我们这次的重点就是通过这个回调函数把整个流程串起来.

首先我们新建一个自动打包的脚本AutoBuild.cs,把它放到工程中Assets\LuaFramework\Editor目录下,我们的大部分功能都会在这个脚本中完成.

接下来我们添加一段代码让编译完成后给一个系统弹框,提示我们包已经打好了:

public static void OnScriptsReloaded()
{
    EditorUtility.DisplayDialog("Complete", "Export package successful!", "Open Folder")
}

添加上述代码后我们会发现一个糟糕的问题,任何一次修改代码编译完成后都会弹一个打包完毕的包,这当然不是我们想要的,因此我们需要加一个标记变量和一个分支语句来进行判断当前是否处于打包的流程中.

3.如何在OnScriptsReloaded中使用条件分支判断

我们尝试用一个静态变量来进行控制.

 Static int clearTag = false;

 public static void clearAndBuild()
 {
    clearTag = true;
     //do something like clean…
 }

 [UnityEditor.Callbacks.DidReloadScripts]
 public static void OnScriptsReloaded()
 {
     //这个分支判断永远都进不去,因为编译完毕后clearTag会被重置回false
    if (clearTag)
    {
       //show clean complete dialog.
    }
 }

结果发现这样做是不可行的, 调试发现clearTag的值怎么都无法被修改,在查阅官方文档后找到原因:

OnScriptsReloaded是unity编译完成才会进入的回调,在编译完成后unity会对所有的静态变量进行初始化,所以不能使用静态变量来作为分支语句的判断条件.

没关系,即便静态变量不能在这里使用,我们还有其它的方法来完成逻辑的判断

  • 使用PlayerPrefs,通过存储到本地的键值对来完成数据的持久化.

  • 通过写本地文件的方式来标记数据,比如我们可以在本地写入一个名叫clear.ini的文件

File.Create("clear.ini");

然后通过

 if( File.Exists("clear.ini"))
 {
    //do something
 }

来做标记判断

显然,后者比前者繁琐了不少,因此我们这里选择采用__PlayerPrefs__来记录数据

 public static void clearAndBuild()
 {
    PlayerPrefs.SetInt("clearTag", 1);
     //do something like clean…
 }

 [UnityEditor.Callbacks.DidReloadScripts]
 public static void OnScriptsReloaded()
 {
     //这里就可以被正确判断了
    if (PlayerPrefs.GetInt("clearTag") == 1)
    {
       //show clean complete dialog.
    }
 }

这样就能正常通过判断了.

4.在packager中新增BuildResource接口

接下来在Packager.cs中增加一个新的函数接口

public static void BuildResource()
{
  #if UNITY_ANDROID
   BuildAndroidResource();
  #elif UNITY_IPHONE
   BuildiPhoneResource();
  #else
    BuildWindowsResource();
  #endif
}

在这里对原本的接口做了一个封装,加入了平台的自动判断,这样做有两个好处
1.在一键打包的过程中不用人工干预选择打哪个平台的包
2.在手动打包时不会出现手抖点错平台的情况
实际工作中时常会出现在android平台下打包不小心点到window或者ios的情况,在项目开发后期资源比较多的情况下,一旦点错就很麻烦,取消打包和回退资源都会浪费掉不少时间.

接下来我们回到AutoBuild.cs文件
我们创建三个菜单功能,分别是打包资源,打包资源并运行,clear wrap files + 打包资源并运行.

    [MenuItem("AutoBuild/Build", false, 100)]
    public static void build()
    {
        PlayerPrefs.SetInt("autobuild", 1);
        Packager.BuildResource();
    }

    [MenuItem("AutoBuild/BuildAndRun", false, 100)]
    public static void buildAndRun()
    {
        PlayerPrefs.SetInt("autobuild", 2);
        Packager.BuildResource();
    }

    [MenuItem("AutoBuild/clearAndBuild", false, 100)]
    public static void clearAndBuild()
    {
        PlayerPrefs.SetInt("clearAndBuild", 1);
        ToLuaMenu.ClearLuaWraps();
    }

为什么要分成这三个功能呢?
1.直接打可执行包
2.跑真机的时候常用的打包后立即运行到真机
3.是重新生成wrap文件后,再打包文件
前两个为常用功能,第三个并不常用

实际的开发过程中,只有在c#接口文件做了修改,我们才会去重新生成wrap文件,在业务逻辑开发的过程中,c#接口的调整相对来说并不频繁,所以把其单独分离了出来.

接下来我们完善上面提到的重要接口OnScriptsReloaded

    [UnityEditor.Callbacks.DidReloadScripts]
    public static void OnScriptsReloaded()
    {
        if (PlayerPrefs.GetInt("clearAndBuild") == 1)
        {
            PlayerPrefs.SetInt("clearAndBuild", 0);
            EditorApplication.delayCall = build;
        }
        else if (PlayerPrefs.GetInt("autobuild") == 1)
        {
            PlayerPrefs.SetInt("autobuild", 0);
            PlayerPrefs.SetInt("exportPackage", 1);
            EditorApplication.delayCall = justBuildPackage;
        }
        else if (PlayerPrefs.GetInt("autobuild") == 2)
        {
            PlayerPrefs.SetInt("autobuild", 0);
            PlayerPrefs.SetInt("exportPackage", 1);
            EditorApplication.delayCall = buildPackageAndRun;
        }
        else if (PlayerPrefs.GetInt("exportPackage") == 1)
        {
            PlayerPrefs.SetInt("exportPackage", 0);

            if (EditorUtility.DisplayDialog("Complete", "Export package successful!", "Open Folder"))
            {
                System.Diagnostics.Process.Start("D:/unity_package/luaframework");
            };
        }
    }

其实逻辑非常简单,每做一步操作时,就通过PlayerPrefs打一个标记,当编译完毕后,通过判断当前的标记值做下一步的操作,并重置当前标记,所有逻辑与上面的流程图中的流程一一对应.

  • 大家需要重点注意的是,上述代码中使用了delaycall来调用函数,而不是直接调用,为什么要这样做?
    在实际开发中发现,editor右下角的小菊花转完以后,editor并不是立即就可以操作了,中间还有一个阻塞刷新的过程,使用delaycall的目的就是让editor刷新完毕后再执行接下来的函数调用,如果在editor没有反应过来的情况下继续执行函数调用,有可能会出现editor崩溃的情况,因此必须使用delaycall来让editor飞一会儿.

接下来我们来完成打包函数

    static void makePackage(bool isRun)
    {
        string[] levels = { "Assets/LuaFramework/Scenes/main.unity"};
        BuildPlayerOptions option = new BuildPlayerOptions();
        option.scenes = levels;

        //caculate current datetime
        string nowTime = System.DateTime.Now.ToString("MMdd-HH_mm");

        string filePath = "D:/unity_package/luaframework/luaframework" + nowTime;

#if UNITY_ANDROID
        filePath += ".apk";
        option.target = BuildTarget.Android;
#elif UNITY_IPHONE
        option.target = BuildTarget.iOS;
#else
        filePath += ".exe";
        option.target = BuildTarget.StandaloneWindows;
#endif
        option.locationPathName = filePath;

        if (isRun)
        {
            option.options = BuildOptions.AutoRunPlayer;
        }
        else
        {
            option.options = BuildOptions.None;
        }

        BuildPipeline.BuildPlayer(option);
    }

这里做了5件事:
1 确定要把哪些场景打包进去
2 根据当前的时间对包进行命名,做到每次打包都可追溯
3 根据不同的平台打对应的可执行包(注意ios生成的是xcode工程,不是可执行文件)
4 根据是否运行的参数来判断是否需要直接运行
5 通过BuildPipeline进行打包

5.调整会阻碍自动打包的地方

既然要一键打包,那在按下打包键后就不希望出现还要人工干预的地方了,所以我们需要对ToluaMenu.cs进行一点小小的修改

注释掉下面这段if提示语句和对应的括号,让分支内的代码自动执行

if (EditorUtility.DisplayDialog("自动生成", "点击确定自动生成常用类型注册文件,  也可通过菜单逐步完成此功能", "确定", "取消"))

6.如何设置打包快捷键

快捷键能极大的提升我们的工作效率,unity editor也贴心的提供了这一功能

快捷键的规则是在菜单的后面接上” _”开头的特殊表达式(注意'_'前面有个空格)
常用的组合快捷键是ctrl, shift, alt,分别用下面的字符进行表示:

`% = ctrl`   `# = Shift`   `& = Alt` 

比如我们把build设置为

[MenuItem("AutoBuild/Build _%#_d", false, 100)] (ctrl + shift + d)

把BuildAndRun设置为

[MenuItem("AutoBuild/BuildAndRun _%#_x", false, 100)] (ctrl + shift + x)

连接好手机,键盘按下ctrl + shift + x,剩下的就等着看运行效果了,是不是很爽

7.制作提示对话框

最后一步增加了一个主动弹出的系统对话框,这一步的主要目的是主动提醒开发者执行结束了,而不是让开发者不停的去关注右下角的小菊花转完没有

if (EditorUtility.DisplayDialog("Complete", "Export package successful!", "Open Folder"))
{
     System.Diagnostics.Process.Start("D:/unity_package/luaframework");
};

在弹出对话框中点击Open Folder即可打开刚刚导出的可执行文件所在的目录,相当方便

8.android自动签名设置

最后说一下android自动签名的设置,每次重启untiy后,android player的keystore签名设置都会清空,需要重新输入,不厌其烦,既然都一键打包了,肯定不希望每次还需要手动填写一次keystore的各种密码信息,要偷懒就要彻底一点,我们新建一个脚本AutoSignKeystore.cs

[InitializeOnLoad]
public class AutoSignKeystore
{

    static AutoSignKeystore()
    {
        PlayerSettings.Android.keystoreName = "D:/keystore/luaframework.keystore";
        PlayerSettings.Android.keystorePass = "luaframework";
        PlayerSettings.Android.keyaliasName = "luafw";
        PlayerSettings.Android.keyaliasPass = "luaframework";
    }
}

设置好keystore文件的路径,密码,别名,别名密码等相关信息,把此脚本放到assets/Editor文件夹下,每次打开unity时就会自动设置好预设的签名信息了.

通过上述设置,按下ctrl+shift+d就能导出可执行文件.

按下ctrl+shift+x就能导出并运行.

一键打包制作完毕!

整个调整几乎没有动到原来的框架,主要功能都在AutoBuild.cs中完成,对原框架只是做了非常少的一些修改,我把修改后的功能放到github上了,如果觉得有用请按Star,谢谢!

https://github.com/CraneInForest/LuaFrameWork_UGUI_AutoBuild.git

一键打包搞定了,但这只方便了开发和调试过程,离真正的自动化打包还有差距,下一篇文章就来讲讲如何结合jenkins做到在网页上全自动打包吧(解放你的电脑和双手,别让打包占用我们宝贵的生命~)

posted @ 2018-08-18 09:46  CraneInForest  阅读(381)  评论(0编辑  收藏  举报