b55453d948
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
141 lines
4.3 KiB
GDScript
141 lines
4.3 KiB
GDScript
extends CharacterBody2D
|
|
|
|
const BULLET_SCENE = preload("res://ships/bullet.tscn")
|
|
|
|
var _fire_cooldown: float = 0.0
|
|
var health: int = 0
|
|
var _dead: bool = false
|
|
var _invincible: bool = false
|
|
var _invincible_timer: float = 0.0
|
|
|
|
func _ready() -> void:
|
|
if GameConfig.player_ship_path.is_empty():
|
|
visible = false
|
|
set_physics_process(false)
|
|
GameConfig.team_selected.connect(_on_team_selected)
|
|
else:
|
|
_init_player()
|
|
|
|
|
|
func _on_team_selected() -> void:
|
|
_init_player()
|
|
|
|
|
|
func _init_player() -> void:
|
|
var sprite := get_node_or_null("Sprite2D")
|
|
if sprite and not GameConfig.player_ship_path.is_empty():
|
|
sprite.texture = load(GameConfig.player_ship_path)
|
|
sprite.scale = Vector2.ONE * GameConfig.player_ship_scale
|
|
set_physics_process(true)
|
|
_respawn()
|
|
|
|
func _physics_process(delta: float) -> void:
|
|
if _dead:
|
|
return
|
|
|
|
# 1. INVINCIBILITY BLINK
|
|
if _invincible:
|
|
_invincible_timer -= delta
|
|
modulate.a = 0.3 if int(_invincible_timer * 8) % 2 == 0 else 1.0
|
|
if _invincible_timer <= 0.0:
|
|
_invincible = false
|
|
modulate.a = 1.0
|
|
|
|
# 2. ROTATION
|
|
if Input.is_action_pressed("move_right"):
|
|
rotation += GameConfig.ship_rotation_speed * delta
|
|
if Input.is_action_pressed("move_left"):
|
|
rotation -= GameConfig.ship_rotation_speed * delta
|
|
|
|
# 3. THRUST
|
|
var thrust := GameConfig.ship_thrust * GameConfig.player_ship_speed_factor
|
|
var max_speed := GameConfig.ship_max_speed * GameConfig.player_ship_speed_factor
|
|
if Input.is_action_pressed("move_up"):
|
|
velocity += Vector2.UP.rotated(rotation) * thrust * delta
|
|
if Input.is_action_pressed("move_down"):
|
|
velocity += Vector2.DOWN.rotated(rotation) * thrust * delta
|
|
|
|
velocity = velocity.limit_length(max_speed)
|
|
|
|
var velocity_before_move := velocity
|
|
move_and_slide()
|
|
_handle_environment_collisions(velocity_before_move)
|
|
|
|
# 4. SHOOT
|
|
_fire_cooldown -= delta
|
|
if Input.is_action_pressed("shoot") and _fire_cooldown <= 0.0:
|
|
_fire_cooldown = GameConfig.ship_fire_rate
|
|
var bullet = BULLET_SCENE.instantiate()
|
|
bullet.global_position = global_position + Vector2.UP.rotated(rotation) * 40.0
|
|
bullet.velocity = Vector2.UP.rotated(rotation) * GameConfig.bullet_speed + velocity
|
|
bullet.source = self
|
|
get_parent().add_child(bullet)
|
|
|
|
# 5. WALL COLLISION
|
|
var bounds = GameConfig.world_bounds
|
|
if global_position.x < bounds.position.x or global_position.x > bounds.end.x:
|
|
velocity.x = 0
|
|
global_position.x = clamp(global_position.x, bounds.position.x, bounds.end.x)
|
|
if global_position.y < bounds.position.y or global_position.y > bounds.end.y:
|
|
velocity.y = 0
|
|
global_position.y = clamp(global_position.y, bounds.position.y, bounds.end.y)
|
|
|
|
func _handle_environment_collisions(previous_velocity: Vector2) -> void:
|
|
for i in get_slide_collision_count():
|
|
var collision := get_slide_collision(i)
|
|
var collider := collision.get_collider() as Node
|
|
if collider == null:
|
|
continue
|
|
var is_hazard: bool = collider.is_in_group("environment_hazard")
|
|
var is_wall: bool = collider.is_in_group("environment_wall")
|
|
if not (is_hazard or is_wall):
|
|
continue
|
|
var normal := collision.get_normal()
|
|
var impact_speed := -previous_velocity.dot(normal)
|
|
if impact_speed <= 0.0:
|
|
continue
|
|
velocity = previous_velocity.bounce(normal) * GameConfig.ship_bounce_restitution
|
|
if is_hazard:
|
|
var damage := int(impact_speed * GameConfig.ship_collision_damage_scale)
|
|
if damage > 0:
|
|
take_damage(damage)
|
|
|
|
|
|
func take_damage(amount: int) -> void:
|
|
if _invincible or _dead:
|
|
return
|
|
health = max(0, health - amount)
|
|
_update_hud()
|
|
if health == 0:
|
|
_die()
|
|
|
|
func _die() -> void:
|
|
_dead = true
|
|
visible = false
|
|
velocity = Vector2.ZERO
|
|
await get_tree().create_timer(GameConfig.ship_respawn_delay).timeout
|
|
_respawn()
|
|
|
|
func _respawn() -> void:
|
|
global_position = _get_spawn_position()
|
|
velocity = Vector2.ZERO
|
|
rotation = 0.0
|
|
health = GameConfig.ship_max_health
|
|
_dead = false
|
|
visible = true
|
|
_invincible = true
|
|
_invincible_timer = GameConfig.ship_invincibility_time
|
|
_update_hud()
|
|
|
|
func _get_spawn_position() -> Vector2:
|
|
var markers := get_tree().get_nodes_in_group("team_a_spawn") + get_tree().get_nodes_in_group("team_b_spawn")
|
|
if markers.size() > 0:
|
|
return markers[randi() % markers.size()].global_position
|
|
return GameConfig.world_bounds.get_center()
|
|
|
|
|
|
func _update_hud() -> void:
|
|
var label = get_node_or_null("/root/World/HUD/HealthLabel")
|
|
if label:
|
|
label.text = "HP: %d / %d" % [health, GameConfig.ship_max_health]
|