おもちゃラボ

Unityで遊びを作ってます

【Unity】シーンが遷移したことを検知する

あるシーンから別のシーンへ遷移するタイミングで何か処理を実行したい場合、シーンが遷移したことを検知する必要があります。Unity5.4からはシーン遷移にSceneManagerを使います。そして、このSceneMangerには「activeSceneChanged」「sceneLoaded」「sceneUnloaded」という3つのデリゲートが用意されています。

デリゲートとは特定のイベントが起こった場合に呼び出すメソッドを指定できる仕組みです。Unityではオブジェクト同士が衝突したときにはOnColliderEnterメソッドなどが自動的に呼ばれますが、このように既に決まったメソッドを呼び出すのではなく、プログラマが自由に呼ばれるメソッドを自由に決められる仕組みがデリゲートになります。

シーン遷移を検知するデリゲートと、登録したメソッドの呼ばれるタイミングは次のようになります。

デリゲート タイミング
activeSceneChanged アクティブなシーンが変更されたとき
sceneLoaded シーンが読み込まれたとき
sceneUnloaded シーンが破棄されたとき


これらのデリゲートの登録方法と、デリゲートの書き方は次のようになります。

ng System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class Test : MonoBehaviour {

    void Start()
    {
        SceneManager.activeSceneChanged += OnActiveSceneChanged;
        SceneManager.sceneLoaded              += OnSceneLoaded;
        SceneManager.sceneUnloaded          += OnSceneUnloaded;    
    }

    void OnActiveSceneChanged( Scene prevScene, Scene nextScene )
    {
        Debug.Log ( prevScene.name + "->"  + nextScene.name );
    }

    void OnSceneLoaded( Scene scene, LoadSceneMode mode )
    {
        Debug.Log ( scene.name + " scene loaded");
    }

    void OnSceneUnloaded( Scene scene )
    {
        Debug.Log ( scene.name + " scene unloaded");
    }
}

Startメソッドの中で デリゲートに呼び出すメソッドを登録しています。Startメソッドに続く3つのメソッドがデリゲートになります。デリゲートの名前は自由に決めてOKですが、戻り値と引数の型は決まっているので注意して下さい。

【Unity】落下地点の座標から、放物線の方程式を求める

落下地点が決まっていて、そこに向かって弾をとばしたり、ミサイルを撃ち込みたいことがあります。落下地点から放物線の方程式を求めるのって、結構難しいんですよね・・・。

ということで、ここでは、打ち出し角度と落下地点の座標を指定することで放物線の係数をもとめる方法を紹介します。

f:id:nn_hokuson:20170526213726p:plain:w600

これにより、指定した落下地点に確実に落下するミサイルなどを作ることが出来ますよ!記事の内容は次のとおりです。

放物線の求め方

放物線を求めるのに必要な情報として次の2つを与えます。

  • 落下地点の座標
  • 射出角度

ただし、打ち出す場所は原点とします。原点以外から撃つ場合は、オフセットを足してあげればOKです。この情報をもとに図を書くとこんな感じになります。

f:id:nn_hokuson:20170526214134p:plain:w500

求める放物線はf(x)=ax^2+bx+cと書けます。コレに上の条件をあたえると・・・

f(0)=0より、c = 0
f(target.x)=target.yより、target.y = a*target.x^2+b*target.x
f'(0)=tan(deg)より、b = tan(deg)

この連立方程式を解くと

a = (target.y - b * target.x) / (target.x * target.x)
b = tan(deg)

と、比較的簡単に放物線の方程式を求めることが出来ます。

放物線を求めるプログラム

落下地点の座標から放物線をもとめるスクリプトは次のようになります(ここでは、実際に放物線の方程式を求めているボールのスクリプトを示しています)

public class BallController : MonoBehaviour {

    public GameObject block;
    Vector3 offset;
    Vector3 target;
    float deg;
 
    IEnumerator ThrowBall()
    {
        float b = Mathf.Tan (deg * Mathf.Deg2Rad);
        float a = (target.y - b * target.x) / (target.x * target.x);
      
        for (float x = 0; x <= this.target.x; x+= 0.3f)
        {
            float y = a * x * x + b * x;
            transform.position = new Vector3 (x, y, 0) + offset;
            yield return null;
        }
    }

    public void SetTarget(Vector3 target, float deg)
    {
        this.offset = transform.position;
        this.target = target - this.offset;
        this.deg = deg;

        StartCoroutine ("ThrowBall");
    }

    void Start()
    {
        // ブロックに向かって60度の角度で射出
        SetTarget ( block.transform.position, 60 );
    }
}

SetTargetメソッドで、ボールの落下地点の座標(target)と、ボールの射出角度(deg)を指定しています。この例では、落下地点はブロックの位置、射出角度は60度を指定しています。射出地点を原点にするためoffsetを保存してから、放物線の係数を計算しています。ThrowBallコルーチンの中で、現在のx座標と放物線の方程式からy座標を求めています。最後にoffsetを戻してボールの位置に反映しています。

実行結果

クリックした点にボールが飛んで行くように修正したプログラムの実行結果がこちらです。

f:id:nn_hokuson:20170526215539g:plain

ここでは射出角度を、射出座標と落下座標のなす角度から求めています。

【Unity】バネの動きを実現する3つの方法

Unityでバネのアニメーションを作る場合、大きく分けて3つの方法があります。

バネの挙動をシミューレートできるSpring2DコンポーネントはPhysicsの物理挙動に従って計算するため、リアルな挙動にしたい場合はこれを使うと良いでしょう。ちょっとバネバネアニメーションを付けたい、という場合には大げさで取り回しが悪かったりします・・・

また、Animationを自分で組み立てる方法もあります。この場合、バネの強度や移動方向など、値が決め打ちになってしまうので、挙動を変えたい場合にはすこし手間が大きい方法です。

最後に、もっとも柔軟性の高い方法として、バネの挙動をスクリプトでシミュレートする方法があります。スクリプトでバネの動きを実現するのは意外と簡単なので、オススメの方法です。

では、それぞれの方法を使う方法を紹介します。

Spring Joint 2Dを使う方法

Spring Joint 2Dコンポーネントを使う場合は、バネに従って動かしたいオブジェクトにこのコンポーネント(Physics2D → Spring Joint 2D)をアタッチします。Spring Joint 2Dをアタッチすると、自動的にPhysics 2Dコンポーネントもアタッチされます。

f:id:nn_hokuson:20170522205248j:plain:w300

Spring Joint 2Dコンポーネントをアタッチすると、シーンビューには次のように緑の線で仮想のバネが表示されます。

f:id:nn_hokuson:20170522213037p:plain:w400

バネを別のオブジェクトに固定するには「Connected Rigid Body」を設定します。ジョイントを固定する座標は「Anchor」、自然長は「Distance」、強度は「Frequency」で設定します。よく使用するのはこれらのパラメータです。ここでは、原点に接続された長さ1のバネをシミュレートします。今回は、Anchor=(0, 0)、Distance=1、Frequency=3に設定しました。

f:id:nn_hokuson:20170522205320p:plain:w300

これ以外のパラメータについては、Unityのサイトを参照してください。

docs.unity3d.com

ゲームを実行すると、次のようにボールが原点に向かってバネ振動します。

f:id:nn_hokuson:20170522213647g:plain

また、上のように原点とボールの間に線を描きたい場合は、次のスクリプトをボールにアタッチしてください。GizmoクラスのDrawLineメソッドを使ってシーンビューに線を描画します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BallController : MonoBehaviour 
{
    void OnDrawGizmos ()
    {
        Gizmos.DrawLine (transform.position, Vector3.zero);
    }
}

スクリプトをアタッチ後、Gameビューの「Gizmos」をクリックして有効化することで、ゲームビューでも線を描画することができます。

f:id:nn_hokuson:20170522213816p:plain:w450

Animationを使う方法

バネのAnimationを自分で組み立てる場合は、Animatorをボールにアタッチします。ボールを選択した状態で、メニューバーから「Window」→「Animation」を選択してください。Animationウインドウが開くので「spring@ball」という名前でAnimation Clipを保存します。

f:id:nn_hokuson:20170522205337p:plain

続いてタイムラインでバネのアニメーションを組み立てます。バネの動作なので時間がたつに従って振幅が減衰するカーブを作成します。今回は次のように、x軸方向にのみ振動するバネを作成しました。

f:id:nn_hokuson:20170522205344p:plain

実行結果はこちらです。アニメーションを自分で作るのはセンスが必要ですね(笑)

f:id:nn_hokuson:20170523203123g:plain

こちらも分かりやすいように、原点とボールの間にGizmoを使って線を描画しています。この方法は上の「Spring Joint 2D」の項目を参照ください。

バネの挙動をスクリプトで書く方法

最後にスクリプトでバネの挙動をシミュレートする方法を紹介します。バネの挙動はイージングの動作と似ています。イージングではターゲット座標と現在の座標の差分が「ボールの移動速度」になりますが、バネではこの差分が「ボールの加速度」になります。

f:id:nn_hokuson:20170523203828p:plain:w300

つまり、ターゲット座標が遠ければ遠いほど大きな力で、ターゲット座標へ向かうことになり、ターゲット座標に近づくにつれて、かかる力も小さくなります。

f:id:nn_hokuson:20170523204225p:plain:w300

イージングでは、ターゲット座標と現在の座標の差分は速度に変換されるため、目標地点を行き過ぎることは決してありませんが、バネの場合は加速度に変換されるので、基本的にはオーバーシュートして、また戻ってを繰り返します。この挙動がバネっぽさに繋がるのですね。

ボールの動きを制御するC#スクリプト(BallController.cs)を作成し、ボールオブジェクトにアタッチします。BallControllerのプログラムは次のようになります。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BallController : MonoBehaviour {

    Vector3 targetPos;
    Vector3 acc, vel, pos;

    void Start () 
    {
        acc = vel = Vector3.zero;
        targetPos = pos = new Vector3(0, 0, 0);
    }

    void OnDrawGizmos ()
    {
        Gizmos.DrawLine (this.pos, this.targetPos);
    }

    void Update () 
    {
        if (Input.GetMouseButton (0))
        {
            this.pos = Camera.main.ScreenPointToRay(Input.mousePosition).origin;
            this.pos.z = 0;
        }
        else
        {
            Vector3 diff = this.targetPos - this.pos;
            this.acc = diff * 0.1f;
            this.vel += this.acc;
            this.vel *= 0.9f;
            this.pos += this.vel;        
        }
        transform.position = this.pos;
    }
}

完成図はこんな感じで、ボールをドラッグすると移動でき、マウスを離すと原点に向かってビヨンビヨンします。原点とボールが仮想のバネでつながれているイメージですね。

f:id:nn_hokuson:20170523204523g:plain

どうでしょうか?スクリプトでバネを実現する方法が一番気持ち良い動きになっている気がしませんか?慣れもあるとは思いますが、細かい動きまで微調整できるのでオススメですよ!