-
새로운 팀원분들 만났습니다. 호호홓
팀명 정하는게 가장 어려운 것 같습니다 ㅠㅠ
먼저 팀 프로젝트 시작 전 개인 프로젝트 시작!
강의를 들으면서 같이 진행하였습니다.
필수 요구사항
1. 캐릭터 만들기 : 사전캠프 때 만들어봐서 간략하게 적겠습니다.
Hierarchy - MainScene -> GameObject - Create Empty(Player)
Hierarchy - MainScene - Player -> GameObject - 2D Object - Sprites - Square(MainCharacter)
MainCharacter.GetComponent<SpriteRenderer>().Sprite = Penguin_idle
2. 캐릭터 이동 : WASD로 움직이며, 마우스의 위치에 따라 바라보는 방향이 바뀐다. 캐릭터를 움직이게 하는 방법은 크게 두가지가 있는데 실제로 사용할 것은 두번째거이긴하지만 첫번째 것도 좋아서 같이 정리해보겠습니다.
1. GetAxis 사용
유니티에서 제공하는 함수를 이용하는 방법으로 Axis에 대한 설정은 Edit - Project Settings - Input Manager에서 확인이 가능합니다.
void Update() { float x = Input.GetAxisRaw("Horizontal"); float y = Input.GetAxisRaw("Vertical"); transform.position += new Vector3(x, y) * Time.deltaTime; }
x축의 값은 Horizontal, y축의 값은 Vertical을 사용하면 됩니다.
Input Manager에 보게되면 Horizontal과 Vertical이 어떤 버튼을 누르게되면 실행되는지 나와있습니다. 일반적으로 방향키 또는 WASD를 누르게 되면 원하는 방향으로 이동하게 됩니다.
x와 y 값을 받아서 transform.position에 입력해주면 되는데 Update 함수는 프레임이 끝날 때마다 실행되는 함수로 컴퓨터마다 프레임이 조금씩 다르기 때문에 그냥 입력받으면 사람마다 이동속도에 차이가 생기게 됩니다. 따라서 deltaTime을 곱해줍니다.
deltaTime은 한 프레임이 끝날 때까지 시간을 체크하는 것으로 1초에 60프레임인 컴퓨터가 있다면 deltaTime = 1/60이 되므로 deltaTime을 곱해주게 되면 1초에 60번씩 1/60이 더해지므로 1이 됩니다.
현재 코드에서 사용한 GetAxisRaw()는 0, 1, -1의 값을 주기 때문에 이동속도가 일정해집니다.
Raw를 사용하지 않는다면 처음 누를 때 조금 느리다가 속도가 일정해집니다.
2. Action 사용
이건 좀 이해하는데에 시간이 오래걸렸습니다.. 잘 정리해보겠습니다.. 화이팅!
Action에 대해서 이야기하기 전에 Delegate를 먼저 알아야합니다.
C# 문법 종합반 4주차에 있는 내용입니다. 그래도 이해는 안되는 중..
Delegate : 메서드의 포인터를 저장하여 다른 메서드의 매개변수로 전달하거나 변수에 할당할 수 있다. 여러 개의 함수를 +=를 통해서 연결할 수 있다. Delegate와 연결될 함수는 매개변수가 같아야한다. Invoke로 호출이 가능하다.
간단하게 다른 함수를 사용할 때 그 함수를 바로 사용하는 것이 아니라 delegate에 함수를 저장해서 delegate로 함수를 불러와 사용한다는 뜻인 것 같습니다. 함수를 불러오기 힘들 때 편하게 불러오기 위해서 사용합니다.
Action<T>: 값을 반환하지 않는 메서드를 나타내는 delegate의 한 종류.
Action<T>는 받은 입력값을 매개변수(T)로 하여 다른 함수에 전달하는 역할을 하는 것 같습니다.
우선 입력값을 먼저 받아보도록 하겠습니다.
최근 유니티가 업그레이드 되면서 Input 기능이 발전하게 되었습니다.
Window - Package Manager - Packages: Unity Registry - Input System 설치
만약 설치가 되어있다면
Packages: In Project에 Input System이 있을겁니다.
설치를 했다면
Project에 폴더를 하나 만들고
Create - Input Action 생성
처음 생성하게되면 모두 빈칸이기 때문에 먼저 Action Maps +로 새로 생성해줍니다.
Actions에서는 움직임을 받기 위해서 Move로 이름을 설정해주고 + UP\DOWN\LEFT\RIGT로 생성해서 WASD로 움직일 수 있도록 만들어줍니다.
Look은 마우스의 위치에 따라 캐릭터가 바라보는 방향을 정해줘야하기 때문에 Binding - Position Mouse를 해주면 됩니다.
참고로 매개변수로 Vector2를 받기 때문에 제목 설정은 Value, Vector2로 설정!
이렇게 설정한 것은 어디에 쓰느냐
//CharacterInputController.cs : CharacterController public void OnMove(InputValue value) { Vector2 moveInput = value.Get<Vector2>().normalized; CallMoveEvent(moveInput); } public void OnLook(InputValue value);
지정해둔 제목에 On을 붙이면 InputValue에 지정해둔 값이 들어오면 자동으로 함수가 발동합니다.
아마도? UP에 설정해둔 W를 누르게 된다면 UP의 값이 value로 들어가게 될겁니다. 왜냐하면 설정을 W가 아니라 다른 버튼으로 한다해도 그 버튼을 누르면 똑같이 위로 올라가게 되기 때문입니다.
value로 들어온 값을 Vector2로 저장해서 CallMoveEvent 함수에 넣어서 CallMoveEvent 함수를 실행시켜줍니다.
추가로
normalized : 벡터의 크기를 1로 저장해준다.
//CharacterController.cs public event Action<Vector2> OnMoveEvent; public void CallMoveEvent(Vector2 direction) { OnMoveEvent?.Invoke(direction); }
OnMove에서 받은 value 값은 direction으로 들어오게 됩니다.
이때, direction 값이 들어올 때마다 direction 값을 저장하여 OnMoveEvent가 실행이됩니다.
실행이 되면 어떻게 되느냐
//Movement.cs private CharacterController _controller; private Vector2 _direction = Vector2.zero; private void Awake() { _controller = GetComponent<CharacterController>(); } private void FixedUpdate() { ApplyMovement(_direction); } private void Start() { _controller.OnMoveEvent += Move; } private void Move(Vector2 vector) { _direction = vector; } private void ApplyMovement(Vector2 direction) { direction = direction * speed; }
OnMoveEvent와 연결된 함수 Move가 실행이 됩니다.
연결하는 것을 '구독'이라고 하는데 구독하는 방법은 Action += 함수 입니다. 구독을 취소하려면 Action -= 함수 를 해주면 됩니다.
구독을 하게되면 OnMoveEvent에 저장된 매개변수가 Move 함수의 매개변수에 들어가게 됩니다.
Move 함수는 매개변수를 지역변수에 저장하는 역할을 하고 있으며, 받은 지역변수를 이용해서 캐릭터가 움직일 수 있도록 설정해주었습니다.
추가로
event : 외부에서 실행하지 못하게 한다.
?. : 입력값이 있을 때만 실행되도록 한다.
GetComponent<>() : 객체에 있는 Component를 가져온다.
여기서 궁금한 점은.. 왜 움직이지? 입니다.
함수끼리 연결된 흐름은 이해가 되었습니다. 근데 마지막 Movement.cs에서 vector의 크기가 1로 고정이 되어있는데.. 더해지는게 아니라서 제자리 걸음을 하는게 맞지 않나? 싶습니다.
벡터에 대해서 제가 잘 모르는걸 수도 있긴한데..
벡터에 +와 - 가 있어서 그대로 가져와도 원래 값에서 +, -가 되는 걸까요?
벡터에 관해서는 좀 더 찾아봐야할 것 같습니다.
우선 움직임은 완성했습니다. WASD에 맞춰서 캐릭터가 잘 움직입니다. 다음 해야할 것은 마우스 방향으로 캐릭터가 보는 것입니다.
이 부분은 Action을 통해서 만들었습니다.
//CharacterInputController.cs public void OnLook(InputValue value) { Vector2 newAim = value.Get<Vector2>(); Vector2 worldPos = _camera.ScreenToWorldPoint(newAim); newAim = (worldPos - (Vector2)transform.position).normalized; if (newAim.magnitude >= 0.9f) { CallLookEvent(newAim); } }
마우스의 위치를 value로 받아서 Vector2로 만들어 좌표를 가져옵니다.
이 좌표는 월드 좌표가 아니기 때문에 월드 좌표로 바꿔줍니다.
마우스의 좌표와 캐릭터의 좌표를 빼서 캐릭터와 마우스 간의 이어진 벡터값을 저장해줍니다.
저장받은 벡터의 크기가 0.9보다 클 때 CallLookEvent를 불러옵니다.
참고로 normalized를 해줬기 때문에 크기는 무조건 1이라 항상 true입니다.
//CharacterController.cs public void CallLookEvent(Vector2 direction) { OnLookEvent?.Invoke(direction); }
OnMoveEvent와 마찬가지로 direction 값이 들어오면 OnLookEvent에 direction이 매개변수로 저장되고 실행됩니다.
//Look.cs private CharacterController _controller; private Vector2 _direction = Vector2.zero; private void Awake() { _controller = GetComponent<CharacterController>(); } private void FixedUpdate() { ApplyLook(_direction); } private void Start() { _controller.OnLookEvent += Looked; } private void Looked(Vector2 vector) { _direction = vector; } private void ApplyLook(Vector2 direction) { float toward = (direction.x > 0) ? 1 : -1; transform.localScale = new Vector3(toward, 1, 1); }
실행되면 OnLookEvent를 구독한 Looked 함수가 OnLookEvent의 매개변수를 받아서 실행됩니다.
vector의 x값이 양수일 경우 즉, 마우스가 캐릭터 오른쪽에 있을 경우에는 캐릭터가 오른쪽으로 볼 수 있도록 하고, 음수일 경우 왼쪽을 보도록 localScale을 설정해주었습니다.
사전캠프 때 캐릭터가 벽에 부딪히면 반대편을 보도록 설정한 것을 참고했는데 거기는 localScale += new로 했더라구요. 그래서 똑같이 += 했더니 캐릭터가 너무 커져서 =으로 바꿔줬습니다. 그 캐릭터는 왜 안커졌을까요??
이것도 의문입니다. 핳
3. 방 만들기
4. 카메라 따라가기
5. 캐릭터 애니메이션 추가
6. 이름 입력 시스템
7. 캐릭터 선택 시스템
와.. 생각보다 한 곳에서 시간을 많이 썼습니다.
사실 강의대로 그냥 따라했다면 시간이 안들었겠지만 왜 저게 저렇게 되는거지라고 생각하니깐 끝도 없더라구요.
Delegate와 Action에 관한 강의가 있었긴 했지만 무슨말이지 하고 넘어가기만 했던게 여기서 발목을 잡는 것 같습니다.
그래도 예제가 아니라 실전에서 직접 사용하다보니깐 좀 더 와닿고 더 많이 고민하게 된 것 같습니다.
그리고 이번에는 팀장님 사다리타기로 결정했는데 안걸렸습니다. 오예!