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라는 의미다. 해당 경우, 화살을 쏘고 리턴한다.  
  • void UCSubAction_Bow::Released()
    • if (!!Owner->GetController<AAIController>())
          {
              Super::Pressed();
              State->OffSubActionMode();

              return;
          }

 


 

 

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

 

 


 

 

 

실행화면