Making a Game - Programming the Weapons - EP 17

by

Video Tutorial

Aims of this Tutorial

  • Allow the server to calculate the damage that the players cause.
  • Send this data back to the clients to display to the players.

Calculate Damage and Death

Last episode, we sent the weapon plan to the server, so we already have that. We also have the minor issue that we have updated the client physics loop to fix some bugs but did not do so for the server, so we just need to make those minor adjustments and we can then use the same helper functions as we did for client to find out the current weapons plan element and use it to determine if we need to fire the gun. This is the new physics loop:

func _physics_process(delta : float) -> void:
	if !enable_physics:
		return
	if !running_plan:
		if waiting_for_completion:
			send_all_plans()
			waiting_for_completion = false
		translation = last_translation
		rotation = last_rotation
		return
	var all_thrust_elements = _get_all_thrust_elements()
	var current_thrust_element : ThrustElement = _get_current_plan_element(all_thrust_elements)
	if current_thrust_element == null:
		last_translation = translation
		last_rotation = rotation
		running_plan = false
		return
	add_central_force(self.transform.basis.xform(current_thrust_element.linear_thrust))
	add_torque(self.transform.basis.xform(current_thrust_element.rotational_thrust))
	
	var all_weapons_elements = _get_all_weapons_elements()
	var current_weapons_element : WeaponsElement = _get_current_plan_element(all_weapons_elements)
	if current_weapons_element == null:
		running_plan = false
		return
	if current_weapons_element.firing and is_alive:
		fire(delta, plan_time)
	
	plan_time += delta

Note that we only fire the fun is we are alive. This is a variable that we will set later, but for now, it is just a member variable that is a bool. We should also note that we pass through the plan_time to the the fire function. This is so that we know at what point we die and can let the client know later. The fire method is very simple, it just calls take_damage() on any player in the beam (from a raycast placed in the same way that it is on the client). This function will then remove the damage from the health and check if it is negative, and if so calls die() which will turn off collisions, record the time of death and set the is_alive variable previously mentioned. These methods are shown below:

func fire(delta : float, time : float) -> void:
	if $GunContainer/RayCast.is_colliding():
		var target : RigidBody = $GunContainer/RayCast.get_collider()
		target.take_damage(BASE_DPS * delta, time)

func take_damage(damage : float, time : float) -> void:
	get_parent().get_parent().console_print("tmp")
	health -= damage
	if health < 0:
		self.die(time)

func die(time : float) -> void:
	is_alive = false
	$CollisionShape.disabled = true
	time_of_death = time

At this point, it is imperative that you make sure the raycast is enabled, or we will never get past the fire function! Now this means that we can calculate the damage players will take, but currently we are not. In order to do this, we need to stop sending the plan right away, but instead calculate everything first and then send the plan. So to fix this, in the arena, change player.send_all_plans() to player.calculate_plans(). This new function should look something like this:

func play_full_plan():
	set_linear_velocity(initial_velocity)
	set_angular_velocity(initial_rotational_velocity)
	set_translation(initial_translation)
	set_rotation(initial_rotation)
	running_plan = true
	plan_time = 0.0
	is_alive = true
	$CollisionShape.disabled = false

func calculate_plans():
	play_full_plan()
	waiting_for_completion = true

Note that the play_full_plan( is the same function we had on the client and that we have introduced a new variable, waiting_for_completion that we used in the physics loop to send the plan once, after we finished calculating everything. We also have a slight amendment to the send_all_plans() function, it also needs to the time of death in its final rpc.

Using this Information in the Client

We now need to save the incoming data into a variable. This is as simple as time_of_death = r_time_of_death in the remote function. Now we can use this in the physics loop to determine if we need to show this player or not:

if time_of_death > 0 and plan_time > time_of_death:
	self.visible = false
	$CollisionShape.disabled = true
else:
	self.visible = true
	$CollisionShape.disabled = false

Note the check that the time_of_death is positive. This is so that, if the time_of_death is still the default (-1), that we never kill the player. With this, the player should now be able to be killed given enough time in the laser!