うにてぃブログ

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

【Unity】UnityPackage で GitHub 上の package.json をルートディレクトリから移動する方法

GitHub上のプロジェクトで、ルートディレクトリにpackage.jsonがなくても、特定のディレクトリからパッケージをインストールする方法を紹介します。

UnityのPackage Managerは、GitHub上のリポジトリからパッケージを直接インストールできる機能を提供しています。通常、ルートディレクトリにpackage.jsonがある必要がありますが、GitHubのRootにpackage.jsonが存在しない場合でも、Getパラメータでpathを渡すことで特定のディレクトリからパッケージをインストールすることができます。

具体的な手順は以下の通りです。

UnityのPackage Managerを開きます。 プロジェクトのパッケージをインストールしたい場所をクリックします。 左上の[+(Add package from git URL)]ボタンをクリックします。 URLフィールドに以下のような書式でGitHubリポジトリのURLを入力します。

https://github.com/ユーザー名/リポジトリ名.git?path=インストールしたいディレクトリのパス

例えば、UniTaskパッケージを以下のディレクトリからインストールしたい場合は、次のようにURLを指定します。

https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask

Enterを押してパッケージをインストールします。 これで、指定したディレクトリからパッケージがインストールされます。GitHubのRootにpackage.jsonが存在しなくても、特定のディレクトリからパッケージをインストールすることができます。

【Unity】EditorCoroutine で WaitForSeconds を使用できない問題の解決方法

UnityのEditorCoroutineでは、通常のWaitForSecondsを利用することができず、代わりにEditorWaitForSecondsを使用する必要があります。これはUnityのEditor内でのコルーチン処理において、時間の経過を待つための仕組みが異なるためです。

EditorCoroutineを使用する際に、通常のWaitForSecondsを用いると正しく動作しません。そのため、EditorCoroutine内ではEditorWaitForSecondsを利用する必要があります。

以下は、EditorWaitForSecondsを使用したサンプルコードです

using UnityEngine;
using UnityEditor;

public class EditorCoroutineExample : MonoBehaviour
{
    [MenuItem("CoroutineExample/StartCoroutineInEditor")]
    static void StartCoroutineInEditor()
    {
        EditorCoroutineUtility.StartCoroutineOwnerless(MyCoroutine());
    }

    static IEnumerator MyCoroutine()
    {
        Debug.Log("Coroutine started in Editor");
        yield return new EditorWaitForSeconds(3); // EditorWaitForSecondsを利用
        Debug.Log("Waited for 3 seconds in Editor");
    }
}

このサンプルコードでは、EditorCoroutineUtility.StartCoroutineOwnerlessを使用してEditor内でのコルーチン処理を開始し、EditorWaitForSecondsを使用して3秒待機します。

このように、UnityのEditorCoroutineでWaitForSecondsを使用する際には、EditorWaitForSecondsを利用することで正常に動作させることができます。


以下は、EditorWindowを継承した場合にEditorCoroutineとEditorWaitForSecondsを使用するサンプルコードです。

EditorWindowを継承した場合は this.StartCoroutine で EditorCoroutine を開始できます。

using UnityEngine;
using UnityEditor;

public class MyEditorWindow : EditorWindow
{
    [MenuItem("Window/MyEditorWindow")]
    static void ShowWindow()
    {
        GetWindow<MyEditorWindow>("My Window");
    }

    void OnGUI()
    {
        GUILayout.Label("Editor Coroutine Example");

        if (GUILayout.Button("Start Coroutine"))
        {
            this.StartCoroutine(MyCoroutine());
        }
    }

    static IEnumerator MyCoroutine()
    {
        Debug.Log("Coroutine started in EditorWindow");
        yield return new EditorWaitForSeconds(3); // EditorWaitForSecondsを利用
        Debug.Log("Waited for 3 seconds in EditorWindow");
    }
}

Builtin Render Pipeline から Universal Render Pipeline (URP) に切り替える方法

URP Assetを作成します

Project SettingsのGraphicsにアセットをセットします

すると確認ダイアログが表示されるのでアセットをセットします

「アプデするよ」って内容が表示されるのでOKを選択します

更新が終わると、URPでないShaderはエラーとなるため、変更する必要があります

例えば、Default-materialはStandardを利用しているのでエラーになります

更新後に作成したオブジェクトには、LitのMaterialがセットされます

【Unity】float で Color.rgb を近似する

Meshの情報を使ってShaderで色を操作する際、RGB値を渡すとfloat3が必要になりますが、多くの色を扱いたい場合はできるだけ使用する数を減らしたいですね。

そのような場合、RGBをfloatに変換し、Shader側でfloatからRGBに変換することで、完全には復元できないかもしれませんが、1つのfloatで済ませることができます。

以下がRGBをfloatに変換する処理です。

/// <summary>
/// RGBをfloatに変換 
/// </summary>
float ColorToFloat(Color color)
{
    var rgb = new Vector3Int(
        Mathf.FloorToInt(color.r * 255f + 0.5f),
        Mathf.FloorToInt(color.g * 255f + 0.5f),
        Mathf.FloorToInt(color.b * 255f + 0.5f)
    );
    float packed = rgb.x * 65536 + rgb.y * 256 + rgb.z;
    return packed / (256 * 256 * 256);
}

復元する際には、以下のようにして、ある程度似た色に戻すことができます。

half4 FloatToColor(float packed)
{
    packed *= 256 * 256 * 256;
    half3 color = half3(0, 0, 0);

    color.b = fmod(packed, 256);
    packed = floor(packed / 256);
    color.g = fmod(packed, 256);
    color.r = floor(packed / 256);
    return half4(color / 255, 1);
}

実際に利用してみた結果、色がそこまで大きくずれていないことが確認できましたね。

【Unity】TextMeshPro でタップした箇所の文字を取得する汎用処理とサンプルコード

TextMeshProを使用して、タップした箇所の文字を取得するための一般的な処理について説明します。

using TMPro;
using UnityEngine.EventSystems;

/// <summary>
/// TextMeshPro のタップ処理
/// </summary>
public static class TextMeshProTapUtility
{
    /// <summary>
    /// タップした箇所の文字を取得
    /// </summary>
    public static bool TryGetTapCharacter(this TMP_Text self, PointerEventData eventData, out char character)
    {
        character = default;
        var index = TMP_TextUtilities.FindIntersectingCharacter(self, eventData.position, eventData.pressEventCamera, false);
        if (index < 0)
            return false;
        
        character = self.textInfo.characterInfo[index].character;
        return true;
    }
    
    /// <summary>
    /// タップした箇所の単語を取得
    /// </summary> 
    public static bool TryGetTapWord(this TMP_Text self, PointerEventData eventData, out string word)
    {
        word = default;
        var index = TMP_TextUtilities.FindIntersectingWord(self, eventData.position, eventData.pressEventCamera);
        if (index < 0)
            return false;
        
        var wordInfo = self.textInfo.wordInfo[index];
        var wordStartIndex = wordInfo.firstCharacterIndex;
        word = self.text.Substring(wordStartIndex, wordInfo.characterCount);
        return true;
    }
    
    /// <summary>
    /// タップした箇所のリンクを取得
    /// </summary>
    public static bool TryGetLink(this TMP_Text self, PointerEventData eventData, out string link)
    {
        link = default;
        var index = TMP_TextUtilities.FindIntersectingLink(self, eventData.position, eventData.pressEventCamera);
        if (index < 0)
            return false;
        
        link = self.textInfo.linkInfo[index].GetLinkID();
        return true;
    }
}

サンプル

以下は、上記のユーティリティクラスを利用したサンプルコードです。

using UnityEngine;
using TMPro;
using UnityEngine.EventSystems;

public class TextMeshProClickHandler : MonoBehaviour, IPointerClickHandler
{
    [SerializeField]
    private TextMeshProUGUI _textMeshPro;

    public void OnPointerClick(PointerEventData eventData)
    {
        if (_textMeshPro.TryGetLink(eventData, out var link))
        {
            Debug.Log("Clicked link: " + link);
        }
        
        if (_textMeshPro.TryGetTapCharacter(eventData, out var character))
        {
            Debug.Log("Clicked character: " + character);
        }
        
        if (_textMeshPro.TryGetTapWord(eventData, out var word))
        {
            Debug.Log("Clicked word: " + word);
        }
    }
}

このサンプルコードは、TextMeshProTapUtilityクラスを使用して、タップした箇所のリンク、文字、および単語を取得する方法を示しています。

【Unity】Unityの組み込みリソースを手軽に確認するツール

UnityEditor上で利用可能なアイコンやGUIStyleを調べるのが手間だと感じていました。そこで、これらのリソースを一覧で確認できる便利なツールを作成しました。

github.com

このツールを使用することで、元々組み込まれているアイコンやGUIStyleに簡単にアクセスできます。手軽にリソースを確認し、開発プロセスを効率化できます。

詳細やツールの使用方法については、GitHubリポジトリをご覧いただければと思います。興味がありましたらぜひチェックしてみてください。

【Unity】テクスチャやスプライトのサムネイルが表示されない

特定の Unity バージョンで、テクスチャやスプライトを設定する際に表示されるウィンドウで、サムネイルが正しく表示されない現象が発生しています。

初めは Unity のキャッシュの変更が原因かと考えていましたが、実際にはバグで表示されていないようでした。

Issue Tracker を見てみると以下のバージョン以降でこの問題が修正されていることが確認できました。

バグの詳細は Unity のIssue Trackerで確認できます

対応策

UnityEditorがキャッシュしていれば正常に表示されるため、以下のように無理やりロードしてキャッシュすることで、バグのあるバージョンでもサムネイルが正しく表示されるようになります。

private static List<Sprite> _cache;

[MenuItem("Tools/CacheThumbnail")]
private static void Cache()
{
    _cache = new List<Sprite>();
    var loadGUIDs = AssetDatabase.FindAssets("t:Sprite", new string[] {"Assets"});
    foreach (var guid in loadGUIDs)
    {
        var path = AssetDatabase.GUIDToAssetPath(guid);
        _cache.Add(AssetDatabase.LoadAssetAtPath<Sprite>(path));
    }
}