feat: Implement core game mechanics including player, enemies, bullets, HUD, and level setup.

This commit is contained in:
2025-11-21 13:18:53 +01:00
commit 245e3e141d
39 changed files with 823 additions and 0 deletions

14
scripts/ammo_pickup.gd Normal file
View File

@@ -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()

View File

@@ -0,0 +1 @@
uid://6gehiohye62a

34
scripts/ammo_spawner.gd Normal file
View File

@@ -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)

View File

@@ -0,0 +1 @@
uid://pdbp514t20bf

44
scripts/bullet.gd Normal file
View File

@@ -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)

1
scripts/bullet.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://c362pdnaor5td

24
scripts/camera_zoom.gd Normal file
View File

@@ -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))

View File

@@ -0,0 +1 @@
uid://cm8prlxndx0kk

65
scripts/enemy.gd Normal file
View File

@@ -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()

1
scripts/enemy.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://b48lm617ynd2n

22
scripts/hud.gd Normal file
View File

@@ -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()

1
scripts/hud.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://cqp2fhlqjmigi

13
scripts/level_0.gd Normal file
View File

@@ -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)

1
scripts/level_0.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://m3ii2yiagrn1

47
scripts/player.gd Normal file
View File

@@ -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()

1
scripts/player.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://b1bepj67ddf6c

36
scripts/spawner.gd Normal file
View File

@@ -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

1
scripts/spawner.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://bgbuwhxk7uqah

74
scripts/turret.gd Normal file
View File

@@ -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)

1
scripts/turret.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://8jk475ak64ud