요약
1. 변수 저장 방법은, 블루프린터 UPROPERTY 방식으로 만들어서 하는방법과,
플레이어 스테이트 클래스를 활용하는 방법이 있다.
2. PlayerState는 PlayerController가 Pawn에 빙의될 때. PlayerState에 생성된 포인터를 전달해놓기 때문에 빙의 후에 된다는걸 명심하자.
오전 : 케릭터 마우스 조작 구현,
오후 : 블렌드 스페이스 애니메이션 수업.
용어 설명
1. 짐벌락 ? : 카메라를 상하 회전 할 때 . 90도 이상으로 넘어가면 꼬이는 현상 90도를 안넘어가게 제한을 두는걸로 방지.
2. Additib : 히트 모션이 포함되어있는? 걸 의미한다.
3. TArray : 언리얼의 배열 클래스
Tip
1. 언리얼에서는 형변환을 할때 cast<>로 해줘야한다 안그럼 큰일남
2. 언리얼에서 정수를 사용할려면 int-32를 사용해야한다.
따로 공부해서 정리해둘것
1.
마우스 움직임 감지도 축매핑으로 조작한다.
오른쪽으로 움직일 때는 1, 왼쪽은 -1 을 반환한다.
void APlayerCharacter::RotationCameraZ(float Scale)
{
if (Scale == 0.f)
return;
mSpringArm->AddRelativeRotation(FRotator(0.f, Scale * 180.f * GetWorld()->GetDeltaSeconds() , 0.f));
}
GetActorRightVector 함수는 내부코드로 가속도를 계산하지만
위에 작성된 AddRelativeRotation은 순전히 더하고 빼기만 한다.
GetWorld 함수는 언리얼 내 변수를 가져올 수 있는 함수.
플레이어 회전 방법에는 2가지가 있다.
void APlayerCharacter::RotationCameraZ(float Scale)
{
if (Scale == 0.f)
return;
//mSpringArm->AddRelativeRotation(FRotator(0.f, Scale * 180.f * GetWorld()->GetDeltaSeconds() , 0.f));
AddControllerYawInput(Scale * 180.f * GetWorld()->GetDeltaSeconds());
// AddControllerYawInput : 폰을 기준으로 회전 하는 방법
}
상하 회전. - 짐벌락 방지 구현 필수
void APlayerCharacter::RotationCameraY(float Scale)
{
if (Scale == 0.f)
return;
mSpringArm->AddRelativeRotation(FRotator(Scale * 180.f * GetWorld()->GetDeltaSeconds(), 0.f, 0.f));
}
카메라 줌 - 최대 최소값 구현 필수
void APlayerCharacter::CameraZoom(float Scale)
{
if (Scale == 0.f)
return;
mSpringArm->TargetArmLength += Scale * -5.f;
}
언리얼에서는 C++의 형변환처럼하면 큰일난다고 한다.
그래서 Cast라는 언리얼 제공 함수를 이용해야하며
//Cast : 해당 타입일 경우 해당 매모리 주소를 형변환하여 반환하고 아닐경우 nullptr을 반환한다.
애니메이션은 여러방법으로 구현할 수 있다.
한가지만 반복만 할 꺼면 Asset 방식을 권장하며.
여러 애니메이션을 구현할 목적이면 블루 프린터가 좋다.
수업에서는 블루 프린터로 진행을 한다.
애니메이션은 AnimInstance 조작한다.
클래스 생성 > 모든클래스 > AnimInstance
수업에서는 전체적인 부모 클래스 애니메이션을 만들고.
그걸 상속받는 각 직업별 애니메이션을 구현한다
경로 폴더를 만든 후. 우클릭 >애니메이션 > 애니메이션 블루프린터 생성.
만든 블루 프린트를 우클릭 해서 레퍼런스 복사후 Knight.cpp로 와서 구현
static ConstructorHelpers::FClassFinder<UAnimInstance> AnimClass(TEXT("/Script/Engine.AnimBlueprint'/Game/Player/Animation/Knight.Knight_C'"));
if (AnimClass.Succeeded())
GetMesh()->SetAnimInstanceClass(AnimClass.Class);
ClassFinder를 할때는 무조건 레퍼런스 주소 뒤에 '_C'를 붙혀줘야 한다.
추후 애니메이션을 구현할때 달리면서 공격하는 경우. 하체 상체를 분리해서 각각 애니메이션을 달리는것과 공격을 구현하는 걸로 Run-Attack을 구현한다.
에니메이션 2개를 섞을 수 있다.
우클릭 > 애니메이션 > 블렌드 스페이스 선택.
수업에서는 Idle-Run 을 하기에 기준을 Speed로 잡는다.
이름은 Speed, 최대 축값을 600(걷는속도), 그리드 분할은 1개로 설정한다.
하지만 실제 게임에서는 버프, 디버프, 등등 기준으로 걷는속도가 변경될 수 있으니 1로 설정하고 계산하는 방식으로 하는걸 권장한다.
위 사진 : 가로축 설정
위 사진 : 가운데 아래 칸에 각 모서리마다 애니메이션 할당.
위 사진 : 에니메이션 편집창으로 와서 만들었던 블렌드 에니메이션을 선언 후. 아래 코딩으로 선언된 계산된 속도값을 설정.
.h
class UE11_API UPlayerAnimInstance : public UAnimInstance
{
GENERATED_BODY()
public:
UPlayerAnimInstance();
protected:
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
float mSpeedRatio;
public:
virtual void NativeInitializeAnimation();
virtual void MativeUpdateAnimation(float DeletaSeconds);
};
.cpp
UPlayerAnimInstance::UPlayerAnimInstance()
{
}
void UPlayerAnimInstance::NativeInitializeAnimation()
{
Super::NativeInitializeAnimation();
}
void UPlayerAnimInstance::NativeUpdateAnimation(float DeletaSeconds)
{
Super::NativeUpdateAnimation(DeletaSeconds);
APlayerCharacter* PlayerCharacter = Cast<APlayerCharacter>(TryGetPawnOwner());
if (IsValid(PlayerCharacter))
{
UCharacterMovementComponent* Movement = PlayerCharacter->GetCharacterMovement();
mSpeedRatio = Movement->Velocity.Size() / Movement->MaxWalkSpeed;
}
}
위 사진 : 그 후 Speed 변수를 앞서 만들었던걸로 지정한다.
PlayerInputComponent->BindAction<APlayerCharacter>(TEXT("NormalAttack"), EInputEvent::IE_Pressed, this, &APlayerCharacter::NomalAttack);
protected:
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
float mSpeedRatio;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = true))
float mMoveDir;
public:
void SetMoveDir(float Dir)
{
mMoveDir = Dir;
}
void APlayerCharacter::MoveFront(float Scale)
{
mMoveDir = Scale;
if (Scale == 0.f)
return;
AddMovementInput(GetActorForwardVector(), Scale);
}
void APlayerCharacter::MoveSide(float Scale)
{
if (mMoveDir == 1)
{
if (Scale == 0.f)
mAnimInst->SetMoveDir(0.f);
else if (Scale == 1.f)
mAnimInst->SetMoveDir(45.f);
else if (Scale == -1.f)
mAnimInst->SetMoveDir(-45.f);
}
else if (mMoveDir == -1.f)
{
if (Scale == 0.f)
mAnimInst->SetMoveDir(180.f);
else if (Scale == 1.f)
mAnimInst->SetMoveDir(135.f);
else if (Scale == -1.f)
mAnimInst->SetMoveDir(-135.f);
}
else if(mMoveDir == 0.f)
{
if (Scale == 0.f)
mAnimInst->SetMoveDir(0.f);
else if (Scale == 1.f)
mAnimInst->SetMoveDir(90.f);
else if (Scale == -1.f)
mAnimInst->SetMoveDir(-90.f);
}
if (Scale == 0.f)
return;
구조 순서상 Front > Side 순이기 떄문에 Front에는 예외 처리 전 MoveDir에 입력 받기 전의 값도 저장한다. 그 후 Side 함수 내에서 각 방향에 맞는 값을 구현한다.
가만히 있다가 공격애니메이션 출력.
달리면서 공격애니메이션. 등
이질감을 해결하기 위한 구현방법이 있다.
수업에서는 몽타주로 진행을 한다.
가운데 몽타주 클릭 > 슬롯 매니저 클릭 > 우측 하단 새 그룹 클릭 > Player 그룹 생성 > 새 필터 Attack 생성 > 가운데 몽타주 새 슬롯 에서 생성된 Player.Attack 클릭 > 가운데 파란 Defualt 클릭 > 우측 상단 디테일란에 섹션 이름 Attack 설정
PlayerAnimInstance.h
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = true))
TArray<UAnimMontage*> mAttackMontageArray;
그 후 캐릭터 애니메이션 에디터 창에서 상단의 클래스 디폴트 클릭 > 좌측 디테일 란에 만들었던 배열 4개까지 증가 후. Attack 애니메이션을 4개까지 복사해서 각각 Slow로 변경 (아래 사진 참조)
Attack 슬롯 생성방법 : 빈공간 우클릭 > 슬롯 검색 > DefaultSlot 클릭 > 디테일란에 생성된 슬롯명 선택
앞서 만든 애니메이션 배열을 사용하기 위해
변수 선언
PlayerAnimInstance.h
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = true))
bool mAttackEnable;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = true))
int32 mAttackIndex;
PlayerAnimInstance.cpp
UPlayerAnimInstance::UPlayerAnimInstance()
{
mMoveDir = 0.f;
mAttackEnable = true;
mAttackIndex = 0;
}
void UPlayerAnimInstance::Attack()
{
// 공격 불가능 상태일때는 공격키 무반응으로 설정
if (!mAttackEnable)
return;
//공격중에 다시 공격버튼을 클릭 못하도록 불가능상태로 변경
mAttackEnable = false;
// Montage_IsPlaying : 인자로 들어간 몽타주가 재생중인지 판단
if (!Montage_IsPlaying(mAttackMontageArray[mAttackIndex]))
{
Montage_SetPosition(mAttackMontageArray[mAttackIndex], 0.f);
Montage_Play(mAttackMontageArray[mAttackIndex]);
mAttackIndex = (mAttackIndex + 1) % mAttackMontageArray.Num();
}
}
PlayerCharacter.cpp
void APlayerCharacter::NomalAttack()
{
mAnimInst->Attack();
}