class_name Tile extends Node3D @export var display_texture: Texture2D @export var inward_face_flip := true @export var continuous_billboard := true @export_range(0.0, 1.0, 0.01) var pulse_strength := 0.08 @export_range(0.05, 4.0, 0.05) var pulse_frequency_hz := 0.45 var tile_material: StandardMaterial3D var base_scale := Vector3.ONE var base_emission_energy := 1.0 var scale_multiplier := 1.0 var phase_offset := 0.0 var viewer_camera: Node3D @onready var mesh_instance := $MeshInstance3D as MeshInstance3D @onready var animation_player := $AnimationPlayer2 as AnimationPlayer func _ready() -> void: var source_material := ((mesh_instance.mesh as PlaneMesh).material as StandardMaterial3D) if source_material != null: tile_material = source_material.duplicate() as StandardMaterial3D mesh_instance.material_override = tile_material if tile_material != null and display_texture != null: tile_material.albedo_texture = display_texture tile_material.emission_texture = display_texture base_emission_energy = tile_material.emission_energy_multiplier base_scale = mesh_instance.scale visible = true if animation_player != null: animation_player.play("flying") func _process(_delta: float) -> void: if viewer_camera != null and continuous_billboard: look_at(viewer_camera.global_position, Vector3.UP) if inward_face_flip: rotate_object_local(Vector3.UP, PI) var osc := Time.get_ticks_msec() * 0.001 var pulse := 1.0 + sin((osc * TAU * pulse_frequency_hz) + phase_offset) * pulse_strength mesh_instance.scale = base_scale * scale_multiplier * pulse if tile_material != null: tile_material.emission_energy_multiplier = base_emission_energy * (0.9 + pulse_strength) func place_on_shell( center: Vector3, direction: Vector3, radius: float, camera: Node3D ) -> void: viewer_camera = camera global_position = center + (direction.normalized() * radius) if viewer_camera != null: look_at(viewer_camera.global_position, Vector3.UP) if inward_face_flip: rotate_object_local(Vector3.UP, PI) func set_scale_multiplier(multiplier: float) -> void: scale_multiplier = max(multiplier, 0.05) func set_phase_offset(phase: float) -> void: phase_offset = phase func set_display_texture(texture: Texture2D) -> void: if texture == null: return display_texture = texture if tile_material != null: tile_material.albedo_texture = texture tile_material.emission_texture = texture func set_global_position_unclamped(pos: Vector3) -> void: global_position = pos