他のオブジェクトの表面に沿ってオブジェクトを移動させる

投稿者: | 2020-02-19


ゲーム中にマウスを使って、他のオブジェクトの表面上に沿ってオブジェクトを移動させます。

空のゲームオブジェクトにスクリプトを付けます。

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

public class moveItemScript : MonoBehaviour
{
    public GameObject player;
    public GameObject item;

    LayerMask mask;
    GameObject item1;
    RaycastHit hit;

    CubeScript2 sc_item1;


    // Start is called before the first frame update
    void Start()
    {
        item1 = Instantiate(item, transform.position, transform.rotation);
        sc_item1 = item1.GetComponent<CubeScript2>();

        mask = ~(1 << 8 | 1 << 9);
    }

    // Update is called once per frame
    void Update()
    {
        if (Physics.Raycast(player.transform.position, player.transform.transform.forward, out hit, Mathf.Infinity, mask))
        {
            sc_item1.ray = true;

            Debug.DrawRay(player.transform.position, player.transform.transform.forward * hit.distance, Color.yellow);

            item1.transform.position = hit.point; // Cubeをレイの当たったところに移動
            item1.transform.rotation = Quaternion.FromToRotation(item.transform.up, hit.normal); // Cubeの上方向をレイが当たったところの表面の方向にする
            item1.transform.position += item1.transform.localScale.y / 1.98f * hit.normal; // Cubeが埋まらないように、表面方向に少し動かす
        }
        else
        {
            sc_item1.ray = false;

        }

    }

}

これには、FPSコントローラの子オブジェクトと、Cubeのプレハブをアタッチします。

CubeのプレハブはコライダーのIs Triggerのチェックを入れて、新規スクリプトとRigidbodyを追加し、RigidbodyのUse Gravityをオフ、is Kinematicをオンにしました。

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

public class CubeScript2 : MonoBehaviour
{
    MeshRenderer meshRenderer;
    Color defaultColor;
    Color defaultColor_Transparent;

    public bool ray = false;
    bool trigStay = false;

    // Start is called before the first frame update
    void Start()
    {
        meshRenderer = GetComponent<MeshRenderer>();
        defaultColor = meshRenderer.material.GetColor("_BaseColor");
        defaultColor_Transparent = defaultColor;
        defaultColor_Transparent.a = 0f;
    }

    // Update is called once per frame
    void Update()
    {
        if (!trigStay && ray)
        {
            meshRenderer.material.SetColor("_BaseColor", defaultColor);
        }
        else {
            meshRenderer.material.SetColor("_BaseColor", Color.red);
            //meshRenderer.material.SetColor("_BaseColor", defaultColor_Transparent);
        }
    }

    private void OnTriggerStay(Collider other)
    {
        trigStay = true;
    }

    private void OnTriggerExit(Collider other)
    {
        trigStay = false;
    }
}

レイの当たったところにCubeを表示する

Physics.Raycast(player.transform.position, player.transform.transform.forward, out hit, Mathf.Infinity, mask)

プレイヤーの視線の方向へレイキャストを飛ばしています。
これが床や他のオブジェクトとぶつかると、変数hitにその情報が入ります。hit.pointでレイがぶつかった場所の座標がわかるので、そこへCubeを移動しました。

すると、Cubeにレイがぶつかり、hit.pointがカメラの近くへ更新されることが繰り替えされて、Cubeがカメラに突撃します。

なので、Cubeにレイヤーを付けて、このレイヤーがついたオブジェクトをレイが無視するようにします。

mask = ~(1 << 8 | 1 << 9);

Playerの胴体などにもぶつからないように、プレイヤーにもレイヤーを設定して、レイヤーマスクを作りました。

他のオブジェクトの表面に沿わせる

レイが当たった部分の表面が向いている方向は、hit.normalでわかります。Cubeの頭上への方向が常にこの方向になるように、Cubeのrotationを変えます。

item1.transform.rotation = Quaternion.FromToRotation(item.transform.up, hit.normal);

Quaternion.FromToRotationメソッドの引数に、2つのVector3を入れると、第1引数の方へ向いているものを第2引数の方へ向けるための回転値がわかるので、これをCubeのtransform.rotationに入れました。

レイの当たった地点へCubeを動かすだけでは、Cubeの座標はCubeの中心を表すので、床や壁に埋まります。

そこで、hit.normalの方向へ、Cubeの大きさの半分くらい動かすことで、Cubeが埋まらなくなります。

item1.transform.position += item1.transform.localScale.y / 1.98f * hit.normal;

item1.transform.localScale.yは、Cubeのローカルの縦方向のサイズですが、Cubeの頭が常にレイが当たった地点の表面の方向を向いているので、Cubeが縦長でも問題ありません。

それでも、床と壁の境目で埋まるのでトリガーを使います。

Cubeが他のオブジェクトがぶつかるとき色を変える


床と壁の境目でCubeが埋まっていることを、OnTriggerStayなどで検知してCubeを非表示にしてみます。コライダーのis Triggerにチェックをいれているオブジェクトが他のオブジェクトとぶつかっている間は常にOnTriggerStayの処理が実行されます。
これ以外にも、レイがどのオブジェクトにもぶつからなくて、Cubeが更新できないときも表示を変えます。今回はCubeの色を透明にすることでCubeが消えたように見せます。まずはわかりやすいように透明でなく不透明な赤にしてみます。

埋まっていたCubeが外へ出てきたことは、OnTriggerExitでわかります。OnTriggerExitはCubeが外へ出た時に一度実行されます。これらの3の条件の組み合わせとその時のCubeの色を表にしました。

すると、OnTriggerStayが動いているときか、レイがどこにも当たっていないときに赤くなれば良いことがわかります。今回は、レイがどこかに当たっていて、且つOnTriggerStayが動いていなければデフォルトの色にしてみます。
OnTriggerExitは関係ないこともわかりました。

しかし、Cubeが外に出たことを検知するために、OnTriggerExitも使っています。

private void OnTriggerStay(Collider other)
    {
        trigStay = true;
    }

    private void OnTriggerExit(Collider other)
    {
        trigStay = false;
    }

レイを飛ばしたときに、レイがなにかに当たったかどうかをCubeについたスクリプトに教えます。

if (Physics.Raycast(player.transform.position, player.transform.transform.forward, out hit, Mathf.Infinity, mask))
{
    sc_item1.ray = true;
	
    //...
}
else
{
    sc_item1.ray = false;

}

これらの変数を使って、上の表の通りに条件を作ってCubeの色を変えました。

if (!trigStay && ray)
{
    meshRenderer.material.SetColor("_BaseColor", defaultColor);
}
else {
    meshRenderer.material.SetColor("_BaseColor", Color.red);
    //meshRenderer.material.SetColor("_BaseColor", defaultColor_Transparent);
}

Cubeのマテリアルを透明にする

Cubeを透明にするには、Cubeのプレハブに新規マテリアルを付けて、Surface Typeを「Transparent」にしておきます。

すると、透明になっていないときに、後ろのオブジェクトの影だけが透けて見えるような状態になります。

これは、マテリアルのReceive Shadowsのチェックを外すと治りました。

Cubeがどこにも重ならないときだけ表示されるようになりました。

アイテムの設置場所を探すようなスクリプトに使えるかもしれません。

一人称ステルスホラーゲームをitch.ioで公開しました。

https://nekoromorph.itch.io/hatch

コメントを残す

メールアドレスが公開されることはありません。