Some checks failed
Godot CI / Import + smoke test (push) Failing after 19s
3D-сцена 4×4м: стены/пол/потолок, окно (свечение), дверь, стол с пека, кровать с одеялом, подарок-коробка на полу, отец-NPC в дверях. Архитектура: - FPVController (CharacterBody3D + Camera3D + RayCast3D) — mouse look, WASD, raycast-pickup, lock_input для катсцен. - Interactable (StaticBody3D base) — toggle через collision_layer. - DialogManager (autoload CanvasLayer) — субтитры snake-case, fullscreen записка, HUD-подсказки, prompt над крестом, await через signal. - act1.gd — state machine: INTRO → EXPLORE_GIFT → INSIDE_GIFT → NOTE_READ → FATHER_FAREWELL (tween x-2.5) → SEEK_DRIVE → ENDING. Канон-реплики из Пролог_Первый_день.md без правок: - Отец intro + про флешку с Шиндой - Записка «Не сожги. Пароль от Wi-Fi на холодильнике. — Бать» - Мысли героя: «новый пека?», «бать ну ты красавчик», «Андрюхино так Андрюхино» GL Compatibility renderer, unshaded материалы плоскими цветами под pixel-art 640×360. Тестировано на RTX 5070 — стартует без ошибок. main.tscn placeholder удалён, main_scene = act1_room.tscn. Не покрыто в MVP (TBD): - Опц. выход на кухню → холодильник → Wi-Fi пароль - Mastermind hack-mini-game для подбора Wi-Fi - Sfxr звуки (открытие коробки, шаги) - Переход в Акт 2 (пока quit на финале) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
90 lines
3 KiB
GDScript
90 lines
3 KiB
GDScript
extends Node3D
|
||
|
||
@onready var player: FPVController = $PlayerFPV
|
||
@onready var gift_box: Interactable = $GiftBox
|
||
@onready var system_unit: Node3D = $SystemUnit
|
||
@onready var note: Interactable = $SystemUnit/Note
|
||
@onready var flash_drive: Interactable = $Desk/FlashDrive
|
||
@onready var father: Node3D = $FatherNPC
|
||
|
||
enum State { INTRO, EXPLORE_GIFT, INSIDE_GIFT, NOTE_READ, FATHER_FAREWELL, SEEK_DRIVE, ENDING }
|
||
var state: State = State.INTRO
|
||
|
||
|
||
func _ready() -> void:
|
||
system_unit.visible = false
|
||
note.visible = false
|
||
gift_box.set_interactable(false)
|
||
flash_drive.set_interactable(false)
|
||
|
||
player.lock_input()
|
||
player.interactable_focused.connect(_on_player_focused)
|
||
player.interactable_unfocused.connect(_on_player_unfocused)
|
||
|
||
DialogManager.set_hud("Акт 1 — Подарок · ЛКМ/ПРОБЕЛ — дальше · ESC — мышь · Q — выход")
|
||
|
||
await get_tree().create_timer(0.8).timeout
|
||
await DialogManager.say("ОТЕЦ", "Ну? Чё стоишь. Распаковывай давай. Сам собирал, между прочим — полночи не спал.")
|
||
await DialogManager.think("Так… новый пека? Серьёзно? После моего динозавра?")
|
||
|
||
state = State.EXPLORE_GIFT
|
||
gift_box.set_interactable(true)
|
||
player.unlock_input()
|
||
|
||
|
||
func _on_player_focused(obj: Node) -> void:
|
||
if obj.has_method("get_prompt"):
|
||
DialogManager.set_prompt(obj.get_prompt())
|
||
|
||
|
||
func _on_player_unfocused() -> void:
|
||
DialogManager.clear_prompt()
|
||
|
||
|
||
func _on_gift_interacted(_p: Node) -> void:
|
||
if state != State.EXPLORE_GIFT:
|
||
return
|
||
state = State.INSIDE_GIFT
|
||
gift_box.set_interactable(false)
|
||
gift_box.visible = false
|
||
system_unit.visible = true
|
||
note.visible = true
|
||
note.set_interactable(true)
|
||
DialogManager.clear_prompt()
|
||
|
||
|
||
func _on_note_interacted(_p: Node) -> void:
|
||
if state != State.INSIDE_GIFT:
|
||
return
|
||
state = State.NOTE_READ
|
||
note.set_interactable(false)
|
||
player.lock_input()
|
||
DialogManager.clear_prompt()
|
||
|
||
await DialogManager.show_note("«Не сожги. Пароль от Wi-Fi на холодильнике. — Бать»")
|
||
await DialogManager.think("Бать ну ты красавчик конечно.")
|
||
|
||
state = State.FATHER_FAREWELL
|
||
await DialogManager.say("ОТЕЦ", "Флешка с Шиндой на столе. Ставь сам, не маленький. Если запорешь — сам и переставляй.")
|
||
|
||
var tween: Tween = create_tween()
|
||
tween.tween_property(father, "position:x", father.position.x - 2.5, 0.8)
|
||
await tween.finished
|
||
father.visible = false
|
||
|
||
state = State.SEEK_DRIVE
|
||
flash_drive.set_interactable(true)
|
||
player.unlock_input()
|
||
|
||
|
||
func _on_drive_interacted(_p: Node) -> void:
|
||
if state != State.SEEK_DRIVE:
|
||
return
|
||
state = State.ENDING
|
||
flash_drive.set_interactable(false)
|
||
player.lock_input()
|
||
DialogManager.clear_prompt()
|
||
|
||
await DialogManager.think("Ну ладно. Андрюхино так Андрюхино. Поехали.")
|
||
await DialogManager.say("СИСТЕМА", "Конец Акта 1. Акт 2 — Установка Шиндовс (TBD).")
|
||
get_tree().quit()
|