tutorial
This commit is contained in:
+1
-1
@@ -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"
|
||||||
|
|||||||
@@ -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")
|
||||||
@@ -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")
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
@@ -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:
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://on1o20vpycgm
|
||||||
@@ -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")
|
||||||
|
|||||||
Reference in New Issue
Block a user