BaseWeapon
헤더
#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "Character/Data/WeaponData.h"
#include "UObject/Object.h"
#include "BaseWeapon.generated.h"
struct FBaseWeaponInfo;
class AModularRobot;
/**
*
*/
UCLASS(BlueprintType, Blueprintable, EditInlineNew, DefaultToInstanced)
class CR4S_API UBaseWeapon : public UObject
{
GENERATED_BODY()
public:
UBaseWeapon();
#pragma region GetSet
FORCEINLINE FGameplayTag GetGameplayTag() const { return WeaponTag; }
void SetGameplayTag(const FGameplayTag GameplayTag);
#pragma endregion
virtual void Initialize(AModularRobot* OwnerCharacter);
#pragma region Attack
public:
virtual void OnAttack();
float ComputeFinalDamage();
protected:
void StartAttackCooldown();
void ResetAttackCooldown();
#pragma endregion
#pragma region WeaponInfo
protected:
UPROPERTY(EditAnywhere,BlueprintReadWrite,Category="Tags")
FGameplayTag WeaponTag;
UPROPERTY(EditAnywhere,BlueprintReadWrite,Category="Tags")
FBaseWeaponInfo BaseInfo;
#pragma endregion
#pragma region Cached
protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Owner")
TObjectPtr<AModularRobot> OwningCharacter;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Owner")
uint8 bCanAttack:1 {true};
FTimerHandle AttackCooldownTimerHandler;
#pragma endregion
};
소스
#include "BaseWeapon.h"
#include "Character/Characters/ModularRobot.h"
UBaseWeapon::UBaseWeapon()
{
}
void UBaseWeapon::OnAttack()
{
}
float UBaseWeapon::ComputeFinalDamage()
{
float FinalDamage=0;
if (UModularRobotStatusComponent* StatusComp=OwningCharacter->FindComponentByClass<UModularRobotStatusComponent>())
{
return FinalDamage=StatusComp->GetAttackPower()*StatusComp->GetAttackPowerMultiplier()*BaseInfo.DamageMultiplier;
}
return FinalDamage;
}
void UBaseWeapon::StartAttackCooldown()
{
bCanAttack=false;
GetWorld()->GetTimerManager().SetTimer(
AttackCooldownTimerHandler,
this,
&UBaseWeapon::ResetAttackCooldown,
BaseInfo.AttackCooldownTime,
false
);
}
void UBaseWeapon::ResetAttackCooldown()
{
bCanAttack=true;
}
void UBaseWeapon::SetGameplayTag(const FGameplayTag GameplayTag)
{
WeaponTag=GameplayTag;
}
void UBaseWeapon::Initialize(AModularRobot* OwnerCharacter)
{
OwningCharacter=OwnerCharacter;
if (CollisionComponent)
{
AActor* OwnerActor=GetOwner();
if (CR4S_ENSURE(LogHong1,OwnerActor))
{
CollisionComponent->IgnoreActorWhenMoving(OwnerActor,true);
}
CollisionComponent->OnComponentBeginOverlap.AddDynamic(this,&ABaseBullet::OnOverlapBegin);
}
}
- 원래는 Actor 기반 클래스였으나 UObject 기반 클래스로 변경
- 로봇이 무기를 장착하면 그에 따라 팔, 어깨쪽의 메시 자체가 변경되는 구조 (마스터 포즈 컴포넌트 사용 예정)
- 따라서, 자체적인 콜리전이나 메시를 가질 필요가 없고, 맵에 배치될 필요도 없으므로 순수 데이터 객체로 충분
- AActor 보다는 순수 데이터 객체로서 가벼운 UObject로 변경
- UCLASS 매크로에서 EditInlineNew, DefaultToInstanced를 추가해 에디터 상에서 객체 추가 가능하도록 설정
- 쿨다운 관련 기능은 근거리, 원거리 공통이므로 해당 부모 클래스에서 구현
- GameplayTag 관련 변수 및 함수 구현 (Get, Set)
- ComputeFinalDamage() : 무기 관련 정보를 이용한 최종 데미지 연산 함수
RangedWeapon
헤더
#include "CoreMinimal.h"
#include "BaseWeapon.h"
#include "Character/Data/WeaponData.h"
#include "RangedWeapon.generated.h"
struct FRangedWeaponData;
class ABaseBullet;
/**
*
*/
UCLASS(BlueprintType, Blueprintable)
class CR4S_API URangedWeapon : public UBaseWeapon
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
URangedWeapon();
#pragma region Override
public:
virtual void OnAttack() override;
virtual void Initialize(AModularRobot* OwnerCharacter) override;
protected:
#pragma endregion
#pragma region TypeSpecificInfo
protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FRangedWeaponInfo TypeSpecificInfo;
#pragma endregion
};
소스
URangedWeapon::URangedWeapon()
{
}
void URangedWeapon::OnAttack()
{
if (!bCanAttack) return;
APlayerController* PC=Cast<APlayerController>(OwningCharacter->GetController());
if (!PC) return;
int32 ViewportX, ViewportY;
PC->GetViewportSize(ViewportX, ViewportY);
float ScreenX=ViewportX*0.5f;
float ScreenY=ViewportY*0.5f;
FVector WorldOrigin;
FVector WorldDirection;
bool bDeprojected=PC->DeprojectScreenPositionToWorld(ScreenX, ScreenY, WorldOrigin, WorldDirection);
if (!bDeprojected) return;
FVector TraceEnd=WorldOrigin+(WorldDirection*TypeSpecificInfo.Range);
FHitResult Hit;
FCollisionQueryParams Params;
Params.AddIgnoredActor(OwningCharacter);
bool bHit=GetWorld()->LineTraceSingleByChannel(
Hit,
WorldOrigin,
TraceEnd,
ECC_Visibility,
Params
);
FVector ImpactPoint;
if (bHit)
{
ImpactPoint=Hit.ImpactPoint;
}
else
{
ImpactPoint=TraceEnd;
}
FVector MuzzleLocation=OwningCharacter->GetMesh()->GetSocketLocation(TypeSpecificInfo.BulletSocketName);
FVector ShootDirection=(ImpactPoint-MuzzleLocation).GetSafeNormal();
if (!CR4S_ENSURE(LogHong1, TypeSpecificInfo.ProjectileClass))
{
return;
}
FActorSpawnParameters SpawnParameters;
SpawnParameters.Instigator=OwningCharacter;
SpawnParameters.Owner = OwningCharacter;
FRotator SpawnRotation=ShootDirection.Rotation();
ABaseBullet* NewProjectile=GetWorld()->SpawnActor<ABaseBullet>(
TypeSpecificInfo.ProjectileClass,
MuzzleLocation,
SpawnRotation,
SpawnParameters
);
float FinalDamage=ComputeFinalDamage();
NewProjectile->Initialize(TypeSpecificInfo.BulletInfo,FinalDamage);
StartAttackCooldown();
}
void URangedWeapon::Initialize(AModularRobot* OwnerCharacter)
{
Super::Initialize(OwnerCharacter);
}
- Initialize() : 해당 객체가 생성되는 시점에서 함께 호출되는 함수로 객체의 무기 관련 정보를 서브시스템으로부터 로드하는 작업 및 오너캐릭터 캐싱 작업을 수행
- OnAttack() : 원거리 무기에 맞게 사거리 값을 기반으로 월드 상의 에임 위치로 LineTrace를 수행하여 목표 지점을 포착, 해당 위치로 총알을 소환하여 발사시키는 로직
트러블슈팅 - 총알끼리 오버랩되는 현상
- 총알이 스폰된 직후 바로 오버랩 이벤트를 발생시키는 현상 발생
- 총알이 짧은 시간에 여러발 생성되면서 서로 겹치는 것이 원인으로 추정
- 콜리전 채널을 추가하면 해결될 것으로 보임


- 프로젝트 세팅 ㅡ> Collision
- Object Channel을 추가
- Projectile로 기본 응답은 Overlap으로 지정
ㅡ> 나머지는 다 오버랩 가능하고 총알들만 채널 설정을 Projectile Object들을 Ignore하도록 설정하면 되므로

결과
참고자료