Стартовый хаб с порталами, песочница отдельным пакетом, присед

- World/Hub: маленькая стартовая комната (код, без ассетов) с порталами на карты;
  новые карты добавляются записью в PORTALS (влезает 6 порталов).
- Песочница (main.tscn) вынесена в world.pck и качается при входе в портал;
  основной пакет игры вместо 19.7 МБ стал 2.1 МБ. Выходы из карт ведут в хаб.
- Экспорт: три пресета (Web — движок и хаб, World, Backrooms).
- Присед: удерживать C (на бегу Shift+C по-прежнему подкат), скорость 3.5.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-27 16:44:24 +05:00
co-authored by Claude Sonnet 5.5
parent 75aa525cb8
commit 9a4142419d
8 changed files with 224 additions and 10 deletions
+34 -3
View File
@@ -74,6 +74,8 @@ const SLIDE_COOLDOWN = 0.3
const SLIDE_CAMERA_DROP = 0.6 const SLIDE_CAMERA_DROP = 0.6
const SLIDE_HEIGHT_FACTOR = 0.5 const SLIDE_HEIGHT_FACTOR = 0.5
const CROUCH_SPEED = 2.5 # Ползём, если над головой низкий потолок const CROUCH_SPEED = 2.5 # Ползём, если над головой низкий потолок
const CROUCH_WALK_SPEED = 3.5 # Скорость в приседе (держим C; на бегу Shift+C — подкат)
const CROUCH_CAMERA_DROP = 0.6
const MIN_SLIDE_SPEED = 7.0 # Минимальная скорость для начала слайда const MIN_SLIDE_SPEED = 7.0 # Минимальная скорость для начала слайда
# --- Ступеньки --- # --- Ступеньки ---
@@ -123,6 +125,7 @@ var bob_time: float = 0.0
# Состояние подката # Состояние подката
var is_sliding: bool = false var is_sliding: bool = false
var is_crouching: bool = false
var slide_cooldown_timer: float = 0.0 var slide_cooldown_timer: float = 0.0
var slide_dir: Vector3 = Vector3.ZERO var slide_dir: Vector3 = Vector3.ZERO
var slide_speed: float = 0.0 var slide_speed: float = 0.0
@@ -248,7 +251,7 @@ func _unhandled_input(event: InputEvent) -> void:
# Битовые флаги состояния для сетевой синхронизации (см. Net.FLAG_*) # Битовые флаги состояния для сетевой синхронизации (см. Net.FLAG_*)
func get_net_flags() -> int: func get_net_flags() -> int:
var flags := 0 var flags := 0
if is_sliding: flags |= Net.FLAG_SLIDING if is_sliding or is_crouching: flags |= Net.FLAG_SLIDING # У призрака присед выглядит как сплюснутая капсула
if is_wallrunning: flags |= Net.FLAG_WALLRUN if is_wallrunning: flags |= Net.FLAG_WALLRUN
if is_dashing: flags |= Net.FLAG_DASH if is_dashing: flags |= Net.FLAG_DASH
if is_mantling: flags |= Net.FLAG_MANTLE if is_mantling: flags |= Net.FLAG_MANTLE
@@ -277,6 +280,8 @@ func _physics_process(delta: float) -> void:
var space_down := Input.is_key_pressed(KEY_SPACE) var space_down := Input.is_key_pressed(KEY_SPACE)
_update_crouch()
# --- Таймеры прыжка (coyote time + jump buffer) --- # --- Таймеры прыжка (coyote time + jump buffer) ---
if is_on_floor(): if is_on_floor():
coyote_timer = COYOTE_TIME coyote_timer = COYOTE_TIME
@@ -338,7 +343,9 @@ func _physics_process(delta: float) -> void:
if is_wallrunning: if is_wallrunning:
_wall_jump(wall_normal) _wall_jump(wall_normal)
elif coyote_timer > 0.0: elif coyote_timer > 0.0:
if not is_sliding or _can_stand(): if (not is_sliding and not is_crouching) or _can_stand():
if is_crouching:
_end_crouch()
velocity.y = JUMP_VELOCITY velocity.y = JUMP_VELOCITY
jump_buffer_timer = 0.0 jump_buffer_timer = 0.0
coyote_timer = 0.0 coyote_timer = 0.0
@@ -360,7 +367,9 @@ func _physics_process(delta: float) -> void:
# 3. Скорость (Спринт) # 3. Скорость (Спринт)
var current_speed := WALK_SPEED var current_speed := WALK_SPEED
if Input.is_key_pressed(KEY_SHIFT): if is_crouching:
current_speed = CROUCH_WALK_SPEED
elif Input.is_key_pressed(KEY_SHIFT):
current_speed = SPRINT_SPEED current_speed = SPRINT_SPEED
# 4. Бег по стене # 4. Бег по стене
@@ -714,6 +723,7 @@ func _try_start_slide() -> void:
if horizontal.length() < MIN_SLIDE_SPEED: if horizontal.length() < MIN_SLIDE_SPEED:
return return
is_crouching = false
is_sliding = true is_sliding = true
slide_dir = horizontal.normalized() slide_dir = horizontal.normalized()
slide_speed = clamp(max(horizontal.length() * SLIDE_START_MULT, SLIDE_START_SPEED), 0.0, MAX_SPEED) slide_speed = clamp(max(horizontal.length() * SLIDE_START_MULT, SLIDE_START_SPEED), 0.0, MAX_SPEED)
@@ -744,6 +754,23 @@ func _process_slide(delta: float, direction: Vector3, horizontal: Vector3) -> Ve
return direction * CROUCH_SPEED return direction * CROUCH_SPEED
return horizontal.move_toward(Vector3.ZERO, CROUCH_SPEED) return horizontal.move_toward(Vector3.ZERO, CROUCH_SPEED)
# --- ПРИСЕД ---
# Держим C на земле — приседаем. Встаём, когда отпустили, и над головой есть место
func _update_crouch() -> void:
var busy := is_sliding or is_dashing or is_wallrunning or is_mantling or not is_on_floor()
if not is_crouching:
if Input.is_key_pressed(KEY_C) and not busy:
is_crouching = true
_set_collider_crouched(true)
elif (busy or not Input.is_key_pressed(KEY_C)) and (is_sliding or _can_stand()):
_end_crouch()
func _end_crouch() -> void:
is_crouching = false
if not is_sliding:
_set_collider_crouched(false)
func _end_slide() -> void: func _end_slide() -> void:
is_sliding = false is_sliding = false
slide_cooldown_timer = SLIDE_COOLDOWN slide_cooldown_timer = SLIDE_COOLDOWN
@@ -784,6 +811,8 @@ func _update_camera(delta: float, input_dir: Vector2) -> void:
var target_offset := 0.0 var target_offset := 0.0
if is_sliding: if is_sliding:
target_offset = -SLIDE_CAMERA_DROP target_offset = -SLIDE_CAMERA_DROP
elif is_crouching:
target_offset = -CROUCH_CAMERA_DROP
elif is_mantling: elif is_mantling:
target_offset = -0.25 target_offset = -0.25
head_offset_y = lerp(head_offset_y, target_offset, 12.0 * delta) head_offset_y = lerp(head_offset_y, target_offset, 12.0 * delta)
@@ -879,6 +908,8 @@ func die() -> void:
death_timer = DEATH_TIME death_timer = DEATH_TIME
if is_sliding: if is_sliding:
_end_slide() _end_slide()
if is_crouching:
_end_crouch()
is_dashing = false is_dashing = false
is_wallrunning = false is_wallrunning = false
is_mantling = false is_mantling = false
+3 -3
View File
@@ -24,10 +24,10 @@ catch_height = 3.0
sight_limited = true sight_limited = true
[node name="Exit" type="Area3D" parent="." unique_id=1540431614] [node name="Exit" type="Area3D" parent="." unique_id=1540431614]
transform = Transform3D(0, 0, 1, 0, 1, 0, -1, 0, 0, -72, -0.15, 9) transform = Transform3D(0, 0, 1, 0, 1, 0, -1, 0, 0, -92.93203, -0.15, 10.390619)
script = ExtResource("5_portal") script = ExtResource("5_portal")
target_map = "world" target_map = "hub"
target_scene = "res://main.tscn" target_scene = "res://World/Hub/hub.tscn"
title = "EXIT" title = "EXIT"
color = Color(0.3, 1, 0.45, 1) color = Color(0.3, 1, 0.45, 1)
+119
View File
@@ -0,0 +1,119 @@
extends Node3D
# Стартовая комната: ничего тяжёлого, только пол, стены и порталы на карты. Сами карты качаются
# отдельными пакетами при входе в портал (World/map_loader.gd) — поэтому игра стартует быстро.
# Чтобы добавить карту — допиши запись в PORTALS: порталы встанут в ряд по центру (влезает 6).
const PORTALS := [
{"title": "SANDBOX", "map": "world", "scene": "res://main.tscn", "pack": "world.pck", "color": Color(0.2, 0.9, 1.0)},
{"title": "BACKROOMS", "map": "backrooms", "scene": "res://World/Backrooms/backrooms.tscn", "pack": "backrooms.pck", "color": Color(1.0, 0.85, 0.25)},
]
const ROOM_WIDTH := 28.0
const ROOM_DEPTH := 18.0
const ROOM_HEIGHT := 6.0
const WALL := 0.5
const PORTAL_SPACING := 4.5
const GRID_SHADER := """
shader_type spatial;
varying vec3 wpos;
void vertex() { wpos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; }
void fragment() {
vec2 cell = wpos.xz / 2.0;
vec2 g = abs(fract(cell - 0.5) - 0.5) / fwidth(cell);
float line = 1.0 - min(min(g.x, g.y), 1.0);
ALBEDO = mix(vec3(0.03, 0.04, 0.06), vec3(0.1, 0.65, 0.85), line * 0.8);
EMISSION = vec3(0.04, 0.3, 0.4) * line;
ROUGHNESS = 0.9;
}
"""
func _ready() -> void:
Net.enter_map("hub")
_build_room()
_build_portals()
_build_environment()
func _build_room() -> void:
var body := StaticBody3D.new()
body.name = "Room"
add_child(body)
var floor_mat := ShaderMaterial.new()
floor_mat.shader = Shader.new()
floor_mat.shader.code = GRID_SHADER
var wall_mat := StandardMaterial3D.new()
wall_mat.albedo_color = Color(0.09, 0.1, 0.14)
wall_mat.roughness = 1.0
var ceiling_mat := StandardMaterial3D.new()
ceiling_mat.albedo_color = Color(0.05, 0.055, 0.08)
var hw := ROOM_WIDTH * 0.5
var hd := ROOM_DEPTH * 0.5
_add_box(body, Vector3(ROOM_WIDTH + WALL * 2, 0.5, ROOM_DEPTH + WALL * 2), Vector3(0, -0.25, 0), floor_mat)
_add_box(body, Vector3(ROOM_WIDTH + WALL * 2, 0.5, ROOM_DEPTH + WALL * 2), Vector3(0, ROOM_HEIGHT + 0.25, 0), ceiling_mat)
_add_box(body, Vector3(ROOM_WIDTH + WALL * 2, ROOM_HEIGHT, WALL), Vector3(0, ROOM_HEIGHT * 0.5, -hd - WALL * 0.5), wall_mat)
_add_box(body, Vector3(ROOM_WIDTH + WALL * 2, ROOM_HEIGHT, WALL), Vector3(0, ROOM_HEIGHT * 0.5, hd + WALL * 0.5), wall_mat)
_add_box(body, Vector3(WALL, ROOM_HEIGHT, ROOM_DEPTH), Vector3(-hw - WALL * 0.5, ROOM_HEIGHT * 0.5, 0), wall_mat)
_add_box(body, Vector3(WALL, ROOM_HEIGHT, ROOM_DEPTH), Vector3(hw + WALL * 0.5, ROOM_HEIGHT * 0.5, 0), wall_mat)
func _add_box(body: StaticBody3D, size: Vector3, pos: Vector3, mat: Material) -> void:
var shape := BoxShape3D.new()
shape.size = size
var cs := CollisionShape3D.new()
cs.shape = shape
cs.position = pos
body.add_child(cs)
var mesh := BoxMesh.new()
mesh.size = size
mesh.material = mat
var mi := MeshInstance3D.new()
mi.mesh = mesh
mi.position = pos
mi.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
add_child(mi)
# Порталы вдоль северной стены лицом в комнату, по центру
func _build_portals() -> void:
var portal_script := load("res://World/portal.gd")
var z := -ROOM_DEPTH * 0.5 + 1.0
for i in PORTALS.size():
var info: Dictionary = PORTALS[i]
var portal := Area3D.new()
portal.set_script(portal_script)
portal.target_map = info.map
portal.target_scene = info.scene
portal.pack_name = info.pack
portal.title = info.title
portal.color = info.color
portal.position = Vector3((i - (PORTALS.size() - 1) * 0.5) * PORTAL_SPACING, 0.0, z)
add_child(portal)
var label := Label3D.new()
label.text = "CHOOSE A MAP"
label.pixel_size = 0.012
label.font_size = 64
label.outline_size = 12
label.modulate = Color(0.7, 0.9, 1.0)
label.position = Vector3(0, ROOM_HEIGHT - 0.35, -ROOM_DEPTH * 0.5 + 0.1)
add_child(label)
func _build_environment() -> void:
var env := Environment.new()
env.background_mode = Environment.BG_COLOR
env.background_color = Color(0.02, 0.025, 0.04)
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
env.ambient_light_color = Color(0.5, 0.58, 0.75)
env.ambient_light_energy = 0.55
var we := WorldEnvironment.new()
we.environment = env
add_child(we)
var lamp := OmniLight3D.new()
lamp.position = Vector3(0, ROOM_HEIGHT - 0.6, 2.0)
lamp.omni_range = 22.0
lamp.light_energy = 0.8
lamp.light_color = Color(0.8, 0.9, 1.0)
lamp.shadow_enabled = false
add_child(lamp)
+1
View File
@@ -0,0 +1 @@
uid://dac7t22u2s6to
+10
View File
@@ -0,0 +1,10 @@
[gd_scene format=3]
[ext_resource type="Script" path="res://World/Hub/hub.gd" id="1_hub"]
[ext_resource type="PackedScene" uid="uid://c3efk6k8xnf57" path="res://Player/Player.tscn" id="2_player"]
[node name="Hub" type="Node3D"]
script = ExtResource("1_hub")
[node name="Player" parent="." instance=ExtResource("2_player")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.2, 5)
+51 -1
View File
@@ -10,7 +10,7 @@ dedicated_server=false
custom_features="" custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="" include_filter=""
exclude_filter="models/backrooms/*, World/Backrooms/*" exclude_filter="models/backrooms/*, World/Backrooms/*, main.tscn, World/Floor/*, World/Sky/*, World/tron_01.ogg, World/forest.gd, World/birds.gd, World/levitate.gd, World/nav-bake.gd, models/building_02*, models/tower_01/*, models/house_02/*, models/cyber-office/*, models/obelisk_01/*, models/Tree_01/*, models/empire-state/*"
export_path="../grid-world/index.html" export_path="../grid-world/index.html"
patches=PackedStringArray() patches=PackedStringArray()
patch_delta_encoding=false patch_delta_encoding=false
@@ -100,3 +100,53 @@ progressive_web_app/icon_512x512=""
progressive_web_app/background_color=Color(0, 0, 0, 1) progressive_web_app/background_color=Color(0, 0, 0, 1)
threads/emscripten_pool_size=8 threads/emscripten_pool_size=8
threads/godot_pool_size=4 threads/godot_pool_size=4
[preset.2]
name="World"
platform="Web"
dedicated_server=false
custom_features=""
export_filter="resources"
export_files=PackedStringArray("res://main.tscn")
include_filter=""
exclude_filter="Player/*, models/Bochi-Rock/*, World/portal.gd"
export_path="../grid-world/world.pck"
patches=PackedStringArray()
patch_delta_encoding=false
patch_delta_compression_level_zstd=19
patch_delta_min_reduction=0.1
patch_delta_include_filters="*"
patch_delta_exclude_filters=""
encryption_include_filters=""
encryption_exclude_filters=""
seed=0
encrypt_pck=false
encrypt_directory=false
script_export_mode=2
[preset.2.options]
custom_template/debug=""
custom_template/release=""
variant/extensions_support=true
variant/thread_support=false
vram_texture_compression/for_desktop=true
vram_texture_compression/for_mobile=false
html/export_icon=true
html/custom_html_shell=""
html/head_include=""
html/canvas_resize_policy=2
html/focus_canvas_on_start=true
html/experimental_virtual_keyboard=false
progressive_web_app/enabled=false
progressive_web_app/ensure_cross_origin_isolation_headers=true
progressive_web_app/offline_page=""
progressive_web_app/display=3
progressive_web_app/orientation=0
progressive_web_app/icon_144x144=""
progressive_web_app/icon_180x180=""
progressive_web_app/icon_512x512=""
progressive_web_app/background_color=Color(0, 0, 0, 1)
threads/emscripten_pool_size=8
threads/godot_pool_size=4
+5 -2
View File
@@ -135,7 +135,10 @@ transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 188.
script = ExtResource("18_navbake") script = ExtResource("18_navbake")
exclude = Array[NodePath]([NodePath("../Architecture/rock"), NodePath("../CharacterBody3D")]) exclude = Array[NodePath]([NodePath("../Architecture/rock"), NodePath("../CharacterBody3D")])
[node name="BackroomsPortal" type="Area3D" parent="." unique_id=982018721] [node name="HubPortal" type="Area3D" parent="." unique_id=982018721]
transform = Transform3D(-0.49999985, 0, -0.8660255, 0, 1, 0, 0.8660255, 0, -0.49999985, -14.694777, 7.7547903, 22.85205) transform = Transform3D(-0.49999985, 0, -0.8660255, 0, 1, 0, 0.8660255, 0, -0.49999985, -14.694777, 7.7547903, 22.85205)
script = ExtResource("19_portal") script = ExtResource("19_portal")
pack_name = "backrooms.pck" target_map = "hub"
target_scene = "res://World/Hub/hub.tscn"
title = "HUB"
color = Color(0.85, 0.9, 1, 1)
+1 -1
View File
@@ -11,7 +11,7 @@ config_version=5
[application] [application]
config/name="world2" config/name="world2"
run/main_scene="uid://6vc0m7mc73f1" run/main_scene="res://World/Hub/hub.tscn"
config/features=PackedStringArray("4.7", "GL Compatibility") config/features=PackedStringArray("4.7", "GL Compatibility")
config/icon="res://icon.svg" config/icon="res://icon.svg"