mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
109 lines
3.2 KiB
GDScript
109 lines
3.2 KiB
GDScript
class_name GameMode
|
|
extends Node3D
|
|
|
|
# Base for game modes (Free Play, Match; later Vs-AI and multiplayer).
|
|
# A mode's scene contains an Arena (the stadium) and a HUD; the mode itself
|
|
# spawns the ball, ships, controllers, and camera in code — variable ship
|
|
# counts with mixed controller types (player/AI/network) is exactly what
|
|
# future modes need. Subclasses override _start() and _on_goal_scored().
|
|
|
|
@export var ship_scene: PackedScene = preload("res://objects/ship.tscn")
|
|
@export var ball_scene: PackedScene = preload("res://objects/ball.tscn")
|
|
|
|
const CAMERA_RIG_SCENE = preload("res://scenes/ship_camera_rig.tscn")
|
|
const MAIN_MENU_SCENE_PATH = "res://scenes/main_menu.tscn"
|
|
|
|
var arena: Arena
|
|
var ball: RigidBody3D
|
|
var ships: Array[Ship] = []
|
|
var _ship_spawn_transforms := {}
|
|
|
|
|
|
func _ready():
|
|
# Group lets the HUD discover the game mode for timer/score signals
|
|
add_to_group("game")
|
|
for child in get_children():
|
|
if child is Arena:
|
|
arena = child
|
|
break
|
|
if not arena:
|
|
push_error("GameMode: scene has no Arena child")
|
|
return
|
|
for goal in arena.get_goals():
|
|
goal.goal_scored.connect(_handle_goal_scored)
|
|
_start()
|
|
|
|
|
|
# Virtual: subclasses spawn their ball/ships/camera here.
|
|
func _start() -> void:
|
|
pass
|
|
|
|
|
|
# Virtual: the ball entered the goal owned (conceded) by `_conceding_team`.
|
|
func _on_goal_scored(_conceding_team: int) -> void:
|
|
pass
|
|
|
|
|
|
# Debounce: a fast ball can re-trigger the goal area before the deferred
|
|
# reset teleports it away, which would double-count the goal.
|
|
var _goal_cooldown := false
|
|
|
|
func _handle_goal_scored(conceding_team: int) -> void:
|
|
if _goal_cooldown:
|
|
return
|
|
_goal_cooldown = true
|
|
get_tree().create_timer(0.5).timeout.connect(func(): _goal_cooldown = false)
|
|
_on_goal_scored(conceding_team)
|
|
|
|
|
|
func spawn_ball() -> RigidBody3D:
|
|
ball = ball_scene.instantiate()
|
|
add_child(ball)
|
|
ball.global_transform = arena.get_ball_spawn()
|
|
return ball
|
|
|
|
|
|
func spawn_ship(team: int, spawn_index: int = 0, controller: ShipController = null) -> Ship:
|
|
var ship: Ship = ship_scene.instantiate()
|
|
ship.name = "ShipTeam%d_%d" % [team, ships.size()]
|
|
add_child(ship)
|
|
var spawns := arena.get_ship_spawns(team)
|
|
var spawn_transform := spawns[spawn_index] if spawn_index < spawns.size() else Transform3D.IDENTITY
|
|
ship.global_transform = spawn_transform
|
|
ship.team = team
|
|
if controller:
|
|
ship.set_controller(controller)
|
|
ships.append(ship)
|
|
_ship_spawn_transforms[ship] = spawn_transform
|
|
return ship
|
|
|
|
|
|
func spawn_camera_rig(target: Ship) -> ShipCameraRig:
|
|
var rig: ShipCameraRig = CAMERA_RIG_SCENE.instantiate()
|
|
add_child(rig)
|
|
rig.target = target
|
|
return rig
|
|
|
|
|
|
func reset_ball() -> void:
|
|
if is_instance_valid(ball):
|
|
_reset_body(ball, arena.get_ball_spawn())
|
|
|
|
|
|
func reset_ships() -> void:
|
|
for ship in ships:
|
|
if is_instance_valid(ship):
|
|
_reset_body(ship, _ship_spawn_transforms[ship])
|
|
|
|
|
|
func _reset_body(body: RigidBody3D, to: Transform3D) -> void:
|
|
# Deferred: a RigidBody3D transform can't be set mid-physics-step
|
|
body.set_deferred("global_transform", to)
|
|
body.set_deferred("linear_velocity", Vector3.ZERO)
|
|
body.set_deferred("angular_velocity", Vector3.ZERO)
|
|
|
|
|
|
func _unhandled_input(event):
|
|
if event.is_action_pressed("ui_cancel"):
|
|
get_tree().change_scene_to_file(MAIN_MENU_SCENE_PATH)
|