9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

UnityAdvent Calendar 2021

Day 25

Spring Joint 2D で ソフトボディを作る

Last updated at Posted at 2021-12-24

この記事は、Unityアドベントカレンダー25日目の記事です。

はじめに

Unity の Spring Joint 2D を利用することで、やわらかい物体(ソフトボディ)を作ることができます。
SpringJoint2Dによるソフトボディ.gif

GitHubリポジトリ

ソフトボディの作り方

パーティクル(Rigidbody)をばね(Spring Joint 2D)で接続すると、
ソフトボディのようにふるまうオブジェクトを作ることができます。

パーティクルを結ぶ@3x.png
image.png

ソフトボディの実装

ソフトボディの実装コード

ソフトボディのパーティクルを表現するクラス(SoftbodyParticle)を用意します。

SoftbodyParticle.cs
using UnityEngine;

public class SoftbodyParticle : MonoBehaviour
{
    [SerializeField] private new Rigidbody2D rigidbody;

    public Rigidbody2D Rigidbody => rigidbody;
}

次に、ソフトボディ(Softbody2D)を実装します。
SoftbodyParticleを円周上に並べ、SpringJoint2Dで接続しています。

Softbody2D.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Security.Cryptography;
using Unity.VisualScripting;
using UnityEngine;

#if UNITY_EDITOR
using UnityEditor;

[CustomEditor(typeof(Softbody2D))]
public class Softbody2DEditor : Editor
{
    public override void OnInspectorGUI()
    {
        if (GUILayout.Button("Create"))
        {
            var component = target as Softbody2D;
            if (component != null)
            {
                component.Create();
            }
        }
        
        base.OnInspectorGUI();
    }
}
#endif

public class Softbody2D : MonoBehaviour
{
    [Header("=== Create Settings ===")]
    [SerializeField] private int particleCount = 32;
    [SerializeField] private float softbodyRadius = 4f;
    [SerializeField] private SoftbodyParticle _particlePrefab;
    [SerializeField] private bool aroundConfigureDistance = false;
    [SerializeField] private bool centerConfigureDistance = false;
    
    [Header("=== Runtime Settings ===")]
    [SerializeField] private float aroundFrequency = 25f;
    [SerializeField] private float centerFrequency = 5f;

    [Header("=== Cache ===")]
    [SerializeField] private SoftbodyParticle[] aroundParticles;
    [SerializeField] private SoftbodyParticle centerParticle;
    [SerializeField] private List<SpringJoint2D> aroundJoints = new List<SpringJoint2D>();
    [SerializeField] private List<SpringJoint2D> centerJoints = new List<SpringJoint2D>();

    private void Update()
    {
        foreach (var joint in aroundJoints)
        {
            joint.frequency = aroundFrequency;
        }
        foreach (var joint in centerJoints)
        {
            joint.frequency = centerFrequency;
        }
    }

    private void OnDrawGizmos()
    {
        Gizmos.matrix = Matrix4x4.Translate(new Vector3(0, 0, -4f));
        foreach (var joint in aroundJoints)
        {
            Gizmos.color = Color.red;
            if (joint != null)
                Gizmos.DrawLine(joint.transform.position, joint.connectedBody.position);
        }
        foreach (var joint in centerJoints)
        {
            Gizmos.color = Color.yellow;
            if (joint != null)
                Gizmos.DrawLine(joint.transform.position, joint.connectedBody.position);
        }
    }

    /// <summary>
    /// ソフトボディの作成
    /// </summary>
    public void Create()
    {
        CreateParticles();
        CreateJoint();
    }

    /// <summary>
    /// パーティクルをJointで接続
    /// </summary>
    private void CreateJoint()
    {
        aroundJoints.Clear();
        centerJoints.Clear();
        
        // 外周パーティクル同士を接続
        for (int i = 0; i < aroundParticles.Length; i++)
        {
            var p1 = aroundParticles[i];
            var p2 = aroundParticles[(i + 1) % aroundParticles.Length];

            var joint1 = p1.gameObject.AddComponent<SpringJoint2D>();
            var joint2 = p2.gameObject.AddComponent<SpringJoint2D>();

            joint1.connectedBody = p2.Rigidbody;
            joint2.connectedBody = p1.Rigidbody;

            aroundJoints.Add(joint1);
            aroundJoints.Add(joint2);
        }
        
        // 中心パーティクルと外周パーティクルを接続
        for (int i = 0; i < aroundParticles.Length; i++)
        {
            var p1 = aroundParticles[i];
            var p2 = centerParticle;

            var joint1 = p1.gameObject.AddComponent<SpringJoint2D>();
            var joint2 = p2.gameObject.AddComponent<SpringJoint2D>();

            joint1.connectedBody  = p2.Rigidbody;
            joint2.connectedBody  = p1.Rigidbody;
            
            centerJoints.Add(joint1);
            centerJoints.Add(joint2);
        }
        
        // Jointの設定
        foreach (var joint in aroundJoints)
        {
            joint.autoConfigureDistance = aroundConfigureDistance;
            joint.enableCollision = false;
        }
        foreach (var joint in centerJoints)
        {
            joint.autoConfigureDistance = aroundConfigureDistance;
            joint.enableCollision = false;
        }
    }

    /// <summary>
    /// パーティクル生成
    /// </summary>
    private void CreateParticles()
    {
        if (centerParticle != null)
        {
            DestroyObject(centerParticle.gameObject);
        }
        foreach (var p in aroundParticles)
        {
            DestroyObject(p.gameObject);
        }

        centerParticle = CreateParticle(Vector3.zero);
        aroundParticles = new SoftbodyParticle[particleCount];
        for (int i = 0; i < particleCount; i++)
        {
            float radian = i * Mathf.PI * 2f / (particleCount - 1);
            var position = new Vector3(Mathf.Cos(radian), Mathf.Sin(radian), 0f) * softbodyRadius;
            aroundParticles[i] = CreateParticle(position);
        }
    }

    /// <summary>
    /// パーティクル作成
    /// </summary>
    private SoftbodyParticle CreateParticle(Vector3 position)
    {
        var p = Instantiate(_particlePrefab, transform);
        p.transform.localPosition = position;
        return p;
    }

    static void DestroyObject(GameObject target)
    {
        if (Application.isPlaying)
            Destroy(target);
        else
            DestroyImmediate(target);
    }
}

 

ソフトボディの作成

パラメータや、Prefabを以下のように設定した状態で、Createボタンを押します。
image.png

以下のようなソフトボディが作成されます。
image.png

結果

SpringJoint2Dによるソフトボディ2.gif

9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?