1 00:00:00,000 --> 00:00:01,650 안녕하세요, 제 이름은 Mark입니다. 2 00:00:01,650 --> 00:00:07,540 저는 수년동안 Unity같은 소프트웨어를 이용해서 저만의 게임을 만들고 싶었습니다. 3 00:00:07,540 --> 00:00:10,690 Unity는 수많은 타이틀을 만든 강력한 게임 엔진입니다. 4 00:00:10,690 --> 00:00:13,320 "컵헤드" ,"네온 화이트", "튜닉", 5 00:00:13,320 --> 00:00:17,830 "아우터 와일즈", "하스스톤", "파이어워치", 6 00:00:17,830 --> 00:00:18,830 심지어 포켓몬 다이다몬드 리메이크에도 사용되었습니다. 7 00:00:18,830 --> 00:00:23,753 그런데 저는 길고 여러 챕터가 있는 튜토리얼 영상을 볼 때마다.. 8 00:00:24,810 --> 00:00:25,890 ..항상 잠이 오더군요. 9 00:00:25,890 --> 00:00:31,500 저는 다른 사람이 하는걸 보면서 따라하는 것보다 직접 시도해보는게 적성에 더 맞더라고요. 10 00:00:31,500 --> 00:00:36,060 그래서 저는 작년에 실제로 효과가 있는 솔루션을 개발했습니다. 11 00:00:36,060 --> 00:00:38,476 이 방법은 3가지 단계로 나뉩니다. 12 00:00:38,476 --> 00:00:41,695 첫째, Unity의 정말 기초적인 것들만 배웁니다. 13 00:00:41,695 --> 00:00:45,710 둘째, 간단한 연습으로 지금까지 배운 것들을 보강합니다. 14 00:00:45,710 --> 00:00:49,355 셋째, 당신이 원하는 대로 나머지를 알아냅니다. 15 00:00:49,355 --> 00:00:50,713 이것은 정말로 효과가 있었습니다! 16 00:00:50,713 --> 00:00:54,912 대략 1년 동안, 저는 이 방법으로 아이폰 게임을 똑같이 만드는 것부터 시작해서 17 00:00:54,912 --> 00:00:59,027 자석을 이용한 저만의 퍼즐 플랫포머 게임까지 개발하고 있습니다. 18 00:00:59,027 --> 00:01:03,620 그리고 저는 100,000개 이상의 경우를 만들 수 있는 상호작용 가능한 비디오 에세이를 출시했습니다. 19 00:01:03,620 --> 00:01:07,031 그런데 잠깐만요, 첫번째 방법은 어떻게 했나요? 20 00:01:07,031 --> 00:01:11,786 소프트웨어가 이해하기가 너무 복잡할 때 기본적인 것들을 어떻게 배우나요? 21 00:01:11,786 --> 00:01:15,763 제가 사용한 방법은, 게임을 만들때 무조건 알아야 할 것들의 목록을 적는 것입니다, 22 00:01:15,763 --> 00:01:17,896 제가 어떤 게임을 만들던 간에 상관없이요. 23 00:01:17,896 --> 00:01:21,589 예를 들어: 어떻게 화면에서 캐릭터를 나타나게 하고 움직일 수 있게 할까요? 24 00:01:21,589 --> 00:01:24,620 어떻게 무언가를 생성되게 하고 나중에 사라지게 할까요? 25 00:01:24,620 --> 00:01:28,780 어떻게 충돌, 게임 오버, 애니메이션, 그리고 사운드 효과를 만들까요? 26 00:01:28,780 --> 00:01:33,540 저는 이 방법들을 긴 튜토리얼을 찾아다니고, Unity 공식 문서를 읽고, 27 00:01:33,540 --> 00:01:37,530 난해한 단어들을 검색하고, 수많은 시행착오를 겪으면서 배웠습니다. 28 00:01:37,530 --> 00:01:41,820 이 영상의 목적은 여러분을 그 번거로움에서 구하는 것입니다. 29 00:01:41,820 --> 00:01:46,380 이 영상은 제가 Unity를 배울 때 있었다면 좋았을 튜토리얼입니다. 30 00:01:46,380 --> 00:01:51,227 앞으로 40분동안 우리는 Unity를 사용해서 플래피 버드를 만들겁니다. 31 00:01:51,227 --> 00:01:56,707 우리가 플래피 버드를 만들고 싶어서가 아니라, 이 중독적인 아이폰 게임을 다시 만드려면, 32 00:01:56,707 --> 00:02:02,220 제가 아까전에 말한 무언가를 생성하는 것부터, 게임 오버를 만드는 방법까지 배울 수 있기 때문입니다. 33 00:02:02,220 --> 00:02:05,323 이 튜토리얼에서는 필요한 모든 단계를 다룹니다. 34 00:02:05,323 --> 00:02:08,460 Unity를 설치하는 것부터, Unity의 UI를 이해하고, 35 00:02:08,460 --> 00:02:14,525 당신의 첫번째 프로그래밍 코드를 작성하고, 친구들과 공유할 수 있도록 게임을 내보내기까지. 36 00:02:14,525 --> 00:02:19,542 그리고 튜토리얼이 끝나면, 당신이 계속해서 나머지 부분을 배울 수 있도록 37 00:02:19,542 --> 00:02:22,749 스스로 할 수 있는 몇가지 구체적인 단계들을 알려드리겠습니다. 38 00:02:22,749 --> 00:02:26,020 괜찮은 것 같나요? 그렇다면 이제 시작하죠. 39 00:02:27,010 --> 00:02:34,930 좋아요. 웹사이트에서 Unity를 받는 것부터 시작합시다. Unity Hub를 다운로드하고 설치하세요. 40 00:02:34,930 --> 00:02:39,020 Unity를 사용하려면 무료 계정이 필요합니다. 41 00:02:39,020 --> 00:02:42,640 계정을 만든 후, Unity 에디터를 설치할 수 있는 창이 나올 겁니다. 42 00:02:42,640 --> 00:02:46,311 저는 이 튜토리얼에서 2021.3버전을 사용하고 있습니다. 43 00:02:46,311 --> 00:02:50,430 만약 당신이 먼 미래에 이 영상을 보고 왜 프로그램이 영상과 다른지 궁금할 수도 있으니까요. 44 00:02:50,430 --> 00:02:53,833 제가 빠른 인터넷을 가지고 있다고 해봅시다. (효과음) 45 00:02:53,833 --> 00:02:55,561 그러나 아직 끝난 것은 아닙니다. 46 00:02:55,561 --> 00:03:00,740 설치 메뉴에서 Unity 에디터의 톱니바퀴 아이콘을 누르고 '모듈 추가'를 누르세요. 47 00:03:00,740 --> 00:03:03,523 Microsoft Visual Studio에 체크 표시가 되어 있는 게 보일 겁니다. 48 00:03:03,523 --> 00:03:06,721 이것은 우리가 프로그래밍 코드를 작성하는 데 사용할 소프트웨어입니다. 49 00:03:06,721 --> 00:03:10,160 '계속(확실하지 않음)'을 누르고 Visual Studio를 설치하세요. 50 00:03:10,160 --> 00:03:15,420 이 화면에서, 아래로 스크롤하고 'Unity를 활용한 게임 개발'은 체크하고, Unity Hub는 해제합니다 51 00:03:15,420 --> 00:03:18,280 왜냐면 Hub는 이미 설치 되있으니까요. (효과음) 52 00:03:18,280 --> 00:03:21,577 저희는 Visual Studio를 사용하기 위해 계정을 만들 필요가 없으므로 이건 생략하세요. 53 00:03:21,577 --> 00:03:24,177 그리고 이 창을 닫으세요. 나중에 실행할 것입니다. 54 00:03:24,177 --> 00:03:32,160 좋아요. 설치가 끝났어요. Unity Hub에서 '새 프로젝트'를 누르고, '모든 템플릿'을 선택하고 '2D 코어'를 선택합니다 55 00:03:32,160 --> 00:03:37,607 '2D 코어'는 2D 게임을 만드는데 적합하도록 설정된 빈 프로젝트입니다. 56 00:03:37,607 --> 00:03:42,320 프로젝트 이름을 정하고, '프로젝트 생성'을 누르고 제대로 시작해봅시다. 57 00:03:43,570 --> 00:03:48,575 첫번째로, 우리는 Unity의 기본 UI에 익숙해질 필요가 있습니다. 58 00:03:48,575 --> 00:03:53,120 다양한 패널들을 살펴보면서, 우리는 화면에 새가 나타나게 할 것입니다. 59 00:03:53,120 --> 00:04:00,030 맞아요, 이게 Unity의 기본 레이아웃입니다. 그리고 이 레이아웃은 4개의 패널로 나뉩니다. 60 00:04:00,030 --> 00:04:05,720 가장 먼저, 아래에는 프로젝트 패널이 있습니다. 여기에는 게임에 들어가는 모든 요소가 포함됩니다. 61 00:04:05,720 --> 00:04:11,288 예를 들면 스프라이트, 사운드 효과, 스크립트, 타일, 폰트 등이 있습니다. 62 00:04:11,288 --> 00:04:14,998 이중 일부는 Unity에서 직접 원하는 대로 만들 수도 있지만 63 00:04:14,998 --> 00:04:18,585 컴퓨터의 다른 곳에서 파일을 드래그해서 프로젝트에 넣을 수도 있습니다. 64 00:04:18,585 --> 00:04:25,544 저는 포토샵에서 새와 파이프의 역할을 할 스프라이트를 만든 후 이 방법으로 파일을 드래그해서 프로젝트에 넣었습니다. 65 00:04:25,544 --> 00:04:27,850 저는 여러분이 스프라이트를 직접 만드는 것을 추천하지만 (그것이 더 재미있지만) 66 00:04:27,850 --> 00:04:33,052 만약 당신이 만들 수 있는 능력이 없다면 설명란의 에셋 다운로드 링크를 확인하세요. 67 00:04:33,052 --> 00:04:38,940 왼쪽에는 계층 구조 패널이 있습니다 여기에는 현재 '장면'에 있는 것들이 포함되며 68 00:04:38,940 --> 00:04:42,960 '장면'은 대부분의 게임에서 레벨의 역할을 합니다. 우선 새를 만드는 것부터 시작합시다 69 00:04:42,960 --> 00:04:50,280 계층 구조에서 빈 오브젝트 생성을 누르면 빈 GameObject가 생성됩니다 70 00:04:50,280 --> 00:04:56,700 GameObject는 투명한 컨테이너와 같습니다 위치를 지정하거나 회전할 수 있고; 71 00:04:56,700 --> 00:05:03,300 크기를 조절할수도 있습니다. 그리고 컨테이너 안에 컴포넌트를 넣어 기능을 추가할 수 있죠 72 00:05:03,300 --> 00:05:08,220 예를 들어, '스프라이트 렌더러' 컴포넌트를 넣으면 GameObject에 이미지를 넣을 수 있죠 73 00:05:08,220 --> 00:05:13,260 이 게임의 모든 것들은 '컴포넌트가 있는 GameObject'로 구성되어 있습니다 74 00:05:13,260 --> 00:05:16,440 새, 파이프, 심지어 UI와 카메라까지 75 00:05:16,440 --> 00:05:22,680 이 모든 것들은 오른쪽의 인스펙터에서 관리할 수 있습니다 76 00:05:22,680 --> 00:05:28,440 빈 GameObject를 선택한 후, 위에서 이름을 정할 수 있습니다 77 00:05:28,440 --> 00:05:34,860 이름은 그냥 Bird라고 하죠 그리고 Transform에서 위치, 기울기, 크기를 조절할 수 있습니다 78 00:05:34,860 --> 00:05:41,520 이제 '컴포넌트 추가'를 누르고, Rendering을 누르고 Sprite Renderer를 선택하세요 79 00:05:41,520 --> 00:05:45,540 이걸 작동하게 하려면, 스프라이트를 선택해야 하는데, 그냥 프로젝트 패널에서 새 이미지를 80 00:05:45,540 --> 00:05:49,620 빈칸에 드래그하면, 그래픽이 생겼네요! 81 00:05:49,620 --> 00:05:53,280 그래픽은 중앙에 있는 씬 패널에 나타납니다 82 00:05:53,280 --> 00:05:57,240 씬에서는 현재 씬에 무엇이 있는지 볼 수 있고, 83 00:05:57,240 --> 00:06:01,560 이 기능들을 이용하여 움직이거나 회전시킬 수 있습니다 84 00:06:01,560 --> 00:06:06,900 이 섹션에는 '게임'이라는 탭이 있는데, 여기선 게임을 실행할때 메인 카메라에서 85 00:06:06,900 --> 00:06:13,440 어떻게 보이는지 확인할 수 있습니다 그리고 여기에선 해상도나 화면비를 설정할 수 있습니다 86 00:06:13,440 --> 00:06:19,080 다른 화면에서 어떻게 보이는지 알 수 있죠 - 저는 1920x1080을 선택하겠습니다 87 00:06:19,080 --> 00:06:23,940 새가 너무 많은 공간을 차지하네요 새의 크기를 줄일 수도 있지만, 저희는 88 00:06:23,940 --> 00:06:29,700 카메라를 축소할 겁니다. 카메라 또한 계층 구조에 있는 GameObject이기에 이를 선택하면 89 00:06:29,700 --> 00:06:35,880 Camera 컴포넌트에서 수치를 조절할 수 있습니다 '크기'를 높이면 카메라가 축소됩니다 90 00:06:36,420 --> 00:06:39,420 그리고 배경 색도 바꾸겠습니다. 좋네요 91 00:06:39,420 --> 00:06:44,340 이제 위에 있는 Play 버튼을 누르면 세상에서 가장 92 00:06:44,340 --> 00:06:47,760 지루한 게임이 시작됩니다 네. 이제 덜 지루하게 만들어 보죠 93 00:06:49,320 --> 00:06:55,440 잠깐 되돌아봅시다 Unity에는 4개의 패널이 있고 그중 프로젝트에는 모든 요소가 들어가고, 94 00:06:55,440 --> 00:07:00,900 계층 구조에는 현재 레벨에 있는 GameObject가 표시되고, 인스펙터에는 95 00:07:00,900 --> 00:07:04,740 GameObject를 마음대로 조절할 수 있고, 씬에서는 레벨을 볼 수 있다. 96 00:07:04,740 --> 00:07:10,800 그리고 GameObject는 Sprite Renderer같은 컴포넌트를 담을 수 있는 투명한 컨테이너다. 97 00:07:11,580 --> 00:07:15,900 두번째로, 저희는 더 많은 컴포넌트를 이용하여 98 00:07:15,900 --> 00:07:19,320 새를 중력에 영향을 받는 물리 오브젝트로 만들고 99 00:07:19,320 --> 00:07:23,400 코드를 이용하여 스페이스 바를 누를 떄마다 새를 위로 날게 만들겁니다 100 00:07:23,400 --> 00:07:29,760 이제 새에다가 Rigidbody2D 컴포넌트를 추가합시다. 101 00:07:29,760 --> 00:07:36,600 이 컴포넌트는 새를 물리 오브젝트로 만들어 줍니다. 그래서 Play 버튼을 누르면 새가 화면 밖으로 떨어집니다. 102 00:07:36,600 --> 00:07:42,279 이 새가 다른 오브젝트와 상호작용하게 만들려면 Collider가 필요합니다 103 00:07:42,279 --> 00:07:43,920 Circle Collider 2D를 추가합시다 104 00:07:43,920 --> 00:07:49,920 씬을 보면 초록색으로 된 선이 보입니다 중심이 약간 안맞기 때문에; 105 00:07:49,920 --> 00:07:55,260 오프셋을 조절해서 움직이겠습니다 여기서 게임 디자인 팁을 주자면 106 00:07:55,260 --> 00:08:00,720 Collider(판정)를 이미지보다 조금 작게 만들면 플레이어가 파이프에 아주 살짝 닿았더라도 107 00:08:00,720 --> 00:08:05,220 게임 오버가 되지 않기에 빡센 느낌이 줄어들게 됩니다 108 00:08:05,220 --> 00:08:10,920 마지막으로, 스크립트를 추가하겠습니다 스크립트를 활용하면 사실상 나만의 컴포넌트를 109 00:08:10,920 --> 00:08:16,680 만들 수 있게 되지만, 우리가 코드를 이용해서 직접 만들어야 합니다 컴포넌트 추가에서 New Script를 선택하고, 110 00:08:16,680 --> 00:08:21,780 이름은 BirdScript라고 하겠습니다. 만든 후, 스크립트를 더블클릭하면 111 00:08:21,780 --> 00:08:27,000 전에 설치한 Visual Studio가 열릴겁니다 112 00:08:27,000 --> 00:08:31,020 프로그래밍의 세계에 온걸 환영합니다! 너무 무섭게 생각하지 마세요; 113 00:08:31,020 --> 00:08:35,460 천천히 설명해드릴 겁니다. 저희는 C#으로 코드를 작성할겁니다 114 00:08:35,460 --> 00:08:41,340 먼저 Start와 Update, 이 2개에 대해 알아보겠습니다 115 00:08:41,340 --> 00:08:46,620 Start는 (이 스크립트가 활성화 돼있으면) 처음으로 실행되는 코드이고, 116 00:08:46,620 --> 00:08:52,620 단 한번만 실행됩니다. Update는 (이 스크립트가 활성화 돼있으면) 항상 실행되는 코드이고, 117 00:08:52,620 --> 00:08:57,360 매 프레임마다 코드가 실행됩니다. 118 00:08:58,080 --> 00:09:02,820 아무튼, 저희가 코드로 작성할 건 - Unity로 다시 돌아가보면 119 00:09:02,820 --> 00:09:08,340 컴포넌트에 있는 숫자들이나 텍스트를 마음대로 조절할 수 있죠 120 00:09:08,340 --> 00:09:13,320 저희는 코드를 이용하여 게임이 실행되는 동안 이것들을 조절할 겁니다 121 00:09:13,320 --> 00:09:17,880 간단한 예시를 들자면 122 00:09:17,880 --> 00:09:24,920 Start에 gameObject(오른쪽 위에 있는걸 의미함)를 치고, 점[.]을 치면 리스트가 보일겁니다 123 00:09:24,920 --> 00:09:25,920 *(리스트가 안보이면, Unity 위에 '창'->'패키지 관리자'->아래로 내려서 'Visual Studio Editor' 선택 후 설치 그후 Unity 위에 '편집'->'환경 설정'->'외부 툴'->Visual Studio 선택 후 전부 체크하고 재실행) 124 00:09:25,920 --> 00:09:30,720 이중 대부분은 인스펙터에 있는걸 나타냅니다 예를 들면 정적(isStatic); 125 00:09:30,720 --> 00:09:38,940 태그, 레이어, 이름이 있죠 name을 선택하고, [=] 쓰고, 큰따옴표[" "]로 126 00:09:38,940 --> 00:09:44,640 새의 이름을 넣으면 됩니다. 마지막으로, 명령 끝엔 항상 세미콜론[;]이 있어야 합니다 127 00:09:45,433 --> 00:09:49,573 그리고 스크립트를 저장하는것도 잊지 마세요 128 00:09:49,573 --> 00:09:54,360 이제 게임을 실행하면... GameObject의 이름이 바뀌였네요. 좋습니다 129 00:09:55,080 --> 00:09:58,560 네, 이제 그 코드는 지우세요. 방금 쓴 코드는 우리가 코드로 130 00:09:58,560 --> 00:10:03,120 게임과 대화할 수 있다는 걸 알려줍니다 저희는 코드를 작성하여 누구와 대화할지 131 00:10:03,120 --> 00:10:08,280 대상을 정할 수 있고, 대화의 주제도 정하고, 132 00:10:08,280 --> 00:10:14,160 그 후 명령을 정할 수 있습니다 저희는 이 짓을 많이 하게 될겁니다. 133 00:10:14,880 --> 00:10:19,740 아무튼 저희가 진짜로 해야할 일은, Rigidbody2D 컴포넌트에서 Info 아래에 보면 134 00:10:19,740 --> 00:10:23,400 Velocity가 회색으로 표시된게 보일겁니다 135 00:10:23,400 --> 00:10:27,120 저희는 위쪽으로 Velocity를 주는 코드를 작성해서 새가 하늘로 날 수 있게 만들겁니다 136 00:10:27,120 --> 00:10:32,640 문제는, 이 스크립트는 GameObject의 윗부분과 Transform끼리만 대화할 수 있고 137 00:10:32,640 --> 00:10:37,200 다른 컴포넌트의 존재도 모릅니다 138 00:10:37,200 --> 00:10:42,480 그래서 이 스크립트에 RigidBody2D를 위한 공간을 만들어야 합니다 139 00:10:42,480 --> 00:10:48,360 그러면 대화를 할 수 있게 되고 명령을 보낼 수 있게 되며 이를 '레퍼런스'라 부릅니다 140 00:10:48,360 --> 00:10:52,380 Class와 Start 사이에 레퍼런스를 작성할 겁니다: 141 00:10:52,380 --> 00:10:57,840 public RigidBody2D myRigidBody; 142 00:11:00,540 --> 00:11:06,480 이제 RigidBody2D를 보관할 공간이 생겼고, 다른 Rigidbody2D와 구분하기 위해 143 00:11:06,480 --> 00:11:12,060 공간의 이름도 정했습니다 public으로 정했기 때문에 스크립트 밖에서도 144 00:11:12,060 --> 00:11:18,240 공간을 건드릴 수 있습니다 저장하고 Unity로 돌아가면, 스크립트 컴포넌트에 145 00:11:18,240 --> 00:11:24,780 RigidBody2D를 위한 공간이 생긴걸 볼 수 있습니다 이제 RigidBody2D 컴포넌트를 빈칸에 드래그하면, 146 00:11:24,780 --> 00:11:28,740 이제 스크립트와 RigidBody간의 통신선이 생겼습니다. 147 00:11:29,340 --> 00:11:36,300 다시 Visual Studio로 돌아와서, Update에 myRigidBody 그리고 점[.]을 치면 148 00:11:36,300 --> 00:11:40,440 이 많은 대화 주제들을 보세요 angularDrag, gravityScale, mass; 149 00:11:40,440 --> 00:11:45,540 이것들이 전부 RigidBody2D에 있는 속성들입니다 우리가 원하는건 velocity죠 150 00:11:45,540 --> 00:11:51,060 전에 했던 것처럼 [=]을 쓰고, 151 00:11:51,060 --> 00:11:57,180 여기에서는 Vector라는 2D 공간에서 위치를 나타내는 값을 이용할 겁니다 152 00:11:57,180 --> 00:12:02,400 여기서 Vector는 새가 나는 방향을 나타내는데 사용할겁니다 153 00:12:02,400 --> 00:12:08,940 저희는 새가 위로 나는걸 원하기 때문에, (0,1)이 좋겠네요. 저는 Vector2.up을 이용할 겁니다 154 00:12:08,940 --> 00:12:14,400 *Vector2.up은 Vector2(0,1)과 같은 의미입니다 그리고 힘을 주려면 155 00:12:14,400 --> 00:12:19,920 Vector에 힘을 곱하면 됩니다 힘은 대충 10 정도면 충분할 겁니다 156 00:12:19,920 --> 00:12:24,720 그리고, 전에 말했지만, Update 안에 있는 코드는 매 프레임마다 실행되기 때문에 157 00:12:24,720 --> 00:12:31,560 저장하고 플레이 해보면... 영원히 날아갑니다 158 00:12:31,560 --> 00:12:36,360 하지만 저희가 원하는건 스페이스 바를 누를 때만 새가 날아가게 하는 것이죠 159 00:12:36,360 --> 00:12:41,040 이제 프로그래밍에서 가장 중요한 요소를 사용할 때입니다: 바로 if 입니다. 160 00:12:41,040 --> 00:12:43,380 if는 마치 관문과도 같습니다 161 00:12:43,380 --> 00:12:47,640 게임이 특정 조건을 만족하지 못한다면, 매 프레임마다 그 코드는 완전히 무시됩니다 162 00:12:47,640 --> 00:12:52,020 반대로, 관문에 적혀 있는 특정 조건을 만족한다면, 163 00:12:52,020 --> 00:12:56,700 통과할 수 있게 되고 코드가 실행됩니다. 164 00:12:56,700 --> 00:13:00,960 저희가 원하는건 "만약 플레이어가 스페이스 바를 누르면, 위쪽으로 velocity를 더한다"죠 165 00:13:00,960 --> 00:13:06,300 if를 적고 괄호 안에 조건을 적으면 됩니다 166 00:13:06,300 --> 00:13:12,360 여기서는 컴포넌트와 대화하는게 아니라 Unity와 대화하게 됩니다 정확히는 Unity의 인풋 시스템이죠 167 00:13:12,360 --> 00:13:20,160 (Input.GetKeyDown, 그리고 괄호 안에 KeyCode.Space) 이렇게 하면 Unity에게 168 00:13:20,160 --> 00:13:25,200 이 프레임에 스페이스를 눌렀는지 물어볼 수 있습니다 그리고 마지막에 ==true 로 마무리합시다 169 00:13:25,200 --> 00:13:31,020 참고로 등호를 1개[=] 만 사용하면 왼쪽에 있는 걸 170 00:13:31,020 --> 00:13:35,280 오른쪽과 같게 만들라는 의미이고, 2개[==]를 사용하면 왼쪽에 있는게 오른쪽에 있는것과 같은지 171 00:13:35,280 --> 00:13:38,520 여부를 확인하라는 의미입니다. 괜찮죠? 172 00:13:38,520 --> 00:13:43,740 아무튼, "만약 스페이스가 눌리면..." 173 00:13:43,740 --> 00:13:48,960 그 후는 중괄호{}를 사용해야 합니다. 중괄호 안에 위로 velocity를 주는 코드를 쓰면 됩니다 174 00:13:50,100 --> 00:13:54,240 이제 Update에서 - 매 프레임마다 게임이 관문에 가서 175 00:13:54,240 --> 00:13:59,340 "스페이스가 눌렸니?"라는 질문을 받은 후, 만약 맞다면 코드가 실행되고 새는 날게 되겠죠, 아니라면 176 00:13:59,340 --> 00:14:02,760 중괄호 안에 있는 코드는 무시하고, 다음 프레임에 다시 시도하게 될겁니다 177 00:14:02,760 --> 00:14:08,160 이제 저장하고 Unity로 돌아와서 플레이해보면, 짜잔: 178 00:14:08,160 --> 00:14:11,460 이제 스페이스를 누를 때마다 새가 위로 올라가네요 179 00:14:11,460 --> 00:14:17,340 이제 저희는 Input에 반응하는 캐릭터를 만들었습니다. 이것은 게임입니다. 좋아요! 180 00:14:17,340 --> 00:14:23,100 그런데, 조작감이 좀 쓰레기같네요 원작 플래피 버드와는 전혀 다른 느낌입니다 181 00:14:23,100 --> 00:14:31,860 그러니 숫자를 좀 바꿔볼까요 저장, 플레이, 그래도 별로네, 정지, 숫자 번경; 182 00:14:31,860 --> 00:14:36,540 저장,... 그런데 이건 너무 느리고 바보같네요 덜 바보같은 짓을 해봅시다 183 00:14:36,540 --> 00:14:41,520 먼저, 함수를 만들겁니다. 스크립트의 윗부분의 RigidBody 레퍼런스 바로 밑에 184 00:14:41,520 --> 00:14:47,160 public float flapStrength를 만듭시다 185 00:14:47,160 --> 00:14:51,960 float란 부동소수점(floating point number)를 뜻합니다. 간단히 말하면 소수점이 있는 숫자라는 의미입니다 186 00:14:51,960 --> 00:14:57,720 다시 Update로 돌아와서, Vector2.up에 10이 아니라 flapStrength를 곱할겁니다 187 00:14:57,720 --> 00:15:02,040 이제 스크립트 컴포넌트를 보면 새로운 공간이 보일겁니다: 188 00:15:02,040 --> 00:15:06,900 Flap Strength. 그리고 이건 언제든지 번경할 수 있습니다; 189 00:15:06,900 --> 00:15:11,340 심지어 플레이 중에도 번경할 수 있습니다 참고로 플레이 중 번경한 값은 190 00:15:11,340 --> 00:15:15,360 저장되지 않기 때문에, 게임의 중요한 요소를 다룰 때에도 191 00:15:15,360 --> 00:15:18,660 아무 숫자나 막 집어넣어도 상관없습니다 192 00:15:18,660 --> 00:15:22,680 그래서, flapStrength와 RigidBody의 중력 스케일이나 193 00:15:22,680 --> 00:15:26,700 질량을 적당히 조절하다 보면 언젠가는 좋은 값을 찾을 수 있을 겁니다 194 00:15:26,700 --> 00:15:29,640 숫자 계속 바꾸기, 이게 게임 디자인이죠 195 00:15:30,300 --> 00:15:31,440 잠깐 되돌아봅시다 196 00:15:31,440 --> 00:15:36,000 코드를 활용하여 플레이 중에도 컴포넌트의 속성을 바꿀 수 있다. 197 00:15:36,000 --> 00:15:40,860 스크립트는 기본적으로 gameObject에 있는 다른 컴포넌트들과 대화할 수 없고, 할려면 198 00:15:40,860 --> 00:15:45,000 원하는 컴포넌트의 레퍼런스(공간)를 만들어야 한다. 199 00:15:45,000 --> 00:15:49,860 레퍼런스(공간)는 코딩으로 만들고, 빈 공간은 Unity에서 드래그 앤 드롭으로 채운다 200 00:15:50,400 --> 00:15:56,280 Start에 있는 코드는 (스크립트가 존재할 때) 한번만 실행되고 Update에 있는 코드는 201 00:15:56,280 --> 00:16:01,920 매 프레임마다 계속 실행된다. 하지만 if를 사용하여 조건 미충족시 코드를 스킵할 수도 있다 202 00:16:01,920 --> 00:16:04,740 그리고 public을 사용하면 *(팁: [SerializeField]를 사용하면 private여도 Unity에서 값 바꿀수 있음) 203 00:16:04,740 --> 00:16:07,860 Unity의 인스펙터에서 값을 바꿀 수 있다. *(팁: [SerializeField]를 사용하면 private여도 Unity에서 값 바꿀수 있음) 204 00:16:09,480 --> 00:16:13,680 플래피 버드의 비밀을 하나 말씀드리자면 205 00:16:13,680 --> 00:16:18,180 새가 파이프를 지나는 것처럼 보이지만 206 00:16:18,180 --> 00:16:23,640 새는 가만히 있고 파이프가 움직이는 겁니다 그래서 이번에는 207 00:16:23,640 --> 00:16:28,620 파이프 생성, 그리고 파이프를 움직이게 하고 없애는것까지 배울겁니다 208 00:16:28,620 --> 00:16:32,280 우선 생성할 오브젝트를 만듭시다 209 00:16:32,280 --> 00:16:35,100 오른쪽에서 왼쪽으로 가는 파이프 2개를요 210 00:16:35,100 --> 00:16:40,500 빈 오브젝트를 만들고, 이름은 pipe로 합시다 일단은 파이프 크기를 맞추기 위해 211 00:16:40,500 --> 00:16:44,520 새 중앙에 놓겠습니다. 그 다음 pipe 안에 빈 오브젝트를 만들고 212 00:16:44,520 --> 00:16:50,700 이름은 top pipe로 하겠습니다 이런 걸 GameObject의 "자식"(child) 이라고 합니다 213 00:16:50,700 --> 00:16:55,080 자식을 이용하면 GameObejct 여러 개를 한 곳에 모와, "부모"를 움직이면 자식도 함께 움직이게 할 수 있습니다 214 00:16:55,980 --> 00:17:00,000 새를 만들 때 했었던 짓을 또 하겠습니다 이미지를 넣기 위해 Sprite Renderer를 추가하고 215 00:17:00,900 --> 00:17:06,840 판정을 위해 Box Collider 2D를 넣겠습니다 Rigidbody는 필요 없습니다 216 00:17:06,840 --> 00:17:13,440 물리의 영향을 받을 필요가 없으니까요. 파이프를 위로 올리겠습니다 - 단, X좌표는 0으로 하세요 217 00:17:13,440 --> 00:17:19,020 그 후, 이걸 복사하고 이름을 bottom pipe로 바꾼 후, 218 00:17:19,020 --> 00:17:24,420 180도 회전하고 아래로 내리겠습니다 219 00:17:24,420 --> 00:17:31,020 여기서 부모 GameObject를 움직여 보면, 파이프 2개가 부모를 중심으로 220 00:17:31,020 --> 00:17:35,400 동시에 움직이는걸 볼 수 있습니다 221 00:17:35,400 --> 00:17:38,940 이제 움직이게 하기 위해 부모에 스크립트를 추가하겠습니다 222 00:17:40,680 --> 00:17:46,080 우선 이동 속도를 위한 변수를 하나 만듭시다 여기에 값을 부여하면, 223 00:17:46,080 --> 00:17:50,700 Unity에서 그 값이 기본으로 설정됩니다 물론 Unity에서 언제든지 바꿀 수 있긴 합니다 224 00:17:50,700 --> 00:17:56,100 이제 Update에 오브젝트를 움직이는 코드를 작성합시다 여기서 225 00:17:56,100 --> 00:18:03,360 그냥 transform.position.x로 할 수 있다면 좋겠지만 아쉽게도 안됩니다 226 00:18:03,360 --> 00:18:08,580 Vector 전체를 한번에 바꿔야 합니다 그리고 이번엔 Vector2 대신 Vector3를 쓸 겁니다 227 00:18:08,580 --> 00:18:14,760 transform은 값 3개가 필요하기 때문이죠 이 게임은 2D이긴 하지만 228 00:18:14,760 --> 00:18:19,980 Unity는 근본적으로 3D 엔진이기 때문에 오브젝트의 Z값도 필요합니다 229 00:18:20,580 --> 00:18:26,160 아무튼, 움직이게 하려면 현재 위치에다가 다른걸 더해야 하기 떄문에 230 00:18:26,160 --> 00:18:31,200 transform.position = transform.position + 231 00:18:31,200 --> 00:18:37,320 그 후 괄호 안에 Vector3.left * moveSpeed; 232 00:18:39,000 --> 00:18:43,500 Unity로 돌아와서, 플레이를 누르면 너무 빠르네요 233 00:18:43,500 --> 00:18:47,520 그냥 moveSpeed에 엄청 작은 값, 234 00:18:47,520 --> 00:18:53,280 예를 들어 0.001을 넣어도 되긴 하지만 문제의 근본적인 원인은 그게 아닙니다 235 00:18:53,280 --> 00:18:58,860 Update에 있는 코드는 가능한 한 많이 실행됩니다 게임 뷰에서 통계를 보면; 236 00:18:58,860 --> 00:19:03,060 게임이 초당 1000프레임으로 구동되고 있는게 보이네요 237 00:19:03,060 --> 00:19:07,680 플레이스테이션 5에게 미안해지네요, 120프레임? 플래피 버드에겐 잽도 안되는군요 238 00:19:07,680 --> 00:19:11,880 아무튼 이게 중요한 이유는, 컴퓨터 사양에 따라 게임이 다른 속도로 구동될 가능성이 있다는 겁니다 239 00:19:11,880 --> 00:19:16,440 컴퓨터 사양에 따라 파이프 속도가 달라지면 안돼죠 240 00:19:16,440 --> 00:19:22,680 시장에 있는 게임에도 이런 실수가 종종 보입니다 - 다크 소울 2에선, 무기 내구도가 프레임과 241 00:19:22,680 --> 00:19:30,660 연관되어 있어서 60프레임에선 30프레임보다 내구도가 2배 빨리 닳았습니다 242 00:19:30,660 --> 00:19:36,420 해결법은 간단합니다 그냥 Time.deltaTime을 곱하면 됩니다 243 00:19:36,420 --> 00:19:41,280 이러면 프레임에 상관 없이 움직임이 일정한 간격으로 발생합니다 244 00:19:41,280 --> 00:19:44,820 Rigidbody에 이게 없어도 됐던 이유는 물리와 관련된 건 타이머가 내장되어 있기 때문이였고, 245 00:19:44,820 --> 00:19:49,320 그 외 상황에는 필요합니다. 이거(혹은 다른거 뭐든지)에 대해 알고 싶다면, 246 00:19:49,320 --> 00:19:53,580 Unity 사용자 매뉴얼을 확인해보세요 정보와 샘플 코드도 있습니다 247 00:19:53,580 --> 00:19:59,400 아무튼, 이제 파이프가 부드럽게 움직이네요 좋습니다 248 00:19:59,400 --> 00:20:04,560 다음으로, 파이프가 무한히 생성되는 시스템을 만들겠습니다 249 00:20:04,560 --> 00:20:10,740 우선 계층 구조에 있는 부모 GameObject를 프로젝트에 드래그하세요 250 00:20:10,740 --> 00:20:16,980 이러면 프리팹이 생성됩니다 프리팹은 이 GameObject 전체의 설계도와도 같죠 251 00:20:16,980 --> 00:20:22,500 그리고 프리팹을 이용하면 이 GameObject들의 바리에이션을 만들기 쉬워집니다 252 00:20:22,500 --> 00:20:26,460 프리팹을 만들었으면, 계층 구조에 있는건 지워도 됩니다. 253 00:20:26,460 --> 00:20:29,460 이제 새로운 빈 오브젝트를 만들고 이름을 PipeSpawner로 지읍시다 254 00:20:30,720 --> 00:20:36,240 카메라 영역 오른쪽에 놓고, 이제 스크립트를 만듭시다 255 00:20:36,240 --> 00:20:41,820 스크립트에는 매초마다 파이프 프리팹의 새로운 바리에이션을 생성하는 코드를 작성할겁니다 - 파이프가 왼쪽으로 움직이는건 256 00:20:41,820 --> 00:20:46,800 이미 전에 했기 덕분에, 파이프가 생성되면 자동으로 왼쪽으로 움직일겁니다 257 00:20:47,400 --> 00:20:51,360 방금 전에 만든 프리팹을 소환하는 코드를 작성할겁니다 258 00:20:51,360 --> 00:20:54,276 일단 프리팹의 레퍼런스부터 만들겠습니다 259 00:20:54,276 --> 00:20:58,680 여기에 public GameObject pipe; 를 작성합니다 260 00:20:58,680 --> 00:21:03,840 그 후 Unity에서 빈칸을 드래그로 채울겁니다. 이 빈칸은 261 00:21:03,840 --> 00:21:07,920 컴포넌트로 채우는게 아니라, 프리팹으로 채워야 합니다 262 00:21:07,920 --> 00:21:11,580 Unity에는 새로운 GameObject를 생성하는 기능이 내장되어 있기에 263 00:21:11,580 --> 00:21:17,880 그걸 써먹으려면, Instantiate, 그리고 괄호를 열면 264 00:21:17,880 --> 00:21:22,980 뭔가 자세히 입력하라고 뜨네요 화살표를 눌러 이 레시피? 같은걸 넘기다 265 00:21:22,980 --> 00:21:28,500 보면 - 4번이 좋아보이는군요 특정한 좌표와 기울기를 가진 GameObject를 생성한다고 하네요 266 00:21:28,500 --> 00:21:32,760 생성할 GameObject는 pipe고; 좌표는 267 00:21:32,760 --> 00:21:37,560 transform.position을 사용하면 이 스크립트가 달린 오브젝트의 268 00:21:37,560 --> 00:21:42,180 좌표를 구할 수 있기에 그걸 쓰면 되고, 기울기는 269 00:21:42,180 --> 00:21:46,800 같은 원리로 transform.rotation을 쓰면 됩니다 270 00:21:48,300 --> 00:21:52,740 플레이 해보면, 맙소사 너무 많이 소환되네요 소환은 되는데 271 00:21:52,740 --> 00:21:55,560 매 프레임마다 소환되고 있습니다 272 00:21:55,560 --> 00:21:59,760 작당한 간격을 두고 소환되게 만듭시다 - Visual Studio로 돌아와서 273 00:21:59,760 --> 00:22:05,100 타이머를 만들겁니다 274 00:22:05,100 --> 00:22:10,140 타이머가 끝나면, 코드 실행, 타이머, 같은 식으로요 275 00:22:10,140 --> 00:22:14,520 일단 변수 몇개를 만들어야 합니다 spawnRate는 소환 간 간격을 276 00:22:14,520 --> 00:22:19,560 나타내고, timer는 말 그대로 타이머입니다 277 00:22:19,560 --> 00:22:22,980 timer는 private로 작성할 겁니다 왜냐면 다른 스크립트에서도 timer를 쓰면 혼동이 오니까요 278 00:22:22,980 --> 00:22:29,400 Update에는, if를 작성할겁니다 만약 timer가 spawnRate보다 작다면: 279 00:22:29,400 --> 00:22:34,980 timer를 1만큼 올릴겁니다 그럴려면 280 00:22:34,980 --> 00:22:39,780 현재 timer에 time.deltaTime을 더하면 됩니다 이러면 매 프레임마다 timer가 올라가고 281 00:22:39,780 --> 00:22:42,600 컴퓨터 사양에 상관없이 타이머가 일정하게 올라갑니다 282 00:22:42,600 --> 00:22:48,480 이 과정은 += 로 줄일 수 있지만, 다른 사람한데 꼽사리 안받으려고 283 00:22:48,480 --> 00:22:53,040 코드 분량을 이 악물고 줄일 필요는 없습니다 284 00:22:53,040 --> 00:22:58,440 만약 timer = timer + ~ 가 읽기 더 편하다면 그렇게 쓰면 됩니다 285 00:22:58,440 --> 00:23:01,740 나중에 생각이 바뀌더라도 그냥 바꾸면 되니까요 286 00:23:01,740 --> 00:23:05,220 아무튼, 제가 전에 if는 관문과도 같단 말을 했었죠 287 00:23:05,220 --> 00:23:09,540 else를 이용하면 관문 옆에 또다른 관문을 만들 수 있습니다 288 00:23:09,540 --> 00:23:14,520 즉, if 조건을 만족하지 못하면 else에 있는 조건을 시도합니다 289 00:23:14,520 --> 00:23:22,560 이제 else 안에 파이프 생성 코드를 쓰고, timer를 0으로 리셋하는 코드도 쓰겠습니다. 이제 매 프레임마다 290 00:23:22,560 --> 00:23:28,500 timer가 spawnRate보다 작은지 확인하고, 만약 맞다면, timer 증가, 아니라면, 즉 291 00:23:28,500 --> 00:23:33,420 timer가 생성 주기와 같거나 높으면, 파이프를 생성하고 timer를 0으로 리셋. 292 00:23:33,420 --> 00:23:38,820 저장하고 플레이하면 - 잘 되네요 근데 한가지 문제점은, 293 00:23:38,820 --> 00:23:44,100 첫 파이프가 생성될때까지 한참 기다려야 된다는 점입니다 파이프가 시작하자마자 나오면 좋을텐데요 294 00:23:44,100 --> 00:23:49,560 파이프 생성 코드를 Start에 복붙하면, 시작하자마자 파이프가 나올겁니다 295 00:23:49,560 --> 00:23:55,320 그런데 이런 식으로 코드 전체를 복붙하는건 좋은 습관이 아닙니다 296 00:23:55,320 --> 00:24:00,900 게임을 만들 때는, 똑같거나 비슷한 코드를 온갖곳에 두는건 피해야 합니다. 이유를 말하자면, 297 00:24:00,900 --> 00:24:06,120 예를 들어 파이프 스폰 방식을 바꾸고 싶다고 하면 온갖곳에 있는 코드를 전부 일일히 바꿔야 할겁니다 298 00:24:06,120 --> 00:24:10,260 대신, 그 코드를 새로운 함수(function)에 넣고, 그 함수를 실행하면 됩니다 299 00:24:10,260 --> 00:24:15,180 Update 밑에, 마지막 중꽐호 위에, 300 00:24:15,180 --> 00:24:21,420 void spawnPipe()라는 함수를 만들고, 파이프 생성 코드를 안에 넣으면 되죠 301 00:24:21,420 --> 00:24:27,720 이제 원래 그게 있던 자리에 spawnPipe()만 넣으면 됩니다 302 00:24:27,720 --> 00:24:32,340 이제 spawnPipe()라는 코드가 실행될 때마다 아래에 있는 함수가 실행됩니다 303 00:24:32,340 --> 00:24:37,800 잘 되는지 확인해보면, 처음 시작할때 파이프 나오고, 타이머 지날때마다 생성되네요. 좋습니다 304 00:24:38,760 --> 00:24:44,700 그런데, 파이프가 항상 중간에 나오니 게임이 너무 단조롭네요 305 00:24:44,700 --> 00:24:49,080 파이프의 높이가 랜덤하게 나왔으면 좋겠는데; 파이프 생성 코드가 어떻게 되있었는지 기억나시나요? 306 00:24:49,080 --> 00:24:52,980 오브젝트의 생성 좌표를 지정할 수 있었던거 기억나나요? 그 값을 바꿉시다 307 00:24:52,980 --> 00:24:58,140 현재는 파이프가 항상 같은 좌표에 생성되고 있죠 308 00:24:58,140 --> 00:25:03,960 X좌표는 같아도 되니 냅두고, Y는... PipeSpawner 위 혹은 아래에 랜덤하게 생성되면 좋겠네요 309 00:25:03,960 --> 00:25:08,520 우선 heightOffset이라는 public 변수를 만듭시다 값은 10정도로 하죠 310 00:25:08,520 --> 00:25:14,220 그리고 이 안에 lowestPoint라는 float를 만들겁니다 함수 안에서 변수를 만들고 있기에, 311 00:25:14,220 --> 00:25:19,800 이 변수는 이 함수 안에서만 사용 가능합니다 312 00:25:19,800 --> 00:25:22,200 또한, ??? 313 00:25:22,200 --> 00:25:29,820 일단 lowestPoint = transform.position.y - heightOffset 그리고 314 00:25:29,820 --> 00:25:36,600 highestPoint도 만들겁니다 lowest와 같지만 - 대신 +를 넣으면 됩니다. 숫자 2개가 만들어졌으니 315 00:25:37,560 --> 00:25:41,400 이제 파이프 생성 코드에 있던 transform.position을 바꾸면 됩니다 316 00:25:41,400 --> 00:25:47,400 여기에 new Vector3를 입력할겁니다. Vector에 우리만의 숫자를 입력할 때마다 new를 317 00:25:47,400 --> 00:25:53,700 써야 합니다. 괄호 안에는 x,y,z좌표를 넣을 겁니다. x는, 318 00:25:53,700 --> 00:25:59,160 PipeSpawner의 x와 같아야 하기에 transform.position.x로 하고 319 00:25:59,160 --> 00:26:05,160 y는, Random.Range, 괄호 열고 최솟값과 최댓값을 320 00:26:05,160 --> 00:26:12,300 적으면 됩니다. 바로 lowestPoint와 highestPoint겠죠 그리고 z는 0으로 하면 됩니다. 321 00:26:14,220 --> 00:26:19,560 Unity로 돌아와서 해보면, 좋아요! 파이프가 두 값 사이에서 랜덤하게 생성되네요 322 00:26:19,560 --> 00:26:24,780 아, 마지막으로, 파이프가 생성된 후 왼쪽으로 가다가 323 00:26:24,780 --> 00:26:30,120 ...계속 가네요. - 좋지 않습니다 화면 밖에서 아무것도 안하고 324 00:26:30,120 --> 00:26:35,100 메모리만 잡아먹고, 너무 많이 생성되면 325 00:26:35,100 --> 00:26:40,140 파이프가 모니터 밖으로 튀어나와 책상을 더럽힐 수도 있습니다 326 00:26:40,140 --> 00:26:45,060 고칠려면, 타이머를 이용해 일정한 시간이 지나면 파이프를 삭제하는것도 되지만, 327 00:26:45,060 --> 00:26:50,700 저희는 파이프의 x좌표를 확인하여 일정 좌표를 넘어가면 그 파이프가 삭제되도록 할겁니다 328 00:26:50,700 --> 00:26:58,260 새를 이용하여 화면 왼쪽의 x좌표를 재겠습니다 저는 -45정도네요 329 00:26:58,260 --> 00:27:06,120 PipeMove 스크립트에서, public float deadZone = -45, 그리고 간단한 if문; 330 00:27:06,120 --> 00:27:13,020 if (transform.position.x < deadZone){ Destroy(gameObject);} 331 00:27:15,720 --> 00:27:18,840 Unity에서 실행해보면, 잘 사라지네요 332 00:27:18,840 --> 00:27:24,420 딱 한개만 더 합시다 333 00:27:24,420 --> 00:27:32,880 334 00:27:32,880 --> 00:27:39,420 335 00:27:39,420 --> 00:27:44,820 336 00:27:44,820 --> 00:27:50,280 337 00:27:51,900 --> 00:27:52,800 338 00:27:52,800 --> 00:27:55,560 339 00:27:55,560 --> 00:27:58,980 340 00:27:58,980 --> 00:28:04,020 341 00:28:04,020 --> 00:28:08,940 342 00:28:08,940 --> 00:28:12,660 343 00:28:12,660 --> 00:28:17,400 344 00:28:17,400 --> 00:28:21,540 345 00:28:21,540 --> 00:28:26,520 346 00:28:26,520 --> 00:28:30,960 347 00:28:32,400 --> 00:28:35,820 348 00:28:35,820 --> 00:28:39,360 349 00:28:39,360 --> 00:28:43,380 350 00:28:43,380 --> 00:28:47,460 351 00:28:47,460 --> 00:28:52,560 352 00:28:52,560 --> 00:28:57,540 353 00:28:57,540 --> 00:29:01,260 354 00:29:01,260 --> 00:29:06,600 355 00:29:06,600 --> 00:29:11,520 356 00:29:11,520 --> 00:29:16,680 357 00:29:16,680 --> 00:29:21,660 358 00:29:21,660 --> 00:29:28,260 359 00:29:28,260 --> 00:29:34,200 360 00:29:34,200 --> 00:29:40,140 361 00:29:40,140 --> 00:29:44,700 362 00:29:46,620 --> 00:29:49,080 363 00:29:49,080 --> 00:29:53,280 364 00:29:53,280 --> 00:29:56,640 365 00:29:56,640 --> 00:29:59,820 366 00:29:59,820 --> 00:30:02,940 367 00:30:03,960 --> 00:30:08,820 368 00:30:08,820 --> 00:30:13,860 369 00:30:13,860 --> 00:30:18,180 370 00:30:18,180 --> 00:30:22,740 371 00:30:22,740 --> 00:30:27,600 372 00:30:27,600 --> 00:30:30,360 373 00:30:30,360 --> 00:30:34,980 374 00:30:34,980 --> 00:30:40,680 375 00:30:40,680 --> 00:30:46,320 376 00:30:46,320 --> 00:30:52,500 377 00:30:52,500 --> 00:30:58,680 378 00:30:59,280 --> 00:31:04,680 379 00:31:04,680 --> 00:31:09,540 380 00:31:09,540 --> 00:31:16,020 381 00:31:16,020 --> 00:31:20,460 382 00:31:20,460 --> 00:31:25,009 383 00:31:25,740 --> 00:31:30,840 384 00:31:30,840 --> 00:31:39,240 385 00:31:39,240 --> 00:31:45,720 386 00:31:45,720 --> 00:31:51,720 387 00:31:52,800 --> 00:31:56,580 388 00:31:56,580 --> 00:32:02,820 389 00:32:05,340 --> 00:32:10,140 390 00:32:10,800 --> 00:32:14,460 391 00:32:15,060 --> 00:32:18,660 392 00:32:18,660 --> 00:32:23,220 393 00:32:23,220 --> 00:32:28,380 394 00:32:28,380 --> 00:32:33,060 395 00:32:33,060 --> 00:32:37,740 396 00:32:37,740 --> 00:32:42,000 397 00:32:42,000 --> 00:32:45,180 398 00:32:45,180 --> 00:32:50,700 399 00:32:50,700 --> 00:32:55,920 400 00:32:55,920 --> 00:33:00,900 401 00:33:00,900 --> 00:33:06,420 402 00:33:06,420 --> 00:33:13,920 403 00:33:13,920 --> 00:33:18,960 404 00:33:18,960 --> 00:33:25,620 405 00:33:25,620 --> 00:33:30,060 406 00:33:30,060 --> 00:33:34,440 407 00:33:34,440 --> 00:33:41,340 408 00:33:41,340 --> 00:33:45,840 409 00:33:45,840 --> 00:33:51,060 410 00:33:51,060 --> 00:33:56,880 411 00:33:56,880 --> 00:34:01,320 412 00:34:01,320 --> 00:34:05,520 413 00:34:05,520 --> 00:34:08,340 414 00:34:08,340 --> 00:34:12,720 415 00:34:12,720 --> 00:34:16,800 416 00:34:16,800 --> 00:34:23,820 417 00:34:23,820 --> 00:34:30,060 418 00:34:30,060 --> 00:34:34,680 419 00:34:34,680 --> 00:34:37,380 420 00:34:37,380 --> 00:34:46,740 421 00:34:46,740 --> 00:34:50,820 422 00:34:50,820 --> 00:34:54,420 423 00:34:54,420 --> 00:34:57,900 424 00:34:57,900 --> 00:35:02,280 425 00:35:03,480 --> 00:35:09,300 426 00:35:09,300 --> 00:35:13,800 427 00:35:13,800 --> 00:35:19,020 428 00:35:19,020 --> 00:35:22,740 429 00:35:22,740 --> 00:35:28,200 430 00:35:28,860 --> 00:35:33,960 431 00:35:35,400 --> 00:35:41,340 432 00:35:41,340 --> 00:35:47,340 433 00:35:47,340 --> 00:35:52,860 434 00:35:52,860 --> 00:35:57,660 435 00:35:57,660 --> 00:36:03,900 436 00:36:03,900 --> 00:36:10,620 437 00:36:10,620 --> 00:36:15,480 438 00:36:15,480 --> 00:36:20,400 439 00:36:22,560 --> 00:36:27,494 440 00:36:27,494 --> 00:36:33,420 441 00:36:33,420 --> 00:36:38,520 442 00:36:39,240 --> 00:36:45,360 443 00:36:45,360 --> 00:36:50,160 444 00:36:50,160 --> 00:36:54,900 445 00:36:54,900 --> 00:36:58,980 446 00:36:58,980 --> 00:37:03,120 447 00:37:03,120 --> 00:37:07,440 448 00:37:07,440 --> 00:37:12,660 449 00:37:12,660 --> 00:37:14,040 450 00:37:14,040 --> 00:37:17,880 451 00:37:17,880 --> 00:37:22,920 452 00:37:22,920 --> 00:37:25,680 453 00:37:25,680 --> 00:37:29,760 454 00:37:29,760 --> 00:37:34,080 455 00:37:34,080 --> 00:37:36,660 456 00:37:36,660 --> 00:37:42,300 457 00:37:42,300 --> 00:37:46,740 458 00:37:46,740 --> 00:37:50,940 459 00:37:50,940 --> 00:37:56,580 460 00:37:56,580 --> 00:37:59,400 461 00:38:01,020 --> 00:38:06,900 462 00:38:06,900 --> 00:38:11,340 463 00:38:11,340 --> 00:38:16,320 464 00:38:16,980 --> 00:38:21,060 465 00:38:21,060 --> 00:38:28,440 466 00:38:30,960 --> 00:38:36,900 467 00:38:36,900 --> 00:38:41,580 468 00:38:43,440 --> 00:38:48,000 469 00:38:48,000 --> 00:38:53,520 470 00:38:53,520 --> 00:38:58,680 471 00:38:59,520 --> 00:39:02,100 472 00:39:02,100 --> 00:39:07,080 473 00:39:07,080 --> 00:39:12,060 474 00:39:12,060 --> 00:39:17,580 475 00:39:17,580 --> 00:39:22,560 476 00:39:23,940 --> 00:39:30,180 477 00:39:30,180 --> 00:39:35,460 478 00:39:35,460 --> 00:39:42,720 479 00:39:44,580 --> 00:39:48,000 480 00:39:48,960 --> 00:39:53,340 481 00:39:57,300 --> 00:40:02,580 482 00:40:02,580 --> 00:40:07,560 483 00:40:07,560 --> 00:40:12,000 484 00:40:13,380 --> 00:40:16,260 485 00:40:16,260 --> 00:40:22,620 486 00:40:22,620 --> 00:40:27,750 487 00:40:27,900 --> 00:40:30,116 488 00:40:31,467 --> 00:40:38,611 489 00:40:38,820 --> 00:40:42,300 490 00:40:42,300 --> 00:40:48,180 491 00:40:48,180 --> 00:40:52,680 492 00:40:52,680 --> 00:40:57,720 493 00:40:57,720 --> 00:41:03,360 494 00:41:03,360 --> 00:41:10,277 495 00:41:10,277 --> 00:41:16,440 496 00:41:16,440 --> 00:41:21,420 497 00:41:21,420 --> 00:41:28,380 498 00:41:28,380 --> 00:41:34,320 499 00:41:34,320 --> 00:41:38,340 500 00:41:38,940 --> 00:41:44,160 501 00:41:44,160 --> 00:41:49,080 502 00:41:49,080 --> 00:41:54,480 503 00:41:54,480 --> 00:42:01,200 504 00:42:01,200 --> 00:42:06,360 505 00:42:06,360 --> 00:42:11,400 506 00:42:11,400 --> 00:42:17,040 507 00:42:17,040 --> 00:42:24,360 508 00:42:24,360 --> 00:42:31,500 509 00:42:31,500 --> 00:42:35,400 510 00:42:35,400 --> 00:42:39,960 511 00:42:39,960 --> 00:42:43,980 512 00:42:43,980 --> 00:42:48,660 513 00:42:48,660 --> 00:42:53,700 514 00:42:53,700 --> 00:42:55,573 515 00:42:55,573 --> 00:43:00,060 516 00:43:00,060 --> 00:43:05,220 517 00:43:05,220 --> 00:43:10,440 518 00:43:10,440 --> 00:43:15,660 519 00:43:15,660 --> 00:43:21,420 520 00:43:21,420 --> 00:43:27,559 521 00:43:27,559 --> 00:43:31,674 522 00:43:31,674 --> 00:43:37,260 523 00:43:37,260 --> 00:43:41,160 524 00:43:41,160 --> 00:43:43,860 525 00:43:43,860 --> 00:43:48,360 526 00:43:48,360 --> 00:43:51,900 527 00:43:51,900 --> 00:43:55,020 528 00:43:55,020 --> 00:44:00,300 529 00:44:00,300 --> 00:44:05,700 530 00:44:05,700 --> 00:44:11,220 531 00:44:11,220 --> 00:44:15,600 532 00:44:15,600 --> 00:44:19,320 533 00:44:19,320 --> 00:44:22,800 534 00:44:22,800 --> 00:44:25,740 535 00:44:25,740 --> 00:44:30,540 536 00:44:30,540 --> 00:44:35,400 537 00:44:35,400 --> 00:44:40,620 538 00:44:40,620 --> 00:44:45,120 539 00:44:45,120 --> 00:44:48,840 540 00:44:48,840 --> 00:44:53,280 541 00:44:53,280 --> 00:44:58,320 542 00:44:58,320 --> 00:45:03,480 543 00:45:03,480 --> 00:45:07,500 544 00:45:07,500 --> 00:45:11,580 545 00:45:11,580 --> 00:45:15,060 546 00:45:15,060 --> 00:45:19,200 547 00:45:19,200 --> 00:45:20,940 548 00:45:20,940 --> 00:45:25,680 549 00:45:25,680 --> 00:45:30,600 550 00:45:30,600 --> 00:45:35,223 551 00:45:35,223 --> 00:45:38,879 552 00:45:38,879 --> 00:45:46,366 553 00:45:46,366 --> 00:45:52,279 554 00:45:52,279 --> 00:45:54,540 555 00:45:54,540 --> 00:45:58,680 556 00:45:58,680 --> 00:46:04,380 557 00:46:04,380 --> 00:46:10,380 558 00:46:10,380 --> 00:46:14,040 559 00:46:14,040 --> 00:46:17,460 560 00:46:17,460 --> 00:46:22,140 561 00:46:22,140 --> 00:46:25,440 562 00:46:25,440 --> 00:46:30,540