Добавил преследование
This commit is contained in:
@@ -98,6 +98,11 @@ const SLIDE_VIGNETTE = 0.45
|
||||
const SPRINT_VIGNETTE = 0.2
|
||||
const VIGNETTE_SPEED = 8.0
|
||||
|
||||
# --- Смерть (камень поймал) ---
|
||||
const DEATH_TIME = 2.5 # Сколько лежим перед возрождением
|
||||
const DEATH_CAMERA_HEIGHT = 0.3 # Камера падает почти на землю
|
||||
const DEATH_ROLL = 75.0 # И заваливается набок (градусы)
|
||||
|
||||
const VIGNETTE_SHADER = """
|
||||
shader_type canvas_item;
|
||||
uniform float intensity : hint_range(0.0, 1.0) = 0.0;
|
||||
@@ -162,8 +167,18 @@ var is_zooming: bool = false
|
||||
|
||||
# Виньетка
|
||||
var vignette_material: ShaderMaterial = null
|
||||
var vignette_rect: ColorRect = null
|
||||
var vignette_value: float = 0.0
|
||||
|
||||
# Смерть
|
||||
signal died
|
||||
signal respawned
|
||||
var is_dead: bool = false
|
||||
var death_timer: float = 0.0
|
||||
var spawn_transform: Transform3D
|
||||
var death_overlay: ColorRect = null
|
||||
var death_label: Label = null
|
||||
|
||||
# Коллайдер (опционально, ищется по имени CollisionShape3D)
|
||||
var collision_shape: CollisionShape3D = null
|
||||
var capsule: CapsuleShape3D = null
|
||||
@@ -188,6 +203,8 @@ func _ready() -> void:
|
||||
default_collision_y = collision_shape.position.y
|
||||
|
||||
_setup_vignette()
|
||||
_setup_death_screen()
|
||||
spawn_transform = global_transform
|
||||
|
||||
func _setup_vignette() -> void:
|
||||
var layer := CanvasLayer.new()
|
||||
@@ -198,6 +215,7 @@ func _setup_vignette() -> void:
|
||||
rect.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
layer.add_child(rect)
|
||||
rect.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
vignette_rect = rect
|
||||
|
||||
var shader := Shader.new()
|
||||
shader.code = VIGNETTE_SHADER
|
||||
@@ -212,6 +230,9 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
|
||||
if is_dead:
|
||||
return
|
||||
|
||||
if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
|
||||
var zoom_factor := head.fov / base_fov
|
||||
rotate_y(-event.relative.x * MOUSE_SENSITIVITY * zoom_factor)
|
||||
@@ -246,6 +267,10 @@ func _get_input_dir() -> Vector2:
|
||||
return input_dir.normalized()
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if is_dead:
|
||||
_process_death(delta)
|
||||
return
|
||||
|
||||
slide_cooldown_timer = maxf(slide_cooldown_timer - delta, 0.0)
|
||||
dash_cooldown_timer = maxf(dash_cooldown_timer - delta, 0.0)
|
||||
wallrun_cooldown_timer = maxf(wallrun_cooldown_timer - delta, 0.0)
|
||||
@@ -812,9 +837,89 @@ func _update_camera(delta: float, input_dir: Vector2) -> void:
|
||||
vignette_value = lerp(vignette_value, target_vignette, VIGNETTE_SPEED * delta)
|
||||
if vignette_material:
|
||||
vignette_material.set_shader_parameter("intensity", vignette_value)
|
||||
# Невидимая виньетка не должна стоить заливки всего экрана (важно для веба)
|
||||
vignette_rect.visible = vignette_value > 0.005
|
||||
|
||||
func _head_bob(time: float) -> Vector3:
|
||||
var pos := Vector3.ZERO
|
||||
pos.y = sin(time * BOB_FREQ) * BOB_AMP
|
||||
pos.x = cos(time * BOB_FREQ / 2) * BOB_AMP
|
||||
return pos
|
||||
|
||||
# --- СМЕРТЬ И ВОЗРОЖДЕНИЕ ---
|
||||
|
||||
func _setup_death_screen() -> void:
|
||||
var layer := CanvasLayer.new()
|
||||
layer.layer = 11
|
||||
add_child(layer)
|
||||
|
||||
death_overlay = ColorRect.new()
|
||||
death_overlay.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
death_overlay.color = Color(0.6, 0.0, 0.0, 0.0)
|
||||
death_overlay.visible = false
|
||||
layer.add_child(death_overlay)
|
||||
death_overlay.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
|
||||
death_label = Label.new()
|
||||
death_label.text = "ПОЙМАЛ!"
|
||||
death_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
death_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
death_label.add_theme_font_size_override("font_size", 96)
|
||||
death_label.add_theme_color_override("font_color", Color.WHITE)
|
||||
death_label.add_theme_constant_override("outline_size", 12)
|
||||
death_label.add_theme_color_override("font_outline_color", Color.BLACK)
|
||||
death_label.visible = false
|
||||
death_overlay.add_child(death_label)
|
||||
death_label.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
|
||||
func die() -> void:
|
||||
if is_dead:
|
||||
return
|
||||
is_dead = true
|
||||
death_timer = DEATH_TIME
|
||||
if is_sliding:
|
||||
_end_slide()
|
||||
is_dashing = false
|
||||
is_wallrunning = false
|
||||
is_mantling = false
|
||||
velocity = Vector3.ZERO
|
||||
shake_strength = 0.25
|
||||
death_label.visible = true
|
||||
death_overlay.visible = true
|
||||
died.emit()
|
||||
|
||||
func _process_death(delta: float) -> void:
|
||||
death_timer -= delta
|
||||
|
||||
# Камера падает и заваливается набок, экран краснеет
|
||||
var k := 1.0 - exp(-6.0 * delta)
|
||||
head.position = head.position.lerp(Vector3(head_base_pos.x, DEATH_CAMERA_HEIGHT, head_base_pos.z), k)
|
||||
head.rotation.z = lerpf(head.rotation.z, deg_to_rad(DEATH_ROLL), k)
|
||||
head.fov = lerpf(head.fov, base_fov, k)
|
||||
shake_strength = lerpf(shake_strength, 0.0, 10.0 * delta)
|
||||
death_overlay.color.a = lerpf(death_overlay.color.a, 0.55, k)
|
||||
|
||||
# Падаем на землю, если умерли в воздухе
|
||||
velocity.x = 0.0
|
||||
velocity.z = 0.0
|
||||
if not is_on_floor():
|
||||
velocity += get_gravity() * delta
|
||||
move_and_slide()
|
||||
|
||||
if death_timer <= 0.0:
|
||||
_respawn()
|
||||
|
||||
func _respawn() -> void:
|
||||
global_transform = spawn_transform
|
||||
velocity = Vector3.ZERO
|
||||
head.position = head_base_pos
|
||||
head.rotation = Vector3.ZERO
|
||||
current_roll = 0.0
|
||||
head_offset_y = 0.0
|
||||
land_offset = 0.0
|
||||
step_offset = 0.0
|
||||
death_overlay.color.a = 0.0
|
||||
death_label.visible = false
|
||||
death_overlay.visible = false
|
||||
is_dead = false
|
||||
respawned.emit()
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
extends NavigationRegion3D
|
||||
# Запекает навмеш при старте игры по статическим коллизиям уровня (пол, здания, деревья).
|
||||
|
||||
const SOURCE_GROUP := "nav_source"
|
||||
|
||||
@export var exclude: Array[NodePath] = [] # Узлы, которые не должны попасть в навмеш (например, сам камень)
|
||||
@export var bake_half_size := 300.0 # Половина стороны квадрата запекания вокруг центра мира
|
||||
@export var cell_size := 1.0 # Крупная ячейка — быстрое запекание (в вебе нет потоков)
|
||||
@export var agent_radius := 2.0
|
||||
@export var agent_height := 5.0
|
||||
|
||||
func _ready() -> void:
|
||||
var nm := NavigationMesh.new()
|
||||
nm.cell_size = cell_size
|
||||
nm.cell_height = 0.25
|
||||
nm.agent_radius = agent_radius
|
||||
nm.agent_height = agent_height
|
||||
nm.agent_max_climb = 0.5
|
||||
nm.geometry_parsed_geometry_type = NavigationMesh.PARSED_GEOMETRY_STATIC_COLLIDERS
|
||||
nm.geometry_source_geometry_mode = NavigationMesh.SOURCE_GEOMETRY_GROUPS_WITH_CHILDREN
|
||||
nm.geometry_source_group_name = SOURCE_GROUP
|
||||
nm.filter_baking_aabb = AABB(Vector3(-bake_half_size, -10, -bake_half_size), Vector3(bake_half_size * 2, 60, bake_half_size * 2))
|
||||
navigation_mesh = nm
|
||||
|
||||
var map := get_world_3d().navigation_map
|
||||
NavigationServer3D.map_set_cell_size(map, nm.cell_size)
|
||||
NavigationServer3D.map_set_cell_height(map, nm.cell_height)
|
||||
|
||||
# Ждём кадр, чтобы процедурные узлы (лес) успели создать коллизии
|
||||
await get_tree().process_frame
|
||||
var excluded: Array[Node] = []
|
||||
for p in exclude:
|
||||
var n := get_node_or_null(p)
|
||||
if n:
|
||||
excluded.append(n)
|
||||
_mark_sources(get_parent(), excluded)
|
||||
bake_navigation_mesh(true)
|
||||
|
||||
# Помечает всё дерево сцены как источник геометрии, обходя исключённые узлы
|
||||
func _mark_sources(node: Node, excluded: Array[Node]) -> void:
|
||||
for c in node.get_children():
|
||||
if c == self or c in excluded:
|
||||
continue
|
||||
if excluded.any(func(e): return c.is_ancestor_of(e)):
|
||||
_mark_sources(c, excluded)
|
||||
else:
|
||||
c.add_to_group(SOURCE_GROUP)
|
||||
@@ -0,0 +1 @@
|
||||
uid://c2k1kj4a2s2su
|
||||
Binary file not shown.
+2
-2
@@ -10,7 +10,7 @@ dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
exclude_filter="World/Sky/sunflowers_puresky_2k.hdr, models/Tree_01/Tree_01.glb"
|
||||
export_path="../grid-world/index.html"
|
||||
patches=PackedStringArray()
|
||||
patch_delta_encoding=false
|
||||
@@ -29,7 +29,7 @@ script_export_mode=2
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
variant/extensions_support=true
|
||||
variant/extensions_support=false
|
||||
variant/thread_support=false
|
||||
vram_texture_compression/for_desktop=true
|
||||
vram_texture_compression/for_mobile=false
|
||||
|
||||
@@ -10,20 +10,14 @@
|
||||
[ext_resource type="AudioStream" uid="uid://ddsynauqeoppw" path="res://World/tron_01.ogg" id="7_272bh"]
|
||||
[ext_resource type="AudioStream" uid="uid://280pvcvxt2n2" path="res://models/Bochi-Rock/Guitar.ogg" id="7_dg77c"]
|
||||
[ext_resource type="PackedScene" uid="uid://evml2s8rmerw" path="res://models/obelisk_01/Obelisk.glb" id="9_kek77"]
|
||||
[ext_resource type="Script" uid="uid://b3ckck8y0rby6" path="res://models/Bochi-Rock/rock-path.gd" id="10_4c57u"]
|
||||
[ext_resource type="Script" uid="uid://o2kqd0cy4knr" path="res://models/Bochi-Rock/rock-follow.gd" id="10_4c57u"]
|
||||
[ext_resource type="PackedScene" uid="uid://cowp33ejxcfcl" path="res://models/Bochi-Rock/Rock.glb" id="11_efxa6"]
|
||||
[ext_resource type="Script" uid="uid://cx16mgkh4bldx" path="res://World/forest.gd" id="12_forest"]
|
||||
[ext_resource type="PackedScene" uid="uid://b6d4btx7xn51j" path="res://models/Tree_01/Tree_01_low.glb" id="13_treelow"]
|
||||
[ext_resource type="Script" uid="uid://j5ultotjrstk" path="res://World/birds.gd" id="14_birds"]
|
||||
[ext_resource type="Script" uid="uid://chtck5eaaddrw" path="res://World/levitate.gd" id="15_levitate"]
|
||||
[ext_resource type="PackedScene" uid="uid://bmprcwbt78ghq" path="res://models/empire-state/Empire-State.glb" id="17_yaehf"]
|
||||
|
||||
[sub_resource type="Curve3D" id="Curve3D_dg77c"]
|
||||
_data = {
|
||||
"points": PackedVector3Array(0, 0, 0, 0, 0, 0, 5.2467575, -0.0029411316, 93.87327, 0, 0, 0, 0, 0, 0, 13.789459, -0.0029335022, 97.604256, 0, 0, 0, 0, 0, 0, 24.239388, -0.0029335022, 102.18522, 0, 0, 0, 0, 0, 0, 37.65388, -0.0029335022, 101.72713, 0, 0, 0, 0, 0, 0, 49.610317, -0.002937317, 93.816925, 0, 0, 0, 0, 0, 0, 54.721954, -0.002937317, 83.662735, 0, 0, 0, 0, 0, 0, 60.17699, -0.002937317, 67.371086, 0, 0, 0, 0, 0, 0, 63.19221, -0.002937317, 50.906624, 0, 0, 0, 0, 0, 0, 63.427063, -0.002937317, 36.6671, 0, 0, 0, 0, 0, 0, 60.360424, -0.002937317, 23.65346, 0, 0, 0, 0, 0, 0, 51.587524, -0.002937317, 10.339436, 0, 0, 0, 0, 0, 0, 37.92818, -0.002937317, -5.663192, 0, 0, 0, 0, 0, 0, 30.353395, -0.0029411316, -11.97097, 0, 0, 0, 0, 0, 0, 24.506142, -0.002937317, -11.578466, 0, 0, 0, 0, 0, 0, 23.492048, -0.0029411316, -8.094779, 0, 0, 0, 0, 0, 0, 26.842072, -0.0029411316, -2.955284, 0, 0, 0, 0, 0, 0, 30.540104, -0.002937317, -0.6889343, 0, 0, 0, 0, 0, 0, 32.266655, -0.0029411316, 7.519619, 0, 0, 0, 0, 0, 0, 29.88159, -0.002937317, 11.079437, 0, 0, 0, 0, 0, 0, 23.867472, -0.002937317, 15.516203, 0, 0, 0, 0, 0, 0, 20.547432, -0.0029411316, 15.771803, 0, 0, 0, 0, 0, 0, 17.486126, -0.0029411316, 10.689701, 0, 0, 0, 0, 0, 0, 12.567432, -0.0029411316, 9.993235, 0, 0, 0, 0, 0, 0, 8.265545, -0.002937317, 16.372534, 0, 0, 0, 0, 0, 0, 1.9048309, -0.002937317, 26.435099, 0, 0, 0, 0, 0, 0, -9.015602, -0.002937317, 34.162575, 0, 0, 0, 0, 0, 0, -19.431889, -0.002937317, 44.664474, 0, 0, 0, 0, 0, 0, -23.387104, -0.002937317, 59.80117, 0, 0, 0, 0, 0, 0, -21.00288, -0.0029335022, 71.96566, 0, 0, 0, 0, 0, 0, -12.79343, -0.0029335022, 78.17484, 0, 0, 0, 0, 0, 0, -3.5642548, -0.002937317, 81.15604, 0, 0, 0, 0, 0, 0, 5.206661, -0.002937317, 82.6712),
|
||||
"tilts": PackedFloat32Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
|
||||
}
|
||||
point_count = 32
|
||||
[ext_resource type="Script" uid="uid://c2k1kj4a2s2su" path="res://World/nav-bake.gd" id="18_navbake"]
|
||||
|
||||
[sub_resource type="PanoramaSkyMaterial" id="PanoramaSkyMaterial_6opy2"]
|
||||
panorama = ExtResource("2_5vw27")
|
||||
@@ -67,20 +61,21 @@ script = ExtResource("15_levitate")
|
||||
amplitude = 5.0
|
||||
period = 8.0
|
||||
|
||||
[node name="rock" type="Path3D" parent="Architecture" unique_id=1419147983]
|
||||
curve = SubResource("Curve3D_dg77c")
|
||||
|
||||
[node name="PathFollow3D" type="PathFollow3D" parent="Architecture/rock" unique_id=2095285038]
|
||||
transform = Transform3D(-0.4002386, -7.500245e-07, -0.916411, 5.684342e-14, 1, -8.184369e-07, 0.916411, -3.275701e-07, -0.4002386, 5.2467575, -0.0029411316, 93.87327)
|
||||
[node name="rock" type="Node3D" parent="Architecture" unique_id=1419147983]
|
||||
transform = Transform3D(-0.4002386, 0, -0.916411, 0, 1, 0, 0.916411, 0, -0.4002386, 5.2467575, -0.0029411316, 93.87327)
|
||||
script = ExtResource("10_4c57u")
|
||||
|
||||
[node name="Rock" parent="Architecture/rock/PathFollow3D" unique_id=1431376827 instance=ExtResource("11_efxa6")]
|
||||
[node name="Rock" parent="Architecture/rock" unique_id=1431376827 instance=ExtResource("11_efxa6")]
|
||||
|
||||
[node name="AudioStreamPlayer3D" type="AudioStreamPlayer3D" parent="Architecture/rock/PathFollow3D/Rock" unique_id=912581689]
|
||||
[node name="AudioStreamPlayer3D" type="AudioStreamPlayer3D" parent="Architecture/rock/Rock" unique_id=912581689]
|
||||
stream = ExtResource("7_dg77c")
|
||||
autoplay = true
|
||||
max_distance = 30.0
|
||||
|
||||
[node name="NavigationAgent3D" type="NavigationAgent3D" parent="Architecture/rock" unique_id=1616493785]
|
||||
height = 5.0
|
||||
radius = 2.0
|
||||
|
||||
[node name="cyber_office" parent="Architecture" unique_id=1003431047 instance=ExtResource("1_2cqfq")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6.9035664, -0.2606399, -24.004528)
|
||||
|
||||
@@ -124,7 +119,11 @@ max_height = 55.0
|
||||
bird_scale = 0.9
|
||||
|
||||
[node name="CharacterBody3D" parent="." unique_id=365865796 instance=ExtResource("2_h2yge")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.0830812, 2.6370785, 0.44329834)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.0830812, 5.621685, 0.44329834)
|
||||
|
||||
[node name="Empire-State" parent="." unique_id=913942978 instance=ExtResource("17_yaehf")]
|
||||
transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 188.18842, -0.0029239655, 0.5043459)
|
||||
|
||||
[node name="Navigation" type="NavigationRegion3D" parent="." unique_id=1471038126]
|
||||
script = ExtResource("18_navbake")
|
||||
exclude = Array[NodePath]([NodePath("../Architecture/rock"), NodePath("../CharacterBody3D")])
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,69 @@
|
||||
extends Node3D
|
||||
# Камень преследует локального игрока, обходя препятствия по навмешу, и ловит его.
|
||||
|
||||
@export var speed := 12.0 # Скорость движения
|
||||
@export var turn_speed := 6.0 # Как быстро разворачивается к игроку
|
||||
@export var catch_distance := 3.0 # С какого расстояния (по горизонтали) ловит игрока
|
||||
@export var catch_height := 4.0 # Игрока выше этого над камнем не достать
|
||||
@export var repath_distance := 1.0 # Пересчитываем путь, когда игрок сместился на столько
|
||||
|
||||
@onready var agent: NavigationAgent3D = $NavigationAgent3D
|
||||
@onready var start_transform := global_transform
|
||||
|
||||
var player: Node3D = null
|
||||
|
||||
func _ready() -> void:
|
||||
agent.target_desired_distance = 1.0
|
||||
agent.path_desired_distance = 1.5
|
||||
agent.path_height_offset = 0.25 # Навмеш лежит чуть выше пола
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if player == null:
|
||||
player = get_tree().get_first_node_in_group("local_player") as Node3D
|
||||
if player == null:
|
||||
return
|
||||
if player.has_signal("respawned"):
|
||||
player.respawned.connect(_on_player_respawned)
|
||||
|
||||
# Пока игрок лежит — стоим над ним
|
||||
if player.get("is_dead"):
|
||||
return
|
||||
|
||||
var flat := player.global_position - global_position
|
||||
var dy := flat.y
|
||||
flat.y = 0.0
|
||||
if flat.length() <= catch_distance and absf(dy) <= catch_height:
|
||||
_face(flat, delta)
|
||||
if player.has_method("die"):
|
||||
player.die()
|
||||
return
|
||||
|
||||
var step: Vector3
|
||||
var map := agent.get_navigation_map()
|
||||
if NavigationServer3D.map_get_iteration_id(map) > 0:
|
||||
if agent.target_position.distance_to(player.global_position) > repath_distance:
|
||||
agent.target_position = player.global_position
|
||||
if not agent.is_navigation_finished():
|
||||
step = agent.get_next_path_position() - global_position
|
||||
|
||||
# Навмеш ещё не готов или игрок вне его — идём напрямую
|
||||
if step == Vector3.ZERO:
|
||||
step = flat
|
||||
|
||||
var d := step.length()
|
||||
if d < 0.001:
|
||||
return
|
||||
global_position += step / d * min(speed * delta, d)
|
||||
_face(Vector3(step.x, 0.0, step.z), delta)
|
||||
|
||||
# Игрок возродился — возвращаемся на старт, чтобы не поймать его сразу снова
|
||||
func _on_player_respawned() -> void:
|
||||
global_transform = start_transform
|
||||
agent.target_position = global_position
|
||||
|
||||
# Плавный разворот: модель смотрит вперёд по -Z
|
||||
func _face(dir: Vector3, delta: float) -> void:
|
||||
if dir.length_squared() < 0.0001:
|
||||
return
|
||||
var target_yaw := atan2(-dir.x, -dir.z)
|
||||
rotation.y = lerp_angle(rotation.y, target_yaw, 1.0 - exp(-turn_speed * delta))
|
||||
@@ -0,0 +1 @@
|
||||
uid://o2kqd0cy4knr
|
||||
@@ -33,3 +33,5 @@ window/stretch/aspect="expand"
|
||||
rendering_device/driver.windows="d3d12"
|
||||
renderer/rendering_method="gl_compatibility"
|
||||
renderer/rendering_method.mobile="gl_compatibility"
|
||||
lights_and_shadows/directional_shadow/size=2048
|
||||
lights_and_shadows/directional_shadow/soft_shadow_filter_quality=1
|
||||
|
||||
Reference in New Issue
Block a user