Unity 5로 '절대강좌 유니티 5' Space Shooter 예제 따라하기 계속. 7-9장.
게임은 용량이 큰 탓에 스냅샷만.

* 유니티 UI로 점수와 주인공 체력 화면에 표시.
* PlayerPrefabs를 이용, 게임자료 저장, 불러오기
* 싱글턴으로 간단히 참조하기, 오브젝트 풀로 부하 줄이기, 공용함수로 한 곳에서 처리하기
* 레이캐스트를 이용, 주인공 무기를 레이저 빔으로 교체

16/1/18 월

* Skybox에 이미지를 적용할 때 Wrap Mode를 Repeat가 아닌 Clamp로 바꿔줘야 연결 부분이 깔끔하게 이어짐을 알게 되다.

* Hierarchy에 마우스 우클릭 UI-Canvas를 눌러 캔버스를 생성하면 EventSystem도 따라 생성된다. Canvas 하위에 Panel, Raw Image, Button 등 만들어 넣고 설정 테스트.

* Hierarchy에 마우스 우클릭 UI-Canvas를 눌러 캔버스를 생성, EventSystem도 따라 생성. Canvas 하위에 Panel 추가, 'PanelScore'라 명명, Image 컴포넌트 Source Image에 04.Images-UI Textures(UI_Textures.unitypackage로 프로젝트에 추가한 것)-textures and Sprites 폴더의 Button-Tab.psd를 드래그. Color는 푸른색으로 설정. 패널 크기 조정하고 위치는 화면 좌상으로 배치. PanelScore 아래에 UI-Text 추가. Text-Text에 'SCORE <color=#ff0000>0000</color>' 입력하여 숫자 빨간색으로. Text 컬러는 연녹색.
Canvas의 Pixel Perfect의 체크를 해제하면 안티알리아싱 기능을 해제하여 외곽선이 뚜렷해 진다.
빈 게임 오브젝트 생성, 'GameUI'라 명명, 'csGameUI.cs' 스크립트 새로 만들어 적 죽일 때 마다 점수 오르게 하는 기능 추가. 스크립트에
//UI 컴포넌트에 접근하기위해 추가한 네임 스페이스
using UnityEngine.UI;
를 추가해 줘야 한다.

*PlayerPrefs메서드를 이용한 게임자료 저장, 불러오기:
//스코어 저장
PlayerPrefs.SetInt("TOT_SCORE", totScore);
//저장된 스코어 정보 불러와 변수에 저장
totScore = PlayerPrefs.GetInt ("TOT_SCORE", 0);

*생명 게이지 구현하기:
위에 만든 패널을 Ctrl-D로 복제한 뒤 조금 아래로 내리고 하위 Text는 제거.
\Resource\Images\hpbar.psd 불러오기. Texture Type: Sprite(2D and UI)로 변경, Apply.
패널 하위에 UI-Image 추가. Source Image에 hpbar.psd 드래그.
Image Type: Filled, Fill Method: Horizontal로 설정.
csPlayerCtrl.cs 스크립트 중 적에게 공격 받을 때(OnTriggerEnter(Collider col){}) 아래 내용 추가, 공격 받아 체력이 줄면 체력 게이지도 줄어들게 처리.
public Image imgHpbar; //플레이어의 Health Bar 이미지
//Image UI항목의 fillAmount 속성을 조절하여 생명 게이지값 조정
imgHpbar.fillAmount = (float)hp / (float)initHp;
7장 유니티UI 따라하기 완료.

* 빈 게임 오브젝트들로 스폰 포인트들 만들고 역시 빈 게임 오브젝트 생성, 'GameManager'라 명명, csGameMgr.cs 스크립트 만들어 게임오버가 아닌 동안 최대 몬스터 갯수만큼 랜덤한 스폰 포인트에 몬스터 생성시키기. 몬스터 추적거리: 10 에서 40으로 변경. csMonsterCtrl.cs에서 몬스터 죽을 경우 태그를 Untagged로 변경하여 죽은 몬스터를 제외하고 최대 몬스터 수 유지하게 처리.

csPlayerCtrl.cs 스크립트에 주인공 사망시
//게임 매니저의 isGameOver 변수값을 변경, 몬스터 출현을 중지시킴
gameMgr.isGameOver = true;
csGameMgr.cs 스크립트 내 게임오버 여부 변수값 변경시켜 몬스터 출현 중지.

* 싱글턴: 매번 변수 선언 뒤 할당하는 번거러운 초기화 대체 가능 기술
//싱글턴 패턴을 위한 인스턴스 변수 선언
public static csGameMgrSingle instance = null;
void Awake(){
instance = this; //csGameMgrSingle 클래스를 인스턴스에 대입
}
//csGameMgrSingle의 싱글턴 인스턴스로 접근, isGameOver 변수값을 변경, 몬스터 출현을 중지시킴
csGameMgrSingle.instance.isGameOver = true;

*오브젝트 풀: 게임오브젝트, 프리팹등을 처음 로드시 모두 생성하여 담아 두고 필요할 때 가져다 사용하는 방식.
//List 자료형을 사용하기 위해 추가해야하는 네임스페이스
using System.Collections.Generic;
//몬스터를 미리 생성, 저장할 리스트 자료형
public List<GameObject> monsterPool = new List<GameObject>();
void Start () {
//몬스터 생성, 오브젝트 풀에 저장
for (int i = 0; i < maxMonster; i++) {
//몬스터 프리팹 생성
GameObject monster = (GameObject)Instantiate(monsterPrefab);
//생성한 몬스터 이름 설정
monster.name = "Monster_"+i.ToString();
//생성한 몬스터 비활성화
monster.SetActive(false);
//생성한 몬스터 오브젝트 풀에 추가
monsterPool.Add(monster);
} }

*공용함수-사운드 처리: 사운드 처리 한 곳에서 처리하기.
csGameMgr.cs 스크립트 수정

//사운드의 볼륨 설정 변수
public float sfxVolumn = 1.0f; //사운드 뮤트 기능
public bool isSfxMute = false; //사운드 공용 함수

public void PlaySfx(Vector3 pos, AudioClip sfx){
	//음소거 옵션이 설정되면 바로 빠져나가기
	if (isSfxMute) return;
	//게임오브젝트 동적 생성
	GameObject soundObj = new GameObject ("Sfx");
	//사운드 발생 위치 지정
	soundObj.transform.position = pos;
	//생성한 게임오브젝트에 AudioSource 컴포넌트 추가
	AudioSource audioSource = soundObj.AddComponent ();
	//AudioSource 속성 설정
	audioSource.clip = sfx;
	audioSource.minDistance = 10.0f;
	audioSource.maxDistance = 30.0f;
	//sfxVolumn 변수로 게임의 전체적 볼륨 설정 기능
	audioSource.volume = sfxVolumn;
	//사운드 실행
	audioSource.Play ();
	//사운드 플레이 종료시 동적 생성한 게임오브젝트 삭제
	Destroy (soundObj, sfx.length);
}
csFireCtrl.cs 스크립트 수정
void Fire(){ csGameMgrPool.instance.PlaySfx (firePos.position, fireSfx); }
8장 게임 매니저 예제 따라하기 완료.

* 9장 레이캐스트 활용
csFireCtrl2.cs에 추가

void Update () {
//Ray를 시각적으로 표시하기 위해 사용
Debug.DrawRay(firePos.position, firePos.forward * 10.0f, Color.green);
if (Input.GetMouseButtonDown (0)){ //마우스 좌클릭시
	Fire();
	//Ray에 맞은 게임오브젝트 정보를 받아올 변수
	RaycastHit hit;
	//Raycast 함수로 Ray를 발사, 맍은 게임 오브젝트가 있을 때 true 반환
	if(Physics.Raycast(firePos.position, firePos.forward, out hit, 10.0f)){
		//Ray에 맞은 게임오브젝트의 Tag 값을 비교, 몬스터 여부 체크
		if(hit.collider.tag == "MONSTER"){
			//SendMessage를 이용, 전달받은 인자를 배열에 담기
			object[] _params = new object[2];
			_params[0] = hit.point; //Ray에 맞은 정확한 위치값(Vector3)
			_params[1] = 20; //몬스터에 입힐 데미지 값
			//몬스터에 데미지 입히는 함수 호출
			hit.collider.gameObject.SendMessage("OnDamage", _params, 
		              SendMessageOptions.DontRequireReceiver);
		}
	}
}}

csMonsterCtrl2.cs에 추가

//몬스터가 Ray에 맞았을 때 호출되는 함수
void OnDamage(object[] _params){
	Debug.Log(string.Format("Hit Ray {0}:{1}", _params [0],_params[1]));
	//혈흔 효과 함수 호출
	CreateBloodEffect ((Vector3)_params [0]);
	//맞은 총알의 Damage 추출해 몬스터 hp 차감
	hp -= (int)_params [1];
	if (hp <= 0) {
		MonsterDie();
	}
	//IsHit Trigger를 발생, Any State > gothit로 전이
	animator.SetTrigger ("IsHit");
}
pfBarrel에 'BARREL'이란 새 tag 추가, csBarrelCtrl.cs 스크립트에 추가
//Barrel이 Ray에 맞았을 때 호출되는 함수
void OnDamage(object[] _params){
	//발사위치
	Vector3 firePos = (Vector3)_params [0];
	//드럼통에 맞은 hit 위치
	Vector3 hitPos = (Vector3)_params [1];
	//입사벡터(Ray의 각도) = 맞은 좌표 - 발사 원점
	Vector3 incomeVector = hitPos - firePos;
	//입사벡터를 정규화 벡터로 변경
	incomeVector = incomeVector.normalized;
	//Ray의 hit 좌표에 입사벡터 각도로 힘 생성
	GetComponent ().AddForceAtPosition (incomeVector * 1000f, hitPos);
	//총알 맞은 횟수 증가 시키고 3회 이상이면 폭발 처리
	if (++hitCount >= 3) {
		ExpBarrel();
	}
}

*레이저빔 구현: Hierarchy-PlayerNew-FirePos 아래 빈 게임오브젝트 만들고 'LaserBeam'이라 명명, 메뉴 Component-Effects-Line Renderer 컴포넌트 추가, Use World Space 체크 해제(해제 안하면 피봇 위치에 레이저가 생성). Materials-Element0에 03.Images-Materials-BulletTrail 매터리얼 적용.
csLaserBeam.cs 스크립트 만들어 LaserBeam에 적용하고 레이저가 눈에 보이게 처리. 9장 레이캐스트 활용 따라하기 완료.