20200812のUnityに関する記事は6件です。

VRセットを買っちゃったが、Unityはどう始まるの? ?

オキュラスクエストを買っちゃったね。

俺の記事を読んで、「エリお兄さんみたいにVR世界に行きたい!!」と思ったでしょう。
アマゾンかメルカリでオキュラスクエストを買っちゃったね。それでは、さっそく始まりましょう!

開発環境構築 Do you know 「RTFM」?

RTFMとは、"read the fucking manual"(マニュアルを読みやがれ)という語句の頭字語である。

ああ、そうなんだ。マニュアルがあるのか。オキュラスクエストでも開発者の育つマニュアルがあります。

このリンクをクリックすると、クエストの開発者向けのトップ画面に行けます。ものすごく読みやすくて、日本語でも読めるものです。 この記事を作家は外人ので、ちょっと英語で読みいただきたいと思います。

注意ポイント: これからの記事はUnityエンジンのみを使います。Unreal派のお友達、すみませんでしたmm

1. 開発環境セットアップ

じゃあ「開発環境を設定する」を読みましょうっか!

tl;dr:

  1. Unity をインストールしてください。
  2. Android Build Supportのプラグインもインストールしてください。
  3. 新しい3Dプロジェクトを作ると、環境構築ができました。

おつかれさん。今日はここまで。 Please Like & Subscribe!

じゃねえよ。 話はまだだ終わってないし。

2. ビッグチョイスタイム:Quest用アプリか、PCVRアプリか?

今気づいたかもしれませんが、マニュアルでこういうメッセージがあります:

Oculus Quest開発
すべてのOculus Quest開発者は、Questストアと追加リソースへのパブリッシングアクセス権を得る前に、コンセプトレビューに合格する必要があります。Questアプリ開発サイクルのできる限り早い段階で、コンセプトドキュメントをレビュー用に提出してください。その他の情報およびコンテキストに関しては、「アプリをOculus Questストアに提出する」をご確認ください。

tl;drとは、もし読者は「燃えろ!爆乳天国VR」というゲームとか作りたいなら、お早めにオキュラスストアのスタッフにコンセプトレビューしてください。18+コンテンツ、グロテスクな暴力ゲームとかはオキュラスストアで売れないので、ちょっと他の物流方法を考えなきゃいけないです。

理由は、クエストのゲームを作ったら、クエストのみで遊べます。オキュラスクエストストアか、前の書いたサイドクエストに売れません!
その上に、クエストはめっちゃ売っている(フェイスブックと信じたら、世界中40万台ぐらい)が、RoadtoVrの記事の通り、Steamに使っているヘッドセットは世界中260万ぐらいです。オキュラスクエストはその中10%です、26万台ぐらい
じゃあどうしますか? ?

PCVR(PCでつないでままできるゲーム)は今でも一番大きいなマーケットです。260万から90%はもう234万台ぐらいので、ありゃはわかったほうがいいだと思います。ストアフロントなどは別の記事(開発が終わったら、書きますぜw)

3. 開発用の準備

開発者モードをONする方法と確認

  1. オキュラスのスマホアプリで設定に行ってください。
  2. 設定 -> その他設定 -> 開発者モード ON
  3. これからケーブルを繋いでください。前にやったら、スマホアプリが繋がれないはずかもしれません。あーめん・・・
  4. 本体をUSBケーブルを繋いだら、Oculus Link(オキュラスリンク)のことを聞いています。あれはゲームのモードだから、NOしてください。本体でデータをアクセスを聞いています。それはOKしてください。
  5. これで繋がりOK、次はUnityを開いてください。
  6. File ->Build Settings ->Platform をAndroidして、 Run Deviceを選んだら、Oculus Questが出ているはずです。
  7. [Build And Run (ビルドして実行)]をクリックして、Oculusデバイス上でアプリを実行します。

NOTE:将来の話ですが、Androidのプロジェクトなら、このファイルはQuestのみを使えるものになります。なんかジェネリックの場合、次の記事に書こうと思っています。

Android Debug Bridgeのデバッグを有効にする

これでCLIで直接Questにものを送れます。

  1. ケーブルを繋いで
  2. Windowsのみドライバーダウンロード: https://developer.oculus.com/downloads/package/oculus-adb-drivers/ 解凍、/oculus-go-adb-driver-2.0/usb_driver/ フォルダーに移動して、android_winusb.inf 右クリック、インストール。
  3. ターミナルでadb devicesを書いてください。Oculusが見つけたら、OK。

UnityのプロジェクトにOculus Integrationを追加する。

これはちょっとむずいかもしれませんが、とりあえずUnityのAssetStoreに行って、Oculus Integrationをインストールして、インポートしてください。めっちゃ時間がかかるが、頑張ってね!

最後のステップ:プロジェクト設定にする

これはただのオプティマイズのものだから、マニュアルの通りを読んでください。

QUESTのアプリを作る場合、こちらのリンク

Rift(PCVR)に向いてる方、こちらのリンク

次の記事で、最初のアプリをやってみますぜ!!

俺のGodotはどうしたの??

エリック、なんでGodotを辞めたんだ?!

tl;dr: Linkedinで日本全国のUnity求人広告:846件。Godot:0件。当たり前だろ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityのPlane上に注視点ヒートマップを作る

目的

VR空間での視線情報の可視化。
個人的覚え書き。

ヒートマップのリポジトリ:https://github.com/sakamo1290/FixationMap

方針

Shaderで注視点を中心に赤→緑→青と色分けする。
時間経過とともに青→緑→赤となるようにする。
demo.gif

色の変化

注視点を中心に赤→緑→青とする。

HSV変換を用いる。https://techblog.kayac.com/unity_advent_calendar_2018_15 を参考にした。

GazeMap.Shader
            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
                float4 worldPos : TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4x4 _TransformMatrix;
            float4 _CubePos;

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                //uv座標をワールド座標に変換
                o.worldPos = mul(_TransformMatrix, float4(-(v.uv.x - 0.5) * 10, 0, -(v.uv.y - 0.5) * 10, 1));
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                UNITY_APPLY_FOG(i.fogCoord, col);
                //ヒートマップの効果範囲(仮置き)
                float maxDia = 0.5;
                //マッピングするための重み
                float probability = (clamp(maxDia - distance(i.worldPos, _CubePos), 0, maxDia)) / 135;
                float3 hsv = rgb2hsv(float3(col.r, col.g, col.b));
                //1frame前のヒートマップに重畳
                float h = hsv.y == 0 ? 0.66 - probability : hsv.x - probability < 0 ? 0 : hsv.x - probability;
                //効果範囲内なら色を変える
                return maxDia> distance(i.worldPos, _CubePos) ? float4(hsv2rgb(float3(h, 1, 1)), 1) : col;
            }

時間経過とともに青→緑→赤

bufferで1つ前のフレームの計算結果を使う

MainGazeMap.cs
    public Material iniMat;
    public Material paintMat;
    public Transform gazePoint;

    Material mainMaterial;
    RenderTexture mainTex;
    void Start()
    {
        mainTex = new RenderTexture(770, 1000, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default);
        Graphics.Blit(mainTex, mainTex, iniMat);

        mainMaterial = GetComponent<Renderer>().material;
        mainMaterial.SetTexture("_MainTex", mainTex);
    }
    void FixedUpdate()
    {
        RenderTexture buf = RenderTexture.GetTemporary(770, 1000); ;
        MatUpdate();
        Graphics.Blit(mainTex, buf, paintMat);
        Graphics.Blit(buf, mainTex);
        RenderTexture.ReleaseTemporary(buf);
    }
    void MatUpdate()
    {
        paintMat.SetVector("_CubePos", new Vector4(gazePoint.position.x, gazePoint.position.y, gazePoint.position.z, 1));
        paintMat.SetTexture("_MainTex", mainTex);
        paintMat.SetMatrix("_TransformMatrix", transform.localToWorldMatrix);
    }

動かす

3DオブジェクトのPlaneを配置して名前をGazeMapにする。
Cubeを作って名前をGazePointとしてGazeMap上に置く。
MainGazeMap.csをアタッチして初期化マテリアルとペイントマテリアルを配置する。
unity image.PNG
初期化マテリアルはRendering ModeをFadeにする。
defaltMat.PNG
ペイントマテリアルは作成したGazeMap.Shaderをあてる。
paintMat.PNG
これで動かすと下の画像のようになる
demo.gif

視野を考慮してマッピング範囲を決める

人間の視野は中心2°が鮮明に、5°までをぼんやりと認識している。
これを考慮してGazeMap.ShaderとMainGazeMap.csを修正する。
視線の開始位置の情報を加える。

GazeMap.Shader
            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                UNITY_APPLY_FOG(i.fogCoord, col);
                //対象ピクセルの角度を計算
                float angle = atan2(distance(i.worldPos, _CubePos), distance(i.worldPos, _GazeOri)) * 57.2974;
                //正規分布を使って中心に重みづけする。計算は参考を参照。
                float probability = exp(-0.36125 * angle * angle)/90;
                float3 hsv = rgb2hsv(float3(col.r, col.g, col.b));
                float h = hsv.y == 0 ? 0.66 - probability : hsv.x - probability < 0 ? 0 : hsv.x - probability;
                //視野範囲内ならマッピングをする
                return angle < 2.5 ? float4(hsv2rgb(float3(h, 1, 1)), 1) : col;
            }
MainGazeMap.cs
    public Transform gazeOrigin;
    void MatUpdate()
    {
        paintMat.SetVector("_CubePos", new Vector4(gazePoint.position.x, gazePoint.position.y, gazePoint.position.z, 1));
        //追加
        paintMat.SetVector("_GazeOri", new Vector4(gazeOrigin.position.x, gazeOrigin.position.y, gazeOrigin.position.z, 1));
        paintMat.SetTexture("_MainTex", mainTex);
        paintMat.SetMatrix("_TransformMatrix", transform.localToWorldMatrix);
    }

修正して動かすと画像のようになる。
円周付近の色の変化が遅くなっていることがわかる。
gaussian.gif

参考

【Unity】RGBをHSVに変換して明るさとかを変えるシェーダー
https://techblog.kayac.com/unity_advent_calendar_2018_15

UnityでRenderTextureをファイルに保存
https://psychic-vr-lab.com/blog/unity/unity%E3%81%A7rendertexture%E3%82%92%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AB%E4%BF%9D%E5%AD%98/

Visual span and other parameters for the generation of heatmaps
https://www.researchgate.net/publication/220810985_Visual_span_and_other_parameters_for_the_generation_of_heatmaps

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】【PlayFab】環境入れたその次に。実際の利用でのログインとユーザーデータ更新

まえがき

PlayFabの入門情報などが増えてきて簡単に使えることまでは分かったが、
では実際に組み込むには具体的にどう書いたら良いの?
というところの情報が意外と少なかったので実際に使う事を想定してコード書いてみました。

PlayFab環境の導入までは済んでいる前提です。
公式

UnityでPlayFabを使い始める方法(インストール〜匿名ログイン)
この辺りがわかりやすいかと思います。

下記コンポーネントを適当なGameObjectにアタッチして使う想定です。

まずはコード全貌

PlayFabManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using PlayFab;
using PlayFab.ClientModels;

public class PlayFabManager : MonoBehaviour {

    /// <summary>
    /// PlayFabに保存するユーザーデータのキー
    /// </summary>
    private string[] PLAYFAB_KEY_STR = new string[] {
        "PlayFabId",
        "Name",
        "RankingValue"
    };
    /// <summary>
    /// PLAYFAB_KEY_STRにアクセスするためのenum
    /// </summary>
    private enum KEY_STR
    {
        ID,
        NAME,
        RANKING_VALUE,
    }
    /// <summary>
    /// クライアント側でユーザーデータ(取得データ)を管理しやすくするためのclass
    /// </summary>
    public class PlayFabUserData {
        public bool getted = false;

        public string id;
        public string name;
        public int rankingValue;
    }

    private int m_retryCnt = 0;
    private int MAX_CNT_RETRY = 10;


    static public PlayFabUserData s_myUserData { private set; get; } = null;

    static public void SetUserData(string _id = null, string _name = null, int _rankingValue = -1)
    {
        if (null == s_myUserData)
        {
            s_myUserData = new PlayFabUserData();
        }

        if (null != _id)
        {
            s_myUserData.id = _id;
        }
        if (null != _name)
        {
            s_myUserData.name = _name;
        }
        if (-1 != _rankingValue)
        {
            s_myUserData.rankingValue = _rankingValue;
        }
    }

    void Start()
    {
        StartCoroutine(LoginCol());
    }


    public IEnumerator LoginCol(bool _forceCreateID = false)
    {
        // タッチできないようにガード用フェードなど入れる
        GameCommonManager.GetInstance().m_loadingMarkManager.ActiveBlueLoadingMark();

        // セーブになくて新しく作ったIDか
        bool isNewID = false;       
        string loadPlayFabID = SaveLoad.Load<string>(SaveLoad.SAVE_KEY.PLAYER_ID_FOR_PLAYFAB);

        if (null == s_myUserData)
        {
            s_myUserData = new PlayFabUserData();
        }

        // コールバック受け取り待ちフラグ
        s_myUserData.getted = false;

        // 既にIDを持っている
        if (!_forceCreateID && false == string.IsNullOrEmpty(loadPlayFabID))
        {
            s_myUserData.id = loadPlayFabID;
        }
        // 今回がはじめて
        else
        {
            s_myUserData.id = GameUtility.GenerateCustomID();
            isNewID = true;
        }

        Debug.Log("ログイン挑戦 id = " + s_myUserData.id);

        PlayFabClientAPI.LoginWithCustomID(new LoginWithCustomIDRequest
        {
            CustomId = s_myUserData.id,
            TitleId = PlayFabSettings.TitleId,
            CreateAccount = true
        }
        , result =>
        {
            Debug.Log("ログイン成功 id=" + s_myUserData.id);
            s_myUserData.getted = true;

            // セーブになかったのに、サーバーからのレスポンスでは新規アカウントではないと言われる
            // = すでに使われているIDと判断
            if (isNewID && false == result.NewlyCreated)
            {
                Debug.Log("成功したが既に使われているIDだったのでもう一度");
                if (MAX_CNT_RETRY < m_retryCnt++)
                {
                    Debug.Log("ログインリトライMAXオーバーで諦め");
                    return;
                }

                // 既に使われているIDだったのでもう一度
                StartCoroutine(LoginCol(true));
                return;
            }

            if (isNewID)
            {
                // ログイン確定したのでセーブに保存
                SaveLoad.Save<string>(SaveLoad.SAVE_KEY.PLAYER_ID_FOR_PLAYFAB, s_myUserData.id);
            }

        }
        , error =>
        {
            Debug.Log(error.GenerateErrorReport());
            s_myUserData.getted = true;
        });

        // コールバック受け取るまで止めておく
        while (false == s_myUserData.getted)
        {
            yield return null;
        }

        // タッチ許可
        GameCommonManager.GetInstance().m_loadingMarkManager.DeactiveBlueLoadingMark();

        yield break;
    }


    /// <summary>
    /// ユーザーデータ更新
    /// </summary>
    [ContextMenu("UpdateUserData")]
    void UpdateUserData()
    {
        var requestPrivate = new UpdateUserDataRequest
        {
            Data = new Dictionary<string, string>()
            {
                {PLAYFAB_KEY_STR[(int)KEY_STR.ID], s_myUserData.id},
                {PLAYFAB_KEY_STR[(int)KEY_STR.NAME], "hoge" },//s_myUserData.name},
            },
            //アクセス許可設定 Key単位で変えたい場合は別のリクエスト作らないとだめぽい
            Permission = UserDataPermission.Private
        };

        PlayFabClientAPI.UpdateUserData(
            requestPrivate
            , result =>
            {
                Debug.Log("Private属性のプレイヤーデータの更新成功");
            }
            , error =>
            {
                Debug.Log(error.GenerateErrorReport());
            }
        );
    }


    /// <summary>
    /// 自分のユーザーデータ取得(id以外)
    /// </summary>
    public void GetMyUserData()
    {
        s_myUserData.getted = false;

        PlayFabClientAPI.GetUserData(
            new GetUserDataRequest()
            {
                PlayFabId = s_myUserData.id
            }
            , result =>
            {
                s_myUserData.name = result.Data[PLAYFAB_KEY_STR[(int)KEY_STR.NAME]].Value;
                s_myUserData.rankingValue = int.Parse(result.Data[PLAYFAB_KEY_STR[(int)KEY_STR.RANKING_VALUE]].Value);

                s_myUserData.getted = true;
            }
            , error =>
            {
                Debug.Log(error.GenerateErrorReport());
                s_myUserData.getted = true;
            }
        );
    }


    /// <summary>
    /// タイトルデータ(マスターデータ)の取得
    /// </summary>
    [ContextMenu("GetTitleData")]
    void GetTitleData()
    {
        PlayFabClientAPI.GetTitleData(new GetTitleDataRequest()
            , result =>
            {
                string key = "TitleDataAAA";
                if (result.Data.ContainsKey(key))
                {
                    Debug.Log(key + ": " + result.Data["TitleDataAAA"]);
                }
                else
                {
                    Debug.Log("マスターデータ無かった key=" + key);
                }
            }
            , error =>
            {
                Debug.Log(error.GenerateErrorReport());
            }
         );
    }


    [ContextMenu("Test_GetMyUserData")]
    private void Test_GetMyUserData()
    {
        StartCoroutine(Test_GetMyUserDataCol());
    }
    private IEnumerator Test_GetMyUserDataCol()
    {
        GetMyUserData();

        while (false == s_myUserData.getted)
        {
            yield return null;
        }

        Debug.Log("GetMyUserData done: \n name =" + s_myUserData.name);
        Debug.Log("id =" + s_myUserData.id);

        yield break;
    }
}


ログイン

まずはログインするためにユニークなIDをクライアント側で作る必要があります。
実はここが一番勘違いしていたところで、てっきり通信レスポンスでPlayerIDみたいなのを受け取ってそれを使うのかと勘違いしていました。

        PlayFabClientAPI.LoginWithCustomID(new LoginWithCustomIDRequest
        {
            CustomId = m_myUserData.id,
            TitleId = PlayFabSettings.TitleId,
            CreateAccount = true
        }

ここのCustomIdに入れた文字列がIDとなるのですが、初学書だとなぜか固定になっていたり、違う値を入れたら違う人としてログインできるという情報止まりになっているパターンが多かったです。
どうすれば良いかというと、ここで各個人でユニークなIDを指定してやれば良い訳ですが、スマホの端末IDなどは変わる可能性があるという事で、kanさんのブログを拝見してその場で生成する方法を取りました。

GameUtility.cs
    /// <summary>
    /// ユニークIDの生成
    /// </summary>

    //IDに使用する文字
    private static readonly string ID_CHARACTERS = "0123456789abcdefghijklmnopqrstuvwxyz";

    //IDを生成する
    static public string GenerateCustomID()
    {
        int idLength = 32;
        StringBuilder stringBuilder = new StringBuilder(idLength);
        var random = new System.Random();

        //ランダムにIDを生成
        for (int i = 0; i < idLength; i++)
        {
            stringBuilder.Append(ID_CHARACTERS[random.Next(ID_CHARACTERS.Length)]);
        }

        return stringBuilder.ToString();
    }

ほぼkanさんのブログにあった通りです。
予め使う文字列を決めておいて、その中から1文字ずつランダムで選択していき、32文字のランダムなIDを生成しています。
ユニークなIDを生成する方法としては、このような方法の他にも、現在時刻を取得してそれを利用する方法などがあるそうですが、推測されやすくなりそうなので今回はやめました。
余談ですが、こういうところでstringBuilderを使わずに+合成してしまうと激重処理になるのでやめましょう

リトライ

そしてこの生成したIDを元にログインをしますが、万が一すでに使われているIDだったら、IDを再度作り直してリトライするという流れにしています。

public IEnumerator LoginCol(bool _forceCreateID = false)
    {
        // ~中略~

        , result =>
        {
            Debug.Log("ログイン成功 id=" + m_myUserData.id);

            // セーブになかったのに、サーバーからのレスポンスでは新規アカウントではないと言われる
            // = すでに使われているIDと判断
            if (isNewID && false == result.NewlyCreated)
            {
                Debug.Log("成功したが既に使われているIDだったのでもう一度");
                if (MAX_CNT_RETRY < m_retryCnt++)
                {
                    Debug.Log("ログインリトライMAXオーバーで諦め");
                    return;
                }

                // 既に使われているIDだったのでもう一度
                StartCoroutine(LoginCol(true));
                return;
            }

まず前提として、IDが正しく作られたらそのIDはローカルにセーブしておく想定です。
(今回端末へのセーブは自前のSaveLoadクラスを使っています各自自分のに置き換えてください。ただのstring保存です)
つまり、ローカルのセーブ情報としてID情報がなければ新規ログイン(ID生成が必要)ということになります。
それに加えてPlayFabのLoginWithCustomIDRequestのresultに、NewlyCreatedというフィールドがあり、これがtrueだと、今回の処理で新しくIDが作られたということになります。
この二つの情報を組み合わせて、今回用意したIDが他の人が使用中のものでないかどうか判断しています。

[使用中IDパターン]

ローカルのセーブ情報無し = 新規で作るぞ!
PlayFabのResult = 新規じゃなかったよ?

他の人が使用中…

[うまくいったパターン]

ローカルのセーブ情報無し = 新規で作るぞ!
PlayFabのResult = 新規だったよ!

私のアカウントとして使える!

という感じです。この辺りは手探りでやったので認識違いあるかもしれません。

ユーザーデータ更新

続いてユーザーデータ(プレイヤーデータ)の更新です。

こちらはほぼ悩むところはなく、更新するキーとバリューをつめて、APIコールするだけです。

/// <summary>
    /// ユーザーデータ更新
    /// </summary>
    [ContextMenu("UpdateUserData")]
    void UpdateUserData()
    {
        var requestPrivate = new UpdateUserDataRequest
        {
            Data = new Dictionary<string, string>()
            {
                {PLAYFAB_KEY_STR[(int)KEY_STR.ID], m_myUserData.id},
                {PLAYFAB_KEY_STR[(int)KEY_STR.NAME], m_myUserData.name},
            },
            //アクセス許可設定 Key単位で変えたい場合は別のリクエスト作らないとだめぽい
            Permission = UserDataPermission.Private
        };

        PlayFabClientAPI.UpdateUserData(
            requestPrivate
            , result =>
            {
                Debug.Log("Private属性のプレイヤーデータの更新成功");
            }
            , error =>
            {
                Debug.Log(error.GenerateErrorReport());
            }
        );
    }

リクエストを作る時に、Permission = UserDataPermission.Privateの所でその値を他の人が参照できるようにするかどうか決められます。
心情としてはDataのDictionary作っているあたりで一つ一つに対してその場で属性決められたら良いと思いましたが、できなさそう?
requestを分けて2つ書いて送れば問題なくは行けましたがちょっと面倒と感じました。

ログインに関してはこのコンポーネントを付けたGameObjectのStart()時に行っていますが、このユーザーデータ更新に関しては、使用イメージとしては

PlayFabManager.SetUserData(null, "hoge", 500);

としてstaticなs_myUserDataに値をセットしたのち

UpdateUserData();

で反映させています。

またまた余談ですが、[ContextMenu("Test_GetMyUserData")]という様にContextMenu属性をメソッドに付けておくと、UnityEditor実行中に、そのコンポーネントが付いているオブジェクトのインスペクタの…からメソッドをテスト実行できるので、確認に便利です。

ユーザーデータ取得

取得メソッドについては特にそのままなのですが、取得完了したかのフラグをPlayFabUserDataのメンバに持たせています。

    [ContextMenu("Test_GetMyUserData")]
    private void Test_GetMyUserData()
    {
        StartCoroutine(Test_GetMyUserDataCol());
    }
    private IEnumerator Test_GetMyUserDataCol()
    {
        GetMyUserData();

        while (false == s_myUserData.getted)
        {
            yield return null;
        }

        Debug.Log("GetMyUserData done: \n name =" + s_myUserData.name);
        Debug.Log("id =" + s_myUserData.id);

        yield break;
    }

こうすることで、例えば取得処理前にタッチガードを入れて、取得完了したらガードを解くという処理をシーケンシャルにかけるようになります。
PlayFabのコールバックに埋め込んでも良いのですが、そのタイミングでしたい処理が増えてくるとコードが汚くなりがちなので、私は好んでこの形で書くことが多いです。

ログインの方も同じような理由でコルーチンにして、タッチガード入れています。

    private IEnumerator LoginCol(bool _forceCreateID = false)
    {
        // タッチできないようにガード用フェードなど入れる
        GameCommonManager.GetInstance().m_loadingMarkManager.ActiveBlueLoadingMark();

        //〜中略~

        // タッチ許可
        GameCommonManager.GetInstance().m_loadingMarkManager.DeactiveBlueLoadingMark();

        yield break;
    }

話が逸れてしまいますが、ActiveBlueLoadingMark()は、activeにするとクルクルアイコンが回る大きなImageがアクティブになるだけです。(タッチを下に通さない)
1枚作っておくとタッチ制御したい時に呼び出すだけでできるので便利です。

まとめ

■匿名ログインではログインに必要なユニークなIDをクライアント側で作る必要がある
■ユーザーデータの更新はキーとバリューセットしてAPIコールするだけ
■PlayFabのコールバックとコルーチンを組み合わせて処理をシーケンシャルに書きやすくもできる

参考

kanのメモ帳
PlayFabマスターへの道

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityでスクリプトファイルがVS Codeで開かない

問題

Unityのエディタ画面でProjectにあるスクリプトファイルをクリックしてもvs codeで任意のファイル・フォルダが開かなかったり、開けてもVS Codeの拡張が働かない・補完が効かないことがありました。

解決

自分で調べても解決できなかったのでUnityお学びグループで質問したところ、@edo_m18 さんに回答いただけました。

Package ManagerからVisual Studio Code Editorの最新にupdateすると解決すると思います。(古いバージョンが悪さをしていたようです)

https://connect.unity.com/post/5d79a5cfedbc2a126f15ecf2

バージョンごとの解決法

Unity 2017-2019
→Package ManagerからVisual Studio Code Editorを最新版にupdateすると解決できた

Unity 5.x
→アセットストアからVSCodeをインポートして設定を変更すると解決できた

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity2020.1からuGUIのタッチ判定範囲を拡張できるようになっている

Unity2020.1からuGUIのタッチ判定を拡張できるプロパティRaycastPaddingが追加されています。

  • 当たり判定サイズを調整するために画像サイズを変更する
  • 当たり判定サイズ調整用のGameObjectの追加

といった呪縛から解放されます。

extension.gif

上記の動画のようにボタンの画像外にタッチ判定範囲を拡張する事ができています。
(逆に縮小も可)

タッチ判定を広げる場合はRaycastPaddingに負の値を、狭める場合は正の値を代入します。

_graphic.raycastPadding = new Vector4(-200f, -200f, -200f, -200f);

ソースコード的にはVector4型で代入するだけです。
UI開発する上で、ボタンの当たり判定サイズを調整するっていうことはよくあるので、その手段が増えたことは、個人的には良いアプデだと思います。

ただColliderと違ってRaycastPaddingはシーンビューでも可視化されないため、設定が分かりづらいという点はアプデに期待です。

Unity1Weekで実務では使用しない最新のUnityバージョンで作業してて気づいた機能でした。普段の業務から離れた開発の重要性を感じた一幕でした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity 3D入門 #13 [本を調べる処理]

本を調べると、メッセージが表示されるようにします。
1. 調べることができる本は他のオブジェクトに比べて光るなど目立たせたい。
2. 本毎に個別のテキストを表示してほしい。

●調べることができる本は他のオブジェクトに比べて光るなど目立たせたい。
MaterialのEmissionによる自己発光により、オブジェクトを目立たせました。
参考URL:https://linemarker.hatenablog.com/entry/2019/01/01/234106
暗い世界だからできる手法ですが、問題はクリアしました。

スクリーンショット 2020-08-12 2.26.32(2).png

●本毎に個別のテキストを表示してほしい。

public string booktext;

上のようにpublic変数として本の内容を用意することで本毎にテキストを使えるようになりました。
また、publicでのテキスト入力について、改行が難しいため、下のURLを参照してtextareaを作成しました。
参考URL:https://zenigane138.hateblo.jp/entry/2018/07/31/225637

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む