[UE] 총알 발사 구현하기
언리얼 엔진에서 3인칭 슈팅 게임(TPS) 총알 발사를 구현하려면 무기 만들기, 총알 생성, 총알 구성, 충돌 돌처리, 대미지 적용, 시각 효과 추가하기 등의 과정이 필요하다. 언리얼 엔진에서 TPS 총알 발사를 구현하려면 블루프린트, 애니메이션 및 물리 시뮬레이션의 조합이 필요하다. 올바른 설정을 통해 플레이어가 게임에 계속 몰입할 수 있는 현실적이고 매력적인 슈팅 경험을 만들 수 있다.
목차
총알 발사 구현하기
언리얼 엔진에서 3인칭 슈팅 게임(TPS) 총알 발사를 구현하려면 다음과 같은 여러 단계가 필요하다.
- 무기 만들기: 먼저 무기용 메시와 총알이 생성되는 소켓이 포함된 무기 청사진을 만들어야 한다. 청사진에 발사 애니메이션과 음향 효과도 추가해야 한다.
- 총알 생성: 플레이어가 무기를 발사할 때 무기 설계도의 소켓에서 총알 발사체를 생성해야 한다. 무기의 발사 애니메이션 블루프린트에서 SpawnActor 노드를 사용하여 이를 수행할 수 있다.
- 총알 구성: 총알 발사체는 총알의 속도, 손상 및 수명과 같은 적절한 설정으로 구성해야 한다. 글머리 기호의 청사진에서 이러한 속성을 설정할 수 있다.
- 충돌 처리: 총알이 생성되면 게임 세계의 다른 개체와의 충돌을 처리해야 한다. 총알의 청사진에 충돌 구성 요소를 추가하고 총알의 스크립트에서 충돌 이벤트를 처리하여 이를 수행할 수 있다.
- 대미지 적용: 총알이 물체와 충돌할 때 총알의 속성에 따라 물체에 대미지를 적용해야 한다. Unreal Engine의 게임플레이 프레임워크에서 Apply Damage 노드를 사용하면 된다.
- 시각 효과 추가: 마지막으로 총구 섬광, 탄환 추적기 및 충격 효과와 같은 시각 효과를 게임에 추가하여 사격 경험을 향상시킬 수 있다.
전반적으로 언리얼 엔진에서 TPS 총알 발사를 구현하려면 블루프린트, 애니메이션 및 물리 시뮬레이션의 조합이 필요하다. 올바른 설정을 통해 플레이어가 게임에 계속 몰입할 수 있는 현실적이고 매력적인 슈팅 경험을 만들 수 있다.
Characters | |
CAnimInstance.h .cpp CPlayer.h .cpp |
|
Utilities | |
CHelpers.h CLog.h .cpp |
|
Weapons | |
CWeapon.h .cpp CWeapon_AR4.h .cpp CWeaponComponent.h .cpp |
|
Widget | |
CUserWidget_HUD.h .cpp | |
CGameMode.h .cpp Global.h CAnimNotifyState_Equip.h .cpp |
파일 및 폴더 추가 - Effect, Sounds, Decal
언리얼 Content 폴더에 필요한 것들 추가
SK_AR4_Skeleton - 소켓 생성
소켓 생성
- b_gun_muzzlerflash 소켓 생성 - Muzzle
- b_gun_shelleject 소켓 생성 - Eject
- Relative Location: (0.0, 0.0, -4.0)
BP_CameraShake_AR4 생성
Blueprint Class 생성 - MatineeCameraShake - BP_CameraShake_AR4 생성
총의 반동 효과를 주기 위해 BP_CameraShake_AR4를 만들었다.
- X: 앞뒤로 흔들리는 효과는 넣지 않았다
- Y: 좌우로 흔들리는 효과.
- Z: 위아래로 흔들리는 효과.
CWeapon
CWeapon.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CWeapon.generated.h"
//직렬화 해야하는 struct는 클래스 사이에 선언해야 한다.
USTRUCT()
struct FWeaponAimData
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere)
float TargetArmLength;
UPROPERTY(EditAnywhere)
FVector SocketOffset;
UPROPERTY(EditAnywhere)
float FieldOfView;
public:
void SetData(class ACharacter* InOwner);
void SetDataByNoneCurve(class ACharacter* InOwner);
};
//abstract를 쓰면 Unreal Editor에서 추상 베이스 클래스로 취급하겠다는 의미. 추상이되면 배치시킬 수 없다.
UCLASS(abstract)
class U2212_04_API ACWeapon : public AActor
{
GENERATED_BODY()
protected:
UPROPERTY(EditDefaultsOnly, Category = "Equip")
FName HolsterSocketName;
UPROPERTY(EditDefaultsOnly, Category = "Equip")
class UAnimMontage* EquipMontage;
UPROPERTY(EditDefaultsOnly, Category = "Equip")
float EquipMontage_PlayRate;
UPROPERTY(EditDefaultsOnly, Category = "Equip")
FName RightHandSocketName;
UPROPERTY(EditDefaultsOnly, Category = "Equip")
FVector LeftHandLocation;
protected:
UPROPERTY(EditDefaultsOnly, Category = "Aim")
FWeaponAimData BaseData;
UPROPERTY(EditDefaultsOnly, Category = "Aim")
FWeaponAimData AimData;
UPROPERTY(EditDefaultsOnly, Category = "Aim")
class UCurveFloat* AimCurve;
UPROPERTY(EditDefaultsOnly, Category = "Aim")
float AimingSpeed = 200;
protected:
UPROPERTY(EditDefaultsOnly, Category = "Hit")
float HitDistance = 3000;
UPROPERTY(EditDefaultsOnly, Category = "Hit")
class UMaterialInstanceConstant* HitDecal;
UPROPERTY(EditDefaultsOnly, Category = "Hit")
class UParticleSystem* HitParticle;
protected:
UPROPERTY(EditDefaultsOnly, Category = "Fire")
class UParticleSystem* FlashParticle;
UPROPERTY(EditDefaultsOnly, Category = "Fire")
class UParticleSystem* EjectParticle;
UPROPERTY(EditDefaultsOnly, Category = "Fire")
class USoundWave* FireSound;
UPROPERTY(EditDefaultsOnly, Category = "Fire")
float RecoilAngle; //Cone의 각도
UPROPERTY(EditDefaultsOnly, Category = "Fire")
TSubclassOf<class UMatineeCameraShake> CameraShakeClass;
private:
UPROPERTY(VisibleAnywhere)
class USceneComponent* Root;
protected:
UPROPERTY(VisibleAnywhere)
class USkeletalMeshComponent* Mesh;
private:
UPROPERTY(VisibleAnywhere)
class UTimelineComponent* Timeline;
public:
FORCEINLINE bool IsInAim() { return bInAim; }
FORCEINLINE FVector GetLeftHandLocation() { return LeftHandLocation; }
public:
ACWeapon();
protected:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;
public:
bool CanEquip();
void Equip();
void Begin_Equip();
void End_Equip();
bool CanUnequip();
void Unequip();
public:
bool CanAim();
void Begin_Aim();
void End_Aim();
private:
UFUNCTION()
void OnAiming(float Output); //조준
public:
bool CanFire();
void Begin_Fire();
void End_Fire();
private:
UFUNCTION()
void OnFiring(); //연사
private:
bool bEquipping;
bool bInAim;
bool bFiring;
bool bReload;
bool bAutoFire = true;
private:
class ACPlayer* Owner;
};
변수 생성
- float
RecoilAngle
- TSubclassOf<class UMatineeCameraShake>
CameraShake
CWeapon.cpp
#include "Weapons/CWeapon.h"
#include "Global.h"
#include "Character/CPlayer.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/TimelineComponent.h"
#include "Components/DecalComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "Camera/CameraShake.h"
#include "Materials/MaterialInstanceConstant.h"
#include "Particles/ParticleSystem.h"
void FWeaponAimData::SetData(ACharacter* InOwner)
{
USpringArmComponent* springArm = CHelpers::GetComponent<USpringArmComponent>(InOwner);
springArm->TargetArmLength = TargetArmLength;
springArm->SocketOffset = SocketOffset;
}
void FWeaponAimData::SetDataByNoneCurve(ACharacter* InOwner)
{
USpringArmComponent* springArm = CHelpers::GetComponent<USpringArmComponent>(InOwner);
springArm->TargetArmLength = TargetArmLength;
springArm->SocketOffset = SocketOffset;
UCameraComponent* camera = CHelpers::GetComponent<UCameraComponent>(InOwner);
camera->FieldOfView = FieldOfView; //curve를 이용하여 FieldOfView를 만들어준다.
}
///////////////////////////////////////////////////////////////////////////////
ACWeapon::ACWeapon()
{
PrimaryActorTick.bCanEverTick = true;
CHelpers::CreateComponent<USceneComponent>(this, &Root, "Root");
CHelpers::CreateComponent<USkeletalMeshComponent>(this, &Mesh, "Mesh", Root);
CHelpers::CreateActorComponent<UTimelineComponent>(this, &Timeline, "Timeline");
//CurveFloat
CHelpers::GetAsset<UCurveFloat>(&AimCurve, "CurveFloat'/Game/Character/Weapons/Curve_Aim.Curve_Aim'");
CHelpers::GetAsset<UMaterialInstanceConstant>(&HitDecal, "MaterialInstanceConstant'/Game/Materials/M_Decal_Inst.M_Decal_Inst'");
CHelpers::GetAsset<UParticleSystem>(&HitParticle, "ParticleSystem'/Game/Effects/P_Impact_Default.P_Impact_Default'");
CHelpers::GetAsset<UParticleSystem>(&FlashParticle, "ParticleSystem'/Game/Effects/P_Muzzleflash.P_Muzzleflash'");
CHelpers::GetAsset<UParticleSystem>(&EjectParticle, "ParticleSystem'/Game/Effects/P_Eject_bullet.P_Eject_bullet'");
CHelpers::GetAsset<USoundWave>(&FireSound, "SoundWave'/Game/Sounds/S_RifleShoot.S_RifleShoot'");
}
void ACWeapon::BeginPlay()
{
Super::BeginPlay();
Owner = Cast<ACPlayer>(GetOwner());
if (HolsterSocketName.IsValid())
CHelpers::AttachTo(this, Owner->GetMesh(), HolsterSocketName);
BaseData.SetDataByNoneCurve(Owner);
if(!!AimCurve)
{
FOnTimelineFloat timeline;
timeline.BindUFunction(this, "OnAiming");
Timeline->AddInterpFloat(AimCurve, timeline); //이벤트 객체를 다룸.
Timeline->SetLooping(false);
Timeline->SetPlayRate(AimingSpeed);
}
}
void ACWeapon::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
bool ACWeapon::CanEquip()
{
bool b = false;
//3개 다 false일 때 b = false
//3개 중 하나라도 true면 b = true
b |= bEquipping;
b |= bReload;
b |= bFiring;
return !b; //b = false일 때 True를 리턴
}
void ACWeapon::Equip()
{
bEquipping = true;
if (!!EquipMontage)
Owner->PlayAnimMontage(EquipMontage, EquipMontage_PlayRate);
}
void ACWeapon::Begin_Equip()
{
if (RightHandSocketName.IsValid())
CHelpers::AttachTo(this, Owner->GetMesh(), RightHandSocketName);
}
void ACWeapon::End_Equip()
{
bEquipping = false;
}
bool ACWeapon::CanUnequip()
{
bool b = false;
b |= bEquipping;
b |= bReload;
b |= bFiring;
return !b;
}
void ACWeapon::Unequip()
{
if (HolsterSocketName.IsValid())
CHelpers::AttachTo(this, Owner->GetMesh(), HolsterSocketName);
}
bool ACWeapon::CanAim()
{
bool b = false;
b |= bEquipping;
b |= bReload;
b |= bInAim;
return !b;
}
void ACWeapon::Begin_Aim()
{
bInAim = true;
//우리는 생성자 ACWeapon()에서 AimCurve를 가져왔다.
if (!!AimCurve) //AimCurve가 있으면 true, 없으면 nullptr이기 때문에 false
{
Timeline->PlayFromStart();
AimData.SetData(Owner);
return;
}
AimData.SetDataByNoneCurve(Owner);
}
void ACWeapon::End_Aim()
{
CheckFalse(bInAim);
bInAim = false;
if (!!AimCurve)
{
Timeline->ReverseFromEnd();
BaseData.SetData(Owner);
return;
}
BaseData.SetDataByNoneCurve(Owner);
}
void ACWeapon::OnAiming(float Output)
{
UCameraComponent* camera = CHelpers::GetComponent<UCameraComponent>(Owner);
camera->FieldOfView = FMath::Lerp(AimData.FieldOfView, BaseData.FieldOfView, Output);
}
bool ACWeapon::CanFire()
{
bool b = false;
b |= bEquipping;
b |= bReload;
b |= bFiring; //발사하고 있는지
return !b;
}
void ACWeapon::Begin_Fire()
{
bFiring = true;
OnFiring();
}
void ACWeapon::End_Fire()
{
CheckFalse(bFiring);
bFiring = false;
}
void ACWeapon::OnFiring()
{
UCameraComponent* camera = CHelpers::GetComponent<UCameraComponent>(Owner);
FVector direction = camera->GetForwardVector();
FTransform transform = camera->GetComponentToWorld();
FVector start = transform.GetLocation() + direction; //눈알 안보다 앞쪽으로 설정하기 때문에 쏘는 방향쪽으로 조금 더해준다.
// Cone 모양의 탄착군 형성. 나갈방향(direction). 몇 도로 만들것인가(RecoilAngle). CWeapon_AR4에서 RecoilAngle의 Default값을 설정하였다.
direction = UKismetMathLibrary::RandomUnitVectorInConeInDegrees(direction, RecoilAngle);
FVector end = transform.GetLocation() + direction * HitDistance;
//DrawDebugLine(GetWorld(), start, end, FColor::Red, true, 5);
TArray<AActor*> ignores;
FHitResult hitResult;
UKismetSystemLibrary::LineTraceSingle(GetWorld(), start, end, ETraceTypeQuery::TraceTypeQuery1, false, ignores, EDrawDebugTrace::None, hitResult,true);
if(hitResult.bBlockingHit) //발사한 LineTrace가 Object에 맞아 Block 충돌이 되었다면
{
if (!!HitDecal)
{
FRotator rotator = hitResult.ImpactNormal.Rotation(); //HitDecal이 붙을 방향(rotator) = 충돌된 곳의 Normal 방향
UDecalComponent* decal = UGameplayStatics::SpawnDecalAtLocation(GetWorld(), HitDecal, FVector(5), hitResult.Location, rotator, 10);
decal->SetFadeScreenSize(0);
}
if(!!HitParticle)
{
//hit된 지점으로부터 최초로 쏜 지점(=나)을 향하도록 설정.
FRotator rotator = UKismetMathLibrary::FindLookAtRotation(hitResult.Location, hitResult.TraceStart);
//Particle이 충돌위치에서 쏜 위치를 향햐도록 나옴.
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), HitParticle, hitResult.Location, rotator);
}
}
if (!!FlashParticle) //Muzzle에서 나오는 FlashParticle
UGameplayStatics::SpawnEmitterAttached(FlashParticle, Mesh, "Muzzle", FVector::ZeroVector, FRotator::ZeroRotator, EAttachLocation::KeepRelativeOffset);
if (!!EjectParticle) //Eject에서 나오는 EjectParticle. 탄피.
UGameplayStatics::SpawnEmitterAttached(EjectParticle, Mesh, "Eject", FVector::ZeroVector, FRotator::ZeroRotator, EAttachLocation::KeepRelativeOffset);
//muzzleLocation 변수를 소켓으로 만든 muzzle 위치로 잡아준다.
FVector muzzleLocation = Mesh->GetSocketLocation("Muzzle");
if (!!FireSound)
UGameplayStatics::SpawnSoundAtLocation(GetWorld(), FireSound, muzzleLocation);
if(!!CameraShakeClass)
{
//PlayerCameraManager는 PlayerController에 있으므로 PlayerController를 가져온다.
APlayerController* controller = Owner->GetController<APlayerController>();
if (!!controller)
controller->PlayerCameraManager->StartCameraShake(CameraShakeClass);
}
}
Cone 만드는 원리 이해 - 구면 좌표계와 유사하다.
FVector FMath::VRandCone(FVector const& Dir, float ConeHalfAngleRad)
{
if (ConeHalfAngleRad > 0.f)
{
float const RandU = FMath::FRand();
float const RandV = FMath::FRand();
// Get spherical coords that have an even distribution over the unit sphere
// Method described at http://mathworld.wolfram.com/SpherePointPicking.html
float Theta = 2.f * PI * RandU;
float Phi = FMath::Acos((2.f * RandV) - 1.f);
// restrict phi to [0, ConeHalfAngleRad]
// this gives an even distribution of points on the surface of the cone
// centered at the origin, pointing upward (z), with the desired angle
Phi = FMath::Fmod(Phi, ConeHalfAngleRad);
// get axes we need to rotate around
FMatrix const DirMat = FRotationMatrix(Dir.Rotation());
// note the axis translation, since we want the variation to be around X
FVector const DirZ = DirMat.GetScaledAxis( EAxis::X );
FVector const DirY = DirMat.GetScaledAxis( EAxis::Y );
FVector Result = Dir.RotateAngleAxis(Phi * 180.f / PI, DirY);
Result = Result.RotateAngleAxis(Theta * 180.f / PI, DirZ);
// ensure it's a unit vector (might not have been passed in that way)
Result = Result.GetSafeNormal();
return Result;
}
else
{
return Dir.GetSafeNormal();
}
}
CWeapon_AR4
CWeapon_AR4.h
#pragma once
#include "CoreMinimal.h"
#include "Weapons/CWeapon.h"
#include "CWeapon_AR4.generated.h"
UCLASS()
class U2212_04_API ACWeapon_AR4 : public ACWeapon
{
GENERATED_BODY()
public:
ACWeapon_AR4();
};
헤더는 변경사항 없음.
CWeapon_AR4.cpp
#include "Weapons/CWeapon_AR4.h"
#include "Global.h"
#include "Components/SkeletalMeshComponent.h"
#include "Animation/AnimMontage.h"
#include "Camera/CameraShake.h"
ACWeapon_AR4::ACWeapon_AR4()
{
USkeletalMesh* mesh;
CHelpers::GetAsset<USkeletalMesh>(&mesh, "SkeletalMesh'/Game/FPS_Weapon_Bundle/Weapons/Meshes/AR4/SK_AR4.SK_AR4'");
Mesh->SetSkeletalMesh(mesh);
//Equip
{
HolsterSocketName = "Rifle_AR4_Holster";
CHelpers::GetAsset<UAnimMontage>(&EquipMontage, "AnimMontage'/Game/Character/Animations/Rifle_Equip_Montage.Rifle_Equip_Montage'");
EquipMontage_PlayRate = 2.0f;
RightHandSocketName = "Rifle_AR4_RightHand";
LeftHandLocation = FVector(-32.5f, 15.5f, 7);
}
//Aim
{
BaseData.TargetArmLength = 200;
BaseData.SocketOffset = FVector(0, 50, 15);
BaseData.FieldOfView = 90;
AimData.TargetArmLength = 80;
AimData.SocketOffset = FVector(0, 55, 10);
AimData.FieldOfView = 65;
}
//Aim
{
RecoilAngle = 0.75f;
CHelpers::GetClass<UMatineeCameraShake>(&CameraShakeClass, "Blueprint'/Game/Weapons/BP_CameraShake_AR4.BP_CameraShake_AR4_C'");
}
}
CWeaponComponent
WeaponComponent.h
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "CWeaponComponent.generated.h"
//BlueprintType을 써서 BP에서 공개되게 한다. 추후에 Animation BP에서 다루어야 한다. 추후에 switch로 Weapon Blending을 할 것이다.
UENUM(BlueprintType)
enum class EWeaponType : uint8
{
AR4, AK47, Pistol, Max,
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FWeaponTypeChanged, EWeaponType, InPrevType, EWeaponType, InNewType);
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class U2212_04_API UCWeaponComponent : public UActorComponent
{
GENERATED_BODY()
private:
UPROPERTY(EditAnywhere, Category = "Settings")
TArray<TSubclassOf<class ACWeapon>> WeaponClasses; //Spawn시킬 때 class 타입을 TSubclassOf로 명시해준다. TArray는 그것에 대한 배열이다.
UPROPERTY(EditAnywhere, Category = "Settings")
TSubclassOf<class UCUserWidget_HUD> HUDClass;
public:
FORCEINLINE bool IsUnarmedMode() { return Type == EWeaponType::Max; }//Max는 아무것도 선택안한 상황.
FORCEINLINE bool IsAR4Mode() { return Type == EWeaponType::AR4; }
FORCEINLINE bool IsAK47Mode() { return Type == EWeaponType::AK47; }
FORCEINLINE bool IsPistolMode() { return Type == EWeaponType::Pistol; }
public:
// Sets default values for this component's properties
UCWeaponComponent();
protected:
// Called when the game starts
virtual void BeginPlay() override;
public:
// Called every frame
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
private:
class ACWeapon* GetCurrWeapon();//현재 무기를 리턴
public:
void SetUnarmedMode();
void SetAR4Mode();
//void SetAK47Mode();
//void SetPistolMode();
private:
void SetMode(EWeaponType InType);
void ChangeType(EWeaponType InType);
public:
void Begin_Equip();
void End_Equip();
public:
void Begin_Aim();
void End_Aim();
public:
void Begin_Fire();
void End_Fire();
public:
bool IsInAim();
FVector GetLeftHandLocation();
public:
FWeaponTypeChanged OnWeaponTypeChanged;
private:
EWeaponType Type = EWeaponType::Max; //Max는 아무것도 선택안한 상황.
private:
class ACPlayer* Owner; //플레이어 변수
TArray<class ACWeapon*> Weapons; //무기 배열 변수
class UCUserWidget_HUD* HUD;
};
WeaponComponent.cpp
#include "Weapons/CWeaponComponent.h"
#include "Global.h"
#include "CWeapon.h"
#include "Character/CPlayer.h"
#include "Widgets/CUserWidget_HUD.h"
UCWeaponComponent::UCWeaponComponent()
{
PrimaryComponentTick.bCanEverTick = true;
CHelpers::GetClass<UCUserWidget_HUD>(&HUDClass, "WidgetBlueprint'/Game/Widgets/WB_HUB.WB_HUB_C'");
}
void UCWeaponComponent::BeginPlay()
{
Super::BeginPlay();
Owner = Cast<ACPlayer>(GetOwner());
CheckNull(Owner);
FActorSpawnParameters params;
params.Owner = Owner;
params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
//받은 타입을 Spawn 시킨다.
for(TSubclassOf<ACWeapon> weaponClass : WeaponClasses)
{
if(!!weaponClass)
{
ACWeapon* weapon = Owner->GetWorld()->SpawnActor<ACWeapon>(weaponClass, params);//weaponClass 타입으로 Spawn시킨다.
Weapons.Add(weapon);
}
}
if(!!HUDClass)
{
HUD = CreateWidget<UCUserWidget_HUD, APlayerController>(Owner->GetController<APlayerController>(), HUDClass);
HUD->AddToViewport(); //Viewport에 추가
HUD->SetVisibility(ESlateVisibility::Hidden);//숨김
}
}
// Called every frame
void UCWeaponComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
}
ACWeapon* UCWeaponComponent::GetCurrWeapon()
{
CheckTrueResult(IsUnarmedMode(), nullptr);//IsUnarmedMode가 true면 무기 리턴해줄수 없으므로 nullptr로 리턴
return Weapons[(int32)Type]; //BP의 to int //현재 선택된 Weapon을 리턴해준다.
}
void UCWeaponComponent::SetUnarmedMode()
{
CheckFalse(GetCurrWeapon()->CanUnequip());//현재 무기가 해제될 수 있는지 체크
GetCurrWeapon()->Unequip(); //무기 해제
ChangeType(EWeaponType::Max);//현재 무기를 Max로 바꾸어줌(Max는 아무것도 선택안한 상태)
HUD->SetVisibility(ESlateVisibility::Hidden);//무기가 없을때 화면에서 숨겨준다.
}
void UCWeaponComponent::SetAR4Mode()
{
SetMode(EWeaponType::AR4);
}
void UCWeaponComponent::SetMode(EWeaponType InType)
{
if(Type == InType) //현재 무기와 장착하려는 무기가 같은 경우
{
SetUnarmedMode(); //무기 장착 해제
return; //무기 장착 해제 후 리턴
}
else if (IsUnarmedMode() == false)
{
CheckFalse(GetCurrWeapon()->CanUnequip());//현재 무기가 해제될 수 있는지 체크
GetCurrWeapon()->Unequip(); //무기 해제
}
CheckNull(Weapons[(int32)InType]); //장착될 무기가 있는지 체크
CheckFalse(Weapons[(int32)InType]->CanEquip());//장착될 무기가 장착될 수 있는 상황인지 체크
Weapons[(int32)InType]->Equip(); // 무기 장착
ChangeType(InType); //무기 변경 알려줌
if (!!HUD)
HUD->SetVisibility(ESlateVisibility::Visible);//화면에 보이도록 켜줌.
}
void UCWeaponComponent::ChangeType(EWeaponType InType)
{
EWeaponType type = Type;
Type = InType; //기존의 무기 타입에서 바뀐 무기 타입을 넣어줌.
if (OnWeaponTypeChanged.IsBound())
OnWeaponTypeChanged.Broadcast(type, InType);//기존 무기 타입, 바뀐 무기 타입
}
void UCWeaponComponent::Begin_Equip()
{
CheckNull(GetCurrWeapon());
GetCurrWeapon()->Begin_Equip();
}
void UCWeaponComponent::End_Equip()
{
CheckNull(GetCurrWeapon());
GetCurrWeapon()->End_Equip();
}
void UCWeaponComponent::Begin_Aim()
{
CheckNull(GetCurrWeapon());
GetCurrWeapon()->Begin_Aim();
}
void UCWeaponComponent::End_Aim()
{
CheckNull(GetCurrWeapon());
GetCurrWeapon()->End_Aim();
}
void UCWeaponComponent::Begin_Fire()
{
CheckNull(GetCurrWeapon());
CheckFalse(GetCurrWeapon()->CanFire());
GetCurrWeapon()->Begin_Fire();
}
void UCWeaponComponent::End_Fire()
{
CheckNull(GetCurrWeapon());
GetCurrWeapon()->End_Fire();
}
bool UCWeaponComponent::IsInAim()
{
CheckNullResult(GetCurrWeapon(), false);
return GetCurrWeapon()->IsInAim();
}
FVector UCWeaponComponent::GetLeftHandLocation()
{
CheckNullResult(GetCurrWeapon(), FVector::ZeroVector);
return GetCurrWeapon()->GetLeftHandLocation();
}
Widget
CUserWidget_HUD 생성
CUserWidget_HUD.h
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "CUserWidget_HUD.generated.h"
UCLASS()
class U2212_04_API UCUserWidget_HUD : public UUserWidget
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintImplementableEvent)
void OnAutoFire();
UFUNCTION(BlueprintImplementableEvent)
void OffAutoFire();
};
Event로 쓰일 함수 2개 생성
- void OnAutoFire();
- void OffAutoFire();
CUserWidget_HUD.cpp
#include "Widgets/CUserWidget_HUD.h"
WB_HUD 생성
유저 인터페이스 - 위젯 블루프린트 - WB_HUD 생성
Designer
HUD로 보여질 텍스트 만들기
- AutoFire
- AR4
- 999/999
Event Graph
Construct 이벤트는 UI Begin Play에 호출되는 함수이다.
CUserWidget_HUD에서 만든 함수를 불러온다.
- OnAutoFire
- OffAutoFire
변수 생성
- LinearColor DefualtColor
실행화면
ABP: Animation Blueprint
AN: Blueprint Class AnimNotify
ANS: Blueprint Class - AnimNotifyState
AO: Aim Offset
BP: Blueprint Class
BS: Blend Space
BF: Blueprint Function Library
CS: Matinee Camera Shake
E: Enum 열거형
DT: Data Table
F: 구조체
I: Blueprint Interface
WB: Widget Bluepprint
UE2212_04
Level - LEVEL | |||
Character | Animation | Rifle | |
Unarmed | |||
Rifle_Equip_Montage | |||
BlendSpaces | AO_Rifle_Aim AO_Rifle_Idle BS_Rifle BS_Rifle_Aim BS_Unarme |
||
Materials |
MaterialLayers |
||
M_UE4Man_Body M_UE4Man_ChestLogo |
|||
Mesh | SK_Mannequin SK_MAnnequin_PhysicsAsset Skel_Mannequin |
||
Montages |
|||
Textures | UE4_LOGO_CARD UE4_Mannequin__normals UE4_Mannequin_MAT_MASKA UE4Man_Logo_N |
||
Weapons | Elven Bow | ||
Greate Hammer | |||
Sword |
|||
Effects |
Materials | ||
Meshes | |||
Texture | |||
P_Eject_bullet P_Impact_Default P_Muzzleflash |
|||
Environment |
Materials | Textures | |
Meshes | |||
Obstacle | |||
FPS_Weapon_Bundle |
Weapons |
Material | |
Meshes | |||
Textures | |||
Materials | Texture | ||
M_Cursor M_Cursor_Inst M_Decal M_Mesh M_White_Inst M_Red_Inst M_Green_Inst M_Blue_Inst M_UE4Man_Body_Inst M_UE4Man_ChesLogo_Inst |
|||
Meshes | Cone_2 Cube_2 Cylinder_2 Sphere_2 |
||
Player | ABP_Player BP_Player |
||
Sounds | |||
Textures | Crosshair | ||
Weapons |
BP_CWeapon_AR4 | ||
'⭐ Unreal Engine > UE FPS TPS' 카테고리의 다른 글
[UE] 총알 & 재장전 구현. Bullet, Magazine, Reload (0) | 2023.03.27 |
---|---|
[UE] 총알 연사, HUD & CrossHair (0) | 2023.03.23 |
[UE] Hand IK, AnimInstance, Fire (0) | 2023.03.21 |
[UE] TPS Weapon, AnimNotify, Aim (0) | 2023.03.17 |
[UE] TPS Weapon - Weapon Framework 짜기 (0) | 2023.03.16 |
댓글
이 글 공유하기
다른 글
-
[UE] 총알 & 재장전 구현. Bullet, Magazine, Reload
[UE] 총알 & 재장전 구현. Bullet, Magazine, Reload
2023.03.27 -
[UE] 총알 연사, HUD & CrossHair
[UE] 총알 연사, HUD & CrossHair
2023.03.23 -
[UE] Hand IK, AnimInstance, Fire
[UE] Hand IK, AnimInstance, Fire
2023.03.21 -
[UE] TPS Weapon, AnimNotify, Aim
[UE] TPS Weapon, AnimNotify, Aim
2023.03.17