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.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!