class_name Ghost extends Node3D # Чужой игрок: капсула, которая плавно догоняет последнее полученное состояние. const SMOOTHING = 14.0 var target_pos := Vector3.ZERO var target_yaw := 0.0 var flags := 0 var body: MeshInstance3D var visor: MeshInstance3D func setup(id: int, pos: Vector3) -> void: name = "Ghost_%d" % id position = pos target_pos = pos var color := Color.from_hsv(fmod(id * 0.618034, 1.0), 0.7, 1.0) var mat := StandardMaterial3D.new() mat.albedo_color = color mat.emission_enabled = true mat.emission = color mat.emission_energy_multiplier = 0.6 var capsule := CapsuleMesh.new() capsule.radius = 0.34 capsule.height = 2.3 body = MeshInstance3D.new() body.mesh = capsule body.material_override = mat add_child(body) # «Визор» показывает, куда смотрит игрок (-Z — вперёд) var box := BoxMesh.new() box.size = Vector3(0.5, 0.15, 0.3) visor = MeshInstance3D.new() visor.mesh = box visor.position = Vector3(0, 0.9, -0.25) add_child(visor) var label := Label3D.new() label.text = "Игрок %d" % (id % 1000) label.billboard = BaseMaterial3D.BILLBOARD_ENABLED label.no_depth_test = true label.position = Vector3(0, 1.6, 0) label.pixel_size = 0.005 add_child(label) func set_target(pos: Vector3, yaw: float, pitch: float, new_flags: int) -> void: target_pos = pos target_yaw = yaw flags = new_flags visor.rotation.x = pitch func _process(delta: float) -> void: var t := 1.0 - exp(-SMOOTHING * delta) position = position.lerp(target_pos, t) rotation.y = lerp_angle(rotation.y, target_yaw, t) var sliding := (flags & Net.FLAG_SLIDING) != 0 var squash := 0.55 if sliding else 1.0 body.scale = body.scale.lerp(Vector3(1, squash, 1), t) var roll := 0.0 if (flags & Net.FLAG_WALLRUN) != 0: roll = 0.35 body.rotation.z = lerp(body.rotation.z, roll, t)