개발/Unity

유니티 - 운동하는 단진자의 로프를 끊기 (Cut the Rope)

피로물든딸기 2023. 4. 3. 12:19
반응형

Unity 전체 링크

 

참고

- 쿼터니언으로 구현한 단진자의 운동
- 쿼터니언으로 구현한 단진자의 운동 확장
- 독립된 단진자의 운동

- 런타임에 단진자 운동 설정하기

- 라인 렌더러를 이용한 단진자 속도의 시각화

- 라인 렌더러로 간단한 화살표 만들기

- 운동하는 단진자의 로프를 끊기

 

이제 스페이스 바를 누르면 라인 렌더러(로프)가 사라져서 단진자가 자연스럽게 운동하도록 해보자.

 

Oscillator에는 Rigidbody를 추가하고 Use Gravity를 체크해제한다.

 

Update에서 Space Bar 키를 입력할 경우에 아래와 같이 처리한다.

이전 글에서 구한 속도를 구하는 방법으로 리지드바디에 속도를 직접 넣는다.

    void Update()
    {
        lrs[0].SetPosition(1, this.transform.position);

        if (Input.GetKeyDown(KeyCode.Space))
        {
            isJump = true;

            lrs[0].enabled = false;
            lrs[1].enabled = false;

            float angle 
                = Mathf.LerpAngle(startAngle, endAngle, (Mathf.Sin(startRot - Mathf.PI / 2) + 1.0f) / 2.0f);

            Vector3 vPO
                = Quaternion.AngleAxis(angle, startPlaneNormal) * startPO;

            rb.velocity = getVelocity(vPO);
        }
    }

 

스페이스 바를 누르면 운동하던 방향으로 그대로 날아간다.

 

여기에 useGravity를 다시 on 해주면 된다.

    rb.velocity = getVelocity(vPO);
    rb.useGravity = true;

 

이동하는 방향으로 움직이면서 중력의 영향으로 물체가 떨어지는 것을 알 수 있다.

 

gravity를 public으로 선언해서 더 적절한 속도를 만들도록 해보자.

 public float gravity = 9.8f;

 

참고로 gravity가 커지면 속도도 커지지만, 라인 렌더러도 커지기 때문에 아래와 같이 처리하였다.

makeArrow(lrs[1], this.transform.position, this.transform.position + getVelocity(vPO) / gravity, startPlaneNormal);

 

gravity = 9.8f로 했을 때 조금 더 힘차게 움직인다.

 

반대 방향의 경우에도 정상적으로 움직인다.

 

전체 코드는 다음과 같다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ExpandedSimpleHarmonicOscillator : MonoBehaviour
{
    public Transform pivot;
    public float speed = 2.0f;
    public GameObject plane;

    public float gravity = 9.8f;

    float startAngle, endAngle;
    Vector3 startPlaneNormal;

    Vector3 startPO;
    float distance;

    public GameObject lineRenderer;
    LineRenderer[] lrs;

    Rigidbody rb;

    float startRot = 0.0f;

    bool isDragging;
    bool isJump;

    [Range(0.0f, 45.0f)]
    public float theta = 30.0f;
    [Range(0.0f, 1.0f)]
    public float ratio = 0.1f;

    float totalEnergy;
    void setLineRenderer(LineRenderer lr, Color color)
    {
        lr.startWidth = lr.endWidth = .1f;
        lr.material.color = color;

        lr.positionCount = 2;
    }

    Vector3 getNormal(Vector3 a, Vector3 b, Vector3 c)
    {
        Plane p = new Plane(a, b, c);
        return p.normal;
    }

    void setPlane(Vector3 a, Vector3 b, Vector3 c)
    {
        plane.transform.position = a;
        plane.transform.up = getNormal(a, b, c);
    }

    void OnMouseDown()
    {
        isDragging = true;
    }

    void OnMouseDrag()
    {
        float distance = Camera.main.WorldToScreenPoint(transform.position).z;

        Vector3 mousePos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, distance);
        Vector3 objPos = Camera.main.ScreenToWorldPoint(mousePos);

        objPos.x = 0;
        this.transform.position = objPos;
    }

    void OnMouseUp()
    {
        initSettings();
        isDragging = false;
    }

    void initSettings()
    {
        float angle = Vector3.Angle(this.transform.position - pivot.position, Vector3.down);

        startAngle = 0.0f; endAngle = angle * 2;

        Vector3 normal
            = getNormal(pivot.position + Vector3.down, pivot.transform.position, this.transform.position);

        startPlaneNormal = normal;

        setPlane(pivot.position + Vector3.down, pivot.transform.position, this.transform.position);

        startPO = (this.transform.position - pivot.position).normalized;
        distance = Vector3.Distance(this.transform.position, pivot.position);

        startRot = 0.0f;

        totalEnergy = this.transform.position.y; // Potential Energy = mgh
    }

    int getCurrentState()
    {
        float hPi = Mathf.PI / 2.0f;
        return (int)(startRot / hPi) % 4;
    }

    float getNormalDirection()
    {
        return getCurrentState() < 2 ? 1.0f : -1.0f;
    }

    void makeArrow(LineRenderer lr, Vector3 start, Vector3 end, Vector3 normal)
    {
        Vector3 vES = (start - end) * ratio;
        Vector3 dot1 = end + Quaternion.AngleAxis(theta, normal) * vES;
        Vector3 dot2 = end + Quaternion.AngleAxis(360 - theta, normal) * vES;

        lr.positionCount = 5;

        lr.SetPosition(0, start);
        lr.SetPosition(1, end);
        lr.SetPosition(2, dot1);
        lr.SetPosition(3, dot2);
        lr.SetPosition(4, end);
    }

    Vector3 getVelocity(Vector3 rope)
    {
        float kinetic = gravity * (totalEnergy - this.transform.position.y);
        float v = Mathf.Sqrt(2 * kinetic); // k = 1/2 m v^2 -> v = sqrt(2k/m)
        Vector3 verocity
            = (Quaternion.AngleAxis(90, startPlaneNormal) * rope).normalized * getNormalDirection();

        return verocity * v;
    }

    void Start()
    {
        initSettings();

        rb = this.GetComponent<Rigidbody>();

        lrs = lineRenderer.GetComponentsInChildren<LineRenderer>();
        setLineRenderer(lrs[0], Color.blue);
        lrs[0].SetPosition(0, pivot.position);

        setLineRenderer(lrs[1], Color.red);
    }

    void Update()
    {
        lrs[0].SetPosition(1, this.transform.position);

        if (Input.GetKeyDown(KeyCode.Space))
        {
            isJump = true;

            lrs[0].enabled = false;
            lrs[1].enabled = false;

            float angle 
                = Mathf.LerpAngle(startAngle, endAngle, (Mathf.Sin(startRot - Mathf.PI / 2) + 1.0f) / 2.0f);

            Vector3 vPO
                = Quaternion.AngleAxis(angle, startPlaneNormal) * startPO;

            rb.velocity = getVelocity(vPO);
            rb.useGravity = true;         
        }
    }

    void FixedUpdate()
    {
        if (isDragging || isJump) return;

        startRot += (Time.fixedDeltaTime * speed);

        float angle 
            = Mathf.LerpAngle(startAngle, endAngle, (Mathf.Sin(startRot - Mathf.PI / 2) + 1.0f) / 2.0f);

        Vector3 vPO 
            = Quaternion.AngleAxis(angle, startPlaneNormal) * startPO;

        this.transform.position 
            = pivot.transform.position + (vPO).normalized * distance;

        makeArrow(lrs[1], this.transform.position, this.transform.position + getVelocity(vPO) / gravity * 2.0f, startPlaneNormal);
    }
}

 

위의 실행결과는 아래의 unitypackage에서 확인 가능하다.

CuttheRope.unitypackage
0.03MB

 

반응형