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を使うのに困りますね。バックフィールドでどんな名前になっているか見えないので。

今までSandboxで課金テスト出来ていたのが、突如レシートの検証が失敗するようになった。

今までSandboxで課金テスト出来ていたのが、iOSのみ突如レシートの検証が失敗するようになった。

購入処理はうまくいくのにレシート検証で失敗するようで以下のようなエラーが出た。

Validation failed:/Client/ValidateIOSReceipt:iTunes Sandbox validation result:21003
(AuthnticationFailed) from https://sandbox.itunes.apple.com/verifyReceipt

最近変えたところといえば、アプリ内課金にサブスクリプションを追加したこと。

しかし追加しようとしているSubscription以外の、ジェムの購入でもエラーが出るようになってしまった。

レシートの検証にはPlayFabを使っている。

はてっ???

いろいろ調べ、知人に相談し、ようやく解決。

結論としてはPlayFabでアプリ共有シークレット(App-Specific Shared Secret)を設定したら解決しました。

1.App Store Connect でApp-Specific Shared Secretを作る。
f:id:wkpn:20220306172655p:plain


2.上記で出来たキーをPlayFabで設定する。
f:id:wkpn:20220306172713p:plain