First-Person Shooter: Dealing damage

Tutorial: First-Person Shooter in Unity - Dealing damage and handling player’s death

This is Elympics First-Person Shooter tutorial: part 6. In this part we’ll be handling player’s death. See: Part 5.

Stats Controller

There’s no component responsible for controlling the health of your players yet, so let’s add StatsController that serves this function.

The first step is to create a new script, StatsController.cs:

public class StatsController : ElympicsMonoBehaviour, IInitializable
{
	[Header("Parameters:")]
	[SerializeField] private float maxHealth = 100.0f;

	[Header("References:")]
	[SerializeField] private DeathController deathController = null;

	private ElympicsFloat health = new ElympicsFloat(0);
	public event Action<float, float> HealthValueChanged = null;

	public void Initialize()
	{
		health.Value = maxHealth;
		health.ValueChanged += OnHealthValueChanged;

		deathController.PlayerRespawned += ResetPlayerStats;
	}

	private void ResetPlayerStats()
	{
		health.Value = maxHealth;
	}

	public void ChangeHealth(float value, int damageOwner)
	{
		if (!Elympics.IsServer || deathController.IsDead)
			return;

		health.Value += value;

		if (health.Value <= 0.0f)
			deathController.ProcessPlayersDeath(damageOwner);
	}

	private void OnHealthValueChanged(float lastValue, float newValue)
	{
		HealthValueChanged?.Invoke(newValue, maxHealth);
	}
}

At the start of the game, this component assigns the maximum possible health state determined by the maxHealth variable to the player. The player’s life points are a key element of the game, which is why they’re strictly controlled and synchronized by the server (ElympicsFloat health).

The most important method of this class is ChangeHealth. This method is responsible for changing the player’s health, which can be done only by the server to avoid problems with bad prediction on the client side. Such problems would result in a large number of subsequent handling steps, e.g. player death. The health variable is of the ElympicsVar type, so although modifications are made on the server, all other clients will be able to synchronize its value.

If the hitpoints fall below zero, the DeathController method will be called. It’s another component responsible for handling the player’s death. The DeathController should also provide information about the current state of the IsDead player (whether they’re alive or not) to avoid modifying hitpoints in the death state.

public class DeathController : ElympicsMonoBehaviour, IUpdatable
{
	[Header("Parameters:")]
	[SerializeField] private float deathTime = 2.0f;

	public ElympicsBool IsDead { get; } = new ElympicsBool(false);
	public ElympicsFloat CurrentDeathTime { get; } = new ElympicsFloat(0.0f);

	public event Action PlayerRespawned = null;
	public event Action<int, int> HasBeenKilled = null;


	private PlayerData playerData = null;

	private void Awake()
	{
		playerData = GetComponent<PlayerData>();
	}

	public void ProcessPlayersDeath(int damageOwner)
	{
		CurrentDeathTime.Value = deathTime;
		IsDead.Value = true;

		Debug.Log(this.gameObject.name + " has been killed!");

		HasBeenKilled?.Invoke((int)PredictableFor, damageOwner);
	}

	public void ElympicsUpdate()
	{
		if (!IsDead || !Elympics.IsServer)
			return;

		CurrentDeathTime.Value -= Elympics.TickDuration;

		if (CurrentDeathTime.Value <= 0)
		{
			//Respawn player
			IsDead.Value = false;
		}
	}
}

The DeathController.cs component is used only to control the player’s death status. Its main parameter is the time of death: the time after which the player should be able to respawn.

The main method of this component is ProcessPlayersDeath called by StatsController when a player’s hitpoints drop below 0. The argument to this method is the id of the player who contributed to the death of this player. This argument is especially useful when monitoring player stats for kills and deaths. After calling the ProcessPlayersDeath function, the IsDead flag is set to true, and the timer starts counting down to the player’s respawn.

Add both of these components to your player prefab:

First-Person Shooter

The final step to allow your projectiles to damage the player is to modify the TryToApplyDamageToTarget method in the xplosionArea component:

Updated ExplosionArea.cs:

    [...]
    private void TryToApplyDamageToTarget(GameObject objectInExplosionRange)
    {
		if (objectInExplosionRange.TryGetComponent<StatsController>(out StatsController targetStatsController))
		{
			targetStatsController.ChangeHealth(-explosionDamage, (int)bulletOwner.PredictableFor);
		}
    }

Check whether the object that was in the blast field contains the StatsController component. If so, modify its value.

From now on the player can be killed!

Explosions from projectile bullets will now deal damage to players!

First-Person Shooter

In the next part we’ll create another weapon based on using raycasts! 🔫↗️


Last modified January 4, 2023: Fix misspellings (0a40727)