90 lines
2.9 KiB
GDScript
90 lines
2.9 KiB
GDScript
@tool
|
|
extends Node3D
|
|
class_name CrowdMarker
|
|
## A designer-placed spot for one billboard spectator. Drop these into a level in the
|
|
## editor; crowd_billboards.gd harvests every marker (via the &"crowd_marker" group) into
|
|
## its MultiMesh at runtime, using each marker's global position and chosen facing.
|
|
##
|
|
## The marker draws a lightweight preview (a slab standing in for the sprite + a forward
|
|
## arrow) so its position and orientation are visible in the viewport. That preview is
|
|
## editor-only and never serialised into the scene, so it costs nothing at runtime.
|
|
|
|
## The six selectable facings, one every 60° about the Y axis. DEG_0 faces the quad's
|
|
## front (+Z) along world +Z; increasing values rotate clockwise looking down.
|
|
enum Orientation {
|
|
DEG_0,
|
|
DEG_60,
|
|
DEG_120,
|
|
DEG_180,
|
|
DEG_240,
|
|
DEG_300,
|
|
}
|
|
|
|
const ORIENTATION_STEPS := 6
|
|
## Metres — matches crowd_billboards.figure_height so the editor preview reads true to scale.
|
|
const PREVIEW_HEIGHT := 2.0
|
|
|
|
@export var orientation := Orientation.DEG_0:
|
|
set(value):
|
|
orientation = value
|
|
_apply_orientation()
|
|
|
|
var _preview: Node3D
|
|
|
|
|
|
func _ready() -> void:
|
|
add_to_group(&"crowd_marker")
|
|
if Engine.is_editor_hint():
|
|
_build_preview()
|
|
_apply_orientation()
|
|
|
|
|
|
# Facing about Y for the current orientation: 0, 60, 120 … degrees as radians.
|
|
func facing_yaw() -> float:
|
|
return float(orientation) * (TAU / float(ORIENTATION_STEPS))
|
|
|
|
|
|
# Unit forward the spectator's front (+Z) points along, on the horizontal plane.
|
|
func facing_dir() -> Vector3:
|
|
var a := facing_yaw()
|
|
return Vector3(sin(a), 0.0, cos(a))
|
|
|
|
|
|
func _apply_orientation() -> void:
|
|
if _preview != null:
|
|
_preview.rotation = Vector3(0.0, facing_yaw(), 0.0)
|
|
|
|
|
|
# Editor-only preview. Children are added without an owner so they are excluded from the
|
|
# saved .tscn (Godot only serialises nodes owned by the scene root).
|
|
func _build_preview() -> void:
|
|
_preview = Node3D.new()
|
|
add_child(_preview)
|
|
|
|
var aspect := 0.5
|
|
var slab := MeshInstance3D.new()
|
|
var quad := QuadMesh.new()
|
|
quad.size = Vector2(PREVIEW_HEIGHT * aspect, PREVIEW_HEIGHT)
|
|
quad.center_offset = Vector3(0.0, PREVIEW_HEIGHT * 0.5, 0.0) # stand on the marker
|
|
slab.mesh = quad
|
|
slab.material_override = _preview_material(Color(0.3, 0.7, 1.0, 0.35))
|
|
_preview.add_child(slab)
|
|
|
|
var arrow := MeshInstance3D.new()
|
|
var shaft := BoxMesh.new()
|
|
shaft.size = Vector3(0.08, 0.08, PREVIEW_HEIGHT * 0.5)
|
|
arrow.mesh = shaft
|
|
# Push the shaft out along +Z (the facing) and up to chest height.
|
|
arrow.position = Vector3(0.0, PREVIEW_HEIGHT * 0.5, PREVIEW_HEIGHT * 0.25)
|
|
arrow.material_override = _preview_material(Color(1.0, 0.85, 0.2, 0.9))
|
|
_preview.add_child(arrow)
|
|
|
|
|
|
func _preview_material(col: Color) -> StandardMaterial3D:
|
|
var mat := StandardMaterial3D.new()
|
|
mat.albedo_color = col
|
|
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
|
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
|
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
|
|
return mat
|