Race 2 «Верхние ярусы», фаза 1: карта, вентиляторы, ускоряющие полосы, правила общие с Race
- maps/race2/: сцена, race2.gd (ворота со световыми столбами, лазеры, вентиляторы, полосы, срыв, дождь, гул), данные и модель из Blender models/race_02 (раскладка layout.py -> build_race.py -> bake.py; свет пока запечён грубо, небо и музыка — копии Race) - objects/vent.gd, objects/boost_strip.gd + хуки apply_vent()/apply_boost() в player/player.gd (MAX_SPEED и остальная физика не тронуты) - race_rules.gd берёт данные у карты (общий код правил для Race и Race 2) - портал RACE 2 в хабе, пресет экспорта Race2 + export_all.sh, Net.TIMED_MAPS/MAP_TITLES для race2 (RPC не менялись; серверу нужен deploy_server.sh) - тесты: race_game_test.sh с MAP=race2 (бот проводит игроков телепортами), race2_check.txt - README: раздел «Карта Race 2», результаты ботов по веткам Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
# Проверка карты Race 2 «по существу» после прохода через портал RACE 2 с настоящими пакетами (по образцу parkour_check.txt).
|
||||
# Вставь в tools/test/bot.gd ВРЕМЕННОЙ копии проекта (make_test_project.sh) перед строкой print("[%s] t=%4.1f сцена=...")
|
||||
# Пакеты: tools/export_all.sh /tmp/site (НЕ в grid-world), затем (cd /tmp/site && python3 -m http.server 8777 --bind 127.0.0.1 &) и
|
||||
# godot --headless --main-pack /tmp/site/index.pck -- --client --name=T --pack-url=http://127.0.0.1:8777/ --tp=2:9.0,1,-7.5
|
||||
# (портал RACE 2 пятый в ряду: x = (4 - 2) * 4.5 = 9.0). Сервер в этом тесте не нужен.
|
||||
# Ожидание: скрипт=true, правила=true, шейдерных_мешей>=1, небо_панорама=true, ворот=13, лазеров=10, вентиляторов=6, полос=15 (см. race2_data.gd), музыка_в_файле=true, сдача=true.
|
||||
|
||||
var sc := get_tree().current_scene
|
||||
if sc != null and sc.name == "Race2" 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 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(" ПРОВЕРКА Race2: скрипт=%s правила=%s шейдерных_мешей=%d небо_панорама=%s ворот=%d лазеров=%d вентиляторов=%d полос=%d музыка_в_файле=%s сдача=%s" % [
|
||||
sc.get_script() != null, sc.get_node_or_null("Rules") != null, shaded, sky_ok, sc._gates.size(), sc._lasers.size(),
|
||||
sc.find_children("*", "Area3D", false, false).filter(func(a): return a.get_script() != null and a.get_script().resource_path.ends_with("vent.gd")).size(),
|
||||
sc.find_children("*", "Area3D", false, false).filter(func(a): return a.get_script() != null and a.get_script().resource_path.ends_with("boost_strip.gd")).size(),
|
||||
ResourceLoader.exists("res://maps/race2/audio/music.ogg"), sc.get_node("Rules").has_method("_give_up")])
|
||||
+54
-3
@@ -8,9 +8,13 @@ extends Node
|
||||
# --fall-at-gate=<n>: после ворот n упасть в пустоту (проверка срыва ниже ворот)
|
||||
# --gu1=<план>, --gu2=<план>: сдача в круге 1 / 2. План: «cancel@1,giveup@2» — когда пройдено столько ворот,
|
||||
# держать G 1,0 с и отпустить (отмена) / держать до сдачи (проверка race_giveup_test.sh)
|
||||
# --map=race2: карта «Гонка 2». Бежать по её маршруту без физики бегуна нельзя (вентиляторы, лазеры, стены), поэтому бот
|
||||
# «проводит» игрока через ворота телепортами по ~0,35 м за кадр (меньше TELEPORT_JUMP: правила считают это бегом); проверяются правила, а не проходимость
|
||||
# (проходимость карты — боты в Blender models/race_02/tests/).
|
||||
# Факты печатаются строками «[имя] …»; сверяет их tools/test/race_game_test.sh.
|
||||
|
||||
const Data = preload("res://maps/race/race_data.gd")
|
||||
var Data = null # данные карты: race_data.gd или (--map=race2) race2_data.gd
|
||||
var map_id := "race"
|
||||
|
||||
var label := "?"
|
||||
var delay := 0.0
|
||||
@@ -46,6 +50,7 @@ var gu_after_cancel := false
|
||||
|
||||
func _ready() -> void:
|
||||
for a in OS.get_cmdline_user_args():
|
||||
if a.begins_with("--map="): map_id = a.substr(6)
|
||||
if a.begins_with("--name="): label = a.substr(7)
|
||||
if a.begins_with("--delay="): delay = float(a.substr(8))
|
||||
if a.begins_with("--expect="): expect = int(a.substr(9))
|
||||
@@ -57,7 +62,11 @@ func _ready() -> void:
|
||||
for e in a.substr(6).split(","):
|
||||
var q := e.split("@")
|
||||
gu_plan[int(a.substr(4, 1))].append({"mode": q[0], "gate": int(q[1])})
|
||||
route = JSON.parse_string(FileAccess.get_file_as_string("res://tools/test/race_route.json")).safe
|
||||
if map_id == "race2":
|
||||
Data = load("res://maps/race2/race2_data.gd")
|
||||
else:
|
||||
Data = load("res://maps/race/race_data.gd")
|
||||
route = JSON.parse_string(FileAccess.get_file_as_string("res://tools/test/race_route.json")).safe
|
||||
|
||||
func gp(w: Array) -> Vector3:
|
||||
return Vector3(w[0], w[2], -w[1])
|
||||
@@ -76,7 +85,7 @@ func say(text: String) -> void:
|
||||
|
||||
func _physics_process(_delta: float) -> void:
|
||||
var sc := get_tree().current_scene
|
||||
if sc == null or sc.name != "Race":
|
||||
if sc == null or sc.name != ("Race2" if map_id == "race2" else "Race"):
|
||||
return
|
||||
rules = sc.get_node_or_null("Rules")
|
||||
p = get_tree().get_first_node_in_group("local_player")
|
||||
@@ -220,7 +229,49 @@ func _race() -> void:
|
||||
return
|
||||
_drive()
|
||||
|
||||
func _drive_tele() -> void:
|
||||
var g: int = rules.passed.get(rules.my_id, 0)
|
||||
if g >= Data.CHECKPOINTS.size() or p.is_dead:
|
||||
return
|
||||
var gate: Array = Data.CHECKPOINTS[g]
|
||||
var yaw := deg_to_rad(gate[2])
|
||||
var fwd := Vector3(-sin(yaw), 0, -cos(yaw))
|
||||
var tgt: Vector3 = gate[0] + fwd * 1.5
|
||||
var step := 0.35 # м за кадр (~21 м/с): круг ~60 с, как у настоящей гонки — иначе зритель C не успеет зайти в разгар круга
|
||||
# лазерный мост: идём медленно (12 м/с) и на ближних подступах к воротам выбираем полосу, где лазер выключен в момент пролёта
|
||||
var pos: Vector3 = p.global_position
|
||||
var lane := -1.0
|
||||
if pos.x > 105.0 and pos.x < 115.0 and pos.z < -535.0 and pos.z > -615.0:
|
||||
step = 0.2
|
||||
for l in Data.LASERS:
|
||||
if l[0].x < 110.0: # левая половина; правая — в паре
|
||||
var dz: float = pos.z - l[0].z # > 0 — ворота ещё впереди (идём в -Z)
|
||||
if dz > -1.0 and dz < 8.0:
|
||||
var t := Time.get_unix_time_from_system() + maxf(dz, 0.0) / 12.0
|
||||
var left_on: bool = fposmod(t + l[5], l[3]) < l[4] or fposmod(t + 0.4 + l[5], l[3]) < l[4] or fposmod(t - 0.4 + l[5], l[3]) < l[4]
|
||||
var right_on: bool = fposmod(t + 1.5 + l[5], l[3]) < l[4] or fposmod(t + 1.9 + l[5], l[3]) < l[4] or fposmod(t + 1.1 + l[5], l[3]) < l[4]
|
||||
lane = 112.0 if left_on else 108.0
|
||||
if left_on and right_on:
|
||||
lane = -2.0 # обе полосы заняты — стоим
|
||||
if lane == -2.0:
|
||||
return
|
||||
if lane > 0.0:
|
||||
p.global_position.x = lane
|
||||
pos.x = lane
|
||||
tgt.x = lane
|
||||
var d: Vector3 = tgt - pos
|
||||
if tgt.y - pos.y > 2.0: # ворота выше (крыша башни): сначала поднимаемся вертикально — иначе упрёмся в стену
|
||||
d = Vector3(0, minf(step, tgt.y - pos.y + 1.5), 0)
|
||||
elif d.length() > step:
|
||||
d = d.normalized() * step
|
||||
p.global_position = pos + d
|
||||
p.velocity = Vector3.ZERO
|
||||
p.rotation.y = yaw
|
||||
|
||||
func _drive() -> void:
|
||||
if map_id == "race2":
|
||||
_drive_tele()
|
||||
return
|
||||
var pos: Vector3 = p.global_position
|
||||
if fr % 120 == 0 and OS.get_cmdline_user_args().has("--bot-log"):
|
||||
say("бег: wi=%d поз=(%.1f, %.1f, %.1f) v=(%.1f, %.1f) пол=%s" % [wi, pos.x, pos.y, pos.z, p.velocity.x, p.velocity.z, p.is_on_floor()])
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
# Игровая проверка режима «Гонка» на локальном сервере: 2 клиента-бота (+ третий, который заходит в разгар круга 1,
|
||||
# наблюдает, участвует в круге 2 и выходит посреди него). Окно не открывается.
|
||||
# tools/test/race_game_test.sh [рабочая_папка=/tmp/race_game]
|
||||
# MAP=race2 — то же для карты «Гонка 2» (бот проводит игроков через ворота телепортами: проверяются правила).
|
||||
# Печатает факты и итог «ПРОВЕРКА ПРОЙДЕНА / ПРОВАЛЕНА». Порт сервера 9080 должен быть свободен.
|
||||
# Настоящий рекорд (user://race_best.cfg) не трогается: рекорд ботов идёт в файлы в рабочей папке.
|
||||
set -uo pipefail
|
||||
SRC="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
W="${1:-/tmp/race_game}"
|
||||
MAP="${MAP:-race}"
|
||||
if [ "$MAP" = race2 ]; then SCENE=res://maps/race2/race2.tscn; MARG="--map=race2"; else SCENE=res://maps/race/race.tscn; MARG=""; fi
|
||||
rm -rf "$W"; mkdir -p "$W"
|
||||
"$SRC/tools/test/make_test_server.sh" "$W/server" >/dev/null
|
||||
"$SRC/tools/test/make_test_project.sh" "$W/project" >/dev/null
|
||||
@@ -19,7 +22,7 @@ SERVER=$!
|
||||
sleep 2
|
||||
run() { # имя, остальные аргументы бота
|
||||
local n="$1"; shift
|
||||
timeout 420 godot --headless --path "$W/project" res://maps/race/race.tscn -- --client --race-log --race-best="$W/best_$n.cfg" --profile="$W/prof_$n.cfg" --name="$n" "$@" > "$W/$n.log" 2>&1 &
|
||||
timeout 420 godot --headless --path "$W/project" $SCENE -- --client $MARG --race-log --race-best="$W/best_$n.cfg" --profile="$W/prof_$n.cfg" --name="$n" "$@" > "$W/$n.log" 2>&1 &
|
||||
}
|
||||
run A --expect=2 --rounds=2 --die-at-gate=3
|
||||
PA=$!
|
||||
|
||||
Reference in New Issue
Block a user