Compare commits

..

17 Commits

Author SHA1 Message Date
DragonSpirit af15c477b9 add hit and merge vfx 2026-04-23 14:59:14 +03:00
DragonSpirit 5d9b0ce738 add kickable tooltips 2026-04-23 14:44:21 +03:00
DragonSpirit 5f61c26563 ui items slots 2026-04-23 14:40:55 +03:00
DragonSpirit 65ba9d2960 fix tutorial 2026-04-23 14:35:59 +03:00
Georgiy Gorin f6f451cffb Жоска наговнякал полный сетап моба 2026-04-23 14:25:58 +03:00
DragonSpirit 271c99ae13 tutorial 2026-04-23 13:55:06 +03:00
DragonSpirit dde4a6431e add tooltip on enemies, update enemy type on merge 2026-04-23 13:47:31 +03:00
Georgiy Gorin d7368e7fa7 Balance update 2026-04-23 13:09:23 +03:00
DragonSpirit 9981ed1ae3 fix metal plate pickup 2026-04-23 13:08:44 +03:00
DragonSpirit e956c4353c fix near enemy kick, fix huge kick on low tier 2026-04-23 11:48:00 +03:00
DragonSpirit 1b3732ea0f do not throw stick into player 2026-04-23 11:43:33 +03:00
DragonSpirit 52d0b00655 fix enemy aligning, add sticks respawn 2026-04-23 11:42:17 +03:00
DragonSpirit c9489321ae add shields and tiers on ui 2026-04-23 11:36:01 +03:00
DragonSpirit bec9389bb8 add animations 2026-04-23 11:23:43 +03:00
Nikolay Fedorov c7f0d9e176 update player scene 2026-04-23 11:00:08 +03:00
Georgiy Gorin 7beb9ffbac Exp updated collisions and tougness tier 2026-04-23 10:10:40 +03:00
DragonSpirit cdb0ffd6a5 exp 2026-04-23 00:51:28 +03:00
74 changed files with 64727 additions and 109 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

+40
View File
@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dcwymnn6xf05c"
path="res://.godot/imported/Tutorial_LeatherArmor.jpeg-da671d8566606a993a070058c18b5a5b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/Tutorial_LeatherArmor.jpeg"
dest_files=["res://.godot/imported/Tutorial_LeatherArmor.jpeg-da671d8566606a993a070058c18b5a5b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

+40
View File
@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://tg6gtoufojyb"
path="res://.godot/imported/Tutorial_LeatherBoots.jpeg-83a29df86efb4f59432b80ca2f2fd7eb.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/Tutorial_LeatherBoots.jpeg"
dest_files=["res://.godot/imported/Tutorial_LeatherBoots.jpeg-83a29df86efb4f59432b80ca2f2fd7eb.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

+40
View File
@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://q86mtqd0if14"
path="res://.godot/imported/Tutorial_StartGame.jpeg-8531e3fdc81134418a0a0e59efc0b95d.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/Tutorial_StartGame.jpeg"
dest_files=["res://.godot/imported/Tutorial_StartGame.jpeg-8531e3fdc81134418a0a0e59efc0b95d.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

+40
View File
@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://baeea1yfs0cnn"
path="res://.godot/imported/Tutorial_shield.jpeg-d9b563c9ca34dc1a19e82a0ec964c1b9.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/Tutorial_shield.jpeg"
dest_files=["res://.godot/imported/Tutorial_shield.jpeg-d9b563c9ca34dc1a19e82a0ec964c1b9.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
+16982
View File
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
[remap]
importer="wavefront_obj"
importer_version=1
type="Mesh"
uid="uid://bwoh5cyhk1tvx"
path="res://.godot/imported/gnome1.obj-3c2c126ceccc265716f51fdf5f468dcf.mesh"
[deps]
files=["res://.godot/imported/gnome1.obj-3c2c126ceccc265716f51fdf5f468dcf.mesh"]
source_file="res://assets/gnome1.obj"
dest_files=["res://.godot/imported/gnome1.obj-3c2c126ceccc265716f51fdf5f468dcf.mesh", "res://.godot/imported/gnome1.obj-3c2c126ceccc265716f51fdf5f468dcf.mesh"]
[params]
generate_tangents=true
generate_lods=true
generate_shadow_mesh=true
generate_lightmap_uv2=false
generate_lightmap_uv2_texel_size=0.2
scale_mesh=Vector3(1, 1, 1)
offset_mesh=Vector3(0, 0, 0)
force_disable_mesh_compression=false
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

+41
View File
@@ -0,0 +1,41 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cu3g76pfo5msw"
path.s3tc="res://.godot/imported/gnome1.png-2233fedc9a63fc34690b63fb81a732de.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/gnome1.png"
dest_files=["res://.godot/imported/gnome1.png-2233fedc9a63fc34690b63fb81a732de.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
+17161
View File
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
[remap]
importer="wavefront_obj"
importer_version=1
type="Mesh"
uid="uid://dx4huc0nh1b4u"
path="res://.godot/imported/gnome2.obj-fd55585fee29d1908e34d48f742f0315.mesh"
[deps]
files=["res://.godot/imported/gnome2.obj-fd55585fee29d1908e34d48f742f0315.mesh"]
source_file="res://assets/gnome2.obj"
dest_files=["res://.godot/imported/gnome2.obj-fd55585fee29d1908e34d48f742f0315.mesh", "res://.godot/imported/gnome2.obj-fd55585fee29d1908e34d48f742f0315.mesh"]
[params]
generate_tangents=true
generate_lods=true
generate_shadow_mesh=true
generate_lightmap_uv2=false
generate_lightmap_uv2_texel_size=0.2
scale_mesh=Vector3(1, 1, 1)
offset_mesh=Vector3(0, 0, 0)
force_disable_mesh_compression=false
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

+41
View File
@@ -0,0 +1,41 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://5l5wflgoofrd"
path.s3tc="res://.godot/imported/gnome2.png-d8c1a3eb7530d4f2929868991bf60044.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/gnome2.png"
dest_files=["res://.godot/imported/gnome2.png-d8c1a3eb7530d4f2929868991bf60044.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
+16839
View File
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
[remap]
importer="wavefront_obj"
importer_version=1
type="Mesh"
uid="uid://dkxr5amwv7i0p"
path="res://.godot/imported/gnome3.obj-ff8c62c7c2be5bf0c9d9db251dfcc714.mesh"
[deps]
files=["res://.godot/imported/gnome3.obj-ff8c62c7c2be5bf0c9d9db251dfcc714.mesh"]
source_file="res://assets/gnome3.obj"
dest_files=["res://.godot/imported/gnome3.obj-ff8c62c7c2be5bf0c9d9db251dfcc714.mesh", "res://.godot/imported/gnome3.obj-ff8c62c7c2be5bf0c9d9db251dfcc714.mesh"]
[params]
generate_tangents=true
generate_lods=true
generate_shadow_mesh=true
generate_lightmap_uv2=false
generate_lightmap_uv2_texel_size=0.2
scale_mesh=Vector3(1, 1, 1)
offset_mesh=Vector3(0, 0, 0)
force_disable_mesh_compression=false
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

+41
View File
@@ -0,0 +1,41 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://baqjuo2utnor2"
path.s3tc="res://.godot/imported/gnome3.png-49c96522ff8f4e952cebf9b5596c03d1.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/gnome3.png"
dest_files=["res://.godot/imported/gnome3.png-49c96522ff8f4e952cebf9b5596c03d1.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
[remap]
importer="wavefront_obj"
importer_version=1
type="Mesh"
uid="uid://b3m2872sdp3ep"
path="res://.godot/imported/stick.obj-4b867b5d040e587c60e2778589aa0361.mesh"
[deps]
files=["res://.godot/imported/stick.obj-4b867b5d040e587c60e2778589aa0361.mesh"]
source_file="res://assets/meshes/stick.obj"
dest_files=["res://.godot/imported/stick.obj-4b867b5d040e587c60e2778589aa0361.mesh", "res://.godot/imported/stick.obj-4b867b5d040e587c60e2778589aa0361.mesh"]
[params]
generate_tangents=true
generate_lods=true
generate_shadow_mesh=true
generate_lightmap_uv2=false
generate_lightmap_uv2_texel_size=0.2
scale_mesh=Vector3(1, 1, 1)
offset_mesh=Vector3(0, 0, 0)
force_disable_mesh_compression=false
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
[remap]
importer="wavefront_obj"
importer_version=1
type="Mesh"
uid="uid://dwb4g8t8oeycp"
path="res://.godot/imported/stone_big.obj-27b6517edbc7f72b68da44e569ddc192.mesh"
[deps]
files=["res://.godot/imported/stone_big.obj-27b6517edbc7f72b68da44e569ddc192.mesh"]
source_file="res://assets/meshes/stone_big.obj"
dest_files=["res://.godot/imported/stone_big.obj-27b6517edbc7f72b68da44e569ddc192.mesh", "res://.godot/imported/stone_big.obj-27b6517edbc7f72b68da44e569ddc192.mesh"]
[params]
generate_tangents=true
generate_lods=true
generate_shadow_mesh=true
generate_lightmap_uv2=false
generate_lightmap_uv2_texel_size=0.2
scale_mesh=Vector3(1, 1, 1)
offset_mesh=Vector3(0, 0, 0)
force_disable_mesh_compression=false
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
[remap]
importer="wavefront_obj"
importer_version=1
type="Mesh"
uid="uid://c06flbdipxqac"
path="res://.godot/imported/stone_small.obj-9c7a28c51451d957607ab126de71ab59.mesh"
[deps]
files=["res://.godot/imported/stone_small.obj-9c7a28c51451d957607ab126de71ab59.mesh"]
source_file="res://assets/meshes/stone_small.obj"
dest_files=["res://.godot/imported/stone_small.obj-9c7a28c51451d957607ab126de71ab59.mesh", "res://.godot/imported/stone_small.obj-9c7a28c51451d957607ab126de71ab59.mesh"]
[params]
generate_tangents=true
generate_lods=true
generate_shadow_mesh=true
generate_lightmap_uv2=false
generate_lightmap_uv2_texel_size=0.2
scale_mesh=Vector3(1, 1, 1)
offset_mesh=Vector3(0, 0, 0)
force_disable_mesh_compression=false
Binary file not shown.
+42
View File
@@ -0,0 +1,42 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://b3xjq4m8fhonq"
path="res://.godot/imported/player_man.glb-c09ea8d824dd49bf945f1e4f9dbd3bba.scn"
[deps]
source_file="res://assets/player_man.glb"
dest_files=["res://.godot/imported/player_man.glb-c09ea8d824dd49bf945f1e4f9dbd3bba.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
gltf/naming_version=2
gltf/embedded_image_handling=1
Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

+41
View File
@@ -0,0 +1,41 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c2k7wyt7ngfgv"
path.s3tc="res://.godot/imported/player_man.png-3e0a4cc36eb1a6978c391c5bc2af439e.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/player_man.png"
dest_files=["res://.godot/imported/player_man.png-3e0a4cc36eb1a6978c391c5bc2af439e.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
+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"
+28 -3
View File
@@ -1,22 +1,47 @@
[gd_scene format=3 uid="uid://cxk2mn5oab341"] [gd_scene format=3 uid="uid://cxk2mn5oab341"]
[ext_resource type="Script" uid="uid://ujlvt8u71hg1" path="res://scripts/Enemy.gd" id="1_enemy"] [ext_resource type="Script" uid="uid://ujlvt8u71hg1" path="res://scripts/Enemy.gd" id="1_enemy"]
[ext_resource type="ArrayMesh" uid="uid://dw74ofxlfq4tq" path="res://assets/gnome.obj" id="2_55jtl"] [ext_resource type="Texture2D" uid="uid://cu3g76pfo5msw" path="res://assets/gnome1.png" id="2_mpr68"]
[ext_resource type="ArrayMesh" uid="uid://bwoh5cyhk1tvx" path="res://assets/gnome1.obj" id="3_ft8do"]
[ext_resource type="ArrayMesh" uid="uid://dx4huc0nh1b4u" path="res://assets/gnome2.obj" id="4_iwav8"]
[ext_resource type="Texture2D" uid="uid://5l5wflgoofrd" path="res://assets/gnome2.png" id="5_1i2q2"]
[ext_resource type="ArrayMesh" uid="uid://dkxr5amwv7i0p" path="res://assets/gnome3.obj" id="6_ly5gv"]
[ext_resource type="Texture2D" uid="uid://baqjuo2utnor2" path="res://assets/gnome3.png" id="7_82o86"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
albedo_color = Color(1, 0.28, 0.18, 1) albedo_texture = ExtResource("2_mpr68")
roughness = 0.8 roughness = 0.8
[sub_resource type="BoxShape3D" id="BoxShape3D_1"] [sub_resource type="BoxShape3D" id="BoxShape3D_1"]
size = Vector3(0.85, 0.85, 0.85) size = Vector3(0.85, 0.85, 0.85)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1gqeq"]
albedo_texture = ExtResource("5_1i2q2")
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_isvm2"]
albedo_texture = ExtResource("7_82o86")
[node name="Enemy" type="CharacterBody3D" unique_id=1639801124] [node name="Enemy" type="CharacterBody3D" unique_id=1639801124]
script = ExtResource("1_enemy") script = ExtResource("1_enemy")
[node name="BodyMesh" type="MeshInstance3D" parent="." unique_id=68242945] [node name="BodyMesh" type="MeshInstance3D" parent="." unique_id=68242945]
material_override = SubResource("StandardMaterial3D_1") material_override = SubResource("StandardMaterial3D_1")
mesh = ExtResource("2_55jtl") mesh = ExtResource("3_ft8do")
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=379819225] [node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=379819225]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.425, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.425, 0)
shape = SubResource("BoxShape3D_1") shape = SubResource("BoxShape3D_1")
[node name="BodyMesh1" type="MeshInstance3D" parent="." unique_id=7037801]
visible = false
material_override = SubResource("StandardMaterial3D_1")
mesh = ExtResource("4_iwav8")
skeleton = NodePath("../BodyMesh")
surface_material_override/0 = SubResource("StandardMaterial3D_1gqeq")
[node name="BodyMesh2" type="MeshInstance3D" parent="." unique_id=299444928]
visible = false
material_override = SubResource("StandardMaterial3D_1")
mesh = ExtResource("6_ly5gv")
skeleton = NodePath("../BodyMesh1")
surface_material_override/0 = SubResource("StandardMaterial3D_isvm2")
+2 -2
View File
@@ -12,7 +12,7 @@ roughness = 0.4
size = Vector3(0.38, 0.22, 0.5) size = Vector3(0.38, 0.22, 0.5)
[sub_resource type="BoxShape3D" id="BoxShape3D_1"] [sub_resource type="BoxShape3D" id="BoxShape3D_1"]
size = Vector3(0.38, 0.22, 0.5) size = Vector3(1.3992603, 0.58835024, 0.49035645)
[node name="Iron" type="CharacterBody3D" unique_id=611454609] [node name="Iron" type="CharacterBody3D" unique_id=611454609]
script = ExtResource("1_iron") script = ExtResource("1_iron")
@@ -23,5 +23,5 @@ material_override = SubResource("StandardMaterial3D_1")
mesh = SubResource("BoxMesh_1") mesh = SubResource("BoxMesh_1")
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=665808333] [node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=665808333]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.2, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00068727136, 0.22091705, -0.0048217773)
shape = SubResource("BoxShape3D_1") shape = SubResource("BoxShape3D_1")
+26
View File
@@ -0,0 +1,26 @@
[gd_scene format=3 uid="uid://1uwmdnvgyaii"]
[ext_resource type="Script" path="res://scripts/IronShield.gd" id="1_ishield"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
albedo_color = Color(0.55, 0.58, 0.62, 1)
roughness = 0.4
metallic = 0.7
[sub_resource type="BoxMesh" id="BoxMesh_1"]
size = Vector3(0.55, 0.7, 0.1)
[node name="IronShield" type="Node3D"]
script = ExtResource("1_ishield")
[node name="ShieldMesh" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.35, 0)
material_override = SubResource("StandardMaterial3D_1")
mesh = SubResource("BoxMesh_1")
[node name="Tooltip" type="Label3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.3, 0)
billboard = 1
text = "[E] Iron Shield
Tier 2 | Hold Shift to block"
outline_size = 6
+3 -3
View File
@@ -10,16 +10,16 @@ roughness = 0.85
size = Vector3(0.5, 0.06, 0.35) size = Vector3(0.5, 0.06, 0.35)
[sub_resource type="BoxShape3D" id="BoxShape3D_1"] [sub_resource type="BoxShape3D" id="BoxShape3D_1"]
size = Vector3(0.5, 0.06, 0.35) size = Vector3(0.7527008, 0.108486325, 0.6336487)
[node name="Leather" type="CharacterBody3D" unique_id=267529517] [node name="Leather" type="CharacterBody3D" unique_id=267529517]
script = ExtResource("1_leather") script = ExtResource("1_leather")
[node name="LeatherMesh" type="MeshInstance3D" parent="." unique_id=2045837180] [node name="LeatherMesh" type="MeshInstance3D" parent="." unique_id=2045837180]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0) transform = Transform3D(1.5, 0, 0, 0, 1.812, 0, 0, 0, 1.784, 0, 0.12694196, 0)
material_override = SubResource("StandardMaterial3D_1") material_override = SubResource("StandardMaterial3D_1")
mesh = SubResource("BoxMesh_1") mesh = SubResource("BoxMesh_1")
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1940111930] [node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1940111930]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00071039796, 0.12997423, 0.0029245168)
shape = SubResource("BoxShape3D_1") shape = SubResource("BoxShape3D_1")
+26
View File
@@ -0,0 +1,26 @@
[gd_scene format=3 uid="uid://bidrinpjiyb2x"]
[ext_resource type="Script" path="res://scripts/LeatherArmor.gd" id="1_leatha"]
[sub_resource type="BoxMesh" id="BoxMesh_1"]
size = Vector3(0.5, 0.6, 0.22)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
albedo_color = Color(0.55, 0.32, 0.14, 1)
roughness = 0.8
[node name="LeatherArmor" type="Node3D"]
script = ExtResource("1_leatha")
[node name="ArmorMesh" type="MeshInstance3D" parent="."]
mesh = SubResource("BoxMesh_1")
material_override = SubResource("StandardMaterial3D_1")
[node name="Tooltip" type="Label3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.0, 0)
billboard = 1
double_sided = true
text = "[E] Leather Armor
+1 Toughness"
font_size = 32
outline_size = 6
+13 -13
View File
@@ -78,21 +78,21 @@ size = Vector3(0.6, 1, 0.6)
[sub_resource type="BoxShape3D" id="BoxShape3D_forge"] [sub_resource type="BoxShape3D" id="BoxShape3D_forge"]
size = Vector3(2, 1.8, 2) size = Vector3(2, 1.8, 2)
[sub_resource type="BoxMesh" id="BoxMesh_etable"]
size = Vector3(1.4, 0.9, 1.4)
[sub_resource type="BoxMesh" id="BoxMesh_ebook"]
size = Vector3(0.7, 0.12, 0.5)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_etable"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_etable"]
albedo_color = Color(0.12, 0.08, 0.22, 1) albedo_color = Color(0.12, 0.08, 0.22, 1)
roughness = 0.6
metallic = 0.2 metallic = 0.2
roughness = 0.6
[sub_resource type="BoxMesh" id="BoxMesh_etable"]
size = Vector3(1.4, 0.9, 1.4)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ebook"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ebook"]
albedo_color = Color(0.55, 0.08, 0.08, 1) albedo_color = Color(0.55, 0.08, 0.08, 1)
roughness = 0.9 roughness = 0.9
[sub_resource type="BoxMesh" id="BoxMesh_ebook"]
size = Vector3(0.7, 0.12, 0.5)
[sub_resource type="BoxShape3D" id="BoxShape3D_etable"] [sub_resource type="BoxShape3D" id="BoxShape3D_etable"]
size = Vector3(1.4, 0.9, 1.4) size = Vector3(1.4, 0.9, 1.4)
@@ -261,18 +261,18 @@ mesh = SubResource("BoxMesh_chimney")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Forge" unique_id=726531191] [node name="CollisionShape3D" type="CollisionShape3D" parent="Forge" unique_id=726531191]
shape = SubResource("BoxShape3D_forge") shape = SubResource("BoxShape3D_forge")
[node name="EnchantingTable" type="StaticBody3D" parent="."] [node name="EnchantingTable" type="StaticBody3D" parent="." unique_id=242813005]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8, 0.45, -8) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8, 0.45, -8)
metadata/is_enchanting_table = true metadata/is_enchanting_table = true
[node name="TableMesh" type="MeshInstance3D" parent="EnchantingTable"] [node name="TableMesh" type="MeshInstance3D" parent="EnchantingTable" unique_id=1719378953]
mesh = SubResource("BoxMesh_etable")
material_override = SubResource("StandardMaterial3D_etable") material_override = SubResource("StandardMaterial3D_etable")
mesh = SubResource("BoxMesh_etable")
[node name="BookMesh" type="MeshInstance3D" parent="EnchantingTable"] [node name="BookMesh" type="MeshInstance3D" parent="EnchantingTable" unique_id=444214832]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.51, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.51, 0)
mesh = SubResource("BoxMesh_ebook")
material_override = SubResource("StandardMaterial3D_ebook") material_override = SubResource("StandardMaterial3D_ebook")
mesh = SubResource("BoxMesh_ebook")
[node name="CollisionShape3D" type="CollisionShape3D" parent="EnchantingTable"] [node name="CollisionShape3D" type="CollisionShape3D" parent="EnchantingTable" unique_id=352273224]
shape = SubResource("BoxShape3D_etable") shape = SubResource("BoxShape3D_etable")
+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")
+34
View File
@@ -0,0 +1,34 @@
[gd_scene format=3 uid="uid://dbvt3pkrej5nq"]
[ext_resource type="Script" uid="uid://sfq4iq0btm0g" path="res://scripts/MetalPlate.gd" id="1_plate"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
albedo_color = Color(0.6, 0.65, 0.72, 1)
metallic = 0.8
metallic_specular = 0.9
roughness = 0.35
[sub_resource type="BoxMesh" id="BoxMesh_1"]
size = Vector3(0.55, 0.08, 0.7)
[sub_resource type="BoxShape3D" id="BoxShape3D_1"]
size = Vector3(1.0203736, 0.124472655, 1.4775146)
[node name="MetalPlate" type="CharacterBody3D" unique_id=1872011585]
script = ExtResource("1_plate")
[node name="PlateMesh" type="MeshInstance3D" parent="." unique_id=436086498]
transform = Transform3D(1.8395851, 0, 0, 0, 1.5228686, 0, 0, 0, 2.050398, 0, 0.16620824, 0)
material_override = SubResource("StandardMaterial3D_1")
mesh = SubResource("BoxMesh_1")
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1642999883]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.005560264, 0.16739155, 0.003918484)
shape = SubResource("BoxShape3D_1")
[node name="Tooltip" type="Label3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.1, 0)
billboard = 1
text = "[E] Metal Plate
+1 Kick Tier"
outline_size = 6
+27
View File
@@ -0,0 +1,27 @@
[gd_scene format=3 uid="uid://cjhyinmjh0c3y"]
[ext_resource type="Script" uid="uid://sklquy2lyugo" path="res://scripts/PlateArmor.gd" id="1_platea"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
albedo_color = Color(0.58, 0.62, 0.7, 1)
metallic = 0.85
metallic_specular = 1.0
roughness = 0.3
[sub_resource type="BoxMesh" id="BoxMesh_1"]
size = Vector3(0.58, 0.72, 0.24)
[node name="PlateArmor" type="Node3D" unique_id=1607276475]
script = ExtResource("1_platea")
[node name="ArmorMesh" type="MeshInstance3D" parent="." unique_id=65147838]
transform = Transform3D(2.0554285, 0, 0, 0, 2.3875203, 0, 0, 0, 4.207259, 0, 0.7950885, -0.16405308)
material_override = SubResource("StandardMaterial3D_1")
mesh = SubResource("BoxMesh_1")
[node name="Tooltip" type="Label3D" parent="." unique_id=2013488670]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.9503746, 0)
billboard = 1
text = "[E] Plate Armor
+1 Toughness"
outline_size = 6
+78 -13
View File
File diff suppressed because one or more lines are too long
+3 -3
View File
@@ -8,16 +8,16 @@ albedo_color = Color(0.45, 0.38, 0.3, 1)
metallic = 0.05 metallic = 0.05
[sub_resource type="SphereShape3D" id="SphereShape3D_1"] [sub_resource type="SphereShape3D" id="SphereShape3D_1"]
radius = 0.25 radius = 0.40034008
[node name="Rock" type="CharacterBody3D" unique_id=1267213194] [node name="Rock" type="CharacterBody3D" unique_id=1267213194]
script = ExtResource("1_rock") script = ExtResource("1_rock")
[node name="RockMesh" type="MeshInstance3D" parent="." unique_id=918783711] [node name="RockMesh" type="MeshInstance3D" parent="." unique_id=918783711]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.25, 0) transform = Transform3D(1.6, 0, 0, 0, 1.6, 0, 0, 0, 1.6, 0, 0.25, 0)
material_override = SubResource("StandardMaterial3D_1") material_override = SubResource("StandardMaterial3D_1")
mesh = ExtResource("2_wmx41") mesh = ExtResource("2_wmx41")
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=2017780451] [node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=2017780451]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.25, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.033173382, 0.43890864, 0)
shape = SubResource("SphereShape3D_1") shape = SubResource("SphereShape3D_1")
+4 -4
View File
@@ -8,17 +8,17 @@ albedo_color = Color(0.55, 0.38, 0.18, 1)
roughness = 0.95 roughness = 0.95
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"] [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
radius = 0.07 radius = 0.15673828
height = 0.65 height = 1.6220214
[node name="Stick" type="CharacterBody3D" unique_id=1893805391] [node name="Stick" type="CharacterBody3D" unique_id=1893805391]
script = ExtResource("1_stick") script = ExtResource("1_stick")
[node name="StickMesh" type="MeshInstance3D" parent="." unique_id=1850002866] [node name="StickMesh" type="MeshInstance3D" parent="." unique_id=1850002866]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.32, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.16618532, 0)
material_override = SubResource("StandardMaterial3D_1") material_override = SubResource("StandardMaterial3D_1")
mesh = ExtResource("2_urm7b") mesh = ExtResource("2_urm7b")
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=653008437] [node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=653008437]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.32, 0) transform = Transform3D(1, 0, 0, 0, 0.025374386, 0.999678, 0, -0.999678, 0.025374386, 0.005088806, 0.15728158, 0.109485604)
shape = SubResource("CapsuleShape3D_1") shape = SubResource("CapsuleShape3D_1")
+25
View File
@@ -0,0 +1,25 @@
[gd_scene format=3 uid="uid://hk7p5sjjxa1w"]
[ext_resource type="Script" uid="uid://cmvl3td1h7qb8" path="res://scripts/StickArmor.gd" id="1_sticka"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
albedo_color = Color(0.48, 0.32, 0.14, 1)
roughness = 0.9
[sub_resource type="BoxMesh" id="BoxMesh_1"]
size = Vector3(0.45, 0.55, 0.2)
[node name="StickArmor" type="Node3D" unique_id=1550651822]
script = ExtResource("1_sticka")
[node name="ArmorMesh" type="MeshInstance3D" parent="." unique_id=564243580]
transform = Transform3D(3.1221292, 0, 0, 0, 2.4342613, 0, 0, 0, 1, 0, 0.38186997, 0)
material_override = SubResource("StandardMaterial3D_1")
mesh = SubResource("BoxMesh_1")
[node name="Tooltip" type="Label3D" parent="." unique_id=243955258]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.072116256, 1.3135526, 0)
billboard = 1
text = "[E] Stick Armor
+1 Toughness"
outline_size = 6
+25
View File
@@ -0,0 +1,25 @@
[gd_scene format=3 uid="uid://bayxgoeu4qxqe"]
[ext_resource type="Script" path="res://scripts/WoodenShield.gd" id="1_wshield"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
albedo_color = Color(0.55, 0.38, 0.18, 1)
roughness = 0.85
[sub_resource type="BoxMesh" id="BoxMesh_1"]
size = Vector3(0.5, 0.65, 0.12)
[node name="WoodenShield" type="Node3D"]
script = ExtResource("1_wshield")
[node name="ShieldMesh" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.35, 0)
material_override = SubResource("StandardMaterial3D_1")
mesh = SubResource("BoxMesh_1")
[node name="Tooltip" type="Label3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.3, 0)
billboard = 1
text = "[E] Wooden Shield
Tier 1 | Hold Shift to block"
outline_size = 6
+21 -2
View File
@@ -10,7 +10,8 @@ const WALL_BOUNCE := 0.4
const WALL_SELF_DMG := 0.4 const WALL_SELF_DMG := 0.4
var kickable_type: String = "boulder" var kickable_type: String = "boulder"
var tier: int = 2 var kick_tier: int = 0
var toughness_tier: int = 2
var state: State = State.IDLE var state: State = State.IDLE
var fly_vel: Vector3 = Vector3.ZERO var fly_vel: Vector3 = Vector3.ZERO
var health: float = 150.0 var health: float = 150.0
@@ -19,6 +20,7 @@ var damage_modifier: float = 1.8
@onready var mesh_node: MeshInstance3D = $BoulderMesh @onready var mesh_node: MeshInstance3D = $BoulderMesh
var boulder_mat: StandardMaterial3D var boulder_mat: StandardMaterial3D
var _tooltip: Label3D
const COLOR_IDLE := Color(0.32, 0.28, 0.22) const COLOR_IDLE := Color(0.32, 0.28, 0.22)
const COLOR_IMPACT := Color(1.0, 1.0, 1.0) const COLOR_IMPACT := Color(1.0, 1.0, 1.0)
@@ -27,6 +29,23 @@ func _ready() -> void:
add_to_group("kickable") add_to_group("kickable")
boulder_mat = mesh_node.material_override.duplicate() as StandardMaterial3D boulder_mat = mesh_node.material_override.duplicate() as StandardMaterial3D
mesh_node.material_override = boulder_mat mesh_node.material_override = boulder_mat
_tooltip = Label3D.new()
_tooltip.text = "Boulder\nRock+Rock\nVery heavy. High damage"
_tooltip.billboard = BaseMaterial3D.BILLBOARD_ENABLED
_tooltip.font_size = 28
_tooltip.outline_size = 6
_tooltip.position = Vector3(0, 1.4, 0)
_tooltip.modulate = Color(1.0, 0.95, 0.8)
_tooltip.visible = false
add_child(_tooltip)
func _process(_delta: float) -> void:
if dead or state != State.IDLE:
_tooltip.visible = false
return
var players := get_tree().get_nodes_in_group("player")
_tooltip.visible = not players.is_empty() and \
(players[0] as Node3D).global_position.distance_to(global_position) < 2.5
func apply_collision_damage(dmg: float) -> void: func apply_collision_damage(dmg: float) -> void:
_take_damage(dmg) _take_damage(dmg)
@@ -64,7 +83,7 @@ func _fly(delta: float) -> void:
handled = true handled = true
break break
elif col3d.is_in_group("player"): elif col3d.is_in_group("player"):
col3d.call("take_damage", int(speed_now * damage_modifier)) col3d.call("take_damage", int(speed_now * damage_modifier), toughness_tier)
fly_vel *= 0.3 fly_vel *= 0.3
handled = true handled = true
break break
+86 -23
View File
@@ -16,7 +16,8 @@ static var first_iron_spawned: bool = false
static var first_essence_spawned: bool = false static var first_essence_spawned: bool = false
var kickable_type: String = "" var kickable_type: String = ""
var tier: int = 1 var kick_tier: int = 1
var toughness_tier: int = 1
var move_speed: float = 3.0 var move_speed: float = 3.0
var health: int = 30 var health: int = 30
var damage_to_player: int = 8 var damage_to_player: int = 8
@@ -44,7 +45,11 @@ var merge_partner: Node = null
var is_upgrading: bool = false var is_upgrading: bool = false
@onready var mesh_node: MeshInstance3D = $BodyMesh @onready var mesh_node: MeshInstance3D = $BodyMesh
#@onready var mesh_node1: MeshInstance3D = $BodyMesh1
#@onready var mesh_node2: MeshInstance3D = $BodyMesh2
var mat: StandardMaterial3D var mat: StandardMaterial3D
var type_label: Label3D
var COLOR_CHASE = Color(1.0, 0.28, 0.18) var COLOR_CHASE = Color(1.0, 0.28, 0.18)
var COLOR_FLY = Color(1.0, 0.85, 0.1) var COLOR_FLY = Color(1.0, 0.85, 0.1)
@@ -54,10 +59,28 @@ const CONTACT_CD = 0.7
const AIR_FRICTION = 0.86 const AIR_FRICTION = 0.86
func _ready() -> void: func _ready() -> void:
process_mode = Node.PROCESS_MODE_PAUSABLE
add_to_group("enemies") add_to_group("enemies")
mat = mesh_node.material_override.duplicate() as StandardMaterial3D mat = mesh_node.material_override.duplicate() as StandardMaterial3D
mesh_node.material_override = mat mesh_node.material_override = mat
#mesh_node1.set_visible(false)
#mesh_node2.set_visible(false)
COLOR_CHASE = mat.albedo_color COLOR_CHASE = mat.albedo_color
type_label = Label3D.new()
type_label.billboard = BaseMaterial3D.BILLBOARD_ENABLED
type_label.position = Vector3(0, 1.6, 0)
type_label.font_size = 28
type_label.outline_size = 5
type_label.modulate = Color.WHITE
add_child(type_label)
func _update_label() -> void:
if type_label == null:
return
var stars := ""
for i in range(enemy_level - 1):
stars += ""
type_label.text = enemy_type + (" " + stars if stars != "" else "")
func setup(type: String, wave: int) -> void: func setup(type: String, wave: int) -> void:
enemy_type = type enemy_type = type
@@ -66,31 +89,37 @@ func setup(type: String, wave: int) -> void:
match type: match type:
"slime": "slime":
move_speed = 2.8 + wave * 0.12 move_speed = 2.8 + wave * 0.12
health = 28 + wave * 4 health = 25 + wave * 4
score_value = 10 score_value = 10
damage_to_player = 8 damage_to_player = 8
enemy_level = 1 enemy_level = 1
"bat": "bat":
move_speed = 5.5 + wave * 0.15 move_speed = 5 + wave * 0.15
health = 14 + wave * 2 health = 40 + wave * 2
score_value = 15 score_value = 20
damage_to_player = 6 damage_to_player = 6
base_scale = 0.7 base_scale = 0.7
mesh_node.scale = Vector3(0.7, 0.7, 0.7) mesh_node.scale = Vector3(0.7, 0.7, 0.7)
COLOR_CHASE = Color(0.6, 0.2, 0.8) COLOR_CHASE = Color(0.6, 0.2, 0.8)
mat.albedo_color = COLOR_CHASE mat.albedo_color = COLOR_CHASE
enemy_level = 2 enemy_level = 2
#mesh_node.set_visible(false)
#mesh_node1.set_visible(true)
"ogre": "ogre":
move_speed = 1.8 + wave * 0.08 move_speed = 3 + wave * 0.08
health = 80 + wave * 12 health = 150 + wave * 12
score_value = 25 score_value = 50
damage_to_player = 18 damage_to_player = 18
base_scale = 1.5 base_scale = 1.5
mesh_node.scale = Vector3(1.5, 1.5, 1.5) mesh_node.scale = Vector3(1.5, 1.5, 1.5)
COLOR_CHASE = Color(0.3, 0.7, 0.3) COLOR_CHASE = Color(0.3, 0.7, 0.3)
mat.albedo_color = COLOR_CHASE mat.albedo_color = COLOR_CHASE
enemy_level = 3 enemy_level = 3
tier = enemy_level #mesh_node1.set_visible(false)
#mesh_node2.set_visible(true)
kick_tier = enemy_level
toughness_tier = enemy_level
_update_label()
func _physics_process(delta: float) -> void: func _physics_process(delta: float) -> void:
match state: match state:
@@ -112,13 +141,31 @@ func _chase(delta: float) -> void:
contact_timer = CONTACT_CD contact_timer = CONTACT_CD
if target.has_method("take_damage"): if target.has_method("take_damage"):
target.take_damage(damage_to_player) target.take_damage(damage_to_player)
FX.hit_spark(target.global_position + Vector3(0, 0.5, 0), get_parent(), Color(0.95, 0.18, 0.08))
if randf() < 0.3:
var knock_dir := diff.normalized() if dist > 0.01 else -global_transform.basis.z
knock_dir.y = 0.0
target.call("receive_kick", knock_dir, 12.0 + kick_tier * 10.0)
if enemy_kick_timer <= 0.0: if enemy_kick_timer <= 0.0:
_try_enemy_kick() _try_enemy_kick()
var sep := Vector3.ZERO
for e in get_tree().get_nodes_in_group("enemies"):
if e == self:
continue
var en := e as Node3D
if en == null:
continue
var away := global_position - en.global_position
away.y = 0.0
var away_dist := away.length()
if away_dist < 2.2 and away_dist > 0.01:
sep += away.normalized() * (2.2 - away_dist)
if dist > 0.05: if dist > 0.05:
var dir := diff.normalized() var dir := diff.normalized()
velocity.x = dir.x * move_speed var move_dir := (dir + sep * 0.6).normalized()
velocity.z = dir.z * move_speed velocity.x = move_dir.x * move_speed
rotation.y = lerp_angle(rotation.y, atan2(dir.x, dir.z), 8.0 * delta) velocity.z = move_dir.z * move_speed
rotation.y = lerp_angle(rotation.y, atan2(move_dir.x, move_dir.z), 8.0 * delta)
velocity.y = 0.0 velocity.y = 0.0
move_and_slide() move_and_slide()
@@ -138,25 +185,31 @@ func _try_enemy_kick() -> void:
var k := node as Node3D var k := node as Node3D
if k == null or not is_instance_valid(k): if k == null or not is_instance_valid(k):
continue continue
if k.get("kickable_type") == "stick":
continue
var kfv = k.get("fly_vel")
if kfv != null and Vector2((kfv as Vector3).x, (kfv as Vector3).z).length() > 15.0:
continue
var d := (k.global_position - global_position) var d := (k.global_position - global_position)
d.y = 0.0 d.y = 0.0
if d.length() < nearest_dist: if d.length() < nearest_dist:
nearest_dist = d.length() nearest_dist = d.length()
nearest_kickable = k nearest_kickable = k
if nearest_kickable != null: if nearest_kickable != null:
nearest_kickable.call("receive_kick", kick_dir, 35.0 + tier * 8.0) nearest_kickable.call("receive_kick", kick_dir, 35.0 + kick_tier * 8.0)
FX.hit_spark(nearest_kickable.global_position + Vector3(0, 0.4, 0), get_parent(), Color(1.0, 0.72, 0.1))
kickable_kick_timer = KICKABLE_KICK_COOLDOWN kickable_kick_timer = KICKABLE_KICK_COOLDOWN
return return
# 2. Kick lower-tier enemy nearest to player direction # 2. Kick lower-toughness enemy nearest to player direction
if tier > 0: if kick_tier > 0:
var nearest_enemy: Node3D = null var nearest_enemy: Node3D = null
var nearest_enemy_dist := ENEMY_KICK_RANGE var nearest_enemy_dist := ENEMY_KICK_RANGE
for node in get_tree().get_nodes_in_group("enemies"): for node in get_tree().get_nodes_in_group("enemies"):
var en := node as Node3D var en := node as Node3D
if en == null or en == self or not is_instance_valid(en): if en == null or en == self or not is_instance_valid(en):
continue continue
if (en.get("tier") if en.get("tier") != null else 0) >= tier: if (en.get("toughness_tier") if en.get("toughness_tier") != null else 0) >= kick_tier:
continue continue
var d := (en.global_position - global_position) var d := (en.global_position - global_position)
d.y = 0.0 d.y = 0.0
@@ -164,15 +217,17 @@ func _try_enemy_kick() -> void:
nearest_enemy_dist = d.length() nearest_enemy_dist = d.length()
nearest_enemy = en nearest_enemy = en
if nearest_enemy != null: if nearest_enemy != null:
nearest_enemy.call("receive_kick", kick_dir, 40.0 + tier * 10.0) nearest_enemy.call("receive_kick", kick_dir, 40.0 + kick_tier * 10.0)
enemy_kick_timer = ENEMY_KICK_COOLDOWN enemy_kick_timer = ENEMY_KICK_COOLDOWN
return return
# 3. Kick player directly if lower tier and in range # 3. Kick player directly if lower toughness and in range
if tier > 0 and to_player.length() < ENEMY_KICK_RANGE: #if kick_tier > 0 and to_player.length() < ENEMY_KICK_RANGE:
var player_tier: int = target.get("tier") if target.get("tier") != null else 0 if to_player.length() < ENEMY_KICK_RANGE:
if player_tier < tier: var player_toughness: int = target.get("toughness_tier") if target.get("toughness_tier") != null else 0
target.call("receive_kick", kick_dir, 35.0 + tier * 8.0) if player_toughness < kick_tier:
target.call("receive_kick", kick_dir, 12.0 + kick_tier * 10.0)
FX.hit_spark(target.global_position + Vector3(0, 0.5, 0), get_parent(), Color(0.95, 0.18, 0.08))
enemy_kick_timer = ENEMY_KICK_COOLDOWN enemy_kick_timer = ENEMY_KICK_COOLDOWN
func _fly(delta: float) -> void: func _fly(delta: float) -> void:
@@ -265,14 +320,22 @@ func _start_merge(other: Node) -> void:
tw.tween_property(other, "global_position", global_position, 0.2) tw.tween_property(other, "global_position", global_position, 0.2)
tw.tween_callback(_on_merge_complete) tw.tween_callback(_on_merge_complete)
static func _next_enemy_type(current: String) -> String:
match current:
"slime": return "bat"
"bat": return "ogre"
_: return "titan"
func _on_merge_complete() -> void: func _on_merge_complete() -> void:
var merge_pos := global_position var merge_pos := global_position
var merge_type := enemy_type var merge_type := _next_enemy_type(enemy_type)
var new_level: int = enemy_level + 1 var new_level: int = enemy_level + 1
var new_wave: int = wave_num var new_wave: int = wave_num
var par := get_parent()
if is_instance_valid(merge_partner): if is_instance_valid(merge_partner):
merge_pos = (global_position + merge_partner.global_position) / 2.0 merge_pos = (global_position + merge_partner.global_position) / 2.0
merge_partner.queue_free() merge_partner.queue_free()
FX.merge_smoke(merge_pos + Vector3(0, 0.3, 0), par)
queue_free() queue_free()
emit_signal("merged", true) emit_signal("merged", true)
var tree := get_tree() var tree := get_tree()
+20 -1
View File
@@ -12,7 +12,8 @@ const WALL_BOUNCE := 0.6
const WALL_SELF_DMG := 0.0 const WALL_SELF_DMG := 0.0
var kickable_type: String = "essence" var kickable_type: String = "essence"
var tier: int = 3 var kick_tier: int = 0
var toughness_tier: int = 3
var state: State = State.IDLE var state: State = State.IDLE
var fly_vel: Vector3 = Vector3.ZERO var fly_vel: Vector3 = Vector3.ZERO
var health: float = 999.0 var health: float = 999.0
@@ -21,6 +22,7 @@ var damage_modifier: float = 0.0
@onready var mesh_node: MeshInstance3D = $EssenceMesh @onready var mesh_node: MeshInstance3D = $EssenceMesh
var essence_mat: StandardMaterial3D var essence_mat: StandardMaterial3D
var _tooltip: Label3D
func _ready() -> void: func _ready() -> void:
add_to_group("kickable") add_to_group("kickable")
@@ -29,6 +31,23 @@ func _ready() -> void:
var tw := create_tween().set_loops() var tw := create_tween().set_loops()
tw.tween_property(self, "position:y", 0.35, 0.6) tw.tween_property(self, "position:y", 0.35, 0.6)
tw.tween_property(self, "position:y", 0.15, 0.6) tw.tween_property(self, "position:y", 0.15, 0.6)
_tooltip = Label3D.new()
_tooltip.text = "Magic Essence\nKick into\nEnchanting Table → Sphere"
_tooltip.billboard = BaseMaterial3D.BILLBOARD_ENABLED
_tooltip.font_size = 28
_tooltip.outline_size = 6
_tooltip.position = Vector3(0, 1.2, 0)
_tooltip.modulate = Color(0.85, 0.7, 1.0)
_tooltip.visible = false
add_child(_tooltip)
func _process(_delta: float) -> void:
if dead or state != State.IDLE:
_tooltip.visible = false
return
var players := get_tree().get_nodes_in_group("player")
_tooltip.visible = not players.is_empty() and \
(players[0] as Node3D).global_position.distance_to(global_position) < 2.5
func apply_collision_damage(_dmg: float) -> void: func apply_collision_damage(_dmg: float) -> void:
pass pass
+51
View File
@@ -0,0 +1,51 @@
class_name FX
# Small burst of colored spheres flying outward from impact point.
static func hit_spark(pos: Vector3, parent: Node, color: Color = Color(1.0, 0.72, 0.1)) -> void:
const COUNT := 8
for i in range(COUNT):
var m := MeshInstance3D.new()
var sphere := SphereMesh.new()
sphere.radius = 0.12
sphere.height = 0.24
sphere.radial_segments = 4
sphere.rings = 2
var mat := StandardMaterial3D.new()
mat.albedo_color = color
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
m.material_override = mat
m.mesh = sphere
parent.add_child(m)
m.global_position = pos
var angle := TAU * float(i) / float(COUNT) + randf() * 0.6
var dist := randf_range(0.7, 1.8)
var end := pos + Vector3(cos(angle) * dist, randf_range(0.3, 1.3), sin(angle) * dist)
var tw := m.create_tween().set_parallel(true)
tw.tween_property(m, "global_position", end, 0.28)
tw.tween_property(m, "scale", Vector3(0.05, 0.05, 0.05), 0.28)
tw.tween_method(func(a: float): mat.albedo_color.a = a, 1.0, 0.0, 0.28)
tw.chain().tween_callback(m.queue_free)
# Expanding smoke sphere for merges.
static func merge_smoke(pos: Vector3, parent: Node) -> void:
var m := MeshInstance3D.new()
var sphere := SphereMesh.new()
sphere.radius = 0.4
sphere.height = 0.8
var mat := StandardMaterial3D.new()
mat.albedo_color = Color(0.90, 0.90, 0.90, 0.85)
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
m.material_override = mat
m.mesh = sphere
parent.add_child(m)
m.global_position = pos
var tw := m.create_tween().set_parallel(true)
tw.tween_property(m, "scale", Vector3(5.5, 3.5, 5.5), 0.55)
tw.tween_method(func(a: float): mat.albedo_color.a = a, 0.85, 0.0, 0.55)
tw.chain().tween_callback(m.queue_free)
+1
View File
@@ -0,0 +1 @@
uid://dqx8r64rkj7j
+23 -4
View File
@@ -1,6 +1,6 @@
extends CharacterBody3D extends CharacterBody3D
const METAL_ARMOR_SCENE := preload("res://scenes/MetalArmor.tscn") const METAL_PLATE_SCENE := preload("res://scenes/MetalPlate.tscn")
signal destroyed signal destroyed
@@ -12,7 +12,8 @@ const WALL_BOUNCE := 0.45
const WALL_SELF_DMG := 0.5 const WALL_SELF_DMG := 0.5
var kickable_type: String = "iron" var kickable_type: String = "iron"
var tier: int = 2 var kick_tier: int = 0
var toughness_tier: int = 2
var state: State = State.IDLE var state: State = State.IDLE
var fly_vel: Vector3 = Vector3.ZERO var fly_vel: Vector3 = Vector3.ZERO
var health: float = 80.0 var health: float = 80.0
@@ -21,6 +22,7 @@ var damage_modifier: float = 0.9
@onready var mesh_node: MeshInstance3D = $IronMesh @onready var mesh_node: MeshInstance3D = $IronMesh
var iron_mat: StandardMaterial3D var iron_mat: StandardMaterial3D
var _tooltip: Label3D
const COLOR_IDLE := Color(0.55, 0.58, 0.62) const COLOR_IDLE := Color(0.55, 0.58, 0.62)
const COLOR_IMPACT := Color(1.0, 1.0, 1.0) const COLOR_IMPACT := Color(1.0, 1.0, 1.0)
@@ -29,6 +31,23 @@ func _ready() -> void:
add_to_group("kickable") add_to_group("kickable")
iron_mat = mesh_node.material_override.duplicate() as StandardMaterial3D iron_mat = mesh_node.material_override.duplicate() as StandardMaterial3D
mesh_node.material_override = iron_mat mesh_node.material_override = iron_mat
_tooltip = Label3D.new()
_tooltip.text = "Iron\nHeavy weapon\n+Leather → Iron Shield\nForge → Metal Plate"
_tooltip.billboard = BaseMaterial3D.BILLBOARD_ENABLED
_tooltip.font_size = 28
_tooltip.outline_size = 6
_tooltip.position = Vector3(0, 1.1, 0)
_tooltip.modulate = Color(1.0, 0.95, 0.8)
_tooltip.visible = false
add_child(_tooltip)
func _process(_delta: float) -> void:
if dead or state != State.IDLE:
_tooltip.visible = false
return
var players := get_tree().get_nodes_in_group("player")
_tooltip.visible = not players.is_empty() and \
(players[0] as Node3D).global_position.distance_to(global_position) < 2.5
func apply_collision_damage(dmg: float) -> void: func apply_collision_damage(dmg: float) -> void:
_take_damage(dmg) _take_damage(dmg)
@@ -69,7 +88,7 @@ func _fly(delta: float) -> void:
handled = true handled = true
break break
elif col3d.is_in_group("player"): elif col3d.is_in_group("player"):
col3d.call("take_damage", int(speed_now * damage_modifier)) col3d.call("take_damage", int(speed_now * damage_modifier), toughness_tier)
fly_vel *= 0.3 fly_vel *= 0.3
handled = true handled = true
break break
@@ -110,7 +129,7 @@ func _hit_forge(forge: Node3D) -> void:
queue_free() queue_free()
if parent == null: if parent == null:
return return
var armor := METAL_ARMOR_SCENE.instantiate() as Node3D var armor := METAL_PLATE_SCENE.instantiate() as Node3D
parent.add_child(armor) parent.add_child(armor)
armor.global_position = spawn_pos armor.global_position = spawn_pos
+26
View File
@@ -0,0 +1,26 @@
extends Node3D
@onready var tooltip: Label3D = $Tooltip
func _ready() -> void:
add_to_group("interactable")
tooltip.visible = false
var tw := create_tween().set_loops()
tw.tween_property(self, "position:y", 0.45, 0.65)
tw.tween_property(self, "position:y", 0.2, 0.65)
func _process(delta: float) -> void:
rotation.y += delta * 1.1
var players := get_tree().get_nodes_in_group("player")
if players.is_empty():
tooltip.visible = false
return
var p := players[0] as Node3D
tooltip.visible = p != null and global_position.distance_to(p.global_position) < 2.5
func interact(player: Node) -> void:
if player.call("apply_iron_shield"):
queue_free()
var mains := get_tree().get_nodes_in_group("main")
if not mains.is_empty():
mains[0].call("show_tutorial", "Tutorial_Shield")
+1
View File
@@ -0,0 +1 @@
uid://bcvyiisb5ugtn
+1
View File
@@ -60,3 +60,4 @@ static func _execute_recipe(a: Node3D, b: Node3D, recipe: Dictionary) -> void:
var result := scene.instantiate() as Node3D var result := scene.instantiate() as Node3D
parent.add_child(result) parent.add_child(result)
result.global_position = pos result.global_position = pos
FX.merge_smoke(pos + Vector3(0, 0.3, 0), parent)
+20 -1
View File
@@ -7,15 +7,34 @@ const MIN_SPEED := 0.3
const WALL_BOUNCE := 0.7 const WALL_BOUNCE := 0.7
var kickable_type: String = "leather" var kickable_type: String = "leather"
var tier: int = 1 var kick_tier: int = 0
var toughness_tier: int = 1
var state: State = State.IDLE var state: State = State.IDLE
var fly_vel: Vector3 = Vector3.ZERO var fly_vel: Vector3 = Vector3.ZERO
var damage_modifier: float = 0.0 var damage_modifier: float = 0.0
@onready var mesh_node: MeshInstance3D = $LeatherMesh @onready var mesh_node: MeshInstance3D = $LeatherMesh
var _tooltip: Label3D
func _ready() -> void: func _ready() -> void:
add_to_group("kickable") add_to_group("kickable")
_tooltip = Label3D.new()
_tooltip.text = "Leather\n+Stick → Boots\n+Rock → Leather Armor\n+Iron → Iron Shield"
_tooltip.billboard = BaseMaterial3D.BILLBOARD_ENABLED
_tooltip.font_size = 28
_tooltip.outline_size = 6
_tooltip.position = Vector3(0, 1.0, 0)
_tooltip.modulate = Color(1.0, 0.95, 0.8)
_tooltip.visible = false
add_child(_tooltip)
func _process(_delta: float) -> void:
if state != State.IDLE:
_tooltip.visible = false
return
var players := get_tree().get_nodes_in_group("player")
_tooltip.visible = not players.is_empty() and \
(players[0] as Node3D).global_position.distance_to(global_position) < 2.5
func apply_collision_damage(_dmg: float) -> void: func apply_collision_damage(_dmg: float) -> void:
pass pass
+26
View File
@@ -0,0 +1,26 @@
extends Node3D
@onready var tooltip: Label3D = $Tooltip
func _ready() -> void:
add_to_group("interactable")
tooltip.visible = false
var tw := create_tween().set_loops()
tw.tween_property(self, "position:y", 0.45, 0.65)
tw.tween_property(self, "position:y", 0.2, 0.65)
func _process(delta: float) -> void:
rotation.y += delta * 1.1
var players := get_tree().get_nodes_in_group("player")
if players.is_empty():
tooltip.visible = false
return
var p := players[0] as Node3D
tooltip.visible = p != null and global_position.distance_to(p.global_position) < 2.5
func interact(player: Node) -> void:
if player.call("apply_leather_armor"):
queue_free()
var mains := get_tree().get_nodes_in_group("main")
if not mains.is_empty():
mains[0].call("show_tutorial", "Tutorial_LeatherArmor")
+1
View File
@@ -0,0 +1 @@
uid://b80k2b1icwlbm
+5 -2
View File
@@ -12,7 +12,7 @@ func _ready() -> void:
tw.tween_property(self, "position:y", 0.1, 0.7) tw.tween_property(self, "position:y", 0.1, 0.7)
func _process(delta: float) -> void: func _process(delta: float) -> void:
rotation.y += delta * 1.2 #rotation.y += delta * 1.2
var players := get_tree().get_nodes_in_group("player") var players := get_tree().get_nodes_in_group("player")
if players.is_empty(): if players.is_empty():
tooltip.visible = false tooltip.visible = false
@@ -21,5 +21,8 @@ func _process(delta: float) -> void:
tooltip.visible = p != null and global_position.distance_to(p.global_position) < 2.5 tooltip.visible = p != null and global_position.distance_to(p.global_position) < 2.5
func interact(player: Node) -> void: func interact(player: Node) -> void:
player.call("apply_upgrade_boots", 10.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")
+235 -6
View File
@@ -27,10 +27,20 @@ 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_hint_ready: bool = false
var tutorial_active: bool = false
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
var wave_label: Label var wave_label: Label
var tier_label: Label
var hp_bar: ColorRect var hp_bar: ColorRect
var hp_bar_bg: ColorRect var hp_bar_bg: ColorRect
var progress_bar: ColorRect var progress_bar: ColorRect
@@ -38,18 +48,26 @@ var progress_bg: ColorRect
var upgrade_panel: Panel var upgrade_panel: Panel
var gameover_panel: Panel var gameover_panel: Panel
# Equipment slots
var equip_fills: Array[ColorRect] = []
var equip_labels: Array[Label] = []
var _equip_prev_tiers: Array[int] = [-1, -1, -1]
func _ready() -> void: func _ready() -> void:
_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:
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
@@ -92,6 +110,7 @@ func _process(delta: float) -> void:
camera.look_at(look_at_pos, Vector3.UP) camera.look_at(look_at_pos, Vector3.UP)
if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT): if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT):
player.set_aim_direction(deg_to_rad(cam_yaw)) player.set_aim_direction(deg_to_rad(cam_yaw))
_update_tier_label()
# ─── Rocks ──────────────────────────────────────────────────────────────────── # ─── Rocks ────────────────────────────────────────────────────────────────────
@@ -106,11 +125,31 @@ func _spawn_rocks() -> void:
for i in range(limit): for i in range(limit):
_spawn_single_rock() _spawn_single_rock()
var sticks_on_field: int = 0
var sticks_pending: int = 0
const STICK_LIMIT := 2
func _spawn_sticks() -> void: func _spawn_sticks() -> void:
for i in range(2): for i in range(STICK_LIMIT):
var stick := STICK_SCENE.instantiate() _spawn_single_stick()
stick.position = _safe_item_position()
add_child(stick) func _spawn_single_stick() -> void:
var stick := STICK_SCENE.instantiate()
stick.position = _safe_item_position()
add_child(stick)
stick.connect("destroyed", _on_stick_destroyed)
sticks_on_field += 1
func _on_stick_destroyed() -> void:
sticks_on_field = maxi(0, sticks_on_field - 1)
if not game_active:
return
if sticks_on_field + sticks_pending < STICK_LIMIT:
sticks_pending += 1
await get_tree().create_timer(20.0).timeout
sticks_pending -= 1
if game_active:
_spawn_single_stick()
func _spawn_single_rock() -> void: func _spawn_single_rock() -> void:
var rock := ROCK_SCENE.instantiate() var rock := ROCK_SCENE.instantiate()
@@ -230,7 +269,9 @@ func _spawn_upgraded_enemy(pos: Vector3, type: String, level: int, w: int) -> Ch
enemy.setup(type, w) enemy.setup(type, w)
enemy.target = player enemy.target = player
enemy.enemy_level = level enemy.enemy_level = level
enemy.tier = level enemy.kick_tier = level
enemy.toughness_tier = level
enemy.call("_update_label")
enemy.global_position = pos enemy.global_position = pos
enemy.connect("died", _on_enemy_died) enemy.connect("died", _on_enemy_died)
enemy.connect("merged", _on_enemy_merged) enemy.connect("merged", _on_enemy_merged)
@@ -311,6 +352,89 @@ 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)
var click_cap := Control.new()
click_cap.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
click_cap.mouse_filter = Control.MOUSE_FILTER_STOP
click_cap.process_mode = Node.PROCESS_MODE_ALWAYS
click_cap.connect("gui_input", _on_tutorial_input)
tutorial_canvas.add_child(click_cap)
func _on_tutorial_input(event: InputEvent) -> void:
if not tutorial_hint_ready:
return
var mb := event as InputEventMouseButton
if mb != null and mb.button_index == MOUSE_BUTTON_LEFT and mb.pressed:
_dismiss_tutorial()
func _set_enemies_paused(paused: bool) -> void:
var mode := Node.PROCESS_MODE_DISABLED if paused else Node.PROCESS_MODE_PAUSABLE
for e in get_tree().get_nodes_in_group("enemies"):
(e as Node).process_mode = mode
spawn_timer.paused = paused
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.jpeg" % key
tutorial_image.texture = load(path) if ResourceLoader.exists(path) else null
tutorial_on_dismiss = on_dismiss
tutorial_hint_ready = false
tutorial_hint.visible = false
tutorial_canvas.visible = true
tutorial_active = true
_set_enemies_paused(true)
await get_tree().create_timer(3.0).timeout
tutorial_hint.visible = true
tutorial_hint_ready = true
func _dismiss_tutorial() -> void:
tutorial_canvas.visible = false
tutorial_active = false
_set_enemies_paused(false)
if tutorial_on_dismiss.is_valid():
tutorial_on_dismiss.call()
# ─── UI ─────────────────────────────────────────────────────────────────────── # ─── UI ───────────────────────────────────────────────────────────────────────
func _create_ui() -> void: func _create_ui() -> void:
@@ -338,6 +462,11 @@ func _make_hud() -> void:
progress_bg = _crect(Vector2(12, 130), Vector2(200, 10), Color(0.1, 0.1, 0.25)) progress_bg = _crect(Vector2(12, 130), Vector2(200, 10), Color(0.1, 0.1, 0.25))
progress_bar = _crect(Vector2(12, 130), Vector2(0, 10), Color(0.4, 0.8, 1.0)) progress_bar = _crect(Vector2(12, 130), Vector2(0, 10), Color(0.4, 0.8, 1.0))
# Tier display
tier_label = _label(Vector2(12, 148), "", 17)
_make_equipment_slots()
func _make_upgrade_panel() -> void: func _make_upgrade_panel() -> void:
upgrade_panel = Panel.new() upgrade_panel = Panel.new()
upgrade_panel.process_mode = Node.PROCESS_MODE_ALWAYS upgrade_panel.process_mode = Node.PROCESS_MODE_ALWAYS
@@ -417,6 +546,96 @@ func _make_gameover_panel() -> void:
restart_btn.connect("pressed", _restart) restart_btn.connect("pressed", _restart)
vbox.add_child(restart_btn) vbox.add_child(restart_btn)
# ─── Equipment slots ──────────────────────────────────────────────────────────
const _EQUIP_SLOT_SIZE := 54
const _EQUIP_SLOT_GAP := 8
const _EQUIP_SLOT_PAD := 3
const _EQUIP_START_X := 12
const _EQUIP_START_Y := 170
const _EQUIP_EMPTY_COL := Color(0.10, 0.10, 0.16)
const _EQUIP_BORDER_COL := Color(0.30, 0.30, 0.42)
# [slot_index][tier] = [fill_color, label_text]
const _EQUIP_DATA := [
[ # shield
[Color(0.10, 0.10, 0.16), ""],
[Color(0.55, 0.38, 0.18), "Wood\nShield"],
[Color(0.55, 0.58, 0.62), "Iron\nShield"],
],
[ # armor (toughness_tier)
[Color(0.10, 0.10, 0.16), ""],
[Color(0.76, 0.47, 0.18), "Leather\nArmor"],
[Color(0.58, 0.68, 1.00), "Metal\nArmor"],
],
[ # boots (kick_tier)
[Color(0.10, 0.10, 0.16), ""],
[Color(0.76, 0.47, 0.18), "Leather\nBoots"],
[Color(0.58, 0.68, 1.00), "Plate\nBoots"],
[Color(0.75, 0.18, 1.00), "Magic\nBoots"],
],
]
func _make_equipment_slots() -> void:
var titles := ["Shield", "Armor", "Boots"]
for i in range(3):
var x := _EQUIP_START_X + i * (_EQUIP_SLOT_SIZE + _EQUIP_SLOT_GAP)
var y := _EQUIP_START_Y
var title := _label(Vector2(x, y), titles[i], 12)
title.size.x = _EQUIP_SLOT_SIZE
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
var border := ColorRect.new()
border.position = Vector2(x, y + 16)
border.size = Vector2(_EQUIP_SLOT_SIZE, _EQUIP_SLOT_SIZE)
border.color = _EQUIP_BORDER_COL
canvas.add_child(border)
var fill := ColorRect.new()
fill.position = Vector2(x + _EQUIP_SLOT_PAD, y + 16 + _EQUIP_SLOT_PAD)
fill.size = Vector2(_EQUIP_SLOT_SIZE - _EQUIP_SLOT_PAD * 2, _EQUIP_SLOT_SIZE - _EQUIP_SLOT_PAD * 2)
fill.color = _EQUIP_EMPTY_COL
canvas.add_child(fill)
equip_fills.append(fill)
var lbl := Label.new()
lbl.position = fill.position
lbl.size = fill.size
lbl.text = ""
lbl.add_theme_font_size_override("font_size", 11)
lbl.add_theme_color_override("font_color", Color.WHITE)
lbl.add_theme_color_override("font_shadow_color", Color(0, 0, 0, 0.85))
lbl.add_theme_constant_override("shadow_offset_x", 1)
lbl.add_theme_constant_override("shadow_offset_y", 1)
lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
lbl.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
canvas.add_child(lbl)
equip_labels.append(lbl)
func _update_equipment_slots() -> void:
if not is_instance_valid(player):
return
var tiers := [
player.get("shield_tier") as int,
player.get("toughness_tier") as int,
player.get("kick_tier") as int,
]
for i in range(3):
var tier: int = tiers[i]
if tier == _equip_prev_tiers[i]:
continue
_equip_prev_tiers[i] = tier
var data: Array = _EQUIP_DATA[i]
var entry: Array = data[clampi(tier, 0, data.size() - 1)]
equip_fills[i].color = entry[0] as Color
equip_labels[i].text = entry[1] as String
if tier > 0:
var tw := create_tween()
tw.tween_property(equip_fills[i], "color", Color.WHITE, 0.06)
tw.tween_property(equip_fills[i], "color", entry[0] as Color, 0.22)
# ─── UI helpers ─────────────────────────────────────────────────────────────── # ─── UI helpers ───────────────────────────────────────────────────────────────
func _label(pos: Vector2, text: String, size: int) -> Label: func _label(pos: Vector2, text: String, size: int) -> Label:
@@ -446,3 +665,13 @@ func _update_labels() -> void:
func _update_progress() -> void: func _update_progress() -> void:
var t := float(kills) / float(kills_for_next) var t := float(kills) / float(kills_for_next)
progress_bar.size.x = 200.0 * t progress_bar.size.x = 200.0 * t
func _update_tier_label() -> void:
if not is_instance_valid(player):
return
var kt: int = player.get("kick_tier")
var tt: int = player.get("toughness_tier")
var st: int = player.get("shield_tier")
var shield_str := "-" if st == 0 else str(st)
tier_label.text = "Kick: %d Tough: %d Shield: %s" % [kt, tt, shield_str]
_update_equipment_slots()
+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
+25
View File
@@ -13,6 +13,31 @@ static var _list: Array[Dictionary] = [
"result_scene": "res://scenes/Boulder.tscn", "result_scene": "res://scenes/Boulder.tscn",
"speed_threshold": 18.0, "speed_threshold": 18.0,
}, },
{
"ingredients": ["stick", "stick"],
"result_scene": "res://scenes/StickArmor.tscn",
"speed_threshold": 18.0,
},
{
"ingredients": ["leather", "rock"],
"result_scene": "res://scenes/LeatherArmor.tscn",
"speed_threshold": 18.0,
},
{
"ingredients": ["leather", "metal_plate"],
"result_scene": "res://scenes/PlateArmor.tscn",
"speed_threshold": 18.0,
},
{
"ingredients": ["rock", "stick"],
"result_scene": "res://scenes/WoodenShield.tscn",
"speed_threshold": 18.0,
},
{
"ingredients": ["iron", "leather"],
"result_scene": "res://scenes/IronShield.tscn",
"speed_threshold": 18.0,
},
] ]
static func find(type_a: String, type_b: String, speed: float) -> Dictionary: static func find(type_a: String, type_b: String, speed: float) -> Dictionary:
+1 -1
View File
@@ -10,7 +10,7 @@ func _ready() -> void:
tw.tween_property(self, "position:y", 0.2, 0.7) tw.tween_property(self, "position:y", 0.2, 0.7)
func _process(delta: float) -> void: func _process(delta: float) -> void:
rotation.y += delta * 1.4 #rotation.y += delta * 1.4
var players := get_tree().get_nodes_in_group("player") var players := get_tree().get_nodes_in_group("player")
if players.is_empty(): if players.is_empty():
tooltip.visible = false tooltip.visible = false
+148
View File
@@ -0,0 +1,148 @@
extends CharacterBody3D
signal destroyed
enum State { IDLE, FLYING }
const AIR_FRICTION := 0.86
const MIN_SPEED := 0.5
const WALL_BOUNCE := 0.45
const WALL_SELF_DMG := 0.4
var kickable_type: String = "metal_plate"
var kick_tier: int = 0
var toughness_tier: int = 2
var state: State = State.IDLE
var fly_vel: Vector3 = Vector3.ZERO
var health: float = 1200.0
var dead: bool = false
var damage_modifier: float = 1.2
@onready var mesh_node: MeshInstance3D = $PlateMesh
@onready var tooltip: Label3D = $Tooltip
var plate_mat: StandardMaterial3D
const COLOR_IDLE := Color(0.6, 0.65, 0.72)
const COLOR_IMPACT := Color(1.0, 1.0, 1.0)
func _ready() -> void:
add_to_group("kickable")
add_to_group("interactable")
plate_mat = mesh_node.material_override.duplicate() as StandardMaterial3D
mesh_node.material_override = plate_mat
tooltip.visible = false
func _process(_delta: float) -> void:
if dead:
tooltip.visible = false
return
var players := get_tree().get_nodes_in_group("player")
if players.is_empty():
tooltip.visible = false
return
var p := players[0] as Node3D
tooltip.visible = p != null and global_position.distance_to(p.global_position) < 2.5
func interact(player: Node) -> void:
if dead:
return
player.call("apply_upgrade_armor")
dead = true
set_physics_process(false)
tooltip.visible = false
remove_from_group("interactable")
remove_from_group("kickable")
var tw := create_tween()
tw.tween_property(self, "scale", Vector3(1.4, 0.1, 1.4), 0.15)
tw.tween_property(self, "scale", Vector3(0.0, 0.0, 0.0), 0.1)
tw.tween_callback(queue_free)
func apply_collision_damage(dmg: float) -> void:
_take_damage(dmg)
func receive_kick(direction: Vector3, force: float) -> void:
fly_vel = direction * force
fly_vel.y = 0.0
state = State.FLYING
func _physics_process(delta: float) -> void:
if state == State.IDLE:
return
_fly(delta)
func _fly(delta: float) -> void:
var speed_now := Vector2(fly_vel.x, fly_vel.z).length()
velocity = fly_vel
velocity.y = 0.0
move_and_slide()
var handled := false
for i in get_slide_collision_count():
var col := get_slide_collision(i)
var col3d := col.get_collider() as Node3D
if col3d == null:
continue
if col3d.has_meta("is_wall"):
var normal := col.get_normal()
normal.y = 0.0
if normal.length() > 0.01:
fly_vel = fly_vel.bounce(normal.normalized()) * WALL_BOUNCE
else:
fly_vel = Vector3.ZERO
_take_damage(speed_now * WALL_SELF_DMG)
handled = true
break
elif col3d.is_in_group("player"):
col3d.call("take_damage", int(speed_now * damage_modifier), toughness_tier)
fly_vel *= 0.3
handled = true
break
elif col3d.is_in_group("enemies") or col3d.is_in_group("kickable"):
if col3d == self:
continue
KickSystem.resolve(self, col3d, fly_vel)
if not dead and is_instance_valid(col3d):
var kick_dir := col3d.global_position - global_position
kick_dir.y = 0.0
if kick_dir.length() > 0.01:
col3d.call("receive_kick", kick_dir.normalized(), speed_now * 0.5)
fly_vel *= 0.45
handled = true
break
if not handled:
fly_vel = velocity
fly_vel.y = 0.0
fly_vel *= pow(AIR_FRICTION, delta * 60.0)
if Vector2(fly_vel.x, fly_vel.z).length() < MIN_SPEED:
fly_vel = Vector3.ZERO
velocity = Vector3.ZERO
state = State.IDLE
mesh_node.rotation.y += delta * speed_now * 0.25
func _take_damage(dmg: float) -> void:
if dead:
return
health -= dmg
_flash()
if health <= 0.0:
_die()
func _die() -> void:
dead = true
state = State.IDLE
set_physics_process(false)
emit_signal("destroyed")
var tw := create_tween()
tw.tween_property(self, "scale", Vector3(1.8, 0.1, 1.8), 0.12)
tw.tween_property(self, "scale", Vector3(0.0, 0.0, 0.0), 0.1)
tw.tween_callback(queue_free)
func _flash() -> void:
var tw := create_tween()
tw.tween_property(plate_mat, "albedo_color", COLOR_IMPACT, 0.04)
var target_color := COLOR_IDLE.lerp(Color.RED, clampf(1.0 - health / 120.0, 0.0, 0.6))
tw.tween_property(plate_mat, "albedo_color", target_color, 0.18)
+1
View File
@@ -0,0 +1 @@
uid://sfq4iq0btm0g
+23
View File
@@ -0,0 +1,23 @@
extends Node3D
@onready var tooltip: Label3D = $Tooltip
func _ready() -> void:
add_to_group("interactable")
tooltip.visible = false
var tw := create_tween().set_loops()
tw.tween_property(self, "position:y", 0.45, 0.65)
tw.tween_property(self, "position:y", 0.2, 0.65)
func _process(delta: float) -> void:
rotation.y += delta * 1.1
var players := get_tree().get_nodes_in_group("player")
if players.is_empty():
tooltip.visible = false
return
var p := players[0] as Node3D
tooltip.visible = p != null and global_position.distance_to(p.global_position) < 2.5
func interact(player: Node) -> void:
if player.call("apply_plate_armor"):
queue_free()
+1
View File
@@ -0,0 +1 @@
uid://sklquy2lyugo
+90 -23
View File
@@ -11,7 +11,15 @@ signal health_changed(current: int, maximum: int)
@export var max_health: int = 100 @export var max_health: int = 100
var health: int = max_health var health: int = max_health
var tier: int = 0 var kick_tier: int = 0
var toughness_tier: int = 0
var has_stick_armor: bool = false
var has_leather_armor: bool = false
var has_plate_armor: bool = false
var has_wooden_shield: bool = false
var has_iron_shield: bool = false
var shield_tier: int = 0
var is_shielding: bool = false
var kick_timer: float = 0.0 var kick_timer: float = 0.0
var invincible_timer: float = 0.0 var invincible_timer: float = 0.0
var is_alive: bool = true var is_alive: bool = true
@@ -20,8 +28,9 @@ var last_move_dir: Vector3 = Vector3.FORWARD
var _aim_yaw: float = 0.0 var _aim_yaw: float = 0.0
var _is_aiming: bool = false var _is_aiming: bool = false
@onready var mesh_node: MeshInstance3D = $BodyMesh @onready var mesh_node: MeshInstance3D = $player_man
@onready var indicator_node: MeshInstance3D = $KickIndicator @onready var indicator_node: MeshInstance3D = $KickIndicator
@onready var anim_player: AnimationPlayer = $AnimationPlayer2
var player_mat: StandardMaterial3D var player_mat: StandardMaterial3D
var indicator_mat: StandardMaterial3D var indicator_mat: StandardMaterial3D
@@ -31,8 +40,8 @@ const BASE_COLOR := Color(0.2, 0.55, 1.0)
func _ready() -> void: func _ready() -> void:
add_to_group("player") add_to_group("player")
player_mat = mesh_node.material_override.duplicate() as StandardMaterial3D player_mat = mesh_node.get_surface_override_material(0).duplicate() as StandardMaterial3D
mesh_node.material_override = player_mat mesh_node.set_surface_override_material(0, player_mat)
_setup_indicator() _setup_indicator()
func _setup_indicator() -> void: func _setup_indicator() -> void:
@@ -76,6 +85,7 @@ func _input(event: InputEvent) -> void:
func _physics_process(delta: float) -> void: func _physics_process(delta: float) -> void:
if not is_alive: if not is_alive:
return return
is_shielding = shield_tier > 0 and Input.is_key_pressed(KEY_SHIFT)
_handle_movement(delta) _handle_movement(delta)
_handle_kick(delta) _handle_kick(delta)
_handle_iframes(delta) _handle_iframes(delta)
@@ -83,6 +93,7 @@ func _physics_process(delta: float) -> void:
_do_kick() _do_kick()
func _handle_movement(delta: float) -> void: func _handle_movement(delta: float) -> void:
var effective_speed := move_speed * (0.2 if is_shielding else 1.0)
var input_x: float = ( var input_x: float = (
float(Input.is_key_pressed(KEY_D) or Input.is_key_pressed(KEY_RIGHT)) - float(Input.is_key_pressed(KEY_D) or Input.is_key_pressed(KEY_RIGHT)) -
float(Input.is_key_pressed(KEY_A) or Input.is_key_pressed(KEY_LEFT)) float(Input.is_key_pressed(KEY_A) or Input.is_key_pressed(KEY_LEFT))
@@ -102,15 +113,15 @@ func _handle_movement(delta: float) -> void:
var move := cam_fwd * (-input_z) + cam_right * input_x var move := cam_fwd * (-input_z) + cam_right * input_x
if move.length() > 0.01: if move.length() > 0.01:
move = move.normalized() move = move.normalized()
velocity.x = move.x * move_speed velocity.x = move.x * effective_speed
velocity.z = move.z * move_speed velocity.z = move.z * effective_speed
last_move_dir = move last_move_dir = move
if not _is_aiming: if not _is_aiming:
var target_y: float = atan2(-move.x, -move.z) var target_y: float = atan2(-move.x, -move.z)
rotation.y = lerp_angle(rotation.y, target_y, 16.0 * delta) rotation.y = lerp_angle(rotation.y, target_y, 16.0 * delta)
else: else:
velocity.x = move_toward(velocity.x, 0.0, move_speed * 12.0 * delta) velocity.x = move_toward(velocity.x, 0.0, effective_speed * 12.0 * delta)
velocity.z = move_toward(velocity.z, 0.0, move_speed * 12.0 * delta) velocity.z = move_toward(velocity.z, 0.0, effective_speed * 12.0 * delta)
if _is_aiming: if _is_aiming:
rotation.y = lerp_angle(rotation.y, _aim_yaw, 14.0 * delta) rotation.y = lerp_angle(rotation.y, _aim_yaw, 14.0 * delta)
_is_aiming = false _is_aiming = false
@@ -132,6 +143,7 @@ func _handle_iframes(delta: float) -> void:
func _do_kick() -> void: func _do_kick() -> void:
kick_timer = kick_cooldown kick_timer = kick_cooldown
_play_kick_blend()
var forward := -global_transform.basis.z var forward := -global_transform.basis.z
forward.y = 0.0 forward.y = 0.0
forward = forward.normalized() if forward.length() > 0.01 else Vector3(0.0, 0.0, -1.0) forward = forward.normalized() if forward.length() > 0.01 else Vector3(0.0, 0.0, -1.0)
@@ -147,9 +159,9 @@ func _do_kick() -> void:
var diff := en.global_position - global_position var diff := en.global_position - global_position
diff.y = 0.0 diff.y = 0.0
var dist := diff.length() var dist := diff.length()
if dist < 0.1 or dist > kick_range: if dist < 0.01 or dist > kick_range:
continue continue
if (diff / dist).dot(forward) >= half_cos: if dist < 1.0 or (diff / dist).dot(forward) >= half_cos:
candidates.append(en) candidates.append(en)
candidate_dists.append(dist) candidate_dists.append(dist)
@@ -165,8 +177,8 @@ func _do_kick() -> void:
var best_diff := best.global_position - global_position var best_diff := best.global_position - global_position
best_diff.y = 0.0 best_diff.y = 0.0
var best_dir := best_diff.normalized() var best_dir := best_diff.normalized()
var obj_tier: int = best.get("tier") if best.get("tier") != null else 0 var obj_toughness: int = best.get("toughness_tier") if best.get("toughness_tier") != null else 0
var diff_tier := tier - obj_tier var diff_tier := kick_tier - obj_toughness
var force: float var force: float
if diff_tier < 0: if diff_tier < 0:
force = 15.0 force = 15.0
@@ -177,6 +189,7 @@ func _do_kick() -> void:
else: else:
force = 80.0 force = 80.0
best.call("receive_kick", best_dir, force) best.call("receive_kick", best_dir, force)
FX.hit_spark(best.global_position + Vector3(0, 0.4, 0), get_parent())
_squish_effect() _squish_effect()
func _try_interact() -> void: func _try_interact() -> void:
@@ -197,6 +210,11 @@ func set_aim_direction(yaw_rad: float) -> void:
_aim_yaw = yaw_rad _aim_yaw = yaw_rad
_is_aiming = true _is_aiming = true
func _play_kick_blend() -> void:
var tw := create_tween()
tw.tween_method(func(v: float): mesh_node.set_blend_shape_value(0, v), 0.0, 1.0, 0.12)
tw.tween_method(func(v: float): mesh_node.set_blend_shape_value(0, v), 1.0, 0.0, 0.38)
func _squish_effect() -> void: func _squish_effect() -> void:
var tw := create_tween() var tw := create_tween()
tw.tween_property(mesh_node, "scale", Vector3(1.3, 0.55, 1.3), 0.07) tw.tween_property(mesh_node, "scale", Vector3(1.3, 0.55, 1.3), 0.07)
@@ -208,18 +226,17 @@ func receive_kick(direction: Vector3, force: float) -> void:
velocity.x = direction.x * force velocity.x = direction.x * force
velocity.z = direction.z * force velocity.z = direction.z * force
invincible_timer = IFRAMES_DURATION * 0.5 invincible_timer = IFRAMES_DURATION * 0.5
_squish_effect()
func take_damage(amount: int) -> void: func take_damage(amount: int, attacker_toughness: int = 0) -> void:
if not is_alive or invincible_timer > 0.0: if not is_alive or invincible_timer > 0.0:
return return
invincible_timer = IFRAMES_DURATION invincible_timer = IFRAMES_DURATION
#health = max(0, health - amount) if is_shielding and shield_tier > 0:
#emit_signal("health_changed", health, max_health) var diff := shield_tier - attacker_toughness
#var tw := create_tween() var mod: float = 0.15 if diff >= 2 else (0.30 if diff == 1 else 0.50)
#tw.tween_property(player_mat, "albedo_color", Color.RED, 0.08) amount = int(amount * mod)
#tw.tween_property(player_mat, "albedo_color", BASE_COLOR, 0.25) _squish_effect()
#if health <= 0:
#_die()
func heal(amount: int) -> void: func heal(amount: int) -> void:
if not is_alive: if not is_alive:
@@ -250,20 +267,70 @@ func apply_upgrade(id: String) -> void:
emit_signal("health_changed", health, max_health) emit_signal("health_changed", health, max_health)
func apply_upgrade_boots(speed_bonus: float, _tier: int) -> void: func apply_upgrade_boots(speed_bonus: float, _tier: int) -> void:
tier += _tier kick_tier += 1
move_speed += speed_bonus move_speed += speed_bonus
var tw := create_tween() var tw := create_tween()
tw.tween_property(player_mat, "albedo_color", Color(1.0, 0.85, 0.2), 0.1) tw.tween_property(player_mat, "albedo_color", Color(1.0, 0.85, 0.2), 0.1)
tw.tween_property(player_mat, "albedo_color", BASE_COLOR, 0.4) tw.tween_property(player_mat, "albedo_color", BASE_COLOR, 0.4)
func apply_upgrade_armor() -> void: func apply_upgrade_armor() -> void:
tier += 1 kick_tier += 1
var tw := create_tween() var tw := create_tween()
tw.tween_property(player_mat, "albedo_color", Color(0.7, 0.8, 1.0), 0.1) tw.tween_property(player_mat, "albedo_color", Color(0.7, 0.8, 1.0), 0.1)
tw.tween_property(player_mat, "albedo_color", BASE_COLOR, 0.5) tw.tween_property(player_mat, "albedo_color", BASE_COLOR, 0.5)
func apply_upgrade_enchant() -> void: func apply_upgrade_enchant() -> void:
tier += 1 kick_tier += 1
var tw := create_tween() var tw := create_tween()
tw.tween_property(player_mat, "albedo_color", Color(0.8, 0.2, 1.0), 0.1) tw.tween_property(player_mat, "albedo_color", Color(0.8, 0.2, 1.0), 0.1)
tw.tween_property(player_mat, "albedo_color", BASE_COLOR, 0.6) tw.tween_property(player_mat, "albedo_color", BASE_COLOR, 0.6)
func apply_stick_armor() -> bool:
if has_stick_armor:
return false
has_stick_armor = true
toughness_tier += 1
var tw := create_tween()
tw.tween_property(player_mat, "albedo_color", Color(0.7, 0.5, 0.2), 0.1)
tw.tween_property(player_mat, "albedo_color", BASE_COLOR, 0.4)
return true
func apply_leather_armor() -> bool:
if has_leather_armor:
return false
has_leather_armor = true
toughness_tier += 1
var tw := create_tween()
tw.tween_property(player_mat, "albedo_color", Color(0.8, 0.5, 0.2), 0.1)
tw.tween_property(player_mat, "albedo_color", BASE_COLOR, 0.4)
return true
func apply_plate_armor() -> bool:
if has_plate_armor:
return false
has_plate_armor = true
toughness_tier += 1
var tw := create_tween()
tw.tween_property(player_mat, "albedo_color", Color(0.6, 0.7, 1.0), 0.1)
tw.tween_property(player_mat, "albedo_color", BASE_COLOR, 0.5)
return true
func apply_wooden_shield() -> bool:
if has_wooden_shield:
return false
has_wooden_shield = true
shield_tier = max(shield_tier, 1)
var tw := create_tween()
tw.tween_property(player_mat, "albedo_color", Color(0.55, 0.38, 0.18), 0.1)
tw.tween_property(player_mat, "albedo_color", BASE_COLOR, 0.4)
return true
func apply_iron_shield() -> bool:
if has_iron_shield:
return false
has_iron_shield = true
shield_tier = max(shield_tier, 2)
var tw := create_tween()
tw.tween_property(player_mat, "albedo_color", Color(0.55, 0.58, 0.62), 0.1)
tw.tween_property(player_mat, "albedo_color", BASE_COLOR, 0.5)
return true
+21 -2
View File
@@ -10,7 +10,8 @@ const WALL_BOUNCE := 0.5
const WALL_SELF_DMG := 0.6 const WALL_SELF_DMG := 0.6
var kickable_type: String = "rock" var kickable_type: String = "rock"
var tier: int = 0 var kick_tier: int = 0
var toughness_tier: int = 0
var state: State = State.IDLE var state: State = State.IDLE
var fly_vel: Vector3 = Vector3.ZERO var fly_vel: Vector3 = Vector3.ZERO
var health: float = 60.0 var health: float = 60.0
@@ -19,6 +20,7 @@ var damage_modifier: float = 1.25
@onready var mesh_node: MeshInstance3D = $RockMesh @onready var mesh_node: MeshInstance3D = $RockMesh
var rock_mat: StandardMaterial3D var rock_mat: StandardMaterial3D
var _tooltip: Label3D
const COLOR_IDLE := Color(0.45, 0.38, 0.30) const COLOR_IDLE := Color(0.45, 0.38, 0.30)
const COLOR_IMPACT := Color(1.0, 1.0, 1.0) const COLOR_IMPACT := Color(1.0, 1.0, 1.0)
@@ -27,6 +29,23 @@ func _ready() -> void:
add_to_group("kickable") add_to_group("kickable")
rock_mat = mesh_node.material_override.duplicate() as StandardMaterial3D rock_mat = mesh_node.material_override.duplicate() as StandardMaterial3D
mesh_node.material_override = rock_mat mesh_node.material_override = rock_mat
_tooltip = Label3D.new()
_tooltip.text = "Rock\n+Rock → Boulder\n+Stick → Wood Shield"
_tooltip.billboard = BaseMaterial3D.BILLBOARD_ENABLED
_tooltip.font_size = 28
_tooltip.outline_size = 6
_tooltip.position = Vector3(0, 1.1, 0)
_tooltip.modulate = Color(1.0, 0.95, 0.8)
_tooltip.visible = false
add_child(_tooltip)
func _process(_delta: float) -> void:
if dead or state != State.IDLE:
_tooltip.visible = false
return
var players := get_tree().get_nodes_in_group("player")
_tooltip.visible = not players.is_empty() and \
(players[0] as Node3D).global_position.distance_to(global_position) < 2.5
func can_merge_with(_other: Node3D, _speed: float) -> bool: func can_merge_with(_other: Node3D, _speed: float) -> bool:
return false return false
@@ -67,7 +86,7 @@ func _fly(delta: float) -> void:
handled = true handled = true
break break
elif col3d.is_in_group("player"): elif col3d.is_in_group("player"):
col3d.call("take_damage", int(speed_now * damage_modifier)) col3d.call("take_damage", int(speed_now * damage_modifier), toughness_tier)
fly_vel *= 0.3 fly_vel *= 0.3
handled = true handled = true
break break
+21 -2
View File
@@ -10,7 +10,8 @@ const WALL_BOUNCE := 0.5
const WALL_SELF_DMG := 0.5 const WALL_SELF_DMG := 0.5
var kickable_type: String = "stick" var kickable_type: String = "stick"
var tier: int = 0 var kick_tier: int = 0
var toughness_tier: int = 0
var state: State = State.IDLE var state: State = State.IDLE
var fly_vel: Vector3 = Vector3.ZERO var fly_vel: Vector3 = Vector3.ZERO
var health: float = 40.0 var health: float = 40.0
@@ -19,6 +20,7 @@ var damage_modifier: float = 0.6
@onready var mesh_node: MeshInstance3D = $StickMesh @onready var mesh_node: MeshInstance3D = $StickMesh
var stick_mat: StandardMaterial3D var stick_mat: StandardMaterial3D
var _tooltip: Label3D
const COLOR_IDLE := Color(0.55, 0.38, 0.18) const COLOR_IDLE := Color(0.55, 0.38, 0.18)
const COLOR_IMPACT := Color(1.0, 1.0, 1.0) const COLOR_IMPACT := Color(1.0, 1.0, 1.0)
@@ -27,6 +29,23 @@ func _ready() -> void:
add_to_group("kickable") add_to_group("kickable")
stick_mat = mesh_node.material_override.duplicate() as StandardMaterial3D stick_mat = mesh_node.material_override.duplicate() as StandardMaterial3D
mesh_node.material_override = stick_mat mesh_node.material_override = stick_mat
_tooltip = Label3D.new()
_tooltip.text = "Stick\n+Leather → Boots\n+Stick → Stick Armor\n+Rock → Wood Shield"
_tooltip.billboard = BaseMaterial3D.BILLBOARD_ENABLED
_tooltip.font_size = 28
_tooltip.outline_size = 6
_tooltip.position = Vector3(0, 1.2, 0)
_tooltip.modulate = Color(1.0, 0.95, 0.8)
_tooltip.visible = false
add_child(_tooltip)
func _process(_delta: float) -> void:
if dead or state != State.IDLE:
_tooltip.visible = false
return
var players := get_tree().get_nodes_in_group("player")
_tooltip.visible = not players.is_empty() and \
(players[0] as Node3D).global_position.distance_to(global_position) < 2.5
func apply_collision_damage(dmg: float) -> void: func apply_collision_damage(dmg: float) -> void:
_take_damage(dmg) _take_damage(dmg)
@@ -64,7 +83,7 @@ func _fly(delta: float) -> void:
handled = true handled = true
break break
elif col3d.is_in_group("player"): elif col3d.is_in_group("player"):
col3d.call("take_damage", int(speed_now * damage_modifier)) col3d.call("take_damage", int(speed_now * damage_modifier), toughness_tier)
fly_vel *= 0.3 fly_vel *= 0.3
handled = true handled = true
break break
+23
View File
@@ -0,0 +1,23 @@
extends Node3D
@onready var tooltip: Label3D = $Tooltip
func _ready() -> void:
add_to_group("interactable")
tooltip.visible = false
var tw := create_tween().set_loops()
tw.tween_property(self, "position:y", 0.45, 0.65)
tw.tween_property(self, "position:y", 0.2, 0.65)
func _process(delta: float) -> void:
rotation.y += delta * 1.1
var players := get_tree().get_nodes_in_group("player")
if players.is_empty():
tooltip.visible = false
return
var p := players[0] as Node3D
tooltip.visible = p != null and global_position.distance_to(p.global_position) < 2.5
func interact(player: Node) -> void:
if player.call("apply_stick_armor"):
queue_free()
+1
View File
@@ -0,0 +1 @@
uid://cmvl3td1h7qb8
+26
View File
@@ -0,0 +1,26 @@
extends Node3D
@onready var tooltip: Label3D = $Tooltip
func _ready() -> void:
add_to_group("interactable")
tooltip.visible = false
var tw := create_tween().set_loops()
tw.tween_property(self, "position:y", 0.45, 0.65)
tw.tween_property(self, "position:y", 0.2, 0.65)
func _process(delta: float) -> void:
rotation.y += delta * 1.1
var players := get_tree().get_nodes_in_group("player")
if players.is_empty():
tooltip.visible = false
return
var p := players[0] as Node3D
tooltip.visible = p != null and global_position.distance_to(p.global_position) < 2.5
func interact(player: Node) -> void:
if player.call("apply_wooden_shield"):
queue_free()
var mains := get_tree().get_nodes_in_group("main")
if not mains.is_empty():
mains[0].call("show_tutorial", "Tutorial_Shield")
+1
View File
@@ -0,0 +1 @@
uid://djbxvch3r5py5