Advanced Multiplayer FPS Game Unreal Engine, C++

👤 Sharing: AI
Okay, here's a simplified, conceptual example of a core mechanic found in advanced multiplayer FPS games, implemented using Unreal Engine and C++. This focuses on hit registration and damage application, incorporating elements like server-side authority and basic replication. This example is designed to be understandable and illustrates a common pattern, not a fully functional game feature.

**Important:** This is a conceptual example.  Implementing a fully functional and robust hit registration system in a real-world FPS requires significantly more complexity, including:

*   **Lag Compensation:** Predicting player positions to account for network latency.
*   **Client-Side Prediction:** Allowing clients to immediately see their actions before confirmation from the server.
*   **Anti-Cheat Measures:** Preventing players from manipulating hit data.
*   **Detailed Hitboxes:** Using more precise collision shapes for accurate hit detection.

```cpp
// MyCharacter.h (Custom Character class Header)
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "MyCharacter.generated.h"

UCLASS()
class MYPROJECT_API AMyCharacter : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AMyCharacter();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

	// Fire function.
	UFUNCTION(BlueprintCallable, Category = "Gameplay")
		void Fire();

	//Function to apply damage.
	UFUNCTION(Server, Reliable, WithValidation)
		void Server_ApplyDamage(AActor* HitActor, float DamageAmount);

	bool Server_ApplyDamage_Validate(AActor* HitActor, float DamageAmount);
	void Server_ApplyDamage_Implementation(AActor* HitActor, float DamageAmount);


private:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat", meta = (AllowPrivateAccess = "true"))
		float WeaponDamage = 20.0f; //Base weapon damage.

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat", meta = (AllowPrivateAccess = "true"))
		float WeaponRange = 5000.0f; //Range of the weapon.

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
	class USkeletalMeshComponent* CharacterMesh;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
	class USceneComponent* MuzzleLocation;

};


// MyCharacter.cpp (Custom Character class implementation)
#include "MyCharacter.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/SceneComponent.h"
#include "Kismet/GameplayStatics.h" // Needed for ApplyPointDamage
#include "Net/UnrealNetwork.h"

AMyCharacter::AMyCharacter()
{
	// Set size for collision capsule
	GetCapsuleComponent()->InitCapsuleSize(55.f, 96.0f);

	// Create a MeshComponent
	CharacterMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("CharacterMesh"));
	CharacterMesh->SetupAttachment(GetCapsuleComponent());

	// Create a MuzzleLocation component
	MuzzleLocation = CreateDefaultSubobject<USceneComponent>(TEXT("MuzzleLocation"));
	MuzzleLocation->SetupAttachment(CharacterMesh, FName("MuzzleFlashSocket")); // Attach to the mesh at the muzzle socket. Adapt this socket to your model.
	MuzzleLocation->SetRelativeLocation(FVector(0.0f, 50.0f, 0.0f));  //Adjust position based on your skeletal mesh
}

void AMyCharacter::BeginPlay()
{
	Super::BeginPlay();

}

void AMyCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	// Bind fire event
	PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &AMyCharacter::Fire);

}

void AMyCharacter::Fire()
{
	if (HasAuthority()) //Only perform the raycast on the server.
	{
		FHitResult Hit;
		FVector StartLocation = MuzzleLocation->GetComponentLocation();
		FVector EndLocation = StartLocation + (GetControlRotation().Vector() * WeaponRange); //Direction from the controller * Range

		FCollisionQueryParams QueryParams;
		QueryParams.AddIgnoredActor(this); //Ignore the player firing the shot.

		bool bHit = GetWorld()->LineTraceSingleByChannel(Hit, StartLocation, EndLocation, ECC_Visibility, QueryParams);

		if (bHit && Hit.GetActor() != nullptr)
		{
			//We hit something, apply damage.
			Server_ApplyDamage(Hit.GetActor(), WeaponDamage);
		}
	}
	else //If we are not the server, call a server RPC to perform the fire function.
	{
		Server_Fire();
	}
}

//Server RPC to call the fire function on the server.
UFUNCTION(Server, Reliable, WithValidation)
	void Server_Fire();

void AMyCharacter::Server_Fire_Implementation()
{
	Fire(); //Call the fire function on the server.
}

bool AMyCharacter::Server_Fire_Validate()
{
	return true; //For now always return true, can add validation logic.
}


//Server RPC function to apply damage.
void AMyCharacter::Server_ApplyDamage_Implementation(AActor* HitActor, float DamageAmount)
{
	if (!IsValid(HitActor)) return;

	// Apply damage.  This assumes the hit actor has a health component or supports damage.
	UGameplayStatics::ApplyPointDamage(HitActor, DamageAmount, (HitActor->GetActorLocation() - GetActorLocation()), FHitResult(), GetController(), this, UDamageType::StaticClass());

	//Log damage.
	UE_LOG(LogTemp, Warning, TEXT("Damage Applied"));

	// Optionally, replicate hit effects (muzzle flash, impact effects) here.  This is crucial for a good player experience.
	//  You'd use a Multicast RPC to do this:  Multicast_PlayImpactEffect(Hit.ImpactPoint, Hit.ImpactNormal);
}

bool AMyCharacter::Server_ApplyDamage_Validate(AActor* HitActor, float DamageAmount)
{
	//Perform some validation to prevent cheating.

	if (!IsValid(HitActor)) //Is the actor valid?
	{
		return false;
	}

	if (DamageAmount <= 0) //Is the damage amount valid?
	{
		return false;
	}

	return true; //Return true, the damage can be applied.
}

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

	// Replicate the variables
	DOREPLIFETIME(AMyCharacter, WeaponDamage);
}
```

**Explanation:**

1.  **`MyCharacter.h`**:
    *   Includes necessary headers.
    *   `WeaponDamage`: Defines the base damage of the weapon.  `EditAnywhere` makes it adjustable in the editor.
    *   `WeaponRange`:  The maximum range the weapon's raycast will travel.
    *   `Fire()`: This is a BlueprintCallable function which means you can call it from blueprints. This function will be used as the initial function to fire the weapon, checking if the current instance has authority or not.
	*   `Server_ApplyDamage`: Server RPC function to apply the damage to a hit actor, this is crucial to ensure server authority and prevent cheating.
    *   Includes replication macros (`GENERATED_BODY()`, `UCLASS()`, `UPROPERTY()`, `UFUNCTION()`).

2.  **`MyCharacter.cpp`**:
    *   **`AMyCharacter::AMyCharacter()`**:  The constructor. Sets default values and creates subobjects.
        *   Creates a `CharacterMesh` (USkeletalMeshComponent) and attaches it to the capsule component of the character. The skeletal mesh is what the player will see as the character model.
		*   Creates a `MuzzleLocation` (USceneComponent) and attaches it to the character mesh at a specified socket. This socket should be present on your skeletal mesh, and it represents the location where the bullets/projectiles will originate.
    *   **`AMyCharacter::Fire()`**: This function handles the weapon firing logic.
        *   It performs a raycast to detect if it hits something.
        *   If it hits something, it calls the `Server_ApplyDamage` RPC function to apply the damage on the server.
        *   If the character doesn't have authority, it calls a server RPC function which then calls the `Fire` function to be performed on the server, ensuring the server has authority over performing the raycast.
	*   **`AMyCharacter::Server_Fire()`**: Server RPC function to call the `Fire` function on the server.
    *   **`AMyCharacter::Server_ApplyDamage()`**: This is a *Server RPC*.
        *   It takes the `HitActor` and `DamageAmount` as parameters.
        *   It first validates that the hit actor is valid and that the damage amount is valid.
        *   Then, it calls `UGameplayStatics::ApplyPointDamage` to apply the damage to the `HitActor`.  This function handles the actual damage calculation and application based on the `HitActor`'s implementation.
    *   **`AMyCharacter::Server_ApplyDamage_Validate()`**: This is the *validation function* associated with the `Server_ApplyDamage` RPC.  It's extremely important to implement validation to prevent clients from sending invalid data (e.g., excessive damage).
    *   **`AMyCharacter::GetLifetimeReplicatedProps()`**:  This function is *essential* for replication.  It tells Unreal Engine which variables need to be replicated from the server to clients. In this case, `WeaponDamage` is replicated.

**Key Concepts:**

*   **Server Authority:**  The server is the ultimate source of truth.  The client *requests* actions (like firing), but the server decides whether the action is valid and what the outcome is.  This is essential to prevent cheating.
*   **RPCs (Remote Procedure Calls):**  RPCs allow clients to call functions on the server (and vice versa).  `UFUNCTION(Server, Reliable, WithValidation)` defines a server RPC that is guaranteed to be executed (reliable) and has a validation function.  `UFUNCTION(Client, Reliable)` defines a client RPC.  There are also unreliable RPCs (`Unreliable`) which are faster but can be dropped if network conditions are poor.
*   **Replication:**  The process of keeping data synchronized between the server and clients.  Variables marked with `UPROPERTY(Replicated)` or `DOREPLIFETIME` will automatically be replicated.
*   **`ApplyPointDamage`**:  A built-in Unreal Engine function that applies damage to an actor.  It takes care of notifying the actor that it has been damaged and allows the actor to handle the damage in a customized way.

**How to Use This Example:**

1.  **Create a new Unreal Engine project (C++ template).**
2.  **Create a new C++ class derived from `Character` called `MyCharacter`.**
3.  **Copy the code into `MyCharacter.h` and `MyCharacter.cpp`.**
4.  **Compile the project.**
5.  **Create a Blueprint class based on `MyCharacter`.**  This is where you'll set the skeletal mesh, animation blueprint, and other visual properties.
6.  **Add a `PlayerStart` actor to your level.**
7.  **Set the "Default Pawn Class" in your Game Mode to the Blueprint class you created.**
8.  **Add Input mappings**.  Go to Edit -> Project Settings -> Input and create Action Mappings for Fire.
9.  **Test in Multiplayer:**  Play in editor with at least two players to see the replication in action.

**Important Notes and Further Improvements:**

*   **Error Handling:**  Add more robust error handling and logging.
*   **Animation:**  Add animation to the firing action.
*   **Sound Effects:**  Add sound effects.  Replicate sound effects using multicast RPCs for all clients to hear.
*   **Impact Effects:** Replicate visual effects (like sparks or blood) on the hit actor. Use a `Multicast` RPC for this.
*   **More Complex Validation:** Implement more sophisticated validation in `Server_ApplyDamage_Validate`, such as checking the player's weapon, ammo, and whether the target is within a reasonable distance.
*   **Lag Compensation:**  Implement lag compensation to improve accuracy.
*   **Health Component:** Create a separate health component to manage the health of actors. This is good practice because it decouples health management from the actor itself.

This example gives you a starting point for building a more advanced multiplayer FPS game.  Remember to prioritize server authority and implement proper validation to prevent cheating. Good luck!
👁️ Viewed: 7

Comments