상세 컨텐츠

본문 제목

pond 프로젝트 14 - c++로 프로젝트 변경

Unreal Engine 4/Project

by hwano 2015. 5. 21. 19:09

본문

 

구조 변경

 

 

 

지금까지 하던 방식은 FishAI라는 actor를 하나 만들어서 여기에 모든 ai시스템을 다 넣었다.

tick에 sequence를 연결하여

move, rotation, targetSetting, fingerSetting, hud 등등을 순차적으로 실행하면서

ai를 구현하는 방법이었다.

 

 

하지만 c++를 이용하게 되어 다시 구조를 좀더 효율적으로 바꾸려고한다.

FishCharacter, Waypoint, Destination, Fingerpoint

네개가 각각 ai의 일부분을 나눠같고 서로에게 영향을 주는 방식이 된다.

 

예를 들어 FishCharacter 내부에는 moveSpeed에 대한 변수가 있고

Fingerpoint 근처에 들어오게되면 Fingerpoint가 Fish의 moveSpeed에 영향을 주게 된다.

 

//Destination은 실체가 없고 그냥 Waypoint안에 변수로만 존재하게 하자

 

 

 

 

클래스 지우기

 

 

엔진소스를 다운받아 언리얼 editor자체를 빌드해 쓰면 쪼금 더 복잡하지만, 내 경우엔

1. VS의 익스플로러에서 해당 cpp와 h를 지운다

2. 윈도우상의 해당폴더로가서 또 지운다.

3. 에디터와 VS를 끈다.

4. 프로젝트의 binary폴더를 비운다.

5. 다시 VS를 실행한다.

 

 

 

 

 

수정한 것을 바로 적용하기

 

 

아직 컴파일이나 디버깅에 대한 개념이 명확히 없어서 잘 모르겠다.

우선 다음과 같은 방법으로 하고 있다.

1. VS의 solution configration을 Debug Game Editor로 두고

    디버그/스타트 뉴 인스턴스해서 에디터를 연다.

2. VS에서 코딩을 수정하고 세이브만 한다음 에디터에서 위쪽에 컴파일 아이콘을 누른다

 

 

 

 

 

 

C++에서의 Print String

 

 

여러 방법이 있는데 이게 가장 쉽다.

GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, TEXT("This is the log!"));

일반 텍스트를 쓰려면 저렇게 하고 int형같은 숫자를 뽑아내려면 TEXT부분을 아래와 같이 바꾼다

FString::FromInt(counter)

float형은

FString::SanitizeFloat(counter)

 

 

 

GetWorld에서 Null이 리턴되는 문제

 

 

SpawnActor를 하려면 UWorld를 받아와야 하는데

UWorld* const World = GetWorld();
if (World) {}

이런식으로 받아와도 계속 null이 나와서 실행이 안되었었다.

 

저 구문이 실행되는 곳이 begin 혹은 tick, gamemode등이어야 한다.

어느 레벨이 시작되어서 혹은 어느 레벨안에서 tick이 발생했을때

이런식으로 명시가 되어야 world를 찾을 수 있는거 같다.

 

//////////////////////////////////////////////////////////////////////////

설명이 이상해서 한번 더 쓰면 spawnVolume 클래스를 만들고

위 구문이 포함된 함수를 만들었다.

그 함수를 spawnVolume.cpp안의 생성자

에서 호출하였는데 실행이 안되었다. 나는 생성자가 begin이나 같은 개념이라고 생각했는데

따로 begin event 나 tick으로 불러야 한다.  

 

//추가

생각해보면 생성자는 컨텐츠브라우저나 새로운 블루프린트를 만들거나

언리얼 에디터 자체가 생성될때 호출이 될거다.

 

그리고 Begin 같은 경우엔 게임 도중 actor가 스폰된다거나 하면 그때서야 호출되는거니

당연히 다른다. 따라서 level에 놓여져 있지 않고 컨텐츠브라우저에만 있는 클래스 상태라면

GetWorld()를 받아올 수 없는게 당연하다.  당연하지.

 

 

 

 

Tick이 작동이 안될 때

 

 

확인은 안해봤는데 블루프린트와 달리 캐릭터클래스가 아니면 tick이 작동이 안될수도 있다고 한다.

작동이 안되면 생성자에

PrimaryActorTick.bCanEverTick = true;

구문을 넣어준다.

 

 

 

 

 

 

Super 클래스

 

 

구문을 보다보면 Super::BeginPlay(); 이런 Super가 들어가는게 많이 있다.

https://docs.unrealengine.com/latest/INT/API/Runtime/Engine/GameFramework/UDamageType/Super/index.html

이게 뭔말이지? 우선 'typedef'가 뭔지 몰라 검색

 

http://blog.naver.com/skout123?Redirect=Log&logNo=50131823652

위에 페이지를 읽어보면 결국

 

Super::BeginPlay()는 현재 클래스의 부모클래스인 aactor의 BeginPlay를 실행한다.

이런말이 되는건가? 잘 모르겠다.

 

 

 

 

 

Override 문법

 

 

헤더파일에 보면 아래와 같은 문장이 있다.

virtual void BeginPlay() override;

 

이게 나온지 얼마 안된 문법이라고 한다.

http://blog.naver.com/kyed203?Redirect=Log&logNo=220106037428

 

상위 클래스의 함수를 override하는데 override 키워드를 붙여주면

상위 클래스에 그 이름의 함수가 없을때 에러를 뱉어준다고 한다.

 

 

 

 

나름대로의 유추

 

 

문법을 제대로 공부하기 싫어 대충 유추해 본다.

 

헤더의 기본 클래스에서

virtual void BeginPlay() override;

요문장으로 beginPlay를 가상함수로 선언한다.

가상함수는 자식클래스에서 부모클래스에 있는 함수지만 내 나름대로

내 상황에 맞춰 다시 설정할꺼야 라는 그런 뜻.

 

그래서 cpp에서

 

void AWaypoint::BeginPlay()
{
 Super::BeginPlay();
}

 

이부분은

 

BeginPlay를 내 나름대로 다시 설정할껀데 우선

Super::BeginPlay로 부모클래스에 있던 원래 BeginPlay 기능을 한번 실행하고

그 아래줄에 내가 따로 추가한 문장들을 실행해야지~.

 

라는 뜻인거 같다..    아님 말고.

 

 

 

 

Waypoint를 Pawn

 

 

다행히 프로그래밍 강좌의 예제가 actor를 pawn하는 거여서 그냥 고대로 가져와 여기 까지는 쉽게

구현했다. 이제 물고기까지 Spawn시키자

 

 

 

 

 

 

 

 

FishCharacter Spawn

 

 

 

UPROPERTY값

 

UPROPERTY()를 많이 쓰게 되는데 그냥 일반 블루프린트에서 staticMesh

컴포넌트를 추가했다던가 했을 때 처럼 보이게 하려면

UPROPERTY(VisibleDefaultsOnly)로 하는게 맞는거 같다.

 

그리고 일반적으로 변수를 만들어 editable로 만들어 details창에서 변수를 설정할때 처럼

값을 만들려면 EditAnywhere로 해준다.

int형이라면 int32로 해야 detail찯으로 노출된다.

 

 

 

각도계산

 

FVector WaypointLocation;
FVector FishLocation;
FVector FishDirection;

WaypointLocation = Waypoint->GetActorLocation();
FishLocation = GetActorLocation();
FishDirection = GetActorForwardVector().GetSafeNormal();

 

tmp = acosf(FVector::DotProduct((WaypointLocation - FishLocation).GetSafeNormal(), FishDirection)) * (180 / 3.1415926);

 

 

 

 

KismetMathLibrary

 

#include "Kismet/KismetMathLibrary.h"

 

 

 

라이브러리 추가

 

위에서 처럼 KismetMathLibrary 추가했더니

unresolved external symbol 에러가 난리가 난다.

나는 모르는 symbol인디 뭐시여? 라는 뜻이란다.

생각해보니 마야 플러그인 만들때도 라이브러리 추가를 안해줘서 에러가 났었던 기억이 난다.

 

해결방법은 못찾고 있다 ㅜ

 

 

Github

 

그냥 github에서 kismet~으로 검색하면 라이브러리 내부 함수들이 보인다.

그걸 가져다 쓰면 된다.

 

 

 

FInterpTo

 

FMath::FInterpTo(Current, Target, DeltaTime, 1.f);

이건데 헷갈리는게 Current Target같은 고정된 값을 넣으면 안된다.

 

float tmp;

tmp = FMath::FInterpTo(tmp, Target, DeltaTime, 1.f);

이런식으로 보간된 각도를 다시 넣어줘야 제대로 작동한다.

 

마지막 interpolate speed 값이 높을수록 빨리 보간된다.

 

회전용으로 썼던 RInterpTo도 마찬가지 여서

마지막 스피드에 물고기와 waypoint와의 각도를 넣으면

서로 바라보는 각도차가 클수록 자연스레 더 빠른값을 같게 된다.

 

 

 

 

 

FingerVolume

 

 

블루프린트에서 C++로 값 전달

 

FingerVolume은 이번엔 따로 안만들고 그냥 TracePlane을 만들어 손가락이 클릭되면 위치가

TracePlane안에 저장되게 한다.

Fish안에 FingerOnOff를 만들어서 손가락이 있고 없고 일때 Fish들의 FingerOnOff값을 바꿔준다.

그러면 Fish내부에서 계산할때 FingerOnOff를 확인하여 On이면 TracePlane에 접근하여 값을

가져가게 한다.

 

그냥 public에

bool FingerOnOff; 이렇게 선언하는 것만으로는 잡히지 않고

https://wiki.unrealengine.com/Blueprints,_Creating_Variables_in_C%2B%2B_For_Use_In_BP

 

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Finger)

요런식으로 uproperty를 달아줘야 된다.

 

근데 이게 다 안잡힌다. bool int는 안되고 int32는 되네? 되는 값만 되는거여 뭐여 짱나

 

 

 

블루프린트에서 C++로 만든 변수가 보이게 하기

 

기본적으로 c++로 만든 클래스를 열어도 C++안에서 만든 변수는 왼쪽 변수 창에서 보이지 않는다.

변수 이름으로 검색해 보면 노드가 검색이 되는데 이게 될때도 있고 안될때도 있고 잘모르겠네.

안되면 껏다 켜야하는듯?

 

잘되던 변수를 똑같이 코드상에서 복사해서 이름만 다르게 3개를 만들었더니 갑자기 또 블루프린트에서 안잡힌다.   검색을 해보니 있긴있는데  hotreloaded에러가 났다.

이런경우 에디터를 껐다 키면 정상적으로 나온다.

 

 

 

 

블루프린트에서 C++간의 통신

 

원래 의도는 TracePlane이라는 클래스를 에디터상에서 생성해서 블루프린트를 작성하고

여태까지 만들었던 FishCharacter나 Waypoint같은 클래스들과 연결시키려고 했다.

근데 그렇게 하면 c++상에서는 에디터에 어떤 클래스들이 있는지 모르니까 하나하나 클래스, 변수들을

검색해서 연결해야 한다. 그게 귀찮고 코드 배우기 싫어서 그냥 TracePlane클래스도 C++에서 만들고

필요로 하는 변수들을 집어 넣을 생각이다.

 

에디터상에서는 그냥 이걸 상속받아 TracePlane을 만들고 블루프린트로 작성하여 c++로 만들어둔 변수에

값을 넣으면 C++상에서 접근하기 용이해진다.

 

 

 

TracePlane

 

정리하면 FishCharacter에

bool FingerOnOff를 만들어둔다.

TracePlane에서 블루프린트로 Finger가 찍히면 Fish들의 FingerOnOff를 true시켜준다.

그리고 Finger의 위치는 TracePlane에 저장해 둔다.

 

그러면 FishCharacter안에서 Movement계산을 할때 FingerOnOff를 검사하여 필요할때

TracePlane에 접근하여 위치값을 가져가게 된다.

 

끗. 이렇게 하자

 

 

 

클래스 이름문제

 

에디터상에서 새로운 블루프린트를 만들때 c++로 만들어둔 클래스를 선택을 하게 된다.

보통 일반적인 블루프린트클래스를 만들때 actor를 선택해서 만들게 되는데 생각해보면

그건 actor를 상속받아 만드는 거다.

무슨말인고 하니 내가 c++로 만든 클래스를 쓰고 싶으면 블루프린트 만들때 내가 만든

c++ 클래스를 선택하게 되면 이름이 중복이 되게 된다.

 

FishCharacter    -  c++상의 클래스

 ㄴ  FishCharacter      - 블루프린트에서 상속받아 만든 블루프린트 어셋

 

이렇게 되니 블루프린트들 안에서 호출할때 클래스 이름이 같은게 두번떠서 무척 헷갈리는 상황이

오게 된다.

 

ue4 프로그래밍 개요 강좌를 보니

FishCharacter로 만들고

BP_FishCharacter 이런식으로 블루프린트를 만드는거 같다...

 

나도 그런식으로 만들어야 겠다.

원래 FishCharacter를 FishCharacter_Base로 고치고 블루프린트를 FishCharacter로 해서 쓰려고 했는데

 

FishCharacter_Base로 해버리니 코드가 엄청나게 지저분해진다.

작업이 불가하게 되어 그냥 상속받은 애들이름을 바꾸는 걸로

 

 

 

클래스 이름변경

 

클래스 이름을 바꿔주려면 딱히 방법이 없다고 한다.

그냥 새 클래스를 만들고 복사해 넣어서 새로 만들어야 한다고 한다......

 

 

 

 

 

C++에 커스텀 이벤트 만들기

 

UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = Finger)
void FingerPress();

 

위와 같이 만들어 주면 된다.

 

UFUNCTION(BlueprintNativeEvent) 이렇게 간단하게도 만들수 있는데 이렇게 만들면 블루프린트에서 자기 클래스에서는 이벤트가 불러지는데 다른 블루프린트에서 cast하여 검색해보면 안잡힌다.

BlueprintCallable까지 해줘야 다른 클래스의 블루프린트에서 접근하더라도 부를 수 있다.

 

 

 

cpp에 만들때는

void AFishCharacter::FingerPress_Implementation()

{}

이처럼 원래 이름뒤에 _Implementation을 추가해야한다(필수)

이게 블루프린트안에서 임의로 event의 동작을 지정할 수 있다. 그렇게 되면 c++안에서 내가 작성했던

event내용은 블루프린트안에서 새로만든 내용들로 override된다.

 

만약 블루프린트안에서 아무 내용도 지정하지 않았다면 C++에서 지정해 놓았던 디폴드 내용을

자동으로 실행하게 되는데 이를 정의한 것이기 때문에 _Implementation을 추가하게 된다.

추가안하면 에러가 나서 실행이 금지 된다.

 

 

 

 

도망가는 물고기

 

도망가는 물고기를 움직임을 표현하기 위해 몇가지 시도해 봤는데 그럴싸한게 없다.

 

손가락으로 찍으면 손가락을 피해서 반대쪽으로 움직여야 한다.

하지만 지금 움직임들은 물고기를 직접 움직이는게 아니고 Waypoint를 따라서 무작정 움직이도록하고

Waypoint를 이동시켜 간접적인 움직임을 주는 방식이다.

 

물고기의 사고를 기준으로 생각해보면 10m 전방의 공간을 바라보며 가다가 오른쪽에서 갑자기 차가

끼어들면 공간기준이 아닌 차와 반대쪽으로 움직인다는 식으로 바뀌기 때문에 움직임에 대한

기준점이 바뀌어 버린다.

 

손가락으로 눌렀을때만 움직임의 기준을 간접이 아닌 직접으로 바꾸는건 말이 안되고.

따라서 위처럼 갑자기 방향을 틀려면 Waypoint를 반대쪽 방향으로 옮겨줘야 하는데 문제가 생긴다.

 

 

 

하지만 손가락이 갑자기 근처에서 튀어나오면 손가락 반대쪽 공간에 특정 포인트를 랜덤으로

찍어주는 것도 안된다. 왜냐면 손가락이 찍히는 순간 랜덤포인트를 쏴버리면 손가락이 근처에서

계속 머문다거나 해버리면 랜덤포인트들이 계속해서 찍혀서 목표점이 덜덜덜 떨리게 되기 때문이다.

 

즉, 원래 있던 경로에 AddOffLocation 시켜서 예를 들면

아래와 같은 방식으로

 

반대쪽으로 점차 이동하도록 하는 방법을 써야 할것이다.

하지만 또 손가락이 반대쪽으로 도망가는 물고기를 계속 쫓아오게 되면

손가락으로 찍기전의 원래 기준점이 되는 Waypoint 위치가 의미가 없이 모호해 지는 상황이 되므로

참으로 애매하다..

 

 

 

 

테스트

 

아직 밥주는 것도 해야되고 리깅 적용해야하고 벽이나 다른 물고기와의 충돌도 적용해야 하고

기타 적용안된게 많지만

블루프린트로 진행하던 것들을 대충 거의다 c++로 옮겼다.

여태까지 정리된 모습은 아래와 같다

 

 

관련글 더보기