3. 3DPlatform Tutorial - 3인칭 게임 튜토리얼
Unity3D 2012. 6. 19. 11:37자료 다운 http://unity3d.com/support/resources/tutorials/3d-platform-game에서 3DPlatformTutorialStart.zip을 다운. 팁 : 목차 ThirdPersonPlayerAnimation 스크립트 Health Pickups => 건강 습득물 => 체력 회복제 The Force Field => 힘의 장 (힘이 작용하는 영역) The In-game HUD - 화면위에 투명하게 제공되는 정보 Stats (Statistics, 통계) 화면 이해하기. Play를 해보면 저 멀리 Lerpz가 서있는 것을 볼 수 있다. 하지만 아직 움직일 수 없고, 카메라도 우리의 캐릭터를 바라볼 필요가가 있다. 이를 위한 몇가지 카메라 스크립트를 가지고 있지만 여기서는 SpringFollowCamera를 사용할 것이다. UnassignedReferenceException 에러는 스크립트 변수가 설정되지 않았을 때 나오는 메시지이고 이 메시지는 매우 자주 보게 될 것이다. 스크립트에서 우리의 Player를 찾을 수 있도록 하기위해 Player 게임오브젝트에 태그를 추가해야 한다. 태그는 나중에 사용될 것이다. 그러므로 캐릭터 제어와 스크립트로 돌아가자. 이제 실행을 해보면 Lerpz는 컨트롤 키에 의해 지형위를 움질일 수 있을 것이다. 대부분의 게임에서 플레이어의 아바타는 순간적인 회전 및 멈춤, 불가능한 거리로의 도약 등 물리적으로 불가능한 능력을 가지고 있다. 캐릭터 제어기(Character Controller)는 물리 엔진으로부터 플레이어의 아바타를 분리하고 기본적인 움직임을 제공한다. 캐릭터 제어기는 플레이어 캐릭터 타입을 위한 움직임을 단순화한다. 그것은 움직이고 오르고 미끄러지는 기본 움직임 시스템에 밀접한 Capsule Collider로 구성되어있다. 당신은 최대 step 및 slope 사이즈를 변경할 수 있다. 캐릭터 제어기는 일반적으로 스크립트와 함께 사용된다. 우리의 프로젝트에서는 3인칭 제어기(Third Person Controller) 스크립트가 이것을 수행한다. 그것은 조이스틱, 키보드, 마우스 또는 다른 입력 장치를 읽고 플레이어의 아바타를 제어한다. 유니티의 인풋 매니저 (Edit > Project Settings > Input Manager) 는 입력장치가 플레이어를 어떻게 제어할지를 정의한다. 지금은 캐릭터 제어기가 애니메이션을 다루지 않기 때문에 Lerpz는 단순히 미끄러져 다닌다. 이제 실행해보면 Lerpz의 정확한 애니메이션을 보게 될 것이다. Lerpz는 15개의 애니메이션을 가지고 있지만 이 튜토리얼에서는 11개만이 사용된다. Hierarchy에서 Player를 선택한 후 Inspector 창에서 Animation 컴포넌트의 Animations 속성을 펼치면 15개의 애니메이션 리스트를 볼 수 있다. 튜토리얼에서 사용하는 11개의 애니메이션은 다음과 같다. 애니메이션의 대부분은 스크립트에 의해 다루어진다. 몇몇 애니메이션은 다른 것들이 단순히 차례대로 그려지는 동안 다른 것들의 위에 그려진다. 스크립트는 대부분이 메시지 응답 함수의 모음이다. 적절한 메시지는 입력장치를 읽고 캐릭터 상태를 업데이트 하는 ThirdPersonController 스크립트에 의해 발생된다. Lerpz의 공격은 분리된 스크립트, ThirdPersonCharacterAttack 에서 다룬다. 이는 나중에 추가할 것이다. ThirdPersonCharacterAttack 은 테스트하는데 유용한 것들을 포함하고 있다. Lerpz’s 의 펀치 동작에 영향을 받는 영역을 표현하기 위한 구를 그리는 기즈모. 기즈모들은 함수를 제어하는 2개의 기즈모 그리기 메시지중 하나안에서 그려진다. 예를 들면, 펀치 위치에 그려지는 노란색 와이어프레임과 그것에 영향을 받는 영역을 보여주는 기즈모는 OnDrawGizmosSelected() 함수에 반응한다. 이 함수는 반드시 static이어야 하고 유니티 에디터에 의해 호출될 것이다. 다른 하나는 매 업데이트 주기마다 유니티 에디터에 의해 호출되는 OnDrawGizmos()함수로 부모 게임오브젝트가 선택이 되었는지 아닌지는 관계없다. 우리의 Lerpz는 하강 속도를 늦추기 위하여 제트팩을 사용할 것이다. 움직임은 이미 구현되었지만 애니메이션은 적용되지 않았다. 이것을 만들기 위해 2개의 파티클 시스템(Particle System)과 1개의 점광원(Point Light)을 추가할 필요가 있다. 팁 이상적으로 각각의 제트마다 점광원이 있어야 하지만, 제트기 배출은 서로가 충분히 가깝기때문에 하나만으로 처리할 수 있다. 빛은 계산하기에는 비싸기 때문에 이것은 간편한 최적화이다. Jet 오브젝트에 Component > Particles 에서 다음을 추가하라. 위와 같이 설정하면 불이 분출되는 것처럼 파티클의 흐름이 변할 것이다. 팁 여기까지 완료했을 때, 파티클 시스템은 Player의 몸통(torso)에 자식으로 첨부될 것이다. 이것은 Player의 움직임에 파티클 시스템이 따라다니도록 할 것이다. Min Size와 Max Size는 파티클의 크기를 정의한다. Min Energy와 Max Energy는 최소/최대 수명을 정의한다. 우리의 파티클은 0.2초라는 짧은 시간동안만 살아있을 것이다. Min Emission과 Max Emission은 매 초마다 생성되는 파티클의 최소/최대 개수이다. 우리는 이 설정을 이용하여 파티클의 수를 50개로 설정하였다. 알림 우리는 여기서 “Simulate in Worldspace”를 해제하였다. 이것은 많고 느린 불보다 뜨겁고 빠른 가스의 분출을 가진 인상을 준다. (파티클 시스템이 움직였을 때 이미 분사된 파티클도 같이 움직이게 한다.) 다음은 Particle Renderer이다. 이 컴포넌트는 각각의 파티클들을 렌더링하는데 사용할 재질을 정의한다. 우리는 화염처럼 보이는 효과를 원하므로 “fire add” 재질을 사용할 것이다. 이것은 프로젝트 뷰의 Particles > Sources > Materials > fire add 에서 찾을 수 있다. 알림 - Cast Shadows와 Receive Shadows는 사용자 정의 shader를 사용하지 않는다면 효과가 없다. 우리의 화염분출은 그럴듯해 보이지만, 파티클 시스템은 단지 작은 이미지들을 뱉어낼 뿐이다. 화염분출을 완성하기 위해서 우리는 분리된 점광원(Point Light) 오브젝트를 만들 것이다. 우리는 이것을 파티클 시스템과 동시에 온오프할 것이다. (우리는 제트마다 하나의 빛을 사용하지 않고 오직 하나의 빛만을 사용할 것이다.) 왜 그림자를 사용하지 않는가? 그림자는 대부분의 하드웨어에서 비싼 연산을 필요로 하므로 가능하면 사용하지 않는다. 다음은 Player 오브젝트가 우리의 Jet와 Jet Ligtt 오브젝트를 포함하는 것이다. 이것을 위해, 먼저 우리의 Jet를 프로젝트 패널에 프리팹으로 추가할 것이다. Jet 오브젝트는 Jet 프리팹과 연결되면서 이름이 푸른색으로 변할것이다. 우리는 제트팩을 위해 Jet 프리팹의 인스턴스 2개를 사용할 것이다. 프리팹을 사용한다는 것은 원본 프리팹을 수정하는 것만으로 2개의 제트를 한꺼번에 수정할 수 있다는 것을 의미한다. 이제 Lerpz가 움직이면 제트팩이 분사하는 불길도 같이 움직일 것이다. 이제 거의 다 됐다. 마지막은 Jet 프리팹과 Jet Light를 점프중에만 활성화되게 하는 것이다. 이것은 스크립트를 통해 수행된다. 이제 우리가 기대하는대로 작동하는 제트팩을 볼 수 있을 것이다. 이 스크립트는 Lerpz의 움직임과 동기화 되도록 2개의 파티클 시스템과 빛을 제어한다. Lerpz는 언제나 쉽게 확인이 되어야 한다. 이는 대부분 아티스트와 게임 디자이너에게 달려있지만 몇가지 요소는 유니티에서 직접 제어해야 한다. 이러한 것들 중 가장 중요한 하나는 그림자와 빛이다. 성능을 위해서, 빛의 효과는 종종 미리 렌더링되어야 한다. 이런 기술(“baking”이라고 불리운다)은 정적인 오브젝트에만 잘 작동한다. 거리의 불빛 아래서 움직이는 캐릭터는 실시간으로 빛에 반응해야 한다. 해결책은 빛이 있어야 할 곳에 위치시키는 것이다. 만약 “baked” 텍스처를 사용한다면, baked 빛이 있는 곳에 빛을 추가해야 한다는 것을 기억하라. 하지만 빛이 움직이는 물체에만 영향을 주도록 만들어라. 이 튜토리얼에서 빛은 이미 장면 곳곳에 위치하고 있다. 그림자는 그래픽 엔진에 의해 실시간으로 계산되고 렌더링되는 빛을 사용하여 제공될 수 있다. 하지만, 이러한 그림자는 매우 비싸다. 게다가 모든 그래픽이 빠르고 효율적으로 계산할 수 있는 것도 아니다. 옛날 그래픽 카드는 전혀 지원하지 못할수도 있다. 이러한 이유로, 우리는 Lerpz를 위해 Blob 그림자를 사용할 것이다. Blob 그림자는 일종의 편법이다. 이것은 빛이 부딪히는 영역을 검사하는 대신에 캐릭터 아래에 단순히 어두운 이미지(여기서는 단지 원형의 검은색 얼룩)를 투영한다. 이것은 거의 모든 하드웨어에서 잘 작동한다. 유니티는 Blob 그림자 프리팹을 Standard Assets 모음에 포함하고 있다. 그러므로 우리가 스스로 만들기 보다는 이것을 사용할 것이다. 이 에셋은 이미 프로젝트의 Blob-Shadow 폴더에 포함되어 있다. 이 시점에서 알아야 할 점은 blob이 Lerpz에게도 그림자를 투영하고 있다는 것이다. 우리는 이러한 일이 발생하는 것을 원하지 않는다. 이것을 피하는 2가지 옵션이 있다. 하나는 blob shadow projector를 선택한 후 나타나는 Projector의 Near Clip Plane(가까운 평면)속성의 값을 더 멀리 설정하는 것이고 다른 하나는 단순히 특별한 레이어에 있는 오브젝트에는 투영하지 않도록 알리는 것이다. 우리는 후자를 사용할 것이다. 다음은 Blob Shadow Projector에게 noShadow 레이어에 있는 오브젝트에는 투영하지 않도록 알려야 한다. 이제 실행을 해보면 거의 기대한 대로 그림자가 작동할 것이다. 연료와 같은 수집가능한 아이템 주변에서 점프하는 것만 제외한다면 말이다. 이것을 시도하면, 당신은 그림자가 아이템위에 그려지는 것을 보게 될 것이다. 알림 - 레이어를 변경할 때 생각없이 모든 자식들에게도 적용하는 것은 위험하다. 링크(게임오브젝트에 연결하기)하는 방법에는 여러가지가 있다. 만약 당신이 원한다면, 우리의 모든 변화를 포함하고 있는 Player 오브젝트를 다른 프로젝트에서 재사용하기 위해서 프리팹으로 만들 수 있다. 대부분의 게임 캐릭터는 위험한 삶을 살아가는데 우리의 Lerpz 역시 예외가 아니다. 그가 죽을 수도 있고 죽은 후 “respawn point”라고 불리우는 안전한 장소에서 다시 부활하게 만들어야할 필요가 있다. 또 다른 문제는 Lerpz가 지형 외부로 떨어지는(fallout) 것이다. 이 문제는 다른 거주민들도 역시 마찬가지이며, 이것들은 적절하게 다루어져야 한다. 최적의 해법은 박스 충돌체를 사용하는 것이다. 우리는 플레이어가 제트팩을 이용할 수 있도록 하기 위하여 이것을 매우 길고 광대하게 만들 것이다. 일단은, 트리거로 사용할 박스 충돌체를 만들도록 하자. 이 스크립트는 ThirdPersonStatus 스크립트에서 모든 작업을 대신하고 있기때문에 매우 단순하다. (이것은 Lerpz에 부착되어야 하지만 아직은 아니다.) 충돌체의 트리거를 다루는 코드는 OnTriggerEnter() 이다. 이 함수는 박스 충돌체가 Lerpz 또는 적들처럼 충돌체 컴포넌트를 가지고 있는 다른 게임오브젝트와 부딪혔을 경우 유니티에 의해 호출된다. 3가지 테스트 : 하나는 Player, 또 다른 하나는 단순한 RigidBody, 세번째는 Character Controller 컴포넌트를 가지고 있는 오브젝이다. 두번째는 상자나 크레이트 같은 것들을 테스트하고(우리는 이러한 아이템들을 가지고 있지 않지만 당신이 원하면 추가할 수 있다.) 세번째는 물리가 부착되지 않은 적들을 테스트하는데 사용된다. 만약 Player가 박스 충돌체(FalloutCatcher)와 부딪히면 코드는 단순히 Lerpz의 ThirdPersonStatus 스크립트에 있는 FalloutDeath() 함수를 호출한다. 만약 충돌체를 가진 다른 오브젝트가 부딪히면 우리는 단순히 그것을 장면에서 제거한다. 그렇지 않으면 영원히 떨어질 것이다. 추가적으로 우리는, 이 강좌에서 Lerpz는 3개의 부활 지점을 가지고 있다. Lerpz가 이 지점들 중에 하나를 만지면, 그것이 활성화되고 그가 죽었을 때 여기서 이 지점에서 다시 나타날 것이다. 부활 지점은 RespawnPrefab 프리팹 오브젝트의 인스턴스들이다. RespawnPrefab의 기본 구조는 아래와 같다. 프리팹이 포함하고 있는 Respawn 스크립트는 부활 지점의 상태를 제어한다.하지만, 어느 지점이 부활 지점인지를 게임이 알게하기 위해서, 우리는 Hierarchy 창에서 master controller 스크립트 아래에 부활 지점들을 가지런히 정렬할 필요가 있다. 이제 이것을 수행하자. 장면이 로드될 때, 유니티는 Respawn 스크립트의 각각의 인스턴스에서 Start() 함수를 호출한다. 주요 구조는 정적(static) 변수를 중심으로 구성된다. static var currentRespawn : Respawn; 이것은 currentRespawn 이라는 전역변수를 정의한다. static 키워드는 스크립트의 모든 인스턴스가 공유한다는 것을 의미한다. 이것은 부활 지점이 하나가 되도록 유지시켜준다. 하지만 장면이 시작할 때, 활성화되는 지점은 없다. 그러므로 하나를 기본값으로 지정해야 할 필요가 있다. 유니티는 static 변수를 Inspector 창에 보여주지 않으므로 각각의 인스턴스에 대하여 설정될 필요가 있는 Initial Respawn 속성을 정의한다. 알림 : 원본 프리팹에 대해서는 위의 과정을 수행할 수 없다. 부활 지점이 Player의 충돌체에 의해 활성화되면, Respawn 스크립트는 가장 먼저 이전 부활 지점을 비활성화 한다. 그리고 나서 currentRespawn 에 자기 자신을 설정한다. - 이 과정은 OnTriggerEnter() 함수에서 수행된다. SetActive() 함수는 적정한 파티클 시스템과 사운드 효과를 발생시킨다. Player의 부활은 Player의 게임상태 대부분을 관리하는 ThirdPersonStatus 스크립트에 의해 다루어진다. 알림 유니티는 사운드 효과를 추가하기가 매우 쉽다. 당신이 이러한 에셋을 추가하려고 계획할 때마다 어떻게 사용할지를 신중하게 고려해야 한다. 예를 들면 당신은 2개의 부활 지점이 소리가 들릴만큼 가까이 놓기를 원하지 않을 것이다. 즉, 부활 지점이 비활성화 되는 소리를 들을 수는 없기 때문에 우리는 “respawn deactivated” 사운드를 포함하지 않는다. 만약 프로젝트를 멀티플레이어 게임으로 변환한다면 당신은 이러한 사운드를 추가하기를 원할 것이고 그에 해당하는 스크립트가 필요할 것이다. 이번 섹션에서는 세트(영화 용어에서)를 건축하고 소품을 놓고 우리의 영웅이 그것들과 상호작용하도록 만드는 스크립트를 작성할 것이다. 우리는 첫번째 단계는 스테이지를 준비하는 것이다. 이 튜토리얼 파일은 수집가능한 다수의 아이템과 함께 거주가능한 기본 레벨 메쉬를 이미 가지고 있다. 우리는 약간의 소품과 요소를 놓을 것이다. 그러나 대부분은 당신을 위해 이미 놓여져 있다. Lighting (광원) 레벨(스테이지, 장면)은 이미 수만의 점광원 뿐만 아니라 환경 광원까지 제공하고 있다. 이 프로젝트의 경우, 빛들은 레벨을 모델링한 아티스트에의하여 놓여졌다. Building Your Own Levels (자신만의 레벨 건축하기) 당신이 실험하기 좋아하거나 심지어 추가적인 레벨을 만들고 싶다면, 장면에 사용된 개별적인 요소들을 프로젝트 패널의 Build Your Own! 폴더에서 찾을 수 있을 것이다. 장면에는 많은 소품들이 있는데 이들을 직접 위치시킬 경우 매우 지루한 튜토리얼이 될 수 있다. 이전 튜토리얼을 읽었다면 당신은 이미 어떻게 하는지를 알고 있다. 그러므로 우리는 체력 회복제, 점프 패드, 부활 지점들로 제한할 것이다. Health Pickups는 이미 프로젝트 패널에 프리팹으로 정의되어 있다. Props 폴더에서 HealthLifePickUpPrefab 을 찾을 수 있을 것이다. 현재, 우리의 영웅의 우주선을 붙잡아 두고 있는 포스 필드(힘의 장)는 애니메이션 되지 않는다. 그것은 단지 정적인 메쉬 텍스처이다. 그리나쁘지 않은 비주얼 효과를 위한 몇가지 방법이 있지만 어떤 것을 선택할 것인가? 가끔은 단순한게 최고이다. 우리는 잔물결이 일어나는 포스 필드 효과를 주기 위해서 텍스처의 UV 좌표를 애니메이션할 것이다. 애니메이션은 짧은 스크립트로 수행이 될 것이며, 이는 프로젝트 패널의 Scripts > Misc 폴더의 FenceTextureOffset 에서 볼수 있을 것이다. var scrollSpeed = 0.25; function FixedUpdate() { var offset = Time.time * scrollSpeed; renderer.material.mainTextureOffset = Vector2(offset,offset); } 첫 번째 라인은 스크롤 속도를 유니티 인터페이스에서 직접 입력할 수 있도록 속성을 노출시킨다. FixedUpdate() 함수는 초당 일련의 횟수 만큼 호출된다. (고정프레임이라는 얘기) mainTextureOffset 속성은 유니티에게 텍스처의 UV 공간안에서 그려질 이미지의 offset을 전달한다. Pretty Properties (깔끔한 속성) 유니티의 Inspector 창에서 속성이나 변수를 보여줄 때, 그 이름은 좀 더 멋지게 보이도록 적용된다. 그래서 scrollSpeed는 Inspector 창에 Scroll Speed로 나타날 것이다. 현재 Lerpz는 어떤 아이템도 집을 수 없다. 우리는 각각의 수집가능한 아이템에 2가지 요소를 추가할 필요가 있다. Hierarchy 패널에서 수집가능한 아이템은 모두 프리팹 인스턴스이고 푸른색으로 표시된다. 원본 프리팹을 수정하면 자동적으로 게임내의 모든 아이템에 적용된다. 우리의 영웅이 수집할 수 있는 2개의 프리팹은 FuelCellPrefab 과 HealthPickUpPrefab 이다. 이것은 프로젝트 패널의 Props 폴더에서 찾을 수 있다. 알림 프리팹에 스핀을 주는 매우 단순한 ObjectRotater 스크립트가 이미 첨부되어 있다. 후반부에는 더욱 복잡한 애니메이션 스크립트를 보게 될 것이다. 충돌체(Collider)는 두가지 쓰임새가 있다 : 다른 오브젝트와의 충돌에 사용하거나, 트리거로 사용한다. Triggers (방아쇠, 자동적으로 동작을 수행하도록 하는 것) 트리거는 보이지 않는 컴포넌트이며 이벤트이다. 유니티에서 트리거는 단순히 Is Trigger 속성이 설정된 충돌체(Collider)이다. 이것은 무언가가 트리거와 부딪혔을 때, 물리적인 개체가 아닌 가상 스위치처럼 작동한다는 것을 의미한다. 트리거는 OnTriggerEnter(), OntriggerStay(), OnTriggerExit() 라는 세가지 이벤트 메시지를 전송한다. 트리거 이벤트 메시지는 트리거 오브젝트에 부착된 어떤 스크립트던지 보내진다. 그러므로 우리는 적절한 스크립트가 필요하다. 플레이어의 최대 체력치(health)는 6이다. 만약 체력이 가득찬 상태에서 Health Pickup을 먹으면 어떻게 되는가? 이 튜토리얼에서는 추가적인 생명력(life)를 주도록 하였다. 이 로직은 플레이어의 상태를 체크하는 ThirdPersonStatus 스크립트에서 찾을 수 있다. (실제 보면 그냥 최대치로 제한하는 내용밖에 없네요;;) FuelCellPrefab 은 거의 같은 방법으로 설정한다. 단지 2가지만 다를 뿐이다. 점프 패드는 밝은 노란색과 검은색의 줄무늬로 표시되어 있다. 이것은 Lerpz를 공중으로 밀어올리기 위해 제안되었다. 우리는 이를 위해 스크립트가 부착된 충돌체를 사용할 것이다. 먼저, 빈 게임오브젝트를 생성하고 Jump Pad Triggers로 이름을 변경하라. 우리는 이를 점프 패드 트리거 오브젝트들의 폴더로 사용할 것이다. 이제 우리의 프리팹을 빌드할 것이다. 오브젝트가 생성되었으니, 이제 프리팹으로 변환할 필요가 있다. 하얀뱀 우리가 만든 프리팹과 동일한 기능을 하는 JumpPad 프리팹이 이미 만들어져 있습니다. 뭐야 이거.. 알림 스크립트는 프리팹과 유사하게 작동한다. 프로젝트 패널에서 스크립트 파일을 수정하면 장면에 있는 모든 복사본에 적용이 된다. 좋은 조직은 당신의 작업흐름이 부드러워 지고 작은 다툼으로 부터 자유로워지게 하는데 중요하다. 당신은 심지어 작은 규모의 프로젝트를 위해서 얼마나 많은 에셋이 필요한지 놀랄 것이다. 게임들은 일반적으로 메뉴, 옵션 화면 등과 같은 그래픽 유저 인터페이스(GUI)를 가지고 있다. 게다가, 게임들은 종종 게임 그 자신의 최상위에 GUI를 덧 씌운다. 이것은 구석에 점수를 보여주는 것처럼 단순할 수 있거나 복잡한 아이콘을 정성스럽게 디자인하거나 인벤토리를 보여줄 수 있다. 이전 시스템은 전통적인 이벤트 드리븐 GUI 모델을 사용하였다. 그러나 유니티 2에서는 즉시 모드(Immediate Mode) GUI로 알려진 새로운 GUI 시스템을 소개한다. 만약 당신이 전통적인 GUI 시스템을 사용했다면, 즉시 모드 GUI 컨셉은 충격으로 다가올 것이다. 여기 예제가 있다. function OnGUI() { If (GUI.Button (Rect(50, 50, 100, 20), "Start Game") ) Application.LoadLevel("FirstLevel"); // load the level. } OnGUI() 는 게임주기마다 최소한 2번은 호출된다. 처음 호출에서는 GUI를 만들고 그것을 그린다. 이 경우, 우리는 지정한 좌표에 단순한 버튼을 그리고 그 안에 “Start Game”을 표시한다. 두번째는 유저 입력이 진행될 때 호출된다. 만약 유저가 버튼을 클릭하면, If(...)문의 조건이 true를 리턴하고 Application.LoadLevel() 이 호출될 것이다. 다른 GUI 요소들 - 레이블, 그룹, 체크박스 등등 - 모두 비슷하게 작동한다. 여기서 분명한 이점은 당신이 GUI를 위한 이벤트 핸들러가 필요하지 않다는 것이다. 모든 것이 OnGUI() 함수에 포함되어 있다. 유니티 2는 두 세트의 즉시 모드 GUI 함수들을 제공한다 : 위의 예제와 같은 기본 GUI 클래스와 이와 유사하지만 시간을 절약하려는 당신을 위해 GUI의 레이아아웃을 다루는 GUILayout 클래스이다. 유니티의 새로운 GUI 시스템에 대해서 더 많은 정보는 아래 링크에서 찾을 수 있다. 우리의 게임은 플레이어의 체력, 생명력, 수집해야할 연료의 숫자를 표시하는 GUI가 필요하다. 그래픽적인 요소는 이미 프로젝트에 포함되어 있다. 우리의 GUI는 다양한 요소를 배치하기 위한 새로운 GUI 컴포넌트를 사용하는 GameHUD 스크립트안에서 다루어진다. 이 스크립트는 Level 게임오브젝트에 첨부될 필요가 있다. (우리는 쉽게 NearCamera 오브젝트나 자신의 고유한 ‘GUI’ 게임오브젝트에 추가할 수도 있다. 이것은 게임의 디자인을 결정하는 핵심이라기 보다는 개인적인 취향이다.) 우리는 Level 게임오브젝트를 레벨한정 상태와 다른 스크립트를 다루기 위해 사용할 것이다. 유니티 2의 새로운 GUI 시스템은 스킨 시스템을 제공한다. 이것은 룩앤필을 완전히 제어할 수 있게 해준다. GUISkin 에셋은 웹사이트의 CSS 파일처럼 유니티의 GUI의 모양을 정의하며 기본 모양을 변경할 필요가 있을때 요구된다. 우리는 폰트를 변경할 것이므로 장면에 GUISkin 을 포함할 필요가 있다. GUI Skin 오브젝트는 Hierarchy 패널에는 추가되지 않는다. 대신에 우리는 우리의 GameHUD 스크립트에서 직접적으로 참조한다. 알림 이미지의 크기를 2의 승수로 만들지 않을 경우 유니티에서 많은 경고를 보게 될 것이다. 이는 성능을 위해 빠른 계산을 하기 위함이다. 이제 플레이어의 체력 정보와 연료 상태를 표시해보자. Health Pie Images 는 배열이다. 유니티는 배열의 크기를 알지 못하므로 0으로 설정된다. 우리는 6개의 파이 이미지를 이 배열에 추가할 것이므로 값을 변경해야 한다. 이제 게임을 시작하면 아래와 같이 HUD를 볼 수 있을 것이다. 모든 게임은 시작 메뉴를 필요로 한다. 알림 스플래시 스크린, 메뉴와 같은 모든 것들은 유니티의 장면(Scene)이다. 하지만 게임 레벨은 아니다. 게임 레벨(일종의 스테이지)은 일반적으로 장면이지만, 장면이 언제나 게임 레벨인것은 아니다. 시작 메뉴를 위해 우리에게 필요한 것들 즉, 아래와 같은 형식이다. 우선은 새로운 (비어있는) 장면을 만들어야 한다. 이제 우리는 메뉴를 만들기 위해 GUI 시스템을 이용할 것이다. 우리는 우리의 스크립트가 실행중이 아니더라도, 즉 에디트 상태에서도 실행되도록 할 것이다. 클래스 상단에 아래 코드를 작성하라. (전체 코드는 아래부분에서 제공할 것이다.) [ExecuteInEditMode] 우리는 LerpzTutorialSkin 에셋을 링크할 필요가 있으므로 아래 코드를 추가한다. public GUISkin gSkin; 우리는 배경을 위한 Texture2D 오브젝트가 필요하다. (Inspector 패널에서 이미지를 지정할 것이다.) public Texture2D backdrop; // 우리의 배경 이미지는 여기에 지정된다. 우리는 플레이어가 “Play” 버튼을 클릭했을 때 “Loading...” 메시지를 표시하기를 원하므로 이를 다루기 위한 플래그가 필요할 것이다. private bool isLoading = false; // 참이면, "Loading..." 메시지를 표시할 것이다. 마지막으로 우리는 OnGUI 함수가 필요하다. void OnGUI() { if (gSkin) GUI.skin = gSkin; else Debug.Log("StartMenuGUI : GUI Skin object missing!"); 위 코드는 GUI Skin 오브젝트의 유효성을 체크한다. 배경 이미지는 배경으로 우리의 이미지를 사용하도록 설정한 GUI.Label 요소이다. 이것은 텍스트가 없고 언제나 우리의 디스플레이의 크기에 자동으로 맞춰진다. GUIStyle backgroundStyle = new GUIStyle(); backgroundStyle.normal.background = backdrop; GUI.Label(new Rect((Screen.width - (Screen.height * 2)) * 0.75f, 0, Screen.height * 2, Screen.height), "", backgroundStyle); 먼저, 우리는 새로운 GUIStyle() 오브젝트를 정의했다. 이는 기본 GUI 스킨 스타일을 덮어쓰기 위해 사용할 것이다. 이 경우, 우리는 단지 기본 배경(normal.background) 스타일을 우리의 배경 이미지로 변경하였다. GUI.Label() 함수에 주어진 Rect 오브젝트는 이미지가 언제나 화면을 가득 채우도록 하기 위한 화면 해상도에 따른 사각형의 크기이다. 위의 코드에 의해 이미지의 가로세로 비율은 일그러지지 않고 유지되며, 화면에 맞게 짤리거나 적절하게 확대/축소 될 것이다. 다음은 타이틀 텍스트이다. 이를 위한 스크립트 코드를 작성하기 전에 먼저 해야할 일이 있다. 우리가 만들었던 LerpzTutorialSkin 에셋에 정의되어 있는 폰트는 거대한 게임 타이틀로는 적절하지 않다. 그러므로 우리는 스킨에 mainMenuTitle이라는 이름의 새로운 GUI Style 을 추가할 것이다. 마지막으로 남은 코드를 아래와 같이 작성한다. GUI.Label(new Rect((Screen.width / 2) - 197, 50, 400, 100), "Lerpz Escapes", "mainMenuTitle"); 알림 GUI Style의 이름은 대소문자를 구분하지 않는다. 그러므로 “mainMenuTitle”과 “mainmenutitle”은 서로 같다. 우리는 좀 더 흥미로운 버튼을 만들기 위해 LerpzTutorialSkin 에셋의 GUI Button 속성을 수정할 필요가 있다. 우리는 시작 메뉴와 HUD에서 같은 GUI 스킨을 사용할 것이다. HUD에서는 기본 폰트를 변화하는 걸로 충분했지만, 시작 메뉴에서는 버튼 텍스트 뒤에 그래픽적 이미지를 가질 필요가 있다. 기본 버튼은 게임의 비주얼 스타일에 적합하지는 않으므로 우리는 그것을 변경할 것이다. 팁 당신의 요소들이 Hover(마우스가 올려진 상태), Focus(선택한 상태) 그리고 Active(클릭된 상태) 이벤트에 반응하기를 원한다면, 당신은 반드시 배경 이미지를 설정해야 한다. 이제 “Play” 버튼을 추가하자. if (GUI.Button(new Rect((Screen.width / 2) - 70, Screen.height - 160, 140, 70), "Play")) { isLoading = true; Application.LoadLevel("TheGame"); // TheGame 레벨을 로드하라. } 위의 코드가 “Play” 버튼을 화면에 그리고 클릭 이벤트를 다루는 모든 것이다. 우리는 “Loading...” 메시지를 보여주기 위해 isLoading 값을 참으로 설정했다. “Quit” 버튼은 위와 유사하게 다루어진다. 하지만 독립실행형( 또는 에디터에서 실행중)인지 확인을 해야 한다. 즉, 웹 스트리밍이 아니라는 것을 보장해야 한다. bool isWebPlayer = (Application.platform == RuntimePlatform.OSXWebPlayer || Application.platform == RuntimePlatform.WindowsWebPlayer); if (!isWebPlayer) { if (GUI.Button(new Rect((Screen.width / 2) - 70, Screen.height - 80, 140, 70), "Quit")) Application.Quit(); } 마지막 단계는 “Loading...” 메시지를 출력하는 것이다. if (isLoading) GUI.Label(new Rect((Screen.width / 2) - 110, (Screen.height / 2) - 60, 400, 70), "Loading...", "mainMenuTitle"); } 다음 단계는 몇가지 음악을 추가하는 것이다. 드디어 우리의 시작 메뉴가 완성이 되었다. 하얀뱀 튜토리얼에는 타이틀 텍스트의 크기를 조절하는 내용이 빠져있습니다. 빠진 부분에 대해서는 제가 추가하였습니다. 우선 타이틀 텍스트의 크기를 Inspector 패널에서 조절할 수 있도록 아래와 같이 멤버변수를 선언합니다. public float gameTitleScale = 2.0f; 다음으로 타이틀 텍스트를 그리기 전에 GUI 시스템의 변환행렬을 이용하여 크기를 조절합니다. GUI.matrix = Matrix4x4.TRS(new Vector3(0, 0, 0), Quaternion.identity, Vector3.one * gameTitleScale); GUI.Label(new Rect((Screen.width / (2 * gameTitleScale)) - 197, 50, 400, 100), "Lerpz Escapes", "mainMenuTitle"); 마지막으로 위에서 설정한 변환행렬이 다른 요소들에게 적용이 되지 않도록 하기위해 행렬을 다시 초기화 합니다. GUI.matrix = Matrix4x4.identity; 이제 Insepctor 패널에 보면 Game Title Scale 라는 속성이 추가되었고 이 값을 조절하여 타이틀 텍스트의 크기를 조절하시면 됩니다. 하얀뱀 한번 Insepctor 패널에 값이 노출되면 소스코드에서 값을 바꿔도 Inspector 패널의 값은 이전 값으로 계속 유지됩니다. 그러므로 소스코드를 변경하였는데 적용되지 않는다고 고민하지 마시고 Insepctor 패널의 값을 변경하세요. 아래는 StartMenuGUI.cs 파일의 전체 소스입니다. using UnityEngine; using System.Collections; [ExecuteInEditMode] public class StartMenuGUI : MonoBehaviour { // Use this for initialization void Start() { } // Update is called once per frame void Update() { } void OnGUI() { if (gSkin) GUI.skin = gSkin; else Debug.Log("StartMenuGUI : GUI Skin object missing!"); // 배경 이미지. GUIStyle backgroundStyle = new GUIStyle(); backgroundStyle.normal.background = backdrop; GUI.Label(new Rect((Screen.width - (Screen.height * 2)) * 0.75f, 0, Screen.height * 2, Screen.height), "", backgroundStyle); // 타이틀. GUI.matrix = Matrix4x4.TRS(new Vector3(0, 0, 0), Quaternion.identity, Vector3.one * gameTitleScale); GUI.Label(new Rect((Screen.width / (2 * gameTitleScale)) - 197, 50, 400, 100), "Lerpz Escapes", "mainMenuTitle"); // 행렬 초기화. GUI.matrix = Matrix4x4.identity; // Play 버튼. if (GUI.Button(new Rect((Screen.width / 2) - 70, Screen.height - 160, 140, 70), "Play")) { isLoading = true; Application.LoadLevel("TheGame"); // TheGame 레벨을 로드하라. } // Quit 버튼. bool isWebPlayer = (Application.platform == RuntimePlatform.OSXWebPlayer || Application.platform == RuntimePlatform.WindowsWebPlayer); if (!isWebPlayer) { if (GUI.Button(new Rect((Screen.width / 2) - 70, Screen.height - 80, 140, 70), "Quit")) Application.Quit(); } // 로딩 메시지. if (isLoading) GUI.Label(new Rect((Screen.width / 2) - 110, (Screen.height / 2) - 60, 400, 70), "Loading...", "mainMenuTitle"); } public float gameTitleScale = 2.0f; public GUISkin gSkin; public Texture2D backdrop; // 우리의 배경 이미지는 여기에 지정된다. private bool isLoading = false; // 참이면, "Loading..." 메시지를 표시할 것이다. } 게임 오버 화면은 플레이어가 도전을 완수하거나 실패했을 때 보여진다. 시작 메뉴와 달리 이 장면은 버튼이라던지 유저와 상호작용하는 그 무엇도 없다. 단지 음악이 재생되는 동안 배경이미지 위에 “Game Over” 메시지만 보여준다. 일단 음악 재생이 완료되거나 유저가 마우스를 클릭하면 자동적으로 Start Menu 장면이 로드된다. 우리는 유니티 에디터를 이용해 더이상 추가할 것이 없다. 메인 카메라 하나면 충분하다. 다음 단계는 스크립트를 만드는 것이다. [ExecuteInEditMode] 시작 메뉴에서도 사용했듯이 에디터에서 스크립트가 수행되도록 하는 코드이다. 시작 메뉴에서는 LerpzTutorialSkin GUI Skin 에셋을 이용하여 전체적으로 스타일을 적용했었다. 또 다른 방법으로는 개별적인 GUI 스타일을 직접적으로 정의하는 방법이 있다. GameOver 스크립트에서는 이 방법을 사용할 것이고 GUI 스타일 변수를 노출하여 Inspector 패널에서 설정할 수 있도록 할 것이다. 아래 변수를 선언하라. public GUIStyle background; public GUIStyle gameOverText; public GUIStyle gameOverShadow; 우리는 “Game Over” 메시지의 크기 값을 기본 폰트보다 더 크게 정의할 필요가 있다. 우리는 이 메시지를 그림자 효과를 주기 위해 2번 렌더링할 것이다. 그러므로 아래와 같이 변수를 선언하라. public float gameOverScale = 1.5f; public float gameOverShadowScale = 1.5f; 이제 OnGUI() 함수를 작성한다. 우선, 시작 메뉴에서 사용했던 방법과 유사하게 배경 이미지를 출력한다. void OnGUI() { GUI.Label(new Rect((Screen.width - (Screen.height * 2)) * 0.75f, 0, Screen.height * 2, Screen.height), "", background); 다음 작업은 “Game Over” 메시지의 그림자 버전을 그리는 것이다. 우리는 텍스트의 크기를 변경하고 화면의 중앙에 출력할 필요가 있다. 다행히도, 우리는 GUI 시스템에 내장된 변환행렬(transform matrix)을 이용하여 크기를 조절할 수 있다. 팁 GUI 변환행렬은 자유로운 이동, 회전, 크기 등을 수행하기 위해서 사용될 수 있다. GUI.Label() 함수에 전달되는 gameOverShadow 스타일의 텍스트가 어두운 그림자 색상이 되도록 하라. GUI.matrix = Matrix4x4.TRS(new Vector3(0, 0, 0), Quaternion.identity, Vector3.one * gameOverShadowScale); GUI.Label(new Rect((Screen.width / (2 * gameOverShadowScale)) - 150, (Screen.height / (2 * gameOverShadowScale)) - 40, 300, 100), "Game Over", gameOverShadow); 마지막으로, 우리는 좀 더 밝은 색상으로 같은 텍스트를 그린다. 유니티의 GUI 시스템은 언제나 코드에 작성된 순서대로 요소들을 렌더링한다. 그러므로 이 텍스트는 그림자의 위에 나타날 것이다. GUI.matrix = Matrix4x4.TRS(new Vector3(0, 0, 0), Quaternion.identity, Vector3.one * gameOverScale); GUI.Label ( new Rect( (Screen.width / (2 * gameOverScale)) - 150, (Screen.height / (2 * gameOverScale)) - 40, 300, 100), "Game Over", gameOverText); } 전체 소스코드는 아래와 같다. using UnityEngine; using System.Collections; [ExecuteInEditMode] public class GameOverGUI : MonoBehaviour { // Use this for initialization void Start() { } // Update is called once per frame void Update() { } void OnGUI() { // 배경 이미지. GUI.Label(new Rect((Screen.width - (Screen.height * 2)) * 0.75f, 0, Screen.height * 2, Screen.height), "", background); // Game Over 텍스트 그림자. GUI.matrix = Matrix4x4.TRS(new Vector3(0, 0, 0), Quaternion.identity, Vector3.one * gameOverShadowScale); GUI.Label(new Rect((Screen.width / (2 * gameOverShadowScale)) - 150, (Screen.height / (2 * gameOverShadowScale)) - 40, 300, 100), "Game Over", gameOverShadow); // Game Over 텍스트. GUI.matrix = Matrix4x4.TRS(new Vector3(0, 0, 0), Quaternion.identity, Vector3.one * gameOverScale); GUI.Label ( new Rect( (Screen.width / (2 * gameOverScale)) - 150, (Screen.height / (2 * gameOverScale)) - 40, 300, 100), "Game Over", gameOverText); // 행렬 초기화. //GUI.matrix = Matrix4x4.identity; } public GUIStyle background; public GUIStyle gameOverText; public GUIStyle gameOverShadow; public float gameOverScale = 1.5f; public float gameOverShadowScale = 1.5f; } 이제 스크립트를 적용할 단계이다. 이제 아래와 같은 화면을 보게 될 것이다. 마지막 작업은 Main Camera 에 두번째 스크립트를 추가하는 것이다. 이 스크립트는 음악 재생이 종료되었는지를 체크한 후 Start Menu 장면을 로드한다. using UnityEngine; using System.Collections; public class GameOverScript : MonoBehaviour { // Use this for initialization void Start() { } // Update is called once per frame void Update() { } void LateUpdate() { if (!audio.isPlaying || Input.anyKeyDown) Application.LoadLevel("StartMenu"); } } 이 코드는 오디오 재생이 끝나거나 플레이어가 키를 눌렀는지를 체크한다. LateUpdate() 함수는 매 프레임마다 호출된다. 하지만 모든 Update() 함수의 호출이 끝난 다음에 호출이 되므로 캐릭터가 움직인 후 쫓아가야 하는 카메라 움직임 같은 경우에 주로 사용된다. 이제 GUI를 완료하였다. Lerpz 는 두 종류의 장애물을 만난다. 하나는 로봇 가드이고 다른 하나는 레이저 방벽이다. 레이저 함정은 레이저 통로에 위치한다. 그리고 우리의 플레이어어가 그 빔을 만질경우 피해를 준다. 통로에 2개의 레이저를 설치할 것이다. 레이저는 위아래로 움직인다. 각각의 레이저 트랩은 같은 수직 평면에서 위아래로 움직이는 빔이다. 플레이어나 적들이 빔에 맞는다면, 대미지를 입을 것이다. 아직은 레이저를 장면에 위치시키려고 하지마라. 당신은 우선 “Use World Space”의 체크박스를 해제할 필요가 있다. 점광원은 레이저의 빛으로 사용되고 레이저와 함께 위아래로 움직일 것이다. 이것은 레이저빔과 같은 착각을 준다. Line Renderer 컴포넌트 라인 렌더러는 3D 공간상에 선을 그린다. 이것은 선이 그려질 점의 배열을 가지고 있다. 라인렌더러는 Trail Renderer 컴포넌트와 같은 기술을 이용하여 그려진다. 레이저 트랩의 핵심은 LaserTrap 스크립트이다. 그러므로 자세히 살펴보자. 레이저 트랩은 스크립트에 의해 위아래로 움직이는 라인 렌더러 컴포넌트다. 스크립트는 또한 충돌을 체크하고 충돌시 발생하는 비주얼 이펙트를 발생시킨다. 이제 스크립트의 몇가지 기본 속성에 대해서 알아보자. 스크립트에는 2개의 함수를 정의했다. Start() 함수에서는 나중을 위해 라인 렌더러 컴포넌트의 초기 위치값을 저장한다. 그리고 라인 렌더러의 두번째 정점(vertex)를 laserWidth에 의해 정의되는 위치와 매칭되도록 설정한다. 이는 복도의 넓이와 매칭되는 빔의 길이를 쉽게 조절할 수 있게 한다. 알림 우리는 라인 렌더러의 배열에서 마지막 정점만을 수동으로 설정했다. 그러나 이를 스크립트에서 다룬다면 더욱 유연하게 레이저 빔을 애니메이션 시킬 수 있다. Update() 함수를 살펴보자. 처음에는 레이저 빔의 움직임을 계산한다. 우리는 이를 위해 Mathf.Sin() 함수를 사용했다. 우리는 현재시간을 Time.time(게임이 시작한 이후의 초 단위 시간)을 이용해 알아내고 그것을 우리의 애니메이션 속도와 곱한 후 timingOffset 값을 더한다. 이것은 우리에게 싸인 곡선에 따른 위치를 알려준다. 마지막으로, 그것을 우리가 원하는 높이(height)로 조절하고 그 결과를 오프셋(offset)으로 사용한다. 다음은 충돌 체크이다. 스크립트에서는 빔의 궤도에 따라 광선을 쏘고 충돌체를 가진 어떤 게임오브젝트가 이에 부딪히는지를 체크하는 것으로 충돌체크를 수행한다. (시간에 기반한 테스트는 반사작용할 시간을 허용한다. 그렇지 않으면 플레이어는 레이캐스팅(광선을 쏘아 부딪히는 것을 검사하는 것)에 의해 매 프레임마다 체력을 잃어버릴 것이다.) 만약 무언가가 부딪힌다면, 우선 그것이 Player 또는 Enemy 인지를 체크한다. 만약 그렇다면, “Apply Damage”를 그것에게 전달한다. 동시에 우리는 이펙트 효과를 발생시키기 위해 hitEffect 게임오브젝트를 인스턴스화 한다. 만약, 플레이를 한 후 레이저 빔에 부딪히면 체력이 소모되는 것을 볼 수 있다. 움직이는 적대자들은 로봇 가드이다. 이들은 레벨 곳곳에 전략적으로 위치한다. Lerpz가 그들의 범위안에 들어서면 그들은 Lerpz에게 접근하여 해를 가한다. 로봇에 대한 사항 대부분의 게임 인공지능은 지능과 같은 것보다는 무엇보다 먼저 행동에 집중된다. 우리의 로봇은 작은 인공지능을 보여준다. 하지만 그저 플레이어가 가는 길을 예측하여 반응할 뿐이다. 이것은 반드시 나쁜 것은 아니다. 많은 플레이어들은 이와 같은 행동을 좋아한다. 이것은 그들이 로봇을 파괴하는 방법을 쉽게 익힐 수 있도록 만든다. 로봇 가드는 매우 단순한 행동 패턴을 가지고 있고 이는 EnemyPoliceGuy 스크립트에 작성되어 있다. 만약 플레이어가 로봇의 탐색 제한 범위를 벗어나면, 로봇은 다시 Idle 모드로 돌아간다. 울타리 또는 둘러싸인 지역은 로봇이 배경으로 떨어지지 않도록 하는데 가장 좋다. 이제 플레이를 하면 로봇이 Idle 애니메이션을 보이면서 서있을 것이다. 이제 스크립트를 추가해야 한다. 로봇을 위한 2개의 스크립트가 있는데 이를 원본 프리팹에 직접 추가할 것이다. 이제 플레이를 해보면, 당신이 가까이 갔을 때 로봇이 반응할 것이다. 플레이어가 로봇을 넉다운 시키면, 죽음의 시퀀스가 시작된다. 우리는 플레이어가 로봇의 범위밖으로 벗어나 로봇이 리셋되기 전까지 로봇이 넘어져서 푸른 불꽃을 쏟아내기를 원한다. 추가적으로, 로봇이 죽을 때, 우리는 로봇이 수집가능한 아이템을 뱉어내길 원한다. 더 많은 스크립트와, 애니메이션 데이터 그리고 다른 요소들을 현재 프리팹(Copper)에 추가하는 것 보다, 우리는 단지 새로운 것을 만들것이다. 플레이어가 로봇을 공격하면, ApplyDamage 메시지가 로봇에게 전달되고, 이는 로봇의 ApplyDamage() 함수를 호출한다. 로봇이 너무 많은 공격을 받으면 -- 우리의 예제에서 HP는 3으로 설정했다. -- 죽게 되고 이때 Die() 함수를 호출하게 된다. Die() 함수는 Copper 게임 오브젝트를 파괴한다. (오브젝트 파괴는 Update() 함수가 완료되기 전까지는 수행되지 않는다. 그러므로 함수의 시작에 오브젝트를 파괴하는 코드를 사용해도 상관없다.) 다음으로, 로봇의 현재 위치에 대체 프리팹을 인스턴스화(복사) 한다. - CopyTransformsRecurse() 함수에서 자식들의 모든 변환을 복사한다. 불꽃 폭발은 단지 또 다른 파티클 시스템 에셋이다. 이것은 정확한 위치에 나타나기 위해 죽어가는 로봇 오브젝트의 자식으로 인스턴스화 된다. 다음 단계는, 우리의 인스턴스를 넘어지고 구르게 하도록 플레이어로부터 멀리 밀어낸다. 마지막으로, 우리는 최대 2개의 픽업 아이템을 인스턴스화 한다. 체력과 연료셀은 50대 50의 확률로 만들어지고 그것들은 랜덤한 방향의 공중으로 튀어오른다. 하얀뱀 로봇가드(Copper) 프리팹에 추가한 스크립트의 속성에 노출된 값들을 지정하는 부분이 튜토리얼에는 빠져있습니다. 그 부분을 추가해보겠습니다. 우선 프로젝트 패널에서 Props > FuelCellPrefab, HealthLifePickUpPrefab 프리팹에 Component > Scripts > Droppable Mover 스크립트를 추가합니다. 다음으로, 추가된 Droppable Mover 스크립트의 Collision Mask 속성을 Everything로 변경합니다. EnemyDamage 스크립트 Explosion Prefab - Enemies > RobotExplode 지정. Dead Model Prefab - Enemies > CopperDead 지정. Health Prefab - Props > HealthLifePickUpPrefab 지정. Fuel Prefab - Props > FuelCellPrefab 지정. Drop Max - 2로 변경. Struck Sound - Sounds > CopperNewHitSFX 지정. <- 다음 챕터에서 다루고 있음. Enemy Police Guy 스크립트 Idle Sound - Sounds > CopperIdleLoop 지정. <- 다음 챕터에서 다루고 있음. Attack Sound - Sounds > CopperNewAttackSFX 지정. <- 다음 챕터에서 다루고 있음. Droppable Pickups & Physics => 떨어뜨릴 수 있는 물체와 물리 체력과 연료 프리팹은 이전에 만들었지만 여기에 DroppableMover 스크립트를 추가해야 한다. 왜냐하면 이들 프리팹에 있는 충돌체(Collider)는 트리거로 설정이 되었고 이는 물리엔진이 무시하기 때문이다. 플레이어와의 지정된 거리내에서 적들을 생성하는 것은 오래된 기법이며 이는 각각의 적들의 상태를 저장할 필요가 없게 만듦으로써 프로세서의 부담을 줄여줄 수 있다. 단순하게 플레이어에게 보이지 않는 로봇들을 삭제함으로써, 우리는 불필요한 AI와 스크립트의 실행을 피할 수 있다. 시작해보자 우리는 이 게임 오브젝트가 좀 더 필요할 것이다. 그러므로 우리의 CopperSpawn 오브젝트들을 관리할 부모 오브젝트를 만들자. 알림 이 기술의 부작용 하나는 당신이 로봇을 죽인 후 범위 밖으로 이동하면, 당신이 다시 돌아왔을 때 새것처럼 다시 나타난다는 것이다. CopperSpawn 오브젝트는 플레이어가 범위 내로 접근하면 Copper 프리팹의 인스턴스를 생성하고 다시 범위밖으로 나가면 자동으로 생성했던 인스턴스를 파괴한다. 이는 EnemyRespawn 스크립트에 작성되어 있다. 이 스크립트의 두 가지 주요 함수는, Start() -- 두고두고 사용할 플레이어의 위치(Transform)를 저장해 둔다. Update() -- 먼저 플레이어가 범위 내에 있는지 체크하여 참이면 로봇을 인스턴스화하고 그렇지 않고 범위 밖에 있으면 로봇 인스턴스를 파괴한다. 또한 편집기에서 사용되는 2개의 기즈모 함수가 있다. 기즈모는 일반적으로 비주얼적인 목적으로 장면 뷰에서만 표시된다. 이 스크립트에서는, 일단 로봇 아이콘을 그리고 오브젝트가 선택되었을 때 생성(spawn) 범위를 보여주는 구체를 그리고 있다. 매번 유니티 편집기의 GUI가 업데이트 되면 OnDrawGizmos() 함수가 호출되어 언제나 아이콘이 보이도록 한다. 반대로 OnDrawGizmosSelected() 함수는 유니티 편집기 GUI에 의해 오브젝트가 선택되어 있는 동안에만 호출된다. 하얀뱀 using UnityEngine; using System.Collections; public class DestroyDeadModel : MonoBehaviour { // Use this for initialization void Start() { player = GameObject.FindWithTag("Player").transform; } // Update is called once per frame void Update() { float distanceToPlayer = Vector3.Distance(transform.position, player.position); if (distanceToPlayer > destroyRange) Destroy(gameObject); } private Transform player; public float destroyRange = 50.0f; } 다른 최적화 방법 위에서 사용한 방법외에, 유니티는 OnBecameVisible() 함수와 OnBecameInvisible() 함수를 제공한다. 이는 화면에 보였을 때와 보이지 않았을 때 호출되는 함수이다. 이를 이용하여 로봇 오브젝트를 장면에서 제거하면 단지 카메라 방향을 돌리는 것만으로도 로봇이 생성/파괴될 수 있다. 이는 우리가 원하는 상황은 아니다. 또 다른 방법으로는, 우리의 것보다 더울 최적화된 것으로, 플레이어의 위치를 확인하기 위해 스크립트 코드를 사용하는 대신 일정 범위에 충돌체(Collider)를 만들고 이를 트리거로 사용하는 것이다. 유니티는 이를 위해 OnTriggerEnter()와 OnTriggerExit() 함수를 제공한다. 하지만 이는 충돌체를 다른 용도로 사용할 오브젝트에 추가되는 스크립트에서는 불가능한 방법이다. 이 챕터에서 우리는 사운드 효과와 2개의 컷씬을 추가할 것이다. 유니티는 오디오에 대하여 상당히 단순한 인터페이스를 가지고 있지만, 고려해야할 몇가지 중요한 사항이 있다. 게임에 필요한 사운드 효과 리스트. 아이템 주위 환경 소리 감금된 우주선 장벽 우주선 우리는 이미 약간의 사운드 효과를 추가하였지만, 아직 15개 이상을 추가해야 할 필요가 있다. 이 강좌에서는 sceneAtmosphere 라는 이름의 반복되는 주변(환경) 소리 파일을 포함하고 있다. 이 파일은 Ogg Vorbis 샘플이다. 우리는 사운드가 자동으로 재생되도록 하기 위해 Play On Awake 을 체크하였다. 이 사운드가 배경음으로써 게임 내의 다른 사운드들을 방해하지 않게 하기 위해 의도적으로 볼륨 레벨을 낮추었다. Pitch 옵션은 재생 속도를 정의하는 것으로 1은 보통 속도를 의미한다. (2는 2배속을 의미한다.) Rolloff 는 사운드의 볼륨이 청취자의 거리에 따라 얼마나 빨리 변화하는지를 정의한다. 우리가 점프 패드 트리거 프리팹을 만들 때 사운드 효과를 추가하지 않았다. 하지만 Jumppad 스크립트에는 이미 사운드를 재생하는 코드가 작성되어 있다. ... if (audio) { audio.Play(); } ... 스크립트 내에는 audio 변수가 정의되어 있지 않다. 이는 유니티 자체에 정의되어 있는 다수의 편의 변수 (convenience variable) 중 하나이며 게임 오브젝트에 첨부된 Audio Source 컴포넌트를 가리킨다. 우리는 이 컴포넌트를 추가하지 않았으므로, audio 변수는 null 이 될 것이고, if() 문은 거짓이 될 것이다. 수집가능한 아이템은 다루기 가장 쉽다. Pickup 스크립트에는 이미 사운드를 재생하는 코드가 작성되어 있으므로 sound 변수의 값만 지정해주면 된다. 팁 프리팹의 인스턴스에 적용 후 Apply 버튼으로 적용하는 것보다 프리팹 원본에 직접 추가하여 시간을 절약할 수 있다. 이번에 구현할 사운드 효과는 아래와 같다. 펀치 펀치 동작과 애니메이션은 ThirdPersonCharacterAttack 스크립트에 의해 다루어진다. 해당 스크립트의 속성을 보면 Punch Sound 속성이 노출되어 있다. 알림 게임 실행중에 이런한 일회성 사운드가 Hierarchy 패널에 잠깐 나타나는 것을 볼 것이다. 이는 정상이다. 맞는 소리(Struch Sound)와 죽는 소리(Death Sound) ThirdPersonStatus 스크립트는 2개의 사운드 효과를 다룬다. 하나는 플레이어가 적에 의해 공격당했을 때 그리고 다른 하나는 플레이어가 죽었을 때 나는 소리이다. 제트 팩 제트 팩은 일회성이 아닌 반복되는 사운드 효과이므로 플레이어 오브젝트에 직접 추가하여 Audio Source 컴포넌트로 만들 것이다. 이 사운드는 JetPackParticleController 스크립트에 의해서 재생이 되며 또한 이 스크립트에 의해 이미 비어있는 Audio Source 컴포넌트가 추가되어 있을 것이다. 위 사운드는 점프 중에만 재생을 한다. 그리고 사운드를 반복하기 위해 Loop 속성을 사용하지 않고 스크립트에서 사운드가 종료될 때 마다 다시 재생시키고 있다. 음악이 재생되는 도중에 다시 Play()함수를 호출하면 언제나 처음부터 다시 재생된다. 자신의 Audio Source 컴포넌트를 오직 제트팩 소리에만 사용하는 플레이어와는 달리 EnemyPoliceGuy 스크립트는 Copper 프리팹의 Audio Source 컴포넌트를 여러가지 사운드로 바꿔가면서 재생하는데 사용한다. 예제 코드는 아래와 같다. if (idleSound) { if (audio.clip != idleSound) { audio.Stop(); audio.clip = idleSound; audio.loop = true; audio.Play(); // play the idle sound. } } 위의 예제는 Idle() 함수에서 볼 수 있다. 위 코드에서 audio.Stop() 함수의 호출은 매우 중요하다. 정지하지 않고 재생중에 사운드를 교체하는 것은 알 수 없는 결과를 가져오기 때문이다. metalHit 사운드는 EnemyDamage 스크립트의 ApplyDamage() 함수에서 재생된다. 사운드 파일을 추가한 후 Copper 프리팹의 Inspector 패널 모습은 아래와 같다. 하얀뱀 위의 스크린샷을 보면 Audio Source 컴포넌트의 Play On Awake 속성이 체크되어 있는데 이럴 경우, 로봇 가드의 근처에 가지 않아도 소리가 나게 된다. 위의 속성을 체크 해제하자. 하지만 여전히 한번 소리가 나면 죽이기 전에는 소리가 사라지지 않는다. ㅡㅡㅋ 요 처리는 나중에... 여기서 우리는 두 개의 컷씬을 사용한다. 첫번째는 플레이어가 모든 연료셀을 모으는 데 성공하여 우주선의 락이 풀렸을 때 발생한다. 이 컷씬은 그림안의 그림(picture-in-picture) 기술을 이용하여 나타나므로, 플레이어는 계속 움직일 수 있다. 알림 여전히 장벽 근처의 상자에 올라 우주선에 접근하는 것이 가능하지만, 우주선은 잠겨서 이륙하지 않을 것이다. (장벽이 풀리기 전까지는 메쉬 충돌체가 트리거 타입으로 변화되지 않는다.) 두번째는 장벽이 비활성화 된 후 플레이어가 우주선을 만졌을 때 발생한다. 이 장면에서는 전체화면으로 플레이가 되며, 우리는 자유와 새로운 모험을 날아가는 것을 보게 된다. 그 후에는 게임이 종료될 것이다. 이제 첫번째 컷씬을 자세히 알아보자. 우리는 장벽에 애니메이션과 사운드 효과를 추가하였다. 이제는 이것을 해제하여보자. 하지만 우선, 우리가 모든 연료셀을 모았는지를 확인하자. if (remainingItems == 0) { levelStateMachine.UnlockLevelExit(); // ...and let our player out of the level. } var exitGateway: GameObject; var levelGoal: GameObject; var unlockedSound: AudioClip; var levelCompleteSound: AudioClip; var mainCamera: GameObject; var unlockedCamera: GameObject; var levelCompletedCamera: GameObject; 위 코드에는 두번째 컷씬에 필요한 변수도 포함되어 있다. levelGoal.GetComponent(MeshCollider).isTrigger = false; 이 코드는 두번째 컷씬이 너무 이르게 트리거 되는 것을 막아주는 중요한 코드이다. 이제는 장벽을 해제하여 보자. function UnlockLevelExit() { mainCamera.GetComponent(AudioListener).enabled = false; 유니티는 하나의 장면에 오직 하나의 Audio Listener 컴포넌트만을 지원한다. 일반적으로 이 컴포넌트는 메인 카메라에 부착이 되어 있다. 그러나 우리는 여러개의 카메라를 사용하고 있으므로 장벽이 비활성화되는 소리는 듣기 위해 잠시 컷씬 카메라의 Audio Listener 컴포넌트를 활성화해야 한다. 다음이 우리의 컷씬 카메라를 작동시키고 Audio Listener 컴포넌트를 활성화하는 코드이다. unlockedCamera.active = true; unlockedCamera.GetComponent(AudioListener).enabled = true; 장벽에 있는 사운드 효과를 정지한다. exitGateway.GetComponent(AudioSource).Stop(); 이제 우리는 장벽이 해제되는 사운드 효과를 컷씬 카메라의 위치에 재생해야 한다. if (unlockedSound) { AudioSource.PlayClipAtPoint(unlockedSound, unlockedCamera.GetComponent(Transform).position, 2.0); } 우리의 사운드 효과가 시작되는 것과 함께 우리는 컷씬 애니메이션 절차를 시작할 수 있다. 우리는 이 절차를 스크립트에서 수행할 것이다. 첫번째 줄은 애니메이션이 시작하기 전에 시간을 지연시켜 플레이어가 의식할 수 있도록 한다. yield WaitForSeconds(1); exitGateway.active = false; // ... the fence goes down briefly... yield WaitForSeconds(0.2); //... pause for a fraction of a second... exitGateway.active = true; //... now the fence flashes back on again... yield WaitForSeconds(0.2); //... another brief pause before... exitGateway.active = false; //... the fence finally goes down forever! 이제 우리는 배에 접근할 수 있다. 이제 우리는 우주선의 메쉬 충돌체에 트리거 속성을 부여해야 한다. levelGoal.GetComponent(MeshCollider).isTrigger = true; 마지막으로 플레이어가 이 결과를 볼 수 있도록 몇 초간 일시정지한다. 그 후 컷씬 카메라를 끄고 다시 NearCamera 를 활성화 한다. 그리고 Audio Listener를 NearCamera로 돌려보낸다. yield WaitForSeconds(4); // give the player time to see the result. // swap the cameras back. unlockedCamera.active = false; // this lets the NearCamera get the screen all to itself. unlockedCamera.GetComponent(AudioListener).enabled = false; mainCamera.GetComponent(AudioListener).enabled = true; 이제 우리가 해야할 일은 컷씬 카메라를 만드는 것이다. 하얀뱀 카메라에 의해 출력되는 내용이 화면 전체가 아닌 우측 상단 일부분이 되도록 하는 설정이 바로 Normalized View Port Rect 부분입니다. 위에서 설정한 값은 화면의 (69%, 69%) 위치에 가로/세로 30%의 크기로 나타나게 하고 있습니다. 이 컷씬 카메라는 스크립트에 의해 활성화될 것이므로 기본적으로 비활성화 시키도록 한다. 비활성화 하는 방법은 Inspector 패널 상단에 있는 체크박스를 해제하기만 하면 된다. 이제 테스트하기 위해 Level오브젝트에 첨부된 LevelStatus 스크립트의 속성을 아래와 같이 설정하라. 팁 위에서 Items Needes 를 2로 설정하여 연료셀을 2개만 먹어도 컷씬이 테스트할 수 있게 하였다. 테스트 후 다시 20으로 돌려놓는 것을 잊지 말아라. 알림 초당 프레임 수 카운터는 최적화 챕터에서 다루고 있다. 이제 플레이를 하고, 연료셀을 모두 모으면 컷씬을 볼 수 있다. 마지막 컷씬은 약간 더 복잡하다. 우주선이 날아가아가도록 해야 하는데 이를 스크립트로 할 수도 있지만 애니메이션 클립을 이용하는게 훨씬 더 쉽다. 게임을 실행하면 우주선이 이륙하는 것을 볼 수 있다. 하지만 우리는 레벨이 완료(게임을 클리어) 되었을 때에만 이륙하기를 원한다. 이것은 스크립트에서 할 것이다. 그러나 유니티는 기본적으로 애니메이션이 자동으로 실행되도록 되어있다. 이것을 멈추어보자. 다음은 2번째 컷씬 카메라를 만든다. 카메라가 바라보는 방향은 상관없다. 위치가 중요하다. 나머지는 스크립트에서 처리할 것이다. 컷씬 카메라 1 과 마찬가지로 우리는 이것을 비활성화 시켜야 한다. 하지만 나머지 설정은 약간 다르다. 주요 차이점은 이 카메라에 의한 컷씬이 구석에 일부분만 나타나는게 아니라 전체화면으로 보여져야 하기때문에 Normalized View Port Rect 의 값을 아래와 같이 지정해야 한다는 점이다. 이 컷씬은 연속된 트리거 메시지를 다뤄야 하므로 첫번재 컷씬보다는 약간 다루기 힘들다. 초기 트리거는 플레이어가 우주선을 터치했을 때 발생한다. 그러므로 우주선 모델은 트리거 이벤트를 다루기 위한 스크립트가 필요하다. private var playerLink : ThirdPersonStatus; function OnTriggerEnter (col : Collider) { playerLink=col.GetComponent(ThirdPersonStatus); if (!playerLink) // not the player. { return; } else { playerLink.LevelCompleted(); } } 하얀뱀 위 코드에는 ThirdPersonStatus 스크립트를 불러오는 부분이 있습니다. 문제는 이 스크립트가 자바스크립트로 작성이 되어있기 때문에 이번 스크립트를 C#으로 만들게 되면 서로 호환이 되지 않습니다. 그러므로 어쩔 수 없이 이번 스크립트 파일은 자바스크립트로 작성해야 합니다. 위의 코드는 플레이어가 우주선을 터치할 경우, ThirdPersonStatus 스크립트의 LevelCompleted() 함수를 호출한다. ThirdPersonStatus 스크립트의 LevelCompleted() 함수는 훨씬 짧다. function LevelCompleted () { levelStateMachine.LevelCompleted(); } levelStateMachine은 LevelStatus 컴포넌트(스크립트)를 불러온 것이다. LevelStatus는 레벨완료 애니메이션이 발생하는 곳이다. function LevelCompleted() { 먼저, 첫번째 컷씬에서 했던 것처럼 Audio Listener 를 전환해야 한다. mainCamera.GetComponent(AudioListener).enabled = false; levelCompletedCamera.active = true; levelCompletedCamera.GetComponent(AudioListener).enabled = true; 다음으로, 우리는 플레이어가 우주선 안에 있는 것처럼 보이게 해야 하므로 그를 안보이게 할 것이다. 이를 위해, 플레이어의 ThirdPersonController 스크립트에 “HidePlayer” 메시지를 보낼것이다. 이 함수는 플레이어를 렌더링하는 것을 멈추게 하여 보이지 않게 한다. playerLink.GetComponent(ThirdPersonController).SendMessage("HidePlayer"); 로봇은 플레이어가 보이는지를 체크하는게 아니라 위치를 체크하므로 안전하게 플레이어의 위치를 500 units 위로 이동할 것이다. playerLink.transform.position+=Vector3.up*500.0; // just move him 500 units 다음은 레벨이 완료되었음을 알리는 사운드 효과를 발생시킬 것이다. 이 경우에는, 우주선이 이륙하는 소리이다. if (levelCompleteSound) { AudioSource.PlayClipAtPoint(levelCompleteSound, levelGoal.transform.position, 2.0); } 이제 우리는 이전에 작성한 타임라인 기반 애니메이션을 시작하고 끝나기를 기다린다. levelGoal.animation.Play(); yield WaitForSeconds (levelGoal.animation.clip.length); 마지막으로, 우리는 “Game Over” 장면을 로드한다. Application.LoadLevel("GameOver"); //...just show the Game Over sequence. 이제 우리는 spaceShip 오브젝트가 레벨이 시작할 때(게임이 시작할 때), 트리거가 아니도록 설정이 되었는지 확인해야 한다. 그리고 playerLink 가 Player 오브젝트를 가리키도록 해야 한다. 우리는 스크립트가 로드될 때 유니티에서 자동으로 이 변수를 초기화 하도록 Awake() 함수 안에 작성할 것이다. private var playerLink: GameObject; // player object. function Awake() { playerLink = GameObject.Find("Player"); if (!playerLink) Debug.Log("Could not get link to Lerpz"); levelGoal.GetComponent(MeshCollider).isTrigger = false; // spaceShip 오브젝트 트리거 해제. } 마지막으로 우리는 Level 오브젝트의 LevelStatus 스크립트의 속성을 아래와 같이 설정해야 한다. 하얀뱀 전체 흐름은 다음과 같다. 1. 우주선과 플레이어가 부딪힌다. 2. 우주선의 OnTriggerEnter() 함수가 플레이어의 LevelComplete() 함수를 호출. 3. 플레이어의 LevelComplete() 함수는 단순히 Level의 LevelComplete() 함수 호출. 4. GameOver 장면 로드. 이제 모든 연료를 모으고 우주선을 터치했을 때 아래와 비슷하게 보여야 한다. 게임에 최적화가 필요한지 알아보는 최상의 방법은 초당 프레임률(fps)을 알아보는 것이다. 우리는 이를 Scripts > GUI 폴더에 있는 FPS 스크립트에서 구현하였다. 이 스크립트는 소스코드에 자세한 설명까지 포함되었으므로 따로 설명을 하지는 않지만, GUIText 컴포넌트가 필요하다는 것을 알아둬야 한다. 게임 뷰의 상단에는 “Stats”라는 버튼이 있다. 이를 활성화하면, 추가적인 정보를 얻을 수 있다. 이러한 통계들은 카메라가 렌더링하고 있는 것들에 기반한다. 그러므로 장면 주변을 돌아다니면 통계의 많은 값들이 변할 것이다. 중요한 요소는 다음과 같다. 장면에 있는 요소들은 여러번 렌더링 될 수 있다 : 그림자, 다수의 카메라, 텍스처에 렌더링하기, 픽셀 광원 등등. 반사 또는 굴절 같은 복잡한 셰이더 역시 추가적인 렌더링을 발생시킬 수 있다. 모든 3D 모델들은 삼각형으로 만들어진다. 삼각형이 적을 수록 더 빨리 렌더링이 된다. 둥글고 곡선형의 오브젝트들은 일반적으로 기본적인 정육면체나 평면처럼 직선형의 오브젝트보다 많은 삼각형을 사용한다. 삼각형들끼리 공유하는 꼭지점이 많을수록 더 복잡한 모델을 렌더링할 수 있다. 재질은 하나 이상의 텍스처를 사용할 수 있다. 팁 파티클 시스템은 파티클마다 2개의 삼각형을 사용한다. 그리고 파티클 시스템은 최소한 하나의 텍스처를 사용한다. (텍스처는 일반적으로 모든 파티클들이 공유한다.) 렌더 텍스처(Render Textures)는 후처리 이펙트, CCTV, 물의 반사, 거울 또는 유리의 굴절 효과와 같은 다양한 효과를 위해 사용된다. 대부분의 그림자는 이 기술을 사용하여 표현된다. 우리의 완성된 게임을 플레이해보면, 2개의 카메라(Near Camera, Far Camera)가 있음을 알 수 있다. Near Camera는 가까운 거리(0.4유닛에서 50유닛까지)의 모든 것을 렌더링한다. 팁 유닛은 정하기 나름이지만 일반적으로 “1유닛 = 1미터”를 많이 사용한다. Far Camera는 50유닛부터 500유닛까지 렌더링한다. 그러나 장면의 서브셋(일부분)만 렌더링한다. 서브셋은 레이어에 의해 정의된다. 장면에 있는 각각의 오브젝트들에게는 레이어가 주어진다. Near Camera는 레이어와 상관없이 모든 요소를 렌더링하지만 Far Camera는 “cameraTwo” 또는 “cameraTwoIgnoreLights” 레이어에 있는 아이템들을 무시한다. 우리는 또한 연료캔과 체력 아이템도 무시하기를 원한다. 이 아이템들은 “noShadow” 레이어에 있다. 그러므로 이것도 체크해제해야 한다. 알림 Far Camera는 스카이박스가 렌더링되는 곳이기도 하다. (위의 설정에서 Clear Flags(배경을 지우는 방법)를 보라.) Near Camera의 Clear Flags 설정은 “Depth Only”이다. 이 때문에 Far Camera에 의해 렌더링된 화면 위에 겹쳐서 그려진다. 하얀뱀 여러개의 카메라가 있을 경우, 렌더링 되는 순서는 Depth(깊이) 설정에 의하여 정해집니다. 숫자가 낮을수록 먼저 그려지는데 여기서는 Far Camera의 깊이 값이 0이고 Near Camera의 깊이 값이 1이므로 Far Camera가 먼저 그려지고 나중에 Near Camera가 그려집니다. 그리고 Far Camera에서 스카이박스(배경)을 그리고 난 후 그 위에 Near Camear가 이미 그려진 내용을 지우지 않도록 근처의 오브젝트들을 그리기 위해 Clear Flags 의 값이 “Depth Only”로 설정되어진 것입니다. Culling Mask에서 체크된 레이어는 렌더링이 되며, 체크되지 않은 레이어는 렌더링 되지 않는다. 이와 같은 최적화는 경비로봇과 아이템에서 작동하는 것을 발견할 수 있다. 아이템 주위의 배경은 언제나 렌더링 되지만 아이템은 플레이어와 가까워져야 렌더링된다.1. First Step
Lerpz 소개하기.
캐릭터 제어기와 3인칭 제어기 스크립트
Lerpz 애니메이션
ThirdPersonPlayerAnimation 스크립트
Gizmos => 기즈모(우리말로 거시기)
The Jec-Pack
파티클 시스템 추가하기
빛 추가하기.
Blob Shadows => 얼룩 그림자.
Blob 그림자 추가하기.
레이어 생성하기.
스크립팅 개념
죽음과 부활
Fallout Death 스크립트
부활 지점 (Respawn Points)
작동하는 방법
2. Setting the Scene
첫번째 단계
소품 놓기 (Placing Props)
Health Pickups => 건강 습득물 => 체력 회복제
The Force Field => 힘의 장 (힘이 작용하는 영역)
수집가능한 아이템 스크립트
점프 패드
3. The GUI
유저 인터페이스
유니티 2의 새로운 GUI 시스템
추가적인 정보
The In-game HUD - 화면위에 투명하게 제공되는 정보
GUI 스킨 오브젝트
시작 메뉴
장면 세팅하기
배경 이미지(Backdrop)
버튼 (Button)
게임 오버
4. Adversaries
레이저 트랩
레이저 트랩 만들기
LaserTrap 스크립트
로봇 가드
찾고 파괴하기
로봇 가드 추가하기.
죽음의 푸른 불꽃.
로봇 가드 스크립트.
로봇 생성과 최적화
작동 방법
5. Audio & Finishing Touches
샘플 노트
게임에 사운드 추가하기.
플레이어
로봇가드
주변(환경, ambient) 소리
점프 패드
수집품
우주선 장벽
플레이어
로봇 가드
Cut Scenes, 컷씬
장벽 해제하기
컷씬 카메라 1 만들기
컷씬 애니메이션.
컷씬 카메라 2 만들기.
6. Optimizing
렌더링 최적화 : 초당 프레임율 관찰하기.
Stats (Statistics, 통계) 화면 이해하기.
렌더링 최적화 : 2개의 카메라 시스템.
'Unity3D' 카테고리의 다른 글
1. Unity Basics - 유니티 기초 (0) | 2012.06.19 |
---|---|
2. Scripting Tutorial - 스크립팅 튜토리얼 (0) | 2012.06.19 |
Game Design 101 - Unity Viewport / 1st & 3rd person camera (0) | 2012.06.18 |
Unity3d Tutorial: Application Class Part5 - targetFrameRate (0) | 2012.06.13 |
Unity3d Tutorial: Application Class Part4 - unityVersion (0) | 2012.06.13 |