Files
aibrashandClaude Sonnet 5.5 fc0139dc05 Структура проекта: папки по назначению, карты самодостаточны, инструменты, README
- Перенос: Net→net, Player→player, World/*→maps/{hub,sandbox,backrooms}, core, objects,
  models/*→chasers/{rock,verity} и maps/*/models. Имена внутри папок с .glb не менялись
  (рядом лежат извлечённые текстуры). Все пути, пресеты экспорта и автолоады обновлены.
- Преследователи: rock-follow.gd → chasers/chaser.gd; камень в песочнице теперь инстанс
  chasers/rock/rock.tscn (как Верити).
- Удалён мусор: неиспользуемое небо sunflowers, Tree_01.glb, rock-path.gd, старая
  модель Верити, unwrap_cache (теперь в .gitignore).
- Основной пакет игры 70 КБ (камень и Верити уехали в пакеты карт).
- tools/: export_all.sh, serve_web.py (nginx-подобный dev-сервер), deploy_server.sh,
  test/ (боты, временный проект и сервер). Net: флаг --client для headless-клиента.
- deploy/: боевые nginx.conf и my-world.service, project.godot выделенного сервера.
- README переписан: что есть и чего нет, структура, карты, преследователи, сеть, пакеты,
  деплой, грабли, дорожная карта (телефон).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 17:47:47 +05:00

117 lines
4.1 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
# Киберпанк-птицы кружат вокруг цели. Один 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))