From 021de5aca4d6604ad85760b5c15cd56012a798a3 Mon Sep 17 00:00:00 2001 From: Voyager Date: Tue, 18 Aug 2026 01:02:13 -0400 Subject: [PATCH] Optimize PortMaster low-memory profile --- docs/PORTMASTER.md | 29 ++++-- main/main.gd | 31 +++++- main/main.tscn | 6 -- main/runtime_performance_profile.gd | 2 +- scripts/build_portmaster.sh | 42 +++++++- scripts/prepare_portmaster_assets.sh | 96 +++++++++++++++++++ tests/controller_ui_navigation_validation.gd | 16 ++++ tests/light_performance_profile_validation.gd | 35 ++++++- tests/shoreline_ambience_validation.gd | 2 + ui/game_ui.gd | 4 + ui/player_menu.gd | 20 ++++ ui/profile_preview.tscn | 2 +- ui/title_screen.gd | 16 +++- world/rain_ambience.gd | 23 ++++- world/shoreline_ambience.gd | 30 +++++- world/world_time_visual_controller.gd | 4 +- 16 files changed, 327 insertions(+), 31 deletions(-) create mode 100755 scripts/prepare_portmaster_assets.sh diff --git a/docs/PORTMASTER.md b/docs/PORTMASTER.md index fd933d6..7746fcc 100644 --- a/docs/PORTMASTER.md +++ b/docs/PORTMASTER.md @@ -207,21 +207,38 @@ The light profile passes these Godot options: - `--audio-output-latency 40` It also passes `NETFISHING_PERFORMANCE_PROFILE=light` and -`NETFISHING_LOW_END=1`. The game renders the 3D world at 50% linear resolution -with nearest-neighbor scaling, reducing 3D pixel work by 75% while the -separately rendered UI retains its canonical resolution. The light profile -also: +`NETFISHING_LOW_END=1`. The game renders the 3D world at 37.5% linear +resolution with nearest-neighbor scaling, reducing 3D pixel work by about 86% +while the separately rendered UI retains its canonical resolution. The light +profile also: - disables the additional full-screen world-pixelation pass; - replaces animated depth-aware water shaders with opaque, per-vertex water; - disables ocean surface motion; - disables foliage wind; - keeps the title water and full-window menu patterns static; -- reduces rain to 128 particles simulated at 12 FPS; and +- replaces the animated cooler water and decorative bubbles with flat water; +- disables decorative title-screen fish and bubbles; +- omits title music, dusk music, rain ambience, and shoreline ambience from the + package while retaining gameplay sound effects, speech, and character calls; +- reduces rain to 48 particles simulated at 8 FPS; and - replaces procedural sky clouds and 81 moving local cloud patches with one flat cloud ceiling. -The normal profile retains the full visual presentation. +PortMaster exports are built in an isolated project copy with a dedicated +micro-texture profile. Every raster texture is capped at 128 pixels on its +longest side. High-volume gameplay catalogs, including fish, character parts, +environment art, equipment, gathering art, and item icons, are capped at 64 +pixels. All remaining raster textures use ETC2 VRAM compression. Short sound +effects, speech, and calls are imported as 22.05 kHz mono QOA samples. The +authoritative artwork and the desktop, Android, and macOS imports are not +modified. Source artwork, design documents, tests, platform-specific icons, +music, and ambience are omitted from the temporary export project. This +build-time profile is always present in the PortMaster package; selecting the +normal runtime profile restores visual effects, but does not restore omitted +audio or replace the micro textures with desktop-resolution assets. + +The normal profile otherwise retains the full visual presentation. To opt a capable device into the normal profile, create the persistent file `netfishing/conf/performance_profile` containing exactly: diff --git a/main/main.gd b/main/main.gd index 9359d4f..934a2fa 100644 --- a/main/main.gd +++ b/main/main.gd @@ -118,6 +118,10 @@ const NetworkWorldSpawnServiceType = preload( ) const TITLE_MUSIC_SILENCE_DB: float = -80.0 +const TITLE_MUSIC_PATH: String = ( + "res://audio/music/title/as_in_four_wolves.ogg" +) +const DUSK_MUSIC_PATH: String = "res://audio/music/world/craft.mp3" const TIME_CROSSING_EPSILON_HOURS: float = 0.000001 const PLAYER_MENU_PATTERN_SCALE: float = 0.85 const PLAYER_MENU_PATTERN_SCROLL_VELOCITY := Vector2(-7.0, -5.0) @@ -273,6 +277,7 @@ func _ready() -> void: _rain_ambience.name = "RainAmbience" add_child(_rain_ambience) _rain_ambience.configure(_world_weather) + _configure_audio_performance_profile(_performance_profile.is_light()) _shoreline_ambience.configure( _player, _test_world.get_saltwater_shoreline_mesh(), @@ -297,6 +302,8 @@ func _ready() -> void: func _configure_presentation_performance_profile(light_profile: bool) -> void: + _game_ui.get_title_screen().set_minimal_presentation(light_profile) + _game_ui.set_light_performance_profile(light_profile) var title_water_material := _title_background.material as ShaderMaterial if title_water_material != null: title_water_material.set_shader_parameter( @@ -343,6 +350,25 @@ func _configure_presentation_performance_profile(light_profile: bool) -> void: ) +func _configure_audio_performance_profile(light_profile: bool) -> void: + if light_profile: + _title_music.stream = null + _dusk_music.stream = null + _shoreline_ambience.set_audio_enabled(false) + _rain_ambience.set_audio_enabled(false) + return + _title_music.stream = _load_optional_audio_stream(TITLE_MUSIC_PATH) + _dusk_music.stream = _load_optional_audio_stream(DUSK_MUSIC_PATH) + _shoreline_ambience.set_audio_enabled(true) + _rain_ambience.set_audio_enabled(true) + + +func _load_optional_audio_stream(path: String) -> AudioStream: + if not ResourceLoader.exists(path, "AudioStream"): + return null + return load(path) as AudioStream + + func _is_dedicated_server_runtime() -> bool: if OS.has_feature("dedicated_server"): return true @@ -1488,7 +1514,8 @@ func _on_natural_time_advanced(advanced_hours: float) -> void: ) ): _dusk_music_played_for_natural_day = true - _dusk_music.play(0.0) + if _dusk_music.stream != null: + _dusk_music.play(0.0) static func _natural_interval_crosses_hour( @@ -1956,6 +1983,8 @@ func _on_quit_requested() -> void: func _show_title_music(restart_from_beginning: bool = false) -> void: _title_music_requested = true _replace_title_music_transition() + if _title_music.stream == null: + return if restart_from_beginning: _title_music.stop() _title_music.volume_db = title_music_volume_db diff --git a/main/main.tscn b/main/main.tscn index ee846d6..d48f42e 100644 --- a/main/main.tscn +++ b/main/main.tscn @@ -12,7 +12,6 @@ [ext_resource type="Script" uid="uid://dwv3nvfke5xtu" path="res://settings/player_settings_manager.gd" id="10_settings"] [ext_resource type="Resource" path="res://items/catalog/item_catalog.tres" id="11_items"] [ext_resource type="Resource" path="res://economy/buyers/main_fishing_shop.tres" id="12_main_shop"] -[ext_resource type="AudioStream" uid="uid://bgkfluumuen4m" path="res://audio/music/title/as_in_four_wolves.ogg" id="13_title_music"] [ext_resource type="Script" uid="uid://bdtb05vkiicd" path="res://ui/ui_pixelation_presenter.gd" id="14_pixelation"] [ext_resource type="PackedScene" path="res://ui/pixelation_reset_overlay.tscn" id="15_reset"] [ext_resource type="PackedScene" path="res://main/world_pixelation_postprocess.tscn" id="16_world_pixelation"] @@ -53,11 +52,9 @@ [ext_resource type="Script" uid="uid://daqtje84gqgu0" path="res://jobs/player_job_service.gd" id="52_player_jobs"] [ext_resource type="Script" uid="uid://drlyytr2o3uw8" path="res://network/network_job_service.gd" id="53_network_jobs"] [ext_resource type="Script" uid="uid://b0c3o2vy76fg3" path="res://world/shoreline_ambience.gd" id="54_shoreline_ambience"] -[ext_resource type="AudioStream" uid="uid://dck3tf8qkadea" path="res://audio/ambience/waves.wav" id="55_waves"] [ext_resource type="Script" uid="uid://c80x8jkdnx0xy" path="res://settings/controller_mapping_manager.gd" id="56_controller_mapping"] [ext_resource type="Script" path="res://settings/keyboard_mouse_mapping_manager.gd" id="keyboard_mouse_mapping"] [ext_resource type="Script" uid="uid://d0jv8n2nfqia0" path="res://network/discovery_client.gd" id="57_discovery"] -[ext_resource type="AudioStream" uid="uid://c4y8puv0n6xk8" path="res://audio/music/world/craft.mp3" id="dusk_music"] [ext_resource type="Resource" path="res://gathering/catalog/gatherable_catalog.tres" id="58_gatherable_catalog"] [ext_resource type="Script" path="res://network/network_world_spawn_service.gd" id="59_world_spawns"] [ext_resource type="Script" path="res://gathering/gathering_controller.gd" id="60_gathering_controller"] @@ -398,13 +395,11 @@ script = ExtResource("keyboard_mouse_mapping") [node name="TitleMusic" type="AudioStreamPlayer" parent="." unique_id=1292939956] unique_name_in_owner = true -stream = ExtResource("13_title_music") volume_db = -80.0 bus = &"Music" [node name="DuskMusic" type="AudioStreamPlayer" parent="." unique_id=992744241] unique_name_in_owner = true -stream = ExtResource("dusk_music") bus = &"Music" [node name="ShorelineAmbience" type="Node" parent="." unique_id=1948037177] @@ -413,7 +408,6 @@ script = ExtResource("54_shoreline_ambience") [node name="WavesAudio" type="AudioStreamPlayer" parent="ShorelineAmbience" unique_id=193530658] unique_name_in_owner = true -stream = ExtResource("55_waves") bus = &"Environment" [node name="WorldPixelationPostprocess" parent="." unique_id=344378000 instance=ExtResource("16_world_pixelation")] diff --git a/main/runtime_performance_profile.gd b/main/runtime_performance_profile.gd index cd6395e..ee2581d 100644 --- a/main/runtime_performance_profile.gd +++ b/main/runtime_performance_profile.gd @@ -8,7 +8,7 @@ const LEGACY_LIGHT_ENVIRONMENT_VARIABLE: String = "NETFISHING_LOW_END" const NORMAL_PROFILE: StringName = &"normal" const LIGHT_PROFILE: StringName = &"light" const NORMAL_WORLD_RENDER_SCALE: float = 1.0 -const LIGHT_WORLD_RENDER_SCALE: float = 0.5 +const LIGHT_WORLD_RENDER_SCALE: float = 0.375 var _profile_name: StringName = NORMAL_PROFILE diff --git a/scripts/build_portmaster.sh b/scripts/build_portmaster.sh index 3625584..66fd4e0 100755 --- a/scripts/build_portmaster.sh +++ b/scripts/build_portmaster.sh @@ -5,6 +5,7 @@ set -euo pipefail readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" readonly PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" readonly TEMPLATE_ROOT="${SCRIPT_DIR}/portmaster" +readonly ASSET_PROFILE_SCRIPT="${SCRIPT_DIR}/prepare_portmaster_assets.sh" readonly GODOT_BIN="${GODOT_BIN:-godot}" PACKAGE_ONLY=0 @@ -86,7 +87,7 @@ if [[ "${NETFISHING_ALLOW_DIRTY_RELEASE:-0}" != "1" ]]; then fi fi -for command_name in git file zip unzip sha256sum; do +for command_name in git file zip unzip sha256sum rsync; do command -v "${command_name}" >/dev/null 2>&1 || { echo "Required command not found: ${command_name}" >&2 exit 1 @@ -95,11 +96,39 @@ done mkdir -p -- "${ARM64_ROOT}" "${RELEASE_ROOT}" if [[ ${PACKAGE_ONLY} -eq 0 ]]; then + readonly EXPORT_PROJECT_ROOT="$( + mktemp -d -t netfishing-portmaster-export.XXXXXX + )" + cleanup_export_project() { + rm -rf -- "${EXPORT_PROJECT_ROOT}" + } + trap cleanup_export_project EXIT INT TERM rm -f -- "${ARM64_EXECUTABLE}" "${ARM64_PCK}" + rsync -a \ + --exclude '/.git/' \ + --exclude '/.godot/' \ + --exclude '/art/source/' \ + --exclude '/art/exported/system_icons/netfishing.ico' \ + --exclude '/art/exported/system_icons/netfishing_1024.png' \ + --exclude '/art/exported/system_icons/netfishing_1024.png.import' \ + --exclude '/audio/ambience/' \ + --exclude '/audio/music/' \ + --exclude '/builds/' \ + --exclude '/docs/design/' \ + --exclude '/tests/' \ + "${PROJECT_ROOT}/" "${EXPORT_PROJECT_ROOT}/" + "${ASSET_PROFILE_SCRIPT}" "${EXPORT_PROJECT_ROOT}" "${GODOT_BIN}" \ --headless \ - --path "${PROJECT_ROOT}" \ - --export-release "Linux ARM64" + --path "${EXPORT_PROJECT_ROOT}" \ + --import + "${GODOT_BIN}" \ + --headless \ + --path "${EXPORT_PROJECT_ROOT}" \ + --export-release "Linux ARM64" \ + "${ARM64_EXECUTABLE}" + cleanup_export_project + trap - EXIT INT TERM fi test -s "${ARM64_EXECUTABLE}" @@ -153,6 +182,8 @@ Engine/export template: $("${GODOT_BIN}" --version) Architecture: AArch64 Minimum linked GLIBC symbol version: GLIBC_2.28 Rendering method: gl_compatibility +Texture profile: 128 px maximum; 64 px gameplay catalogs; ETC2 compressed +Audio profile: 22.05 kHz mono QOA effects; music and ambience omitted The executable and PCK were exported from the release commit listed above. Repository working-tree changes were not included in game content. @@ -187,8 +218,9 @@ selected data root. Device-local configuration remains under \`netfishing/conf/\`. PortMaster launches use the light performance profile by default. Put the word \`normal\` in \`netfishing/conf/performance_profile\` to opt a capable device -into the normal rendering profile. See \`netfishing/licenses/\` for bundled -credits and license information. +into the normal rendering profile. PortMaster packages omit music and ambience +in both profiles. See \`netfishing/licenses/\` for bundled credits and license +information. ## Controls diff --git a/scripts/prepare_portmaster_assets.sh b/scripts/prepare_portmaster_assets.sh new file mode 100755 index 0000000..0429f69 --- /dev/null +++ b/scripts/prepare_portmaster_assets.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if [[ $# -ne 1 ]]; then + echo "usage: scripts/prepare_portmaster_assets.sh " >&2 + exit 2 +fi + +readonly PROJECT_ROOT="$1" +readonly GENERAL_TEXTURE_LIMIT=128 +readonly GAMEPLAY_TEXTURE_LIMIT=64 +readonly AUDIO_SAMPLE_RATE=22050 + +if [[ ! -f "${PROJECT_ROOT}/project.godot" ]]; then + echo "PortMaster asset project is missing project.godot: ${PROJECT_ROOT}" >&2 + exit 1 +fi + +limit_texture_imports() { + local asset_root="$1" + local size_limit="$2" + local import_file + local updated=0 + + if [[ ! -d "${asset_root}" ]]; then + return + fi + while IFS= read -r -d '' import_file; do + if ! grep -q '^importer="texture"$' "${import_file}"; then + continue + fi + if ! grep -q '^process/size_limit=' "${import_file}"; then + continue + fi + sed -i \ + -e 's|^compress/mode=.*$|compress/mode=2|' \ + -e "s|^process/size_limit=.*$|process/size_limit=${size_limit}|" \ + "${import_file}" + updated=$((updated + 1)) + done < <(find "${asset_root}" -type f -name '*.import' -print0) + printf 'PortMaster texture limit %d px: %d imports under %s\n' \ + "${size_limit}" "${updated}" "${asset_root#${PROJECT_ROOT}/}" +} + + +configure_audio_imports() { + local asset_root="$1" + local import_file + local updated=0 + + if [[ ! -d "${asset_root}" ]]; then + return + fi + while IFS= read -r -d '' import_file; do + if ! grep -q '^importer="wav"$' "${import_file}"; then + continue + fi + sed -i \ + -e 's|^force/mono=.*$|force/mono=true|' \ + -e 's|^force/max_rate=.*$|force/max_rate=true|' \ + -e "s|^force/max_rate_hz=.*$|force/max_rate_hz=${AUDIO_SAMPLE_RATE}|" \ + -e 's|^compress/mode=.*$|compress/mode=2|' \ + "${import_file}" + updated=$((updated + 1)) + done < <(find "${asset_root}" -type f -name '*.wav.import' -print0) + printf 'PortMaster audio profile %d Hz mono: %d imports under %s\n' \ + "${AUDIO_SAMPLE_RATE}" "${updated}" \ + "${asset_root#${PROJECT_ROOT}/}" +} + +# The entire PortMaster project uses a deliberately small, VRAM-compressed +# texture ceiling. Fonts are unaffected, preserving readable interface text +# while raster artwork is reduced for one-gigabyte shared-memory devices. +limit_texture_imports "${PROJECT_ROOT}" "${GENERAL_TEXTURE_LIMIT}" + +# These catalogs contain most of the authored high-resolution textures and +# account for the largest runtime residency. At a 640x480 output, 64 pixels +# remains sufficient for the deliberately low-fidelity light profile. +for gameplay_root in \ + "art/exported/characters" \ + "art/exported/environment" \ + "art/exported/items" \ + "fish/species" \ + "gathering" \ + "items/icons" \ + "world"; do + limit_texture_imports \ + "${PROJECT_ROOT}/${gameplay_root}" \ + "${GAMEPLAY_TEXTURE_LIMIT}" +done + +# Short speech, call, and gameplay effects remain available, but use a small +# mono QOA representation to reduce both package and resident sample memory. +configure_audio_imports "${PROJECT_ROOT}/audio/sfx" +configure_audio_imports "${PROJECT_ROOT}/sound/dialogue" diff --git a/tests/controller_ui_navigation_validation.gd b/tests/controller_ui_navigation_validation.gd index 8355ef1..d8e6a94 100644 --- a/tests/controller_ui_navigation_validation.gd +++ b/tests/controller_ui_navigation_validation.gd @@ -187,6 +187,22 @@ func _validate_low_end_profile_contract() -> void: assert(launcher.contains("NETFISHING_LOW_END=1")) assert(launcher.contains("--max-fps 30")) assert(launcher.contains("--audio-output-latency 40")) + var builder: String = FileAccess.get_file_as_string( + "res://scripts/build_portmaster.sh" + ) + assert(builder.contains("prepare_portmaster_assets.sh")) + assert(builder.contains("netfishing-portmaster-export")) + var asset_profile: String = FileAccess.get_file_as_string( + "res://scripts/prepare_portmaster_assets.sh" + ) + assert(asset_profile.contains("GENERAL_TEXTURE_LIMIT=128")) + assert(asset_profile.contains("GAMEPLAY_TEXTURE_LIMIT=64")) + assert(asset_profile.contains("AUDIO_SAMPLE_RATE=22050")) + assert(asset_profile.contains("compress/mode=2")) + assert(asset_profile.contains("fish/species")) + assert(asset_profile.contains("art/exported/characters")) + assert(builder.contains("--exclude '/audio/ambience/'")) + assert(builder.contains("--exclude '/audio/music/'")) func _make_button( diff --git a/tests/light_performance_profile_validation.gd b/tests/light_performance_profile_validation.gd index 30911c6..88179b3 100644 --- a/tests/light_performance_profile_validation.gd +++ b/tests/light_performance_profile_validation.gd @@ -63,14 +63,14 @@ func _validate_profile_resolution() -> void: assert(not normal.is_light()) assert(is_equal_approx(normal.get_world_render_scale(), 1.0)) assert(light.is_light()) - assert(is_equal_approx(light.get_world_render_scale(), 0.5)) + assert(is_equal_approx(light.get_world_render_scale(), 0.375)) assert(legacy.is_light()) func _validate_main_profile(main: Node) -> void: var profile: RuntimePerformanceProfile = main.get("_performance_profile") assert(profile != null and profile.is_light()) - assert(is_equal_approx(root.scaling_3d_scale, 0.5)) + assert(is_equal_approx(root.scaling_3d_scale, 0.375)) var pixelation: Node = main.get_node("%WorldPixelationPostprocess") assert(bool(pixelation.call("is_light_performance_profile"))) pixelation.call("set_gameplay_active", true) @@ -118,6 +118,21 @@ func _validate_main_profile(main: Node) -> void: "scroll_velocity_pixels" ) == Vector2.ZERO ) + var game_ui := main.get_node("%GameUI") as GameUI + var title_screen := game_ui.get_title_screen() + assert(title_screen != null) + assert(not title_screen.is_decorative_presentation_active()) + var player_menu := game_ui.get("_player_menu") as PlayerMenu + assert(player_menu != null) + assert(not player_menu.is_cooler_water_effect_enabled()) + assert((main.get_node("%TitleMusic") as AudioStreamPlayer).stream == null) + assert((main.get_node("%DuskMusic") as AudioStreamPlayer).stream == null) + assert( + (main.get_node("%WavesAudio") as AudioStreamPlayer).stream == null + ) + assert( + (main.get_node("RainAmbience") as AudioStreamPlayer).stream == null + ) func _validate_normal_profile(main: Node) -> void: @@ -141,6 +156,10 @@ func _validate_normal_profile(main: Node) -> void: assert(_count_foliage_wind_overrides( island.get_node("Terrain/Visual") ) > 0) + var game_ui := main.get_node("%GameUI") as GameUI + var player_menu := game_ui.get("_player_menu") as PlayerMenu + assert(player_menu != null) + assert(player_menu.is_cooler_water_effect_enabled()) var title_background := main.get_node("%TitleBackground") as ColorRect var title_material := title_background.material as ShaderMaterial @@ -167,6 +186,14 @@ func _validate_normal_profile(main: Node) -> void: var rain := visuals.get_node("LocalRain") as GPUParticles3D assert(rain.amount == WorldTimeVisualController.RAIN_PARTICLE_AMOUNT) assert(rain.fixed_fps == 30) + assert((main.get_node("%TitleMusic") as AudioStreamPlayer).stream != null) + assert((main.get_node("%DuskMusic") as AudioStreamPlayer).stream != null) + assert( + (main.get_node("%WavesAudio") as AudioStreamPlayer).stream != null + ) + assert( + (main.get_node("RainAmbience") as AudioStreamPlayer).stream != null + ) func _validate_ocean_fishing_coverage( @@ -222,8 +249,8 @@ func _validate_minimal_weather(main: Node) -> void: var rain := visuals.get_node("LocalRain") as GPUParticles3D assert(rain.amount == WorldTimeVisualController.LIGHT_RAIN_PARTICLE_AMOUNT) assert(rain.fixed_fps == WorldTimeVisualController.LIGHT_RAIN_FIXED_FPS) - assert(rain.amount == 128) - assert(rain.fixed_fps == 12) + assert(rain.amount == 48) + assert(rain.fixed_fps == 8) var clouds := visuals.get_node("LocalStormClouds") as LocalStormCloudLayer assert(clouds.get_patch_count() == 1) var ceiling := clouds.get_node("CloudCeiling") as MeshInstance3D diff --git a/tests/shoreline_ambience_validation.gd b/tests/shoreline_ambience_validation.gd index 4297d72..78829f1 100644 --- a/tests/shoreline_ambience_validation.gd +++ b/tests/shoreline_ambience_validation.gd @@ -35,6 +35,8 @@ func _run() -> void: root.add_child(controller) await process_frame assert(runtime_audio.bus == &"Environment") + assert(runtime_audio.stream == null) + controller.set_audio_enabled(true) assert(controller.near_distance < controller.far_distance) assert(controller.near_volume_db > controller.far_volume_db) var runtime_waves := runtime_audio.stream as AudioStreamWAV diff --git a/ui/game_ui.gd b/ui/game_ui.gd index 42fb025..2a1bde5 100644 --- a/ui/game_ui.gd +++ b/ui/game_ui.gd @@ -1723,6 +1723,10 @@ func get_title_screen() -> TitleScreenType: return _title_screen +func set_light_performance_profile(light_profile: bool) -> void: + _player_menu.set_light_performance_profile(light_profile) + + func set_effective_ui_pixel_size(pixel_size: int) -> void: _title_settings_panel.set_effective_ui_pixel_size(pixel_size) _pause_settings_panel.set_effective_ui_pixel_size(pixel_size) diff --git a/ui/player_menu.gd b/ui/player_menu.gd index 5016464..de314c4 100644 --- a/ui/player_menu.gd +++ b/ui/player_menu.gd @@ -149,6 +149,7 @@ const BAG_STORAGE_COLUMNS: int = 5 const BAG_STORAGE_MINIMUM_SLOTS: int = 15 const SALE_CONFIRMATION_SIZE := Vector2(520.0, 190.0) const CONTROLLER_PICKUP_HOLD_SECONDS: float = 0.42 +const LIGHT_COOLER_WATER_COLOR := Color(0.037, 0.27, 0.375, 1.0) @onready var _navigation_cluster: BubbleClusterType = %NavigationCluster @onready var _presentation_scale_root: Control = %PlayerMenuPresentationScaleRoot @@ -253,6 +254,7 @@ var _sale_buyer_override: FishBuyerProfileType var _shop_cooler_context_active: bool = false var _cooler_original_parent: Node var _cooler_original_index: int = -1 +var _authored_cooler_water_material: ShaderMaterial var _confirmation_original_parent: Node var _confirmation_original_index: int = -1 var _catalog: FishPoolType @@ -325,6 +327,9 @@ var _motion_elapsed: float = 0.0 func _ready() -> void: + _authored_cooler_water_material = ( + _cooler_water_surface.material as ShaderMaterial + ) _configure_inventory_transition_group() _cooler_original_parent = _cooler_page.get_parent() _cooler_original_index = _cooler_page.get_index() @@ -445,6 +450,21 @@ func _update_cooler_water_mask() -> void: ) +func set_light_performance_profile(light_profile: bool) -> void: + _cooler_water_surface.material = ( + null if light_profile else _authored_cooler_water_material + ) + _cooler_water_surface.color = ( + LIGHT_COOLER_WATER_COLOR if light_profile else Color.WHITE + ) + if not light_profile: + _update_cooler_water_mask() + + +func is_cooler_water_effect_enabled() -> bool: + return _cooler_water_surface.material is ShaderMaterial + + func setup( player: PlayerType, inventory: FishInventoryType, diff --git a/ui/profile_preview.tscn b/ui/profile_preview.tscn index 27313ec..c9bf60e 100644 --- a/ui/profile_preview.tscn +++ b/ui/profile_preview.tscn @@ -18,7 +18,7 @@ script = ExtResource("1") [node name="Viewport" type="SubViewport" parent="."] transparent_bg = true size = Vector2i(320, 330) -render_target_update_mode = 4 +render_target_update_mode = 2 world_3d = SubResource("PreviewWorld") [node name="WorldEnvironment" type="WorldEnvironment" parent="Viewport"] diff --git a/ui/title_screen.gd b/ui/title_screen.gd index 8fa371e..0da2b19 100644 --- a/ui/title_screen.gd +++ b/ui/title_screen.gd @@ -155,6 +155,7 @@ var _decorative_bubbles: Array[TextureRect] = [] var _decorative_bubble_tweens: Dictionary[int, Tween] = {} var _transition_bubble_ids: Dictionary[int, bool] = {} var _pending_cluster_bubbles: int = 0 +var _minimal_presentation: bool = false var _awaiting_start_input: bool = false var _start_prompt_elapsed: float = 0.0 var _navigation_focus_active: bool = false @@ -260,6 +261,14 @@ func setup( _begin_title_entry() +func set_minimal_presentation(enabled: bool) -> void: + _minimal_presentation = enabled + if _minimal_presentation: + _stop_decorative_presentation() + elif visible: + _start_decorative_presentation() + + func reopen() -> void: _cancel_title_entry_transition() _cancel_title_settings_transition() @@ -1513,6 +1522,7 @@ func _start_decorative_presentation() -> void: not _decorative_presentation_ready or not visible or _decorative_presentation_active + or _minimal_presentation ): return _decorative_generation += 1 @@ -1977,7 +1987,11 @@ func _get_idle_decorative_bubble_count() -> int: func _emit_navigation_bubble_flurry() -> void: - if not _decorative_presentation_active or not visible: + if ( + _minimal_presentation + or not _decorative_presentation_active + or not visible + ): return var bubble_count: int = _decorative_rng.randi_range( TRANSITION_BUBBLE_COUNT_MIN, diff --git a/world/rain_ambience.gd b/world/rain_ambience.gd index acea9fd..0a3a9a1 100644 --- a/world/rain_ambience.gd +++ b/world/rain_ambience.gd @@ -12,10 +12,24 @@ const SILENT_VOLUME_DB: float = -60.0 var _weather_service: WorldWeatherService var _fade_tween: Tween var _is_active: bool = false +var _audio_enabled: bool = false func _ready() -> void: bus = &"Environment" + volume_db = SILENT_VOLUME_DB + + +func set_audio_enabled(enabled: bool) -> void: + if _audio_enabled == enabled: + return + _audio_enabled = enabled + if not _audio_enabled: + _silence_immediately() + stream = null + return + if not ResourceLoader.exists(RAIN_STREAM_PATH, "AudioStreamOggVorbis"): + return var rain_stream := load(RAIN_STREAM_PATH) as AudioStreamOggVorbis var runtime_stream := ( rain_stream.duplicate() as AudioStreamOggVorbis @@ -25,7 +39,10 @@ func _ready() -> void: if runtime_stream != null: runtime_stream.loop = true stream = runtime_stream - volume_db = SILENT_VOLUME_DB + if _is_active: + _transition_to_rain( + _weather_service != null and _weather_service.is_raining() + ) func configure(weather_service: WorldWeatherService) -> void: @@ -53,6 +70,8 @@ func set_active(active: bool) -> void: if not active: _silence_immediately() return + if not _audio_enabled: + return _transition_to_rain( _weather_service != null and _weather_service.is_raining() ) @@ -66,7 +85,7 @@ func _on_weather_changed( func _transition_to_rain(is_raining: bool) -> void: - if not _is_active: + if not _is_active or not _audio_enabled: return _stop_fade() if is_raining: diff --git a/world/shoreline_ambience.gd b/world/shoreline_ambience.gd index 81e9c65..b86da69 100644 --- a/world/shoreline_ambience.gd +++ b/world/shoreline_ambience.gd @@ -1,6 +1,8 @@ class_name ShorelineAmbience extends Node +const WAVES_STREAM_PATH: String = "res://audio/ambience/waves.wav" + @export_range(0.0, 20.0, 0.1) var near_distance: float = 2.0 @export_range(1.0, 100.0, 0.5) var far_distance: float = 24.0 @export_range(-40.0, 12.0, 0.5) var near_volume_db: float = 1.0 @@ -15,15 +17,37 @@ var _shoreline_segments: Array[PackedVector2Array] = [] var _distance_update_remaining: float = 0.0 var _target_volume_db: float = -80.0 var _is_active := false +var _audio_enabled: bool = false func _ready() -> void: _waves_audio.bus = &"Environment" - _configure_audio_loop(_waves_audio.stream) + _waves_audio.stream = null _waves_audio.volume_db = -80.0 set_process(false) +func set_audio_enabled(enabled: bool) -> void: + if _audio_enabled == enabled: + return + _audio_enabled = enabled + if not _audio_enabled: + _waves_audio.stop() + _waves_audio.stream = null + set_process(false) + return + if not ResourceLoader.exists(WAVES_STREAM_PATH, "AudioStream"): + return + _waves_audio.stream = load(WAVES_STREAM_PATH) as AudioStream + _configure_audio_loop(_waves_audio.stream) + if _is_active: + set_process(true) + _update_target_volume() + _waves_audio.volume_db = _target_volume_db + if _waves_audio.stream != null: + _waves_audio.play() + + func configure(listener: Node3D, shoreline_mesh: MeshInstance3D) -> void: _listener = listener _shoreline_segments = _extract_shoreline_segments(shoreline_mesh) @@ -34,8 +58,10 @@ func configure(listener: Node3D, shoreline_mesh: MeshInstance3D) -> void: func set_active(active: bool) -> void: _is_active = active - set_process(active) + set_process(active and _audio_enabled) if active: + if not _audio_enabled: + return _distance_update_remaining = 0.0 _update_target_volume() _waves_audio.volume_db = _target_volume_db diff --git a/world/world_time_visual_controller.gd b/world/world_time_visual_controller.gd index 821f7ef..4beee85 100644 --- a/world/world_time_visual_controller.gd +++ b/world/world_time_visual_controller.gd @@ -12,8 +12,8 @@ const RAIN_VISIBILITY_AABB := AABB( Vector3(27.0, 12.0, 27.0), ) const RAIN_PARTICLE_AMOUNT: int = 2240 -const LIGHT_RAIN_PARTICLE_AMOUNT: int = 128 -const LIGHT_RAIN_FIXED_FPS: int = 12 +const LIGHT_RAIN_PARTICLE_AMOUNT: int = 48 +const LIGHT_RAIN_FIXED_FPS: int = 8 const RAIN_VELOCITY_MIN: float = 16.0 const RAIN_VELOCITY_MAX: float = 20.0 const RAIN_DROP_SIZE := Vector3(0.014, 0.34, 0.014)