うにてぃブログ

主にUnityとC#に関する記事を書いていきます

【Unity】MultiColumnHeaderState.Column の変数まとめ

MultiColumnHeaderState.Column は TreeView で複数カラムを実装する際に利用するクラスです

メモがてら各変数のが何に影響するかをまとめます

Column Class

クラスは以下のように定義されています

    public class Column
    {
      public float width = 50f;
      public bool sortedAscending;
      public GUIContent headerContent = new GUIContent();
      public string contextMenuText;
      public TextAlignment headerTextAlignment = TextAlignment.Left;
      public TextAlignment sortingArrowAlignment = TextAlignment.Center;
      public float minWidth = 20f;
      public float maxWidth = 1000000f;
      public bool autoResize = true;
      public bool allowToggleVisibility = true;
      public bool canSort = true;
      public int userData;
    }

変数

new MultiColumnHeaderState.Column 
{
    // 初期幅
    width = 95,
 
    // 初回ソートを昇順にするかどうか
    sortedAscending = true,
 
    // 表示されるカラム名
    headerContent = new GUIContent("Column Title", "Tooltip"),
 
    // カラムを右クリックした際に表示される、コンテキストメニューの表示名
    // 未定義の場合は headerContent が表示される
    contextMenuText = "Title";
  
    // カラム名の表示位置
    headerTextAlignment = TextAlignment.Right,
 
    // ソート矢印の表示位置
    sortingArrowAlignment = TextAlignment.Left,
 
    // 最小幅
    minWidth = 60,
 
    // 最大幅
    maxWidth = 1000,
 
    // ResizeToFit を呼び出した際に自動的に幅が変わるかどうか
    autoResize = true,
 
    // コンテキストメニューで表示非表示を切り替えられるかどうか
    // false の場合非表示にすることができない
    allowToggleVisibility = true,
 
    // カラムをソートできるかどうか
    canSort = true,
 
    // なにかしらのデータを付与できる
    userData = 1, 

},

【Unity】TreeView でソートを実装する

TreeView でソートをお手軽に実装するには MultiColumnHeader を利用する

MultiColumnHeader では ソート用のイベントとして sortingChanged が用意されており、これにイベントを登録することでどのカラムがソートされたかを判定できる

        internal TreeView(TreeViewState state, MultiColumnHeader multiColumnHeader) : base(state, multiColumnHeader)
        {
            multiColumnHeader.sortingChanged += OnSortingChanged;
            Reload();
        }

ソート処理全貌は以下のようになる

internal class SortableTreeView : TreeView
{
    internal SortableTreeView(TreeViewState state, MultiColumnHeader multiColumnHeader) : base(state, multiColumnHeader)
    {
        multiColumnHeader.sortingChanged += OnSortingChanged;        
        Reload();
    }
 
    /// <summary>
    /// ソート変更
    /// </summary>
    private void OnSortingChanged(MultiColumnHeader multiColumnHeader)
    {
        if (GetRows().Count <= 1)
            return;
        
        Sort(GetRows());
    }
 
    /// <summary>
    /// ソートを行う
    /// </summary>
    private void Sort(IList<TreeViewItem> rows)
    {
        // カラム別のソート順を取得する
        var sortedColumns = multiColumnHeader.state.sortedColumns;
        if (multiColumnHeader.sortedColumnIndex == -1 ||
            sortedColumns.Length == 0
           )
            return;
        
        // ソートするためにTreeViewItem をキャストする
        var cast = rootItem.children.Cast<TreeViewItem>();
        // 昇順ソートかどうかの判定
        var ascending = multiColumnHeader.IsSortedAscending(sortedColumns[0]);
        // 並べ替えするために 最初のソートカラムを基準にIOrderedEnumerable に変換する
        var items = InitialOrder(cast, ascending);
          
        // 2番以降のカラムをソートしていく
        for (var i = 1; i < sortedColumns.Length; i++)
        {
            // 昇順かどうか 
            ascending = multiColumnHeader.IsSortedAscending(sortedColumns[i]);
            // ThenBy で更にソートしていく
            switch (sortedColumns[i])
            {
                case 0:
                    items = ThenBy(items, i => i.displayName, ascending);
                    break;
                case 1:
                    items = ThenBy(items, i => i.id, ascending);
                    break;
            }
        }
        IOrderedEnumerable<T> ThenBy<T, TKey>(IOrderedEnumerable<T> source, Func<T, TKey> selector, bool ascending)
        {
            return @ascending ? source.ThenBy(selector) : source.ThenByDescending(selector);
        }
 
        // ソート後の TreeViewItem を再度子供に登録
        rootItem.children = items.Cast<TreeViewItem>().ToList();
        // Rows もソートされているためこちらにも並べ替えた結果を追加する
        rows.Clear();
        foreach (var item in rootItem.children)
        {
            rows.Add(item);
        }
        Repaint();
    }
   
    /// <summary>
    /// IOrderedEnumerable 変換
    /// カラムによって何をソート対象にするかを記述する
    /// </summary>
    private IOrderedEnumerable<DetailTreeViewItem> InitialOrder(IEnumerable<TreeViewItem> items, bool ascending)
    {
        switch (multiColumnHeader.state.sortedColumns[0])
        {
            case 1:
                return Order(items, i => i.id);
            default:
                return Order(items, i => i.displayName);
        }
        
        IOrderedEnumerable<T> Order<T, TKey>(IEnumerable<T> source, Func<T, TKey> selector)
        {
            return @ascending ? source.OrderBy(selector) : source.OrderByDescending(selector);
        }
    }
    
    protected override IList<TreeViewItem> BuildRows(TreeViewItem root)
    {
        if (!root.hasChildren)
            return new List<TreeViewItem>();
 
        var rows = base.BuildRows(root);
        // コンパイル走った際にソートしている必要があるので初回生成時もソートする
        Sort(rows);
        return rows;
    }

【Unity】UnityEditor上で SpriteAtlas の有効無効を切り替える

Editor 上で SpriteAtlas の有効無効を切り替えるのは ProjectSettings より行う

Edit / Project Settings... / Editor SpritePacker / Mode

設定項目

表記 内容
Disable 無効
Sprite Atlas V1 - Enabled For Builds バイナリビルド時のみ SpriteAtlas を有効
Sprite Atlas V1 - Always Enabled 常に SpriteAtlas を有効

2022

Unity2022 からは Sprite Atlas V2 が Experimental じゃなくなったようでちょっと UI が変わっている

【Unity】URP 利用時に SceneView カメラ の Rendering Renderer を変更する

Universal Render Pipeline Asset の Rendering / Renderer List が複数ある場合

Camera の Rendering / Renderer を変更しても GameView は変更されるが、SceneView は変更されない

SceneView のカメラの設定は、Universal Render Pipeline Asset の Rendering / Renderer List の Default に設定されてるやつが反映されるようで

Default を確認したいやつに変更することで SceneView でも確認したいカメラの設定で確認することができる

【Unity】SceneAsset はシリアライズしても UnityEditor 以外では利用できない

以下のようにフィールドを定義して、SceneAsset をセットすることができる

public class SampleMonoBehaviour : MonoBehaviour
{
    [SerializeField]
    private UnityEngine.Object _object;
}

しかし、SceneAsset は UnityEditor でしか利用できないため、実機で参照を取得しようとしても null になる

using UnityEngine;

namespace UnityEditor
{
  public class SceneAsset : Object
  {
    private SceneAsset()
    {
    }
  }
}

そのため、Build Settings の Scenes in Build に Scene を追加してロードするか

Scene のアセットバンドルをロードして SceneManager から読み込む必要がある

【Unity】子供のオブジェクトを整列させるコンポーネント

整列させたいけど、スクリプト書くのが面倒なときにご利用ください

using UnityEngine;
using UnityEngine.UI;
 
#if UNITY_EDITOR
using UnityEditor;
#endif
 
/// <summary>
/// GameObjectを整列させるためのツール
/// </summary>
public class ChildObjectAlignment : MonoBehaviour
{
    private enum Mode
    {
        XY,
        XZ,
    }
 
    [SerializeField]
    private bool _applyOnAwake;
     
    [SerializeField]
    private Vector2 _spacing;
 
    /// <summary>
    /// 開始位置
    /// </summary>
    [SerializeField]
    private GridLayoutGroup.Corner _startCorner;
    [SerializeField]
    private GridLayoutGroup.Axis _startAxis;
    [SerializeField]
    private Mode _target;
     
    /// <summary>
    /// 固定するか
    /// </summary>
    [SerializeField]
    private bool _isFixedCount;
    [SerializeField]
    private int _fixedCount;
     
    private void Awake()
    {
        if (_applyOnAwake)
            DoAlignment();
    }
 
    /// <summary>
    /// 整列開始
    /// </summary>
    public void DoAlignment()
    {
        var childCount = transform.childCount;
        if (childCount <= 0)
            return;
 
        // 縦横に並べる数を確保
        var count = new Vector2Int(1, 1);
        var index = _startAxis == GridLayoutGroup.Axis.Horizontal ? 0 : 1;
        var index2 = (index + 1) % 2;
        count[index] = childCount;
 
        // 個数上限
        if (_isFixedCount)
        {
            count[index] = _fixedCount;
            count[index2] = Mathf.CeilToInt(childCount / (float) _fixedCount);
        }
 
        var size = new Vector2(
            (count.x - 1) * _spacing.x,
            (count.y - 1) * _spacing.y
        );
 
        var startPosition = new Vector2(size.x / 2f, size.y / 2f);
        if (_startCorner == GridLayoutGroup.Corner.UpperLeft || _startCorner == GridLayoutGroup.Corner.LowerLeft)
            startPosition.x *= -1;
        if (_startCorner == GridLayoutGroup.Corner.LowerLeft || _startCorner == GridLayoutGroup.Corner.LowerRight)
            startPosition.y *= -1;
         
        // 座標毎のDiff
        var diff = new Vector2(_spacing.x, _spacing.y);
        if (_startCorner == GridLayoutGroup.Corner.UpperRight || _startCorner == GridLayoutGroup.Corner.LowerRight)
            diff.x *= -1;
        if (_startCorner == GridLayoutGroup.Corner.UpperRight || _startCorner == GridLayoutGroup.Corner.UpperLeft)
            diff.y *= -1;
 
        for (var i = 0; i < childCount; i++)
        {
            var child = transform.GetChild(i);
            var position = new Vector2Int(0, 0)
            {
                [index] = i % count[index],
                [index2] = Mathf.FloorToInt(i / (float)count[index])
            };
            var localPosition = startPosition + position * diff;
            switch (_target)
            {
                case Mode.XY:
                    child.localPosition = localPosition;
                    break;
                case Mode.XZ:
                    child.localPosition = new Vector3(localPosition.x, 0f, localPosition.y);
                    break;
            }
        }
    }
}

#if UNITY_EDITOR
 
[CustomEditor(typeof(ChildObjectAlignment))]
public class ChildObjectAlignmentEditor : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
         
        if (GUILayout.Button("Apply"))
        {
            var t =  target as ChildObjectAlignment;
            t.DoAlignment();
        }
    }
}
 
#endif

【Unity】URP (Universal Render Pipeline) の 2D Light を試す

最新のUnity で 2D Light を試してみたくて、やり方を調べたのでここにメモしておきます

環境

Unity - 2022.1.0b16 Universal RP - 13.1.7

導入

Universal RP の追加

PackageManager より Universal RP をインストール

Universal Render Pipeline Asset の作成

Create / Rendering / URP Asset (with 2D Renderer)

Universal Render Pipeline Asset の適応

Edit / Project Settings / Graphics の Scriptable Render Pipeline Settings に先程作成した URPA を設定する

2D Light の設置

URPA を設定することで 2D のライトを設置できるので、適切な位置に生成する

これにより Sprite Renderer がライト影響を受けるようになる

2D Light の影を表示するように設定

Light Type が Global の場合は影を設定することができないので、他の Light Type にする

そうすると Shadow の項目があるので有効にする

2D Light の影を表示するオブジェクトの追加

光を遮りたいオブジェクトに Shadow Caster 2D を追加し、Casts Shadows を有効にする

これでオブジェクトが光を遮って影を表示することができました

他の設定に関しては公式ドキュメントを参考にしてください Shadow Caster 2D | Universal RP | 7.1.8