본문 바로가기

카테고리 없음

Unity 3D Helicopter Sky Box

유니티 (Unity 3D) 헬기 비행 시뮬레이터 만들기 Part 8



Sky Box 와 그림자 설정하기


앞장에서는 지형 위에서 헬기가 비행할 때 메인 카메라가 계속 헬기를 주시하면서 화면 중앙에 헬기가 오도록 해봤습니다. 

화면을 벗어나서 헬기가 비행한다면 이용자가 헬기 조종이 불가능하기 때문입니다

여기서는 지형에 추가하여 구름과 하늘을 표현할 수 있는 Sky Box 를 설정해보겠습니다


스카이박스와 그림자는 스크립트가 아닌 유니티 에디터에서 설정해주는 것 만으로 작업이 완성됩니다


Sky Box 설정 순서

1. Project 뷰의 Assets 폴더 위에서 마우스 우측을 누르고 Import Package... > Skyboxes 을 선택하여 유니티가 지원하는 스카이박스를 임포트한다

2. Edit > Render Settings... 를 선택하고 Inspector 뷰에서 Skybox Material 항목에서 원하는 재질을 골라서 선택해주면 된다.


그림자 설정

Hierarchy 뷰에서 Drectional Light 를 선택하고 Inspector 뷰에서 Shadow Type 을 Soft Shadow 등으로 설정해주면 된다



헬기의 그림자와 멀리 하늘이 보이도록 설정한 화면



스카이박스와 그림자와는 상관 없지만 비행특성을 고려하여 수정한 코드

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 flyAttDelta;

	float maxSpeedForPitch;
	
	float AGL; //Above Ground Level
	
	int STATUS;
	const int STATUS_GROUND = 0;
	const int STATUS_TAKEOFF = 1;
	const int STATUS_HOVER = 2;
	const int STATUS_FLYING = 3;
	const int STATUS_BRAKE = 4;
	
	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;
		maxSpeedForPitch = 0.0f;

		TOW = 50f;
		AGL = 0.0f;
		
		powerDelta = 0.0f;
		pitchDelta = 0.0f;
		yawDelta = 0.0f;
		speedDelta = 0.0f;
		turnDelta = 0.0f;
		flyAttDelta = 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_TAKEOFF:
			status_takeoff();
			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_TAKEOFF;
		}
	}
	/*
    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) {
				//if(powerDelta -0.01f > 0f)
					powerDelta += -0.01f;
			}
		} else {
			powerDelta = 0.0f;
		}
		if (POWER + powerDelta < 100f && POWER + powerDelta > 0.0f) {
			POWER += powerDelta;
		} 

		// 동력이 이륙중량을 초과하면 상승시작, 
		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.01f;
			} else if (Input.GetKey ("down")) {
				pitchDelta += -0.01f;
			}else{
				if(pitchDelta>0.0f){
					pitchDelta += -0.05f;
				}
				if(pitchDelta<0.05f) {
					pitchDelta = 0.0f;
				}
			}
		}
		if (_PITCH + pitchDelta > -60f && _PITCH + pitchDelta < 60f) {
			_PITCH += pitchDelta;
		}
		applyPitch();
	}
	
	
	void applyPitch() { 
		
		var cosVal = Mathf.Cos (_PITCH*Mathf.PI/180f);
		if(STATUS==STATUS_FLYING) {
			verticalSpeed *= cosVal*4f;
		}else {
			verticalSpeed *= cosVal*3f;
		}
		var sinVal = Mathf.Sin (_PITCH*Mathf.PI/180f);

		maxSpeedForPitch = sinVal*12f;

		if (sinVal > 0) {
			if (moveSpeed < maxSpeedForPitch) // 무한정 가속되지 않도록 최대속도 설정
				speedDelta += 0.01f;
		} else if (sinVal < 0.0f) {
			if (speedDelta > 0.0f)
				speedDelta += -0.01f;
		}
		moveSpeed = sinVal*speedDelta;

		if(STATUS > STATUS_TAKEOFF){
			setAttitudeForPitch();
		}
	}


	void setAttitudeForPitch() {
		if(flyAttDelta < 0.2f) {
			flyAttDelta += 0.001f;
		}
		PITCH = _PITCH*flyAttDelta;

		//print ("_PITCH="+_PITCH+", PITCH="+PITCH);
	}
	
	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="+Mathf.Round(_PITCH)+" SPEED="+Mathf.Round(moveSpeed)+", ROLL="+Mathf.Round(ROLL)+
		       ", YAW="+Mathf.Round(YAW)+", POWER="+Mathf.Round(POWER)+", STATUS="+Mathf.Round(STATUS)+
		       ", AGL="+Mathf.Round(AGL)+", ALT="+Mathf.Round(transform.position.y));
	}
	
	void status_ground() {
		ROLL = 0.0f;
		PITCH = 0.0f;
	}

	void status_takeoff() {

		flyAttDelta = 0.0f;
		if(AGL>1.0f) {
			STATUS = STATUS_FLYING;
		}
	}

	
	void status_hover() {

	}
	
	void status_flying() {
		/*
		if (moveSpeed < 0.5f) {
			STATUS = STATUS_HOVER;
		}*/
	}
	
	void status_break(){
		
		moveSpeed *= 0.96f;
		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;
		}
	}
	
	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;
	}
}