【Unity】ヘルプボックスを表示する PropertyDrawer

はじめに

インスペクター上のフィールドにヘルプボックスを表示するアトリビュートを作成しました。
使用イメージ

    [HelpBox(@"Help Box
Help Box
Help Box")]
    [SerializeField]
    private float m_Value;

ソースコード

HelpBoxAttribute

using System;
using UnityEngine;

[AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)]
public class HelpBoxAttribute : PropertyAttribute
{
    public readonly string Text;

    public HelpBoxAttribute(string text)
    {
        Text = text;
    }
}

HelpBoxDrawer

using System;
using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(HelpBoxAttribute))]
public class HelpBoxDrawer : PropertyDrawer
{
    private float m_Height;
    private HelpBoxAttribute m_HelpBoxAttribute => attribute as HelpBoxAttribute;
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        var helpBoxAttribute = attribute as HelpBoxAttribute;

        EditorGUI.PropertyField(position, property, label, true);
        position = new Rect(0.0f, position.y + EditorGUI.GetPropertyHeight(property, label, true) + 5.0f, position.width, m_Height);

        EditorGUI.HelpBox(position, helpBoxAttribute.Text, MessageType.Info);
    }

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        var content = new GUIContent(m_HelpBoxAttribute.Text);
        m_Height = GUI.skin.box.CalcHeight(content, EditorGUIUtility.currentViewWidth);
        
}

【Unity】コンポーネントパターンの ScriptableObject

コンポーネントパターンを適用した ScriptableObject のベースクラスです。

ソースコード

public class ComponentScriptableObject : ScriptableObject
{
    [SerializeField]
    protected List<ScriptableObject> m_Components = new();

    protected ScriptableObject Add(Type type)
    {
        if (type.IsClass && !type.IsAbstract && type.IsSubclassOf(typeof(ScriptableObject)))
        {
            var component = CreateInstance(type) as ScriptableObject;
            AssetDatabase.AddObjectToAsset(component, this);
            m_Components.Add(component);

            if (component is ComponentScriptableObject componentScriptableObject)
            {
                componentScriptableObject.AddRequirements();
            }

            return component;
        }

        throw new ArgumentException(type.GetTypeInfo().Name);
    }

    protected virtual IEnumerable<Type> Requirements()
    {
        yield break;
    }

    private void AddRequirements()
    {
        Requirements().ToList().ForEach(type => Add(type));
    }

    protected List<T> GetAllInChildren<T>() where T : ScriptableObject
    {
        var list = m_Components.Where(x => x is T).Cast<T>().ToList();
        list.AddRange(list.Where(x => x is ComponentScriptableObject).Cast<ComponentScriptableObject>().SelectMany(x => x.GetAllInChildren<T>()).ToList());
        return list;
    }

    protected static T Create<T>(string path) where T : ScriptableObject
    {
        var data = CreateInstance<T>();
        AssetDatabase.CreateAsset(data, path);
        if (data is ComponentScriptableObject componentScriptableObject)
        {
            componentScriptableObject.AddRequirements();
        }

        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();

        return data;
    }

    protected static T Load<T>(string path) where T : ScriptableObject
    {
        var data = AssetDatabase.LoadAssetAtPath<T>(path);
        if (data == null)
        {
            return null;
        }

        if (data is ComponentScriptableObject componentScriptableObject)
        {
            var settings = componentScriptableObject.m_Components.Select(x => x.GetType()).ToList();
            componentScriptableObject.Requirements().Where(type => !settings.Contains(type)).ToList().ForEach(type => componentScriptableObject.Add(type));
        }

        return data;
    }
}

public abstract class ComponentScriptableObject<T1> : ComponentScriptableObject where T1 : ScriptableObject
{
    public T Add<T>() where T : T1
    {
        return Add(typeof(T)) as T;
    }

    public void RemoveAll<T>() where T : T1
    {
        var components = GetAllInChildren<T>();
        foreach (var component in components)
        {
            m_Components.Remove(component);
            DestroyImmediate(component, true);
        }

        m_Components.RemoveAll(x => x is T);
    }

    public bool TryGet<T>(out T component) where T : T1
    {
        component = Get<T>();
        return component != null;
    }

    public T Get<T>() where T : T1
    {
        return m_Components.FirstOrDefault(x => x is T) as T;
    }

    public List<T> GetAllInChildren<T>() where T : T1
    {
        return base.GetAllInChildren<T>();
    }

    public static T Create<T>(string path) where T : T1
    {
        return ComponentScriptableObject.Create<T>(path);
    }

    public static T Load<T>(string path) where T : T1
    {
        return ComponentScriptableObject.Load<T>(path);
    }
}

【Unity】背景をアルファ0でスクリーンショットを撮る

はじめに

ScreenCapture クラスを使用して ゲーム画面をキャプチャすることができますが,
背景を透過してキャプチャしたかったのでクラスを作成しました。
docs.unity3d.com

完成図

ゲーム画面

キャプチャ結果

コード

public static class ScreenCapture
{
    public static void Capture(Camera targetCamera, Vector2Int captureSize)
    {
        // デスクトップに保存
        string desktopDirectoryPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
        var path = desktopDirectoryPath + "/" + DateTime.Now.ToString("yyyyMMdd-HHmmss") + ".png";

        var screenShot = new Texture2D(captureSize.x, captureSize.y, TextureFormat.ARGB32, false);
        var renderTexture = new RenderTexture(screenShot.width, screenShot.height, 32);
        targetCamera.clearFlags = CameraClearFlags.SolidColor;
        targetCamera.backgroundColor = new Color(0, 0, 0, 0);
        var prev = targetCamera.targetTexture;
        targetCamera.targetTexture = renderTexture;
        targetCamera.Render();
        targetCamera.targetTexture = prev;
        RenderTexture.active = renderTexture;
        screenShot.ReadPixels(new Rect(0, 0, screenShot.width, screenShot.height), 0, 0);
        screenShot.Apply();

        var bytes = screenShot.EncodeToPNG();
        UnityEngine.Object.DestroyImmediate(screenShot);
        File.WriteAllBytes(path, bytes);

        Debug.Log("ScreenCaptre: " + path);
    }
}

【WSL2】tmux セットアップ, メモ

はじめに

WSL2(Ubuntu)にて tmux のセットアップ手順のメモです.

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.3 LTS
Release:        22.04
Codename:       jammy

インストール

以下コマンドで tmux をインストールします.

sudo apt update
sudo apt install tmux

~/.tmux.conf

以下を参考に設定. qiita.com qiita.com tech.visasq.com

man page

https://linux.die.net/man/1/tmux

【Unity】回転する矩形と円形の接触判定

はじめに

以前矩形と円形の接触判定を行う処理の記事を書きましたが,
今回は矩形が回転した場合にも対応します.
yshinya09.hatenablog.com

完成形

以下のように回転する矩形と円形の接触判定をとります.

実装

図形の定義

円形と矩形のクラスをそれぞれ以下のように定義しました。
矩形は Unity の Rect クラスだと回転を持てない為, 定義しなおしました。

// 円形.
record Circle2d(Vector2 Center, float Radius);
// 矩形.
record Rect2d(Vector2 Center, float width, float height, float rotation);

接触判定

private static bool Overlap(Circle2d circle, Rect2d rect2d)
{
    var rect = new Rect(rect2d.Center.x - rect2d.Width / 2.0f, rect2d.Center.y - rect2d.Height / 2.0f, rect2d.Width, rect2d.Height);;

    var cos = Mathf.Cos(rect2d.Rotation * Mathf.Deg2Rad);
    var sin = Mathf.Sin(rect2d.Rotation * Mathf.Deg2Rad);
    var diff = circle.Center - rect.center;
    var pos = rect.center + new Vector2(diff.x * cos + diff.y * -sin, diff.x * sin + diff.y * cos);

    // Rect 上で最も Circle の Center に近い点を求める.
    var x = Mathf.Max(rect.xMin, Mathf.Min(pos.x, rect.xMax));
    var y = Mathf.Max(rect.yMin, Mathf.Min(pos.y, rect.yMax));
    var nearestPoint = new Vector2(x, y);

    // Rect 上で最も Circle の Center に近い点が, Circle の半径より近いをチェック.
    return (nearestPoint - pos).sqrMagnitude <= circle.Radius * circle.Radius;
}

【Unity】hierarchyで選択したGameObjectの座標を右クリックでコピーする

概要

これ

ペースト

Main Camera: 0.000, 1.000, -10.000
Directional Light: 0.000, 3.000, 0.000

コード

public class CopyPositionToClipboard
{
    [MenuItem("GameObject/Copy Position", false, 0)]
    private static void CopyPosition()
    {
        if (Selection.transforms.Length <= 0)
        {
            return;
        }

        var sb = new StringBuilder();
        foreach (Transform t in Selection.transforms)
        {
            var position = t.position;
            sb.AppendLine(t.name + ": " + position.x.ToString("F3") + ", " + position.y.ToString("F3") + ", " + position.z.ToString("F3"));
        }

        EditorGUIUtility.systemCopyBuffer = sb.ToString();
    }
}