공부/Unreal

24.07.23

월러비 2024. 7. 27. 22:36

Enemy 생성

히트 리엑션 : 애니메이션

  • 헤더 파일 - ‘애님 몽타주’ 자료형 변수 선언 - cpp 파일 - 몽타주 에셋 가져오기 - 몽타주 실행
//header
private:
	UPROPERTY(EditAnywhere, Category = "Montage")
		class UAnimMontage* DamagedMontage;
		
//cpp
CHelpers::GetAsset<UAnimMontage>(&DamagedMontage, "AnimMontage'/Game/Characters/Montages/HitReaction_Montage.HitReaction_Montage'");

PlayAnimMontage(DamagedMontage, 1.5);

히트 리엑션 : 히트 스탑

  • float 자료형으로 값을 체크할때는 0으로 딱 떨어지지 않고 0에 가까워지는 값도 나오기때문에 값 체크를 0으로 하지 않는다.
    • ‘0에 가까운 근삿값은 0 반환’ 함수를 호출한다. ⇒ FMath::IsNearlyZero(들어가는 시간)
    • IsNearlyZero에서 0에 가까운 값이 뭔지 알아내는 법 : F12 - ErrorTolerance 확인 - SMALL_NUMBER 마우스 클릭 - 얼마인지 확인 ⇒ 1.e-8f : 10의 -8승
  • ‘캐릭터’는 사망했다면 Nullptr로 반환된다.
  • 델리게이트는 ‘람다식’으로 치환할 수 있다.
  • 색 변경 타이머에서 ‘타이머 핸들’을 멤버로 만든 이유 : 타이머가 삭제될 때 사용되는 문법이다.
    • 타이머를 멈출 이유가 없다면, ‘지역 변수’로 선언한 다음 없애도 된다.
  • 헤더 파일 - ‘히트 스탑’ 함수 선언 : 시간을 받는다. - cpp 파일 - ‘히트 스탑’ 함수 정의 - ‘0 근삿값 확인’ 함수를 호출해서 0이 나오는지 확인한다. (받아오는 시간이 0이라면 히트스탑을 실행하지 않는다.) - 캐릭터들을 저장할 배열을 선언한다. - 월드에서 현재 열려있는 레벨의 액터의 수 만큼 반복한다. - 액터를 하나씩 받아 ‘캐릭터’로 형변환한다. - 받은 캐릭터의 ‘시간 흐름 제어’ 변수를 사용해 액터의 시간을 지연시킨다. - 지연시킨 캐릭터는 ‘캐릭터들을 저장할 배열’에 추가한다. - 타이머용 ‘타이머 핸들’ 선언 - 타이머용 ‘타이머 델리게이트’ 선언 - 선언된 ‘타이머 델리게이트’의 ‘람다문 호출’ 함수를 호출한다. ⇒ 함수이름 : ‘대입’으로 사용한다. , 받아올 파라미터는 없다. - 람다식 정의 - ‘캐릭터 배열’만큼 반복한다. - 캐릭터가 ‘사망’했는지 확인한다. - 캐릭터가 있다면 변경한 속도를 다시 되돌린다. - 월드에서 ‘타이머 매니저’를 가져와 타이머를 설정한다. : 이전에 선언한 ‘타이머 핸들’, 색 변경에서 썼던 델리게이트 부분을 ‘람다식’으로 치환한 문법의 델리게이트를 사용한다. , 매개변수로 받은 시간, 반복 여부 - ‘히트스탑 함수’에 지정할 시간을 넣어 호출
//header
private:
	FTimerHandle ChangeColor_TimerHandle;
	
//cpp
//IsNearlyZero : 근삿값을 반환하는 함수
CheckTrue(FMath::IsNearlyZero(InTime));

TArray<ACharacter*> characters;
for (AActor* actor : GetWorld()->GetCurrentLevel()->Actors)
{
	ACharacter* character = Cast<ACharacter>(actor);

	if (!!character)
	{
		character->CustomTimeDilation = 1e-3f;

		characters.Add(character);
	}
}

//히트스탑 코드
FTimerDelegate timerDelegate;

//간단한 람다 정의
timerDelegate.BindLambda([=]() 
	{
		for (ACharacter* character : characters)
		{
			//캐릭터가 있다면 : 사망하지 않았다면
			if (!!character)
			{
				character->CustomTimeDilation = 1.0f;
			}
		}
	});

FTimerHandle timerHandle;

GetWorld()->GetTimerManager().SetTimer(timerHandle, timerDelegate, InTime, false);

PlayHitStop(0.05); //HitStop

히트 리엑션 : 히트 사운드

  • 언리얼은 헤더파일의 위치를 클래스 자료형 명과 거의 일치시켜놨으니 찾아보는것이 편하다.
    • 그래도 찾아보기전에 검색이 더 빠르다.
  • 헤더 파일 - ‘사운드 실행’ 함수 선언 : 사운드 웨이브 변수를 받는다. - ‘사운드 웨이브’ 클래스 변수 선언 - cpp 파일 - ‘사운드 실행’ 함수 정의 - 매개변수로 ‘사운드 변수’가 들어온것이 없는지 확인 - ‘사운드를 위치에 스폰’ 함수를 ‘월드, 사운드 파일, 스폰 위치’를 넣어 호출한다. - ‘사운드 실행’ 함수를 ‘사운드 웨이브’ 변수를 넣어 호출한다.
//header
UPROPERTY(EditAnywhere, Category = "Sound")
		class USoundWave* DamagedSoundWave;

void PlaySoundWave(class USoundWave* InSoundWave);

//cpp
CheckNull(InSoundWave);

UGameplayStatics::SpawnSoundAtLocation(GetWorld(), InSoundWave, GetActorLocation());

PlaySoundWave(DamagedSoundWave);

히트 리엑션 : 히트 파티클

  • 파티클 : 파티클 시스템 자료형
  • 헤더 파일 - ‘파티클 시스템’ 자료형 변수 선언 - ‘파티클 실행’ 함수 선언 : 파티클 변수를 받는다. - cpp 파일 - ‘파티클 시스템’ 에셋 파일 가져오기 - ‘파티클 실행’ 함수 정의 - 들어온 파티클이 있는지 확인 - ‘파티클 소환’ 함수를 ‘월드, 파티클 파일, 위치, 회전값, 플레이 후 자동 삭제 여부’를 넣어 호출한다.
//header
UPROPERTY(EditAnywhere, Category = "Particle")
		class UParticleSystem* DamagedParticle;
		
void PlayEffect(class UParticleSystem* InParticle);

//cpp
CheckNull(InParticle);

UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), InParticle, GetActorLocation(), GetActorRotation(), true);

PlayEffect(DamagedParticle);

히트 리엑션 : 넉백 (Launch)

  • cpp 파일 - HP 컴포넌트의 ‘사망 확인’ 함수 호출해서 사망 확인 - 사망이 아닐떄 실행할 코드 정리 - ‘현재 위치 가져오기’ 함수를 호출해서 ‘시작’ 변수를 선언하고 저장 - 공격자의 ‘폰 가져오기’ 함수를 호출하고 ‘액터 위치 가져오기’ 함수를 호출해서 ‘타겟’ 변수를 선언하고 저장한다. - 플레이어에서 적의 방향으로 밀려야하니 ‘타겟’ 변수에서 ‘시작’ 변수를 뺸다. (적이 플레이어를 바라보는 방향이 생긴다.) - 나온 ‘방향’ 변수를 ‘일반화’ 함수를 호출한다. - ‘캐릭터 밀기’ 함수를 호출하여 ‘밀리는 값, XY 또 밀리는 순간이 오면 밀리게 하기’ 를 넣는다. - ‘액터 회전 세팅’ 함수를 호출한다. : ‘바라 보는 회전값 찾기’ 함수를 ‘현재 자신의 위치, 공격자 위치’를 넣어 호출한다.
//cpp
//사망이 아니라면
if (HealthPoint->IsDead() == false)
{
	FVector start = GetActorLocation(); //현재 캐릭터 지점
	FVector target = EventInstigator->GetPawn()->GetActorLocation();
	FVector direction = target - start;
	direction.Normalize();

	LaunchCharacter(-direction * 100, false, false);

	SetActorRotation(UKismetMathLibrary::FindLookAtRotation(start, target));
}

상태 데이터 관리 컴포넌트

  • UCLASS 위에 선언하는 ENUM은 블루프린트에서 호출 가능하다.
  • 선언한 ‘열거형 자료형’은 사용할 수 있게 변수로 선언한다.
  • 상태가 바뀌었을때 상태가 연결되있는 함수를 콜하는 ‘델리게이트’를 정의한다.

상태 열거형 정의

  • 헤더 파일 - 블루프린트에서도 호출할 수 있도록 UCLASS 외부에 정의한다. - 열거형 자료형을 사용할 수 있도록 변수로 선언한다.
//header
UENUM()
enum class EStateType : uint8
{
	Idle = 0, Evade, Equip, Damaged, Action, Dead, Max, 
};

private:
	EStateType Type;

상태가 바뀌었을때 호출할 델리게이트 선언

  • 블루프린트에서도 호출할 수 있도록 ‘DYNAMIC 델리게이트 메크로’ 로 선언한다.
  • 델리게이트는 선언한 자료형을 사용하기 위해 선언한 자료형의 이름에 ‘On’을 붙여서 변수로 선언한다.
  • 헤더파일 - 델리게이트 선언 : 바뀐 상태, 현재 상태 를 받는다. - 델리게이트 자료형 변수 선언 - ‘상태 변경’ 함수를 ‘상태 열거형’ 변수를 받아 선언한다. - cpp 파일 - ‘상태 변경’ 함수 정의 - ‘상태 열거형’ 자료형 변수를 선언하고, 이전에 선언한 ‘상태 타입’ 변수를 저장한다. ⇒ 상태 변경 전 상태를 저장 - 기존에 있던 ‘상태 타입’ 변수를 새로 매개변수로 들어온 상태를 저장한다. - 델리게이트 변수에 연결된 함수가 있는지 확인한다. - 있다면, 델리게이트 변수에 연결된 모든 함수를 실행한다.
//header
private:
	void ChangeType(EStateType InType); //델리게이트에 연결할 함수

public:
	FStateTypeChanged OnStateTypeChanged;
	
//cpp
//이전 타입과 현재 타입 바꾸기
EStateType prevType = Type;
Type = InType;

if (OnStateTypeChanged.IsBound())
{
	OnStateTypeChanged.Broadcast(prevType, InType);
}

상태 함수 정의

  • 헤더 파일 - ‘기본 상태 모드’ 함수 선언 - ‘뒤로 회피 상태 모드’ 함수 선언 - ‘기본 상태 확인’ 함수를 선언하고, 정의를 바로 진행한다. - cpp 파일 - ‘기본 상태 함수’ 정의 - ‘상태 변경 함수’를 ‘기본 상태’를 넣어 호출한다. - ‘뒤로 회피’ 상태 함수 정의 - ‘상태 변경 함수’를 ‘뒤로 회피’ 상태를 넣어 호출한다.
//header
public:
	void SetIdleMode();
	void SetEvadeMode();

public:
	FORCEINLINE bool IsIdleMode() { return Type == EStateType::Idle; }
	
//cpp
void UCStateComponent::SetIdleMode()
{
	ChangeType(EStateType::Idle);
}

void UCStateComponent::SetEvadeMode()
{
	ChangeType(EStateType::Evade);
}

플레이어에 회피 함수 연결

  • 선언과 정의를 한번에 표현하는 상황 : 클래스 내부의 변수를 값이 바뀌지않고 안전하게 외부에서 서용될 수 있게하기 때문이다.
  • 델리게이트 이벤트 : 델리게이트 정의된 클래스 변수 선언 - 선언된 변수 컴포넌트 생성 - 선언된 컴포넌트 변수에 있는 델리게이트 변수에 함수 연결
    • 이때, 다이나믹은 블루프린트와도 연결되는 함수인데 이걸 생성자에 연결하면 안된다.
    • ‘다이나믹 델리게이트’ : 블루프린트 프로그래밍이다.
      • 블루프린트 클래스에서 값을 넣고 수정하는 것은 ‘게임모드’가 아니다.
      • 플레이 버튼을 누른 다음 ‘BeginPlay’ 함수 실행 이후가 ‘게임모드’다.
    • 생성자 : 값을 초기화 하기 위해 사용하는 함수다.
      • 초기화한 값이 블루프린트에서 기본값으로 들어간다.
      • 이 블루프린트 클래스를 배치하면, 이때 블루프린트에 들어간 기본값이 쓰이는 것이다.
      • 즉, 생성자는 ‘게임 실행 모드’가 아니다. = 이벤트를 연결하면 안된다.
    • 이벤트는 ‘게임이 실행된 이후’에 연결되어야 한다.
  • ‘전방선언’은 ‘포인터’ 자료형만 가능하다.
    • ex. class UAnimMontage*
    • 즉, enum 자료형은 전방선언이 불가능하다.
    • 이렇게 전방선언을 못하는 경우가 생긴다면? : 헤더파일에 해당 자료형이 있는 헤더파일을 #include 해줘야 한다.
  • 플레이어 헤더 파일 - ‘뒤로 회피 활성화’ 함수 선언 - ‘뒤로 회피 실행’ 함수 선언 - ‘상테 데이터 컴포넌트’ 선언 - 델리게이트 연결 함수인 ‘상태 변경 함수’ 선언 - ‘뒤로 회피’ 애니메이션 몽타주 변수 선언 - ‘음직임 컴포넌트’ 헤더 파일 - ‘이동 정지’ 함수 선언 - ‘움직임 컴포넌트 cpp 파일 - ‘이동 정지’ 함수 정의 - ‘움직임 가능 확인’ 변수 true false - 플레이어 cpp 파일 - ‘뒤로 회피’ 함수 델리게이트 연결 - ‘뒤로 회피 활성화’ 함수 정의 - 상태가 ‘기본 상태’인지 ‘상태 데이터 컴포넌트’ 변수로 확인 - ‘이동 컴포넌트’가 ‘이동’ 인지 확인한다. - ‘입력한 축의 값을 가져오는 함수’를 ‘x축 입력’ 액션의 이름을 넣어 W를 눌렀을때의 전방전진할때 ‘뒤로 회피’ 동작이 실행되지 않게 한다. - 상태를 ‘뒤로 회피 모드’ 함수를 호출한다. - 선언한 ‘상태 컴포넌트’ 변수에 있는 ‘상대 타입 변경 델리게이트’에 ‘상태 변경 함수’를 연결한다. - ‘상태 데이터 컴포넌트’ 클래스의 델리게이트와 연결될 ‘상태 변경 함수’ 정의 - 바뀌는 상태를 받아 switch 문을 정의한다. - 바뀌는 상태가 ‘회피’ 상태라면 ‘뒤로 회피’ 함수를 호출한다. - ‘뒤로 회피 실행’ 함수 정의 - 무기 장착중이 아니라면 ‘움직임 컴포넌트’의 ‘카메라 회전 허용’ 함수를 호출한다. - ‘애니메이션 몽타주 실행’ 함수를 ‘뒤로 회피’ 몽타주 변수를 넣어 실행한다.
//플레이어 header
private:
	void OnEvade();
	void BackStep();
	
UPROPERTY(VisibleAnywhere)
		class UCStateComponent* State;
		
UFUNCTION()
		void OnStateTypeChanged(EStateType InPrevType, EStateType InNewType);
		
UPROPERTY(EditAnywhere, Category = "Evade")
		class UAnimMontage* BackStepMontage;
		
//Movement header
public:
	void Move();
	void Stop();

//Movement cpp	
bCanMove = true;

//플레이어 cpp
State->OnStateTypeChanged.AddDynamic(this, &ACPlayer::OnStateTypeChanged);

CheckFalse(State->IsIdleMode());
CheckFalse(Movement->CanMove());

//x축 값이 0 이상 들어왔다면? : 전진하고 있는거니까 '백스탭' 하지 마라는것이다.
CheckTrue(InputComponent->GetAxisValue("MoveForward") >= 0.0f);

State->SetEvadeMode();

switch (InNewType)
{
	case EStateType::Evade:
		BackStep();
		break;
}

//무기를 장착하고 있지 않다면
if (bEquipped == false)
{
	Movement->EnableControlRotation();
}

PlayAnimMontage(BackStepMontage);
  • 백스텝 애니메이션 몽타주 파일 - 슬롯 설정 : 풀바디

회피 : 백스탭 노티파이

  • 헤더 파일 - 노티파일 이름 설정 함수 선언 - 노티파이 이벤트 정의 함수 선언 - cpp 파일 - 이름 : 백스탭 종료 - 실행할 함수 : 백스탭 종료 - 플레이어 헤더 파일 - ‘백스탭 종료’ 함수 선언 - 플레이어 cpp 파일 - ‘백스탭 종료’ 함수 정의 - 장착중이 아니라면 ‘움직임 컴포넌트’의 ‘마우스 컨트롤 회전 비활성화’ 함수 호출 - ‘상태 컴포넌트’의 ‘기본 상태 모드 설정’ 함수 호출
//header
public:
	void End_BackStep();
	
//cpp
//무기를 장착하고 있지 않다면
if (bEquipped == false)
{
	Movement->DisableControlRotation();
}

State->SetIdleMode();

Switch 문 들려쓰기 옵션 설정 방법

  • 도구 - 옵션 - 텍스트 편집기 - C++ - 코드 스타일 - 서식 - 들여쓰기 - case 레이블 들여쓰기 체크

클래스 내의 같은 이름 한번에 바꾸는법

  • Ctrl + H - 현재 이름 → 바꿀 이름 - Alt + A

무기 시스템 약간의 설명

  • 피격의 히트스탑 딜레이는 피격마다 다르게 설정한다.

Native가 있는 함수와 없는 함수의 차이점

  • ex. AnimInstance의 NativeBeginPlay와 액터 상속 클래스의 BeginPlay 함수
  • 일반적으로 Native가 붙은 함수는 이에 대응하는 블루프린트 함수가 존재한다.
    • BlueprintBeginPlay 함수가 따로 존재한다.
  • C로만 작성되어있는 함수는 Native가 붙지 않는다.
  • 즉, 그냥 대비를 만드는 정도로 생각하면 된다.

'공부 > Unreal' 카테고리의 다른 글

24.07.25  (0) 2024.07.31
24.07.24  (0) 2024.07.29
24.07.22  (0) 2024.07.26
24.07.19  (0) 2024.07.26
24.07.18  (1) 2024.07.23