본문 바로가기

카테고리 없음

Unity 3D Helicopter Terrain

유니티(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;
	}
}