2025유니티수업

0703 유니티(Car)

버스창문 2025. 7. 3. 15:35

* 유니티 맵에 안개끼게 하기

 

멀어지면 안보이게 안개가 끼는 효과

 

체크해제하면 씬에서 맵이 보임

 

 

 

________________________________________________________

자동차 게임

 

자동차랑 터레인 셋팅

 

자동차는 RigidBody에 회전 잠금 안해둠

안해야 코너링이 가능

자동차 말고도 어지간한 탑승물들은 전부 저걸 체크안하는 편

 

 

Wheel Colider

Wheel Coliders안에 있는 바퀴들에 부착

 

아래있는것들은 기본셋팅이 이륜구동(승용차용) 으로 되어있는데

4륜구동으로 만들고싶으면 알아서 유니티 홈페이지쪽에 표보고 설정하면됨

 

Suspension Spring : 충격을 흡수하기 위해서 튕기는 강도

Forward Friction : 정면 마찰

Sideways Friction : 측면 마찰(코너링)

 

 

휠을 다 잡아두면 그냥 난리가 나는데,

무게중심을 잡질 못해서 그렇다.

 

 

 

큐브 하나 깔고 크기 늘려놓고 Mesh 끄면

안흔들린다

    /*public enum ePos
    {
        Front_Left, Front_Right, Back_Left, Back_Right
    }
    [SerializeField] public Dictionary<ePos, WheelCollider> WheelColliders;*/
    
    
    [Header("콜라이더")]
    public WheelCollider FL_c;
    public WheelCollider FR_c;
    public WheelCollider BL_c;
    public WheelCollider BR_c;
    
    [Header("모델")]
    public Transform FL_m;
    public Transform FR_m;
    public Transform BL_m;
    public Transform BR_m;

    [Header("무게 중심")]
    public Vector3 centerOfMass = new Vector3(0,-0.5f, 0); // 무게중심
    Vehicle은 무게중심을 안잡으면 차가 기울어지게됨
    [SerializeField] private Rigidbody rb;
    
    위에 Dictionary로 못한 이유 => dictionary가 유니티 인스펙터 창에서 안잡힘

 

 

 

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

[CustomEditor(typeof(PlayerCar))]
public class PlayerCarEditor : Editor
{
    public override void OnInspectorGUI()
    {
        serializedObject.Update();

        // MonoBehaviour 의 다른 필드가 있으면 그대로 그려주기
        DrawDefaultInspector();

        EditorGUILayout.Space();
        EditorGUILayout.LabelField("Wheel Colliders", EditorStyles.boldLabel);

        var comp = (PlayerCar)target;

        // Dictionary 초기화
        if (comp.WheelColliders == null)
            comp.WheelColliders = new Dictionary<PlayerCar.ePos, WheelCollider>();

        // enum 값 순회하며 ObjectField 그리기
        foreach (PlayerCar.ePos pos in Enum.GetValues(typeof(PlayerCar.ePos)))
        {
            EditorGUI.BeginChangeCheck();

            // 기존에 할당된 콜라이더
            comp.WheelColliders.TryGetValue(pos, out WheelCollider existing);

            // 필드 렌더링
            WheelCollider assigned = (WheelCollider)EditorGUILayout.ObjectField(
                pos.ToString(), existing, typeof(WheelCollider), true);

            // 값이 바뀌었으면 Dictionary에 반영
            if (EditorGUI.EndChangeCheck())
            {
                comp.WheelColliders[pos] = assigned;
                EditorUtility.SetDirty(comp);
            }
        }

        EditorGUILayout.Space();
        EditorGUILayout.LabelField("Wheel Models", EditorStyles.boldLabel);

        if (comp.WheelModels == null)
            comp.WheelModels = new Dictionary<PlayerCar.ePos, Transform>();

        // enum 값 순회하며 ObjectField 그리기
        foreach (PlayerCar.ePos pos in Enum.GetValues(typeof(PlayerCar.ePos)))
        {
            EditorGUI.BeginChangeCheck();

            // 기존에 할당된 콜라이더
            comp.WheelModels.TryGetValue(pos, out Transform existing);

            // 필드 렌더링
            Transform assigned = (Transform)EditorGUILayout.ObjectField(
                pos.ToString(), existing, typeof(Transform), true);

            // 값이 바뀌었으면 Dictionary에 반영
            if (EditorGUI.EndChangeCheck())
            {
                comp.WheelModels[pos] = assigned;
                EditorUtility.SetDirty(comp);
            }
        }

        serializedObject.ApplyModifiedProperties();
    }
}

이런게 있어서 해봤지만, 값을 할당할수가 없어서 의미가 없어짐

 

 

 

 

 

 

 

 

____________________________________________________________

 

차 움직이기

 

 

    void FixedUpdate()
    {
        this.carCurrentSpeed = rb.velocity.sqrMagnitude;
        this.Steer = Input.GetAxis("Horizontal");
        this.forward = Mathf.Clamp(Input.GetAxis("Vertical"),0,1); // w키만 받기 위해 Clamp를 활용해서 0 이하로 안내려가게함
        this.back = -1 * Mathf.Clamp(Input.GetAxis("Vertical"),-1,0); // s키만 받기 위해 Clamp를 활용해서 0 이상으로 안올라가게함
                                                                      // -1을 곱해서 양수로 만들어서 계산을 용이하게 함

        if (Input.GetKey(KeyCode.W)) // 전진중 일때
        {
            StartCoroutine(ForwardCar());
        }
        else if (Input.GetKey(KeyCode.S)) // 후진중 일때
        {
            StartCoroutine(BackwardCar());
        }

        if (this.reverse)
        {
            this.motor = -1 * this.back;
            this.brake = forward;
        }
        else
        {
            this.motor = forward;
            this.brake = back;
        }

        //이륜구동 만드는거라 뒷바퀴만 회전시킬거임
        #region 뒷 바퀴 회전 X축(전방 후방)
        this.BL_c.motorTorque = this.maxToque * motor;
        this.BR_c.motorTorque = this.maxToque * motor;
        #endregion
        //앞바퀴는 회전 시킬거임
        #region 앞 바퀴 회전 Y축(좌우)
        this.FL_c.steerAngle = this.Steer * this.maxSteerAngle;
        this.FR_c.steerAngle = this.Steer * this.maxSteerAngle;
        #endregion

    }



    private IEnumerator ForwardCar()
    {
        yield return new WaitForSeconds(0.1f);
        this.carCurrentSpeed = 0.1f;
        /*if(this.back > 0)
        {
            this.reverse = true;
        }
        else
        {
            this.reverse = false;
        }*/
        this.reverse = (this.back > 0);
    }
    private IEnumerator BackwardCar()
    {
        yield return new WaitForSeconds(0.1f);
        this.carCurrentSpeed = 0.1f;
        /*if(this.back > 0)
        {
            this.reverse = true;
        }
        else
        {
            this.reverse = false;
        }*/
        this.reverse = (this.back > 0);
    }

 

 

 

#region 모델링도 회전 시키기
this.FL_m.localEulerAngles = new Vector3(FL_c.transform.localEulerAngles.x, this.Steer*maxSteerAngle, FL_c.transform.localEulerAngles.z);
this.FR_m.localEulerAngles = new Vector3(FR_c.transform.localEulerAngles.x, this.Steer*maxSteerAngle, FR_c.transform.localEulerAngles.z);
// 앞바퀴는 y축도 회전 시켜야함
#endregion

 

 

 

 

 

 

최종 코드 형태


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

//1.Wheel Colider <- 바닥 마찰로 이동
//2.Wheel Models <- 콜라이더랑 같이 회전
//

public class PlayerCar : MonoBehaviour
{

    [Header("콜라이더")]
    public WheelCollider FL_c;
    public WheelCollider FR_c;
    public WheelCollider BL_c;
    public WheelCollider BR_c;

    [Header("모델")]
    public Transform FL_m;
    public Transform FR_m;
    public Transform BL_m;
    public Transform BR_m;

    [Header("무게 중심")]
    public Vector3 centerOfMass = new Vector3(0,-0.5f, 0); // 무게중심
    
    [Header("Car Angle")]
    public float maxSteerAngle = 35f; // 최대 조향각
     public float maxMotorTorque = 2500f; // 최대 토크(가속력)
     public float maxBrakeTorque = 3500f; // 브레이크 토크(감속력)
     public float maxSpeed = 200f; // 브레이크 토크(감속력)
    public float emergencyBrakeForce = 80000;

     [Header("Car Current Speed")] public float carCurrentSpeed = 0f; 

    private Rigidbody rb;

    public float SteerInput = 0f; //ad키를 받는 회전
    public float motorInput; // 차량의 모터토크를 저장하기 위한 변수
    public bool isEmergencyBraking = false;

    void Start()
    {
        this.rb = GetComponent<Rigidbody>();
        if(this.rb != null)
        {
            rb.centerOfMass = centerOfMass;
        }
    }
    private void Update()
    {
        this.SteerInput = Input.GetAxis("Horizontal");



        this.motorInput = Input.GetAxis("Vertical");


        this.isEmergencyBraking = Input.GetKey(KeyCode.LeftShift);
        this.carCurrentSpeed = rb.velocity.sqrMagnitude;
    }
    void FixedUpdate()
    {



 


        HandleMotor();


        
    }

    void HandleMotor()
    {
        //비상 브레이크를 밟을 시, 차의 모든 구동력을 통제해야함
        if (isEmergencyBraking)
        {
            //뒷바퀴 속도 0으로

            this.BL_c.motorTorque =0;
            this.BR_c.motorTorque =0;
                                  

            //뒷바퀴에 브레이크걸면 멈춰짐

            this.BL_c.brakeTorque = this.emergencyBrakeForce;
            this.BR_c.brakeTorque = this.emergencyBrakeForce;

        }
        else
        {
            //비상 브레이크가 아니라면, 일반적인 주행
            //이륜구동 만드는거라 뒷바퀴만 회전시킬거임
            #region 뒷 바퀴 회전 X축(전방 후방)
            this.BL_c.motorTorque = this.maxMotorTorque * motorInput;
            this.BR_c.motorTorque = this.maxMotorTorque * motorInput;
            #endregion
            //앞바퀴는 좌우 회전 시킬거임
            #region 앞 바퀴 회전 Y축(좌우)
            this.FL_c.steerAngle = this.SteerInput * this.maxSteerAngle;
            this.FR_c.steerAngle = this.SteerInput * this.maxSteerAngle;
            #endregion
            //뒷바퀴에 걸었던 브레이크 해제

            this.BL_c.brakeTorque = 0;
            this.BR_c.brakeTorque = 0;
        }
    }

    private void LateUpdate()
    {
        #region 모델링도 회전 시키기

        #endregion
        this.UpdateWheelModel(this.FL_c,FL_m);
        this.UpdateWheelModel(this.FR_c,FR_m);
        this.UpdateWheelModel(this.BL_c,BL_m);
        this.UpdateWheelModel(this.BR_c,BR_m);
    }

    void UpdateWheelModel(WheelCollider col , Transform model)
    {
        Vector3 pos;
        Quaternion rot;

        col.GetWorldPose(out pos, out rot);
        model.position = pos;
        model.rotation = rot;
    }
}

 

 

 

 

______________________________________________________________

 

경로 만들기(선까지 보이도록)

pathPoint에 스크립트 부착

 

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

public class PathPoint : MonoBehaviour
{
    public Color lineColor = Color.black;
    public List<Transform> NodeList;
    private void OnDrawGizmos()
    {
        Gizmos.color = lineColor;
        this.NodeList = new List<Transform>();
        Transform[] PathTr = GetComponentsInChildren<Transform>();
        for(int i = 0; i<PathTr.Length; i++)
        {
            if (PathTr[i] != this.transform)
            {
                this.NodeList.Add(PathTr[i]);
            }
        }
        for(int i = 0; i< this.NodeList.Count; i++)
        {
            Vector3 curNode = this.NodeList[i].position; //현재 노드
            Vector3 prevNode = Vector3.zero; //이전 노드
            if(i > 0)
            {
                prevNode = NodeList[i-1].position;
            }
            else if(i == 0 && NodeList.Count > 1) // 분명 2개 이상 들어가있는데 첫번째 노드라면
            {
                prevNode = this.NodeList[NodeList.Count - 1].position; // 이전 노드는 마지막 노드
            }
            Gizmos.DrawLine(prevNode, curNode); // 이전거랑 다음거랑 선으로 그음
            Gizmos.DrawSphere(curNode, 0.8f); // 현재위치에 구체 그리기
        }

    }
}

그냥 배열 순서대로 노드 순서 잡아서 걔네끼리 순서대로 선으로 연결되도록 하는 내용

 

뭔가 실행을 안해도 다 할당되고 선이 보이는데

OnDrawGizmo는 Editor실행과 상관없이 그냥 계속 실행되는 내용이기 때문이다.

단, 그래서 

this.NodeList = new List<Transform>();

 

이 부분은 반드시 OnDrawGizmo안에 있어야한다.

안그러면 저 OnDrawGizmo가 계속 실행되서 안에 무한히 들어간다

 

이렇게 된다.

 

 

________________________________________________________________

 

경로따라 움직이는 차 만들기

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

public class AICar : MonoBehaviour
{
    public enum ePos//그냥 int로써 사용
    {
        Front_Left, Front_Right, Back_Left, Back_Right
    }
    [SerializeField] List<WheelCollider> wheelColliders = new List<WheelCollider>();
    [SerializeField] List<Transform> wheelModels = new List<Transform>();

    Rigidbody rb;

    [Header("Car Setting")]
    public Vector3 centerOfMass = new Vector3(0, -0.5f, 0);
    [SerializeField] float maxSteerAngle = 35f;
    [SerializeField] float maxMotorTorque = 2500f;
    [SerializeField] float maxSpeed;
    [SerializeField] float emergencyBrakeForce;
    [Header("Car Status")] public float currentSpeed = 0;

    [Header("Path Line")]
    [SerializeField] List<Transform> NodeList = new List<Transform>();
    [SerializeField] int curNodeIdx = 0;

    void Start()
    {
        this.rb = GetComponent<Rigidbody>();
        if (rb != null)
            this.rb.centerOfMass = this.centerOfMass;
        var pathArray = GameObject.Find("PathPoints").transform;
        if (pathArray != null)
        {
            pathArray.GetComponentsInChildren<Transform>(NodeList);
            //이렇게하면 NodeList에 담김 -> Add와 똑같음
            this.NodeList.RemoveAt(0);
        }
    }
    private void FixedUpdate() // 이동부분(바퀴 콜라이더들 이동시키는 부분)
    {
        ApplySteer();
        Drive();
        CheckWayPointDist();
    }
    void ApplySteer() // 앞바퀴가 path를 따라서 회전하는 메서드
    {
        Vector3 relativeVector = this.transform.InverseTransformPoint(this.NodeList[curNodeIdx].position);
        // 월드 좌표를 차량의 로컬 좌표로 변환해서 상대적인 위치를 계산한다
        // 내 좌표 기준 저게 어느 위치에 있는지를 준다

        float newSteer = (relativeVector.x / relativeVector.magnitude) * maxSteerAngle;
        //그 위치의 x좌표를 이용해서 조향각을 계산
        // 경로라인의 x좌표 / 경로라인의 길이 * 최대 조향각
        this.GetWheelCol(ePos.Front_Left).steerAngle = newSteer;
        this.GetWheelCol(ePos.Front_Right).steerAngle = newSteer;
    }
    void Drive() // path를 따라서 이동
    {
        this.currentSpeed = 2f * Mathf.PI * this.GetWheelCol(ePos.Front_Left).radius * this.GetWheelCol(ePos.Front_Left).rpm * 60f / 1000;
        //2 *Pi * r = 원의 둘레 => 한바퀴 돌면 이동하는 거리
        //rpm => round per minute => 1분에 몇바퀴 도는가
        // 위에거랑 곱하면 1분에 이동하는 거리
        // * 60 -> 1시간에 이동하는 거리 (m단위)
        // / 1000 -> m단위였던걸 km로(1km = 1000m)
        // => 즉 1시간동안 이동거리(km/h)

        if (this.currentSpeed < maxSpeed)
        {
            GetWheelCol(ePos.Back_Left).motorTorque = this.maxMotorTorque;
            GetWheelCol(ePos.Back_Right).motorTorque = this.maxMotorTorque;
        }
        else
        {
            GetWheelCol(ePos.Back_Left).motorTorque = 0;
            GetWheelCol(ePos.Back_Right).motorTorque = 0;
        }
    }
    void CheckWayPointDist() // 경로를 체크해서 인덱스를 다시 0으로
    {
        if(Vector3.Distance(this.transform.position, this.NodeList[curNodeIdx].position) <= 2.5f)
        {
            if(this.curNodeIdx++ == this.NodeList.Count-1)
                { this.curNodeIdx = 0; }
        }
    }

    private WheelCollider GetWheelCol(ePos pos)
    {
        return this.wheelColliders[(int)pos];
    }
    private Transform GetWheelModel(ePos pos)
    {
        return this.wheelModels[(int)pos];
    }

    private void LateUpdate() // 바퀴 모델 회전 표현하는 부분
    {
        for(int pos = 0;pos < 4; pos++)
        {
            Vector3 wheel_Pos;
            Quaternion wheel_Rot;
            this.GetWheelCol((ePos)pos).GetWorldPose(out wheel_Pos, out wheel_Rot);
            this.GetWheelModel((ePos)pos).position = wheel_Pos;
            this.GetWheelModel((ePos)pos).rotation = wheel_Rot;
        }
    }
}