Лес на MultiMesh, киберпанк-птицы у башни, левитация обелиска

- Деревья: упрощённая модель, MultiMesh, коллизия стволов
- Птицы кружат вокруг Tower_01 (шейдерные крылья, свечение)
- Обелиск увеличен, поднят и плавно левитирует

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-21 09:29:15 +05:00
co-authored by Claude Sonnet 5.5
parent 4c4d0403cf
commit d759cf6a54
9 changed files with 328 additions and 13 deletions
+116
View File
@@ -0,0 +1,116 @@
@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))