Avatar customization: drag-to-rotate preview, fill the dead space, seam fix
checkpoint / checkpoint (push) Successful in 1m45s
checkpoint / checkpoint (push) Successful in 1m45s
- The preview is now a turntable: drag it to rotate the avatar and see all sides; it slowly auto-spins after ~2.5s of no input. Starts facing the camera so you see the face. "drag to rotate" hint under the viewport. - The big grey void to the right of the controls is gone -- the preview panel now expands to fill the leftover width; the controls dock to the right edge at a fixed width. - Camera reframed to fit the whole ~2 m body; softer preview lights. - Placeholder avatar: torso/arms/legs now share one 0.46 m depth so a limb always covers the torso slice behind it -- kills the bright "cyan seam" (it was a lit sliver of the torso side peeking past the thinner arm). Verified headless (boot, 2p multiplayer, avatar smoke source+pack, fresh-clone) + eyeballed the customization screen and the avatar seams in rendered screenshots. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
This commit is contained in:
@@ -76,6 +76,16 @@ var _preview_root: Node3D = null
|
||||
## Currently selected slot id.
|
||||
var _selected_slot: String = ""
|
||||
|
||||
## Turntable: the preview avatar's Y rotation (radians), whether the player is
|
||||
## currently dragging it, and how long since they last touched it (drives the
|
||||
## slow idle auto-spin). Starts facing the camera.
|
||||
var _turntable_yaw: float = PI
|
||||
var _turntable_dragging: bool = false
|
||||
var _turntable_idle: float = 0.0
|
||||
const _TURNTABLE_IDLE_SPIN_DELAY := 2.5
|
||||
const _TURNTABLE_IDLE_SPIN_SPEED := 0.5
|
||||
const _TURNTABLE_DRAG_SPEED := 0.01
|
||||
|
||||
## UI node references.
|
||||
var _slot_list: VBoxContainer = null
|
||||
var _item_list: VBoxContainer = null
|
||||
@@ -142,10 +152,11 @@ func _build_ui() -> void:
|
||||
root_hbox.add_theme_constant_override("separation", 16)
|
||||
add_child(root_hbox)
|
||||
|
||||
# --- Left: preview ---
|
||||
# --- Left: preview (takes all the leftover width) ---
|
||||
var preview_panel := PanelContainer.new()
|
||||
preview_panel.name = "PreviewPanel"
|
||||
preview_panel.custom_minimum_size = Vector2(480, 0)
|
||||
preview_panel.custom_minimum_size = Vector2(420, 0)
|
||||
preview_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
root_hbox.add_child(preview_panel)
|
||||
|
||||
var preview_margin := MarginContainer.new()
|
||||
@@ -155,15 +166,30 @@ func _build_ui() -> void:
|
||||
preview_margin.add_theme_constant_override("margin_bottom", 8)
|
||||
preview_panel.add_child(preview_margin)
|
||||
|
||||
var preview_vbox := VBoxContainer.new()
|
||||
preview_vbox.add_theme_constant_override("separation", 6)
|
||||
preview_margin.add_child(preview_vbox)
|
||||
|
||||
_preview_container = SubViewportContainer.new()
|
||||
_preview_container.name = "PreviewViewportContainer"
|
||||
_preview_container.stretch = true
|
||||
_preview_container.custom_minimum_size = Vector2(464, 560)
|
||||
preview_margin.add_child(_preview_container)
|
||||
_preview_container.custom_minimum_size = Vector2(400, 520)
|
||||
_preview_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_preview_container.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
_preview_container.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
_preview_container.gui_input.connect(_on_preview_gui_input)
|
||||
preview_vbox.add_child(_preview_container)
|
||||
|
||||
var rotate_hint := Label.new()
|
||||
rotate_hint.text = "drag to rotate"
|
||||
rotate_hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
rotate_hint.add_theme_color_override("font_color", Color(1, 1, 1, 0.5))
|
||||
rotate_hint.add_theme_font_size_override("font_size", 12)
|
||||
preview_vbox.add_child(rotate_hint)
|
||||
|
||||
_preview_viewport = SubViewport.new()
|
||||
_preview_viewport.name = "PreviewViewport"
|
||||
_preview_viewport.size = Vector2i(464, 560)
|
||||
_preview_viewport.size = Vector2i(400, 520)
|
||||
_preview_viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
|
||||
_preview_viewport.transparent_bg = true
|
||||
# Its own 3D world -- a SubViewport that shares the (Control-only) main
|
||||
@@ -178,17 +204,17 @@ func _build_ui() -> void:
|
||||
env.background_mode = Environment.BG_COLOR
|
||||
env.background_color = Color(0.10, 0.11, 0.14)
|
||||
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||
env.ambient_light_color = Color(0.6, 0.6, 0.65)
|
||||
env.ambient_light_energy = 1.0
|
||||
env.ambient_light_color = Color(0.62, 0.64, 0.7)
|
||||
env.ambient_light_energy = 0.7
|
||||
world_env.environment = env
|
||||
_preview_viewport.add_child(world_env)
|
||||
|
||||
# Camera for the preview.
|
||||
# Camera for the preview -- framed to fit the full ~2 m avatar.
|
||||
_preview_camera = Camera3D.new()
|
||||
_preview_camera.name = "PreviewCamera"
|
||||
# look_at needs a global transform, which only exists once in the tree.
|
||||
_preview_camera.look_at_from_position(
|
||||
Vector3(0, 1.0, 3.2), Vector3(0, 0.9, 0), Vector3.UP)
|
||||
Vector3(0, 1.05, 3.5), Vector3(0, 0.98, 0), Vector3.UP)
|
||||
_preview_camera.current = true
|
||||
_preview_viewport.add_child(_preview_camera)
|
||||
|
||||
@@ -201,12 +227,12 @@ func _build_ui() -> void:
|
||||
var key := DirectionalLight3D.new()
|
||||
key.name = "PreviewKeyLight"
|
||||
key.rotation_degrees = Vector3(-35, 35, 0)
|
||||
key.light_energy = 1.2
|
||||
key.light_energy = 1.0
|
||||
_preview_viewport.add_child(key)
|
||||
var fill := DirectionalLight3D.new()
|
||||
fill.name = "PreviewFillLight"
|
||||
fill.rotation_degrees = Vector3(-20, -140, 0)
|
||||
fill.light_energy = 0.4
|
||||
fill.light_energy = 0.35
|
||||
_preview_viewport.add_child(fill)
|
||||
|
||||
# --- Right: controls ---
|
||||
@@ -403,6 +429,31 @@ func _on_equip_pressed(asset_id: String) -> void:
|
||||
loadout_changed.emit(loadout)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Turntable (drag to rotate the preview, slow auto-spin when idle)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if _preview_root == null:
|
||||
return
|
||||
if _turntable_dragging:
|
||||
_turntable_idle = 0.0
|
||||
else:
|
||||
_turntable_idle += delta
|
||||
if _turntable_idle > _TURNTABLE_IDLE_SPIN_DELAY:
|
||||
_turntable_yaw += delta * _TURNTABLE_IDLE_SPIN_SPEED
|
||||
_preview_root.rotation.y = _turntable_yaw
|
||||
|
||||
|
||||
func _on_preview_gui_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
_turntable_dragging = event.pressed
|
||||
_turntable_idle = 0.0
|
||||
elif event is InputEventMouseMotion and _turntable_dragging:
|
||||
_turntable_yaw -= event.relative.x * _TURNTABLE_DRAG_SPEED
|
||||
_turntable_idle = 0.0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Preview refresh
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user