here’s my projectile class
could you try using mine as a blueprint?
you will need to assign a PSC to the blueprint to get anything to show up visually
.h
// Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
#include "VictoryProjectile.generated.h"
UCLASS()
class AVictoryProjectile : public AActor
{
GENERATED_UCLASS_BODY()
/** initial setup */
virtual void PostInitializeComponents() OVERRIDE;
/** setup velocity */
void InitVelocity(FVector& ShootDirection);
/** handle hit */
void OnImpact(FHitResult const& HitResult);
/** Bounces */
void OnBounce(FHitResult const& HitResult);
//multiplayer
public:
//due to first frame not being right because velocity
//is not set right yet
void CorrectRotationFollowVelocity();
public:
/** Projectile Lifetime in Seconds */
UPROPERTY(EditDefaultsOnly, Category=ProjLifetime)
float VictoryLifeTime;
/** Collision Radius */
UPROPERTY(EditDefaultsOnly, Category=ProjCollision)
float CollisionSphereRadius;
/** Acceleration Multiplier EVERY TICK, can increase velocity up to VictoryProjMaxSpeed*/
UPROPERTY(EditDefaultsOnly, Category=ProjVelocity)
float VictoryAccelMult;
protected:
//Tick
virtual void Tick(float DeltaSeconds) OVERRIDE;
/** movement component */
UPROPERTY(VisibleDefaultsOnly, Category=ProjMovement)
TSubobjectPtr MovementComp;
/** collisions */
UPROPERTY(VisibleDefaultsOnly, Category=ProjCollision)
TSubobjectPtr CollisionComp;
/** effects for explosion */
UPROPERTY(EditDefaultsOnly, Category=Effects)
UParticleSystem* ExplosionTemplate;
/** controller that fired me (cache for damage calculations) */
AVictoryGamePlayerController* MyController;
/** did it explode? */
UPROPERTY(Transient, ReplicatedUsing=OnRep_Exploded)
bool bExploded;
/** [client] explosion happened */
UFUNCTION()
void OnRep_Exploded();
/** trigger explosion */
void Explode(const FHitResult& Impact);
/** shutdown projectile and prepare for destruction */
void DisableAndDestroy();
/** update velocity on client */
virtual void PostNetReceiveVelocity(const FVector& NewVelocity) OVERRIDE;
};
.cpp
// Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
#include "VictoryGame.h"
#define ROTATE_FOLLOW_VEL_DELAY 0.03
AVictoryProjectile::AVictoryProjectile(const class FPostConstructInitializeProperties& PCIP) : Super(PCIP)
{
//Core Properties
CollisionSphereRadius = 5;
VictoryAccelMult = 1.04;
VictoryLifeTime = 10;
CollisionComp = PCIP.CreateDefaultSubobject(this, TEXT("SphereComp"));
//Collision Size
CollisionComp->InitSphereRadius(CollisionSphereRadius);
//Network
CollisionComp->AlwaysLoadOnClient = true;
CollisionComp->AlwaysLoadOnServer = true;
CollisionComp->BodyInstance.SetCollisionEnabled(ECollisionEnabled::QueryOnly);
CollisionComp->BodyInstance.SetObjectType(COLLISION_PROJECTILE);
CollisionComp->BodyInstance.SetResponseToAllChannels(ECR_Ignore);
CollisionComp->BodyInstance.SetResponseToChannel(ECC_WorldStatic, ECR_Block);
CollisionComp->BodyInstance.SetResponseToChannel(ECC_WorldDynamic, ECR_Block);
CollisionComp->BodyInstance.SetResponseToChannel(ECC_Pawn, ECR_Block);
//Set Root
RootComponent = CollisionComp;
//Add Collision
Components.Add(CollisionComp);
MovementComp = PCIP.CreateDefaultSubobject(this, TEXT("ProjectileComp"));
MovementComp->UpdatedComponent = CollisionComp;
//Proj Speed
MovementComp->Speed = 1000;
MovementComp->MaxSpeed = 2000;
//teeesting
MovementComp->bRotationFollowsVelocity = false;
//add movement component
Components.Add(MovementComp);
//tick
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.TickGroup = TG_PrePhysics;
//Replication
RemoteRole = ROLE_SimulatedProxy;
bReplicateInstigator = true; //for projectiles, see actor description
bReplicateMovement = true;
//ProjectileMovementComponent Settings
//No gravity
MovementComp->ProjectileGravityScale = 0;
//Bounces
MovementComp->BounceVelocityStopSimulatingThreshold = 5; //uu/sec
MovementComp->bShouldBounce = 1;
MovementComp->Friction = 0;
MovementComp->Bounciness=1;
}
void AVictoryProjectile::CorrectRotationFollowVelocity()
{
MovementComp->bRotationFollowsVelocity = true;
}
void AVictoryProjectile::PostInitializeComponents()
{
Super::PostInitializeComponents();
/** Set the bounce delegate - called when the projectile bounces */
MovementComp->SetBounceDelegate(FOnImpactDelegate::CreateUObject(this, & AVictoryProjectile::OnBounce));
//Stop Delegate!
MovementComp->SetStopDelegate(FOnImpactDelegate::CreateUObject(this, &AVictoryProjectile::OnImpact));
//Ignore the spawner of this projectile
CollisionComp->MoveIgnoreActors.Add(Instigator);
//Set Lifetime
SetLifeSpan( VictoryLifeTime );
MyController = Cast(GetInstigatorController());
//correct rot follow vel
GetWorldTimerManager().SetTimer(this,
&AVictoryProjectile::CorrectRotationFollowVelocity,
ROTATE_FOLLOW_VEL_DELAY,
false
);
}
void AVictoryProjectile::InitVelocity(FVector& ShootDirection)
{
if (MovementComp)
{
MovementComp->Velocity = ShootDirection * MovementComp->Speed;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//Projectile Bounce Delegate
void AVictoryProjectile::OnBounce(FHitResult const& HitResult)
{
//if (MyController) MyController->OptJoyHUD("Bounce!!");
//hit a pawn? Then do impact
AActor* HitActor = HitResult.GetActor();
if (!HitActor) return;
//do impact
if (HitActor->IsA(APawn::StaticClass())) OnImpact(HitResult);
}
//Projectile Stop Delegate
void AVictoryProjectile::OnImpact(FHitResult const& HitResult)
{
if (Role == ROLE_Authority && !bExploded)
{
Explode(HitResult);
DisableAndDestroy();
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void AVictoryProjectile::Explode(const FHitResult& Impact)
{
// effects and damage origin shouldn't be placed inside mesh at impact point
const FVector NudgedImpactLocation = Impact.ImpactPoint + Impact.ImpactNormal * 10.0f;
//Spawn Emitter At Impact Location
if (ExplosionTemplate)
{
UGameplayStatics::SpawnEmitterAtLocation(
GetWorld(),
ExplosionTemplate,
NudgedImpactLocation,
Impact.ImpactNormal.Rotation(),
true); //auto destroy when explosion finishes
}
bExploded = true;
}
void AVictoryProjectile::DisableAndDestroy()
{
SetActorHiddenInGame(true);
MovementComp->StopMovementImmediately();
// give clients some time to show explosion
SetLifeSpan( 1.0f );
}
void AVictoryProjectile::OnRep_Exploded()
{
FVector ProjDirection = GetActorRotation().Vector();
const FVector StartTrace = GetActorLocation() - ProjDirection * 200;
const FVector EndTrace = GetActorLocation() + ProjDirection * 150;
FHitResult Impact;
//FCollisionQueryParams(FName InTraceTag, bool bInTraceComplex=false, const AActor* InIgnoreActor=NULL)
if (!GetWorld()->LineTraceSingle(Impact, StartTrace, EndTrace, COLLISION_PROJECTILE, FCollisionQueryParams(TEXT("ProjClient"), true, Instigator)))
{
// failsafe
Impact.ImpactPoint = GetActorLocation();
Impact.ImpactNormal = -ProjDirection;
}
Explode(Impact);
}
void AVictoryProjectile::PostNetReceiveVelocity(const FVector& NewVelocity)
{
if (MovementComp)
{
MovementComp->Velocity = NewVelocity;
}
}
int32* AVictoryProjectile::GetReplicationList(uint8* Recent, FPropertyRetirement* Retire, int32* Ptr, UPackageMap* Map, UActorChannel* Channel, FReplicationFlags RepFlags)
{
Ptr = Super::GetReplicationList(Recent, Retire, Ptr, Map, Channel, RepFlags);
DOREP(AVictoryProjectile, bExploded);
return Ptr;
}
//```
void AVictoryProjectile::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
if (bExploded) return;
if (!MovementComp) return;
//~~~~~~~~~~~~~~~~~~~~~~~~
//Acceleration, server side only
if (Role == ROLE_Authority)
{
/** Current velocity of moved component, replicated to clients using Actor ReplicatedMovement property. */
MovementComp->Velocity *= VictoryAccelMult;
}
/*
if (MyController) MyController->JoyHUD->Optimize("Ticking!");
if (MyController) MyController->Optimize("Ticking!");
*/
}