ImpactEffect is no replicated to the client
SWeapon.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "SWeapon.generated.h"
class USkeletalMeshComponent;
class UDamageType;
class UCameraShake;
// Contains info of a single hit scan LineTrace
USTRUCT()
struct FHitScanTrace {
GENERATED_BODY()
public:
UPROPERTY()
FVector_NetQuantize TraceTo;
// FVector_NetQuantizeNormal -- to setup direction unit vector from -1 to 1
UPROPERTY()
TEnumAsByte<EPhysicalSurface> SurfaceType;
};
UCLASS()
class COOPGAME_API ASWeapon : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ASWeapon();
protected:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
USkeletalMeshComponent* MeshComp;
void PlayFireEffects(FVector TraceEndPoint);
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
UParticleSystem* DefaultImpactEffect;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
UParticleSystem* FleshImpactEffect;
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
FName MuzzleSocketName;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
UParticleSystem* TracerEffect;
UFUNCTION(BlueprintCallable, Category = "Weapon")
virtual void Fire();
UFUNCTION(Server, Reliable, WithValidation)
void ServerFire();
UPROPERTY(ReplicatedUsing=OnRep_HitScanTrace)
FHitScanTrace HitScanTrace;
UFUNCTION()
void OnRep_HitScanTrace();
void PlayImpactEffects(EPhysicalSurface SurfaceType, FVector ImpactLocation);
virtual void GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const override;
SWeapon.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "SWeapon.h"
#include "Components/SkeletalMeshComponent.h"
#include "DrawDebugHelpers.h"
#include "Kismet/GameplayStatics.h"
#include "Particles/ParticleSystem.h"
#include "Particles/ParticleSystemComponent.h"
#include "Engine/SkeletalMesh.h"
#include "PhysicalMaterials/PhysicalMaterial.h"
#include "CoopGame.h"
#include "SCharacter.h"
#include "Net/UnrealNetwork.h"
ASWeapon::ASWeapon()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
//PrimaryActorTick.bCanEverTick = true;
MeshComp = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("MeshComp"));
RootComponent = MeshComp;
MuzzleSocketName = "MuzzleSocket";
TracerTargetName = "BeamEnd";
//....
bIsReloading = false;
bAutoReloading = true;
bCanFire = true;
SetReplicates(true);
NetUpdateFrequency = 75.0f;
MinNetUpdateFrequency = 35.0f;
/....
}
void ASWeapon::Fire()
{
if (Role < ROLE_Authority) {
ServerFire();
}
UE_LOG(LogTemp, Warning, TEXT("FIRING!!!!"));
ASCharacter* ActorOwner = Cast<ASCharacter>(GetOwner());
if (!ActorOwner) {
return;
}
if (ActorOwner->GetIsDead()) {
ActorOwner->HanldeDeath();
}
if (CurrentAmmoInClip > 0 && bIsReloading == false) {
FVector EyeLocation;
FRotator EyeRotation;
// Trace the world, from pawn eyes to crosshair location
// this is for FPP(First Person Point)
// default func by the UE4
// created custom from GetActorEyesViewPoint(..) => GetPawnViewLocation() which we override in SCharacter.cpp file
ActorOwner->GetActorEyesViewPoint(EyeLocation, EyeRotation);
FVector ShootDirection = EyeRotation.Vector();
FVector TraceEnd = EyeLocation + (ShootDirection * projectileLengthInCm);
FVector TraceEndPoint = TraceEnd;
UE_LOG(LogTemp, Warning, TEXT("Eye Location %a"), *EyeLocation.ToString()); // returns IDK what some address or so. 0x1312312312-23p
UE_LOG(LogTemp, Warning, TEXT("Eye Rotation %s"), *EyeRotation.ToString());
UE_LOG(LogTemp, Warning, TEXT("EyeRotation.Vector() %s"), *EyeRotation.Vector().ToString());
UE_LOG(LogTemp, Warning, TEXT("TraceEnd %s"), *TraceEnd.ToString());
FCollisionQueryParams CollQearyParams;
CollQearyParams.AddIgnoredActor(ActorOwner); // do not collide with owner
CollQearyParams.AddIgnoredActor(this); // do no collide with SWeapon
CollQearyParams.bTraceComplex = true; // it will trace against each individual triangle of the mesh we hitting. IT GIVES us EXACT result (not just simple collision like CAPSULE or so, but something like head or leg)
CollQearyParams.bReturnPhysicalMaterial = true; // to get the physical material of a mesh!!!
FHitResult Hit;
EPhysicalSurface SurfaceType = SurfaceType_Default;
// ECC_Visibility - tells us that it will check collision with everything what is visible in scene
// If sth block the linetrace then do some actions
if (GetWorld()->LineTraceSingleByChannel(Hit, EyeLocation, TraceEnd, COLLISION_WEAPON, CollQearyParams)) {
// Blocking hit! Process damage
AActor* DamagedActor;
DamagedActor = Hit.GetActor();
SurfaceType = UPhysicalMaterial::DetermineSurfaceType(Hit.PhysMaterial.Get());
PlayImpactEffects(SurfaceType, Hit.ImpactPoint);
TraceEndPoint = Hit.ImpactPoint;
float ActualDamage = BaseDamage;
if (SurfaceType == SURFACE_FLESHVULNERABLE) {
ActualDamage = HeadshotDamage; // damage for headshot;
}
UGameplayStatics::ApplyPointDamage(DamagedActor, ActualDamage, ShootDirection, Hit, ActorOwner->GetInstigatorController(), this, DamageType);
}
if (DebugWeaponDrawing > 0) {
DrawDebugLine(GetWorld(), EyeLocation, TraceEnd, FColor::White, false, 1.0f, 0, 1.0f);
}
PlayFireEffects(TraceEndPoint);
ActorOwner->AddCameraRecoil();
DecreaseCurrentAmmo();
UE_LOG(LogTemp, Warning, TEXT("Okay I can Start Fire"));
if (Role == ROLE_Authority) {
HitScanTrace.TraceTo = TraceEnd;
HitScanTrace.SurfaceType = SurfaceType;
}
LastTimeFired = GetWorld()->TimeSeconds;
}
else if (RemainingAmmo > 0 && bAutoReloading == true && bIsReloading == false) {
StartReloading();
UE_LOG(LogTemp, Warning, TEXT("Start Relading From Fire"));
}
}
void ASWeapon::OnRep_HitScanTrace()
{
// Play CosmeticEffects
PlayFireEffects(HitScanTrace.TraceTo);
PlayImpactEffects(HitScanTrace.SurfaceType, HitScanTrace.TraceTo);
}
void ASWeapon::PlayImpactEffects(EPhysicalSurface SurfaceType, FVector ImpactPoint)
{
UParticleSystem* SelectedEffect = nullptr;
switch (SurfaceType)
{
case SURFACE_FLESHMATERIAL:
case SURFACE_FLESHVULNERABLE:
SelectedEffect = FleshImpactEffect;
break;
default:
SelectedEffect = DefaultImpactEffect;
break;
}
if (SelectedEffect) {
FVector MuzzleLocation = MeshComp->GetSocketLocation(MuzzleSocketName);
FVector ShootDirection = ImpactPoint - MuzzleLocation;
ShootDirection.Normalize();
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), SelectedEffect, ImpactPoint, ShootDirection.Rotation());
UE_LOG(LogTemp, Warning, TEXT("Hit.ImpactNormal.Rotation(): %s"), *ShootDirection.Rotation().ToString());
}
}
void ASWeapon::StartFire()
{
// LastTime e.g 1 and TimeBetweenShots = 0.1 = 1+0.1 = 1.1; GetWorld()->TimeSeconds = 1;
UE_LOG(LogTemp, Warning, TEXT("void ASWeapon::StartFire()"));
float FirstDelay = FMath::Max(LastTimeFired + TimeBetweenShots - GetWorld()->TimeSeconds, 0.0f);
GetWorldTimerManager().SetTimer(TimerHandle_DelayBetweenShots, this, &ASWeapon::Fire, TimeBetweenShots, true, FirstDelay);
}
void ASWeapon::PlayFireEffects(FVector TraceEndPoint)
{
if (MuzzleFlashEffect) {
UGameplayStatics::SpawnEmitterAttached(MuzzleFlashEffect, MeshComp, MuzzleSocketName);
}
if (TracerEffect) {
FVector MuzzleLocation = MeshComp->GetSocketLocation(MuzzleSocketName);
UParticleSystemComponent* TracerComp = UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), TracerEffect, MuzzleLocation);
if (TracerComp) {
TracerComp->SetVectorParameter(TracerTargetName, TraceEndPoint);
}
}
APawn* MyOwner = Cast<APawn>(GetOwner());
if (MyOwner) {
APlayerController* PC = Cast<APlayerController>(MyOwner->GetController());
if (PC) {
PC->ClientPlayCameraShake(FireCamShake);
}
}
}
ASCharacter.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "SCharacter.generated.h"
class UCameraComponent;
class USpringArmComponent;
class ACustomProjectile;
class ASWeapon;
class USHealthComponent;
UCLASS()
class COOPGAME_API ASCharacter : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
ASCharacter();
UPROPERTY(Replicated)
ASWeapon* CurrentWeapon;
void StartFire();
UFUNCTION()
void OnHealthChanged(USHealthComponent* OwningHealthComponent, float Health, float HealthDelta, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser);
virtual void GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const override;
Ascharacter.cpp
void ASCharacter::BeginPlay()
{
Super::BeginPlay();
DefaultFOV = CameraComp ? CameraComp->FieldOfView : DefaultFOV;
HealthComp->OnHealthChanged.AddDynamic(this, &ASCharacter::OnHealthChanged);
// the same can be used like this: HasAuthority();
if (HasAuthority()) {
// Spawn Default Weapon
FActorSpawnParameters SpawnParams;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
CurrentWeapon = GetWorld()->SpawnActor<ASWeapon>(StarterWeapon, FVector::ZeroVector, FRotator::ZeroRotator, SpawnParams);
if (CurrentWeapon) {
CurrentWeapon->SetOwner(this);
CurrentWeapon->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetNotIncludingScale, WeaponSocketName);
}
}
}
void ASCharacter::StartFire()
{
if (CurrentWeapon) {
CurrentWeapon->StartFire();
}
}
void ASCharacter::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ASCharacter, CurrentWeapon);
}