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

Unity WebGLでおえかきした画像の保存

はじめに おえかきアプリを作っていて保存機能を実装することになったが、WebGLなのでFile.WriteAllBytes()もApplication.persistentDataPath使えない…となったので、javascriptプラグインを作ったお話。 ※おえかきアプリの実装はメインではないので割愛します。 開発環境 Unity 2020.3.25f1 Visual Studio Code プラグインの作成(.jslib) Unityには、ブラウザのスクリプトと通信を行う方法が大きく2つあります。 1. Application.ExternalCall() か SendMessage()を使う。 2. プラグイン(.jslib)を作り、コードを記述。 今回は後者の方法で実装を進めます。 plugin.jslib mergeInto(LibraryManager.library, { ExportTextureJS:function(base64){ // 手順1 const image = Pointer_stringify(base64); // 手順2 var bin = atob(image.replace(/^.*,/, '')); var buffer = new Uint8Array(bin.length); for (var i = 0; i < bin.length; i++) { buffer[i] = bin.charCodeAt(i); } try{ var blob = new Blob([buffer.buffer], { type: 'image/png' }); }catch (e){ return; } // 手順3 var url = (window.URL || window.webkitURL); var dataUrl = url.createObjectURL(blob); var a = document.createElement('a'); a.download = "picture.png"; a.href = dataUrl; a.click(); }, }); 手順1 javascript文字列への変換 const image = Pointer_stringify(base64); まず引数から入ってきたbase64文字列を、ヘルパー関数Pointer_stringify()を使用してJavaScript文字列に変換します。 これは文字列がemscripten ヒープ内のポインターとして渡されるためです。 手順2 Blob形式への変換 var bin = atob(image.replace(/^.*,/, '')); var buffer = new Uint8Array(bin.length); for (var i = 0; i < bin.length; i++) { buffer[i] = bin.charCodeAt(i); } try{ var blob = new Blob([buffer.buffer], { type: 'image/png' }); }catch (e){ return; } 変換した文字列をBlob形式のFileに変換します。 手順3 アンカー要素を使用して保存 var url = (window.URL || window.webkitURL); var dataUrl = url.createObjectURL(blob); var a = document.createElement('a'); a.download = "picture.png"; a.href = dataUrl; a.click(); HTMLのアンカー要素<a>を作ってdataURLを渡し、クリックイベントを強制的に呼び出します。 プラグイン呼び出し側の実装(C#) using System; using UnityEngine; using UnityEngine.UI; using System.Runtime.InteropServices; public class ExportTexture : MonoBehaviour { #if UNITY_WEBGL && !UNITY_EDITOR [DllImport("__Internal")] private static extern void ExportTextureJS(string base64); #endif [SerializeField] private RawImage _image; public void OnPressExport() { #if UNITY_WEBGL && !UNITY_EDITOR var texture = (Texture2D)_image.texture; var png = texture.EncodeToPNG(); var base64 = Convert.ToBase64String(png); ExportTextureJS("data:image/png;base64," + base64); #endif } } ブラウザでのみ動くため#if UNITY_WEBGL && !UNITY_EDITOR #endifで括っています。 プラグインの関数のインポート [DllImport("__Internal")] private static extern void ExportTextureJS(string base64); jslibに記述した関数を呼び出せるようにしています。 この時、関数名と引数を一致させる必要があります。 テクスチャのbase64文字列への変換、関数の呼び出し var texture = (Texture2D)_image.texture; var png = texture.EncodeToPNG(); var base64 = Convert.ToBase64String(png); ExportTextureJS("data:image/png;base64," + base64); 今回はRawImageを使用しているので、RawImageのtextureをTexture2Dにキャストして、EncodeToPNG()を使用してbyte[]に変換。さらにそれをSystem.Convert.ToBase64String()に渡してbase64形式に変換しています。 ExportTextureJS()に渡す際に"data:image/png;base64,"と連結しているのは、今回HTMLのアンカー要素に埋め込む手順を踏んでいるためです。(ただここでやる理由はない) 動作物 おわりに 物凄く雑に紹介しましたが、面倒くさいというのが伝わったなら幸いです。 追伸:PlayCanvasならこんなに面倒くさくない リンク・参考記事など GitLab Unity(WebGL)のC#とブラウザのJavaScriptの連携 JS: base64文字列をBlob形式のFileに変換する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

マテリアル設定を一括コピーして VRoid で生成した VRChat 用アバターのマテリアル再設定を自動化する

私は VRMConverterForVRChat を利用して VRoid で生成した VRM 形式のアバターを VRChat に持ち込んでいます。 VRoidAvatarSetupUtils を併用すれば VRCSDK パラメータの再設定1やオブジェクトのコピーが自動化できて便利です。それでも、シェーダの変更やシェーダプロパティといったマテリアルの設定は手動で行う必要があります。インポートのたびに繰り返すのは面倒ですよね。 というわけで、ソースとして指定したマテリアルの設定を取得して複数のマテリアルに一括で適用するツールを作成してみました。 MaterialSetupUtils の導入方法 Booth もしくは GitHub から取得した Zip ファイルを展開 (解凍) します。 .unitypackage ファイルを、Assets にドラッグ&ドロップします。 MaterialSetupUtils の使い方 メニューバーで [Tools] - [MaterialSetupUtils] を開きます。    Source Material としてパラメータをコピーしたいマテリアルを指定します。 コピーしたいシェーダプロパティにチェックを入れます。Filter (Prefix) でプロパティをフィルタできます(前方一致、大文字と小文字を区別)。また、画面下部でチェックの一括設定やで保存・読込ができます。 Assets でコピー対象のマテリアルを選択します(複数選択可)。このとき、検索機能で対象を絞り込んだり、★で検索条件を保存しておくと便利です。 画面下部の Copy を押下すると、選択中のマテリアルに対してプロパティのコピー処理が実行されます。 不具合報告 GitHub の Issue もしくは 私の Twitter アカウントまでお気軽にご連絡ください。 参考リンク VRM から VRChat へアップロードする流れ VRoid から生成した VRChat 用アバターのパラメータ再設定(DynamicBone 含む)を自動で行う Material の Shader プロパティーを表示 その 1(Editor 拡張) UnityEngine.Material UnityEngine.Shader Playable Layers と Expressions をコピーする機能を追加したフォーク版を公開しています。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Unity]Cube map テクスチャをモデルに投影する

小ネタ。 XR関係が多いと思いますが、何らかの経緯で取得したテクスチャがキューブマップ(Cube map)で、それをモデル上に投影しようと思ったとします。というのが今回の課題。 texCUBE を使う シェーダには3次元ベクトルでキューブマップを参照する関数 texCUBE があり、これを利用することになるでしょう。 Skybox シェーダを使う すでに texCUBE を使用した Skybox シェーダがありますから、これを使えばよさそうです。が、そのままではうまくいきません。 Skybox シェーダの何が足りないのか Unity はバージョンごとに組み込みシェーダのソースコードが公開されていますので、 ここから Built in Shaders をダウンロードして DefaultResourcesExtra/Skybox.shader を見てみますと、頂点シェーダに o.texcoord = v.vertex.xyz; という記述があります。Cube map を引くための座標にモデルのベクトルを直接投入していますね。通常の Skybox は移動することがないのでこれで良いのですが、カメラや物体座標も適用しないと任意のモデルには貼り付けられません。 修正 //o.texcoord = v.vertex.xyz; o.texcoord = mul(UNITY_MATRIX_MV, v.vertex); 先ほどの行をこのように修正して、Quadなどでも Cubemap(のうちそのモデルが視界を覆っている部分)を表示できるようになりました。 実際に修正したものを利用するには、この Skybox.shader を自身のプロジェクトにコピーして、シェーダの名前を変更するのがいいでしょう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】1文字ずつ文字を表示する処理

はじめに 今後エンジニアとして活動するか分からないので,ここを一旦の区切りとし,自分が作ったものの中で皆の役に立ちそうなものを共有します。 メイン 今回のスクリプトには,1文字ずつ表示する機能の他に下記のものを実装しました。 自動進行 一時停止 速度調整 この他に,UnityのTextMeshProに対応しています。 using UnityEngine; using TMPro; public class SentenceDisplay : MonoBehaviour { [SerializeField, TextArea] private string[] _sentences; [SerializeField] private float _charaDisplayInterval = 0.1f; [SerializeField] private float _textAutoProgressInterval = 2.0f; [SerializeField] private bool _auto, _highSpeed, _wait; [SerializeField] private float _highSpeedValue = 2; private float _progressTimer; private float _autoProgressTimer; // リッチテキストが進行中かどうか private bool _inRichTextDuringProgress; private string _currentSentence; // 現在の文 private int _currentLineIndex; // 現在の行数 private int _currentCharaCount; // 現在の文字数 private TextMeshProUGUI _textMeshPro; private void Awake() { _textMeshPro = GetComponentInChildren<TextMeshProUGUI>(); } private void Start() { SetSentence(); } private void Update() { var inputAction = Input.GetMouseButtonDown(0); // 文字の表示が完了している if (SentenceDisplayIsComplete()) { if (_auto) _autoProgressTimer += Time.deltaTime; bool autoTimeElapsed = _textAutoProgressInterval <= _autoProgressTimer; // 未表示のテキストが残っている,尚且つ(待機時間が完了,またはボタンが押される) if (_currentLineIndex < _sentences.Length && (autoTimeElapsed || inputAction)) { SetSentence(); } return; } // 文字の表示が完了していない else { _autoProgressTimer = 0; if (inputAction) { // 文字を全て表示 _textMeshPro.text = _currentSentence; _currentCharaCount = _currentSentence.Length; } } var specifiedRangeIn = _currentCharaCount < _currentSentence.Length; if (!_wait && specifiedRangeIn) { // 次に表示する文字によって処理を変更する switch (_currentSentence[_currentCharaCount]) { // RichTextを進行中に case '<': _inRichTextDuringProgress = true; break; // RichTextを終了 case '>': _inRichTextDuringProgress = false; _currentCharaCount++; return; } if (_inRichTextDuringProgress) { _currentCharaCount++; return; } _progressTimer += Time.deltaTime; if (_highSpeed) _progressTimer *= _highSpeedValue; if (_charaDisplayInterval < _progressTimer) { _progressTimer = 0f; _textMeshPro.text = _currentSentence.Substring(0, _currentCharaCount + 1); _currentCharaCount++; } } } private void SetSentence() { // 現在のテキストを更新 _currentSentence = _sentences[_currentLineIndex]; _currentLineIndex++; _textMeshPro.text = string.Empty; _currentCharaCount = 0; } private bool SentenceDisplayIsComplete() => _currentCharaCount == _currentSentence.Length; } かなりシンプルな構造だと思うので,機能追加や修正もしやすいと思います。 他,セットするテキストの配列をJsonから取得すると,使用の幅が広がるかなと(本来ならこの取得方法含めて記事を書く予定だった)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】テキストを1文字ずつ表示する処理

はじめに 今後エンジニアとして活動するか分からないので,ここを一旦の区切りとし,自分が作ったものの中で皆の役に立ちそうなものを共有します。 メイン 今回のスクリプトには,1文字ずつ表示する機能の他に下記のものを実装しました。 自動進行 一時停止 速度調整 この他に,UnityのTextMeshProに対応しています。 using UnityEngine; using TMPro; public class SentenceDisplay : MonoBehaviour { [SerializeField, TextArea] private string[] _sentences; [SerializeField] private float _charaDisplayInterval = 0.1f; [SerializeField] private float _textAutoProgressInterval = 2.0f; [SerializeField] private bool _auto, _highSpeed, _wait; [SerializeField] private float _highSpeedValue = 2; private float _progressTimer; private float _autoProgressTimer; // リッチテキストが進行中かどうか private bool _inRichTextDuringProgress; private string _currentSentence; // 現在の文 private int _currentLineIndex; // 現在の行数 private int _currentCharaCount; // 現在の文字数 private TextMeshProUGUI _textMeshPro; private void Awake() { _textMeshPro = GetComponentInChildren<TextMeshProUGUI>(); } private void Start() { SetSentence(); } private void Update() { var inputAction = Input.GetMouseButtonDown(0); // 文字の表示が完了している if (SentenceDisplayIsComplete()) { if (_auto) _autoProgressTimer += Time.deltaTime; bool autoTimeElapsed = _textAutoProgressInterval <= _autoProgressTimer; // 未表示のテキストが残っている,尚且つ(待機時間が完了,またはボタンが押される) if (_currentLineIndex < _sentences.Length && (autoTimeElapsed || inputAction)) { SetSentence(); } return; } // 文字の表示が完了していない else { _autoProgressTimer = 0; if (inputAction) { // 文字を全て表示 _textMeshPro.text = _currentSentence; _currentCharaCount = _currentSentence.Length; } } var specifiedRangeIn = _currentCharaCount < _currentSentence.Length; if (!_wait && specifiedRangeIn) { // 次に表示する文字によって処理を変更する switch (_currentSentence[_currentCharaCount]) { // RichTextを進行中に case '<': _inRichTextDuringProgress = true; break; // RichTextを終了 case '>': _inRichTextDuringProgress = false; _currentCharaCount++; return; } if (_inRichTextDuringProgress) { _currentCharaCount++; return; } _progressTimer += Time.deltaTime; if (_highSpeed) _progressTimer *= _highSpeedValue; if (_charaDisplayInterval < _progressTimer) { _progressTimer = 0f; _textMeshPro.text = _currentSentence.Substring(0, _currentCharaCount + 1); _currentCharaCount++; } } } private void SetSentence() { // 現在のテキストを更新 _currentSentence = _sentences[_currentLineIndex]; _currentLineIndex++; _textMeshPro.text = string.Empty; _currentCharaCount = 0; } private bool SentenceDisplayIsComplete() => _currentCharaCount == _currentSentence.Length; } かなりシンプルな構造だと思うので,機能追加や修正もしやすいと思います。 他,セットするテキストの配列をJsonから取得すると,使用の幅が広がるかなと(本来ならこの取得方法含めて記事を書く予定だった)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オーブをVFXGraphでつくる

VFXGraph修行中メモ 完成品 元ネタ ParticleSystemで作られてたやつをもとにVFXGraphで再実装した。 作り方 VFXGraph VFXShaderGraph: Orbのリング部分 5つのパーツから構成される Circle: オーブの輪郭 EnergyLineHead: エネルギー線の先端軌跡 EnergyLine: エネルギー線 OrbCore: オーブの中心の光 Smoke: オーブから出力される煙 1. Circle: オーブの輪郭 VFXShaderGraphのリング - 極座標でリングを作る. - SmoothStepで輪郭をくっきりさせる Circle - 3つのリングを一定間隔で生成して動きのあるリングを作る - VFXShaderGraphのリングを描画要素に指定 - サイズはLifeに応じて大きくしつつ、Alphaは開始と終わりで薄くする. 2. EnergyLineHead: エネルギー線の先端軌跡 EnergyLineHead - 中心付近からランダム位置に生成、角度とベクトルもランダムに設定 - Turbulenceで軌跡をいいかんじにバラけさせる - TriggerEventRate(OverTime)でEnergyLine用のイベントを発火する 3. EnergyLine: エネルギー線 EnergyLine - 末尾サイズを少しすぼめつつ、色は末尾を透明に. - HDRColorで光らせる 4. OrbCore: オーブの中心の光 OrbCore - 開始角度とLifetime、SpawnRateをいい感じに調整してウニョウニョ動いているように見せる. - テクスチャは煙っぽいものやウニョウニョ感が出るものを使う 5. Smoke: オーブから出力される煙 Smoke - RandomVelocity/RandomAngleで射出 - Alphaは徐々に薄く、色は暗めに 感想 構成要素の分解ができれば意外と作れる..! Turbulenceで乱数軌道を作るところと、TriggerEventRate(OverTime) xOverDistanceがハマリポイントだった.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む