[UE] Behavior Tree: 활 조준 및 발사하기
Player에 구현한 화살 발사를 궁수 AI에도 적용한다. SetFocus를 사용하여 적을 지속적으로 바라보게 만든다. 몽타주를 재생시켜 활의 시위를 당기고 화살 발사 준비를 마친 후 CSubAction_Bow 코드를 재사용하여 화살을 발사한다. CSubAction_Bow 코드 내에 AIController일 때도 Press()와 Released(), 즉 조준 후 발사가 가능하도록 코드를 추가한다.
목차
Plugins |
||||
Weapon |
||||
Resource |
||||
Icon128.png weapon_thumbnail_icon.png |
||||
Source |
||||
Weapon | ||||
SWeaponCheckBoxes.h .cpp SWeaponDetailsView.h .cpp SWeaponDoActionData.h .cpp SWeaponEquipmentData.h .cpp SWeaponHitData.h .cpp SWeaponLeftArea.h .cpp Weapon.Build.cs WeaponAssetEditor.h .cpp WeaponAssetFactory.h .cpp WeaponCommand.h .cpp WeaponContextMenu.h .cpp WeaponModule.h .cpp WeaponStyle.h .cpp |
||||
Source | ||
BehaviorTree | ||
CBTService_Melee.h .cpp CBTService_Range.h .cpp CBTTaskNode_Action.h .cpp CBTTaskNode_Equip.h .cpp CBTTaskNode_Hitted.h .cpp CBTTaskNode_Patrol.h .cpp CBTTaskNode_Speed.h .cpp CEnvQueryContext_Target.h .cpp 생성 CPatrol.h .cpp |
||
Characters | ||
CAIController.h .cpp CAnimInstance.h .cpp CEnemy.h .cpp CEnemy_AI.h.cpp CPlayer.h.cpp ICharacter.h .cpp |
||
Components | ||
CAIBehaviorComponent.h .cpp CFeetComponent.h .cpp CMontagesComponent.h .cpp CMovementComponent.h .cpp CStateComponent.h .cpp CStatusComponent.h .cpp CWeaponComponent.h .cpp CZoomComponent.h .cpp |
||
Notifies | ||
CAnimNotifyState_BeginAction.h .cpp CAnimNotifyState_BowString.h .cpp CAnimNotify_CameraShake.h .cpp CAnimNotify_End_Parkour.h .cpp CAnimNotifyState_EndAction.h .cpp CAnimNotify_EndState.h .cpp CAnimNotifyState.h .cpp CAnimNotifyState_CameraAnim.h .cpp CAnimNotifyState_Collision.h .cpp CAnimNotifyState_Combo.h .cpp CAnimNotifyState_Equip.h .cpp CAnimNotifyState_SubAction.h .cpp |
||
Parkour | ||
CParkourComponent.h .cpp | ||
Utilities | ||
CHelper.h CLog.h .cpp |
||
Weapons | ||
CArrow.h .cpp CAura.h .cpp CCamerModifier.h .cpp CGhostTrail.h .cpp CRotate_Object.h .cpp CThornObject.h .cpp CAnimInstance_Bow.h .cpp CAttachment_Bow.h .cpp CDoAction_Around.h .cpp CDoAction_Bow.h .cpp CDoAction_Combo.h .cpp CDoAction_Warp.h .cpp CSubAction_Around.h .cpp CSubAction_Bow.h .cpp CSubAction_Fist.h .cpp CSubAction_Hammer.h .cpp CSubAction_Sword.h .cpp CDoAction_Warp.h .cpp CAttachment.h .cpp CDoAction.h .cpp CEquipment.h .cpp CSubAction.h .cpp CWeaponAsset.h .cpp CWeaponData.h .cpp CWeaponStructures.h .cpp |
||
Global.h CGameMode.h .cpp U2212_06.Build.cs |
||
U2212_06.uproject | ||
방법1. 활을 쏘는 Enemy: 화살 발사하기
CWeaponComponent
CWeaponComponent.h
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "CWeaponComponent.generated.h"
UENUM(BlueprintType)
enum class EWeaponType : uint8
{
Fist, Sword, Hammer, Warp, Around, Bow, Max,
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FWeaponTypeChanged, EWeaponType, InPrevType, EWeaponType, InNewType);
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class U2212_06_API UCWeaponComponent : public UActorComponent
{
GENERATED_BODY()
private://DataAsset을 받아온다.
UPROPERTY(EditAnywhere, Category = "DataAsset")
class UCWeaponAsset* DataAssets[(int32)EWeaponType::Max];
public:
FORCEINLINE EWeaponType GetWeaponType() { return Type; }
public: //무기 Type이 맞는지 확인해주는 함수들
FORCEINLINE bool IsUnarmedMode() { return Type == EWeaponType::Max; }
FORCEINLINE bool IsFistMode() { return Type == EWeaponType::Fist; }
FORCEINLINE bool IsSwordMode() { return Type == EWeaponType::Sword; }
FORCEINLINE bool IsHammerMode() { return Type == EWeaponType::Hammer; }
FORCEINLINE bool IsWarpMode() { return Type == EWeaponType::Warp; }
FORCEINLINE bool IsAroundMode() { return Type == EWeaponType::Around; }
FORCEINLINE bool IsBowMode() { return Type == EWeaponType::Bow; }
public:
UCWeaponComponent();
protected:
virtual void BeginPlay() override;
public:
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
private:
bool IsIdleMode();//StateComponent, WeaponComponent 둘 다 같은 레벨이다. 서로 소유할 수 없는 관계이기 때문에 참조만해서 리턴받기 위해 IsIdleMode를 사용한다.
public:
class ACAttachment* GetAttachment();
class UCEquipment* GetEquipment();
class UCDoAction* GetDoAction();
public:
UFUNCTION(BlueprintCallable)
class UCSubAction* GetSubAction();
public: //무기 세팅
void SetUnarmedMode();
void SetFistMode();
void SetSwordMode();
void SetHammerMode();
void SetWarpMode();
void SetAroundMode();
void SetBowMode();
void DoAction();
public:
UFUNCTION(BlueprintCallable)
void SubAction_Pressed();
UFUNCTION(BlueprintCallable)
void SubAction_Released();
private:
void SetMode(EWeaponType InType);
void ChangeType(EWeaponType InType);
public: //무기가 바뀌었을때 통보해줄 delegate
FWeaponTypeChanged OnWeaponTypeChange;
private:
class ACharacter* OwnerCharacter;
EWeaponType Type = EWeaponType::Max;
private:
//가비지 콜랙터가 삭제하지 않도록 UPROPERTY를 붙여 직렬화 시켜준다. 직렬화되면 가비지 콜렉터가 삭제하지 않는다. UPROPERTY가 없으면 터진다.
UPROPERTY()
class UCWeaponData* Datas[(int32)EWeaponType::Max];//실제로 생성된 데이터
};
직렬화하여 블루프린트에서 보이게 하기
- UFUNCTION(BlueprintCallable)
class UCSubAction* GetSubAction(); - UFUNCTION(BlueprintCallable)
void SubAction_Pressed(); - UFUNCTION(BlueprintCallable)
void SubAction_Released();
CWeaponComponent.cpp
#include "Components/CWeaponComponent.h"
#include "Global.h"
#include "CStateComponent.h"
#include "GameFramework/Character.h"
#include "Weapons/CWeaponAsset.h"
#include "Weapons/CWeaponData.h"
#include "Weapons/CAttachment.h"
#include "Weapons/CEquipment.h"
#include "Weapons/CDoAction.h"
#include "Weapons/CSubAction.h"
UCWeaponComponent::UCWeaponComponent()
{
//Tick을 실행시켜주는 코드
PrimaryComponentTick.bCanEverTick = true;
}
void UCWeaponComponent::BeginPlay()
{
Super::BeginPlay();
OwnerCharacter = Cast<ACharacter>(GetOwner());
for (int32 i=0; i < (int32)EWeaponType::Max; i++)
{
if (!!DataAssets[i]) //DataAssets[i]이 있다면(=무기가 할당되어 있다면)
DataAssets[i]->BeginPlay(OwnerCharacter, &Datas[i]);//BeginPla y 시 OwnerCharacter에 Spawn시켜준다.
}
}
void UCWeaponComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (!!GetDoAction())//DoAction이 있다면
GetDoAction()->Tick(DeltaTime);//DoAction의 Tick을 콜 해준다.
if (!!GetSubAction())//SubAction이 있다면
GetSubAction()->Tick(DeltaTime);//SubAction의 Tick을 콜 해준다.
}
bool UCWeaponComponent::IsIdleMode()
{
return CHelpers::GetComponent<UCStateComponent>(OwnerCharacter)->IsIdleMode();
}
ACAttachment* UCWeaponComponent::GetAttachment()
{
CheckTrueResult(IsUnarmedMode(), nullptr);
CheckFalseResult(!!Datas[(int32)Type], nullptr);
return Datas[(int32)Type]->GetAttachment();//실제 데이터를 리턴
}
UCEquipment* UCWeaponComponent::GetEquipment()
{
CheckTrueResult(IsUnarmedMode(), nullptr);
CheckFalseResult(!!Datas[(int32)Type], nullptr);
return Datas[(int32)Type]->GetEquipment();//실제 데이터를 리턴
}
UCDoAction* UCWeaponComponent::GetDoAction()
{
CheckTrueResult(IsUnarmedMode(), nullptr);
CheckFalseResult(!!Datas[(int32)Type], nullptr);
return Datas[(int32)Type]->GetDoAction();//실제 데이터를 리턴
}
UCSubAction* UCWeaponComponent::GetSubAction()
{
CheckTrueResult(IsUnarmedMode(), nullptr);
CheckFalseResult(!!Datas[(int32)Type], nullptr);
return Datas[(int32)Type]->GetSubAction();//실제 데이터를 리턴
}
void UCWeaponComponent::SetUnarmedMode()
{
GetEquipment()->Unequip();
ChangeType(EWeaponType::Max);
}
void UCWeaponComponent::SetFistMode()
{
CheckFalse(IsIdleMode());
SetMode(EWeaponType::Fist);
}
void UCWeaponComponent::SetSwordMode()
{
CheckFalse(IsIdleMode());
SetMode(EWeaponType::Sword);
}
void UCWeaponComponent::SetHammerMode()
{
CheckFalse(IsIdleMode());
SetMode(EWeaponType::Hammer);
}
void UCWeaponComponent::SetWarpMode()
{
CheckFalse(IsIdleMode());
SetMode(EWeaponType::Warp);
}
void UCWeaponComponent::SetAroundMode()
{
CheckFalse(IsIdleMode());
SetMode(EWeaponType::Around);
}
void UCWeaponComponent::SetBowMode()
{
CheckFalse(IsIdleMode());
SetMode(EWeaponType::Bow);
}
void UCWeaponComponent::SetMode(EWeaponType InType)
{
if (Type == InType)
{
SetUnarmedMode();
return;
}
else if (IsUnarmedMode() == false)
{
GetEquipment()->Unequip();
}
if (!!Datas[(int32)InType])//실제 데이터가 있다면
{
Datas[(int32)InType]->GetEquipment()->Equip();
ChangeType(InType);
}
}
void UCWeaponComponent::ChangeType(EWeaponType InType)
{
EWeaponType prevType = Type;
Type = InType;
if (OnWeaponTypeChange.IsBound())
OnWeaponTypeChange.Broadcast(prevType, InType);
}
void UCWeaponComponent::DoAction()
{
if (!!GetDoAction())
GetDoAction()->DoAction();
}
void UCWeaponComponent::SubAction_Pressed()
{
if (!!GetSubAction())
GetSubAction()->Pressed();
}
void UCWeaponComponent::SubAction_Released()
{
if (!!GetSubAction())
GetSubAction()->Released();
}
변경사항 없음
CSubAction_Bow
CSubAction_Bow.h
#pragma once
#include "CoreMinimal.h"
#include "Weapons/CSubAction.h"
#include "Components/TimelineComponent.h"
#include "CSubAction_Bow.generated.h"
USTRUCT()
struct FAimData
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere)
float TargetArmLength = 100;
UPROPERTY(EditAnywhere)
FVector SocketOffset = FVector(0, 30, 10);
UPROPERTY(EditAnywhere)
bool bEnableCameraLag;
UPROPERTY(EditAnywhere)
FVector CameraLocation;
};
UCLASS(Blueprintable)
class U2212_06_API UCSubAction_Bow : public UCSubAction
{
GENERATED_BODY()
private:
UPROPERTY(EditAnywhere, Category = "Aiming")
class UCurveVector* Curve;
UPROPERTY(EditAnywhere, Category = "Aiming")
FAimData AimData;
UPROPERTY(EditAnywhere, Category = "Aiming")
float AimingSpeed = 200;
public:
UCSubAction_Bow();
public:
virtual void Pressed() override;
virtual void Released() override;
public:
void BeginPlay(class ACharacter* InOwner, class ACAttachment* InAttachment, class UCDoAction* InDoAction) override;
public:
void Tick_Implementation(float InDeltaTime) override;
private:
UFUNCTION()
void OnAiming(FVector Output);
private:
class USpringArmComponent* SpringArm;
class UCameraComponent* Camera;
private:
FTimeline Timeline;
//SubAction는 Actor가 아니기 때문에 Component를 가질 수 없다. SubAction은 UObject 상속이다.
//그래서 TimelineComponent가 아닌 Timeline으로 작업한다.
private:
FAimData OriginData;
private:
float* Bend;
};
변경사항 없음.
CSubAction_Bow.cpp
#include "Weapons/SubActions/CSubAction_Bow.h"
#include "Global.h"
#include "AIController.h"
#include "GameFramework/Character.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "Components/CStateComponent.h"
#include "Weapons/Attachments/CAttachment_Bow.h"
UCSubAction_Bow::UCSubAction_Bow()
{
CHelpers::GetAsset<UCurveVector>(&Curve, "CurveVector'/Game/Weapons/Bow/Curve_Aiming.Curve_Aiming'");//Editor에서 만든 CurveVector를 할당한다.
}
void UCSubAction_Bow::Pressed()
{
CheckTrue(State->IsSubActionMode());
//Enemy AI
if (!!Owner->GetController<AAIController>())
{
Super::Pressed();
State->OnSubActionMode();
return;
}
CheckNull(SpringArm);
CheckNull(Camera);
Super::Pressed();
State->OnSubActionMode();
OriginData.TargetArmLength = SpringArm->TargetArmLength;
OriginData.SocketOffset = SpringArm->SocketOffset;
OriginData.bEnableCameraLag = SpringArm->bEnableCameraLag;
OriginData.CameraLocation = Camera->GetRelativeLocation();
SpringArm->TargetArmLength = AimData.TargetArmLength;
SpringArm->SocketOffset = AimData.SocketOffset;
SpringArm->bEnableCameraLag = AimData.bEnableCameraLag;
Camera->SetRelativeLocation(AimData.CameraLocation);
Timeline.PlayFromStart();//Timeline 동작 시작.
}
void UCSubAction_Bow::Released()
{
CheckFalse(State->IsSubActionMode());
//Enemy AI
if (!!Owner->GetController<AAIController>())
{
Super::Pressed();
State->OffSubActionMode();
return;
}
CheckNull(SpringArm);
CheckNull(Camera);
Super::Released();
State->OffSubActionMode();
SpringArm->TargetArmLength = OriginData.TargetArmLength;
SpringArm->SocketOffset = OriginData.SocketOffset;
SpringArm->bEnableCameraLag = OriginData.bEnableCameraLag;
Camera->SetRelativeLocation(OriginData.CameraLocation);
Timeline.ReverseFromEnd();//Timeline 뒤집기
}
void UCSubAction_Bow::BeginPlay(ACharacter* InOwner, ACAttachment* InAttachment, UCDoAction* InDoAction)
{
Super::BeginPlay(InOwner, InAttachment, InDoAction);
SpringArm = CHelpers::GetComponent<USpringArmComponent>(InOwner);
Camera = CHelpers::GetComponent<UCameraComponent>(InOwner);
FOnTimelineVector timeline;
timeline.BindUFunction(this, "OnAiming");//OnAiming함수를 묶어서 콜한다.
Timeline.AddInterpVector(Curve, timeline);
Timeline.SetPlayRate(AimingSpeed);
ACAttachment_Bow* bow = Cast<ACAttachment_Bow>(InAttachment);//CAttachment_Bow의 멤버를 사용하기 위해 캐스팅한다.
if (!!bow)
Bend = bow->GetBend();//CSubAction_Bow에서 선언한 Bend 변수에 CAttachment_Bow의 Bend값을 GetBend()로 가져와서 넣어준다.
}
void UCSubAction_Bow::Tick_Implementation(float InDeltaTime)
{
Super::Tick_Implementation(InDeltaTime);
//TimelineComponent로 사용하는 경우 자기자신이 Tick이 있기 때문에 아래 작업을 안 해도 상관없으나 CSubAction_Bow의 경우 TimelineComponent가 없기 때문에 Tick을 반드시 넣어주어야 한다.
Timeline.TickTimeline(InDeltaTime);
}
void UCSubAction_Bow::OnAiming(FVector Output)
{
Camera->FieldOfView = Output.X;//조준 활성화와 해제에 앞뒤 ZoomIn&Out에 Output.X값이 쓰인다.
if (!!Bend)
*Bend = Output.Y;//에디터의 Curve_Aiming에서 설정한 Y값. 0~100까지 나온다.
}
헤더 추가
- #include "AIController.h"
Enemy_AI에서 화살 쏘기를 실행하기 위해 코드를 추가. 아래의 코드가 없으면 활 조준을 하지 않아 화살을 발사하지 않는다.
- void UCSubAction_Bow::Pressed()
- if (!!Owner->GetController<AAIController>())
{
Super::Pressed();
State->OnSubActionMode();
return;
}- 명령을 수행하는 주체(Owner)의 Controller가 AIIController 캐스팅이 가능하다면 Enemy라는 의미다. 해당 경우, 화살을 쏘고 리턴한다.
- if (!!Owner->GetController<AAIController>())
- void UCSubAction_Bow::Released()
- if (!!Owner->GetController<AAIController>())
{
Super::Pressed();
State->OffSubActionMode();
return;
}
- if (!!Owner->GetController<AAIController>())
BP_CEquipment_Bow 생성
Event Graph
DA_Bow
BT_Range
실행화면
Avoid 구현하기
CEnvQueryContext_Target 생성
새 C++ 클래스 - EnvQueryContext - CEnvQueryContext_Target 생성
CEnvQueryContext_Target.h
#pragma once
#include "CoreMinimal.h"
#include "EnvironmentQuery/EnvQueryContext.h"
#include "CEnvQueryContext_Target.generated.h"
UCLASS()
class U2212_06_API UCEnvQueryContext_Target : public UEnvQueryContext
{
GENERATED_BODY()
private:
void ProvideContext(FEnvQueryInstance& QueryInstance, FEnvQueryContextData& ContextData) const override;
};
CEnvQueryContext_Target.cpp
#include "BehaviorTree/CEnvQueryContext_Target.h"
#include "Global.h"
#include "EnvironmentQuery/EnvQueryTypes.h"
#include "EnvironmentQuery/Items/EnvQueryItemType_Actor.h"
#include "Characters/CEnemy_AI.h"
#include "Characters/CAIController.h"
#include "BehaviorTree/BlackboardComponent.h"
void UCEnvQueryContext_Target::ProvideContext(FEnvQueryInstance& QueryInstance, FEnvQueryContextData& ContextData) const
{
Super::ProvideContext(QueryInstance, ContextData);
//Query를 수행하는 Actor인 ACEnemy_AI 캐스팅
ACEnemy_AI* ai = Cast<ACEnemy_AI>(QueryInstance.Owner.Get());//QueryInstance.Owner.Get()는 UObject 리턴이다. Querier가 Object, Actor, Location 등이 될 수 있으니 어느것이든 다 받을수 있도록 최상위형인 UObject로 받기 위해QueryInstance.Owner.Get()으로 받았다.
ACAIController* controller = ai->GetController<ACAIController>();//Blackboard를 가져오기 위해 먼저 controller를 캐스팅한다.
UBlackboardComponent* blackboard = controller->GetBlackboardComponent();//controller 내의 Blackboard를 가져온다.
AActor* target = Cast<AActor>(blackboard->GetValueAsObject("Target"));//Blackboard의 Target를 가져다쓴다.
//target을 리턴할 것이기 때문에 Actor return이 되는 UEnvQueryItemType_Actor를 사용한다.
UEnvQueryItemType_Actor::SetContextHelper(ContextData, target);
//위치를 리턴해야 되는 경우 아래처럼 UEnvQueryItemType_VectorBase를 사용하면 된다.
//UEnvQueryItemType_VectorBase::
}
void UCEnvQueryContext_Target::ProvideContext(FEnvQueryInstance& QueryInstance, FEnvQueryContextData& ContextData) const
- ACEnemy_AI* ai = Cast<ACEnemy_AI>(QueryInstance.Owner.Get());
- Query를 수행하는 Actor인 ACEnemy_AI 캐스팅
- QueryInstance.Owner.Get()는 UObject 리턴이다. Querier가 Object, Actor, Location 등이 될 수 있으니 어느것이든 다 받을수 있도록 최상위형인 UObject로 받기 위해QueryInstance.Owner.Get()으로 받았다.
- ACAIController* controller = ai->GetController<ACAIController>();
- Blackboard를 가져오기 위해 먼저 controller를 캐스팅한다.
- UBlackboardComponent* blackboard = controller->GetBlackboardComponent();
- controller 내의 Blackboard를 가져온다.
- AActor* target = Cast<AActor>(blackboard->GetValueAsObject("Target"));
- Blackboard의 Target를 가져다쓴다.
- UEnvQueryItemType_Actor::SetContextHelper(ContextData, target);
- target을 리턴할 것이기 때문에 Actor return이 되는 UEnvQueryItemType_Actor를 사용한다.
Querier가 Object, Actor, Location 등이 될 수 있으니 어느것이든 다 받을수 있도록 최상위형인 UObject로 받기 위해
QueryInstance.Owner.Get()
으로 받았다.
EQ_Range 생성
인공 지능 - 인바이런먼트 쿼리 - EQ_Range 생성
BB_Range
Avoid_Location 변수 추가
BT_Range
실행화면
'⭐ Unreal Engine > UE RPG AI' 카테고리의 다른 글
[UE] Behavior Tree: 워프(Warp), EQS (0) | 2023.08.09 |
---|---|
[UE] Behavior Tree: 활 Enemy 생성, Player 바라보기 (0) | 2023.08.07 |
[UE] Behavior Tree: 무기 장착 Abort (0) | 2023.08.01 |
[UE] Behavior Tree: 피격(Hitted) (0) | 2023.07.31 |
[UE] Behavior Tree: 무기 장착 및 공격 (0) | 2023.07.27 |
댓글
이 글 공유하기
다른 글
-
[UE] Behavior Tree: 워프(Warp), EQS
[UE] Behavior Tree: 워프(Warp), EQS
2023.08.09 -
[UE] Behavior Tree: 활 Enemy 생성, Player 바라보기
[UE] Behavior Tree: 활 Enemy 생성, Player 바라보기
2023.08.07 -
[UE] Behavior Tree: 무기 장착 Abort
[UE] Behavior Tree: 무기 장착 Abort
2023.08.01 -
[UE] Behavior Tree: 피격(Hitted)
[UE] Behavior Tree: 피격(Hitted)
2023.07.31