Download

Victor's ShooterGame Jetpack [TUTORIAL]

This, like the Use system tutorial, is a tutorial that i made in the UE4 beta forums, and now i put them here, so more people can enjoy them.

The code is now uploaded to github, feel free to grab it, instructions on how to add to a base ShooterGame example are there. GitHub - vblanco20-1/UnrealJetpack: Unreal Engine 4 Jetpack and Walljump code

The first thing ive done from a challenge, A jetpack system for the Shooter game. And ill explain how it works here and why it works that way. The system mimics a bit the way HALO reach jetpack worked. When you press the ****ebar key, it starts the jetpack and you fly up, when you release it, it stops and gravity gets you. The jetpack has fuel so you cant fly forever, and you need to take care of how much do you fly, Everything its configurable, so you can use it whatever you want, if you make the fuel time be 9999999, you have technically infinite flying time.
This time, unlike my Use system, i release the original source files, as its implemented on ShooterGame demo, you just need to paste onto the SourceFolder of ShooterGame, and recompile to enjoy it.

The files that change are:
ShooterHUD.cpp: to add a number on the screen representing the percentage of the fuel you have, this one is completely optional, you dont need to add it if oyu dont want the big number with the percentage.

JetpackCharacter.h and JetpackCharacter.cpp: to add the jetpack enable and disable logic, and to call Start and Stop jetpack functions, that enable or disable it.

JetpackMovementComponent.h and cpp: To add the actual logic for the modified movement when you are using the jetpack.

Code Explanation:
JetpackCharacter.h



// Somewhere public, this property is the replicated property for the jetpack use, it will be send to everyone so its syinced.
	UPROPERTY(Transient, Replicated)
	uint8 bIsUsingJetpack : 1;

     // the start and stop functions are called when the user presses ****e or releases it, and handle if the jetpack shold fly or not
	UFUNCTION(BlueprintCallable, Category=Jetpack)
        virtual void StartJetpack();	
	UFUNCTION(BlueprintCallable, Category=Jetpack)
	virtual void StopJetpack();

// This Enable and Disable functions handle the "burnout" state of the jetpack, when you deplete the fuel, it needs some time to recover
	virtual void DisableJetpack();
	virtual void EnableJetpack();

// for the enable and disable. This one is not replicated, as its not really needed, Enable and disable are client only functions, and handle imput mostly
	bool bIsJetpackEnabled;


	/** This is the time in seconds the jetpack has to spend while its "burned" to be re enabled again */ 
	UPROPERTY(Category="Character Movement", EditAnywhere, BlueprintReadWrite)
	float JetpackRecovery;

      // This function is the one that allows the client to set the jetpack state in the server, so the variable is replicated
	UFUNCTION(reliable, server)
	virtual void ServerSetJetpack(bool bNewJetpack);	


JetpackCharacter.cpp




// REPLICATION!! , this function takes care of replicating every property that should be replicated

void AJetpackCharacter::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	// everyone except local owner: flag change is locally instigated
	DOREPLIFETIME_CONDITION(AJetpackCharacter, bIsUsingJetpack, COND_SkipOwner);

}

//////////////////////////////////////////////////
// Start and Stop jetpack functions, you should add keybindings to them by blueprint or by config, the blueprint in the zip comes with that already set up in the E key
void AJetpackCharacter::StopJetpack()	
{
	
	if (Role < ROLE_Authority) // in a client
	{
		ServerSetJetpack(false); // call server to change variable
	}
	bIsUsingJetpack = false; // and then update the local version

}

void AJetpackCharacter::StartJetpack()	
{
	if(bIsJetpackEnabled) // check for if it is enabled, so you cant start flying again while the jetpack disabled due to the player depleting it
	{
		if (Role < ROLE_Authority) // we are in a client
		{
			ServerSetJetpack(true); // call server to change the variable
		}
		bIsUsingJetpack = true; // and then upate the local version
	}
}
void AJetpackCharacter::ServerSetJetpack_Implementation(bool bNewJetpack)
{
	// This is the server version, so the bIsUsingJetpack variable is replicated properly across the network
	bIsUsingJetpack = bNewJetpack;
}


////////////////////////////////////////////////////////
// Enable and Disable jetpack functions, for the "burnour" behaviour. Disable Jetpack is called from the movement component every time the jetpack runs out of fuel,
// and it sets a timer to re enable the jetpack again
void AJetpackCharacter::DisableJetpack()
{
	// Debug Message
	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("JetpackDisabled"));
	bIsJetpackEnabled = false;

	GetWorldTimerManager().SetTimer(this, &AShooterCharacter::EnableJetpack, JetpackRecovery, false);
}
void AJetpackCharacter::EnableJetpack()
{
	// Debug Message
	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, TEXT("JetpackEnabled"));
	bIsJetpackEnabled = true;
}



As you can see, the Character part mostly deals with imput and replication of the state of the jetpack, now comes the actual logic of it, in the Movement Component.
Movement Components are a part that is completeely new in UE4, in UE3, they didnt exist, the movement logic was done inside the actual **** and Controller. Now, it is in a separate component, wich you can swap if needed, and it has several useful properties.

JetpackMovementComponent.h: Here we add the declaration for several editable variables for the jetpack, and add functions



#pragma once
#include "JetpackCharacterMovement.generated.h"

UCLASS(HeaderGroup=Component)
class UJetpackCharacterMovement : public UShooterCharacterMovement
{
	GENERATED_UCLASS_BODY()

	
	/**Override the Perform Movement function so we can add the jetpack logic there */
	virtual void PerformMovement(float DeltaTime) OVERRIDE;

	/** Upwards Strength of the jetpack, the more it is, the bigger is the acceleration for the jetpack, if its too low, the gravity has more power and you dont even fly */
	UPROPERTY(Category="Character Movement", EditAnywhere, BlueprintReadWrite)
	float JetpackStrength ;

	/** maximum fuel for the jetpack, this goes in seconds, and its depleted with simple time, so if its 2, you can only fly for 2 seconds */
	UPROPERTY(Category="Character Movement", EditAnywhere, BlueprintReadWrite)
	float JetpackMaxFuel;

	/** Multiplier for the jetpack fuel regeneration, uses the time, if its 0.5, and the JetpackMaxFuel is 2 seconds, that means that it will take 4 seconds to be back at 100% */
	UPROPERTY(Category="Character Movement", EditAnywhere, BlueprintReadWrite)
	float JetpackRefuelRate;


	/** Holds the current fuel amount */
	float Jetpackfuel;
	
};



JetpackCharacterMovement.cpp, the implementation of it.
It sees if the owner character has bIsUsingJetpack to true, and if its true, it applies the upwars force and uses some fuel, if the player is not using the jetpack, the code regenerates the fuel.
After all that, it just calls CharacterMovement::PerformMovement, wich takes care of everything else.

Continues below, post is waaay too long




//Constructor, here we set the default properties for the movement component, This default variables ive put here work fine, but you can change them if you want, the one that you shouldnt  change is Jetpackfuel, that one is a runtime property, that will hold the current fuel, so its best to initialize at 0.
UJetpackCharacterMovement::UJetpackCharacterMovement(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{
	JetpackStrenght = 3000;
	JetpackMaxFuel = 2;
	JetpackRefuelRate = 0.5;
	Jetpackfuel = 0;
}
///////////////////////////////////////////////////
// The meat of the system, this function is called every frame to do the movement of the character, If you want to add some custom movement behaviour, is best to add it here
void  UJetpackCharacterMovement::PerformMovement(float DeltaTime) 
{

	// get the owner
	 AJetpackCharacter* ShooterCharacterOwner = Cast<AJetpackCharacter>(****Owner);
	if (JetpackCharacterOwner)	
	{
		// using jetpack, so fly up
		if(JetpackCharacterOwner->bIsUsingJetpack)
		{
			// make fuel decrease, as you are flying
			Jetpackfuel -= DeltaTime;
			// jetpack is depleted, disable it and stop flying
			if(Jetpackfuel < 0) 
			{
				JetpackCharacterOwner->StopJetpack();
				JetpackCharacterOwner->DisableJetpack();
			}
			// Add some acceleration to the Upward velocity, so the character is propulsed upwards
			Velocity.Z += JetpackStrenght*DeltaTime	;
		}
		
		else if(JetpackCharacterOwner->bIsJetpackEnabled == true)
		{
			// only refuel the jetpack when you are not flying and the jetpack is enabled
			Jetpackfuel+= DeltaTime*JetpackRefuelRate;
			if(Jetpackfuel >= JetpackMaxFuel)
			{
				Jetpackfuel = JetpackMaxFuel;
			}
		}
	}	
	
	// do the CharacterMovement version of PerformMovement, this function is the one that does the normal movement calculations.
	Super::PerformMovement(DeltaTime);
}


and the last thing is the HUD code, that draws a number to the screen. This one is optional and not needed at all, but it can be useful.

ShooterHUD.h




// On top part of void AShooterHUD::DrawHUD(), wich is the main draw function

if (My**** && My****->IsAlive()) // JETPACK ENERGY
	{
               // Get the movement component, and check if its the correct class
		UJetpackCharacterMovement* movecmp = Cast<UJetpackCharacterMovement>(My****->GetMovementComponent());
		if(movecmp)
		{
                        // calculate the percentage of fuel the jetpack has
			int Energy = movecmp->Jetpackfuel/movecmp->JetpackMaxFuel * 100;		

                         // and draw the number to the screen, left side.
			Canvas->DrawText(BigFont,FString::FromInt(Energy),0,Canvas->ClipY/3);
		}
	}



Second part: Walljumping
I found that the jetpack was a lot of fun, and decided to continue for a bit. Now ive added walljumping.
What the logic does, the walljumping is activated with ****ebar, but this time it will only trigger when you are NOT on the ground, to separate it from the normal jump.
When the user tryes to walljump, i get the central point with GetActorLocation(), and i do several traces (20 by default), in a circle, I use maths for that.
Then i get the closest hit, and from that hit i get the normal direction of the wall. After that, i multiply that by a value, and then add some upwards strenght, so you also jump upwards when you walljump.
At the end, i apply that new force with the function LaunchCharacter, wich is a function that throws the character on the air, kinda like the jump but with the force vector you put in the function. On clients, i call a server function that does that, becouse if i do the LaunchCharacter function from the client, it does nothing.
Walljump function:




void AJetpackCharacter::WallJump()
{
	AShooterPlayerController* MyPC = Cast<AShooterPlayerController>(Controller);
	if (MyPC && MyPC->IsGameIn****llowed() && !CharacterMovement->IsMovingOnGround()) // character is valid and its on the air
	{
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("Walljump"));

		/////////////
                // Get important vectors, TraceStart for the origin, and Front and SIde, wich will be used to calculate the whole circle of traces
		FVector TraceStart = GetActorLocation();
		FVector Front = GetActorRotation().Vector();
		FVector Side = FVector::CrossProduct(Front,FVector::UpVector);

                // some debug lines to help me see if those 2 vectors are correct
		DrawDebugLine(GetWorld(),TraceStart,TraceStart+Front*WallJumpTraceDistance,FColor::Blue,true,3);
		DrawDebugLine(GetWorld(),TraceStart,TraceStart+Side*WallJumpTraceDistance,FColor::Red,true,3);
                 // minimum from hitpoint to the actor location, we init it at such a high value so it gets replaced at any hit
		float MinDistance = 9999999;
                // hit location and normal, to store best location and normal
		FVector HitLocation = FVector::ZeroVector;
		FVector HitNormal;
                // this for loop does the circle of traces
		for(int i = 0; i< WallJumpTraces;i++)
		{
			float traceangle = 360/WallJumpTraces * i; // angle of the trace
			
                          // Using trigonometry we get all the directions in the 360 angle from only the front and side vector
			FVector TraceDir = Front*FMath::Sin(traceangle) + Side*FMath::Cos(traceangle);
                        // the end of the trace is the direction + the magnitude of the distance
			FVector TraceEnd = TraceStart+TraceDir*WallJumpTraceDistance;
                       // more debug lines
			DrawDebugLine(GetWorld(),TraceStart,TraceEnd,FColor::Black,true,3);

                         // Perform trace to retrieve hit info, shamelessly copypasted from the weapon, this part does a line check agains the whole world and returns FHitResult, wich holds what and where you hit
			static FName TraceTag = FName(TEXT("WeaponTrace"));
			
			FCollisionQueryParams TraceParams(TraceTag, true, Instigator);
			TraceParams.bTraceAsyncScene = true;
			TraceParams.bReturnPhysicalMaterial = true;
			FHitResult Hit(ForceInit);
			GetWorld()->LineTraceSingle(Hit, TraceStart, TraceEnd, COLLISION_WEAPON, TraceParams);
                        // something other than air was hit
			if(Hit.bBlockingHit)
			{
                                // we check the distance, if the distance beetween the hit and the character is less than the one before, store its values
                                // this way we make sure the stored HitLocation and HitNormal are the ones from the closest hit 
				if( (Hit.Location-TraceStart).Size() < MinDistance)
				{
					HitLocation = Hit.Location;
					HitNormal = Hit.Normal;
					MinDistance = (Hit.Location-TraceStart).Size();
				}
			}
		}
                // if HitLocation is 0, means that something was properly hit and the location of the hit was properly stored
		if(HitLocation != FVector::ZeroVector)
		{
                       // yellow debug ball in the place where the hit hitted
			DrawDebugSphere(GetWorld(),HitLocation,20,20,FColor::Yellow,true,5);

			if(Role < ROLE_Authority) // not server, client
			{
                                // call server to add the force for you
				ServerAddForce(HitNormal * WalljumpHorizontalStrenght + FVector::UpVector * WalljumpUpwardsStrenght);		
			}
                        // add the force anyway
			LaunchCharacter(HitNormal * WalljumpHorizontalStrenght + FVector::UpVector * WalljumpUpwardsStrenght,false,true);
		}

	}
}
void AJetpackCharacter::ServerAddForce_Implementation(FVector force)
{
	LaunchCharacter(force,false,true);
}




I hope this code and its explanation is useful for the people, its some cool stuff, and maiby we can try this in real multiplayer, all into a jetpack enabled shootergame server :smiley: .
If you have any doubts, ask me and ill answer you, and if you find a error, problem, or just make a cool enhancment, feel free to tell me it, and ill add it to the main post

Youtube video if its workings (**** this text limit is SOOO annoying)

https://www.youtube.com/watch?v=8ZGl4bp-umg

Looks great, vblanco. Thanks for the tutorial and source.

Im working on uploading the code to a proper site like Github, and creating a proper WIKI page, but for now, untill i learn how to do wiki formatting and clean up the code to upload in github, i added these 2 tutorials.

Hey vblanco,

Thanks for moving over your tutorial! We have a tutorial HUB now if you want to post this there too. You can go to https://wiki.unrealengine.com/Category:Tutorials to find out more!

Kickass!!! Thanks for sharing!

Very helpful thanks!!

Not related, but I wanted to apologize for the censor bar bleeping “Space”. I’m making fixes to the forum software now to reduce the chances of these happening. So sorry about any inconvenience. :slight_smile:

Very nice :slight_smile:

Also, Not really a problem but you put ‘Strenght’ instead of ‘Strength’? Typo ^^

very nice, thanks for the tutorial …

Uploaded the code to Github, and updated it so it works in the new version.

Very cool, I am new to Unreal as I just started yesterday but was really looking for a tutorial on how to wall jump. I would like wall jumping enabled without the jetpack though. Is that possible with your version?

Of course, they are completely separated things, if you dont want the jetpack, just dont call the StartJetpack and StopJetpack functions from keys, leaving only the Walljump function binded.

Hi guys,

I’m tryingd to follow your tutorial about jetpack. But I have some issues.
I’m working on FPSGame template. I create two classes “AJetpackCharacter” and “UJetpackMovementComponent”.

Indeed this part doesn’t seem good to my compilator :


AJetpackCharacter::AJetpackCharacter(const class FPostConstructInitializeProperties& PCIP)
:Super(PCIP.SetDefaultSubobjectClass<UJetpackMovementComponent>(ACharacter::CharacterMovementComponentName))

It says that UJetpackMovementComponent : indentificator is not declared. So I don’t know how to fix it. :confused:

Thanks :slight_smile:

Its not finding the class, add " #include “JetpackMovementComponent.h” " to the top line of the cpp file

Thank you. I feel stupid for this mistake :confused:.

The last problem I have is when I press RightMouseButton to use my Jetpack, my character doesn’t fly. It looks like I’m stuck on the ground.
I had to jump first, then I can fly. My solution is to call the function Jump when i want to use Jetpack like this:

[CODE fr]
void AMyProjectCharacter::StartJetpack()
{
bIsUsingJetpack = true;

SoundJetpack(bIsUsingJetpack);

ACharacter::Jump();	

}






void UMyCharacterMovementComponent::PerformMovement(float DeltaTime)
{
AMyProjectCharacter* CharacterOwner = Cast(PawnOwner);

if (CharacterOwner->bIsUsingJetpack)
{	
	Velocity.Z += JetpackStrenght*DeltaTime;

	if (Velocity.Z >= JetpackVelocityMax)
	{
		Velocity.Z = JetpackVelocityMax;
	}
   }
Super::PerformMovement(DeltaTime);

}





Despite this problem, I can use jetpack.

Can you help me to solve that problem, please :)

Yes, i am aware of that. Its due to the movement component. Other solution, instead of calling jump, is to set physics to falling and then applying the force, but jump works best most of the time

Awesome tutorial! Thanks man :smiley: I’ve been trying to take your wall jump system and add a type of “wall running” system but can’t seem to get a good lead. Any tips on where to start?

Ive already though on how to add wallrun, what i planned is use the same trace that the walljump uses, but instead of jumping in the other direction, you make the pawn go into flying state and make it move in the direction of the wall for a few seconds, if you get too far from the wall, it goes back to “falling” physics, wich make it normal again.