README: заметки для будущего меня; тестовые сцены в tools/test
- Договорённости с владельцем, замеры мувсета (стенами высоту не набрать), исправление подтягивания, конвейер Parkour и его грабли, ловушка с ресурсами, загружаемыми по строке - tools/test: measure_movement, mantle_test, parkour_gaps, parkour_tour, parkour_check.txt Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WxZo3ekR5m1af437Qzqwc1
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
extends Node
|
||||
# Подтягивание на уступы: тонкая полка и сплошной блок, коллизия «коробка» и «меш», ходьба и спринт, высоты 1,2–2,7 м.
|
||||
# Запуск: godot --headless --path . res://tools/test/mantle_test.tscn -- --client (ожидание: везде «ДА»)
|
||||
var root3d: Node3D
|
||||
var p
|
||||
# kind: "thin" (полка 0.6 м, под ней пусто) или "block" (сплошной блок от пола); shape: "box" или "mesh"
|
||||
func build(kind: String, shape: String, h: float) -> void:
|
||||
if root3d: root3d.queue_free()
|
||||
await get_tree().physics_frame
|
||||
root3d = Node3D.new(); add_child(root3d)
|
||||
var sb := StaticBody3D.new(); root3d.add_child(sb)
|
||||
var fl := CollisionShape3D.new(); var fb := BoxShape3D.new(); fb.size = Vector3(60, 1, 60); fl.shape = fb; fl.position = Vector3(0, -0.5, 0); sb.add_child(fl)
|
||||
var size := Vector3(10, 0.6, 12) if kind == "thin" else Vector3(10, h, 12)
|
||||
var cy := h - 0.3 if kind == "thin" else h * 0.5
|
||||
var cs := CollisionShape3D.new()
|
||||
if shape == "box":
|
||||
var b := BoxShape3D.new(); b.size = size; cs.shape = b
|
||||
else:
|
||||
var m := BoxMesh.new(); m.size = size; cs.shape = m.create_trimesh_shape()
|
||||
cs.position = Vector3(0, cy, -8 - size.z * 0.5) # передняя грань на z=-8
|
||||
sb.add_child(cs)
|
||||
func trial(kind: String, shape: String, h: float, sprint: bool) -> String:
|
||||
await build(kind, shape, h)
|
||||
p = (load("res://player/player.tscn") as PackedScene).instantiate()
|
||||
root3d.add_child(p)
|
||||
p.global_position = Vector3(0, 1.17, 0)
|
||||
await get_tree().physics_frame
|
||||
Input.action_press("move_forward")
|
||||
if sprint: Input.action_press("sprint")
|
||||
var f := 0; var jumped := false; var ok := false
|
||||
while f < 420:
|
||||
await get_tree().physics_frame; f += 1
|
||||
var dz: float = p.global_position.z + 8.0 # до грани
|
||||
if not jumped and dz < (-1.0 if not sprint else 0.0) + 2.4 and p.is_on_floor() and f > 30:
|
||||
jumped = true; Input.action_press("jump")
|
||||
if jumped and p.is_on_floor() and p.global_position.z < -8.2 and absf(p.global_position.y - (h + 1.16)) < 0.35:
|
||||
ok = true; break
|
||||
for a in ["move_forward","sprint","jump"]: Input.action_release(a)
|
||||
var res := "ДА " if ok else "нет"
|
||||
p.queue_free()
|
||||
return res
|
||||
func _ready() -> void:
|
||||
print("высота уступа: 1.2 1.6 2.0 2.4 2.7")
|
||||
for kind in ["thin", "block"]:
|
||||
for shape in ["box", "mesh"]:
|
||||
for sprint in [false, true]:
|
||||
var row := "%-5s %-4s %-6s " % [kind, shape, "спринт" if sprint else "ходьба"]
|
||||
for h in [1.2, 1.6, 2.0, 2.4, 2.7]:
|
||||
row += await trial(kind, shape, h, sprint) + " "
|
||||
print(row)
|
||||
get_tree().quit()
|
||||
@@ -0,0 +1 @@
|
||||
uid://c1vd8asub3ox6
|
||||
@@ -0,0 +1,6 @@
|
||||
[gd_scene format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tools/test/mantle_test.gd" id="1"]
|
||||
|
||||
[node name="T" type="Node"]
|
||||
script = ExtResource("1")
|
||||
@@ -0,0 +1,98 @@
|
||||
extends Node
|
||||
# Замеры мувсета ботом: дальность прыжка / двойного / рывка на спринте, бег по стене, прыжок от стены.
|
||||
# Запуск: godot --headless --path . res://tools/test/measure_movement.tscn -- --client (~1 мин, печатает таблицу)
|
||||
# Замеры мувсета: дальность прыжков, рывок, бег по стене, прыжок от стены
|
||||
var root3d: Node3D
|
||||
var p
|
||||
func world(with_floor: bool, wall: bool) -> void:
|
||||
if root3d: root3d.queue_free()
|
||||
await get_tree().physics_frame
|
||||
root3d = Node3D.new(); add_child(root3d)
|
||||
var sb := StaticBody3D.new(); root3d.add_child(sb)
|
||||
if with_floor:
|
||||
var cs := CollisionShape3D.new(); var b := BoxShape3D.new(); b.size = Vector3(400, 1, 800); cs.shape = b; cs.position = Vector3(0, -0.5, -300); sb.add_child(cs)
|
||||
if wall:
|
||||
var cs2 := CollisionShape3D.new(); var b2 := BoxShape3D.new(); b2.size = Vector3(1, 400, 800); cs2.shape = b2; cs2.position = Vector3(-4.5, 0, -300); sb.add_child(cs2)
|
||||
var cs3 := CollisionShape3D.new(); var b3 := BoxShape3D.new(); b3.size = Vector3(1, 400, 800); cs3.shape = b3; cs3.position = Vector3(4.5, 0, -300); sb.add_child(cs3)
|
||||
func spawn(pos: Vector3, vel := Vector3.ZERO) -> void:
|
||||
p = (load("res://player/player.tscn") as PackedScene).instantiate()
|
||||
root3d.add_child(p)
|
||||
p.global_position = pos
|
||||
p.velocity = vel
|
||||
func press(a: String) -> void: Input.action_press(a)
|
||||
func release(a: String) -> void: Input.action_release(a)
|
||||
func tap_dash() -> void:
|
||||
var e := InputEventAction.new(); e.action = "dash"; e.pressed = true
|
||||
Input.parse_input_event(e)
|
||||
func release_all() -> void:
|
||||
for a in ["move_forward","move_left","move_right","sprint","jump","crouch","dash"]: Input.action_release(a)
|
||||
func frames(n: int) -> void:
|
||||
for i in n: await get_tree().physics_frame
|
||||
|
||||
# прыжок с разбега по ровному полу: variant: single / dj / dash / djdash / dashdj
|
||||
func jump_exp(variant: String) -> void:
|
||||
await world(true, false)
|
||||
spawn(Vector3(0, 1.17, 0))
|
||||
await frames(5)
|
||||
press("move_forward"); press("sprint")
|
||||
await frames(60)
|
||||
var z0: float = p.global_position.z
|
||||
press("jump")
|
||||
var maxy: float = 0.0; var f := 0; var did2 := false; var did3 := false; var lz := 0.0
|
||||
while f < 400:
|
||||
await get_tree().physics_frame; f += 1
|
||||
maxy = maxf(maxy, p.global_position.y - 1.17)
|
||||
if not did2 and p.velocity.y <= 0.0 and not p.is_on_floor():
|
||||
did2 = true
|
||||
if variant in ["dj", "djdash"]:
|
||||
release("jump"); await get_tree().physics_frame; press("jump")
|
||||
elif variant in ["dash", "dashdj"]:
|
||||
tap_dash()
|
||||
if did2 and not did3 and f > 40 and variant in ["djdash", "dashdj"]:
|
||||
did3 = true
|
||||
if variant == "djdash": tap_dash()
|
||||
else: release("jump"); await get_tree().physics_frame; press("jump")
|
||||
if f > 10 and p.is_on_floor():
|
||||
lz = p.global_position.z; break
|
||||
release_all()
|
||||
print("прыжок %-7s: дальность %.1f м, высота %.2f м, время %.2f с" % [variant, z0 - lz, maxy, f / 60.0])
|
||||
p.queue_free()
|
||||
|
||||
# бег по стене: летим вдоль левой стены со скоростью v, стена слева
|
||||
func wall_exp(v: float, jump_after: float, then: String) -> void:
|
||||
await world(false, true)
|
||||
spawn(Vector3(-3.4, 100, 0), Vector3(0, 0, -v))
|
||||
press("move_forward")
|
||||
var z0: float = p.global_position.z
|
||||
var f := 0; var run_f := 0; var started := false; var y_start := 0.0; var z_start := 0.0; var jumped := false; var x_max := -99.0; var z_j := 0.0; var y_j := 0.0
|
||||
var landed_x := 0.0; var landed_z := 0.0; var t_j := 0
|
||||
while f < 500:
|
||||
await get_tree().physics_frame; f += 1
|
||||
if p.is_wallrunning:
|
||||
if not started: started = true; y_start = p.global_position.y; z_start = p.global_position.z
|
||||
run_f += 1
|
||||
if started and not jumped and (run_f / 60.0) >= jump_after:
|
||||
jumped = true; z_j = p.global_position.z; y_j = p.global_position.y; t_j = f
|
||||
release("move_forward"); press("move_forward")
|
||||
press("jump"); await get_tree().physics_frame; release("jump")
|
||||
if then == "dash": await frames(15); tap_dash()
|
||||
if then == "dj": await frames(25); press("jump"); await get_tree().physics_frame; release("jump")
|
||||
if jumped:
|
||||
x_max = maxf(x_max, p.global_position.x)
|
||||
if p.global_position.y < y_j - 3.0:
|
||||
landed_x = p.global_position.x; landed_z = p.global_position.z; break
|
||||
if not jumped and started and not p.is_wallrunning and f > 30:
|
||||
break
|
||||
release_all()
|
||||
if jump_after < 0: pass
|
||||
print("стена v=%.0f: бежали %.2f с, %.1f м вдоль, провал по y %.2f м; прыжок от стены (после %.1fс, затем %s): отлёт от стены до x=%.1f (стена x=-4), вперёд %.1f м, за %.2f с" % [v, run_f / 60.0, (z_start - (z_j if jumped else p.global_position.z)), y_start - (y_j if jumped else p.global_position.y), jump_after, then, x_max, z_j - landed_z, (f - t_j) / 60.0])
|
||||
p.queue_free()
|
||||
|
||||
func _ready() -> void:
|
||||
for v in ["single", "dj", "dash", "djdash", "dashdj"]:
|
||||
await jump_exp(v)
|
||||
await wall_exp(13.0, 99.0, "none")
|
||||
await wall_exp(13.0, 0.5, "none")
|
||||
await wall_exp(13.0, 0.5, "dash")
|
||||
await wall_exp(13.0, 0.5, "dj")
|
||||
get_tree().quit()
|
||||
@@ -0,0 +1 @@
|
||||
uid://ddgc0duo4j3mq
|
||||
@@ -0,0 +1,6 @@
|
||||
[gd_scene format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tools/test/measure_movement.gd" id="1"]
|
||||
|
||||
[node name="T" type="Node"]
|
||||
script = ExtResource("1")
|
||||
@@ -0,0 +1,22 @@
|
||||
# Проверка карты Parkour «по существу» после прохода через портал с настоящими пакетами.
|
||||
# Вставь в tools/test/bot.gd ВРЕМЕННОЙ копии проекта (make_test_project.sh) перед строкой print("[%s] t=%4.1f сцена=...")
|
||||
# и запусти: godot --headless --main-pack <site>/index.pck -- --client --name=T --pack-url=http://127.0.0.1:<порт>/ --tp=2:4.5,1,-8
|
||||
# Ожидание: скрипт=true, шейдерных_мешей>=1, небо_панорама=true, музыка_играет=true, лазеров_и_ворот=10.
|
||||
# Если в пакете не хватает файла, вместо этого будут false/0 (а имя сцены всё равно «Parkour»).
|
||||
|
||||
var sc := get_tree().current_scene
|
||||
if sc != null and sc.name == "Parkour" and int(t) % 3 == 0:
|
||||
var shaded := 0
|
||||
for mi in sc.find_children("*", "MeshInstance3D", true, false):
|
||||
if mi.get_surface_override_material_count() > 0 and mi.get_surface_override_material(0) is ShaderMaterial:
|
||||
shaded += 1
|
||||
var music := false
|
||||
var areas := 0
|
||||
for n in sc.get_children():
|
||||
if n is AudioStreamPlayer and n.stream is AudioStreamOggVorbis and n.playing:
|
||||
music = true
|
||||
if n is Area3D and n.get_child_count() == 2:
|
||||
areas += 1
|
||||
var env := sc.find_children("*", "WorldEnvironment", false, false)
|
||||
var sky_ok: bool = env.size() > 0 and env[0].environment.sky != null and env[0].environment.sky.sky_material is PanoramaSkyMaterial and env[0].environment.sky.sky_material.panorama != null
|
||||
print(" ПРОВЕРКА Parkour: скрипт=%s шейдерных_мешей=%d небо_панорама=%s музыка_играет=%s лазеров_и_ворот=%d" % [sc.get_script() != null, shaded, sky_ok, music, areas])
|
||||
@@ -0,0 +1,68 @@
|
||||
extends Node
|
||||
# Проходимость пропасти контейнеров на настоящей карте Parkour: прыжок у края, двойной прыжок на спуске, подруливание к цели.
|
||||
# Запуск: godot --headless --path . res://tools/test/parkour_gaps.tscn -- --client (ожидание: все зазоры «ДА»)
|
||||
# Стартовые точки заданы в коде — после правки layout.py обнови их
|
||||
var level
|
||||
var p
|
||||
var f := 0
|
||||
# (имя, x, z_старта(центр), z_края, ожидаемая посадка z-диапазон, высота пола)
|
||||
var cases := [
|
||||
["k0->k1 (18 м)", 4.0, -465.0, -475.0, 45.0],
|
||||
["k2->k3 (22 м)", 2.0, -544.0, -549.0, 48.0] ,
|
||||
]
|
||||
func _ready() -> void:
|
||||
level = (load("res://maps/parkour/parkour.tscn") as PackedScene).instantiate()
|
||||
add_child(level)
|
||||
p = level.get_node("Player")
|
||||
p.set_physics_process(false)
|
||||
await get_tree().physics_frame
|
||||
for variant in ["dj", "dash+dj"]:
|
||||
# имя, x старта, пол старта, z старта, z края, пол цели, z цели ближний, дальний, x цели
|
||||
for c in [
|
||||
["exit->k1 18", 4.0, 44.0, -446.0, -457.7, 45.0, -476.0, -488.0, 5.0],
|
||||
["k1->k2 16", 0.0, 45.0, -479.0, -487.7, 46.5, -504.0, -516.0, -4.0],
|
||||
["k2->k3 22", -2.5, 46.5, -507.0, -515.7, 48.0, -538.0, -550.0, -2.5],
|
||||
["k3->k4 24", 7.0, 48.0, -541.0, -549.7, 49.0, -574.0, -586.0, 7.5],
|
||||
["k4->k5 20", 7.0, 49.0, -577.0, -585.7, 50.5, -606.0, -618.0, 2.0],
|
||||
["k5->k6 22", 2.0, 50.5, -609.0, -617.7, 52.0, -640.0, -656.0, 0.0]]:
|
||||
await trial(c, variant)
|
||||
get_tree().quit()
|
||||
func key(a: String, down: bool) -> void:
|
||||
if down: Input.action_press(a)
|
||||
else: Input.action_release(a)
|
||||
func trial(c: Array, variant: String) -> void:
|
||||
p.set_physics_process(true)
|
||||
p.global_position = Vector3(c[1], c[2] + 1.18, c[3])
|
||||
p.velocity = Vector3.ZERO
|
||||
p.is_dead = false
|
||||
await get_tree().physics_frame
|
||||
key("move_forward", true); key("sprint", true)
|
||||
var fr := 0; var jumped := false; var did := 0; var res := "промах"
|
||||
while fr < 360:
|
||||
await get_tree().physics_frame; fr += 1
|
||||
if not jumped and p.is_on_floor() and p.global_position.z < c[4] + 1.0 and fr > 20:
|
||||
jumped = true; key("jump", true); did = 0
|
||||
if jumped and not p.is_on_floor(): # подруливание к центру цели
|
||||
var dx: float = c[8] - p.global_position.x
|
||||
key("move_left", dx < -0.6); key("move_right", dx > 0.6)
|
||||
if jumped and did == 0 and not p.is_on_floor() and p.velocity.y < 0.0:
|
||||
did = fr
|
||||
if variant == "dj" or variant == "dj+dash": key("jump", false); await get_tree().physics_frame; key("jump", true)
|
||||
if variant == "dash+dj":
|
||||
var e := InputEventAction.new(); e.action = "dash"; e.pressed = true; Input.parse_input_event(e)
|
||||
if jumped and did > 0 and variant == "dj+dash" and fr == did + 8:
|
||||
var e2 := InputEventAction.new(); e2.action = "dash"; e2.pressed = true; Input.parse_input_event(e2)
|
||||
if jumped and did > 0 and variant == "dash+dj" and fr == did + 14:
|
||||
key("jump", false); await get_tree().physics_frame; key("jump", true)
|
||||
if jumped and fr > did + 25 and did > 0 and p.is_on_floor():
|
||||
var z: float = p.global_position.z
|
||||
if z < c[6] + 0.5 and z > c[7] - 0.5 and absf(p.global_position.y - (c[5] + 1.16)) < 0.3:
|
||||
res = "ДА (z=%.1f)" % z
|
||||
else:
|
||||
res = "упал/на старте (z=%.1f y=%.1f)" % [z, p.global_position.y]
|
||||
break
|
||||
if p.global_position.y < c[2] - 25.0:
|
||||
res = "упал вниз"; break
|
||||
for a in ["move_forward","sprint","jump","move_left","move_right"]: key(a, false)
|
||||
print("%-8s %-10s %s" % [variant, c[0], res])
|
||||
p.set_physics_process(false)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cfy83ai6f6hw5
|
||||
@@ -0,0 +1,6 @@
|
||||
[gd_scene format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tools/test/parkour_gaps.gd" id="1"]
|
||||
|
||||
[node name="T" type="Node"]
|
||||
script = ExtResource("1")
|
||||
@@ -0,0 +1,26 @@
|
||||
extends Node
|
||||
# Облёт карты Parkour для скриншотов: телепорт игрока по точкам, кадры пишет Godot (--write-movie).
|
||||
# Запуск (окно мелькнёт):
|
||||
# godot --path . --resolution 1152x648 --fixed-fps 10 --write-movie /tmp/shots/p.png --quit-after 42 res://tools/test/parkour_tour.tscn -- --client
|
||||
# Кадры: p00000005.png и т. д.; точки — массив spots (позиция в координатах Godot, рыскание, наклон головы)
|
||||
var level
|
||||
var p
|
||||
var frame := 0
|
||||
# позиция центра капсулы (Godot), рыскание в градусах, наклон головы в градусах
|
||||
var spots := [
|
||||
[Vector3(0, 1.25, 6), 0, 6], [Vector3(0, 11.0, -196), 0, 3], [Vector3(-3.4, 10.0, -222), 0, 0], [Vector3(2, 14.0, -310), 0, 20],
|
||||
[Vector3(0, 46.0, -352), 0, 3], [Vector3(0, 46.0, -400), 0, 0], [Vector3(0, 49.0, -560), 0, 6], [Vector3(0, 76.0, -672), 0, 14],
|
||||
]
|
||||
func _ready() -> void:
|
||||
level = (load("res://maps/parkour/parkour.tscn") as PackedScene).instantiate()
|
||||
add_child(level)
|
||||
p = level.get_node("Player")
|
||||
p.set_physics_process(false)
|
||||
func _process(_d: float) -> void:
|
||||
frame += 1
|
||||
var i := int(frame / 5)
|
||||
if i < spots.size() and frame % 5 == 1:
|
||||
var s = spots[i]
|
||||
p.global_position = s[0]
|
||||
p.rotation.y = deg_to_rad(s[1])
|
||||
p.head.rotation.x = deg_to_rad(s[2])
|
||||
@@ -0,0 +1 @@
|
||||
uid://drhimdbliiox8
|
||||
@@ -0,0 +1,6 @@
|
||||
[gd_scene format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tools/test/parkour_tour.gd" id="1"]
|
||||
|
||||
[node name="T" type="Node"]
|
||||
script = ExtResource("1")
|
||||
Reference in New Issue
Block a user