0. 개요
과제가 당장 다음주 목요일까지기 때문에 빨리 진행을 하고 싶어서 강의를 모두 들었다!! 이제... 토요일에 UI관련 강의들만 들으면 과제에 집중할 수 있다.!!
음.. 그치만 일단 주말간에는 월요일까지 제출해야하는.. 챌린지반 과제를 먼저 끝내야겠다..
그치만 언리얼이 더 재밌는걸! 히히히! 고로 다음 강의 듣기 전까진 아이템들 설계를 모두 끝마치고 레벨 정도 미리 만들어두어야겠다.
또, 오늘 우수 TIL에 뽑혀서 뿌듯했다! 엣햄!
열심히했던 보람을 느낄 수 있어서 좋았다.. ㅎㅎ
사실 이번 부트 캠프를 시작하기 전 까진 뭐랄까.. 그동안 노력한것들을 인정받지 못해서 생각이 너무 많아졌고, 우울했었는데.. 아직도 많이 부족하고, 더 성장해야 하지만 적어도 지금까지 노력해온 것들은 인정받는 느낌을 많이 받아 너무 감사드리고 행복한 것 같다.
뿌.듯.!
좀 더 열심히 노력해서.. 더욱 더 좋은 작품을 만들 수 있도록..! ㅎㅎ
+ 오늘 지난번에 과제로 만들었던 드론을 조금 수정했다.
오늘 TIL은 강의 정리 살짝과 드론 수정 내용에 대해 작성할 것이다.
1. 강의 정리
1. 인터페이스 기반 아이템 클래스 설계하기
이번 강의는 이전에 과제를 할 때 여러번 사용해보면서 감을 익혀갔던 인터페이스에 대해서 배웠다.
<인터페이스란?>
일종의 계약서이다.
해당 인터페이스 클래스를 상속받은 클래스는 반드시 인터페이스에 존재하는 함수를 구현해야 한다.
인터페이스는 추상 클래스이며, 순수 가상함수로 구성된다.
(음.. 변수도 가질 순 있겠지만 추상 클래스니까 클래스 객체를 생성하지는 못할 것이다.)
<상속 vs 인터페이스>
상속 | 인터페이스 |
부모 클래스의 함수(기능)을 물려받는 개념이다. | 물려 받는게 아니라 어떤 함수들을 구현하기로 약속하는 개념이다. |
음.. 결국 인터페이스도 추상 클래스라서 인터페이스를 쓰는 클래스는 상속을 받긴하지만, 위에서의 상속은 이런 문법적 상속 보다는 개념적으로 접근한 설명인 것 같다.
<언리얼에서 지원해주는 인터페이스>
언리얼에서 지원해주는 인터페이스는 UInterface라는 클래스로 지원해준다.
즉, 우리가 언리얼에서 인터페이스를 구현할 때는 UInterface 클래스를 상속받아 만들면 된다.
생성을 하게 되면, 클래스가 2개가 생성되어 나온다.
U접두사가 붙은 클래스는 언리얼 리플렉션 시스템에 등록하기 위한 클래스이기 때문에 우리가 수정할 필요가 없는 클래스이다.
또, MinimalAPI라는 지정자는 현재 모듈에서만 접근이 가능하도록 제한한다는 것이다.
우리가 작성해야할 곳은 I접두사가 붙은 클래스이다.
!!! 주의할점 !!!
사실상 인터페이스는 추상 클래스이기 때문에 정의부가 없다.
따라서 cpp파일이 필요없어 보일 수 있는데, 인터페이스의 cpp파일을 지워선 안된다.
언리얼 엔진에서는 빌드할 때, cpp와 h을 페어로 확인하기 때문에 cpp파일을 임의로 지우게 되면, 빌드 과정에서 꼬일 수 있다.
또한, 언리얼 컨벤션에서도 cpp 파일과 h 파일은 반드시 페어로 생성해야한다고 나와있다.
(..고 한다... ㅋㅋㅋ 사실.. 아직 따로 안찾아봤다..)
때문에 사용하지 않는다고 cpp파일을 지우면 안된다!
<인터페이스의 활용>
강의에서 진행하면서 구조를 짰는데, 예시를 보여주자면 다음과 같다.
- 아이템 인터페이스
- 부모 클래스 (Item)
- 지뢰 클래스
- 힐링 클래스
- 공통 코인 클래스
- 작은 코인 클래스
- 큰 코인 클래스
아이템들이 반드시 가져야할 기능들을 묶어서 인터페이스로 만들어서 해당 인터페이스를 상속받은 베이스 클래스인 Item 클래스를 만든다.
ㄴ> 이러면 Item들은 반드시 인터페이스의 순수 가상 함수들을 구현해야하기 때문에 특정 형태를 반드시 갖추도록 제한된다. (때문에 계약서라는 표현을 하신듯 하다.)
이후 부모 클래스인 Item을 상속받아 다양한 아이템들을 만든다.
2. 충돌 이벤트로 획득되는 아이템 구현하기
이번에도 이전에 과제를 할 때, 사용해보았던 충돌 이벤트 바인딩 방법에 관한 강의였다.
Overlap 함수들의 시그니처에서 쓰임새를 잘 몰랐던 인자들에 대해서 약간의 설명을 들어서 좋았다.
(해당 인자들은 다룰 이야기가 너무 많아서 차후 수업에서 다룬다고 하신다.)
<충돌 이벤트란?>
액터나 컴포넌트가 다른 객체와 상호작용할 때 발생하는 것을 이야기한다.
이러한 충돌 이벤트는 다음과 같이 2가지로 나뉜다.
- 오버랩 이벤트
- 히트 이벤트
충돌 이벤트를 발생시키기 위해서는 콜리전(충돌체)이 필요한데, 보통 메시의 콜리전을 사용하지 않고, 따로 콜리전 영역을 잡아 사용한다고 한다.
<콜리전 프리셋>
대상에 따라 어떤 종류의 충돌을 감지하고, 어떤 종류의 충돌을 무시할지 정하는 것이다.
이미 언리얼에서 제공해주는 프리셋들을 활용할 수 있고, 원하는 설정을 직접 커스텀으로 만들어 줄 수도 있다.
아니면 특정 콜리전 프리셋을 자주 사용한다면, 프로젝트 세팅에서 설정해 저장해둘 수도 있다.
NoCollision | 충돌 자체를 감지하지 않는다. |
BlockAll | 모든 객체랑 막겠다. 충돌 처리를 하겠다. |
OverlapAll | 보통 트리거 존을 만들 때 이 셋팅을 많이 사용한다. |
BlockAllDynamic | 움직이는 객체들이랑만 충돌한다. |
OverlapAllDynamic | 움직이는 객체들이랑만 오버랩 이벤트를 발생 시킨다. |
Pawn | Pawn과 충돌 이벤트를 발생한다. |
이러한 콜리전 프리셋 세팅은 C++ 클래스에서 디폴트로 설정해주는 것이 편하다.
Collision->SetCollisionProfileName("OverlapAllDynamic");
이때, 철자를 틀리지 않도록 주의해야 한다...
커스텀을 선택하면 우리가 원하는 대로 설정이 가능하다.
또, 프로젝트 세팅에서 프리셋을 추가적으로 만들어 둘 수도 있다.
뿐만 아니라 트레이스 채널도 만들어 트래이싱 시 사용할 수 도 있다.
이전에 언리얼로 2D 게임에 도전했을 때 찾아보고 사용했었는데.. 꽤 유용해서 기억에 남는다.
<추가적으로 알면 좋은 내용>
Overlap의 경우 두 오브젝트의 프리셋 중 하나만 Overlap 설정이 되어 있으면 이벤트가 발생한다.
즉, A : Block, B : Overlap이면 오버렙 이벤트가 발생함.
또 Overlap의 경우 오버렙 이벤트 생성 설정은 둘다 true로 주어야 한다.
충돌 설정에 관련해서 표로 정리해뒀었는데.. 어디있는지 못찾겠다.. ㅠㅠ 나중에 찾으면 추가할 것이다.. ㅠ
https://dev.epicgames.com/documentation/ko-kr/unreal-engine/collision-in-unreal-engine---overview
<BeginOverlap과 EndOverlap 함수 시그니처>
virtual void OnItemOverlap(UPrimitiveComponent* OverlappedComp
, AActor* OtherActor
, UPrimitiveComponent* OtherComp
, int32 OtherBodyIndex
, bool bFromSweep,
const FHitResult& SweepResult) override;
virtual void OnItemEndOverlap(UPrimitiveComponent* OverlappedComp
, AActor* OtherActor
, UPrimitiveComponent* OtherComp
, int32 OtherBodyIndex) override;
이중에서 Overlap 함수에 있는 뒤에 3가지 인자는 물리 시뮬레이션과 연관이 깊은 것들인데, 이는 나중에 다룬다고 하셨다.
또 주의할 점은 이런 바인딩 거는 함수들은 반드시 리플렉션 시스템에 등록해주어야 한다.
그래야지만 런타임중에 이런 함수들을 찾을 수 있기 때문이다.
(인터페이스 함수의 경우 인터페이스에다가 붙여주면 된다. => 라이더에서 경고를 던저주는걸 보니.. 그래도 UFUNTION을 재정의한 쪽에도 추가해주는게 좋을 것 같다.)
<바인딩을 거는 구조로 만든 이유!>
왜 이렇게 바인딩을 거는 구조를 사용할까?
이벤트 바인딩을 하는 장점은 런타임 중에서 이런 바인딩이 이루어 진다.
게임 실행 전에는 어떤 객체가 어떤 상황에서 이벤트를 발생시킬지 알 수없다.
예를들어 새로 생성된 적과 이벤트가 발생하는 경우가 있는데, 이때 어떤 이벤트가 발생하고 어떤 함수를 호출하는지 모르기 때문에 이런 처리들은 동적으로 처리해줄 필요가 있다.
때문에 런타임 중에 바인딩을 걸어 동적으로 처리해주기 위해 바인딩으로 이벤트 처리를 해주게 되는 것이다.
<뷰포트에 로그 띄우기>
GEngine->AddOnScreenDebugMessage()
GEninge에 접근해 AddOnScreenDebugMessage()을 사용하면 된다.
해당 함수는 Blueprint에서의 PrintString 노드와 동일하다. ㅎㅎ
떄문에 첫번째 인자인 Key(?)에 입력한 숫자가 동일하면, 한 줄로만 출력된다.
2. 드론 수정
1. 수정 이유
튜터님께서 날개가 회전하면 더 이쁠 것 같다는 말씀을 해주셔서 바로 구현해보기로 결정!
2. 이슈
문제는 드론 메시가 통 메시여서 날개만 존재하는게 아님..
3DMax, Maya를 써봤지만 단순히 FBX파일 열어서 정점들 보고, 정점으로 이어진 면들 삭제 정도 밖에 안해봤다..
(ㅎㅎ 그니까 그냥 설치해보고 열어만 본 수준 ㅋㅋㅋㅋㅋㅋ 이게 써본건 아니긴한데.. 사용~은 해봤다 ㅋㅋㅋ)
요는 메시 편집을 어떻게 하는지 모르고 기술이 없는 상황에서 어떻게 해결하느냐..? 이다.. ㅠ
3. 드론 메시 분리하기
드론 메시를 분리하기 위해 정보를 찾다가
yoosorang님께 메시 임포트할 때, 병합하지 않고 받기로 해보라는 아이디어를 받았다.
Unreal5 애니메이션 없는 드론의 프로펠러 돌리기(언리얼 에디터로 스태틱 메시 분리)
🔷개요비행체를 언리얼에서 구현할 때 날개가 있는 비행체라면 이 날개를 돌려야 현실적인 구현이 가능하다.하지만 에셋을 사용할 경우 하나의 fbx로 들어오기 때문에 애니메이션이 따로 포함
yoosorang.tistory.com
문제는.. 몰랐지만 난 이미.. 메시를 병합하지 않고 임포트하고 있었다.. ㄴ(ㅇㅁㅇ)ㄱ 어쨰서..
때문에 내가 할 수 있는 최선 중 하나인 모델링 모드 하나하나 다 써보기!!!를 해봤다.
그러다가 우연히 메시를 분리할 수 있는 기능을 찾았다..!!!!
모델링 모드에 트라이 앵글 선택이라는 것이 있었다.
해당 기능은 Vertex가 연결된 삼각망을 기준으로 메시의 영역을 잡아준다..!
(DX때가 떠오르는 재밌는 경험!! ㅋㅋㅋ)
브러시마다 잡히는 영역이 다른데, 모든 브러시를 사용해본 것은 아니지만 All in Group 브러시를 사용하면, 그룹 단위로 잘 선택되는 것을 확인할 수 있었다.
(뭔가 그룹이 내부적으로 설정되어 있어서 그 그룹대로 잡히는 것인지 아니면, 내부적인 계산으로 그룹을 판별해 잡는 건지는 모르겠다. 경우에 따라서 원하는 선택이 안될 수도 있겠다는 생각이 들었다.)
이후 메시 편집에서 Separate 편집을 하면, 원본 메시에서는 해당 부분이 삭제되고, 삭제한 부분은 새로운 메시로 생성해 저장해준다.
확실히 언리얼에서 모델링 기능도 꽤 많이 지원해주는걸 알 수 있었다.
4. 드론 메시에 소캣 달기
처음에는 소캣을 생각하지 못하고, StaticMesh에는 본을 달 수 없다고 생각해서.. 스켈레탈 메시로 만들어서 본을 생성하고, 소캣을 만들고, 스킨 바인딩도 해보고.. 진짜 이것저것 기능이란 기능은 다 눌러본 것 같다..
하지만 깔끔히 나오는게 없어서 고민하다가..
시간이 꽤 흐르고나니 StaticMesh에 소캣을 못다나?!라는 생각이 들어 검색해보니.. 소캣을 다는 기능을 지원해주고 있었다… ㅋㅋㅋㅋㅋㅋ
(StaticMeshComponent의 디테일 탭에서 부모 소캣이라는 항목을 몇 번을 봤는데 왜 이리 늦게 떠올린건지… ㅠ)
소캣 매니저를 이용해 소캣을 만들어주면 된다.
(저 +버튼을 눌러서 소캣을 추가하는건데.. 눈에 잘 안들어와서 저거 찾는것도 한참 해맸다.. ㅋㅋㅋ)
5. 날개 부착 및 회전 로직 짜기
이제 드디어 준비가 다 끝났다!!!!! 좀 후련한 기분이 든다.
날개의 경우 StaticMeshComponent를 2개 추가해주어서 Socket에 부착해주었다.
이때, Socket에 부착하는 방법을 몰라 해맸는데.. SetupAttachment의 2번째 인자가 바로 Socket의 이름을 받을 수 있었다..!!
WingStaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WingMesh1"));
WingStaticMeshComp->SetupAttachment(StaticMeshComp, FName("WingSocket1"));
Wing2StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WingMesh2"));
Wing2StaticMeshComp->SetupAttachment(StaticMeshComp, FName("WingSocket2"));
이렇게 부착까지 마쳐주었다.
이제 회전 로직을 짜야하는데, 사실상 필요한 대부분의 것들이 이미 구현되어 있다.
나는 드론이 움직이는 속도에 따라 날개의 회전속도에 변화를 주고 싶었다.
즉, 드론의 속도가 빠를 수록 날개는 더 빨리 회전한다.
⇒ 심지어 이미 가속, 감속이 적용되어 있고, 그 변화량이 사인 그래프 보간까지 들어가 있기 때문에 꽤 자연스럽다.
float TargetBladeRotationSpeed = BaseBladeRotationSpeed + (SpeedFactor * CurrentSpeed);
BladeRotationSpeed = FMath::FInterpTo(BladeRotationSpeed, TargetBladeRotationSpeed, DeltaTime, 2.0f);
FRotator CurrentRotation = WingStaticMeshComp->GetRelativeRotation();
CurrentRotation.Yaw += BladeRotationSpeed * DeltaTime;
WingStaticMeshComp->SetRelativeRotation(CurrentRotation);
CurrentRotation = Wing2StaticMeshComp->GetRelativeRotation();
CurrentRotation.Yaw += BladeRotationSpeed * DeltaTime;
Wing2StaticMeshComp->SetRelativeRotation(CurrentRotation);
다닥다닥 붙어있어서 좀 그렇긴한데..
기본 날개 회전 속도는 드론이 공중에 가만히 있을 때, 날개가 멈춰있으면 이상하기 때문에 기본 회전 속도를 준 것이다.
이후 이미 이동에서 계산해두었던, CurrentSpeed와 변수로 가지고 있던 SpeedFactor를 이용해 목표로 도달해야할 날개의 회전 속도를 구한다.
이후 선형 보간을 이용해 회전 변위량을 자연스럽게 변경하고, 이렇게 변경한 최종 회전을 적용해주는 코드이다.
6. 결과
드론의 속도에 따른 회전 & 드론의 시동을 끄면 날개 회전이 점점 멈추도록 구현했다.
3. 마무리
캬하~~ 이번에도 좋은 아이디어 주셨고, 바로 적용했다.
또 그 과정에서 이런저런 이슈들이 많았고 해결하는데 시간이 걸렸지만..
(심지어 코드 쪽도 아니라서... ㅠ 그래도 기능 이것저것 알게 됐으니 만족! ㅎㅎ)
그래도 꽤 그럴싸한 드론이 완성된거 같아서 꽤나 만족스럽다. ㅎㅎ
다음으로 추천해주신 아이디어가 있는데, 그걸 바탕으로 8번 과제를 구상하고 있다.
다음주 TIL에 구상한걸 정리해서 한번 작성해볼 생각이다.
생각처럼 잘 나올지 모르지만.. 음.. 지금 생각으로는 꽤? 그럴싸한 결과물이 나올거 같다. ㅎㅎ
바로 시작해야지!! 히히히
그리고 이놈의 감기는 떨어질 생각을 안한다... ㅠ
조금은 많이 나아지긴 했지만.. ㅠ
'프로그래밍 > Unreal 부트캠프' 카테고리의 다른 글
TIL 2025.02.11 기록 (0) | 2025.02.11 |
---|---|
TIL 2025.02.10 기록 (0) | 2025.02.10 |
TIL 2025.02.05 기록 (0) | 2025.02.05 |
TIL 2025.02.04 기록 (0) | 2025.02.04 |
TIL 2025.02.03 기록 (0) | 2025.02.03 |