【Unity】曲がる弾をつくる(2D)

曲がる弾をつくります。

初めはSlerpでつくろうと思ったのですが、
複数の方向へ飛ばしたかったので二次ベジェ曲線を使って作りました。
ベジェ曲線に辿り着くまでに、
LerpとSlerpもかじったので
この二つについても書いていきたいと思います。
二次ベジェ曲線のところだけ知りたいという方は
こちらから飛んでください。
(二次ベジェ曲線ではLerpを使います)

LerpとSlerpについて

*Lerp

Vector3.Lerp
Lerpは線形補間です。
字面の通り、与えられた二点をで補間します。
・使い方

Vector3.Lerp(posA, posB, ratio)

第3引数のratioposAからposBまでを結んだ直線の上を進んだ割合です。
0.0f~1.0fで指定します。
つまり、
ratioが0.0fのときLerpはposAの位置を返し
ratioが0.5fのときLerpはposAposBの中間地点を返し
ratioが1.0fのときLerpはposBの位置を返します。
f:id:aoaoaoaoaoaoaoi:20181028201806p:plain

*Slerp

Vector3.Slerp
Slerpは球面補間です。
字面の通り、与えられた二点をで補間します。
・使い方

Vector3.Slerp(posA, posB, ratio)

第3引数のratioposAからposBまでを結んだ直線の上を進んだ割合です。
0.0f~1.0fで指定します。
つまり、
ratioが0.0fのときLerpはposAの位置を返し
ratioが0.5fのときLerpはposAposBの中間地点を返し
ratioが1.0fのときLerpはposBの位置を返します。
f:id:aoaoaoaoaoaoaoi:20181028203434p:plain

単に曲げるだけならSlerpでも良かったのですが、
自由に曲げて弾を出したかったので
二次ベジェ曲線を使ってみました。

*二次ベジェ曲線

※上のLerpとSlerpの動画内では追尾弾を実装していますが、
以下のベジェ曲線では追尾機能は実装していません。
二次ベジェ曲線については、
動画もついている以下の記事がとても分かり易いです。

setchi.hatenablog.com

二次ベジェ曲線の作り方を、
ざっくりまとめると以下のようになります。
(上の記事がとても分かり易いので、ぜひ上の記事を読んでください)

*材料

スタート地点の座標
中継地点の座標(弾自身が中継するわけではない)
ターゲットの座標
f:id:aoaoaoaoaoaoaoi:20181028205311p:plain

*作り方

スタート地点から中継地点までのベクトル上、
そして中継地点からターゲットまでのベクトル上を
それぞれ点(ピンクの丸水色の丸)に走らせます。
f:id:aoaoaoaoaoaoaoi:20181028210635p:plain

②先ほどベクトル上を走らせていた点を、ベクトルで結びます。
f:id:aoaoaoaoaoaoaoi:20181028211149p:plain

③先ほど結んで作ったベクトル上にを走らせます。
→この点が弾の軌道になります
f:id:aoaoaoaoaoaoaoi:20181028211853p:plain
ざっくり作り方を説明すると以上になります。

ベクトル上を走る点はLerpで走らせます。
弾の軌道を考察していくと、以下のようになります。
(全ての弾が自身の走るベクトルを、同じ割合で進んでいく場合)
①弾の進行が0%のとき
スタート地点から中継地点を走る点スタート地点
中継地点からターゲットまでを走る点中継地点から始まります。
このとき、この二点を結んだベクトル
スタート地点から中継地点を結ぶベクトルとなり、
進行は0%なので弾の位置はちょうどスタート地点となります。
f:id:aoaoaoaoaoaoaoi:20181028212911p:plain

②弾の進行が50%のとき
スタート地点から中継地点を走る点は、
自身が走るベクトルのちょうど中間地点にいます。
同じく、中継地点からターゲットまでを走る点は、
自身が走るベクトルのちょうど中間地点にいます。
上の二点を結んだベクトル上を走るも、
自身が走るベクトル(上の二点を結んだベクトル)のちょうど中間地点にいます。
f:id:aoaoaoaoaoaoaoi:20181028213709p:plain

③弾の進行が100%のとき
スタート地点から中継地点を走る点中継地点
中継地点からターゲットまでを走る点ターゲットに到着します。
このとき、二点を結んだベクトル
中継地点からターゲットを結ぶベクトルとなり、
この上を走る
ゴールであるターゲットに到着します。
f:id:aoaoaoaoaoaoaoi:20181028214058p:plain

まとめると
二つのベクトル上に点を走らせ、
その点をつないだベクトル上にを走らせることで
曲がった線の軌道を描いています。
(図では分かりにくいと思うので、ぜひ上の分かり易い記事を読んでください)

上の動きをコードにすると以下のようになります。
弾のオブジェクトにつけています。

void Update () {
         //弾の進行具合(Lerpの第三引数に入れる)
        time += Time.deltaTime;
        //二次ベジェ曲線を使う
        //スタートから中継地点をつなぐベクトル上を走る点の位置
        var  firstVec= Vector3.Lerp(startPos, relayPos, time);
        //中継地点からターゲットまでをつなぐベクトル上を走る点の位置
        var SecondVec = Vector3.Lerp(relayPos, targetPos, time);
        //上の二点をつなぐベクトル上を走る点(弾)の位置
        var vec = Vector3.Lerp(firstVec, SecondVec, time);
        //弾の位置を代入する
        this.transform.position = vec;
        }

relayPosを複数にする(弾ごとに変える)ことで
弾に複数の弧を描かせることができます。
(上と下から、左と右から、など)
一番上にある
上と下から弾を出している動画のコードは
以下のように書いています。

・弾を複製するコード
右のキャラクターにつけています(スタート地点)

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

public class TamaToPlayerFollow : MonoBehaviour
{
    //プレイヤー(ターゲット)
    public GameObject player;
    //弾のプレハブ
    public GameObject tama;
    //弾を0.1秒ごとに打ち出す
    private float targetTime = 0.1f;
    private float currentTime = 0;
    //中継地点1
    public GameObject greenPoint;
    //中継地点2
    public GameObject greenPoint1;
    //中継地点を割り振るための変数
    int count = 0;

    // Update is called once per frame
    void Update()
    {
        //弾を0.1秒ごとに打ち出すためのもの
        currentTime += Time.deltaTime;
        if(targetTime < currentTime)
        {
            currentTime = 0;
            //敵の位置を保存
            var pos = this.gameObject.transform.position;
            //弾のプレハブを作成
            var t = Instantiate(tama) as GameObject;
            //弾の初期位置を敵の位置にする
            t.transform.position = pos;
            //弾につけているスクリプト、TamaTobasuコンポネントを保存する
            var cash=t.GetComponent<TamaTobasu>();
            //スタート地点を弾のスクリプトに渡す
            cash.CharaPos = this.transform.position;
            //弾を一つ打ち出すたびに中継地点を変える
            count++;
            //中継地点を弾のスクリプトに渡す
            if(count%2==1) cash.GreenPos=greenPoint.transform.position;
            else cash.GreenPos = greenPoint1.transform.position;
            //プレイヤー(ターゲット)の位置を弾のスクリプトに渡す
            cash.PlayerPos = player.transform.position;
        }

・弾を動かすコード
弾につけています

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

public class TamaTobasu : MonoBehaviour {

    //それぞれの位置を保存する変数
    //スタート地点
    private Vector2 charaPos;
    public Vector2 CharaPos { set { charaPos = value; } }
    //ゴール地点
    private Vector2 playerPos;
    public Vector2 PlayerPos { set { playerPos = value; } }
    //中継地点
    private Vector2 greenPos;
    public Vector2 GreenPos { set { greenPos = value; } }
    //進む割合を管理する変数
    float time;

    // Update is called once per frame
    void Update () {
        //弾の進む割合をTime.deltaTimeで決める
        time += Time.deltaTime;
       
        //二次ベジェ曲線
        //スタート地点から中継地点までのベクトル上を通る点の現在の位置
        var a = Vector3.Lerp(charaPos, greenPos, time);
        //中継地点からターゲットまでのベクトル上を通る点の現在の位置
        var b = Vector3.Lerp(greenPos, playerPos, time);
        //上の二つの点を結んだベクトル上を通る点の現在の位置(弾の位置)
        this.transform.position = Vector3.Lerp(a, b, time);
    }
}

参考

【Unity】ベジェ曲線を学び、実装する
[Unity] Vector3.Lerpの使い方