diff --git a/controllers/GameController.gd b/controllers/GameController.gd index ae222c6..6eefed9 100644 --- a/controllers/GameController.gd +++ b/controllers/GameController.gd @@ -103,6 +103,13 @@ var keyboard_steering: bool = false func _ready() -> void: + # Most visuals still animate per rendered frame. The main/surface scenes opt + # their roots out of global interpolation, then opt their physics-follow + # camera and Ship.tscn hulls back in. + # Running the camera after ordinary physics nodes lets it follow the ships' + # newest centroid, and interpolation fills the display frames in between. + process_physics_priority = 1 + camera.reset_physics_interpolation() # #148: the main theme, which outlives this scene - see Music.ensure(). # This script is the root of BOTH the space scene and the planet # surface, so one call here covers the whole game. @@ -292,7 +299,7 @@ func _process(delta: float) -> void: _update_pivot(delta) # Keep the heading fresh while a control is held. Fleet advances its own - # anchor in _process, so this only needs to report the direction. The + # anchor in _physics_process, so this only needs to report the direction. The # keyboard takes precedence, and releasing the keys has to stop the fleet # itself - unlike the stick, there is no centred-again signal to hook. # #124: the breach drives. One check here covers the stick, the keyboard @@ -303,6 +310,12 @@ func _process(delta: float) -> void: else: _apply_steering() + +func _physics_process(delta: float) -> void: + _update_camera(delta) + + +func _update_camera(delta: float) -> void: var target: Vector2 = fleet.get_centroid() if camera.global_position.distance_to(target) <= camera_snap_distance: # Snap and stop dead - see camera_snap_distance. Assigning the exact diff --git a/core/GameState.gd b/core/GameState.gd index 1c522d6..a4f8827 100644 --- a/core/GameState.gd +++ b/core/GameState.gd @@ -2634,12 +2634,24 @@ func _advance_resource_converters(delta: float) -> void: building.conversion_progress = progress -func _advance_distorder_drain(delta: float) -> void: +func _distorder_boost_in_use() -> bool: + for fleet in get_tree().get_nodes_in_group("player_fleet"): + if fleet.has_method("is_using_speed_boost") and fleet.is_using_speed_boost(): + return true + return false + + +func _advance_distorder_drain(delta: float, boost_in_use: bool = true) -> void: if fleet_speed_multiplier <= 1.0: # Nothing owed at 1.0x - and no stale fractional progress should # carry over into a LATER, unrelated boost. _distorder_drain_progress = 0.0 return + if not boost_in_use: + # The fader is a selected cruise speed, not an always-running machine. + # Keep any already-earned fraction so stopping cannot erase a nearly + # complete unit of cost, but accrue nothing while the fleet is parked. + return # Normalized against the CURRENT ceiling, so "full boost" always costs # ENERGY_DRAIN_PER_SECOND however many distorder vessels widened it - # a bigger fleet buys more speed, not cheaper speed. @@ -2897,7 +2909,7 @@ func _process(delta: float) -> void: _advance_planning() _advance_repairs(delta) _advance_resource_converters(delta) - _advance_distorder_drain(delta) + _advance_distorder_drain(delta, _distorder_boost_in_use()) _advance_quests() for e in extractors: e.mining_progress = float(e.get("mining_progress", 0.0)) + delta * EXTRACTOR_RATE diff --git a/project.godot b/project.godot index a1f319a..84a0d71 100644 --- a/project.godot +++ b/project.godot @@ -36,6 +36,10 @@ theme/default_font_size=78 pointing/emulate_mouse_from_touch=false +[physics] + +common/physics_interpolation=true + [rendering] renderer/rendering_method="gl_compatibility" diff --git a/surface/PlanetSetupEditor.tscn b/surface/PlanetSetupEditor.tscn index 7e5eaa5..b2d74cd 100644 --- a/surface/PlanetSetupEditor.tscn +++ b/surface/PlanetSetupEditor.tscn @@ -5,6 +5,7 @@ [ext_resource type="Script" path="res://ui/IconButton.gd" id="4"] [node name="PlanetSetupEditor" type="Node2D"] +physics_interpolation_mode = 2 script = ExtResource("1") [node name="BackgroundLayer" parent="." instance=ExtResource("3")] diff --git a/surface/PlanetSurface.tscn b/surface/PlanetSurface.tscn index ba9bb47..4f61101 100644 --- a/surface/PlanetSurface.tscn +++ b/surface/PlanetSurface.tscn @@ -19,6 +19,7 @@ [ext_resource type="Script" path="res://ui/QuestPanel.gd" id="quest_panel"] [node name="PlanetSurface" type="Node2D"] +physics_interpolation_mode = 2 script = ExtResource("1") [node name="BackgroundLayer" parent="." instance=ExtResource("3")] @@ -47,8 +48,11 @@ script = ExtResource("dash_ctrl") script = ExtResource("ability_layout") [node name="Camera2D" type="Camera2D" parent="."] +process_physics_priority = 2 +physics_interpolation_mode = 1 position = Vector2(0, 0) zoom = Vector2(1, 1) +process_callback = 0 [node name="SurfaceFeatures" type="Node2D" parent="."] diff --git a/tests/test_distorder_energy.gd b/tests/test_distorder_energy.gd index 74bd6a9..0d974dd 100644 --- a/tests/test_distorder_energy.gd +++ b/tests/test_distorder_energy.gd @@ -113,11 +113,45 @@ func _process(delta: float) -> bool: root.add_child(main) fleet = main.get_node("Fleet") fader = main.get_node("UI/SpeedFader") - fleet.spawn_ships(1, Vector2.ZERO, PackedStringArray([GameState.VESSEL_DISTORDER])) + # A carrier supplies the hold the Propulser burns from. The previous + # one-Propulser fixture had zero cargo capacity, so its depletion check + # accidentally began with no energy regardless of what it deposited. + fleet.spawn_ships(2, Vector2.ZERO, PackedStringArray([ + GameState.VESSEL_CARRIER, GameState.VESSEL_DISTORDER])) fader._process(0.0) if not fader.visible: return _fail_now("fader hidden with a distorder ship in the fleet") + if not bool(ProjectSettings.get_setting( + "physics/common/physics_interpolation", false)): + return _fail_now("physics interpolation is disabled for boosted flight") + if fleet.ships[0].physics_interpolation_mode \ + != Node.PHYSICS_INTERPOLATION_MODE_ON: + return _fail_now("the boosted vessel is not physics-interpolated") + if main.get_node("Camera2D").physics_interpolation_mode \ + != Node.PHYSICS_INTERPOLATION_MODE_ON: + return _fail_now("the fleet camera is not physics-interpolated") + # #199: choosing a higher cruise speed is free while parked. Once a + # heading is active, the exact same multiplier resumes its normal drain. + game_state.deposit_fleet_resource(GameState.RESOURCE_ENERGY, 30) + game_state.set_fleet_speed_multiplier(GameState.MAX_FLEET_SPEED_MULTIPLIER) + var parked_energy: int = game_state.fleet_resource(GameState.RESOURCE_ENERGY) + game_state._advance_distorder_drain(5.0, + game_state._distorder_boost_in_use()) + if game_state.fleet_resource(GameState.RESOURCE_ENERGY) != parked_energy: + return _fail_now("a parked fleet consumed boost energy") + fleet.move_in_direction(Vector2.RIGHT) + if not fleet.is_using_speed_boost(): + return _fail_now("boosted cruise did not report its energy use") + game_state._advance_distorder_drain(1.0, + game_state._distorder_boost_in_use()) + if game_state.fleet_resource(GameState.RESOURCE_ENERGY) >= parked_energy: + return _fail_now("moving at boosted cruise consumed no energy") + fleet.stop() + print("parked boost was free; moving boost resumed drain") + + game_state.withdraw_fleet_resource(GameState.RESOURCE_ENERGY, + game_state.fleet_resource(GameState.RESOURCE_ENERGY)) game_state.deposit_fleet_resource(GameState.RESOURCE_ENERGY, 4) game_state.set_fleet_speed_multiplier(GameState.MAX_FLEET_SPEED_MULTIPLIER) fader._process(0.0) diff --git a/tests/test_energy_propulser_dash.gd b/tests/test_energy_propulser_dash.gd index 25b8e80..4cd3bad 100644 --- a/tests/test_energy_propulser_dash.gd +++ b/tests/test_energy_propulser_dash.gd @@ -67,14 +67,14 @@ func _process(delta: float) -> bool: return _fail("the Propulser that dashed did not enter cooldown") # The burst should cover substantially more than a linear quarter of # its distance at a quarter of its duration, then ease into its stop. - fleet.set_process(false) + fleet.set_physics_process(false) fleet._advance_dash(fleet.dash_duration * 0.25) var early_distance: float = fleet.get_centroid().distance_to(first_origin) if early_distance <= DASH_DISTANCE * 0.45 \ or early_distance >= DASH_DISTANCE * 0.7: return _fail("dash has no useful ease-out curve (quarter-time distance %.1f)" \ % early_distance) - fleet.set_process(true) + fleet.set_physics_process(true) dash._process(0.0) if not main.get_node("UI/DashButton").enabled: return _fail("one cooling Propulser disabled a second ready one") diff --git a/ui/MainMenu.tscn b/ui/MainMenu.tscn index ce47cc7..336865d 100644 --- a/ui/MainMenu.tscn +++ b/ui/MainMenu.tscn @@ -4,6 +4,7 @@ [ext_resource type="Script" uid="uid://dcvdmc68b3nve" path="res://ui/IconButton.gd" id="2"] [node name="MainMenu" type="Node2D" unique_id=2119550059] +physics_interpolation_mode = 2 script = ExtResource("1") [node name="BackgroundLayer" type="CanvasLayer" parent="." unique_id=1572211077] diff --git a/vessels/Fleet.gd b/vessels/Fleet.gd index 3e44c4d..d88e970 100644 --- a/vessels/Fleet.gd +++ b/vessels/Fleet.gd @@ -230,6 +230,7 @@ func teleport_to(world_pos: Vector2) -> void: ship.move_target += displacement ship.velocity = Vector2.ZERO ship.fleet_velocity = Vector2.ZERO + ship.reset_physics_interpolation() steer_direction = Vector2.ZERO dash_remaining = 0.0 dash_direction = Vector2.ZERO @@ -242,7 +243,7 @@ func teleport_to(world_pos: Vector2) -> void: func move_in_direction(direction: Vector2) -> void: - # Joystick steering. Sets the heading; _process advances the anchor. + # Joystick steering. Sets the heading; _physics_process advances the anchor. # # This used to park a target 2000 units ahead and let the ships chase # it. That made formation changes barely register while flying: with @@ -275,7 +276,13 @@ func is_dashing() -> bool: return dash_remaining > 0.0 -func _process(delta: float) -> void: +func is_using_speed_boost() -> bool: + return GameState.fleet_speed_multiplier > 1.0 \ + and steer_direction != Vector2.ZERO and not is_dashing() \ + and not ships.is_empty() + + +func _physics_process(delta: float) -> void: # #158: a building may pay a local cost out of the hold when the fleet is # actually standing at it, and the autoload has no other way to know # where that is. Written before the early return below - a parked fleet diff --git a/vessels/Ship.tscn b/vessels/Ship.tscn index 886ee21..0226ab2 100644 --- a/vessels/Ship.tscn +++ b/vessels/Ship.tscn @@ -6,6 +6,7 @@ radius = 16.0 [node name="Ship" type="Area2D"] +physics_interpolation_mode = 1 script = ExtResource("1") [node name="CollisionShape2D" type="CollisionShape2D" parent="."] diff --git a/world/IntroDirector.gd b/world/IntroDirector.gd index 84382e1..bf8eb7d 100644 --- a/world/IntroDirector.gd +++ b/world/IntroDirector.gd @@ -485,6 +485,7 @@ func _complete_whiteout() -> void: whiteout_teleported = true fleet.teleport_to(DEFAULT_SPAWN) camera.global_position = fleet.get_centroid() + camera.reset_physics_interpolation() camera.offset = Vector2.ZERO diff --git a/world/Main.tscn b/world/Main.tscn index 89cecce..1b4bd19 100644 --- a/world/Main.tscn +++ b/world/Main.tscn @@ -57,6 +57,7 @@ shader_parameter/cell_size = 60.0 shader_parameter/bg_color = Color(0.02, 0.02, 0.08, 1) [node name="Main" type="Node2D"] +physics_interpolation_mode = 2 script = ExtResource("3") [node name="BackgroundLayer" type="CanvasLayer" parent="."] @@ -94,8 +95,11 @@ script = ExtResource("dash_ctrl") script = ExtResource("ability_layout") [node name="Camera2D" type="Camera2D" parent="."] +process_physics_priority = 2 +physics_interpolation_mode = 1 position = Vector2(0, 0) zoom = Vector2(1, 1) +process_callback = 0 [node name="EnemySpawner" type="Node2D" parent="."] script = ExtResource("4")