テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity】ブラシを使ってステージにGameObjectをサクサクっと配置する

f:id:tsubaki_t1:20191016203522j:plain

ステージに木や岩を配置したい

 ステージを作る際、 木や岩をランダムに配置したい という物があります。出来れば地形に沿った形で配置出来ればよいです。規模が小さかったり規則的に配置している場合はGameObjectで配置(Ctrl + Shiftで地形に沿って配置)を使用すればよいのですが、規模が少し大きめだったりランダムに配置したい場合、これを行うのは少し手間が大きいです。
 良い感じに「適当に配置」した後、「適当に向きを回転」し、場合によっては「適当に拡縮」するというのは予想以上に時間が取られます。まぁ楽しいんですが草木や岩、ゴミ等の自然に配置されないオブジェクトに関しては、出来ればブラシ等で簡単に配置したい所でしょう。

f:id:tsubaki_t1:20191016204915j:plain
規則的に並んでいたり、全てがユニークなオブジェクトならプログラムで何とか…

 ブラシで配置となるとTerrainの機能で草木を配置するのはあるんですが、これは後々の微調整が面倒くさいといった問題があります。なので今回はPolyBrushとProBuilderを使用してオブジェクトの配置を行います。

今回やること

 今回は、PolyBrushでステージ上に木や岩といったGameObjectを配置してみます。この量になるとD&Dではしんどいのですが、ブラシ単位だと割と何とかなります。

f:id:tsubaki_t1:20191016205739j:plain
ステージを岩や木で飾る

 なお注意点として、オブジェクトを配置する土台となるメッシュのスケールはscale( 1.1.1)が望ましいです。異なっている場合でも配置は可能ですが、ブラシで選択した場所と異なる場所に配置されることがあります。
 Planeを拡縮で地面を作っている場合、代りにProBuilderで地面を作ってください。

手順1:準備

 オブジェクトを配置していきます。まずはPolyBrushを現在のプロジェクトにインポートして、オブジェクトを配置する前までの操作を行います。

  1. Window>PackageManagerでパッケージマネージャーを開きPolyBrushをインポート
  2. Tools>PolyBrush>PolyBrushWindowでPolyBrushのウィンドウを開く
  3. Scatter Prefabs on Mesh(右から2番目)を選択

f:id:tsubaki_t1:20191016210722j:plain
PolyBrushのセットアップ

手順2:配置するPrefabの登録

 PolyBrushで配置するPrefabを登録します。ここではPrefabの向きやサイズ等の調整も行います。

  1. PrefabPalette-Defaultを選択して、Add Palette...を選択。新しいパレットを作成。
    • ここで指定した名前がパレット名になります。
  2. CurrentPalleteにPrefabをドラッグ&ドロップで登録。
  3. CurrentPalleteにある登録したPrefabを選択。設定を埋める
    ※この作業は登録した全てのPrefabに対して行う
  4. ブラシで配置したいPrefabにチェックを入れる

f:id:tsubaki_t1:20191016212705g:plain
パレットの作成とPrefabの登録

f:id:tsubaki_t1:20191016213415j:plain
Prefabを配置するときの設定

f:id:tsubaki_t1:20191016213618j:plain
配置するPrefabの選択

手順3:ブラシで塗る

 最後にブラシでPrefabを配置していきます。最初に塗るブラシの最大サイズや強度を設定しておくと後々楽なので、先にセットアップしておきます。

  1. Brush Radius Min/MaxでMaxを適当な大きさ(だいたい編集したい範囲の1/3くらいが楽な印象)を設定
  2. Strangthを0.8~0.9辺りに調整

あとは塗っていきます。

  • メッシュ上でクリックしながらドラッグで、ブラシの範囲内にPrefabを配置
  • Ctrlを押しながらドラッグで、範囲内のPrefabを削除(PolyBrushで配置したもののみ)
  • Ctrlを押しながらスクロールで、ブラシの大きさを変更

f:id:tsubaki_t1:20191016215104g:plain
Prefabを塗る

 なお、PrefabScatterSettingsでPrefabを配置するときの調整が出来ます。とりあえずUse PivotとHit Surface is Parentにチェックは入れて、Avoid Overlapは木を生やす時には入れて、それ以外はStrengthで調整するのが良さそうです。

  • Use Pivot:Pivotがちゃんとしてれば配置したPrefabが浮かなくなります。
  • Hit Surface is Parent:配置したオブジェクトの子オブジェクトとしてPrefabを配置します。
  • Avoid Overlap:同じ個所に何度もPrefabを配置しなくなります。例えば木が密集しすぎるのを防ぎます。

f:id:tsubaki_t1:20191016215610j:plain
Prefabを配置するときのオプション

補足

  • これ、配置するのはステージだけに限定する機能ではありません。敵とかを大量に配置する場合、コレで並べてしまうのは割と面白いと思います。その時はAvoid Overlapにチェックを。
  • 配置するのがPrefabなので、LODやスクリプト、ダメージ判定等も設定出来ます。
  • 配置する際、同じ場所に何度もメッシュが配置されるかもしれませんが(通称:ぶっ刺し)、最近の端末ならある程度は気にしなくても大丈夫です。下手にユニークなメッシュを作るより同じオブジェクトを向きを変えつつ大量に配置した方が安い時もあります。
  • 仕組み上、大量にGameObjectを配置します。ある程度は何とかなるかもしれませんが、量が桁違いになるとSubSceneや、Transformをキャッシュしておいて実行時に配置するスクリプトを用意するのが良いかもしれません。オブジェクトが4万個とか超えるなら。
  • 配置するPrefabはVariantで元のPrefabから派生しておくと、コンポーネントをつけたり調整する場合に色々と楽出来ます。
  • 配置するPrefabのマテリアルはInstancingを有効にしましょう。
  • 配置する際にブラシで配置する機能は土台となるオブジェクトにColliderは無くても動作します。移動判定をNavMeshで作る場合にはうれしい?
  • 配置するPrefabを切り替える場合、プレハブにチェックを入れたり外したりするよりPaletteを切り替える方が楽です。Paletteの複製はPaletteのアセットを複製すれば良いです
  • アセットの操作でPaletteを複製した場合、一度Paletteを変更した後に一覧表示されるようになります。
  • PaletteからPrefabを消すにはCtrl + Backspaceです。

関連

一定以上の規模になると、プロシージャルで作った方が良い

learning.unity3d.jp

規模が少ない場合に使えるTips

tsubakit1.hateblo.jp

ProBuilderで地面を作る場合

  1. Window>PackageManagerでパッケージマネージャーを開きProBulderをインポート
  2. Tools>ProBuilder>Editors>Open Shape Editor Menu Itemを選択
  3. Shape SelectorでPlaneを選択し、設定を埋めてBuildをクリック
    • Width:幅
    • Length:奥行
    • Width/length Segments:幅・奥行のメッシュ分割数

f:id:tsubaki_t1:20191016211356j:plain
Open Shape Editor Menu Itemを開く

f:id:tsubaki_t1:20191016211326j:plain
Shape Toolで作るメッシュの大きさを指定

【Unity】LODGroupの設定を一気に変更する

f:id:tsubaki_t1:20191015221051g:plain

LODGroup

 視覚上のポリゴンを削減するという用途でLODGroupという機能は非常にありがたい機能です。これは単純にローポリのモデルに差し替えるというだけではなく、遠距離に要るキャラクターのメッシュを非表示にする(≒アニメーションとスキニングのコストを削減出来る)といった点でもありがたい技術ではあります。

 このLOD Group、モデル切り替えの条件が「画面内におけるモデルの高さ」という条件で、調整には少し苦労するかもしれません。距離ベースのLODと異なり、近づいた時にいきなり巨大なオブジェクトが唐突に表れるという事は無いのですが、どの距離で表示するのかといった事は開発者のノリに依存します。

f:id:tsubaki_t1:20191015222835g:plain
元々の大きさに関係なく、画面の占有率(高さベース)でLODが切り替わる

複数のLODを編集したい

 このLODの仕組みで困るのが、複数のLODを一括変更できないという点です。複数のLODを選択すると、Multi Object Editing not supportと表示され、編集ができません。

 これは少し面倒くさいです。例えばキャラクター等をLODでカリングしようと思ったとき、キャラクター毎にLOD Groupを調整する必要が出てきます。LOD Groupは複数の設定項目を持てる上、数字入力が出来ないので本当に面倒くさいです。

 

f:id:tsubaki_t1:20191015223457j:plain
LODGroupを複数調整する場合、本当に面倒くさい

LODの設定を一気に上書きするコード

 LODの設定はスクリプトから一気に更新してしまいます。例えば下のようなコードで

using UnityEngine;

public class LODGroupSettings : MonoBehaviour
{
    [SerializeField] LODGroup src;

    void Start()
    {
        UpdateLOD();
    }

    [ContextMenu("UpdateLOD")]
    public void UpdateLOD()
    {
        var dst = GetComponent<LODGroup>();
        var srcLOD = src.GetLODs();
        var dstLOD = dst.GetLODs();

        for (int i = 0; i < srcLOD.Length; i++)
        {
            dstLOD[i].screenRelativeTransitionHeight = srcLOD[i].screenRelativeTransitionHeight;
            dstLOD[i].fadeTransitionWidth = srcLOD[i].fadeTransitionWidth;
        }
        dst.SetLODs(dstLOD);
    }
}

src にコピー元となるLODGroup(コピー先と同じLODの数)のPrefabを登録します。これで実行時にLODの設定が差し変わる他、コンテキストメニューからUpdateLODを選択すればLODが切り替わります。

 

f:id:tsubaki_t1:20191015224349j:plain
コピー元を登録して、コンテキストメニューでUpdateLODを選択する場合

注意:複数編集にPresetが使えそうに見えるが、使ってはいけない

 複数編集という点で、使えそうな設定にPresetという機能があります。この機能を使用すると、確かにLODを一気に更新することができますが、実はコレは罠です。フフフ…

 Presetの設定はコンポーネントの設定をコピーしてくれますが、実はLODGroupで表示・非表示を切り替えるメッシュもコピーしてしまいます。つまり、階層下ではない別のLODGroupに所属するRendererを対象にしてしまいます。これでLODが効かなくなったり、レンダラーの登録が全て剥がれたりして大惨事を引き起こすかもしれません。

f:id:tsubaki_t1:20191015225338g:plain
違う階層のLODを参照してしまっている図

感想

 LODの切り替え距離それ自体はProjectSettings>QualityのLOD Biasで大雑把に調整出来ますが、全体で調整されてしまうので、特定の物をテンプレートに従って一気に差し替えられるというアプローチは、便利と感じています。

 まぁ現状モバイルだとポリゴンを削減するという点でのLODは(一画面1200万ポリゴンとか行ってないなら)そこまで気にしなくても良いと思いますが。ポリゴン数以外でも、スキンメッシュやAnimatorの計算を距離で削減出来るのは、ゲームによっては結構アリかなと思います。

【Unity】出来るだけ簡単にNew Input Systemを使いたい

新しいInput Systemは少し面倒くさい

 新しいInput Systemですが、正直な意見として面倒くさいというのがあります。アクションにキーを割り振るという思想は非常に理にかなっていますし、応用で色々出来るのは悪くないとは思いますが、以前のInput Managerのようにすぐ使えないのはすごくマイナスです。

 そこで、サクッと使える使い方について確認してみます。

PlayerInputを使用する

 サクッとプレイヤーの入力を取得するアプローチとしてPlayerInputコンポーネントがあります。このコンポーネントを使えば面倒くさい作業が多少楽になります。

  • WASDやGamepadに対応した移動等を含むInputActionsを生成してくれる。
  • SendMessageで該当のメソッドを呼び出してくれる
  • InputSystemの初期化

 特にInputActionsの生成は面倒くさい項目の一つなので、とりあえず動くものがサクッと手に入るのは非常に楽でよいです。ここで生成するInputActionsのアクションマップには「WASDキーやGamePadでの移動」「マウス操作や右スティックの視点移動」「マウスクリックやゲームパッドに反応する射撃」の操作が含まれます。

f:id:tsubaki_t1:20191014213847j:plain
作られたInputActions。とりあえず移動、視点移動、射撃がカバーされる

 なおこの記事はUnity 2019.2 × InputSystem 1.0 の組み合わせで作成されています。

PlayerInputの利用手順

  1. 入力を受け付けたいオブジェクトにPlayerInputコンポーネントを登録。
  2. PlayerInputのCreateActionsを押して、アクションマップを生成。
  3. PlayerInputのDefaultMapPlayerに変更。
  4. PlayerInputのBehaviourをSendMessage`に変更

 

f:id:tsubaki_t1:20191014210641j:plain
PlayerInputからActionMapを作るボタン

 あとは下のコードを記述してPlayerInputをアタッチしたGameObjectに追加します。入力はそのまま使用せず、いったんフィールドにキャッシュして使用します。これは現状InputSystemが変化した値を検出するためです。

 なおSendMessageと聞いて顔をしかめるかもしれませんが、今の端末性能的に一フレーム数回のSendMessageなど誤差のようなものです。

using UnityEngine;
using UnityEngine.InputSystem;

public class Example: MonoBehaviour
{
    public Vector2 move { get; private set; }
    public Vector2 look { get; private set; }
    public bool fire { get; private set; }

    public void OnMove(InputValue value) => move = value.Get<Vector2>();
    public void OnLook(InputValue value) => look = value.Get<Vector2>();
    public void OnFire(InputValue value) => fire = value.Get<float>() > 0;

    void Update()
    {
        Debug.Log($"Move[{move}], Look[{look}], Fire[{fire}]");
    }
}

 もしポーリングするスタイルで使用したい場合、下のような記述になります。playerInput.actions["アクション名"]でアクションの情報が取得できるので、あとはReadValueで現在の状態を取得していく感じです。

using UnityEngine;
using UnityEngine.InputSystem;

public class Example: MonoBehaviour
{
    InputAction move, look, fire;
    void Start()
    {
        var playerInput = GetComponent<PlayerInput>();
        move = playerInput.actions["Move"];
        look = playerInput.actions["Look"];
        fire = playerInput.actions["Fire"];
    }

    void Update()
    {
        var m = move.ReadValue<Vector2>();
        var l = look.ReadValue<Vector2>();
        var f = fire.ReadValue<float>() > 0;

        Debug.Log($"Move {m}, Look {l}, Fire {f}");
    }
}

 なおPlayerInputにはUnityEventを受け取るという機能もありますが、これはUnity 2019.3では動作しませんでした。同梱のサンプルも動作しないのでバグと思われます(2019.2では動作します)。Unity 2019.3 b7だと正常に動作しました

キー入力やゲームパッドの取得

 デバッグ目的等でサクッと特定のキー入力を使用したい場合、下のような形で取得出来ます。InputActionのように入力をアクション単位で抽象化出来ないので、サクッと使える反面、複数の環境に対応させようと思ったら少し面倒くさいことになるかもしれません。

// マウスの左クリックを検出
Mouse.current.leftButton.wasPressedThisFrame;
// キーボードのスペースキーを検出
Keyboard.current.spaceKey.wasPressedThisFrame;
// ゲームパッドの左スティックを検出
Gamepad.current.leftStick.ReadValue();

関連

tsubakit1.hateblo.jp

【Unity】新しいインプットシステムで、バーチャルパッドに対応させる

f:id:tsubaki_t1:20191014002517g:plain

バーチャルパッドに対応させる

 新しいInput Systemでバーチャルパッドを作ってみます。モバイルでは殆どの環境ではゲームパッドもキーボードもマウスも繋がっていないので、バーチャルパッドは悪い選択肢ではないと思います。特にカメラが追跡するタイプのサードパーソンゲームでは。

 今回はゲームパッドの入力をオーバーライドしてUIからの操作を注入するアプローチをとります。前提としてゲームがNew Input Systemでゲームパッドに対応している必要があります。今回のゲームの場合「Leftスティックで移動」「Westボタンで弾が出る」という操作を事前に実装し、そのゲームにバーチャルパッド対応を行います。

f:id:tsubaki_t1:20191014004033j:plain
ActionMapsを定義して、ゲームパッドとキーボード操作に対応させる

 なお、このバーチャルパッドは現行主流の押した位置からの差分ではなく、一世代古い固定位置からのバーチャルパッドです。

手順1:UIを作る

 最初にUIのレイアウトを決めます。バーチャルスティック一つにボタンが二つといった感じでしょうか。アンカーも使って画面サイズが変わってもちゃんと追随できるようにしておきます。このUIはボタンで作っておくと、実際にタッチさせているかどうかが視覚的に分かりやすくて良いです。スティックは親を作っておくと座標を動かしやすくて良いです。  

f:id:tsubaki_t1:20191014005157j:plain
UIの構成

 ゲームを再生してみるとUIが反応しないかもしれません。これはEventSystemに古いInput Systemのものを使用している為です。StandaloneInputModuleInputSystemUIInputModuleに変更してやれば使用可能になります。

f:id:tsubaki_t1:20191014005941j:plain
EventSystemを新しいInputSystemに対応させる

手順2:UIのボタンを押したら弾を出す

 「Westボタンで弾が出る」の動作を、UIのボタンを押したら射撃を行うようにします。正確にはボタンのタップに合わせてWestボタンのステータスを更新するだけです。結果としてUIのボタンを押すと弾が出ます。

  1. UIのWestボタンOnScreenButtonコンポーネントを追加
  2. Control PathにButton West[GamePad]を選択

 これでUIのボタンを押せばButton Westが押された事になります。

 一点注意としては、このボタンのタップ判定はButtonのIntractableではなくImageのRaycast Targetが使用される点です。なのでUIを一気に無効化する場合はCanvasGroupのBlock Raycaster等を使用します。

f:id:tsubaki_t1:20191014011821g:plain
ボタンを押したら弾が出る

手順3:スティックを動かしたら動く

 「Leftスティックで移動」の動作を、UIのバーチャルスティックを動かしたら動く形に変更します。

  1. UIのスティック(ドラッグで動くUI)にOnScreenStickコンポーネントを追加
  2. Control PathにLeft Stick[GamePad]を選択

 これでスティックを動かせばプレイヤーが動くようになります。あとはMovementRangeで感度を調整したりします。

f:id:tsubaki_t1:20191014013227j:plain
コンポーネントの設定

オマケ:ボタン操作でプレイヤーを動かす

 OnScreenButtonではゲームパッドのボタンだけでなくキー入力も上書き出来るので、シミュレーションで「上」「下」「左」「右」といった厳密な操作が必要な場合は、ボタンとキーを上書きするのも面白いかもしれません。

f:id:tsubaki_t1:20191014012733g:plain
スティックではなくボタンで動かす

f:id:tsubaki_t1:20191014012827j:plain
ボタンにキー操作を割り当てる

関連

tsubakit1.hateblo.jp