はじめに
OculusQuestで凧あげしてみました。
やっぱり足があると安定する pic.twitter.com/KdMUGX3oIP
— 高浜 (@SatoshiTakahama) April 11, 2020
開発環境
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で線を引いて作ります。なわとびの縄のシミュレーションを参考にさせていただきました。手動だと大変なのでスクリプトを作成しました。
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コンポーネントをつけます。糸は風の影響を受けないようにレイヤーで設定しておくとよいです。

風
・凧を持ち上げるために、上方に力を加えるスクリプトを使います。Rigidbodyが風を受けるようにするを参考にさせていただきました。
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オブジェクトを適当な大きさで作成し、上記スクリプトをつけます。