BlenderとUnityで作ったものをVR(Meta Quest/Oculus Link)で動かします

ブログの女の子を作る #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 CPUNVMe SSDのおかげで、BlenderとUnityの作業がとても快適になりました!
速いパソコンは使っていて気持ち良いですね。
 

(2) 必要な作業をまとめる

アクションはいろんなモノの組み合わせで出来ています。
 
まずは、必要なものをまとめてみました。
 

【前編】風魔法を作って、コントローラから撃つ

  1. Unityの「パーティクルシステム」で風魔法の見た目を作る。
  2. キャラクターのための「当たり判定用コライダー」を付ける。
  3. コントローラから「RayCast」と「照準用のビーム」を出して、RayCastが当たったところに風魔法(1+2)を出す
 

【後編】モーションと表情を作って、風魔法が当たったら動かす

  1. Unityのアセット「VeryAnimation」でモーションを作る。
  2. Blenderの「シェイプキー」で表情を作る。
  3. 風魔法がキャラクターに当たった時に、モーションして表情を変える
  4. (おまけ)カメラが近づいたらキャラクターがこっちを向く
こんな感じですね。
この記事では【前編部分】を紹介しています。
 


(4) 風魔法には3つの役目がある

風魔法には3つの役目があります。
 
  1. 小さな竜巻のような風魔法が見えること
  2. オブジェクトを動かすこと
  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)

    • 風魔法大好き
    • 2023年 8月 22日

    ほんとにすごいですね!
    見ててわかりやすく、動画を見たらほんとにすごいって思いました!
    いま、Unityに興味があって
    scriptを組む際の言語って何言語になるのですか?

      • だーしゅ
      • 2023年 8月 26日

      風魔法大好きさん、コメントありがとうございます。
      言語はC#ですね。

      以前は、この記事に書いた方法しかなかったのですが、Magica Cloth 2の「風ゾーン」を使うとパーティクルを当てることなく、特定のエリアだけ風を吹かすことができるようになりました。
      便利ですね。
      風の設定 | Magica Cloth 2

ブログの女の子を作る #34 NavMeshで巡回してモーションする2(外を歩く)【Unity】

ブログの女の子を作る #36 パーティクルとコライダーの風魔法を当てる【後編】【Unity】


最近のコメント

だーしゅ
IT関係のお仕事してます。
3Dモデルの女の子は「ブログノ・スージー」。VRは楽しいですね。

[当ブログについて]