이번에는 많이들 궁금해하는 C++와 블루프린트의 연동에 대해 알아보겠습니다.
언리얼 프로젝트 제작에 있어서 개발 방식을 C++로만 해야할지, 블루프린트만 사용 할지,
두 언어를 혼합할지를 결정하는 것은 어려운 일입니다.
팀의 구성이나 개발자 분들의 선호에 따라 정답이란 존재하지 않는 부분이기 때문이지요.
이번 강좌에서는 여러분들의 참고를 위해서 C++ 클래스를 블루프린트로 확장하는 방법에 대해 알아보겠습니다.
지난 강좌에서 제작한 AWeapon 클래스를 상속받은 블루프린트를 만들어보겠습니다.
콘텐츠 브라우저에서 [신규추가 > 블루프린트 클래스]를 선택하고 부모 클래스를 Weapon으로 지정해 BP_CppWeapon이라는 블루프린트를 제작합시다.
결과는 아래와 같이 블루프린트 클래스가 생성되고 Weapon을 상속받은 BP_CppWeapon이라는 클래스가 새롭게 생성된 것을 확인할 수 있습니다.
이제 이 블루프린트를 더블클릭해서 열어보면,
이전에 제작한 컴포넌트들이 (상속됨)이라는 형태로 이미 만들어져 있는 것을 볼 수 있습니다.
이전에 제작한 BP_Weapon과 유사하지만 다만 Weapon이라는 스켈레탈메시 컴포넌트를 선택해도 오른쪽
디테일 윈도우가 텅 비어 있어서 아무 것도 할 수 없다는 점이 다릅니다.
(디테일 창 아무것도 안보인다.)
이러한 이유는 C++에서 제작한 컴포넌트가 블루프린트에서 어떻게 보여질지를 우리가 지정하지 않았기 때문입니다.
언리얼오브젝트 권한
기본적으로 C++에서 제작된 언리얼 오브젝트나 데이터는 블루프린트에서는 편집이나 수정할 수 없는 것이 원칙입니다. 따라서 BP_Weapon과 같이 만들려면 C++에서 블루프린트에 충분한 권한을 주어야 합니다.
이러한 권한은 클래스 / 멤버 변수 / 멤버 함수의 세 종류에서 지정할 수 있습니다.
[3번은 제일 밑에 있음]
1. 클래스의 권한
언리얼 오브젝트가 블루프린트에서 상속받을 수 있는 클래스가 되려면
UCLASS 매크로 안에 아래의 두 가지 타입을 넣어 주어야 합니다.
1. BlueprintType : 이 C++ 클래스는 블루프린트에서 변수로 선언이 가능한 타입임을 지정합니다.
2. Blueprintable : 블루프린트에서 이 C++ 클래스를 상속받아서, 새롭게 클래스를 확장할 수 있습니다.
하지만 우리가 제작한 AWeapon 클래스는 위의 두 가지 키워드를 선언하지 않았는데도 불구하고,
블루프린트에서 잘 상속받아서 잘 쓰고 있는 상황입니다.
이것은 AWeapon이 상속받은 AActor에 이미 위의 두 선언이 들어가 있기 때문입니다.
AActor를 선택하고 F12키를 눌러서 AActor의 정의를 보면
아래와 같이 BlueprintType과 Blueprintable이 가장 먼저 선언되어 있음을 확인할 수 있습니다.
따라서 액터 AActor를 상속받은 C++ 클래스는 블루프린트에서 상속 가능하고,
블루프린트에서 변수형으로 지정할 수 있습니다.
UCLASS(BlueprintType, Blueprintable ...
class ENGINE_API AActor : public UObject
{
2. 멤버 변수의 권한
멤버 변수의 권한은 UPROPERTY 매크로를 통해서 지정할 수 있습니다.
블루프린트 로직에서 해당 멤버 변수에 읽기쓰기를 할 수 있는지, 읽기만 하는지에 대한 권한을 각각 BlueprintReadWrite, BlueprintReadOnly 값을 지정할 수 있습니다.
우리가 생성한 스켈레탈메시 컴포넌트인 Weapon 변수에 대해 BluerprintReadWrite 키워드를 부여한다고 가정해봅시다.
이 변수의 UPROPERTY(BlueprintReadWrite)라고 선언하면 스켈레탈 메시 컴포넌트는 CDO에서 액터와 함께
정적으로 생성되도록 지정했는데 블루프린트에서 오브젝트 값을 변경한다는 의미가 되어버립니다.
이렇다면 굳이 이 컴포넌트를 정적으로 생성하는 것이 의미가 없을 뿐더러, 엔진에서도 혼란스러워 할 것입니다.
따라서 CreateDefaultSubobject로 생성하는 정적인 컴포넌트들은 모두 UPROPERTY(BlueprintReadOnly)로 선언해야 초기 설계 개념에도 부합한 선언이 되겠습니다.
정리하자면 컴포넌트를 선언에 사용된 멤버 변수는 BluerprintReadOnly을 지정해야 한다라고 이해하시면 됩니다.
이번에는 언리얼 오브젝트가 아닌 일반 밸류에 사용하는 float 형 변수 BaseDamage를 클래스 선언에 추가하겠습니다.
(이후 설명부터는 float , string , int32등과 같이 스택을 사용해 값을 전달하는 타입을 밸류타입이라고 통칭해서 이야기하겠습니다.)
블루프린트에서 검의 데미지를 항시 고정시키려면 UPROPERTY(BlueprintReadOnly)를 사용하고 물약 효과 등에 의해 검의 데미지를 블루프린트에서 변경하고 싶다면 UPROPERTY(BlueprintReadWrite)로 선언해주면 됩니다.
아래는 C++의 두 멤버 변수의 블루프린트 권한을 지정한 코드입니다.
//weapon.h
{
public:
UPROPERTY(BlueprintReadOnly) //메시 못 바꾸게
class USkeletalMeshComponent* Weapon;
UPROPERTY(BlueprintReadWrite) //대미지는 변경 되게
float BaseDamage;
}
컴파일을 완료한 후 BP_CppWeapon을 열고,
내 블루프린트 윈도우의 눈동자 모양의 옵션 버튼을 누르고 상속된 변수를 체크합시다.
그러면 아래와 같이 Weapon이라는 그룹에 BaseDamage 변수가 나타나는 것을 확인할 수 있습니다.
이것이 블루프린트에서 상속받은 변수입니다. Weapon과 BaseDamage를 각각 이벤트 그래프로 끌어다 놓으면,
Weapon은 읽기만, BaseDamage는 읽기 쓰기가 가능하게 UI가 나옵니다.
에디터 인터페이스에서 권한
편집가능여부 / 편집화면출력 여부
변수가 상속받아서 블루프린트 로직에서 읽거나 쓰기가 가능한 것은 확인했지만,
여전히 Weapon 스켈레탈메시 컴포넌트는 블루프린트에서 편집이 안되고,
BaseDamage 변수의 값도 편집할 수 있는 공간이 없습니다.
이는 블루프린트 스크립트의 읽고 쓰기 권한과
무관하게 에디터 인터페이스에서 변수를 편집할 수 있게 지정해주어야 하기 때문입니다.
이를 지정하는 키워드는 여섯 개나 되어서 조금 복잡합니다.
아래의 여섯 가지 중 하나를 변수의 성격과 쓰임새에 따라 여러분들이 직접 지정해주어야 합니다.
- EditDefaultsOnly
- EditInstanceOnly
- EditAnywhere
- VisibleDefaultsOnly
- VisibleInstanceOnly
- VisibleAnywhere
여섯 가지 키워드를 잘 살펴보면
전자인 Edit / Visible 그룹과
후자인 DefaultsOnly / InstanceOnly / Anywhere의 세가지로 나눌 수 있습니다.
전자인 Edit와 Visible은 편집이 가능한지, 보여주기만 할 것인지를 지정하는데 사용합니다.
위에서 액터의 컴포넌트는 설계 단위에서부터 목적이 명확하기 때문에 향후에 바꿀일이 있으면 안된다고 설명드렸습니다. 따라서 우리의 스켈레탈 메시 컴포넌트는 Visible을 사용해야합니다. (보여주기만)
오브젝트에 Visible 키워드를 사용하면 오브젝트 레퍼런스는 변경이 되지 않지만,
오브젝트 내 속성들은 블루프린트에서 편집이 가능합니다.
그러면 딱 우리가 원하는 결과가 나옵니다.
반면, 밸류타입인 BaseDamage를 Visible로 지정하면 이는 읽기만 가능해집니다.
따라서 밸류타입은 대부분 Edit를 사용하는 것이 일반적입니다.
후자인 DefaultsOnly / InstanceOnly / Anywhere은 변수의 활용 용도에 따라 다릅니다.
DefaultsOnly는 클래스 설계도에서만 변수 편집 화면을 보여주도록 지정하는 키워드입니다.
InstanceOnly는 언리얼 오브젝트의 인스턴스에서만 변수 편집 화면을 보여주게 만드는 키워드이지요.
Anywhere은 말 그대로 둘 다 보여주게 하는 키워드입니다.
예를 들어서 하나의 몬스터를 기획하는데, 공격 범위와 몬스터의 레벨을 저장할 변수를 두 개를 선언했습니다.
이 몬스터는 에디터에서 블루프린트를 맵에 드래그하여 배치하는데, 플레이어의 시작지점과 가까운 위치에 있는 몬스터는 낮은 레벨로, 플레이어와 멀어질 수록 배치된 몬스터는 점점 높은 레벨로 설정할 수 있도록 몬스터를 설계한다고 가정합시다.
맵에 배치된 모든 몬스터의 공격 범위를 동일하게 만들려면, 공격 범위에 해당하는 변수는 DefaultsOnly키워드를 사용해 모든 배치된 몬스터 액터들이 같은 값을 가지도록 설계하는 것이 관리적인 측면에서 유리할 겁니다.
하지만 몬스터 레벨의 정보는 플레이어 시작 위치로부터의 거리에 따라 배치된 몬스터 액터 인스턴스들마다 점점 높아지는 값을 가져야 합니다.
이 경우 레벨 변수에 InstanceOnly 키워드를 사용하는 것이 관리적인 측면에서 편리하겠지요.
이 값들의 용도를 정리하면 다음과 같습니다.
|
Edit |
Visible |
DefaultsOnly |
인스턴스가 공통으로 가져야 할 값의 편집 |
설계화면에서만 보여질 값 |
InstanceOnly |
인스턴스 별로 다르게 있어야 할 값의 편집 |
인스턴스에서만 보여질 값 |
Anywhere |
범용적으로 변경되는 값의 편집 |
객체 레퍼런스 및 모든 화면에서 보여질 값 |
이를 우리의 Weapon 클래스 멤버 변수에 적용해봅시다.
객체 레퍼런스에 해당하는 스켈레탈메시 컴포넌트는 VisibleAnywhere 키워드를, 모든 인스턴스가 공통으로 가져야 할 BaseDamage는 기획의도에 따라 설계시 데미지 값으로 통일하고 싶으면 EditDefaultsOnly를 사용하고, 월드 상의 액터 인스턴스마다 다른 데미지 값을 가지고 싶으면 EditInstanceOnly를, 모든 곳에서 데미지 값을 변경하게 만들고 싶으면 EditAnywhere 키워드를 지정하면 됩니다.
드문 경우이기는 한데, 에디터에서 편집은 안되고 읽기 전용으로 보여지기만 원한다면 VisibleAnywhere을 사용해도 무방하고, 노출 범위에 따라 VisibleDefaultsOnly 혹은 VisibleInstanceOnly를 사용하면 됩니다.
아래는 용도에 맞게 UPROPERTY 매크로에 키워드를 추가한 코드 입니다.
[인스턴스만 생성된 인스턴스에만 보이고, 디폴트로 해야 블루프린트에서도 보인다]
{
UPROPERTY(BlueprintReadOnly, VisiableAnyWhere)
class USkeletalMeshComponent* Weapon;
UPROPERTY(BlueprintReadWrite, EditInstanceOnly) //EditDefaultsOnly
float BaseDamage;
}
그러면 이제 스켈레탈메시 컴포넌트에 대해서 블루프린트 에디터에서 편집이 가능해지고,
액터 기본 값 목록에서 BaseDamage 값을 편집할 수 있게 되었습니다.
Category
현재 변수들은 기본적으로 카테고리가 상위 클래스 이름인 Weapon으로 지정되어 있습니다.
에디터에서 보여지는 카테고리 이름을 변경하고 싶으면 Category 키워드를 추가해주면 됩니다.
아래는 에디터에서 보여줄 카테고리 정보까지 지정한 최종 코드입니다.
{
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "Stat")
float BaseDamage;
}
meta = AllowPrivateAccess
이 외에도 UPROPERTY에는 meta 네임스페이스에서 지정된 여러가지 키워드를 사용해 에디터에서
도움말과 같은 것들을 출력할 수 있거나 에디터 연동 관련해서 기타 다양한 값들을 지정하는 것이 가능한데,
meta 키워드 중에서 눈여겨볼 부분은 AllowPrivateAccess라는 값입니다.
블루프린트는 C++ 클래스를 상속받는 개념이므로 기본적으로 public이나 protected 변수들만 적용이 가능합니다.
하지만 AllowPrivateAccess 키워드를 추가하면 private 변수도 블루프린트 스크립트에서 활용이 가능합니다.
이는 OOP 설계시 멤버 변수에 직접 접근하는 디자인보다 액세서(Accessor)라 불리는 함수를 통하는 디자인 방식을 선호하는 분을 위해 제공하는 기능입니다.
아래와 같이 UPROPERTY 매크로에 해당 키워드를 추가하면 OOP 코딩 설계를 따르면서도,
블루프린트로도 노출하는 변수 제작이 가능합니다.
{
public:
float GetMyHP() { return HP; } //액세서
private:
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "Stat", meta = (AllowPrivateAccess = "true"))
float HP;
}
여기서 AllowPrivateAccess 부분이 없으면, 빌드가 오류난다.
private 인데 BlueprintReadWrite이니 허용이 안되는 거 같다
3. 멤버 함수의 노출
언리얼 오브젝트에서 클래스 멤버 함수도 마찬가지로 기본적으로 블루프린트에서 사용할 수 없지만,
BlueprintCallable이라는 키워드를 사용하면 블루프린트에서 호출이 가능합니다.
함수를 블루프린트에서 사용하고자 위의 키워드를 사용할 때에는 반드시 카테고리를 지정해서 블루프린트에서 검색할 수 있게 만들어주어야 합니다.
따라서 아래 코드는 Weapon 카테고리와 하위의 Stat 카테고리를 선언한 구문입니다.
{
public:
UFUNCTION(BlueprintCallable, Category = "Weapon|Stat")
float GetDamage() { return BaseDamage; }
private:
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "Stat", meta = (AllowPrivateAccess = "true"))
float BaseDamage;
}
이 외에도 멤버 함수에는 블루프린트에서의 이벤트로 지정할 수 있는 BlueprintImplementableEvent 키워드와
C++와 블루프린트 두 군데에서 모두 이벤트를 처리할 수 있는 BlueprintNativeEvent 등의 키워드들이 있습니다.
전자는 이벤트 구현의 의무를 완젼히 블루프린트에 부여하고 싶을때에 사용하고,
후자는 C++에서 이벤트 로직을 구현하되, 블루프린트에서도 대신 구현할 수 있게 만들 때 유용합니다.
'Unreal > Study' 카테고리의 다른 글
10. 언리얼 C++ 딜리게이트 + 종류 + 바인딩 (0) | 2019.05.30 |
---|---|
9. INI 설정과 런타임 애셋 로딩 + INI구성 + 레퍼런싱 + config (0) | 2019.05.29 |
7. 액터의 제작+ 트랜스폼 + 블루프린트 액터, C++액터 (0) | 2019.05.28 |
6. 언리얼 오브젝트의 계층 구조+레벨+액터검색,순회 (0) | 2019.05.27 |
5. UClass와 리플렉션 + 로그디버깅 (0) | 2019.05.27 |