extends Node3D ## Visible, dodgeable Alien Grunt projectile. Swept ray prevents tunnelling. const SPEED := 8.0 const LIFETIME := 3.0 var direction := Vector3.FORWARD var damage := 8.0 var shooter: CharacterBody3D var player: CharacterBody3D var age := 0.0 var spent := false func _ready() -> void: add_to_group("boss_projectiles") var model: Node3D = preload("res://assets/boss/hornet_textured.scn").instantiate() add_child(model) for area in model.find_children("*", "Area3D", true, false): area.collision_layer = 0 area.collision_mask = 0 var anim := model.get_node_or_null("AnimationPlayer") as AnimationPlayer if anim != null: # The exported native scene already marks idle as looping. anim.play("idle") var mesh := SphereMesh.new() mesh.radius = 0.055 mesh.height = 0.11 var glow := StandardMaterial3D.new() glow.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED glow.albedo_color = Color(1, 0.7, 0.1) mesh.material = glow var marker := MeshInstance3D.new() marker.mesh = mesh marker.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF add_child(marker) if direction.length_squared() > 0: look_at(global_position + direction) func _physics_process(dt: float) -> void: if spent: return age += dt if age >= LIFETIME or not is_instance_valid(shooter) or shooter.dead \ or not is_instance_valid(player) or player.hp <= 0: queue_free() return var next := global_position + direction * SPEED * dt var query := PhysicsRayQueryParameters3D.create(global_position, next, 1 | 2) query.exclude = [shooter.get_rid()] var hit := get_world_3d().direct_space_state.intersect_ray(query) if not hit.is_empty(): spent = true if hit.collider == player: player.take_damage(damage) queue_free() else: global_position = next