Pakage Manager - Unity Registry - Visual Scripting를 설치해 비주얼 스크립팅 플러그인을 다운받을 수 있다.
게임 오브젝트에 script Machine 컴포넌트를 추가해 비주얼 스크립트를 사용할 수 있다.
이후 그래프를 추가하여 일반 스크립트의 흐름도 같이 사용할 수 있다.
우클릭으로 흐름도의 노드를 추가하여 문장을 만들 수 있다.
에러가 나면 콘솔에 오류가 뜨고 그래프 내에도 빨간색으로 표시된다.
변수의 영역은 Graph, Object, Scene, App, Saved로 나뉘어진다.
좌측의 Variables를 추가해 드래그 앤 드롭으로 끌어와서 Get Variable을 할 수 있다.
Set Variable은 우클릭 후 검색해서 사용 가능하다.
Script Machine의 Source를 Graph로 하면 Graph에 종속되고 Embed로 하면 해당 컴포넌트에 종속된다.
그래프를 만들고 다른 그래프에 서브 그래프로 넣어서 함수 같이 사용할 수 있다.
Event와 Trigger를 설정해 코루틴도 사용이 가능하다.
상태 머신
Script Graph는 상태 머신으로 Animator의 Transition과 같이 조건에 의한 상태의 변화의 흐름도를 작성할 수 있는 그래프이다.
조건에서 여러 개의 동작을 같이 수행할 때 좋다.
일정 거리 이내일 때 쫓아오는 모습
플레이 영상
고전게임 '플래피 버드' 모작
- 타이틀 씬과 게임씬 구현
- Input 이벤트와 파티클 구현
- 씬마다 사운드 구현
- 버튼 구현
한 주 후기
이번 주는 비주얼 스크립팅인 State Machine과 Script Machine에 대해 배웠다. 비주얼 스크립팅은 빠르게 프로토타입을 만들거나 프로그래머가 비개발자에게 간단하게 흐름을 보여주기 위해 사용한다고 한다. 나도 지금 사용 방법을 배워놨으니 나중에 쓸 일이 있지 않을까 하고 열심히 배웠다.
배우는 동안 새로운 작업 방식에서 힘든 일이 꽤 있었는데 먼저 Variables의 종류(Graph, Object, Scene, App, Saved)에 따라 변수가 사용될 수 있고 없다는 것을 이해하는 게 어려웠다. 예를 들어 Graph는 스크립트 같이 해당 graph를 가지고 있는 오브젝트는 하나의 graph를 수정하면 같이 영향을 받는다는 것이고 Object는 그 한 오브젝트 안에서만 종속되는 것에 대해 사용해보면서 익히는 것이 어려웠던 것 같다. 처음엔 여기 저기 사용도 해보고 한 씬에서 전역변수처럼 사용하려고 Scene변수에 다 넣어도 보고 이러다가 다음 씬으로 넘어갈 때 다시 짜기도 해보고 그러면서 익혔다. 그래서 생각보다 더 많은 기능을 추가할 시간이 부족했던 것 같다. 익히는 시간은 좀 더뎠지만 이제 확실히 이해했으니 다음엔 더 많은 기능을 추가해보도록 노력해야겠다.
다음 주엔 어떤 내용을 배울지 기대가 된다. 다음 주도 집중을 잃지 않고 열심히 배우도록 노력해야겠다. 다음 주도 파이팅!
스크립터블 오브젝트는 컴포넌트에 붙일 수 없다. (MonoBehavior를 상속받지 않기 때문)
이런 식으로 간접적으로 참조는 가능하다.
플레이 영상
한 주 후기
이번 주는 수업을 들으면서 토이 프로젝트를 만드는 시간과 발표하는 시간이 있었다. 스크립터블 오브젝트라는 개념을 처음 배웠는데 외국 유투브 영상에서만 아 이런식으로 사용하는구나라고 봤었지 실제로 사용해보는 것은 처음이라 재미있었다. 나중에 RPG 프로젝트를 할 때 장비에 적용해보는 것도 고민해봐야겠다.
좀 어려웠던 부분은 스크립터블 오브젝트를 쓰면서 습득한 순서대로 키보드 숫자에 맞게 무기를 스왑하는 기능을 만들었는데 처음에 초기 설정값을 맞추는 게 쉽지 않았다. enum타입과 int 배열로 순서를 맞춰서 해결했는데 잘 동작하였다.
발표는 첫 번째로 하게 되었는데 항상 하고나면 별 거 아니지만 이전에 긴장하게 되는 것 같다. 여러 번 발표하면서 익숙해지는 게 제일 좋은 방법이라고 생각이 들었다.
다음 번에는 하나의 프로젝트를 만들 때 끝까지 완성할 수 있도록 해봐야겠다. 다음 주도 파이팅!
이후에 안에서 폰트를 적용한 후 Text칸에 복사해주면 된다. 이전의 사진에 Solid라고 써져있으면 Solid 폰트를 사용해야 한다.
이를 적용한 화면이다.
미니맵
RenderTexture와 카메라를 만들고 카메라로 미니맵 구도를 잡은 후 RenderTexture에 입힌다.
Canvas와 Image를 만들고 이미지를 적절한 위치에 이동시킨 후 마스크 컴포넌트를 붙인다.
이후 그 하위 오브젝트에 Raw Image를 붙이고 이전에 미니맵 카메라가 찍은 RenderTexture를 붙이면 된다.
한 주 후기
이번 주 수업은 로그인 창을 만들어 외부 이미지 텍스트를 가져오는 것과 미니맵 만드는 것을 배웠다. 텍스트에 이미지 폰트를 넣을 수 있다는 점은 몰랐는데 새로운 방법을 알게 되어서 좋았고 Render Texture에 대해서는 알고 있었지만 이것을 활용하여 동적으로 움직이는 간단한 미니맵을 만들 수 있다는 것을 알게되어서 좋았다. 수업 진도가 끝나고 나 혼자서 버그를 고쳐보는 시간이 있었는데 이동 하는 중에 캐릭터가 덜덜 떨리는 버그가 있었다. 처음엔 코드 상의 문제인 줄 알고 계속 수정해보고 했으나 쉽지 않았다. 이것 저것 만져보다 RigidBody의 Freeze Rotation의 x축을 끄고 나선 이 문제가 해결되었다. 아마 velocity를 사용하여 앞으로 움직일 때 넘어지다가 원래 상태로 다시 돌아올려고 해서 문제가 생겼던 것 같았다. 이렇게 나 혼자서 프로젝트 퀄리티 향상을 위해 버그를 고치거나 새로운 것을 추가하는 작업을 자주 해야겠다는 생각이 들었다.
이번 주는 수업을 듣고 나서 추석에 본가를 내려가느라 차가 막혀서 조금 피곤했다. 틈틈히 유데미 강의를 잘 시청하여 흐름이 끊기지 않게 해야겠다. 다음 주도 파이팅!
Magica Voxel은 포토샵 툴과 비슷한 인터페이스 구조를 가지고 3D 픽셀 모델링을 할 수 있다.
모델링이 끝난 후 우측 하단에 있는 Object 타입으로 Export하면 3개의 파일이 생성된다.
이후에 이 세 개의 파일을 유니티에 넣어주면 된다.
* 이것들을 다 넣어주지 않으면 기본색인 회색 빛이 되어서 수동으로 Material을 지정해줘야 하는 경우가 생긴다.
defaultMat의 Material을 palette로 바꿔주면 해결된다.
과제 내용
Magica Voxel을 이용하여 캐릭터 2명 이상, NPC 2명 이상, 외부 건물, 내부 건물, 아이템 3개 이상을 만드는 것이다.
아래 사진은 내가 만든 외부 건물과 캐릭터들이다.
아래 사진은 내부 건물과 아이템들이다. * 바나나 집
한 주 후기
이번 주 수업은 내가 사용할 에셋들을 내가 만들어보는 시간이어서 좋았다. 사실 모델링적인 부분이나 이런 것들은 내가 만들 일이 없을 것 같았는데 이번에 경험해보니 어렵기도 하고 막상 해보면 몰입하게 되고 꽤나 재밌었던 시간이었다.
모델링한 패키지들을 받아와서 Size Factor들을 조절하고 Material을 입히는 것을 새로 배울 수 있었다. 어려웠던 점들 중에 하나가 이 가져온 캐릭터의 애니메이션을 적용하는 게 쉽지 않았던 것이다. 나는 어도비사의 Mixamo라는 애니메이션 생성 사이트를 이용했는데 애니메이션이 잘 적용되려면 캐릭터들의 팔 다리가 기본적으로 길어야 한다는 것을 깨닫고 다시 늘리고 비율 맞추는 작업을 여러번 진행했던 것 같다. 확실히 내가 만든 것들이 모델링적인 부분에서 퀄리티가 떨어지는 게 느껴졌다. 디테일적인 부분을 신경 쓰면 밑도 끝도 없을 뿐만 아니라 그런 센스가 있는 편도 아니라 나중에 제대로 만들게 된다면 먼저 여러 영상들을 참고하여 따라해보면서 실력을 길러야겠다는 생각이 들었다.
한 주 한 주 지내면서 새로운 것을 배울 때마다 '아직 부족하구나'라는 생각과 '이런 게 있었어?' 하는 호기심이 생기는 것 같다. 미래의 발전된 나를 위해서 긍정적으로 생각하고 실력을 기르는 시간이 더 필요한 것 같다. 다음주도 화이팅하자!
2. 플랫폼의 자식 스케일을 조정하여(부모의 콜라이더 크기와 같게) 여기에 Mesh Renderer 컴포넌트를 붙인다.
3. OnCollisionEnter에서 태그로 체크하여 Ground를 공의 부모로 만들어주면 공의 모양이 깨지거나 하는 일이 없어진다.
ProGrid, ProBuilder
ProGrid는 씬에 가상의 격자무늬를 만들어 오브젝트를 설정한 간격으로 이동하게 만들 수 있다.
ProBuilder는 간단한 프로토타입을 만들 때 자주 쓰이는 패키지인데
여러가지 도형을 만들 수도 있을 뿐만 아니라 UV 에디터를 사용하여 포토샵으로 수정한 메시를 다시 입힐 수도 있다.
플레이 영상
이동과 점프는 AddForce로 구현하였다.
아래 움직이는 플랫폼에 닿았을 때는 포스트프로세싱 Voulme과 Layer를 사용하여 푸른색 효과를 주었다.
흐르는 듯한 용암은 Texture에 Wrap mode를 repeat로 두고 코드로 Material offset을 움직여서 만들었다.
한 주 후기
이번 주는 복습하는 느낌으로 공 굴리기 프로젝트를 강사님을 따라 만들어 보았다. 움직이거나 점프, 움직이는 플랫폼, 포스트 프로세싱 등을 제외하면 교육생들 본인이 자유롭게 만들 수 있는 시간이 주어졌다. 그래서 지금까지 배웠던 것들을 다시 복습하고 내가 놓친 부분을 다시 돼새기는 시간이 되었던 것 같아서 좋았다.
이번 주에는 위에서 적어놓은 것처럼 유니티에서 부모 오브젝트와 자식 오브젝트 간의 관계를 이용하여 트랜스폼에 영향을 주고 안 주고에 대해 새로 배웠고 이후에 자습 시간을 통해 유데미 강의인 'C#과 Unity로 3D 게임 개발하기'에서 '아르곤 침공'의 우주선을 만들 때 복습하는 시간을 가질 수 있었다. 복습하면서 중간에 놓친 부분 중 하나는 용암의 Offset을 조정하는 단계에서 '왜 계속 같은 모양만 보이지?' 하고 있었는데 용암 Texture의 Wrap mode가 Repeat가 아니고 Clamp여서 그랬던 거였다. 이렇게 복습하는 기회가 없었다면 나중에 좀 더 고생을 했을 거라고 생각이 들었다. 사실 지금까지 배웠던 내용들이 많은 만큼 까먹거나 이해를 했다고 해도 막상 내가 생각한 방식대로 써먹지 못할 때가 생길 수도 있다고 생각한다. 그래서 수업시간뿐만 아니라 개인적으로도 복습하는 시간을 많이 가져야겠다고 생각했다.
유니티는 기본적으로 Standard라는 쉐이더형을 제공해서 알아서 물리적인 연산을 처리해준다.
쉐이더 파일
- 쉐이더 파일은 프로젝트 폴더에 우클릭하여 필요한 쉐이더 종류를 선택하고 생성하면 된다. (이 글에서는 Unlit Shader를 선택하였다 - UI 쪽에 사용되는 쉐이더)
- 쉐이더 파일에서 Material 부분의 Shader의 경로를 지정할 수 있는데 아래 사진의 파란색 Shader 뒷 부분의 경로가 해당 쉐이더 파일의 경로이다(자신이 수정 가능).
- Properties는 인스펙터 창에서 조절할 수 있는 것들을 적는 부분이다.
- SubShader Tags 에는 렌더 타입을 정할 수 있다.
- Pass를 여러개 작성하면 여러 효과도 줄 수 있지만 하나당 드로우콜이 발생하여 성능에 과부하를 준다.
- Pass의 CGPROGRAM과 ENDCG는 이 사이에 Cg언어를 사용하겠다는 표시다.
- #pragma는 vertex와 fragment 쉐이더에 어떤 이름의 함수로 지정할 것인가를 정의한다.
- struct type의 vertex, fragment에서 필요한 매개변수들을 초기화하고 실제로 사용할 변수들(sampler2D~float - 위에서 매칭한 것들)까지 선언해준다.
- 이후 vert에서 버텍스를 연결해주고 frag에서 색을 입혀주면 쉐이더가 만들어진다.
위에서의 결과물
- vert의 o.uv = o.uv + _Time;에서 움직이는 텍스처를 만들어줬다.
- frag의 return c - lerp~에서 위의 _DissolveTextrue의 빨간색 삼각형 부분을 빼줬다.
과제 내용
- 첫 번째 과제는 버튼을 눌렀을 때 PlayOneShot을 사용하여 여러번 클릭하더라도 중복으로 소리가 안 들리게 하는 것이다.
- 두 번째 과제는 Capture버튼을 눌렀을 때 UI를 제외하여 캡처하면서 텍스처 해상도는 반으로 줄이는 것이다. - 짝수행과 짝수 열만 출력되게 옮기기
한 주 후기
이번 주는 쉐이더에 대해 배웠다. 나는 Material을 이용할 줄만 알지 쉐이더에 대한 건 전혀 알지 못했었는데 이번 교육을 들으면서 Shader의 구조도 알아보고 사용법도 간단하게 알게 되었다. 사실 처음 쉐이더 강의를 듣게 되었을 때는 조금 어려운 느낌이 들었다. 지금까지 교육을 들으면서 고비가 2번 있었는데 UI강의를 들을 때(UI 종류가 너무 많았다) 이번에 쉐이더를 들으면서 cg언어라는 새로운 언어를 접하고 안에 경험해보지 못한 형식에 따라 코드를 작성하는 부분이 어렵게 느껴졌던 것 같다. 그런데 긍정적으로 따라가다 보니까 대략적으로나마 이런식으로 사용하는구나! 하고 이해할 수 있게되어 다행이었다.
이번 주 과제를 진행하면서 첫 번째 과제인 사운드 부분은 쉽게 해결할 수 있었는데 두 번째 과제인 텍스처의 해상도를 줄이는 작업은 쉽지 않았던 것 같다. 특히나 위 사진의 짝수 행과 짝수 열만 옮기는 로직을 생각하는 게 과제를 받은 날의 저녁 늦게까지 하면서 고민했던 문제였다. 최근에 그래도 테스트를 위해 알고리즘 문제를 여럿 풀어봤던 것이 도움이 되었는지 생각을 비우고 다음날 몇 번 적어서 테스트를 해보니까 '이렇게 쉽게 해결할 수 있던 거였어?' 할 문제였던 것 같다. 정답이라고 할 순 없지만 해결을 했다는 것에 뿌듯함을 느낄 수 있었던 과제였다.
이번 주는 스타터스 조기 인턴으로 인한 내부 테스트와 동기들 회식으로 정신 없었던 한 주였던 것 같다. 테스트를 보고나서 내가 어느 부분이 얼마나 부족한지 객관적으로 판단하는 계기가 되었고 동기들과의 회식을 참여하면서 동기들과 더 친해질 수 있는 기회가 되었다. 다음 주도 재밌게 수업 들어야겠다. 파이팅!
위 사진은 조이스틱인데 rect transform 개념도 그렇고 절대적 상대적 위치에 대한 개념에서 많이 헷갈렸던 것 같다.
Input.OnDrag에서 받은 값에서 background 좌표를 뺸 벡터(이동한 벡터 값)에서 background 반지름에서 handler 반지름을 뺀 벡터(파란 부분)로 나누어주어 비율을 계산하고 최대 크기를 1로 고정하여 이를 handle의 position으로 적용시켜준 것이다.
mapbox 사이트에서 맵을 가져올 수 있는데 맵에 커스텀 스킨을 입히거나 좌표를 변경하거나 해서 가져올 수도 있다. 나중에는 AR 게임에 대해서도 배운다는데 기대가 된다.
과제 내용
- Velocity over Lifetime으로 시간에 따른 원 크기, 각도 조절
- Emission과 maxParticles로 파티클 갯수 조절
- Color over Lifetime으로 시간에 따른 색 바꾸기
- Size over Lifetime으로 시간에 따른 크기 조절
한 주 후기
이번 주는 닷지 토이프로젝트를 클론코딩하는 시간을 가졌다. 닷지 게임 자체가 되게 단순하고 만들기 어렵지 않을 것이라 생각했지만 rect transform의 앵커, 피벗 부분이나 Canvas와 Canvas 밖의 스프라이트(플레이어, 총알)가 다른 좌표를 사용하는 게 어려웠다. 조이스틱을 구현하는데 그냥 transform.position과 transform.anchoredPosition의 차이가 이해하는데 꽤 걸렸다. 간단한 닷지 프로젝트 하나에도 여러가지 구현들이 적용되는구나 하고 느꼈다.
클론코딩을 하면서 내가 구현한 방식은 이거였는데 이런 식으로 구현하면 더 효율적으로 구현할 수 있구나 (ex : 반환형 함수를 사용해서 전달하여 굳이 전역 변수를 선언하지 않아도 되는 경우 등) 라는 걸 많이 배운 것 같다. 나중엔 매개변수와 반환형 함수를 적절히 사용하여 굳이 불필요한 메모리 낭비나 종속성을 피하는 방향으로 가야겠다고 생각했다.
이번 주에는 비가 너무 많이와서 정신 없었지만 그래도 한 주 동안 많은 것을 배웠다. 이후로도 배운 것을 복습하면서 내재화시키는 노력을 해야겠다. 다음주도 파이팅!
- 오브젝트 클릭 후 Window- animation - animation에서 애니메이션을 생성할 수 있다.
- 더블클릭하면 키 프레임가 생성됨
- 녹화버튼 누르고 씬에서 수정시 키프레임이 생성됨
- 기본은 Dopesheet
아래의 Dopesheet 대신 Curves를 선택하면 사진처럼 프레임의 높이를 조절해 연결을 부드럽게도 만들 수 있다.
애니메이션 이벤트
애니메이션의 작대기 버튼의 이벤트를 프레임에 생성하여 해당 오브젝트 안의 함수를 실행할 수도 있다.
애니메이터
애니메이터는 실제 오브젝트에 추가되는 조건에 따른 애니메이션을 보여주는 컴포넌트다.
- 조건 변수인 Parameters가 있음
- Transition으로 조건을 설정 가능함
- 한 애니메이션에서 화살표(Transition)로 연결된 다른 애니메이션으로만 접근 가능함
- Any State에 연결된 애니메이션은 조건 충족시 Entry에 실행과 상관 없이 실행됨
애니메이터 레이어
추가 레이어를 사용하여 기존 애니메이션을 덮어 쓰거나 다른 애니메이션과 가중치를 정해 섞는다던가 할 수 있다.
ex) 뛰는 애니메이션이 있고 공격 버튼을 눌렀을 때 팔만 공격 모션으로 덮어쓰고 싶은 경우
트랜지션
애니메이터의 화살표 부분으로 조건에 따른 실행 여부나 다음 애니메이션으로의 연결을 다룬다.
- Condition은 화살표 끝방향 애니메이션의 실행 조건임
- Has Exit Time 조절으로 다음 애니메이션이 부드럽게 이어지거나 현 애니메이션이 다 끝난 후 다음 애니메이션이 실행되는 등을 정할 수 있다.
코드에서 애니메이터 다루기
Animator로 접근하여 코드에서 애니메이션 파라미터 값을 바꿀 수도 있다.
포스트 프로세싱
카메라의 필터처럼 보는 화면에 효과를 부여하는 기술이다.
- 패키지 매니저에서 설치가 가능하다.
Post-process Layer
어떤 것만 선택해서 효과를 보여줄 지, 어떤 걸 트리거로 설정해서 포스트 프로세싱을 적용할지를 결정하는 컴포넌트이다.
Post-process Volume
생성한 Profile을 기준으로 어떤효과를 줄 지 정할 수 있다.
- Is Global을 체크하여 월드에 효과를 줄 지 아니면 범위로만 효과를 줄 지 정할 수 있음
- Blend Distance로 거리에 따라 서서히 효과를 줄 수 있음
- Weight로 효과에 대한 전체적인 값을 조정이 가능함
- Priority로 우선 적용 값을 넣을 수 있음
파티클
Particle System이라는 컴포넌트로 오브젝트에 파티클 효과를 부여할 수 있다.
- 모양, 주기, 속도, 갯수 등등 여러가지를 정할 수 있음
과제 내용
내 캐릭터를 선택한 후 적이 피를 전부 잃을 때까지 각도와 힘을 조절하여 포탄을 쏘는 게임이다.
- UI와 Sprite로 제작하여 World Space 공간에서 UI와 게임 화면을 구분해 제작
- 각도와 파워가 적용된 총알 발사
- 좀비 위의 hp UI가 다 줄었을 때 씬 재시작
한 주 후기
이번 주에는 애니메이션, 애니메이터, 포스트 프로세싱, 파티클에 대해서 배웠다. 내가 직접 애니메이션의 키프레임을 찍어서 애니메이션을 만들어 보기도 하고 그 애니메이션을 여러개 만들어서 애니메이터에서 조건에 따라 실행해 보기도 하고 마지막으로 코드에서 이를 조작하는 것까지 할 수 있게 되었다. 또 포스트 프로세싱을 적용하여 공포게임 같은 연출을 보여줄 수도 있고 파티클로 눈이 내리는 연출 같은 것을 만들 수 있다는 걸 알게 되었다.
이번 주 과제를 만들면서 UI 카메라와 게임화면 카메라를 구분하여 두는 것이 제일 어려웠는데 '카메라가 왜 두 개나 필요하지?' 라는 의문과 '그렇다면 어떤 방법으로 그 화면들을 구분하지?' 하는 의문이 해결되지 않았었다. 결국엔 이것 저것 건드려보다가 답을 찾았는데 첫 번째 카메라는 Culling Mask를 UI만 체크하여 UI 요소들만 찍게 하고 Depth only를 사용하여 다른 배경들은 투명하게 만든 다음, 두 번째 카메라는 Culling Mask를 UI를 제외한 나머지 요소들을 체크하면 첫 번째 카메라는 UI 카메라로 게임 진행 중에 게임 화면 위에 떠 있어야 하는 파워 게이지 같은 요소들을 보여주고 두 번째 카메라는 게임 카메라로 총알을 따라다니고 다른 오브젝트들을 비춰주게 함으로써 둘의 사용이 구분될 수 있었다.
이번 주의 과제도 배운 내용을 가지고 잘 적용할 수 있는 시간이 되었으면 좋겠다. 포스트 프로세싱이나 애니메이션, 파티클 시스템 모두 눈으로 보이는 재미있는 효과들을 다루기 때문에 과제를 제작하는 시간이 즐거울 것이라 예상하고 있다. 또한 이번 주 수업은 미니 프로젝트들을 만들어 보는 것인데 추가적으로 질문할 거리도 많고 '이런식으로 만들어지는 거였어?' 하는 생각이 드는 등 수업도 기대가 된다.
다음 주도 꾸준하게 이해가 될 때까지 질문하고 고민하여 완벽히 습득하는 시간이 됐으면 좋겠다. 다음 주도 파이팅!
이번 주에는 유니티 생애주기, 싱글톤 패턴과 옵저버 패턴, UI에 대해서 배웠다. 유니티 생애주기가 왜 이런 순서로 되어있고 프로그래밍을 할 때 이 생애주기를 참고하면 된다고 배웠다. 싱글톤 패턴은 게임 매니저와 같이 객체들을 관리하는 객체를 따로 두어 유일한 객체로 만들고 이 객체에 대한 접근은 어디서든 할 수 있게 하는 패턴이다. 옵저버 패턴은 subject가 object가 하는 일들을 대신 호출해주고 object는 subject로만 함수를 실행하게 되는 패턴이다. 옵저버 패턴은 싱글톤 패턴과 자주 혼용되어 사용된다고 배웠다. UI는 팝업 창, 버튼 등등 유저가 상호작용할 수 있는 것들인데 유니티의 내장 UI와 TextMesh Pro, Extension UI도 설치해 사용해 보는 시간을 가졌다.
이번 주에 배우면서 느낀 점은 UI요소들이 너무 많다는 점이었다. UI를 배우는 것 자체가 코드 보다는 일단 다 씬에 배치해보고 어떤 기능인지 아는 게 중요한데 수가 많다보니까 시간이 조금 더 걸린 것 같았다. 또 Rect transform 개념이 이해하기 쉽지 않았는데 앵커에서 축끼리 min과 max가 같으면 포인트가 되고 너비를 줄 수 있고, min max가 다르면 범위가 되어 마진 값을 줄 수 있다고 생각하니까 이해가 되었다. pivot 부분도 헷갈렸는데 pivot은 좌측 아래부터 x와 y값이 있는 로컬 기준점이라고 생각하니 이해가 되었다.
이번 주에 배운 UI들은 많기도 하고 내가 모르는 부분이 더 많은 느낌이었다. 되게 간단한 버튼 UI나 Text UI만 다룰 수 있었는데 다른 여러가지 UI들도 계속 사용해봐야지 용도를 이해할 수 있는 것들이기에 앞으로 계속 써봐야겠다는 생각이 들었다. 이번에 과제를 할 때도 기본 과제와 심화과제의 UI를 다른 방식으로 사용해서 제출하는 것을 목표로 하고 있는데 이것으로 UI 적용 부분에서 다른 상황에도 적용할 수 있는 유연성을 가졌으면 좋겠다.
UI까지 배우면서 이렇게 계속 진행하면 뭔가 만들 수 있을 것 같다는 생각이 들었다. 내가 성장하는 느낌이 들고 이게 많은 양의 지식을 습득하는 데 긍정적인 영향을 주는 것 같아서 기분이 좋았다. 기본 과제부터 심화 과제까지 정복하여 잘해지고 싶은 마음 뿐이다. 다음 주도 화이팅 하자!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameObjectExample : MonoBehaviour
{
// Start is called before the first frame update
[SerializeField]
private GameObject goPrefab;
void Start()
{
// 1. 오브젝트 생성
// PrimitiveType.미리 정의된 오브젝트들
// 0,0,0에 생성됨
// GameObject go = GameObject.CreatePrimitive(PrimitiveType.Cube);
// PrimitiveType.Capsule;
// PrimitiveType.Cylinder;
// PrimitiveType.Plane;
// PrimitiveType.Quad;
// PrimitiveType.Sphere;
// 2. 오브젝트 파괴
// Destroy(go);
// Destroy(go, 3f);
// 커스터마이징 사용할 때 아니면 사용 X
// DestroyImmediate(go);
// 3. 오브젝트의 활성화 여부 확인
// 본인이 체크되어있냐 (부모 활성화에 상관 없이 체크면 true 체크해제면 false)
// 자식이 켜있고 부모가 꺼져있을 때
// ex) preload 미리 불러오기 외부로부터 동적으로 생성 - SetActive 켜져있음
// 이후 부모를 끄는 상태가 있음
// Debug.Log(go.activeSelf);
// 하이어라키상에서 활성화 되어있는지 여부를 알려줌 (부모 비활성화시 비활성화시 체크 false 체크해제 false)
// Debug.Log(go.activeInHierarchy);
//4. Tag 확인
// GameObject의 꼬리표(표식)
// 한 오브젝트 당 하나만 가능
// Array GameObject[] 가 있을 때
// foreach를 돌려서 go.tag == "Player"; go.CompareTag("tag"); 등 물리 엔진을 다룰 때 사용
// 프로젝트를 이관할 때 Export Pakage와 폴더 전체를 전송하는 경우 중 각 폴더만 Export하면
// Project Setting(태그, 등등) 이 포함이 안 된다.
// export 패키지는 중간중간 필요한 리소스를 받을 때 사용한다.
// Jenkins는 파일 / 셋팅, 깃허브 연동 => 프로젝트를 서버에서 빌드
// 소스코드를 GIT, SVN, Mercurial, Collaborate 버전관리툴을 사용해서 올린다.
// 젠킨스로는 바뀐 부분만 올려짐 세팅 같은 건 바꿀 수 없게 세팅을 해놓음
// boolean 반환
// Debug.Log(gameObject.CompareTag("GOExample"));
// Find는 하이러키 창의 위에서부터 찾음
//5. Tag로 검색 - 해당 태그를 가지고있는 오브젝트 저장
// GameObject goWithTag1 = GameObject.FindWithTag("Player");
// GameObject goWithTag2 = GameObject.FindWithTag("Finish");
// Debug.Log(goWithTag1 != null);
// Debug.Log(goWithTag2 != null);
// Transform.Find("");
// 자식들만 가져올 수 있는데 Active false도 가져올 수 있음
// FindWithTag("");
// GameObject.Find("");
// SetActive가 꺼진 애들은 가져오지 못함
// GetComponent(Active or not Active).gameObject();
// 선택해서 가져올 수 있다.
//6. 이름으로 검색
GameObject goWithFind1 = GameObject.Find("FindWithName");
// GameObject goWithFind2 = GameObject.Find("FindWithName2");
// Debug.Log(goWithFind1 != null);
// Debug.Log(goWithFind2 != null);
// 사용자가 정의한 게임오브젝트 생성
GameObject goTest = Instantiate(goPrefab);
goTest.name = "InstanceSphere";
goTest.transform.parent = goWithFind1.transform;
goTest.transform.localPosition = Vector3.zero;
goTest.transform.localRotation = Quaternion.identity;
goTest.transform.localScale = Vector3.one;
}
void Update()
{
}
}
트랜스폼
TransformExample.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TransformExample : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
// Transform 반환
Transform tr1 = transform.Find("FindInChild");
Transform tr2 = transform.Find("FindInChild2");
// GameObject go1 = new GameObject();
// Transform과 GameObject는 상호참조관계
// Transform trG = go1.transform;
// GameObject goT = tr1.gameObject;
Debug.Log(transform.GetChild(0).name);
// tr1의 오브젝트를 맨 밑으로 보내겠다.
tr1.SetAsLastSibling();
Debug.Log(transform.GetChild(0).name);
// tr1의 오브젝트를 맨 위로 보내겠다.
tr1.SetAsFirstSibling();
Debug.Log(transform.GetChild(0).name);
// tr1을 index(1)번으로 보내겠다.
// 언제쓰냐 GetComponents 다 가져올 때 특정 순서를 가져옴 Getcomponent는 첫 번째 것 가져옴
// Sprite (0,0,0) (0,0,0) 두 개가 있을 때 하이어러키 상에서 맨 아래에 있는게 제일 앞에 보임 (z축은 의미가 없음)
// => 인덱스 0번부터 그리기(렌더링하기) 때문에
tr1.SetSiblingIndex(1);
// tr1의 부모 이름 가져오기
Debug.Log(tr1.parent.name);
// 최상위 것 가져옴
// tr1.root
}
void Update()
{
// 부모 자식 관계에서 자식 스크립트에서 transform.position은 월드 좌표 기준 position이다.
// 컴파일 에러
// transform.position은 Vector3이다. Vector는 struct(값에 의한 복사)이므로 못 씀
// transform.position.x += 0.01f;
// 부모 자식 관계에서 자식 스크립트에서 transform.rotation은 월드 좌표 기준 rotation이다.
// Vector3 vecP = transform.localPosition;
// vecP.x += 0.01f;
// transform.localPosition = vecP;
// Vector3 vecR = transform.localRotation.eulerAngles; -> Vector3
// vecR.x += 0.01f;
// transform.localRotation = Quaternion.Euler(vecR);
// lossyScale은 root 스케일(월드) localScale은 로컬 스케일(로컬)
// Vector3 vecS = transform.lossyScale;
// vecS.x += 0.01f;
// 에러
// transform.lossyScale = vecS;
// Euler 3축 Quternion 4축
// Update는 초당 30번 실행된다. 30fps
// 1Unit = 1M
}
}
컴포넌트
ComponentExample.cs
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class ComponentExample : MonoBehaviour // 컴포넌트 상속
{
void Start()
{
// MonoBehaviour를 상속받는 클래스들은 new로 생성하면 안 된다.
// GameObject를 생성하고(이미 있을 시 그냥) AddComponent로 컴포넌트를 붙여야한다.
// 변수로 컴포넌트를 선언하면 asrc.~ 등 바로 접근이 가능하다.
AudioSource asrc = transform.AddComponent<AudioSource>(); // Camera, GameObject (MonoBehaviour)를 상속하는 것이라면 가능
// 이미 있으면 Getcomponent로 이 클래스에 붙어있는 컴포넌트를 가져옴
AudioSource asrc2 = transform.GetComponent<AudioSource>();
transform.GetComponent<Component>();
// 아래 방식으로 Active가 꺼진 애들을 가져올 수 있다.
GameObject go = new GameObject();
GameObject.Find("");
go.transform.Find("");
go.GetComponentInChildren<MeshRenderer>(true);
MeshRenderer[] meshArray = go.transform.GetComponentsInChildren<MeshRenderer>(true);
foreach (var mesh in meshArray)
{
mesh.enabled = false;
}
Component[] coms = transform.GetComponentsInChildren<Component>(true);
AudioSource[] asrcArray = transform.GetComponentsInChildren<AudioSource>(true);
}
void Update()
{
}
}
카메라
// Camera
// Clear Flags는 배경을 뭘로 할거냐 SkyBox, Solid Color,
// Depth Only 카메라의 뒷배경은 투명 - UI, Sprite, Video, WebPage ex) 로그인창 => 3rd party Library, 내부카메라 WebPage
// 이 때 기존 카메라에 UI 띄우는 경우도 있는데 최적화 Draw Call 문제와 UI.Object가 카메라에 같이 비치는 문제가 있다.
// => 웬만하면 UI 카메라와 게임 카메라를 분리를 한다.
// Don't Clear는 (Blit Clear가 클리어를 하는데 전체 프레임을 한 번 지워준다) => 이 클리어를 안 하겠다는 거임
// Culling Mask - Layer 개념 비슷
// 1번 카메라는 오브젝트만 비추고 2번 카메라는 캐릭터만 비추고 싶을 때 Post Processing(후처리 과정) ex) 사진 필터
// 캐릭터에만 효과를 주고싶을 때 Layer와 Culling Mask를 사용
// Projection
// - Perspective : 원근 적용
// FOV(FiledOfView) : Vertical/Horizontal 60도만큼 화면을 찍어서 가져옴
// - Orthographic : 원근 적용 X (카메라 각도가 직각)
// Size : Unity Unit 1(1m) 중점 기준으로 a만큼 사이즈가 잡힘(높이) 이에 대해 가로는 해상도에 따라 달라짐
// 16:9일 때 세로가 5라면 세로는 5/9*16이 됨
// - Clipping Planes
// Near : 제일 가까운 부분 (~보다 가까워지면 안 보임)
// Far : 가시거리 (~가 넘어가면 안 보임)
// - ViewportRect
// X, Y 는 좌측 아래를 기준으로 0~1의 위치
// W, H 는 화면의 0~1만큼의 크기
// TargetTexture
// Render Textrue를 만들고 타겟 텍스처에 넣고 Material 알베도 부분에 입혀서 오브젝트에 입히면
// CCTV같이 입혀진다.
// Occlusion Culling
// Occlusion 계산은 CPU를 사용 Occlusion 가리고 안 가리고는 GPU를 사용
// 겹쳐보이는 부분이 있을 때 안 그려줌
// 인스펙터 위의 static 옆에 Occluder (가리는 애), Occludee (가려지는 애) 잘 선택해서 체크
// GPU와 CPU에 사용량에 따라 사용
// Occulsion -> Yellow box 형성 기준
// 1. Occlusion Area
// 2. Dynamic Area
// 파란색 박스가 unit을 나누는 기준
씬, 벡터 , Input, 물리
씬
SceneExample.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneExample : MonoBehaviour
{
// 씬을 관리하는 경우
// 1. 실제로 게임상/기능상 화면전환/기능전환
// 2. 리소스 관리 차원 -> 던전분리 ex) RPG Dungeon이동
// 3. OpenWorld RPG - Object Pooling, LOD 관리
// 1번 씬에서 2번씬으로 이동시 1번 씬에 있던 오브젝트는 모두 삭제됨 (Additive를 안 했다고 가정)
void Start()
{
DontDestroyOnLoad(this.gameObject); // 씬을 로드를 했을 때 파라미터 부분을 Destroy 안 하겠다.
// File - Build Setting - Scenes In Build에 씬을 드래그앤드롭으로 추가해야함
// 인덱스를 통해서 불러오는 방식
// SceneManager.LoadScene(1);
// 씬의 이름을 통해서 불러오는 방식
// 비슷할 때 풀 경로로 써주면 됨
// SceneManager.LoadScene("2");
// SceneManager.LoadScene("2", LoadSceneMode.Single);
// Scene 1이 살아있는 상태로 불림
// 뒤에 생긴 카메라가 앞에 씬 카메라 덮어씌워짐(UI 설명할 때 처럼 한 번 더 그림)
// 웬만하면 Addtive 씬에는 카메라를 안 넣음 (일부러 겹치고 카메라를 사용해야될 때만 사용)
SceneManager.LoadScene("Scenes/2", LoadSceneMode.Additive);
}
}
벡터
Day5.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
public class Day5 : MonoBehaviour
{
// Vector -> struct 방향 / 크기
// △x △y 방향
Vector3 vec = new Vector3(0,0,0);
Vector3 vec2 = new Vector3(10,10,10);
float currentVel;
// 배열 선언 : 한정자 자료형[] 변수이름 = new 자료형[사이즈];
private Vector3[] vecArray = new Vector3[3];
// 리스트 : 동적 배열 List<자료형/클래스/구조체> 변수명 = new List<앞과 같음>();
private List<Vector3> vecList = new List<Vector3>();
// 딕셔너리 : Dictionary<키의 자료형/인스턴스형, 값의 자료형/인스턴스형> 변수명 = new Dictionary<앞과 동일>();
private Dictionary<string, Vector3> vecDictionary = new Dictionary<string, Vector3>();
private float prev;
private float threshhold = 2;
// Start is called before the first frame update
void Start()
{
// 벡터의 크기
Debug.Log(vec2.magnitude);
// 벡터 정규화 (0~1)사이
// normailized되지만 vector2의 값이 바뀌지는 않음
Debug.Log(vec2.normalized);
// 값이 바뀌는 건 함수
// vec2.Normalize()
// vec2.Scale()
//Vector3.one; (1, 1, 1)
//Vector3.zero; (0, 0, 0)
//Vector3.left; (-1, 0, 0)
//Vector3.right; (1, 0, 0)
//Vector3.up; (0, 1, 0)
//Vector3.down; (0, -1, 0)
//Vector3.forward; (0, 0, 1)
//Vector3.back; (0, 0, -1)
// 두 벡터 사이의 거리를 잼
// ex) 충돌 처리
Debug.Log(Vector3.Distance(vec, vec2));
Transform obj = transform;
Transform obj2 = transform;
Vector3.Distance(obj.position, obj2.position);
// 간단한 충돌처리 각 오브젝트에 가상의 원을 씌움
// 각 원의 센터를 그림
// 반지름 r1, r2일 때
// 원이 겹치는 부분 계산 중심의 거리 < r1 + r2 이면 충돌
vecList.Add(vec2);
vecList.Add(vec);
vecList.Add(vec);
vecList.Add(vec);
// List의 한도값 - 갯수 한정 (따로 안 줬을 때 기본 값은 메모리가 계속 두 배로 늘어남)
// vecList.Capacity = 10;
// ~을 포함하는지 확인하고 bool 값을 반환
// vecList.Contains(vec);
// vecList의 1번 인덱스에 vec값을 넣음 (인덱스 1번부터 우측으로 밀려남)
// vecList.Insert(1, vec);
// vec을 앞에서부터 찾아서 없앰
// vecList.Remove(vec);
// (LINQ)
// 나중에 배움
// 자바의 Map 개념이랑 비슷
// 사전 : 단어, 뜻 Dictionary : 키(key), 값(value)
// vecDictionary.Add("Test", vec);
// Vector3 o = vecDictionary["Test"];
// vecDictionary.Add("vec2", vec2);
// 참조는 없으면 에러 뜰 수 있음
// vecDictionary["Test"].Normalize();
// 대입은 권장하지 않음 => 원래 값이 있거나 키가 null 값일 수 있기 때문
// vecDictionary["vec"] = vec;
// 갖고있는 키를 다 반환
// vecDictionary.Keys
Debug.Log(vecList[0]);
Debug.Log(vecDictionary["vec"]);
}
// Update is called once per frame
void Update()
{
var currentVel = Vector3.one;
// Lerp(Linear Interpolation) 선형 보간
// time 값에 따라 vec과 vec2 사이의 거리를 반환(0~1)
Debug.Log(Vector3.Lerp(vec, vec2, 0.5f));
// clamp는 값을 잘라놓은 것(그냥lerp) unClamp 잘라놓지 않은 것
// 값에 비례해서 자름(-1이면 거리의 -1배만큼 vec에서 거리만큼 전으로 계산해서 반환)
// Vector3.LerpUnclamped(vec, vec2, -1f);
// 2D게임을 만들 땐 Vector3.Lerp 대신 Mathf.Lerp를 써도 쓸만함
// (0~1) 비례해서 0.5만큼의 거리만큼 계산
// Mathf.Lerp(vec.x, vec2.x, 0.5f);
// 50f라는 실제 값을 넣었을 때 비율을 반환
// vec.x가 0이고 vec2.x가 100일 때 50을 넣었다치면 그 비율인 0.5가 나옴
// Mathf.InverseLerp(vec.x, vec2.x, 50f);
// ex) vec과 vec2 사이에 2초만에 도착하고 싶을 때
// transform.position = Vector3.Lerp(vec, vec2, Time.time * 0.5f);
// (Mathf.Sin(Time.time) + 1) / 2) -> 0부터 1 => vec, vec2 사이를 왔다갔다함
// transform.position = Vector3.Lerp(vec, vec2, (Mathf.Sin(Time.time) + 1) / 2);
// 위의 Sin 함수를 쓴 것과 같은 효과
// Sigmoid를 사용 활성함수 0과 1으로 가까워질수록 변화값이 작고
// 0.5로 가까워질수록 변화값이 크다.
// transform.position = Vector3.SmoothDamp(vec, vec2, ref currentVel, Time.deltaTime);
// Debug.Log(Vector3.SmoothDamp(vec,vec2, ref currentVel, Time.deltaTime));
}
}
Input
// Input
private float prev;
private float threshhold = 2;
//Update문 안에서
// 현재 마우스 위치
// vec2 = Input.mousePosition;
// 0:좌클릭, 1:우클릭, 2:휠버튼
// Input.GetMouseButton(0)
// 엔터는 KeyCode.Return을 씀
// KeyPadEnter는 우측 숫자패드의 엔터이다.
// GetKey, GetMouseButton 등 받으면
// 키보드 이벤트 3가지
// 눌렀을 때, 누르고 있을 때, 누른걸 땠을 때
// Input.GetKeyDown(KeyCode.L); // 눌렀을 때
// Input.GetKey(KeyCode.L); // 누르고 있을 때
// Input.GetKeyUp(KeyCode.L); // 누른걸 땠을 때
// Update문에서 GetKey는 프레임당 한번 씩 받음(캐릭터 이동 등)
// 한 번만 받아야되면 GetKeyDown이나 GetKeyUp 사용
// 위와 같음
// Input.GetMouseButtonDown(0);
// Input.GetMouseButton(0);
// Input.GetMouseButtonUp(0);
// esc버튼인데 모바일로 가면 backButton이다.
// Input.GetKey(KeyCode.Escape);
//Input.GetKey(KeyCode.A);
//Debug.Log(Input.GetKey(KeyCode.A));
//Input.GetMouseButton(0);
//0 for left button, 1 for right button, 2 for the middle button
//Input.GetKeyDown(KeyCode.A);
//Input.GetKeyUp(KeyCode.A);
// 기기에 있는 가속도계 => 핸드폰 기울이는 로직을 사용할 때 씀
// Input.acceleration
// 기기 방향
// Input.compass
// 기기 회전 값을 잡을 때
// Input.gyro
// 기기 터치 관련
// Input.touches
// 모바일 멀티터치 index 0 1 2 3 4 ~~
// 터치에 따라 리스트가 구현이 되어있음
Touch t = Input.GetTouch(0);
Touch t2 = Input.GetTouch(0);
// 멀티 터치 갯수를 반환
// Input.touchCount;
// 위와 동치 touches는 멀티터치를 배열로 갖고있음
// Input.touches.Length;
// 터치 이동한 만큼 델타 벡터를 계산
// t.deltaPosition;
// 줌인 줌아웃
if (Input.touchCount == 2)
{
if (Mathf.Abs(Vector2.Distance(t.position, t2.position) - prev) > threshhold) return;
if (prev > Vector2.Distance(t.position, t2.position))
{
//pinch in
}
if (prev < Vector2.Distance(t.position, t2.position))
{
//pinch in
}
prev = Vector2.Distance(t.position, t2.position);
}
물리
FixedUpdateEx.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FixedUpdateEx : MonoBehaviour
{
// 유니티 엔진, 렌더링 엔진, 물리 엔진
// 옛날엔 3D 밖에 지원을 안 했음 => 원래는 3D 물리 엔진만 지원했음(PhysX)
// static
// 3D rigidbody
// Kinematic : 물리법칙 받지 X but 충돌처리는 됨 ex) 맵
// Dynamic : 물리법칙 O ex) 플레이어
// 2d UGUI 나중에 2D물리엔진 지원 (Box2d)
// 강체, static, dynamic, kinematic(3D의 Static과 동치)
// 2D rigidbody
// Kinematic - AddforceV에는 움직이지는 않는데 간접적인 힘에는 움직임
// Static : 3D Kinematic과 동치 - 아예 움직이지 않음
// Dynamic - 다 움직임
// Rigidbody는 물리의 영향을 받아야 될 때 붙이면 됨
// transform.position.x 조작은 Render 엔진 부분이라 static이어도 이동하긴 함
// velocity.x 조작은 이동 안 함
// Animation의 움직임이 기본적으로 transform이나 물리엔진 이동부분을 override함
// 두 객체의 충돌구현
// 적어도 둘 중 하나는 rigidbody를 가져야함
// collider는 둘 다 가져야함
// rigidbody info에서 속도나 이런 거 확인 가능
// mass : 질량
// drag : 위치에 대한 저항
// Angular Drag : 각도에 대한 저항(회전 저항)
// Use Gravity : 중력 체크
// Is Kinematic : 물리 체크
// Interpolate : 충돌 검출은 프레임별로 일어나는데 전의 프레임을 참고함
// Extrapolate : 충돌 검출에 다음 프레임을 참고함
// 객체의 Collider에 줄 수 있고
// Project Setting에서 Physics 부분에 넣으면 모든 물체에 기본으로 적용됨
// Physics Material
// - Dynamic Friction : 움직이고 있을 때 마찰력
// ex) 공이 굴러가고 있을 때 마찰력
// - Static Friction : 멈춰 있을 때 마찰력
// ex) 다른 공에 부딪혔을 때 멈춰있던 공의 마찰력
// - Bounciness : 탄성 계수
// - Friction Combine : 두 개체 사이의 마찰력
// -- averge 평균을 내겠다 minimum 작은 쪽의 마찰력을 내겠다
// -- Maximum 더 큰 값을 내겠다 multiply 곱해서 내겠다
// Bounce Combine : 두 개체 사이의 탄성력
// -- averge 평균을 내겠다 minimum 작은 쪽의 마찰력을 내겠다
// -- Maximum 더 큰 값을 내겠다 multiply 곱해서 내겠다
// Physics
// Default Material
// 기본 Physics Material
// Bounce Threshold
// 다른 물체에 부딪혔을 때 부딪치는 물체의 속력이 이 값보다 낮을 시 탄성 작용 없음
// Sleeping Threshold
// Sleep 모드일 때 일정 값보다 큰 힘이 작용했을 때 Awake 상태가 되는 기준 값
// Default Contact Offset => 얼마를 붙어있는 걸로 기준을 잡을래?
// contact는 모서리의 버텍스를 가져오기 때문에 각 모서리의 길이보다 짧으면 붙었다고 가정
// Queries Hit Backfaces
// 면에 대해서 hit을 감지하는데 뒷면에서 올 때도 감지할거냐?
// Enable Adaptive Force
// 가중치 ex) 당구 벽에 공 겹쳤을 때 하중
// Cloth Gravity
// 천(옷) 중력
// Collision Detection
// - Discrete : 고정
// - Continuous : 더 잦은 충돌 검출 (부하up)
// ex) 유저가 벽뚫기 하는 경우에 씀
// - 속도가 빠를 때(deltapos가 클 때)
// - Collider 빈 부분이 있거나
// - Collider 검출이 안 되거나
// Contraints - 6개 다 키면 Kinematic과 동치
// - Freeze Position 앞으로만 움직여야 되면 다른 좌표 값을 체크(고정됨)
// - Freeze Rotation 달려가다가 옆에서 쳤을 때 주사위 굴러가듯이 넘어가면 Rotation을 막아버림
// ex) 좌우 각도만 있으면 된다 Freeze Rotation의 X,Z를 체크함
// Capsule Collider vs Sphere Collider
// Capsule Collider Height를 1로하면 Sphere와 같아짐
// 실무에서는 Capsule Collider를 더 많이 씀 (범용성이 넓음)
// Collider
// Edit모드에서 크기 조절 가능
// Trigger vs Collision
// Trigger : 범위 침범 O
// ex) Rpg 특정 장소에 도달(보이지 않는 공간을 만들고 플레이어 도달) => Trigger 사용
// Rpg에선 인스턴싱 오브젝트가 많을 시(의도하지 않게 길막 생길 수 있음) 웬만하면 트리거
// Collision : 범위 침범 X
// ex) 당구 게임, 퍼즐 게임이나 어드벤처 게임에서 길을 막을 오브젝트
// Joint : 연결을 시켜주거나 상호작용이 가능하게 만들어줌
// Fixed Joint
// 처음 생성된 거리가 유지가 됨
// - Connected Body(rigidbody 참조해야함)
// - Break Force 일정 값이 넘어가면 끊어짐 Infinity면 절대 안 부셔짐
// - Break Toque 일정 회전 값이 끊어짐 Infinity면 절대 안 부셔짐
// ex) cut the rope(공룡 사탕 먹는 게임) spring Joint 사용
// Character Joint
// 3D 모델 흔들리는 모습 다 구현 가능
// Hinge Joint
// 문 경첩 수직 Axis를 기준으로 회전 가능
// axis 움직이는 축
// 기준점이 되는 좌표
// Spring Joint
// 매달려서 연결 됨
// Terrain
// Mesh Collider
// 3D 객체를 다루는 파일에서 자주 나옴
// Shaded WireFrame에서 볼 수 있음
// 더 자세히 콜라이더 체크를 할 수 있음
// Mesh collider 일반 상태에서는 trigger가 불가능하다
// - Convex
// Convex Hull (구멍채우기)
// 체크해야 Trigger가 가능하다
// 컨벡스헐 알고리즘 - 가장 바깥의 점을 찾아서 이어줌
// 도형에서 안쪽으로 파여져 있는 부분이 컨벡스
// 이렇게 하기 싫으면 버텍스를 다 검출하여
// 컨벡스헐 생성이 안 되면 콜라이더를 생성하여 계산
// ex) 스왐피는 동적생성
// - 정점을 가져와서 MeshFilter에 CreateMesh를 실행하여
// Mesh배열을 가져온 후 메시 콜라이더에 넣음
// 메시를 구획으로 나누어서 동적 생성
// 물줄기가 떨어짐 - 원형의 콜라이더에 파티클을 씌운 것임
// 물 - 흘러야됨 / 뭉쳐져야됨
// 지름보다 작은 콜라이더를 넣고 - 뭉쳐짐
// Friction을 0으로 넣음 - 흘름
// Bounceness를 0.6정도로 하면 더 자연스러움
// Wheel Collider
// 레이싱 게임을 목적으로 만든 콜라이더
// 댐핑, 서스펜션 등등이 있음
// Rigidbody2D
// - Auto Mass 질량을 자동으로 계산해줌(size 기반)
// Distance Joint
// 거리를 조정할 수 있음
// Effector 2D
// Collider에 Used By Effector을 체크 해야함
// Buoyancy Effector 2D
// Particle System Collision
// - Lifetime Loss
// Collision과 만나면 빨리 사라지게 할 수 있다.
// Collider를 타고 파티클이 진행할 수 있다.
private ForceMode m;
Rigidbody m_Rigidbody;
public float m_Thrust = 20f;
private int m_nCount = 0;
void Start()
{
//Fetch the Rigidbody from the GameObject with this script attached
m_Rigidbody = GetComponent<Rigidbody>();
// 물리 끄고싶을 때는 키네마틱을 켜버림
// m_Rigidbody.isKinematic = true;
// 중력 가속도 * 시간 => 속도 / 속도 * 시간 => 거리 // 9.81 * 0.02 * 0.02
}
// Update는 Fps -> Cpu(프레임)에 종속적
// Render쪽에 주로 쓰임 (시각적)
// - 사람이 초당 볼 수 있는 한계에 맞춰져 있음
// Frame과 Delta.Time은 반비례 관계
// FixedUpdate는 Tick(Cpu 시간을 재는 단위 - 고정)에 종속적
// => 프레임 저하에 상관 X(프레임에 종속 X) - 0.02초에 한 번 불림
// 실무에선 0.1초에 한 번으로 불림
// 물리 엔진은 예측 개념이 조금 있는데
// 프레임이 끊겼을 때 순간 이동을 한다거나 버그가 생길 수 있음
void FixedUpdate()
{
// 정속
// m_Rigidbody.velocity = new Vector3(0, 5f, 0);
// 방향을 가진 힘을 줌 (월드 포지션 기준)
// 한 번 더 감싸는 큐브가 있다고 쳤을 때 부모 객체의 각도를 돌렸을 때
// 예상치 못한 방향으로 힘이 가해질 수 있음
// m_Rigidbody.AddForce();
// 해당 트랜스폼이 바라보는 방향에 대한 벡터
// transform.forward;
// 특정 지점에 힘들 줌 (토크랑 같이 먹음)
// m_Rigidbody.AddForceAtPosition();
//F(힘) = ma
//a(가속도) = F / m(질량)
//a = deltaV / deltaT
//a = (v2 - v1) / (t2 - t1) 가속도
//a - 9.8(중력) = v2 / t2
// m = 1, F = 20, t2 = 0.02
// v1 = 0 t1 = 0
// a - 9.8 = v2 / t2
// v2 = (a - 9.8) * t2
// v2 = (F / m - 9.8) * t2
// v2 = (F - 9.8) * t2
// v2 = (20 - 9.8) * 0.02
// v2 = 0.2 RigidbodyInfo에서 확인 가능
// Ex)
// v2 = (a - 9.8) * t2
// v2 = (F/m - 9.8) * t2
// v2 = (F - 9.8) * 0.02
if (Input.GetKeyDown(KeyCode.A))
{
m_nCount = 50;
m = ForceMode.Force;
// force * deltaTime / mass
// F = 20 * 0.02 / 1
// v2 = 0.204
}
if (Input.GetKeyDown(KeyCode.S))
{
m_nCount = 50;
m = ForceMode.Acceleration;
// force * deltaTime
// F = 20 * 0.02
//
}
if (Input.GetKeyDown(KeyCode.D))
{
m_nCount = 50;
m = ForceMode.Impulse;
// force / mass
// F = 20 / 1
//
}
if (Input.GetKeyDown(KeyCode.F))
{
m_nCount = 50;
m = ForceMode.VelocityChange;
// force
// F = 20
}
if (m_nCount > 0)
{
m_nCount--;
m_Rigidbody.AddForce(transform.up * m_Thrust, m);
Debug.Log(m_Rigidbody.velocity);
Debug.Log(Time.fixedDeltaTime);
}
// 원래 개념은 카메라에서 눈에 보이지않는 Ray(선) 을 쐈을 때 처음으로 걸리는 객체
// 유니티에서의 개념은 쏘는 부분(object, 좌표)에서 쏴서(필터링) 특정 객체가 맞으면 true를 반환
// 제일 처음 만난 콜라이더를 반환함
// 다 반환받고 싶으면 CastAll을 붙임 => 반환값 Array
RaycastHit hit;
// 현재 포지션에서 forward 방향으로 레이저를 100fm 만큼 쐈을 때 맞으면 true
// 거리 안 쓰면 무한 : float.PositiveInfinity
// Layer는 특정 객체를 검출하게 할 수 있다.
// out hit
// RaycastHit은 구조체로 값 전달밖에 못 하는데 out으로 전달하면 참조로 받아
// hit을 Raycast함수문 탈출 후에도 사용할 수 있다.
if (Physics.Raycast(transform.position, Vector3.forward, out hit, 100f, LayerMask.NameToLayer("UI")))
{
hit.transform.GetComponent<GameObject>().SetActive(false);
}
// 캐스트 되는 범위를 박스 형태로 날림
if (Physics.BoxCast(transform.position, Vector3.one, Vector3.forward, out hit))
{
}
RaycastHit[] hits = Physics.BoxCastAll(transform.position, Vector3.one, Vector3.forward);
// Physics.CapsuleCast()
// Physics.SphereCast();
// Physics.Linecast();
}
// Edit - Project Setting - Physics Layer Collision Matrix - 체크를 해제하면 충돌을 막을 수 있다.
// OnTriggerEnter/Stay/Exit
// Collider 영역에 진입했을 때
private void OnTriggerEnter(Collider other)
{
}
// Collider 영역에 있을 때
private void OnTriggerStay(Collider other)
{
}
// Collider 영역에서 나왔을 때
private void OnTriggerExit(Collider other)
{
}
// OnCollisionEnter/Stay/Exit
// 부딪쳤을 때
private void OnCollisionEnter(Collision collision)
{
// 사각형 사각형
// 충돌 포인트들을 가져올 수 있음
// collision.contacts
// 부딪친 충돌체의 컴포넌트들을 가져올 수 있음
// collision.transform.GetComponent<>();
}
// 붙어있는 상태
private void OnCollisionStay(Collision collision)
{
}
// 부딪쳤다 떨어졌을 때
private void OnCollisionExit(Collision collision)
{
}
}
실습 과제
물리를 이용한 플랫폼형 게임을 만들어봤다.
한 주 후기
이번 주에는 게임 오브젝트부터 각 컴포넌트가 무슨 일을 하는지, 또 유니티에서 물리 법칙이 어떤 방식으로 이루어지는지를 배웠다. 현실의 물리 법칙(ex : F = ma)이 내부 물리 법칙 계산과 같은 것에 놀랐고 이 값들을 조정하여 나만의 게임을 만들 수도 있다는 것을 알았다.
실습 과제를 진행하면서 어려운 점이 많았다. 내 프로젝트는 2D로 진행했는데 3D를 주로 다뤄보다가 2D 컴포넌트를 쓰려니 까먹고 바꿔써야 하는 걸 안 바꿔 쓴 적이 많았다. 프로젝트 중 첫 난관은 Vector.Velocity의 x축으로 속도를 조절하려니 중력 값이 override돼서 플레이어가 생각대로 움직이지 않는 것이었다. 이는 그냥 내가 수동으로 중력 값을 넣어줘서 해결하였다. 두 번째 난관은 Raycast부분에서 어쩔 때는 hit이 되고 어쩔 때는 hit이 안 되는 것 때문에 애를 먹었다. DrawRay로 선을 그려가면서 해보았지만 안 되는 것에 시간을 많이 낭비한 것 같다. 여기서 첫 번째 문제는 3D환경에서 사용하던 것처럼 Physics를 사용한 것이었고 두 번째 문제는 바닥의 끝으로 갔을 때 Ray로 검출하다보니 캐릭터 transform의 위치 때문에 검출이 안 될 때가 있던 것이었다. 이후에 첫 번째 문제는 Physics2D를 사용해서 해결하였고 두 번째 문제는 Boxcast로 바닥을 체크해 해결하였다.
이번 주차에 토이 프로젝트를 진행하면서 느낀 점은 기능 기능을 따로 구현하는 것이 아니라 작은 게임을 처음부터 구현한다는 점이 좋았다. 처음부터 스스로 구현하다보니 어떻게 구현하면 좋을지에 대해서 먼저 생각하는 시간도 있고 그 생각대로 프로그래밍을 하는 것과 그 구현방식에 따른 다른 구현 방법도 달라지는 것이 재밌었다. 그리고 내가 부족한 점이 어느 부분에 있다는 것을 알 수 있어서 좀 더 성장할 수 있었다고 생각이 들었다.
이전의 개발 교육 기간에 들었던 이론들을 기반으로 눈에 보이는 결과물이 있는 실습 시간으로 들어오니까 학습 성취도도 늘고 재밌게 교육에 참여할 수 있었던 것 같다. 학습이 재밌고 내가 성장하는 걸 보면서 동기부여도 되는 것 자체도 재밌는 것 같다. 이 마음가짐으로 끝까지 교육을 수행했으면 좋겠다. 다음주도 파이팅!