20200627のUnityに関する記事は13件です。

[Unity]エラー: Disconnecting is no longer implemented の対処

対処法

Hierarchyの中にあるプレハブで青く表示されているものを右クリック。
「Unpack Prefab」をクリックする。

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

【爆速メモ】tinkercadからエクスポートして、blenderやunityへインポートする際のサイズ感について

あるある

tinkercadからエクスポートしたobjファイルを、blenderやunityでインポートしたら超絶サイズがでかい


なので

tinkercadで1cmくらいのオブジェクトをエクスポートすれば、blenderやunityにとってちょうどよいサイズ感となる。

  • blenderだと頂点数が2万切るので、その気になればclusterのアバターに使えそう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Oculus Platform APIをコールバックからTaskへ変換する

はじめに

Oculus Storeからアプリを配布する時はOculus Platform APIが必須になっています。
そのOculus Platform APIは結果をコールバックで返すので処理が冗長になってしまいますが簡単にTask化できたので紹介します。

開発環境

  • System.Threading.Tasksを扱える設定およびバージョンのUnity
    • Unity2017 + Experimental (.NET 4.6 Equivalent)
    • Unity2018 + .NET 4.x Equivalent
    • Unity2019以上
  • Oculus Integration v1.41.0以上

コールバックの場合

以下はエンタイトルメントチェックのベストプラクティスのサンプルコードです。Oculus Platform APIはIsUserEntitledToApplication()のようにOnComplete()で結果が返ってくるので同様の呼び出しが続くとコールバック地獄になって処理の流れが分かりにくくなります。

using UnityEngine;
using Oculus.Platform;

public class AppEntitlementCheck: MonoBehaviour {

  void Awake ()
  {
    try
    {
      Core.AsyncInitialize();
      Entitlements.IsUserEntitledToApplication().OnComplete(EntitlementCallback); // ここがコールバック
    }
    catch(UnityException e)
    {
      Debug.LogError("Platform failed to initialize due to exception.");
      Debug.LogException(e);
      // Immediately quit the application.
      UnityEngine.Application.Quit();
    }
  }

  void EntitlementCallback (Message msg)
  {
    if (msg.IsError)
    {
      Debug.LogError("You are NOT entitled to use this app.");
      UnityEngine.Application.Quit();
    }
    else
    {
      Debug.Log("You are entitled to use this app.");
    }
  }
}

Taskへ変換する

スクリプティング定義シンボルにOVR_PLATFORM_ASYNC_MESSAGESを追加します。
image.png

するとTaskを返すGen()メソッドがOculus.Platform.Requestクラスで使えるようになるので以下のように書く事ができます。

private async void Awake()
{
    try
    {
        if (!Core.Initialized())
        {
            var initialized = await Core.AsyncInitialize().Gen(); // ここをTask化
            if (initialized.IsError)
            {
                Debug.Log($"failed initialize: {initialized.GetError().Message}");
                return;
            }
        }

        var entitlements = await Entitlements.IsUserEntitledToApplication().Gen(); // ここをTask化
        if (entitlements.IsError)
        {
            Debug.Log($"failed entitlement: {entitlements.GetError().Message}");
            return;
        }

        var user = await Users.GetLoggedInUser().Gen(); // ここをTask化
        if (user.IsError)
        {
            Debug.Log($"failed get user: {user.GetError().Message}");
            return;
        }
        Debug.Log($"{user.Data.ID}, {user.Data.OculusID}");
    }
    catch (UnityException e)
    {
        Debug.LogException(e);
        Application.Quit();
    }
}

更にUniTaskと組み合わせるとタイムアウトも簡単に扱えます。

var initialized = await Core.AsyncInitialize().Gen().AsUniTask().Timeout(TimeSpan.FromSeconds(10));

1つ残念なのはGen()が返すTaskTaskCompletionSourceで作られてprivateフィールドにあるのですがTrySetCanceled()を呼ぶメソッドが用意されていないのでCancellationTokenでキャンセルできない事です。どうしてもキャンセルしたい場合はSDKに多少の変更が必要です。

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

[Azure DevOps] Unity の Android 版自動ビルド環境を構築する (Self-hosted 環境)

はじめに

[Azure DevOps] クラウドで Unity 自動ビルド環境の構築 では Win64 でビルドを行ったのですが、Android 版の場合は署名が必要になります。

今回は Android のビルド&コード署名までを行いたいと思います。

ゴール

  • Unity の Self-hosted 環境で Android ビルドと APK の署名

事前準備

以下の記事で事前準備をしておいてください。

ソースコード

まずはソースコードです。

trigger:
- master

variables:
  Unity.TargetBuild: 'Android'
  Unity.ProjectPath: '$(Build.SourcesDirectory)'

  Keystore.FileName: '<Keystore FileName>'
  Keystore.Alias: '<Alias Name>'

  Output.FileName: 'drop'

pool:
  name: 'Unity Agent for Mac'

jobs:
  - job: Unity
    displayName: 'Unity Android build started.'
    condition: or(not(variables['Unity.UserName']), not(variables['Unity.Password']), not(variables['Unity.SerialKey']))
    workspace:
      clean: outputs

    steps:
    - task: UnityGetProjectVersionTask@1
      name: UnityGetProjectVersion
      displayName: 'Getting a version of Unity'
      inputs:
        unityProjectPath: '$(Unity.ProjectPath)'

    - task: UnityBuildTask@3
      displayName: 'Unity Building.'
      condition: succeeded()
      inputs:
        buildTarget: '$(Unity.TargetBuild)'
        unityProjectPath: '$(Unity.ProjectPath)'
        outputPath: '$(Build.BinariesDirectory)'
        outputFileName: '$(Output.FileName)'

    - task: AndroidSigning@3
      displayName: 'Signing the APK File.'
      inputs:
        apkFiles: '$(Build.BinariesDirectory)/*.apk'
        apksignerKeystoreFile: '$(Keystore.FileName)'
        apksignerKeystorePassword: '$(Keystore.Password)'
        apksignerKeystoreAlias: '$(Keystore.Alias)'
        apksignerKeyPassword: '$(Keystore.Password)'
        zipalign: true

    - task: CopyFiles@2
      displayName: 'Copying builds...'
      condition: succeeded()
      inputs:
        SourceFolder: '$(Build.BinariesDirectory)'
        Contents: '*.apk'
        TargetFolder: '$(Build.ArtifactStagingDirectory)'
        CleanTargetFolder: true
        OverWrite: true

    - task: PublishBuildArtifacts@1
      displayName: 'Publish builds...'
      condition: succeeded()
      inputs:
        PathtoPublish: '$(Build.ArtifactStagingDirectory)'
        ArtifactName: 'Release'
        publishLocation: 'Container'

APK ファイルの署名

Android の署名に必要な

  • Keystore ファイル
  • Keystore パスワード
  • Keystore エイリアス

を YAML で利用します。

variables:
  Keystore.FileName: '<Keystore FileName>'
  KeyStore.Alias: '<Alias Name>'
#  KeyStore.Password: 事前に Variables に設定しておく

- task: AndroidSigning@3
  displayName: 'Signing the APK File.'
  inputs:
    apkFiles: '$(Build.BinariesDirectory)/*.apk'
    apksignerKeystoreFile: '$(Keystore.FileName)'
    apksignerKeystorePassword: '$(Keystore.Password)'
    apksignerKeystoreAlias: '$(Keystore.Alias)'
    apksignerKeyPassword: '$(Keystore.Password)'
    zipalign: true

ソースコードをダラダラと書きましたが、[Azure DevOps] クラウドで Unity 自動ビルド環境の構築 との違いは署名の部分だけです。
[Azure DevOps] 証明書などの保存方法 で保存されているセキュアファイルライブラリから Keystore ファイルを参照するだけで、署名付きの APK ファイルが作成されます。

※最近では Gradle を利用したビルド環境を作ることも多いですが、今回は Gradle は利用せずに Unity で生成された APK をそのまま利用しています。

おわりに

署名自体はそんなに難しくないです。
Pipelines のセキュアファイルライブラリが便利ですので、それが出来れば Android ビルドも怖くないです。

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

[Unity初心者Tips]FindとGetComponentを実行しエラーにどこから呼び出されたかを出す便利メソッド

なにをやるのか

Unityで、FindしたやつからGetComponentするという処理は、プログラムの設計によってシングルトンでやってないやつだとよくやります。それの便利版です。説明要らない方は最後へジャンプ。

GameObject gameObject = GameObject.Find("HOGEgo");
HogeComponent hogeComponent = gameObject.GetComponent<HogeComponent>();

親切にエラーを出してあげる

この時、それぞれでnullの可能性があるので、その場合にはエラーを吐いた方がうるさいゲームデザイナーの相手をしなくていい親切に何がおかしいか判った方が効率が好いので、以下のコードになります。

HogeComponent hogeComponent;
GameObject gameObject = GameObject.Find("HOGEgo");
if(gameObject == null){
  Debug.LogError("HOGEgo is not found");
}
else{
  hogeComponent = gameObject.GetComponent<HogeComponent>();
  if(gameObject == null){
    Debug.LogError("HogeComponent is not found");
  }
}

共用できるclassのメソッドにする

折角なので、適当なclassで何処でも使えるようにジェネリック化したstaticのメソッドにしましょう。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace CommonTools
{
    public class Tools : MonoBehaviour
    {
        public static T GetComponentInObject<T>(string objectName)
        {
            T c = default(T);
            GameObject gameObject = GameObject.Find(objectName);
            if (gameObject == null) {
                Debug.LogError(objectName + " is not found");
            }
            else {
                c = gameObject.GetComponent<T>();
                if (hogeComponent == null) {
                    Debug.LogError( nameof(T)+ " is not found");
                }
            }
            return c;
        }
    }
}

まあまあ良い感じですね!ここまではありがちです。

どこでやったか判るようにする

上のままでも問題ないんですけど、色々なところから呼び出されるのでエラーを見ても呼び出し元が判らないと修正に困るという問題があります。
うるさいゲームデザイナーの相手をしなくていい親切に何がおかしいか判った方が効率が好いので、ここで呼び出し元が判るようにします。それには以下の仕組みを使います。

呼び出し元はstackから辿れる

アセンブラの知識がある方はご存知の通り、メソッドの呼び出し毎に戻りアドレスがpushされてstackに格納されて積まれます。そこを辿れば、どこから呼び出されているのか判るわけです。
参考:StackFrame クラス
https://docs.microsoft.com/ja-jp/dotnet/api/system.diagnostics.stackframe?view=netcore-3.1

リフレクションで名前を取得する

C#のリフレクションを利用すると、呼び出し元の名前が取得できます。
参考:MemberInfo.ReflectedType プロパティ
https://docs.microsoft.com/ja-jp/dotnet/api/system.reflection.memberinfo.reflectedtype?view=netcore-3.1

エラーで呼び出し元を出す完成版

上記を組み合わせてエラーのためのメソッドをFindGetComponentで共有化した完成例がこちらです。
https://gist.github.com/JunShimura/866cfe8736e4b40f35bf50113748cdd0

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace CommonTools
{
    public class Tools : MonoBehaviour
    {
        public static T GetComponentInObject<T>(string objectName)
        {
            T c = default(T);
            GameObject go = GameObject.Find(objectName);
            if (go == null) {
                LogError(objectName);
            }
            else {
                c = go.GetComponent<T>();
                if (c == null) {
                    LogError(objectName + "." + nameof(T));
                }
            }
            return c;
        }
        static private void LogError(string s)
        {
            // StackFrameクラスでstackを2階層戻る
            System.Diagnostics.StackFrame objStackFrame = new System.Diagnostics.StackFrame(2);
            string methodName = objStackFrame.GetMethod().Name;
            Debug.LogError(s + " is not found at "
                + objStackFrame.GetMethod().ReflectedType.FullName
                + "." + objStackFrame.GetMethod().Name);
        }

    }

}

こうすると、

class GameDirector{
    void Start()
    {
        // hpゲージを取得
        hpGaugeImage = Tools.GetComponentInObject<Image>("hogeGauge");

このようなエラーになります。
image.png

うるさいゲームデザイナーの相手をしなくていい親切に何がおかしいか判った方が効率が好いですね!

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

URPで無理やり Custom Post Processing する

URPとCustom Post Processing

URP (Universal Render Pipeline)における公式実装によるCustom Post Processingは現時点(2020/06/27)で非対応です。しかし公式ブログには今後対応予定と記載されているので、この記事を読む前に最新の情報をチェックしてくださいね!

非対応だってよ、どうするよ

とりあえずURPのソースを見てみました。PostProcessPass.csにビルトインのポストエフェクトのパスが定義されています。
PostProcessPass.cs
はい、どう見ても決め打ちでカスタムエフェクトを挟む隙などありません。PostProcessPassのことはいったん忘れます。

今回の主役はRendererFeatureという機能になります。これはURPのForward Rendererのセットアップ中に任意の処理を挟み込める機能で、これを使えば任意のパスを設定することが可能です。今回はこいつを使って独自にポストプロセスのパスを挟み込むという算段です。

なお今回のコードはUnity公式のURPサンプルをベースにしていますのでそちらも参考にどうぞ。

RendererFeature

RendererFeatureを使って任意のパスを作成するには、主にふたつのクラスが必要です。ひとつはScriptableRendererFeatureを継承したクラス、もうひとつはScriptableRenderPassを継承したクラスです。前者はパスのセットアップと挟み込みを担当し、後者は実際のパスの動作を定義します。

とりあえず今回は画面をグレイスケール化するポストエフェクトを挟んでみます。

CustomPostProcess.cs
using UnityEngine;
using UnityEngine.Rendering.Universal;

public class CustomPostProcess : ScriptableRendererFeature
{
    [System.Serializable]
    public class CustomPostProcessSettings
    {
        //パスの実行タイミング
        public RenderPassEvent Event = RenderPassEvent.BeforeRenderingPostProcessing;
        //使用するシェーダー
        public Shader GrayScaleShader;
    }

    public CustomPostProcessSettings settings = new CustomPostProcessSettings();

    private CustomPostProcessPass pass;

    // ScriptableRendererFeatureはScriptableObjectとしてRendererData内部に格納される。
    // ScriptableObjectのシリアライズのタイミングで呼ばれる。
    public override void Create()
    {
        this.name = "Custom PostProcess";
        pass = new CustomPostProcessPass(settings.Event, settings.GrayScaleShader);
    }

    //パスの差し込み。URPのSetupで呼ばれる。
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        //入出力を指定してパスを初期化
        pass.Setup(renderer.cameraColorTarget, RenderTargetHandle.CameraTarget);
        //パスの差し込み
        renderer.EnqueuePass(pass);
    }
}
CustomPostProcessPass.cs
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class CustomPostProcessPass : ScriptableRenderPass
{
    //CommandBufferの取得に使用する名前
    const string k_RenderCustomPostProcessingTag =
        "Render Custom PostProcessing Effects";

    //入出力
    private RenderTargetIdentifier passSource;
    private RenderTargetHandle passDestination;

    //Blitに使用するマテリアル
    private Material grayScaleMaterial;

    //一時的なレンダーターゲット(パスの入出力が同一の場合、いちど中間バッファを挟んでBlitする必要があるため)
    RenderTargetHandle m_TemporaryColorTexture;

    public CustomPostProcessPass(RenderPassEvent renderPassEvent, Shader grayScaleShader)
    {
        //パスの実行タイミングを指定
        this.renderPassEvent = renderPassEvent;
        if (grayScaleShader)
            grayScaleMaterial = new Material(grayScaleShader);

        //一時バッファの設定
        m_TemporaryColorTexture.Init("_TemporaryColorTexture");
    }

    public void Setup(RenderTargetIdentifier source, RenderTargetHandle destination)
    {
        this.passSource = source;
        this.passDestination = destination;
    }

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        //レンダリング情報(画面サイズなど)。一時バッファの作成に使用。
        RenderTextureDescriptor opaqueDesc = renderingData.cameraData.cameraTargetDescriptor;
        opaqueDesc.depthBufferBits = 0;

        var cmd = CommandBufferPool.Get(k_RenderCustomPostProcessingTag);

        Render(cmd, ref renderingData, opaqueDesc);

        context.ExecuteCommandBuffer(cmd);
        CommandBufferPool.Release(cmd);
    }

    void Render(CommandBuffer cmd, ref RenderingData renderingData, RenderTextureDescriptor opaqueDesc)
    {
        cmd.GetTemporaryRT(m_TemporaryColorTexture.id, opaqueDesc, FilterMode.Bilinear);

        //エフェクトの適用
        DoEffectGrayScale(cmd, passSource, m_TemporaryColorTexture, opaqueDesc);

        //出力先へコピー
        if (passDestination == RenderTargetHandle.CameraTarget)
        {
            Blit(cmd, m_TemporaryColorTexture.Identifier(), passSource);
        }
        else
        {
            Blit(cmd, m_TemporaryColorTexture.Identifier(), passDestination.Identifier());
        }
    }

    private void DoEffectGrayScale(CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetHandle destination,
        RenderTextureDescriptor opaqueDesc)
    {
        Blit(cmd, source, destination.Identifier(), grayScaleMaterial, 0);
    }

    public override void FrameCleanup(CommandBuffer cmd)
    {
        if (passDestination == RenderTargetHandle.CameraTarget)
            cmd.ReleaseTemporaryRT(m_TemporaryColorTexture.id);
    }
}

これでRendererFeatureが定義できました。

RendererFeatureの登録

次に、このRendererFeatureをURPに登録します。
URP Assetに設定したRenderer AssetのInspectorを開きます。
image.png
下部にAdd Renderer Featureというボタンがあるので押します。出現したメニューに先ほど作成したCustom Post Processの項目があるので、選択して追加します。
image.png
これでRendererFeatureが設定できました。

シェーダーの作成

あとは実際に画面をグレイスケール化するシェーダーを書きましょう。
URP (というかSRP)ではシェーダーはHLSLで書きます。ShaderLab (Cg/HLSL)と基本的な文法は同じですが、いろいろとお作法に相違点があるので注意が必要です。
URPに含まれているビルトインのポストエフェクトシェーダーを見ながら書くと手っ取り早いのでオススメ。

GrayScale.shader
Shader "Hidden/Universal Render Pipeline/Custom/GrayScale"
{
    Properties
    {
        _MainTex("Source", 2D) = "white" {}
    }

    HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        #include "Packages/com.unity.render-pipelines.universal/Shaders/PostProcessing/Common.hlsl"

        TEXTURE2D_X(_MainTex);

        half4 Frag(Varyings input) : SV_Target
        {
            float4 source = SAMPLE_TEXTURE2D_X(_MainTex, sampler_LinearClamp, input.uv);
            float y = 0.299 * source.r + 0.587 * source.g + 0.144 * source.b;
            return half4(y, y, y, 1.0);
        }

    ENDHLSL

    SubShader
    {
        Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline"}
        LOD 100
        ZTest Always ZWrite Off Cull Off

        Pass
        {
            Name "GrayScale"

            HLSLPROGRAM
                #pragma vertex Vert
                #pragma fragment Frag
            ENDHLSL
        }
    }
}

あとはこのシェーダーをRendererFeatureにセットして……

image.png

image.png

見慣れた画面がグレイスケールになりました。

おしまい

今回はサンプルとしてグレイスケール化をやってみましたが、スクリプト側でやってることはシェーダーによるBlitだけなので、適当に拡張できると思います。
あとは普通のポストプロセスと同様にVolumeからエフェクトの設定ができれば最高なのですが、そのへんはめんどくさいので公式対応に任せておくとして、こんなところで終わりにしたいと思います。

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

CameraのforceIntoRenderTextureをtrueにした際のFrameDebuggerの変化(と少しPostProcessLayerの話)

前回の記事
CameraのDepthTextureModeをDepth有効にした際のFrameDebuggerの変化
の関連で、次は同じ条件で forceIntoRenderTexture を true にした時の変化をメモしておきます。
Camera-forceIntoRenderTexture - Unity スクリプトリファレンス

DepthCheck.cs
public class DepthCheck : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        var camera = GetComponent<Camera>();
        camera.forceIntoRenderTexture = true;
    }
}

スクリーンショット 2020-06-27 12.36.21.png

  • Event数は3
  • Statsは
    • Batches 2
    • SetPass 2
  • メイン描画の描画先は Temp Buffer になっている
  • 描画パスの末尾にImageEffectが追加されている
    • 内容としては、DrawDynamicのBlit()で、TempBufferをno nameにコピーしている

この構成どこかで見たなーと思うかもしれませんが、
PostProcessingStackV2で、PostProcessをかけた時と似たような構成になっています。

forceIntoRenderTextureをfalseにして、PostProcessLayerをカメラに付けた時の状態はこちら
スクリーンショット 2020-06-27 13.02.07.png

  • メイン描画部分のRenderTargetはTempBufferになっている
  • BeforeImageEffectsのコマンドが追加されている
    • 最初のDraw Dynamicで TempBuffer を_TargetPool0にコピー
    • 次のBuiltInStack DrawMeshで _TargetPool0 を TempBufferに戻している
  • 末尾にImageEffectsが追加されている
    • 内容としては、DrawDynamicのBlit()で、TempBufferをno nameにコピーしている

ちなみに、PostProcessLayerのDirectly to Camera Targetのフラグを立てると、BuiltInStack DrawMeshで直接no nameにコピーを行うことで、最後のTempBufferをno nameにコピーするステップを削減できます。
スクリーンショット 2020-06-27 13.10.23.png
ただやはり、 それでもメイン部の描画はTempBufferにされるところが注意点かな。


Camera-forceIntoRenderTexture - Unity スクリプトリファレンス に

If set to true camera rendering will always happen into a RenderTexture instead of direct into the backbuffer. This can be useful if you have no image effects but want to use command buffers to act on the current rendering target.
trueに設定すると、カメラのレンダリングは、バックバッファーに直接ではなく、常にRenderTextureで行われます。これは、イメージエフェクトはないが、コマンドバッファーを使用して現在のレンダリングターゲットを操作する場合に役立ちます。

とあるように、PostProcessingStackV2みたいにコマンドバッファでImageEffectっぽいことをしようと思った時は、
forceIntoRenderTextureをtrueにすることで、PostProcessingStackと同様の描画フローを辿れそうですね。


ちなみに、前回の記事
CameraのDepthTextureModeをDepth有効にした際のFrameDebuggerの変化
と一緒に指定すると、こうなります。

DepthCheck.cs
using UnityEngine;

public class DepthCheck : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        var camera = GetComponent<Camera>();
        camera.depthTextureMode = DepthTextureMode.Depth;
        camera.forceIntoRenderTexture = true;
    }
}

スクリーンショット 2020-06-27 12.27.13.png

  • 前段にUpdateDepthTextureが追加される
    • 描画先はCameraDepthTexture
  • メイン描画部はTempBufferに描画される
  • 終段にImageEffects DrawDynamicでTempBufferをno name(フレームバッファ)にコピーしている
  • Event数は5
    • 深度バッファ作成に2
    • メイン描画に2
    • 最終コピーで1
  • Statsは
    • Batches 3
    • SetPass 3

今回も作業メモとして残しておきます。
ここから深度バッファ周りを触っていく予定。

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

【UE4:View】UnityでいうSceneViewどこ?

Unityだと再生中でもSceneViewで自由にカメラ動かしてオブジェクトの確認できるんだけど
UE4しばらく触っててそれをどうやるのかがわからなかった

Toolbarの一番右に「Eject」ってボタンがあって「Eject - Possess」で
UnityでいうGameViewとSceneViewの切り替えができた(同時に表示するのはできない?)
SceneView.png
ショートカット「F8」押下でも切り替えできた

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

unityでプロジェクトをimportすると.wav(音声)ファイルでfreezeする!

かなりハマったのでメモがてらqiitaにて投稿。

久しぶりにunityでゲーム開発することになり、クラウド上に置いてあった数年前のプロジェクトをimportしようとすると「importing assets」のところでフリーズ...

一晩中おいてもまだ終了していないところをみると、完全に止まっている模様。

unityを強制終了しても「importing assets」 windowは開いたままで、最近買ったmacbook pro(2019年モデル)のファンがフル回転しているに、まだ何か処理が走っているみたい。

ということで、やむなくパソコンを再起動し、unityで再度プロジェクトのimportを試してみるも、同じファイル(.wav)でフリーズしている...

ちなみに使っている開発環境はこんな感じ。

MacOS: 10.15.5
unity: 2018.2.0f2

解決策

ネットでいろいろ検索してみたところ下記のページを発見。

Installing Unity on macOS Catalina

32bitのFMOD Toolってやつが macOS 10.15に対応してなくて、unityの2017.4ならOKだけど、2018.1/2018.2はダメ? そんな感じのことが書かれている。

私のversionがまさに2018.2なのでもしかしてこれが原因?ということで、unity hubから2018のLTSの2018.4をインストール。

インストール後、これまたunity hubのなかから自分がimportしたいprojectのバージョンを変更。

Screen Shot 2020-06-27 at 8.06.06.png

最新版に上げてから再度プロジェクトを開いてみると...立ち上がった!!!

もし同じように.wavファイルでフリーズしてprojectがimportできないという場合は、ぜひ試してみてくださいね。

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

C#触ってて便利と思った機能について

はじめに

最近になってUnityで開発することが増え、C#に触ることが多くなったので「使ってこれは便利だな」と感じたものをここに備忘録がてらまとめておきます。(主はC++からC#を触ってるのでC++と比較しての視点で書いてます。)
Unity2019.4.0f1時点でのバージョンはC#7.3が標準だそうです。

プロパティ構文

C++ではことあるごとにGetter/Setterを作らないといけなかったがC#ならこのように記述できる。

test.cs
public class BaseCompornent : MonoBehaviour {
    //座標
    public Vector3 Position
    {
        set { transform.position = value; }
        get { return transform.position; }
    }
    public float PosX
    {
        set
        {
            Vector3 pos = Position;
            pos.x = value;
            Position = pos;
        }
        get { return Position.x; }
    }

参照するときはこのようにメソッドを呼び出すことなくメンバ変数のように扱える。

test.cs
    public void Function()
    {
        PosX += 100;
        var posX = posX;
        PosX++;
    }

Unityでゲーム作ってるとどうしてもクラス間の値の受け渡しが多くなるため、この機能はとても助かる。
ちなみにget/setのどちらかは省略することが可能である。
また自動実装プロパティといって単純に「get; set;」記述することもできる。その際はプロパティ自体がデータを保有することになる。

名前付き引数

良く調べたらこの機能C++ではないそうです。(C#は4.0から実装されたみたい)
簡単に言えば指定した引数だけを渡せれるってやつです。
デフォルト引数と兼ねたコード例です。

test.cs
    public void Function()
    {
        AddPosition();
        AddPosition(15.0f, 25.0f);
        AddPosition(1.0f,20.0f, 30.0f);
        AddPosition(vy: 30.0f);     //指定することが可能
    }

    public void AddPosition(float vx = 0.0f, float vy = 0.0f, float vz = 0.0f)
    {
        PosX += vx;
        PosY += vy;
        PosZ += vz;
    }

何気にこの機能最近知ったので、上手いこと使いこなしていきたいですね。
VBには昔からある機能だそうです。

LINQ

最初、LINQとはなんぞやとなりましたが要はC#独自のSQLみたいなやつです。(正式名称は統合言語クエリと呼ぶみたい)
データベース関係に疎いので上手い使い方は出来ていないかもしれませんが、現在開発中のコードではこんな感じで使っています。

LINQ_Test.cs
    private void RemovePort(BaseNode node, Port port)
    {
        var targetEdge = edges.ToList().Where(x => x.output.portName == port.portName && x.output.node == port.node);

        if (targetEdge.Any())
        {
            var edge = targetEdge.First();
            edge.input.Disconnect(edge);
            RemoveElement(targetEdge.First());
        }

        //省略
    }

やってることとしては「where」の部分でエッジリストから「エッジの出力ポート名と対象のポートのポート名が同じ かつ 対象ノードと出力先のノードが同じ」という条件のエッジを抽出しています。
そして「Any()」の行で抽出したエッジがあれば if 文内の処理をするようにしています。
指定した条件の要素を抽出したいときに結構使う便利だなと感じます。

リフレクション

この機能もC++には現状ありません。(これぞとばかりにC++に恨みがあるんかって感じですけど)
何が出来るかというと、クラスの情報をデータとして扱えます。(メタデータ)Typeクラスを使って実装します。
コード例も開発のものから引っ張てきました。

BaseNode.cs
public class BaseNode : Node
{
    public string Guid;
    public Type type;
    public bool EntryPoint;

    public BaseNode()
    {
        Guid = System.Guid.NewGuid().ToString();
        type = this.GetType();
        title = type.Name.Replace("Node", "");  //クラス名から "Node" を削除したもの
        EntryPoint = false;
    }

まだこれは単純ですが、BaseNodeのメンバにTypeクラスの変数を宣言し、コンストラクタの「GetType()」でBaseNodeクラスをタイプオブジェクトとして格納しています。また文字列としても取得することが出来ます。「type.Name」でクラス名を取得し、"Node"部分を削除してそのままNodeのタイトルとして流用しています。
こうすることでこのBaseNodeクラスを継承したクラスでも機能が働き、子クラスのクラス名を勝手に取得して反映させてくれます。
Typeクラスからインスタンスを生成をすることもできます。詳しくは参考記事をご覧ください。

おわりに

Unityを触ってて便利だなと感じたC#の機能はこんな感じです。(あとの機能はC++にもある機能だったりので省いてます。)
まだ他にも「式木」とかあるんですけど、使いどころがあまりないと感じたので割愛。
もっとこういう使い方あるよとか便利な機能があるよっていうアドバイスがありましたら気軽にコメントしてくれるととても助かります。

参考記事

C#とC++の比較
Unityで使える!C#の便利な機能7選
オプション引数・名前付き引数
C#のプロパティについて調べてみた
はじめてのLINQ
C#リフレクションTIPS 55連発
リフレクションを利用して文字列からクラス操作

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

CameraのDepthTextureModeをDepth有効にした際のFrameDebugの変化

設定し直すのが面倒なので画像付きでメモしておきます。

環境はこちら

  • Unity2019.4.0f1
  • 新規シーン
  • Cameraの設定は下記
    • HDRなし
    • MSAAなし
    • ClearFlag は SolidColor
  • Lightingは下記
    • Shadowなし
  • この状態でキューブを置く

スクリーンショット 2020-06-27 2.01.11.png
で、この状態になるわけですが、
FrameDebugを確認すると
スクリーンショット 2020-06-27 2.01.22.png
こんな感じ。

  • ClearとCube描画でEvent2つ。
  • Statsを見ると
    • Batches は 1
    • SetPass は 1
  • RenderTargetは no name

で、ココでCameraのDepthTextureModeをDepthに設定

DepthCheck.cs
public class DepthCheck : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        var camera = GetComponent<Camera>();
        camera.depthTextureMode = DepthTextureMode.Depth;
    }
}

すると、FrameDebugは下記。
スクリーンショット 2020-06-27 2.02.07.png

  • Eventは4つ
    • まずDepthTextureを更新して、その後先程の描画を行う
  • Statsを見ると
    • Batches は 2
    • SetPass は 2
  • RenderTargetは
    • UpdateDepthBuffer は CameraDepthTexture
    • メイン描画部分は no name

UpdateDepthTextureが描画前に追加されます。


とりあえずのメモ書きということで置いておきます。
我ながらレベルの低い事をやっているけれども、それでもログを取っておけば今後の記事でリンクも貼れて手間が解消できるはず…。

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

CameraのDepthTextureModeをDepth有効にした際のFrameDebuggerの変化

設定し直すのが面倒なので画像付きでメモしておきます。

環境はこちら

  • Unity2019.4.0f1
  • 新規シーン
  • Cameraの設定は下記
    • HDRなし
    • MSAAなし
    • ClearFlag は SolidColor
  • Lightingは下記
    • Shadowなし
  • この状態でキューブを置く

スクリーンショット 2020-06-27 2.01.11.png
で、この状態になるわけですが、
FrameDebugを確認すると
スクリーンショット 2020-06-27 2.01.22.png
こんな感じ。

  • ClearとCube描画でEvent2つ。
  • Statsを見ると
    • Batches は 1
    • SetPass は 1
  • RenderTargetは no name

で、ココでCameraのDepthTextureModeをDepthに設定

DepthCheck.cs
public class DepthCheck : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        var camera = GetComponent<Camera>();
        camera.depthTextureMode = DepthTextureMode.Depth;
    }
}

すると、FrameDebugは下記。
スクリーンショット 2020-06-27 2.02.07.png

  • Eventは4つ
    • まずDepthTextureを更新して、その後先程の描画を行う
  • Statsを見ると
    • Batches は 2
    • SetPass は 2
  • RenderTargetは
    • UpdateDepthBuffer は CameraDepthTexture
    • メイン描画部分は no name

UpdateDepthTextureが描画前に追加されます。


とりあえずのメモ書きということで置いておきます。
我ながらレベルの低い事をやっているけれども、それでもログを取っておけば今後の記事でリンクも貼れて手間が解消できるはず…。

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

【Oculus/SteamVR】バイブレーション機能の周波数の範囲を調べてみた【Unity】

TL;DR

多分この範囲、違ってたら教えてください

  • Rift: 160-320Hz
  • Rift S: 160-500Hz
  • Quest: 160-500Hz
  • SteamVR: 0-360Hz

はじめに

現行のOculus/SteamVRのバイブレーション機能のメソッドで共通しているfrequency(周波数)の範囲について調べてみました。

// Oculus Integration 17.0 (OVRPlugin 1.49.0)
SetControllerVibration (float frequency, float amplitude, Controller controllerMask)

// SteamVR Plugin V2
Execute(float secondsFromNow, float durationSeconds, float frequency, float amplitude, SteamVR_Input_Sources inputSource)

Oculusのバイブレーション機能

OculusのSetControllerVibration()ではfrequency0-1で指定します。

振幅と振動数の期待値は0から1の間の値(両端を含む)です。値が大きいほど、コントローラーの振動がより強くなるか、振動数が大きくなります。

引用元: Unity Haptics

上記のページには周波数の値について言及がないのですが、古いドキュメントに下記のような記載がありました。

振動を有効にするには振動数を指定します。0.0fを指定すると、160Hzで振動します。1.0fを指定すると、Riftでは320Hz、Rift Sでは500Hzで振動します。

引用元: Haptic Feedback

この範囲が変わっていないとすると、0-1指定はこのような対応になりそうです。

  • Rift: 160-320Hz
  • Rift S: 160-500Hz
  • Quest: 160-500Hz (Rift Sと同じコントローラーのはず1

SteamVR

SteamVRの場合はドキュメントのDescriptionに記載がありました。

// How often the haptic motor should bounce (0 - 320 in hz. The lower end being more useful)

引用元: Class SteamVR_Action_Vibration | SteamVR Unity Plugin

デバイス毎に違いがないのか気になるところですが、SteamVRでは0-360Hzの値を指定できるようです。

最後に(ポエム)

Oculusでは同じ0-1の指定で周波数の最大値が360Hz/500Hzと異なるデバイスがありますが、体験をデザインする上でこれって最適なんでしょうか?同じアプリでもRiftとRift Sでは感じ方が意図せず変わってしまうことを危惧しています。

例えば「軽めの振動と重めの振動」のような使い分けなら大丈夫そうですが、特定のモノ(リンゴなど)に触った時の最適な周波数は同じ値になる気がします。下記のような実装で一応固定値の指定はできますが、用意されているメソッドの使い道から外れていてかなり気持ち悪いです。どうするのがいいんでしょうね?

// 周波数を200Hzで指定する
var clampedFrequency = GetClamp01(200f);
OVRInput.SetControllerVibration (clampedFrequency, amplitude, controllerMask)

// デバイスの差異を無くすためのメソッド
float GetClamp01(float frequency)
{
    if(Rift)
    {
        return (frequency - 160f) / (360f - 160f);
    }
    if(RiftS || Quest)
    {
        return (frequency - 160f) / (500f - 160f);
    }
}

参考

[SteamVR Plugin V2] コントローラーへバイブレーション機能を付ける - Unity+UnrealEngine4+Blog.


  1. "QuestとRift Sは、共通の「Oculus Touch」コントローラーを使用しています。"(引用元: 「Oculus Quest」「Oculus Rift S」「Oculus Go」どれを買う? オススメVRデバイス徹底比較 | Mogura VR) 

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