목차

     

     


     

     

    Enlarge CrossHair, Bullet, Reload

     

     
    Characters
      CAnimInstance.h .cpp
    CPlayer.h .cpp
    Utilities
      CHelpers.h
    CLog.h .cpp
    Weapons
      CBullet.h .cpp 생성
    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 

     


     

    SK_AR4_Skeleton

     

    소켓 생성 - Muzzle_Bullet

     

     


     

     

    CPlayer

     

    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);
    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;
    }

    키 입력 추가

    • void ACPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { PlayerInputComponent->BindAction("Reload", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::Reload); }

     

     

    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);
    public:
    void UpdateSpreadRange(float InRadius, float InMaxRadius); //외부로부터 값을 세팅할 함수
    private:
    TArray<class UBorder*> Borders;
    TArray<FVector2D> Alignments;
    private:
    float Radius; //CrossHair 현재값
    float MaxRadius; //CrossHair 최대값
    private:
    enum class EDirection //직렬화x. 내부적으로만 사용
    {
    Top, Bottom, Left, Right, Max,
    };
    };

    변수 생성

    • float Radius;
    • float MaxRadius;

     

    함수 생성

    • void UpdateSpreadRange(float InRadius, float InMaxRadius); //외부로부터 값을 세팅할 함수

     

    Enum 열거체 생성 

    • enum class EDirection //직렬화x. 내부적으로만 사용

     

    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);
    float minimum = 0.0f, maximum = 0.0f;
    for (int32 i = 0; i< (int32)EDirection::Max; i++)
    {
    switch((EDirection)i)
    {
    case EDirection::Top:
    {
    minimum = Alignments[i].Y;
    maximum = Alignments[i].Y + MaxRadius;
    }
    break;
    case EDirection::Bottom:
    {
    minimum = Alignments[i].Y;
    maximum = Alignments[i].Y - MaxRadius;
    }
    break;
    case EDirection::Left:
    {
    minimum = Alignments[i].X;
    maximum = Alignments[i].X + MaxRadius;
    }
    break;
    case EDirection::Right:
    {
    minimum = Alignments[i].X;
    maximum = Alignments[i].X - MaxRadius;
    }
    break;
    } //switch
    float value = FMath::Lerp<float>(minimum, maximum, Radius);//선택값(Radius)을 minimum부터 maximum까지 선형보간
    UCanvasPanelSlot* slot = UWidgetLayoutLibrary::SlotAsCanvasSlot(Borders[i]);
    switch((EDirection)i)
    {
    case EDirection::Top:
    case EDirection::Bottom:
    slot->SetAlignment(FVector2D(Alignments[i].X, value));
    break;
    case EDirection::Left:
    case EDirection::Right:
    slot->SetAlignment(FVector2D(value, Alignments[i].Y));
    break;
    } //switch
    }
    }
    void UCUserWidget_CrossHair::UpdateSpreadRange(float InRadius, float InMaxRadius)
    {
    Radius = InRadius;
    MaxRadius = InMaxRadius;
    }

     

     

     


     

     

     

    CBullet & BP_CBullet 생성

     

    CBullet.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "GameFramework/Actor.h"
    #include "CBullet.generated.h"
    UCLASS()
    class U2212_04_API ACBullet : public AActor
    {
    GENERATED_BODY()
    private:
    UPROPERTY(VisibleAnywhere)
    class UCapsuleComponent* Capsule;
    UPROPERTY(VisibleAnywhere)
    class UStaticMeshComponent* Mesh;
    UPROPERTY(VisibleAnywhere)
    class UProjectileMovementComponent* Projectile; //projectile은 RootComponent가 충돌체여야 한다.
    public:
    ACBullet();
    protected:
    virtual void BeginPlay() override;
    public:
    void Shoot(const FVector& InDirection);
    private:
    UFUNCTION()
    void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);
    };

    변수 생성 UPROPERTY(VisibleAnywhere)

    • class UCapsuleComponent* Capsule
    • class UStaticMeshComponent* Mesh
    • class UProjectileMovementComponent* Projectile
      • projectile은 RootComponent가 충돌체여야 한다.
    • TSubclassOf<class ACBullet> BulletClass

     

    함수 생성

    • void Shoot(const FVector& InDirection)

     

    함수 생성 UFUNCTION()

    • void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);

     

     

     

    CBullet.cpp

    더보기
    #include "Weapons/CBullet.h"
    #include "Global.h"
    #include "Components/StaticMeshComponent.h"
    #include "Components/CapsuleComponent.h"
    #include "GameFramework/ProjectileMovementComponent.h"
    #include "Materials/MaterialInstanceConstant.h"
    ACBullet::ACBullet()
    {
    CHelpers::CreateComponent<UCapsuleComponent>(this, &Capsule, "Capsule");
    CHelpers::CreateComponent<UStaticMeshComponent>(this, &Mesh, "Mesh", Capsule);
    CHelpers::CreateActorComponent<UProjectileMovementComponent>(this, &Projectile, "Projectile");//ProjectileMovementComponent는 ActorComponent이다
    Capsule->SetRelativeRotation(FRotator(90, 0, 0));
    Capsule->SetCapsuleHalfHeight(50);
    Capsule->SetCapsuleRadius(2);
    Capsule->SetCollisionProfileName("BlockAllDynamic"); //Block이어서 Hit연산을 한다.
    UStaticMesh* mesh;
    CHelpers::GetAsset<UStaticMesh>(&mesh, "StaticMesh'/Game/Meshes/Sphere.Sphere'");
    Mesh->SetStaticMesh(mesh);
    Mesh->SetRelativeScale3D(FVector(1, 0.025f, 0.025f));//이쑤시개 모양
    Mesh->SetRelativeRotation(FRotator(90, 0, 0));
    UMaterialInstanceConstant* material;
    CHelpers::GetAsset<UMaterialInstanceConstant>(&material, "MaterialInstanceConstant'/Game/Materials/M_Bullet_Inst.M_Bullet_Inst'");
    Mesh->SetMaterial(0, material);
    Projectile->InitialSpeed = 2e+4f;
    Projectile->MaxSpeed = 2e+4f;
    Projectile->ProjectileGravityScale = 0;
    }
    void ACBullet::BeginPlay()
    {
    Super::BeginPlay();
    Projectile->SetActive(false);
    Capsule->OnComponentHit.AddDynamic(this, &ACBullet::OnHit);
    }
    void ACBullet::Shoot(const FVector& InDirection)
    {
    SetLifeSpan(3);
    Projectile->Velocity = InDirection * Projectile->InitialSpeed; //Projectile(총알) 속도 설정
    Projectile->SetActive(true);
    }
    void ACBullet::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp,
    FVector NormalImpulse, const FHitResult& Hit)
    {
    Destroy(); //Hit되면 Destroy해준다.
    }

    void Shoot에서 Projectile 속도 설정 + 활성화

     

     

     

    BP_CBullet

    Mesh - 머티리얼 - 엘리먼트0 - M_Bullet_Inst로 설정

     

     


     

    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;
    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();
    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 토글
    public:
    bool CanReload();
    void Reload();
    private:
    bool bEquipping;
    bool bInAim;
    bool bFiring;
    bool bReload;
    bool bAutoFire = true;
    private:
    class ACPlayer* Owner;
    private:
    FTimerHandle AutoFireHandle; //타이머 핸들 추가
    private:
    class UCUserWidget_CrossHair* CrossHair;
    private:
    float CurrSpreadRadius;
    float LastAddSpreadTime;
    private:
    uint8 CurrMagazineCount; //현재 탄 수
    };

    변수 생성 UPROPERTY(EditDefaultsOnly, Category = "Fire")

    • float SpreadSpeed
    • float MaxSpreadAlignment
    • CWeapon_AR4에서 SpreadSpeed, MaxSpreadAlignment 기본값을 설정한다.
    • TSubclassOf<class ACBullet> BulletClass

     

    변수 생성 UPROPERTY(EditDefaultsOnly, Category = "Magazine")

    • uint8 MaxMagazineCount
    • class UAnimMontage* ReloadMontage
    • float ReloadMontage_PlayRate

     

    Inline 함수

    • FORCEINLINE uint8 GetCurrMagazineCount() { return CurrMagazineCount; } //현재 탄 수
    • FORCEINLINE uint8 GetMaxMagazineCount() { return MaxMagazineCount; } //최대 탄 수

     

    재장전 관련 함수 생성

    • bool CanReload()
    • void Reload()

     

    변수 생성 private:

    • float CurrSpreadRadius
    • float LastAddSpreadTime
    • uint8 CurrentMagazineCount

     

     

    CWeapon.cpp

    더보기
    #include "Weapons/CWeapon.h"
    #include "Global.h"
    #include "CBullet.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); //몽타주 애니메이션 플레이
    }

     

     


     

     

     

     

    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;
    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;
    }
    }

    Magazine 기본값 추가

    • MaxMagazineCount = 30; //최대 탄 수 = 30으로 설정
    • CHelpers::GetAsset<UAnimMontage>(&ReloadMontage, "Reload 애니메이션 몽타주 레퍼런스 경로 ");
    • ReloadMontage_PlayRate = 1.5f;

     

     


     

    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:
    void Reload(); //재장전
    public:
    FWeaponTypeChanged OnWeaponTypeChanged;
    private:
    EWeaponType Type = EWeaponType::Max; //Max는 아무것도 선택안한 상황.
    private:
    class ACPlayer* Owner; //플레이어 변수
    TArray<class ACWeapon*> Weapons; //무기 배열 변수
    class UCUserWidget_HUD* HUD;
    };

    함수 추가

    • void Reload();

     

     

    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(); //연사 해제
    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::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();
    }

    TickComponent에서 탄 수(=총알)를 매 틱 업데이트한다.

    • if(!!GetCurrWeapon()) {
      uint8 currCount = GetCurrWeapon()->GetCurrMagazineCount(); //현재 무기의 현재 탄 수
      uint8 maxCount = GetCurrWeapon()->GetMaxMagazineCount(); //현재 무기의 최대 탄 수
      HUD->UpdateMagazine(currCount, maxCount); //HUD에 탄 수 업데이트 }

     

    새롭게 추가한 함수 정의

    • void UCWeaponComponent::Reload(); { GetCurrWeapon()->Reload(); }
      • Weapon 내에 정의된 void ACWeapon::Reload();함수를 호출한다. 

     

     

     


     

     

     

     

     

    CUserWidget_HUD

     

    CUserWidget_HUD.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Blueprint/UserWidget.h"
    #include "CUserWidget_HUD.generated.h"
    UCLASS()
    class U2212_04_API UCUserWidget_HUD : public UUserWidget
    {
    GENERATED_BODY()
    public:
    UFUNCTION(BlueprintImplementableEvent)
    void OnAutoFire();
    UFUNCTION(BlueprintImplementableEvent)
    void OffAutoFire();
    public:
    UFUNCTION(BlueprintImplementableEvent)
    void UpdateMagazine(uint8 InCurr, uint8 InMax); //현재 탄 수 업데이트
    };

    현재의 총알 수를 업데이트하는 함수 추가

    • void UpdateMagazine(uint8 InCurr, uint8 InMax);

     


     

     

    M_Bullet & M_Bullet_Inst 생성

     

    머티리얼 - M_Bullet 생성

     

    M_Bullet

     

     

     M_Bullet 우클릭 -  M_Bullet_Inst 생성

     

    M_Bullet_Inst

     

     

     

    Global Scalar Parameter Values

    • Metallic: 금속성. 1=금속성 최대. 0=금속성 없음.
    • Roughness: 표면 거칠기 정도. Roughness가 높을수록 반사가 적어진다.

     

    Global Vector Parameter

    • Emissive: sRGB 사용.
      • 언리얼 Emissive의 경우 1을 넘는 값을 사용할 수 있다. 
      • 언리얼의 경우 1보다 큰 값이면 밖으로 방출한다(=발광한다). 보통 다른 프로그램의 경우 1이 최대값이고 외곽선 안으로만 적용된다.
    • Specular: 정반사광

     

     

     


     

     

     

    WB_HUD

     

    Update Magazine 이벤트 추가

    • 화면에 현재 탄 수/ 최대 탄 수가 표시된다.

     


     

     

    Rifle_Reload_Montage

     

    커브 - LeftHand 할당

     

    변곡점 할당: (0, 0), (0.088, 1), (2.0448, 1), (2.8, 0)

    (0, 0), (2.0448, 1) 두 점에 탄젠트 할당.

     

     


     

     

    실행화면

     

    연사를 계속할수록 CrossHair가 늘어난다.

    총알에 발사함에 따라 탄의 개수가 줄어든다.

    Reload의 수행 시 애니메이션 몽타주에서 지정한 LeftHand 커브가 적용되어 왼쪽 손이 총에 자연스럽게 붙는다.

     

     


     

     

     

     

     

    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
    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