OculusQuestで凧あげしてみた

# はじめに
OculusQuestで凧あげしてみました。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">やっぱり足があると安定する <a href="https://t.co/KdMUGX3oIP">pic.twitter.com/KdMUGX3oIP</a></p>&mdash; 高浜 (@SatoshiTakahama) <a href="https://twitter.com/SatoshiTakahama/status/1248938952205070337?ref_src=twsrc%5Etfw">April 11, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

#開発環境
Unity 2019.3.6f1
XR Interaction Toolkit 0.9.3
Oculus XR Plugin 1.2.0

#作成方法
##凧本体
・Planeオブジェクトを適当な大きさで作成し、RigidbodyコンポーネントとClothコンポーネントをつけます。
・裏面も見えるように、Cull Offにしたシェーダーを設定します。

##持ち手
・CUBEオブジェクトを適当な大きさで作成し、Hinge Jointコンポーネントと、XR Grab Interactableコンポーネントをつけます。

##糸
・見えないようにしたSphereオブジェクトの間をLine Rendererで線を引いて作ります。[なわとびの縄のシミュレーション](http://nn-hokuson.hatenablog.com/entry/2018/01/30/200050)を参考にさせていただきました。手動だと大変なのでスクリプトを作成しました。

```C#:Rope.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(LineRenderer))]
public class Rope : MonoBehaviour
{

    [Tooltip("Material for LineRenderer")]
    public Material material;
    [Tooltip("Number of line vertices")]
    public int numVertices = 10;
    [Tooltip("Object at start (RequireComponent(typeof(HingeJoint)))")]
    public GameObject startObject;
    [Tooltip("Object at end (RequireComponent(typeof(Rigidbody)))")]
    public GameObject endObject;
    [Tooltip("Layer name for rope")]
    public string ropeLayer;

    private LineRenderer line;
    private List<GameObject> m_verticesList = new List<GameObject>();

    void Start()
    {
        Vector3 startPosition = new Vector3(0, 0, 0);
        Vector3 endPosition = new Vector3(10.0f, 0, 0);
        int div = numVertices - 1;
        if (startObject != null)
        {
            startPosition = startObject.transform.position;
            div++;
        }
        if (endPosition != null)
        {
            endPosition = endObject.transform.position;
            div++;
        }
        Vector3 space = (endPosition - startPosition) / div;

        Vector3 position = startPosition;
        if (startObject != null)
        {
            startObject.transform.parent = transform;
            m_verticesList.Add(startObject);
            position += space;
        }

        Quaternion rotation = startObject.transform.rotation;
        Vector3 scale = new Vector3(0.1f, 0.1f, 0.1f);
        for (int i = 0; i < numVertices; i++)
        {
            GameObject sphere = CreateSphere(position, rotation, scale);
            if (m_verticesList.Count > 0)
            {
                m_verticesList[m_verticesList.Count - 1].GetComponent<HingeJoint>().connectedBody = sphere.GetComponent<Rigidbody>();
            }
            m_verticesList.Add(sphere);
            position += space;
        }
        if (endObject != null)
        {
            m_verticesList[m_verticesList.Count - 1].GetComponent<HingeJoint>().connectedBody = endObject.GetComponent<Rigidbody>();
            endObject.transform.parent = transform;
            m_verticesList.Add(endObject);
        }

        line = GetComponent<LineRenderer>();
        if (material != null)
        {
            line.material = new Material(material);
        }
    }

    void Update()
    {
        line.positionCount = m_verticesList.Count;
        int idx = 0;
        foreach (GameObject v in m_verticesList)
        {
            line.SetPosition(idx, v.transform.position);
            idx++;
        }
    }

    private GameObject CreateSphere(Vector3 position, Quaternion rotation, Vector3 scale)
    {
        GameObject sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
        sphere.transform.parent = transform;
        sphere.transform.position = position;
        sphere.transform.rotation = rotation;
        sphere.transform.localScale = scale;
        Rigidbody rb = sphere.AddComponent<Rigidbody>();
        rb.useGravity = true;
        rb.mass = 0.1f;
        rb.drag = 100.0f;
        HingeJoint joint = sphere.AddComponent<HingeJoint>();
        joint.enableCollision = true;
        //JointLimits limits = joint.limits;
        //limits.min = 0;
        //limits.bounciness = 0;
        //limits.bounceMinVelocity = 0;
        //limits.max = 90;
        //joint.limits = limits;
        //joint.useLimits = true;
        JointSpring hingeSpring = joint.spring;
        hingeSpring.spring = 5;
        hingeSpring.damper = 10;
        hingeSpring.targetPosition = 0;
        joint.spring = hingeSpring;
        joint.useSpring = true;
        sphere.GetComponent<MeshRenderer>().enabled = false;
        if (ropeLayer != null)
        {
            sphere.layer = LayerMask.NameToLayer(ropeLayer);
        }
        return sphere;
    }

    public void Add()
    {
        GameObject sphere = m_verticesList[m_verticesList.Count - 1];
        Vector3 position = sphere.transform.position;
        Vector3 prevPosiotion = position;
        position += position - m_verticesList[m_verticesList.Count - 2].transform.position;
        sphere.transform.position = position;

        Quaternion rotation = m_verticesList[m_verticesList.Count - 2].transform.rotation;
        Vector3 scale = new Vector3(0.1f, 0.1f, 0.1f);
        sphere = CreateSphere(prevPosiotion, rotation, scale);
        HingeJoint joint = sphere.GetComponent<HingeJoint>();
        joint.connectedBody = m_verticesList[m_verticesList.Count - 1].GetComponent<Rigidbody>();
        m_verticesList.Insert(m_verticesList.Count - 1, sphere);

        sphere = m_verticesList[m_verticesList.Count - 3];
        joint = sphere.GetComponent<HingeJoint>();
        joint.connectedBody = m_verticesList[m_verticesList.Count - 2].GetComponent<Rigidbody>();
    }
}
```

・適当なオブジェクトを作成して、上記スクリプトとLine Rendererコンポーネントをつけます。糸は風の影響を受けないようにレイヤーで設定しておくとよいです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215278/f2b8dc8b-9373-a6e0-1736-0f0bc7bfb840.png)


##風
・凧を持ち上げるために、上方に力を加えるスクリプトを使います。[Rigidbodyが風を受けるようにする](http://ftvoid.com/blog/post/752)を参考にさせていただきました。

```C#:Wind.cs
using UnityEngine;
using System.Collections;

public class Wind : MonoBehaviour
{
    public float coefficient;   // 空気抵抗係数
    public Vector3 velocity;    // 風速

    void OnTriggerStay(Collider other)
    {
        if (other.attachedRigidbody)
        {
            // 相対速度計算
            var relativeVelocity = velocity - other.attachedRigidbody.velocity;

            // 空気抵抗を与える
            other.attachedRigidbody.AddForce(coefficient * relativeVelocity);
        }
    }
}.cs
```

・見えないようにしたSphereオブジェクトを適当な大きさで作成し、上記スクリプトをつけます。