extends CanvasLayer ## "TAP TO PLAY" gate for the web/touch build (autoload `WebStartGate`). Browsers only allow ## requestFullscreen / screen.orientation.lock from inside a user gesture, so instead of ## piggybacking the first *gameplay* tap (which used to race the resize/orientation flip and ## strand the joystick — a dropped touchend left the stick stuck), we capture a dedicated tap ## on a full-screen panel BEFORE play. The tap drives Controls.request_fullscreen_landscape() ## and the gate dismisses, so all the fullscreen/orientation churn happens with no in-flight ## touch to lose. ## ## Re-armable, so the player is never stuck windowed: whenever we're NOT fullscreen (boot, or ## after they left via Esc / Android back / edge swipe) the gate re-appears and its tap goes ## back in — fullscreen otherwise persists across menu ↔ game, so tapping Play never lands you ## windowed. Where fullscreen is impossible (iPhone Safari has no requestFullscreen) it shows ## once and then stays out of the way instead of nagging. Only active on web touch; no-op else. const _POLL: float = 0.4 # seconds between browser fullscreen-state polls var _panel: ColorRect var _active: bool = false # web + touch: the only case this gate does anything var _fs_supported: bool = true var _dismissed_unsupported: bool = false # iPhone: tapped once, don't show again var _poll_accum: float = 0.0 func _ready() -> void: layer = 127 # above HUD/touch UI, just below OrientationGuard (128) so "rotate" wins in portrait process_mode = Node.PROCESS_MODE_ALWAYS _active = OS.has_feature("web") and Controls.use_touch_ui() if not _active: return _fs_supported = Controls.fullscreen_supported() _panel = ColorRect.new() _panel.color = Color(0.05, 0.03, 0.02, 0.94) _panel.set_anchors_preset(Control.PRESET_FULL_RECT) _panel.mouse_filter = Control.MOUSE_FILTER_STOP # swallow the tap from the game behind it add_child(_panel) var center := CenterContainer.new() center.set_anchors_preset(Control.PRESET_FULL_RECT) center.mouse_filter = Control.MOUSE_FILTER_IGNORE _panel.add_child(center) var box := VBoxContainer.new() box.alignment = BoxContainer.ALIGNMENT_CENTER box.add_theme_constant_override("separation", 16) center.add_child(box) var title := Label.new() title.text = "TAP TO PLAY" title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER title.add_theme_font_size_override("font_size", 52) title.add_theme_color_override("font_color", Color(1.0, 0.78, 0.22)) box.add_child(title) var sub := Label.new() sub.text = "Fullscreen, landscape" sub.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER sub.add_theme_font_size_override("font_size", 22) sub.add_theme_color_override("font_color", Color(0.85, 0.85, 0.85)) box.add_child(sub) func _input(event: InputEvent) -> void: if not _active or not _panel.visible: return var gesture := (event is InputEventScreenTouch and (event as InputEventScreenTouch).pressed) \ or (event is InputEventMouseButton and (event as InputEventMouseButton).pressed) if gesture: # The native DOM listener (controls_manager.gd) enters fullscreen synchronously on this # same tap; this is the belt-and-suspenders explicit call. Controls.request_fullscreen_landscape() _panel.visible = false if not _fs_supported: _dismissed_unsupported = true # iPhone: don't reappear, there's nothing to enter get_viewport().set_input_as_handled() func _process(delta: float) -> void: if not _active: return _poll_accum += delta if _poll_accum < _POLL: return _poll_accum = 0.0 # Portrait: OrientationGuard owns the screen — stand down until they turn the device. if _is_portrait(): _panel.visible = false return if not _fs_supported: # Can't ever enter fullscreen (iPhone) — offer the tap once, then get out of the way. _panel.visible = not _dismissed_unsupported return # Show the gate whenever we're not fullscreen, and arm so the next tap (re-)enters. var fs := Controls.is_browser_fullscreen() _panel.visible = not fs if not fs: Controls.arm_fullscreen() func _is_portrait() -> bool: var s := get_viewport().get_visible_rect().size return s.y > s.x