[29. Pawn Movement Component #1]
일반적으로 Movement Component를 만들지 않고 자동으로 제작된 컴포넌트를 이용한다. (라고 하는데 확실치 않음)
우선 PawnMovementComponent를 상속받는 C++ 클래스를 하나 만든다. (ColliderMovementComponent)
// Collider.h
UPROPERTY(VisibleAnywhere, Category = "Movement")
class UColliderMovementComponent* OurMovementComponent;
// Collider.cpp
#include "ColliderMovementComponent.h"
ACollider::ACollider()
{
// .. 생략
OurMovementComponent = CreateDefaultSubobject<UColliderMovementComponent>(TEXT("OurMovementComponent"));
OurMovementComponent->UpdatedComponent = RootComponent; // RootComponent를 제어함
}
그리고 Collider에서 해당 컴포넌트를 사용하기 위해 변수로 선언한다.
UpdatedComponent에 RootComponent를 대입하면 OurMovementComponent가 RootComponent를 제어한다는 의미이다.
AddMovementInput 함수의 정의를 보면 GetMovementComponent 함수를 받아서 사용하는데, 폰은 기본적으로 MovementComponent가 없어서 null을 반환받게된다. (찾아보니 그렇다는것 같음)
캐릭터와 기본 폰은 받아서 사용하지만 따로 만든 폰은 그렇지 않다.
우선은 이동을 구현하기 위해 MoveForward와 MoveRight에 작성해 두었던 AddInputMovement 함수를 삭제하고 다른 함수를 사용해보자.
// Collider.cpp
void ACollider::MoveForward(float Value)
{
FVector Forward = GetActorForwardVector();
if (OurMovementComponent)
{
OurMovementComponent->AddInputVector(Forward * Value);
}
}
void ACollider::MoveRight(float Value)
{
FVector Right = GetActorRightVector();
if (OurMovementComponent)
{
OurMovementComponent->AddInputVector(Right * Value);
}
}
그리고 우리가 만든 ColliderMovementComponent를 사용해야 하기 때문에 GetMovementComponent 메소드를 재정의 한다.
// Collider.h
virtual UPawnMovementComponent* GetMovementComponent() const override;
// Collider.cpp
UPawnMovementComponent* ACollider::GetMovementComponent() const
{
return OurMovementComponent;
}
아직까지도 별다른 변화는 없다.
이제 ColliderMovementComponent를 만들어 줄 때이다.
// ColliderMovementComponent.h
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
// ColliderMovementComponent.cpp
void UColliderMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (!PawnOwner || !UpdatedComponent || ShouldSkipUpdate(DeltaTime))
{
return;
}
FVector DesiredMovementThisFrame = ConsumeInputVector().GetClampedToMaxSize(1.f);
if (!DesiredMovementThisFrame.IsNearlyZero())
{
FHitResult Hit;
SafeMoveUpdatedComponent(OUT DesiredMovementThisFrame, OUT UpdatedComponent->GetComponentRotation(), true, OUT Hit);
// if we bump into something, slide along the side of it
if (Hit.IsValidBlockingHit())
{
SlideAlongSurface(DesiredMovementThisFrame, 1.f - Hit.Time, Hit.Normal, Hit);
}
}
}
폰이나 액터 클래스 생성시 기본으로 있던 Tick 함수를 똑같이 오버라이딩 한 것과 같다.
컴포넌트이기때문에 함수명이 TickComponent이다.
마찬가지로 Super의 TickComponent를 호출해주고 내용을 작성한다.
MovementComponent가 폰을 소유하지 않거나, MovementComponent가 제어할 컴포넌트가 없거나, 스킵해야 하는 경우 Tick을 즉시 종료한다.
DesiredMovementThisFrame에 대입하는 ConsumInputVector는 Collider에서 정의했던 MoveForward, MoveRight에 의해서 ControllInputVector라는 변수에 값이 계속 누적된다. (다만 입력이 없으면 0이 계속 곱해지기 때문에 0만 더해짐)
ConsumInputVector를 호출하면 누적된 값을 반환받고 ControllInputVector는 0으로 초기화된다.
그 뒤, 최대 크기가 1.f로 고정된 벡터의 복사본을 통해 값을 보정한다.
해당 값이 0에 근접하지 않다면 움직이게 된다.
마지막으로 만약 물체와 부딪혔다면, 해당 물체에 충돌한 상태로 가만히 있는게 아니라 조금씩 미끄러져서 움직이게 한다.
[30. Pawn Movement Component #2]
우선 마켓플레이스에서 Infinity Blade: Adversaries를 다운 받는다.
이제 Critter에 Static Mesh 대신 Skeletal Mesh를 사용할 것이기 때문에 StaticmeshComponent를 SkeletalMeshComponent로 변경해준다.
그리고 더이상 컨트롤하지 않을 것이기 때문에 AutoPossessPlayer도 주석처리를 해준다.
그럼에도 불구하고 실행 시 해당 폰으로 빙의가 된다면 블루프린트 클래스에서 해당 옵션이 설정되어 있는지 확인 후 비활성화 시켜준다.
메시는 SK_Greater_Spider, 애니메이션은 ExoGame_Greater_Spider_Idle로 설정한다.
크기는 너무 큰것 같으니 0.3으로 줄이고 월드에 몇개 배치한다.
아직 Critter는 콜리전 처리를 하지 않았기 때문에 따로 충돌이 일어나지는 않는다.
[31. Pawn Camera Rotation]
마우스 이동에 따라 회전을 구현하기 위해 우선 축 매핑을 해준다.
마우스 X, Y는 키 매핑과 다르게 따로 음수를 설정하지 않아도 값이 알아서 들어간다.
// Collider.h
private:
void YawCamera(float AxisValue);
void PitchCamera(float AxisValue);
FVector2D CameraInput;
// Coliider.cpp
ACollider::ACollider()
{
// .. 생략
CameraInput = FVector2D(0.f, 0.f);
}
void ACollider::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
FRotator NewRotation = GetActorRotation();
NewRotation.Yaw += CameraInput.X;
SetActorRotation(NewRotation);
}
void ACollider::YawCamera(float AxisValue)
{
CameraInput.X = AxisValue;
}
void ACollider::PitchCamera(float AxisValue)
{
CameraInput.Y = AxisValue;
}
실행 후 마우스를 좌우로 움직이면 잘 움직인다.
SetActorRotation을 통해 액터 자체를 회전시킨 것이기 때문에 SpringArm으로 붙어있던 카메라도 같이 회전되어서 좌우로 움직인 것이다.
이번에 위아래로 움직이는 것은 액터 대신 카메라만 움직이도록 한다.
void ACollider::Tick(float DeltaTime)
{
// .. 생략
FRotator NewSpringArmRotation = SpringArm->GetComponentRotation();
NewSpringArmRotation.Pitch += CameraInput.Y;
SpringArm->SetWorldRotation(NewSpringArmRotation);
}
이제 위아래도 잘 움직인다.
다만 문제점은 스프링 암은 오브젝트에 충돌시 길이가 짧아지고 다시 복구되기 때문에 아래를 보거나 위를 보면 스프링 암의 위치가 틀어져서 조작이 엉망이 된다.
NewSpringArmRotation.Pitch = FMath::Clamp(NewSpringArmRotation.Pitch + CameraInput.Y, -80.f, -15.f);
Clamp를 통해 각의 범위를 보정해주면 문제를 해결할 수 있다.
[32. Environment Assets]
우선 Sun Temple이라는 예제 프로젝트를 다운로드 받는다.
영상에서는 Learn에 있지만 현재는 마켓플레이스에서 받으면 된다.
이 프로젝트의 리소스를 FirstProject에서 사용할 것이기 때문에 이주(Migrate) 시키도록 한다.
경로는 FirstProject의 Content 폴더이다.
현재까지 작업한 레벨을 저장하려면 [파일 - 다른 이름으로 레벨 저장] 을 눌러서 TestMaps로 저장해준다.
앞으로 SunTemple에서 작업할 것이므로 해당 레벨로 설정해준다.
'언리얼 엔진 > UE4 C++' 카테고리의 다른 글
[UE C++] The Character Class #2 (0) | 2022.09.06 |
---|---|
[UE C++] The Character Class #1 (0) | 2022.09.05 |
[UE C++] The Pawn Class #1 (0) | 2022.09.04 |
[UE C++] The Actor Class #2 (0) | 2022.09.04 |
[UE C++] The Actor Class #1 (0) | 2022.09.03 |