Explosion_der_Spiele/scripts/billboard/billboard_sprite.gd
copilot 1cc8aa9b9d Add village 3D wiring, voice lines, and 1.74 m world scale.
Houses ~8.5 m; chimps capped ~0.85 m with safer axis fitting. Large GLB/PBR packs stay local until Git LFS (Forgejo HTTP 413).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-15 12:02:57 +02:00

119 lines
3.5 KiB
GDScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@tool
extends Node3D
class_name BillboardSprite
## Mario-tree billboard: upright Quad, yaw-only toward camera.
## Size always via SpriteFit (world height), never raw pixels × constant.
## @tool: faces the editor viewport camera while editing.
@export var texture: Texture2D:
set(v):
texture = v
if is_node_ready():
_apply()
## Meters; prefer SpriteFit.WORLD_* constants when setting from code.
@export var target_height: float = 1.74:
set(v):
target_height = v
if is_node_ready():
_apply()
@export var max_width: float = 2.2:
set(v):
max_width = v
if is_node_ready():
_apply()
@export var modulate_color: Color = Color.WHITE:
set(v):
modulate_color = v
if is_node_ready():
_set_albedo(modulate_color if not _highlight else _highlight_color)
@onready var _mesh_instance: MeshInstance3D = $MeshInstance3D
var _quad_height: float = 1.0
var _highlight: bool = false
var _highlight_color: Color = Color(1.35, 1.25, 0.35)
func _ready() -> void:
_ensure_mesh()
_apply()
func _process(_delta: float) -> void:
_face_camera_yaw_only()
if Engine.is_editor_hint():
return
if _highlight and _mesh_instance and _mesh_instance.material_override:
var pulse := 0.85 + 0.15 * sin(Time.get_ticks_msec() * 0.012)
(_mesh_instance.material_override as BaseMaterial3D).albedo_color = _highlight_color * pulse
func set_texture(tex: Texture2D) -> void:
texture = tex
_apply()
func set_target_height(h: float) -> void:
target_height = h
_apply()
func set_highlight(enabled: bool, color: Color = Color(1.35, 1.25, 0.35)) -> void:
if Engine.is_editor_hint():
return
_highlight = enabled
_highlight_color = color
if not enabled:
_set_albedo(modulate_color)
func flash(color: Color = Color(2.0, 2.0, 1.2), duration: float = 0.12) -> void:
if Engine.is_editor_hint():
return
_set_albedo(color)
var tree := get_tree()
if tree == null:
tree = Engine.get_main_loop() as SceneTree
if tree:
await tree.create_timer(duration, true).timeout
if is_instance_valid(self) and not _highlight:
_set_albedo(modulate_color)
func _ensure_mesh() -> void:
if _mesh_instance == null:
_mesh_instance = get_node_or_null("MeshInstance3D") as MeshInstance3D
func _set_albedo(c: Color) -> void:
_ensure_mesh()
if _mesh_instance and _mesh_instance.material_override:
(_mesh_instance.material_override as BaseMaterial3D).albedo_color = c
func _apply() -> void:
_ensure_mesh()
if _mesh_instance == null:
return
var quad := QuadMesh.new()
var size := SpriteFit.world_quad_size(texture, target_height, max_width)
var w := size.x
var h := size.y
quad.size = Vector2(w, h)
_quad_height = h
_mesh_instance.mesh = quad
_mesh_instance.position = Vector3(0.0, h * 0.5, 0.0)
_mesh_instance.rotation = Vector3.ZERO
var mat := StandardMaterial3D.new()
mat.billboard_mode = BaseMaterial3D.BILLBOARD_DISABLED
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
mat.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA_SCISSOR
mat.alpha_scissor_threshold = 0.1
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
mat.albedo_color = modulate_color if not _highlight else _highlight_color
if texture != null:
mat.albedo_texture = texture
_mesh_instance.material_override = mat
func _face_camera_yaw_only() -> void:
var cam := get_viewport().get_camera_3d()
if cam == null:
return
var to_cam := cam.global_position - global_position
to_cam.y = 0.0
if to_cam.length_squared() < 0.0001:
return
look_at(global_position - to_cam, Vector3.UP)