[UE] 총알 연사, HUD & CrossHair

전반적으로 언리얼 엔진에서 TPS 총알 발사를 구현하려면 블루프린트, 애니메이션 및 물리 시뮬레이션의 조합이 필요하다. 올바른 설정을 통해 플레이어가 게임에 계속 몰입할 수 있는 현실적이고 매력적인 슈팅 경험을 만들 수 있다.
목차
연사(AutoFire) 구현하기
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_CrossHair.h .cpp 생성 CUserWidget_HUD.h .cpp |
|
CGameMode.h .cpp Global.h CAnimNotifyState_Equip.h .cpp |
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; //반동률 protected: UPROPERTY(EditDefaultsOnly, Category = "UI") TSubclassOf<class UCUserWidget_CrossHair> CrossHairClass; //CrossHair 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; } 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(); //연사 public: void ToggleAutoFire(); //연사 On Off 토글 private: bool bEquipping; bool bInAim; bool bFiring; bool bReload; bool bAutoFire = true; private: class ACPlayer* Owner; private: FTimerHandle AutoFireHandle; //타이머 핸들 추가 private: class UCUserWidget_CrossHair* CrossHair; };
변수 생성
- bool bEnableCameraLag;
- float RecoilRate;
- TSubclassOf<class UCUserWidget_CrossHair> CrossHairClass;
- class UCUserWidget_CrossHair* CrossHair;
연사에 필요한 함수 선언.
- FORCEINLINE bool IsAutoFire() { return bAutoFire; }
- void ToggleAutoFire();
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" #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'"); } 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); } } 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; 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)); } void ACWeapon::ToggleAutoFire() { bAutoFire = !bAutoFire; }
평상시 Camera Lag 있도록 설정
- SetData(ACharacter* InOwner) 내에
- springArm->bEnableCameraLag = bEnableCameraLag; 넣어준다.
- CWeapon_AR4 내에도 Camera Lag 설정을 해준다.
새롭게 선언한 함수 정의
- void ACWeapon::ToggleAutoFire() { bAutoFire = !bAutoFire; }
총기반동 구현 (총기반동으로 발사 후 Aim이 위로 조금 올라오는것을 구현한다)
- Owner->AddControllerPitchInput(-RecoilRate * UKismetMathLibrary::RandomFloatInRange(0.8f, 1.2f));
- RecoilRate이 클수록 반동을 크다.
Set Timer 설정방법
- GetWorld 내의 GetTimerManager의 SetTimer를 사용한다.


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" #include "Widgets/CUserWidget_CrossHair.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.bEnableCameraLag = true; BaseData.TargetArmLength = 200; BaseData.SocketOffset = FVector(0, 50, 15); BaseData.FieldOfView = 90; AimData.bEnableCameraLag = false; AimData.TargetArmLength = 80; AimData.SocketOffset = FVector(0, 55, 10); AimData.FieldOfView = 65; } //Fire { RecoilAngle = 0.75f; CHelpers::GetClass<UMatineeCameraShake>(&CameraShakeClass, "Blueprint'/Game/Weapons/BP_CameraShake_AR4.BP_CameraShake_AR4_C'"); AutoFireInterval = 0.15f; //연사 시간간격 RecoilRate = 0.05f; } //UI { CHelpers::GetClass<UCUserWidget_CrossHair>(&CrossHairClass, "WidgetBlueprint'/Game/Widgets/WB_CrossHair.WB_CrossHair_C'"); } }
Fire 관련 변수 기본값 설정
- AutoFireInterval = 0.15f
- RecoilRate = 0.05f
UI를 넣어준다. (CrossHair)
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); 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: FWeaponTypeChanged OnWeaponTypeChanged; private: EWeaponType Type = EWeaponType::Max; //Max는 아무것도 선택안한 상황. private: class ACPlayer* Owner; //플레이어 변수 TArray<class ACWeapon*> Weapons; //무기 배열 변수 class UCUserWidget_HUD* HUD; };
CWeaponComponent.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); if (!!HUD) { if (!!GetCurrWeapon()) //현재 무기가 있다면 GetCurrWeapon()->IsAutoFire() ? HUD->OnAutoFire() : HUD->OffAutoFire(); //연사 3항 연산자 OnOff 스위치 else //현재 무기가 없다면 HUD->OffAutoFire(); //연사 해제 } } 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(); } void UCWeaponComponent::ToggleAutoFire() { CheckNull(GetCurrWeapon()); GetCurrWeapon()->ToggleAutoFire(); }
TickComponent에서 HUD의 연사모드 On Off를 보여주는것을 조정한다.
- void UCWeaponComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
- 현재 무기가 있다면, 화면에 표시되는 연사 On Off를 3항 연산자로 바꾸어준다.
- 현재 무기가 없다면, 화면에 표시되는 연사 Off로 바꾸어준다.
HUD, Cross Hair 넣기
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; private: UPROPERTY(VisibleAnywhere) class UCWeaponComponent* Weapon; 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(); };
변동사항 없음
CPlayer.cpp
#include "CPlayer.h" #include "Global.h" #include "CAnimInstance.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 "Materials/MaterialInstanceDynamic.h" ACPlayer::ACPlayer() { PrimaryActorTick.bCanEverTick = true; CHelpers::CreateComponent<USpringArmComponent>(this, &SpringArm, "SpringArm", GetCapsuleComponent()); CHelpers::CreateComponent<UCameraComponent>(this, &Camera, "Camera", SpringArm); 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; } 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("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); } 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; }
언리얼 - 프로젝트 세팅 - 입력 - 액션매핑에 넣어준 AutoFire 키입력을 CPlayer에 넣어준다.
- PlayerInputComponent->BindAction("AutoFire", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent:: ToggleAutoFire);
이것 이외의 변동사항 없음.
WB_CrossHair 생성
유저 인터페이스 - 위젯 블루프린트 - WB_CrossHair 생성


Designer


Graph
노드 없음.
CUserWidget_CrossHair 생성
C++ 클래스 추가 - UserWidget - CUserWidget_CrossHair 생성


CUserWidget_CrossHair.h
#pragma once #include "CoreMinimal.h" #include "Blueprint/UserWidget.h" #include "CUserWidget_CrossHair.generated.h" UCLASS() class U2212_04_API UCUserWidget_CrossHair : public UUserWidget { GENERATED_BODY() protected: void NativeOnInitialized() override; void NativeTick(const FGeometry& MyGeometry, float InDeltaTime); private: TArray<class UBorder*> Borders; TArray<FVector2D> Alignments; };
CUserWidget_CrossHair.cpp
#include "Widgets/CUserWidget_CrossHair.h" #include "Global.h" #include "Blueprint/WidgetTree.h" #include "Blueprint/WidgetLayoutLibrary.h" #include "Components/CanvasPanel.h" #include "Components/CanvasPanelSlot.h" #include "Components/Border.h" //#define LOG_UCUserWidget_CrossHair 1 void UCUserWidget_CrossHair::NativeOnInitialized() { Super::NativeOnInitialized(); UCanvasPanel* panel = Cast<UCanvasPanel>(WidgetTree->RootWidget);//RootWidget은 CrossHair로 만든 Top,Bottom,Right,Left를 모두 포함한다. CheckNull(panel); TArray<UWidget*> widgets = panel->GetAllChildren(); for (UWidget* widget : widgets) { UBorder* border = Cast<UBorder>(widget); //border를 가져온다. if (border == nullptr) continue; Borders.Add(border); UCanvasPanelSlot* slot = UWidgetLayoutLibrary::SlotAsCanvasSlot(border); Alignments.Add(slot->GetAlignment()); } #if LOG_UCUserWidget_CrossHair for (int32 i = 0; i < Borders.Num(); i++) { UBorder* border = Borders[i]; CLog::Log(border->GetName()); CLog::Log(Alignments[i].ToString()); } #endif } void UCUserWidget_CrossHair::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) { Super::NativeTick(MyGeometry, InDeltaTime); }
Construct가 먼저 실행되고 그 후에 OnInitialize가 실행된다.
실행화면

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 | S_RifleShoot | ||
Textures | Crosshair | ||
Weapons |
BP_CameraShake_AR4 BP_CWeapon_AR4 |
||
Widgets | NotoSansKR-Black NotoSansKR-Black_Font WB_CrossHair WB_HUB |
'⭐ Unreal Engine > UE FPS TPS' 카테고리의 다른 글
[UE] 재장전(탄창 생성 및 제거) (0) | 2023.03.28 |
---|---|
[UE] 총알 & 재장전 구현. Bullet, Magazine, Reload (0) | 2023.03.27 |
[UE] 총알 발사 구현하기 (0) | 2023.03.22 |
[UE] Hand IK, AnimInstance, Fire (0) | 2023.03.21 |
[UE] TPS Weapon, AnimNotify, Aim (0) | 2023.03.17 |
댓글
이 글 공유하기
다른 글
-
[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 …. -
[UE] 총알 & 재장전 구현. Bullet, Magazine, Reload
[UE] 총알 & 재장전 구현. Bullet, Magazine, Reload
2023.03.27목차 Enlarge CrossHair, Bullet, Reload Characters CAnimInstance.h .cppCPlayer.h .cppUtilities CHelpers.hCLog.h .cppWeapons CBullet.h .cpp 생성CWeapon.h .cppCWeapon_AR4.h .cppCWeaponComponent.h .cppWidget CUserWidget_CrossHair.h .cppCUserWidget_HUD.h .cppCGameMode.h .cppGlobal.hCAnimNotifyState_Equip.h .cpp SK_AR4_Skeleton 소켓 생성 - Muzzle_Bullet CPlayer CPlayer.cpp더보기#include "CPlayer.h"#incl… -
[UE] 총알 발사 구현하기
[UE] 총알 발사 구현하기
2023.03.22언리얼 엔진에서 3인칭 슈팅 게임(TPS) 총알 발사를 구현하려면 무기 만들기, 총알 생성, 총알 구성, 충돌 돌처리, 대미지 적용, 시각 효과 추가하기 등의 과정이 필요하다. 언리얼 엔진에서 TPS 총알 발사를 구현하려면 블루프린트, 애니메이션 및 물리 시뮬레이션의 조합이 필요하다. 올바른 설정을 통해 플레이어가 게임에 계속 몰입할 수 있는 현실적이고 매력적인 슈팅 경험을 만들 수 있다. 목차 총알 발사 구현하기 언리얼 엔진에서 3인칭 슈팅 게임(TPS) 총알 발사를 구현하려면 다음과 같은 여러 단계가 필요하다. 무기 만들기: 먼저 무기용 메시와 총알이 생성되는 소켓이 포함된 무기 청사진을 만들어야 한다. 청사진에 발사 애니메이션과 음향 효과도 추가해야 한다.총알 생성: 플레이어가 무기를 발사할… -
[UE] Hand IK, AnimInstance, Fire
[UE] Hand IK, AnimInstance, Fire
2023.03.21Unreal Engine 4의 Hand IK(Inverse Kinematics)는 캐릭터의 가상 손이 사실적인 방식으로 환경 및 개체와 상호 작용할 수 있도록 하는 기능이다. 대상 개체의 위치와 방향을 기반으로 캐릭터 손의 위치와 방향을 계산하여 작동한다. 언리얼 엔진 4에서 Hand IK를 사용하려면 스켈레탈 메시가 있는 캐릭터와 손 애니메이션이 포함된 애니메이션 세트가 있어야 한다. Hand IK 시스템은 애니메이션 데이터를 사용하여 캐릭터 손의 위치와 방향을 실시간으로 계산한다. 목차 Weapon Unreal Engine 4의 Hand IK(Inverse Kinematics)는 캐릭터의 가상 손이 사실적인 방식으로 환경 및 개체와 상호 작용할 수 있도록 하는 기능이다. 대상 개체의 위치와 방향…
댓글을 사용할 수 없습니다.