[UE] FPS(일인칭 슈팅 게임)

목차
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 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 |
필요한 에셋 추가하기, 소켓 추가하기
SK_Mannequin_Arms 가져오기

Player Arm으로 사용할 메쉬를 가져온다.
SM_T4_Sight 가져오기


머터리얼 슬롯 - 엘리먼트0 - M_KA47_Pattern 할당
SK_KA47_Skeleton - 소켓 추가

DotSight 소켓 추가
SK_Mannequin_Arms_Skeleton - 소켓 추가

Rifle_AK47_RightHand 추가
Arm
CAnimInstance_Arms 생성
새 C++ 클래스 - AnimInstance - CAnimInstance_Arms 생성
CAnimInstance_Arms.h
#pragma once #include "CoreMinimal.h" #include "Animation/AnimInstance.h" #include "Weapons/CWeaponComponent.h" #include "CAnimInstance_Arms.generated.h" UCLASS() class U2212_04_API UCAnimInstance_Arms : public UAnimInstance { GENERATED_BODY() protected: UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Weapons") EWeaponType WeaponType = EWeaponType::Max; public: void NativeBeginPlay() override; void NativeUpdateAnimation(float DeltaSeconds) override; private: UFUNCTION() void OnWeaponTypeChanged(EWeaponType InPrevType, EWeaponType InNewType); private: class ACPlayer* OwnerCharacter; class UCWeaponComponent* Weapon; };
CAnimInstance_Arms.cpp
#include "Character/CAnimInstance_Arms.h" #include "Global.h" #include "CPlayer.h" void UCAnimInstance_Arms::NativeBeginPlay() { Super::NativeBeginPlay(); OwnerCharacter = Cast<ACPlayer>(TryGetPawnOwner()); CheckNull(OwnerCharacter); Weapon = CHelpers::GetComponent<UCWeaponComponent>(OwnerCharacter); CheckNull(Weapon); Weapon->OnWeaponTypeChanged.AddDynamic(this, &UCAnimInstance_Arms::OnWeaponTypeChanged); } void UCAnimInstance_Arms::NativeUpdateAnimation(float DeltaSeconds) { Super::NativeUpdateAnimation(DeltaSeconds); CheckNull(OwnerCharacter); } void UCAnimInstance_Arms::OnWeaponTypeChanged(EWeaponType InPrevType, EWeaponType InNewType) { WeaponType = InNewType; }
ABP_Player_Arms 생성
애니메이션 - 애니메이션 블루프린트 - 부모클래스: CAnimInstance_Arms, 스켈레톤: Skel_Mannequin_Arms -
ABP_Player_Arms 생성


AnimGraph

CPlayer
CPlayer.h
#pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "CPlayer.generated.h" UCLASS() class U2212_04_API ACPlayer : public ACharacter { GENERATED_BODY() private: UPROPERTY(EditDefaultsOnly, Category = "Camera") FVector2D PitchRange = FVector2D(-40, +40); private: UPROPERTY(VisibleAnywhere) class USpringArmComponent* SpringArm; UPROPERTY(VisibleAnywhere) class UCameraComponent* Camera; UPROPERTY(VisibleAnywhere) class UStaticMeshComponent* Backpack; UPROPERTY(VisibleAnywhere) class USkeletalMeshComponent* Arms; private: UPROPERTY(VisibleAnywhere) class UCWeaponComponent* Weapon; public: FORCEINLINE UStaticMeshComponent* GetBackpack() { return Backpack; } FORCEINLINE USkeletalMeshComponent* GetArms() { return Arms; } public: ACPlayer(); protected: virtual void BeginPlay() override; public: virtual void Tick(float DeltaTime) override; virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; private: void OnMoveForward(float InAxisValue); void OnMoveRight(float InAxisValue); void OnHorizontalLook(float InAxisValue); void OnVerticalLook(float InAxisValue); private: void OnRun(); void OffRun(); };
변수 추가
- class USkeletalMeshComponent* Arms;
인라인 함수 추가
- FORCEINLINE UStaticMeshComponent* GetBackpack() { return Backpack; }
- FORCEINLINE USkeletalMeshComponent* GetArms() { return Arms; }
CPlayer.cpp
#include "CPlayer.h" #include "Global.h" #include "CAnimInstance.h" #include "CAnimInstance_Arms.h" #include "Weapons/CWeaponComponent.h" #include "GameFramework/SpringArmComponent.h" #include "GameFramework/CharacterMovementComponent.h" #include "Camera/CameraComponent.h" #include "Components/CapsuleComponent.h" #include "Components/InputComponent.h" #include "Components/StaticMeshComponent.h" #include "Components/SkeletalMeshComponent.h" #include "Materials/MaterialInstanceDynamic.h" ACPlayer::ACPlayer() { PrimaryActorTick.bCanEverTick = true; CHelpers::CreateComponent<USpringArmComponent>(this, &SpringArm, "SpringArm", GetCapsuleComponent()); CHelpers::CreateComponent<UCameraComponent>(this, &Camera, "Camera", SpringArm); CHelpers::CreateComponent<UStaticMeshComponent>(this, &Backpack, "Backpack", GetMesh(), "Backpack"); CHelpers::CreateComponent<USkeletalMeshComponent>(this, &Arms, "Arms", Camera); CHelpers::CreateActorComponent<UCWeaponComponent>(this, &Weapon, "Weapon"); USkeletalMesh* mesh; CHelpers::GetAsset<USkeletalMesh>(&mesh, "SkeletalMesh'/Game/Character/Mesh/SK_Mannequin.SK_Mannequin'"); GetMesh()->SetSkeletalMesh(mesh); GetMesh()->SetRelativeLocation(FVector(0, 0, -90)); GetMesh()->SetRelativeRotation(FRotator(0, -90, 0)); TSubclassOf<UCAnimInstance> animInstance; CHelpers::GetClass<UCAnimInstance>(&animInstance, "AnimBlueprint'/Game/Player/ABP_Player.ABP_Player_C'"); GetMesh()->SetAnimClass(animInstance); GetCharacterMovement()->MaxWalkSpeed = 400; SpringArm->SetRelativeLocation(FVector(0, 0, 60)); SpringArm->TargetArmLength = 200; SpringArm->bUsePawnControlRotation = true; SpringArm->bEnableCameraLag = true; UStaticMesh* staticMesh; CHelpers::GetAsset<UStaticMesh>(&staticMesh, "StaticMesh'/Game/FPS_Weapon_Bundle/Backpack/Backpack.Backpack'"); Backpack->SetStaticMesh(staticMesh); Backpack->SetCollisionProfileName("NoCollision"); CHelpers::GetAsset<USkeletalMesh>(&mesh, "SkeletalMesh'/Game/Character_Arms/Mesh/SK_Mannequin_Arms.SK_Mannequin_Arms'"); Arms->SetSkeletalMesh(mesh); Arms->SetRelativeLocation(FVector(-14.25f, -5.85f, -156.935f)); Arms->SetRelativeRotation(FRotator(-0.5f, -11.85f, -1.2f)); Arms->SetVisibility(false); //안 보이도록 기본값 세팅 TSubclassOf<UCAnimInstance_Arms> armsAnimInstance; CHelpers::GetClass<UCAnimInstance_Arms>(&armsAnimInstance, "AnimBlueprint'/Game/Player/ABP_Player_Arms.ABP_Player_Arms_C'"); Arms->SetAnimClass(armsAnimInstance); } void ACPlayer::BeginPlay() { Super::BeginPlay(); //Super가 BP의 BeginPlay를 콜한다. GetController<APlayerController>()->PlayerCameraManager->ViewPitchMin = PitchRange.X; //각 제한 걸기 GetController<APlayerController>()->PlayerCameraManager->ViewPitchMax = PitchRange.Y; //각 제한 걸기 } void ACPlayer::Tick(float DeltaTime) { Super::Tick(DeltaTime); } void ACPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); PlayerInputComponent->BindAxis("MoveForward", this, &ACPlayer::OnMoveForward); PlayerInputComponent->BindAxis("MoveRight", this, &ACPlayer::OnMoveRight); PlayerInputComponent->BindAxis("HorizontalLook", this, &ACPlayer::OnHorizontalLook); PlayerInputComponent->BindAxis("VerticalLook", this, &ACPlayer::OnVerticalLook); PlayerInputComponent->BindAction("Run", EInputEvent::IE_Pressed, this, &ACPlayer::OnRun); PlayerInputComponent->BindAction("Run", EInputEvent::IE_Released, this, &ACPlayer::OffRun); PlayerInputComponent->BindAction("AR4", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SetAR4Mode);//Object를 Weapon으로 받는다. PlayerInputComponent->BindAction("AK47", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SetAK47Mode);//Object를 Weapon으로 받는다. PlayerInputComponent->BindAction("Aim", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::Begin_Aim); PlayerInputComponent->BindAction("Aim", EInputEvent::IE_Released, Weapon, &UCWeaponComponent::End_Aim); PlayerInputComponent->BindAction("Fire", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::Begin_Fire); PlayerInputComponent->BindAction("Fire", EInputEvent::IE_Released, Weapon, &UCWeaponComponent::End_Fire); PlayerInputComponent->BindAction("AutoFire", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::ToggleAutoFire); PlayerInputComponent->BindAction("Reload", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::Reload); } void ACPlayer::OnMoveForward(float InAxisValue) { FRotator rotator = FRotator(0, GetControlRotation().Yaw, 0); FVector direction = FQuat(rotator).GetForwardVector(); AddMovementInput(direction, InAxisValue); } void ACPlayer::OnMoveRight(float InAxisValue) { FRotator rotator = FRotator(0, GetControlRotation().Yaw, 0); FVector direction = FQuat(rotator).GetRightVector(); AddMovementInput(direction, InAxisValue); } void ACPlayer::OnHorizontalLook(float InAxisValue) { AddControllerYawInput(InAxisValue); } void ACPlayer::OnVerticalLook(float InAxisValue) { AddControllerPitchInput(InAxisValue); } void ACPlayer::OnRun() { GetCharacterMovement()->MaxWalkSpeed = 600; } void ACPlayer::OffRun() { GetCharacterMovement()->MaxWalkSpeed = 400; }
Arms를 카메라에 붙여 생성한다.
- CHelpers::CreateComponent<USkeletalMeshComponent>(this, &Arms, "Arms", Camera);
Arms 기본값 세팅
- CHelpers::GetAsset<USkeletalMesh>(&mesh, "SkeletalMesh'/Game/Character_Arms의 레퍼런스 경로'");
- Arms->SetSkeletalMesh(mesh);
- Arms->SetRelativeLocation(FVector(-14.25f, -5.85f, -156.935f));
- Arms->SetRelativeRotation(FRotator(-0.5f, -11.85f, -1.2f));
- Arms->SetVisibility(false);
Arms의 AnimInstance 설정
- TSubclassOf<UCAnimInstance_Arms> armsAnimInstance;
- CHelpers::GetClass<UCAnimInstance_Arms>(&armsAnimInstance, "ABP_Player 레퍼런스 경로'");
- Arms->SetAnimClass(armsAnimInstance);
위와 같이 CPlayer에 Arms를 생성하고 정보를 기입하고 컴파일을 실행한다.
BP_CPlayer

Arms - 렌더링 - 테스트를 하기 위해 Visible을 체크하여 Arms가 화면에 보이도록 만들어준다. 테스트가 끝난 후 체크를 해제해준다.


Animation - Anim Class - ABP_Player_Arms_C 할당
메시 - Skeletal Mesh - SK_Mannequin_Arms 할당
머터리얼 - 엘리먼트0 - < M_UE4Man_Body_Inst 할당
Weapon
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; 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; };
자식에서 재정의할 수 있도록 가상함수로 변경한다.
- void Begin_Aim(); →변경 virtual void Begin_Aim();
- void End_Aim(); →변경 virtual void End_Aim();
- CWeapon_AK47에서 Begin_Aim()와 End_Aim()를 재정의한다.
자식에서 접근이 가능하도록 private을 protected로 변경
- private →변경 protected: class UCUserWidget_CrossHair* CrossHair;
CWeapon.cpp
#include "CPlayer.h" #include "Global.h" #include "CAnimInstance.h" #include "CAnimInstance_Arms.h" #include "Weapons/CWeaponComponent.h" #include "GameFramework/SpringArmComponent.h" #include "GameFramework/CharacterMovementComponent.h" #include "Camera/CameraComponent.h" #include "Components/CapsuleComponent.h" #include "Components/InputComponent.h" #include "Components/StaticMeshComponent.h" #include "Components/SkeletalMeshComponent.h" #include "Materials/MaterialInstanceDynamic.h" ACPlayer::ACPlayer() { PrimaryActorTick.bCanEverTick = true; CHelpers::CreateComponent<USpringArmComponent>(this, &SpringArm, "SpringArm", GetCapsuleComponent()); CHelpers::CreateComponent<UCameraComponent>(this, &Camera, "Camera", SpringArm); CHelpers::CreateComponent<UStaticMeshComponent>(this, &Backpack, "Backpack", GetMesh(), "Backpack"); CHelpers::CreateComponent<USkeletalMeshComponent>(this, &Arms, "Arms", Camera); CHelpers::CreateActorComponent<UCWeaponComponent>(this, &Weapon, "Weapon"); USkeletalMesh* mesh; CHelpers::GetAsset<USkeletalMesh>(&mesh, "SkeletalMesh'/Game/Character/Mesh/SK_Mannequin.SK_Mannequin'"); GetMesh()->SetSkeletalMesh(mesh); GetMesh()->SetRelativeLocation(FVector(0, 0, -90)); GetMesh()->SetRelativeRotation(FRotator(0, -90, 0)); TSubclassOf<UCAnimInstance> animInstance; CHelpers::GetClass<UCAnimInstance>(&animInstance, "AnimBlueprint'/Game/Player/ABP_Player.ABP_Player_C'"); GetMesh()->SetAnimClass(animInstance); GetCharacterMovement()->MaxWalkSpeed = 400; SpringArm->SetRelativeLocation(FVector(0, 0, 60)); SpringArm->TargetArmLength = 200; SpringArm->bUsePawnControlRotation = true; SpringArm->bEnableCameraLag = true; UStaticMesh* staticMesh; CHelpers::GetAsset<UStaticMesh>(&staticMesh, "StaticMesh'/Game/FPS_Weapon_Bundle/Backpack/Backpack.Backpack'"); Backpack->SetStaticMesh(staticMesh); Backpack->SetCollisionProfileName("NoCollision"); CHelpers::GetAsset<USkeletalMesh>(&mesh, "SkeletalMesh'/Game/Character_Arms/Mesh/SK_Mannequin_Arms.SK_Mannequin_Arms'"); Arms->SetSkeletalMesh(mesh); Arms->SetRelativeLocation(FVector(-14.25f, -5.85f, -156.935f)); Arms->SetRelativeRotation(FRotator(-0.5f, -11.85f, -1.2f)); Arms->SetVisibility(false); //안 보이도록 기본값 세팅 TSubclassOf<UCAnimInstance_Arms> armsAnimInstance; CHelpers::GetClass<UCAnimInstance_Arms>(&armsAnimInstance, "AnimBlueprint'/Game/Player/ABP_Player_Arms.ABP_Player_Arms_C'"); Arms->SetAnimClass(armsAnimInstance); } void ACPlayer::BeginPlay() { Super::BeginPlay(); //Super가 BP의 BeginPlay를 콜한다. GetController<APlayerController>()->PlayerCameraManager->ViewPitchMin = PitchRange.X; //각 제한 걸기 GetController<APlayerController>()->PlayerCameraManager->ViewPitchMax = PitchRange.Y; //각 제한 걸기 } void ACPlayer::Tick(float DeltaTime) { Super::Tick(DeltaTime); } void ACPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); PlayerInputComponent->BindAxis("MoveForward", this, &ACPlayer::OnMoveForward); PlayerInputComponent->BindAxis("MoveRight", this, &ACPlayer::OnMoveRight); PlayerInputComponent->BindAxis("HorizontalLook", this, &ACPlayer::OnHorizontalLook); PlayerInputComponent->BindAxis("VerticalLook", this, &ACPlayer::OnVerticalLook); PlayerInputComponent->BindAction("Run", EInputEvent::IE_Pressed, this, &ACPlayer::OnRun); PlayerInputComponent->BindAction("Run", EInputEvent::IE_Released, this, &ACPlayer::OffRun); PlayerInputComponent->BindAction("AR4", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SetAR4Mode);//Object를 Weapon으로 받는다. PlayerInputComponent->BindAction("AK47", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SetAK47Mode);//Object를 Weapon으로 받는다. PlayerInputComponent->BindAction("Aim", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::Begin_Aim); PlayerInputComponent->BindAction("Aim", EInputEvent::IE_Released, Weapon, &UCWeaponComponent::End_Aim); PlayerInputComponent->BindAction("Fire", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::Begin_Fire); PlayerInputComponent->BindAction("Fire", EInputEvent::IE_Released, Weapon, &UCWeaponComponent::End_Fire); PlayerInputComponent->BindAction("AutoFire", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::ToggleAutoFire); PlayerInputComponent->BindAction("Reload", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::Reload); } void ACPlayer::OnMoveForward(float InAxisValue) { FRotator rotator = FRotator(0, GetControlRotation().Yaw, 0); FVector direction = FQuat(rotator).GetForwardVector(); AddMovementInput(direction, InAxisValue); } void ACPlayer::OnMoveRight(float InAxisValue) { FRotator rotator = FRotator(0, GetControlRotation().Yaw, 0); FVector direction = FQuat(rotator).GetRightVector(); AddMovementInput(direction, InAxisValue); } void ACPlayer::OnHorizontalLook(float InAxisValue) { AddControllerYawInput(InAxisValue); } void ACPlayer::OnVerticalLook(float InAxisValue) { AddControllerPitchInput(InAxisValue); } void ACPlayer::OnRun() { GetCharacterMovement()->MaxWalkSpeed = 600; } void ACPlayer::OffRun() { GetCharacterMovement()->MaxWalkSpeed = 400; }
변경사항 없음.
CWeapon_AK47
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; };
변수 생성
- class UStaticMeshComponent* Sight;
함수 재정의
- 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_AR4.BP_CameraShake_AR4_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으로 붙는다. } 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(); }
Weapon에서 정의한 Begin_Aim()와 End_Aim() 재정의
- void ACWeapon_AK47::Begin_Aim()
- void ACWeapon_AK47::End_Aim()
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; };
Delegate 추가
- 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);
변수 추가
- FWeaponTypeChanged OnWeaponTypeChanged;
- FWeaponAim_Arms_Begin OnWeaponAim_Arms_Begin;
- FWeaponAim_Arms_End OnWeaponAim_Arms_End;
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::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); }
실행화면

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 Rifle_Reload_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 |
||
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 |
Backpack | backpact M_Backpack |
|
Weapons |
Material | ||
Meshes | |||
Textures | |||
SM_AR4_Mag SM_AR4_Mag_Empty |
|||
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 | S_RifleShoot | ||
Textures | Crosshair | ||
Weapons |
BP_CameraShake_AR4 BP_CBullet BP_CMagazine_AK47 BP_CMagazine_AR4 BP_CWeapon_AK47 BP_CWeapon_AR4 |
||
Widgets | NotoSansKR-Black NotoSansKR-Black_Font WB_CrossHair WB_HUB |
'⭐ Unreal Engine > UE FPS TPS' 카테고리의 다른 글
[UE] Pistol (0) | 2023.04.01 |
---|---|
[UE] FPS, Pistol (0) | 2023.03.31 |
[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, Pistol
[UE] FPS, Pistol
2023.03.31 -
[UE] Mixamo, 무기 추가(AK47)
[UE] Mixamo, 무기 추가(AK47)
2023.03.29 -
[UE] 재장전(탄창 생성 및 제거)
[UE] 재장전(탄창 생성 및 제거)
2023.03.28
댓글을 사용할 수 없습니다.