- 投稿日:2020-11-17T20:42:25+09:00
ARcore ver1.21を使おうとしてエラー吐かれまくった話
ARcoreがバージョンアップし、ついにAnchorが最長1年保つことになりましたーわーい。
てなわけで、今までこき使ってたUnity2018.4.20プロジェクトのARcore(バージョンは忘れた)に上書きしたところ、エラー吐かれまくってビルドが出来なくなった。どう対処したか
解決策から書くとUnity 2020.1.13に切り替えることで解決しました。
Unity 2020.1.13では特に設定は弄らなくていいが、ビルドの前にGraphicsAPIsのvulkanを削除しないとエラーが出る。↓画像参照
ちなみにUnity 2019.4.14でもエラー吐かれまくって一向に解決の目途が立ちませんでした。(全ギレ)Unity2018.4.20でのエラー内容
エラーの内容はこんな感じ。
BuildFailedException: Main Gradle template is not used in this build. ARCore SDK for Unity requires gradle plugin version >= 3.6.0. Nevigate to 'Project Settings > Player > Android Tab > Publish Settings > Build', check 'Custom Gradle Template'. Then edit the generated file 'Assets/Plugins/Android/mainTemplate.gradle' by adding dependency 'com.android.tools.build:gradle:3.6.0.'.
Custom Gradle Templateにチェックを入れろとのこと。
チェック入れてもっかいビルドかけるとまた別のエラーが。CommandInvokationFailure: Gradle build failed.とのこと。
その後は
Project Settings > Player > Configuration > Scripting Backend を Mono→IL2CPPにしたり、
mainTemplate.gradleにclasspathを追記したり、書き換えたりしたり、
exportかけてAndroidStudioで開くも読み込み時にエラーやUpdate失敗やらexport自体が失敗するなどで解決せず。オチ
ARcore ver1.21を使いたい場合は2020.1以降のUnityを使おうね!
参考
・https://developers.google.com/ar/develop/unity/android-11-build
・https://forum.unity.com/threads/you-have-enabled-the-vulkan-graphics-api-which-is-not-supported-by-arcore.896741/
- 投稿日:2020-11-17T19:03:09+09:00
【Unity】マルチディスプレイにおける座標の扱い
はじめに
Unityでは、スタンドアロンモードのみマルチディスプレイに対応しています。(対応OS:Windows、Mac、Linux)
Windowsでマルチディスプレイ対応のアプリを作成していたところ、
座標の扱いで何度か混乱してしまったため、書き留めておきたいと思います。環境
- Windows 10
- Unity 2019.2.19f1
- Visual Studio 2019
スクリーン座標とワールド座標
Unityをさわったことがある方ならご存知だと思いますが、Unityの座標にはスクリーン座標系とワールド座標系が存在します。
スクリーン座標系は画面上の座標で、左下隅が (0, 0)となります。
ワールド座標系はUnityのゲーム空間上の座標で、Scene直下にあるオブジェクトであればInspectorのTransform > Position の値となります。
マウス位置(Input.mousePosition)は画面上のどこかを表すものなので、当然スクリーン座標系となります。マルチディスプレイの場合のマウス位置は?
マルチディスプレイの場合、Input.mousePositionで取得できる座標は、メインディスプレイ基準の座標となります。
例えば、1920 x 1080のディスプレイ2台を以下のように配置していた場合
座標は以下のようになります。
ワールド座標に変換したい
取得したマウス位置の座標をワールド座標に変換するには、
① 画面ごとのスクリーン座標に変換
② 該当する画面のカメラでワールド座標に変換
の2段階の変換を行う必要があります。// cameras という変数に、ディスプレイ番号順のカメラが保持されている想定です private Vector2? ConvertScreenPointToWorld(Vector2 position) { // 1. 画面ごとのスクリーン座標に変換 // ※ 各画面の左下隅を(0, 0)とする座標に変換されます var relativePosition = Display.RelativeMouseAt(position); // ※ 気持ち悪いですが、z座標にディスプレイ番号がセットされます... var displayIndex = (int)relativePosition.z; if (displayIndex >= cameras.Count) return null; // 2. 該当する画面のカメラでワールド座標に変換 var camera = cameras[displayIndex]; return camera.ScreenToWorldPoint(relativePosition); }ちなみに、Unityエディター上では
Display.RelativeMouseAt()
は動作しないのでご注意ください。
- 投稿日:2020-11-17T17:46:10+09:00
Unity+WebGL+WebSocketをやる時はNativeWebSocketを使おう
前置き
タイトル出落ちですが、日本語情報が皆無だったので。
配布先
NativeWebSocket
https://github.com/endel/NativeWebSocket動作環境
- 対象バージョン:Unity2019以上
- .NET4.x 対応必須(PlayerSettings > Other Settings > API compatiblity Level)
特徴
- System.Net.WebSockets を使用するため外部ライブラリ非使用
- WebGL/HTML5サポート
- 主要なプラットフォームに対応
- シンプルなAPI
- ライセンスはApache 2.0(wikipedia)
個人的な感想
特殊な事情がなければ、こいつだけでもう良いんじゃないかな。
- 投稿日:2020-11-17T16:50:07+09:00
【Android】なんとかAndroidアプリのaabファイルのビルドに成功した件
【Android】GooglePlayアプリのkeystoreパスがわからず新規アプリ登録した件
https://qiita.com/technopixelinc2019/items/e7150caeadd1aff6ba4d上記の記事の続きです。
初心者向け、かつ自分への備忘録として記載残しておきます。ビルドしようとすると「Gradle build failed」と出るので調べてみる。
【参考URL】
https://nn-hokuson.hatenablog.com/entry/2017/09/05/202327#Gradle-build-failed%E3%81%8C%E5%87%BA%E3%82%8BUnity2019%E3%81%8B%E3%82%89参考ページによると、Unity2019からはBuild Settingsの項目から「Build System」が削除されたとの事。以前のver.なら「Systemの項目をInternalに設定」で良かったらしい。
Unity2019以降の場合は、Build Settingsの「Export Project」にチェックを入れて、AndoroidStudio用にExportして、Gradleをアップグレードさせると良いとの事。
ここは結局Unityに戻す方法がわからず、挫折。【参考URL:動画】
https://www.youtube.com/watch?v=RNDYJPMEo98代わりに、GradleをDLできるページを発見。
https://services.gradle.org/distributions/
→最新と思われる物(この時は6.8)を落としてきて「※任意のフォルダ」に入れるunityに戻り下記から参照パスを変更
Edit→Preferences→Gradle installed with Unity(recommenceded)もともと参照していた所から自分でいれたフォルダへ参照パスを変更
C:/Program Files/Unity/Hub/Editor/2019.2.12f1/Editor/Data/PlaybackEngines/AndroidPlayer/Tools/gradle
↓
「※任意のフォルダ」のパスこれで無事、aabファイルのビルドに成功しました!
■反省点
コマンドプロンプトでバッチなどを叩いたりする前に、そのパスの中身を確認する
batファイルはあるかなど、先に見て置く
SDKも同様、参照元にデータがあるのか見て置くと良いかも■今後
とりあえずビルド成功したので、GooglePlayコンソールでリリースしてみる
- 投稿日:2020-11-17T15:29:44+09:00
unityのオブジェクトをC#で生成、子要素に移動する方法
※Unity実践リファレンスの内容を参考にしています。
<エンジニアのためのUnity実践リファレンス ~ ゲーム開発にすぐに役立つスクリプト入門>
https://www.amazon.co.jp/dp/B00WHEJI8W/ref=cm_sw_em_r_mt_dp_K82SFb5NXTQ27動的にC#でゲームオブジェクトを生成し、それを特定のゲームオブジェクトの子要素として移動させるためのコードになります
// test.cs // Start is called before the first frame update void Start() { //C#で使用する変数名にparentAという名前で作り、 //ヒエラルキー上のオブジェクト名をParentGO_Aとして生成 var parentA = new GameObject("ParentGO_A"); var parentB = new GameObject("ParentGO_B"); var childA = new GameObject("childrenGO_A"); var childB = new GameObject("childrenGO_B"); var childBchildA = new GameObject("childrenBchildrenGO_A"); //childAをparentAの子要素にトランスフォーム //これでchildAのゲームオブジェクトを指定したゲームオブジェクトの子要素に変更 childA.transform.parent = parentA.transform; childB.transform.parent = parentB.transform; //特定の要素の子要素にする場合に、2階層下の要素としても1回で変更できる //この場合は childBchildA.transform.parent = childB.transform; }ちゃんと階層構造を持ったままゲームオブジェクトが生成されました。
この機能を使って特定の条件下で生成したゲームオブジェクトがあれば、それをFind関数を利用して見つけたのちに、子要素へ移動させるということもできそうです。
- 投稿日:2020-11-17T00:44:53+09:00
【Unity】PowerPointファイル表示+音声合成でスライドの動画作成を自動化
先日PowerPointファイルを利用して発表動画を作成する機会があったのですが,何度も噛んだり,制限時間に間に合わなかったり撮り直すことになりました.
すごく喉が痛くなったので,機械に読み上げてもらうようにしようと思った次第です.
やろうとしていることは以下のようなものです.
- PowerPointファイルの読み込み
- スライドを画像に変換し,ノートに書かれている内容を取得
- 画像をUnityに読み込み
- ノートの文を音声合成APIに投げ,音声ファイルを作成
- スライド画像を表示し,音声を再生させる
- (アバターを表示し,口パクさせる)
今回は5までのやり方を示そうと思います.
ここまででナレーションありのスライド動画をUnityで表示させることができるようになります.ナレーションはスライドのメモを読み上げる形で実現します.UnityRecorder等を利用することで動画をエクスポートすることもできます.
今回の記事で実装した内容はGitHubで公開しています.
詳しくはそちらを確認いただけるとありがたいです.6まで実装すると以下のような動画がPowerPointファイルを投げるだけで作成できるようになりました.
YouTubeの怪しい美容広告みたいになりました.PowerPointファイルの読み込み
Microsoft.Office.Interop.PowerPointライブラリを利用することで,C#で.pptxや.pptといったPowerPointファイルを開いたり,修正,画像の書き出しといったことが可能になります.
以下のコードで,スライドを1枚の画像として書き出し,ノートをstringで取得します.
var SLIDE_PATH = "スライドの場所" var FILE_PATH = "画像の保存場所" // ノートと画像保存場所のリスト var slideList = new List<(string, string)>(); var app = new Microsoft.Office.Interop.PowerPoint.Application(); // スライドを開く var ppt = app.Presentations.Open(SLIDE_PATH, MsoTriState.msoTrue, MsoTriState.msoFalse, MsoTriState.msoFalse); var width = (int) ppt.PageSetup.SlideWidth; var height = (int) ppt.PageSetup.SlideHeight; var slideList = new List<SlideDataRaw>(); for (var i = 1; i <= ppt.Slides.Count; i++) { // 非表示スライドは無視 if (ppt.Slides[i].SlideShowTransition.Hidden == MsoTriState.msoTrue) continue; // ノート var note = ppt.Slides[i].NotesPage.Shapes.Placeholders[2].TextFrame.TextRange.Text; if (note == "") continue; // JPEGとして保存 var file = FILE_PATH + $"/slide{i:0000}.jpg"; ppt.Slides[i].Export(file, "jpg", width, height); slideList.Add((note, file)); } ppt.Close(); app.Quit();UnityでPowerPointファイルを利用するのに一番問題となるのは,画像の保存場所です.
一般的にUnityではデータの保存場所としてApplication.persistentDataPath
などを利用しますが,これらは実行時Unityがアクセス権を持っており,PowerPointSDKからこの場所にデータの保存が行えないという問題がありました.
なのでFILE_PATH
にはC:\Users\User\Documents
などUnityが触れない場所を設定する必要があります.スライド画像をUnityに読み込む
一般的な
.jpg
ファイルの読み込み方法と同じです.
以下のスクリプトで.jpg
ファイルをTexture2D
に変換します.private static Texture2D LoadImage(string path) { byte[] binary; try { using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read)) { var length = (int) fs.Length; binary = new byte[length]; fs.Read(binary, 0, length); fs.Close(); } } catch(IOException exception) { Debug.Log(exception); return null; } var texture = new Texture2D(0, 0); texture.LoadImage(binary); return texture; }テキストを音声合成しAudioClipを作成
先ほど取得できたスライドのノートを音声合成APIに送信し,AudioClipを作成します.
音声合成は必要になった場合にリアルタイムで合成結果を受け取るストリーミング音声合成が一般的ですが,今回は発言内容が事前にすべて決まっているため,先にリクエストを全て送信しておき,結果をキャッシュするようにします.今回は音声合成APIとしてWatson Text To Speech APIを利用しました.
Watson Text To Speechの利用方法
- IBM Cloudにログイン(アカウントを持っていない場合は作成します)
これでWatson側の設定は以上です.
WatsonにはIBM Watson SDK for Unityという便利なUnity用SDKが存在し,これを利用することで多くのサンプルやUtilityスクリプトを利用できますが,今回はText To Speechのみを利用するため,自分でWebRequestを実装します.
まず,先ほど取得したAPIキーからアクセストークンを取得するリクエストを記述する必要があります.音声合成リクエストにはAPIキーではなくこのトークンを送る必要があります.
トークンを取得するリクエストは以下のように記述します.
public string GetAccessToken(string apikey) { var form = new WWWForm(); form.AddField("grant_type", "urn:ibm:params:oauth:grant-type:apikey"); form.AddField("apikey", apiKey); form.AddField("response_type", "cloud_iam"); using (var request = UnityWebRequest.Post(AUTH_URL, form)) { request.SetRequestHeader("Content-type", "application/x-www-form-urlencoded"); request.SendWebRequest(); while(!request.isDone && !_cancelled){} if (request.responseCode != 200L) { Debug.LogError($"[GenerateAudio] Request Failed ({request.responseCode}): {request.error}\nat{request.url}"); return; } var json = request.downloadHandler.text; return JsonConvert.DeserializeObject<IamTokenResponse>(json).AccessToken; } }今回はJsonのパースにNewtonsoft Jsonを利用しました.どんなものでもよく,AccessTokenパラメータのValueが目的のアクセストークンです.
一応,Newtonsoft Jsonの場合のパースする対象オブジェクトは以下のような構造になっています.
public class IamTokenResponse { [JsonProperty("access_token", NullValueHandling = NullValueHandling.Ignore)] public string AccessToken { get; set; } [JsonProperty("refresh_token", NullValueHandling = NullValueHandling.Ignore)] public string RefreshToken { get; set; } [JsonProperty("token_type", NullValueHandling = NullValueHandling.Ignore)] public string TokenType { get; set; } [JsonProperty("expires_in", NullValueHandling = NullValueHandling.Ignore)] public long? ExpiresIn { get; set; } [JsonProperty("expiration", NullValueHandling = NullValueHandling.Ignore)] public long? Expiration { get; set; } }そしてリクエストを送信し,合成結果を取得するのは以下のように記述します.
public byte[] GetSynthesizeVoice(string text) { var rqStr = JsonConvert.SerializeObject(new JObject {["text"] = text}); var url = $"{URL}/v1/synthesize?voice=ja-JP_EmiV3Voice"; using (var request = new UnityWebRequest(url, "POST")) { request.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(rqStr)); request.downloadHandler = new DownloadHandlerBuffer(); request.SetRequestHeader("Content-Type", "application/json"); request.SetRequestHeader("Accept", "audio/wav"); request.SetRequestHeader("Authorization", $"Bearer {ACCESS_TOKEN}"); request.SendWebRequest(); while(!request.isDone && !_cancelled){} if (request.responseCode != 200L) return null; return request.downloadHandler.data; } }
ACCESS_TOKEN
には先ほど取得したアクセストークンを,URL
にはWatsonのページで取得したUrlを記述してください.
これで合成音声のバイナリを取得できたので,UnityのAudioClipに変換する作業を行います.方法は私の前回の記事Google Cloud Text-To-Speechを利用してUnityでキャラクターをフルボイスに!でも使わせていただいた,WAVクラスを利用します.
var wav = new WAV(GetSynthesizeVoice("こんにちは")); var audioClip = AudioClip.Create("TextToSpeech", wav.SampleCount, 1, wav.Frequency, false);スライドを表示
最後に,画像表示と音声再生を同時に行い,音声が終了したら次のスライドを表示し音声も再生させる機構を実装します.
実装は簡単で,音声再生に使うAudioSourceをUpdateで監視しておき,
isPlaying
(再生中)がfalse
になったら次の処理を行います.public class Presenter : MonoBehaviour { [SerializeField] private RawImage image; [SerializeField] private AudioSource source; private int _currentIndex; private SlideData[] _slide; public void StartPresentation(SlideData[] data) { Debug.Log("[Presenter] Start presentation."); _slide = data; _currentIndex = -1; } private void Update() { if (_slide == null || source.isPlaying) return; if (_currentIndex >= _slide.Length - 1) return; Debug.Log("[Presenter] Next slide."); ++_currentIndex; image.texture = _slide[_currentIndex].image; source.clip = _slide[_currentIndex].clip; source.Play(); } }これでスライドの読み込みから音声合成,スライド送りが実装できました.
終わりに
今回はPowerPointファイルをUnityで読み込み,音声合成を行うことでナレーションありのスライド動画を自動で作成する手順を紹介しました.
今回示したコードのサンプルは,実際には「リクエスト可能サイズに合わせたテキストの分割」や「分割された音声ファイルの結合」が必要です.そのような処理も含めた,UniSlideToMovieというサンプルプロジェクトをGitHubで公開していますのでそちらも確認していただけたらと思います.
また,この次の記事として,今回触れられなかった「6. アバターを表示し,口パクさせる」処理について書きたいと思います.そちらもよろしくお願いします.