[UE] 총알 & 재장전 구현. Bullet, Magazine, Reload
목차
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 |
'⭐ Unreal Engine > UE FPS TPS' 카테고리의 다른 글
[UE] Mixamo, 무기 추가(AK47) (0) | 2023.03.29 |
---|---|
[UE] 재장전(탄창 생성 및 제거) (0) | 2023.03.28 |
[UE] 총알 연사, HUD & CrossHair (0) | 2023.03.23 |
[UE] 총알 발사 구현하기 (0) | 2023.03.22 |
[UE] Hand IK, AnimInstance, Fire (0) | 2023.03.21 |
댓글
이 글 공유하기
다른 글
-
[UE] Mixamo, 무기 추가(AK47)
[UE] Mixamo, 무기 추가(AK47)
2023.03.29 -
[UE] 재장전(탄창 생성 및 제거)
[UE] 재장전(탄창 생성 및 제거)
2023.03.28 -
[UE] 총알 연사, HUD & CrossHair
[UE] 총알 연사, HUD & CrossHair
2023.03.23 -
[UE] 총알 발사 구현하기
[UE] 총알 발사 구현하기
2023.03.22