This commit is contained in:
2026-04-23 13:55:06 +03:00
parent dde4a6431e
commit 271c99ae13
9 changed files with 223 additions and 2 deletions
+1 -1
View File
@@ -15,6 +15,6 @@ compatibility/default_parent_skeleton_in_mesh_instance_3d=true
[application] [application]
config/name="KickSurvivors" config/name="KickSurvivors"
run/main_scene="res://scenes/Main.tscn" run/main_scene="res://scenes/MainMenu.tscn"
config/features=PackedStringArray("4.6", "Forward Plus") config/features=PackedStringArray("4.6", "Forward Plus")
config/icon="res://icon.svg" config/icon="res://icon.svg"
+8
View File
@@ -0,0 +1,8 @@
[gd_scene format=3 uid="uid://mainmenu2024"]
[ext_resource type="Script" path="res://scripts/MainMenu.gd" id="1_menu"]
[node name="MainMenu" type="Control"]
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource("1_menu")
+3
View File
@@ -21,3 +21,6 @@ func _process(delta: float) -> void:
func interact(player: Node) -> void: func interact(player: Node) -> void:
if player.call("apply_iron_shield"): if player.call("apply_iron_shield"):
queue_free() queue_free()
var mains := get_tree().get_nodes_in_group("main")
if not mains.is_empty():
mains[0].call("show_tutorial", "Tutorial_Shield")
+3
View File
@@ -21,3 +21,6 @@ func _process(delta: float) -> void:
func interact(player: Node) -> void: func interact(player: Node) -> void:
if player.call("apply_leather_armor"): if player.call("apply_leather_armor"):
queue_free() queue_free()
var mains := get_tree().get_nodes_in_group("main")
if not mains.is_empty():
mains[0].call("show_tutorial", "Tutorial_LeatherArmor")
+3
View File
@@ -23,3 +23,6 @@ func _process(delta: float) -> void:
func interact(player: Node) -> void: func interact(player: Node) -> void:
player.call("apply_upgrade_boots", 2.0, tier) player.call("apply_upgrade_boots", 2.0, tier)
queue_free() queue_free()
var mains := get_tree().get_nodes_in_group("main")
if not mains.is_empty():
mains[0].call("show_tutorial", "Tutorial_LeatherBoots")
+86 -1
View File
@@ -27,6 +27,16 @@ var kills_for_next: int = 10
var game_active: bool = false var game_active: bool = false
var upgrading: bool = false var upgrading: bool = false
# Tutorial
var tutorial_canvas: CanvasLayer
var tutorial_image: TextureRect
var tutorial_hint: Label
var tutorial_active: bool = false
var tutorial_hint_ready: bool = false
var tutorial_timer: float = 0.0
var tutorial_on_dismiss: Callable = Callable()
var shown_tutorials: Dictionary = {}
# UI nodes # UI nodes
var canvas: CanvasLayer var canvas: CanvasLayer
var score_label: Label var score_label: Label
@@ -40,17 +50,25 @@ var upgrade_panel: Panel
var gameover_panel: Panel var gameover_panel: Panel
func _ready() -> void: func _ready() -> void:
process_mode = Node.PROCESS_MODE_ALWAYS
_spawn_level() _spawn_level()
_create_camera() _create_camera()
_create_ui() _create_ui()
_create_tutorial_overlay()
_spawn_player() _spawn_player()
_spawn_rocks() _spawn_rocks()
_spawn_sticks() _spawn_sticks()
_start_game()
add_to_group("main") add_to_group("main")
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
show_tutorial("Tutorial_StartGame", _start_game)
func _input(event: InputEvent) -> void: func _input(event: InputEvent) -> void:
if tutorial_active and tutorial_hint_ready:
var mb := event as InputEventMouseButton
if mb != null and mb.button_index == MOUSE_BUTTON_LEFT and mb.pressed:
_dismiss_tutorial()
return
var motion := event as InputEventMouseMotion var motion := event as InputEventMouseMotion
if motion != null and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED: if motion != null and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
cam_yaw -= motion.relative.x * MOUSE_SENS cam_yaw -= motion.relative.x * MOUSE_SENS
@@ -80,6 +98,12 @@ func _create_camera() -> void:
add_child(camera) add_child(camera)
func _process(delta: float) -> void: func _process(delta: float) -> void:
if tutorial_active:
tutorial_timer -= delta
if tutorial_timer <= 0.0 and not tutorial_hint_ready:
tutorial_hint_ready = true
tutorial_hint.visible = true
return
if is_instance_valid(player): if is_instance_valid(player):
var yaw_r: float = deg_to_rad(cam_yaw) var yaw_r: float = deg_to_rad(cam_yaw)
var pitch_r: float = deg_to_rad(cam_pitch) var pitch_r: float = deg_to_rad(cam_pitch)
@@ -335,6 +359,67 @@ func _restart() -> void:
get_tree().paused = false get_tree().paused = false
get_tree().reload_current_scene() get_tree().reload_current_scene()
# ─── Tutorial ─────────────────────────────────────────────────────────────────
func _create_tutorial_overlay() -> void:
tutorial_canvas = CanvasLayer.new()
tutorial_canvas.process_mode = Node.PROCESS_MODE_ALWAYS
tutorial_canvas.visible = false
add_child(tutorial_canvas)
var bg := ColorRect.new()
bg.color = Color(0.0, 0.0, 0.0, 0.78)
bg.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
tutorial_canvas.add_child(bg)
tutorial_image = TextureRect.new()
tutorial_image.anchor_left = 0.1
tutorial_image.anchor_right = 0.9
tutorial_image.anchor_top = 0.07
tutorial_image.anchor_bottom = 0.84
tutorial_image.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
tutorial_image.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL
tutorial_canvas.add_child(tutorial_image)
tutorial_hint = Label.new()
tutorial_hint.anchor_left = 0.5
tutorial_hint.anchor_right = 0.5
tutorial_hint.anchor_top = 0.88
tutorial_hint.offset_left = -280
tutorial_hint.offset_right = 280
tutorial_hint.text = "Нажмите ЛКМ чтобы продолжить"
tutorial_hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
tutorial_hint.add_theme_font_size_override("font_size", 22)
tutorial_hint.add_theme_color_override("font_color", Color.WHITE)
tutorial_hint.add_theme_color_override("font_shadow_color", Color(0, 0, 0, 0.9))
tutorial_hint.add_theme_constant_override("shadow_offset_x", 2)
tutorial_hint.add_theme_constant_override("shadow_offset_y", 2)
tutorial_hint.visible = false
tutorial_canvas.add_child(tutorial_hint)
func show_tutorial(key: String, on_dismiss: Callable = Callable()) -> void:
if shown_tutorials.get(key, false):
if on_dismiss.is_valid():
on_dismiss.call()
return
shown_tutorials[key] = true
var path := "res://assets/%s.png" % key
tutorial_image.texture = load(path) if ResourceLoader.exists(path) else null
tutorial_on_dismiss = on_dismiss
tutorial_active = true
tutorial_hint_ready = false
tutorial_timer = 3.0
tutorial_hint.visible = false
tutorial_canvas.visible = true
get_tree().paused = true
func _dismiss_tutorial() -> void:
tutorial_active = false
tutorial_canvas.visible = false
get_tree().paused = false
if tutorial_on_dismiss.is_valid():
tutorial_on_dismiss.call()
# ─── UI ─────────────────────────────────────────────────────────────────────── # ─── UI ───────────────────────────────────────────────────────────────────────
func _create_ui() -> void: func _create_ui() -> void:
+115
View File
@@ -0,0 +1,115 @@
extends Control
static var volume: float = 100.0
var settings_panel: Panel
func _ready() -> void:
process_mode = Node.PROCESS_MODE_ALWAYS
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
_build_ui()
_apply_volume(volume)
func _build_ui() -> void:
var bg := ColorRect.new()
bg.color = Color(0.06, 0.04, 0.10)
bg.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
add_child(bg)
var title := Label.new()
title.text = "KickSurvivors"
title.add_theme_font_size_override("font_size", 52)
title.add_theme_color_override("font_color", Color.WHITE)
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
title.anchor_left = 0.5
title.anchor_right = 0.5
title.anchor_top = 0.0
title.offset_left = -200
title.offset_right = 200
title.offset_top = 110
add_child(title)
var vbox := VBoxContainer.new()
vbox.add_theme_constant_override("separation", 14)
vbox.anchor_left = 0.5
vbox.anchor_right = 0.5
vbox.anchor_top = 0.5
vbox.anchor_bottom = 0.5
vbox.offset_left = -110
vbox.offset_right = 110
vbox.offset_top = -90
vbox.offset_bottom = 90
add_child(vbox)
vbox.add_child(_btn("Играть", _on_play))
vbox.add_child(_btn("Настройки", _on_settings))
vbox.add_child(_btn("Выход", _on_exit))
_build_settings_panel()
func _btn(text: String, cb: Callable) -> Button:
var b := Button.new()
b.text = text
b.custom_minimum_size = Vector2(220, 54)
b.add_theme_font_size_override("font_size", 20)
b.connect("pressed", cb)
return b
func _build_settings_panel() -> void:
settings_panel = Panel.new()
settings_panel.visible = false
settings_panel.anchor_left = 0.5
settings_panel.anchor_right = 0.5
settings_panel.anchor_top = 0.5
settings_panel.anchor_bottom = 0.5
settings_panel.offset_left = -200
settings_panel.offset_right = 200
settings_panel.offset_top = -130
settings_panel.offset_bottom = 130
add_child(settings_panel)
var vbox := VBoxContainer.new()
vbox.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
vbox.alignment = BoxContainer.ALIGNMENT_CENTER
vbox.add_theme_constant_override("separation", 18)
settings_panel.add_child(vbox)
var lbl := Label.new()
lbl.text = "Настройки"
lbl.add_theme_font_size_override("font_size", 26)
lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
vbox.add_child(lbl)
var vol_lbl := Label.new()
vol_lbl.name = "VolumeLabel"
vol_lbl.text = "Громкость: %d" % int(volume)
vol_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
vbox.add_child(vol_lbl)
var slider := HSlider.new()
slider.min_value = 0
slider.max_value = 100
slider.step = 1
slider.value = volume
slider.custom_minimum_size = Vector2(320, 32)
slider.connect("value_changed", func(v: float) -> void:
volume = v
vol_lbl.text = "Громкость: %d" % int(v)
_apply_volume(v)
)
vbox.add_child(slider)
vbox.add_child(_btn("Назад", func(): settings_panel.visible = false))
func _apply_volume(v: float) -> void:
var db := linear_to_db(v / 100.0) if v > 0.0 else -80.0
AudioServer.set_bus_volume_db(0, db)
func _on_play() -> void:
get_tree().change_scene_to_file("res://scenes/Main.tscn")
func _on_settings() -> void:
settings_panel.visible = true
func _on_exit() -> void:
get_tree().quit()
+1
View File
@@ -0,0 +1 @@
uid://on1o20vpycgm
+3
View File
@@ -21,3 +21,6 @@ func _process(delta: float) -> void:
func interact(player: Node) -> void: func interact(player: Node) -> void:
if player.call("apply_wooden_shield"): if player.call("apply_wooden_shield"):
queue_free() queue_free()
var mains := get_tree().get_nodes_in_group("main")
if not mains.is_empty():
mains[0].call("show_tutorial", "Tutorial_Shield")