본문 바로가기

Unity3D Car/Friction & Slip

Wheel Friction & Slip in Unity

유니티에서 자동차 바퀴의 마찰력(Friction)과 미끄러짐(Slip) 정도를 제어하는 예


앞선 내용 참고 : http://micropilot.tistory.com/category/Unity3D%20Car/HandBrake


유니티 문서에서 볼 수 있는 마찰력과 미끄러짐 관련 그래프는 다음과 같으며 바퀴와 지면의 속도가 증가함에 따라서 마찰력과 미끄러짐 정도를 보여주고 있다

그래프에 따르면, 비퀴의 속도가 점차 증가함에 따라서 마찰력이 증가하여 최고점에 이르는데, 이 점을 Extremum 이라고 한다. 또 속도가 그 이상 증가하면 마찰력은 오히려 감소하고 미끄러짐 정도는 증가한다. 비퀴의 속도가 계속 증가하면 결국 마찰력이 감소하다가 더 이상 감소하지 않으며 미끄러짐 정도만 증가하는 구간이 나타나는데 이 점을 Asymptote 라고 한다.



수직축: 마찰력 (Friction Force)

가로축: 미끄러짐 정도








유니티에서는 다음과 같은 속성들을 에디터나 스크립트를 통해서 마찰력과 미끄러짐 정도를 조정할 수 있다


asymptoteSlip Asymptote point slip (default 2).

asymptoteValue Force at the asymptote slip (default 10000).

extremumSlip Extremum point slip (default 1).

extremumValue Force at the extremum slip (default 20000).

stiffness       Multiplier for the extremumValue and asymptoteValue values (default 1).


일반적으로 바퀴의 마찰력을 실행시간에 변경하기 위해서는 위의 속성 중에서 stiffness 를 사용한다. stiffness 는 디폴트 값이 1이며 1일 경우에는 설정된 Friction, Slip 값이 그대로 적용되고, 1보다 작으면 그 비율만큼 마찰력이 작아진다. 만약 stiffness가 0이면 마찰력이 전혀 없는 상태가 된다

stiffness 값은 마찰력(asymptoteValue, extremumValue)에 곱해져서 적용되는 마찰력의 비율을 결정한다

stiffness (0) : 마찰력이 전혀 없다

stiffness (1) : 마찰력이 최대로 적용된다


[유니티 에디터에서 Friction, Slip 속성 설정]



WheelCollider.forwardFriction, WheelCollider.sidewaysFriction 오브젝트를 통해 이들 속성의 값을 변경할 수 있으며

forwardFriction, sidewaysFrictionWheelFrictionCurve 클래스형 참조변수이다


다음은 자동차의 브레이크를 적용하면서 주행방향을 변경할 시에 나타나는 뒷바퀴의 슬립현상을 스크립트에서 재현하기 위해 테스트한 내용이다.


Car.cs

using UnityEngine;

using System.Collections;


public class Car : MonoBehaviour {

// 자동차 바퀴 방향조종을 위한 Transform 4개

public Transform tireTransformFL;

public Transform tireTransformFR;

public Transform tireTransformRL;

public Transform tireTransformRR;

public WheelCollider colliderFR;

public WheelCollider colliderFL;

public WheelCollider colliderRR;

public WheelCollider colliderRL;

// 바퀴 회전을 위한 Transform

public Transform wheelTransformFL;

public Transform wheelTransformFR;

public Transform wheelTransformRL;

public Transform wheelTransformRR;

// 속도에 따라서 방향전환율을 다르게 적용하기 위한 준비

public float highestSpeed = 500f;

public float lowSpeedSteerAngle = 0.1f;

public float highSpeedStreerAngle = 25f;

// 감속량

public float decSpeed = 7f;

// 속도제한을 위한 변수들

public float currentSpeed;

public float maxSpeed = 350f;    // 전진 최고속도

public float maxRevSpeed = 100f; // 후진 최고속도

// 백라이트 조정

public GameObject brakeLight;

public GameObject reverseLight;

public Material backBrakeIdle;

public Material backBrakeLight;

public Material backReverseIdle;

public Material backReverseLight;

public int maxTorque = 20;

private float prevSteerAngle;

private bool bHandBraked = false;


// Use this for initialization

void Start () {

rigidbody.centerOfMass = new Vector3(0,-0.9f,0.5f); // 무게중심이 높으면 차가 쉽게 전복된다

}

// Update is called once per frame

void FixedUpdate () {

HandBrake ();

SideSlip ();

Control ();

}

void Update() {

// 앞바퀴 2개를 이동방향으로 향하기

tireTransformFL.Rotate (Vector3.up, colliderFL.steerAngle-prevSteerAngle, Space.World);

tireTransformFR.Rotate (Vector3.up, colliderFR.steerAngle-prevSteerAngle, Space.World);

prevSteerAngle = colliderFR.steerAngle;

WheelSuspension();

}

void Control() {

// 최고속도 제한

// WheelCollider.rpm 전진:+, 후진:-

currentSpeed = 2 * 3.14f * colliderRL.radius * colliderRL.rpm * 60 / 1000;

float direction = Input.GetAxis("Vertical"); //전진:0.1~1, 후진:-0.1~-1

//print ("direction:" + direction);

float torque = maxTorque * direction;

if(!bHandBraked && direction>0 && currentSpeed<maxSpeed) {

//print ("전진");

colliderFR.motorTorque = torque;

colliderFL.motorTorque = torque;

}else if(!bHandBraked && direction<0 && Mathf.Abs(currentSpeed)<maxRevSpeed) {

//print ("후진");

colliderFR.motorTorque = torque;

colliderFL.motorTorque = torque;

}else{

colliderFR.motorTorque = 0;

colliderFL.motorTorque = 0;

}

BackLight ();

// 전후진 키를 누르지 않으면 제동이 걸리도록 한다

if (!Input.GetButton ("Vertical")) {

colliderRR.brakeTorque = decSpeed;

colliderRL.brakeTorque = decSpeed;

reverseLight.renderer.material = backReverseIdle;

brakeLight.renderer.material = backBrakeLight;

} else {

colliderRR.brakeTorque = 0;

colliderRL.brakeTorque = 0;

}

// 속도에 따라 방향전환율을 달리 적용하기 위한 계산

float speedFactor = rigidbody.velocity.magnitude / highestSpeed;

/** Mathf.Lerp(from, to, t) : Linear Interpolation(선형보간)

* from:시작값, to:끝값, t:중간값(0.0 ~ 1.0)

* t가 0이면 from을 리턴, t가 1이면 to 를 리턴함, 0.5라면 from, to 의 중간값이 리턴됨

*/

float steerAngle = Mathf.Lerp (lowSpeedSteerAngle, highSpeedStreerAngle, 1/speedFactor);

//print ("steerAngle:" + steerAngle);

steerAngle *= Input.GetAxis("Horizontal");

//좌우 방향전환

colliderFR.steerAngle = steerAngle;

colliderFL.steerAngle = steerAngle;

// 바퀴회전효과

wheelTransformFL.Rotate (-colliderFL.rpm/60*360 * Time.fixedDeltaTime, 0, 0);

wheelTransformFR.Rotate (-colliderFR.rpm/60*360 * Time.fixedDeltaTime, 0, 0);

wheelTransformRL.Rotate (-colliderRL.rpm/60*360 * Time.fixedDeltaTime, 0, 0);

wheelTransformRR.Rotate (-colliderRR.rpm/60*360 * Time.fixedDeltaTime, 0, 0);

}

// 브레이크 등, 후진 등 점멸제어

void BackLight() { 

float direction = Input.GetAxis("Vertical"); //전진:0.1~1, 후진:-0.1~-1, 아무키도 안눌리면:0


if (direction==1) { //전진

reverseLight.renderer.material = backReverseIdle;

brakeLight.renderer.material = backBrakeIdle;

} else if (direction==-1) { //후진


reverseLight.renderer.material = backReverseLight;

brakeLight.renderer.material = backBrakeIdle;

}

if (!Input.GetButton ("Vertical")  || bHandBraked) {

reverseLight.renderer.material = backReverseIdle;

brakeLight.renderer.material = backBrakeLight;

}

}

private RaycastHit hit;

private Vector3 wheelPos;

void WheelSuspension() {

if(Physics.Raycast(colliderFR.transform.position, -colliderFR.transform.up,

                  out hit,colliderFR.radius+colliderFR.suspensionDistance)){

wheelPos = hit.point +(colliderFR.radius * colliderFR.transform.up);

//print ("지면 충돌");

}else{

wheelPos = colliderFR.transform.position - (colliderFR.transform.up * colliderFR.suspensionDistance);

//print ("충돌 아님");

}

wheelPos.y +=3f; // 정상적인 모델이라면 이부분이 없어도 됨

tireTransformFR.position = wheelPos;

if(Physics.Raycast(colliderFL.transform.position, -colliderFL.transform.up,

                  out hit,colliderFL.radius+colliderFL.suspensionDistance)){

wheelPos = hit.point + (colliderFL.radius * colliderFL.transform.up);

}else{

wheelPos = colliderFL.transform.position - (colliderFL.transform.up * colliderFL.suspensionDistance);

}

wheelPos.y +=3f;

tireTransformFL.position = wheelPos;

if(Physics.Raycast(colliderRL.transform.position, -colliderRL.transform.up,

                  out hit,colliderRL.radius+colliderRL.suspensionDistance)){

wheelPos = hit.point + (colliderRL.radius * colliderRL.transform.up);

}else{

wheelPos = colliderRL.transform.position - (colliderRL.transform.up * colliderRL.suspensionDistance);

}

wheelPos.y +=3f;

tireTransformRL.position = wheelPos;

if(Physics.Raycast(colliderRR.transform.position, -colliderRR.transform.up,

                  out hit,colliderRR.radius+colliderRR.suspensionDistance)){

wheelPos = hit.point + (colliderRR.radius * colliderRR.transform.up);

}else{

wheelPos = colliderRR.transform.position - (colliderRR.transform.up * colliderRR.suspensionDistance);

}

wheelPos.y +=3f;

tireTransformRR.position = wheelPos;

}


void HandBrake(){

if(Input.GetButton("Jump")) {

bHandBraked = true;

//print ("핸드브레이크 작동");

//colliderFL.motorTorque = 0;

//colliderFR.motorTorque = 0;

//colliderFL.brakeTorque = 100;

//colliderFR.brakeTorque = 100;

colliderRL.brakeTorque = 100;

colliderRR.brakeTorque = 100;

}else{

colliderFL.brakeTorque = 0;

colliderFR.brakeTorque = 0;

colliderRL.brakeTorque = 0;

colliderRR.brakeTorque = 0;

bHandBraked = false;

//print ("핸드브레이크 해제");

}

}


void SideSlip() {

// 앞바퀴 브레이크 적용시 뒷바퀴가 미끄러지도록 마찰력을 줄임

if (!Input.GetButton ("Vertical")) { 

WheelFrictionCurve wfc = new WheelFrictionCurve();


wfc.asymptoteSlip = colliderRL.sidewaysFriction.asymptoteSlip;

wfc.asymptoteValue = colliderRL.sidewaysFriction.asymptoteValue;

wfc.extremumSlip = colliderRL.sidewaysFriction.extremumSlip;

wfc.extremumValue = colliderRL.sidewaysFriction.extremumValue;

wfc.stiffness = 0.01f;


colliderRL.sidewaysFriction = wfc;

colliderRR.sidewaysFriction = wfc;


//colliderRL.forwardFriction = wfc;

//colliderRR.forwardFriction = wfc;

} else {

WheelFrictionCurve wfc = new WheelFrictionCurve();

wfc.asymptoteSlip = colliderRL.sidewaysFriction.asymptoteSlip;

wfc.asymptoteValue = colliderRL.sidewaysFriction.asymptoteValue;

wfc.extremumSlip = colliderRL.sidewaysFriction.extremumSlip;

wfc.extremumValue = colliderRL.sidewaysFriction.extremumValue;

wfc.stiffness = 1f;

colliderRL.sidewaysFriction = wfc;

colliderRR.sidewaysFriction = wfc;


colliderRL.forwardFriction = wfc;

colliderRR.forwardFriction = wfc;

}

}

}