diff --git a/assets/attacker.svg b/assets/attacker.svg
new file mode 100644
index 0000000..cc4ca21
--- /dev/null
+++ b/assets/attacker.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/assets/attacker.svg.import b/assets/attacker.svg.import
new file mode 100644
index 0000000..1992951
--- /dev/null
+++ b/assets/attacker.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://p3urintydjlo"
+path="res://.godot/imported/attacker.svg-150f6909959cc42ad50b11689a4301db.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/attacker.svg"
+dest_files=["res://.godot/imported/attacker.svg-150f6909959cc42ad50b11689a4301db.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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/assets/bullet.svg b/assets/bullet.svg
new file mode 100644
index 0000000..c036713
--- /dev/null
+++ b/assets/bullet.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/assets/bullet.svg.import b/assets/bullet.svg.import
new file mode 100644
index 0000000..98100fb
--- /dev/null
+++ b/assets/bullet.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://voltvmrkr73s"
+path="res://.godot/imported/bullet.svg-63ca26e6536cc1d6a6067a48f66d9b99.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/bullet.svg"
+dest_files=["res://.godot/imported/bullet.svg-63ca26e6536cc1d6a6067a48f66d9b99.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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/assets/tower_forceground.svg.import b/assets/tower_forceground.svg.import
index 8a40759..5d191f3 100644
--- a/assets/tower_forceground.svg.import
+++ b/assets/tower_forceground.svg.import
@@ -2,7 +2,7 @@
importer="texture"
type="CompressedTexture2D"
-uid="uid://dq3gmuse32md2"
+uid="uid://b4a5wo7y6orr0"
path="res://.godot/imported/tower_forceground.svg-9f6d44720ea623d40c26c9524e3dd5b1.ctex"
metadata={
"vram_texture": false
diff --git a/project.godot b/project.godot
index d6091c3..0694de8 100644
--- a/project.godot
+++ b/project.godot
@@ -49,3 +49,14 @@ skill_dash={
"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":32,"key_label":0,"unicode":32,"echo":false,"script":null)
]
}
+weapon_fire={
+"deadzone": 0.5,
+"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":1,"position":Vector2(163, 27),"global_position":Vector2(171, 107),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null)
+]
+}
+
+[layer_names]
+
+2d_physics/layer_1="Player"
+2d_physics/layer_2="Hostile"
+2d_physics/layer_3="Bullet"
diff --git a/scenes/farms/enemy_farm.tscn b/scenes/farms/enemy_farm.tscn
new file mode 100644
index 0000000..99fee40
--- /dev/null
+++ b/scenes/farms/enemy_farm.tscn
@@ -0,0 +1,12 @@
+[gd_scene load_steps=2 format=3 uid="uid://cy2xpilh8v7vx"]
+
+[ext_resource type="Script" path="res://scripts/enemy_farm.gd" id="1_66qev"]
+
+[node name="EnemyFarm" type="Node2D" node_paths=PackedStringArray("common_parent")]
+script = ExtResource("1_66qev")
+common_parent = NodePath(".")
+
+[node name="RespawnTimer" type="Timer" parent="."]
+autostart = true
+
+[connection signal="timeout" from="RespawnTimer" to="." method="spawn"]
diff --git a/scenes/main.tscn b/scenes/main.tscn
index 6901ac7..3102a3c 100644
--- a/scenes/main.tscn
+++ b/scenes/main.tscn
@@ -1,10 +1,33 @@
-[gd_scene load_steps=3 format=3 uid="uid://l4mybb2dw107"]
+[gd_scene load_steps=6 format=3 uid="uid://l4mybb2dw107"]
[ext_resource type="PackedScene" uid="uid://cwkcf8h5rspoh" path="res://scenes/objects/player.tscn" id="1_nq1ke"]
[ext_resource type="PackedScene" uid="uid://cxlgt8h33u7os" path="res://scenes/objects/tower.tscn" id="2_betjv"]
+[ext_resource type="PackedScene" uid="uid://cppmks4ln28yd" path="res://scenes/objects/attacker.tscn" id="3_jg22m"]
+[ext_resource type="PackedScene" uid="uid://cy2xpilh8v7vx" path="res://scenes/farms/enemy_farm.tscn" id="3_ogjsq"]
+
+[sub_resource type="Curve2D" id="Curve2D_gwd20"]
+_data = {
+"points": PackedVector2Array(0, 0, 0, 0, -309, -271, 0, 0, 0, 0, -309, 850, 0, 0, 0, 0, 1369, 849, 0, 0, 0, 0, 1368, -272, 0, 0, 0, 0, -309, -271)
+}
+point_count = 5
[node name="Main" type="Node2D"]
-[node name="Player" parent="." instance=ExtResource("1_nq1ke")]
+[node name="EnemyFarm" parent="." node_paths=PackedStringArray("path_follow", "common_target") instance=ExtResource("3_ogjsq")]
+path_follow = NodePath("../EnemySpawnPath/EnemySpawnPoint")
+target = ExtResource("3_jg22m")
+common_target = NodePath("../Player")
+
+[node name="BulletFarm" type="Node2D" parent="."]
+
+[node name="Player" parent="." node_paths=PackedStringArray("weapon_bullet_parent") instance=ExtResource("1_nq1ke")]
+weapon_bullet_parent = NodePath("../BulletFarm")
[node name="Tower" parent="." instance=ExtResource("2_betjv")]
+
+[node name="EnemySpawnPath" type="Path2D" parent="."]
+curve = SubResource("Curve2D_gwd20")
+
+[node name="EnemySpawnPoint" type="PathFollow2D" parent="EnemySpawnPath"]
+position = Vector2(-309, -271)
+rotation = 1.5708
diff --git a/scenes/objects/attacker.tscn b/scenes/objects/attacker.tscn
new file mode 100644
index 0000000..a469515
--- /dev/null
+++ b/scenes/objects/attacker.tscn
@@ -0,0 +1,19 @@
+[gd_scene load_steps=3 format=3 uid="uid://cppmks4ln28yd"]
+
+[ext_resource type="Texture2D" uid="uid://p3urintydjlo" path="res://assets/attacker.svg" id="1_56wxx"]
+[ext_resource type="Script" path="res://scripts/attacker.gd" id="1_d7hg4"]
+
+[node name="Attacker" type="CharacterBody2D"]
+collision_layer = 2
+collision_mask = 7
+script = ExtResource("1_d7hg4")
+
+[node name="AttackerSprite" type="Sprite2D" parent="."]
+position = Vector2(5.96046e-08, -12)
+scale = Vector2(0.1, 0.1)
+texture = ExtResource("1_56wxx")
+
+[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="."]
+position = Vector2(3.55271e-15, -12)
+scale = Vector2(0.2, 0.2)
+polygon = PackedVector2Array(2.08165e-12, -222, -256, 222, 256, 222)
diff --git a/scenes/objects/bullet.tscn b/scenes/objects/bullet.tscn
new file mode 100644
index 0000000..0147d94
--- /dev/null
+++ b/scenes/objects/bullet.tscn
@@ -0,0 +1,27 @@
+[gd_scene load_steps=4 format=3 uid="uid://h4fxpxqc6bpt"]
+
+[ext_resource type="Script" path="res://scripts/bullet.gd" id="1_7ic61"]
+[ext_resource type="Texture2D" uid="uid://voltvmrkr73s" path="res://assets/bullet.svg" id="1_tytsj"]
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_r7dy5"]
+size = Vector2(8, 24)
+
+[node name="Bullet" type="CharacterBody2D"]
+collision_layer = 4
+collision_mask = 2
+script = ExtResource("1_7ic61")
+
+[node name="BulletSprite" type="Sprite2D" parent="."]
+position = Vector2(2.08165e-12, 2.08165e-12)
+rotation = 1.5708
+scale = Vector2(0.25, 0.25)
+texture = ExtResource("1_tytsj")
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
+rotation = 1.5708
+shape = SubResource("RectangleShape2D_r7dy5")
+
+[node name="DisposeTimer" type="Timer" parent="."]
+autostart = true
+
+[connection signal="timeout" from="DisposeTimer" to="." method="_on_timed_out"]
diff --git a/scenes/objects/player.tscn b/scenes/objects/player.tscn
index a27259d..d02ba69 100644
--- a/scenes/objects/player.tscn
+++ b/scenes/objects/player.tscn
@@ -1,17 +1,25 @@
-[gd_scene load_steps=4 format=3 uid="uid://cwkcf8h5rspoh"]
+[gd_scene load_steps=5 format=3 uid="uid://cwkcf8h5rspoh"]
-[ext_resource type="Script" path="res://scripts/PlayerMovement.gd" id="1_e1gjl"]
+[ext_resource type="Script" path="res://scripts/player.gd" id="1_sa7t7"]
[ext_resource type="Texture2D" uid="uid://b4daomndc1ag" path="res://assets/player.svg" id="2_3pad2"]
+[ext_resource type="PackedScene" uid="uid://h4fxpxqc6bpt" path="res://scenes/objects/bullet.tscn" id="2_v83ov"]
[sub_resource type="CircleShape2D" id="CircleShape2D_k56cq"]
radius = 52.0096
-[node name="Player" type="CharacterBody2D" node_paths=PackedStringArray("dash_cooldown_timer")]
-script = ExtResource("1_e1gjl")
+[node name="Player" type="CharacterBody2D" node_paths=PackedStringArray("dash_cooldown_timer", "weapon_bullet_parent", "fire_cooldown_timer")]
+collision_mask = 3
+script = ExtResource("1_sa7t7")
dash_cooldown_timer = NodePath("DashCooldown")
+weapon_bullet_scene = ExtResource("2_v83ov")
+weapon_bullet_parent = NodePath("")
+fire_cooldown_timer = NodePath("ShootCooldown")
[node name="DashCooldown" type="Timer" parent="."]
+[node name="ShootCooldown" type="Timer" parent="."]
+wait_time = 0.2
+
[node name="PlayerSprite" type="Sprite2D" parent="."]
position = Vector2(1.90735e-06, 1.78814e-06)
scale = Vector2(0.1, 0.1)
@@ -20,4 +28,5 @@ texture = ExtResource("2_3pad2")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("CircleShape2D_k56cq")
-[connection signal="timeout" from="DashCooldown" to="." method="_on_cooled_down"]
+[connection signal="timeout" from="DashCooldown" to="." method="_on_dash_cooled_down"]
+[connection signal="timeout" from="ShootCooldown" to="." method="_on_fire_cooled_down"]
diff --git a/scenes/objects/tower.tscn b/scenes/objects/tower.tscn
index 1f5ae79..8b8c385 100644
--- a/scenes/objects/tower.tscn
+++ b/scenes/objects/tower.tscn
@@ -1,17 +1,23 @@
-[gd_scene load_steps=4 format=3 uid="uid://cxlgt8h33u7os"]
+[gd_scene load_steps=5 format=3 uid="uid://cxlgt8h33u7os"]
-[ext_resource type="Script" path="res://scripts/Tower.gd" id="1_58egr"]
+[ext_resource type="Script" path="res://scripts/tower.gd" id="1_d5o8q"]
[ext_resource type="Texture2D" uid="uid://jxub8lma4oud" path="res://assets/tower_background.svg" id="2_ynj1l"]
+[ext_resource type="Script" path="res://scripts/tower_health_display.gd" id="3_ijb8h"]
[sub_resource type="CircleShape2D" id="CircleShape2D_7mogk"]
radius = 129.139
-[node name="Tower" type="Area2D"]
-script = ExtResource("1_58egr")
+[node name="Tower" type="Area2D" node_paths=PackedStringArray("sprite")]
+collision_mask = 3
+script = ExtResource("1_d5o8q")
+sprite = NodePath("TowerSprite")
[node name="TowerSprite" type="Sprite2D" parent="."]
scale = Vector2(0.25, 0.25)
texture = ExtResource("2_ynj1l")
+script = ExtResource("3_ijb8h")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("CircleShape2D_7mogk")
+
+[connection signal="body_entered" from="." to="." method="_on_someone_entered"]
diff --git a/scripts/PlayerMovement.gd b/scripts/PlayerMovement.gd
deleted file mode 100644
index 0a59507..0000000
--- a/scripts/PlayerMovement.gd
+++ /dev/null
@@ -1,29 +0,0 @@
-extends CharacterBody2D
-
-@export var speed = 1200
-@export var speed_multiplier = 20
-@export var friction = 0.9
-
-@export var dash_cooldown_duration = 1.0
-@export var dash_cooldown_timer: Timer
-
-func deal_move(delta):
- var input_direction = Input.get_vector("move_left", "move_right", "move_up", "move_down")
- velocity = velocity.move_toward(input_direction * speed, speed * delta)
- velocity = velocity * friction
-
- var is_dash = Input.is_action_pressed("skill_dash")
- if is_dash && dash_cooldown_timer.is_stopped():
- velocity *= speed_multiplier
- dash_cooldown_timer.start(dash_cooldown_duration)
-
-func _on_cooled_down():
- dash_cooldown_timer.stop()
-
-func _ready():
- var screen_size = get_viewport_rect().size
- position = Vector2(screen_size.x / 2, screen_size.y / 2)
-
-func _physics_process(delta):
- deal_move(delta)
- move_and_slide()
diff --git a/scripts/Tower.gd b/scripts/Tower.gd
index d42fdac..7c4c990 100644
--- a/scripts/Tower.gd
+++ b/scripts/Tower.gd
@@ -1,5 +1,35 @@
extends Area2D
-func _ready():
+signal tower_broken
+
+@export var sprite: Sprite2D
+
+@export var max_health = 100
+@export var max_energy = 20
+
+var health: float
+var energy: float
+
+func move_to_center():
var screen_size = get_viewport_rect().size
position = Vector2(screen_size.x / 2, screen_size.y / 2)
+
+func take_damage(amount: float):
+ health -= amount
+ if health <= 0:
+ tower_broken.emit()
+ get_tree().paused = true
+
+func _ready():
+ move_to_center()
+
+ health = max_health
+ energy = max_energy
+
+func _process(_delta):
+ sprite.regeneration_progress = health / max_health
+
+func _on_someone_entered(body):
+ if body is Enemy:
+ take_damage(body.damage)
+ body.queue_free()
diff --git a/scripts/attacker.gd b/scripts/attacker.gd
new file mode 100644
index 0000000..7527094
--- /dev/null
+++ b/scripts/attacker.gd
@@ -0,0 +1,17 @@
+extends Enemy
+
+@export var speed = 800
+@export var friction = 0.9
+
+@export var target: Node2D
+
+func deal_move(delta):
+ if target:
+ var angle = get_angle_to(target.position)
+ var direction = Vector2(cos(angle), sin(angle))
+ velocity = velocity.move_toward(direction * speed, speed * delta)
+ velocity = velocity * friction
+
+func _physics_process(delta):
+ deal_move(delta)
+ move_and_slide()
diff --git a/scripts/bullet.gd b/scripts/bullet.gd
new file mode 100644
index 0000000..3ff4f46
--- /dev/null
+++ b/scripts/bullet.gd
@@ -0,0 +1,15 @@
+extends CharacterBody2D
+
+@export var damage = 12.0
+
+func _physics_process(delta):
+ var collision = move_and_collide(velocity * delta)
+
+ if collision:
+ var collider = collision.get_collider()
+ if collider is Enemy:
+ collider.take_damage(damage)
+ queue_free()
+
+func _on_timed_out():
+ queue_free()
diff --git a/scripts/enemy.gd b/scripts/enemy.gd
new file mode 100644
index 0000000..6abd6b2
--- /dev/null
+++ b/scripts/enemy.gd
@@ -0,0 +1,16 @@
+class_name Enemy
+
+extends CharacterBody2D
+
+@export var damage = 8.0
+@export var max_health = 20
+
+var health: float
+
+func _ready():
+ health = max_health
+
+func take_damage(amount: float):
+ health -= amount
+ if health <= 0:
+ queue_free()
diff --git a/scripts/enemy_farm.gd b/scripts/enemy_farm.gd
new file mode 100644
index 0000000..4199672
--- /dev/null
+++ b/scripts/enemy_farm.gd
@@ -0,0 +1,23 @@
+extends Node2D
+
+@export var common_parent: Node2D
+@export var path_follow: PathFollow2D
+
+@export var target: PackedScene
+@export var common_target: Node2D
+
+func spawn():
+ var instance = target.instantiate()
+
+ # Randomize
+ path_follow.progress_ratio = randi()
+ instance.position = path_follow.position
+
+ var target_scale = randf_range(0.2, 0.95)
+ instance.scale = Vector2(target_scale, target_scale)
+ instance.speed = randi_range(800, 1400)
+
+ instance.target = common_target
+
+ # Add into common parent
+ common_parent.add_child(instance)
diff --git a/scripts/player.gd b/scripts/player.gd
new file mode 100644
index 0000000..8547add
--- /dev/null
+++ b/scripts/player.gd
@@ -0,0 +1,54 @@
+extends CharacterBody2D
+
+@export var speed = 1200
+@export var speed_multiplier = 20
+@export var friction = 0.9
+
+@export var dash_cooldown_duration = 1.0
+@export var dash_cooldown_timer: Timer
+
+@export var weapon_bullet_speed = 3200
+@export var weapon_bullet_scene: PackedScene
+@export var weapon_bullet_parent: Node2D
+
+@export var fire_cooldown_duration = 0.2
+@export var fire_cooldown_timer: Timer
+
+func deal_move(delta):
+ var input_direction = Input.get_vector("move_left", "move_right", "move_up", "move_down")
+ velocity = velocity.move_toward(input_direction * speed, speed * delta)
+ velocity = velocity * friction
+
+ var is_dash = Input.is_action_pressed("skill_dash")
+ if is_dash && dash_cooldown_timer.is_stopped():
+ velocity *= speed_multiplier
+ dash_cooldown_timer.start(dash_cooldown_duration)
+
+func deal_weapon_shoot():
+ var is_shooting = Input.is_action_pressed("weapon_fire")
+ if is_shooting && fire_cooldown_timer.is_stopped():
+ var mouse_position = get_global_mouse_position()
+ var direction = (mouse_position - position).normalized()
+ var bullet = weapon_bullet_scene.instantiate()
+ bullet.rotation = direction.angle()
+ weapon_bullet_parent.add_child(bullet)
+
+ bullet.global_position = global_position
+ bullet.velocity = direction * weapon_bullet_speed
+
+ fire_cooldown_timer.start(fire_cooldown_duration)
+
+func _on_dash_cooled_down():
+ dash_cooldown_timer.stop()
+
+func _on_fire_cooled_down():
+ fire_cooldown_timer.stop()
+
+func _ready():
+ var screen_size = get_viewport_rect().size
+ position = Vector2(screen_size.x / 2, screen_size.y / 2)
+
+func _physics_process(delta):
+ deal_move(delta)
+ deal_weapon_shoot()
+ move_and_slide()
diff --git a/scripts/tower_health_display.gd b/scripts/tower_health_display.gd
new file mode 100644
index 0000000..8808f2c
--- /dev/null
+++ b/scripts/tower_health_display.gd
@@ -0,0 +1,7 @@
+extends Sprite2D
+
+@export var regeneration_progress = 0.5
+
+func _process(delta):
+ set_modulate(Color(1, 1, 1, regeneration_progress))
+ rotation_degrees += 1