초보 코린이의 성장 일지

UE4 Feet IK, Behavior, HealBar 본문

언리얼

UE4 Feet IK, Behavior, HealBar

코오린이 2023. 7. 21. 17:29

IK는 어제 코드를 통해 마무리가 되었으며, 이제 ABP에서 처리를 해서 값들을 먹여줘야 한다.

1. 이제 True로 보내줄 링크된 애님 레이어가 하나 필요하다 생성해줄 것이다.

1. 우선은 연결을 해주고, 레이어 안에 작업을 시작해 보겠다.

 

1. 입력이 들어온 상태에서 IK를 섞어줘야한다.

2. IK를 먹일 왼발 가상의 본으로 시작해서 정해진 기준까지 천천히 계산을 누적해 나간다.

1. 오른발도 동일하게 값을 누적해 나간다.

1. 마지막 허리를 계산해주는데 값이 들어오는것 중에 큰값을 사용했다.

1. 최종 상태.

1. 사용할 애니메이션에 IK값을 적용 시키도록 커브값을 양발에 준 상태이다.

1. 자연스럽게 IK가 적용된 모습을 확인할 수 있다.

2. IK는 이제 끝이났다.


 

1. AI를 만들어주기 위해 파생클래스로 Enemy_AI를 생성해준다.

#pragma once

#include "CoreMinimal.h"
#include "Characters/CEnemy.h"
#include "CEnemy_AI.generated.h"

UCLASS()
class U2212_06_API ACEnemy_AI : public ACEnemy
{
	GENERATED_BODY()

private:
	UPROPERTY(EditDefaultsOnly, Category = "AI")
	// Behavior를 바꿔서 사용할 수 도 있기 때문에, 이 위치에 선언해놓고 사용
		class UBehaviorTree* BehaviorTree;

private:
	UPROPERTY(EditDefaultsOnly, Category = "Label")
	// 위젯 어느정도 시간 지나가면 사라지도록
		float LabelViewAmount = 3000.0f;

#if WITH_EDITOR // 에디터에서만 보이도록 선언
private:
	UPROPERTY(VisibleDefaultsOnly)
	// AI 이름 등 출력 용도
		class UWidgetComponent* LabelWidget;
#endif

private:
	UPROPERTY(VisibleDefaultsOnly)
		class UCWeaponComponent* Weapon;

	UPROPERTY(VisibleDefaultsOnly)
		class UCAIBehaviorComponent* Behavior;
public:
	ACEnemy_AI();

protected:
	virtual void BeginPlay() override;

public:
	virtual void Tick(float DeltaTime) override;

private:
	void UpdateLabelRenderScale();

};
#include "Characters/CEnemy_AI.h"
#include "Global.h"
#include "Components/CWeaponComponent.h"
#include "Components/CAIBehaviorComponent.h"
#include "Components/WidgetComponent.h"
#include "Components/CStatusComponent.h"
#include "Widgets/CUserWidget_Label.h"

ACEnemy_AI::ACEnemy_AI()
{
	PrimaryActorTick.bCanEverTick = true;

	CHelpers::CreateComponent<UWidgetComponent>(this, &LabelWidget, "Label", GetMesh());

	CHelpers::CreateActorComponent<UCWeaponComponent>(this, &Weapon, "Weapon");
	CHelpers::CreateActorComponent<UCAIBehaviorComponent>(this, &Behavior, "Behavior");

	TSubclassOf<UCUserWidget_Label> labelClass;
	CHelpers::GetClass<UCUserWidget_Label>(&labelClass, "WidgetBlueprint'/Game/Widgets/WB_Label.WB_Label_C'");
	LabelWidget->SetWidgetClass(labelClass); // WidgetClass로 받아야한다.
	LabelWidget->SetRelativeLocation(FVector(0, 0, 220));
	LabelWidget->SetDrawSize(FVector2D(120, 0));
	LabelWidget->SetWidgetSpace(EWidgetSpace::Screen);

}

void ACEnemy_AI::BeginPlay()
{
	Super::BeginPlay();

	LabelWidget->InitWidget();

	// BP위젯에 있는 이벤트들 연결.
	UCUserWidget_Label* label = Cast<UCUserWidget_Label>(LabelWidget->GetUserWidgetObject());
	label->UpdateHealth(Status->GetHealth(), Status->GetMaxHealth());
	label->UpdateName(GetName());
	label->UpdateControllerName(GetController()->GetName());
}

void ACEnemy_AI::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	UCUserWidget_Label* label = Cast<UCUserWidget_Label>(LabelWidget->GetUserWidgetObject());

	if (!!label)
	{
		label->UpdateHealth(Status->GetHealth(), Status->GetMaxHealth());

		UpdateLabelRenderScale();
	}
}

void ACEnemy_AI::UpdateLabelRenderScale()
{
	UCUserWidget_Label* label = Cast<UCUserWidget_Label>(LabelWidget->GetUserWidgetObject());
	CheckNull(label);

	APlayerCameraManager* cameraManager = UGameplayStatics::GetPlayerCameraManager(GetWorld(), 0);

	// 거리 구하기
	FVector cameraLocation = cameraManager->GetCameraLocation();
	FVector targetLocation = GetController()->GetTargetLocation();

	// 거리 비교하기 위해 담아두기
	float distance = FVector::Distance(cameraLocation, targetLocation);
	float sizeRate = 1.0f - (distance / LabelViewAmount);

	// 거리 비교해서 보일지 말지 설정 및 사이즈 조정
	if (distance > LabelViewAmount)
	{
		label->SetVisibility(ESlateVisibility::Collapsed);

		return;
	}

	label->SetVisibility(ESlateVisibility::Visible);
	label->SetRenderScale(FVector2D(sizeRate, sizeRate));

}

1. BP에서 만든 위젯 이벤트 받아와서 연결.

2. HP Bar 거리 비례해서 보일지 말지, 사이즈 조정해준다.


1. AI 행동을 통제할 컴포넌트를 하나 생성해준다.

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "CAIBehaviorComponent.generated.h"

UENUM(BlueprintType)
enum class EAIStateType : uint8
{
	// 대기, 추격, 액션, 순찰, hit, 회피, 사망
	Wait = 0, Approach, Action, Patrol, Hitted, Avoid, Dead, Max,
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FAIStateTypeChanged, EAIStateType, InPrevType, EAIStateType, InNewType);

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class U2212_06_API UCAIBehaviorComponent : public UActorComponent
{
	GENERATED_BODY()

private:
	UPROPERTY(EditAnywhere, Category = "Key")
	// 위에 Type 상태를 다룰것,
		FName AIStateTypeKey = "AIState";

	UPROPERTY(EditAnywhere, Category = "Key")
		FName TargetKey = "Target";

private:
	// 헤당 상태인지 판단,
	EAIStateType GetType();

public:
	// 해당 상태들
	bool IsWaitMode();
	bool IsApproachMode();
	bool IsActionMode();
	bool IsPatrolMode();
	bool IsHittedMode();
	bool IsAvoidMode();
	bool IsDeadMode();

public:	
	UCAIBehaviorComponent();

protected:
	virtual void BeginPlay() override;

public:
	// 외부에서 Blackboard 세팅
	FORCEINLINE void SetBlackboard(class UBlackboardComponent* InBlackboard) { Blackboard = InBlackboard; }

public:
	class ACharacter* GetTarget();

public:
	// 해당 타입들
	void SetWaitMode();
	void SetApproachMode();
	void SetActionMode();
	void SetPatrolMode();
	void SetHittedMode();
	void SetAvoidMode();
	void SetDeadMode();

private:
	void ChangeType(EAIStateType InType);

public:
	FAIStateTypeChanged OnAIStateTypeChanged;

private:
	// 위에서 받을 값 저장,
	class UBlackboardComponent* Blackboard;

};
#include "Components/CAIBehaviorComponent.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "BehaviorTree/BlackboardComponent.h"

UCAIBehaviorComponent::UCAIBehaviorComponent()
{
	PrimaryComponentTick.bCanEverTick = true;

}

void UCAIBehaviorComponent::BeginPlay()
{
	Super::BeginPlay();

	
}

EAIStateType UCAIBehaviorComponent::GetType()
{
	return (EAIStateType)Blackboard->GetValueAsEnum(AIStateTypeKey);

}

bool UCAIBehaviorComponent::IsWaitMode()
{
	return GetType() == EAIStateType::Wait;
}

bool UCAIBehaviorComponent::IsApproachMode()
{
	return GetType() == EAIStateType::Approach;
}

bool UCAIBehaviorComponent::IsActionMode()
{
	return GetType() == EAIStateType::Action;
}

bool UCAIBehaviorComponent::IsPatrolMode()
{
	return GetType() == EAIStateType::Patrol;
}

bool UCAIBehaviorComponent::IsHittedMode()
{
	return GetType() == EAIStateType::Hitted;
}

bool UCAIBehaviorComponent::IsAvoidMode()
{
	return GetType() == EAIStateType::Avoid;
}

bool UCAIBehaviorComponent::IsDeadMode()
{
	return GetType() == EAIStateType::Dead;
}

void UCAIBehaviorComponent::SetWaitMode()
{
	ChangeType(EAIStateType::Wait);
}

void UCAIBehaviorComponent::SetApproachMode()
{
	ChangeType(EAIStateType::Approach);
}

void UCAIBehaviorComponent::SetActionMode()
{
	ChangeType(EAIStateType::Action);
}

void UCAIBehaviorComponent::SetPatrolMode()
{
	ChangeType(EAIStateType::Patrol);
}

void UCAIBehaviorComponent::SetHittedMode()
{
	ChangeType(EAIStateType::Hitted);
}

void UCAIBehaviorComponent::SetAvoidMode()
{
	ChangeType(EAIStateType::Avoid);
}

void UCAIBehaviorComponent::SetDeadMode()
{
	ChangeType(EAIStateType::Dead);
}

void UCAIBehaviorComponent::ChangeType(EAIStateType InType)
{
	EAIStateType prevType = GetType();

	Blackboard->SetValueAsEnum(AIStateTypeKey, (uint8)InType);

	if (OnAIStateTypeChanged.IsBound())
		OnAIStateTypeChanged.Broadcast(prevType, InType);
}

1. Behavior에사 사용할 Type들을 정리해준다.

2. 어떠한 행동을 할지, 어떠한 상태인지 판단해줄 것이다.


1. Enemy HP, Name 등을 나타내기 위한 위젯을 하나 추가해준다.

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "CUserWidget_Label.generated.h"

UCLASS()
class U2212_06_API UCUserWidget_Label : public UUserWidget
{
	GENERATED_BODY()

public:
    UFUNCTION(BlueprintImplementableEvent, Category = "Label")
        void UpdateHealth(float InHealth, float InMaxHealth);

    UFUNCTION(BlueprintImplementableEvent, Category = "Label")
        void UpdateName(const FString& InName);

    UFUNCTION(BlueprintImplementableEvent, Category = "Label")
        void UpdateControllerName(const FString& InName);

};
#include "CUserWidget_Label.h"

1. 위젯을 만들고 안에서 나중에 값을 받아오기 위해 기본적인 작업만 해준다.

1. BP안에서 위젯을 생성해준다.

2. 위젯을 클릭하고 들어가서, 블루프린트 부모를 변경해준다.

3. 만들어 놓은 CUserWidget_Label을 선택해준다.

1. Size Box를 추가해주고, 디자인으로 스크린을 변경해준다.

2. Size Box 수치는 위와 같이 지정해준다.

1. Vertical Box와 Text를 추가해준다.

1. Name으로 이름을 변경해주고, 변수인지 체크해준다.

2. 색상 및 사이즈를 조정, 폰트 선택, 그다음 중앙으로 정렬시켜준다.

1. Text를 하나 더 추가해주고, 위와 동일하게 수치만 조금 변경해서 설정해준다.

 

1. Border를 추가해준다,

2. Padding 수치를 조정해주고, 알파 수치값으로 투명도를 준다.

3. Pivot를 0.5로 설정해준다.

1. Progress Bar를 추가해주고, Health로 이름을 변경해준다.

2. Background값은 0을 주고, Percent 수치 조정, 색 조정, Pivot 값을 0.5로 준다.

1. 그래프로 들어가서 이벤트들을 연결해준다.


1. AI를 상속받아서 Enemy를 생성해준다.

1. 보기 편하도록 Enemy 색상을 변경해준다.


1. 맵을 넓혀주고, Enemy를 올려놓는다.

2. HealBar가 잘 나오는걸 확인할 수 있다.


https://www.youtube.com/watch?v=FLHXfSe9ASw 

 

'언리얼' 카테고리의 다른 글

UE4 Behavior Tree, Patrol  (0) 2023.07.25
UE4 Behavior Tree  (0) 2023.07.24
UE4 Parkour Final, Feet IK  (0) 2023.07.20
UE4 Parkour Climb  (0) 2023.07.19
UE4 Parkour  (0) 2023.07.18
Comments