본문 바로가기

Unity3D Car/Collision

Car Collision effect in Unity

유니티에서 자동차가 다른 오브젝트에 충돌할 때 충돌음과 스파크가 출력되는 예


앞선 내용 참고 : 


자동차가 다른 물체에 충돌할 때 소음과 스파크가 생기는 현상을 표현하려면 사운드 파일과 스파크 효과가 준비되어야 한다

사운드 파일은 인터넷에서 무료로 배포하는 것을 사용하면 되고 스파크 효과는 유니티가 제공하는 패키지를 임포트하고 사용하면 된다.


[사용된 사운드 파일, 충돌음, 스키드음, 엔진음]

Sounds.zip




[스파크 패키지를 유니티 프로젝트 뷰로 임포트하는 절차]

  1. Assets > Import Package > Particles > Misc > Sparks 를 선택하여 프로젝트 뷰로 임포트한다
  2. 위에서 임포트한 Sparks는 이미 Prefab 으로 만들어져 있기 때문에 스크립트에서 GameObject 변수를 선언하고 드래그하여 할당할 수 있다
  3. 스파크의 형태를 편집하려면 Sparks 프리팹을 프로젝트 뷰에서 드래그하여 Scene 뷰에 올려놓고 Inspector 뷰에서 다양한 속성을 변경해 가면서 편집을 한 후에 프로젝트 뷰에 있는 Sparks 프리팹을 드래그하여 Inspector 뷰의 스크립트 콤포넌트에 있는 변수에 할당한다
  4. 스파크를 선택하고 Inspector 뷰에서 Autodestruct 항목에 체크하면 스파크가 발생한 후에 자동으로 인스턴스가 소멸되므로 스크립트에 직접 삭제할 필요가 없다
  5. 스크립트에서 Instantiate() 를 이용하여 스파크 원본 오브젝트를 이용하여 인스턴스를 생성하면 화면에 스파크가 출력된다



[충돌음을 프리팹으로 등록하고 사용하는 절차]

  1. 빈 오브젝트(EmptyObject)를 생성한다
  2. 빈 오브젝트에 Audio Source 콤포넌트를 추가한다
  3. Audio Source 콤포넌트의 Audio Clip 항목에 사운드 파일을 드래그하여 할당한다
  4. Audio Source 콤포넌트의 Play On Awake 항목을 선택한다
  5. 프로젝트 뷰에 Prefab을 생성한다
  6. 프리팹에 위에서 만든 사운드 오브젝트를 드래그하여 할당한다
  7. Hierarchy 뷰에서 사운드 오브젝트를 삭제한다
  8. 스크립트에 GameObject 변수를 생성하고 Inspector 뷰에서 해당 변수에 프리팹을 드래그하여 할당한다
  9. 스크립트에서 Instantiate()를 사용하여 원본 프리팹 오브젝트의 인스턴스를 생성하면 화면에 사운드가 출력된다



[자동차가 충돌시 충돌음과 스파크를 일으키는 코드]

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 = 30;

private float prevSteerAngle;

private bool bHandBraked = false;


private float lowStiffness = 0.2f;

private float highStiffness = 1f;


public Texture2D speedometerDial;

public Texture2D speedometerPointer;


public GameObject sparkPrefab;

public GameObject collisionSndPrefab;


// 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();

EngineSound ();

}

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;

}

}


void EngineSound() {

audio.pitch = currentSpeed / maxSpeed + 1;

}


//speedometerDial(320X170), speedometerPointer(320X40)

void OnGUI() {

GUI.DrawTexture (new Rect(Screen.width/2-160,Screen.height-170,320,170),speedometerDial);

float speedFactor = Mathf.Abs (currentSpeed / maxSpeed);

float rotationAngle = Mathf.Lerp (0, 180, speedFactor);

GUIUtility.RotateAroundPivot(rotationAngle, new Vector2(Screen.width/2,Screen.height-20));

GUI.DrawTexture (new Rect(Screen.width/2-160,Screen.height-40,320,40),speedometerPointer);

}


void OnCollisionEnter(Collision other) {

if (other.gameObject.name.IndexOf ("Terrain") != -1 ||

other.gameObject.name.IndexOf ("skidPrefab") != -1)

return;

if(other.transform != transform && other.contacts.Length!=0){

for(int i=0;i<other.contacts.Length;i++){

Instantiate(sparkPrefab, other.contacts[i].point,Quaternion.identity);

GameObject collisionSndInst = (GameObject)Instantiate(collisionSndPrefab, other.contacts[i].point,Quaternion.identity);

collisionSndInst.AddComponent<GameObjectDestroy>();

}

}

}

}