【Unity(C#)】オブジェクトに当たると途切れるレーザーの実装方法

##レーザー
`Ray`と`LineRenderer`で実装します。

`Ray`がオブジェクトに当たったかどうかの判定を行い、`LineRenderer`の終点の位置を変えることで
レーザーの途切れを表現します。

そのためには`Ray`と`LineRenderer`の始点、終点を合わせておく必要があります。

```C#:RayとLineRendererの位置を合わせる
 void OnRay()
    {      
        float lazerDistance = 10f;
        Vector3 direction = hand.transform.forward * lazerDistance;
        Vector3 pos = hand.transform.position;

        RaycastHit hit;
        Ray ray = new Ray(pos, hand.transform.forward);
        lineRenderer.SetPosition(0,hand.transform.position);

        if (Physics.Raycast(ray, out hit, lazerDistance))
        {
            lineRenderer.SetPosition(1, pos + direction);
        }
        
        Debug.DrawRay(ray.origin, pos + direction, Color.red, 0.1f);
    }
```

画像のようにぴったりと合わせることができました。(赤い線=`Ray`,青い線=`LineRenderer`)
![ray_Line.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/74502bba-4633-5484-9860-8184bdf2fd93.png)

##貫通してしまう
先程のコードのままでは画像のようにオブジェクトを貫通してしまいます。
![Penetration.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/f3545fde-d5f2-435c-c1e7-07423479d9ba.png)

この問題を解決するうえで、`Ray`と`LineRenderer`をぴったりと合わせたことが鍵となります。

`Ray`はオブジェクトとの衝突判定を行うことができます。
また、**オブジェクトと衝突した座標を取得**することもできます。

つまり、**`LineRenderer`の終点に`Ray`が衝突した座標を当てはめれば
オブジェクトに当たった箇所でレーザー(LineRenderer)が途切れているように見せることができる**ということです。


コードに落とし込むとこうです。

```C#
        if (Physics.Raycast(ray, out hit, lazerDistance))
        {
            hitPos = hit.point; //オブジェクトとの衝突座標を取得
            lineRenderer.SetPosition(1, hitPos); //LineRendererの終点に当てはめる
        }
```



##デモ
OculusQuestでビルドしたデモになります。
![Ray_LineRenderer.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/ca685663-dcca-d23e-4405-daa87678b2d3.gif)

遮蔽物でレーザーが途切れているように見えます。ばっちりです。

デモにおいて、始点も少しずらしているので
始点のずらし方に関しても最終的なコードと合わせて記述していきます。

##最終的なコード

```C#
using UnityEngine;

[RequireComponent(typeof(LineRenderer))]
public class Laser : MonoBehaviour
{
    [SerializeField]
    GameObject hand;

    LineRenderer lineRenderer;
    Vector3 hitPos;
    Vector3 tmpPos;

    float lazerDistance = 10f;
    float lazerStartPointDistance = 0.15f;
    float lineWidth = 0.01f;

     void Reset()
    {
        lineRenderer = this.gameObject.GetComponent<LineRenderer>();
        lineRenderer.startWidth = lineWidth;
    }

    void Start()
    {
        lineRenderer = this.gameObject.GetComponent<LineRenderer>();
        lineRenderer.startWidth = lineWidth;
    }


    void Update()
    {
        OnRay();      
    }

    void OnRay()
    {      
        Vector3 direction = hand.transform.forward * lazerDistance;
        Vector3 rayStartPosition = hand.transform.forward*lazerStartPointDistance;
        Vector3 pos = hand.transform.position;
        RaycastHit hit;
        Ray ray = new Ray(pos+rayStartPosition, hand.transform.forward);

        lineRenderer.SetPosition(0,pos + rayStartPosition);

        if (Physics.Raycast(ray, out hit, lazerDistance))
        {
            hitPos = hit.point;
            lineRenderer.SetPosition(1, hitPos);
        }
        else
        {      
            lineRenderer.SetPosition(1, pos + direction);
        }

        Debug.DrawRay(ray.origin, ray.direction * 100, Color.red, 0.1f);

    }
}
```
##始点をずらす
始点をずらしているのは下記の箇所です。

```C#
    Vector3 rayStartPosition = hand.transform.forward*lazerStartPointDistance;
    Ray ray = new Ray(pos+rayStartPosition, hand.transform.forward);
    Vector3 pos = hand.transform.position;
    lineRenderer.SetPosition(0,pos + rayStartPosition);
```

Shaderで始点をずらしているようにみせる[^1]こともできますが、
今回は始点そのものの座標を調節してます。

手にコライダーが付いている状態で手のTransformを参照して`Ray`を出した場合、
手のPivot、コライダーの大きさによっては
`Ray`が手のコライダーに衝突してしまって判定が取れない場合があります。
そもそもの`Ray`の始点をずらすことで意図しない衝突を回避できます。


[^1]: [参考リンク](https://qiita.com/OKsaiyowa/items/04750cb937ebe19f688a#%E3%81%8A%E3%81%BE%E3%81%91)