forked from woofmeow/straywild
Optimize PortMaster low-memory profile
This commit is contained in:
parent
b27883d115
commit
021de5aca4
16 changed files with 327 additions and 31 deletions
|
|
@ -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:
|
||||
|
|
|
|||
31
main/main.gd
31
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
|
||||
|
|
|
|||
|
|
@ -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")]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
96
scripts/prepare_portmaster_assets.sh
Executable file
96
scripts/prepare_portmaster_assets.sh
Executable file
|
|
@ -0,0 +1,96 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -ne 1 ]]; then
|
||||
echo "usage: scripts/prepare_portmaster_assets.sh <temporary-project-root>" >&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"
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue