필요한 개념
<FireMontage>
총을 쏘는 동작 추가(따로 총 쏘는 동작이 있어야 함, 탕탕 느낌)
몽타주 만들어서 Upperbody만 주고 쓰면 됨
Firing() 할때 플레이 시켜줌
!! CameraShake하면 Fire 몽타주(총 쏘는 동작 몽타주) 굳이 따로 줄필요 없다.

<CameraShake>
애니메이션 넣어도 밑밑해 보임
-> CamShake를 넣는다.(CamraShake BP(UCameraShake를 상속 받는)로 만들면 됨, 굳이 C로 만들 필요는 없다.)
즉, CameraShake로 부터 상속 받는 BP 클래스를 만들고 cpp에서 해당 클래스 타입을 가져와 카메라 쉐이크를 플레이하도록 한다.
Tip)
BP 클래스와 Cpp의 통신이 이루어져야 하는 클래스가 아니라면 굳이 Cpp 클래스 기반으로 BP를 제작할 필요가 없다.
CameShake 열어보면

Single Instance : CameraShake을 여러 번 사용했을 때 ON 인 경우 하나, OFF이면 겹쳐 진동이 재생됩니다.
Oscillation Duration(진동 지속 시간) : 0.1
Oscillation Blend In Time(카메라 흔들리기 까지 걸리는 시간) : 0
Oscillation Blend Out Time(카메라 흔들리고 다시 돌아가는 시간) : 0
FOV Oscillation(화각에 의한 진동)
Rot Oscillation(Rotation Oscillation, 회전에 의한 진동)
Loc Oscillation(Location Oscillation, 이동에 의한 진동)
X
Amplitude(진폭)
Frequency(빈도)
Initial Offset(초기 오프셋)
Waveform(파형)
Y(좌, 우는 Y축)
Amplitude(진폭) : 3
Frequency(빈도) : 3
Initial Offset(초기 오프셋)
Waveform(파형)
Z(위, 아래 수직)
Amplitude(진폭) : 2
Frequency(빈도) : 2
Initial Offset(초기 오프셋)
Waveform(파형)
2번째 인자가 Yaw(좌 ~ 우), 3번째 인자는 Pitch(위 ~ 아래)이다.
우리가 RandomUintVectorInElipticalConeInDegrees(OutDirection, 0.2f, 0.3f)를 주었었다.
이 값과 CameShake값을 일치시킴(좀 더 자연스러운 반동이 만들어짐)
CameShake는 Player일때만 실행시킴, 적이나 이런애들일때는 안씀(그 구조를 생각한다면 Player클래스에만 구현해주면 된다.)
* CameraShake 할시 참고
CameraManager는 PlayerController에 있음
PlayerController를 통해서 CameraShake를 수행시킴
// GetController는 <>template이다.
GetController<APlayerController>()->PlayerCameraManager->PlayCameraShake(CameraShakeClass);
그래도 먼가 허전!
<Fire Effect>
Particle을 추가해준다.(Fire Effect) -> EjectBullet(탄 떨어지는 거), MuzzleFlash
2개다 같은 방법으로 CRifle에서 Fire()할때 재생시켜주면 된다.
(단, Socket 위치를 잡아서 처리해야함(EjectBullet, MuzzleFlash에 해당))
ex) UGameplayStatics::SpawnEmitterAttached(FlashParticle, Mesh, "MuzzleFlash", FVector::ZeroVector, FRotator::ZeroRotator, EAttachLocation::KeepRelativeOffset);


탄피의 사이즈를 조정하려면 스켈레톤 소켓에서 RelativeScale을 줄여주면 된다.
* 적용을 하면 중간중간에 파티클이 끊기는 경우가 있다.
1) 직접적인 이유는 몽타주 때문이다. Fire하는 몽타주가 플레이 되면서 몽타주가 움직이면서 총구 위치가 흔들려서 가끔 안나옴, Camera Shake가 들어가면 굳이 Fire몽타주를 넣을 필요는 없다.
2) 또 다른 이유가 될 수 있는 것은 너무 많이 재생되면 시스템에서 제한을 한다.(뷰포트로 실행은 제한을 하지만 독립형 게임(실제 게임화면)에서는 끊기는게 좀 덜하다.)
그래서 나중에 성능에 따라 제한해주는 것을 고민해 봐야한다.(-> 실무에서는)
<Play Sound>
사운드의 위치는 총구에서 사운드가 들려야 한다.
사운드는 항상 사운드큐를 생성해서 사용함

사운드 큐의 레퍼런스 복사해서 재생시키면 됨
// 총구 위치에서 사운드가 실행됨
FVector muzzleLocation = Mesh->GetSocketLocation("MuzzleFlash");
// 3 : 실행시킬 위치 4 : 방향
UGameplayStatics::PlaySoundAtLocation(GetWorld(), FireSoundCue, muzzleLocation);
* BP 사운드 큐에서 감쇠 사용
Attenuation -> Override Attenuation -> true로 해주면 Attenuation Distance 속성이 켜진다.
감쇠 거리(소리가 닿을 거리) : 소리가 발생하는 곳으로부터 멀어질수록 소리가 작아지는 것(굳이 Attenuation 객체 안만들고 이렇게 쓸 수도 있음)

<총알 궤적 표현>
총구(Muzzle) 위치에서 쏴야 자연스럽게 나옴
보통 플레이어는 하지 않고 적만 구현함(적의 총알 궤적은 보이게 하고 플레이어는 하지 않음)
Bullet 클래스를 만들어서 플레이어에게는 주지 않고 적에게만 주면 됨
우리는 적이 없으니까 플레이어 궤적으로 표현(나중에 적 만들면 플레이어 궤적 없애고 적만 표현하면 됨)
투사체(Projectile) 이용 -> ActorComponent
// CHelpers.h
// CreateComponent는 USceneComponent 생성이므로 같은 방식으로 UActorComponent에 대한 생성함수를 만든다.
// Projectile은 UActorComponent이다.(따로 부모컴포넌트 있고 그런게 아님)
// 액터컴포넌트는 부모에 붙는 것들이 없다.
template<typename T>
static void CreateActorComponent(AActor* InActor, T** InComponent, FName InName)
{
*InComponent = InActor->CreateDefaultSubobject<T>(InName);
}
ParticleSystem을 더 향상시킨 -> 나이이가라 이펙트
총알 궤적을 나이아가라로 만들어야 훨씬 이쁨(이거 아니면 유로를 사서써라)
Material에서 총알 Material 만드는데
Constant4Vector -> constant가 붙어서 외부에서 수정안하겠다는 의미(수정하고 싶다면 VectorParameter을 쓰면 됨)

그리고 각 색상 값이 1보다 크면 더 강조되는 색깔이 됨
이미시브에 값을 넣으면 외곽선이 빛나 보이는 효과가 됨
* 살짝 총알이 조준점 아래 맞는거 같다?
충돌 Fire Impact Efffect Particle(벽에 충돌되어서 튕기는 Fire Particle)을 넣어주고 총알 궤적 부딪힐시 제거 처리하면 깔끔해짐
Mesh에 Collision 세팅해서 얘가 어디 충돌이 되었다면 총알 궤적을 사라지게 만듬(따로 충돌체 만들지 않고 처리)
// SetUpdatedComponent() : 지정된 컴포넌트를 Projectile의 물리에 의해 업데이트 되도록 처리
// Projectile이 물리에 의해 움직이면서 Mesh를 갱신함
Projectile->SetUpdatedComponent(Mesh);
// 어딘가 충돌했다면 호출됨
Mesh->OnComponentHit.AddDynamic(this, &ACBullet::OnHit);
void ACBullet::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
// 자기자신 제거
Destroy();
}
<벽 같은데 충돌해서 나는 불꽃 임펙트(Impact)>
얘는 모든 것에 맞아도 임펙트 효과가 되도록 하고, 맞았다면 그 물체의 위치와 그 물체의 NoramlVector 방향으로 Particle을 효과를 실행시킴

이번에는 모빌리티가 무버블이 아닌 화면에 보이는 모든 것을 추적함
start지점 부터 end지점까지 모두 탄흔을 만듬
이번에는 ECollisionChannel::ECC_Visibility(보이는것 다)
if (GetWorld()->LineTraceSingleByChannel(hitResult, start, end, ECollisionChannel::ECC_Visibility, params))
{
// ImpactNormal : 충돌체에 대한 수직 벡터(Normal 벡터), 충돌체에 대한 수직 방향(즉 맞은 애의 NormalVector)
// 이 방향을 가져와야 그 방향에서 파티클을 수행함
// 그림 참고(3-1)
FRotator rotator = hitResult.ImpactNormal.Rotation();
// 3 : 위치 4 : 방향(회전), 5 : 크기
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ImpactParticle, hitResult.Location, rotator, FVector(2));
}
<데칼(맞는 곳에 구멍나도록(탄흔))>

* 데칼은 메털리얼로 불러옴
UPROPERTY(VisibleDefaultsOnly, Category = "Rifle")
class UMaterialInstanceConstant* DecalMaterial;
* Decal을 Spawn시키는 함수
// UGameplayStatics::SpawnDecalAtLocation() 함수로 Spawn함
// 3 : 크기 4 : 위치 5 : 회전 6 : LifeSpan(얼마만큼 오래 있다가(몇 초 뒤에) 사라질지)
UGameplayStatics::SpawnDecalAtLocation(GetWorld(), DecalMaterial, FVector(5), hitResult.Location, rotator, 10.0f);
CPlayer.h 추가된 내용
...
UCLASS()
class UONLINE_03_CPP_API ACPlayer : public ACharacter, public IIRifle
{
GENERATED_BODY()
...
private:
// CameraShake
UPROPERTY(EditDefaultsOnly, Category = "Camera")
TSubclassOf<class UCameraShake> CameraShakeClass;
...
public:
void PlayCameraShake();
};
CPlayer.cpp 추가된 내용
...
ACPlayer::ACPlayer()
{
...
CHelpers::GetClass<UCUserWidget_CrossHair>(&CrossHairClass, "WidgetBlueprint'/Game/Widgets/WB_CrossHair.WB_CrossHair_C'");
CHelpers::GetClass<UCameraShake>(&CameraShakeClass, "Blueprint'/Game/BP_CameraShake.BP_CameraShake_C'");
}
...
void ACPlayer::PlayCameraShake()
{
GetController<APlayerController>()->PlayerCameraManager->PlayCameraShake(CameraShakeClass);
}
CRifle.h 추가된 내용
...
UCLASS()
class UONLINE_03_CPP_API ACRifle : public AActor
{
GENERATED_BODY()
private:
...
UPROPERTY(VisibleDefaultsOnly, Category = "Rifle")
class UAnimMontage* FireMontage;
// MuzzleFlash
UPROPERTY(VisibleDefaultsOnly, Category = "Rifle")
class UParticleSystem* FlashParticle;
// Eject Bullet(탄 떨어지는 효과)
UPROPERTY(VisibleDefaultsOnly, Category = "Rifle")
class UParticleSystem* EjectParticle;
// Fire Impact Efffect Particle(벽에 충돌되어서 튕기는 Fire Particle)
UPROPERTY(VisibleDefaultsOnly, Category = "Rifle")
class UParticleSystem* ImpactParticle;
// 사운드 큐
UPROPERTY(VisibleDefaultsOnly, Category = "Rifle")
class USoundCue* FireSoundCue;
// 클래스니까 TSubclassOf로 한정시킴(리플렉션)
UPROPERTY(VisibleDefaultsOnly, Category = "Rifle")
TSubclassOf<class ACBullet> BulletClass;
UPROPERTY(VisibleDefaultsOnly, Category = "Rifle")
class UMaterialInstanceConstant* DecalMaterial;
protected:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;
};
CRifle.cpp 추가된 내용
...
#include "Particles/ParticleSystem.h"
#include "Sound/SoundCue.h"
#include "Materials/MaterialInstanceConstant.h"
ACRifle::ACRifle()
{
...
CHelpers::GetAsset<UAnimMontage>(&FireMontage, "AnimMontage'/Game/Character/Montages/Rifle_Fire_Montage.Rifle_Fire_Montage'");
CHelpers::GetAsset<UParticleSystem>(&FlashParticle, "ParticleSystem'/Game/Particles_Rifle/Particles/VFX_Muzzleflash.VFX_Muzzleflash'");
CHelpers::GetAsset<UParticleSystem>(&EjectParticle, "ParticleSystem'/Game/Particles_Rifle/Particles/VFX_Eject_bullet.VFX_Eject_bullet'");
CHelpers::GetAsset<UParticleSystem>(&ImpactParticle, "ParticleSystem'/Game/Particles_Rifle/Particles/VFX_Impact_Default.VFX_Impact_Default'");
CHelpers::GetAsset<USoundCue>(&FireSoundCue, "SoundCue'/Game/Sounds/S_RifleShoot_Cue.S_RifleShoot_Cue'");
CHelpers::GetClass<ACBullet>(&BulletClass, "Blueprint'/Game/BP_CBullet.BP_CBullet_C'");
CHelpers::GetAsset(&DecalMaterial, "MaterialInstanceConstant'/Game/Materials/M_Decal_Inst.M_Decal_Inst'");
}
// Firing() 함수내에 총을 쏠때 모든 것이 수행됨
void ACRifle::Firing()
{
// 총을 쏠 수 있는 캐릭터 일시 수행해야함
IIRifle* rifle = Cast<IIRifle>(OwnerCharacter);
CheckNull(rifle);
FVector start, end, direction;
rifle->GetLocationAndDirection(start, end, direction);
//OwnerCharacter->PlayAnimMontage(FireMontage);
// player일때만 cameshake 수행
ACPlayer* player = Cast<ACPlayer>(OwnerCharacter);
if (!!player)
player->PlayCameraShake();
// Muzzle Flash Particle 수행
// SpawnEmitterAttached : 얘는 어딘가에 Attach시켜서 수행
// 1: 실행할 particleSystem, 2 : 어느 컴포넌트에 붙일 거냐(총 컴포넌트에 붙임) 3 : 붙여질 소켓 이름 4 : 소켓 위치에서 더 보정할 위치값(Zero는 안하겠다.) 5 : 소켓 회전에서 더 보정할 회전값(Zero는 안하겠다.)
// 6 : KeepRelativeOffset -> 상대 위치로 잡아줌
UGameplayStatics::SpawnEmitterAttached(FlashParticle, Mesh, "MuzzleFlash", FVector::ZeroVector, FRotator::ZeroRotator, EAttachLocation::KeepRelativeOffset);
// Eject Bullet Particle 수행
UGameplayStatics::SpawnEmitterAttached(EjectParticle, Mesh, "EjectBullet", FVector::ZeroVector, FRotator::ZeroRotator, EAttachLocation::KeepRelativeOffset);
// 총구 위치에서 사운드가 실행됨
FVector muzzleLocation = Mesh->GetSocketLocation("MuzzleFlash");
// 3 : 실행시킬 위치 4 : 방향
UGameplayStatics::PlaySoundAtLocation(GetWorld(), FireSoundCue, muzzleLocation);
// 2 : 위치, 3 : 발사방향(회전값으로 주면됨)
if (!!BulletClass)
GetWorld()->SpawnActor<ACBullet>(BulletClass, muzzleLocation, direction.Rotation());
FCollisionQueryParams params;
params.AddIgnoredActor(this);
params.AddIgnoredActor(OwnerCharacter);
FHitResult hitResult;
// 이번에는 모빌리티가 무버블이 아닌 화면에 보이는 모든 것을 추적함
// start지점 부터 end지점까지 모두 탄흔을 만듬
// 이번에는 ECollisionChannel::ECC_Visibility(보이는것 다)
if (GetWorld()->LineTraceSingleByChannel(hitResult, start, end, ECollisionChannel::ECC_Visibility, params))
{
// ImpactNormal : 충돌체에 대한 수직 벡터(Normal 벡터), 충돌체에 대한 수직 방향(즉 맞은 애의 NormalVector)
// 이 방향을 가져와야 그 방향에서 파티클을 수행함
FRotator rotator = hitResult.ImpactNormal.Rotation();
// 3 : 위치 4 : 방향(회전), 5 : 크기
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ImpactParticle, hitResult.Location, rotator, FVector(2));
// 3 : 크기 4 : 위치 5 : 회전 6 : LifeSpan(얼마만큼 오래 있다가(몇 초 뒤에) 사라질지)
UGameplayStatics::SpawnDecalAtLocation(GetWorld(), DecalMaterial, FVector(5), hitResult.Location, rotator, 10.0f);
}
if (GetWorld()->LineTraceSingleByChannel(hitResult, start, end, ECollisionChannel::ECC_WorldDynamic, params))
{
AStaticMeshActor* staticMeshActor = Cast<AStaticMeshActor>(hitResult.GetActor());
if (!!staticMeshActor)
{
UStaticMeshComponent* meshComponent = Cast<UStaticMeshComponent>(staticMeshActor->GetRootComponent());
if (!!meshComponent)
{
if (meshComponent->BodyInstance.bSimulatePhysics)
{
// 힘을 주는 방향을 만듬(Owner에서 쏘는 방향으로 방향벡터가 만들어짐)
direction = staticMeshActor->GetActorLocation() - OwnerCharacter->GetActorLocation();
// 방향벡터니까 정규화 시켜줌(크기를 1로 만듬)
direction.Normalize();
// AddImpulseAtLocation() : 지정된 위치에서 지정된 방향으로 나가는 힘을 주는 함수
// 2번 위치에서 1번 길이의 방향으로 힘을 주겠다라는 의미
meshComponent->AddImpulseAtLocation(direction * meshComponent->GetMass() * 100, OwnerCharacter->GetActorLocation());
return;
}
}//if(!!meshComponent)
}//if(!!staticMeshActor)
}
}
CBullet.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CBullet.generated.h"
UCLASS()
class UONLINE_03_CPP_API ACBullet : public AActor
{
GENERATED_BODY()
private:
// 총알
UPROPERTY(VisibleDefaultsOnly)
class UStaticMeshComponent* Mesh;
// 전방으로 전진하게 해줄
UPROPERTY(VisibleDefaultsOnly)
class UProjectileMovementComponent* Projectile;
public:
ACBullet();
protected:
virtual void BeginPlay() override;
private:
UFUNCTION()
void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);
};
CBullet.cpp
#include "CBullet.h"
#include "Global.h"
#include "Components/StaticMeshComponent.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Materials/MaterialInstanceConstant.h" // 프로그램에서 실시간적으로 색을 바꿀게 아니라서 constnat만 불러옴
ACBullet::ACBullet()
{
// <>안넣어줘도 2번째 인자 T타입이 들어감
CHelpers::CreateComponent(this, &Mesh, "Mesh");
// 얘는 액터 컴포넌트
CHelpers::CreateActorComponent(this, &Projectile, "Projectile");
UStaticMesh* mesh;
// 얘도 1번째 인자로 매칭됨 <>생략 가능
CHelpers::GetAsset(&mesh, "StaticMesh'/Game/Meshes/M_Sphere.M_Sphere'");
Mesh->SetStaticMesh(mesh);
UMaterialInstanceConstant* material;
CHelpers::GetAsset(&material, "MaterialInstanceConstant'/Game/Materials/M_Bullet_Inst.M_Bullet_Inst'");
Mesh->SetMaterial(0, material);
// 총알로 보이도록 크기 조정
Mesh->SetRelativeScale3D(FVector(1.0f, 0.025f, 0.025f));
Projectile->InitialSpeed = 2e+4f; // 20000
Projectile->MaxSpeed = 2e+4f; // 20000
// 중력제거를 위해 0을 줌
Projectile->ProjectileGravityScale = 0.0f;
// SetUpdatedComponent() : 지정된 컴포넌트를 Projectile의 물리에 의해 업데이트 되도록 처리
// Projectile이 물리에 의해 움직이면서 Mesh를 갱신함
Projectile->SetUpdatedComponent(Mesh);
InitialLifeSpan = 3.0f;
}
void ACBullet::BeginPlay()
{
Super::BeginPlay();
// Dynamic Spars Delegate이다.
Mesh->OnComponentHit.AddDynamic(this, &ACBullet::OnHit);
}
void ACBullet::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
// 자기자신 제거
Destroy();
}
CHelpers.h 추가된 내용
...
class UONLINE_03_CPP_API CHelpers
{
public:
...
// CreateComponent는 USceneComponent 생성이므로 같은 방식으로 UActorComponent에 대한 생성함수를 만든다.
// 액터컴포넌트는 부모에 붙는 것들이 없다.
template<typename T>
static void CreateActorComponent(AActor* InActor, T** InComponent, FName InName)
{
*InComponent = InActor->CreateDefaultSubobject<T>(InName);
}
};
결과
'Unreal Engine 4 > C++' 카테고리의 다른 글
| <Unreal C++> 37 - Action RPG (Avoiding(Montages)) (0) | 2022.05.03 |
|---|---|
| <Unreal C++> 33 - Action RPG(Player & Animation Setting & Avoiding(State)) (0) | 2022.05.02 |
| <Unreal C++> 27 - Gun Shooting Mode(Aiming), Fire & CrossHair (0) | 2022.04.25 |
| <Unreal C++> 24 - Gun Shooting Mode(Rifle Equip) (0) | 2022.04.24 |
| <Unreal C++> 22 - SweepTrace (0) | 2022.04.24 |