netfishing/ui/profile_preview.gd

240 lines
6.9 KiB
GDScript3
Raw Normal View History

class_name ProfilePreview
extends SubViewportContainer
const ControllerMappingManagerType = preload(
"res://settings/controller_mapping_manager.gd"
)
const UIReferencePresentationType = preload(
"res://ui/ui_reference_presentation.gd"
)
2026-08-04 22:34:43 -04:00
const FRONT_FACING_YAW: float = PI
const DEFAULT_CAMERA_DISTANCE: float = 1.9
const MIN_CAMERA_DISTANCE: float = 1.05
const MAX_CAMERA_DISTANCE: float = 2.8
const CAMERA_ZOOM_STEP: float = 0.14
const CAMERA_TARGET_HEIGHT: float = 0.95
@export_range(0.1, 2.0, 0.05) var drag_sensitivity: float = 0.012
@export_range(0.1, 4.0, 0.1) var keyboard_speed: float = 1.8
@onready var _preview_root: Node3D = %PreviewRoot
2026-08-04 22:34:43 -04:00
@onready var _preview_environment: WorldEnvironment = %WorldEnvironment
@onready var _preview_sun: DirectionalLight3D = %KeyLight
@onready var _preview_camera: Camera3D = $Viewport/Camera3D
@onready var _pixelation_material: ShaderMaterial = material as ShaderMaterial
var _dragging: bool = false
var _camera_yaw: float = 0.0
var _camera_pitch: float = 0.0
var _camera_distance: float = DEFAULT_CAMERA_DISTANCE
var _visuals: Node3D
var _controller_mapping_manager: ControllerMappingManagerType
2026-08-04 22:34:43 -04:00
var _source_environment: WorldEnvironment
var _source_sun: DirectionalLight3D
var _world_pixel_size: int = PlayerSettings.DEFAULT_WORLD_PIXEL_SIZE
func setup_controller_mapping(
mapping_manager: ControllerMappingManagerType,
) -> void:
_controller_mapping_manager = mapping_manager
2026-08-04 22:34:43 -04:00
func setup_world_lighting(
world_environment: WorldEnvironment,
world_sun: DirectionalLight3D,
) -> void:
_source_environment = world_environment
_source_sun = world_sun
_sync_world_lighting()
func set_world_pixel_size(pixel_size: int) -> void:
_world_pixel_size = clampi(
pixel_size,
PlayerSettings.MIN_WORLD_PIXEL_SIZE,
PlayerSettings.MAX_WORLD_PIXEL_SIZE,
)
_refresh_pixelation_filter()
func _ready() -> void:
focus_mode = Control.FOCUS_ALL
texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
gui_input.connect(_on_gui_input)
resized.connect(_refresh_pixelation_filter)
get_window().size_changed.connect(_refresh_pixelation_filter)
_configure_preview_lighting()
2026-08-04 22:34:43 -04:00
reset_view()
_visuals = PlayerVisualPresenter.instantiate_visuals()
_preview_root.add_child(_visuals)
var rod := _visuals.find_child("FishingRod", true, false) as Node3D
if rod != null:
rod.visible = false
var catch_display := _visuals.find_child("CatchDisplay", true, false) as Node3D
if catch_display != null:
catch_display.visible = false
call_deferred("_refresh_pixelation_filter")
func apply_appearance_profile(profile: Dictionary) -> void:
if _visuals != null:
PlayerVisualPresenter.apply_appearance(_visuals, profile)
func reset_view() -> void:
2026-08-04 22:34:43 -04:00
_preview_root.rotation.y = FRONT_FACING_YAW
_camera_yaw = 0.0
_camera_pitch = 0.0
_camera_distance = DEFAULT_CAMERA_DISTANCE
_apply_camera_orbit()
if _preview_camera != null:
_preview_camera.position.z = DEFAULT_CAMERA_DISTANCE
func _process(delta: float) -> void:
2026-08-04 22:34:43 -04:00
_sync_world_lighting()
if not has_focus():
return
var axis := Input.get_axis("ui_left", "ui_right")
var right_stick: float = (
_controller_mapping_manager.get_role_axis(
ControllerMappingManagerType.ROLE_RIGHT_STICK_X
)
2026-08-04 09:48:11 -04:00
if _controller_mapping_manager != null
else Input.get_joy_axis(0, JOY_AXIS_RIGHT_X)
)
if absf(right_stick) > 0.2:
axis = right_stick
if absf(axis) > 0.1:
_rotate(axis * keyboard_speed * delta)
func _on_gui_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.shift_pressed:
if event.button_index == MOUSE_BUTTON_WHEEL_UP and event.pressed:
_zoom_preview(-CAMERA_ZOOM_STEP)
grab_focus()
accept_event()
return
if event.button_index == MOUSE_BUTTON_WHEEL_DOWN and event.pressed:
_zoom_preview(CAMERA_ZOOM_STEP)
grab_focus()
accept_event()
return
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT:
_dragging = event.pressed
if event.pressed:
grab_focus()
accept_event()
elif event is InputEventMouseMotion and _dragging:
_rotate(event.relative.x * drag_sensitivity)
_camera_pitch = clampf(
_camera_pitch + event.relative.y * drag_sensitivity,
-0.55,
0.55,
)
_apply_camera_orbit()
accept_event()
func _notification(what: int) -> void:
if what == NOTIFICATION_VISIBILITY_CHANGED and not is_visible_in_tree():
_dragging = false
func _rotate(amount: float) -> void:
_camera_yaw = fposmod(_camera_yaw + amount, TAU)
_apply_camera_orbit()
func _zoom_preview(amount: float) -> void:
if _preview_camera == null:
return
_camera_distance = clampf(
_camera_distance + amount,
MIN_CAMERA_DISTANCE,
MAX_CAMERA_DISTANCE,
)
_apply_camera_orbit()
func _apply_camera_orbit() -> void:
if _preview_camera == null:
return
var horizontal_distance := cos(_camera_pitch) * _camera_distance
_preview_camera.position = Vector3(
sin(_camera_yaw) * horizontal_distance,
CAMERA_TARGET_HEIGHT + sin(_camera_pitch) * _camera_distance,
cos(_camera_yaw) * horizontal_distance,
)
_preview_camera.look_at(
Vector3(0.0, CAMERA_TARGET_HEIGHT, 0.0),
Vector3.UP,
)
2026-08-04 22:34:43 -04:00
func _sync_world_lighting() -> void:
# The customization viewport deliberately does not follow gameplay time of
# day. Keep this method as a compatibility seam for existing setup callers.
pass
func _configure_preview_lighting() -> void:
if _preview_environment == null or _preview_sun == null:
return
var environment := Environment.new()
environment.background_mode = Environment.BG_COLOR
environment.background_color = Color("082431")
environment.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
environment.ambient_light_color = Color("b9d8d4")
environment.ambient_light_energy = 1.15
environment.reflected_light_source = Environment.REFLECTION_SOURCE_DISABLED
_preview_environment.environment = environment
_preview_sun.rotation_degrees = Vector3(-35.0, -25.0, 0.0)
_preview_sun.light_color = Color("fff1d0")
_preview_sun.light_energy = 1.1
_preview_sun.light_indirect_energy = 0.0
_preview_sun.shadow_enabled = false
func _refresh_pixelation_filter() -> void:
if not is_node_ready() or _pixelation_material == null:
2026-08-04 22:34:43 -04:00
return
_pixelation_material.set_shader_parameter(
"logical_grid_size",
Vector2(calculate_pixelated_grid_size(
size,
Vector2(get_window().size),
_world_pixel_size,
)),
)
static func calculate_pixelated_grid_size(
control_size: Vector2,
window_size: Vector2,
pixel_size: int,
) -> Vector2i:
var safe_window_size := Vector2(
maxf(window_size.x, 1.0),
maxf(window_size.y, 1.0),
)
var world_grid_size: Vector2i = PlayerSettings.get_world_grid_size(
pixel_size,
Vector2i(roundi(safe_window_size.x), roundi(safe_window_size.y)),
)
var world_pixel_extent: float = (
safe_window_size.y / float(maxi(world_grid_size.y, 1))
)
var presentation_scale: float = UIReferencePresentationType.get_scale(
safe_window_size
)
var grid_size: Vector2 = (
control_size * presentation_scale / maxf(world_pixel_extent, 0.001)
)
return Vector2i(
maxi(roundi(grid_size.x), 1),
maxi(roundi(grid_size.y), 1),
)