feat: Implement core game mechanics including player, enemies, bullets, HUD, and level setup.
This commit is contained in:
14
scripts/ammo_pickup.gd
Normal file
14
scripts/ammo_pickup.gd
Normal 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()
|
||||
1
scripts/ammo_pickup.gd.uid
Normal file
1
scripts/ammo_pickup.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://6gehiohye62a
|
||||
34
scripts/ammo_spawner.gd
Normal file
34
scripts/ammo_spawner.gd
Normal 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)
|
||||
1
scripts/ammo_spawner.gd.uid
Normal file
1
scripts/ammo_spawner.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://pdbp514t20bf
|
||||
44
scripts/bullet.gd
Normal file
44
scripts/bullet.gd
Normal 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
1
scripts/bullet.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c362pdnaor5td
|
||||
24
scripts/camera_zoom.gd
Normal file
24
scripts/camera_zoom.gd
Normal 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))
|
||||
1
scripts/camera_zoom.gd.uid
Normal file
1
scripts/camera_zoom.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cm8prlxndx0kk
|
||||
65
scripts/enemy.gd
Normal file
65
scripts/enemy.gd
Normal 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
1
scripts/enemy.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b48lm617ynd2n
|
||||
22
scripts/hud.gd
Normal file
22
scripts/hud.gd
Normal 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
1
scripts/hud.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cqp2fhlqjmigi
|
||||
13
scripts/level_0.gd
Normal file
13
scripts/level_0.gd
Normal 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
1
scripts/level_0.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://m3ii2yiagrn1
|
||||
47
scripts/player.gd
Normal file
47
scripts/player.gd
Normal 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
1
scripts/player.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b1bepj67ddf6c
|
||||
36
scripts/spawner.gd
Normal file
36
scripts/spawner.gd
Normal 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
1
scripts/spawner.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bgbuwhxk7uqah
|
||||
74
scripts/turret.gd
Normal file
74
scripts/turret.gd
Normal 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
1
scripts/turret.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://8jk475ak64ud
|
||||
Reference in New Issue
Block a user