This commit is contained in:
Georgiy Gorin
2026-04-23 16:27:03 +03:00
8 changed files with 246 additions and 10 deletions
+71
View File
@@ -0,0 +1,71 @@
[preset.0]
name="Windows Desktop"
platform="Windows Desktop"
runnable=true
dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter="*jpeg"
exclude_filter=""
export_path="../Kick/KickSurvivors.exe"
patches=PackedStringArray()
patch_delta_encoding=false
patch_delta_compression_level_zstd=19
patch_delta_min_reduction=0.1
patch_delta_include_filters="*"
patch_delta_exclude_filters=""
encryption_include_filters=""
encryption_exclude_filters=""
seed=0
encrypt_pck=false
encrypt_directory=false
script_export_mode=2
[preset.0.options]
custom_template/debug=""
custom_template/release=""
debug/export_console_wrapper=1
binary_format/embed_pck=false
texture_format/s3tc_bptc=true
texture_format/etc2_astc=false
shader_baker/enabled=false
binary_format/architecture="x86_64"
codesign/enable=false
codesign/timestamp=true
codesign/timestamp_server_url=""
codesign/digest_algorithm=1
codesign/description=""
codesign/custom_options=PackedStringArray()
application/modify_resources=true
application/icon=""
application/console_wrapper_icon=""
application/icon_interpolation=4
application/file_version=""
application/product_version=""
application/company_name=""
application/product_name=""
application/file_description=""
application/copyright=""
application/trademarks=""
application/export_angle=0
application/export_d3d12=0
application/d3d12_agility_sdk_multiarch=true
ssh_remote_deploy/enabled=false
ssh_remote_deploy/host="user@host_ip"
ssh_remote_deploy/port="22"
ssh_remote_deploy/extra_args_ssh=""
ssh_remote_deploy/extra_args_scp=""
ssh_remote_deploy/run_script="Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}'
$action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}'
$trigger = New-ScheduledTaskTrigger -Once -At 00:00
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings
Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true
Start-ScheduledTask -TaskName godot_remote_debug
while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 }
Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue"
ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue
Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue
Remove-Item -Recurse -Force '{temp_dir}'"
+16
View File
@@ -120,6 +120,22 @@ func setup(type: String, wave: int) -> void:
kick_tier = enemy_level
toughness_tier = enemy_level
_update_label()
_apply_mesh(enemy_level)
func _apply_mesh(level: int) -> void:
var idx := clampi(level, 1, 3)
var mesh_res := load("res://assets/gnome%d.obj" % idx) as Mesh
if mesh_res == null:
return
var new_mat := StandardMaterial3D.new()
var tex := load("res://assets/gnome%d.png" % idx) as Texture2D
if tex != null:
new_mat.albedo_texture = tex
new_mat.albedo_color = Color.WHITE
mesh_node.mesh = mesh_res
mesh_node.material_override = new_mat
mat = new_mat
COLOR_CHASE = Color.WHITE
func _physics_process(delta: float) -> void:
match state:
+11
View File
@@ -0,0 +1,11 @@
class_name GameSettings
## "immortal" — no damage, tier-based kick force
## "survival" — takes damage, fixed kick force
static var difficulty: String = "immortal"
## enemy spawn interval in seconds
static var enemy_spawn_interval: float = 10.0
## item (rock/stick) respawn delay in seconds
static var item_respawn_delay: float = 20.0
+1
View File
@@ -0,0 +1 @@
uid://deu62njwuhjd3
+5
View File
@@ -52,6 +52,11 @@ static func resolve(owner: Node3D, other: Node3D, owner_vel: Vector3) -> bool:
static func _execute_recipe(a: Node3D, b: Node3D, recipe: Dictionary) -> void:
var pos := (a.global_position + b.global_position) * 0.5
var parent := a.get_parent()
# Emit before freeing so respawn counters in Main decrement correctly.
if a.has_signal("destroyed"):
a.emit_signal("destroyed")
if b.has_signal("destroyed"):
b.emit_signal("destroyed")
a.queue_free()
b.queue_free()
var scene: PackedScene = load(recipe["result_scene"])
+6 -6
View File
@@ -177,7 +177,8 @@ func _on_stick_destroyed() -> void:
return
if sticks_on_field + sticks_pending < STICK_LIMIT:
sticks_pending += 1
await get_tree().create_timer(10.0).timeout
await get_tree().create_timer(GameSettings.item_respawn_delay).timeout
sticks_pending -= 1
if game_active:
_spawn_single_stick()
@@ -213,7 +214,7 @@ func _on_rock_destroyed() -> void:
return
if rocks_on_field + rocks_pending < _get_rock_limit():
rocks_pending += 1
await get_tree().create_timer(10.0).timeout
await get_tree().create_timer(GameSettings.item_respawn_delay).timeout
rocks_pending -= 1
if game_active:
_spawn_single_rock()
@@ -244,7 +245,7 @@ func _start_game() -> void:
Enemy.first_iron_spawned = false
Enemy.first_essence_spawned = false
_update_labels()
spawn_timer.wait_time = 1.4
spawn_timer.wait_time = GameSettings.enemy_spawn_interval
spawn_timer.connect("timeout", _on_spawn_timer)
spawn_timer.start()
@@ -252,7 +253,7 @@ func _on_spawn_timer() -> void:
if not game_active or upgrading:
return
_spawn_enemy()
spawn_timer.wait_time = SPAWN_TIME # max(0.25, 1.4 - wave * 0.07)
spawn_timer.wait_time = GameSettings.enemy_spawn_interval
func _spawn_enemy() -> void:
var enemy := ENEMY_SCENE.instantiate() as CharacterBody3D
@@ -306,6 +307,7 @@ func _spawn_upgraded_enemy(pos: Vector3, type: String, level: int, w: int) -> Ch
enemy.kick_tier = level
enemy.toughness_tier = level
enemy.call("_update_label")
enemy.call("_apply_mesh", level)
enemy.global_position = pos
enemy.connect("died", _on_enemy_died)
enemy.connect("merged", _on_enemy_merged)
@@ -318,8 +320,6 @@ func _spawn_upgraded_enemy(pos: Vector3, type: String, level: int, w: int) -> Ch
var old_size: Vector3 = s3d.size
col_shape.shape = BoxShape3D.new()
(col_shape.shape as BoxShape3D).size = old_size * s
var color := Color(1.0, 1.0, 0.5) if level > 2 else Color(1.0, 0.9, 0.3)
tw.tween_property(enemy.mat, "albedo_color", color, 0.25)
if level >= 3 and not first_boss_spawned:
first_boss_spawned = true
_start_boss_phase()
+130 -2
View File
@@ -3,6 +3,7 @@ extends Control
static var volume: float = 100.0
var settings_panel: Panel
var difficulty_panel: Panel
func _ready() -> void:
process_mode = Node.PROCESS_MODE_ALWAYS
@@ -46,17 +47,63 @@ func _build_ui() -> void:
vbox.add_child(_btn("Выход", _on_exit))
_build_settings_panel()
_build_difficulty_panel()
func _big_btn(text: String, width: float, cb: Callable) -> Button:
var b := _btn(text, cb)
b.custom_minimum_size = Vector2(width, 72)
b.add_theme_font_size_override("font_size", 18)
return b
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)
for state in ["normal", "hover", "pressed", "focus", "disabled"]:
var sb := StyleBoxFlat.new()
sb.bg_color = Color(0.18, 0.13, 0.30) if state == "normal" else (
Color(0.28, 0.20, 0.46) if state == "hover" else
Color(0.12, 0.09, 0.22))
sb.border_width_left = 1
sb.border_width_right = 1
sb.border_width_top = 1
sb.border_width_bottom = 1
sb.border_color = Color(0.50, 0.38, 0.75)
sb.corner_radius_top_left = 6
sb.corner_radius_top_right = 6
sb.corner_radius_bottom_left = 6
sb.corner_radius_bottom_right = 6
sb.content_margin_left = 20
sb.content_margin_right = 20
sb.content_margin_top = 12
sb.content_margin_bottom = 12
b.add_theme_stylebox_override(state, sb)
b.connect("pressed", cb)
return b
static func _make_opaque_panel() -> Panel:
var p := Panel.new()
var sb := StyleBoxFlat.new()
sb.bg_color = Color(0.08, 0.06, 0.14)
sb.border_width_left = 2
sb.border_width_right = 2
sb.border_width_top = 2
sb.border_width_bottom = 2
sb.border_color = Color(0.35, 0.28, 0.55)
sb.corner_radius_top_left = 8
sb.corner_radius_top_right = 8
sb.corner_radius_bottom_left = 8
sb.corner_radius_bottom_right = 8
sb.content_margin_left = 24
sb.content_margin_right = 24
sb.content_margin_top = 20
sb.content_margin_bottom = 20
p.add_theme_stylebox_override("panel", sb)
return p
func _build_settings_panel() -> void:
settings_panel = Panel.new()
settings_panel = _make_opaque_panel()
settings_panel.visible = false
settings_panel.anchor_left = 0.5
settings_panel.anchor_right = 0.5
@@ -105,8 +152,89 @@ 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 _build_difficulty_panel() -> void:
difficulty_panel = _make_opaque_panel()
difficulty_panel.visible = false
difficulty_panel.anchor_left = 0.5
difficulty_panel.anchor_right = 0.5
difficulty_panel.anchor_top = 0.5
difficulty_panel.anchor_bottom = 0.5
difficulty_panel.offset_left = -310
difficulty_panel.offset_right = 310
difficulty_panel.offset_top = -240
difficulty_panel.offset_bottom = 240
add_child(difficulty_panel)
var margin := MarginContainer.new()
margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
margin.add_theme_constant_override("margin_left", 24)
margin.add_theme_constant_override("margin_right", 24)
margin.add_theme_constant_override("margin_top", 20)
margin.add_theme_constant_override("margin_bottom", 20)
difficulty_panel.add_child(margin)
var vbox := VBoxContainer.new()
vbox.alignment = BoxContainer.ALIGNMENT_CENTER
vbox.add_theme_constant_override("separation", 16)
margin.add_child(vbox)
var title := Label.new()
title.text = "Выберите сложность"
title.add_theme_font_size_override("font_size", 26)
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
vbox.add_child(title)
vbox.add_child(_big_btn("Бессмертный\nНет урона · тир пинков от мелких врагов", 540, func() -> void:
GameSettings.difficulty = "immortal"
get_tree().change_scene_to_file("res://scenes/Main.tscn")
))
vbox.add_child(_big_btn("Выживание\nПолучаешь урон · фиксированная сила пинка", 540, func() -> void:
GameSettings.difficulty = "survival"
get_tree().change_scene_to_file("res://scenes/Main.tscn")
))
# Spawn speed row
var spawn_lbl := Label.new()
spawn_lbl.text = "Скорость спавна:"
spawn_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
spawn_lbl.add_theme_font_size_override("font_size", 16)
spawn_lbl.add_theme_color_override("font_color", Color(0.85, 0.78, 1.0))
vbox.add_child(spawn_lbl)
var hbox := HBoxContainer.new()
hbox.alignment = BoxContainer.ALIGNMENT_CENTER
hbox.add_theme_constant_override("separation", 10)
vbox.add_child(hbox)
var spawn_options := [
["Медленно", 15.0, 35.0],
["Нормально", 10.0, 20.0],
["Быстро", 5.0, 8.0],
]
var spawn_btns: Array[Button] = []
for opt in spawn_options:
var sb_btn := _btn(opt[0] as String, Callable())
sb_btn.custom_minimum_size = Vector2(110, 40)
sb_btn.add_theme_font_size_override("font_size", 15)
var enemy_iv: float = opt[1]
var item_iv: float = opt[2]
sb_btn.connect("pressed", func() -> void:
GameSettings.enemy_spawn_interval = enemy_iv
GameSettings.item_respawn_delay = item_iv
for b in spawn_btns:
b.modulate = Color(1, 1, 1)
sb_btn.modulate = Color(0.7, 1.0, 0.5)
)
hbox.add_child(sb_btn)
spawn_btns.append(sb_btn)
# default: normal
spawn_btns[1].modulate = Color(0.7, 1.0, 0.5)
vbox.add_child(_btn("Назад", func(): difficulty_panel.visible = false))
func _on_play() -> void:
get_tree().change_scene_to_file("res://scenes/Main.tscn")
difficulty_panel.visible = true
func _on_settings() -> void:
settings_panel.visible = true
+6 -2
View File
@@ -85,7 +85,7 @@ func _input(event: InputEvent) -> void:
func _physics_process(delta: float) -> void:
if not is_alive:
return
is_shielding = shield_tier > 0 and Input.is_key_pressed(KEY_SHIFT)
is_shielding = shield_tier > 0 and Input.is_key_pressed(KEY_SPACE)
_handle_movement(delta)
_handle_kick(delta)
_handle_iframes(delta)
@@ -180,7 +180,9 @@ func _do_kick() -> void:
var obj_toughness: int = best.get("toughness_tier") if best.get("toughness_tier") != null else 0
var diff_tier := kick_tier - obj_toughness
var force: float
if diff_tier < 0:
if GameSettings.difficulty == "survival":
force = 50.0
elif diff_tier < 0:
force = 15.0
elif diff_tier == 0:
force = 50.0
@@ -229,6 +231,8 @@ func receive_kick(direction: Vector3, force: float) -> void:
_squish_effect()
func take_damage(amount: int, attacker_toughness: int = 0) -> void:
if GameSettings.difficulty == "immortal":
return
if not is_alive or invincible_timer > 0.0:
return
invincible_timer = IFRAMES_DURATION