Структура проекта: папки по назначению, карты самодостаточны, инструменты, 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>
This commit is contained in:
@@ -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))
|
||||
Reference in New Issue
Block a user