From 245e3e141d88fb9c2d498a41fc9eeb484f73ba85 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Fri, 21 Nov 2025 13:18:53 +0100 Subject: [PATCH] feat: Implement core game mechanics including player, enemies, bullets, HUD, and level setup. --- .editorconfig | 4 ++ .gitattributes | 2 + .gitignore | 3 + .vscode/settings.json | 3 + assets/cat_tank_hull.png | Bin 0 -> 507 bytes assets/cat_tank_hull.png.import | 40 ++++++++++++ assets/cat_tank_turret.png | Bin 0 -> 563 bytes assets/cat_tank_turret.png.import | 40 ++++++++++++ icon.svg | 1 + icon.svg.import | 43 +++++++++++++ project.godot | 43 +++++++++++++ scenes/ammo_pickup.tscn | 18 ++++++ scenes/bullet.tscn | 17 ++++++ scenes/bullet_ap.tscn | 20 ++++++ scenes/bullet_he.tscn | 21 +++++++ scenes/enemy.tscn | 33 ++++++++++ scenes/hud.tscn | 98 ++++++++++++++++++++++++++++++ scenes/level_0.tscn | 28 +++++++++ scenes/player.tscn | 26 ++++++++ scripts/ammo_pickup.gd | 14 +++++ scripts/ammo_pickup.gd.uid | 1 + scripts/ammo_spawner.gd | 34 +++++++++++ scripts/ammo_spawner.gd.uid | 1 + scripts/bullet.gd | 44 ++++++++++++++ scripts/bullet.gd.uid | 1 + scripts/camera_zoom.gd | 24 ++++++++ scripts/camera_zoom.gd.uid | 1 + scripts/enemy.gd | 65 ++++++++++++++++++++ scripts/enemy.gd.uid | 1 + scripts/hud.gd | 22 +++++++ scripts/hud.gd.uid | 1 + scripts/level_0.gd | 13 ++++ scripts/level_0.gd.uid | 1 + scripts/player.gd | 47 ++++++++++++++ scripts/player.gd.uid | 1 + scripts/spawner.gd | 36 +++++++++++ scripts/spawner.gd.uid | 1 + scripts/turret.gd | 74 ++++++++++++++++++++++ scripts/turret.gd.uid | 1 + 39 files changed, 823 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 assets/cat_tank_hull.png create mode 100644 assets/cat_tank_hull.png.import create mode 100644 assets/cat_tank_turret.png create mode 100644 assets/cat_tank_turret.png.import create mode 100644 icon.svg create mode 100644 icon.svg.import create mode 100644 project.godot create mode 100644 scenes/ammo_pickup.tscn create mode 100644 scenes/bullet.tscn create mode 100644 scenes/bullet_ap.tscn create mode 100644 scenes/bullet_he.tscn create mode 100644 scenes/enemy.tscn create mode 100644 scenes/hud.tscn create mode 100644 scenes/level_0.tscn create mode 100644 scenes/player.tscn create mode 100644 scripts/ammo_pickup.gd create mode 100644 scripts/ammo_pickup.gd.uid create mode 100644 scripts/ammo_spawner.gd create mode 100644 scripts/ammo_spawner.gd.uid create mode 100644 scripts/bullet.gd create mode 100644 scripts/bullet.gd.uid create mode 100644 scripts/camera_zoom.gd create mode 100644 scripts/camera_zoom.gd.uid create mode 100644 scripts/enemy.gd create mode 100644 scripts/enemy.gd.uid create mode 100644 scripts/hud.gd create mode 100644 scripts/hud.gd.uid create mode 100644 scripts/level_0.gd create mode 100644 scripts/level_0.gd.uid create mode 100644 scripts/player.gd create mode 100644 scripts/player.gd.uid create mode 100644 scripts/spawner.gd create mode 100644 scripts/spawner.gd.uid create mode 100644 scripts/turret.gd create mode 100644 scripts/turret.gd.uid diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f28239b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +root = true + +[*] +charset = utf-8 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8ad74f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0af181c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Godot 4+ specific ignores +.godot/ +/android/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..95dcf22 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "godotTools.editorPath.godot4": "/home/mathias/Desktop/Godot_v4.5.1-stable_linux.x86_64" +} \ No newline at end of file diff --git a/assets/cat_tank_hull.png b/assets/cat_tank_hull.png new file mode 100644 index 0000000000000000000000000000000000000000..7b1768fc1bc34ac7578f4aed14fd1b4366241113 GIT binary patch literal 507 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D%zw|crbhE&XX zJ8PrgVFQ6S{f><`?9BZEM|yTmT^K1DkbXn*=4l?D*lw1bM#+Q9B`=B()afiS3e0>L zbo=ts{WeDhueOc{`JiD*!1L9 z*ZLj*k5_6bmnEq`$?xcRCl`O{^(uegxyomxg}1Jr92l>&C_L`c^EY16N8Bv+-rWx^ zy%nmhwCLai2a)fqI(jEAUiIwNpNs7c<_Qg38M2{-n#EBU6|<<`7mSx2_}9iSzNj_N z%*eRC`1k%ZH&1S?_^86gCA(LT|7q~Uy^6B6Hl0byPgbn5oO}M>wC6&?A9yy1CGhfO zaDVvnC12@cpIn0O0VxRtaY1at@BO=GB#t)u;=87PqdvB$GEX{Ej7xwj3(^>p=fS?83{1OQE~;Ftga literal 0 HcmV?d00001 diff --git a/assets/cat_tank_hull.png.import b/assets/cat_tank_hull.png.import new file mode 100644 index 0000000..23e902b --- /dev/null +++ b/assets/cat_tank_hull.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bxvveyvfp8gmq" +path="res://.godot/imported/cat_tank_hull.png-a49130f175b9a61446fee1a66af4f122.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/cat_tank_hull.png" +dest_files=["res://.godot/imported/cat_tank_hull.png-a49130f175b9a61446fee1a66af4f122.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 diff --git a/assets/cat_tank_turret.png b/assets/cat_tank_turret.png new file mode 100644 index 0000000000000000000000000000000000000000..a4677f1bc6c89b4ca548af631c54f8d184a6ef63 GIT binary patch literal 563 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D%z-+H<@hE&XX zJ9A?elcPY}|L)3`*L~i)5s?udRu8*kq(AUB321NhS7qV$ELC^E=irge^?HVrL1>)A z#?PWnnuktRMJ9LMo!K&X{jTWaduP_qy*XP{;E&past?Y;iL(jB<*uqZwf+&@lv@O3c?PoVJpqQGlsKsu4-mE zFkNA4)#n4#m?nt?FvhCnG91X=p1q~}kc3Clnl#~_E>=&g4R4qp+qln5?KmV6D4~4b zsOR&X!=m4rri8}}vNV2-+P>6-by{vgLHYm3wyG+c0V~Zn-dqPn{NTu-QsodRQNQ`}@hQ4T^&Fy@eHv%m#W$vXWn3f@puKaJuiq^# z=~dxLo4zx|vP>5EWsz#{FpKH0U-JI#^1n?3%$=XSXVh}Yzn#b$ejs&^z2K)f21~K+ xrW2j|j{cXt$NeBhy5yt&It7j{G6?z;d0 literal 0 HcmV?d00001 diff --git a/assets/cat_tank_turret.png.import b/assets/cat_tank_turret.png.import new file mode 100644 index 0000000..c67dee9 --- /dev/null +++ b/assets/cat_tank_turret.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ddktwa3t2dnlv" +path="res://.godot/imported/cat_tank_turret.png-457811b15b6bbddead7047b686919c6d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/cat_tank_turret.png" +dest_files=["res://.godot/imported/cat_tank_turret.png-457811b15b6bbddead7047b686919c6d.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 diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..c6bbb7d --- /dev/null +++ b/icon.svg @@ -0,0 +1 @@ + diff --git a/icon.svg.import b/icon.svg.import new file mode 100644 index 0000000..58d2997 --- /dev/null +++ b/icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ddmjl3yshqcjw" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.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 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..e1bc4e5 --- /dev/null +++ b/project.godot @@ -0,0 +1,43 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="CatTank" +run/main_scene="uid://cfwyr25ftoqpk" +config/features=PackedStringArray("4.5", "Forward Plus") +config/icon="res://icon.svg" + +[input] + +kb_w={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null) +] +} +kb_a={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null) +] +} +kb_s={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null) +] +} +kb_d={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null) +] +} + +[rendering] + +textures/canvas_textures/default_texture_filter=0 diff --git a/scenes/ammo_pickup.tscn b/scenes/ammo_pickup.tscn new file mode 100644 index 0000000..3a0b720 --- /dev/null +++ b/scenes/ammo_pickup.tscn @@ -0,0 +1,18 @@ +[gd_scene load_steps=4 format=3 uid="uid://mmopickup123"] + +[ext_resource type="Script" path="res://scripts/ammo_pickup.gd" id="1_pickup"] +[ext_resource type="Texture2D" uid="uid://ddmjl3yshqcjw" path="res://icon.svg" id="2_icon"] + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_pickup"] +size = Vector2(32, 32) + +[node name="AmmoPickup" type="Area2D"] +script = ExtResource("1_pickup") + +[node name="Sprite2D" type="Sprite2D" parent="."] +modulate = Color(0, 1, 0, 1) +scale = Vector2(0.25, 0.25) +texture = ExtResource("2_icon") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +shape = SubResource("RectangleShape2D_pickup") diff --git a/scenes/bullet.tscn b/scenes/bullet.tscn new file mode 100644 index 0000000..c94bbe3 --- /dev/null +++ b/scenes/bullet.tscn @@ -0,0 +1,17 @@ +[gd_scene load_steps=4 format=3 uid="uid://b6x4y208a1b2c"] + +[ext_resource type="Script" path="res://scripts/bullet.gd" id="1_bullet"] +[ext_resource type="Texture2D" uid="uid://ddmjl3yshqcjw" path="res://icon.svg" id="2_icon"] + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_1"] +size = Vector2(16, 16) + +[node name="Bullet" type="Area2D"] +script = ExtResource("1_bullet") + +[node name="Sprite2D" type="Sprite2D" parent="."] +scale = Vector2(0.2, 0.2) +texture = ExtResource("2_icon") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +shape = SubResource("RectangleShape2D_1") diff --git a/scenes/bullet_ap.tscn b/scenes/bullet_ap.tscn new file mode 100644 index 0000000..db26615 --- /dev/null +++ b/scenes/bullet_ap.tscn @@ -0,0 +1,20 @@ +[gd_scene load_steps=4 format=3 uid="uid://bulletap12345"] + +[ext_resource type="Script" path="res://scripts/bullet.gd" id="1_bullet"] +[ext_resource type="Texture2D" uid="uid://ddmjl3yshqcjw" path="res://icon.svg" id="2_icon"] + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_1"] +size = Vector2(16, 16) + +[node name="BulletAP" type="Area2D"] +script = ExtResource("1_bullet") +speed = 700.0 +damage = 50.0 +ammo_type = 0 + +[node name="Sprite2D" type="Sprite2D" parent="."] +scale = Vector2(0.2, 0.2) +texture = ExtResource("2_icon") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +shape = SubResource("RectangleShape2D_1") diff --git a/scenes/bullet_he.tscn b/scenes/bullet_he.tscn new file mode 100644 index 0000000..d895848 --- /dev/null +++ b/scenes/bullet_he.tscn @@ -0,0 +1,21 @@ +[gd_scene load_steps=4 format=3 uid="uid://bullethe68aa0"] + +[ext_resource type="Script" path="res://scripts/bullet.gd" id="1_bullet"] +[ext_resource type="Texture2D" uid="uid://ddmjl3yshqcjw" path="res://icon.svg" id="2_icon"] + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_1"] +size = Vector2(20, 20) + +[node name="BulletHE" type="Area2D"] +script = ExtResource("1_bullet") +speed = 400.0 +damage = 20.0 +ammo_type = 1 + +[node name="Sprite2D" type="Sprite2D" parent="."] +modulate = Color(1, 0, 0, 1) +scale = Vector2(0.25, 0.25) +texture = ExtResource("2_icon") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +shape = SubResource("RectangleShape2D_1") diff --git a/scenes/enemy.tscn b/scenes/enemy.tscn new file mode 100644 index 0000000..0db4a25 --- /dev/null +++ b/scenes/enemy.tscn @@ -0,0 +1,33 @@ +[gd_scene load_steps=5 format=3 uid="uid://enemy1234567"] + +[ext_resource type="Script" path="res://scripts/enemy.gd" id="1_enemy"] +[ext_resource type="Texture2D" uid="uid://bxvveyvfp8gmq" path="res://assets/cat_tank_hull.png" id="2_hull"] +[ext_resource type="Texture2D" uid="uid://ddktwa3t2dnlv" path="res://assets/cat_tank_turret.png" id="3_turret"] + +[sub_resource type="CircleShape2D" id="CircleShape2D_enemy"] +radius = 24.0 + +[node name="Enemy" type="CharacterBody2D"] +script = ExtResource("1_enemy") + +[node name="Sprite2D" type="Sprite2D" parent="."] +modulate = Color(1, 0.5, 0.5, 1) +rotation = 1.5707964 +texture = ExtResource("2_hull") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +shape = SubResource("CircleShape2D_enemy") + +[node name="Turret" type="Node2D" parent="."] + +[node name="Sprite2D" type="Sprite2D" parent="Turret"] +modulate = Color(1, 0.5, 0.5, 1) +rotation = 1.5707964 +texture = ExtResource("3_turret") + +[node name="HealthBar" type="ProgressBar" parent="."] +offset_left = -24.0 +offset_top = -40.0 +offset_right = 24.0 +offset_bottom = -34.0 +show_percentage = false diff --git a/scenes/hud.tscn b/scenes/hud.tscn new file mode 100644 index 0000000..a97468d --- /dev/null +++ b/scenes/hud.tscn @@ -0,0 +1,98 @@ +[gd_scene load_steps=2 format=3 uid="uid://hud12345678"] + +[ext_resource type="Script" path="res://scripts/hud.gd" id="1_hud"] + +[node name="HUD" type="CanvasLayer"] +script = ExtResource("1_hud") + +[node name="Control" type="Control" parent="."] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 + +[node name="HealthBar" type="ProgressBar" parent="Control"] +layout_mode = 0 +offset_left = 20.0 +offset_top = 20.0 +offset_right = 220.0 +offset_bottom = 50.0 +value = 100.0 + +[node name="AmmoContainer" type="VBoxContainer" parent="Control"] +layout_mode = 1 +anchors_preset = 3 +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -150.0 +offset_top = -80.0 +offset_right = -20.0 +offset_bottom = -20.0 +grow_horizontal = 0 +grow_vertical = 0 + +[node name="APLabel" type="Label" parent="Control/AmmoContainer"] +layout_mode = 2 +text = "AP: 20" +horizontal_alignment = 2 + +[node name="HELabel" type="Label" parent="Control/AmmoContainer"] +layout_mode = 2 +text = "HE: 10" +horizontal_alignment = 2 + +[node name="GameOverScreen" type="Control" parent="."] +visible = false +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="ColorRect" type="ColorRect" parent="GameOverScreen"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0, 0, 0, 0.5) + +[node name="Label" type="Label" parent="GameOverScreen"] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -46.0 +offset_top = -13.0 +offset_right = 46.0 +offset_bottom = 13.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_font_sizes/font_size = 32 +text = "GAME OVER" + +[node name="RestartButton" type="Button" parent="GameOverScreen"] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -48.0 +offset_top = 40.0 +offset_right = 48.0 +offset_bottom = 71.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "Restart" + +[connection signal="pressed" from="GameOverScreen/RestartButton" to="." method="_on_restart_button_pressed"] diff --git a/scenes/level_0.tscn b/scenes/level_0.tscn new file mode 100644 index 0000000..645b7b0 --- /dev/null +++ b/scenes/level_0.tscn @@ -0,0 +1,28 @@ +[gd_scene load_steps=6 format=3 uid="uid://cfwyr25ftoqpk"] + +[ext_resource type="PackedScene" uid="uid://cywexaqp3mvxh" path="res://scenes/player.tscn" id="1_7wnc2"] +[ext_resource type="Script" uid="uid://m3ii2yiagrn1" path="res://scripts/level_0.gd" id="1_level"] +[ext_resource type="Script" uid="uid://bgbuwhxk7uqah" path="res://scripts/spawner.gd" id="2_spawner"] +[ext_resource type="PackedScene" uid="uid://enemy1234567" path="res://scenes/enemy.tscn" id="3_enemy"] +[ext_resource type="PackedScene" path="res://scenes/hud.tscn" id="4_hud"] +[ext_resource type="Script" path="res://scripts/camera_zoom.gd" id="5_camera"] +[ext_resource type="Script" path="res://scripts/ammo_spawner.gd" id="6_ammo_spawner"] + +[node name="Level0" type="Node2D"] +script = ExtResource("1_level") + +[node name="player" parent="." instance=ExtResource("1_7wnc2")] +position = Vector2(234, 192) + +[node name="Camera2D" type="Camera2D" parent="player"] +zoom = Vector2(3, 3) +script = ExtResource("5_camera") + +[node name="Spawner" type="Node" parent="."] +script = ExtResource("2_spawner") +enemy_scene = ExtResource("3_enemy") + +[node name="AmmoSpawner" type="Node" parent="."] +script = ExtResource("6_ammo_spawner") + +[node name="HUD" parent="." instance=ExtResource("4_hud")] diff --git a/scenes/player.tscn b/scenes/player.tscn new file mode 100644 index 0000000..5bd47d7 --- /dev/null +++ b/scenes/player.tscn @@ -0,0 +1,26 @@ +[gd_scene load_steps=6 format=3 uid="uid://cywexaqp3mvxh"] + +[ext_resource type="Script" uid="uid://b1bepj67ddf6c" path="res://scripts/player.gd" id="1_g2els"] +[ext_resource type="Texture2D" uid="uid://bxvveyvfp8gmq" path="res://assets/cat_tank_hull.png" id="2_dqkch"] +[ext_resource type="Script" uid="uid://8jk475ak64ud" path="res://scripts/turret.gd" id="3_qhqgy"] +[ext_resource type="Texture2D" uid="uid://ddktwa3t2dnlv" path="res://assets/cat_tank_turret.png" id="4_qlg0r"] + +[sub_resource type="CircleShape2D" id="CircleShape2D_tuyoq"] +radius = 24.0 + +[node name="tank" type="CharacterBody2D"] +script = ExtResource("1_g2els") + +[node name="Sprite2D" type="Sprite2D" parent="."] +rotation = 1.5707964 +texture = ExtResource("2_dqkch") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +shape = SubResource("CircleShape2D_tuyoq") + +[node name="turret" type="Node2D" parent="."] +script = ExtResource("3_qhqgy") + +[node name="Sprite2D" type="Sprite2D" parent="turret"] +rotation = 1.5707964 +texture = ExtResource("4_qlg0r") diff --git a/scripts/ammo_pickup.gd b/scripts/ammo_pickup.gd new file mode 100644 index 0000000..54850a7 --- /dev/null +++ b/scripts/ammo_pickup.gd @@ -0,0 +1,14 @@ +extends Area2D + +@export_enum("AP", "HE") var ammo_type: int = 0 +@export var amount: int = 10 + +func _ready(): + body_entered.connect(_on_body_entered) + +func _on_body_entered(body): + if body.is_in_group("player"): + var turret = body.get_node_or_null("turret") + if turret and turret.has_method("add_ammo"): + turret.add_ammo(ammo_type, amount) + queue_free() diff --git a/scripts/ammo_pickup.gd.uid b/scripts/ammo_pickup.gd.uid new file mode 100644 index 0000000..c687b49 --- /dev/null +++ b/scripts/ammo_pickup.gd.uid @@ -0,0 +1 @@ +uid://6gehiohye62a diff --git a/scripts/ammo_spawner.gd b/scripts/ammo_spawner.gd new file mode 100644 index 0000000..814573a --- /dev/null +++ b/scripts/ammo_spawner.gd @@ -0,0 +1,34 @@ +extends Node + +@export var ammo_scene: PackedScene = preload("res://scenes/ammo_pickup.tscn") +@export var spawn_interval_min: float = 5.0 +@export var spawn_interval_max: float = 15.0 +@export var spawn_radius: float = 300.0 + +func _ready(): + spawn_loop() + +func spawn_loop(): + var wait_time = randf_range(spawn_interval_min, spawn_interval_max) + await get_tree().create_timer(wait_time).timeout + spawn_ammo() + spawn_loop() + +func spawn_ammo(): + var player = get_tree().get_first_node_in_group("player") + if not player: + return + + var pickup = ammo_scene.instantiate() + + # Randomize type + pickup.ammo_type = randi() % 2 # 0 or 1 + pickup.amount = 10 if pickup.ammo_type == 0 else 5 # More AP than HE + + # Random position around player + var angle = randf() * TAU + var distance = randf_range(100.0, spawn_radius) + var pos = player.global_position + Vector2.RIGHT.rotated(angle) * distance + + pickup.global_position = pos + get_parent().add_child(pickup) diff --git a/scripts/ammo_spawner.gd.uid b/scripts/ammo_spawner.gd.uid new file mode 100644 index 0000000..dac95e8 --- /dev/null +++ b/scripts/ammo_spawner.gd.uid @@ -0,0 +1 @@ +uid://pdbp514t20bf diff --git a/scripts/bullet.gd b/scripts/bullet.gd new file mode 100644 index 0000000..7a29c00 --- /dev/null +++ b/scripts/bullet.gd @@ -0,0 +1,44 @@ +extends Area2D + +@export var speed: float = 500.0 +@export var lifetime: float = 2.0 +@export var damage: float = 10.0 +@export_enum("AP", "HE") var ammo_type: int = 0 +@export var explosion_radius: float = 100.0 +var shooter_group: String = "player" + +func _ready(): + # Connect collision signal + body_entered.connect(_on_body_entered) + + # Delete bullet after lifetime seconds + await get_tree().create_timer(lifetime).timeout + queue_free() + +func _physics_process(delta): + position += Vector2.RIGHT.rotated(rotation) * speed * delta + +func _on_body_entered(body): + if body.is_in_group(shooter_group): + return + + if ammo_type == 0: # AP + if body.has_method("take_damage"): + body.take_damage(damage) + elif ammo_type == 1: # HE + explode() + + queue_free() + +func explode(): + # Visual effect could go here + # Find all potential targets + # If shooter is player, target enemies. If shooter is enemy, target player. + var target_group = "enemy" if shooter_group == "player" else "player" + var targets = get_tree().get_nodes_in_group(target_group) + + for target in targets: + var distance = global_position.distance_to(target.global_position) + if distance <= explosion_radius: + if target.has_method("take_damage"): + target.take_damage(damage) diff --git a/scripts/bullet.gd.uid b/scripts/bullet.gd.uid new file mode 100644 index 0000000..c111eee --- /dev/null +++ b/scripts/bullet.gd.uid @@ -0,0 +1 @@ +uid://c362pdnaor5td diff --git a/scripts/camera_zoom.gd b/scripts/camera_zoom.gd new file mode 100644 index 0000000..c73d138 --- /dev/null +++ b/scripts/camera_zoom.gd @@ -0,0 +1,24 @@ +extends Camera2D + +@export var zoom_speed: float = 0.5 +@export var min_zoom: float = 1.0 +@export var max_zoom: float = 5.0 +@export var zoom_smoothing: float = 10.0 + +var target_zoom: Vector2 + +func _ready(): + target_zoom = zoom + +func _process(delta): + zoom = zoom.lerp(target_zoom, zoom_smoothing * delta) + +func _unhandled_input(event): + if event is InputEventMouseButton: + if event.is_pressed(): + if event.button_index == MOUSE_BUTTON_WHEEL_UP: + target_zoom += Vector2(zoom_speed, zoom_speed) + elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN: + target_zoom -= Vector2(zoom_speed, zoom_speed) + + target_zoom = target_zoom.clamp(Vector2(min_zoom, min_zoom), Vector2(max_zoom, max_zoom)) diff --git a/scripts/camera_zoom.gd.uid b/scripts/camera_zoom.gd.uid new file mode 100644 index 0000000..6707039 --- /dev/null +++ b/scripts/camera_zoom.gd.uid @@ -0,0 +1 @@ +uid://cm8prlxndx0kk diff --git a/scripts/enemy.gd b/scripts/enemy.gd new file mode 100644 index 0000000..d06aef9 --- /dev/null +++ b/scripts/enemy.gd @@ -0,0 +1,65 @@ +extends CharacterBody2D + +@export var speed: float = 100.0 +@export var max_health: float = 100.0 +var current_health: float + +var player: Node2D +@onready var health_bar = $HealthBar +@onready var turret = $Turret + +@export var bullet_scene: PackedScene = preload("res://scenes/bullet.tscn") +@export var fire_rate: float = 2.0 +var can_shoot: bool = true + +func _ready(): + add_to_group("enemy") + current_health = max_health + if health_bar: + health_bar.max_value = max_health + health_bar.value = current_health + health_bar.hide() # Hide initially, show on damage + + # Find the player in the scene tree + player = get_tree().get_first_node_in_group("player") + if not player: + # Fallback: try to find by name if group not set + player = get_parent().get_node_or_null("player") + +func _physics_process(_delta): + if player: + var direction = (player.global_position - global_position).normalized() + velocity = direction * speed + rotation = direction.angle() + move_and_slide() + + # Aim turret at player + if turret: + turret.look_at(player.global_position) + + # Shoot if close enough + if global_position.distance_to(player.global_position) < 400.0 and can_shoot: + shoot() + +func shoot(): + var bullet = bullet_scene.instantiate() + bullet.shooter_group = "enemy" + get_tree().root.add_child(bullet) + bullet.global_position = turret.global_position + bullet.global_rotation = turret.global_rotation + + can_shoot = false + await get_tree().create_timer(fire_rate).timeout + can_shoot = true + +func take_damage(amount: float): + current_health -= amount + if health_bar: + health_bar.show() + health_bar.value = current_health + + if current_health <= 0: + die() + +func die(): + queue_free() diff --git a/scripts/enemy.gd.uid b/scripts/enemy.gd.uid new file mode 100644 index 0000000..028274f --- /dev/null +++ b/scripts/enemy.gd.uid @@ -0,0 +1 @@ +uid://b48lm617ynd2n diff --git a/scripts/hud.gd b/scripts/hud.gd new file mode 100644 index 0000000..6b0c9d9 --- /dev/null +++ b/scripts/hud.gd @@ -0,0 +1,22 @@ +extends CanvasLayer + +@onready var health_bar = $Control/HealthBar +@onready var game_over_screen = $GameOverScreen +@onready var ap_label = $Control/AmmoContainer/APLabel +@onready var he_label = $Control/AmmoContainer/HELabel + +func update_health(current, max_val): + health_bar.max_value = max_val + health_bar.value = current + +func update_ammo(ap, he): + ap_label.text = "AP: " + str(ap) + he_label.text = "HE: " + str(he) + +func show_game_over(): + game_over_screen.show() + get_tree().paused = true + +func _on_restart_button_pressed(): + get_tree().paused = false + get_tree().reload_current_scene() diff --git a/scripts/hud.gd.uid b/scripts/hud.gd.uid new file mode 100644 index 0000000..f582344 --- /dev/null +++ b/scripts/hud.gd.uid @@ -0,0 +1 @@ +uid://cqp2fhlqjmigi diff --git a/scripts/level_0.gd b/scripts/level_0.gd new file mode 100644 index 0000000..b2531b7 --- /dev/null +++ b/scripts/level_0.gd @@ -0,0 +1,13 @@ +extends Node2D + +@onready var player = $player +@onready var hud = $HUD + +func _ready(): + if player: + player.health_changed.connect(hud.update_health) + player.died.connect(hud.show_game_over) + + var turret = player.get_node("turret") + if turret: + turret.ammo_changed.connect(hud.update_ammo) diff --git a/scripts/level_0.gd.uid b/scripts/level_0.gd.uid new file mode 100644 index 0000000..3c80e03 --- /dev/null +++ b/scripts/level_0.gd.uid @@ -0,0 +1 @@ +uid://m3ii2yiagrn1 diff --git a/scripts/player.gd b/scripts/player.gd new file mode 100644 index 0000000..a6cad9d --- /dev/null +++ b/scripts/player.gd @@ -0,0 +1,47 @@ +extends CharacterBody2D + + +@export var max_speed: float = 150.0 +@export var acceleration: float = 250.0 +@export var deceleration: float = 120.0 +@export var rotation_speed: float = 2.5 +@export var max_health: float = 100.0 + +signal health_changed(current, max) +signal died + +var current_health: float + +func _ready(): + add_to_group("player") + current_health = max_health + health_changed.emit(current_health, max_health) + +func take_damage(amount: float): + current_health -= amount + health_changed.emit(current_health, max_health) + + if current_health <= 0: + die() + +func die(): + died.emit() + +func _physics_process(delta): + #rotate + var turn_input = Input.get_action_strength("kb_d") - Input.get_action_strength("kb_a") + rotation += turn_input * rotation_speed * delta + + #move + var move_input = Input.get_action_strength("kb_w") - Input.get_action_strength("kb_s") + + #current hull direction + var forward_dir = Vector2.RIGHT.rotated(rotation) + + #calc velocity + var forward_speed = velocity.dot(forward_dir) + var target_speed = move_input * max_speed + forward_speed = move_toward(forward_speed, target_speed, (acceleration if move_input != 0 else deceleration) * delta) + velocity = forward_dir * forward_speed + + move_and_slide() diff --git a/scripts/player.gd.uid b/scripts/player.gd.uid new file mode 100644 index 0000000..7956f95 --- /dev/null +++ b/scripts/player.gd.uid @@ -0,0 +1 @@ +uid://b1bepj67ddf6c diff --git a/scripts/spawner.gd b/scripts/spawner.gd new file mode 100644 index 0000000..98f6556 --- /dev/null +++ b/scripts/spawner.gd @@ -0,0 +1,36 @@ +extends Node + +@export var enemy_scene: PackedScene +@export var min_spawn_time: float = 1.0 +@export var max_spawn_time: float = 7.0 + +func _ready(): + start_spawn_timer() + +func start_spawn_timer(): + var time = randf_range(min_spawn_time, max_spawn_time) + await get_tree().create_timer(time).timeout + spawn_enemy() + start_spawn_timer() + +func spawn_enemy(): + if not enemy_scene: + return + + var enemy = enemy_scene.instantiate() + get_parent().add_child(enemy) + + # Calculate random position outside the screen + var viewport_rect = get_viewport().get_visible_rect() + var camera = get_viewport().get_camera_2d() + var center = Vector2.ZERO + if camera: + center = camera.get_screen_center_position() + # Adjust rect size for zoom + viewport_rect.size /= camera.zoom + + # Pick a random angle and a distance slightly larger than half the diagonal + var angle = randf() * TAU + var distance = viewport_rect.size.length() / 2.0 + 100.0 # +100 buffer + + enemy.global_position = center + Vector2.RIGHT.rotated(angle) * distance diff --git a/scripts/spawner.gd.uid b/scripts/spawner.gd.uid new file mode 100644 index 0000000..412a3e9 --- /dev/null +++ b/scripts/spawner.gd.uid @@ -0,0 +1 @@ +uid://bgbuwhxk7uqah diff --git a/scripts/turret.gd b/scripts/turret.gd new file mode 100644 index 0000000..c33160c --- /dev/null +++ b/scripts/turret.gd @@ -0,0 +1,74 @@ +extends Node2D + +@export var rotation_speed: float = 3.0 +@export var bullet_ap_scene: PackedScene = preload("res://scenes/bullet_ap.tscn") +@export var bullet_he_scene: PackedScene = preload("res://scenes/bullet_he.tscn") +@export var fire_rate: float = 0.5 + +@export var max_ammo_ap: int = 20 +@export var max_ammo_he: int = 10 +var ammo_ap: int +var ammo_he: int + +signal ammo_changed(ap, he) + +enum AmmoType {AP, HE} +var current_ammo_type: AmmoType = AmmoType.AP + +var can_shoot: bool = true + +func _ready(): + ammo_ap = max_ammo_ap + ammo_he = max_ammo_he + # Defer emit to ensure connections are ready + call_deferred("emit_ammo_signal") + +func emit_ammo_signal(): + ammo_changed.emit(ammo_ap, ammo_he) + +func _process(delta): + var mouse_pos = get_global_mouse_position() + var target_angle = (mouse_pos - global_position).angle() + + # Sanftes Nachdrehen + var angle_diff = wrapf(target_angle - global_rotation, -PI, PI) + global_rotation += clamp(angle_diff, -rotation_speed * delta, rotation_speed * delta) + + if Input.is_key_pressed(KEY_1): + current_ammo_type = AmmoType.AP + if Input.is_key_pressed(KEY_2): + current_ammo_type = AmmoType.HE + + if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) and can_shoot: + shoot() + +func shoot(): + if current_ammo_type == AmmoType.AP: + if ammo_ap > 0: + ammo_ap -= 1 + else: + return # Out of ammo + elif current_ammo_type == AmmoType.HE: + if ammo_he > 0: + ammo_he -= 1 + else: + return # Out of ammo + + ammo_changed.emit(ammo_ap, ammo_he) + + var bullet_scene = bullet_ap_scene if current_ammo_type == AmmoType.AP else bullet_he_scene + var bullet = bullet_scene.instantiate() + get_tree().root.add_child(bullet) + bullet.global_position = global_position + bullet.global_rotation = global_rotation + + can_shoot = false + await get_tree().create_timer(fire_rate).timeout + can_shoot = true + +func add_ammo(type_index: int, amount: int): + if type_index == 0: # AP + ammo_ap += amount + elif type_index == 1: # HE + ammo_he += amount + ammo_changed.emit(ammo_ap, ammo_he) diff --git a/scripts/turret.gd.uid b/scripts/turret.gd.uid new file mode 100644 index 0000000..177cba0 --- /dev/null +++ b/scripts/turret.gd.uid @@ -0,0 +1 @@ +uid://8jk475ak64ud