act1: FPV-комната с подарком, запиской и флешкой
Some checks failed
Godot CI / Import + smoke test (push) Failing after 19s
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>
This commit is contained in:
parent
d1c28d088b
commit
d1b3afd006
15 changed files with 733 additions and 50 deletions
13
main.gd
13
main.gd
|
|
@ -1,13 +0,0 @@
|
||||||
extends Node2D
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
var w: int = ProjectSettings.get_setting("display/window/size/viewport_width")
|
|
||||||
var h: int = ProjectSettings.get_setting("display/window/size/viewport_height")
|
|
||||||
print("Farming Simulator pre-alpha. Pixel viewport: %dx%d" % [w, h])
|
|
||||||
|
|
||||||
|
|
||||||
func _input(event: InputEvent) -> void:
|
|
||||||
if event is InputEventKey and event.pressed:
|
|
||||||
if event.keycode == KEY_ESCAPE or event.keycode == KEY_Q:
|
|
||||||
get_tree().quit()
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
uid://dog6k44wwqkvh
|
|
||||||
35
main.tscn
35
main.tscn
|
|
@ -1,35 +0,0 @@
|
||||||
[gd_scene load_steps=2 format=3]
|
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://main.gd" id="1_main"]
|
|
||||||
|
|
||||||
[node name="Main" type="Node2D"]
|
|
||||||
script = ExtResource("1_main")
|
|
||||||
|
|
||||||
[node name="Background" type="ColorRect" parent="."]
|
|
||||||
offset_right = 640.0
|
|
||||||
offset_bottom = 360.0
|
|
||||||
color = Color(0.0784314, 0.0784314, 0.117647, 1)
|
|
||||||
|
|
||||||
[node name="TitleLabel" type="Label" parent="."]
|
|
||||||
offset_left = 0.0
|
|
||||||
offset_top = 130.0
|
|
||||||
offset_right = 640.0
|
|
||||||
offset_bottom = 160.0
|
|
||||||
text = "FARMING SIMULATOR"
|
|
||||||
horizontal_alignment = 1
|
|
||||||
|
|
||||||
[node name="SubLabel" type="Label" parent="."]
|
|
||||||
offset_left = 0.0
|
|
||||||
offset_top = 180.0
|
|
||||||
offset_right = 640.0
|
|
||||||
offset_bottom = 210.0
|
|
||||||
text = "pre-alpha — pixel viewport 640x360"
|
|
||||||
horizontal_alignment = 1
|
|
||||||
|
|
||||||
[node name="HintLabel" type="Label" parent="."]
|
|
||||||
offset_left = 0.0
|
|
||||||
offset_top = 290.0
|
|
||||||
offset_right = 640.0
|
|
||||||
offset_bottom = 320.0
|
|
||||||
text = "ESC или Q — выход"
|
|
||||||
horizontal_alignment = 1
|
|
||||||
|
|
@ -12,9 +12,13 @@ config_version=5
|
||||||
|
|
||||||
config/name="Farming Simulator"
|
config/name="Farming Simulator"
|
||||||
config/description="Подросток-фермер CS/TF2/PUBG-кейсов в стране Р, 2020-2022. Pre-alpha."
|
config/description="Подросток-фермер CS/TF2/PUBG-кейсов в стране Р, 2020-2022. Pre-alpha."
|
||||||
run/main_scene="res://main.tscn"
|
run/main_scene="res://scenes/prologue/act1_room.tscn"
|
||||||
config/features=PackedStringArray("4.6", "GL Compatibility")
|
config/features=PackedStringArray("4.6", "GL Compatibility")
|
||||||
|
|
||||||
|
[autoload]
|
||||||
|
|
||||||
|
DialogManager="*res://scenes/components/dialog_ui.tscn"
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
|
|
||||||
window/size/viewport_width=640
|
window/size/viewport_width=640
|
||||||
|
|
|
||||||
94
scenes/components/dialog_ui.tscn
Normal file
94
scenes/components/dialog_ui.tscn
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/ui/dialog_manager.gd" id="1_dlg"]
|
||||||
|
|
||||||
|
[node name="DialogManager" type="CanvasLayer"]
|
||||||
|
layer = 10
|
||||||
|
script = ExtResource("1_dlg")
|
||||||
|
|
||||||
|
[node name="HudLabel" type="Label" parent="."]
|
||||||
|
offset_left = 8.0
|
||||||
|
offset_top = 4.0
|
||||||
|
offset_right = 632.0
|
||||||
|
offset_bottom = 22.0
|
||||||
|
text = ""
|
||||||
|
modulate = Color(0.85, 0.85, 0.85, 1)
|
||||||
|
|
||||||
|
[node name="PromptLabel" type="Label" parent="."]
|
||||||
|
offset_left = 0.0
|
||||||
|
offset_top = 200.0
|
||||||
|
offset_right = 640.0
|
||||||
|
offset_bottom = 220.0
|
||||||
|
text = ""
|
||||||
|
horizontal_alignment = 1
|
||||||
|
modulate = Color(1, 0.95, 0.6, 1)
|
||||||
|
|
||||||
|
[node name="SubtitlePanel" type="PanelContainer" parent="."]
|
||||||
|
offset_left = 20.0
|
||||||
|
offset_top = 260.0
|
||||||
|
offset_right = 620.0
|
||||||
|
offset_bottom = 350.0
|
||||||
|
|
||||||
|
[node name="V" type="VBoxContainer" parent="SubtitlePanel"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Who" type="Label" parent="SubtitlePanel/V"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "[Я]"
|
||||||
|
modulate = Color(0.9, 0.8, 0.5, 1)
|
||||||
|
|
||||||
|
[node name="Text" type="Label" parent="SubtitlePanel/V"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = ""
|
||||||
|
autowrap_mode = 3
|
||||||
|
|
||||||
|
[node name="Hint" type="Label" parent="SubtitlePanel/V"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "ЛКМ или ПРОБЕЛ — дальше"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
modulate = Color(0.55, 0.55, 0.55, 1)
|
||||||
|
|
||||||
|
[node name="NoteOverlay" type="Control" parent="."]
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_right = 640.0
|
||||||
|
offset_bottom = 360.0
|
||||||
|
mouse_filter = 0
|
||||||
|
|
||||||
|
[node name="DarkBG" type="ColorRect" parent="NoteOverlay"]
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_right = 640.0
|
||||||
|
offset_bottom = 360.0
|
||||||
|
color = Color(0, 0, 0, 0.75)
|
||||||
|
|
||||||
|
[node name="Center" type="CenterContainer" parent="NoteOverlay"]
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_right = 640.0
|
||||||
|
offset_bottom = 360.0
|
||||||
|
|
||||||
|
[node name="P" type="PanelContainer" parent="NoteOverlay/Center"]
|
||||||
|
custom_minimum_size = Vector2(420, 180)
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="V2" type="VBoxContainer" parent="NoteOverlay/Center/P"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="NoteOverlay/Center/P/V2"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Записка"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
modulate = Color(0.9, 0.8, 0.5, 1)
|
||||||
|
|
||||||
|
[node name="Text" type="Label" parent="NoteOverlay/Center/P/V2"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = ""
|
||||||
|
autowrap_mode = 3
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Hint" type="Label" parent="NoteOverlay/Center/P/V2"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "ЛКМ или ПРОБЕЛ — закрыть"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
modulate = Color(0.55, 0.55, 0.55, 1)
|
||||||
25
scenes/components/player_fpv.tscn
Normal file
25
scenes/components/player_fpv.tscn
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
[gd_scene load_steps=3 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/player/fpv_controller.gd" id="1_fpv"]
|
||||||
|
|
||||||
|
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_player"]
|
||||||
|
height = 1.7
|
||||||
|
radius = 0.3
|
||||||
|
|
||||||
|
[node name="PlayerFPV" type="CharacterBody3D"]
|
||||||
|
script = ExtResource("1_fpv")
|
||||||
|
|
||||||
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.85, 0)
|
||||||
|
shape = SubResource("CapsuleShape3D_player")
|
||||||
|
|
||||||
|
[node name="Camera3D" type="Camera3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.6, 0)
|
||||||
|
current = true
|
||||||
|
fov = 70.0
|
||||||
|
near = 0.1
|
||||||
|
far = 50.0
|
||||||
|
|
||||||
|
[node name="InteractRay" type="RayCast3D" parent="Camera3D"]
|
||||||
|
target_position = Vector3(0, 0, -2.5)
|
||||||
|
collision_mask = 1
|
||||||
305
scenes/prologue/act1_room.tscn
Normal file
305
scenes/prologue/act1_room.tscn
Normal file
|
|
@ -0,0 +1,305 @@
|
||||||
|
[gd_scene load_steps=42 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/prologue/act1.gd" id="1_act1"]
|
||||||
|
[ext_resource type="Script" path="res://scripts/interactables/interactable.gd" id="2_int"]
|
||||||
|
[ext_resource type="PackedScene" path="res://scenes/components/player_fpv.tscn" id="3_player"]
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="mesh_floor"]
|
||||||
|
size = Vector3(4, 0.1, 4)
|
||||||
|
|
||||||
|
[sub_resource type="BoxShape3D" id="shape_floor"]
|
||||||
|
size = Vector3(4, 0.1, 4)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="mesh_wall_ns"]
|
||||||
|
size = Vector3(4, 2.5, 0.1)
|
||||||
|
|
||||||
|
[sub_resource type="BoxShape3D" id="shape_wall_ns"]
|
||||||
|
size = Vector3(4, 2.5, 0.1)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="mesh_wall_ew"]
|
||||||
|
size = Vector3(0.1, 2.5, 4)
|
||||||
|
|
||||||
|
[sub_resource type="BoxShape3D" id="shape_wall_ew"]
|
||||||
|
size = Vector3(0.1, 2.5, 4)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="mesh_window"]
|
||||||
|
size = Vector3(1.5, 1.0, 0.06)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="mesh_door"]
|
||||||
|
size = Vector3(0.06, 2.0, 1.0)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="mesh_desk"]
|
||||||
|
size = Vector3(1.5, 0.7, 0.6)
|
||||||
|
|
||||||
|
[sub_resource type="BoxShape3D" id="shape_desk"]
|
||||||
|
size = Vector3(1.5, 0.7, 0.6)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="mesh_old_pc"]
|
||||||
|
size = Vector3(0.25, 0.4, 0.3)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="mesh_bed"]
|
||||||
|
size = Vector3(2.0, 0.4, 1.0)
|
||||||
|
|
||||||
|
[sub_resource type="BoxShape3D" id="shape_bed"]
|
||||||
|
size = Vector3(2.0, 0.4, 1.0)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="mesh_blanket"]
|
||||||
|
size = Vector3(1.95, 0.06, 0.95)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="mesh_pillow"]
|
||||||
|
size = Vector3(0.5, 0.1, 0.25)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="mesh_gift"]
|
||||||
|
size = Vector3(0.5, 0.4, 0.5)
|
||||||
|
|
||||||
|
[sub_resource type="BoxShape3D" id="shape_gift"]
|
||||||
|
size = Vector3(0.5, 0.4, 0.5)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="mesh_sys"]
|
||||||
|
size = Vector3(0.35, 0.5, 0.45)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="mesh_note"]
|
||||||
|
size = Vector3(0.18, 0.012, 0.12)
|
||||||
|
|
||||||
|
[sub_resource type="BoxShape3D" id="shape_note"]
|
||||||
|
size = Vector3(0.18, 0.012, 0.12)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="mesh_flash"]
|
||||||
|
size = Vector3(0.06, 0.02, 0.025)
|
||||||
|
|
||||||
|
[sub_resource type="BoxShape3D" id="shape_flash"]
|
||||||
|
size = Vector3(0.06, 0.02, 0.025)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="mesh_father"]
|
||||||
|
size = Vector3(0.4, 1.7, 0.4)
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="mat_floor"]
|
||||||
|
shading_mode = 0
|
||||||
|
albedo_color = Color(0.55, 0.35, 0.22, 1)
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="mat_wall"]
|
||||||
|
shading_mode = 0
|
||||||
|
albedo_color = Color(0.79, 0.71, 0.54, 1)
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="mat_ceiling"]
|
||||||
|
shading_mode = 0
|
||||||
|
albedo_color = Color(0.85, 0.83, 0.78, 1)
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="mat_window"]
|
||||||
|
shading_mode = 0
|
||||||
|
albedo_color = Color(0.6, 0.85, 1.0, 1)
|
||||||
|
emission_enabled = true
|
||||||
|
emission = Color(0.6, 0.85, 1.0, 1)
|
||||||
|
emission_energy_multiplier = 0.5
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="mat_door"]
|
||||||
|
shading_mode = 0
|
||||||
|
albedo_color = Color(0.29, 0.18, 0.09, 1)
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="mat_desk"]
|
||||||
|
shading_mode = 0
|
||||||
|
albedo_color = Color(0.36, 0.23, 0.11, 1)
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="mat_old_pc"]
|
||||||
|
shading_mode = 0
|
||||||
|
albedo_color = Color(0.43, 0.43, 0.43, 1)
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="mat_blanket"]
|
||||||
|
shading_mode = 0
|
||||||
|
albedo_color = Color(0.45, 0.15, 0.15, 1)
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="mat_pillow"]
|
||||||
|
shading_mode = 0
|
||||||
|
albedo_color = Color(0.92, 0.88, 0.78, 1)
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="mat_gift"]
|
||||||
|
shading_mode = 0
|
||||||
|
albedo_color = Color(0.1, 0.1, 0.12, 1)
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="mat_sys"]
|
||||||
|
shading_mode = 0
|
||||||
|
albedo_color = Color(0.3, 0.32, 0.38, 1)
|
||||||
|
emission_enabled = true
|
||||||
|
emission = Color(0.2, 0.5, 0.9, 1)
|
||||||
|
emission_energy_multiplier = 0.6
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="mat_note"]
|
||||||
|
shading_mode = 0
|
||||||
|
albedo_color = Color(0.95, 0.95, 0.9, 1)
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="mat_flash"]
|
||||||
|
shading_mode = 0
|
||||||
|
albedo_color = Color(0.78, 0.18, 0.18, 1)
|
||||||
|
emission_enabled = true
|
||||||
|
emission = Color(0.78, 0.18, 0.18, 1)
|
||||||
|
emission_energy_multiplier = 0.4
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="mat_father"]
|
||||||
|
shading_mode = 0
|
||||||
|
albedo_color = Color(0.18, 0.22, 0.4, 1)
|
||||||
|
|
||||||
|
[node name="Act1Room" type="Node3D"]
|
||||||
|
script = ExtResource("1_act1")
|
||||||
|
|
||||||
|
[node name="Floor" type="StaticBody3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.05, 0)
|
||||||
|
|
||||||
|
[node name="Mesh" type="MeshInstance3D" parent="Floor"]
|
||||||
|
mesh = SubResource("mesh_floor")
|
||||||
|
material_override = SubResource("mat_floor")
|
||||||
|
|
||||||
|
[node name="Coll" type="CollisionShape3D" parent="Floor"]
|
||||||
|
shape = SubResource("shape_floor")
|
||||||
|
|
||||||
|
[node name="Ceiling" type="StaticBody3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.55, 0)
|
||||||
|
|
||||||
|
[node name="Mesh" type="MeshInstance3D" parent="Ceiling"]
|
||||||
|
mesh = SubResource("mesh_floor")
|
||||||
|
material_override = SubResource("mat_ceiling")
|
||||||
|
|
||||||
|
[node name="Coll" type="CollisionShape3D" parent="Ceiling"]
|
||||||
|
shape = SubResource("shape_floor")
|
||||||
|
|
||||||
|
[node name="WallN" type="StaticBody3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.25, -2.05)
|
||||||
|
|
||||||
|
[node name="Mesh" type="MeshInstance3D" parent="WallN"]
|
||||||
|
mesh = SubResource("mesh_wall_ns")
|
||||||
|
material_override = SubResource("mat_wall")
|
||||||
|
|
||||||
|
[node name="Coll" type="CollisionShape3D" parent="WallN"]
|
||||||
|
shape = SubResource("shape_wall_ns")
|
||||||
|
|
||||||
|
[node name="Window" type="MeshInstance3D" parent="WallN"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.3, 0.06)
|
||||||
|
mesh = SubResource("mesh_window")
|
||||||
|
material_override = SubResource("mat_window")
|
||||||
|
|
||||||
|
[node name="WallS" type="StaticBody3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.25, 2.05)
|
||||||
|
|
||||||
|
[node name="Mesh" type="MeshInstance3D" parent="WallS"]
|
||||||
|
mesh = SubResource("mesh_wall_ns")
|
||||||
|
material_override = SubResource("mat_wall")
|
||||||
|
|
||||||
|
[node name="Coll" type="CollisionShape3D" parent="WallS"]
|
||||||
|
shape = SubResource("shape_wall_ns")
|
||||||
|
|
||||||
|
[node name="WallE" type="StaticBody3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.05, 1.25, 0)
|
||||||
|
|
||||||
|
[node name="Mesh" type="MeshInstance3D" parent="WallE"]
|
||||||
|
mesh = SubResource("mesh_wall_ew")
|
||||||
|
material_override = SubResource("mat_wall")
|
||||||
|
|
||||||
|
[node name="Coll" type="CollisionShape3D" parent="WallE"]
|
||||||
|
shape = SubResource("shape_wall_ew")
|
||||||
|
|
||||||
|
[node name="DoorPanel" type="MeshInstance3D" parent="WallE"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.06, -0.25, 0)
|
||||||
|
mesh = SubResource("mesh_door")
|
||||||
|
material_override = SubResource("mat_door")
|
||||||
|
|
||||||
|
[node name="WallW" type="StaticBody3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.05, 1.25, 0)
|
||||||
|
|
||||||
|
[node name="Mesh" type="MeshInstance3D" parent="WallW"]
|
||||||
|
mesh = SubResource("mesh_wall_ew")
|
||||||
|
material_override = SubResource("mat_wall")
|
||||||
|
|
||||||
|
[node name="Coll" type="CollisionShape3D" parent="WallW"]
|
||||||
|
shape = SubResource("shape_wall_ew")
|
||||||
|
|
||||||
|
[node name="Desk" type="StaticBody3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.35, -1.5)
|
||||||
|
|
||||||
|
[node name="Mesh" type="MeshInstance3D" parent="Desk"]
|
||||||
|
mesh = SubResource("mesh_desk")
|
||||||
|
material_override = SubResource("mat_desk")
|
||||||
|
|
||||||
|
[node name="Coll" type="CollisionShape3D" parent="Desk"]
|
||||||
|
shape = SubResource("shape_desk")
|
||||||
|
|
||||||
|
[node name="OldPC" type="MeshInstance3D" parent="Desk"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5, 0.55, 0)
|
||||||
|
mesh = SubResource("mesh_old_pc")
|
||||||
|
material_override = SubResource("mat_old_pc")
|
||||||
|
|
||||||
|
[node name="FlashDrive" type="StaticBody3D" parent="Desk"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.3, 0.36, 0.1)
|
||||||
|
script = ExtResource("2_int")
|
||||||
|
prompt = "взять флешку"
|
||||||
|
|
||||||
|
[node name="Mesh" type="MeshInstance3D" parent="Desk/FlashDrive"]
|
||||||
|
mesh = SubResource("mesh_flash")
|
||||||
|
material_override = SubResource("mat_flash")
|
||||||
|
|
||||||
|
[node name="Coll" type="CollisionShape3D" parent="Desk/FlashDrive"]
|
||||||
|
shape = SubResource("shape_flash")
|
||||||
|
|
||||||
|
[node name="Bed" type="StaticBody3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5, 0.2, 1.4)
|
||||||
|
|
||||||
|
[node name="Mesh" type="MeshInstance3D" parent="Bed"]
|
||||||
|
mesh = SubResource("mesh_bed")
|
||||||
|
material_override = SubResource("mat_desk")
|
||||||
|
|
||||||
|
[node name="Coll" type="CollisionShape3D" parent="Bed"]
|
||||||
|
shape = SubResource("shape_bed")
|
||||||
|
|
||||||
|
[node name="Blanket" type="MeshInstance3D" parent="Bed"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.23, 0)
|
||||||
|
mesh = SubResource("mesh_blanket")
|
||||||
|
material_override = SubResource("mat_blanket")
|
||||||
|
|
||||||
|
[node name="Pillow" type="MeshInstance3D" parent="Bed"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.7, 0.31, 0)
|
||||||
|
mesh = SubResource("mesh_pillow")
|
||||||
|
material_override = SubResource("mat_pillow")
|
||||||
|
|
||||||
|
[node name="GiftBox" type="StaticBody3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.2, -0.3)
|
||||||
|
script = ExtResource("2_int")
|
||||||
|
prompt = "открыть коробку"
|
||||||
|
|
||||||
|
[node name="Mesh" type="MeshInstance3D" parent="GiftBox"]
|
||||||
|
mesh = SubResource("mesh_gift")
|
||||||
|
material_override = SubResource("mat_gift")
|
||||||
|
|
||||||
|
[node name="Coll" type="CollisionShape3D" parent="GiftBox"]
|
||||||
|
shape = SubResource("shape_gift")
|
||||||
|
|
||||||
|
[node name="SystemUnit" type="Node3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.25, -0.3)
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
[node name="Mesh" type="MeshInstance3D" parent="SystemUnit"]
|
||||||
|
mesh = SubResource("mesh_sys")
|
||||||
|
material_override = SubResource("mat_sys")
|
||||||
|
|
||||||
|
[node name="Note" type="StaticBody3D" parent="SystemUnit"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.26, 0)
|
||||||
|
script = ExtResource("2_int")
|
||||||
|
prompt = "прочитать записку"
|
||||||
|
|
||||||
|
[node name="Mesh" type="MeshInstance3D" parent="SystemUnit/Note"]
|
||||||
|
mesh = SubResource("mesh_note")
|
||||||
|
material_override = SubResource("mat_note")
|
||||||
|
|
||||||
|
[node name="Coll" type="CollisionShape3D" parent="SystemUnit/Note"]
|
||||||
|
shape = SubResource("shape_note")
|
||||||
|
|
||||||
|
[node name="FatherNPC" type="Node3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.7, 0.85, 0)
|
||||||
|
|
||||||
|
[node name="Mesh" type="MeshInstance3D" parent="FatherNPC"]
|
||||||
|
mesh = SubResource("mesh_father")
|
||||||
|
material_override = SubResource("mat_father")
|
||||||
|
|
||||||
|
[node name="PlayerFPV" parent="." instance=ExtResource("3_player")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1.2)
|
||||||
|
|
||||||
|
[connection signal="interacted" from="Desk/FlashDrive" to="." method="_on_drive_interacted"]
|
||||||
|
[connection signal="interacted" from="GiftBox" to="." method="_on_gift_interacted"]
|
||||||
|
[connection signal="interacted" from="SystemUnit/Note" to="." method="_on_note_interacted"]
|
||||||
32
scripts/interactables/interactable.gd
Normal file
32
scripts/interactables/interactable.gd
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
class_name Interactable extends StaticBody3D
|
||||||
|
|
||||||
|
@export var prompt: String = "Взаимодействовать"
|
||||||
|
@export var enabled: bool = false
|
||||||
|
|
||||||
|
signal interacted(player: Node)
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_apply_collision()
|
||||||
|
|
||||||
|
|
||||||
|
func set_interactable(v: bool) -> void:
|
||||||
|
enabled = v
|
||||||
|
_apply_collision()
|
||||||
|
|
||||||
|
|
||||||
|
func _apply_collision() -> void:
|
||||||
|
if enabled:
|
||||||
|
collision_layer = 1
|
||||||
|
else:
|
||||||
|
collision_layer = 0
|
||||||
|
|
||||||
|
|
||||||
|
func get_prompt() -> String:
|
||||||
|
return prompt
|
||||||
|
|
||||||
|
|
||||||
|
func interact(player: Node) -> void:
|
||||||
|
if not enabled:
|
||||||
|
return
|
||||||
|
interacted.emit(player)
|
||||||
1
scripts/interactables/interactable.gd.uid
Normal file
1
scripts/interactables/interactable.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://c04k1gckreqao
|
||||||
100
scripts/player/fpv_controller.gd
Normal file
100
scripts/player/fpv_controller.gd
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
class_name FPVController extends CharacterBody3D
|
||||||
|
|
||||||
|
@export var move_speed: float = 3.0
|
||||||
|
@export var mouse_sensitivity: float = 0.0025
|
||||||
|
@export var gravity: float = 9.8
|
||||||
|
|
||||||
|
@onready var camera: Camera3D = $Camera3D
|
||||||
|
@onready var interact_ray: RayCast3D = $Camera3D/InteractRay
|
||||||
|
|
||||||
|
signal interactable_focused(obj: Node)
|
||||||
|
signal interactable_unfocused
|
||||||
|
signal interact_pressed(obj: Node)
|
||||||
|
|
||||||
|
var _captured: bool = false
|
||||||
|
var _locked: bool = false
|
||||||
|
var _focused_obj: Node = null
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_capture_mouse()
|
||||||
|
|
||||||
|
|
||||||
|
func _capture_mouse() -> void:
|
||||||
|
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||||
|
_captured = true
|
||||||
|
|
||||||
|
|
||||||
|
func _release_mouse() -> void:
|
||||||
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||||
|
_captured = false
|
||||||
|
|
||||||
|
|
||||||
|
func _input(event: InputEvent) -> void:
|
||||||
|
if event is InputEventMouseMotion and _captured and not _locked:
|
||||||
|
rotate_y(-event.relative.x * mouse_sensitivity)
|
||||||
|
camera.rotate_x(-event.relative.y * mouse_sensitivity)
|
||||||
|
camera.rotation.x = clamp(camera.rotation.x, -PI * 0.45, PI * 0.45)
|
||||||
|
elif event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
|
||||||
|
if not _captured:
|
||||||
|
_capture_mouse()
|
||||||
|
return
|
||||||
|
if _locked:
|
||||||
|
return
|
||||||
|
if _focused_obj != null:
|
||||||
|
interact_pressed.emit(_focused_obj)
|
||||||
|
if _focused_obj.has_method("interact"):
|
||||||
|
_focused_obj.interact(self)
|
||||||
|
elif event is InputEventKey and event.pressed:
|
||||||
|
if event.keycode == KEY_ESCAPE:
|
||||||
|
if _captured:
|
||||||
|
_release_mouse()
|
||||||
|
else:
|
||||||
|
_capture_mouse()
|
||||||
|
elif event.keycode == KEY_Q:
|
||||||
|
get_tree().quit()
|
||||||
|
|
||||||
|
|
||||||
|
func _physics_process(delta: float) -> void:
|
||||||
|
if _locked:
|
||||||
|
velocity = Vector3.ZERO
|
||||||
|
move_and_slide()
|
||||||
|
_update_focus()
|
||||||
|
return
|
||||||
|
var input_dir: Vector2 = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
|
||||||
|
var direction: Vector3 = (transform.basis * Vector3(input_dir.x, 0.0, input_dir.y)).normalized()
|
||||||
|
velocity.x = direction.x * move_speed
|
||||||
|
velocity.z = direction.z * move_speed
|
||||||
|
if not is_on_floor():
|
||||||
|
velocity.y -= gravity * delta
|
||||||
|
else:
|
||||||
|
velocity.y = 0.0
|
||||||
|
move_and_slide()
|
||||||
|
_update_focus()
|
||||||
|
|
||||||
|
|
||||||
|
func _update_focus() -> void:
|
||||||
|
interact_ray.force_raycast_update()
|
||||||
|
var new_focus: Node = null
|
||||||
|
if interact_ray.is_colliding():
|
||||||
|
var col: Object = interact_ray.get_collider()
|
||||||
|
if col != null and col is Node and col.has_method("get_prompt"):
|
||||||
|
new_focus = col
|
||||||
|
if new_focus != _focused_obj:
|
||||||
|
if new_focus != null:
|
||||||
|
interactable_focused.emit(new_focus)
|
||||||
|
else:
|
||||||
|
interactable_unfocused.emit()
|
||||||
|
_focused_obj = new_focus
|
||||||
|
|
||||||
|
|
||||||
|
func lock_input() -> void:
|
||||||
|
_locked = true
|
||||||
|
|
||||||
|
|
||||||
|
func unlock_input() -> void:
|
||||||
|
_locked = false
|
||||||
|
|
||||||
|
|
||||||
|
func get_focused() -> Node:
|
||||||
|
return _focused_obj
|
||||||
1
scripts/player/fpv_controller.gd.uid
Normal file
1
scripts/player/fpv_controller.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://r5r7c1wxtyot
|
||||||
90
scripts/prologue/act1.gd
Normal file
90
scripts/prologue/act1.gd
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
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()
|
||||||
1
scripts/prologue/act1.gd.uid
Normal file
1
scripts/prologue/act1.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://dcn3oei0t5ley
|
||||||
78
scripts/ui/dialog_manager.gd
Normal file
78
scripts/ui/dialog_manager.gd
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
extends CanvasLayer
|
||||||
|
|
||||||
|
signal advanced
|
||||||
|
|
||||||
|
@onready var subtitle_panel: PanelContainer = $SubtitlePanel
|
||||||
|
@onready var who_label: Label = $SubtitlePanel/V/Who
|
||||||
|
@onready var text_label: Label = $SubtitlePanel/V/Text
|
||||||
|
@onready var hint_label: Label = $SubtitlePanel/V/Hint
|
||||||
|
|
||||||
|
@onready var note_overlay: Control = $NoteOverlay
|
||||||
|
@onready var note_text: Label = $NoteOverlay/Center/P/V2/Text
|
||||||
|
|
||||||
|
@onready var prompt_label: Label = $PromptLabel
|
||||||
|
@onready var hud_label: Label = $HudLabel
|
||||||
|
|
||||||
|
var _waiting: bool = false
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
subtitle_panel.visible = false
|
||||||
|
note_overlay.visible = false
|
||||||
|
prompt_label.visible = false
|
||||||
|
hud_label.visible = false
|
||||||
|
|
||||||
|
|
||||||
|
func say(who: String, line: String) -> Signal:
|
||||||
|
who_label.text = "[%s]" % who
|
||||||
|
text_label.text = line
|
||||||
|
hint_label.text = "ЛКМ или ПРОБЕЛ — дальше"
|
||||||
|
subtitle_panel.visible = true
|
||||||
|
note_overlay.visible = false
|
||||||
|
_waiting = true
|
||||||
|
return advanced
|
||||||
|
|
||||||
|
|
||||||
|
func think(line: String) -> Signal:
|
||||||
|
return say("Я", line)
|
||||||
|
|
||||||
|
|
||||||
|
func show_note(text: String) -> Signal:
|
||||||
|
note_text.text = text
|
||||||
|
note_overlay.visible = true
|
||||||
|
subtitle_panel.visible = false
|
||||||
|
_waiting = true
|
||||||
|
return advanced
|
||||||
|
|
||||||
|
|
||||||
|
func set_prompt(text: String) -> void:
|
||||||
|
if text == "":
|
||||||
|
prompt_label.visible = false
|
||||||
|
return
|
||||||
|
prompt_label.text = "[ЛКМ] %s" % text
|
||||||
|
prompt_label.visible = true
|
||||||
|
|
||||||
|
|
||||||
|
func clear_prompt() -> void:
|
||||||
|
prompt_label.visible = false
|
||||||
|
|
||||||
|
|
||||||
|
func set_hud(text: String) -> void:
|
||||||
|
hud_label.text = text
|
||||||
|
hud_label.visible = text != ""
|
||||||
|
|
||||||
|
|
||||||
|
func _input(event: InputEvent) -> void:
|
||||||
|
if not _waiting:
|
||||||
|
return
|
||||||
|
var advance: bool = false
|
||||||
|
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
|
||||||
|
advance = true
|
||||||
|
elif event is InputEventKey and event.pressed and (event.keycode == KEY_SPACE or event.keycode == KEY_ENTER):
|
||||||
|
advance = true
|
||||||
|
if advance:
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
subtitle_panel.visible = false
|
||||||
|
note_overlay.visible = false
|
||||||
|
_waiting = false
|
||||||
|
advanced.emit()
|
||||||
1
scripts/ui/dialog_manager.gd.uid
Normal file
1
scripts/ui/dialog_manager.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://eb6vorv2mguj
|
||||||
Loading…
Add table
Add a link
Reference in a new issue