In this post we're going to see how to use the http module that is…
Introduction to UE4 Networking – Part 2
In the previous post we have created a basic networking behavior that reduces the health and bomb count of the character that presses the Throw Bomb input. In this final part, we’re going to add the spawn functionality for the bomb and configure our code so every character is damaged if he’s inside the explosion radius of the spawned bomb.
Before we start editing our existing code, here is the end result:
The final functionality will be the following:
- If the character has any bombs left, when he presses the Throw Bomb input, a bomb will get spawned
- When the bomb bounces on the ground, we’re going to “arm” it
- After a certain amount of time has passed since the bomb is armed, an explosion will take place
- If any player is inside the explosion radius, he will take damage
Creating the Bomb class
The bomb actor constists of:
- A static mesh
- A projectile movement component (we’re going to use that in order to register the Bounce logic)
- A sphere collision component (the projectile movement component works when a collision component is the root of our actor)
Create a class that inherits the Actor class and name it Bomb. Then, add the following header before the .generated.h file:
#include "GameFramework/ProjectileMovementComponent.h"When you’re done with that, add the following declarations:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
public: // Sets default values for this actor's properties ABomb(); // Called when the game starts or when spawned virtual void BeginPlay() override; protected: /** The static mesh of the comp */ UPROPERTY(VisibleAnywhere) UStaticMeshComponent* SM; /** The projectile movement comp */ UPROPERTY(VisibleAnywhere) UProjectileMovementComponent* ProjectileMovementComp; /** Sphere comp used for collision. Movement component need a collision component as root to function properly */ UPROPERTY(VisibleAnywhere) USphereComponent* SphereComp; /** The delay until explosion */ UPROPERTY(EditAnywhere, Category = BombProps) float FuseTime = 2.5f; UPROPERTY(EditAnywhere, Category = BombProps) float ExplosionRadius = 200.f; UPROPERTY(EditAnywhere, Category = BombProps) float ExplosionDamage = 25.f; /** The particle system of the explosion */ UPROPERTY(EditAnywhere) UParticleSystem* ExplosionFX; private: /** Marks the properties we wish to replicate */ virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override; UPROPERTY(ReplicatedUsing = OnRep_IsArmed) bool bIsArmed = false; /** Called when bIsArmed gets updated */ UFUNCTION() void OnRep_IsArmed(); /** Arms the bomb for explosion */ void ArmBomb(); /** Called when our bomb bounces */ UFUNCTION() void OnProjectileBounce(const FHitResult& ImpactResult, const FVector& ImpactVelocity); /** Performs an explosion after a certain amount of time */ void PerformDelayedExplosion(float ExplosionDelay); /** Performs an explosion when called */ UFUNCTION() void Explode(); //Simulate explosion functions /** * The multicast specifier, indicates that every client will call the SimulateExplosionFX_Implementation. * You don't have to generate an implementation for this function. */ UFUNCTION(Reliable, NetMulticast) void SimulateExplosionFX(); /** The actual implementation of the SimulateExplosionFX */ void SimulateExplosionFX_Implementation(); |
The reason we’re using a multicast function is because the SimulateExplosionFX essentially provides some “eye-candy” for our game. Since that isn’t important in terms of gameplay, it’s safe for every client to call his own implementation. Moreover, you can omit the reliable specifier in order to avoid any bottlenecks in case of bad internet connection. It’s up to you to decide what’s really important for your game and mark it as reliable.
Moving on to the logic for our Bomb class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
// Sets default values ABomb::ABomb() { // 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; SphereComp = CreateDefaultSubobject<USphereComponent>(FName("SphereComp")); SetRootComponent(SphereComp); SM = CreateDefaultSubobject<UStaticMeshComponent>(FName("SM")); SM->SetupAttachment(SphereComp); ProjectileMovementComp = CreateDefaultSubobject<UProjectileMovementComponent>(FName("ProjectileMovementComp")); ProjectileMovementComp->bShouldBounce = true; //Since we need to replicate some functionality //for this actor, we need to mark it as true SetReplicates(true); } // Called when the game starts or when spawned void ABomb::BeginPlay() { Super::BeginPlay(); //Register that function that will be called in any bounce event ProjectileMovementComp->OnProjectileBounce.AddDynamic(this, &ABomb::OnProjectileBounce); } void ABomb::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); //Tell the engine that we wish to replicate the bIsArmed variable DOREPLIFETIME(ABomb, bIsArmed); } void ABomb::ArmBomb() { if (bIsArmed) { //Chance the base color of the static mesh to red UMaterialInstanceDynamic* DynamicMAT = SM->CreateAndSetMaterialInstanceDynamic(0); DynamicMAT->SetVectorParameterValue(FName("Color"), FLinearColor::Red); } } void ABomb::OnProjectileBounce(const FHitResult& ImpactResult, const FVector& ImpactVelocity) { //If the bomb is not armed and we have authority, //arm it and perform a delayed explosion if (!bIsArmed && Role == ROLE_Authority) { bIsArmed = true; ArmBomb(); PerformDelayedExplosion(FuseTime); } } void ABomb::OnRep_IsArmed() { //Will get called when the bomb is armed //from the authority client if (bIsArmed) { ArmBomb(); } } void ABomb::PerformDelayedExplosion(float ExplosionDelay) { FTimerHandle TimerHandle; FTimerDelegate TimerDel; TimerDel.BindUFunction(this, FName("Explode")); GetWorld()->GetTimerManager().SetTimer(TimerHandle, TimerDel, ExplosionDelay, false); } void ABomb::Explode() { SimulateExplosionFX(); //We won't use any specific damage types in our case TSubclassOf<UDamageType> DmgType; //Do not ignore any actors TArray<AActor*> IgnoreActors; //This will eventually call the TakeDamage function that we have overriden in the Character class UGameplayStatics::ApplyRadialDamage(GetWorld(), ExplosionDamage, GetActorLocation(), ExplosionRadius, DmgType, IgnoreActors, this, GetInstigatorController()); FTimerHandle TimerHandle; FTimerDelegate TimerDel; TimerDel.BindLambda([&]() { Destroy(); }); //Destroy the actor after 0.3 seconds. GetWorld()->GetTimerManager().SetTimer(TimerHandle, TimerDel, 0.3f, false); } void ABomb::SimulateExplosionFX_Implementation() { if (ExplosionFX) { UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ExplosionFX, GetTransform(), true); } } |
The last things we need in order to test our functionality is modify the code we’ve written in our character class in the previous section. Specifically, we’re going to add the actual logic for the bomb spawn and we’re going to remove the hardcoded take damage function calls.
Open up the header file of your character and add the following library before the .generated.h file:
#include "Bomb.h"Then, declare a subclass property of our bomb:
1 2 3 4 |
public: /** Bomb Blueprint */ UPROPERTY(EditAnywhere) TSubclassOf<ABomb> BombActorBP; |
As a last step, modify the AttempToSpawnBomb and SpawnBomb functions to match the following functionality:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
void ANetworkingTutCharacter::AttempToSpawnBomb() { if (HasBombs()) { if (Role < ROLE_Authority) { ServerSpawnBomb(); } else SpawnBomb(); } } void ANetworkingTutCharacter::SpawnBomb() { //Decrease the bomb count and update the text in the local client //OnRep_BombCount will be called in every other client BombCount--; UpdateCharText(); FActorSpawnParameters SpawnParameters; SpawnParameters.Instigator = this; SpawnParameters.Owner = GetController(); //Spawn the bomb GetWorld()->SpawnActor<ABomb>(BombActorBP, GetActorLocation() + GetActorForwardVector() * 200, GetActorRotation(),SpawnParameters); } |
Save and compile your code. Create a Blueprint based on your bomb and assign the Shape Sphere to your bomb’s static mesh. Moreover, make sure that your Sphere Component covers the whole static mesh (in my case the desired sphere radius equals to 55 units) and has a valid Collision Profile assigned to it (ie Projectile / BlockAll). Moreover, assign a valid explosion particle system to your Blueprint (I’ve used the P_Explosion that comes in with the starter content).
Then, make sure to reference your Bomb Blueprint to your Character’s blueprint and test your functionality!
Followed the tutorial yesterday and I couldn’t get my bomb to work.
I randomly got an idea today and set CollisionPresets to “Projectile” from inside of my Bomb_BP blueprint under the SphereComp (Inherited) and it started working.
I could have just missed you setting it earlier since I was tired at the time of doing this.
Thank you for tutorial
Cheers for putting this together.
Been a massive help getting my head around the basics of C++ networking in Unreal.
Still got to work out exactly how it’s all working so I can work build on it but it’ll be a great starting point.
Max is right by the way, we never enabled a collision profile / default response for the SphereComponent in the contructor so it’ll just fall through the floor if you dont something like:
SphereComp->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Block);
Thanks Again Orfeus.
I’ve updated my post. Thank you both!
-Orfeas