asmdefファイルをEditor以下に自動追加するエディタ拡張

追記:2020/05/02

githubにリポジトリ作ったのでパッケージマネージャからもインポートできます。

https://github.com/gentome/CreateAsmdef.git

上のアドレスをpackageManagerから入力するだけです。

unity2018.4くらいじゃないと対応してないかもしれません。

その場合はmanifest.jsonを直接書き換えるか、下のソースコードをそのままコピーしてください。


AssemblyDifination何かと便利です。

コンパイル早くなったりするし。プロジェクト大きくなるほど効果があると思います。

ただ、Editorフォルダがあるアセットとかだとすごく面倒なことになります。Editorフォルダに別にasmdefファイルを置かないといけないからです。

Editorフォルダが10個あったりしたら10個作らないといけないはずです。大変な作業になります。

で、面倒だったので何とかエディタ拡張をしてみました。

自動でasmdefファイルを作って、PlatformをEditorのみにしたりするようになってます。

試しにコーギーエンジンに使ってみたら、それなりにうまくいきました。Editorフォルダ内のスクリプトが別のEditorフォルダを参照していたら、それは追加してあげないとむりです。

アセットによっては、最初からasmdefファイルを作ってくれている親切なデベロッパーの方もいます。

一応やる前にバックアップくらいとっておいたほうがいいかもしんないす。

使ったUnityバージョンは2019.3です。

2020年8月現在、CorgiEngineはAsmdefファイルが作ってあります。

Menu➡WIndow➡CreateAsemdefから開けます

AssemblyDefinationで区切ったファイルからAssembly.CSharpへの参照は出来ないってことを覚えておかないと、はまることがあると思います。

2019/07/06改良しました。

  • Editorフォルダ以下でcsファイルがなければasmdefを作成しません。

2019/12/26改良しました。

  • Editorフォルダの時、[csファイルが無い、且つ、Dllファイルがある時]だけasmdefファイルを作成しません。csファイルがなくてもフォルダがあるかもしれないので、一応作成する。
  • 参照を入れるときの処理を変更しました。

2020/05/02改良しました。

  • githubにリポジトリ作りました。UnityのPackageManagerからインポートできます。

2020/07/16改良しました。

  • Macだと動かない不具合を修正。パスの区切り文字が違うことが原因だった。

2020/09/04修正しました。

  • ルートフォルダのasmdefファイルへのGuidが見つからない不具合。AssetDataBaseRefresh()を呼ぶようにしたら直った気がする。

困ったこと

internalで修飾してあるクラスなんかがあると動かなくなる場合があります。同じアセンブリ内にアクセスを制限する修飾です。

Type.GetType()という文字列から型を取得するメソッドがあり、アセンブリ名を使うことがあります。

Doozyのグラフエディタで発生したんですが、ノードを繋ぐ時にGetTypeで型を調べているみたいです。

その時にアセンブリ名を参照する文字列を使っています。

Type.GetType("Doozy.Engine.UI.Connections.UIConnection, Doozy.asmdef, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null")

こんな感じの。

で、「一度ノードを作った後」に「asmdefファイルを作成してしまう」と、アセンブリ名(上の例でいえば”Doozy.asmdef”)が変わってしまうため、ノードを繋ぐ時に型を取得できなくなりエラーになります。

グラフエディタを使う前にasmdefファイルを作っておけば、とりあえずは大丈夫だと思います。

もうすでにノードを作っていたら、どうすればよいのか分かりませんでした。


using System.IO;
using System.Collections;
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
using UnityEditor.Compilation;
using System.Runtime.CompilerServices;
using System.Threading;
/// <summary>
/// Editorフォルダ以下においてください
/// </summary>
public class AsmdefUtility : EditorWindow
{
    readonly string desc = $@"
選択したフォルダ直下にasmdefファイルを作ります。
Editorフォルダ以下にも作ります
Editorフォルダ以下の場合、アセットのルートフォルダに作成したasmdefファイルへの参照を追加します
Assets直下のフォルダを指定しないとおかしくなるかも
参照が足りないときもあるので、その場合は自分で追加する必要があります";
  
    [MenuItem("Window/CreateAsmdef")]
    static void Open()
    {
        GetWindow<AsmdefUtility>();
    }
    private void OnGUI()
    {
        GUILayout.Label(desc);
        if (GUILayout.Button("create"))
        {
            string filepath = EditorUtility.OpenFolderPanel("title", "Assets", "_Project");
            
            if (string.IsNullOrEmpty(filepath))//何も選択されていなければ
            {
                return;
            }
            DirectoryInfo rootInfo = new DirectoryInfo(filepath);
            AsmdefCreator.CreateAll(rootInfo);

            AssetDatabase.Refresh();
        }

        GUILayout.Label("選択したフォルダ以下のasmdefファイルを全部消します。");
        if (GUILayout.Button("Delete"))
        {
            string filepath = EditorUtility.OpenFolderPanel("", "Assets", "");
            if (string.IsNullOrEmpty(filepath))
            {
                return;
            }
            DirectoryInfo info = new DirectoryInfo(filepath);
            DeleteAsmdef.Delete(info);
            AssetDatabase.Refresh();
            Debug.Log($"{DeleteAsmdef.count}個のファイルを消去しました");
        }
    }
}
   

internal class AsmdefCreator
{
    static int count;
    public static void CreateAll(DirectoryInfo rootInfo)
    {
        count = 0;

        //最初に選択フォルダ直下のasmdefファイルを作る
        var fileInfo = MakeFile(rootInfo);
        WriteAsmdefStr( new StreamWriter(fileInfo.fs),fileInfo.asmName );
        AssetDatabase.Refresh();//ここで再度読み込まないとGuidが生成されないのかもしれない
        //Editorフォルダからルートフォルダ直下のasmdefファイルを参照するためにGUIDを取得
        string rootAsmdefGuid = 
            AssetDatabase.AssetPathToGUID(
                fileInfo.fs.Name.Substring(fileInfo.fs.Name.IndexOf("Assets")) 
                );
        
        if (string.IsNullOrEmpty(rootAsmdefGuid))
        {
            Debug.LogError($"asmdefguidを取得できていない:fileIndo = {fileInfo.fs.Name}");
        }
        
        foreach (DirectoryInfo info in rootInfo.GetDirectories())
        {
            Create(info, rootAsmdefGuid);
        }
        Debug.Log($"{count}個 asmdefファイルを作成しました");
    }
    /// <summary>
    /// 再帰的にEditorフォルダが無いか調べていく
    /// </summary>
    /// <param name="dirInfo">調査ちゅうのフォルダ</param>
    /// <param name="rootAsmdefGuid">ルートフォルダのGUID</param>
    static void Create(DirectoryInfo dirInfo,string rootAsmdefGuid)
    {
        if (dirInfo.Name == "Editor")//エディタフォルダの場合
        {
            //スクリプトファイルが無くてDllファイルのみであれば作成しない
            //どちらもない時は作成する
            if (dirInfo.GetFiles("*.cs").Length <= 0)//スクリプトファイルがない
            {
                if (dirInfo.GetFiles("*.dll").Length > 0)//スクリプトファイルが無くてdllファイルがあれば作成しない
                {
                    //Debug.Log("csファイルが無くDLLファイルがあったので作成しない-" + dirInfo.FullName);
                    return;
                }
            }
            var fileInfo = MakeFile(dirInfo, rootAsmdefGuid);
            WriteAsmdefStr( new StreamWriter(fileInfo.fs), fileInfo.asmName, rootAsmdefGuid);
            return;//Editorフォルダに当たったら下の階層のフォルダは処理しない
        }

        foreach (DirectoryInfo inforow in dirInfo.GetDirectories())
        {
            Create(inforow,rootAsmdefGuid);
        }
    }
    static (FileStream fs,string asmName) MakeFile(DirectoryInfo info)
    {
        string asmName = AsmdefName(info);
        //macとwindowsで区切り文字が違うのでIO.Path.DirectorySeparatorCharを使う
        string fullName = info.FullName + Path.DirectorySeparatorChar + asmName;

        return (File.Create(fullName),asmName);
    }
    static (FileStream fs, string asmName) MakeFile(DirectoryInfo info,string rootGuid)
    {
        string asmName = AsmdefName(info, rootGuid);
        //macとwindowsで区切り文字が違うのでIO.Path.DirectorySeparatorCharを使う
        string fullName = info.FullName + Path.DirectorySeparatorChar + asmName;
        return (File.Create(fullName), asmName);
    }
    /// <summary>
    /// asmdefファイルにJson文字列を書き込む
    /// </summary>
    /// <param name="asmName"></param>
    /// <returns></returns>
    static void WriteAsmdefStr(StreamWriter sw,string asmName )
    {
        var str = new AsmdefJson();
        str.name = asmName;
        sw.Write(str.ToString());
        sw.Flush();
        sw.Close();
        count++;
    }

    /// <summary>
    /// asmdefファイルにJson文字列を書き込む
    /// </summary>
    /// <param name="asmName"></param>
    /// <param name="rootAsmdefGuid"></param>
    /// <returns></returns>
    static void WriteAsmdefStr( StreamWriter sw ,string asmName, string rootAsmdefGuid)
    {
        var json = new AsmdefJson();
        json.name = asmName;
        json.references.Add($"GUID:{rootAsmdefGuid}");
        json.includePlatforms.Add("Editor");
        sw.Write(json.ToString());
        sw.Flush();
        sw.Close();
        count++;
    }
    /// <summary>
    /// asmdefファイルの名前が被らないように乱数入れたりしてみてる
    /// </summary>
    /// <param name="info"></param>
    /// <returns></returns>
    static string AsmdefName(DirectoryInfo info)
    {
        return  info.Name  + ".asmdef";
    }
    static string AsmdefName(DirectoryInfo info,string rootGuid)
    {
        int rand = Random.Range(0, 10000);
        return info.Parent.Name + info.Name + rand + ".asmdef";
    }
}
/// <summary>
/// 選択したフォルダ以下のasmdefファイルを全て消去する
/// </summary>
internal  class DeleteAsmdef 
{
    public static int count = 0;
    public static void Delete(DirectoryInfo dirInfo)
    {
        foreach (FileInfo fileinfo in dirInfo.GetFiles("*.asmdef"))
        {
            int index = fileinfo.FullName.IndexOf("Assets");
            string path = fileinfo.FullName.Substring(index);
            AssetDatabase.DeleteAsset(path);
            count++;
        }
        foreach (DirectoryInfo childInfo in dirInfo.GetDirectories())
        {
            Delete(childInfo);
        }
    }
}
/// <summary>
/// JsonUtilityを使うために作ったクラス
/// </summary>
[System.Serializable]
internal class AsmdefJson
{
    public string name;
    public bool allowUnsafeCode = false, overrideReferences = false, autoReferenced = true;
    public List<string> references = new List<string>(),
        optionallUnityReferences = new List<string>(),
        includePlatforms = new List<string>(),
        excludePlatforms = new List<string>(),
        precompiledReferences = new List<string>(),
        defineConstraints = new List<string>(),
        versionDefines = new List<string>();

    public override string ToString()
    {
        return JsonUtility.ToJson(this);
    }
}

コメント

  1. sam より:

    便利そうなツールをありがとうございます!!
    試しましたが、macだと動かないようです・・・・・・
    File couldn’t be read

    An infinite import loop has been detected. The following Assets were imported multiple times, but no changes to them have been detected. Please check if any custom code is trying to import them:
    Assets/XWeaponTrail\XWeaponTrail.asmdef
    Assets/XWeaponTrail/Editor\XWeaponTrailEditor3554.asmdef

    等でエラーになりました。
    スクショはこちらです。
    https://imgur.com/a/mj90W2I

    • げんとめ(管理人) より:

      ご指摘どうもです!
      多分ですけど、ファイルパスの区切り文字がWindowsとMacで違うことが原因だったんじゃないかと推測します。
      私がMacを持っていないので確認できないんですが、一応は修正しました。
      必要であれば試してみてください。

タイトルとURLをコピーしました