[Unity] コライダーをBox Colliderでクリッピングする

# はじめに
　Box Colliderで領域を指定し、領域内で対象となるコライダーに対する衝突が行われないようにします。
　また、クリッピングした断面での衝突が行われるようにします。
# サンプル動画
 ![2024-05-26-16-22-22.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3799491/072c80a8-b052-8ad0-df37-7d1ceee6f144.gif)
　クリッピング領域内ではボールがオブジェクトをすり抜け、領域外では衝突していて、またオブジェクト内ではクリッピング領域が断面として機能し、ボールがオブジェクトを離れるとクリッピング面をすり抜けるようになっている様子がわかるかと思います。
# 動作環境
- Windows11
- Unity(2022.3.7f1)
# 導入方法
###### 3種類のオブジェクトを作成します。
 - クリッピングされるオブジェクト（gifのオレンジ色のオブジェクト）
 - クリッピングするオブジェクト（緑色のオブジェクト）
 - 衝突するオブジェクト（灰色のオブジェクト）
 ### クリッピングされるオブジェクト
　空のオブジェクトを作成し、その子としてクリッピングされるオブジェクトを作成します。
　"CollidedObj"という名前のレイヤーを作り、オブジェクト（子の方）に割り当てます。
 　ColliderとRigidbodyが必要なのですが、Colliderは初めからアタッチされているため、Rigidbodyのみを追加します。
　RigidbodyのIs Kinematicにチェックを入れておきます。
　ColliderのIs Triggerにチェックが入っていないことを確認します。
　Ctrl+Dでクリッピングされるオブジェクトを複製します。
　レイヤーをDefaultに指定しなおします。
　"CollidedObj"という名前のタグを指定し、割り当てます。
　ColliderのIs Triggerにチェックを入れます。
  
:::note info
クリッピングされるオブジェクトにはスクリプトをアタッチしません。
:::
### クリッピングするオブジェクト
　空のオブジェクトを作成し、その子としてCubeを作成します。
　"ClippingPlane"という名前のタグを作成し、割り当てます。
　Rigidbodyを追加し、Is Kinematicにチェックを入れます。
　ColliderのIs Triggerにチェックを入れます。
　Scaleを調整します。
 
:::note warn
CubeのScaleはクリッピングされるオブジェクトを充分に囲うことのできるだけの大きさにします。
:::
　Cubeを複製します。
　Cubeのスケールのy値を0にし、平面にします。
　Positionのy座標を複製元のCubeの底辺に合わせます。
　タグをUntaggedに指定し直します。
　ColliderのIs Triggerのチェックを外します。
　複製元のCube（平面にしていないほう）に以下のコードをアタッチします。
~~~C#:ClippingPlane.cs
    public class ClippingPlane : MonoBehaviour
    {
        private bool isCrossingWithCollidedObj; 
        [SerializeField] internal Collider crossSection;
        
        // Start is called before the first frame update
        void Start()
        {
            crossSection.enabled = false;
            isCrossingWithCollidedObj = false;
        }

        private void OnTriggerEnter(Collider other)
        {
        
            if (other.gameObject.CompareTag("CollidingObj"))
            {
                if (isCrossingWithCollidedObj)
                {
                    other.GetComponent<Rigidbody>().excludeLayers 
                    += LayerMask.GetMask("CollidedObj");
                }
            }
            else if (other.gameObject.CompareTag("CollidedObj"))
            {
                crossSection.enabled = true;
                isCrossingWithCollidedObj = true;
            }
        }

        private void OnTriggerExit(Collider other)
        {
            if (other.gameObject.CompareTag("CollidingObj"))
            {
                other.GetComponent<Rigidbody>().excludeLayers 
                -= LayerMask.GetMask("CollidedObj");
            }
            else if (other.gameObject.CompareTag("CollidedObj"))
            {
                crossSection.enabled = false;
                isCrossingWithCollidedObj = false;
            }
        }
    }
~~~
### 衝突するオブジェクト
　任意の3Dオブジェクトを作成します。
　"CollidingObj"という名前のタグを作成し、アタッチします。
　Rigidbodyを追加します。
　以下のコードをアタッチします。
~~~C#:CollidingObj.cs
    public class CollidingObj : MonoBehaviour
    {
        private ClippingPlane clippingPlane;
        private Collider collidedObj;
        private void OnTriggerEnter(Collider other)
        {
            if (other.gameObject.CompareTag("ClippingPlane"))
            {
                clippingPlane = other.gameObject.GetComponent<ClippingPlane>();
            }
        }

        private void OnTriggerExit(Collider other)
        {
            if (other.gameObject.CompareTag("CollidedObj") && clippingPlane != null)
            {
                Physics.IgnoreCollision(clippingPlane.crossSection, GetComponent<Collider>());
            }
        }
    }

~~~


# コード一覧
### クリッピングするオブジェクト
~~~C#:ClippingPlane.cs
    public class ClippingPlane : MonoBehaviour
    {
        private bool isCrossingWithCollidedObj; 
        [SerializeField] internal Collider crossSection;
        
        // Start is called before the first frame update
        void Start()
        {
            crossSection.enabled = false;
            isCrossingWithCollidedObj = false;
        }

        private void OnTriggerEnter(Collider other)
        {
        
            if (other.gameObject.CompareTag("CollidingObj"))
            {
                if (isCrossingWithCollidedObj)
                {
                    other.GetComponent<Rigidbody>().excludeLayers 
                    += LayerMask.GetMask("CollidedObj");
                }
            }
            else if (other.gameObject.CompareTag("CollidedObj"))
            {
                crossSection.enabled = true;
                isCrossingWithCollidedObj = true;
            }
        }

        private void OnTriggerExit(Collider other)
        {
            if (other.gameObject.CompareTag("CollidingObj"))
            {
                other.GetComponent<Rigidbody>().excludeLayers 
                -= LayerMask.GetMask("CollidedObj");
            }
            else if (other.gameObject.CompareTag("CollidedObj"))
            {
                crossSection.enabled = false;
                isCrossingWithCollidedObj = false;
            }
        }
    }
~~~
### 衝突するオブジェクト（クリッピングされないほうのオブジェクト）
~~~C#:CollidingObj.cs
    public class CollidingObj : MonoBehaviour
    {
        private ClippingPlane clippingPlane;
        private Collider collidedObj;
        private void OnTriggerEnter(Collider other)
        {
            if (other.gameObject.CompareTag("ClippingPlane"))
            {
                clippingPlane = other.gameObject.GetComponent<ClippingPlane>();
            }
        }

        private void OnTriggerExit(Collider other)
        {
            if (other.gameObject.CompareTag("CollidedObj") && clippingPlane != null)
            {
                Physics.IgnoreCollision(clippingPlane.crossSection, GetComponent<Collider>());
            }
        }
    }

~~~
# 参考サイト
- Physics-IgnoreCollision - Unityスクリプトリファレンス
https://docs.unity3d.com/ja/current/ScriptReference/Physics.IgnoreCollision.html
- Unity - Scripting API: Rigidbody.excludeLayers
https://docs.unity3d.com/ScriptReference/Rigidbody-excludeLayers.html
# さいごに
　このスクリプトはCubeなどの単純な形のオブジェクトでは正しく働くのですが、複雑な形のオブジェクトではうまく作動しません。今後の修正でMesh Colliderなどのコライダーにも対応させる予定です。