- 2023/09/02 【Unity】【エディタ拡張】Game View の解像度を取得する
- 2023/08/02 【Unity】【C#】指定ワールド位置がカメラに映っているか調べる
- 2022/11/30 【Unity】マテリアルで使用しているシェーダを調べる
- 2022/10/01 【Unity】【エディタ拡張】フィールドの値などをインスペクタで横に並べて表示する
- 2022/06/20 【Unity】【C#】音量設定用にデシベル(dB)変換をする
« prev next »
【Unity】【エディタ拡張】Game View の解像度を取得する 
2023/09/02 Sat [edit]
ランタイム時の画面解像度は (Screen.width, Screen.height) で取得できるのだが、エディタスクリプト上で Screen で画面解像度を取得すると、よくわからない値になる。

調べてみたら、UnityStats.screenRes というプロパティ(Unityマニュアルではなぜか見つからない)で取得できるとあったので、いつものように静的な関数として定義しておくと便利だと思った。簡単なサンプルを載せておこう。
(※) Unity 2020.3.34f1 / Windows11(x64) で確認
●Game View の解像度を取得する (戻値:Vector2Int 型)
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
public static partial class Utils //※クラス名は任意
{
/// <summary>
/// Game View の解像度を取得する (戻値:Vector2Int 型)
/// 2023/09/02 Fantom
/// http://fantom1x.blog130.fc2.com/blog-entry-431.html
/// </summary>
/// <returns>x = width, y = height</returns>
public static Vector2Int GetGameViewResolution()
{
var res = UnityStats.screenRes.Split('x'); //"1920x1080" 等
return new Vector2Int(int.Parse(res[0]), int.Parse(res[1]));
}
}
#endif
●エディタスクリプトからの使用例 (戻値:Vector2Int 型)
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(GameViweResoTest))] //クラス名は任意(ヒエラルキーにアタッチ)
public class GameViweResoTestEditor : Editor //クラス名は任意(※Editorフォルダに置く)
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
GUILayout.Space(15);
if (GUILayout.Button("GameViewResolution"))
{
Debug.Log($"Screen: {Screen.width}x{Screen.height}");
var reso = Utils.GetGameViewResolution();
Debug.Log($"GameViewResolution: {reso.x}x{reso.y}");
}
GUILayout.Space(15);
}
}
戻値に Vector2Int を使ってるが、x が width, y が height となる。
ここでは MonoBehaviour を継承した適当なスクリプト(GameViweResoTest.cs)を作成し、ヒエラルキーにアタッチしている。
1280x720, 1920x1080 等、定番の解像度を使ってるが、「Free Aspect」の状態でも取得できるようだ。そのときに 425x776 など、実際の画面上の解像度(?)になるらしい。

●結果例 (GameView: 4K UHD)
GameViewResolution: 3840x2160
GameViewResolution (Tuple): 3840x2160
現在の Unityバージョン(掲載時点:Unity2020)だと C# も Tuple 型が使えるので(必要なら、.Net 4.x にする)、戻値を Tuple 型にしておくと、少しばかり簡潔になるので良いかもしれない。
●Game View の解像度を取得する (戻値:Tuple 型)
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
public static partial class Utils //※クラス名は任意
{
/// <summary>
/// Game View の解像度を取得する (戻値:Tuple 型)
/// 2023/09/02 Fantom
/// http://fantom1x.blog130.fc2.com/blog-entry-431.html
/// </summary>
/// <returns>w = width, h = height</returns>
public static (int w, int h) GetGameViewResolutionAsTuple()
{
var res = UnityStats.screenRes.Split('x'); //"1920x1080" 等
return (int.Parse(res[0]), int.Parse(res[1]));
}
}
#endif
●エディタスクリプトからの使用例 (戻値:Tuple 型)
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(GameViweResoTest))] //クラス名は任意(ヒエラルキーにアタッチ)
public class GameViweResoTestEditor : Editor //クラス名は任意(※Editorフォルダに置く)
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
GUILayout.Space(15);
if (GUILayout.Button("GameViewResolution"))
{
Debug.Log($"Screen: {Screen.width}x{Screen.height}");
var (w, h) = Utils.GetGameViewResolutionAsTuple();
Debug.Log($"GameViewResolution (Tuple): {w}x{h}");
}
GUILayout.Space(15);
}
}
注意点として、エディタスクリプトは任意の「Editor」フォルダ内に入れて使うか、プリプロセッサ(#if UNITY_EDITOR~#endif)で囲んで使う(通常はヒエラルキーでアタッチするスクリプトはプリプロセッサで囲み、エディタのみ使うものは Editor フォルダに入れるのが簡単)。これらは Unityエディタ上では使えるが、ランタイムでは使えない。あくまで開発支援用スクリプトとなる。
自由に改造して使うと良い。
(関連記事)
【Unity】【C#】画面解像度とアクペクト比(整数)を求める
【Unity】【C#】指定ワールド位置がカメラに映っているか調べる
【Unity】固定背景画像(2D)を表示する
【Unity】【C#】指定ワールド位置がカメラに映っているか調べる 
2023/08/02 Wed [edit]
最近はチュートリアル・ノウハウ系が多かったので、久しぶりにお役たちコード(?)を書いておこう。
まぁ、VRM Live Viewer 開発し始めてからどうしても偏ってしまうが、私も最初から知ってたわけじゃないからね。必要に応じて調べてるうちに Unity にも詳しくなったとも言える。何でも勉強するならテーマがあった方が頭に入るね。極端な話、VRM Live Viewer はオープンソースの塊で、再利用可能コードは数年間に渡って公開してるし、アルゴリズムなどもアプリ開発以前から公開してるし、全部無料で手に入るものばかりなので、誰でも作れることになる。つまり、世界中の人と条件は同じなのだ。積み重ねがどれだけ重要かがわかるね(笑)。
それでは、さっそく今回のテーマのコードを書いてみよう。と言ってもかなり簡単なんだけどね。
●指定のワールド位置がカメラに映っているか調べる
using UnityEngine;
public static partial class Extensions //※クラス名は任意
{
/// <summary>
/// カメラの Viewport 内に表示されているか?
/// 2023/08/01 Fantom
/// http://fantom1x.blog130.fc2.com/blog-entry-430.html
/// </summary>
/// <param name="cam">判定するカメラ</param>
/// <param name="pos">対象ワールド位置(transform.position)</param>
/// <returns></returns>
public static bool InCamera(this Camera cam, Vector3 pos)
{
if (cam == null)
return false;
var vp = cam.WorldToViewportPoint(pos);
return (0f <= vp.x && vp.x <= 1f && 0f <= vp.y && vp.y <= 1f);
}
}
Camera.WorldToViewportPoint の内容は公式マニュアル見て欲しいが、戻り値の Vector3 は翻訳すると『ビューポート空間は正規化されており、カメラに対して相対的です。カメラの左下は (0,0) です。右上は (1,1) です。Z 位置はカメラからのワールド単位です。』となっているので、要するに値が 0~1 に入ってれば、カメラのビューポートに入ってると考えて良い。
ただ、指定位置は transform.position 等、あくまで「点」でしかないので、メッシュなどが大きい場合、中心(Pivot)は画面から外れてるが、メッシュの一部が画面に少し見えている、なんてことが起こるかもしれない。そういうときはメッシュのバウンディングボックス(Renderer.bounds)等の8隅を調べるなどすれば良いかもしれない。
また、Unity では全ての GameObject は Transform 持ってるので、いっそ transform を引数にしたものを作っておくのも良いかもね。
●指定のワールド位置がカメラに映っているか調べる (Transform 指定)
using UnityEngine;
public static partial class Extensions //※クラス名は任意
{
/// <summary>
/// カメラの Viewport 内に表示されているか?
/// 2023/08/01 Fantom
/// http://fantom1x.blog130.fc2.com/blog-entry-430.html
/// </summary>
/// <param name="cam">判定するカメラ</param>
/// <param name="tr">対象Transform</param>
/// <returns></returns>
public static bool InCamera(this Camera cam, Transform tr)
{
if (cam == null || tr == null)
return false;
var vp = cam.WorldToViewportPoint(tr.position);
return (0f <= vp.x && vp.x <= 1f && 0f <= vp.y && vp.y <= 1f);
}
}
まぁ、好きに改造して使って欲しい。
今回も VRM Live Viewer で使ってる再利用可能コードの一部となる。タイトルにもある「特定位置がカメラに映っているか調べる」は、ポストエフェクトの被写界深度の自動フォーカスで「ボーン対象」の選択に使っている。ボーンの選択が無い頃はモデルの「Head」(顔の辺り)1つが対象になっていて、要するに顔が常にフォーカスされていた。しかし、カメラモーションによっては下(足)から上(顔)へ接写など、一時的に顔が画面から外れてしまうことがあったんだよね。そういうとき顔以外にもフォーカスを当てる必要があった。つまり「顔が画面から外れていれば、他の近いボーンをフォーカス対象とする」ようにしたかったんだよね。
まぁそんなことを VRM Live Viewer リリース以来、約5年近くやってる。最近はたまに「VRM Live Viewer を github で公開しろ」とか言ってくる人もいるが、冒頭に述べたように内容的にはオープンソースの塊だったりする。それをアプリに最適化、一部都合良く手を加えて改造してるので、ほとんど汎用性は無い。汎用的にするためにアプリ依存コードを除外して、一般化すればオープンソースと同じになる(元に戻る)。なので、全然意味が無い。ブログの冒頭でも言ってるように、最初からどうやって作ったかも、使ってるオープンソースも全部書いてあるので、本当に意味を感じない。
まぁ、言ってくる人は学生さん(?)が多い気がするが、私も学生の頃は何でもできるわけじゃなかったよ。でも四苦八苦して色々考えてたのがいい経験になったのは間違いない。最初から「答えを教えろ」(そしてコピーして作ったものを自分のものにさせろ)というのは、かなり失礼なことだと思う。前述したように「世界中の人と同じ条件で作っている」(恐らく同じVRMアプリ開発者内では最底辺の環境)ので、同じことは誰でもできる。Unity なんてお金を出せば AssetStore でいくらでも優れたアセットを導入できるからね。私は無料で配布してるので、有料アセットなど買えないのだよ。だからオープンソースと自分のこれまでの知識と技術を駆使してるだけで、特別なことはしてない。せいぜい自分なりの工夫を重ねてるだけで、何でもやって貰えるのが当たり前と思われても非常に困る(← 一時期クレクレが年間50件は超えていて頭が痛かった=仮に1件5万~30万取れるとすると250~1500万の仕事をタダ働きすることになる。普通にサラリーマンの給料で換算しても約5年だと1500~2500万を無料で提供してるからね。これ以上仕事してタダでよこせと言われても、自分ならどうかと考えてみて欲しい)。
経験不足は経験でしか補えないからね。私は学生時代書いたコード(C, C++)を未だに持っているが、物凄く苦労して2週間~1ヶ月くらいかけて作ったコード(システム)も、今では1日くらいで作れる。それくらい人は成長できるのだよ。答えはブログでもオープンソースでもググって大量の資料でも、今では充実してるので(昔はそんなに便利じゃなかった)、ヒントはやるから諦めないで精進しろとしか言えないのが本音なんだよね。今後はビジュアルスクリプティングでコンポーネント組み合わせるだけで作れるし、AI がコード自動生成してくれる時代も来るから、今よりもっと「自分で考える」ことができない人増えるだろうね。与えてくれるのが当たり前って考え方を捨てないと、成長はできないと思うよ。
(関連記事)
【Unity】固定背景画像(2D)を表示する
【Unity】【C#】画面解像度とアクペクト比(整数)を求める
【Unity】【C#】HDR Color を計算(変換)する
【Unity】【C#】ガンマ(Gamma, sRGB) - リニア(Linear) 値の相互変換
【Unity】【C#】3DText(TextMesh) を半透明より手前に表示する
【Unity】マテリアルで使用しているシェーダを調べる 
2022/11/30 Wed [edit]
わりと何でもない事だけど、意外とググってもあまり出てこなかったので、メモ。
簡単に言うと、判別はシェーダの名前(文字列)で判別するのが簡単。名前はインスペクタで Material をクリックしたときに出る情報の一番上にある「Shader」の文字列だね。Standard シェーダを使っていれば「Standard」、MToon シェーダを使ってるなら「VRM/MToon」となってるあれだ。正確に判別するなら、スラッシュ('/')を含めたフルパスにすれば良いし、バリエーション[Standard (Specular setup) 等]を含めるなら一部を判別するのも良い。
(※) Unity 2020.3.34f1 / Windows11(x64) で確認
そう考えるとコードにするのは簡単だ。例えば以下のような感じで判別すれば良いだろう。
●マテリアルに Standard Shader が当てられていたら、メッセージを出す
Material material; //何かのマテリアルがセットされているとする
if (material.shader.name.Equals("Standard", StringComparison.Ordinal))
{
Debug.Log("This material uses Standard Shader.");
}
Eqauls に StringComparison.Ordinal オプションを付けてるのは、以前記事にした「StartsWith, EndsWith が遅いのなら、Equals も遅いのか?」のときの知見から。"==" 演算子でも良いが、参照比較と紛らわしいので(文字列の場合はおおよそ大丈夫だが)、最近は Ordinal オプション付きで比較することが多い(実行速度も速い)。Unity マニュアルにも文字列比較の遅さが指摘されているが、私が実験した所、Ordinal オプション(単純な文字列の並びだけで比較)さえ付けていれば、実行速度が極度に遅くなることは無かった(100文字程度なら全然変わらない)。C# のバージョンにもよるかもだが、覚えておくと役に立つこともあるだろう。
以上の事が理解できたなら、よく使うものは拡張メソッドにしておくのが簡単だ。例えば Stanard, MToon 判別なら、以下のように作るのも良い。
●Material に Standard Shader 系/MToon 系が使われているか?(バリエーションも含む)
using System;
using UnityEngine;
/// <summary>
/// Material に Standard Shader 系が使われているか?(バリエーションも含む)
/// 2022/11/30 Fantom
/// http://fantom1x.blog130.fc2.com/blog-entry-421.html
/// </summary>
/// <param name="material">調べる Material</param>
/// <returns>true のとき Standard (バリエーション含む)</returns>
public static bool IsAnyStandardShader(this Material material)
{
if (material == null)
return false;
return material.shader.name.StartsWith("Standard", StringComparison.Ordinal);
}
/// <summary>
/// Material に MToon Shader 系が使われているか?(バリエーションも含む)
/// ※"VRM/MToon" だが、UniVRM 0.89時点なので注意(1.0以降は"VRM10/MToon10")
/// 2022/11/30 Fantom
/// http://fantom1x.blog130.fc2.com/blog-entry-421.html
/// </summary>
/// <param name="material">調べる Material</param>
/// <returns>true のとき MTonn (MToon10 等含む)</returns>
public static bool IsAnyMToonShader(this Material material)
{
if (material == null)
return false;
return material.shader.name.Contains("MToon");
}
実際にはプロジェクトに何のシェーダが含まれているかで真になる範囲は異なってしまうので、厳密にしたければ、Equals(name, StringComparison.Ordinal) でフルパス完全一致にするのも良いだろう。その辺は好きにやって欲しい。
問題は文字列判別なので、「Autodesk Interactive」のように名前が変わったもの(Unity2017 以前は「Standard (Roughness setup)」となっていた)は注意が必要だけどね。厳密にするならプリプロセッサ(#if~)で Unity バージョン分けした方が良いかも知れない。
最近は VRM Live Viewer でもシェーダを使う機能が多くなってきた。実はなるべく Unity の標準機能で作った方が後バージョンの互換性が高いので、以前は避けていた感あるが、Unity は既にこれまでの Standard Shader 等を使う BRP(Built-in Render Pipeline) はレガシーとしているので、いずれ SRP(Scriptable Render Pipeline) に移行しなくてはならないのがわかってるんだよね。
問題はシェーダを使った機能は互換性が全く無くなってしまうこと。しかし現行の Unity製アプリやサービス(特に2020年以前からあるもの)は BRP を前提として作られているので、なかなか移行タイミングが難しい。全てを捨てて移行するならともかく、過去の大量の資産(対応素材)も捨てることになるからね。なので Asset Store でも「SRPとの互換性」が表示されていたり、BRP/SRP 両方の .unitypackage が含まれているものも出てきている。まぁ、有料アセットはサポートあるもの多いけど、無料アセットはほぼ絶望的だね。まず移行できるものは少ない。なので少し諦めがついたという所かな。こうなれば SRP に移行する直前まで、BRP でできることを何でも入れてしまえ、みたいな(笑)。まぁ、結果できることは増えているよ。しかし本当に SRP に移行するときには、ほぼ全て作り直し(内部的には別物)しなくちゃならないけどね。
実際、無料アプリって報酬0だから、システム的な仕様変更・移行はめちゃくちゃしんどい。それに現代は「アプリは無料で作って貰えるのが当たり前」って風潮が強いので「あれ欲しい・これ欲しい。次は〇〇あったらいいな」と延々と言われ続ける。企業なら先行投資としていずれ利益になるから良いかもだが、個人ではただ単に知らない人にタカられてるだけに過ぎないからね。超有名なソフトの開発者が通常表に出て来ず、雲隠れしてしまう気持ちが何となくわかる。無名な私ですら年間100件ほど来るのだ。有名なら1000件は下らないだろう(有名なクリエーターが年間700件[=1日2件]でも大量に時間を取られる事に気づき、仕方なく有料にした話もある)。
よく考えてみればわかるが、個人に対する要求はその人の貴重な活動時間を奪い、他人のために無償で時間を使え、と言われてるようなものだ。これはその人がやりたい活動や予定を阻害してるのと同義となる。逆に自分が何かやろうと思っていたときに、他人の都合で邪魔されたら嫌だろう?特に個人で活動・配布してくれてる人には気をつけて欲しいかな。要求だけ突きつけて、規約も守らず、いい加減に使われていたら、やる気を無くす人も多い(パッタリやめてしまう人も少なくない)。いつでも「自分が逆の立場だったらどう感じるか?」を考えて欲しいね。そういうお互いの理解が無いとずっとは続けられない(だいたい1~2年が最初の山、3年超えると安定し、5年で世の中の状況が変わり進退を決める時が来る。10年超えはもう活動もその人の一部となる。そこまで行ったら沢山の出会いと別れを経験する[=もう活動やめてしまった人の方が圧倒的に多くなる]。結局他人の思いつきでタダ働きさせられた事実だけが残る)。あと5年経ったら今活動している人どれくらい残ってるかな…?ちなみに私は20年はとっくに超えている(昔はSNS自体が存在しなかっただけ)。もうそうなると知識×経験の掛け合わせで、無限にアイデアは浮かぶようになる(現に VRM Live Viewer は4年を超えてるがアイデアは尽きたことなく、今なお増え続けている)。時間はいくらあっても足りないくらいにね(笑)。
(関連記事)
【Unity】【C#】StartsWith, EndsWith が遅いのなら、Equals も遅いのか?
【Unity】ガンマ(Gamma, sRGB) - リニア(Linear) 値の相互変換
【Unity】Quality (グラフィック品質) を文字列で取得/設定する
【Unity】【エディタ拡張】フィールドの値などをインスペクタで横に並べて表示する 
2022/10/01 Sat [edit]
プロジェクトが大きくなってくるとデバッグが大変になってくるね。VRM Live Viewer は既に4周年を超えてしまったこともあって、とても巨大なシステムになってしまっている。Unity では Debug.Log を使うことも多いのだが、多くのオブジェクトがあると個々の状態が調べづらいので、最近はよくインスペクタをデバッグ代わりに使っている。
ただ独自クラス・構造体などを使うことも多いので、デフォルトだと全て縦に並んでしまうんだよね。なので Vector3 などの (x, y, z) のようにまとまってる要素は横に並べてすっきり表示したいことが多い。
そんな時は エディタ拡張の PropertyDrawer を使うと良い。簡単なものをメモとして載せておくので、色々応用すると良いだろう。

(※) Unity 2020.3.34f1 / Windows11(x64) で確認
●X, Y, Z 各軸の true / false 構造体の定義とインスペクタでの表示(PropertyDrawer)
using System;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Example //※名前は任意
{
//定義クラス・構造体
/// <summary>
/// X, Y, Z 各軸の true / false 構造体
/// </summary>
[Serializable]
public struct AxisBool
{
public bool x;
public bool y;
public bool z;
public AxisBool(bool x, bool y, bool z)
{
this.x = x;
this.y = y;
this.z = z;
}
}
#if UNITY_EDITOR
//エディタ拡張(インスペクタ表示)
[CustomPropertyDrawer(typeof(AxisBool))]
public class AxisBoolDrawer : PropertyDrawer
{
static readonly GUIContent X_LABEL = new GUIContent("X");
static readonly GUIContent Y_LABEL = new GUIContent("Y");
static readonly GUIContent Z_LABEL = new GUIContent("Z");
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUIUtility.singleLineHeight;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var xProperty = property.FindPropertyRelative("x");
var yProperty = property.FindPropertyRelative("y");
var zProperty = property.FindPropertyRelative("z");
//名前
label = EditorGUI.BeginProperty(position, label, property);
Rect contentPosition = EditorGUI.PrefixLabel(position, label);
//ラベル
contentPosition.width *= 1f / 3f; //3つ並べる場合 (n 個のとき、1 / n)
EditorGUI.indentLevel = 0;
EditorGUIUtility.labelWidth = 15f; //ラベル幅(適当)
//各要素
EditorGUI.PropertyField(contentPosition, xProperty, X_LABEL);
contentPosition.x += contentPosition.width;
EditorGUI.PropertyField(contentPosition, yProperty, Y_LABEL);
contentPosition.x += contentPosition.width;
EditorGUI.PropertyField(contentPosition, zProperty, Z_LABEL);
EditorGUI.EndProperty();
}
}
#endif
}
ポイントは定義クラス・構造体用の PropertyDrawer を継承したクラスを作ることで([CustomPropertyDrawer] 属性も付ける)、これは以前に書いた「インスペクタで入力不可(Disable)な属性を作る」と同じである。要するに表示位置を計算して描くだけなので、定型処理(文)のように考えた方が楽だろう。
テストは適当に GameObject にアタッチしたスクリプトに、フィールドとして追加すれば良い(public または [SerializeField] 属性を付ける)。
ついでにスクショのもう1つの方も書いておくと、以下のようになる。
●X, Y, Z 各軸の Min~Max 構造体の定義とインスペクタでの表示(PropertyDrawer)
using System;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Example //※名前は任意
{
//定義クラス・構造体
/// <summary>
/// X, Y, Z 各軸の Min~Max 構造体
/// </summary>
[Serializable]
public struct RangeVector3
{
public float xMin;
public float xMax;
public float yMin;
public float yMax;
public float zMin;
public float zMax;
public RangeVector3(float xMin, float xMax, float yMin, float yMax, float zMin, float zMax)
{
this.xMin = xMin;
this.xMax = xMax;
this.yMin = yMin;
this.yMax = yMax;
this.zMin = zMin;
this.zMax = zMax;
}
}
#if UNITY_EDITOR
//エディタ拡張(インスペクタ表示)
[CustomPropertyDrawer(typeof(RangeVector3))]
public class RangeVector3Drawer : PropertyDrawer
{
static readonly GUIContent X_MIN_LABEL = new GUIContent("X Min");
static readonly GUIContent X_MAX_LABEL = new GUIContent("X Max");
static readonly GUIContent Y_MIN_LABEL = new GUIContent("Y Min");
static readonly GUIContent Y_MAX_LABEL = new GUIContent("Y Max");
static readonly GUIContent Z_MIN_LABEL = new GUIContent("Z Min");
static readonly GUIContent Z_MAX_LABEL = new GUIContent("Z Max");
const float lineSpace = 2f; //行間
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUIUtility.singleLineHeight * 3 + lineSpace * 2;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var xMinProperty = property.FindPropertyRelative("xMin");
var xMaxProperty = property.FindPropertyRelative("xMax");
var yMinProperty = property.FindPropertyRelative("yMin");
var yMaxProperty = property.FindPropertyRelative("yMax");
var zMinProperty = property.FindPropertyRelative("zMin");
var zMaxProperty = property.FindPropertyRelative("zMax");
//名前
label = EditorGUI.BeginProperty(position, label, property);
Rect contentPosition = EditorGUI.PrefixLabel(position, label);
//ラベル
contentPosition.width *= 1f / 2f; //2つ並べる場合 (n 個のとき、1 / n)
EditorGUI.indentLevel = 0;
EditorGUIUtility.labelWidth = 40f; //ラベル幅(適当)
//各要素
contentPosition.height = EditorGUIUtility.singleLineHeight;
EditorGUI.PropertyField(contentPosition, xMinProperty, X_MIN_LABEL);
contentPosition.x += contentPosition.width;
EditorGUI.PropertyField(contentPosition, xMaxProperty, X_MAX_LABEL);
contentPosition.x -= contentPosition.width;;
contentPosition.y += EditorGUIUtility.singleLineHeight + lineSpace;
EditorGUI.PropertyField(contentPosition, yMinProperty, Y_MIN_LABEL);
contentPosition.x += contentPosition.width;
EditorGUI.PropertyField(contentPosition, yMaxProperty, Y_MAX_LABEL);
contentPosition.x -= contentPosition.width;;
contentPosition.y += EditorGUIUtility.singleLineHeight + lineSpace;
EditorGUI.PropertyField(contentPosition, zMinProperty, Z_MIN_LABEL);
contentPosition.x += contentPosition.width;
EditorGUI.PropertyField(contentPosition, zMaxProperty, Z_MAX_LABEL);
EditorGUI.EndProperty();
}
}
#endif
}
こちらも contentPosition を横・縦に移動して描いているだけと考えれば色々応用は利くだろう。日本語だとラベル幅からはみ出るかも知れないが(私は英語インターフェイスに慣れてしまってるので、日本語版は使ったことがない)、その辺はよしなに(笑)。
(参考)
・【Unity】最小と最大の値を管理する構造体を作りたいの
(関連記事)
【Unity】インスペクタで入力不可(Disable)な属性を作る
【Unity】インスペクタの表示項目を動的に変更する
【Unity】インスペクタの UnityEvent を並べ替え可能にする
【Unity】インスペクタの配列やリストを並べ替え可能にする
【Unity】インスペクタの値を保持したまま変数をリネームする
【Unity】独自のギズモ(Gizmo)を表示する
- 関連記事
-
-
【Unity】IncrementalCompiler でのエラー:Unloading broken assembly Packages/com.unity.incrementalcompiler/Editor/Plugins/Unity.PureCSharpTests.dll, this assembly can cause crashes in the runtime
-
【Unity】プロ生ちゃんを動かす!(Unity4)
-
【Unity】ARCore を使って現実世界にプロ生ちゃんを召喚してみる
-
【Unity】【C#】ピンチ操作を取得してコールバックする
-
【Unity】色形式:Unity の Color と Android の ARGB(int32) の相互変換をする
-
category: Unity
thread: ゲーム開発
janre: コンピュータ
tag: Unityリファレンス Unityライブラリ エディタ拡張【Unity】【C#】音量設定用にデシベル(dB)変換をする 
2022/06/20 Mon [edit]
これも VRM Live Viewer の音量設定を作ったときのメモ。
Unity には AudioMixer という複数の音量(Master, Music, SE, ...等)をミキシングして出力できる便利な機能があるのだけれど、設定する値は 0~1f ではなく、デシベル[dB](-80dB~0dB) なんだよね。
デシベル変換はググればいくらでも出てくるけど、定型処理なら拡張メソッドにしておくと楽なので、いつものように簡単に定義しておいたもの。
あと AudioMixer 使う際に気になる事があったので、少しばかり工夫してる。
(参考)デシベルのすすめ (KAYAC engineers' blog)
(※) Unity 2020.3.34f1 / Windows11(x64) で確認
■音量(0~1f) → デシベル(-80~0dB) に変換する
●音量(0~1f) → デシベル(-80~0dB) に変換する
using UnityEngine;
namespace Exsample
{
public static partial class Extensions //※クラス名は任意
{
//無音とみなすデジベル(dB) や音量
const float DECIBEL_MIN = -80f; //この dB 以下をミュート(音無し)とする
const float DECIBEL_MUTE = DECIBEL_MIN * 10; //単位: dB (-80dB だと僅かに聞こえる気がするので、とりあえず10倍してる)
const float VOLUME_MIN = 0.0001f; //この volume 以下をミュート(音無し)とする
/// <summary>
/// 音量(0~1f) → デシベル(-80(-800)~0dB) に変換
/// ※ただし、volume ≤ 0.0001f のとき -800 [db] を返す
/// (参考) https://techblog.kayac.com/linear-vs-decibel
/// </summary>
/// <param name="volume">0~1f</param>
/// <returns>-80(-800)~0 [dB]</returns>
public static float ToDecibel(this float volume)
{
if (volume <= VOLUME_MIN) //0.0001f を最小値とする
return DECIBEL_MUTE; //-800dB (-80dB だと僅かに聞こえる気がするので、とりあえず10倍してる)
var db = 20 * Mathf.Log10(volume);
return Mathf.Clamp(db, DECIBEL_MUTE, 0f);
}
}
}
参考資料そのままだが、冒頭に書いた「少しばかり工夫してる」とは、最小値 (0.0001) 以下では DECIBEL_MUTE (-800dB) を返すところで、AudioMixer の Threshold のデフォ値(-80dB) 付近だと、わずかに音が聞こえる気がするからだ。もちろん Threshold の値を上げても良いが、毎回調整するのは面倒なので(笑)、とりま 10倍した値(-800dB)を返すようにしてる。これをそのまま AudioMixer.SetFloat に当ててやれば、確実に無音(ミュート)できる。
(参考)デシベルのすすめ (KAYAC engineers' blog)
アプリではわかりやすいように音量を 0~100(%) 表記してるが、やはり定型処理となるので、以下のような簡単な拡張メソッド(.NET 4.x)を作るのも良いだろう。
//音量(0~100%) → デシベル(-80(-800)~0dB) に変換
public static float PercentToDecibel(this float per) => ToDecibel(per * 0.01f);
■デシベル(-80~0dB) → 音量(0~1f) に変換する
●デシベル(-80~0dB) → 音量(0~1f) に変換する
using UnityEngine;
namespace Exsample
{
public static partial class Extensions //※クラス名は任意
{
/// <summary>
/// デシベル(-80~0dB) → 音量(0~1f) に変換
/// (参考) https://techblog.kayac.com/linear-vs-decibel
/// </summary>
/// <param name="decibel">-80~0dB</param>
/// <returns>0~1f</returns>
public static float FromDecibel(this float decibel)
{
if (decibel <= DECIBEL_MIN) //-80dB を最小値とする
return 0f;
return Mathf.Pow(10f, decibel / 20f);
}
}
}
これも参考資料そのままなので、詳細はそちらを参考にして欲しい。
(参考)デシベルのすすめ (KAYAC engineers' blog)
これも少し手を加えたのは、-80dB を引数に与えると 0.0001 の計算値になるので、強制的に 0 (無音) にしている所。この方が実用的で使いやすかっただけのこと。
例えば簡単にテストすると、以下のような結果になる。
var volMin = 0f;
var volMax = 1f;
var volOverMin = -1f;
var volOverMax = 2f;
//volume (0~1f) → dB (-80(-800)~0dB) 変換
var dBMin = volMin.ToDecibel();
var dBMax = volMax.ToDecibel();
var dBOverMin = volOverMin.ToDecibel();
var dBOverMax = volOverMax.ToDecibel();
Debug.Log($"volMin = {volMin}, ToDecibel = {dBMin}");
Debug.Log($"volMax = {volMax}, ToDecibel = {dBMax}");
Debug.Log($"volOverMin = {volOverMin}, ToDecibel = {dBOverMin}");
Debug.Log($"volOverMax = {volOverMax}, ToDecibel = {dBOverMax}");
//dB (-80(-800)~0dB) → volume (0~1f) 変換
var dBMinToVol = dBMin.FromDecibel();
var dBMaxToVol = dBMax.FromDecibel();
var dBOverMinToVol = dBOverMin.FromDecibel();
var dBOverMaxToVol = dBOverMax.FromDecibel();
Debug.Log($"dBMin FromDecibel = {dBMinToVol}");
Debug.Log($"dBMax FromDecibel = {dBMaxToVol}");
Debug.Log($"dBOverMin FromDecibel = {dBOverMinToVol}");
Debug.Log($"dBOverMax FromDecibel = {dBOverMaxToVol}");
volMax = 1, ToDecibel = 0
volOverMin = -1, ToDecibel = -800
volOverMax = 2, ToDecibel = 0
dBMin FromDecibel = 0
dBMax FromDecibel = 1
dBOverMin FromDecibel = 0
dBOverMax FromDecibel = 1
(関連記事)
【Unity】【C#】HDR Color を計算(変換)する
【Unity】【C#】ガンマ(Gamma, sRGB) - リニア(Linear) 値の相互変換
【C#】文字列 → float (浮動小数点) 変換でエラーが出るときは…