今回はUnityで主人公が通った場所にある草をなぎ倒すような機能を作成したいと思います。
ずいぶん前からこの機能作りたかったんですが、Terrainに生やした草はWind Settings for Grassで設定すれば草全部をなびかせることは出来ますが、Wind Zoneのような機能を使って一部だけをなびかせるといった事が出来ませんでした。
なので放棄していたんですが、Unity Blogを見ていたらこの機能をVisual Effect Graphを使って実現していたので、サンプルをダウンロードしてその機能を解析してみました。
上の「Sample #04 – Grass Wind」のところで紹介されている機能です。
解析しましたと言っても正直なところ細かい部分は何をやっているのかわからないのでわかった部分だけでとりあえず作ってみましたという感じです。
よりちゃんとしたものはUnityさんでダウンロード出来るサンプルを見てください。(._.)
今回の機能は
上のようになりました。
途中で草をなぎ倒す割合を増やしてより倒れるようにしています。
この機能ではスタンダードアセットのGrassテクスチャを使用しますので、スタンダードアセットをインポートするか、もしくは別の草のテクスチャ2D画像を用意しておいてください。
今回の機能を作成する前の条件
今回の機能を作成するにはVisual Effect Graphが使える事が条件になります。
Visual Effect Graphを使うにはHDRPの設定等が必要になりますので、
を参照してください。
既に作成段階に入っているゲーム等のプロジェクトの設定を変えてしまうので、機能を試すだけなら新しいプロジェクトを作成した方がいいです。
今回の機能はVisual Effect Graphを使って生やした草を使いますので、Terrainの機能で生やした草は使えません。
機能をどうやって実現するのか?
今回の機能はVFX(Visual Effect Graph)を使った作業がメインになります。
VFXで作ったパーティクル(個々の草)をTerrainのメッシュ状に配置し、VFXの操作で風によって草を動かしたり、主人公の位置を取得してその影響で草の表示を変えてなぎ倒しているように見せます。
全部をいきなりやると混乱するので、段階に分けて作成していきたいと思います。
という順番で作成していきます。
VFXテンプレートファイルとゲームオブジェクトの作成
Assetsエリア内でCreate→Visual Effects→Visual Effect Graphを選択し、名前をGrassWindVFXとします。
GrassWindVFXをヒエラルキー上にドラッグ&ドロップし、GrassWindVFXテンプレートファイルが設定されているゲームオブジェクトを作成します。
GrassWindVFXゲームオブジェクトを選択し、インスペクタのVisual EffectのAsset Templateの右のEditボタンを押します。
新しくコンテキストの流れを作成するので、デフォルトで作成されているSpawnコンテキストからQuad Outputコンテキストまでをマウスドラッグで全部を選択し削除します。
VFXで草のパーティクルを配置する
VFXで草のパーティクルを配置していきます。
草は後でTerrain上に配置しますが、その前に草のパーティクルを作って表示出来るようにしておきます。
まずは空のコンテキストの流れを作成します。
VFX上の何もないところでSpaceキーを押しSystem→Empty Particle Systemを選択します。
Quad Outputコンテキストを削除し、何もない所でSpaceキーを押してContext→Lit Quad Outputコンテキスト(Output Particle URP Lit QuadやOutput Particle HDRP Lit Quad等を選択する必要がある時もある)を選択し、UpdateのParticleからLit Quad Outputにドラッグし繋げます。
Spawnコンテキストの設定
Spawnを選択した状態でSpaceキーを押し、Spawn→Single Burstブロックを選択しConstantに1を設定します。
Single Burstブロックは1度だけConstantに指定した数のパーティクルを生成します。
これで1つのパーティクルだけが表示されるようになります(現時点では表示されない)。
Initializeコンテキストの設定
次はInitializeコンテキストを選択した状態でSpaceキーを押し、検索窓でSet Sizeを入力し、Sizeを1にします。
これでパーティクルが1つ見えるようになりました。
Spaceキーを押して検索窓にSet Target Positionを入力し、Yに2を入力します。
Set Target Positionはパーティクルの終端の位置設定です。
このTarget Positionはパーティクルの終端の位置なので、この値を操作して草がなびいて見えているように見せます。
Spaceキーを押して検索窓にSet Colorを入力し、Set Color Random(Per-Component)を選択します。
Set Color RandomブロックのMax(もしくはB)のXを1、YとZを0にして赤色を設定します。
これでパーティクルの色が設定したMin(もしくはA)とMaxの間のランダム値が設定されることになります。
Lit Quad Outputコンテキストの設定
Lit Quad Outputコンテキスト(他のOutputを選んでいる場合はそれに対して)のBase Color Mapにスタンダードアセットの2DテクスチャであるGrassをセットします。
Assets→Standard Assets→Environment→Terrain Assets→BillboardTextures→GrassFrond02AlbedoAlphaを設定します。
BlendをMaskedにします。
Lit Quad Outputコンテキストを選択した状態でSpaceキーを押しOrientation→Connect Targetブロックを選択します。
Pivot Shiftを0.5(画像では0になってます)にします。
Connect TargetのTarget Positionから左にドラッグし、Operator→Attribute→Get Attribute : target Positionを選択します。
OrientationをCameraにしてカメラの方向を向かせつつパーティクルのターゲット位置(Initializeで設定済)に合わせてパーティクルが拡大・縮小がされるようになります。
シーンビュー上でマウスドラッグして視点を回転させて草のパーティクルを見てみると、草の上部がターゲット位置である(0, 2, 0)を保ちつつ草は2Dのテクスチャであるにもかかわらず草の正面がカメラ方向に常に回転され3Dのように見えることがわかります。
次に個々の草のサイズを指定した最小と最大のサイズでランダムに決定出来るようにします。
Lit Quad Outputコンテキストを選択した状態でSpaceキーを押し検索窓にSet Sizeを入力しSet Sizeブロックを選択します。
インスペクタのRandomにPer Componentを設定します。
Min(もしくはA)とMax(もしくはB)にそのまま値を入力してもいいのですが、ここであえてパラメータを使って値を設定したいと思います。
VFXのBlackboardを押し、ウインドウを開きます。
+を押してfloat型のパラメータを2つ作成し、名前の部分をダブルクリックしてGrassMinSizeとGrassMaxSizeとします。
またExposeにチェックを入れてインスペクタ上でパラメータを設定出来るようにします。
GrassMinSizeとGrassMaxSizeをVFX上にドラッグ&ドロップしGrassMinSizeをSet Size RandomのMinにGrassMaxSizeをMaxに接続します。
これでインスペクタで項目にチェックを入れることで草の最小と最大サイズを設定する事が出来ます。
チェックを入れない場合はVFXで設定した値がデフォルト値として使用されます。
ここまでで一つの草のパーティクルを表示する事が出来ました。
TerrainのメッシュをObjファイルに出力する
草のパーティクルが出来たので次はその草を生やす地面であるTerrainからObjファイルに出力します。
まずはヒエラルキー上で右クリックから3D Object→Terrainを選択してTerrainを作成し、インスペクタのTerrainのTerrain WidthとTerrain Lengthを50に設定し作成します(数値は適当なものを指定してください)。
Terrainの地面にテクスチャを塗り、凸凹を付けておきます。
TerrainのTransformのPositionとRotationのXYZは全て1、Scaleは1としておきます。
Terrainで地面を作ってもそのメッシュファイルが見えないのでこの後使用するPoint Cacheの機能が使えません。
そこでまずはTerrainからObjファイルを作成しTerrainのメッシュを見える形にファイルとして保存することにします。
普通に考えたらここで挫折するところですが・・・(^_^;)
UnityのCommunityWikiにTerrainからObjファイルを出力するコードがありますのでそのC#バージョンをコピーします。
上記のリンクが切れていたので以下のExportTerrain.csも確認してみてください。
Assetsエリア内にEditorという名前のフォルダがなければEditorという名前のフォルダを作成します。
Editor内にExportTerrainという名前のC#スクリプトを作成し、先ほどコピーしたスクリプトを貼り付けます。
ヒエラルキー上のTerrainを選択した状態でUnityメニューに追加されているTerrainメニューからExport To Obj…を選択します。
ExportTerrainウインドウが表示されるので、Export Formatでメッシュの出力形式、Resolutionで解像度を設定し、Exportボタンを押します。
これでTerrainのObjファイルが作成されます。
これでTerrainからObjファイルが作成されました。
Point CacheにTerrainのメッシュを指定し、Terrain上に草のパーティクルを配置する
Terrainのメッシュが出来たので次はPoint Cache用のファイルを作成します。
Point Cacheの使い方については
を参照してください。
Point Cacheは指定したメッシュ上のポイントをキャッシュしておき、そのポイント上にパーティクルを表示する時に使用します。
UnityメニューのWindow→Visual Effects→Utilities→Point Cache Bake Toolを選択します。
Meshに先ほど作成したTerrainのObjのdefaultをドラッグ&ドロップし設定します。
使用しないのでExport系のチェックは外しておきます。
Point Countを5000にポイントする数を指定します。
Save to pCache file…ボタンを押しキャッシュファイルを保存します。
次にVFXのSpawnコンテキストのSingle BurstブロックのCountを5000にし、InitializeコンテキストのCapacityを5000にします。
これで5000のパーティクルが生成され、Capacityが5000なので最大5000のパーティクルが同時に生存します(今回は一回しか生成しないので生成したものがそのまま表示される)。
Initializeコンテキストを選択した状態でSpaceキーを押しAttribute→Map→Set Position from Mapを選択します。
Set Position from Mapブロックを選択しInitializeコンテキスト内で最初に実行されるように一番上にドラッグします。
次にVFX上の何もない所でSpaceキーを押しOperator→Utility→Point Cacheを選択し、Assetに先ほど作成したTerrainのPoint Cacheファイルを設定します。
Point CacheノードのAttributeMap positionからドラッグし、Set Position from MapのAttributeに接続します。
これでPoint Cacheの位置にパーティクルが表示されるようになります。
またInitializeのBoundsのCenterとSizeを調整してパーティクルがシーンビュー(視点が範囲内だと見える)とゲームビュー(カメラが範囲内だと見える)で見えるように範囲を調整します。
ここで5000個のパーティクルが異常に伸縮されて表示されていると思います。
これはSet Target PositionでXYZを(0, 2, 0)としている為です。
パーティクルの終端が全て(0, 2, 0)になっているのでそこに向けて伸びているわけですね。
個々のパーティクルの終端はパーティクルの個々の現在の位置と個々のパーティクルの方向から計算すると良さそうです。
なので、個々のパーティクルの位置と方向をSet Target Positionに繋げるようにします。
VFX上の何もない所でSpaceキーを押しOperator→Attribute→Get Attribute : positionを選択します。
同じようにOperator→Attribute→Get Attribute : directionを選択します。
個々のパーティクルの終端(Target Position)はその位置からパーティクルの方向のベクトルを足せばいいので、VFX上の何もない所でSpaceキーを押し検索窓でaddと入力し出てきた結果のAddノードを選択します。
Get Attribute : positionとGet Attribute : directionからAddノードのAとBにそれぞれ接続し、AddノードからSet Target Positionブロックに接続します。
これで5000個の草がTerrain上に表示されました。
ただ草の向いている方向が横向きなのでそこを調整していきます。
Initializeコンテキストを選択した状態でSpaceキーを押しAttribute→Map→Set Direction from Mapを選択します。
Set Direction from MapをドラッグしSet Target Positionブロックの前に移動させます。
Value BiasとValue ScaleのYを1、それ以外を0に設定します。
これで草が横向きからある程度縦向きに並んだと思います。
これでTerrain上に5000個の草を生やすことが出来ました。(^^)/
風(Wind Zoneではなく自分で設定)で草を動かす
Terrain上に草を生やすことが出来たので次は風によって草がなびいているようにしていきます。
ここからがメインの処理になりますね。
草がなびいているように見せる為に草のTarget Positionに変化を加えます。
Lit Quad Outputコンテキストを選択した状態でSpaceキーを押し検索窓にset targetと入力し、出てきた候補の中からSet Target from Mapを選択します。
選択した状態でインスペクタのCompositionをAddに変更します。
Add Target from MapのSample ModeをSample2DLODにします。
これでテクスチャからSample Positionに指定した位置の部分を抽出し、Target Positionに足すようにします。
VFX上の何もない所でSpaceキーを押し、検索窓にget targetと入力しGet Attribute : targetPositionを選択します。
次に草の揺れの周波数をあらわすパラメータをVector2で作成し、名前をGrassMovementFrequencyとします。
GrassMovementFrequencyパラメータをVFX上にドラッグ&ドロップします。
VFX上の何もない所でSpaceキーを押し検索窓にtimeと入力し、出てきた候補からTotal Time(BuiltIn)を選択します。
さらに何もない所でSpaceキーを押して検索窓にmultiplyを入力しMultiplyノードを選択します。
GrassMovementFrequencyをMultiplyのA、TotalTimeをBに接続します。
GrassMovementFrequency(草を揺らす周波数)とTotalTimeをかけた値を出力します。
何もない所でSpaceキーを押し検索窓にaddを入力し、Addノードを選択します。
Get Attribute : targetPositionをAddのA、MultiplyをBに接続します。
AddをAdd Target Position from MapのSampling positionに接続します。
元々のTaget Positionに草を揺らす周波数のTotalTimeでの値を足してTarget Positionに足しています。
ここまでで風の影響を与えるノードが出来たので、わかりやすいようにグループ分けをしておきます。
風の影響を与えるノードを全部選択し、右クリックからGroup Selectionを選択しタイトル部分をダブルクリックして名前を付けます。
今回は「Wind effects on the grass」という名前を付けました。
これでインスペクタでGrassMovementFrequencyの値を変えることで草の揺れる頻度が変わります。
主人公が通った場所の草をなぎ倒す
地面に草を生やして風で揺らすというところまできました。
後は主人公キャラクターが草の所を通ったらなぎ倒す機能を作成するだけです。
草をなぎ倒すにはやはりパーティクルの終端であるTarget Positionを変更するようにします。
主人公の位置はパラメータとVFX Parameter Binderコンポーネントを使用して取得します。
VFX上のパラメータは静的なものなのでVFX Parameter Binderコンポーネントを使って主人公の位置を常にVFX上で変化した値を取得出来るようにします。
また草をなぎ倒す為に変化を与える方法は
のように分けて考えていきます。
主人公の位置をVFX Parameter Binderを使って取得する
まずは主人公が歩いたりして移動した時にVFXのパラメータから主人公の位置を取得出来るようにします。
まずはGrassWindVFXゲームオブジェクトを選択してインスペクタのAdd Componentを押して、検索窓にvfxと入力し出てきた候補の中からVFX Parameter Binderを選択します。
(カテゴリから探しましたが見つからなかった為に検索窓で検索する方法にしました)
VFX Parameter BinderのPrameter BindingsにPositionが追加されるので選択します。
するとParameterにPositionが入力されているのでこの部分をEthanPositionという名前にします。
EthanPosition(任意の名前に変えてください)だとエラーがでる場合ははその後に_positionを付けてEthanPosition_positionとします(VFXGraphのプロパティ名はEthanPositionのまま)。
Targetにはヒエラルキーに配置した主人公キャラクターを設定します。
今回はスタンダードアセットのThirdPersonControllerを配置しTargetに設定します(Assets→Standard Assets→Characters→ThirdPersonCharacters→Prefabs→ThirdPersonController)。
これで主人公の位置がVFXで作成したパラメータEthanPositionに反映されるようになります。
VFXでVector3のEthanPositionを作成します。
このパラメータを使う事で主人公の現在位置を取得する事が出来ます。
草と主人公との距離に応じて草の変化率を計算する
主人公の現在位置を取得出来るようになったので、草と主人公の距離に応じて草のなぎ倒される比率を計算する部分を作成していきます。
VFX上の何もない所でSpaceキーを押し検索窓にget positionと入力し、Get Attribute : positionを選択します。
何もない所でSpaceキーを押しParameter→EthanPositionを選択します。
何もない所でSpaceキーを押しOperator→Math→Vector→Distanceを選択します。
Get Attribute : positionをVectorのA、EthanPositionをBに接続し、個々の草のパーティクルと主人公との距離をDistanceで計算します。
何もない所でSpaceキーを押しOperator→Random→Random Numberを選択します。
Random NumberのMinには0.9、Maxには1.1を入力します(数値は適宜変更してください)。
何もない所でSpaceキーを押し検索窓にMultiplyを入力しMultiplyノードを選択します。
DistanceをMultiplyのA、Random NumberをBに接続します。
このMultiplyの結果は草と主人公の距離にランダムな値を掛けたfloat値が出力されます。
次にVFXのパラメータにAnimation CurveのStrengthToBendGrassを作成します。
StrengthToBendGrassのアニメーションカーブの部分を押し、出てきたアニメーションカーブのグラフの左のキーをマウスの右クリックをしてEdit Keyを選択し、Timeに0、Valueに1を入力します。
次のキーをEdit KeyでTimeに0.5、Valueに1を入力し、次のキーをTimeに1、Valueに0を入力します。
今回はデフォルトで設定されている3つのキーを使いましたが右クリックからAdd Keyを選択する事で新たなキーを増やすことも出来ます。
これでStrengthToBendGrassパラメータが出来たのでドラッグ&ドロップします。
何もない所でSpaceキーを押しOperator→Sampling→Sampling Curveを選択します。
StrengthToBendGrassをSampling CurveのCurve、先ほど作成したMultiplyをTimeに接続します。
ここまで作成したノードをマウスのドラッグで全部選択し右クリックからGroup Selectionを選択し、タイトルの名前を「Calculate of the rate of bending the grass」とします。
これで草と主人公との距離に応じて草の変化率を計算するノードの作成が出来ました。
草の位置から主人公の位置を引いて草の方向を求める
次に草の位置から主人公の位置を引くことで主人公からみた草の向きのベクトルを計算します。
まずはVFX上の何もない所でSpaceキーを押しget positionと入力してGet Attribute : positionノードを選択します。
何もない所でSpaceキーを押しParameter→EthanPositionを選択します。
何もない所でSpaceキーを押し検索窓にsubtractを入力しSubtractノードを選択します。
Get Attribute : positionをSubtractのA、EthanPositionをBに接続します。
これで個々のパーティクルの位置から主人公の位置を引いたベクトル(主人公からみた草の向き)が計算されます。
何もない所でSpaceキーを押し検索窓にMultiplyを入力しMultiplyノードを選択します。
Subtractの出力をMultiplyのA、「草と主人公との距離に応じて草の変化率を計算する」の項目で作成した最終的なSampling Curveノードの出力をBに接続します。
これで主人公から見た草の方向と草と主人公との距離による変化率をかけて草をなぎ方向と力をベクトルを計算しています。
何もない所でSpaceキーを押し検索窓にget targetと入力し、Get Attribute : targetPositionを選択します。
何もない所でSpaceキーを押し検索窓にaddと入力し、Addノードを選択します。
先ほどのMultiplyをAddのA、Get Attribute : targetPositionをBに接続します。
これで現在のパーティクルの終端であるTarget Positionと主人公から見た草の方向をかけることでどの程度草に力を加えるかを求めています。
ここまでのノードをマウスドラッグで選択し、右クリックからGroup Selectionを選択しタイトルを「Calculation of grass target position」とします。
次にLit Quad Outputコンテキストを選択しSpaceキーを押して検索窓にset targetと入力し、Set Target Positionブロックを選択します。
Set Target PositionブロックはAdd Target Position from Mapより上にドラッグして移動させます。
ここまで出来たら先ほど作ったCalculation of grass target positionグループの最後の出力をSet Target Positionブロックに接続します。
これで完成です!!(^^)/
VFXグラフの全体像は以下のようになり、
SpawnとInitializeコンテキストの辺りは以下のようになり、
Outputコンテキストの辺りは以下のようになりました。
終わりに
VFXのサンプルがダウンロード出来るので結構早く理解できるかな?と思っていたんですが、わたくしのパソコンの影響でVFXグラフの操作が非常に重くノードがまずどのようにつながっているのか把握するのに苦労しました。(^_^;)
グループ個別に見ていく事で少しづつ把握出来ましたが、Unityさんのサンプルの中ではハイトマップを使っていたり細かい処理も作られていますがそこら辺は何をしているのかを把握できていません。
さらに細かい処理を見たい方はUnityさんのサンプルをダウンロードして確認し、色々試してみてください。