- 投稿日:2022-01-31T22:50:13+09:00
【Unity】リソースデータ(アセット)読み込みについてまとめてみた
リソースデータについて Unity プロジェクトに「画像」「3Dモデル」「音声」「動画」など、ファイルサイズが大きいアセットが含まれる ユーザーに届ける方法は大きく分けると2つ 全アセットをビルドに含める DLC(ダウンロードコンテンツ)としてゲーム内(アプリ内)でダウンロードさせる Resources 旧来の Unity 開発で頻繁に利用されていたアセットの動的読み込みを行うための仕組み Resources という名前がついたフォルダは、ビルド時にアプリのバイナリに同梱される Resources.Load("path/to/asset") といったAPIから読み込みが行える 現在は非推奨 メモリ管理が難しくなる アプリの起動時間が長くなる リソースを特定のプラットフォームにのみ配信するなどの対応ができない Resources を使ってもよいケース プロトタイプ開発時 アプリが立ち上がっている間常駐するリソース メモリをあまり使わない場合 プラットフォーム間でリソースを切り替える必要がない場合 StreamingAssets アセットを変換しないでそのままアプリに持っていく仕組み バイナリとして保存するのでデシリアライズが必要 プラットフォームごとに配置されるフォルダは異なり、 Application.streamingAssetsPath で取得できる Unity Editor、Windows、Linux、PS4、Xbox、Switch は Application.dataPath + "/StreamingAssets" macOS は Application.dataPath + "/Resources/Data/StreamingAssets" iOS は Application.dataPath + "/Raw" Android は圧縮された APK/JAR ファイル "jar:file://" + Application.dataPath + "!/assets" Android の場合は UnityWeRequest を使ってロードする StreamingAssets を使ってもよいケース ビルド済みの AssetBundle を格納する AssetBundle は変更しなければビルドし直し不要のため、ビルド時間の削減 ビルド前処理に buildTarget に指定されたプラットフォームの AssetBundle のみを StreamingAssets に配置する必要がある プラットフォームごとに変換が不要なアセットの読み込み AssetBundle Import済アセットのバイナリデータを固めたモノ DLCとしてダウンロードすることで、ビルドに含まれないリソースを追加読み込みできる AssetBundle として使えるアセットは 画像 オーディオファイル フォント Scene Prefab フォルダ C#スクリプトを固めることはできない 通常の AssetBundle と Streamed AssetBundle に分類できる 通常の AssetBundle Scene を除く任意の Asset を固めた AssetBundle のこと 1つ以上の Asset が固められたバイナリデータをファイルとして出力したもの 内部的には Lz4 などの圧縮アルゴリズムを用いて圧縮されたファイル Streamed AssetBundle Scene そのものを AssetBundle として固めたもの Scene 内の GameObject から参照されているアセットも一緒に固められる 圧縮などの仕組みは通常の AssetBundle と変わらない AssetBundle を Load した時点で SceneManager からアクセス可能になる AssetBundle でできること DLC としてアセットを動的ロードする バリエーションを持たせる 複数のプラットフォーム、国、地域に配信するときなど、リソースデータにバリエーションをもたせたい AssetBundle Variants 機能を使う 任意の粒度でまとめる 設定した AssetBundle Name 単位でグルーピングされる APIを叩いてグルーピング可能 依存関係を構築する Addressable Asset System アドレスを指定することでリソースデータをロードできる機能 アドレスは Asset ごとに付けることができる任意の名前 ローカルのデータか、リモートから AssetBundle をロードするか設定ひとつで変更できる 呼び出し側は同じインターフェースでロードできる 使い方 Unity2019.3以上ならば Package Manager から Addressables をインストールすることで使用可能 Window > Asset Management > Addressables > Settings を選択すると。 AddressableAssetsData フォルダが作成される Addressable にチェックを入れることで、Addressable の管理対象になる Addressables Group に追加される フォルダ構成 アセット 内容 AddressableAssetSettings.asset ・プロジェクトのAddressableの設定が保存されている AssetGroups/ ・Groupに属しているアセットのGUIDやLabel情報などを管理 AssetGroups/Built In Data.asset ・Resources や Scene in Buildに関わる情報を管理 AssetGroups/Default Local Group.asset ・Playerビルドに含めるアセットを管理・初期状態に作成される Schemas/ ・Groupの詳細な設定を管理・GroupごとにSchemasが配置される AssetGroupTemplates/ ・Groupの新規作成時につかうテンプレ・新規でGroup Templeteを作成したときは AddressableAssetSettings に追加しなければならない DataBuilders/ ・ビルド、エディタ再生時に使用される設定・AssetBundleのビルド方法を定義したScriptableObjectが配置される DefaultObject.asset ・AddressableAssetSettings.assetの場所を示すScriptableObject ※ DataBuilders や AssetGroupTemples はあまり編集しない AssetBundle をビルドする 新規の場合h AddressableGroups で、 Build > New Build > Default Build Script を選択する すでにビルド済みの AssetBundle を更新する場合は Update a Previous Build Build される場所は以下で設定できる LoadBuildPath: Library/com.unity.addressables/[BuildTarget](デフォルト) Playerビルド時に一時的に StreamingAssets にコピーされることで Player ビルドに同梱される RemoteBuildPath: ServerData/[BuildTarget](デフォルト) プロジェクトルート直下に作成される ServerData 以下に作成された AssetBundle を RemoteLoadPath のURLに対応するようにアップロードする必要がある AssetBundle を読み込む 通常はアプリ起動時に自動的に Addressable の初期化処理が実行される 何らかの理由でスクリプトから初期化、実体化したい場合は Addressables.LoadAssetAsync() してから Instantiate() する 直接 Addressables.InstantiateAsync() する // Load してから Instatiate() var gameobject = await Addressables.LoadAssetAsync<GameObject>("Assets/Prefabs/Cube.prefab"); var obj = Instantiate(go); // 直接 Instatiate() Addressables.LoadAssetAsync<GameObject>("Assets/Prefabs/Cube.prefab"); 参考文献
- 投稿日:2022-01-31T22:50:13+09:00
【Unity】リソースデータ (Resources/StreamingAssets/AssetBundle) 読み込みについてまとめてみた
リソースデータについて Unity プロジェクトに「画像」「3Dモデル」「音声」「動画」など、ファイルサイズが大きいアセットが含まれる ユーザーに届ける方法は大きく分けると2つ 全アセットをビルドに含める DLC(ダウンロードコンテンツ)としてゲーム内(アプリ内)でダウンロードさせる Resources 旧来の Unity 開発で頻繁に利用されていたアセットの動的読み込みを行うための仕組み Resources という名前がついたフォルダは、ビルド時にアプリのバイナリに同梱される Resources.Load("path/to/asset") といったAPIから読み込みが行える 現在は非推奨 メモリ管理が難しくなる アプリの起動時間が長くなる リソースを特定のプラットフォームにのみ配信するなどの対応ができない Resources を使ってもよいケース プロトタイプ開発時 アプリが立ち上がっている間常駐するリソース メモリをあまり使わない場合 プラットフォーム間でリソースを切り替える必要がない場合 StreamingAssets アセットを変換しないでそのままアプリに持っていく仕組み バイナリとして保存するのでデシリアライズが必要 プラットフォームごとに配置されるフォルダは異なり、 Application.streamingAssetsPath で取得できる Unity Editor、Windows、Linux、PS4、Xbox、Switch は Application.dataPath + "/StreamingAssets" macOS は Application.dataPath + "/Resources/Data/StreamingAssets" iOS は Application.dataPath + "/Raw" Android は圧縮された APK/JAR ファイル "jar:file://" + Application.dataPath + "!/assets" Android の場合は UnityWeRequest を使ってロードする StreamingAssets を使ってもよいケース ビルド済みの AssetBundle を格納する AssetBundle は変更しなければビルドし直し不要のため、ビルド時間の削減 ビルド前処理に buildTarget に指定されたプラットフォームの AssetBundle のみを StreamingAssets に配置する必要がある プラットフォームごとに変換が不要なアセットの読み込み AssetBundle Import済アセットのバイナリデータを固めたモノ DLCとしてダウンロードすることで、ビルドに含まれないリソースを追加読み込みできる AssetBundle として使えるアセットは 画像 オーディオファイル フォント Scene Prefab フォルダ C#スクリプトを固めることはできない 通常の AssetBundle と Streamed AssetBundle に分類できる 通常の AssetBundle Scene を除く任意の Asset を固めた AssetBundle のこと 1つ以上の Asset が固められたバイナリデータをファイルとして出力したもの 内部的には Lz4 などの圧縮アルゴリズムを用いて圧縮されたファイル Streamed AssetBundle Scene そのものを AssetBundle として固めたもの Scene 内の GameObject から参照されているアセットも一緒に固められる 圧縮などの仕組みは通常の AssetBundle と変わらない AssetBundle を Load した時点で SceneManager からアクセス可能になる AssetBundle でできること DLC としてアセットを動的ロードする バリエーションを持たせる 複数のプラットフォーム、国、地域に配信するときなど、リソースデータにバリエーションをもたせたい AssetBundle Variants 機能を使う 任意の粒度でまとめる 設定した AssetBundle Name 単位でグルーピングされる APIを叩いてグルーピング可能 依存関係を構築する Addressable Asset System アドレスを指定することでリソースデータをロードできる機能 アドレスは Asset ごとに付けることができる任意の名前 ローカルのデータか、リモートから AssetBundle をロードするか設定ひとつで変更できる 呼び出し側は同じインターフェースでロードできる 使い方 Unity2019.3以上ならば Package Manager から Addressables をインストールすることで使用可能 Window > Asset Management > Addressables > Settings を選択すると。 AddressableAssetsData フォルダが作成される Addressable にチェックを入れることで、Addressable の管理対象になる Addressables Group に追加される フォルダ構成 アセット 内容 AddressableAssetSettings.asset ・プロジェクトのAddressableの設定が保存されている AssetGroups/ ・Groupに属しているアセットのGUIDやLabel情報などを管理 AssetGroups/Built In Data.asset ・Resources や Scene in Buildに関わる情報を管理 AssetGroups/Default Local Group.asset ・Playerビルドに含めるアセットを管理・初期状態に作成される Schemas/ ・Groupの詳細な設定を管理・GroupごとにSchemasが配置される AssetGroupTemplates/ ・Groupの新規作成時につかうテンプレ・新規でGroup Templeteを作成したときは AddressableAssetSettings に追加しなければならない DataBuilders/ ・ビルド、エディタ再生時に使用される設定・AssetBundleのビルド方法を定義したScriptableObjectが配置される DefaultObject.asset ・AddressableAssetSettings.assetの場所を示すScriptableObject ※ DataBuilders や AssetGroupTemples はあまり編集しない AssetBundle をビルドする 新規の場合h AddressableGroups で、 Build > New Build > Default Build Script を選択する すでにビルド済みの AssetBundle を更新する場合は Update a Previous Build Build される場所は以下で設定できる LoadBuildPath: Library/com.unity.addressables/[BuildTarget](デフォルト) Playerビルド時に一時的に StreamingAssets にコピーされることで Player ビルドに同梱される RemoteBuildPath: ServerData/[BuildTarget](デフォルト) プロジェクトルート直下に作成される ServerData 以下に作成された AssetBundle を RemoteLoadPath のURLに対応するようにアップロードする必要がある AssetBundle を読み込む 通常はアプリ起動時に自動的に Addressable の初期化処理が実行される 何らかの理由でスクリプトから初期化、実体化したい場合は Addressables.LoadAssetAsync() してから Instantiate() する 直接 Addressables.InstantiateAsync() する // Load してから Instatiate() var gameobject = await Addressables.LoadAssetAsync<GameObject>("Assets/Prefabs/Cube.prefab"); var obj = Instantiate(go); // 直接 Instatiate() Addressables.LoadAssetAsync<GameObject>("Assets/Prefabs/Cube.prefab"); 参考文献
- 投稿日:2022-01-31T20:43:42+09:00
C# - AES128で1ブロックだけ暗号化する
概要 AES128で16byteのbyte配列を暗号化・復号するサンプルコードです。 注意事項 投稿直前にMS DocのAesManagedクラスの説明を見に行ったらobsoleteな方法らしいことが分かった。。。orz Derived cryptographic types are obsolete. Use the Create method on the base type instead. けどせっかくなので反面教師として載せときます。 と思ったら1行変えるだけで問題ないようなので直しました。 ※セキュアなコーディングとかは詳しくないので、正式なものに使うのに耐えれるかは保証できません。 ソースコード AES128Sample.cs using System; using System.IO; using System.Security.Cryptography; class AES128Sample { [STAThread] static void Main(string[] args) { byte[] plain = new byte[]{0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xaa,0xbb,0xcc,0xdd,0xee,0xff}; byte[] key = new byte[]{0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f}; byte[] cipher; cipher = Encrypt1BlockAes128(plain, key); DumpArray(cipher); DumpArray(Decrypt1BlockAes128(cipher, key)); } static void DumpArray(byte[] a) { for ( int i=0; i<a.Length; i++ ) { Console.Write(a[i].ToString("X02")); } Console.WriteLine(); } static byte[] Decrypt1BlockAes128(byte[] cipher, byte[] key) { return EncDec1BlockAes128(cipher, key, false); } static byte[] Encrypt1BlockAes128(byte[] plain, byte[] key) { return EncDec1BlockAes128(plain, key, true); } static byte[] EncDec1BlockAes128(byte[] a, byte[] key, bool isEncryptMode) { const int N = 16; // 128bit = 16bytes if ( a == null || key == null ) { throw new ArgumentNullException(); } if ( a.Length != N || key.Length != N ) { throw new ArgumentException(); } byte[] buf = new byte[N]; //using (AesManaged aes = new AesManaged()) // obsoleted using (Aes aes = Aes.Create()) { aes.BlockSize = N*8; aes.Key = key; aes.IV = new byte[N]; // all zero. refer to https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/arrays/single-dimensional-arrays aes.Mode = CipherMode.ECB; aes.Padding = PaddingMode.None; ICryptoTransform encryptorOrDecryptor; if ( isEncryptMode ) { encryptorOrDecryptor = aes.CreateEncryptor(aes.Key, aes.IV); } else { encryptorOrDecryptor = aes.CreateDecryptor(aes.Key, aes.IV); } using (var ms = new MemoryStream(a)) using (var cs = new CryptoStream(ms, encryptorOrDecryptor, CryptoStreamMode.Read)) { cs.Read(buf, 0, N); } } return buf; } } 検算 Test Vector的なやつ FIPS-197 の C.1 AES-128 (Nk=4, Nr=10) PLAINTEXT: 00112233445566778899aabbccddeeff KEY: 000102030405060708090a0b0c0d0e0f round[10].output 69c4e0d86a7b0430d8cdb78070b4c55a 参考サイト MS Doc - AES class 暗号利用モード - Wikipedia FIPS-197
- 投稿日:2022-01-31T18:58:58+09:00
Prism コードサンプル学習:19-NavigationParticipation
Prism コードサンプル学習:19-NavigationParticipation はじめに 以下の記事の続きです。 https://qiita.com/mngreen/items/69d5f993b28d629db896 19-NavigationParticipation 本サンプルは、ボタンを押下するとタブコントロールにタブアイテムが追加されるサンプルです。 ボタンを押下するごとにタブのビューのカウントがインクリメントされます。 以下のINavigationAware.OnNavigateToメソッドを実装することで、UIの追加および遷移時にカウントがインクリメントされる処理を実装されています。 using Prism.Mvvm; using Prism.Regions; namespace ModuleA.ViewModels { public class ViewAViewModel : BindableBase, INavigationAware { private string _title = "ViewA"; public string Title { get { return _title; } set { SetProperty(ref _title, value); } } private int _pageViews; public int PageViews { get { return _pageViews; } set { SetProperty(ref _pageViews, value); } } public ViewAViewModel() { } public void OnNavigatedTo(NavigationContext navigationContext) { PageViews++; } public bool IsNavigationTarget(NavigationContext navigationContext) { return true; } public void OnNavigatedFrom(NavigationContext navigationContext) { } } } ユーザーがビューを追加するために必要な処理はとくになく、過去の記事で見た通りにIRegionManager.RequestNavigateメソッドを用いること、そしてTabControlにRegionManagerを用いて名前を与えることです。 今回のサンプルの変化点としては、VMでINavigationAwareインターフェイスを実現しているため、この処理でMvvmHelpersの処理が実行されている点が異なるところです。 実際にUI要素のDataContextからVMを取得し、実行されるということがわかります。 おわりに 今回は、RegionManager.RequestNavigateメソッドを利用し、タブアイテムが増えていくサンプルを見ていきました。 ただ、大きな流れ(クラス間の関連やメソッド呼び出し)はあまり大きく変わってはいませんでしたが、INavigationAwareインターフェイスを実現することでビュー遷移後に処理が実行される仕組みを読み取ることができました。 次回、20-NavigateToExistingViewsについて見ていこうと思います。
- 投稿日:2022-01-31T01:09:16+09:00
C#er、Delphiを学ぶ③(Delphiで依存性の注入やってみる)
はじめに Delphiを始めてちょうど1か月くらい経った。慣れたようで慣れないようで...なわけだが、一番思うのは「WEB上のDelphi関連の記事が少なすぎる」という問題である。とりわけ設計にまつわる記事をほぼほぼ見かけず、これじゃWell Architectなコード書けんやろと思う。そんな中、Delphi沼にちょいと足を踏み込んだ身として何かしら貢献できたらなぁーと思い、修行がてら書こうと思ったわけで。 仕様 はじめての「でるふぁいででぃーあい」といったところで、まずは単純な感じ。DIコンテナは使わず、コンストラクタにインターフェースを注入する、いわゆる「Poor man's DI」での実装。 ファイル(a.txt)から最初の1行を読みとる 読み取った内容を違うファイル(b.txt)に書き込む インターフェースの実装方法について エンバカの公式サイト(docwiki)みたら、インターフェースにGUIDついてた。しかも「TInterfacedObject」なるものも継承してる。なにこれ超怖い、わからない。検索したら、Delphiでのインターフェースの実装方法にまつわる以下の記事が出てきて参考になった。 インターフェース使っててハマりそうなところをつらつらと http://delphimaniacs.blogspot.com/2013/12/delphi-interface.html なるへそ。要は凝ったことしない限りGUIDつけんでもいいが、付けといた方が無難といったところか。ショートカット「Ctrl + Alt + G」でGUIDを追加ヨシッと。Visual Studioと同じキーの割り当て設定してるよね?(タイトル回収) 実装 FileReaderクラス ファイルを読み込む役割を担うクラス。GetContent関数の中で、ファイルの1行目の内容を返してくれる。 FileReader.pas unit FileReader; interface uses System.Classes; type IFileReader = interface ['{FA610526-A7B4-4C44-AB9D-143B50578D48}'] function GetContent(): string; end; TFileReader = class(TInterfacedObject, IFileReader) private FData: TStream; FDataReader: TStreamReader; public constructor Create(AFileName: string); destructor Destroy; override; function GetContent(): string; end; implementation uses System.SysUtils; constructor TFileReader.Create(AFileName: string); begin inherited Create; FData := TFileStream.Create(AFileName, fmOpenRead); FDataReader := TStreamReader.Create(FData); end; destructor TFileReader.Destroy; begin FreeAndNil(FDataReader); FreeAndNil(FData); inherited; end; function TFileReader.GetContent(): string; begin if not FDataReader.EndOfStream then begin Result := FDataReader.ReadLine; end; end; end. FileWriterクラス ファイルへの書き込みの役割を担うクラス。Write関数内で引数で渡された内容をファイルに吐き出す。 FileWriter.pas unit FileWriter; interface uses System.Classes; type IFileWriter = interface ['{7C912111-3757-40DD-94FD-173377F8FE95}'] procedure Write(AContent: string); end; TFileWriter = class(TInterfacedObject, IFileWriter) private FData: TStream; FDataWriter: TStreamWriter; public constructor Create(AFileName: string); destructor Destroy; override; procedure Write(AContent: string); end; implementation uses System.SysUtils; constructor TFileWriter.Create(AFileName: string); begin inherited Create; FData := TFileStream.Create(AFileName, fmCreate); FDataWriter := TStreamWriter.Create(FData); end; destructor TFileWriter.Destroy; begin FreeAndNil(FDataWriter); FreeAndNil(FData); inherited; end; procedure TFileWriter.Write(AContent: string); begin FDataWriter.WriteLine(AContent); end; end. FileService コンストラクタで受け取ったReader,Writerを元に、仕様を実現する役割を担うクラス(命名適当や、すまん )。 FileService.pas unit FileService; interface uses FileWriter, FileReader; type TFileService = class private FFileReader: IFileReader; FFileWriter: IFileWriter; public constructor Create(AFileReader: IFileReader; AFileWriter: IFileWriter); procedure Execute; end; implementation uses System.SysUtils; constructor TFileService.Create(AFileReader: IFileReader; AFileWriter: IFileWriter); begin inherited Create; FFileReader := AFileReader; FFileWriter := AFileWriter; end; procedure TFileService.Execute; var FileContent: string; begin FileContent := FFileReader.GetContent(); FFileWriter.Write(FileContent) end; end. エントリポイント(呼び出し元) Reader,Writerのインスタンスを生成し、コンストラクタに注入する。実装イメージは↓ FileReader := TFileReader.Create('C:\tmp\a.txt'); FileWriter := TFileWriter.Create('C:\tmp\b.txt'); FileService := TFileService.Create(FileReader, FileWriter); FileService.Execute(); さいごに こんな感じでビジネスロジックと永続化層を分離できましたとさ。一緒くたな感じでDelphiでユニットテストも書いてみたいな。メモリリークしてるやもですが許してくださいm(__)m 参考URL DIとかSOLID原則で参考になるなと思った記事 .NETにおけるSOLID設計原則とデザインパターン
- 投稿日:2022-01-31T01:04:04+09:00
C#で動画を作ってみた話
はじめに C#の知識が.Net Framework 3.5あたりで止まっているので、ちょっとまずいなと思っている黒髭和熊です。 まぁ、作るのには困らないんですけどね・・・ さて、今回ですが、Youtubeで動画を公開しているのですが、その動画のオープニングを作りたいなぁと思っていました。 で、昔から使ってるキャラクタを動かしたいなと思ったんですが、簡単にできないかなー?と思ったんです。 面倒なので、描いた絵をプログラムで動かせば思い通りにできるんじゃないか?ということで調べて作りました。 動画はこれ。 ちなみに、作った動画をDavinci Resolveで音をつけたりフェードアウトさせています。 そんなお話です。 使ったもの Visual Studio C# AForge.NET 概要 動画のオープニングだけじゃなくて、ほかの動画も作ることを考えています。 なので、アニメーションを行う部分は抽象化し、動画にする部分は共通の処理にしています。 ですが、そこはあまり重要ではないので触れません。(知りたい人がいれば書きます) 今回使用したAForgeをどう使っているかを紹介できればと思っています。 クラス図はこんな感じです。 Form1で管理して、描画は別のクラス、といった感じになります。 実際の画面はこんな感じ。 さて、処理についてですが、作ったプログラムに沿った形で説明していきます。 AForgeの使用方法にあまり関係ないところがありますが、クラス図と比較しながら見ていただければと思います。 AbstructAnimationクラスを実装したChannelOpeningクラスをForm1に登録します。 ChannelOpeningクラスあオープニング動画の描画を行うクラスになります。 Form1.cs private VideoFileWriter videoWriter = null; private Thread thread = null; private Bitmap bmp = null; private AbstractAnimation currentAnime = null; // ・・・省略・・・ private void Form1_Load(object sender, EventArgs e) { this.SetStyle(ControlStyles.ResizeRedraw, true); this.SetStyle(ControlStyles.DoubleBuffer, true); this.SetStyle(ControlStyles.UserPaint, true); this.SetStyle(ControlStyles.AllPaintingInWmPaint, true); cmbAnime.Items.Add(new AnimeTest(this)); cmbAnime.Items.Add(new IngressHyperion(this)); cmbAnime.Items.Add(new ChannelOpening(this)); cmbAnime.SelectedIndex = 0; } 実行はボタンのクリックで開始します。 このタイミングで、AForge.Video.FFMPEG.VideoFileWriterのインスタンスを生成します。 Form1.cs private void btnExecute_Click(object sender, EventArgs e) { // ・・・省略・・・ bmp = new Bitmap(AnimationConfig.bmpWidth, AnimationConfig.bmpHeight); // アニメーション初期化 currentAnime.Initalize(); if (AnimationConfig.isWriteMode == true) { var text = txtLength.Text; var movieLength = AnimationConfig.movieLength; AnimationConfig.movieLength = movieLength; AnimationConfig.writeFrameCnt = 0; videoWriter = new VideoFileWriter(); videoWriter.Open(@"C:\data\test4.mp4", bmp.Width, bmp.Height, AnimationConfig.framerate, VideoCodec.MPEG4); } VideoFileWriter.Open()メソッドで保存するファイル名や動画のサイズ、フォーマットを指定します。 ここで気を付けるべきはフォーマット。 AVIで保存すると結構大きなファイルになります。 フォーマットはVideoCodec列挙型で定義されています。 定義内容は公式ドキュメントのVideoCodec Enumerationを見てもらえばわかります。 私はVideoCodec.MPEG4を選択しています。 フレームレートはAnimationConfigクラスで60を指定しており、その値を利用しています。 ボタンを押したタイミングでスレッドを作成し、描画用メソッドdraw()を呼び出しています。 その中でChannelOpeningクラスの描画した内容をVideoFileWriterのWriteVideoFrameメソッドで書き込んでいます。 Form1.cs // ・・・省略・・・ thread = new Thread((ThreadStart)delegate () { long tm = 0; long frm = 0; while (thread != null) { tm = DateTime.Now.Ticks; draw(); // ・・・省略・・・ private void draw() { using (Graphics g = Graphics.FromImage(bmp)) { currentAnime.OnDraw(g); if (AnimationConfig.isWriteMode == true && videoWriter != null) { videoWriter.WriteVideoFrame(bmp); } } } 書き込みはすごく簡単で、VideoFileWriter.WriteVideoFrame()メソッドに作成したビットマップを渡すだけです。 書き込みが終わったら、Close()メソッドで、VideoFileWriterを閉じればOKです。 Form1.cs videoWriter.Close(); 終わりに ざっくりですが、C#で動画を作ってみた話を紹介しました。 Magick.NETも試したのですが、私の実装が悪かったせいか、動画を作れないことがあったので断念。 AForge.NETはそんなこともなく割と簡単に使えたので採用しました。 音もやればできるみたいですが、まぁ、音はDavinci Resolve使ったほうが楽だったので^^; 実際のコードはたぶんGithubに公開すると思います。 いつになるかわかりませんがw Githubにリポジトリ作りました。 こちらになります。 あ、最後に。 よかったら私のYoutubeのチャンネルも見てみてください。 そのうちこの記事の内容も動画にしようと思っています。
- 投稿日:2022-01-31T00:06:07+09:00
UnityでVRに物理的ボタンを作る(VR Beginner: The Escape Roomのコードを読んでみる)
この記事について タイトルだけを読むと「何言ってるんだ?」と思われるかもしれませんが、Unity上で「ボタン」というとUIボタンの記事が出てきてしまうので「物理的なボタン」と書かせていただきました。 この記事で実装したいのは以下のようなものです。 3Dオブジェクトがボタンとして存在して、それをコントローラーで一定上押すと入力処理が走るというのが今回作成するものです。 これが作りたかったんや! Twitterにあげた動画 参考プロジェクト この記事で使用しているソースコードはUnityのチュートリアルプロジェクトの「VR Beginner: The Escape Room」のものを利用しております。 VR Beginner: The Escape Room 環境 Mac unity 2020.3.25 実装! オブジェクトの作成については省略します。 キューブを作って配置しただけです。 今回の中で最小の単位は3Dオブジェクトが一つだけで大丈夫です。 失礼、あとはプレイヤーも必要ですが、今回の実装ではスルーします。 がんばれ。 そしてこのオブジェクトに対してスクリプトをくっつけます。 いきなり完成形を貼り付けます。中に書いてあるコメントがそのまま説明になるかと思います。 ButtonManager using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; public class ButtonManager : MonoBehaviour { [System.Serializable] public class ButtonPressedEvent : UnityEvent { } public ButtonPressedEvent OnButtonPressed; public Vector3 Axis = new Vector3(0,-1,0 ); public float MaxDistance; public float ReturnSpeed = 10.0f; Vector3 m_StartPosition; Rigidbody m_Rigidbody; Collider m_Collider; bool m_Pressed = false; void Start() { m_Rigidbody = GetComponent<Rigidbody>(); m_Collider = GetComponentInChildren<Collider>(); m_StartPosition = transform.position; } void FixedUpdate() { // 押し込む方向の指定(自身の方向をワールド空間内での方向へ変換する) Vector3 worldAxis = transform.TransformDirection(Axis); // MAxDistanceを利用して最大移動地点を指定する Vector3 end = transform.position + worldAxis * MaxDistance; // スタート時点の位置からの現在までの移動量を測定(平方根) float m_CurrentDistance = (transform.position - m_StartPosition).magnitude; RaycastHit info; float move = 0.0f; // 戻る方向に物体がなければ元の位置に戻る処理。あったら逆方向(押し込まれる方向)へ移動する。 if (m_Rigidbody.SweepTest(-worldAxis, out info, ReturnSpeed * Time.deltaTime + 0.005f)) {//hitting something, if the contact is < mean we are pressed, move downward move = (ReturnSpeed * Time.deltaTime) - info.distance; } else { move -= ReturnSpeed * Time.deltaTime; } // 初期地点から最大移動距離の間に収まる値に位置が変換される float newDistance = Mathf.Clamp(m_CurrentDistance + move, 0, MaxDistance); // 新しい位置を設定する m_Rigidbody.position = m_StartPosition + worldAxis * newDistance; // ボタンが押されていない状態 & 新しく移動する予定の場所までの距離と最大移動距離が近侍であるならIF内処理 // ボタンが押されている状態 & しく移動する予定の場所までの距離と最大移動距離が近侍でないならELSEIF内処理 if (!m_Pressed && Mathf.Approximately(newDistance, MaxDistance)) {//was just pressed m_Pressed = true; OnButtonPressed.Invoke(); } else if (m_Pressed && !Mathf.Approximately(newDistance, MaxDistance)) {//was just released m_Pressed = false; } } } あとはオブジェクトにRigidbodyをアタッチして、is Kinematicにチェックを入れて重力を使用するのチェックを外します。 アタッチしたスクリプトの変数に値を設定するのですが、設定値は以下の通りです。 項目名 内容 On Button Pressed ボタンが押された時の処理 軸 ボタンが押される方向 最大距離 どれだけ押されたらイベントが発生するか 戻るスピード ボタンが抑えれる際にオブジェクトが認識される距離 これだけです! あとはご自由にここに発動したい処理を入れて実行してみましょう! 終了! おすすめ VR Beginner: The Escape Room * おそらくやりがいことが全て載ってる神プロジェクト * VRゲームをUnityで作りたいならこのプロジェクトを読めば大体載ってそう * UI関連は載っていないので注意 * XR Interaction Toolkit対応 * Device BaseなのでActionBaseでゲームを作りたい人は注意 XR Interaction Toolkit Examples * XR Interaction Toolkitのサンプルプロジェクト * こちらはUI関連のサポートもあるし各オブジェクトの動作やテレポートなんかも載っているがちょっと難しく感じる * 上が初心者向けならこっちは慣れてきた時に触ると良いかな? * こちらはDeviceBaseとActionBaseが両方とも触れる