diff --git a/effects/explosions/explosion.gd b/effects/explosions/explosion.gd index 6c982342..bda67b55 100644 --- a/effects/explosions/explosion.gd +++ b/effects/explosions/explosion.gd @@ -1,5 +1,6 @@ extends AnimatedSprite3D +## An audio clip to play when this object enters the scene. @export var audio: AudioStreamPlayer3D func _ready() -> void: diff --git a/effects/hyperspace/hyperspace_effect.gd b/effects/hyperspace/hyperspace_effect.gd index 12d047d7..55c6296f 100644 --- a/effects/hyperspace/hyperspace_effect.gd +++ b/effects/hyperspace/hyperspace_effect.gd @@ -1,13 +1,25 @@ extends MeshInstance3D +## Performs the visual effect accompanying a hyperspace jump. +## +## Besides being visually interesting, this effect also serves to hide the scene transition, where otherwise nodes would pop in and out. + @export var hyperspace_controller: HyperspaceController + +## An audio clip to play when a hyperspace jump begins. @export var jump_out_audio: AudioStreamPlayer -const INITIAL_ROTATION = Vector3.ZERO -const INITIAL_SCALE = Vector3.ZERO -const JUMP_OUT_SCALE = Vector3.ONE * 2.7 +## The rotation the mesh should have at the start and end of a jump. +@export var initial_rotation := Vector3.ZERO + +## The scale the mesh should have at the start and end of a jump. +@export var initial_scale := Vector3.ZERO + +## The scale that the mesh should animate to at the peak of the jump. +@export var jump_out_scale := Vector3.ONE * 2.7 -const JUMP_DURATION_SEC: float = 3 +## The total duration (in s) of the jump effect, including jumping out and jumping in. +@export var total_jump_duration_sec := 3.0 func _random_radians() -> float: return randf_range(0, 2 * PI) @@ -20,29 +32,32 @@ func _random_rotation() -> Vector3: ) func _on_jump_started(_destination: StarSystem) -> void: - self.rotation = INITIAL_ROTATION - self.scale = INITIAL_SCALE + self.rotation = self.initial_rotation + self.scale = self.initial_scale self.visible = true + # Jump out effect var tween := self.create_tween() - tween.tween_property(self, "rotation", _random_rotation(), JUMP_DURATION_SEC / 2) - tween.parallel().tween_property(self, "scale", JUMP_OUT_SCALE, JUMP_DURATION_SEC / 2) - tween.tween_callback(_jump_out_finished) + tween.tween_property(self, "rotation", _random_rotation(), self.total_jump_duration_sec / 2) + tween.parallel().tween_property(self, "scale", self.jump_out_scale, self.total_jump_duration_sec / 2) jump_out_audio.play() -func _jump_out_finished() -> void: + await tween.finished + + # Replace scene for node in self.get_tree().get_nodes_in_group("star_system"): node.visible = false node.process_mode = Node.PROCESS_MODE_DISABLED self.hyperspace_controller.load_jump_destination() - var tween := self.create_tween() - tween.tween_property(self, "rotation", INITIAL_ROTATION, JUMP_DURATION_SEC / 2) - tween.parallel().tween_property(self, "scale", INITIAL_SCALE, JUMP_DURATION_SEC / 2) - tween.tween_callback(_jump_in_finished) + # Jump in effect + tween = self.create_tween() + tween.tween_property(self, "rotation", self.initial_rotation, self.total_jump_duration_sec / 2) + tween.parallel().tween_property(self, "scale", self.initial_scale, self.total_jump_duration_sec / 2) + + await tween.finished -func _jump_in_finished() -> void: self.visible = false self.hyperspace_controller.finish_jump() diff --git a/effects/main_camera.gd b/effects/main_camera.gd index 1e450387..b4b355f9 100644 --- a/effects/main_camera.gd +++ b/effects/main_camera.gd @@ -1,8 +1,12 @@ extends Camera3D +## A node to automatically follow with the camera. @export var follow_target: Node3D +## The material rendering the starfield below the game plane. @export var starfield_material: StandardMaterial3D + +## How much to dampen movement of the starfield below the game plane, while it moves in concert with the camera. @export var starfield_parallax_dampening: float = 50 func _process(_delta: float) -> void: diff --git a/galaxy/galaxy.gd b/galaxy/galaxy.gd index 6de83f44..4fda3ac6 100644 --- a/galaxy/galaxy.gd +++ b/galaxy/galaxy.gd @@ -1,8 +1,14 @@ extends Resource class_name Galaxy +## Represents a galaxy of star systems. +## +## Only one galaxy is playable at any given time, but testing or add-on content may want to swap out the default galaxy. + +## A list of all systems in the galaxy. @export var systems: Array[StarSystem] +## Looks up a system by name. func get_system(name: StringName) -> StarSystem: for system in systems: if system.name == name: diff --git a/galaxy/hyperspace_controller.gd b/galaxy/hyperspace_controller.gd index ad7564df..8471958d 100644 --- a/galaxy/hyperspace_controller.gd +++ b/galaxy/hyperspace_controller.gd @@ -1,20 +1,41 @@ extends Node class_name HyperspaceController +## Coordinates the player's hyperspace jumps and loading new scenes. + +## The playable galaxy, used to determine system connections. @export var galaxy: Galaxy + +## The current star system. @export var current_system: StarSystem -signal jump_started(destination: StarSystem) +## Fires when the player changes the jump destination, or it resets because of a performed jump. signal jump_destination_changed(new_destination: StarSystem) + +## Fires when the player initiates a hyperspace jump, before any changes have occurred. +signal jump_started(destination: StarSystem) + +## Fires when the hyperspace destination has been loaded and added to the current scene, but before the full visual effect has finished. signal jump_destination_loaded(new_system: StarSystem) + +## Fires when the hyperspace jump has been completed, and the visual effect has finished. signal jump_finished(new_system: StarSystem) +## Whether a jump is currently being performed. +## +## This property should not be written to outside the class! var jumping = false + +## The hyperspace destination that the player currently has selected. +## +## This property should not be written to outside the class! var jump_destination: StarSystem -var loaded_system_nodes = {} + +## Used to keep systems around in memory, so their state is remembered. +var _loaded_system_nodes = {} func _ready() -> void: - self.loaded_system_nodes[self.current_system.name] = get_tree().get_first_node_in_group("star_system") + self._loaded_system_nodes[self.current_system.name] = get_tree().get_first_node_in_group("star_system") func set_jump_destination(destination: StarSystem) -> void: assert(current_system != destination, "Current system should not be the same as the jump destination") @@ -29,7 +50,7 @@ func start_jump() -> void: assert(jump_destination != null, "Jump destination not set") assert(current_system != jump_destination, "Current system should not be the same as the jump destination") - if not self.loaded_system_nodes.has(jump_destination.name): + if not self._loaded_system_nodes.has(jump_destination.name): ResourceLoader.load_threaded_request(jump_destination.scene_path()) jumping = true @@ -39,13 +60,13 @@ func load_jump_destination() -> void: assert(jump_destination != null, "Jump destination not set") assert(current_system != jump_destination, "Current system should not be the same as the jump destination") - var node: Node = self.loaded_system_nodes.get(jump_destination.name) + var node: Node = self._loaded_system_nodes.get(jump_destination.name) if node == null: print("Instantiating node for system ", jump_destination.name) var new_scene := ResourceLoader.load_threaded_get(jump_destination.scene_path()) as PackedScene node = new_scene.instantiate() - self.loaded_system_nodes[jump_destination.name] = node + self._loaded_system_nodes[jump_destination.name] = node get_parent().add_child(node) else: print("Restoring node for system ", jump_destination.name) diff --git a/galaxy/star_systems/star_system.gd b/galaxy/star_systems/star_system.gd index 74ca4324..57d04741 100644 --- a/galaxy/star_systems/star_system.gd +++ b/galaxy/star_systems/star_system.gd @@ -1,8 +1,16 @@ extends Resource class_name StarSystem +## Describes the non-visual characteristics of a single star system. +## +## Visual characteristics, pre-existing nodes, etc., should all be saved into a scene that matches the [method scene_path]. + +## The human-readable name of this star system. @export var name: StringName + +## All hyperspace connections that this system has to other systems. @export var connections: Array[StringName] = [] +## The resource path to this star system's scene. func scene_path() -> String: return "res://galaxy/star_systems/%s.tscn" % self.name.to_snake_case() diff --git a/game.tscn b/game.tscn index 76f3dd7a..90b200d5 100644 --- a/game.tscn +++ b/game.tscn @@ -147,6 +147,7 @@ can_sleep = false script = ExtResource("7_6citj") hyperspace_controller = NodePath("../HyperspaceController") ship_def = ExtResource("8_s4vsk") +free_when_destroyed = false [node name="AudioListener3D" type="AudioListener3D" parent="Player"] transform = Transform3D(1, -1.21652e-31, 0, -1.21652e-31, 1, 0, 0, 0, 1, 0, 0, 0) diff --git a/gui/player_vitals.gd b/gui/player_vitals.gd index 2e3334e3..ee2898c9 100644 --- a/gui/player_vitals.gd +++ b/gui/player_vitals.gd @@ -4,10 +4,10 @@ extends GridContainer @export var shield_bar: ProgressBar func _on_player_ship_hull_changed(ship: Ship) -> void: - assert(ship.ship_def.hull >= ShipDef.MIN_HULL_VALUE, "Ship definition should not have 0 hull") + assert(ship.ship_def.hull > 0.0, "Ship definition should not have 0 hull") self.hull_bar.max_value = ship.ship_def.hull self.hull_bar.value = ship.hull func _on_player_ship_shield_changed(ship: Ship) -> void: - self.shield_bar.max_value = ship.ship_def.shield if ship.ship_def.shield >= ShipDef.MIN_SHIELD_VALUE else 1.0 + self.shield_bar.max_value = 1.0 if is_zero_approx(ship.ship_def.shield) else ship.ship_def.shield self.shield_bar.value = ship.shield diff --git a/gui/target_info.gd b/gui/target_info.gd index 5dcd04f8..3d04c478 100644 --- a/gui/target_info.gd +++ b/gui/target_info.gd @@ -1,22 +1,24 @@ extends VBoxContainer +## Displays information and vitals about the ship that the player is targeting, if any. + @export var target_label: Label @export var vitals_container: Container @export var hull_bar: ProgressBar @export var shield_bar: ProgressBar @export var pick_sound: AudioStreamPlayer -var target: Ship = null +var _target: Ship = null func _on_player_ship_target_changed(_player_ship: Ship, targeted_ship: Ship) -> void: - if targeted_ship == self.target: + if targeted_ship == self._target: return - if self.target != null: - self.target.ship_hull_changed.disconnect(_on_target_ship_hull_changed) - self.target.ship_shield_changed.disconnect(_on_target_ship_shield_changed) + if self._target != null: + self._target.ship_hull_changed.disconnect(_on_target_ship_hull_changed) + self._target.ship_shield_changed.disconnect(_on_target_ship_shield_changed) - self.target = targeted_ship + self._target = targeted_ship if targeted_ship == null: self.target_label.text = "No target" @@ -32,18 +34,18 @@ func _on_player_ship_target_changed(_player_ship: Ship, targeted_ship: Ship) -> self._update_shield() func _on_target_ship_hull_changed(ship: Ship) -> void: - assert(ship == self.target, "Should only be notified about changes to the target") + assert(ship == self._target, "Should only be notified about changes to the target") self._update_hull() func _on_target_ship_shield_changed(ship: Ship) -> void: - assert(ship == self.target, "Should only be notified about changes to the target") + assert(ship == self._target, "Should only be notified about changes to the target") self._update_shield() func _update_hull() -> void: - assert(self.target.ship_def.hull >= ShipDef.MIN_HULL_VALUE, "Ship definition should not have 0 hull") - self.hull_bar.max_value = self.target.ship_def.hull - self.hull_bar.value = self.target.hull + assert(self._target.ship_def.hull > 0.0, "Ship definition should not have 0 hull") + self.hull_bar.max_value = self._target.ship_def.hull + self.hull_bar.value = self._target.hull func _update_shield() -> void: - self.shield_bar.max_value = self.target.ship_def.shield if self.target.ship_def.shield >= ShipDef.MIN_SHIELD_VALUE else 1.0 - self.shield_bar.value = self.target.shield + self.shield_bar.max_value = 1.0 if is_zero_approx(self._target.ship_def.shield) else self._target.ship_def.shield + self.shield_bar.value = self._target.shield diff --git a/ships/pirate.gd b/ships/pirate.gd index 1e339d7b..10a7de8a 100644 --- a/ships/pirate.gd +++ b/ships/pirate.gd @@ -1,26 +1,43 @@ extends Ship +## AI for a ship that behaves like a pirate, attacking nearby ships on sight. + +## The range (in m) in which other ships will be detected by the pirate. @export var detection_range: float + +## The minimum range (in m) that this ship will try to maintain from its target. @export var preferred_distance: float + +## The maximum range (in m) at which this ship will fire its weapon at the target. @export var fire_range: float + +## For firing and thrusting, the tolerance (in degrees) for being slightly off-rotated. @export var direction_tolerance_deg: float = 10.0 +## State machine for this AI. How it behaves will depend on which state it's in at any given time. enum State { + ## Nothing is happening. The AI is waiting to detect a target. IDLE, + + ## Pursuing a target actively. PURSUE, + + ## Firing upon the target. ATTACK, + + ## Retreating from the target. RETREAT } -var current_state: State = State.IDLE -var direction_tolerance_rad: float +var _current_state: State = State.IDLE +var _direction_tolerance_rad: float func _ready() -> void: super() - self.direction_tolerance_rad = deg_to_rad(self.direction_tolerance_deg) + self._direction_tolerance_rad = deg_to_rad(self.direction_tolerance_deg) func _acquire_closest_target() -> void: - var ships := get_tree().get_nodes_in_group("ships") + var ships := self.get_tree().get_nodes_in_group("ships") ships.erase(self) var closest_ship: Ship = null @@ -36,64 +53,64 @@ func _acquire_closest_target() -> void: func _physics_process(delta: float): if self.target == null: - self.current_state = State.IDLE + self._current_state = State.IDLE - match current_state: + match _current_state: State.IDLE: - idle_behavior(delta) + self._idle_behavior(delta) State.PURSUE: - pursue_behavior(delta) + self._pursue_behavior(delta) State.ATTACK: - attack_behavior(delta) + self._attack_behavior(delta) State.RETREAT: - retreat_behavior(delta) + self._retreat_behavior(delta) func _desired_direction() -> Vector3: var target_direction := (self.target.global_transform.origin - self.global_transform.origin).normalized() - return target_direction if self.current_state != State.RETREAT else - target_direction + return target_direction if self._current_state != State.RETREAT else - target_direction func _pointing_in_direction(direction: Vector3) -> bool: var current_direction = -self.global_transform.basis.z - return current_direction.angle_to(direction) <= self.direction_tolerance_rad + return current_direction.angle_to(direction) <= self._direction_tolerance_rad -func idle_behavior(_delta: float): +func _idle_behavior(_delta: float): self._acquire_closest_target() if self.target != null: - self.current_state = State.PURSUE + self._current_state = State.PURSUE -func pursue_behavior(_delta: float): +func _pursue_behavior(_delta: float): if self._pointing_in_direction(self._desired_direction()): - thrust_step(1.0) + self.thrust_step(1.0) var distance = self.global_transform.origin.distance_to(self.target.global_transform.origin) if distance <= fire_range: - self.current_state = State.ATTACK + self._current_state = State.ATTACK elif distance > detection_range: - self.current_state = State.IDLE + self._current_state = State.IDLE -func attack_behavior(_delta: float): +func _attack_behavior(_delta: float): if self._pointing_in_direction(self._desired_direction()): - fire() + self.fire() var distance = self.global_transform.origin.distance_to(self.target.global_transform.origin) if distance < preferred_distance: - self.current_state = State.RETREAT + self._current_state = State.RETREAT elif distance > fire_range: - self.current_state = State.PURSUE + self._current_state = State.PURSUE -func retreat_behavior(_delta: float): +func _retreat_behavior(_delta: float): if self._pointing_in_direction(self._desired_direction()): - thrust_step(1.0) + self.thrust_step(1.0) var distance = self.global_transform.origin.distance_to(self.target.global_transform.origin) if distance >= preferred_distance: - self.current_state = State.ATTACK + self._current_state = State.ATTACK func _integrate_forces(state: PhysicsDirectBodyState3D) -> void: if self.target == null: - self.current_state = State.IDLE + self._current_state = State.IDLE - if self.current_state == State.IDLE: + if self._current_state == State.IDLE: return var desired_basis = Basis.looking_at(self._desired_direction()) diff --git a/ships/player.gd b/ships/player.gd index 4ea00a72..629a8a17 100644 --- a/ships/player.gd +++ b/ships/player.gd @@ -1,19 +1,12 @@ extends Ship +## The player's ship. + @export var hyperspace_controller: HyperspaceController +## When using the "absolute" control scheme, this is the tolerance (in radians) for being slightly off-rotated while enabling thrusters. const ABSOLUTE_DIRECTION_TOLERANCE_RAD = 0.1745 -func _ready() -> void: - super() - self.free_when_destroyed = false - -func _on_jump_destination_loaded(_system: StarSystem) -> void: - self.linear_velocity = Vector3.ZERO - self.angular_velocity = Vector3.ZERO - self.position = _random_unit_vector() * self.ship_def.hyperspace_arrival_radius - self.set_target(null) - func set_target(targeted_ship: Ship) -> void: if self.target != null: self.target.set_targeted_by_player(false) @@ -23,10 +16,17 @@ func set_target(targeted_ship: Ship) -> void: if self.target != null: self.target.set_targeted_by_player(true) +func _on_jump_destination_loaded(_system: StarSystem) -> void: + self.linear_velocity = Vector3.ZERO + self.angular_velocity = Vector3.ZERO + self.position = _random_unit_vector() * self.ship_def.hyperspace_arrival_radius + self.set_target(null) + func _random_unit_vector() -> Vector3: var angle := randf_range(0, 2 * PI) return Vector3(cos(angle), 0, sin(angle)) +## Cycles through systems connected to this one, for picking a hyperspace jump destination. func _next_system_connection() -> StarSystem: var connections := self.hyperspace_controller.current_system.connections if connections.size() == 0: @@ -40,6 +40,7 @@ func _next_system_connection() -> StarSystem: return self.hyperspace_controller.galaxy.get_system(connections[index + 1]) if index + 1 < connections.size() else null +## Cycles through ships in the current system, for picking a target. func _next_ship_target() -> Ship: var ships := get_tree().get_nodes_in_group("ships") ships.erase(self) diff --git a/ships/ship.gd b/ships/ship.gd index 69fae9ec..a02f31d7 100644 --- a/ships/ship.gd +++ b/ships/ship.gd @@ -1,43 +1,86 @@ extends RigidBody3D class_name Ship +## Represents a ship currently in play. +## +## Subclasses implement player or AI controls, although a derelict ship can be represented by this class alone. + +## The definition of this ship's type. @export var ship_def: ShipDef + +## A scene to instantiate when the ship fires its weapon. @export var bullet: PackedScene + +## A scene to instantiate when the ship is destroyed. @export var explosion: PackedScene + +## An audio clip to play while the ship's thrusters are active. +## +## This audio stream should be a looping sound. Rather than restarting from the beginning each time the thruster is used, the audio stream is paused and resumed, and evenutally loops. @export var thruster_audio: AudioStreamPlayer3D + +## A material to overlay on this ship's mesh when it is targeted by the player. @export var target_overlay_material: BaseMaterial3D + +## This ship's mesh. @export var mesh_instance: MeshInstance3D +## Whether the ship node should be removed from the scene tree and freed when it is destroyed. +## +## If false, the ship will instead be made invisible and stop participating in physics and collision calculations. This is mostly useful for the player's ship, which is often expected to exist (even if invisible) by other nodes. +@export var free_when_destroyed: bool = true + +## Fires when this ship's hull integrity changes (e.g., due to damage). signal ship_hull_changed(ship: Ship) + +## Fires when this ship's shield strength changes (e.g., due to damage). signal ship_shield_changed(ship: Ship) + +## Fires when this ship is destroyed, before the node is freed. signal ship_destroyed(ship: Ship) + +## Fires when this ship changes its target. signal ship_target_changed(ship: Ship, targeted_ship: Ship) -# TODO: Prefix properties that meant to be private with underscores +## The ship's current hull integrity. +## +## This property should not be written to outside the class! var hull: float + +## The ship's current shield strength. +## +## This property should not be written to outside the class! var shield: float -var last_fired_msec: int = 0 + +## The ship's current target. +## +## This property should not be written to outside the class! var target: Ship = null -var free_when_destroyed: bool = true + +## The last tick (in ms) when the ship fired its weapon. +var _last_fired_msec: int = 0 func _ready() -> void: self.mass = self.ship_def.mass_kg self.hull = self.ship_def.hull - assert(self.hull >= ShipDef.MIN_HULL_VALUE, "Hull must be greater than 0") + assert(self.hull > 0, "Hull must be greater than 0") self.shield = self.ship_def.shield self.emit_signal("ship_hull_changed", self) self.emit_signal("ship_shield_changed", self) +## Damage this ship by the [code]shield[/code] and [code]hull[/code] amounts specified in the given dictionary. +## +## If the ship has shields up, damage is applied to the shields first, then the hull, in proportion. func damage(dmg: Dictionary) -> void: var hull_dmg: float = dmg.get("hull", 0.0) var shield_dmg: float = dmg.get("shield", 0.0) var apply_hull_dmg_pct := 1.0 - if self.shield >= ShipDef.MIN_SHIELD_VALUE: - assert(self.hull >= ShipDef.MIN_HULL_VALUE, "Hull must be greater than 0 if shield is up") + if self.shield > 0.01: # compare with epilson to mitigate floating point rounding issues + assert(self.hull > 0.0, "Hull must be greater than 0 if shield is up") var actual_shield_dmg := minf(self.shield, shield_dmg) self.shield -= actual_shield_dmg @@ -46,16 +89,19 @@ func damage(dmg: Dictionary) -> void: # Reduce hull damage proportionally to the shield damage applied apply_hull_dmg_pct -= actual_shield_dmg / shield_dmg - if self.shield < ShipDef.MIN_SHIELD_VALUE: + if apply_hull_dmg_pct > 0.0: self.hull -= hull_dmg * apply_hull_dmg_pct self.emit_signal("ship_hull_changed", self) - if self.hull < ShipDef.MIN_HULL_VALUE: - explode() + if self.hull <= 0.01: # compare with epilson to mitigate floating point rounding issues + self._explode() +## Attempt to fire the ship's weapon. +## +## This will silently fail if not enough time has passed since the last firing. func fire() -> void: var now := Time.get_ticks_msec() - if now - self.last_fired_msec < self.ship_def.fire_interval_msec: + if now - self._last_fired_msec < self.ship_def.fire_interval_msec: return var bullet_instance: RigidBody3D = self.bullet.instantiate() @@ -66,11 +112,12 @@ func fire() -> void: bullet_instance.linear_velocity = self.linear_velocity bullet_instance.apply_central_impulse(bullet_instance.transform.basis * self.ship_def.fire_force * Vector3.FORWARD) - self.last_fired_msec = now + self._last_fired_msec = now -func explode() -> void: +## Destroys the ship. +func _explode() -> void: var explosion_instance: AnimatedSprite3D = self.explosion.instantiate() - get_parent().add_child(explosion_instance) + self.get_parent().add_child(explosion_instance) explosion_instance.global_transform = self.global_transform self.emit_signal("ship_destroyed", self) @@ -82,6 +129,9 @@ func explode() -> void: self.collision_mask = 0 self.collision_layer = 0 +## Invoked by subclasses to apply a thrust force to the ship. +## +## This is expected to be invoked during a physics step (i.e., [code]_physics_process[/code]) and not otherwise. func thrust_step(magnitude: float) -> void: if self.thruster_audio.stream_paused: self.thruster_audio.stream_paused = false @@ -90,9 +140,13 @@ func thrust_step(magnitude: float) -> void: self.apply_central_force(self.transform.basis * Vector3.FORWARD * self.ship_def.thrust * magnitude) +## Invoked by subclasses on physics steps where no thrust should be applied. func thrust_stopped() -> void: self.thruster_audio.stream_paused = true +## Sets the ship's target. +## +## This must be used instead of changing the [member target] property, to correctly connect and fire signals. func set_target(targeted_ship: Ship) -> void: if targeted_ship == self.target: return @@ -106,6 +160,7 @@ func set_target(targeted_ship: Ship) -> void: self.emit_signal("ship_target_changed", self, targeted_ship) +## Invoked when the player's target changes to or from this ship, to apply or remove the corresponding visual indicator. func set_targeted_by_player(targeted: bool) -> void: self.mesh_instance.material_overlay = self.target_overlay_material if targeted else null diff --git a/ships/ship_def.gd b/ships/ship_def.gd index f1212849..57acb5f6 100644 --- a/ships/ship_def.gd +++ b/ships/ship_def.gd @@ -1,14 +1,30 @@ extends Resource class_name ShipDef +## Defines a ship type and its base properties. + +## The mass of the ship (in kg), to be applied to the ship's rigidbody. @export var mass_kg: float + +## The max hull strength of the ship, representing the damage it can withstand. @export var hull: float + +## The max shield strength of the ship, representing the damage it can withstand. @export var shield: float + +## The force of the ship's thrust (in N), when the throttle is fully opened. @export var thrust: float + +## The maximum turn speed of the ship (in rad/s). +## +## Despite the name, this is not actually a torque force as such. @export var torque: float + +## The minimum interval (in ms) between the ship's weapon firing successively. @export var fire_interval_msec: int + +## The force with which the ship's weapon fires (in N). @export var fire_force: float -@export var hyperspace_arrival_radius: float -const MIN_SHIELD_VALUE = 0.001 -const MIN_HULL_VALUE = 0.001 +## When arriving from a hyperspace jump, the ship's position will be randomized around the system center. This is the maximum radius (in m) that the randomized position is allowed to occupy. +@export var hyperspace_arrival_radius: float diff --git a/user/user_preferences.gd b/user/user_preferences.gd index af3c6b22..d933c653 100644 --- a/user/user_preferences.gd +++ b/user/user_preferences.gd @@ -1,9 +1,20 @@ extends Node -enum ControlScheme {RELATIVE = 0, ABSOLUTE = 1} +## Describes the user's preferred control scheme. +enum ControlScheme { + ## Directional keys apply a change relative to the player ship's current orientation. + RELATIVE = 0, + ## Directional keys apply an absolute change, ignoring the player ship's orientation. + ABSOLUTE = 1 +} + +## Fires whenever a user preference changes. signal preferences_updated +## The control scheme that the user has selected. +## +## This property should not be written to outside the class! var control_scheme := ControlScheme.RELATIVE func set_control_scheme(value: ControlScheme) -> void: diff --git a/utils/shader_precompiler.gd b/utils/shader_precompiler.gd index 48c7cb1c..f0b13ac3 100644 --- a/utils/shader_precompiler.gd +++ b/utils/shader_precompiler.gd @@ -6,6 +6,7 @@ extends SubViewport ## compiled and everything loaded into memory. For some reason, this still doesn't ## cut out _all_ of the hiccup later, but at least reduces it meaningfully. +## Scenes to prerender. @export var precompile: Array[PackedScene] = [] func _ready() -> void: diff --git a/weapons/blaster/blaster.gd b/weapons/blaster/blaster.gd index e836fb4f..5e406a10 100644 --- a/weapons/blaster/blaster.gd +++ b/weapons/blaster/blaster.gd @@ -1,17 +1,28 @@ extends RigidBody3D +## How long before the blaster bolt expires (in milliseconds). @export var lifetime_msec: int = 2000 + +## How much damage the blaster bolt does to shields. +## +## If a ship has shields, damage is applied to the shields first, then the hull, in proportion. @export var damage_shield: float = 80 + +## How much damage the blaster bolt does to a hull. +## +## If a ship has shields, damage is applied to the shields first, then the hull, in proportion. @export var damage_hull: float = 40 + +## An explosion to instantiate upon collision. @export var explosion: PackedScene -var spawn_time_msec: int +var _spawn_time_msec: int func _ready() -> void: - self.spawn_time_msec = Time.get_ticks_msec() + self._spawn_time_msec = Time.get_ticks_msec() func _process(_delta: float) -> void: - if Time.get_ticks_msec() - spawn_time_msec > lifetime_msec: + if Time.get_ticks_msec() - self._spawn_time_msec > self.lifetime_msec: self.queue_free() func _on_body_entered(body: Node) -> void: @@ -24,8 +35,8 @@ func _on_body_entered(body: Node) -> void: if body is Ship: var ship: Ship = body ship.damage({ - "shield": damage_shield, - "hull": damage_hull + "shield": self.damage_shield, + "hull": self.damage_hull }) self.queue_free()