프로그래밍/Unreal 부트캠프

TIL 2025.01.07 기록

Rozentea 2025. 1. 7. 23:05

0. 개요


오늘은 미뤄두었던 온라인 강의를 다 듣고나서  언리얼을 열심히 달렸다..

첫 강의는 디자인 패턴이었는데, 이전에 자주 사용했던 싱글톤 패턴부터 처음 들어보는 데코레이턴 패턴, 옵저버 패턴을 배웠다.

싱글톤과 옵저버를 제외한 데코레이턴 패턴은 어떤 상황에서 사용하면 좋을지 아직 감이 전혀 안온다..

그래도 어떤 구조로 만드는지와 어떤식으로 동작하는 디자인 패턴인지는 이해할 수 있어서 그래도 괜찮았던 것 같다.

 

두번째 강의는 STL에 관해 배웠다. STL의 경우 이전부터 자주 사용하기도 했었고, 계속 공부해서 정리도 해왔어서 크게 어려움 없이 들을 수 있었다. 하지만 여전히 부족하다고 생각해 계속해서 더 공부해 봐야겠다.

 

마지막 강의는 드디어..!  c++로 언리얼을 사용하는 강의였다.

아직 초반부라서 크게 어려울게 없었다.

ㄴ> 이번에 라이더를 사용해볼 예정이다.

또, 이전에는 몰랐었던 기능들을 몇가지 알게되어 기록해두려 한다.

 

마지막으로 언리얼 프로젝트... 오늘은 사운드 쪽에서 많이 막힌것 같다.

원하는 사운드를 찾기도 쉽지 않고, 사운드를 배치해서 사용하는 것도 생각처럼 쉽지 않았다.

그나마 몇 시간 사용해보니 조금씩은 알아가는 것 같아 다행이긴 한데, 사운드는 너무 어려운것 같다.. ㅋㅋㅋ

ㄴ> 효과음을 최대한 찾아보고 적당히 넣어주고, 배경음 관련해서 관리해주는 정도만 하고 넘어가는게 좋을 것 같다.

 

오늘 TIL은 마지막 프로젝트 부분을 제외하면 이번에 알게된 부분만 작성할 생각이다.

 

1. 강의


1. 디자인 패턴

디자인 패턴이랑 "개발 시 반복적으로 등장하는 문제를 해결하기 위한 일반화 된 솔루션"이라고 정의할 수 있다.

 

1. 디자인 패턴의 종류

- 생성 패턴

: 새로운 객체를 만들어내는 것과 관련된 패턴

   ex) 싱글톤 패턴

- 구조 패턴

: 객체가 여러가지 있을 때 객체 간을 어떻게 연동시킬지, 어떻게 구조를 잡아갈지에 대한 디자인 패턴
   ex) 데코레이턴 패턴

- 행동 패턴

: 객체의 상태가 변경될 때 어떻게 할 것인가에 대한 디자인 패턴

   부품이 서로 어떻게 상호작용할지에 대한 패턴이다.

   ex) 옵저버 패턴

 

2. 예시 패턴들 공부

- 싱글톤 패턴 (하나의 객체만 생성하는 패턴)
   ㄴ> 생성자들을 private 필드에 두어 일반적인 방법으로는 객체를 생성할 수 없게 막아둠.
   ㄴ> 대신 GetInstance()와 같은 함수를 만들어 해당 함수를 통해서만 객체를 생성할 수 있게 해둔다.
   ㄴ> 복사연산자와 대입 연산자를 지울 수 있다.
             ㄴ> ex) Airplane(const Airplane&) = delete; 예전에 배우긴 했지만 사용한적은 없어서.. 잊었었다.
             ㄴ> 지우지 않는다면, private 필드에 배치해 감춰둘 수 있다.
   ㄴ> 요는 static 키워드를 이용해 클래스 내에 정적 객체를 동적할당해 오로지 해당 객체의 주소만 리턴하고, 새로운 객체는 만들지 못하게            방지하는 디자인 패턴이다.
   ㄴ> 즉 프로그램 내에서 해당 클래스로 만든 객체는 오로지 하나만 사용가능하게 만든 것.

 

- 대코레이턴 패턴 (객체에 동적으로 기능을 추가하는 패턴)
    ㄴ> 처음 보는 패턴이라 뭔가 난해한데..
    ㄴ> 이해하기로는 추상 클래스를 만들어 해당 클래스를 이용해 여러 자식을 만들고,
    ㄴ> 실제로 사용할 때는 인터페이스를 기반으로 계속해 클래스를 감싸서(랩핑) 점점 확장해 나가며 구조를 잡아가는 디자인 패턴으로 보인다.
    ㄴ> 때문에 순수 가상 함수를 타고타고 올라가서 제일 먼저 랩핑된 클래스의 함수를 호출해주고, 다음 래핑된 클래스의 함수를 호출해주면서 타고타고 내려와 결국 마지막에 랩핑된 클래스의 해당 함수가 실행되며 끝나는 구조.
    ㄴ> 어떤 객체가 있을 때, 동적으로 기능을 추가할 수 있는 디자인 패턴

 

- 옵저버 패턴 (서브젝트 클래스의 데이터 수정사항을 파악해 연관된 기능들을 업데이트 해주는 패턴)
     ㄴ> 데이터를 변경할 때 이를 사용하는 모든 것들이 바뀌게 하고 싶을 때 사용
     ㄴ> 즉, 데이터를 수정할 때마다 데이터를 이용하는 모든 것들이 업데이트 되어야할 때 사용
     ㄴ> 옵저버를 붙일 클래스에서 옵저버라는 추상 클래스를 배열로 지니고 있고, 해당 옵저버 추상 클래스를 상속 받은 여러 것들을 만들어둔다.
     ㄴ> 옵저버 추상 클래스는 updata()라는 가상함수를 지니고 있고, 자식 클래스쪽에선 이 update()를 오버라이딩해 데이터 변화에 따른 수정 사항을 추가해준다.
     ㄴ> 대상 클래스에는 옵저버를 등록할 수 있는 함수, 그리고 데이터가 변경될 때 알려주는 함수가 필요하다.

 

2. STL 

- 배열

      ㄴ> 연속된 메모리 공간을 사용한다.
      ㄴ> 메모리가 연속적으로 할당되어 있어 접근이 매우 빠르다.
      ㄴ> 연속적일 뿐인데 왜 빠를까? 캐시 때문임.. 미리 다음 인덱스를 준비할 수 있음.
      ㄴ>컴퓨터 구조를 공부하면 캐시를 공부해야함.
      ㄴ> 랜덤 엑세스(조회)가 O(1)이 필요
      ㄴ> 배열 추가는 일반 적으로 상수지만, 배열의 크기가 늘어날 경우 O(N)이 걸린다.
      ㄴ> 마찬가지로 삭제도 가장 뒤 원소 삭제는 O(1)이지만, O(N)이 보통 걸린다.

개발자들은 항상 최악의 경우를 상정해야 한다고 하신다..

      ㄴ>  최악의 경우를 잘 생각 못하는 경우가 많았어서 연습해야할 것 같다.

- 정렬 알고리즘
      ㄴ> sort()의 경우 보통 O(NlogN)이 걸린다.

- 검색
      ㄴ> 일반적으로는 O(N)이다. 첫 원소부터 끝까지 보기 떄문.
      ㄴ> 하지만 정렬된 경우엔 이진 검색(binary_search)을 하면, O(NlogN)이 가능하다.
 
- 링크드 리스트
      ㄴ> 각 노드는 자신의 앞 뒤 노드의 주소들을 가지고 있다.
      ㄴ> 삽입 삭제가 좋다.
      ㄴ> 하지만, 조회를 한다면 앞에서 혹은 뒤에서 N노드까지 타고타고가야하기 때문에 O(N)이 걸린다.

- 스택
      ㄴ>후입선출의 성격을 가진 자료구조이다.
      ㄴ> 모바일 앱을 사용하면 뒤로 가기 버튼을 누르면 이전에 화면이 나오는데, 이를 스택을 활용해 만든다.
      ㄴ> Undo/Redo와 같은 기능도 스택을 사용한다.
      ㄴ> 즉, 역순의 기능에 많이 사용한다.
      ㄴ> 스택은 백터로 쉽게 구현이 가능하다.

- 큐
      ㄴ> 선입선출의 성격을 가진 자료구조이다.
      ㄴ> 대기열같은게 큐..

 

3. 언리얼 엔진 1일차 수업

- 언리얼 엔진의 특징

1. 실시간 렌더링 기능
       ㄴ> 그래픽이라는게 컴퓨터 자원을 많이 사용하기 때문에 렌더링 기술이 뒷받침되지 않으면, 최적화가 안되어 여러 문제가 발생하게 된다.
       ㄴ> 하지만 언리얼은 이러한 그래픽 최적화가 너무 잘 되어있기  때문에 실시간 렌더링에 강하다.
2. 블루프린트
       ㄴ> C++이라는 언어 장벽이 있는데, 블루프린트를 사용하면, 보다 쉽게 로직 구현을 할 수 있도록 되어있다.
3. 모든 플랫폼에서 가능
       ㄴ> 한번 구현하면 크로스 컴파일을 지원하기 때문에 어려움을 조금 덜어준다.
4. 활성화된 커뮤니티
       ㄴ> 엔진을 무료로 제공하기 때문에 사용하는 사람이 많고, 이로 인해 커뮤니티가 활성화 된다.

 

- cpp로 액터 생성
        ㄴ> cpp와 h파일은 언리얼 에디터에서 액터 생성을 통해 만들어야 한다.

       ㄴ> 그러지 않으면 꼬일 수 있는것 같다...

- UE_LOG()
        ㄴ> 콘솔창이나 뷰포트에 출력할 로그를 입력할 수 있다.

- 로그 필터링
        ㄴ> 에디터의 로그창에서 필터를 이용해 원하는 로그만 필터링해 확인할 수 있다.

2. 언리얼


1. 반사 레이저 버그 수정

우선 결론부터 이야기 하자면, 반사 벡터를 구하는 방법에 대해서는 전혀 문제가 없었다.

단순히 거울이 다음 레이저를 생성한 뒤 레이저가 트레이스를 할 때, 해당 거울에 다시 걸려서 레이저가 튀는 것이었다...

때문에 레이저의 트레이스 시작지점에 offset을 주어 해결했다.

 

어제 확인했던 부분

1. 기존 mirror vector 대신 reflection vector 노드를 사용해 보았다.

> 결과 다를게 없다.

> 내부적으로는 safe normalize를 하는지 안하는지 차이가 있지만.. 이로 인한 문제는 아니었다.

 

2. print string으로 값들 출력해보기

> vector가 1, 0, 0이나 -1, 0, 0을 넘어서면 문제가 발생하는 것을 확인함.

> 각으로 확인할 경우 두 방향벡터의 사잇각이 90도를 넘어가는 순간 발생하는 것을 확인함.

> 사실 이 때문에 수식이 틀렸나..? 노말 벡터를 뒤집어야 하나..? 등의 고민을 하게된 주 원인이었다.. ㅠ

 

3. 식에서 사용되는 벡터들 모두 정규화 해보기

> 달라지는 것이 하나도 없었다..

> 이땐 몰랐지만, 애초에 식이나 값이 잘못된 것은 아니었으니까..

 

4. Make Rot From Z 대신 다른 것들로 변경해보기

> 애초에 Make Rot From Z를 사용하는게 맞는게, 레이저로 사용중인 원기둥 메시가 Z축을 향하도록 만들어졌기 때문.

> Make Rot From Z,X,Y 이 3가지 함수는 벡터 하나만 가지고 회전을 정해준다.

> 원래는 회전값을 구하려면 벡터가 2개가 있어야 하는데, 해당 함수들은 1개만 있어도 만들어진다.

> 내부적으로 연산을 하긴하는데, 벡터 하나만 가지고 회전을 구해서 오류가 발생할 수 있다고도 생각했다.

 

5. 스태틱 메시를 변경 모든 레이저 함수들을 기존 Z축 방향 -> X축 방향으로 변경하기

> 변경 도중 몇 개를 놓치는 등 순탄하지는 않았지만.. 공교롭게도 동일한 결과가 발생했다.

> 이쯤 해보니.. 정말 수식이 잘못된 건가 아니면 다른 문제는 없을까 하는 생각을 하게되었다.

 

6. 반사 벡터가 거울면과 닿는지 확인

> 생각 도중 반사 벡터가 다시 본인을 생성한 거울과 Hit돼 문제가 발생할 수 있다는 생각을 하게 되었다.

> 이를 검증하기 위해서 레이저에게 본인을 생성한 거울과는 Hit를 무시하도록 설정해주었다.

> 확인 결과.. 정확하게 문제가 발생하던 90도 이후부터 레이저가 고정되는 것을 확인함.

> 아..! 얘 거울면에 닿아서 발생하는 문제구나..!

> 정확히 레이저의  라인 트레이스 무시 뿐아니라 레이저의 스태틱 메시 콜리전을 끄는 등 다양한 시도 끝에 봉착한 사진이다..

> 즉 위 사진은 라인 트레이싱에서 거울을 무시한게 아니라 단순 레이저의 콜리전만 껐었던 6번 바로 직전 단계 사진..

 

7. 어떻게 해결할까 하다가 Offset을 주도록 변경

> 위 사진 처럼 반사 레이저가 생성되는 지점 및 레이저의 라인 트레이스의 시작점을 거울로 부터 조금 떨어뜨리는 것이다.

> 솔직히 말하면.. 에초에 offset을 주도록 영상에서 만들었는데, offset을 주기 위해 방향 벡터에 2를 곱하는 부분에 내가 1을 넣어버려 생긴 문제였다...

> 하지만 그래도 이번 문제 덕분에 여러가지 다양하게 해보고, 디버깅을 재밌게 한것 같아서.. 그리고 또 이번일로 느낀것이 많았기 때문에 나쁜 경험은 아니었던 것 같다.

> 다시는 실수 하지 말아야지.. ㅠ

 

8.  결과

90도 이상도 잘 돌아가진다.. ;ㅅ;

 

사실 이번 버그 수정은 혼자서는 시간이 훨씬 더 오래걸렸을 것 같다.

저녁 늦게까지 한 문제만 가지고 시달렸고, 너무 피곤했어서.. 솔직히 중간에 포기하고 내일하고 싶었다.

또 마음 한켠으로는 "수식이 문제 아니야?", "아 모르겠다.. 그냥 거울 회전을 조금 막아서 해결해볼까.." 등등

디버깅을 포기하는 쪽으로 마음이 점차 기울었었다.

미니 프로젝트이고, 이렇게 이 프로젝트를 더 연장해서 만드는 수강생도 없었어서.. 중요하지 않다고 생각을 점차하게 됐나보다..

 

하지만 같이 고민해주고, 의견을 내주고, 코드를 분석해준 동기 수강생분들 덕분에 어제 안에 문제를 찾고 해결할 수 있었던 것 같다.

 

위에서 했던 안좋은 생각들에 대해서는 이전에 프로젝트들을 진행할 때 많이 느끼고, 반성했었던 부분이었는데, 아직도 한켠으로는 막막해지면 쉽게 포기하려고 하는 마음이 남아있는 것 같다...

이번 경험을 통해 포기하지 않고, 조금 더 해보는 마인드를 다시 한번 먹을 수 있었고, 동기분들 처럼 보다 근본적인 원인을 찾으려 노력하고, 분석하고, 고민하는 자세를 더 가져야겠다는 생각을 할 수 있어서 좋은 경험이었다..!

 

2. 나이아가라(파티클) 버그 수정

파티클.. 더 잘 만들고 싶다...

 

이전에 나이아가라를 GPU로 동작 시켰을 때, 월드에서 전혀 보이지 않는 문제가 있었다..

때문에 CPU로 동작하게 했는데, 지금은 한번만 파티클을 생성하니까 크게 문제가 되지 않을 테지만, 나중을 생각하면 꼭 왜 안돼는지가 찾고 싶었다.

 

오늘 더 찾아보니 다음과 같은 문제가 있을 수 있다고 한다.

프로퍼티에서 바운드 셋팅을 fixed로 할 경우 fixed bound가 작을 때 문제가 될 수 있다고 한다.

 

하지만 내 경우에는 몇 번 값을 변경하다가 다시 시도해보니 처음 셋팅값으로도 잘 동작해서 해당 문제가 아닐 수 있다..

시간을 조금 더 두고 테스트를 진행해봐야겠다.

 

하지만 나이아가라를 GPU로 동작시킬 때 위의 문제로 원하는 출력을 못얻어 낼 수 있을 것 같아서 정리해두었다.

 

언리얼5 - 레벨에서 나이아가라 이펙트가 보이지 않을때 해결방법

게임이펙트카페(http://cafe.naver.com/unrealfx)에서 운영중인 카페특강전용사이트입니다.

gamefx.co.kr

 

 

 

3. 플레이어 발자국 소리 추가

플레이어의 발이 바닥에 닿을때 마다 소리가 잘 나는 것을 확인할 수 있다.

 

AnimNotify 블루 프린트를 만들어서 Received Notify함수를 오버라이딩해 스켈레탈 메시에 적용할 애니메이션에서 호출해주도록 만들었다.

함수 자체는 별거 없다.

플레이어 아래쪽으로 라인트레이스를 쏴서 지면에 닿아있는지 확인하고, 사운드를 실행시켜주도록 구현했다.

 

피직스 머티리얼마다 소리를 다르게 하고 싶다면, 피직스 머티리얼을 생성하고, 레이를 쐈을 때 해당 피직스 머티리얼이 무엇인지 확인해 분기처리 해주면 된다.

 

4. BGM 추가

우선 테스트를 진행하기 위해 라디오 액터를 만들어 음악을 실행해 보았다.

 

BGM을 앰비언트 사운드로 넣으려 했지만, 같은 Level에서 BGM이 변경되어야 할때.. 그때 어떻게 해야할지 모르겠어서 우선은 사운드 큐 만들어 루프 돌려주고, 어테뉴에이션으로 거리에 따라 볼륨이 조절되도록 만들어 자연스럽게 이어지도록 만들어 주었다.

 

조금 아쉬운 점은 카메라가 기준이어서.. 카메라를 회전했을 때, 소리가 나는 쪽에 닿아버리면 사운드가 바뀌게 된다..

이점은 수정해보고 싶다.

 

- 사운드 큐

사운드 큐에서 루핑을 하면, 사운드를 반복 시킬 수 있다.

또, 사운드 큐에서 사운드 영역 밖으로 나갔다가 들어올 때, 음악을 재시작 할지, 아니면 들리지는 않지만 음악이 진행되던 것으로 하여 중간부터 시작할지를 정할 수 있다. (매우 유용! 높게 평가..! ㅇㅁㅇb)

 

- 어테뉴에이션

어테뉴에이션에는 정말 다양한 기능들이 존재하지만.. 우선운 2가지를 사용했다.

 

볼륨 : 말 그대로 소리 볼륨 자체를 거리에 따라 어떻게할지 설정이다.

기본적으로 어테뉴에이션(볼륨)은 내부원 안에 있을 때는 최대값을 가지고, 내부원 밖에서 부터 끝지점까지 갈떄 거리에 따라 감쇠효과가 발생한다.

 

공간화 : 공간화는 액터가 배치된 기준으로 왼쪽에서 사운드가 나는 것같은지, 오른쪽으로 사운드가 나는 것 같은지를 정해준다..

음.. 서라운드 음악이나 FPS게임을 할때 다른 플레이어의 발자국, 총소리를 생각해보면 된다.

 

5. 상호작용 표시 머티리얼 만들기 (아웃라인)

이전에 플랫포머 만들기 과제를 진행할 때, 머티리얼과 UI를 이용해 상호작용이 가능한 액터를 표시해주는 기능을 만드신 분이 있으셨는데,

그분께서 노드 하나하나 설명을 해주시면서 올리신 영상이 있어서 보고 만들어 봤다..

 

프레넬 노드

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/using-fresnel-in-your-materials?application_version=4.27

 

5. 고민하고 있는 것.

1. 아웃라인

더보기

음.. 아웃라인을 보여주는 것을 어떻게 할지에 대해 고민중이다.

단순히 된다. 구현했다가 아니라 조금 더 느낌있는 완성도 있는 아웃라인 효과를 만들고 싶기 때문이다.

 

1. 라인 트레이스를 이용해 플레이어가 현재 상호작용 할 수 있는 오브젝트들만 변경해준다.

 이 방식의 경우 매 tick마다 라인 트레이스를 쏴주어 확인해주어야 하는데.. 매 틱마다 쏘는것도 별로라 생각되고, 이 방식으로 하면, 한번에 상호작용이 가능한 모든 액터들을 확인할 수 도 없다.

또, 라인 트레이스를 벗어나는 순간을 확인해주는 이밴트도 없어서 오버레이한 머티리얼을 원상복구 시켜주기도 어렵다 판단했다.

 

2. 전체 관리자 액터를 만든다.

 이전에 DirectX를 이용해 게임을 만들었을 때 처럼 상호작용이 가능한 오브젝트들을 관리하는 관리자 객체를 하나 만들면 어떨까 하는 생각이다.

 

3. 레벨 블루프린트에서 관리해준다.

 레벨에서 변수를 하나 만들어, 플레이어가 Scan을 하면, 레벨의 변수가 바뀌어 각 상호작용 액터들 틱에서는 해당 변수를 확인해 머티리얼을 일정 시간동안 오버레이 해주면 어떨까 하는 생각이었다.

 

4. 머티리얼이 아니라 포스트 프로세스를 이용해 뎁스 스텐실을 확인해 그린다.

 이전 Dx로도 외곽선 검출은 포스트 프로세스로 만들기도 했어서.. 이게 맞는것 같기도하고.. 어렵다.. 어느게 맞는지 모르겠다.