8 분 소요

유데미 x 스나이퍼팩토리 10주 완성 프로젝트 캠프 - 유니티 기초 학습 2주차 내용이다.

월요일 수업

오늘은 팩맨 게임을 만들어 보는 시간을 가졌다

image

먼저 움직임 코드이다.

    Vector3 dir = new Vector3(
    Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
    if (dir.sqrMagnitude > 0.01f)
    {
    Vector3 forward = Vector3.Slerp(transform.forward, dir,
    rotationSpeed * Time.deltaTime / Vector3.Angle(transform.forward, dir));
    transform.LookAt(transform.position + forward);
    }
    charCtrl.Move(dir * moveSpeed * Time.deltaTime);


1주차 포스팅에서 움직임을 다뤘는데 움직이는 코드를 하나 더 알아둬야겠다.

우선 CharacterController 클래스에 대해서 알아보자 CharacterController는 Rigidbody 물리를 활용하지 않는 3인 또는 1인 플레이어에 주로 사용이 됩니다.
이 말은 즉 CharacterController로 캐릭터의 움직임을 구현한다면 물리적 요소는 직접 코딩으로 구현해줘야 한다는 소리이다.

그렇다면 무조건 Rigidbody을 이용한 이동이 간편한거 아닌가 ? 할 수 있겠지만, RigidBody는 성능 문제가 발생할 수 있다. 물리 연산은 성능의 무리를 주는 경우가 있어서 최적화 하는 방법을 모르고 막 사용 하다 치명적인 문제를 만들 수 있다.

따라서 상황에 맞게 Rigidbody를 사용한 움직임을 사용할지 CharacterController를 사용한 움직임을 사용할지 개발자 원하는대로 하면 된다.

다음은 sqrMagnitude이다. 이 함수는 두 점간의 거리의 제곱에 루트를 한 값으로 두 점간의 거리의 차이를 2차원 함수값으로 계산해준다. 물론 Vector3.Distance를 사용하면 바로 거리를 알 수 있지만 Distance는 sqr에 비해 연산속도가 느리며 쓸데 없는 계산을 해서이다.
정교한 값을 구할때는 Distance를 사용하고 2D게임을 만들거나 단순하게 두 점간의 거리를 구할땐 sqrMagnitude가 낫다.

다음은 Vector3.Slerp이다. 백터 이동에서 수학적으로 다루어진 Lerp(선형 보간)과 Slerp(구면 선형 보간)이 있는데 간단하게 설명하자면 Lerp(선형 보간)는 그려진 가상의 호에서의 직선 거리, Slerp(구면 선형 보간)는 그려진 가상의 호를 그대로의 거리라고 이해하면 좋을 것 같다.

참고- 선형 보간과 구면 선형 보간

따라서 위 코드를 설명하자면 움직임이 인지되면 구면 선형 보간을 이용해 forward값을 정하고 LookAt을 이용하여 해당 위치를 바라보고 Move를 통해 이동한다.

    anim.SetFloat("Speed", charCtrl.velocity.magnitude);


위에서 언급한 sqrMagnitude와 같은 기능을 하지만 차이점은 sqrMag는 정확성이 상대적으로 낮지만 성능이 좋고 Mag는 정확성이 높지만 상대적으로 성능이 낮다.
그 후 애니메이션을 다루는 법을 상세히 배웠다.

그 다음 도트를 바닥에 배치해 도트를 먹으면 승리하거나 패배하는 씬을 만들었다.
또 적을 만들어 게임의 난이도를 올렸다.

씬 전환같은 경우에는 1주차에 올렸으니 이번에는 적의 이동을 구현하는 Navigation에 대해 알아보자 !

Navigation은 Ai의 이동에 필요하며 어떤 한 지점에서 다른 지점으로 이동하는 길을 알려주는 역할을 한다. 물론 그냥 이동이라면 코드로 간단하게 적어서 움직일 수 있지만 장애물들이 존재하는 맵이라면 꼭 필요한 기능이다. 너무 잘 설명이 되어있는 블로그를 발견해서 올린다. 다시 사용할 일이 있다면 다시 공부하고 사용해야겠다.

참고 - 네비게이션



    agent.destination = target.transform.position;

그 후 Update문에 저 코드를 넣으면 적이 계속해서 플레이어 위치를 따라 최적의 길로 올 것이다.

요약은 이정도로 하고 과제로는 3가지가 있었다.

  1. 다양한 구조물로 새로운 맵을 만들기
  2. 여러단계의 스테이지를 만들어 보기
  3. 누적 점수를 표현해 보기

사실 1번과 2번은 너무 간단하기 때문에 따로 요약할게 없어보이고 3번을 얘기를 적어야겠다. 3번을 해결하는 방법으로는 많은 방법이 있다고 생각한다. 오브젝트를 만들어 dontdestroyonload를 사용하거나 싱글톤을 만들거나 등등 근데 여기서 사용한 방법은 static키워드이다.

static키워드는 정적을 의미한다. 객체를 생성하지 않고도 멤버에 접글할 수 있게 만드는 키워드로 클래스, 필드, 메서드 등에 붙여서 static 멤버로 만들 수 있다. 즉 변할 일이 없고 객체와 상관없는 필드나 메소드에 사용이 가능하다.

static은 선언하는 즉시 데이터에 저장이 된다.(런타임 이전에 이미 생성) 그리고 종료까지 쭉 가지고 있게 된다. 따라서 GC가 실행되지 않기 때문에 메모리 낭비가 심하기에 잘 생각하고 사용해야 겠다.
월요일 요약 끝 !

수요일 수업

수요일 수업은 유니티짱으로 가위바위보를 하는 게임을 만들었다.

우선 화면 이동을 구현했다.

// 기본 위치 저장
    defPosition = target.transform.position;
    defRotation = target.transform.rotation;
    defZoom = Camera.main.fieldOfView;
    // 왼쪽 드래그로 카메라 이동
    if (Input.GetMouseButton(0))
    {
    target.transform.Translate(
    -Input.GetAxis("Mouse X") / 10,
    Input.GetAxis("Mouse Y") / 10,
    0);
    }
    // 오른쪽 드래그로 카메라 회전
    if (Input.GetMouseButton(1))
    {
    target.transform.Rotate(
    -Input.GetAxis("Mouse Y") * 10,
    -Input.GetAxis("Mouse X") * 10,
    0);
    }
    // 휠 회전으로 확대/축소
    if (Input.GetAxis("Mouse ScrollWheel") != 0)
    {
    Camera.main.fieldOfView += (20 * Input.GetAxis("Mo
    use ScrollWheel"));
    if (Camera.main.fieldOfView < 10)
    Camera.main.fieldOfView = 10;
    else if (Camera.main.fieldOfView > 100)
    Camera.main.fieldOfView = 100;
    }
    // 휠 클릭으로 설정 초기화
    if (Input.GetMouseButton(2))
    {
    target.transform.position = defPosition;
    target.transform.rotation = defRotation;
    Camera.main.fieldOfView = defZoom;
    }


우선 해당 코드의 설명은 주석에 있다.
딱히 어려운건 없지만 fieldOfView를 알아보도록 하자 !
fieldOfView는 해석하자면 사람의 시야라고 할 수 있다.
유니티에서는 Camera.main.fieldOfView로 카메라의 시야를 뜻한다.
즉 카메라의 시야를 조정해서 줌 인과 줌 아웃을 구현한 예이다.
그래픽스 수업에서 배운 기억이 있어서 정리를 하면

  • 낮은 fov
    1. 카메라에 보이는 좌우폭이 좁다.
    2. 상대적으로 사물이 가까워 보인다.
    3. 왜곡이 적다.
    4. 정면으로 이동시 속도감이 떨어진다.
      반면에 높은 fov는 낮은 fov와 정 반대이다.

다음은 DateTime 클래스의 멤버를 알아보자

    System.DateTime dt = System.DateTime.Now; // 현재
    dt.Year; // 년
    dt.Month; // 월
    dt.Day; // 일
    dt.Hour; // 시
    dt.Minute; // 분
    dt.Second; // 초
    dt.Millisecond; // 밀리초
    dt.DayOfWeek == System.DayOfWeek.Monday; // 요일

이렇게 구성이 되어 있으니 잘 기억하고 사용해야겠다.

다음은 PlayerPrefs 클래스의 사용이다.

    PlayerPrefs.DeleteKey("str"); // 해당 키의 값을 삭제
    PlayerPrefs.DeleteAll(); // 모든 값을 삭제
    PlayerPrefs.SetFloat("f_num", 1.23f); // 실수 기록
    float f_num = PlayerPrefs.GetFloat("f_num"); // 실수 얻기
    PlayerPrefs.SetInt("i_num", 123); // 정수 기록
    int i_num = PlayerPrefs.GetInt("i_num"); // 정수 얻기
    PlayerPrefs.SetString("str","abc"); // 문자열 기록
    string str = PlayerPrefs.GetString("str"); // 문자열 얻기
    PlayerPrefs.Save(); // 지금 당장 변경사항 저장

PlayerPrefs는 유니티에서 제공해주는 데이터 관리 클래스이다. PlayerPrefs는 현장실습에서 다뤘던 기억이 있다. 그때는 게임 내 데이터들을 Json형태로 저장했었다.
그때 기억으로는 정말 간단하게 데이터를 저장할 수 있지만 보안상 문제로 중요 데이터를 저장하고 사용하지 않는다고 배웠다.

다음은 음성 파일 재상하기를 배웠다. AudioSource와 AudioClip에 대해서 먼저 알아보자 !
AudioSource는 음악 재생기 AudioClip은 재생시킬곡을 뜻한다. 음원만 있다고 재생할 수 없고 기기만 있다고 재생할 수 없기에 둘 다 필요한 요소이다.

변수 선언 후 AudioSource 컴포넌트를 추가하고 GetComponent로 해당 컴포넌트의 기능을 사용하면 된다. 실습에서 사용한 함수는 PlayOneShot()이다. Play()와 다르게 여러개의 파일을 재생하고 싶을 때 사용한다.

    if (Input.GetMouseButtonDown(0))
    {
        //스크린의 마우스 위치가 시작점
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit; //Ray의 충돌에 대한 결과 정보를 저장하는 구조체
        if (Physics.Raycast(ray, out hit, 100)) //hit이 있다면 True 없다면 false
        //100은 거리 제한이다. 
        {
            Debug.Log("hit!");
        }
    }

다음은 Ray에 대해서 알아보자 !
코드가 어떤 행동을 하는지는 주석을 달아놓았다.
Ray는 직선의 시작점과 방향을 가지고 있는 단순한 구조체다.
먼저 RaycastHit에 대해서 보자

먼저 RaycastHit.point를 이용하여 월드에서 레이캐스팅이 감지된 위치를 얻을 수 있다.

Vector3 hitPosition = hitData.point;
또는 RaycastHit.distance를 사용하여 Ray의 원점에서 충돌 지점까지의 거리를 구할수 있다.

float hitDistance = hitData.distance;
Tag와 같은 히트 된 대상 객체의 Collider 세부 정보를 얻을수도 있다.

//이번 실습에서 사용한 예제이다.
string tag = hitData.collider.tag;
RaycastHit.transform을 사용하여 충돌 객체의 Transform에 대한 참조를 얻을 수도 있다.

// Gets a Game Object reference from its Transform
GameObject hitObject = hitData.transform.gameObject;


이렇게 구성 되어 있다. (출처는 아래 링크 !)
Raycast에 있는 매개변수 중 Layer Mask도 있는데 레이어에 따라 충돌체를 필터링하는 기능이다. 값을 전달하는 방식은 아래 링크에 설명이 잘 되어있으니 링크를 달겠다.
뿐만 아니라 Trigger collider를 무시하는 법도 잘 정리되어 있다.
참고 - Ray 설명

다음은 Animator.StringToHash에 대해 알아보자 !
animator나 material, shader의 parameter를 호출할 때, 간편하게 string형으로 호출하는 경우가 많다. 간편하지만, 성능상으로는 매번 문자열에서 Hash로 변환하기 때문에 성능상 좋지않다고 한다. 따라서 위처럼 animator.SetTrigger(“aa”)가 아닌 int a = Animator.StringToHash(“aa”)처럼 멤버편수로 캐싱해놓고 사용한다.
예시

int id = Animator.StringToHash("Attack");
animator.SetTrigger(id);

int id = Shader.PropertyToID("_MainMap");
material.SetTexture(id, texture);

int id = Shader.PropertyToID("_MainColor");
shader.SetGlobalColor(id, color);

(위 예시)

// 모션 스테이트의 ID 얻기
private int motionIdol = Animator.StringToHash("Base Layer.Idol");
// 재생 중인 동작이 대기 동작인지 검사
if (animator.GetCurrentAnimatorStateInfo(0).fullPathHash == motionIdol)
    animator.SetBool("Motion_Idle", true);
else
    animator.SetBool("Motion_Idle", false);

animator.GetCurrentAnimatorStateInfo(0).fullPathHash는 animator에서 현재 플레이 중인 animation의 이름을 얻는 방법이다. 예제에서는 if문으로 위에서 hash로 만든 변수값을 이용해 if문을 사용했다.

다음으로는 가위바위보를 구현했다. 어렵지 않아서 스크립트를 보면 바로 이해할 수 있는 수준의 코딩이였다. 단 const를 사용해서 상수화를 이용해 애니메이션 액션을 진행하는데 int로 주는정도 ?가 끝이였다.
수업은 여기서 끝이고 과제로는 가위바위보가 UGUI가 아닌 OnGui로 만들어서 UGUI로 만드는 거였다. 과제 또한 어렵지 않아서 SetActive()와 OnClick()을 이용해 버튼이 눌리면 어떤 값을 전달하는지 조절만 하면 돼서 쉽게 끝냈다.

수요일 수업 끝 !

금요일 수업

금요일 수업은 이론 수업으로 진행했다.
상속부터 시작해서 유니티의 기능등 다양한 이론을 공부했는데 그 중 내가 정리하면 좋을 내용들을 정리해보는 시간을 가졌다.

우선 수업에서 배우지는 않았지만 상속을 알기전에 객체 지향 프로그래밍을 아는게 좋다고 생각한다.
객체 지향은 (Object-Oriented Programming, OOP)이란 여러 독립적인 부품들의 조합, 즉 객체들의 유기적인 협력과 결합으로 파악하고자 하는 컴퓨터 프로그래밍의 패러다임을 의미한다. 이런 어려운 말 보다는 그냥 프로그램을 보다 유연하고 변경이 용이하게 만들 수 있다는 점이다. 또 각각의 부품들이 독릭접인 역할을 가지기 때문에 코드의 변경을 최소화하고 유지보수를 하는 데 유리하다. 또 코드의 재사용을 통해 반복적인 코드를 최소화하고, 코드를 최대한 간결하게 표현할 수 있다.
특징으로는 추상화, 상속, 다형성, 캡슐화이다.

먼저 객체(Object)란 ? 우리가 보고 느끼고 인지할 수 있는 그 모든 것이라고 표시가 되어 있다.
다음은 특징들의 정리이다.

  1. 추상화(Abstration) - 객체에서 필요한 공통된 부분을 추출하는 것을 의미한다.
  2. 상속(Inheritance) - 부모클래스의 속성과 기능을 이어받아 자식 클래스가 사용하는 것을 의미한다.
  3. 캡슐화(Encapsultation) - 연관있ㄴ느 변수와 메소드를 묶어주는 작업을 의미한다.
    접근 제어 지시자를 통해 외부로부터 접근을 제한하여 객체내에서만 접근이 가능하도록 해주며, 이를 정보은닉이라고 한다.
  4. 다형성(Ploymorphism) - 프로그래밍 언어의 자료형 체계의 성질을 나타내는 것으로, 동일한 변수, 함수명등이 다양한 방법으로 기능하늑 것을 말하며 오버라이딩, 오버로딩이란 형태로 제공된다.
    여기서 오버라이딩과 오버로딩은 ?
    -오버라이딩(Overriding) : 부모 클래스에서 상속된 메서드의 다른 구현을 제공하는 자식 클래스의 기능입니다. -오버로딩(Overloading) : 클래스가 이름은 같지만 함수의 기능(매개변수의 개수, 자료형 등)이 다른 메서드를 만들 수 있는 기능입니다.
    나는 오버라이딩과 오버로딩을 이해할 때 라이딩은 올라타다 즉 자식이 부모위에 올라탄다 이런식으로 이해했던 기억이 있다.

여기까지 객체 지향을 알아봤다.

C#에서는 다이아몬드(호출의 모호함에서 발생하는 문제가 다이아몬드를 닮아서) 문제 때문에 다중 상속을 지원하지 않는데(그래서 인터페이스로 구현을 많이 함) 유니티 스크립트는 기본적으로 MonoBehaviour를 상속하기 있기 때문에 다른 클래스를 상속할 수 없다. 또 상속을 많이 사용할 경우 오히려 코드를 재사용하기 힘든 경우가 생길 수 있는데 이런 문제를 해결하기 위한 방법 중 하나는 컴포넌트 패턴이다.

디자인 패턴 중 하나인 컴포넌트 패턴은 미리 만들어진 부품을 조립하여 완성된 오브젝트를 만드는 방식이다. 즉 빈 껍데기인 게임 오브젝트에 미리 만들어진 컴포넌트를 조립하여 개발을 한다.
장점으로는 유연한 재사용과 독립성 덕분에 기능 추가와 삭제가 쉽다.

MonoBehavior란 ? 유니티 씬에서 게임 오브젝트의 동작과 인터랙션을 생성하는 데 사용되는 특수한 유형의 C# 스크립트입니다. 용도로는 게임 오브젝트 움직임 제어, 충돌 처리, 게임 상태 업데이트, 게임 오브젝트 애니메이션등 다양한 메서드가 있다. 대표적으로 Start(), Update, OnTriggerEnter()가 포함된다.

브로드캐스팅이란 ? 송신 호스트가 전송한 데이터가 네트워크에 연결된 모든 호스트에 전송되는 방식이다. 유니티에서는 컴포넌트의 어떤 기능을 실행하고 싶을 때 메시지를 브로드캐스팅 방식으로 사용을 한다.


using - using에 네임스페이스를 지정하면 해당 네임 스페이스에 들어 있는 코드를 현재 스크립트로 불러온다. 예시로는 using.UnityEngine.Debug를 선언하면 Debug를 사용할 수 있다.

짐벌락 - 어떤 축의 회전이 다른 축의 회전에 영향을 미친다는 사실과 세 번 나누어 축을 회전하는 방식때문에 오일러각(3D 벡터를 사용해 3D 회전을 나타내는 표현)체계에서는 특정한 경우 앞선 두 번의 회전에 의해 세 번째 회전의 자유도가 상실되어 세 축 중 한 축의 회전을사용없게 되는 현상이 발생함. 이것을 짐벌락이라 부름.

쿼터니언 - 쿼터니언은 한 번에 회전하는 방식이기 때문에 오일러각과 달리 짐벌락 현상이 없으며 90도 회전을 제대로표현할 수 있음.
유니티에서는 쿼터니언을 사용한다. rotation.eulerAngles로 오일러각을 가져올 수 있다.

이론 시간에 정리할 내용은 이 정도로 마무리하고 실습을 알아보자 !
OnGui를 통해 메세지창을 구현했고 (1280 x 720 기준) 정해진 텍스트까지 구현했다. 또 메세지 총 개수를 입력해 정해진 시간이 지날 때 마다 한 글자씩 출력하도록 설정을 해서 천천히 출력하는 효과를 주었다.
어려운건 없어서 코드를 보면 이해할 수 있는 수준이다.

과제로는 팔을 터치할 경우 날짜와 시간을 메세지로 출력을 하는 것이다.
팔을 터치 하는것은 유니티짱의 모델로 들어가 팔에 해당하는 위치를 찾아 Tag를 Hand로 바꿔서 Touch 스크립트에서 Hand에 대한 추가 코딩을 진행하였고, 시간은 위에 정리한 Date클래스를 이용해 메세지를 출력을 하였다 !
어려운건 없기에 문제가 없었다.

이렇게 2주차도 잘 마무리를 하였고 추가적으로 공부할 내용은 따로 공부해야겠다 !

본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다. #프로젝트캠프 #프로젝트캠프후기 #유데미 #udemy #스나이퍼팩토리 #웅진씽크빅 #인사이드아웃 #IT개발캠프 #개발자부트캠프 #unity #유니티 #게임개발 #메타버스