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

Oculus Link 時に FMOD failed to initialize the output device エラーが出たときの対応方法

Oculus Link しようとした時に、FMOD failed to initialize the output device エラーが出たときの対応方法です。このエラーが出ると、AVPro ではファイルがありませんというエラーが出るようになり、知らないと迷走するなと思いました。(実際迷走した)

前提

  • Windows10
  • Unity 2019.2.02f
  • Oculus Integration 12.0
  • AVPro
  • 元々開発中のプロジェクトで、本エラーが発生する前は Unity Editor で正常に動作していた
    • ビルドして、adb install すれば、Oculus Questでも正常に動作していた

再現手順

  1. Oculusの管理アプリケーションをインストール
  2. Unity から Editor 実行

エラー

Unity Edtior から実行すると、

FMOD failed to initialize the output device.: "Error initializing output device. " (60)

とエラーが発生した後に、AVPro からも以下エラーが発生した。

[AVProVideo] Error: Loading failed.  File not found, codec not supported, video resolution too high or insufficient system resources.
UnityEngine.Debug:LogError(Object)
RenderHeads.Media.AVProVideo.MediaPlayer:UpdateErrors() (at Assets/AVProVideo/Scripts/Components/MediaPlayer.cs:1783)
RenderHeads.Media.AVProVideo.MediaPlayer:Update() (at Assets/AVProVideo/Scripts/Components/MediaPlayer.cs:1107)

解決方法

Oculus の管理アプリケーションをインストールすると、音声の箇所が下記の通り、 ヘッドホン(Oculus Virtual Audio Device)となるようです。この設定値を変更するだけで解消されます。
2019-12-23_14h34_05.png

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

NLogを使ってAzure Table Storageにログを書きこむ

NuGetを検索するとAzure Table Storage用のTargetをいくつか見かけますが、.NET Coreへの対応状況や依存ライブラリの最新化状況などの制約があって微妙です。
また、Azure Table StorageまわりのSDKはCosmosDBに切り出されたりしてややこしいので、たかがEntityの追加のみで依存関係を増やしたくありません。
そんなわけで、NLog標準のWebServiceTargetを使った書き込みを試したら結構ハマりどころがあったので共有します。

事前準備

コンソールアプリのプロジェクトを作成

ターゲットフレームワークはNLogがサポートするものであれば何でも良いですが、.NET Core 2.0〜3.0までで動作確認をしました。

NLogのインストール

NuGetからインストールします。この記事では4.6.8の利用を前提としていますが、多少前後しても問題ないと思います。

Azure StorageのSAS URLの作成

SASとはShared Access Signatureの略で、あらかじめリソースやそれに対するアクション(参照、書き込みなど)、利用可能期間を制限したトークンです。これを含むURLを使うことで、リクエスト都度署名をするといった手間も省けるためWebServiceTargetで利用する上では好都合というわけです。

作成は簡単で、Azure Portalで対象ストレージアカウントを選択し、メニューの「Shared Access Signature」に移動します。対象リソースなどを選んで「SASと接続文字列を作成する」ボタンを押下後、表示された「Table service の SAS URL」を控えておいてください。
azureportal.png

NLogの設定

まずは以下の通りWebServiceTargetを設定します。名前は何でもいいですが、ここではAZTBLTargetとしてみました。

NLog.config
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      throwExceptions="true">

    <targets>
        <target name="AZTBLTarget" xsi:type="WebService" protocol="JsonPost" url="<SAS URL>">
            <parameter name="PartitionKey" layout="${callsite}" />
            <parameter name="RowKey" layout="${ticks}.${sequenceid:padCharacter=0:padding=6}" />
            <parameter name="LocalTimestamp" layout="${date:format=yyyy-MM-ddTHH\:mm\:ss.fffffff}" />
            <parameter name="Level" layout="${level:uppercase=true}" />
            <parameter name="Callsite" layout="${callsite}" />
            <parameter name="Message" layout="${message}" />
            <parameter name="Error" layout="${exception:format=Message, ToString:separator=\n}" />
        </target>
    </targets>

    <rules>
        <logger name="*" minlevel="Trace" writeTo="AZTBLTarget" />
    </rules>
</nlog>

簡単に説明すると、ターゲットの種類としてWebServiceのものを定義していて、その通信手続きとしてJSONデータのPOST、そのJSONのパラメータとしてPartitionKeyRowKeyを含む7項目を送信するようにしています。なおPartitionKeyRowKeyはTable Storageとして必須の項目です。

さて、説明の中でurlアトリビュートの説明を飛ばしていましたが、ここがハマりポイントです。コピペしてurlに貼り付けるだけではうまくいきません。先の手順で取得したSAS URLを、以下の通り修正する必要があります。

生成されたもの
https://accountname.table.core.windows.net/?&sv=2019-02-02&ss=t&srt=sco&sp=wau&se=2030-01-04T08:21:42Z&st=2020-01-04T00:21:42Z&spr=https&sig=xxxxxxxxx%3D
修正後
https://accountname.table.core.windows.net/nlogtable?$format=application/json&amp;sv=2019-02-02&amp;ss=t&amp;srt=sco&amp;sp=wau&amp;se=2030-01-04T08:21:42Z&amp;st=2020-01-04T00:21:42Z&amp;spr=https&amp;sig=xxxxxxxxx%3D

修正内容は以下の通り。

  • URIの末尾に/<テーブル名>をつける。当たり前ですが、コピペでは動かず一瞬「ウッ」てなります。修正後の例は、テーブル名をnlogtableのケースです。
  • &&amp;に置換。これもXMLに一般的な話だと思いますが、エラーになるので小心者の私は「えっえっ」てなりました。
  • $format=application/jsonをつける。Insert Entityの仕様を見る限りAcceptヘッダーは任意なのですが、なぜかこれを渡さないと415エラーとなります。しかしながらNLogのWebServiceTargetではなぜかHTTPヘッダーにAcceptを設定することができませんでした。諦めかけていたところ、Payload format for Table service operationsを読んだら$formatで指定できることを発見したため、これをURLに含んでやります。

ログの書き込み

それでは実際に書き込んでみましょう。本記事のポイントは設定方法ですので、書き込み自体は一般的な手順に従えばOKかと思います。ちょっとハマるかもしれないポイントとしては、最終行のLogManager.Shutdown();を実行しないと、ログ送信前にプロセスが終了してしまいTable Storgeに書き込まれない場合があるところでしょうか。

using NLog;

namespace NLogToAzureTable
{
    class Program
    {
        static void Main(string[] args)
        {
            // NLog.configを出力ディレクトリにコピーするように設定してください。
            // もしくは、以下の通り明示的にパスを指定することもできます。2つめの引数falseはエラーを無視するか否かの設定です(false=無視しない)
            // LogManager.Configuration = new XmlLoggingConfiguration("/path/to/NLog.config", false);

            // ロガーの初期化・取得
            var logger = LogManager.GetCurrentClassLogger();

            // ログの書き込み
            logger.Info("test message for info");
            logger.Warn("test message for warn");

            // ロガーの終了。プロセスを終了する前にログを送信するために必要
            LogManager.Shutdown();
        }
    }
}

非同期書き込みの利用

NLogには便利な非同期処理の仕組みAsyncWrapperターゲットがあります。ログメッセージをキューに貯めてバッチ的に別スレッドで処理してくれるといった便利なものです。
使い方は超簡単で、targetsasyncアトリビュートにtrueを設定するだけで全てのターゲットへの書き込みが非同期に行われます。

NLog.config
:中略
<targets async="true">
:中略

一部の書き込みのみを非同期にしたい場合は、ラッパーの名の通り非同期にしたいターゲットをAsyncWrapperで囲みます。このとき、ruleに設定するnameもラッパーのものにすることに注意です。

NLog.config
    <targets>
        <target name="AZTBLTargetAsync" xsi:type="AsyncWrapper">
            <target name="AZTBLTarget" xsi:type="WebService" protocol="JsonPost" url="<SAS URL>">
                <parameter name="PartitionKey" layout="${callsite}" />
                 :中略
            </target>
        </target>
    </targets>

    <rules>
        <logger name="*" minlevel="Trace" writeTo="AZTBLTargetAsync" />
    </rules>

詳細は公式Wikiを読んでみてください。
https://github.com/nlog/NLog/wiki/AsyncWrapper-target

補足

  • PartitionKeyに設定する値。記事では便宜上出力元メソッドの名前を指定していますが、ユーザーとかデバイスとかを一意に特定できるものを設定した方が調査しやすいと思います・
  • パフォーマンスや負荷は未知数。AsyncWrapperを使うことでメイン処理への影響はないと思いますが、書き込み速度や負荷は計測していません。WebServiceTargetに一般的な議論かと思います。詳しい方アドバイスください!!
  • 出力レベル。そもそもリモートでログを取る時点で常にデバッグ情報が欲しいわけではないと思いますので、minlevelをエラーに設定するなど最適化が必要です。
  • Logging Frameworkについて。実装ライブラリを自由に変更できるようにDIなフレームワークが.NET Coreでは用意されていますが、今回は未考慮です。

おまけ:UnityでNLogを使ってAzure Table Storageに書き込み

そもそも何でAzure Table Storageにログを書き込もうと思ったかというと、Gateboxというデバイス向けのアプリ開発をはじめたことがきっかけです。実機デバッグする際にコンソール出力やファイルファイルを選択できないことから、リモートにログ出力をする方法を探ることにしました。ログのためにサーバーを用意するのもアレなのでAzureに・・・という経緯です。

UnityのConsoleへのログ出力用ターゲットも同梱したファクトリーぽいものを作りましたのでよかったら使ってみてください。

NLogFactoryForUnity
https://github.com/uezo/NLogFactoryForUnity

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

UnityでHaskellのコードを実行する手法の模索

概要

Unityでゲーム制作をするためにはC#を使う。しかし、場合によっては他の言語を用いたいことがあり、Unityではネイティブプラグインという方法で、外部で生成されたDLLを利用することが可能である。
筆者は、ダンジョン生成のプログラムをHaskellで作成し、それをUnity内で利用したいという経緯があったので、今回、HaskellのシンプルなコードをCabalとStackを利用してDLLを作成し、それをUnityから利用する効率的な方法を模索した。

環境はWindows 10 64bit

1. 準備

Stackで新しいプロジェクトを作る。

stack new haskell-foreign-library-test

一度ビルドstack buildして、package.yamlからcabalファイル(haskell-foreign-library-test.cabal)を生成する(バックでhpackが動いている)。

後述するが、ライブラリ作成にはCabalの機能foreign-libraryを使う。しかし、現在(2019/01/04)、cabalファイルを生成するhpackは、これに未対応なようので、泣く泣くpackage.yamlは削除しておく。

hpackのforeign-library対応は、議論されているようだ。

ついでに.gitignoreのcabalの行を削除して、cabalファイルをバージョン管理に含めておきたいところ。

2. HaskellプログラムのDLL作成

Haskell側の全てのコードはここ(masatoko/haskell-foreign-library-test)で見られる。

2.1. Haskellのコード

Haskellのコードは非常にシンプルなものにした。受け取った整数に1を足して返すだけの関数plus_oneだ。
これをforeign exportで出力しておく。

src/Lib.hs
module Lib where

foreign export ccall plus_one :: Int -> IO Int

plus_one :: Int -> IO Int
plus_one x = return $ x + 1

2.2. hs_init関数のラッパーをC言語で作成

参考(8.2. GHCでFFIを使う)

外部からplus_oneを利用するには、使う前にGHCのランタイムシステムを初期化し、使ってから終了させなければならない。
hs_init() -> plus_one() -> hs_exit() という流れ。
しかし、hs_init()は引数をとり、外部(C#)から利用する際、引数を渡すのが面倒になるので、hs_init()のラッパー関数hs_init_wrapper()を作成しておく。

crsc/mylib.h
#pragma once

extern "C" {
void hs_init_wrapper(void);
}
csrc/mylib.cpp
#include "HsFFI.h"
#include "mylib.h"
#include "Lib_stub.h" // 今回は不要。ここでplus_one()を使うことができる。

void hs_init_wrapper(void) {
  int argc = 2;
  char *argv[] = {(char *)"+RTS", (char *)"-A32m", NULL};
  char **pargv = argv;
  hs_init(&argc, &pargv);
}

mylib.cppでインクルードされているLib_stub.hは自動生成される。
.stack-work/dist/*/build/mylib/mylib-tmp/Lib_stub.hに出力されていた。)
中身を見てみよう。

Lib_stub.h
#include "HsFFI.h"
#ifdef __cplusplus
extern "C" {
#endif
extern HsInt plus_one(HsInt a1);
#ifdef __cplusplus
}
#endif

plus_oneがエクスポートされている。
外部からより使いやすくするために、関数を加工して、また別の関数として出力しておくのもいいと思う。

2.3. ビルド

stack buildの1コマンドでDLLを生成してくれたら嬉しい。その方法を調べた。
Cabalはv2.0.0.2(2017年7月)からforeign-libraryでDLL(macの場合はdylib)を作成できるようになったらしい。これを使いたい。公式の説明

haskell-foreign-library-test.cabal
library
  hs-source-dirs:
      src
  other-modules:
      Lib
  build-depends:
      base >=4.7 && <5
  default-language: Haskell2010

foreign-library mylib
  type:
      native-shared

  if os(Windows)
    options: standalone

  other-modules:
      Lib
  build-depends:
      base >=4.7 && <5
  hs-source-dirs:
      src
  c-sources:
      csrc/mylib.cpp
  default-language: Haskell2010

これでビルドすればDLLが作成される。

stack build

2.4. 生成されたDLLを確認

生成されたDLLのパスは、stack buildのログの最後のほうに表示されている。
今回は、.stack-work/install/426b9cbc/lib/mylib.dll に生成されていた。

nmコマンドで中身を確認。

nm mylib.dll

以下の行を発見し、関数が出力されていることを確認した。

0000000067881620 T hs_init_wrapper
0000000067886c20 T hs_exit
0000000067881580 T plus_one

3. UnityでのDLLの利用

生成したDLLをUnityで使ってみた。
開発時のEditorの挙動で少し厄介なことがあったので、それの対策が必要だった。

3.1. DLLの配置

Assetsフォルダ配下にPluginsフォルダを作成し、そこにDLLを配置すればUnityが認識してくれる。
今回は素直にAssets/Plugins/mylib.dllに配置した。

3.2. 各種設定を64ビットにする

生成されたDLLは64ビットのバイナリなので、Platform settings > Windows > x86_x64 だけにチェックを入れる。

dll-64bit.JPG

また、Build SettingsのArchitectureをx86_64にしておく。
architecture.JPG

3.3. 利用するコードの記述

以下は適当な変数counterを1秒ごとに1足して、画面上のテキストに反映させるだけのプログラムである。
しかし、1点だけ注意することがある。

MylibWrapper.cs
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.UI;

public class MylibWrapper : MonoBehaviour {

    [DllImport("mylib")]
    public static extern void hs_init_wrapper();
    [DllImport("mylib")]
    public static extern void hs_exit();
    [DllImport("mylib")]
    public static extern int plus_one(int x);

    public Text text; // counterを確認するためのText

    int counter = 0;

    private void Awake() {
        hs_init_wrapper();
    }

    private void Update() {
        counter = plus_one(counter);
        print(counter);
        text.text = $"{counter}";
    }

    void OnApplicationQuit() {
        // エディター上でhs_exit()すると次回のhs_init()でエラー。
        // エディター上ではhs_exit()を実行させないのを一時的な解決方法とする。
#if UNITY_EDITOR

#else
        hs_exit();
#endif
    }
}

エディター上での2度目の起動時に落ちる問題の解決

ghcのランタイムシステムは、hs_exit()した後にhs_init()できないようになっている。
しかし、Unityでは、エディター上でロードされたDLLはUnityを終了するまでアンロードされないため、
エディター上でゲームを開始して、hs_init_wrapper()し、ゲームを停止した時にhs_exit()し、次回のゲーム開始時に再びhs_init_wrapper()を実行するような仕組みにすると、hs_init_ghc: reinitializing the RTS after shutdown is not currently supportedというエラーが発生して落ちてしまう。

ソースコードを見ると、rts_shutdownフラグがこれを拒んでいる。

それであれば、エディター上ではhs_exit()を実行しないという方法しか残されていない。
ソースコードを見ると、hs_init()を複数回実行しても、hs_init_countで呼び出し回数をカウントされるものの、2回目以降は何の処理もしておらず、問題ないだろう。(本当に?)
よって、エディター上ではゲーム開始のたびにhs_init_wrapper()が呼ばれるが、ゲーム終了時にはhs_exit()を呼ばないという方針とした。#if UNITY_EDITOR ~ #else ~ #endif の部分。

4. 実行

エディター上でも、ビルドした実行ファイルでも、うまく動作した。
exec.JPG

5. まとめ

64bitという制約付きであるものの、HaskellのコードをUnity上で動作させることができた。

しかし、いくつか懸念もある。

  • 【懸念1】 AndroidやiOSで動作するアプリを作れるのか?

Mobile Haskellというプロジェクトがあるので、これを使ってDLLを作れるかどうかにかかっていそう。検証は苦しそうなので今はしない。

  • 【懸念2】 SwitchやPS4で動作するゲームを作れるのか?

それぞれのハードウェアで動作するバイナリ(DLL)が作れるのかどうか。ハードウェアの仕様がわからないので、これも未知数。

Windows上でゲームを公開するだけならば不安は無いが、将来的にモバイル端末やコンシューマー機で公開する可能性がある場合は、これが原因で断念せざるをえなくなる可能性があるので注意が必要。

  • 活用方法の検討
    • Haskellでダンジョン生成をしてUnityに取り込む。
    • C#で書きたくない複雑な関数をHaskellで記述してUnityで使う。
    • Haskellでゲームロジックを書いて、全ての描画はUnityに任せる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityでHaskellのコードを実行する方法の模索

概要

Unityでのゲーム制作には基本的にはC#を使う。しかし、場合によっては他の言語を用いたいことがあり、Unityではネイティブプラグインという方法で、外部で生成されたDLLを利用することが可能である。
筆者は、ダンジョン生成のプログラムをHaskellで作成し、それをUnity内で利用したいという経緯があったので、今回、HaskellのシンプルなコードをCabalとStackを利用してDLLを作成し、それをUnityから利用する効率的な方法を模索した。

環境はWindows 10 64bit

1. 準備

Stackで新しいプロジェクトを作る。

stack new haskell-foreign-library-test

一度ビルドstack buildして、package.yamlからcabalファイル(haskell-foreign-library-test.cabal)を生成する(バックでhpackが動いている)。

後述するが、ライブラリ作成にはCabalの機能foreign-libraryを使う。しかし、現在(2019/01/04)、cabalファイルを生成するhpackは、これに未対応なようので、泣く泣くpackage.yamlは削除しておく。

hpackのforeign-library対応は、議論されているようだ。

ついでに.gitignoreのcabalの行を削除して、cabalファイルをバージョン管理に含めておきたいところ。

2. HaskellプログラムのDLL作成

Haskell側の全てのコードはここ(masatoko/haskell-foreign-library-test)で見られる。

2.1. Haskellのコード

Haskellのコードは非常にシンプルなものにした。受け取った整数に1を足して返すだけの関数plus_oneだ。
これをforeign exportで出力しておく。

src/Lib.hs
module Lib where

foreign export ccall plus_one :: Int -> IO Int

plus_one :: Int -> IO Int
plus_one x = return $ x + 1

2.2. hs_init関数のラッパーをC言語で作成

参考(8.2. GHCでFFIを使う)

外部からplus_oneを利用するには、使う前にGHCのランタイムシステムを初期化し、使ってから終了させなければならない。
hs_init() -> plus_one() -> hs_exit() という流れ。
しかし、hs_init()は引数をとり、外部(C#)から利用する際、引数を渡すのが面倒になるので、hs_init()のラッパー関数hs_init_wrapper()を作成しておく。

crsc/mylib.h
#pragma once

extern "C" {
void hs_init_wrapper(void);
}
csrc/mylib.cpp
#include "HsFFI.h"
#include "mylib.h"
#include "Lib_stub.h" // 今回は不要。ここでplus_one()を使うことができる。

void hs_init_wrapper(void) {
  int argc = 2;
  char *argv[] = {(char *)"+RTS", (char *)"-A32m", NULL};
  char **pargv = argv;
  hs_init(&argc, &pargv);
}

mylib.cppでインクルードされているLib_stub.hは自動生成される。
.stack-work/dist/*/build/mylib/mylib-tmp/Lib_stub.hに出力されていた。)
中身を見てみよう。

Lib_stub.h
#include "HsFFI.h"
#ifdef __cplusplus
extern "C" {
#endif
extern HsInt plus_one(HsInt a1);
#ifdef __cplusplus
}
#endif

plus_oneがエクスポートされている。
外部からより使いやすくするために、関数を加工して、また別の関数として出力しておくのもいいと思う。

2.3. ビルド

stack buildの1コマンドでDLLを生成してくれたら嬉しい。その方法を調べた。
Cabalはv2.0.0.2(2017年7月)からforeign-libraryでDLL(macの場合はdylib)を作成できるようになったらしい。これを使いたい。公式の説明

haskell-foreign-library-test.cabal
library
  hs-source-dirs:
      src
  other-modules:
      Lib
  build-depends:
      base >=4.7 && <5
  default-language: Haskell2010

foreign-library mylib
  type:
      native-shared

  if os(Windows)
    options: standalone

  other-modules:
      Lib
  build-depends:
      base >=4.7 && <5
  hs-source-dirs:
      src
  c-sources:
      csrc/mylib.cpp
  default-language: Haskell2010

これでビルドすればDLLが作成される。

stack build

2.4. 生成されたDLLを確認

生成されたDLLのパスは、stack buildのログの最後のほうに表示されている。
今回は、.stack-work/install/426b9cbc/lib/mylib.dll に生成されていた。

nmコマンドで中身を確認。

nm mylib.dll

以下の行を発見し、関数が出力されていることを確認した。

0000000067881620 T hs_init_wrapper
0000000067886c20 T hs_exit
0000000067881580 T plus_one

3. UnityでのDLLの利用

生成したDLLをUnityで使ってみた。
開発時のEditorの挙動で少し厄介なことがあったので、それの対策が必要だった。

3.1. DLLの配置

Assetsフォルダ配下にPluginsフォルダを作成し、そこにDLLを配置すればUnityが認識してくれる。
今回は素直にAssets/Plugins/mylib.dllに配置した。

3.2. 各種設定を64ビットにする

生成されたDLLは64ビットのバイナリなので、Platform settings > Windows > x86_x64 だけにチェックを入れる。

dll-64bit.JPG

また、Build SettingsのArchitectureをx86_64にしておく。
architecture.JPG

3.3. 利用するコードの記述

以下は適当な変数counterを1秒ごとに1足して、画面上のテキストに反映させるだけのプログラムである。
しかし、1点だけ注意することがある。

MylibWrapper.cs
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.UI;

public class MylibWrapper : MonoBehaviour {

    [DllImport("mylib")]
    public static extern void hs_init_wrapper();
    [DllImport("mylib")]
    public static extern void hs_exit();
    [DllImport("mylib")]
    public static extern int plus_one(int x);

    public Text text; // counterを確認するためのText

    int counter = 0;

    private void Awake() {
        hs_init_wrapper();
    }

    private void Update() {
        counter = plus_one(counter);
        print(counter);
        text.text = $"{counter}";
    }

    void OnApplicationQuit() {
        // エディター上でhs_exit()すると次回のhs_init()でエラー。
        // エディター上ではhs_exit()を実行させないのを一時的な解決方法とする。
#if UNITY_EDITOR

#else
        hs_exit();
#endif
    }
}

エディター上での2度目の起動時に落ちる問題の解決

ghcのランタイムシステムは、hs_exit()した後にhs_init()できないようになっている。
しかし、Unityでは、エディター上でロードされたDLLはUnityを終了するまでアンロードされないため、
エディター上でゲームを開始して、hs_init_wrapper()し、ゲームを停止した時にhs_exit()し、次回のゲーム開始時に再びhs_init_wrapper()を実行するような仕組みにすると、hs_init_ghc: reinitializing the RTS after shutdown is not currently supportedというエラーが発生して落ちてしまう。

ソースコードを見ると、rts_shutdownフラグがこれを拒んでいる。

それであれば、エディター上ではhs_exit()を実行しないという方法しか残されていない。
ソースコードを見ると、hs_init()を複数回実行しても、hs_init_countで呼び出し回数をカウントされるものの、2回目以降は何の処理もしておらず、問題ないだろう。(本当に?)
よって、エディター上ではゲーム開始のたびにhs_init_wrapper()が呼ばれるが、ゲーム終了時にはhs_exit()を呼ばないという方針とした。#if UNITY_EDITOR ~ #else ~ #endif の部分。

4. 実行

エディター上でも、ビルドした実行ファイルでも、うまく動作した。
exec.JPG

5. まとめ

64bitという制約付きであるものの、HaskellのコードをUnity上で動作させることができた。

しかし、いくつか懸念もある。

  • 【懸念1】 AndroidやiOSで動作するアプリを作れるのか?

Mobile Haskellというプロジェクトがあるので、これを使ってDLLを作れるかどうかにかかっていそう。検証は苦しそうなので今はしない。

  • 【懸念2】 SwitchやPS4で動作するゲームを作れるのか?

それぞれのハードウェアで動作するバイナリ(DLL)が作れるのかどうか。ハードウェアの仕様がわからないので、これも未知数。

Windows上でゲームを公開するだけならば不安は無いが、将来的にモバイル端末やコンシューマー機で公開する可能性がある場合は、これが原因で断念せざるをえなくなる可能性があるので注意が必要。

  • 活用方法の検討

    • Haskellでダンジョン生成をしてUnityに取り込む。
    • C#で書きたくない複雑な関数をHaskellで記述してUnityで使う。
    • Haskellでゲームロジックを書いて、全ての描画はUnityに任せる。
  • 追記1-Haskell関数からStringを返す(2020/01/05)

戻り値をStringにする方法はこのブログ記事が参考になりました。

  • 追記2-エディターが重くなる問題について(2020/01/06)

本記事のplus_oneのようなシンプルな関数ではなくて、プロシージャルなダンジョンを生成するような、少し複雑な関数を実行したところ、関数の実行後にエディタがかくかくしてしまい、とても開発できるような状況でなくなってしまう問題が発生することがわかりました。原因不明であり、対処方法が判明するまではこの方法はやめておくべきかもしれません。
GHCのランタイムの処理がバックグラウンドで残っていて、Unityの処理を邪魔しているんですかね?どんなコードが問題を発生させるのかを調べたいですが、それが分かったとしても、問題を避けるようなコーディングをせねばならず、そんな不自由なプログラミングはしたくはありませんが。。
ひとまずはオフラインでダンジョンをファイルに書き出しておいて、それをUnityで読み込むという平凡な仕組みにしましたが、筆者はHaskell活用の夢をまだ諦めてはいないぞ。

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

【Unity】子のAnimatorを親から操作してみるテスト

概要

例えば、こんなアニメーションをつけたい時に、
sample_anim.gif

子要素が三つあって、動きは同じなんだけど、開始のタイミングだけずらしたい。
動きは同じなんだけど、Animationのキーは、三倍打たなきゃいけない・・・面倒だなぁ。

Animationで、タイミングだけ指定できないかな?やってみよう!

SerializeFieldはAnimationでキーが打てる

そもそもMonoBehaviourスクリプトのSerializeFieldは、Animationで操作できます。ColorをSerializeFieldに出して、Animationで色を変える・・・なんてのは、よくやるテクニックです。

今回は、子要素のAnimatorのパラメーターを、SerializeFieldに用意して、親のアニメーションのキーから操作するようにします。
hierarchy.png

Animatorの構造

子供のAnimatroはこんな感じ。
シンプルな構造で、Any StateからBoolパラメーターで、即遷移するようにしました。
※TriggerではなくBoolの方が、アニメーションから制御しやすい
child.controller
子要素は3つありますが、全部同じAnimatorを使っています。
アニメーションの更新があった場合でも、一つのAnimatorを更新すれば良いのでラクチンです。

親のAnimatorはこんな感じです。
root.controller
用途にあわせて自由に作りましょう。
今回は、in Triggerで入って、out Triggerではけるような動きを作っています。

スクリプト

childにアタッチするスクリプトはこんな感じです。
子要素のAnimatorのパラメーターと同じSerializeFieldを用意し、UpdateでSetBoolするだけです。

SerializeAnim.cs
using UnityEngine;

public class SerializeAnim : MonoBehaviour
{
    Animator mAnimator;
    [SerializeField] public bool BoolWait;
    [SerializeField] public bool BoolStay;
    [SerializeField] public bool BoolIn;
    [SerializeField] public bool BoolOut;
    [SerializeField] public bool BoolRotate;

    void Awake()
    {
        mAnimator = GetComponent<Animator>();
    }

    void Update()
    {
        mAnimator.SetBool("wait", BoolWait);
        mAnimator.SetBool("stay", BoolStay);
        mAnimator.SetBool("in", BoolIn);
        mAnimator.SetBool("out", BoolOut);
        mAnimator.SetBool("rotate", BoolRotate);
    }
}

root_in.animにキーを打つ

root_in.animに、キーを打ってみます。
Add Propertyを選択すると、子要素のSerializeFieldが追加できます。
add_property.png
1秒ごとにズラして、child1のBool in、child2のBool in、child3のBool inのキーを打ちます。
animation_in.png
他のAnimationも同じように準備します。

さて、実行だ!

test.gif
おおー、うまく動いてますねー。

メリット/デメリット

  • 長所
    • 子供の動きを作るのが一つだけでいい
    • 更新があった場合、一つだけ更新すれば良いので、修正が少なくて済む
  • 短所
    • Animationウインドウのプレビューで動きを見ることができない

サンプル

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

[Unity] invalid build pathでAndroidビルドが出来ない

事象

Invalid build path: c:/develop/unity/test
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr) (at C:/buildslave/unity/build/Modules/IMGUI/GUIUtility.cs:179)

Unity 2019.2.13f1 でAndroidビルドをしようとすると、.apkファイルの保存ができなかった。

ビルドボタン選択時、表示されるダイアログで、(保存先の?)フォルダ選択しかできず
いつもなら、保存する.apkファイルの名前を指定して保存する手順のはず。。。

いつものビルドパターン
3.png

今回のビルドパターン
5.png

とりあえず、プロジェクトフォルダを選択してみると、このエラー。

いや、なんで?????

解決方法

プロジェクトフォルダ以外のフォルダを指定する。

ただし、プロジェクトフォルダ配下のフォルダはいくつか試してみてダメだったので、それより手前のフォルダを指定する必要があるかも。

今回は適当にフォルダを掘って、C:\newを指定してみました。
するとビルドが始まり、success後、指定したフォルダに.apkファイルが出来た。。。

いや、なんで?????

ちなみに

環境
Windows10
Unity 2019.2.13f1
AndroidSDK 26.1.1

UnityでOculusQuestのビルドを試していたところ発生。

AndroidSDKのバージョンと、Unityのバージョンに互換性がないと動かないらしいけど、2週間ほど前にビルド実績があるのでおそらく関係なし。

Questビルド用に、プロジェクトの設定を色々変えていたのが原因?
分かり次第、追記したい。。

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