FCB - C++ / Blueprints

JUSTICE_DER·2023년 2월 23일
0

🌵UNREAL

목록 보기
16/42

Unreal Engine 5 – Full Course for Beginners
https://www.youtube.com/watch?v=6UlU_FsicK8
한글이 아닌 다른 언어로 또 다른 언어의 설명을 듣는다는 것은,
정확한 이해에 대해 좋은 선택이 아닐 수 있지만,
해당 영상으로 전체적인 더 높은 시야를 가질 수 있다는게 느껴진다.
그 이후 다른 강의들로 더 깊게 이해할 수 있겠다는 자신감이 든다.
열심히 해보자.

C++: Basics

  • C++를 이제부터 다룬다.
    New C++ class로 파일을 만들 수 있다.

  • C++ class 중 가장 많이 쓰이는 것은 Object Class이다.
    다음과 같이 생성하고 이름짓는다.

  • 헤더파일과 cpp파일이 존재하는데,
    헤더파일은 다음과 같이 정의되어 있다.
    Object로 생성하면, 다음과 같이 UObject라는 것을 자동으로 상속받게 된다.

  • 이 중 위의 #include 된 3줄은, Unreal의 기본 헤더파일들이고,
    UClASS는 매크로라고 한다.

  • 그 다음 자주 쓰이는 것은 Actor Class이다.
    보면 이번에는 A가 붙어서 AActor를 자동으로 상속받게 된다.

  • AActor는 BP에서와 마찬가지로 UObject를 상속받는다.

    참고

  • 헤더는 아래와 같다.

  • 아래 부분을 중심으로 보면 되는데,
    BeginPlay와 Tick은 BP에서와 역할이 같다.


Inheritance Hierachy in UE5

  • 뷰포트에 실제로 배치할 수 있는 물체가 Actor였고,
    루트노드인 Object를 상속받고, 다음과 같은 구조를 가진다.
    여기까지 보면 여태 배운 BP구조와 동일하다.

  • AController가 소유(possess)할 수 있는 APawn, ACharacter이다.
    APawn은 보통 AI가 Control하는 경우이고, 대부분의 경우에 사용된다.
    ACharacter는 더 구체적인 경우이고, 캐릭터의 움직임 등
    세부적인 기능을 사용하고 싶으면 해당 클래스를 사용한다.

  • AController는 사용자의 input을 받을 수 있고,
    다른 Actor에 해당 input을 mapping할 수 있다.

  • 일반적으로 widget(UI?)을 만들 때, Character와 관련되지 않은 경우,
    PlayerController를 사용한다고 한다.
    그대로 직역했는데 왜인지 모르겠다.

  • Actor를 상속받는 것에
    AInfo라는 것도 있는데, 해당 class를 직접 쓰는 경우는 없고,
    해당 하위 클래스 3개(1개는 잘림)를 쓴다고 한다.

  • AGameModeBase는 GameMode와 관련된 클래스로,
    GameMode에 대한 설정이나 로직들이 들어가게 된다.
    더 세부적인 기능은 AGameMode에 존재.

  • AGameStateBase도 비슷하고,
    멀티플레이의 경우, AGameMode는 서버에만 존재하고,
    AGameStateBase는 서버와 클라이언트에 존재한다고 한다.

  • 레벨의 전환이나, 어떤 게임 공통적인 연산은 AGameModeBase로 두고,
    Match Time처럼 플레이어 모두가 보는 것은 AGameStateBase로 놓는다고 한다.
    보통 둘이 서로 같이 도우며 일한다고 한다.

  • 하나 잘렸던게 APlayerState이다.
    굉장히 특이한 클래스라고 한다.
    모든 플레이어가 항상 알아야할 모든 것에 사용하는 클래스?
    라고 한다.
    보통 플레이어의 이름같은 경우 사용된다고 한다.
    (이름같이 멀티플레이어에서 다른 플레이어가 또 다른 플레이어의 정보를 항상 알아야하는 것에 사용)

  • UObject에서 AActor부분을 봤고,
    나머지 2개의 계층도 존재한다.

  • UGameInstance는 beginplay같은 이벤트 말고,
    다른 이벤트들을 해당 클래스에서 호출한다고 한다.
    레벨이 로드되기 전에 실행한다.
    보통 레벨에서 레벨로 이동하는 경우에 특정 변수를 기억하고 싶다면, 사용한다고 한다.
    UGameInstatnce는 레벨이 바뀌어도 파괴되지 않는다.
    (다른 widgets, actor들은 파괴된다)

  • UUserWidget(UI)전에 수많은 계층이 있지만,
    C++에선 웬만해서 건들일이 없는 존재라고 한다.
    widget을 C++로 다루는 것은 BP에 비해 에너지가 많이 소요되는 작업이기 때문이란다.

  • Widget같은 not mission critical(게임에 있어 중요하지 않은)한 것들은 BP에서 구현한다고 한다.

  • Loop같은 경우는, C++보다 BP에서 상당히 시간이 걸리기 때문에,
    주로 C++에서 작성한다고 한다.

  • 보통 ACharacter를 상속받는 복잡한 mission critical한 기능들은 C++코드로 작성하고,
    해당 클래스를 상속받는 BP를 만들어서 실제로 기능을 실행하는 방식을 쓴다고 한다.

UnrealEngine C++ 전체계층구조


C++: UCLASS, UPROPERTY, UFUNCTION

  • 함수를 작성하고 실행하는데 있어서 오류가 발생한다.

    따라하면서 디버그하는데 오류가 나서,
    파일도 새로 만들고, VS관련 Plugin도 설치해봤다.
    아무 코드도 적지 않고 실행해봐도 똑같은 오류가 생긴다.

unreal C++ E1835 (고질적인 문제인거같다)
https://forums.unrealengine.com/t/new-c-template-gives-errors-in-ue5-1/725236/8

https://stackoverflow.com/questions/74891461/brand-new-ue5-c-project-wont-build-with-no-errors

  • Unreal Engine에서만 인식하는 키워드가 있는데, VS만 사용해서 생기는 문제같다.

그 외의 문제
[글로벌 세이더 라이브러리 누락]
https://www.reddit.com/r/unrealengine/comments/jxf1id/game_files_required_to_initialize_the_global/

[외부에서 환경수정]
https://forum.unity.com/threads/the-solution-projectname-has-been-modified-outside-the-environment.1156955/

[빌드전용으로 오류 무시하기]
https://forums.unrealengine.com/t/new-c-template-gives-errors-in-ue5-1/725236/9

그러니까...
직접 컴퓨터-드라이브-파일에서 C++를 열면 안되고,
언리얼에서 C++파일부터 열고,
코드 변경시 외부에서 환경수정이 뜬다면, 리로드하고,
해당 파일을 디버그 시에는 항상 DegugGamemodeEditor로 되어있는지 확인하는 방식으로 해야되겠는데..

문제는 언리얼에서 C++코드 작성했던게 보이지 않아서

외부로 파일을 열 수 밖에 없었다는 것이다.


  • 일단 아래처럼 private int하나와, public int하나를 두었고,
    private를 다루는 getter setter함수를 간단하게 설정하였다.

  • MyActor (생성만 하고 코드수정은 안함)
    MyObject를 만들었다.
    해당 클래스를 BP로 만들어보려고 한다.

  • MyObject는 만들 수 없게 되어있다.
    MyActor만 보인다.

  • 그 이유는, UClass 매크로 때문이다.

  • AActor를 타고 들어가면, UCLASS매크로가 보이는데,
    헤당 AActor에는 UCLASS가 blueprintable로 BP화가 가능하게 되어있다.

  • UObject에는 따로 구현된게 없어서
    직접 UCLASS를 구현해야 한다고 한다.

  • UObject에 AActor처럼 선언부 위에 매크로를 쓴다면
    모든 UObject를 BP화 할 수 있게 돼서 문제가 되기 때문에,
    MyObject.h 헤더파일에 구현을 한다.

  • 코드 수정후 Game~Editor로 (반드시) 디버그하면,
    언리얼이 다시 로딩이 되고,

  • 드디어 MyObject를 BP로 만들 수 있게 된다...!!
    다시금 언리얼로 2D게임을 만드는게 파리잡는데 바주카포를 쓴다는 말이 생각난다.

  • 함수도 뜨지 않는다.
    결론부터 말하면, 이것도 매크로 선언해야 한다.

  • UPROPERTY로 변수의 블루프린트에서의 설정을,
    UFUNCTION으로 함수의 블루프린트에서의 설정을 하였다.

  • 함수가 불러와 진다.
    pure는 실행순서가 필요없는 초록색 함수였다.

  • [코드를 수정하는 방법]
    컴파일하고 저장하고 현재 UE창을 끈다.
    디버그 중인 창에서 빨간 멈춤표시 누른다.
    코드를 수정하고 다시 디버깅한다.
    문제 없으면 다시 UE창이 뜬다.
    디버깅중인 상태일동안 UE엔진이 켜지기 때문에 끄면 안된다.

  • set함수를 수정하였는데, 해당 변수를 ref형태로 받고 싶으면,
    해당 UPARAM매크로에 ref를 넣고, &까지 붙여야한다고 한다.

-그렇게 ref num으로 set하도록 수정하였다.


C++: USTRUCT

  • C++에서 struct를 만들어도 BP로 사용하려면,
    USTRUCT로 넘겨주어야 한다고 한다.

  • 일단 MyObject.h 헤더에 struct를 생성한다.
    중요한게, std::string을 사용하는 것이 아니라,
    언리얼은 문자열을 위해서 FString이라는 자료형을 사용한다고 한다.

  • 그렇게 Book이라는 구조체를
    이름/번호/평점을 두어 만들어 보았다.
    그리고 USTRUCT도 붙여준다.

  • 오류가 난다.
    그 이유는, MyObject도 C++에서 UMyObject로 자동으로 이름을 바꾸었는데,
    Struct의 이름도 그냥 쓰면 안되고, F를 반드시 붙여야 한다고 한다.

  • 그래도 오류가 난다.
    참고로 오류목록 부분은, 전체 솔루션에-빌드전용으로 하는 것이 좋다.
    목록을 보니, struct의 generatebody가 없다는 뜻인 것 같다.

  • C++에서 클래스와 구조체는 거의 비슷했다.
    그래서 구조도 비슷한데,
    처음에 이름지을때 클래스는 U / 구조체는 F였고,
    클래스는 UCLASS / 구조체는 USTRUCT,
    그리고 Generate도 아래처럼 비슷하지만 다른 매크로를 통해 실행한다.
    참고로 USTRUCT 안에는 UFUNCTION을 넣을 수 없다.

  • Book 구조체가 생겼다.

  • 구조체는 pin을 split할 수 있었다.
    하지만 이건 권한 부여를 하지 않았다라는 말이 뜨는 것 같다.

이전 BP(2) 글 참고

  • 아래처럼 설정하면 된다.
    이름만 readonly로 하였다.
    참고로 뒤의 EditAnywhere는,
    유니티의 [Serialized]처럼 탭에서 변수를 수정할 수 있게 보여주는 역할을 한다.
    안해도 보이던데...

  • 정상적으로 split이 되고, 값도 탭에서 값도 설정이 가능한 모습이다.

  • 참고로 그냥 editanywhere없이 설정했던 OPIv라는 int변수도 탭에서 바꿀 순 있다.
    [참고]

이 프로퍼티는 아키타입이나 인스턴스 양쪽의 프로퍼티 창에서 
편집할 수 있습니다. 
이 지정자는 어떤 "Visible" 지정자와도 호환되지 않습니다.

  • set members를 하고, 탭을 보니, read only인 bName은 보이지 않는 모습이다.
    나머지는 체크하여 pin을 나오게하고, 직접 값을 대입할 수 있게 된다.

C++: Unreal Functions

  • C++ 문법과는 다르게,
    USTRUCT 내부에 UFUNCTION을 둘 수 없다고 한다.

  • 그냥 STRUCT는 된다.

  • UCLASS로 지정한 Class에 [static]Test함수를 만들었다.
    이게 언리얼상에 String을 print하도록 하고 싶다면 어떻게 해야할까 막막하다.

  • print string unreal c++이라고 구글에 치고,
    대개 docs.로 시작하는 문서 사이트를 열면 알아낼 수 있다.

  • include 부분을 먼저 넣고,
    [헤더 포함 할 때, 순서 조심]
    UKismetSystemLibrary::PrintString를 일단 넣는다.

  • 무조건 클래스는 C++에서 포인터로 받아야한다.
    절대 ref로 넣지 않는다.

static void PrintString
(
    UObject * WorldContextObject,
    const FString & InString,
    bool bPrintToScreen,
    bool bPrintToLog,
    FLinearColor TextColor,
    float Duration
)
  • 그래서 위의 규칙에 맞게 UObject*와 const Fstring인 문자열을 넣었다.
    여기서 FBook은 쓰이지 않았다.

  • 레벨 블루프린트의 Beginplay에 연결해보았다.
    그냥 test로 선언이 되고,
    그냥 self를 설정하니 프린트 된다.

  • static을 빼도 정상동작한다.
    하지만 BP가 다음과 같이 바뀌게 된다.
    해당 클래스의 이름으로만 TEST에 접근할 수 없기 때문에,
    객체를 직접 자동으로 만들어서 Test에 접근하는 방식같다.


C++: Enumerations

  • enum도 C++에서 만들고 BP에서 인식하게 할 수 있는데,
    그렇게 하기 위해선 UENUM이라는 매크로를 쓰면 된다.

  • 다음 노드로 enum값을 가져와 본다.

  • 보면 cherry만 명칭이 다른데,
    코드상에 UMETA로 Display되는 명칭을 바꿀 수 있다.

  • 참고하면 된다.
    enum, class, funciton등 메타데이터에 대한 부분을 보이거나 숨길 수 있는 간단한 기능이다.
    https://docs.unrealengine.com/4.27/ko/ProgrammingAndScripting/GameplayArchitecture/Metadata/


Converting BP to Cpp

  • C++의 클래스, 구조체, enum등 BP로 가져오는 것을 해봤었다.
    이번에는 BP의 것을 C++로 가져와보도록 한다.

  • 한가지 문제가 있는데, BP에서 선언한 클래스는 C++로 가져올 수 있는 방법이 없다.

  • 일단 CPP로 캐릭터, 액터 클래스를 생성한다.
    그리고 실제 BP 객체에 들어가서 class settings를 각각 설정한다.

  • 캐릭터 내의 해당 BP를 CPP로 바꿔본다.
    왼쪽마우스를 누르면,
    플레이어의 카메라로부터 전방의 Linetrace(Raycast)하는 노드들이었다.
    Line에 맞은 pickup객체는 사라지게 된다.

  • 가장 맨 앞 노드부터 본다.
    마우스를 위에 올리면, 설명이 나오는데,
    맨 아래, Gameplay Statics를 주목해야한다.

  • 그대로 구글에 치면, 해당 Gameplay Statics 기능에 대한 노드들이 나오고,
    거기서 Get Player Camera Manager를 찾을 수 있게 된다.

사이트로

  • 그러면 이전의 print string때와 똑같이
    헤더파일부터 include하고, 맨 위의 줄을 복사하고, Syntax에 맞게 사용하면 끝이다.

  • 해당 기능을 추가하기 전에 먼저
    #include "Kismet/GameplayStatics.h"를 하고,
    void RemovePickup()라고 멤버함수를 만든다.
    해당 함수 내에서 Camera Manager를 가져오는게 논리상 맞다.

  • 그래서 다음과 같이 코드를 짠다.
    해당 클래스로 객체를 만들고, RemovePickup함수를 호출한다면,
    해당 호출한 객체의 것이 실행되도록 하는 것이다.

//실제로 사용할 함수
void ACPP_Character::RemovePickup()
{
	UGameplayStatics::GetPlayerCameraManager(this, 0);
}
  • 그 다음 노드는 Vector관련이다.
    위의 함수의 반환값에 해당하는 APlayerCameraManager* 객체를 가지고,
    Vector값을 추출해야한다.

  • 그래서 해당기능의 return값을 저장한다.
    여기서 타입을 auto로 지정했는데, 파이썬에서 a = 10; 하면 자동으로 a가 int가 되듯,
    C++도 해당 기능을 지원하므로 사용해보았다.

  • 여기서 temp는 APlayerCameraManager*라는 포인터객체를 의미하고,
    포인터 객체의 멤버함수, 멤버변수에 접근하기 위해 ->를 사용한다.
    굳이 tmp로 바꾸지 않고 사용해도 된다.
    그렇게 된다면 UGameplayStatics::GetPlayerCameraManager를 계속 쳐야할 것이다.

  • 의문인 것은, 두 가지 함수가 다 존재하는데,
    Get Actor Location은 private라 사용하지 못하고,
    검색해도 안나오는 Get Camera Location을 사용한다고 한다.
    혼자한다면 꽤나 고생할 부분

  • 다음에 할 것은, Forward Vector를 * 연산하여
    Linetrace노드에 넣는 코드이다.

  • 해당 연산부터 먼저하면, 다음과 같을 것이다.

auto tmpVec = tmpCam->GetCameraLocation() + 
(tmpCam->GetActorForwardVector() * 500000);
  • 그 다음 Linetrace는 UKismetSystemLibrary이다.
    검색해도 안나온다. 안나오는게 꽤 된다..
    일단 헤더만 추가해놓고, #include "Kismet/KismetSystemLibrary.h"
    강의를 보니 그냥 linetracesingle함수를 사용한다.
    기능이 가장 비슷한가보다. 똑같이 start Vec, end Vec이 존재한다.

  • 아래와 같이 코드를 작성하였다.

  • 최대한 해당 노드에 맞게 작성해보았다.
    해당 LineTraceSingle과 변수의 순서는 맞지 않지만, 대강 맞긴하다.
    start, end를 tmpCamLoc, tmpVec으로 넣어주었고,
    TraceChannel의 경우, ETrace뭐시기의 Query1을 해주면 된다고 한다. 혼자서는 절대 못찾을듯
    그리고 미리 false로 설정한 traceComplex값과, 중간에 배열로 들어가야하는데, 아무것도 넣지 않은 ActorsToIgnore배열을 넣었고,
    Drqw Debug Type을 해당 For duration으로 혼자서는 절대 못찾을듯
    Hit result로는 outHit을 넣었고, (노드에는 입력으로 없다)
    true로 설정한 ignoreSelf값을 넣었다.
    그리고 추가로 Trace Color도 설정가능한데, 안넣어도 된다고 한다.


Converting BP to Cpp (2)

  • 이제 pickup을 cpp로 만든것도 써보기 위해서 header파일을 넣는다.
#include "CPP_Pickup.h"
  • 이렇게 작성한다.
    GetActor와 Cast가 어떤원리로 if문에 쓰이는지는 모르겠으나 동작한다.

  • Cast는 Unreal CPP에서만 쓸 수 있는 예약어인데, 아래와 일맥상통한다.
    그리고 Destroy한다.

  • UFUNCTION으로 Blueprintable설정하고, 불러와서 왼쪽클릭시 해당함수가 실행되도록 한다.

  • 정상적으로 동작한다!

  • 다시 알아야할 것은,
    캐릭터가 CPP_Character을 상속받고,
    사라질 물체가 CPP_Pickup을 상속받기 때문이고,
    Cast라는 특정 기능때문에 최종적으로 동작하게 된다.
    (B의 parent가 A면, B=A이기도 하다)

profile
Time Waits for No One

0개의 댓글