ブログの女の子を作る #35 パーティクルとコライダーの風魔法を当てる【前編】【Unity】
前回の記事で、キャラクターがいろんな場所に移動するようになりました。
勝手に動いてくれるのは良いんですが、基本的には動いてるだけでした。そして、キャラクターの服や髪に触ることはできますが、触ってもキャラクターの反応はありません。もう少しリアクションが欲しいところです。
今回は、風の魔法を当てるとキャラがアクションする、を作ってみたいと思います。
少し記事が長いので、前後編に分けてます。
目次
(1) 開発環境
今回の作業中に開発環境が変わりました。
・Windows 10 Pro 2004
・Unity 2019.4.0f1
・CPU:AMD Ryzen 7 3700X
・マザーボード:ASUS TUF GAMING X570-PLUS
・SSD:ASX8200PNP-1TT-C XPG SX8200 Pro 1TB
・グラボ:ASUS ROG-STRIX-RTX2060S-O8G-GAMING
・Meta Quest
・Oculus Link
高速なRyzen CPUとNVMe SSDのおかげで、BlenderとUnityの作業がとても快適になりました!
速いパソコンは使っていて気持ち良いですね。
(2) 必要な作業をまとめる
アクションはいろんなモノの組み合わせで出来ています。
まずは、必要なものをまとめてみました。
【前編】風魔法を作って、コントローラから撃つ
- Unityの「パーティクルシステム」で風魔法の見た目を作る。
- キャラクターのための「当たり判定用コライダー」を付ける。
- コントローラから「RayCast」と「照準用のビーム」を出して、RayCastが当たったところに風魔法(1+2)を出す。
【後編】モーションと表情を作って、風魔法が当たったら動かす
- Unityのアセット「VeryAnimation」でモーションを作る。
- Blenderの「シェイプキー」で表情を作る。
- 風魔法がキャラクターに当たった時に、モーションして表情を変える。
- (おまけ)カメラが近づいたらキャラクターがこっちを向く
こんな感じですね。
この記事では【前編部分】を紹介しています。
(4) 風魔法には3つの役目がある
風魔法には3つの役目があります。
- 小さな竜巻のような風魔法が見えること
- オブジェクトを動かすこと
- キャラクターとの当たり判定を検知できること
1番の見た目はパーティクルで作れますし、パーティクル自体にも「衝突判定(Collision)」があるので3番の当たり判定もできそうです。
あとは2番だけなんですが、パーティクルは Magica Cloth に干渉できないので、オブジェクトを動かすことができません。動かすには、専用の「Magica Clothのコライダー」を使う必要があります。
というわけで今回は、1番は「パーティクル」、2番は「Magica Clothのコライダー」、3番はパーティクルではなく「Unityのコライダー」を使うことにしました。
(5) Unityの「パーティクルシステム」で風魔法の見た目を作る
まずは、風魔法の見た目を作ります。
パーティクルシステムとは?
Unityの「Particle System(パーティクルシステム)」は、大量の小さな2D画像を生成し、物理的な挙動をアニメーションすることで、雲や炎などの流体をシミュレーションできる機能です。基本的にはパラメータを変更するだけで使えます。
パラメータ数が多いので、どれを変更するとどう変わるのかを探るのが大変ですが。。。
少し前のUnityのバージョンアップで竜巻っぽい形が作れるようになったらしいので、その機能を使って風魔法を作ってみました。
小さな竜巻のような見た目を作る
Unityのメニューで「GameObject -> Effects -> Particle System」をクリックすると、白いもの(パーティクル)が下から上に飛び出します。
この設定をいろいろ変更することで、パーティクルが出る時間や速度、色や形などが変わります。
試行錯誤の上、こんな感じの見た目になりました。
まだパラメータを理解しきれていないこともあって、イメージ通りの形にするのがなかなか難しい。。。
とりあえずは竜巻っぽいので、これで良しとします。
パーティクルのパラメータを調整する
今回設定したパラメータの値です。主要なパラメータに説明を書いてます。
各パラメータの詳細な説明は公式サイトで。
[Particle System、Emission]
パーティクルシステムの全体的な設定と量を設定します。
・Duration:パーティクルが出る時間。5秒。
・Start Color:パーティクルの色。時間経過で緑色→白色に変化。半透明です。
・Rate over Time:パーティクルの量です。
[Shape]
パーティクルが出る形や方向を設定します。
・Shape:パーティクルが出る形。コーン型は竜巻にちょうど良かった。
・Velocity Over Lifetime:パーティクルの速度。形の調整ができます。Orbital Z、Radial、Speed Modifierなどを調整していくと、竜巻の形になります。
[Noise]
パーティクルの動きにノイズを追加します。
・Frequency:ノイズの強弱。緩やかにしてます。
[Trails、Renderer]
パーティクルの軌跡と画像を設定します。
・Texture Mode:軌跡を伸ばすか繰り返すかなど。いまはTileですがStretchも面白いですよ。
・Trail Material:軌跡のマテリアル。Default-ParticleSystemマテリアルを使ってます。
ShapeとTrailsがポイントですね。
今回は、このパーティクルを「WindMagicPS」というオブジェクトの下に配置しました。
(6) どうやって風魔法でオブジェクトを動かすか
Magica Clothには「Directional Wind」という風を吹かせるコンポーネントがあります。
風向きや強さを指定できるので、下から上へ強く吹かせると今回の用途にちょうど良い気がします。
ですが、「ステージの環境風」向けなので、キャラクター全員に風が当たってしまいます。「範囲魔法系の風魔法」なら良いかもしれませんが。。。
試してみたところ、クロスコンポーネントの共通クラス「BaseCloth」を操作するAPI(例えば、パラメータの風の影響率を変更するExternalForce_WindInfluence)を使うと、コンポーネント毎に風の強さを設定できました。
これなら、特定のキャラクターだけに風を当てることもできますが、今回の風魔法として使うには少し使いずらそうです。
今回は「Magica Clothのコライダー」を使うことにしました。
(7) 風魔法に「2種類のコライダー」を付ける
「Magica Clothのコライダー」で動かすことは決まりましたが、もう一つ必要なコライダーがありました。
キャラクターとの当たり判定を検知するための「Unityのコライダー」ですね。
この2種類のコライダーを「動作確認用のオブジェクト」にアタッチしていきます。
動作確認用のオブジェクトにRigidbodyをアタッチする
コライダーは見えませんので、同じ位置に「動作確認用のオブジェクト」があると、確認作業がしやすいです。
今回は Sphereオブジェクト を使いました。
動作確認用で、あとから非表示(Mesh Rendererをオフ)にするので、形はなんでもOKです。
そして、このオブジェクトに「Rigidbody」をアタッチしています。Rigidbodyを使うと「物理特性を使った制御」ができるんですが、今回は物理制御の用途では使いません。コライダーを使って当たり判定する場合、少なくとも片方のオブジェクトに「Rigidbody」を付けてないと検出できないので、そのためだけに付けてます。
「Use Gravity」はオフ、「Is Kinematic」はオンにしてますよ。
Magica Clothのコライダーをアタッチする
「Magica Clothのコライダー」の MagicaCapsuleCollider を2つ作成し、十字型にして配置しています。
Unityのコライダーをアタッチする
同様に、「Unityのコライダー」の CapsuleCollider を同じ形で作成し、位置を重ねて配置します。
少しわかりにくいですが、ほぼ同じ大きさになっています。
こちらは「キャラクターとの当たり判定用」ですね。当たった時、スクリプト側でイベント検出できるようになります。
プレハブ化しておく
オブジェクトは「WindMagicObj」という名前を付けました。
今後使いやすいように、「WindMagicObj」と先ほど作成した「WindMagicPS(パーティクルシステムのオブジェクト)」は、「プレハブ化」しておきましょう。オブジェクトをProjectビューにD&DすればOKですね。
これで風魔法の準備はできました!
(8) どうやって風魔法を撃つか
ゲームの世界では、魔法を撃つ時にいろんな方法があります。呪文を唱えたり、決まった順序でボタンを押したり、VRならジェスチャーで空中にルーン文字的なものを書くのもありますね。
Meta Questではハンドトラッキングが使えるので、それとジェスチャーを組み合わせるとカッコ良さそうですが、実装も大変そうなので、今回はシンプルに「コントローラーのトリガーを引く」だけにしておきます。
トリガーを引いた時、カメラ正面に固定的に魔法を出すのが一番簡単ですが、せっかくのVRですので「コントローラからビーム的なものを出して、ビームが当たった場所に風魔法を出す」にしましょう。狙ってる個所を分かりやすくする「銃のレーザー照準器」みたいな感じですね。これならなんとかできそうです。
(9) 「風魔法を撃つ」スクリプトを作る
風魔法をコントローラから撃つスクリプトを作っていきます。
カメラ移動のスクリプト「TransformMover.cs」について
私の開発環境では「Meta Quest(Oculus Link)のカメラ移動」するために、この記事で紹介した「OVR Player Controller.cs」から、必要な個所(右スティックで30度ずつ向きを変えるなど)を抜粋したスクリプト「TransformMover.cs」を作ってます。
一部流用した場合、ライセンス的にどこまで公開して良いかわかりませんでしたので、この記事では、風魔法用に追加したロジックだけを記載しています。ご了承くださいね。
OVRCameraRigに「Line Renderer」をアタッチする
コントローラから「照準用のビーム」を出す下準備として、Oculusのカメラオブジェクト(OVRCameraRig)に「Line Renderer」をアタッチしておきます。
アタッチした後は、パラメータ値を変更することで「ビームの見た目」を変更します。
今回は、やや細めで半透明な緑色のビームにしてみました。
風魔法のメインメソッド「ShootWindMagic」を作る
風魔法のメインとなる処理の「変数定義とメソッド」です。
[TransformMover.cs]
[SerializeField] private Transform LeftHandAnchor; // 左コントローラ [SerializeField] private Transform RightHandAnchor; // 右コントローラ [SerializeField] private LineRenderer LaserPointerRenderer; // レーザーポインタ [SerializeField] private float MaxDistance = 100.0f; // レーザーの最大距離 [SerializeField] private Transform WindMagicObj; // 風魔法オブジェクト [SerializeField] private Transform WindMagicPs; // 風魔法パーティクルシステム private int timeCount; // 風魔法が出るまでの遅延時間カウント
オブジェクトをスクリプト内で使うための変数定義です。
50から53行目で、風魔法の見た目とパーティクルのオブジェクトを定義してますね。
[TransformMover.cs]
// 風魔法を撃つ private void ShootWindMagic() { // Rayが当たったオブジェクト RaycastHit hitObject; // コントローラからRayを出す Ray ray = new Ray(RightHandAnchor.position, RightHandAnchor.forward); // レーザーの起点 LaserPointerRenderer.SetPosition(0, ray.origin); // Rayがオブジェクトに当たった場合 if (Physics.Raycast(ray, out hitObject, MaxDistance)) { // Rayが当たった所までレーザーを出す LaserPointerRenderer.SetPosition(1, hitObject.point); timeCount += 1; if (timeCount > 5) { // 風魔法のパーティクルシステムを生成 Transform windPs = Instantiate(WindMagicPs); windPs.transform.position = new Vector3(hitObject.point.x, hitObject.point.y, hitObject.point.z); Destroy(windPs.gameObject, 10.0f); // 風魔法オブジェクトを上方向に移動 IEnumerator moveUpper = MoveUpper(WindMagicObj, hitObject); StartCoroutine(moveUpper); timeCount = 0; } } else { // 一定距離までレーザーを出す LaserPointerRenderer.SetPosition(1, ray.origin + ray.direction * MaxDistance); timeCount = 0; } }
このメソッドで、コントローラから「照準用のビーム」と「RayCast」を出して、RayCastが当たったところに風魔法を出します。
82,83行目の「Rayクラス」で見えない当たり判定用の光線を、92,93行目の「LineRendererクラス」で見える照準用のビームを出してます。
1フレーム毎に風魔法が出ると多すぎなので、timeCount を使って出る間隔を調整しています。ここでは単純にカウントを使ってますが、Time.deltaTime を使うのも良いかも知れません。
99から102行目で「風魔法のパーティクル」を生成して、Rayが当たった場所に移動し、10秒後にインスタンスを明示的に破棄してます。
最後に、104から106行目で「風魔法のオブジェクト(コライダー付き)」を上方向に移動するメソッドを呼んでます。
「MoveUpper」メソッドで風魔法を上にゆっくり移動する
このメソッドでは、風魔法をランダムで位置をずらして生成して、上方向に移動させます。
[TransformMover.cs]
// 風魔法オブジェクトを上方向に移動 IEnumerator MoveUpper(Transform WindObj, RaycastHit hitObject) { for (int windCount = 0; windCount < 10; windCount++) { // 風魔法オブジェクトを生成 Transform wind = Instantiate(WindObj); wind.transform.position = new Vector3(hitObject.point.x, hitObject.point.y, hitObject.point.z) + AgentMotion.OffsetPosition(0.2f); for (int moveCount = 0; moveCount < 15; moveCount++) { // 上方向に移動 wind.Translate(0, 0.07f, 0); yield return null; } Destroy(wind.gameObject); } }
127行目の AgentMotion.OffsetPosition() で位置をずらしてますが、これは以前の記事で作成したメソッドですね。メソッド側で public static を定義することで、共通メソッドっぽく呼べるようにしてます。
131から133行目で風魔法を移動する際、以前の記事で「ゆっくりキャラクターを回転」する時に使ったコルーチンを使ってますよ。
[AgentMotion.cs]
// 位置をずらす public static Vector3 OffsetPosition(float offset) { return new Vector3(Random.Range(-offset, offset), 0.0f, Random.Range(-offset, offset)); }
先ほどの OffsetPositionメソッド です。本来であれば他のクラスからも使われるような便利メソッドは、専用の共通クラスを別途作成してまとめた方が良いですね。
トリガーを押した時に実行する
ShootWindMagicメソッドを「右トリガーを押した時」に呼べば、コントローラから風魔法を撃てるようになります。
[TransformMover.cs]
private void LateUpdate() { // 右トリガーを押した場合、風魔法を撃つ if (OVRInput.Get(OVRInput.Button.SecondaryIndexTrigger)) { ShootWindMagic(); } // 右トリガーを離した場合、Rayを消す if (OVRInput.GetUp(OVRInput.Button.SecondaryIndexTrigger)) { Ray ray = new Ray(RightHandAnchor.position, RightHandAnchor.forward); LaserPointerRenderer.SetPosition(0, ray.origin); LaserPointerRenderer.SetPosition(1, ray.origin + ray.direction * 0); } // 左スティックによる移動 Vector2 leftStick = OVRInput.Get(OVRInput.RawAxis2D.LThumbstick); transform.Translate(leftStick.x / 20, 0, leftStick.y / 20); // 右スティックによる上下移動 Vector2 rightStick = OVRInput.Get(OVRInput.RawAxis2D.RThumbstick); transform.Translate(0, rightStick.y / 30, 0); // 右スティックによる向き変更 Vector3 euler = transform.rotation.eulerAngles; float rotateInfluence = SimulationRate * Time.deltaTime * RotationAmount * RotationScaleMultiplier;
142から146行目で右トリガーの入力を検知して、ShootWindMagicメソッドを実行してます。
必要なオブジェクトをD&Dで登録する
今回の修正で「TransformMover.cs」スクリプトに変数をいくつか追加しましたので、Inspector上で「動作に必要なオブジェクト」をD&Dで登録しておきます。
・Left Hand Anchor:LeftHandAnchor (Transform)
・Right Hand Anchor:RightHandAnchor (Transform)
・Laser Pointer Renderer:OVRCameraRig (Line Renderer)
・Max Distance:100
・Wind Magic Obj:Wind Magic Obj (Transform)
・Wind Magic Ps:Wind Magic PS (Transform)
ですね。
これで風魔法を撃つ準備ができました。
風魔法を撃ってみる!
コントローラーのトリガーを引くと、風魔法が出ました!
「動作確認用オブジェクト」は非表示にしてましたので、一時的にレンダラーをオンにすると、こんな感じになります。
オブジェクトの位置がよく分かりますね。
まとめ
パーティクスシステムで「見た目」、2種類のコライダーで「オブジェクトへの干渉と当たり判定」を持った風魔法を作りました。そして、コントローラからビームとRayを出して、当たったところから風魔法を撃つことができました!
次は、モーションと表情を作って、風魔法が当たった時のプログラムを作ります。
後編に続きます。
コメント
トラックバックは利用できません。
コメント (2)
ほんとにすごいですね!
見ててわかりやすく、動画を見たらほんとにすごいって思いました!
いま、Unityに興味があって
scriptを組む際の言語って何言語になるのですか?
風魔法大好きさん、コメントありがとうございます。
言語はC#ですね。
以前は、この記事に書いた方法しかなかったのですが、Magica Cloth 2の「風ゾーン」を使うとパーティクルを当てることなく、特定のエリアだけ風を吹かすことができるようになりました。
便利ですね。
風の設定 | Magica Cloth 2