Unity3D

유니티 레이싱 게임 만들기[2] - 서스펜션

상연 2023. 1. 29. 02:50

목차

    Unity Racing Game 등으로 검색을 하면 많은 정보가 나온다.

    https://www.youtube.com/watch?v=x0LUiE0dxP0 

     

    그 중에서 위의 강의에서 구현된 모습이 내가 원하는 움직임에 가깝다고 생각이 들어 따라가보려고 한다.

    Wheel Collider가 없이 Raycast를 기반으로 구현한다고 한다.

     

    서스펜션이란

    https://post.naver.com/viewer/postView.naver?volumeNo=30923170&memberNo=34517836 

     

    [자동차 상식] 서스펜션은 이렇게 움직인다! (Feat. 진동)

    [BY Beyond Motors] 안녕하세요 Beyond Motors입니다.이번 포스트에서는 자동차 서스펜션의 원리를 기계...

    m.post.naver.com

    우선, Arcade Racing Car 제작이 목표이기때문에 실제 자동차와 같이 굉장히 복잡한 연산보다는, 약식으로 구현되어있다고 생각하면 된다.

    그렇기 때문에 서스펜션을 완전하게 이해하기 보다는 주요한 흐름을 이해하는것이 중요한데, 위의 블로그 글이 그정도의 이해를 하기에 적당한 듯 하다.

    서스펜션에서 스프링과 댐퍼의 역할을 이해하면 위의 강의에서 제공되는 코드를 이해할 수 있다고 생각한다.

    실제 환경에서는 타이어의 유압 등에 따라서 타이어의 반지름 등이 고정값을 가지지 않겠지만,

    우리는 아케이드 레이싱이기때문에 타이어가 Static 하다고 고려하고 제작을 한다.

    그러면 이제 차량에서 바퀴가 있어야 할 부분 위쪽쯤에서 Raycast를 지면 방향으로 발사를 하게 되는데

    간단하게 Raycast의 최대길이는 스프링의 최대길이 + 타이어의 지름이다.

    추후 코드를 통해 이해하겠지만, 여러 물리학을 통해 스프링은 압축되거나, 늘어날것이고 그러한 과정에서 발생하는 힘이 차체중심에 가하게 되면서 차가 마치 공중에 떠 있는것과 같이  된다.

    이 부분에 대해서는 강의를 직접 들어보며 코드를 쳐보는것을 추천한다.

    강의를 따라가면 이렇게 오브젝트 구성을 하게 되는데

    이렇듯 최상위 오브젝트, 부모가 차체의 중심이 된다고 생각하면된다.

    따라서 중심이 되는 오브젝트가 Rigidbody Component를 보유하게 되는데

    실제 차량의 무게 처럼 Mass를 적절히 주어야 차가 제대로 작동하기 때문에 1500을 입력해주었다.

    (참고로 Mass 1당 1kg 라고 생각하면 된다!)

    그리고 그 다음은 차체의 Collider를 담당하게 될 오브젝트

    나중에는 어떻게 될 지 모르겠으나, 우선은 가장 간단하게 표현 가능한 Box Collider로 차체를 감싸주었다.

    그리고 마지막으로 지면에 Raycast를 발사할 원점이다.

    4곳에서 지면을 향해 발사하고 거리를 얻으며 계산하여 부모 오브젝트에있는 Rigidbody에 힘을 가해 서스펜션을 구현하게 된다.

    지면으로 Raycast  발사

    이제 각 지점에서 Raycast를 발사 해보자.

    if(Physics.Raycast(transform.position, -transform.up, out RaycastHit hit, maxLength + wheelRadius))

    각 지점(transform.position)에서 Local Position의 아래 방향(-transform.up)으로 Raycast를 발사해서 얻은 정보를 'hit'에 저장한다(out RaycastHit hit), 단 Raycast는 무한의 길이를 갖는것이 아닌, 최대 maxLength + wheelRadius 만큼의 길이를 갖는다.

    즉, maxLength + wheelRadius 범위내에 Raycast 가 hit 하지 않으면 차체의 그 부분은 지면과 닿아있지 않다와 같다.

    반대로 닿아있으면 Raycast의 길이에 따라 차체의 각 부분에서 차에 힘을 가할 수 있다.

     

    maxLength + wheelRadius 은 어떻게 나온 것인가?

    wheelRadius는 말 그대로 바퀴의 반지름이다.

    maxLength 는 스프링이 최대 팽창? 이완 됐을때의 길이이다.

    https://ko.khanacademy.org/computing/computer-programming/programming-natural-simulations/programming-oscillations/a/spring-forces 

     

    용수철의 힘 (개념 이해하기) | 진동 | Khan Academy

    수학, 예술, 컴퓨터 프로그래밍, 경제, 물리학, 화학, 생물학, 의학, 금융, 역사 등을 무료로 학습해 보세요. 칸아카데미는 어디에서나 누구에게나 세계 최고의 무료 교육을 제공하는 미션을 가진

    ko.khanacademy.org

    스프링에 대해서는 해당 글을 읽어보면 이해하기 쉽다.

     

    코드와 주석

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class ScrWheel : MonoBehaviour
    {
        //차체 중심에 위치한 Rigidbody를 가져온다.
        private Rigidbody rb;
    
        [Header("Suspension")]
        public float restLength; //스프링의 기본상태에서의 길이
        public float springTravel; //스프링의 수축,팽창 범위
        public float springStiffness; //스프링의 강성
        public float damperStiffness; // 댐퍼의 강성
    
        private float minLength; //스프링의 최대길이
        private float maxLength; //스프링의 최소길이
        private float lastLength; //이전 프레임 업데이트에서의 스프링의 길이
        private float springLength; //현재 프레임에서의 스프링 길이
        private float springVelocity; //스프링의 속력
        private float springForce; //스프링의 힘
        private float damperForce; //댐퍼의 힘
    
    
        private Vector3 suspensionForce;
    
        [Header("Wheel")]
        public float wheelRadius;
    
        void Start()
        {
            rb = GetComponentInParent<Rigidbody>();
    
            minLength = restLength - springTravel;
            maxLength = restLength + springTravel;
        }
    
        //물리학이기 때문에 FixedUpdate
        private void FixedUpdate()
        {
            if(Physics.Raycast(transform.position, -transform.up, out RaycastHit hit, maxLength + wheelRadius)){
                lastLength = springLength; //이전 프레임의 스프링 길이 저장.
                springLength = hit.distance - wheelRadius; //스프링의 길이 Raycast 길이에서 바퀴의 반지름을 뺀 부분
                springLength = Mathf.Clamp(springLength, minLength, maxLength); //스프링 길이를 최소 최대값을 통해 범위내에 있도록 한정하는것.
                springVelocity = (lastLength - springLength) / Time.fixedDeltaTime; 
                // 스프링의 속력, 이전길이에서 현재길이를 뺐을때
                // 음수 -> 스프링이 수축
                // 양수 -> 스프링이 이완
                // 이전-현재 값을 Time.fixedDeltaTime으로 나누어  값을 얻어주어 이전 프레임 대비 현재 스프링이 '얼마나' 수축되고 이완되었는지 값을 저장한다.
                springForce = -springStiffness * (springLength - restLength);
                //Fspring =−k*x
                //스프링의 힘을 구하는 공식이다. k는 강성을 나타내는 상수로 springStiffness이다.
                //x는 스프링의 기본 길이(restLength)에서 얼마만큼 수축 / 이완했는지에 대한 길이이므로
                //Current Spring Length 에서 RestLength 를 빼면 구할 수 있다.
                 damperForce = damperStiffness * springVelocity;
                //댐퍼는 서스펜션 내 스프링이 수축과 이완을 반복하는 과정에서 스프링 운동을 '완화'하는 역할을 한다.
                //댐퍼가 없다면 차체가 계속해서 흔들리기만 하기 때문에 댐퍼를 통해 해당 스프링의 반동을 감쇠시켜주어야한다.
                //댐퍼의 크기가 클수록 크게 감쇠하여 스프링의 운동이 적어지기 때문에 딱딱하고 너무 작으면 출렁인다.
                suspensionForce = (springForce + damperForce) * transform.up;
                
                rb.AddForceAtPosition(suspensionForce, hit.point);
            }
        }
    
    }

    Damper 관련해서는 위에 링크한 글들을 보면 스프링의 힘과 연관되어서 어떤 수식이 있는지 나타나있으므로 그 부분을 참조해서 보면 더욱 더  이 간단한 서스펜션 코드에 대해 이해하기 쉬울 것이라고 생각한다.

    댐퍼를 설정한 상황 -> 차가 안정적이다.
    댐퍼가 없는 상황 -> 마냥 붕붕 뛰어다닌다.

     

    'Unity3D' 카테고리의 다른 글

    유니티 레이싱 게임 만들기[1] - How?  (0) 2023.01.29