Add default light profile for PortMaster
This commit is contained in:
parent
a62cce6404
commit
a3e0830bd1
16 changed files with 454 additions and 42 deletions
|
|
@ -140,30 +140,52 @@ following in the installed game:
|
|||
- A single button never cancels auto-map.
|
||||
- Holding both bumpers together for 1.25 seconds cancels auto-map.
|
||||
|
||||
## Low-end performance profile
|
||||
## PortMaster performance profile
|
||||
|
||||
The launcher keeps the normal game profile on unknown and stronger hardware.
|
||||
It enables the low-end profile only when the Linux device tree reports one of
|
||||
the identifiers used by Allwinner H616/H700 XX handhelds:
|
||||
Every PortMaster launch defaults to the light performance profile, regardless
|
||||
of device family. This reflects the low-end hardware that makes up most of the
|
||||
PortMaster ecosystem and keeps the canonical package conservative by default.
|
||||
|
||||
- `allwinner,h616`
|
||||
- `sun50iw9p1`
|
||||
- `allwinner,sun50i-h700`
|
||||
|
||||
The low-end profile passes these Godot options:
|
||||
The light profile passes these Godot options:
|
||||
|
||||
- `--single-window`
|
||||
- `--disable-vsync`
|
||||
- `--max-fps 30`
|
||||
- `--audio-output-latency 40`
|
||||
|
||||
It also passes `NETFISHING_LOW_END=1`. The game renders the 3D world at 75%
|
||||
linear resolution with nearest-neighbor scaling, reducing 3D pixel work by
|
||||
about 44% while the separately rendered UI retains its canonical resolution.
|
||||
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:
|
||||
|
||||
Do not use Godot's low processor mode for this profile. It reduces idle CPU
|
||||
usage by sleeping between updates and is not a game-performance optimization.
|
||||
- disables the additional full-screen world-pixelation pass;
|
||||
- replaces animated depth-aware water shaders with opaque, per-vertex water;
|
||||
- disables ocean surface motion;
|
||||
- reduces rain to 256 particles simulated at 15 FPS; and
|
||||
- replaces procedural sky clouds and 81 moving local cloud patches with one
|
||||
flat cloud ceiling.
|
||||
|
||||
Before adding a device family, record its exact NUL-separated device-tree
|
||||
`compatible` value from hardware. Do not infer detection from a retail product
|
||||
name alone.
|
||||
The normal profile retains the full visual presentation.
|
||||
|
||||
To opt a capable device into the normal profile, create the persistent file
|
||||
`netfishing/conf/performance_profile` containing exactly:
|
||||
|
||||
```text
|
||||
normal
|
||||
```
|
||||
|
||||
On a typical muOS installation, the full path is:
|
||||
|
||||
```text
|
||||
/mnt/mmc/ports/netfishing/conf/performance_profile
|
||||
```
|
||||
|
||||
Set the file to `light`, or remove it, to restore the default. An externally
|
||||
provided `NETFISHING_PERFORMANCE_PROFILE=normal` or `light` environment value
|
||||
takes precedence over the persistent file. Invalid values safely fall back to
|
||||
the light profile and are reported in `netfishing/log.txt`.
|
||||
|
||||
Do not use Godot's low processor mode for the light profile. It reduces idle
|
||||
CPU usage by sleeping between updates and is not a game-performance
|
||||
optimization.
|
||||
|
|
|
|||
16
main/main.gd
16
main/main.gd
|
|
@ -39,6 +39,9 @@ const PixelationResetOverlayType = preload(
|
|||
const WorldPixelationPostprocessType = preload(
|
||||
"res://main/world_pixelation_postprocess.gd"
|
||||
)
|
||||
const RuntimePerformanceProfileType = preload(
|
||||
"res://main/runtime_performance_profile.gd"
|
||||
)
|
||||
const NetworkSessionType = preload("res://network/network_session.gd")
|
||||
const DiscoveryClientType = preload("res://network/discovery_client.gd")
|
||||
const DedicatedServerConfigType = preload(
|
||||
|
|
@ -235,6 +238,7 @@ var _data_folder_dialog: FileDialog
|
|||
var _data_folder_picker_generation: int = 0
|
||||
var _restore_data_setup_after_picker: bool = false
|
||||
var _application_initialized := false
|
||||
var _performance_profile: RuntimePerformanceProfileType
|
||||
var _pending_existing_root_path := ""
|
||||
var _local_recovery_attempt_id: String = ""
|
||||
var _dedicated_runtime: bool = false
|
||||
|
|
@ -244,10 +248,17 @@ var _rain_ambience: RainAmbienceType
|
|||
|
||||
|
||||
func _ready() -> void:
|
||||
_performance_profile = RuntimePerformanceProfileType.from_environment()
|
||||
_dedicated_runtime = _is_dedicated_server_runtime()
|
||||
if _dedicated_runtime:
|
||||
call_deferred("_start_dedicated_server")
|
||||
return
|
||||
_world_pixelation.set_light_performance_profile(
|
||||
_performance_profile.is_light()
|
||||
)
|
||||
_test_world.set_light_performance_profile(
|
||||
_performance_profile.is_light()
|
||||
)
|
||||
DisplayServer.window_set_title("NETfishing")
|
||||
_rain_ambience = RainAmbienceType.new()
|
||||
_rain_ambience.name = "RainAmbience"
|
||||
|
|
@ -441,6 +452,7 @@ func _initialize_application(dedicated: bool) -> void:
|
|||
_world_weather,
|
||||
_player,
|
||||
Callable(_player, "get_active_gameplay_camera"),
|
||||
_performance_profile.is_light(),
|
||||
)
|
||||
if not _world_time.natural_time_advanced.is_connected(
|
||||
_on_natural_time_advanced
|
||||
|
|
@ -1324,9 +1336,7 @@ func _apply_world_pixelation(pixel_size: int) -> void:
|
|||
var root_viewport: Viewport = get_viewport()
|
||||
root_viewport.scaling_3d_mode = Viewport.SCALING_3D_MODE_NEAREST
|
||||
root_viewport.scaling_3d_scale = (
|
||||
0.75
|
||||
if OS.get_environment("NETFISHING_LOW_END") == "1"
|
||||
else 1.0
|
||||
_performance_profile.get_world_render_scale()
|
||||
)
|
||||
root_viewport.msaa_3d = Viewport.MSAA_DISABLED
|
||||
root_viewport.screen_space_aa = Viewport.SCREEN_SPACE_AA_DISABLED
|
||||
|
|
|
|||
50
main/runtime_performance_profile.gd
Normal file
50
main/runtime_performance_profile.gd
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
class_name RuntimePerformanceProfile
|
||||
extends RefCounted
|
||||
|
||||
const PROFILE_ENVIRONMENT_VARIABLE: String = (
|
||||
"NETFISHING_PERFORMANCE_PROFILE"
|
||||
)
|
||||
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
|
||||
|
||||
var _profile_name: StringName = NORMAL_PROFILE
|
||||
|
||||
|
||||
static func from_environment() -> RuntimePerformanceProfile:
|
||||
return from_name(
|
||||
StringName(OS.get_environment(PROFILE_ENVIRONMENT_VARIABLE)),
|
||||
OS.get_environment(LEGACY_LIGHT_ENVIRONMENT_VARIABLE) == "1",
|
||||
)
|
||||
|
||||
|
||||
static func from_name(
|
||||
profile_name: StringName,
|
||||
legacy_light_requested: bool = false,
|
||||
) -> RuntimePerformanceProfile:
|
||||
var profile := RuntimePerformanceProfile.new()
|
||||
if profile_name == LIGHT_PROFILE or (
|
||||
profile_name.is_empty() and legacy_light_requested
|
||||
):
|
||||
profile._profile_name = LIGHT_PROFILE
|
||||
else:
|
||||
profile._profile_name = NORMAL_PROFILE
|
||||
return profile
|
||||
|
||||
|
||||
func get_profile_name() -> StringName:
|
||||
return _profile_name
|
||||
|
||||
|
||||
func is_light() -> bool:
|
||||
return _profile_name == LIGHT_PROFILE
|
||||
|
||||
|
||||
func get_world_render_scale() -> float:
|
||||
return (
|
||||
LIGHT_WORLD_RENDER_SCALE
|
||||
if is_light()
|
||||
else NORMAL_WORLD_RENDER_SCALE
|
||||
)
|
||||
1
main/runtime_performance_profile.gd.uid
Normal file
1
main/runtime_performance_profile.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://8vm104r2264h
|
||||
|
|
@ -5,6 +5,7 @@ extends CanvasLayer
|
|||
|
||||
var _pixel_size: int = PlayerSettings.DEFAULT_WORLD_PIXEL_SIZE
|
||||
var _gameplay_active: bool = false
|
||||
var _light_performance_profile: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -26,6 +27,15 @@ func set_gameplay_active(active: bool) -> void:
|
|||
_refresh_grid()
|
||||
|
||||
|
||||
func set_light_performance_profile(enabled: bool) -> void:
|
||||
_light_performance_profile = enabled
|
||||
_refresh_grid()
|
||||
|
||||
|
||||
func is_light_performance_profile() -> bool:
|
||||
return _light_performance_profile
|
||||
|
||||
|
||||
func get_grid_size() -> Vector2i:
|
||||
return PlayerSettings.get_world_grid_size(
|
||||
_pixel_size,
|
||||
|
|
@ -38,6 +48,7 @@ func _refresh_grid() -> void:
|
|||
return
|
||||
var effect_enabled: bool = (
|
||||
_gameplay_active
|
||||
and not _light_performance_profile
|
||||
and _pixel_size != PlayerSettings.MIN_WORLD_PIXEL_SIZE
|
||||
)
|
||||
_screen_grid.visible = effect_enabled
|
||||
|
|
|
|||
|
|
@ -140,7 +140,10 @@ The package requires an ARM64 device, two analog sticks, GLIBC 2.28 or newer,
|
|||
and the \`weston_pkg_0.2\` runtime.
|
||||
|
||||
Save data and device-local configuration remain under \`netfishing/conf/\`.
|
||||
See \`netfishing/licenses/\` for bundled credits and license information.
|
||||
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.
|
||||
EOF
|
||||
|
||||
(
|
||||
|
|
@ -162,6 +165,10 @@ grep -q '"version": 4' "${STAGE_ROOT}/port.json"
|
|||
grep -q '"name": "netfishing.zip"' "${STAGE_ROOT}/port.json"
|
||||
grep -q '^# PORTMASTER: netfishing.zip, NETfishing.sh$' \
|
||||
"${STAGE_ROOT}/NETfishing.sh"
|
||||
grep -Fq 'PROFILE_PATH="$CONFDIR/performance_profile"' \
|
||||
"${STAGE_ROOT}/NETfishing.sh"
|
||||
grep -Fq 'NETFISHING_PERFORMANCE_PROFILE=light' \
|
||||
"${STAGE_ROOT}/NETfishing.sh"
|
||||
if grep -q 'GPTOKEYB' "${STAGE_ROOT}/NETfishing.sh"; then
|
||||
echo "GPTOKEYB must not be enabled for NETfishing." >&2
|
||||
exit 1
|
||||
|
|
|
|||
|
|
@ -78,17 +78,36 @@ fi
|
|||
|
||||
NETFISHING_GODOT_OPTIONS=()
|
||||
NETFISHING_GAME_ENVIRONMENT=()
|
||||
DEVICE_COMPATIBILITY=""
|
||||
if [[ -r /proc/device-tree/compatible ]]; then
|
||||
DEVICE_COMPATIBILITY="$(tr '\0' ' ' </proc/device-tree/compatible)"
|
||||
elif [[ -r /sys/firmware/devicetree/base/compatible ]]; then
|
||||
DEVICE_COMPATIBILITY="$(
|
||||
tr '\0' ' ' </sys/firmware/devicetree/base/compatible
|
||||
)"
|
||||
PERFORMANCE_PROFILE="${NETFISHING_PERFORMANCE_PROFILE:-}"
|
||||
PROFILE_PATH="$CONFDIR/performance_profile"
|
||||
if [[ -z "$PERFORMANCE_PROFILE" && -r "$PROFILE_PATH" ]]; then
|
||||
IFS= read -r PERFORMANCE_PROFILE < "$PROFILE_PATH" || true
|
||||
PERFORMANCE_PROFILE="${PERFORMANCE_PROFILE%$'\r'}"
|
||||
fi
|
||||
case "$DEVICE_COMPATIBILITY" in
|
||||
*allwinner,h616*|*sun50iw9p1*|*allwinner,sun50i-h700*)
|
||||
NETFISHING_GAME_ENVIRONMENT+=("NETFISHING_LOW_END=1")
|
||||
case "$PERFORMANCE_PROFILE" in
|
||||
normal)
|
||||
NETFISHING_GAME_ENVIRONMENT+=(
|
||||
"NETFISHING_PERFORMANCE_PROFILE=normal"
|
||||
)
|
||||
;;
|
||||
light|"")
|
||||
NETFISHING_GAME_ENVIRONMENT+=(
|
||||
"NETFISHING_PERFORMANCE_PROFILE=light"
|
||||
"NETFISHING_LOW_END=1"
|
||||
)
|
||||
NETFISHING_GODOT_OPTIONS+=(
|
||||
--single-window
|
||||
--disable-vsync
|
||||
--max-fps 30
|
||||
--audio-output-latency 40
|
||||
)
|
||||
;;
|
||||
*)
|
||||
echo "Unknown performance profile '$PERFORMANCE_PROFILE'; using light." >&2
|
||||
NETFISHING_GAME_ENVIRONMENT+=(
|
||||
"NETFISHING_PERFORMANCE_PROFILE=light"
|
||||
"NETFISHING_LOW_END=1"
|
||||
)
|
||||
NETFISHING_GODOT_OPTIONS+=(
|
||||
--single-window
|
||||
--disable-vsync
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ readonly -a QUICK_TESTS=(
|
|||
|
||||
readonly -a RUNTIME_TESTS=(
|
||||
"tests/inventory_notepad_art_validation.gd"
|
||||
"tests/light_performance_profile_validation.gd"
|
||||
"tests/logbook_runtime_validation.gd"
|
||||
"tests/player_experience_ui_validation.gd"
|
||||
"tests/title_credits_validation.gd"
|
||||
|
|
|
|||
|
|
@ -61,8 +61,12 @@ func _validate_low_end_profile_contract() -> void:
|
|||
var launcher: String = FileAccess.get_file_as_string(
|
||||
"res://scripts/portmaster/NETfishing.sh"
|
||||
)
|
||||
assert(launcher.contains("allwinner,h616"))
|
||||
assert(launcher.contains("sun50iw9p1"))
|
||||
assert(launcher.contains("NETFISHING_PERFORMANCE_PROFILE:-"))
|
||||
assert(launcher.contains("$CONFDIR/performance_profile"))
|
||||
assert(launcher.contains("normal)"))
|
||||
assert(launcher.contains("light|\"\")"))
|
||||
assert(launcher.contains("NETFISHING_PERFORMANCE_PROFILE=normal"))
|
||||
assert(launcher.contains("NETFISHING_PERFORMANCE_PROFILE=light"))
|
||||
assert(launcher.contains("NETFISHING_LOW_END=1"))
|
||||
assert(launcher.contains("--max-fps 30"))
|
||||
assert(launcher.contains("--audio-output-latency 40"))
|
||||
|
|
|
|||
108
tests/light_performance_profile_validation.gd
Normal file
108
tests/light_performance_profile_validation.gd
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
extends SceneTree
|
||||
|
||||
const MainScene: PackedScene = preload("res://main/main.tscn")
|
||||
const RuntimePerformanceProfileType = preload(
|
||||
"res://main/runtime_performance_profile.gd"
|
||||
)
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
_validate_profile_resolution()
|
||||
OS.set_environment(
|
||||
RuntimePerformanceProfileType.PROFILE_ENVIRONMENT_VARIABLE,
|
||||
"light",
|
||||
)
|
||||
root.size = Vector2i(640, 480)
|
||||
var main: Node = MainScene.instantiate()
|
||||
root.add_child(main)
|
||||
for _frame: int in 4:
|
||||
await process_frame
|
||||
if not bool(main.get("_application_initialized")):
|
||||
main.call("_activate_selected_data_path", "", true)
|
||||
for _frame: int in 8:
|
||||
await process_frame
|
||||
assert(bool(main.get("_application_initialized")))
|
||||
_validate_main_profile(main)
|
||||
_validate_minimal_weather(main)
|
||||
main.queue_free()
|
||||
for _frame: int in 4:
|
||||
await process_frame
|
||||
OS.unset_environment(
|
||||
RuntimePerformanceProfileType.PROFILE_ENVIRONMENT_VARIABLE
|
||||
)
|
||||
print("Light performance profile validation: PASS")
|
||||
quit()
|
||||
|
||||
|
||||
func _validate_profile_resolution() -> void:
|
||||
var normal := RuntimePerformanceProfileType.from_name(&"normal")
|
||||
var light := RuntimePerformanceProfileType.from_name(&"light")
|
||||
var legacy := RuntimePerformanceProfileType.from_name(&"", true)
|
||||
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(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))
|
||||
var pixelation: Node = main.get_node("%WorldPixelationPostprocess")
|
||||
assert(bool(pixelation.call("is_light_performance_profile")))
|
||||
pixelation.call("set_gameplay_active", true)
|
||||
var screen_grid := pixelation.get_node("ScreenGrid") as ColorRect
|
||||
assert(screen_grid != null and not screen_grid.visible)
|
||||
|
||||
var pond := main.get_node(
|
||||
"TestWorld/Regions/StarterIslandRegion/WaterBodies/Pond/VisualWater"
|
||||
) as MeshInstance3D
|
||||
var ocean := main.get_node(
|
||||
"TestWorld/Regions/StarterIslandRegion/WaterBodies/Ocean/VisualWater"
|
||||
) as MeshInstance3D
|
||||
assert(pond.material_override is StandardMaterial3D)
|
||||
assert(ocean.material_override is StandardMaterial3D)
|
||||
assert(not pond.material_override is ShaderMaterial)
|
||||
assert(not ocean.material_override is ShaderMaterial)
|
||||
assert(not ocean.is_processing())
|
||||
|
||||
|
||||
func _validate_minimal_weather(main: Node) -> void:
|
||||
var visuals: WorldTimeVisualController = main.get_node(
|
||||
"%WorldTimeVisualController"
|
||||
)
|
||||
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)
|
||||
var clouds := visuals.get_node("LocalStormClouds") as LocalStormCloudLayer
|
||||
assert(clouds.get_patch_count() == 1)
|
||||
var ceiling := clouds.get_node("CloudCeiling") as MeshInstance3D
|
||||
assert(ceiling != null and ceiling.mesh is PlaneMesh)
|
||||
assert(ceiling.material_override == null)
|
||||
assert((ceiling.mesh as PlaneMesh).material is StandardMaterial3D)
|
||||
|
||||
visuals.apply_weather_immediately(WorldWeatherService.Weather.RAINY)
|
||||
assert(rain.emitting and rain.amount_ratio > 0.99)
|
||||
assert(clouds.visible and clouds.get_storm_amount() > 0.87)
|
||||
var cloud_material := (
|
||||
(ceiling.mesh as PlaneMesh).material as StandardMaterial3D
|
||||
)
|
||||
assert(cloud_material.albedo_color.a < 0.65)
|
||||
var test_world := main.get_node("TestWorld") as TestWorld
|
||||
var runtime_environment := (
|
||||
test_world.get_world_environment().environment as Environment
|
||||
)
|
||||
var sky_material := (
|
||||
runtime_environment.sky.sky_material as ShaderMaterial
|
||||
)
|
||||
assert(is_zero_approx(float(
|
||||
sky_material.get_shader_parameter("cloud_coverage")
|
||||
)))
|
||||
assert(is_zero_approx(float(
|
||||
sky_material.get_shader_parameter("cloud_opacity")
|
||||
)))
|
||||
1
tests/light_performance_profile_validation.gd.uid
Normal file
1
tests/light_performance_profile_validation.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://c6xqcslinxd1a
|
||||
|
|
@ -13,6 +13,9 @@ const DISTANCE_FADE_START: float = 240.0
|
|||
const DISTANCE_FADE_END: float = 300.0
|
||||
const WRAP_RADIUS: float = FIELD_HALF_EXTENT
|
||||
const MAXIMUM_OPACITY: float = 1.0
|
||||
const LIGHT_CEILING_SIZE: float = 520.0
|
||||
const LIGHT_CEILING_ALTITUDE: float = 20.0
|
||||
const LIGHT_MAXIMUM_OPACITY: float = 0.72
|
||||
const BASE_VELOCITY := Vector2(0.825, -0.45)
|
||||
const BODY_OUTLINE: Array[Vector2] = [
|
||||
Vector2(-6.9, -0.8),
|
||||
|
|
@ -50,12 +53,23 @@ var _target: Node3D
|
|||
var _material: ShaderMaterial
|
||||
var _multimesh: MultiMesh
|
||||
var _cloud_mesh: MultiMeshInstance3D
|
||||
var _light_cloud_mesh: MeshInstance3D
|
||||
var _light_material: StandardMaterial3D
|
||||
var _drift_offset := Vector2.ZERO
|
||||
var _storm_amount: float = 0.0
|
||||
var _light_performance_profile: bool = false
|
||||
|
||||
|
||||
func setup(target: Node3D) -> void:
|
||||
func setup(
|
||||
target: Node3D,
|
||||
light_performance_profile: bool = false,
|
||||
) -> void:
|
||||
_target = target
|
||||
_light_performance_profile = light_performance_profile
|
||||
if _light_performance_profile:
|
||||
_build_light_cloud_ceiling()
|
||||
_update_light_position()
|
||||
else:
|
||||
_build_cloud_field()
|
||||
_update_cloud_transforms()
|
||||
set_process(true)
|
||||
|
|
@ -65,8 +79,19 @@ func set_storm_amount(amount: float, color: Color) -> void:
|
|||
_storm_amount = clampf(amount, 0.0, 1.0)
|
||||
var should_be_visible: bool = _storm_amount > 0.001
|
||||
if should_be_visible and not visible:
|
||||
if _light_performance_profile:
|
||||
_update_light_position()
|
||||
else:
|
||||
_update_cloud_transforms()
|
||||
visible = should_be_visible
|
||||
if _light_performance_profile:
|
||||
if _light_material != null:
|
||||
var light_color: Color = color
|
||||
light_color.a = (
|
||||
_storm_amount * LIGHT_MAXIMUM_OPACITY
|
||||
)
|
||||
_light_material.albedo_color = light_color
|
||||
return
|
||||
if _material == null:
|
||||
return
|
||||
_material.set_shader_parameter("weather_opacity", _storm_amount)
|
||||
|
|
@ -78,12 +103,19 @@ func get_storm_amount() -> float:
|
|||
|
||||
|
||||
func get_patch_count() -> int:
|
||||
return GRID_AXIS_COUNT * GRID_AXIS_COUNT
|
||||
return (
|
||||
1
|
||||
if _light_performance_profile
|
||||
else GRID_AXIS_COUNT * GRID_AXIS_COUNT
|
||||
)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if not visible or _target == null or not is_instance_valid(_target):
|
||||
return
|
||||
if _light_performance_profile:
|
||||
_update_light_position()
|
||||
return
|
||||
_drift_offset += BASE_VELOCITY * delta
|
||||
_drift_offset = Vector2(
|
||||
_wrap_axis(_drift_offset.x),
|
||||
|
|
@ -128,6 +160,37 @@ func _build_cloud_field() -> void:
|
|||
visible = false
|
||||
|
||||
|
||||
func _build_light_cloud_ceiling() -> void:
|
||||
_light_material = StandardMaterial3D.new()
|
||||
_light_material.resource_name = "light_cloud_ceiling"
|
||||
_light_material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
_light_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
_light_material.cull_mode = BaseMaterial3D.CULL_DISABLED
|
||||
_light_material.albedo_color = Color(0.22, 0.25, 0.26, 0.0)
|
||||
var ceiling_mesh := PlaneMesh.new()
|
||||
ceiling_mesh.size = Vector2.ONE * LIGHT_CEILING_SIZE
|
||||
ceiling_mesh.material = _light_material
|
||||
_light_cloud_mesh = MeshInstance3D.new()
|
||||
_light_cloud_mesh.name = "CloudCeiling"
|
||||
_light_cloud_mesh.position.y = LIGHT_CEILING_ALTITUDE
|
||||
_light_cloud_mesh.mesh = ceiling_mesh
|
||||
_light_cloud_mesh.cast_shadow = (
|
||||
GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||
)
|
||||
add_child(_light_cloud_mesh)
|
||||
visible = false
|
||||
|
||||
|
||||
func _update_light_position() -> void:
|
||||
if _target == null or not is_instance_valid(_target):
|
||||
return
|
||||
global_position = Vector3(
|
||||
_target.global_position.x,
|
||||
0.0,
|
||||
_target.global_position.z,
|
||||
)
|
||||
|
||||
|
||||
func _build_cloud_body_mesh() -> ArrayMesh:
|
||||
var outline := PackedVector2Array(BODY_OUTLINE)
|
||||
var surface_tool := SurfaceTool.new()
|
||||
|
|
|
|||
|
|
@ -13,6 +13,14 @@ const FOLIAGE_MATERIAL_NAMES: Array[StringName] = [
|
|||
&"leaf_light",
|
||||
&"leaf_dark",
|
||||
]
|
||||
const NORMAL_SALT_WATER_MATERIAL: Material = preload(
|
||||
"res://world/materials/stylized_water.tres"
|
||||
)
|
||||
const NORMAL_FRESH_WATER_MATERIAL: Material = preload(
|
||||
"res://world/materials/stylized_water_fresh.tres"
|
||||
)
|
||||
const LIGHT_SALT_WATER_COLOR := Color(0.11, 0.345, 0.435)
|
||||
const LIGHT_FRESH_WATER_COLOR := Color(0.18, 0.46, 0.50)
|
||||
|
||||
@export_group("Owned Nodes")
|
||||
@export_node_path("MeshInstance3D")
|
||||
|
|
@ -60,6 +68,48 @@ func get_saltwater_shoreline_mesh() -> MeshInstance3D:
|
|||
return get_node_or_null(^"ShorelineRibbons/Ocean") as MeshInstance3D
|
||||
|
||||
|
||||
func set_light_performance_profile(enabled: bool) -> void:
|
||||
var pond := get_node_or_null(
|
||||
^"WaterBodies/Pond/VisualWater"
|
||||
) as MeshInstance3D
|
||||
var ocean := get_node_or_null(
|
||||
^"WaterBodies/Ocean/VisualWater"
|
||||
) as MeshInstance3D
|
||||
if pond != null:
|
||||
pond.material_override = (
|
||||
_create_light_water_material(
|
||||
LIGHT_FRESH_WATER_COLOR,
|
||||
&"light_fresh_water",
|
||||
)
|
||||
if enabled
|
||||
else NORMAL_FRESH_WATER_MATERIAL
|
||||
)
|
||||
if ocean != null:
|
||||
ocean.material_override = (
|
||||
_create_light_water_material(
|
||||
LIGHT_SALT_WATER_COLOR,
|
||||
&"light_salt_water",
|
||||
)
|
||||
if enabled
|
||||
else NORMAL_SALT_WATER_MATERIAL
|
||||
)
|
||||
var surface_motion := ocean as WaterSurfaceMotion
|
||||
if surface_motion != null:
|
||||
surface_motion.set_motion_enabled(not enabled)
|
||||
|
||||
|
||||
func _create_light_water_material(
|
||||
color: Color,
|
||||
material_name: StringName,
|
||||
) -> StandardMaterial3D:
|
||||
var material := StandardMaterial3D.new()
|
||||
material.resource_name = str(material_name)
|
||||
material.albedo_color = color
|
||||
material.roughness = 1.0
|
||||
material.shading_mode = BaseMaterial3D.SHADING_MODE_PER_VERTEX
|
||||
return material
|
||||
|
||||
|
||||
func get_spawn_surface_triangles(
|
||||
material_names: Array[StringName],
|
||||
minimum_global_y: float,
|
||||
|
|
|
|||
|
|
@ -50,6 +50,10 @@ func get_sun() -> DirectionalLight3D:
|
|||
return _sun
|
||||
|
||||
|
||||
func set_light_performance_profile(enabled: bool) -> void:
|
||||
_starter_island.set_light_performance_profile(enabled)
|
||||
|
||||
|
||||
func get_fishable_water_regions() -> Array[FishableWaterRegion]:
|
||||
var waters: Array[FishableWaterRegion] = []
|
||||
for region: WorldRegion in _get_regions():
|
||||
|
|
|
|||
|
|
@ -35,6 +35,12 @@ func _process(_delta: float) -> void:
|
|||
)
|
||||
|
||||
|
||||
func set_motion_enabled(enabled: bool) -> void:
|
||||
set_process(enabled and amplitude > 0.0)
|
||||
if not enabled:
|
||||
position.y = _base_height
|
||||
|
||||
|
||||
static func get_default_height_offset() -> float:
|
||||
return calculate_height_offset(
|
||||
_current_time_seconds(),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ class_name WorldTimeVisualController
|
|||
extends Node
|
||||
|
||||
const UPDATE_INTERVAL_SECONDS: float = 0.1
|
||||
const LIGHT_UPDATE_INTERVAL_SECONDS: float = 0.25
|
||||
const SUN_YAW_DEGREES: float = -32.0
|
||||
const WEATHER_TRANSITION_SECONDS: float = 10.0
|
||||
const RAIN_EMITTER_OFFSET := Vector3(0.0, 7.0, 0.0)
|
||||
|
|
@ -11,6 +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 = 256
|
||||
const LIGHT_RAIN_FIXED_FPS: int = 15
|
||||
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)
|
||||
|
|
@ -92,6 +95,7 @@ var _weather_to: WorldWeatherService.Weather = (
|
|||
WorldWeatherService.Weather.SUNNY
|
||||
)
|
||||
var _weather_transition: float = 1.0
|
||||
var _light_performance_profile: bool = false
|
||||
|
||||
|
||||
func setup(
|
||||
|
|
@ -101,6 +105,7 @@ func setup(
|
|||
weather_service: WorldWeatherService = null,
|
||||
rain_target: Node3D = null,
|
||||
rain_camera_provider: Callable = Callable(),
|
||||
light_performance_profile: bool = false,
|
||||
) -> void:
|
||||
_time_service = time_service
|
||||
_weather_service = weather_service
|
||||
|
|
@ -108,6 +113,7 @@ func setup(
|
|||
_sun = sun
|
||||
_rain_target = rain_target
|
||||
_rain_camera_provider = rain_camera_provider
|
||||
_light_performance_profile = light_performance_profile
|
||||
if not _prepare_runtime_environment():
|
||||
set_process(false)
|
||||
return
|
||||
|
|
@ -135,7 +141,12 @@ func _process(delta: float) -> void:
|
|||
1.0,
|
||||
)
|
||||
_elapsed += delta
|
||||
if _elapsed < UPDATE_INTERVAL_SECONDS:
|
||||
var update_interval: float = (
|
||||
LIGHT_UPDATE_INTERVAL_SECONDS
|
||||
if _light_performance_profile
|
||||
else UPDATE_INTERVAL_SECONDS
|
||||
)
|
||||
if _elapsed < update_interval:
|
||||
return
|
||||
_elapsed = 0.0
|
||||
_apply_time(_time_service.get_time_hours())
|
||||
|
|
@ -187,10 +198,16 @@ func _prepare_runtime_environment() -> bool:
|
|||
func _prepare_rain() -> void:
|
||||
_rain = GPUParticles3D.new()
|
||||
_rain.name = "LocalRain"
|
||||
_rain.amount = RAIN_PARTICLE_AMOUNT
|
||||
_rain.amount = (
|
||||
LIGHT_RAIN_PARTICLE_AMOUNT
|
||||
if _light_performance_profile
|
||||
else RAIN_PARTICLE_AMOUNT
|
||||
)
|
||||
_rain.amount_ratio = 0.0
|
||||
_rain.lifetime = 1.25
|
||||
_rain.fixed_fps = 30
|
||||
_rain.fixed_fps = (
|
||||
LIGHT_RAIN_FIXED_FPS if _light_performance_profile else 30
|
||||
)
|
||||
_rain.local_coords = false
|
||||
_rain.visibility_aabb = RAIN_VISIBILITY_AABB
|
||||
var process_material := ParticleProcessMaterial.new()
|
||||
|
|
@ -220,7 +237,7 @@ func _prepare_storm_clouds() -> void:
|
|||
_storm_clouds = LocalStormCloudLayerType.new()
|
||||
_storm_clouds.name = "LocalStormClouds"
|
||||
add_child(_storm_clouds)
|
||||
_storm_clouds.setup(_rain_target)
|
||||
_storm_clouds.setup(_rain_target, _light_performance_profile)
|
||||
|
||||
|
||||
func _apply_time(time_hours: float) -> void:
|
||||
|
|
@ -259,6 +276,33 @@ func _apply_time(time_hours: float) -> void:
|
|||
var fog_color: Color = _blended_color(
|
||||
NIGHT_FOG, DAY_FOG, WARM_FOG, daylight, warmth
|
||||
)
|
||||
if _light_performance_profile:
|
||||
var simple_cloud_color := _blended_color(
|
||||
NIGHT_CLOUD_SHADOW,
|
||||
DAY_CLOUD_SHADOW,
|
||||
WARM_CLOUD_SHADOW,
|
||||
daylight,
|
||||
warmth,
|
||||
)
|
||||
var simple_overcast: float = _weather_value(
|
||||
0.0,
|
||||
0.55,
|
||||
0.82,
|
||||
0.0,
|
||||
)
|
||||
sky_top = sky_top.lerp(simple_cloud_color, simple_overcast)
|
||||
sky_horizon = sky_horizon.lerp(
|
||||
simple_cloud_color.lightened(0.08),
|
||||
simple_overcast,
|
||||
)
|
||||
ground_bottom = ground_bottom.lerp(
|
||||
simple_cloud_color.darkened(0.12),
|
||||
simple_overcast * 0.65,
|
||||
)
|
||||
ground_horizon = ground_horizon.lerp(
|
||||
simple_cloud_color,
|
||||
simple_overcast * 0.75,
|
||||
)
|
||||
var foggy_fog_amount: float = _weather_value(0.0, 0.0, 0.0, 1.0)
|
||||
var rain_fog_amount: float = _weather_value(0.0, 0.0, 1.0, 0.0)
|
||||
var fog_daylight_amount: float = daylight * foggy_fog_amount
|
||||
|
|
@ -399,6 +443,8 @@ func _apply_water_environment(
|
|||
foggy_horizon_occlusion: float,
|
||||
fog_render_color: Color,
|
||||
) -> void:
|
||||
if _light_performance_profile:
|
||||
return
|
||||
var water_tint := _blended_color(
|
||||
NIGHT_WATER_TINT,
|
||||
Color.WHITE,
|
||||
|
|
@ -561,6 +607,15 @@ func _apply_sky_clouds(daylight: float, warmth: float) -> void:
|
|||
daylight,
|
||||
warmth,
|
||||
)
|
||||
if _light_performance_profile:
|
||||
_sky_material.set_shader_parameter("cloud_coverage", 0.0)
|
||||
_sky_material.set_shader_parameter("cloud_opacity", 0.0)
|
||||
if _storm_clouds != null:
|
||||
_storm_clouds.set_storm_amount(
|
||||
_weather_value(0.0, 0.42, 0.88, 0.0),
|
||||
lower_cloud_color,
|
||||
)
|
||||
return
|
||||
_sky_material.set_shader_parameter(
|
||||
"cloud_coverage",
|
||||
_weather_value(0.0, 0.58, 0.985, 0.0),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue