Roguelike 2D vs 3D

Just quick video comparison of two roguelike project 3D from GameDev.tv and 2D from ZenvaAcademy

Generally 2D feel a smoother, and I haven’t triggered any errors during gameplay.

3D have some intriguing script choices and I couldn’t fully understand of them.

Mainly why I opened 3D was to see how it’s melee and range attack done for enemy type, but I feel a bit disappointed on first error with

reset_game_state()

which was throwing error and asking to be

get_tree().call_deferred("reload_current_scene")

instead of

get_tree().reload_current_scene()

another one is

E 0:01:04:374   AttackComponent.gd:28 @ reset_exceptions(): The passed Node must be an instance of CollisionObject3D.
  <C++ Error>   Required object "rp_node" is null.
  <C++ Source>  scene/3d/physics/shape_cast_3d.cpp:459 @ remove_exception()
  <Stack Trace> AttackComponent.gd:28 @ reset_exceptions()
                PlayerAttack.gd:24 @ enter()
                StateMachine.gd:35 @ _transition_to_next_state()
                PlayerState.gd:26 @ check_attack()
                PlayerRun.gd:17 @ handle_input()
                StateMachine.gd:20 @ _unhandled_input()

with relevant code →

@onready var attack_shapecast: ShapeCast3D = get_parent()

func deal_damage(damage: float, knockback: Vector3) -> void:
	if attack_shapecast.enabled == false: return
	attack_shapecast.force_shapecast_update()
	for index in attack_shapecast.get_collision_count():
		var collider = attack_shapecast.get_collider(index)
		if collider.has_node("HealthComponent"):
			var health_component = collider.get_node("HealthComponent") as HealthComponent
			health_component.take_damage(damage)
			attack_shapecast.add_exception(collider)
			temporary_exceptions.append(collider)
		if collider.has_node("KnockbackComponent"):
			var knockback_component = collider.get_node("KnockbackComponent") as KnockbackComponent
			knockback_component.add_knockback(knockback)
		if shake_on_damage:
			get_viewport().get_camera_3d().quick_shake(0.75)
			
func reset_exceptions() -> void:
	for exception in temporary_exceptions:
		attack_shapecast.remove_exception(exception)
	temporary_exceptions = []

It feel like demonstrating coding, but not align with Godot expectations. What issue is actually here?

func reset_exceptions() -> void:
	for exception in temporary_exceptions:
		if is_instance_valid(exception):
			attack_shapecast.remove_exception(exception)
	temporary_exceptions = []

check for is_instance_valid fixed it, is this similar to deferred because temporary_exceptions is most of the time empty ?