Unityプログラマーに優しい画像データの作り方

自分がアーティストさんにデータ作ってもらうときに、こうだとありがたいってことをメモ

アイコンとベースは分ける

一般的にこういう画像データより

こういう風に分けてもらったほうが、それぞれを使い回せるので便利だしメモリ節約になる

余白は無い方が調整しやすい


こういうデータ👆より、

こういうデータ👆のほうが調整しやすい。

文字を装飾したい時でない限り、画像に文字は載せない(ローカライズのため)

プログラマーの方で文字は載せるので、アーティストさんの画像の方には文字は載せないで欲しい。

こういうボタン👆も...


こういう画像👆さえ用意してもらえばOK

そして、ローカライズを考えて文字装飾はタイトルロゴなど一部に留めるように。

コーナーが同じでサイズ違いのボタン/ウィンドウは作れるので1個あればOK


こういうデータが一個あれば...

Unity側でこういうのも作れるし、

こういうのも作れるし、

こういうのも作れます。

っというのもUnity側で、

こんな感じでコーナーを設定して、コーナーはそのままに真ん中の部分を拡大したオブジェクトを作ることができるから。さらに画像のPixel Per Unitという値をいじることで画像におけるコーナー部分の占める比率も変えることができるので上記のように色んなバリエーションが作れます。

色違いで、乗算だけで行けるものは白で作ってもらえると助かる


こういうデータがあると、プログラマー側(Unity Editor側)で、
とか
とか
を作れるので便利だしメモリ節約になる。

けど乗算だとアーティストさんが意図した見た目にならないこともあるようなのでアーティストさんと要相談。

他にも思いついたら追記します。

MacのUnityでPlatformをPCにしているときにはGPUInstancingが正常に動くが、AndroidやiOSに設定すると絵が正しく表示されない

この件、めちゃくちゃハマったのですが、結論から言うとShader側で設けているBufferに対してCPU側から送るバッファのサイズがおかしかったのが原因でした。

なんで、Platform設定によってうまくいったりいかなかったりするんだろう???

しかもWindowsでEditorを動かしている際はPlatformの設定に関わらずうまく動くという...

一応、いかにサンプルを作りました。
github.com

P.S

DirectX* or desktop OpenGL doesn't really support half or fixed data types.

という記載を見かけたので、DirectXを使っているWindowsやdesktop OpenGLなんかではhalfがfloatに変換されているのかもって思いました。

検索キーワード用:DrawMeshInstancedIndirect,URP

UniTaskのWhenAnyで終了しなかった側のタスクはどうなる?

質問

UniTaskのWhenAnyで終了しなかった側のタスクはどうなるのでしょうか?

回答

タスクは走り続けます。

意図的にそうするなら良いのですが気が付かずに放置しちゃうことが多々あります。特にUniRxのIObservableをToUniTaskするときとか。ちゃんとキャンセルするようにしましょう。

例↓

public class UniTaskTest : MonoBehaviour
{
    void Start()
    {
        var cts = new CancellationTokenSource();
        UniTask.Void(async () =>
        {
            var index = await UniTask.WhenAny(WaitKeyDown1(cts.Token), WaitKeyDown2(cts.Token));
            Debug.Log($"task {index+1} is fired.");
            cts.Cancel();//どっちか終わったみたいなのでキャンセルする
        });
    }

    private async UniTask WaitKeyDown1(CancellationToken token)
    {
        while (!token.IsCancellationRequested)
        {
            if (Input.GetKeyDown(KeyCode.Alpha1))
                break;
            await UniTask.Yield();
        }
        Debug.Log("WaitKeyDown1 Complete!");
    }
    
    private async UniTask WaitKeyDown2(CancellationToken token)
    {
        while (!token.IsCancellationRequested)
        {
            if (Input.GetKeyDown(KeyCode.Alpha2))
                break;
            await UniTask.Yield();
        }
        Debug.Log("WaitKeyDown2 Complete!");
    }
}

FacebookSDKの組み込みに苦労した

FacebookSDKの組み込みに苦労した。

Android版。

1. You don't have the Android SDK setup!というエラー

なぜかSDKのパスを認識していないらしい。

  1. Preferences>External Tools>Android SDK
  2. 設定済みのパスをコピーしてチェックを外す
  3. コピーしたパスを貼り付ける
  4. 再度チェックをいれる

でエラーがでなくなりました。

2.Your android debug keystore file is missing!というエラー

デバッグ用のkeystoreが無いよ。ということらしい。Macの場合、
/Users/ユーザー名/.android
以下にデバッグ用のkeystoreを作ってあげると解決する。
作るのがめんどくさいので、テキトーに空のプロジェクトを作ってAndroidビルドすると勝手にできる。


これらのエラー、Forum見ると数年直っていないっぽい。なんとかして欲しい。

C#では型でもswitch文で分岐できるらしい

最近、型でもswitch文で分岐できるということを知った。
例えば以下のような感じ。

using UnityEngine;

public interface TestInterface
{
}

public class TypeA : TestInterface
{
}

public class TypeB : TestInterface
{
}

public class TypeC : TestInterface
{
}

public class TestTypeSwitch : MonoBehaviour
{
    private TestInterface testType = new TypeC();
    
    void Start()
    {
        switch (testType)
        {
            case TypeA:
                print("This is Type A.");
                break;
            case TypeB:
                print("This is Type B.");
                break;
            case TypeC://testTypeはTypeC型なのでここを通る
                print("This is Type C.");
                break;
        }
    }
}

あまり使うシチュエーションは無い気がするけど。

リソース読み込み終わったらチョメチョメしたいをUniTaskとUniRxで

Unity開発者ギルドで、何か読むのにオススメのコードを募集したら、かめふぃさんのコードをご紹介いただきました。
github.com


色々と学ぶところが多かったのですが、今日はそのうちの1つを忘れないようにメモ。

リソース読み込み終わったらチョメチョメしたいっていうのをUniTaskとUniRxで良い感じに書かれていたので、自分も真似して書いてみました。

using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using UnityEngine;

//Icon名を渡すと、Resourcesから読み込んで非同期で返すクラス
public class IconProvider : IDisposable
{
    private Dictionary<string, Sprite> _cache = new Dictionary<string, Sprite>();
    
    public async UniTask<Sprite> GetIcon(string name)
    {
        //キャッシュにあったらキャッシュの方を使う
        if (_cache.ContainsKey(name))
        {
            return _cache[name];
        }

        _cache[name] = await Resources.LoadAsync<Sprite>("Sprites/Icons/" + name) as Sprite;
        Debug.Assert(_cache[name] != null);//nullは許さない

        return _cache[name];
    }

    public void Dispose()
    {
        _cache = null;
    }
}
using UniRx;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;

//アイコンを読み込んでImageに挿し込むテスト
public class IconProviderTest : MonoBehaviour
{
    [SerializeField] private string iconName;
    private readonly IconProvider _iconProvider = new IconProvider();
    private Image _image;

    void Start()
    {
        _image = GetComponent<Image>();

        LoadIcon();
    }

    [ContextMenu("LoadIcon")]
    void LoadIcon()
    {
        _iconProvider.GetIcon(iconName)
            .ToObservable()
            .Take(1)
            .Subscribe(sprite => { _image.sprite = sprite; });
    }
}

ポイントとしては、GetIconで返ってきたUniTaskをToObservableしているところ。
こんな事ができるんですね〜。
Subscribeすると、GetIconの処理が終わったタイミングで購読した処理を走らせてくれるみたいです。
すごい!

P.S
これだったら、ContinueWithで良いんじゃない?っていうご指摘を受けました。
たしかに...

 _iconProvider.GetIcon(iconName)
    .ContinueWith(sprite => { _image.sprite = sprite; });

これでいけますね。Observableを返せて嬉しいのは、もっと細かくタイミングを制御したいときですかね〜。

プロパティをSerializeFieldにしたい

Unityをやっているとこんなふうに書けたら良いのに...って思うことないですか?

[SerializeField]
public string Name { get; private set; }

要は、プロパティをSerializeFieldにしてInspector上に表示したり値を保存したりしたいのです。しかし、これはうまくいきません。

なので、しかたなく以下のように書いていました。

[SerializeField] private string name;
public string Name => name;

わざわざあたらしくnameというメンバ変数を書き足すのです。めんどくさいです。

しかし、これを解決する書き方を教わりました。

[field: SerializeField]
public string Name { get; private set; }

SerializeFieldの前にfield:をつけるんですね。なぜこれでうまくいきのでしょう?

そもそも、なぜ

public string Name { get; private set; }

このようにかけるかというと、これはC# 3.0 で導入された自動プロパティ(auto-property, auto-implemented property)という機能によるものです。

これは、プロパティのget/setの中身の省略できるしくみですが、内部的には、

private string __name;
public string Name
{
  get { return this.__name; }
  set { this.__name = value; }
}

というようなコードに相当するものが生成されています。 (__nameという変数にプログラマは直接アクセスできません。)

このコンパイラーによって生成されるフィールド(この例で言うと __name)は、バックフィールド(baking field: 後援フィールド)と呼ばれます。

先のコードでSerializeFieldの前に書いたfield:は、このバックフィールドを対象にしますよっという記述なのです。

なので、__nameがSerializeFieldになり、めでたくInspector上にも表示されるというわけです。

参考:

ufcpp.net

ufcpp.net


P.S
バックフィールドにSerializeField属性をつけるのは良くないという記事を見つけました。
色々理由は書かれていますが、自分としては、バッキングフィールドの命名が仕様化されていないという点が一番怖いかなと思いました。
ないとは思うけど、万一、命名規則が変わったりしたら...
qiita.com

P.S part2
もう一点使わないほうが良い理由を見つけました。バックフィールドにSerializeFieldを使っているとFormerlySerializedAsを使うのに困りますね。バックフィールドでどんな名前になっているか見えないので。