[UE] FPS, Pistol

Characters | |
CAnimInstance.h .cpp CAnimInstance_Arms.h .cpp CPlayer.h .cpp |
|
Utilities | |
CHelpers.h CLog.h .cpp |
|
Weapons | |
CBullet.h .cpp CMagazine.h .cpp CWeapon.h .cpp CWeapon_AK47.h .cpp CWeapon_AR4.h .cpp CWeapon_Pistol.h .cpp 생성 CWeaponComponent.h .cpp |
|
Widget | |
CUserWidget_CrossHair.h .cpp CUserWidget_HUD.h .cpp |
|
CGameMode.h .cpp Global.h CAnimNotifyState_Equip.h .cpp CAnimNotifyState_Reload.h .cpp |
AK47 - 어깨 견착 수정하기
FirstPerson_Idle - 애디티브 레이어 트랙 추가 (lowerarm_r, hand_r)
FirstPerson_Idle 애니메이션을 0프레임을 제외한 프레임 전부 삭제.
애디티브 레이어 트랙 추가: lowerarm_r, hand_r



lowerarm_r 회전값 주기
- Roll: 27.914
- Pitch: -7.567
- Yaw: 39.082

hand_r 회전값 주기
- Roll: -13.05
- Pitch: 49.932
- Yaw: -12.2
AK47 총기 Camera Shake 수정하기
BP_CameraShake_AK47 생성
BP_CameraShake_A4를 복사하여 BP_CameraShake_AK47 생성

AK47 총기의 반동을 표현할 Camaera Shake를 생성하고 값을 넣어준다.
- Oscillation Duration = 0 이면 값이 적용이 되지 않는다. Camera Shake 효과를 사용하려면 0보다 큰 값이 들어가야 한다.
- Los Oscillation X(앞뒤), Y, Z 값을 적용한다.
Los Oscillation - X의 값을 조금 크게 주어 총기가 앞뒤로 흔들리는 효과를 구현한다.
CWeapon_AK47 - Camera Shake 할당, Arms 위치 조정
CWeapon_AK47.h
#pragma once #include "CoreMinimal.h" #include "Weapons/CWeapon.h" #include "CWeapon_AK47.generated.h" UCLASS() class U2212_04_API ACWeapon_AK47 : public ACWeapon { GENERATED_BODY() private: UPROPERTY(EditDefaultsOnly, Category = "Equip") FName LeftHandSocketName; private: UPROPERTY(VisibleAnywhere) class UStaticMeshComponent* Sight; public: ACWeapon_AK47(); public: void Begin_Equip() override; void End_Equip() override; void Begin_Aim() override; void End_Aim() override; };
변동사항 없음.
CWeapon_AK47.cpp
#include "Weapons/CWeapon_AK47.h" #include "Global.h" #include "CMagazine.h" #include "CWeaponComponent.h" #include "Character/CPlayer.h" #include "Components/StaticMeshComponent.h" #include "Components/SkeletalMeshComponent.h" #include "Animation/AnimMontage.h" #include "Camera/CameraShake.h" #include "Widgets/CUserWidget_CrossHair.h" ACWeapon_AK47::ACWeapon_AK47() { USkeletalMesh* mesh; CHelpers::GetAsset<USkeletalMesh>(&mesh, "SkeletalMesh'/Game/FPS_Weapon_Bundle/Weapons/Meshes/AR4/SK_AR4.SK_AR4'"); Mesh->SetSkeletalMesh(mesh); CHelpers::CreateComponent<UStaticMeshComponent>(this, &Sight, "Sight", Mesh, "DotSight"); UStaticMesh* staticMesh; CHelpers::GetAsset<UStaticMesh>(&staticMesh, "StaticMesh'/Game/FPS_Weapon_Bundle/Weapons/Meshes/Accessories/SM_T4_Sight.SM_T4_Sight'"); Sight->SetStaticMesh(staticMesh); Sight->SetRelativeScale3D(FVector(1, 0.95f, 1)); Sight->SetCollisionProfileName("NoCollision");//AK47와 T4_Sight가 충돌되지 않도록 T4_Sight를 No Collision으로 만들어준다. //AK { LeftHandSocketName = "Rifle_AK47_LeftHand"; } //Equip { HolsterSocketName = "Rifle_AK47_Holster"; CHelpers::GetAsset<UAnimMontage>(&EquipMontage, "AnimMontage'/Game/Character/Animations/Rifle_Equip_AK47_Montage.Rifle_Equip_AK47_Montage'"); EquipMontage_PlayRate = 2.0f; RightHandSocketName = "Rifle_AK47_RightHand"; LeftHandLocation = FVector(-35, 15.5f, 7); } //Aim { BaseData.bEnableCameraLag = true; BaseData.TargetArmLength = 200; BaseData.SocketOffset = FVector(0, 50, 15); BaseData.FieldOfView = 90; AimData.bEnableCameraLag = false; AimData.TargetArmLength = 30; AimData.SocketOffset = FVector(-55, 0, 10); AimData.FieldOfView = 55; } //Fire { RecoilAngle = 0.75f; CHelpers::GetClass<UMatineeCameraShake>(&CameraShakeClass, "Blueprint'/Game/Weapons/BP_CameraShake_AK47.BP_CameraShake_AK47_C'"); AutoFireInterval = 0.1f; //연사 시간간격(=연사 속도) RecoilRate = 0.05f; SpreadSpeed = 2.0f; MaxSpreadAlignment = 2.0f; } //UI { CHelpers::GetClass<UCUserWidget_CrossHair>(&CrossHairClass, "WidgetBlueprint'/Game/Widgets/WB_CrossHair.WB_CrossHair_C'"); } //Magazine { MaxMagazineCount = 30; //최대 탄 수 = 30으로 설정 CHelpers::GetAsset<UAnimMontage>(&ReloadMontage, "AnimMontage'/Game/Character/Animations/Rifle_Reload_Montage.Rifle_Reload_Montage'"); ReloadMontage_PlayRate = 1.5f; MagazineBoneName = "b_gun_mag"; CHelpers::GetClass<ACMagazine>(&MagazineClass, "Blueprint'/Game/Weapons/BP_CMagazine_AK47.BP_CMagazine_AK47_C'"); MagazineSocketName = "Rifle_Magazine"; } } void ACWeapon_AK47::Begin_Equip() { if (LeftHandSocketName.IsValid()) CHelpers::AttachTo(this, Owner->GetMesh(), LeftHandSocketName); //AK47(=this)이 Owner->GetMesh()의 LeftHandSockName으로 붙는다. Owner->GetArms()->SetRelativeTransform(ArmsMeshTransform); //CWeapon에서 만든 FTransform ArmsMeshTransform을 가져다 쓴다. } void ACWeapon_AK47::End_Equip() { Super::Begin_Equip(); //부모 Begin_Equip()이 콜 되어 오른손으로 간다. Super::End_Equip(); //부모 End_Equip()이 콜 되어 마무리된다. } void ACWeapon_AK47::Begin_Aim() { Super::Begin_Aim(); if (!!CrossHair) CrossHair->SetVisibility(ESlateVisibility::Hidden); Owner->GetMesh()->SetVisibility(false); Owner->GetBackpack()->SetVisibility(false); Owner->GetArms()->SetVisibility(true); CHelpers::AttachTo(this, Owner->GetArms(), RightHandSocketName); CHelpers::GetComponent<UCWeaponComponent>(Owner)->OnWeaponAim_Arms_Begin.Broadcast(this); } void ACWeapon_AK47::End_Aim() { Super::End_Aim(); if (!!CrossHair) CrossHair->SetVisibility(ESlateVisibility::Visible); Owner->GetMesh()->SetVisibility(true); Owner->GetBackpack()->SetVisibility(true); Owner->GetArms()->SetVisibility(false); CHelpers::AttachTo(this, Owner->GetMesh(), RightHandSocketName); CHelpers::GetComponent<UCWeaponComponent>(Owner)->OnWeaponAim_Arms_End.Broadcast(); }
위에서 만든 Camaera Shake 효과를 AK47에 적용시킨다.
- CHelper::
Arms 위치 조정
- Begin_Equip()
- Owner->GetArms()->SetRelativeTransform(ArmsMeshTransform)
- 방금 전에 부모 클래스인 CWeapon에서 만든 FTransform ArmsMeshTransform을 가져다 쓴다.
- Owner->GetArms()->SetRelativeTransform(ArmsMeshTransform)
BP_CWeapon_AK47에 Camera Shake 할당

에셋 및 스켈리톤 소켓 추가
Pistol 가져오기

Skel_Mannequin - 소켓 추가

Hand_R 우클릭 - 소켓 생성 - Pistol_RightHand 생성

middle_01_l 우클릭 - 소켓 생성 - Pistol_Magazine 생성
BP_CMagazine_Pistol 생성
BP_CMagazine_AK47 복제하여 BP_CMagazine_Pistol 생성

Mesh Full
- Static Mesh - Static Mesh - Pistol_Mag
- 머터리엘 - 엘리먼트0 - M_WeaponMaster_01_Inst
Mesh Empty
- Static Mesh - Static Mesh - Pistol_Mag_Empty
- 머터리엘 - 엘리먼트0 - M_WeaponMaster_01_Inst
Pistol
CWeapon_Pistol
CWeapon 파생 C++ 생성 - CWeapon_Pistol 생성
CWeapon_Pistol.h
#pragma once #include "CoreMinimal.h" #include "Weapons/CWeapon.h" #include "CWeapon_Pistol.generated.h" UCLASS() class U2212_04_API ACWeapon_Pistol : public ACWeapon { GENERATED_BODY() public: ACWeapon_Pistol(); protected: void BeginPlay() override; public: void Begin_Equip() override; void Begin_Aim() override; void End_Aim() override; };
CWeapon_Pistol.cpp
#include "Weapons/CWeapon_Pistol.h" #include "Global.h" #include "CMagazine.h" #include "CWeaponComponent.h" #include "Character/CPlayer.h" #include "Components/StaticMeshComponent.h" #include "Components/SkeletalMeshComponent.h" #include "Animation/AnimMontage.h" #include "Camera/CameraShake.h" #include "Widgets/CUserWidget_CrossHair.h" ACWeapon_Pistol::ACWeapon_Pistol() { USkeletalMesh* mesh; CHelpers::GetAsset<USkeletalMesh>(&mesh, "SkeletalMesh'/Game/MilitaryWeapSilver/Weapons/Pistols_A.Pistols_A'"); Mesh->SetSkeletalMesh(mesh); //Equip { HolsterSocketName = NAME_None; CHelpers::GetAsset<UAnimMontage>(&EquipMontage, "AnimMontage'/Game/Character/Animations/Rifle_Equip_AK47_Montage.Rifle_Equip_AK47_Montage'"); EquipMontage_PlayRate = 2; RightHandSocketName = "Pistol_RightHand"; LeftHandLocation = FVector(0, 15, 0); } //Aim { BaseData.bEnableCameraLag = true; BaseData.TargetArmLength = 200; BaseData.SocketOffset = FVector(0, 50, 15); BaseData.FieldOfView = 90; AimData.bEnableCameraLag = false; AimData.TargetArmLength = 30; AimData.SocketOffset = FVector(-55, 0, 10); AimData.FieldOfView = 55; } //Fire { RecoilAngle = 1.5f; CHelpers::GetClass<UMatineeCameraShake>(&CameraShakeClass, "Blueprint'/Game/Weapons/BP_CameraShake_AK47.BP_CameraShake_AK47_C'"); AutoFireInterval = 0.3f; RecoilRate = 0.05f; SpreadSpeed = 6; MaxSpreadAlignment = 4; } //UI { CHelpers::GetClass<UCUserWidget_CrossHair>(&CrossHairClass, "WidgetBlueprint'/Game/Widgets/WB_CrossHair.WB_CrossHair_C'"); } //Magazine { MaxMagazineCount = 5; CHelpers::GetAsset<UAnimMontage>(&ReloadMontage, "AnimMontage'/Game/Character/Animations/Rifle_Reload_Montage.Rifle_Reload_Montage'"); ReloadMontage_PlayRate = 1.5f; MagazineBoneName = NAME_None; CHelpers::GetClass<ACMagazine>(&MagazineClass, "Blueprint'/Game/Weapons/BP_CMagazine_Pistol.BP_CMagazine_Pistol_C'"); MagazineSocketName = "Pistol_Magazine"; } //Arms { ArmsMeshTransform.SetLocation(FVector(0, 5.1f, -156.6)); ArmsMeshTransform.SetRotation(FQuat(FRotator(0, -4.8f, 0))); ArmsLeftHandTransform.SetLocation(FVector(0, 11, 0)); ArmsLeftHandTransform.SetRotation(FQuat(FRotator(0, 180, 180))); } } void ACWeapon_Pistol::BeginPlay() { Super::BeginPlay(); Mesh->SetVisibility(false); } void ACWeapon_Pistol::Begin_Equip() { Super::Begin_Equip(); Mesh->SetVisibility(true); Owner->GetArms()->SetRelativeTransform(ArmsMeshTransform); } void ACWeapon_Pistol::Begin_Aim() { Super::Begin_Aim(); if (!!CrossHair) CrossHair->SetVisibility(ESlateVisibility::Hidden); Owner->GetMesh()->SetVisibility(false); Owner->GetBackpack()->SetVisibility(false); Owner->GetArms()->SetVisibility(true); CHelpers::AttachTo(this, Owner->GetArms(), RightHandSocketName); CHelpers::GetComponent<UCWeaponComponent>(Owner)->OnWeaponAim_Arms_Begin.Broadcast(this); } void ACWeapon_Pistol::End_Aim() { Super::End_Aim(); if (!!CrossHair) CrossHair->SetVisibility(ESlateVisibility::Visible); Owner->GetMesh()->SetVisibility(true); Owner->GetBackpack()->SetVisibility(true); Owner->GetArms()->SetVisibility(false); CHelpers::AttachTo(this, Owner->GetMesh(), RightHandSocketName); CHelpers::GetComponent<UCWeaponComponent>(Owner)->OnWeaponAim_Arms_End.Broadcast(); }
HolsterSocketName이 없으므로 NAME_None;으로 넣어준다.
MagazineBoneName이 없으므로 NAME_None;으로 넣어준다.
CWeapon_Pistol 기반 블루프린트 생성 - BP_CWeapon_Pistol 생성
BP_CWeapon_Pistol
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; UPROPERTY(EditAnywhere) bool bEnableCameraLag; 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; UPROPERTY(EditDefaultsOnly, Category = "Fire") float AutoFireInterval; //연사 시간간격 UPROPERTY(EditDefaultsOnly, Category = "Fire") float RecoilRate; //반동률 UPROPERTY(EditDefaultsOnly, Category = "Fire") float SpreadSpeed; //얼마나 빨리 벌려질거냐 UPROPERTY(EditDefaultsOnly, Category = "Fire") float MaxSpreadAlignment; //얼마나 넓게 벌려질거냐 UPROPERTY(EditDefaultsOnly, Category = "Fire") TSubclassOf<class ACBullet> BulletClass; protected: UPROPERTY(EditDefaultsOnly, Category = "UI") TSubclassOf<class UCUserWidget_CrossHair> CrossHairClass; //CrossHair protected: UPROPERTY(EditDefaultsOnly, Category = "Magazine") uint8 MaxMagazineCount; UPROPERTY(EditDefaultsOnly, Category = "Magazine") class UAnimMontage* ReloadMontage; UPROPERTY(EditDefaultsOnly, Category = "Magazine") float ReloadMontage_PlayRate; UPROPERTY(EditDefaultsOnly, Category = "Magazine") FName MagazineBoneName; //탄창이 숨겨졌다 보이게 바뀌는 BoneName UPROPERTY(EditDefaultsOnly, Category = "Magazine") TSubclassOf<class ACMagazine> MagazineClass; UPROPERTY(EditDefaultsOnly, Category = "Magazine") FName MagazineSocketName; protected: UPROPERTY(EditDefaultsOnly, Category = "Arms") FTransform ArmsMeshTransform; UPROPERTY(EditDefaultsOnly, Category = "Arms") FTransform ArmsLeftHandTransform; 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 bool IsAutoFire() { return bAutoFire; } FORCEINLINE FVector GetLeftHandLocation() { return LeftHandLocation; } FORCEINLINE uint8 GetCurrMagazineCount() { return CurrMagazineCount; } //현재 탄 수 FORCEINLINE uint8 GetMaxMagazineCount() { return MaxMagazineCount; } //최대 탄 수 public: ACWeapon(); protected: virtual void BeginPlay() override; public: virtual void Tick(float DeltaTime) override; public: bool CanEquip(); void Equip(); virtual void Begin_Equip(); virtual void End_Equip(); bool CanUnequip(); void Unequip(); public: bool CanAim(); virtual void Begin_Aim(); virtual void End_Aim(); private: UFUNCTION() void OnAiming(float Output); //조준 public: bool CanFire(); void Begin_Fire(); void End_Fire(); private: UFUNCTION() void OnFiring(); //연사 public: void ToggleAutoFire(); //연사 On Off 토글 public: bool CanReload(); void Reload(); void Eject_Magazine(); void Spawn_Magazine(); void Load_Magazine(); void End_Reload(); private: bool bEquipping; bool bInAim; bool bFiring; bool bReload; bool bAutoFire = true; protected: class ACPlayer* Owner; private: FTimerHandle AutoFireHandle; //타이머 핸들 추가 protected: //자식에서 CrossHair 접근하게 하기위해 protected로 설정 class UCUserWidget_CrossHair* CrossHair; private: float CurrSpreadRadius; float LastAddSpreadTime; private: uint8 CurrMagazineCount; //현재 탄 수 private: class ACMagazine* Magazine; };
변수 생성 UPROPERTY(EditDefaultsOnly, Category = "Arms")
- FTransform ArmsMeshTransform
- FTransform ArmsLeftHandTransform
CWeapon.cpp
#include "Weapons/CWeapon.h" #include "Global.h" #include "CBullet.h" #include "CMagazine.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" #include "Widgets/CUserWidget_CrossHair.h" void FWeaponAimData::SetData(ACharacter* InOwner) { USpringArmComponent* springArm = CHelpers::GetComponent<USpringArmComponent>(InOwner); springArm->TargetArmLength = TargetArmLength; springArm->SocketOffset = SocketOffset; springArm->bEnableCameraLag = bEnableCameraLag; } void FWeaponAimData::SetDataByNoneCurve(ACharacter* InOwner) { USpringArmComponent* springArm = CHelpers::GetComponent<USpringArmComponent>(InOwner); springArm->TargetArmLength = TargetArmLength; springArm->SocketOffset = SocketOffset; springArm->bEnableCameraLag = bEnableCameraLag; 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'"); CHelpers::GetClass<ACBullet>(&BulletClass, "Blueprint'/Game/Weapons/BP_CBullet.BP_CBullet_C'"); } 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); } if(!!CrossHairClass) { CrossHair = CreateWidget<UCUserWidget_CrossHair, APlayerController>(Owner->GetController<APlayerController>(), CrossHairClass); CrossHair->AddToViewport(); CrossHair->SetVisibility(ESlateVisibility::Hidden); CrossHair->UpdateSpreadRange(CurrSpreadRadius, MaxSpreadAlignment); } CurrMagazineCount = MaxMagazineCount; //현재 탄 수 = 최대 탄 수로 설정하고 시작. } void ACWeapon::Tick(float DeltaTime) { Super::Tick(DeltaTime); if(LastAddSpreadTime >= 0.0f) //쏘고 있었다면 { if(GetWorld()->GetTimeSeconds() - LastAddSpreadTime >= AutoFireInterval + 0.25f) { CurrSpreadRadius = 0.0f; //CrossHair 현재값 LastAddSpreadTime = 0.0f; //마지막으로 추가한 시간 if (!!CrossHair) CrossHair->UpdateSpreadRange(CurrSpreadRadius, MaxSpreadAlignment); } //if } } 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; if (!!CrossHair) CrossHair->SetVisibility(ESlateVisibility::Visible); } 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); if (!!CrossHair) //무기해제 시 CrossHair가 있으면 CrossHair->SetVisibility(ESlateVisibility::Hidden);//CrossHair 꺼줌 } 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; if(bAutoFire) { GetWorld()->GetTimerManager().SetTimer(AutoFireHandle, this, &ACWeapon::OnFiring, AutoFireInterval, true, 0); return; } OnFiring(); } void ACWeapon::End_Fire() { CheckFalse(bFiring); if(GetWorld()->GetTimerManager().IsTimerActive(AutoFireHandle))//AutoFireHandle이 활성화 있다면 { GetWorld()->GetTimerManager().ClearTimer(AutoFireHandle);//AutoFireHandle 해제 } 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); } Owner->AddControllerPitchInput(-RecoilRate * UKismetMathLibrary::RandomFloatInRange(0.8f, 1.2f)); if (CurrSpreadRadius <= 1.0f) { //쏘는 동안 계속 CrossHair의 크기를 가산한다. CurrSpreadRadius += SpreadSpeed * GetWorld()->GetDeltaSeconds(); if (!!CrossHair) CrossHair->UpdateSpreadRange(CurrSpreadRadius, MaxSpreadAlignment); } LastAddSpreadTime = GetWorld()->GetTimeSeconds(); //마지막 시간을 저장. Tick에서 이 값을 사용한다. if(!!BulletClass) { FVector location = Mesh->GetSocketLocation("Muzzle_Bullet"); FActorSpawnParameters params; params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; //Bullet을 Spawn시킨다. ACBullet* bullet = GetWorld()->SpawnActor<ACBullet>(BulletClass, location, direction.Rotation(), params); if (!!bullet) bullet->Shoot(direction); } if(CurrMagazineCount >= 1) //쏠 때마다 현재 탄 수를 줄여준다. { CurrMagazineCount--; } else { if (CanReload()) Reload(); } } void ACWeapon::ToggleAutoFire() { bAutoFire = !bAutoFire; } bool ACWeapon::CanReload() { bool b = false; b |= bEquipping; b |= bReload; return !b; } void ACWeapon::Reload() { bReload = true; End_Aim(); End_Fire(); if (!!ReloadMontage) //재장전 몽타주가 있다면 Owner->PlayAnimMontage(ReloadMontage, ReloadMontage_PlayRate); //몽타주 애니메이션 플레이 } void ACWeapon::Eject_Magazine() { if (MagazineBoneName.IsValid()) //MagazineBoneName이 있다면. CWeapon_AR4에 MagazineBoneName을 설정했다. Mesh->HideBoneByName(MagazineBoneName, EPhysBodyOp::PBO_None); //MagazineBoneName이하를 숨겨준다. 이 때, Physics는 꺼준다. CheckNull(MagazineClass); FTransform transform = Mesh->GetSocketTransform(MagazineBoneName); ACMagazine* magazine = GetWorld()->SpawnActorDeferred<ACMagazine>(MagazineClass, transform, nullptr, nullptr, ESpawnActorCollisionHandlingMethod::AlwaysSpawn); magazine->SetEject(); magazine->SetLifeSpan(5); magazine->FinishSpawning(transform); } void ACWeapon::Spawn_Magazine() { CheckNull(MagazineClass); FActorSpawnParameters params; params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; Magazine = GetWorld()->SpawnActor<ACMagazine>(MagazineClass, params); CHelpers::AttachTo(Magazine, Owner->GetMesh(), MagazineSocketName); } void ACWeapon::Load_Magazine() { CurrMagazineCount = MaxMagazineCount; if (MagazineBoneName.IsValid()) Mesh->UnHideBoneByName(MagazineBoneName);//재장전 후에 MagazineBoneName이하를 보이게 켜준다. if (!!Magazine) Magazine->Destroy(); } void ACWeapon::End_Reload() { bReload = false; }
변경사항 없음
CWeaponComponent
CWeaponComponent.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); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FWeaponAim_Arms_Begin, class ACWeapon*, InThisWeapon); DECLARE_DYNAMIC_MULTICAST_DELEGATE(FWeaponAim_Arms_End); 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: void ToggleAutoFire(); //연사 토글 public: void Reload(); //재장전 void Eject_Magazine(); void Spawn_Magazine(); void Load_Magazine(); void End_Reload(); private: UFUNCTION() void On_Begin_Aim(class ACWeapon* InThisWeapon); UFUNCTION() void On_Begin_End(); public: FWeaponTypeChanged OnWeaponTypeChanged; FWeaponAim_Arms_Begin OnWeaponAim_Arms_Begin; FWeaponAim_Arms_End OnWeaponAim_Arms_End; private: EWeaponType Type = EWeaponType::Max; //Max는 아무것도 선택안한 상황. private: class ACPlayer* Owner; //플레이어 변수 TArray<class ACWeapon*> Weapons; //무기 배열 변수 class UCUserWidget_HUD* HUD; };
함수 추가
- void SetPistolMode();
CWeaponComponent.cpp
#include "Weapons/CWeaponComponent.h" #include "Global.h" #include "CWeapon.h" #include "Character/CPlayer.h" #include "CWeaponComponent.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);//숨김 } OnWeaponAim_Arms_Begin.AddDynamic(this, &UCWeaponComponent::On_Begin_Aim); OnWeaponAim_Arms_End.AddDynamic(this, &UCWeaponComponent::On_Begin_End); } // Called every frame void UCWeaponComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); if (!!HUD) { if (!!GetCurrWeapon()) //현재 무기가 있다면 GetCurrWeapon()->IsAutoFire() ? HUD->OnAutoFire() : HUD->OffAutoFire(); //연사 3항 연산자 OnOff 스위치 else //현재 무기가 없다면 HUD->OffAutoFire(); //연사 해제 if(!!GetCurrWeapon()) //현재 무기가 있다면 { uint8 currCount = GetCurrWeapon()->GetCurrMagazineCount(); //현재 무기의 현재 탄 수 uint8 maxCount = GetCurrWeapon()->GetMaxMagazineCount(); //현재 무기의 최대 탄 수 HUD->UpdateMagazine(currCount, maxCount); //HUD에 탄 수 업데이트 } } } 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::SetAK47Mode() { SetMode(EWeaponType::AK47); } void UCWeaponComponent::SetPistolMode() { SetMode(EWeaponType::Pistol); } 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(); } void UCWeaponComponent::ToggleAutoFire() { CheckNull(GetCurrWeapon()); GetCurrWeapon()->ToggleAutoFire(); } void UCWeaponComponent::Reload() { CheckNull(GetCurrWeapon()); GetCurrWeapon()->Reload(); } void UCWeaponComponent::Eject_Magazine() { CheckNull(GetCurrWeapon()); GetCurrWeapon()->Eject_Magazine(); } void UCWeaponComponent::Spawn_Magazine() { CheckNull(GetCurrWeapon()); GetCurrWeapon()->Spawn_Magazine(); } void UCWeaponComponent::Load_Magazine() { CheckNull(GetCurrWeapon()); GetCurrWeapon()->Load_Magazine(); } void UCWeaponComponent::End_Reload() { CheckNull(GetCurrWeapon()); GetCurrWeapon()->End_Reload(); } void UCWeaponComponent::On_Begin_Aim(ACWeapon * InThisWeapon) { for (ACWeapon* weapon : Weapons) { if (weapon == InThisWeapon) continue; weapon->SetHidden(true); } } void UCWeaponComponent::On_Begin_End() { for (ACWeapon* weapon : Weapons) weapon->SetHidden(false); }
함수 정의
- void UCWeaponComponent::SetAK47Mode() { SetMode(EWeaponType::AK47); }
BP_CPlayer

Weapon - BP_CWeapon_Pistol 할당
실행화면

'⭐ Unreal Engine > UE FPS TPS' 카테고리의 다른 글
[UE] Pistol (0) | 2023.04.01 |
---|---|
[UE] FPS(일인칭 슈팅 게임) (0) | 2023.03.30 |
[UE] Mixamo, 무기 추가(AK47) (0) | 2023.03.29 |
[UE] 재장전(탄창 생성 및 제거) (0) | 2023.03.28 |
[UE] 총알 & 재장전 구현. Bullet, Magazine, Reload (0) | 2023.03.27 |
댓글
이 글 공유하기
다른 글
-
[UE] Pistol
[UE] Pistol
2023.04.01 -
[UE] FPS(일인칭 슈팅 게임)
[UE] FPS(일인칭 슈팅 게임)
2023.03.30 -
[UE] Mixamo, 무기 추가(AK47)
[UE] Mixamo, 무기 추가(AK47)
2023.03.29언리얼 엔진에서 TPS(Third-Person Shooter) 게임에 무기를 추가하려면 여러 단계를 거쳐야 한다. 3D 모델링 소프트웨어를 사용하여 무기 자산을 생성하거나 마켓플레이스 또는 기타 소스에서 가져올 수 있다. 자산에는 무기의 모델, 애니메이션 및 사운드가 포함되어야 한다. 목차 Characters CAnimInstance.h .cppCPlayer.h .cppUtilities CHelpers.hCLog.h .cppWeapons CBullet.h .cppCMagazine.h .cpp CWeapon.h .cppCWeapon_AK47.h .cppCWeapon_AR4.h .cppCWeaponComponent.h .cppWidget CUserWidget_CrossHair.h .cppCUserW… -
[UE] 재장전(탄창 생성 및 제거)
[UE] 재장전(탄창 생성 및 제거)
2023.03.28언리얼 엔진에서 'SpawnActorDeferred'는 액터 클래스의 인스턴스를 생성할 수 있게 해주지만 실제 생성은 현재 프레임 끝까지 지연시킨다. 이것은 한 번에 많은 액터를 생성하고 싶을 때나 액터 생성이 성능 면에서 비용이 많이 드는 경우에 유용할 수 있다. 목차 Actor Lifecycle Characters CAnimInstance.h .cppCPlayer.h .cppUtilities CHelpers.hCLog.h .cppWeapons CBullet.h .cppCMagazine.h .cpp 생성CWeapon.h .cppCWeapon_AR4.h .cppCWeaponComponent.h .cppWidget CUserWidget_CrossHair.h .cppCUserWidget_HUD.h ….
댓글을 사용할 수 없습니다.