Unity converts C# scripts to DLLs, Prefab and other files without missing reference methods

/

unity 中将c#打包成dll中

把纯c#打包成dll的流程就没有什么好说的了,就是新建一个c#类库项目,写好代码,直接编译就好了,唯一需要注意的就是在编译的时候,记得选择的目标框架版本不要太高

你想要在dll中使用unity的api,只需要把unity自己的dll引用到dll库项目中来就好了。 
unity 引擎+编辑器的 dll 地址一般在你的unity安装目录下,例: 
C:\Program Files\Unity\Editor\Data\Managed\

这个目录下存放了 unity 的托管代码,名字叫 UnityEditor.dllUnityEngine.dll

然后把这2个dll引用到vs的类库项目里就好了,现在我们就可以把.cs脚本也打包成dll了。 
打包好以后在编辑器里:

这里面那个 WWWMgr 就是那个继承了 MonoBehaviour 的类,可以直接把它拖到对象上去了,用法就和一个脚本没有任何区别了。

这里有一个特别需要注意的地方,就是我们在代码里可能有用到了 #if 这种预编译命令的地方,但是跑到unity里却不管用了~ 
因为是预编译命令,所以在编译阶段后,就没有效果了,但是我们不能不用它呀,那要怎么办呢? 
这个时候我们就需要专门设置一下我们的c#类库项目的生成选项了,具体步骤是:右键项目 -> 选择属性.

那个条件编译符号,我们把需要的宏填进去就好了。多个宏的间隔方法我知道的有3种,用空格( ),逗号(,),分号(;),这3种都可以用。。。 
这里需要注意一点的就是,比如我们填了 UNITY_ANDROID 这个宏,那这个dll就已经默认当前是android平台环境了!对,它不会到引擎里再去检测这个东西了,因为这个dll已经过了编译阶段了,宏的使命已经完成了,在输出dll的时候它已经被删掉了。所以多平台就意味着我们要打多个dll,这一点一定要注意

PS:打包dll的内容就说到这了,有需要加密混淆的同学再去加密那个dll就好了

///

Unity converts C# scripts to DLLs, Prefab and other files without missing reference methods

The scripts that will be used in the Prefab and Scene files in unity are assigned a guiid and fileID to store the association with the script. Such as:
m_Script: {fileID: 11500000, guid: 39b76c511f7a56549a8e07bf28e0daae, type: 3}
So how do you convert a C# script to a DLL, and files like Prefab don't lose script references?
is actually very simple, just need to change the guid and fileID from the class that originally pointed to the cs file to the class in the DLL file.

Guid: pointing to the file
fileID: points to the class in the file. If it is a MonoScript type component with the same class name and file name, the fileID is always 11500000. For details, please refer toThis article

The rest is how to implement this process in batches and automatically. Here with the help of an AssetStoreDLLSwitcher plugin

step

1. New empty project, import DLLSwitcher plugin

The downside of this plugin is that the documentation is scarce. Change the project settings (Edit->Project Settings->Editor) as follows, as indicated by the only one txt:

Version Control: Hidden Meta files
Asset Serialization: Force text

2. Import the unity package that needs to be dlled into the project.

HereUIWIdgets 1.9.3.unitypackageFor example 

3. Package the CS code into a dll (this step will have a packaging problem, which will be adjusted later)

  1. Here VS to create a new dll project, directly copy the UIWidgets directory to the VS project.
  2. Then include all the cs files into the project.
  3. Add the UnityEngine and UnityEditor related dll references to the project to ensure that the target dll can be generated normally.
  4. (optional) addUNITY_EDITORMacro to build configuration
    The dll generated in this way will contain the Editor related code. In the middle, you may encounter some cases where the code does not pass, most of which are related to the Unity version (such asUNITY_5_3_OR_NEWER) Cause, add these macros to the build configuration.

 

4 After generating the dll file, copy it to the Plugins directory of the Unity project.

At this time, the project will definitely report an error, because there are 2 copies of all the classes in the project, one in the cs file and one in the dll file.

5 Open the DLLSwitcher plugin, drag the generated dll into the Dll File, and click Replace From Src To Dll.

Onekey switch your script between source code and dll without missingReference

Drag your dll or the directory including the dlls here

Select the directory of your source code

Select the resource directory which you want to replace the script reference

What's the plugin for?
For some reason,  you may need to replace the dll file of you project with source code(Or replace source code with dll file) . 
But when you done this, Unity will loose references to related scripts, and this plugin is help you to fix this problem.

How to use it?
1. Change a couple of settings in the editor(Edit->Project Settings->Editor, Requires Unity Pro)
    Version Control: Meta files
    Asset Serialization: Force text
2. Open our plugin window(Window->DllSwitcher), Set the dll file's path, source code 's directory and the target dictory you need to replace.
3. Click the replace button.

Warn:
1. It's important to backup your project before use this plugin.
2. About the dll Dependency: Also the only case you have to put you dependented dll file int the same directory is that 
   some class in your dll inherit from an class of other dll, But we suggest you always put the dependency together.

打开DLLSwitcher插件,将生成的dll拖入Dll File后,点击Replace From Src To Dll。

Observation: After the replacement is completed, you can find that all Prefab files and Scene files have been rewritten.

Guid: The guid that originally corresponded to each cs file now uniformly points to the guid of the dll file ({guid: 91ea3ce3edb1f944cad92192c2b590d0}
fileID: The fileID that was always 11500000 is changed to the new encryption ID. The encryption generation algorithm here is "s\0\0\0" + type.Namespace + type.Name The value of the UTF-8 encoded Byte value is encrypted by the MD4 algorithm.

6 Delete all cs files in the Unity project

After that, the Scene can be run normally, and the Prefab function is normal.
But! ! At this time, if the Build Unity project will report an error, because we were lazy before, we packaged the Editor-related code into the dll. If there is no problem in the Editor, the package will report an error.

At this time, you need to adjust step 3

7 Package the CS code into a dll without UnityEditor (corresponding to the previous step 3 adjustment)

  1. Here VS to create a new dll project, directly copy the UIWidgets directory to the VS project.
  2. Then include all the cs files except the Editor related code into the project.
  3. Add the UnityEngine-related dll reference to the project to ensure that the target dll can be generated normally, removing the dependency on UnityEditor.dll.
  4. Related macros according to the Unity version (egUNITY_5_3_OR_NEWER) added to the build configuration. RemoveUNITY_EDITORMacro.
    Click to generate If it is found that it cannot be generated, generally some of the Editor code does not have Exclude. Remove the Cleaner code until Build Success

8 will point to a file such as Prefab that depends on the UnityEditor dll, and redirect to a dll that does not depend on UnityEditor.

Unfortunately, this plugin does not contain the dll redirect function, it can only write a method on its own.link
The dll containing the Editor dependency is set to be used only under the Editor.
The dll that does not contain the Editor dependency is set to be used in other environments.

When developing, replace the prefab and other associations from src to the dll containing the Editor dependency. When building the package, move the association to the dll that does not contain the Editor dependency.

Written at the end

Of course, there is a once-and-for-all way to completely remove the Editor-related code when the dll is built, and in the Unity Project.Only keep the relevant cs file under the Editor folder. What needs special handling here is that some plugins will be used.UNITY_EDITORThe macro causes this part of the code to be manually separated and placed in the Editor folder for processing.
As for which method to choose, it is a matter of opinion. I prefer to use this method. After all, it is very troublesome to switch the dll association back and forth between a project and a package. Separating the Editor code, although a lot of work will be done in the early stage, there is no such thing as switching dll associations later.

/

脚本在Prefab中被引用的原理
脚本被引用有两种情况

a.prefab引用的是cs文件脚本

b.prefab引用的是dll文件中的脚本


区别一:
对于第一种情况,脚本的文件名必须和类名相同,且必须为MonoBehaviour类,不管脚本里面有一个或多个类,只有和文件名相同的类名的才能被挂接上

对于第二种情况,一个脚本可以包含多个MonoBehaviour类,如果把它打成dll后,它里面的所有类都是可以被挂接的

区别二:

prefab挂了脚本,打成AssetBundle后,加载运行的时候,只有第一种情况的脚本是可以生效的,挂的dll是无效,因为bundle加载并初始化的时候,unity是从包内的脚本中去搜索的,并不会从包内的dll中去搜索(这也是脚本更新的拦路虎之一,解决方法要么动态挂脚本,要么挂的脚本不热更,跟包走)


引用的原理,如下图:

用文本编辑器打开prefab文件,如上图,挂载的脚本如上紫框内所示,fileID脚本中类的信息,guild表示脚本所在的文件ID

a.直接挂载脚本

    这种情况下,fileID的值永远是11500000,它实际上指的是MonoScript类型组件,它的值由ClassID * 10000所得,详情见官方文档,而guid能直接定位到MonoScript脚本文件

    所以它是通过guid找到脚本文件,然后挂载这个脚本中与脚本文件名相同的类
b.直接挂载DLL中的脚本

   这种情况下,fileID的值是由"s\0\0\0" + type.Namespace + type.Name的值转换成MD4的值,guid指的是这个dll所对应的文件(MD4的算法贴在最后面)
   所以它是通过guid找到dll文件,然后生成里面所有类的MD4,然后和这个值做比对,相同的则挂载上去


脚本引用修复
一、原来是挂的脚本(可以通过fileID等于1150000判断),要替换成新的脚本

       a.把prefab中的guid通过文本工具统一替换成新脚本的guid

       b.直接把新脚本的.mate文件里面的guid改成prefab中的

       c.如果新脚本已经有地方有的,不能改.mate,就只能使用a方法

二、原来是挂的DLL中的脚本(可以通过fileID不等于1150000判断),要替换成新的脚本

       a.把prefab中的guid改成新脚本的guid,把fileID统一改成1150000

三、原来是挂的脚本,要替换成dll中的脚本

       a.把prefab中的guid改成dll的guid,把fileID统一改成DLL中类的Type算出来的MD4的值

四、原来是挂的DLL中的脚本,要替换成新DLL中的脚本

       a.把prefab中的guid改成新dll的guid,把fileID统一改成新DLL中类的Type算出来的MD4的值


注:如写工具时,要获得一个本地脚本中包含哪一些类,可以使用AssetDatabase.Load,它返回的是一个MonoScript,通过它可以获得到所有类的信息

MD4算法如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
 
namespace cn.crashByNull
{
    public class MD4 : HashAlgorithm
    {
        private uint _a;
        private uint _b;
        private uint _c;
        private uint _d;
        private uint[] _x;
        private int _bytesProcessed;
 
        public MD4()
        {
            _x = new uint[16];
 
            Initialize();
        }
 
        public override void Initialize()
        {
            _a = 0x67452301;
            _b = 0xefcdab89;
            _c = 0x98badcfe;
            _d = 0x10325476;
 
            _bytesProcessed = 0;
        }
 
        protected override void HashCore(byte[] array, int offset, int length)
        {
            ProcessMessage(Bytes(array, offset, length));
        }
 
        protected override byte[] HashFinal()
        {
            try
            {
                ProcessMessage(Padding());
 
                return new[] { _a, _b, _c, _d }.SelectMany(word => Bytes(word)).ToArray();
            }
            finally
            {
                Initialize();
            }
        }
 
        private void ProcessMessage(IEnumerable
   
     bytes)
        {
            foreach (byte b in bytes)
            {
                int c = _bytesProcessed & 63;
                int i = c >> 2;
                int s = (c & 3) << 3;
 
                _x[i] = (_x[i] & ~((uint)255 << s)) | ((uint)b << s);
 
                if (c == 63)
                {
                    Process16WordBlock();
                }
 
                _bytesProcessed++;
            }
        }
 
        private static IEnumerable
    
      Bytes(byte[] bytes, int offset, int length)
        {
            for (int i = offset; i < length; i++)
            {
                yield return bytes[i];
            }
        }
 
        private IEnumerable
     
       Bytes(uint word)
        {
            yield return (byte)(word & 255);
            yield return (byte)((word >> 8) & 255);
            yield return (byte)((word >> 16) & 255);
            yield return (byte)((word >> 24) & 255);
        }
 
        private IEnumerable
      
        Repeat(byte value, int count)
        {
            for (int i = 0; i < count; i++)
            {
                yield return value;
            }
        }
 
        private IEnumerable
       
         Padding() { return Repeat(128, 1) .Concat(Repeat(0, ((_bytesProcessed + 8) & 0x7fffffc0) + 55 - _bytesProcessed)) .Concat(Bytes((uint)_bytesProcessed << 3)) .Concat(Repeat(0, 4)); } private void Process16WordBlock() { uint aa = _a; uint bb = _b; uint cc = _c; uint dd = _d; foreach (int k in new[] { 0, 4, 8, 12 }) { aa = Round1Operation(aa, bb, cc, dd, _x[k], 3); dd = Round1Operation(dd, aa, bb, cc, _x[k + 1], 7); cc = Round1Operation(cc, dd, aa, bb, _x[k + 2], 11); bb = Round1Operation(bb, cc, dd, aa, _x[k + 3], 19); } foreach (int k in new[] { 0, 1, 2, 3 }) { aa = Round2Operation(aa, bb, cc, dd, _x[k], 3); dd = Round2Operation(dd, aa, bb, cc, _x[k + 4], 5); cc = Round2Operation(cc, dd, aa, bb, _x[k + 8], 9); bb = Round2Operation(bb, cc, dd, aa, _x[k + 12], 13); } foreach (int k in new[] { 0, 2, 1, 3 }) { aa = Round3Operation(aa, bb, cc, dd, _x[k], 3); dd = Round3Operation(dd, aa, bb, cc, _x[k + 8], 9); cc = Round3Operation(cc, dd, aa, bb, _x[k + 4], 11); bb = Round3Operation(bb, cc, dd, aa, _x[k + 12], 15); } unchecked { _a += aa; _b += bb; _c += cc; _d += dd; } } private static uint ROL(uint value, int numberOfBits) { return (value << numberOfBits) | (value >> (32 - numberOfBits)); } private static uint Round1Operation(uint a, uint b, uint c, uint d, uint xk, int s) { unchecked { return ROL(a + ((b & c) | (~b & d)) + xk, s); } } private static uint Round2Operation(uint a, uint b, uint c, uint d, uint xk, int s) { unchecked { return ROL(a + ((b & c) | (b & d) | (c & d)) + xk + 0x5a827999, s); } } private static uint Round3Operation(uint a, uint b, uint c, uint d, uint xk, int s) { unchecked { return ROL(a + (b ^ c ^ d) + xk + 0x6ed9eba1, s); } } } public static class FileIDUtil { public static int Compute(Type t) { string toBeHashed = "s\0\0\0" + t.Namespace + t.Name; using (HashAlgorithm hash = new MD4()) { byte[] hashed = hash.ComputeHash(System.Text.Encoding.UTF8.GetBytes(toBeHashed)); int result = 0; for (int i = 3; i >= 0; --i) { result <<= 8; result |= hashed[i]; } return result; } } } } 
       
      
   

/

获取localID

 static int GetLocalID(Object obj)
    {
        PropertyInfo info = typeof(SerializedObject).GetProperty("inspectorMode", BindingFlags.NonPublic | BindingFlags.Instance);
        SerializedObject sObj = new SerializedObject(obj);
        info.SetValue(sObj, InspectorMode.Debug, null);
        SerializedProperty localIdProp = sObj.FindProperty("m_LocalIdentfierInFile");
        return localIdProp.intValue;
    }
 

获取特定目录下面的localID和guid

 public static void GetInfo()
    { //获取特定目录guid local信息 这里以dll为例子
        var dDll = new Dictionary<string, string>();
        var monoBehaviours = Resources.FindObjectsOfTypeAll<MonoScript>();
        foreach (var m in monoBehaviours)
        {
            if (m == null || m.GetClass() == null)
                continue;
            var path = AssetDatabase.GetAssetPath(m);
            if (path.StartsWith(DLLPath))//dll所在目录 过滤条件等
            {
                var localID = GetLocalID(m);
                var GUID = AssetDatabase.AssetPathToGUID(path);
                dDll.Add(m.GetClass().FullName, $"  m_Script: {{fileID:{localID}, guid: {GUID}, type:3}}");
            }
        }
        //Debug.Log(dDll); 
    }
 

/

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值