유니티(Unity 3D) 헬기 비행 시뮬레이터 만들기 Part 06
간단한 지형을 생성하고 설정하기
앞장에서는 헬기가 비행하면서 방향전환할 때 동체에 경사를 적용하여 항공기가 실제로 선회하는 효과를 구현해봤습니다
지금부터 구현하려고 하는 기능들은 지형(Terrain)이 존재해야만 실감나고 그 기능의 효과를 확인할 수 있는 것들이 대부분이기 때문에 우선 지형을 생성하려고 합니다.
지금까지는 지형을 대신해서 커다랗고 납작한 Cube를 지면으로 대신 사용하면서 Cube 에 ground 라는 태그를 붙여서 헬기와 지면과의 접촉을 인지하는 트리거에서 사용했습니다.
우선 지형을 생성하고 생성된 지형에 terrain 라는 태그를 붙이고 Terrain 을 생성하면 기본으로 추가된 Terrain Collider의 Is Trigger 속성을 사용하여 지면과 헬기의 충돌(접촉)여부를 인지하도록 설정해 보겠습니다
지형을 생성하고 설정하는 구체적인 방법은 제 블로그의 Unity 3D 항목 아래의 Terrain 관련 메뉴를 참조하시기 바랍니다.
지금까지 작성했던 Helicopter.cs 파일에서는 트리거 함수의 태그명을 ground 에서 terrain 으로 변경해주면 바로 사용될 수 있습니다.
void OnTriggerEnter(Collider other) { if (other.gameObject.tag == "terrain") { STATUS = STATUS_GROUND; verticalSpeed = 0f; } } void OnTriggerExit(Collider other) { if(other.gameObject.tag=="terrain") { STATUS = STATUS_HOVER; } }
지형 위에서 헬기가 비행하는 모습 ( 키 조작이 서툴러서 헬기가 하드랜딩하고 말았습니다 ^^)
다음 장에서는 메인 카메라가 헬기의 이동 위치를 항상 주시하도록 설정하여 헬기가 화면 밖으로 사라지지 않도록 해보겠습니다
using UnityEngine; using System.Collections; public class Helicopter : MonoBehaviour { public float rotSpeed; public float moveSpeed; public float TOW; // Take Off Weight public float verticalSpeed; public float lateralSpeed; public float _PITCH; //키보드에서 입력용으로 사용 float PITCH; //기체에 적용할 때 사용 float ROLL; public float YAW; public float POWER; float powerDelta; float pitchDelta; float yawDelta; float speedDelta; float turnDelta; float AGL; //Above Ground Level int STATUS; const int STATUS_GROUND = 0; const int STATUS_HOVER = 1; const int STATUS_FLYING = 2; const int STATUS_BRAKE = 3; void Start () { _PITCH = 0.0f; // -45 ~ 45 deg PITCH = 0.0f; ROLL = 0.0f; // -45 ~ 45 deg YAW = 0.0f; // 0 ~ 359 deg POWER = 20f; // 0 ~ 100 rotSpeed = 0.0f; moveSpeed = 0.0f; lateralSpeed = 0.0f; verticalSpeed = 0f; TOW = 50f; AGL = 0.0f; powerDelta = 0.0f; pitchDelta = 0.0f; yawDelta = 0.0f; speedDelta = 0.0f; turnDelta = 0.0f; STATUS = STATUS_GROUND; } void Update () { inputPower (); inputPitch (); inputRoll (); inputYaw (); printControls (); // for debug checkAGL (); switch (STATUS) { case STATUS_GROUND: status_ground(); break; case STATUS_HOVER: status_hover(); break; case STATUS_FLYING: status_flying(); break; case STATUS_BRAKE: status_break(); break; default: break; } //헬기의 X(Pitch), Y(Yaw), Z(Roll) 죽을 따라 회전 transform.rotation = Quaternion.Euler (new Vector3 (PITCH, YAW, ROLL)); // 상승/하강 transform.Translate (Vector3.up * Time.deltaTime * verticalSpeed); // 전진/후진 transform.Translate (Vector3.forward * Time.deltaTime * moveSpeed); // 좌우이동 transform.Translate (Vector3.right * Time.deltaTime * lateralSpeed); } void OnTriggerEnter(Collider other) { if (other.gameObject.tag == "terrain") { STATUS = STATUS_GROUND; //print ("Trigger Enter"); } } void OnTriggerExit(Collider other) { //print ("Trigger Exit"); if(other.gameObject.tag=="terrain") { STATUS = STATUS_HOVER; } } /* void OnCollisionEnter(Collision other) { print ("충돌"); if(other.gameObject.tag=="terrain") { } }*/ void inputPower() { // Collective 조작시 if (Input.GetKey (KeyCode.W)) { //동력증가 powerDelta += 0.01f; } else if (Input.GetKey (KeyCode.S)) { //동력감소 if(POWER>0.0f) { powerDelta += -0.01f; } } else { powerDelta = 0.0f; } if (POWER + powerDelta < 100f) { POWER += powerDelta; } else { POWER = 100f; } // 동력이 이륙중량을 초과하면 상승시작, if (POWER > TOW) { verticalSpeed = (POWER / TOW - 1f); } else if (POWER < TOW) { if(STATUS==STATUS_GROUND) verticalSpeed = 0f; else if(transform.position.y>=0f) verticalSpeed = (POWER / TOW - 1f); } } void inputPitch() {// Cyclic 전/후 조작시 //UP, DOWN 키를 동시에 누르면 브레이크를 작동한다 if (Input.GetKey ("up") && Input.GetKey ("down")) { if(STATUS!=STATUS_GROUND) return; _PITCH = 0.0f; STATUS = STATUS_BRAKE; return; } else { // UP 키는 전방속도, DOWN키는 후방속도 조절 if (Input.GetKey ("up")) { pitchDelta += 0.0025f; } else if (Input.GetKey ("down")) { pitchDelta += -0.0025f; }else{ pitchDelta = 0.0f; } } if (_PITCH + pitchDelta > -45f && _PITCH + pitchDelta < 45f) { _PITCH += pitchDelta; } applyPitch(); } void applyPitch() { var cosVal = Mathf.Cos (_PITCH*Mathf.PI/180f); verticalSpeed *= cosVal; var sinVal = Mathf.Sin (_PITCH*Mathf.PI/180f); if (sinVal > 0) { if (speedDelta < 10.0f) // 무한정 가속되지 않도록 10이하만 사용 speedDelta += 0.01f; } else if (sinVal < 0.0f) { if (speedDelta > 0.0f) speedDelta += -0.01f; } moveSpeed = sinVal*speedDelta; } void inputRoll() { //LEFT, RIGHT 키는 동체의 좌우경사각(ROLL) 조절 if (STATUS == STATUS_GROUND) { return; } if (Input.GetKey ("left")) { turnDelta += 0.007f; if(ROLL< 45f) ROLL += turnDelta; } else if (Input.GetKey ("right")) { turnDelta += 0.007f; if(ROLL> -45f) ROLL += -turnDelta; } else { if(turnDelta>0.1f) turnDelta += -0.01f; else if(turnDelta<0.1f) turnDelta = 0.01f; } applyRoll (); } void applyRoll() { var rad = ROLL * Mathf.PI / 180f; var sinVal = Mathf.Sin (rad); lateralSpeed = -sinVal; //동체가 기울어진 방향으로 선회가 되려면 그 방향으로 기수를 조금씩 회전하도록 한다 //경사량에 따라서 선회량도 증가한다 var rate = Mathf.Abs (ROLL) / 45f; if (ROLL > 0) { YAW += -(rate*1.1f); } else if (ROLL < 0) { YAW += rate*1.1f; } } void inputYaw() { // Rudder // A키는 좌회전, D키는 우회전 if (Input.GetKey (KeyCode.A)) { yawDelta += -0.007f; } else if (Input.GetKey (KeyCode.D)) { yawDelta += 0.007f; } else { if(Mathf.Abs(yawDelta)>0) { if(yawDelta>0f) yawDelta += -0.03f; else if(yawDelta<0f) yawDelta += 0.03f; if(Mathf.Abs(yawDelta)<=0.03f) yawDelta = 0.0f; } } YAW += yawDelta; YAW %= 360f; } void printControls() { print ("PITCH="+PITCH+", ROLL="+ROLL+", YAW="+YAW+", POWER="+POWER+", STATUS="+STATUS+", AGL="+AGL+", ALT="+transform.position.y); } void status_ground() { ROLL = 0.0f; PITCH = 0.0f; } void status_hover() { if(moveSpeed>0.5f) { STATUS = STATUS_FLYING; } } void status_flying() { if (moveSpeed < 0.5f) { STATUS = STATUS_HOVER; } } void status_break(){ moveSpeed *= 0.95f; verticalSpeed = 0.0f; if (Mathf.Abs (moveSpeed) < 0.05f) { moveSpeed = 0f; STATUS = STATUS_GROUND; } } void checkAGL() { float y = Terrain.activeTerrain.SampleHeight (transform.position); AGL = (transform.position.y - y); if (AGL < 0.2237293f) { //0.2237293f : 지면에서 헬기의 중심부까지의 높이 initHeli (); STATUS = STATUS_GROUND; } //print ("AGL=" + AGL); } void initHeli(){ //PITCH = 0.0f; //ROLL = 0.0f; //YAW = 0.0f; //POWER = 0.0f; //moveSpeed = 0.0f; //verticalSpeed = 0.0f; //lateralSpeed = 0.0f; Vector3 pos = transform.position; pos.y = 0.2237293f + Terrain.activeTerrain.SampleHeight (transform.position);; transform.position = pos; } }