- Деревья: упрощённая модель, MultiMesh, коллизия стволов - Птицы кружат вокруг Tower_01 (шейдерные крылья, свечение) - Обелиск увеличен, поднят и плавно левитирует Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
117 lines
4.1 KiB
GDScript
117 lines
4.1 KiB
GDScript
@tool
|
||
extends Node3D
|
||
# Киберпанк-птицы кружат вокруг цели. Один MultiMesh, взмахи крыльев считает шейдер.
|
||
|
||
@export var target_path: NodePath
|
||
|
||
var target: Node3D
|
||
@export var count := 12
|
||
@export var min_radius := 55.0
|
||
@export var max_radius := 100.0
|
||
@export var min_height := 15.0
|
||
@export var max_height := 150.0
|
||
@export var speed_range := Vector2(0.15, 0.35) # рад/с вокруг цели
|
||
@export var bird_scale := 1.6
|
||
@export var seed_value := 7
|
||
|
||
const SHADER := """
|
||
shader_type spatial;
|
||
render_mode cull_disabled, unshaded, fog_disabled;
|
||
uniform float flap_speed = 9.0;
|
||
uniform float flap_amp = 0.9;
|
||
varying vec3 col;
|
||
void vertex() {
|
||
float ph = INSTANCE_CUSTOM.x;
|
||
// COLOR.r — вес крыла (0 у тела, 1 на кончике)
|
||
VERTEX.y += sin(TIME * flap_speed * (0.8 + INSTANCE_CUSTOM.y * 0.4) + ph) * flap_amp * COLOR.r;
|
||
VERTEX.z += COLOR.r * 0.25 * cos(TIME * flap_speed + ph);
|
||
col = INSTANCE_CUSTOM.z > 0.5 ? vec3(1.0, 0.1, 0.8) : vec3(0.0, 0.95, 1.0);
|
||
}
|
||
void fragment() {
|
||
// Светлее к кончикам крыльев — HDR для свечения
|
||
ALBEDO = col * (5.0 + 3.0 * COLOR.r);
|
||
}
|
||
"""
|
||
|
||
var _mm: MultiMesh
|
||
var _orbit: Array[Dictionary] = []
|
||
|
||
func _ready() -> void:
|
||
target = get_node_or_null(target_path) as Node3D
|
||
_build()
|
||
|
||
func _build() -> void:
|
||
var rng := RandomNumberGenerator.new()
|
||
rng.seed = seed_value
|
||
|
||
var mat := ShaderMaterial.new()
|
||
var sh := Shader.new()
|
||
sh.code = SHADER
|
||
mat.shader = sh
|
||
|
||
_mm = MultiMesh.new()
|
||
_mm.transform_format = MultiMesh.TRANSFORM_3D
|
||
_mm.use_custom_data = true
|
||
_mm.mesh = _make_mesh()
|
||
_mm.mesh.surface_set_material(0, mat)
|
||
_mm.instance_count = count
|
||
for i in count:
|
||
_orbit.append({
|
||
"r": rng.randf_range(min_radius, max_radius),
|
||
"h": rng.randf_range(min_height, max_height),
|
||
"w": rng.randf_range(speed_range.x, speed_range.y) * (1.0 if rng.randf() > 0.3 else -1.0),
|
||
"a": rng.randf() * TAU,
|
||
"bob": rng.randf() * TAU,
|
||
})
|
||
_mm.set_instance_custom_data(i, Color(rng.randf() * TAU, rng.randf(), 1.0 if rng.randf() < 0.3 else 0.0, 0.0))
|
||
|
||
var mmi := MultiMeshInstance3D.new()
|
||
mmi.name = "Flock"
|
||
mmi.multimesh = _mm
|
||
mmi.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||
mmi.custom_aabb = AABB(Vector3(-400, -50, -400), Vector3(800, 400, 800))
|
||
mmi.top_level = true # координаты глобальные
|
||
add_child(mmi)
|
||
_update(0.0)
|
||
|
||
# Угловатая птица: тело-ромб, хвост и два крыла-«клинка» (вес крыла в цвете вершины)
|
||
func _make_mesh() -> ArrayMesh:
|
||
var st := SurfaceTool.new()
|
||
st.begin(Mesh.PRIMITIVE_TRIANGLES)
|
||
var tris := [
|
||
# [вершина, вес крыла]
|
||
# тело (носом в -Z)
|
||
[Vector3(0, 0, -1.2), 0.0], [Vector3(-0.25, 0, 0.3), 0.0], [Vector3(0.25, 0, 0.3), 0.0],
|
||
[Vector3(0, 0.2, 0.0), 0.0], [Vector3(-0.25, 0, 0.3), 0.0], [Vector3(0, 0, -1.2), 0.0],
|
||
[Vector3(0, 0.2, 0.0), 0.0], [Vector3(0, 0, -1.2), 0.0], [Vector3(0.25, 0, 0.3), 0.0],
|
||
# хвост
|
||
[Vector3(-0.2, 0, 0.3), 0.0], [Vector3(0, 0, 1.4), 0.0], [Vector3(0.2, 0, 0.3), 0.0],
|
||
# левое крыло
|
||
[Vector3(-0.2, 0, -0.5), 0.0], [Vector3(-2.6, 0, 0.5), 1.0], [Vector3(-0.2, 0, 0.4), 0.0],
|
||
# правое крыло
|
||
[Vector3(0.2, 0, -0.5), 0.0], [Vector3(0.2, 0, 0.4), 0.0], [Vector3(2.6, 0, 0.5), 1.0],
|
||
]
|
||
for t in tris:
|
||
st.set_color(Color(t[1], 0, 0))
|
||
st.add_vertex(t[0])
|
||
return st.commit()
|
||
|
||
func _process(delta: float) -> void:
|
||
_update(delta)
|
||
|
||
func _update(delta: float) -> void:
|
||
if _mm == null or target == null:
|
||
return
|
||
var c := target.global_position
|
||
var t := Time.get_ticks_msec() * 0.001
|
||
for i in _orbit.size():
|
||
var o: Dictionary = _orbit[i]
|
||
o["a"] += o["w"] * delta
|
||
var a: float = o["a"]
|
||
var pos := c + Vector3(cos(a) * o["r"], o["h"] + sin(t * 0.7 + o["bob"]) * 3.0, sin(a) * o["r"])
|
||
# Летим по касательной, лёгкий крен в сторону поворота
|
||
var dir := Vector3(-sin(a), 0, cos(a)) * signf(o["w"])
|
||
var b := Basis.looking_at(dir, Vector3.UP)
|
||
b = b.rotated(dir, -0.35 * signf(o["w"])).scaled(Vector3.ONE * bird_scale)
|
||
_mm.set_instance_transform(i, Transform3D(b, pos))
|