게임 구성 요소의 정리
게임 구성 요소의 분류
세 개의 레이어를 기준으로 게임을 구성하는 다양한 기능의 구현
게임 플로우를 위해 보강할 내용
죽었을 때 NPC와 플레이어의 처리
이동 속도의 적용
포션/스크롤 아이템의 추가 구현과 캐릭터의 적용
스탯 기능 및 UI 기능의 보강
캐릭터 관련 보완사항 수정
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Character/ABCharacterBase.h"
#include "Engine/StreamableManager.h"
#include "Interface/ABCharacterAIInterface.h"
#include "ABCharacterNonPlayer.generated.h"
/**
*
*/
UCLASS(config=ArenaBattle)
class ARENABATTLE_API AABCharacterNonPlayer : public AABCharacterBase, public IABCharacterAIInterface
{
GENERATED_BODY()
public:
AABCharacterNonPlayer();
protected:
virtual void PostInitializeComponents() override;
protected:
void SetDead() override;
void NPCMeshLoadCompleted();
UPROPERTY(config)
TArray<FSoftObjectPath> NPCMeshes;
TSharedPtr<FStreamableHandle> NPCMeshHandle;
// AI Section
protected:
virtual float GetAIPatrolRadius() override;
virtual float GetAIDetectRange() override;
virtual float GetAIAttackRange() override;
virtual float GetAITurnSpeed() override;
virtual void SetAIAttackDelegate(const FAICharacterAttackFinished& InOnAttackFinished) override;
virtual void AttackByAI() override;
FAICharacterAttackFinished OnAttackFinished;
virtual void NotifyComboActionEnd() override;
};
ABCharacterNonPlayer.h
// Fill out your copyright notice in the Description page of Project Settings.
#include "Character/ABCharacterNonPlayer.h"
#include "Engine/AssetManager.h"
#include "AI/ABAIController.h"
#include "CharacterStat/ABCharacterStatComponent.h"
AABCharacterNonPlayer::AABCharacterNonPlayer()
{
GetMesh()->SetHiddenInGame(true);
AIControllerClass = AABAIController::StaticClass();
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
}
void AABCharacterNonPlayer::PostInitializeComponents()
{
Super::PostInitializeComponents();
ensure(NPCMeshes.Num() > 0);
int32 RandIndex = FMath::RandRange(0, NPCMeshes.Num() - 1);
NPCMeshHandle = UAssetManager::Get().GetStreamableManager().RequestAsyncLoad(NPCMeshes[RandIndex], FStreamableDelegate::CreateUObject(this, &AABCharacterNonPlayer::NPCMeshLoadCompleted));
}
void AABCharacterNonPlayer::SetDead()
{
Super::SetDead();
AABAIController* ABAIController = Cast<AABAIController>(GetController());
if (ABAIController)
{
ABAIController->StopAI();
}
FTimerHandle DeadTimerHandle;
GetWorld()->GetTimerManager().SetTimer(DeadTimerHandle, FTimerDelegate::CreateLambda(
[&]()
{
Destroy();
}
), DeadEventDelayTime, false);
}
void AABCharacterNonPlayer::NPCMeshLoadCompleted()
{
if (NPCMeshHandle.IsValid())
{
USkeletalMesh* NPCMesh = Cast<USkeletalMesh>(NPCMeshHandle->GetLoadedAsset());
if (NPCMesh)
{
GetMesh()->SetSkeletalMesh(NPCMesh);
GetMesh()->SetHiddenInGame(false);
}
}
NPCMeshHandle->ReleaseHandle();
}
float AABCharacterNonPlayer::GetAIPatrolRadius()
{
return 800.0f;
}
float AABCharacterNonPlayer::GetAIDetectRange()
{
return 400.0f;
}
float AABCharacterNonPlayer::GetAIAttackRange()
{
return Stat->GetTotalStat().AttackRange + Stat->GetAttackRadius() * 2;
}
float AABCharacterNonPlayer::GetAITurnSpeed()
{
return 2.0f;
}
void AABCharacterNonPlayer::SetAIAttackDelegate(const FAICharacterAttackFinished& InOnAttackFinished)
{
OnAttackFinished = InOnAttackFinished;
}
void AABCharacterNonPlayer::AttackByAI()
{
ProcessComboCommand();
}
void AABCharacterNonPlayer::NotifyComboActionEnd()
{
Super::NotifyComboActionEnd();
OnAttackFinished.ExecuteIfBound();
}
ABCharacterNonPlayer.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Character/ABCharacterBase.h"
#include "InputActionValue.h"
#include "Interface/ABCharacterHUDInterface.h"
#include "ABCharacterPlayer.generated.h"
/**
*
*/
UCLASS()
class ARENABATTLE_API AABCharacterPlayer : public AABCharacterBase, public IABCharacterHUDInterface
{
GENERATED_BODY()
public:
AABCharacterPlayer();
protected:
virtual void BeginPlay() override;
virtual void SetDead() override;
public:
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
// Character Control Section
protected:
void ChangeCharacterControl();
void SetCharacterControl(ECharacterControlType NewCharacterControlType);
virtual void SetCharacterControlData(const class UABCharacterControlData* CharacterControlData) override;
// Camera Section
protected:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, Meta = (AllowPrivateAccess = "true"))
TObjectPtr<class USpringArmComponent> CameraBoom;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, Meta = (AllowPrivateAccess = "true"))
TObjectPtr<class UCameraComponent> FollowCamera;
// Input Section
protected:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivateAccess = "true"))
TObjectPtr<class UInputAction> JumpAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivateAccess = "true"))
TObjectPtr<class UInputAction> ChangeControlAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivateAccess = "true"))
TObjectPtr<class UInputAction> ShoulderMoveAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivateAccess = "true"))
TObjectPtr<class UInputAction> ShoulderLookAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivateAccess = "true"))
TObjectPtr<class UInputAction> QuaterMoveAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivateAccess = "true"))
TObjectPtr<class UInputAction> AttackAction;
void ShoulderMove(const FInputActionValue& Value);
void ShoulderLook(const FInputActionValue& Value);
void QuaterMove(const FInputActionValue& Value);
ECharacterControlType CurrentCharacterControlType;
void Attack();
// UI Section
protected:
virtual void SetupHUDWidget(class UABHUDWidget* InHUDWidget) override;
};
ABCharacterPlayer.h
// Fill out your copyright notice in the Description page of Project Settings.
#include "Character/ABCharacterPlayer.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "InputMappingContext.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "ABCharacterControlData.h"
#include "UI/ABHUDWidget.h"
#include "CharacterStat/ABCharacterStatComponent.h"
AABCharacterPlayer::AABCharacterPlayer()
{
// Camera
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
CameraBoom->TargetArmLength = 400.0f;
CameraBoom->bUsePawnControlRotation = true;
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
FollowCamera->bUsePawnControlRotation = false;
// Input
static ConstructorHelpers::FObjectFinder<UInputAction> InputActionJumpRef(TEXT("/Script/EnhancedInput.InputAction'/Game/ArenaBattle/Input/Actions/IA_Jump.IA_Jump'"));
if (nullptr != InputActionJumpRef.Object)
{
JumpAction = InputActionJumpRef.Object;
}
static ConstructorHelpers::FObjectFinder<UInputAction> InputChangeActionControlRef(TEXT("/Script/EnhancedInput.InputAction'/Game/ArenaBattle/Input/Actions/IA_ChangeControl.IA_ChangeControl'"));
if (nullptr != InputChangeActionControlRef.Object)
{
ChangeControlAction = InputChangeActionControlRef.Object;
}
static ConstructorHelpers::FObjectFinder<UInputAction> InputActionShoulderMoveRef(TEXT("/Script/EnhancedInput.InputAction'/Game/ArenaBattle/Input/Actions/IA_ShoulderMove.IA_ShoulderMove'"));
if (nullptr != InputActionShoulderMoveRef.Object)
{
ShoulderMoveAction = InputActionShoulderMoveRef.Object;
}
static ConstructorHelpers::FObjectFinder<UInputAction> InputActionShoulderLookRef(TEXT("/Script/EnhancedInput.InputAction'/Game/ArenaBattle/Input/Actions/IA_ShoulderLook.IA_ShoulderLook'"));
if (nullptr != InputActionShoulderLookRef.Object)
{
ShoulderLookAction = InputActionShoulderLookRef.Object;
}
static ConstructorHelpers::FObjectFinder<UInputAction> InputActionQuaterMoveRef(TEXT("/Script/EnhancedInput.InputAction'/Game/ArenaBattle/Input/Actions/IA_QuaterMove.IA_QuaterMove'"));
if (nullptr != InputActionQuaterMoveRef.Object)
{
QuaterMoveAction = InputActionQuaterMoveRef.Object;
}
static ConstructorHelpers::FObjectFinder<UInputAction> InputActionAttackRef(TEXT("/Script/EnhancedInput.InputAction'/Game/ArenaBattle/Input/Actions/IA_Attack.IA_Attack'"));
if (nullptr != InputActionAttackRef.Object)
{
AttackAction = InputActionAttackRef.Object;
}
CurrentCharacterControlType = ECharacterControlType::Quater;
}
void AABCharacterPlayer::BeginPlay()
{
Super::BeginPlay();
APlayerController* PlayerController = Cast<APlayerController>(GetController());
if (PlayerController)
{
EnableInput(PlayerController);
}
SetCharacterControl(CurrentCharacterControlType);
}
void AABCharacterPlayer::SetDead()
{
Super::SetDead();
APlayerController* PlayerController = Cast<APlayerController>(GetController());
if (PlayerController)
{
DisableInput(PlayerController);
}
}
void AABCharacterPlayer::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent);
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &ACharacter::Jump);
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);
EnhancedInputComponent->BindAction(ChangeControlAction, ETriggerEvent::Triggered, this, &AABCharacterPlayer::ChangeCharacterControl);
EnhancedInputComponent->BindAction(ShoulderMoveAction, ETriggerEvent::Triggered, this, &AABCharacterPlayer::ShoulderMove);
EnhancedInputComponent->BindAction(ShoulderLookAction, ETriggerEvent::Triggered, this, &AABCharacterPlayer::ShoulderLook);
EnhancedInputComponent->BindAction(QuaterMoveAction, ETriggerEvent::Triggered, this, &AABCharacterPlayer::QuaterMove);
EnhancedInputComponent->BindAction(AttackAction, ETriggerEvent::Triggered, this, &AABCharacterPlayer::Attack);
}
void AABCharacterPlayer::ChangeCharacterControl()
{
if (CurrentCharacterControlType == ECharacterControlType::Quater)
{
SetCharacterControl(ECharacterControlType::Shoulder);
}
else if (CurrentCharacterControlType == ECharacterControlType::Shoulder)
{
SetCharacterControl(ECharacterControlType::Quater);
}
}
void AABCharacterPlayer::SetCharacterControl(ECharacterControlType NewCharacterControlType)
{
UABCharacterControlData* NewCharacterControl = CharacterControlManager[NewCharacterControlType];
check(NewCharacterControl);
SetCharacterControlData(NewCharacterControl);
APlayerController* PlayerController = CastChecked<APlayerController>(GetController());
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
{
Subsystem->ClearAllMappings();
UInputMappingContext* NewMappingContext = NewCharacterControl->InputMappingContext;
if (NewMappingContext)
{
Subsystem->AddMappingContext(NewMappingContext, 0);
}
}
CurrentCharacterControlType = NewCharacterControlType;
}
void AABCharacterPlayer::SetCharacterControlData(const UABCharacterControlData* CharacterControlData)
{
Super::SetCharacterControlData(CharacterControlData);
CameraBoom->TargetArmLength = CharacterControlData->TargetArmLength;
CameraBoom->SetRelativeRotation(CharacterControlData->RelativeRotation);
CameraBoom->bUsePawnControlRotation = CharacterControlData->bUsePawnControlRotation;
CameraBoom->bInheritPitch = CharacterControlData->bInheritPitch;
CameraBoom->bInheritYaw = CharacterControlData->bInheritYaw;
CameraBoom->bInheritRoll = CharacterControlData->bInheritRoll;
CameraBoom->bDoCollisionTest = CharacterControlData->bDoCollisionTest;
}
void AABCharacterPlayer::ShoulderMove(const FInputActionValue& Value)
{
FVector2D MovementVector = Value.Get<FVector2D>();
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
AddMovementInput(ForwardDirection, MovementVector.X);
AddMovementInput(RightDirection, MovementVector.Y);
}
void AABCharacterPlayer::ShoulderLook(const FInputActionValue& Value)
{
FVector2D LookAxisVector = Value.Get<FVector2D>();
AddControllerYawInput(LookAxisVector.X);
AddControllerPitchInput(LookAxisVector.Y);
}
void AABCharacterPlayer::QuaterMove(const FInputActionValue& Value)
{
FVector2D MovementVector = Value.Get<FVector2D>();
float InputSizeSquared = MovementVector.SquaredLength();
float MovementVectorSize = 1.0f;
float MovementVectorSizeSquared = MovementVector.SquaredLength();
if (MovementVectorSizeSquared > 1.0f)
{
MovementVector.Normalize();
MovementVectorSizeSquared = 1.0f;
}
else
{
MovementVectorSize = FMath::Sqrt(MovementVectorSizeSquared);
}
FVector MoveDirection = FVector(MovementVector.X, MovementVector.Y, 0.0f);
GetController()->SetControlRotation(FRotationMatrix::MakeFromX(MoveDirection).Rotator());
AddMovementInput(MoveDirection, MovementVectorSize);
}
void AABCharacterPlayer::Attack()
{
ProcessComboCommand();
}
void AABCharacterPlayer::SetupHUDWidget(UABHUDWidget* InHUDWidget)
{
if (InHUDWidget)
{
InHUDWidget->UpdateStat(Stat->GetBaseStat(), Stat->GetModifierStat());
InHUDWidget->UpdateHpBar(Stat->GetCurrentHp());
Stat->OnStatChanged.AddUObject(InHUDWidget, &UABHUDWidget::UpdateStat);
Stat->OnHpChanged.AddUObject(InHUDWidget, &UABHUDWidget::UpdateHpBar);
}
}
ABCharacterPlayer.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "GameData/ABCharacterStat.h"
#include "ABCharacterStatComponent.generated.h"
DECLARE_MULTICAST_DELEGATE(FOnHpZeroDelegate);
DECLARE_MULTICAST_DELEGATE_OneParam(FOnHpChangedDelegate, float /*CurrentHp*/);
DECLARE_MULTICAST_DELEGATE_TwoParams(FOnStatChangedDelegate, const FABCharacterStat& /*BaseStat*/, const FABCharacterStat& /*ModifierStat*/);
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class ARENABATTLE_API UABCharacterStatComponent : public UActorComponent
{
GENERATED_BODY()
public:
// Sets default values for this component's properties
UABCharacterStatComponent();
protected:
virtual void InitializeComponent() override;
public:
FOnHpZeroDelegate OnHpZero;
FOnHpChangedDelegate OnHpChanged;
FOnStatChangedDelegate OnStatChanged;
void SetLevelStat(int32 InNewLevel);
FORCEINLINE float GetCurrentLevel() const { return CurrentLevel; }
FORCEINLINE void AddBaseStat(const FABCharacterStat& InAddBaseStat) { BaseStat = BaseStat + InAddBaseStat; OnStatChanged.Broadcast(GetBaseStat(), GetModifierStat()); }
FORCEINLINE void SetBaseStat(const FABCharacterStat& InBaseStat) { BaseStat = InBaseStat; OnStatChanged.Broadcast(GetBaseStat(), GetModifierStat()); }
FORCEINLINE void SetModifierStat(const FABCharacterStat& InModifierStat) { ModifierStat = InModifierStat; OnStatChanged.Broadcast(GetBaseStat(), GetModifierStat()); }
FORCEINLINE const FABCharacterStat& GetBaseStat() const { return BaseStat; }
FORCEINLINE const FABCharacterStat& GetModifierStat() const { return ModifierStat; }
FORCEINLINE FABCharacterStat GetTotalStat() const { return BaseStat + ModifierStat; }
FORCEINLINE float GetCurrentHp() const { return CurrentHp; }
FORCEINLINE void HealHp(float InHealAmount) { CurrentHp = FMath::Clamp(CurrentHp + InHealAmount, 0, GetTotalStat().MaxHp); OnHpChanged.Broadcast(CurrentHp); }
FORCEINLINE float GetAttackRadius() const { return AttackRadius; }
float ApplyDamage(float InDamage);
protected:
void SetHp(float NewHp);
UPROPERTY(Transient, VisibleInstanceOnly, Category = Stat)
float CurrentHp;
UPROPERTY(Transient, VisibleInstanceOnly, Category = Stat)
float CurrentLevel;
UPROPERTY(VisibleInstanceOnly, Category = Stat, Meta = (AllowPrivateAccess = "true"))
float AttackRadius;
UPROPERTY(Transient, VisibleInstanceOnly, Category = Stat, Meta = (AllowPrivateAccess = "true"))
FABCharacterStat BaseStat;
UPROPERTY(Transient, VisibleInstanceOnly, Category = Stat, Meta = (AllowPrivateAccess = "true"))
FABCharacterStat ModifierStat;
};
ABCharacterStatComponent.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Interface/ABAnimationAttackInterface.h"
#include "Interface/ABCharacterWidgetInterface.h"
#include "Interface/ABCharacterItemInterface.h"
#include "GameData/ABCharacterStat.h"
#include "ABCharacterBase.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(LogABCharacter, Log, All);
UENUM()
enum class ECharacterControlType : uint8
{
Shoulder,
Quater
};
DECLARE_DELEGATE_OneParam(FOnTakeItemDelegate, class UABItemData* /*InItemData*/);
USTRUCT(BlueprintType)
struct FTakeItemDelegateWrapper
{
GENERATED_BODY()
FTakeItemDelegateWrapper() {}
FTakeItemDelegateWrapper(const FOnTakeItemDelegate& InItemDelegate) : ItemDelegate(InItemDelegate) {}
FOnTakeItemDelegate ItemDelegate;
};
UCLASS()
class ARENABATTLE_API AABCharacterBase : public ACharacter, public IABAnimationAttackInterface, public IABCharacterWidgetInterface, public IABCharacterItemInterface
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AABCharacterBase();
virtual void PostInitializeComponents() override;
protected:
virtual void SetCharacterControlData(const class UABCharacterControlData* CharacterControlData);
UPROPERTY(EditAnywhere, Category = CharacterControl, Meta = (AllowPrivateAccess = "true"))
TMap<ECharacterControlType, class UABCharacterControlData*> CharacterControlManager;
// Combo Action Section
protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Animation)
TObjectPtr<class UAnimMontage> ComboActionMontage;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = "true"))
TObjectPtr<class UABComboActionData> ComboActionData;
void ProcessComboCommand();
void ComboActionBegin();
void ComboActionEnd(class UAnimMontage* TargetMontage, bool IsProperlyEnded);
virtual void NotifyComboActionEnd();
void SetComboCheckTimer();
void ComboCheck();
int32 CurrentCombo = 0;
FTimerHandle ComboTimerHandle;
bool HasNextComboCommand = false;
// Attack Hit Section
protected:
virtual void AttackHitCheck() override;
virtual float TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, AActor* DamageCauser) override;
// Dead Section
protected:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Stat, Meta = (AllowPrivateAccess = "true"))
TObjectPtr<class UAnimMontage> DeadMontage;
virtual void SetDead();
void PlayDeadAnimation();
float DeadEventDelayTime = 5.0f;
// Stat Section
protected:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Stat, Meta = (AllowPrivateAccess = "true"))
TObjectPtr<class UABCharacterStatComponent> Stat;
// UI Widget Section
protected:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Widget, Meta = (AllowPrivateAccess = "true"))
TObjectPtr<class UWidgetComponent> HpBar;
virtual void SetupCharacterWidget(class UABUserWidget* InUserWidget) override;
// Item Section
protected:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Equipment, Meta = (AllowPrivateAccess = "true"))
TObjectPtr<class USkeletalMeshComponent> Weapon;
UPROPERTY()
TArray<FTakeItemDelegateWrapper> TakeItemActions;
virtual void TakeItem(class UABItemData* InItemData) override;
virtual void DrinkPotion(class UABItemData* InItemData);
virtual void EquipWeapon(class UABItemData* InItemData);
virtual void ReadScroll(class UABItemData* InItemData);
// Stat Section
public:
int32 GetLevel();
void SetLevel(int32 InNewLevel);
void ApplyStat(const FABCharacterStat& BaseStat, const FABCharacterStat& ModifierStat);
};
ABCharacterBase.h
// Fill out your copyright notice in the Description page of Project Settings.
#include "Character/ABCharacterBase.h"
#include "Components/CapsuleComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "ABCharacterControlData.h"
#include "Animation/AnimMontage.h"
#include "ABComboActionData.h"
#include "Physics/ABCollision.h"
#include "Engine/DamageEvents.h"
#include "CharacterStat/ABCharacterStatComponent.h"
#include "UI/ABWidgetComponent.h"
#include "UI/ABHpBarWidget.h"
#include "Item/ABItems.h"
DEFINE_LOG_CATEGORY(LogABCharacter);
// Sets default values
AABCharacterBase::AABCharacterBase()
{
// Pawn
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
// Capsule
GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
GetCapsuleComponent()->SetCollisionProfileName(CPROFILE_ABCAPSULE);
// Movement
GetCharacterMovement()->bOrientRotationToMovement = true;
GetCharacterMovement()->RotationRate = FRotator(0.0f, 500.0f, 0.0f);
GetCharacterMovement()->JumpZVelocity = 700.f;
GetCharacterMovement()->AirControl = 0.35f;
GetCharacterMovement()->MaxWalkSpeed = 500.f;
GetCharacterMovement()->MinAnalogWalkSpeed = 20.f;
GetCharacterMovement()->BrakingDecelerationWalking = 2000.f;
// Mesh
GetMesh()->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, -100.0f), FRotator(0.0f, -90.0f, 0.0f));
GetMesh()->SetAnimationMode(EAnimationMode::AnimationBlueprint);
GetMesh()->SetCollisionProfileName(TEXT("NoCollision"));
static ConstructorHelpers::FObjectFinder<USkeletalMesh> CharacterMeshRef(TEXT("/Script/Engine.SkeletalMesh'/Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_Cardboard.SK_CharM_Cardboard'"));
if (CharacterMeshRef.Object)
{
GetMesh()->SetSkeletalMesh(CharacterMeshRef.Object);
}
static ConstructorHelpers::FClassFinder<UAnimInstance> AnimInstanceClassRef(TEXT("/Game/ArenaBattle/Animation/ABP_ABCharacter.ABP_ABCharacter_C"));
if (AnimInstanceClassRef.Class)
{
GetMesh()->SetAnimInstanceClass(AnimInstanceClassRef.Class);
}
static ConstructorHelpers::FObjectFinder<UABCharacterControlData> ShoulderDataRef(TEXT("/Script/ArenaBattle.ABCharacterControlData'/Game/ArenaBattle/CharacterControl/ABC_Shoulder.ABC_Shoulder'"));
if (ShoulderDataRef.Object)
{
CharacterControlManager.Add(ECharacterControlType::Shoulder, ShoulderDataRef.Object);
}
static ConstructorHelpers::FObjectFinder<UABCharacterControlData> QuaterDataRef(TEXT("/Script/ArenaBattle.ABCharacterControlData'/Game/ArenaBattle/CharacterControl/ABC_Quater.ABC_Quater'"));
if (QuaterDataRef.Object)
{
CharacterControlManager.Add(ECharacterControlType::Quater, QuaterDataRef.Object);
}
static ConstructorHelpers::FObjectFinder<UAnimMontage> ComboActionMontageRef(TEXT("/Script/Engine.AnimMontage'/Game/ArenaBattle/Animation/AM_ComboAttack.AM_ComboAttack'"));
if (ComboActionMontageRef.Object)
{
ComboActionMontage = ComboActionMontageRef.Object;
}
static ConstructorHelpers::FObjectFinder<UABComboActionData> ComboActionDataRef(TEXT("/Script/ArenaBattle.ABComboActionData'/Game/ArenaBattle/CharacterAction/ABA_ComboAttack.ABA_ComboAttack'"));
if (ComboActionDataRef.Object)
{
ComboActionData = ComboActionDataRef.Object;
}
static ConstructorHelpers::FObjectFinder<UAnimMontage> DeadMontageRef(TEXT("/Script/Engine.AnimMontage'/Game/ArenaBattle/Animation/AM_Dead.AM_Dead'"));
if (DeadMontageRef.Object)
{
DeadMontage = DeadMontageRef.Object;
}
// Stat Component
Stat = CreateDefaultSubobject<UABCharacterStatComponent>(TEXT("Stat"));
// Widget Component
HpBar = CreateDefaultSubobject<UABWidgetComponent>(TEXT("Widget"));
HpBar->SetupAttachment(GetMesh());
HpBar->SetRelativeLocation(FVector(0.0f, 0.0f, 180.0f));
static ConstructorHelpers::FClassFinder<UUserWidget> HpBarWidgetRef(TEXT("/Game/ArenaBattle/UI/WBP_HpBar.WBP_HpBar_C"));
if (HpBarWidgetRef.Class)
{
HpBar->SetWidgetClass(HpBarWidgetRef.Class);
HpBar->SetWidgetSpace(EWidgetSpace::Screen);
HpBar->SetDrawSize(FVector2D(150.0f, 15.0f));
HpBar->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
// Item Actions
TakeItemActions.Add(FTakeItemDelegateWrapper(FOnTakeItemDelegate::CreateUObject(this, &AABCharacterBase::EquipWeapon)));
TakeItemActions.Add(FTakeItemDelegateWrapper(FOnTakeItemDelegate::CreateUObject(this, &AABCharacterBase::DrinkPotion)));
TakeItemActions.Add(FTakeItemDelegateWrapper(FOnTakeItemDelegate::CreateUObject(this, &AABCharacterBase::ReadScroll)));
// Weapon Component
Weapon = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Weapon"));
Weapon->SetupAttachment(GetMesh(), TEXT("hand_rSocket"));
}
void AABCharacterBase::PostInitializeComponents()
{
Super::PostInitializeComponents();
Stat->OnHpZero.AddUObject(this, &AABCharacterBase::SetDead);
Stat->OnStatChanged.AddUObject(this, &AABCharacterBase::ApplyStat);
}
void AABCharacterBase::SetCharacterControlData(const UABCharacterControlData* CharacterControlData)
{
// Pawn
bUseControllerRotationYaw = CharacterControlData->bUseControllerRotationYaw;
// CharacterMovement
GetCharacterMovement()->bOrientRotationToMovement = CharacterControlData->bOrientRotationToMovement;
GetCharacterMovement()->bUseControllerDesiredRotation = CharacterControlData->bUseControllerDesiredRotation;
GetCharacterMovement()->RotationRate = CharacterControlData->RotationRate;
}
void AABCharacterBase::ProcessComboCommand()
{
if (CurrentCombo == 0)
{
ComboActionBegin();
return;
}
if (!ComboTimerHandle.IsValid())
{
HasNextComboCommand = false;
}
else
{
HasNextComboCommand = true;
}
}
void AABCharacterBase::ComboActionBegin()
{
// Combo Status
CurrentCombo = 1;
// Movement Setting
GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None);
// Animation Setting
const float AttackSpeedRate = Stat->GetTotalStat().AttackSpeed;
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
AnimInstance->Montage_Play(ComboActionMontage, AttackSpeedRate);
FOnMontageEnded EndDelegate;
EndDelegate.BindUObject(this, &AABCharacterBase::ComboActionEnd);
AnimInstance->Montage_SetEndDelegate(EndDelegate, ComboActionMontage);
ComboTimerHandle.Invalidate();
SetComboCheckTimer();
}
void AABCharacterBase::ComboActionEnd(UAnimMontage* TargetMontage, bool IsProperlyEnded)
{
ensure(CurrentCombo != 0);
CurrentCombo = 0;
GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Walking);
NotifyComboActionEnd();
}
void AABCharacterBase::NotifyComboActionEnd()
{
}
void AABCharacterBase::SetComboCheckTimer()
{
int32 ComboIndex = CurrentCombo - 1;
ensure(ComboActionData->EffectiveFrameCount.IsValidIndex(ComboIndex));
const float AttackSpeedRate = Stat->GetTotalStat().AttackSpeed;
float ComboEffectiveTime = (ComboActionData->EffectiveFrameCount[ComboIndex] / ComboActionData->FrameRate) / AttackSpeedRate;
if (ComboEffectiveTime > 0.0f)
{
GetWorld()->GetTimerManager().SetTimer(ComboTimerHandle, this, &AABCharacterBase::ComboCheck, ComboEffectiveTime, false);
}
}
void AABCharacterBase::ComboCheck()
{
ComboTimerHandle.Invalidate();
if (HasNextComboCommand)
{
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
CurrentCombo = FMath::Clamp(CurrentCombo + 1, 1, ComboActionData->MaxComboCount);
FName NextSection = *FString::Printf(TEXT("%s%d"), *ComboActionData->MontageSectionNamePrefix, CurrentCombo);
AnimInstance->Montage_JumpToSection(NextSection, ComboActionMontage);
SetComboCheckTimer();
HasNextComboCommand = false;
}
}
void AABCharacterBase::AttackHitCheck()
{
FHitResult OutHitResult;
FCollisionQueryParams Params(SCENE_QUERY_STAT(Attack), false, this);
const float AttackRange = Stat->GetTotalStat().AttackRange;
const float AttackRadius = Stat->GetAttackRadius();
const float AttackDamage = Stat->GetTotalStat().Attack;
const FVector Start = GetActorLocation() + GetActorForwardVector() * GetCapsuleComponent()->GetScaledCapsuleRadius();
const FVector End = Start + GetActorForwardVector() * AttackRange;
bool HitDetected = GetWorld()->SweepSingleByChannel(OutHitResult, Start, End, FQuat::Identity, CCHANNEL_ABACTION, FCollisionShape::MakeSphere(AttackRadius), Params);
if (HitDetected)
{
FDamageEvent DamageEvent;
OutHitResult.GetActor()->TakeDamage(AttackDamage, DamageEvent, GetController(), this);
}
#if ENABLE_DRAW_DEBUG
FVector CapsuleOrigin = Start + (End - Start) * 0.5f;
float CapsuleHalfHeight = AttackRange * 0.5f;
FColor DrawColor = HitDetected ? FColor::Green : FColor::Red;
DrawDebugCapsule(GetWorld(), CapsuleOrigin, CapsuleHalfHeight, AttackRadius, FRotationMatrix::MakeFromZ(GetActorForwardVector()).ToQuat(), DrawColor, false, 5.0f);
#endif
}
float AABCharacterBase::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
Stat->ApplyDamage(DamageAmount);
return DamageAmount;
}
void AABCharacterBase::SetDead()
{
GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None);
PlayDeadAnimation();
SetActorEnableCollision(false);
HpBar->SetHiddenInGame(true);
}
void AABCharacterBase::PlayDeadAnimation()
{
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
AnimInstance->StopAllMontages(0.0f);
AnimInstance->Montage_Play(DeadMontage, 1.0f);
}
void AABCharacterBase::SetupCharacterWidget(UABUserWidget* InUserWidget)
{
UABHpBarWidget* HpBarWidget = Cast<UABHpBarWidget>(InUserWidget);
if (HpBarWidget)
{
HpBarWidget->UpdateStat(Stat->GetBaseStat(), Stat->GetModifierStat());
HpBarWidget->UpdateHpBar(Stat->GetCurrentHp());
Stat->OnHpChanged.AddUObject(HpBarWidget, &UABHpBarWidget::UpdateHpBar);
Stat->OnStatChanged.AddUObject(HpBarWidget, &UABHpBarWidget::UpdateStat);
}
}
void AABCharacterBase::TakeItem(UABItemData* InItemData)
{
if (InItemData)
{
TakeItemActions[(uint8)InItemData->Type].ItemDelegate.ExecuteIfBound(InItemData);
}
}
void AABCharacterBase::DrinkPotion(UABItemData* InItemData)
{
UABPotionItemData* PotionItemData = Cast<UABPotionItemData>(InItemData);
if (PotionItemData)
{
Stat->HealHp(PotionItemData->HealAmount);
}
}
void AABCharacterBase::EquipWeapon(UABItemData* InItemData)
{
UABWeaponItemData* WeaponItemData = Cast<UABWeaponItemData>(InItemData);
if (WeaponItemData)
{
if (WeaponItemData->WeaponMesh.IsPending())
{
WeaponItemData->WeaponMesh.LoadSynchronous();
}
Weapon->SetSkeletalMesh(WeaponItemData->WeaponMesh.Get());
Stat->SetModifierStat(WeaponItemData->ModifierStat);
}
}
void AABCharacterBase::ReadScroll(UABItemData* InItemData)
{
UABScrollItemData* ScrollItemData = Cast<UABScrollItemData>(InItemData);
if (ScrollItemData)
{
Stat->AddBaseStat(ScrollItemData->BaseStat);
}
}
int32 AABCharacterBase::GetLevel()
{
return Stat->GetCurrentLevel();
}
void AABCharacterBase::SetLevel(int32 InNewLevel)
{
Stat->SetLevelStat(InNewLevel);
}
void AABCharacterBase::ApplyStat(const FABCharacterStat& BaseStat, const FABCharacterStat& ModifierStat)
{
float MovementSpeed = (BaseStat + ModifierStat).MovementSpeed;
GetCharacterMovement()->MaxWalkSpeed = MovementSpeed;
}
ABCharacterBase.cpp
ABItemData를 상속받는 ABPotionItemData, ABScrollItemData 생성
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Item/ABItemData.h"
#include "GameData/ABCharacterStat.h"
#include "ABWeaponItemData.generated.h"
/**
*
*/
UCLASS()
class ARENABATTLE_API UABWeaponItemData : public UABItemData
{
GENERATED_BODY()
public:
UABWeaponItemData();
FPrimaryAssetId GetPrimaryAssetId() const override
{
return FPrimaryAssetId("ABItemData", GetFName());
}
public:
UPROPERTY(EditAnywhere, Category = Weapon)
TSoftObjectPtr<USkeletalMesh> WeaponMesh;
UPROPERTY(EditAnywhere, Category = Stat)
FABCharacterStat ModifierStat;
};
ABWeaponItemData.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Item/ABItemData.h"
#include "GameData/ABCharacterStat.h"
#include "ABScrollItemData.generated.h"
/**
*
*/
UCLASS()
class ARENABATTLE_API UABScrollItemData : public UABItemData
{
GENERATED_BODY()
public:
UABScrollItemData();
FPrimaryAssetId GetPrimaryAssetId() const override
{
return FPrimaryAssetId("ABItemData", GetFName());
}
public:
UPROPERTY(EditAnywhere, Category = Stat)
FABCharacterStat BaseStat;
};
ABScrollItemData.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Item/ABItemData.h"
#include "ABPotionItemData.generated.h"
/**
*
*/
UCLASS()
class ARENABATTLE_API UABPotionItemData : public UABItemData
{
GENERATED_BODY()
public:
UABPotionItemData();
FPrimaryAssetId GetPrimaryAssetId() const override
{
return FPrimaryAssetId("ABItemData", GetFName());
}
public:
UPROPERTY(EditAnywhere, Category = Hp)
float HealAmount;
};
ABPotionItemData.h
각 포션 생성, 각각 30, 100, 200, 500으로 값 설정
스크롤과 무기까지 추가
무기의 종류가 많아 ABItems라는 헤더파일을 따로 생성
#pragma once
#include "ABWeaponItemData.h"
#include "ABScrollItemData.h"
#include "ABPotionItemData.h"
ABItems.h
따로 include해줌
HpBar 위젯에 체력을 나타내는 Text를 추가
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "ABUserWidget.h"
#include "GameData/ABCharacterStat.h"
#include "ABHpBarWidget.generated.h"
/**
*
*/
UCLASS()
class ARENABATTLE_API UABHpBarWidget : public UABUserWidget
{
GENERATED_BODY()
public:
UABHpBarWidget(const FObjectInitializer& ObjectInitializer);
protected:
virtual void NativeConstruct() override;
public:
void UpdateStat(const FABCharacterStat& BaseStat, const FABCharacterStat& ModifierStat);
void UpdateHpBar(float NewCurrentHp);
FString GetHpStatText();
protected:
UPROPERTY()
TObjectPtr<class UProgressBar> HpProgressBar;
UPROPERTY()
TObjectPtr<class UTextBlock> HpStat;
UPROPERTY()
float CurrentHp;
UPROPERTY()
float MaxHp;
};
ABHpBarWidget.h
// Fill out your copyright notice in the Description page of Project Settings.
#include "UI/ABHpBarWidget.h"
#include "Components/ProgressBar.h"
#include "Components/TextBlock.h"
#include "Interface/ABCharacterWidgetInterface.h"
UABHpBarWidget::UABHpBarWidget(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
MaxHp = -1.0f;
}
void UABHpBarWidget::NativeConstruct()
{
Super::NativeConstruct();
HpProgressBar = Cast<UProgressBar>(GetWidgetFromName(TEXT("PbHpBar")));
ensure(HpProgressBar);
HpStat = Cast<UTextBlock>(GetWidgetFromName(TEXT("TxtHpStat")));
ensure(HpStat);
IABCharacterWidgetInterface* CharacterWidget = Cast<IABCharacterWidgetInterface>(OwningActor);
if (CharacterWidget)
{
CharacterWidget->SetupCharacterWidget(this);
}
}
void UABHpBarWidget::UpdateStat(const FABCharacterStat& BaseStat, const FABCharacterStat& ModifierStat)
{
MaxHp = (BaseStat + ModifierStat).MaxHp;
if (HpProgressBar)
{
HpProgressBar->SetPercent(CurrentHp / MaxHp);
}
if (HpStat)
{
HpStat->SetText(FText::FromString(GetHpStatText()));
}
}
void UABHpBarWidget::UpdateHpBar(float NewCurrentHp)
{
CurrentHp = NewCurrentHp;
ensure(MaxHp > 0.0f);
if (HpProgressBar)
{
HpProgressBar->SetPercent(CurrentHp / MaxHp);
}
if (HpStat)
{
HpStat->SetText(FText::FromString(GetHpStatText()));
}
}
FString UABHpBarWidget::GetHpStatText()
{
return FString::Printf(TEXT("%.0f/%0.f"), CurrentHp, MaxHp);
}
ABHpBarWidget.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "UI/ABHUDWidget.h"
#include "Interface/ABCharacterHUDInterface.h"
#include "ABHpBarWidget.h"
#include "ABCharacterStatWidget.h"
UABHUDWidget::UABHUDWidget(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
}
void UABHUDWidget::UpdateStat(const FABCharacterStat& BaseStat, const FABCharacterStat& ModifierStat)
{
FABCharacterStat TotalStat = BaseStat + ModifierStat;
HpBar->UpdateStat(BaseStat, ModifierStat);
CharacterStat->UpdateStat(BaseStat, ModifierStat);
}
void UABHUDWidget::UpdateHpBar(float NewCurrentHp)
{
HpBar->UpdateHpBar(NewCurrentHp);
}
void UABHUDWidget::NativeConstruct()
{
Super::NativeConstruct();
HpBar = Cast<UABHpBarWidget>(GetWidgetFromName(TEXT("WidgetHpBar")));
ensure(HpBar);
CharacterStat = Cast<UABCharacterStatWidget>(GetWidgetFromName(TEXT("WidgetCharacterStat")));
ensure(CharacterStat);
IABCharacterHUDInterface* HUDPawn = Cast<IABCharacterHUDInterface>(GetOwningPlayerPawn());
if (HUDPawn)
{
HUDPawn->SetupHUDWidget(this);
}
}
ABHUDWidget.cpp
캐릭터 스탯에 따라 UI 업데이트
이득우의 언리얼 프로그래밍 Part2 - 언리얼 게임 프레임웍의 이해 강의 | 이득우 - 인프런
이득우 | 대기업 현업자들이 수강하는 언리얼 C++ 프로그래밍 전문 과정입니다. 언리얼 C++ 프로그래밍을 사용해 핵&슬래시 로그라이크 게임 예제를 처음부터 끝까지 체계적으로 제작하는 방법을
www.inflearn.com
'게임 개발 > 언리얼 5' 카테고리의 다른 글
언리얼 5 - 게임의 완성 (0) | 2024.12.23 |
---|---|
언리얼5 - 리플렉션을 활용하는 언리얼 에디터 UI와 UI의 확장 (2) | 2024.12.19 |
언리얼 5 - 헤드업디스플레이의 구현 (0) | 2024.12.18 |
언리얼 5 - 행동트리 모델의 구현 (0) | 2024.12.16 |
언리얼5 - 행동트리 모델의 이해 (1) | 2024.12.15 |