Unity でビルド時に自動でバージョンに日付を付与する
Unity でビルド時に自動でバージョンに日付を付与するEditor拡張
PlayerSettings.bundleVersion が0.1の時 ビルド時に 0.1.yyyymmddに変更します。
ビルド完了時、もしくはエラー発生時に PlayerSettings.bundleVersion を元に戻します。
Magic Leapの場合は PlayerSettings.Lumin.versionName も合わせて更新している。
#if UNITY_EDITOR using System; using UnityEditor; using UnityEditor.Build; using UnityEditor.Build.Reporting; using UnityEngine; public class BuildVersionWithDate : IPreprocessBuildWithReport, IPostprocessBuildWithReport { public static string date = DateTime.Now.ToString("yyyyMMdd"); private string baseVersion; private string baseLuminVersionName; private BuildTarget baseTarget; /// ビルド前処理 public void OnPreprocessBuild(BuildReport _report) { Application.logMessageReceived += OnBuildError; baseVersion = PlayerSettings.bundleVersion; //バージョンに日付を付与する PlayerSettings.bundleVersion = $"{baseVersion}.{date}"; baseTarget = _report.summary.platform; //MagicLeapはLumin.versionNameも更新 if (baseTarget == BuildTarget.Lumin) { baseLuminVersionName = PlayerSettings.Lumin.versionName; PlayerSettings.Lumin.versionName = PlayerSettings.bundleVersion; } } /// ビルド後処理 public void OnPostprocessBuild(BuildReport _report) { Restore(); } private void OnBuildError(string condition, string stackTrace, LogType type) { if (type == LogType.Error) { Restore(); } } //Rollback private void Restore() { Application.logMessageReceived -= OnBuildError; //バージョンを元に戻す PlayerSettings.bundleVersion = baseVersion; if (baseTarget == BuildTarget.Lumin) { PlayerSettings.Lumin.versionName = baseLuminVersionName; } AssetDatabase.SaveAssets(); } // 開発モードか? private bool isDevelopment(BuildReport report) { return (report.summary.options & BuildOptions.Development) != 0; } /// 実行順 /// MagicLeapにおいて、versionNameを動的に設定するために /// MagicLeapManifestBuildProcessorより先に実行したいため-1を設定する public int callbackOrder { get { return -1; } } } #endif
UIWidgets メモ/リンク集
UIWidgets
Flutterリファレンス
Tipsなど
可変framerateを停止する
本家のReadmeにもあるが、unity 2019.3以降はUIWidgetsがOnDemandRenderAPIを使用して実装されるため、アニメーションが動作していないときは、フレームレートが自動的に落ちるようです。
バッテリー消費量を削減できるメリットがある反面、UnityのGameView自体のFPSも落ちてしまうので、3D CGがカクついてしまいます。
- 以下のコードを書くことで無効化できます。
- ドキュメントにはVsyncもOffにしろとあったけど、やらなくても、動いたみたい。
public class UIWidgetsExample : UIWidgetsPanel { protected override void OnEnable() { base.OnEnable(); //可変framerateを停止させる Window.onFrameRateCoolDown = () => { }; Window.onFrameRateSpeedUp = () => { }; } //.... }
Splitview
- Splitview のAddon作った github.com
Unityで特定の点を中心にScaleを変化させる (ScaleAround)
UnityでGameObjectの原点ではなく、指定した点を中心にScaleを変更する方法。
rotationは原点と回転軸を指定して回転できる RotateAround があるけど、Scaleには無いので作った。
タッチパネルの2本指操作や、両手VRコントローラで拡大させる時に、拡大の中心を動的に変更できて便利。
/// pivot を中心に、target のScaleを変化させる public void ScaleAround(GameObject target, Vector3 pivot, Vector3 newScale) { Vector3 targetPos = target.transform.localPosition; Vector3 diff = targetPos - pivot; float relativeScale = newScale.x / target.transform.localScale.x; Vector3 resultPos = pivot + diff * relativeScale; target.transform.localScale = newScale; target.transform.localPosition = resultPos; }
実際は、Transformの原点で拡大するんだけど、動的に位置を補正している。
Unity Bolt Tips4.State Machine を利用する
BoltではState Machineを記述することができます。
State Machine とは?
- State Machine は 1つ以上の状態(State)と 状態間の遷移(Transition)で構成されます。
- 状態は、特定のアクションを実行し続けます。
- 状態は、特定のセンサーからのイベントを検知した際に、別の状態へ遷移します。
参考:状態機械
BoltにおけるStateMachineの作成
Projectタブで、右クリック >
Create
>Bolt
>State Macro
で作成できます。初期状態ではStateGraphには何もありません。
右クリック >Create Flow State
で最初のStateを作成します。
ダブルクリックすると、Stateの中身を記述できます。
- On Enter State (Stateが開始されたとき)
- Update (そのState中に毎フレーム呼ばれる)
- On Exit State (Stateが終了する時)
のUnitを起点として、それぞれの記述ができます。
別のStateはまた、右クリック > 'Create Flow State' で作成できます。 最初のStateをCtrlを押しながら選択し、2つ目のStateへカーソルを動かすと、Transition を作成できます。
Transition の矢印をダブルクリックすると Transitionの記述ができます。
最初はTrigger Transition
のUnitがあるだけです。
例えば以下のような記述を行うことでSpaceキーが押されたら遷移するという記述を行うことができます。
AnyState
- AnyStateのUnitを利用すると、どのStateであっても、遷移のトリガー条件を満たせば、他の状態を呼び出すことができます。
- これらを利用して複数のStateを同時に実行することも可能です。
呼び出し順番
また、On Enter State の呼び出し順番などは、以下のブログの方が調査結果をまとめているので参考になると思います。
続き
Unity Bolt Tips3.Formula Unitを利用する
Boltには Formula Unit という、論理式や数式を直接評価できる強力なユニットがあります。
簡単に言うと、Unit内で テキストコードを書くことで、if文や計算式を記述できます。
その使い方を書きます。
Formula Unit のメリット
通常Boltで分岐処理や加減算処理を行うには、Branch Unit (IF文) や Add Unit (足し算) などを利用するのですが、複雑な計算を記述するとノードが複雑なり、読みづらくなってきてしまいます。
Formula Unit に直接テキスト形式のコードを記述することで、ある程度複雑な分岐を簡潔に記載することができ、書きやすさ、読みやすさが向上します。
Formula Unit のデメリット
- パフォーマンスが悪い(遅い)です。
- オーバーヘッドが発生してしまうため、通常の演算子ユニットを利用するのに比べて Formula Unit のを使用する方が著しく遅くなるとマニュアルに記載されています。
- そのため、Updateなど毎フレーム呼び出す処理中で利用する事は推奨されていません。
Formula Unitの使い方
Bolt編集画面で、右クリックから Formula Unitを検索、選択します。
Formula Unitは 数式を入力するテキストフィールドと、入力引数の数を指定するInputsの2つの項目があります。
テキストフィールドに条件式などを記述することで、任意の制御を行えます。
- 入力引数は前から、a,b....z とアルファベット変数に割り当てられるので、式内で利用できます。
- if 文や3項演算, Vector2/Vector3/Vector4 型なども扱うことができます。
- abs/sin/cos/floor などの数式も利用できます。
記述内容に意味はあまり無いですがサンプルとして
式の内容にエラーがある場合は、例外が発生します。
EvaluationException: no viable alternative at input '<EOF>' at line 0:-1 Bolt.Dependencies.NCalc.Expression.Evaluate (Bolt.Flow flow) (at <......>:0)
使える式や制御方法の詳細はマニュアルを確認してください。
続き
Unity Bolt Tips2.Boltの独自ノードをC#で作成する
前回の続きです BoltのノードはUnityのC#のメソッドが利用できるのですが、自分で関数ノードを作成して再利用する事もできます。
Boltのノード作成の基礎的なメモ
MacroとEmbed
BoltのフローのソースにはMacroかEmbedを選択して利用できます。
それぞれの違いは以下の通り
項目 | Embed | Macro |
---|---|---|
関係性 | グラフはFlow Machine Component 自体に埋め込まれます | グラフはMacro AssetとしてProjectに保存され、Component から参照されます。 |
再利用性 | グラフを他のFlow Machineから再利用することはできません。 ただし、Prefab間で共有することはできます。 Component自体が削除されると、グラフも一緒に削除されます。 |
同じMacroを複数のFlowMachine間で共有することができます。 あるGameObjectのFlowMachineを削除した場合でも、Macro AssetはProjectに残ります。 |
永続性 | FlowMachineの設定を、EmbedからMacroに変更するとグラフ自体が削除されます。 | MacroからEmbedに切り替えてもグラフ自体はEmbedとして残ります。 |
Sceneの参照 | Prefabとして保存されていない場合は、Embed内のグラフはScene内のGameObjectを参照することができます。 | Macroの状態ではどのSceneにも属していないため、シーン内のGameObjectを参照することはできません。 |
Prefab | Editor上でPrefabをInstanciateした場合動作しないことがある。 | Editor上でもすべてのPrefab内で利用することができます。 |
Macroとして再利用性の高いノードをライブラリとして作成することで、効率的な開発を行うことができます。
Macro利用時のPrefabからのGameObject量産時の変数汚染
ただし、PrefabのGameObjectのFlowMachineとしてMacroを利用した場合 記述内容にも寄るかもしれませんが、 Prefabからシーンに生成されたGameObject間で変数の汚染が発生する事があります。
その場合は、PrefabのMacroをConvertボタンを押してEmbedに変換してやると、解決する場合があります。
詳細調査中なので、間違ってるかもしれません。
SuperUnit
俗にいう関数は、SuperUnitとして定義できます。
右クリック-> Add NodeでSuperUnitを作成
ダブルクリックで編集できます
SuperUnitの入り口と出口はInput/Outputを利用します。 InputノードにControl Inputs(Outputs)キーを入れる必要があります。
MacroをドラッグアンドドロップすることでSuperUnitとして利用することができる
SuperUnitの公式のマニュアルはこちらにあります。
C#で独自Boltノードを作成する
- C#でコードを書く
public class HogeTest { public void TakeDamage(int damage) { Debug.Log("Damage"); } }
SetupWizardでAssemblyを設定しておくと、ノードとして呼び出すことができます。
日本語のノードも作成可能です。
public class GraphTest : MonoBehaviour { public int 日本語ノード(int 体力,int 気力) { return 0; } }
- このスレッドのように Unitクラスを継承してBolt特有のプロパティーをきちんと設定することで、より細かい設定も可能です。
using Bolt; using Ludiq; [UnitTitle("InOutUnit")] [UnitCategory("Littlewing/Test")] public class InOutUnit : Unit { [DoNotSerialize] public ControlInput input { get; private set; } [DoNotSerialize] public ControlOutput output { get; private set; } [DoNotSerialize] public ValueInput valueIn { get; private set; } [DoNotSerialize] public ValueOutput valueOut { get; private set; } protected override void Definition() { input = ControlInput("in", Enter); output = ControlOutput("output"); valueIn = ValueInput<float>("valueIn"); valueOut = ValueOutput<float>("valueOut", ReturnFloat); Requirement(valueIn, valueOut); Succession(input, output); } public ControlOutput Enter(Flow flow) { return output; } public float ReturnFloat(Flow flow) { return flow.GetValue<float>(valueIn); } }
Outputを複数使い分ける 分岐/非同期
ControlOutput を条件に合わせて使い分けたり、Coroutine などを利用して非同期でOutputを行いたい場合は flowのreferenceを取得して、任意のタイミングでRunを実行することで出力タイミングや、出力先を変更することができます。
また、以下の例では
[NullMeansSelf]
を利用して、TransformにデフォルトでSelfを設定- float
value
にデフォルト値0.5を設定 [PortLabelHidden]
を利用して、入力Pininput
の名前を隠すtransform
とvalue
は 表示名称に変数名をそのまま利用Succession()
を利用して,Relationを明示[TypeIcon]
でアイコンの設定
などを行っています。
using Bolt; using Ludiq; using UnityEngine; [UnitOrder(0)] [UnitSurtitle("Sur Title")] [UnitSubtitle("Sub Title")] [UnitTitle("InOutUnit2")] [UnitShortTitle("Short Title")] [TypeIcon(typeof(Transform))] [UnitCategory("Littlewing/Test")] public class InOutUnit2 : Unit { [DoNotSerialize] [PortLabelHidden] public ControlInput input { get; private set; } [PortLabel("output1")] [DoNotSerialize] public ControlOutput output1 { get; private set; } [PortLabel("output2")] [DoNotSerialize] public ControlOutput output2 { get; private set; } [DoNotSerialize] [NullMeansSelf] public ValueInput transform { private set; get; } [DoNotSerialize] public ValueInput value { private set; get; } private Flow flow; private GraphReference reference; private Transform m_transform; private float m_value; private ControlOutput out1 => output1; private ControlOutput out2 => output2; protected override void Definition() { input = ControlInput(nameof(input), Enter); output1 = ControlOutput(nameof(output1)); output2 = ControlOutput(nameof(output2)); transform = ValueInput<Transform>(nameof(transform), null); value = ValueInput<float>(nameof(value), 0.5f); Succession(input, output1); Succession(input, output2); } public ControlOutput Enter(Flow flow) { this.flow = flow; reference = flow.stack.ToReference(); m_transform = flow.GetValue<Transform>(transform); m_value = flow.GetValue<float>(value); bool b = true; if (b) Out1(); else Out2(); return null; } private void Out1() { Flow.New(reference).Run(out1); // この呼び方もできる //flow.Invoke(out1); } private void Out2() { Flow.New(reference).Run(out2); // この呼び方もできる //flow.Invoke(out2); } }