20210806のUnityに関する記事は11件です。

URP非対応のアセットを使用する際のシェーダー設定関連

手順を忘れると、URP対応時に時間を取られるのでメモとして記録します。 もっと簡単に設定できる方法があれば、コメントを頂けると助かります。 URPに対応していないシェーダーがある場合 テクスチャが割り当てられていない状態の表示になります。 URP対応する場合の基本的な方法 以下をクリックすると一般的なシェーダーはURPに対応したシェーダーに一括で変換することができる。 Edit > RenderPipeline > UniversalRenderPipeline > UpgradeProjectMatelialsToUniversalRPMaterials 上手くシェーダーが変換されない条件 カスタムシェーダーを使用している場合(もう少し詳細な条件がありそう) Prefabにマテリアルが組み込まれている場合 カスタムシェーダーを使用していた場合の対応 一つづつピンクに表示されているマテリアルを選択し、標準のシェーダーに変更する。 ※複数同時にシェーダーを変更しようとすると、テクスチャが剥がれてしまうので、元のテクスチャを探すのが面倒になります。 UpgradeProjectMatelialsToUniversalRPMaterialsを実行する。ここでテクスチャが表示されているはず。 テクスチャに透過する部分がある場合、透過しない状態になっているので、AlpahaClippingにチェックを入れる。 Prefabにマテリアルが組み込まれている場合の対応 本ケースの場合、シェーダーの編集自体ができなくなっているため、PrefabのInspecterからExtractMaterialsをクリックし、マテリアルをPrefab外に出力すると、編集が可能になる。 UpgradeProjectMatelialsToUniversalRPMaterialsを実行する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Symbolアカウントに登録したメタデータを取得するバックエンドをNapkinで自作する

はじめに 本記事は前回の記事(Symbolアカウントにメタデータ登録するバックエンドをNapkinで自作する)で作成したAPIにアクセスし、武器データを取得するためのAPIをNapkinで作成する方法の解説記事です。 主にマルチシグアカウントのメタデータ取得の方法なので、そのあたりに関することをしたい人には役に立つかと思います。たまたま初めて本記事にたどり着いた方は事前知識として前回記事を一度ご覧いただくことをおすすめします。 準備するもの ■Symbolデスクトップウォレット こちらの記事を参考にお持ちでない場合はインストールしてください https://note.com/nembear/n/n56d2c9a28e8a ※ただし、本番稼働するまではテストネットで進めましょう テストネット上で、 ・Gameアカウント ・Playerアカウント をそれぞれ準備しておきましょう。 ■Napkinアカウント こちらを参考に作成しておいてください https://paiza.hatenablog.com/entry/2021/07/21/130000 モジュールと環境変数を準備しておいてください [モジュール] ・symbol-sdk ・request-promise-native [環境変数] ・APIKEY こちらは前回と同じです ・SOURCEADDRESS こちらはゲームアカウントのアドレスです。そのアドレスが署名をしているメタデータであればゲーム内で使用できるという判別のためです。(ここでのアドレスとはウォレットから取得できるアドレスです。) ソースコード解説 以下全文です。後ほどブロックごとに解説します。 import requestPromise from "request-promise-native"; import * as symbol from "symbol-sdk"; const SOURCEADDRESS = process.env.SOURCEADDRESS; const addressUint8Array = symbol.RawAddress.stringToAddress(SOURCEADDRESS); const addressBase32 = symbol.Convert.uint8ToHex(addressUint8Array); const getDataFromSymbol = async (NODE, param) => { const result = await requestPromise(`${NODE}${param}`); return result; }; const isValidJson = (value) => { try { JSON.parse(value); } catch (e) { return false; } return true; }; export default async (req, res) => { if(req.query.apikey ==! process.env.apikey) { throw TypeError("Wrong APIKEY ERROR"); } else { let NODE = "https://sym-main-01.opening-line.jp:3001/"; if (req.query.mode == "TEST_NET") { NODE = "https://sym-test-01.opening-line.jp:3001/"; } const address = req.query.address; const param1 = "account/" + address + "/multisig"; const maltisigData = await getDataFromSymbol(NODE,param1); const maltisigDataJson = JSON.parse(maltisigData); const multisigAddresses = maltisigDataJson.multisig.multisigAddresses; const rawAddresses = []; for (const multisigAddresse of multisigAddresses) { const unit8address = symbol.Convert.hexToUint8(multisigAddresse); const rawAddress = symbol.RawAddress.addressToString(unit8address); rawAddresses.push(rawAddress); } const metaDataValues = []; for (const rawAddress of rawAddresses) { let isAcquiredMetaData = false; let count = 1; while (isAcquiredMetaData == false) { const param2 = "metadata?pageNumber=" + count + "&targetAddress=" + rawAddress; const metaDataBase = await getDataFromSymbol(NODE, param2); const metaDatas = JSON.parse(metaDataBase); if (metaDatas.data.length === 0) { isAcquiredMetaData = true; break; } for (const metaData of metaDatas.data) { if (metaData.metadataEntry.sourceAddress == addressBase32) { const metaValue = Buffer.from(metaData.metadataEntry.value, "hex").toString("utf-8"); if (isValidJson(metaValue)) { metaDataValues.push(JSON.parse(metaValue)); isAcquiredMetaData = true; } } } count++; } } const data = { "data": metaDataValues, }; res.json(data); } } import requestPromise from "request-promise-native"; import * as symbol from "symbol-sdk"; const SOURCEADDRESS = process.env.SOURCEADDRESS; const addressUint8Array = symbol.RawAddress.stringToAddress(SOURCEADDRESS); const addressBase32 = symbol.Convert.uint8ToHex(addressUint8Array); const getDataFromSymbol = async (NODE, param) => { const result = await requestPromise(`${NODE}${param}`); return result; }; const isValidJson = (value) => { try { JSON.parse(value); } catch (e) { return false; } return true; }; ゲームアカウントのアドレスはウォレットから取得できるRawAddressを後ほどSymbol APIでデータを取得するためにBase32のアドレスにするためごにょごにょします。 あとは、ノードからデータを取得するための関数 getDataFromSymbolと メタデータがJSON形式かどうか判別するための関数isValidJsonです。 if(req.query.apikey ==! process.env.apikey) { throw TypeError("Wrong APIKEY ERROR"); } else { let NODE = "https://sym-main-01.opening-line.jp:3001/"; if (req.query.mode == "TEST_NET") { NODE = "https://sym-test-01.opening-line.jp:3001/"; } const address = req.query.address; const param1 = "account/" + address + "/multisig"; const maltisigData = await getDataFromSymbol(NODE,param1); const maltisigDataJson = JSON.parse(maltisigData); const multisigAddresses = maltisigDataJson.multisig.multisigAddresses; const rawAddresses = []; for (const multisigAddresse of multisigAddresses) { const unit8address = symbol.Convert.hexToUint8(multisigAddresse); const rawAddress = symbol.RawAddress.addressToString(unit8address); rawAddresses.push(rawAddress); } 例のごとくAPIKEYが正しいかどうか判別し、正しければテストネットかメインネットなのか。なお、テストネット、メインネットともにオープニングライン社のノードをお借りしています。 maltisigData最初にまずはプレイヤーのアドレスが署名者のマルチシグアカウントを調べます。 maltisigDataJsonJSON形式にしmultisigAddressesそのプレイヤーアカウントが署名者のマルチシグアカウントを全て取得。 それぞれ、RawAddressにコンバート const metaDataValues = []; for (const rawAddress of rawAddresses) { let isAcquiredMetaData = false; let count = 1; while (isAcquiredMetaData == false) { const param2 = "metadata?pageNumber=" + count + "&targetAddress=" + rawAddress; const metaDataBase = await getDataFromSymbol(NODE, param2); const metaDatas = JSON.parse(metaDataBase); if (metaDatas.data.length === 0) { isAcquiredMetaData = true; break; } for (const metaData of metaDatas.data) { if (metaData.metadataEntry.sourceAddress == addressBase32) { const metaValue = Buffer.from(metaData.metadataEntry.value, "hex").toString("utf-8"); if (isValidJson(metaValue)) { metaDataValues.push(JSON.parse(metaValue)); isAcquiredMetaData = true; } } } count++; } } const data = { "data": metaDataValues, }; res.json(data); 取得したRawAddressが持つメタデータを取得します。その際に、そのメタデータの署名者がゲームアカウントであるかを確認。そうであれば配列に格納していく。を繰り返します。 これで、プレイヤーアカウントに紐付いているこのゲームに関するメタデータが取得できましたので、それをJSONで返して終わりです。 あとは、例えばUnityなどでこのAPIに接続してデータ取得。そのデータに沿って武器を格納庫に入れる。こんな感じでゲームとSymbolの連携が完了です。 繰り返しますが、メタデータ取得のためには事前に登録しておく必要があるので、事前にこちらの記事をお読みください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(C#)System.RandomとUnityEngine.Randomの違い

はじめに unityでC#を使っている大学生です。スクリプトを書いていて躓いたことを備忘録として残します。 同じ疑問をお持ちになった方是非お役立てください。 今回は、乱数についてです。 Randomクラス使用時の落とし穴 差異はどこにあるか 同時に System名前空間のRandomクラスを用いた疑似乱数生成の記述方法 UnityEngine名前空間のRandomクラスを用いた疑似乱数生成の記述方法 についても触れていきます。 落とし穴 まず無知な私は、UnityEngine名前空間を使っていたので、愚直にRandomクラスを使おうとしてエラーを吐きました。 // エラー 'Random' は、'UnityEngine.Random' と 'System.Random' 間のあいまいな参照です int a = Random.Range(0,10); RandomクラスがSystem名前空間とUnityEngine名前空間の両方に存在するので、どちらのクラスを使用しているのか分からないのが問題なようです。 解決法 クラスを使う際に名前空間を指定すると簡単に解決できます。 // UnityEngine名前空間のクラスであることを明示(0以上10未満の乱数が返る) int RUNDOM_NUMBER_1 = UnityEngine.Random.Range(0,10); // System名前空間のクラスであることを明示(0以上10未満の乱数が返る) System.Random r = new System.Random(); int RANDOM_NUMBER_2 = r.Next(10); 使い分け 大きな差はインスタンスを生成するか否かのようです。 System名前空間ではインスタンスを生成する為、いくつも乱数が必要な場合に有用性を発揮します。 対してUnityEngine名前空間ではそのようなステップを踏まないので、乱数の取得が簡単です。 またSystem名前空間の方は乱数の返り値が整数ですが、UnityEngine名前空間のほうでは浮動小数点数です。 パフォーマンスでも差があるようで、UnityEngine.Randomのメソッドのほうが高速だそうです。 乱数を複数個扱わないのであればこちらを使うに越したことはなさそうですね。 (参考) Unity公式ドキュメント Versus System.Random This class has the same name as the .NET Framework class System.Random and serves a similar purpose, but differs in some key ways: Static vs instanced UnityEngine.Random is a static class, and so its state is globally shared. Getting random numbers is easy, because there is no need to new an instance and manage its storage. However, static state is problematic when working with threads or jobs (the generator will error if used outside the main thread), or if multiple independent random number generators are required. In those cases, managing instances of System.Random would be a better option. Performance Methods in UnityEngine.Random have been measured to be between 20% and 40% faster than their equivalents in System.Random. (和訳) System.Randomとの違い このクラスは、.NET Framework のクラス System.Random と同じ名前で、同じような目的を持っていますが、いくつかの重要な点で異なります。 静的 vs インスタンス UnityEngine.Randomは静的なクラスであり、その状態はグローバルに共有されます。インスタンスを生成したり、ストレージを管理したりする必要がないので、乱数の取得が容易です。しかし、スレッドやジョブを使用する場合や、複数の独立した乱数生成器が必要な場合には、静的な状態は問題となります。このような場合には、System.Randomのインスタンスを管理する方が良いでしょう。 パフォーマンス UnityEngine.Randomのメソッドは、System.Randomのメソッドに比べて20%から40%高速であることが測定されています。 記述方法 System.Randomを使う // シード値(1000)を使用して初期化 // シード値が変わらなければ毎回同じ乱数を返す System.Random r = new System.Random(1000); // 0以上10未満の乱数を整数で返す int RANDOM_NUMBER_1 = r.Next(10); // -10以上10未満の乱数を整数で返す int RANDOM_NUMBER_2 = r.Next(-10,10); // 0以上Int32.MaxValue(32bit符号付き整数の最大有効値:2147483647)未満の乱数を整数で返す int RANDOM_NUMBER_3 = r.Next(); Random.Next メソッド Next(Int32) :指定した最大値より小さい 0 以上のランダムな整数を返します。 Next(Int32, Int32) :指定した範囲内のランダムな整数を返します。 Next() :0 以上のランダムな整数を返します。 またMicrosoft公式ドキュメントに プロセス開始時に一度だけジェネレーターにシードが与えられ,その後は完全にスクリプトの制御下に置かれることになります。 との記載があるのでシード値を乱数(可変のもの)にすれば毎度違う乱数を返すようにもできます。 UnityEngine.Randomを使う // 0以上10未満の乱数を浮動小数点数で返す int RANDOM_NUMBER = UnityEngine.Random.Range(0,10); Random.Range public static float Range(float minInclusive, float maxInclusive); Returns a random float within minInclusive..maxInclusive. (訳:minInclusive..maxInclusive内のランダムな浮動小数点数を返します。) 終わりに はじめは同じに見えましたがよくよく調べるとしっかりと違いがありました。 目的に合わせて使い分けていきたいです。 そしてどれも指定した最大値"未満"の乱数を返すという点に注意が必要だと思いました。 参考記事 [https://docs.microsoft.com/ja-jp/dotnet/api/system.random.next?view=net-5.0] [https://docs.unity3d.com/ScriptReference/Random.html]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity(C#)】UniWebView4使ってUnityでWebView表示

はじめに UniWebView4とはアプリ内でWEBサイトを表示したい場面で役に立つアセットです。 初めて使用する際にいろいろとわからないことが多かったので メモを残しておきます。 バージョン情報 諸々名前 バージョン Unity 2020.3.4f1 UniWebView4 4.9.0 UniRx 7.1.0 デモ フルスクリーンで半透明のWebViewを表示し、WebView上のボタン押下でCubeを動かすサンプルです。 コード まずはHTMLです。 デモなので超簡易です。作成したらStreamingAssetsに保存します。 <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=yes" /> <head> <title>Sample</title> </head> <head> <a href="web-view-demo://close"> <button style="position: fixed; top: 10px; right: 10px; padding: 6px 40px;">閉じる</button> </a> </body> <li> <a href="web-view-demo://up"> <button>↑</button> </a> </li> <li> <a href="web-view-demo://down"> <button>↓</button> </a> </li> <li> <a href="web-view-demo://right"> <button>→</button> </a> </li> <li> <a href="web-view-demo://left"> <button>←</button> </a> </li> フルスクリーン表示する際はHTML側でレスポンシブ対応しておかないとios端末で正しく表示されませんでした。 <meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=yes" /> 次にC#側のWebView生成コードです。 using UniRx; using UniRx.Triggers; using UnityEngine; /// <summary> /// WebViewクラス /// </summary> public class WebViewDemo { /// <summary> /// UniWebView /// </summary> private UniWebView _uniWebView; /// <summary> /// WebViewを生成 /// </summary> /// <param name="player">動かすオブジェクト</param> /// <param name="backGroundColor">背景色</param> /// <param name="url">開きたいURL</param> public void ShowWebViewPlayerController(string url, Transform player, Color? backGroundColor = null) { //既に存在している場合は開くだけ if (_uniWebView != null) { _uniWebView.Show(); return; } var webViewObject = new GameObject("WebViewObject"); //キャッシュのクリア webViewObject.OnDestroyAsObservable().Subscribe(_ => _uniWebView.CleanCache()).AddTo(webViewObject); _uniWebView = webViewObject.AddComponent<UniWebView>(); //スキームを追加 _uniWebView.AddUrlScheme("web-view-demo"); //メッセージ受け取りイベント _uniWebView.OnMessageReceived += (view, message) => { switch (message.Path) { //閉じるボタン押下 case "close": //WebViewを非表示にする if (_uniWebView != null) _uniWebView.Hide(true); break; //↑ボタン押下 case "up": player.Translate(Vector3.forward); break; //↓ボタン押下 case "down": player.Translate(Vector3.down); break; //→ボタン押下 case "right": player.Translate(Vector3.right); break; //←ボタン押下 case "left": player.Translate(Vector3.left); break; } }; //画面の向きが変わるたびに解像度を変更 _uniWebView.OnOrientationChanged += (view, orientation) => { _uniWebView.Frame = new Rect(0, 0, Screen.width, Screen.height); }; //画面サイズを設定 _uniWebView.Frame = new Rect(0, 0, Screen.width, Screen.height); //背景色 var color = Color.white; color.a = 0.5f; _uniWebView.BackgroundColor = backGroundColor ?? color; //ツールバー非表示 _uniWebView.SetShowToolbar(false); //横スクロールバー非表示 _uniWebView.SetHorizontalScrollBarEnabled(false); //インジケータ _uniWebView.SetShowSpinnerWhileLoading(true); _uniWebView.SetSpinnerText("ロード中"); //内部保持してるサイトを読み込み var exchangeUrl = UniWebViewHelper.StreamingAssetURLForPath(url); _uniWebView.Load(exchangeUrl); //画面表示 _uniWebView.Show(); } } OnMessageReceivedで受け取ったメッセージに応じた処理を行っています。 当然ながら、HTML側のスキーム等、C#側と合わせないと動きません。 構造体をデフォルト引数に 小ネタですが、Colorをデフォルト引数として設定するのは構造体の都合上難しいようでした。 【参考リンク】:Unity3d c# - Vector3 as default parameter ですので、下記のようにNull許容型として定義し、関数内で値の有無を見てデフォルト値を設定しています。 public void Hoge(Color? c = null) { var defaultColor = Color.white; hogehoge.Color = c ?? defaultColor ; } ??はNull合体演算子と呼ばれるもので、??の左側が評価されてnullを返した場合、??の右側を評価するという書き方です。 【参考リンク】:??(null合体演算子) 最後に使う側のサンプルです。 ボタン押下で指定したHTMLのWebViewを開きます。 using UnityEngine; using UnityEngine.UI; public class Test : MonoBehaviour { [SerializeField] private Button _testButton; [SerializeField] private Transform _player; void Start() { _testButton.onClick.AddListener(ShowWebView); } private void OnDestroy() { _testButton.onClick.RemoveListener(ShowWebView); } private void ShowWebView() { var webView = new WebViewDemo(); webView.ShowWebViewPlayerController("SampleHTML.html",_player); } } 最後に 超簡単かつドキュメントがしっかりしていて、有料の価値のある非常に良いアセットだと個人的には思いました。 一点だけ下記直せずです。 iosだと問題なく動いたのですが、手元のAndroid端末(ver11)だと Hideの呼び出し時に一瞬カクついてWebViewが閉じるバグがありました。 何か知ってる人いたら助けてください。 参考リンク UniWebView もう逃げない。HTMLのviewportをちゃんと理解する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityを利用してthree.js用のパノラマ画像を作成する

VRツアーをthree.jsで作成しようとサンプル素材を探したところ 1フロア各所のパノラマ画像が全然なく Unityを利用してパノラマ画像を作成しました インストール Unity2020.3 Unity Recorderをpackageから 部屋のアセットを購入、インポート シーン設定 ライティングやマテリアルの設定で何度も録画するため、 タグ機能を使い複数の場所を同時に出力できるようにする タグをr1 - r7までつくり カメラの複製、位置移動、タグ設定を繰り返す Unity Recorder設定 Recording Mode -> Single Frame Capture Source -> 360 View Camera -> TaggedCamera Tag -> r1 * Output File File Name -> r1 * Add Recorderで、追加して*部分を割当 Recordしてファイル生成 three.jsに埋め込み コードは、公式サンプルの https://threejs.org/examples/webgl_panorama_equirectangular.html を参考 テクスチャのパスだけ変えて webgl_panorama_equirectangular.html const texture = new THREE.TextureLoader().load( 'textures/r1.jpg' ); ブラウザで確認 つづき
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【ParticleSystem】RateOverTimeの罠

ParticleSystemを使って間隔の長いエフェクトを作ろうとしたらハマったので残しておきます。 波紋のようなエフェクトを0.5秒ごとに発生させたかったので、 [Emmision]-[Rate over Time] に2を設定しました。 すると、ParticleSystemのGameObjectを活性にした約0.5秒後からエフェクトが発生しました。 どうやら0秒の時点ではパーティクルは発生しないらしい…?? RateOverTimeの説明はこちら。 Rate over Time: ユニットタイム毎に放出されるパーティクルの数 一応英語版も見てみる。(英語版にしか情報ないこと結構ある) Rate over Time: The number of particles emitted per unit of time. どちらも条件を満たす中で一体どのタイミングでパーティクルが放出されるのかは書いてない。 どこかに情報はないかと探していたらForum内にこんな公式コメントがありました。 Assuming you are using Rate over Time emission? In which case, particles are spawned at the end of the emission interval. Eg, rate over time of 10, will emit a particle at 0.1, 0.2 etc. So there will never be an emission exactly at time 0 with this mode. You can get an emission event exactly at time 0 with a burst. Forumでコメントするだけじゃなくてマニュアルにも書いてー! まとめ RateOverTimeの割合を満たす中で一番遅いタイミングでパーティクルは放出される。 つまりParticleSystemを開始した瞬間にはパーティクルは放出されない。 ParticleSystemを開始した瞬間に出したい場合はBurstsのTime=0に設定すれば実現可能。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

開発期間におけるアセット管理戦略の選択

今回の主な話題:開発期間におけるアセット管理戦略の選択、iOS 14起動時にクラッシュする、IL2CPP暗号化:iOSでglobal-metadata.datの復号化問題、プログラムでコントロールできる3Dアクションの実現方法、Unity Editor内ScreenのWidthとHeight。 Resource Q:パッケージ化にAssetBundleが必要ですが、開発中にAssetBundleを使うことはあまり適していません。上記の2つのロードには、独自の問題があります。 Resource:Resourcesフォルダーの中に置く必要があり、パッケージ化は少し処理しにくいです。 AssetDatabase:非同期ロード方法はなく、開発中に非同期ロードが必要な状況をシミュレートする方法はありません。 現在、プロジェクトにResourcesを使用しています。パッケージャーが2つのプロジェクトに分割し、APKプロジェクトにResourcesを除外し、単独のバージョンを作成してアップデート命令を管理しますが、それでも非常に面倒です。ほとんどの会社はAssetDatabaseを使用していますが、何か非同期ロードをシミュレートする方法はありますか? また、上記の2つの方法では、実際のアンロード状況をシミュレートすることはできません。チームメンバーが不当に操作すると、AssetBundleを開いた後、メモリリークや参照の欠落が発生する可能性があります。皆さんの会社が開発中に採用するアセット管理法を共有できますか? ① AssetDatabaseを使用すると、非同期をシミュレートするためにランダムな遅延を追加できます。アセットロードのインターフェイスはカプセル化でき、配置によってAssetDatabaseとAssetBundleのどちらに行うことを決めます。 パッケージャーはパッケージされたAssetBundleをSVNに自動的に提出し、いつでもアップデートすることができます。エディターでAssetBundleをテストする方がより便利です。 さらに、新しいAddressableを試すこともできます。 ② あるプロジェクトチームのアプローチは、通常はResourcesを使用してロードします。AssetBundleまたは公式パッケージの場合、Resourcesディレクトリによって特定のルールに従って1つの新しいプロジェクトを自動的に生成します。これで2つのプロジェクトを維持する必要はありません。 ③ 以前にはLoadFromFileAsyncでAssetDatabaseの非同期をシミュレートしていました。 後で試した1つの方法は、単独のアートプロジェクトを使用してAssetBundleを処理し、プログラムプロジェクトはどんなアセットにも担当しなく、アートプロジェクトのみを使って生成するAssetBundleをパッケージすることです。少しだけ変更するでも2つのUnityプロジェクトを操作する必要があるため、複雑に聞こえますが、利点は非常に多いです。 ④ 以前は、Resourcesでロードしていました。ただアセットがパッケージ化されないためにEditor/Resourcesディレクトリに配置します。 そして、リリースする際にFlagを切り替え、Editor / ResourcesディレクトリのアセットをロードしてAssetBundleパッケージを作成します。しかし、現在にはすべてAddressablesに任せて管理します。 iOS Q: iOS 14がリリースされた後、クラッシュ問題が発生しました。モデルには関係なく、iOS 14である限り、必然にクラッシュします、iOS 13の場合は正常です。使用しているUnityバージョンは2018.2.3です。誰かが以前にこれに遭ったことがありますか? 私たちのプロジェクトもiOS14に問題があります。2つの表現があります。 1、起動時にクラッシュし、数回再起動すれば通過できます。 2、起動後ある段階に入ると、クラッシュします。(回避できません) XCodeから確認すると、いつもGfxDriverがエラーを報告していました。検出したら、XCodeの[BuildSetting-Packaging-Product Name]に日本語が含めるとこの問題が発生することがわかりました。変更したらよくなります。 根本的な原因はまだわかりません。個人的には、Product NameがHeader Folder Pathに影響があって、コードのロードバスに日本語がある場合に問題が引き起こす(初期のUnityの状況と同様)と推測します。 iOS Q1: クラッキングの難易度を上げるには、global-metadata.datファイルを暗号化する必要があります。 達成する方法は次のとおりです。 1.暗号化はAndroidおよびXcodeプロジェクトのエクスポート後に実行され、global-metadata.datはバイト単位で暗号化されます。 2.変更: Android:\Editor\Data\il2cpp\libil2cpp\vm\MetadataLoader.cpp Mac:Unity.app\Contents\il2cpp\libil2cpp\vm\MetadataLoader.cpp ファイル内のLoadMetadataFile関数は、ファイルからロードされた内容をバイトごとに復号化します。 現在の問題は、Androidでは問題がなく、ゲームの暗号化と実行は正常に行えます。しかし、Macでは、復号化機能が有効になっていませんようです。ゲームを起動するとクラッシュが発生しました。 皆さんにお聞きたいです。 global-metadata.datがMacでのロードはこの関数を介しますか?MacのUnityインストールディレクトリにも1つのPlaybackEnginesフォルダーがあり、MetadataLoader.hなどのIL2CPP関連ファイルも含まれていますが、このファイルは読み取り専用であり、変更できず、MetadataLoader.cppが見つかりません。Macで復号化が機能しないのは、このファイルと関係がありますか? A1:iOSでは直接にリンクしてコンパイル済みです。 PlaybackEngines\iOSSupport\Trampoline\Libraries\libil2cpp.a 自分でプロジェクトから.aを削除し、IL2CPPソースコードをプロジェクトにインポートする必要がある場合があります。 Q2: プロジェクトとは、iOS Support/Tramponlineフォルダー内のXcodeプロジェクトですか?Unity.app\Contents\il2cpp\libil2cppにあるすべてのファイルをこのプロジェクトに追加する必要がありますか? A2:エクスポートされたプロジェクトエンジニアリングを指します。具体的にどのファイルを指すことは自分で試す必要があります。Windowsで1つのIL2CPPのVSプロジェクトをエクスポートしてファイルとコンパイルパラメータを比較できます。 Q3: 最近、このiOS暗号化Metadata問題が発生しました。最後に解決しましたか? この記事を読むと、Unityのソースコードが必要なようです。 A3:IL2CPPコードをXCodeプロジェクトに直接ドラッグすれば可能なようです。 Animation Q:私たちの需要は、下図のように、プログラムで柔軟な拍手アクションを実現したいです。 真ん中は1つの大きいモデル、両側には両手で、拍手アクションをさせたいです。すべての青い点は拍手ポイントとして使用でき、プログラムでどちらの点に拍手することをコントロールする必要があります。 現在考えている方法は下記のようです。 1、拍手アニメーションをたくさん実現してから、Blend Treeで2Dブレンディングを行うことができますが、この方法は正確ではありません。 2、Final IKを使用して両手のアクションをコントロールできますが、動きは不自然です。 Kアクション作業を最小限に抑えたいですが、何か他の方法ありますか? ① A1:このような需要は基本的にIKを使用する必要があります。Final IKは現在のUnityエンジンにある最も成熟したIKソリューションです。それでも機能しない場合は、自分で開発することを検討する必要があります。 ② A2: 以前、2D FreeformDirectionalのBlendTreeを使用して上下左右の動きを融合した経験がありました、融合効果は理想的でした。理論的に、最左、最右、最上、最下の4つの拍手動作を別々作成し、2つのパラメーターで左右と上下の融合をコントロールすれば、より直線的に対応でき、精確さも納得できるようです。しかし、私にはテスト用のアセットもないし、キミが精度への要件もわかりません。Demoを作成して試すことをお勧めします。 Editor Unity2018.4.23バージョンでテストを行って、1つの9:16解像度を増加しました。この時点で、取得されるWidthとHeightは515×916です。Gameウィンドウのサイズを変更すると、この値も変更されます。これについて具体的なルールな何ですか? 対応する解像度のScale値を掛けたそうとわかりました。例えば、720×1280の解像度でGameウィンドウのScale値は0.741であり、9:16に変更したら解像度はちょうど533×948(720×0.741 : 1280×0.741)になります。現在には、この値を取得できるAPIはありますか? いくつのUI操作があるため、例えば、1枚の画像を画面と同じサイズに設定されます。しかし、EditorのUI解像度は720×1280であるため、期待値とは異なります。解像度が720×1280(これも9:16)に設定されている場合、取得された値は正しくなります。 主にルールを知りたいです、そして515×916に基づいて720×1280を推測できます。 Editorで、GameウィンドウがAspect Ratioモードを採用する場合、Screenが取得するのは実際のGameウィンドウのサイズです。GameウィンドウがFixed Resolutionモードを採用する場合、Screenが取得するのは設定されたウィンドウのサイズです。 UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、パフォーマンス分析と最適化ソリューション及びコンサルティングサービスを提供している会社でございます。 今なら、UWA GOTローカルツールが15日間に無償試用できます!! よければ、ぜひ! UWA公式サイト:https://jp.uwa4d.com UWA GOT OnlineレポートDemo:https://jp.uwa4d.com/u/got/demo.html UWA公式ブログ:https://blog.jp.uwa4d.com
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity AssetBundle効率的な暗号化ケースの共有

AssetStudioの普及により、Unityプロジェクトに使用されたAssetBundleアセットは、様々なユーザーが簡単に解凍して抽出できるため、AssetBundleアセットパッケージの安全問題は注意すべきものになりました。 過去に、公式ドキュメントを理解することで、主にAssetBundle.LoadFromMemory(Async)を介してアセットパッケージの暗号化を実現できました。公式ドキュメントでは、この方法について次のように説明しています。 Use this method to create an AssetBundle from an array of bytes. This is useful when you have downloaded the data with encryption and need to create the AssetBundle from the unencrypted bytes. Compared to LoadFromMemoryAsync, this version is synchronous and will not return until it is done creating the AssetBundle object. 公式ドキュメントのサンプルコードは下記のようです。 サンプルコード using UnityEngine; using UnityEngine.Networking; using System.Collections; public class ExampleClass : MonoBehaviour { byte[] MyDecription(byte[] binary) { byte[] decrypted = new byte[1024]; return decrypted; } IEnumerator Start() { var uwr = UnityWebRequest.Get("http://myserver/myBundle.unity3d"); yield return uwr.SendWebRequest(); byte[] decryptedBytes = MyDecription(uwr.downloadHandler.data); AssetBundle.LoadFromMemory(decryptedBytes); } } 注意する必要があるのは、AssetBundle.LoadFromMemory(Async)の方法に対して、Unity公式は「AssetBundle foudamentals」という文の中に明確に指摘しました。 Unity’s recommendation is not to use this API. AssetBundle.LoadFromMemoryAsync loads an AssetBundle from a managed-code byte array (byte[] in C#). It will always copy the source data from the managed-code byte array into a newly-allocated, contiguous block of native memory. If the AssetBundle is LZMA compressed, it will decompress the AssetBundle while copying. Uncompressed and LZ4-compressed AssetBundles will be copied verbatim. The peak amount of memory consumed by this API will be at least twice the size of the AssetBundle: one copy in native memory created by the API, and one copy in the managed byte array passed to the API. Assets loaded from an AssetBundle created via this API will therefore be duplicated three times in memory: once in the managed-code byte array, once in the native-memory copy of the AssetBundle and a third time in GPU or system memory for the asset itself. Prior to Unity 5.3.3, this API was known as AssetBundle.CreateFromMemory. Its functionality has not changed. 公式の説明から、AssetBundle.LoadFromMemory(Async)の使用コストは非常に高く、お勧めできないのは当然です。ただし、AssetBundleを暗号化して、ユーザーがAssetStudioなどのツールを使用してAssetBundleアセットを簡単に抽出できないようにするためのより効率的で便利な方法はありますか? Unity APIを確認すると、LoadFromFileの最後に1つのoffsetパラメーターがあることが見つかりました。では、このパラメーターの用途は何ですか?AssetBundleアセットがAssetStudioによって直接抽出されるのを防ぐことはできますか?まず、公式ドキュメントのインターフェース説明を見てください。 public static AssetBundle LoadFromFile(string path, uint crc, along offset); Parameters Returns AssetBundle Loaded AssetBundle object or null if failed. Description Synchronously loads an AssetBundle from a file on disk. The function supports bundles of any compression type. In case of lzma compression, the data will be decompressed to the memory. Uncompressed and chunk-compressed bundles can be read directly from disk. Compared to LoadFromFileAsync, this version is synchronous and will not return until it is done creating the AssetBundle object. This is the fastest way to load an AssetBundle. 公式ドキュメントのサンプルコードにはoffsetパラメーターのデモンストレーションを提供してありませんから、ここで表示しません。次に、自分が作成したテストコードを使用してデモンストレーションします。 まず、XAssetが生成したAssetBundleファイル内容をオフセットします。Unityがパッケージした後にすべてのAssetBundleファイルをトラバースして、ファイルにoffsetを添付してから覆います。コードは次のように、 コード foreach (string bundleName in bundleNames) { string filepath = outputPath + "/" + bundleName; // hashcodeを利用してoffsetする string hashcode = manifest.GetAssetBundleHash(bundleName).ToString(); ulong offset = Utility.GetOffset(hashcode); if ( offset > 0) { byte[] filedata = File.ReadAllBytes(filepath); int filelen = ((int)offset + filedata.Length); byte[] buffer = new byte[filelen]; copyHead(filedata, buffer, (uint)offset); copyTo(filedata, buffer, (uint)offset); FileStream fs = File.OpenWrite(filepath); fs.Write(buffer, 0, filelen); fs.Close(); offsets += filepath + " offset:" + offset + "\n"; } WriteItem(stream, bundleName, filepath, hashcode); } 次に、負荷テストを実行します。「offsetパラメーターを使用してAssetBundleをロードする」と「復号化されたファイルをシミュレートし、メモリからAssetBundleをロードする」を別々に行って、中の1つのTextureをロードして表示します。下記のコードを参照できます、 コード // offsetに基づいてAssetBundleをロードする async void onLoadWithOffsetClicked() { if (offsetBundle) offsetBundle.Unload(true); var current_memory = Profiler.GetTotalAllocatedMemoryLong(); display_image.texture = null; var path = System.IO.Path.Combine(Application.streamingAssetsPath, "assets_previews_offset"); var assetBundleRequest = AssetBundle.LoadFromFileAsync(path, 0, 294); await assetBundleRequest; var texture = assetBundleRequest.assetBundle.LoadAsset<Texture2D>("download.jpg"); display_image.texture = texture; offsetBundle = assetBundleRequest.assetBundle; Debug.Log("Offset Load Complete:" + (Profiler.GetTotalAllocatedMemoryLong() - current_memory)); } // Menmoryに基づいてAssetBundleをロードする async void onLoadWithMemoryClicked() { if (memoryBundle) memoryBundle.Unload(true); var current_memory = Profiler.GetTotalAllocatedMemoryLong(); display_image.texture = null; var path = System.IO.Path.Combine(Application.streamingAssetsPath, "assets_previews"); WWW www = new WWW("file://" + path); await www; var request = AssetBundle.LoadFromMemoryAsync( www.bytes); await request; var texture = request.assetBundle.LoadAsset<Texture2D>("download.jpg"); display_image.texture = texture; memoryBundle = request.assetBundle; www.Dispose(); Debug.Log("Memory Load Complete:"+ (Profiler.GetTotalAllocatedMemoryLong() - current_memory)); } そして、上記の2つの関数で実行されたProfilerデータ分析とサンプリングの結果を見てみましょう。 テストシーンのスクリーンショット offsetパラメーターを使用してAssetBundleをロードする前のメモリ状況 offsetパラメーターを使用してAssetBundleをロードする後のメモリ状況 LoadFromMemoryを使用してロードする前のメモリ状況 LoadFromMemoryを使用してロードする後のメモリ状況 比べたら、LoadFromMemoryを使用するとメモリが鮮明に増加し、さらにロードプロセス中に1つのメモリピークがあります。 AssetBundleのアセットをオフセットしたため、理論的にはAssetStudioがUnityプロジェクトのAssetBundleを直接分析できないことは間違いありません。次に、理論が実践のテストに耐えられるかどうかを確認しましょう。 テスト後、offsetが添付されていない場合には、AssetStudioを使用してAssetBundleにあるアセットを簡単にプレビューできます。次の図を参照してください(会社のプロジェクトリソースであるため、コーディングする必要があります)。 offsetのあるアセット 結果は理論上の推測結果と一致していることがわかりました。以下を参照してください。 テスト中に、AssetStudioの一部の古いバージョンは、offsetのあるアセットを解析すると、直接クラッシュすることがあるとわかりました。実際に、アセットの暗号化については、ほとんどの場合には初心者を防げますが専門家を防げません。シンプルな方法も複雑な方法も、逆コンパイラー専門家にとっては何度なく無力です。ある人がIDAを使用して通信暗号化アルゴリズムを逆コンパイラーしたのを見たことがあるので、ここではこれ以上詳細な分析は行いません。 UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、パフォーマンス分析と最適化ソリューション及びコンサルティングサービスを提供している会社でございます。 今なら、UWA GOTローカルツールが15日間に無償試用できます!! よければ、ぜひ! UWA公式サイト:https://jp.uwa4d.com UWA GOT OnlineレポートDemo:https://jp.uwa4d.com/u/got/demo.html UWA公式ブログ:https://blog.jp.uwa4d.com
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】Sprite Resolverで2Dアニメーション

使用したUnityのバージョン:2021.1.16f1 2D Animationパッケージ:6.0.4 Affinity Designerでキャラクターを作る 部品ごとにグループ分けし、名前を付けます。 ボーンを使う場合はボーンの名前と被りがちなので、グループ名の先頭にアンダーバーをつけるなどして区別します。 今回は目の部分を差し替えるアニメーションを作成したいと思います。 これをPSD形式で書き出し、拡張子を.psbに変更します。 Unityにインポート 作ったpsbファイルをUnityにインポートし、インポートしたアセットをシーンにドロップします。 差し替えるスプライトの表示したくないバリエーションは、Hierarchyで非アクティブ化しておきます。 ボーンを使う場合は、ボーンを動かしたときに差し替えるスプライトがズレないように、スプライトをボーンに追従するようにしておきましょう。 Sprite Library Assetsを作成 Assetsフォルダに2D->Sprite Library Assetsを作成します。 Category名を設定し、Entryを追加してSelectボタンを押し、差し替えるスプライトを設定していきます。 キャラクターに2D Animation->Sprite Libraryコンポーネントを追加し、このSprite Library Assetを設定します。 Animatorを追加 キャラクターにMiscellaneous->Animatorでコンポーネントを追加します。 次にAssetsフォルダにAnimator Controllerを作成し、AnimatorコンポーネントのControllerに設定します。 Sprite Resolverを追加 Hierarchyで差し替えたい目のスプライトを選択し、2D Animation->Sprite Resolverコンポーネントを追加し、Categoryに先ほど作成したSprite LibraryのCategoryを設定します。 これでLabelを変更すればスプライトが差し変わるようになったと思います。 アニメーションさせる Animationウインドウで新規Animationを作成します。 Animationウインドウでレコードモードにして、Sprite ResolverのLabelを変更しながらキーフレームを作ります。 しかしこのままでは動かないので、キーフレームを選択し、右クリックでBoth Tangents->Constantに設定しましょう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VRC Avatar Parameter Driver を併用した物の出し入れ・左右の持ち替えの実装例と同期の話(後編)

はじめに どうも。「バーチャルためにならない改変お姉さん」の水無月せきなです。 記事を書いている間に VRChat が Unity 2019 に移行していました。自分が使っている SDK の古さに驚いていたのですが、Unity も VRChat クライアントのバージョンも古いものとなってしまいました。読まれる方には大変申し訳なく思いますが、移行直前に解決したことのお話なのでお許しを…… さて前回は、物を出し入れするのに少し凝ったことをやろうとした話をしました。今回はその続きで、実装時に頭を抱えた、同期に関してのお話です。 環境 Unity:2018.4.20f1 VRChat:VRChat 2021.3.1(Build 1114) VRCSDK:VRCSDK3-AVATAR-2021.01.28.19.08_Public ここから先は憶測を含みますので、その点ご了承ください。 実装時に起きた問題 「同期するはずなのに同期しない」 Avatars 3.0 の導入当初、「SDK2 のアバターと違い、後から来た人にも同期される」というのを見た覚えがあります。 その高い自由度とともに、「同期される」というのは1つのキーワードだった気がしますが、だからと言って絶対に同期されるわけではありません。 そう、目の前で見ている人にすら同期されないことも起こりえます。 (実例が今回の私です) どの記事で見たかという記憶は定かではないですが、「ステートマシンではなくパラメータの値が同期されるため、遷移の組み方によっては意図したように相手に見えない(つまり『同期されない』)ことはある」ということで、私もわかっていたつもりでした。 そんな私が当初組んでいた(そして同期しなかった)、オブジェクトの ON ・OFF に関するレイヤーがこちらです。 条件と処理の流れは以下のような感じです。 1: ON にするパラメータは True 、既に ON になっているオブジェクトは0個    ⇒ 個数のカウントを1、オブジェクトを True に。 2: ON にするパラメータは True 、既に ON になっているオブジェクトは1個    ⇒ 個数のカウントを2、オブジェクトを True に。 3: ON にするパラメータは False 、既に ON になっているオブジェクトは1個    ⇒ 個数を0、オブジェクトを OFF に。 4: ON にするパラメータは False 、既に ON になっているオブジェクトは2個    ⇒ 個数のカウントを1、オブジェクトを OFF に。 (番号を振り忘れましたが、右に流れるルートはキャンセルするルートです) ON になっているオブジェクトの個数は、Bool のパラメータをビットに見立てた2進数として保持しています。 また、途中のステートマシンに付けた VRC Avatar Parameter Driver でこのパラメータの値を変更しています。 以上が概略ですが、このように実装したら、他の人に同期されませんでした。具体的な例を挙げると、「何も持っていない状態からオブジェクトを ON にすると、メニューから OFF にしても他の人からはずっと ON の状態になる」というものです。 原因は、1.トランジションの条件で参照するパラメータの値を、その遷移先のステートマシンで変更していたこと 2.なおかつそれを考慮した条件設定や遷移の組み方をしていなかったことでした。 「同期しているから『同期しない』」 わかりにくい表現となってしまったので、「1.トランジションの条件で参照するパラメータの値を、その遷移先のステートマシンで変更していたこと」から行きましょう。 まず「パラメータの値が同期される」ということについて、パラメータの値が変わる度に同期が行われるのだと私は思っていました。つまり、メニューを操作してパラメータの値が変更されるとすぐに他の人に同期され、それぞれで Animator 上の移動処理がすぐに行われると思っていたわけです。 また、途中のステートマシンの前後では通過するように条件を設定しており、ここでパラメータの値を変更しても、既にルートに入っているので大丈夫だと思ったのです。 しかし今回の事象を見る限り、私の認識は間違っていたようです。 トランジションの条件と移動後の私側で、ON になっているオブジェクトの個数を示すパラメータの値をまとめます。 (1・2は OFF ⇒ ON 、3・4は ON ⇒ OFF) ルート 1 2 3 4 条件 0 1 1 2 移動後 1 2 0 1 何もオブジェクトを ON にしていない状態から OFF ⇒ ON ⇒ OFF にした場合、私の側では1・3のルートを通ります。他の方から見てもそうなるように期待したわけですが、ならなかったのは何度も書いている通りです。 「ON になったらずっと ON になっているように見える」ということから1・2のどこかで止まっていることは確実なので、3・4で余分な物である VRC Avatar Parameter Driver をとりあえず無効化してみました。 すると、同期します。 「原因はお前か!」 ですが、1・2では有効化したままで同期されているので、VRC Avatar Parameter Driver 自体が原因ではありません。 1・2では有効化したままでも同期され、3・4では有効化した途端に同期されなくなります。何が違うのでしょうか。 改めて表を見ましょう。 ルート 1 2 3 4 条件 0 1 1 2 移動後 1 2 0 1 私が1のルートを移動すると個数のカウントは1になり、2のルートの条件を満たします。しかし、3のルートを移動すると個数のカウントは0になり、3・4どちらの条件も満たしません。 ついでに、3・4で VRC Avatar Parameter Driver を無効化した場合は1・2の移動後の値のままなので、3・4どちらの条件も満たします。 仮に、パラメータの同期がメニュー操作後すぐではなく、私の Animator 上の移動が終わってから同期しているなら、今回起きた同期ズレは説明がつきます。 つまり、パラメータの値の同期はアバター着用者側での Animator 処理終了後に行われるため、条件を満たさないように値を変更していれば当然状態がズレるということです。 同期の仕様についてはあくまで状況から導いた仮説であり、未検証のものです。しかし、これを踏まえた上でやり直したのが前回であり(ON・OFF の部分は別のやり方ですが)、こちらは一応同期も確認できたので、まあまあの確からしさはあるのかなと思っています。 では、「2.なおかつそれを考慮した条件設定や遷移の組み方をしていなかったこと」に移りましょう。 今回のような同期ズレを起こさないためには、トランジションの条件で参照するパラメータの値を(少なくともそのレイヤー内では?)変更しないことが一番でしょう。 ただ、私にはどうしてもそれが出来ない部分がありました。 なので次善の策として、変更しない別のパラメータを用意してトランジションを追加するなり、「途中で変えた値で同期する」ことを考えた設定・遷移の組み方をするしかないのかなと思います。 おわりに VRChat のアップデートで Bool のパラメータが解禁された際、同じようなことをしようとして同じように同期しなかった時がありました。 その時は理由がわからなかったのですが、ようやく自分なりに原因を知ることができました。長かった…… 「 Avatars 3.0 のアバターでも同期ズレすることはある」というのはこういうことを言われていたのでしょうし、既に誰かが書かれているかもしれません。 自分の浅学さを恥じる次第です。 同期の確認と原因究明にあたっては私のフレンドさん達、特にライルさんに協力していただきました。ありがとうございました。 どなたかのお役に立てば幸いです。それでは。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】モグラ叩きを作ってみた

関連記事 【Unity×Arduino】モグラたたき ArduinoとUnityを連携したバージョンのモグラたたきです。 開発環境 Windows 10 pro Unity ver 202.3.13f1 Microsoft Visual Studio Community 2019 Version 16.8.4 仕様 モグラのプロパティ クリックすると引っ込む クリック時に赤くなる 不規則にいい感じで出現する 3匹 デザイン(以下の画像) モグラの挙動(不規則に出現させるなど) MOGURAオブジェクトに割り当てたコードが以下です。 MoguraManeger.cs using System.Collections; using System.Collections.Generic; using UnityEngine; public class MoguraManager : MonoBehaviour { public int max;//モグラの上下移動の上限位置 public int min;//モグラの上下移動の下限位置 public float speed;//モグラの移動スピード void Start() { } int direction = -1;//モグラの移動方向(1:up) void Update() { Vector3 pos = this.transform.position; var rand = new System.Random(); int random = rand.Next(minValue: 5, maxValue: 15); if (pos.y >= 5) { direction = -1; } else if (pos.y < rand.Next(min,max)) { direction = 1; GetComponent<Renderer>().material.color = Color.yellow; } else { } pos.y += direction * speed; this.transform.position = pos; } //モグラがクリックされた時 public void click() { Vector3 pos = this.transform.position; if (pos.y > -4.8) { GetComponent<Renderer>().material.color = Color.red; if (direction > 0) { direction = direction * (-1); } } } } このコードの else if (pos.y < rand.Next(min,max)) にて、上下運動するモグラの最下位置をランダムに指定しています。 モグラごとに、rand.Nextの引数を別々に設定しています。 最下位置が不規則に変化することで、モグラが出現するまでの 時間をランダムに変動させています。 モグラオブジェクトのInspectorには、下記のような設定をしました。 pic.twitter.com/8vxKKXV1LU— Asayan (@L4R2h) August 5, 2021 成果物 pic.twitter.com/qWfp8mesSt— Asayan (@L4R2h) August 5, 2021 途中からモグラに海洋生物感が出てきたので、 地面を青色に変更した。 おわりに たたいてる感をもっと出したい。 ハードウェア的な入力をもとに、画面内のもぐらをたたいてみたい。 参考資料 Unity3D EventTriggerが反応しない時の対処法 スクリプトからGameObjectの色を変更する オブジェクトに画像を貼り付ける
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む