extends Node const BACKDROP_SCENE := preload("res://addons/nebula_backdrop/backdrop.tscn") var backdrop: NebulaBackdrop var panel: PanelContainer var status: Label var camera_slider: HSlider var mode_picker: OptionButton var speed_slider: HSlider var brightness_slider: HSlider var pause_toggle: CheckBox var reduced_motion_toggle: CheckBox var capture_path := "" func _ready() -> void: var background := CanvasLayer.new() background.layer = -10 add_child(background) backdrop = BACKDROP_SCENE.instantiate() as NebulaBackdrop background.add_child(backdrop) backdrop.set_preset_resource(NebulaPlaceholderLayers.create_preset()) _build_controls() for argument in OS.get_cmdline_user_args(): if argument.begins_with("--capture="): capture_path = argument.trim_prefix("--capture=") if not capture_path.is_empty(): pause_toggle.button_pressed = true await get_tree().process_frame await RenderingServer.frame_post_draw var output := get_viewport().get_texture().get_image() var result := output.save_png(capture_path) print("DEMO_CAPTURE result=", result, " size=", output.get_size()) get_tree().quit(0 if result == OK else 1) func _process(_delta: float) -> void: if status != null: var mode := "autoscroll" if backdrop.motion_mode == NebulaBackdrop.MotionMode.AUTOSCROLL else "camera input" var hold := " / held" if backdrop.paused or backdrop.reduced_motion else "" status.text = "%s%s\nLayer offsets: %.1f / %.1f / %.1f px" % [mode, hold, backdrop.layer_phase(0), backdrop.layer_phase(1), backdrop.layer_phase(2)] func _label(value: String, size: int = 15) -> Label: var label := Label.new() label.text = value label.add_theme_font_size_override("font_size", size) label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART return label func _slider(parent: VBoxContainer, title: String, low: float, high: float, value: float, step: float, callback: Callable) -> HSlider: var label := _label(title + ": " + String.num(value, 2)) parent.add_child(label) var slider := HSlider.new() slider.min_value = low slider.max_value = high slider.step = step slider.value = value slider.value_changed.connect(func(number: float) -> void: label.text = title + ": " + String.num(number, 2) callback.call(number) ) parent.add_child(slider) return slider func _build_controls() -> void: var layer := CanvasLayer.new() add_child(layer) panel = PanelContainer.new() panel.position = Vector2(24, 24) panel.custom_minimum_size = Vector2(342, 0) var style := StyleBoxFlat.new() style.bg_color = Color(0.02, 0.025, 0.035, 0.96) style.border_color = Color(0.21, 0.26, 0.31) style.set_border_width_all(1) style.content_margin_left = 20 style.content_margin_right = 20 style.content_margin_top = 18 style.content_margin_bottom = 18 panel.add_theme_stylebox_override("panel", style) layer.add_child(panel) var column := VBoxContainer.new() column.add_theme_constant_override("separation", 12) panel.add_child(column) column.add_child(_label("NEBULA / FREE CONTROLLER", 12)) column.add_child(_label("Horizontal parallax", 24)) column.add_child(_label("Three original placeholder layers:\ngradient / checker grid / stars.")) var mode := OptionButton.new() mode_picker = mode mode.add_item("Autoscroll") mode.add_item("Camera input") mode.item_selected.connect(func(index: int) -> void: backdrop.motion_mode = index as NebulaBackdrop.MotionMode camera_slider.editable = index == 1 if index == 1: backdrop.set_camera_position(Vector2(camera_slider.value, 0.0)) ) column.add_child(mode) speed_slider = _slider(column, "Speed / source px per second", -240, 240, backdrop.speed, 1, func(value: float) -> void: backdrop.speed = value) camera_slider = _slider(column, "Camera X / source pixels", -1024, 1024, 0, 1, func(value: float) -> void: backdrop.set_camera_position(Vector2(value, 0.0))) camera_slider.editable = false brightness_slider = _slider(column, "Brightness", 0, 1, backdrop.brightness, 0.05, func(value: float) -> void: backdrop.brightness = value) var pause := CheckBox.new() pause_toggle = pause pause.text = "Pause current frame" pause.toggled.connect(func(value: bool) -> void: backdrop.paused = value) column.add_child(pause) var reduced := CheckBox.new() reduced_motion_toggle = reduced reduced.text = "Reduced motion / hold frame" reduced.toggled.connect(func(value: bool) -> void: backdrop.reduced_motion = value) column.add_child(reduced) status = _label("", 13) column.add_child(status) column.add_child(_label("Replace placeholders with your own licensed layers. This demo includes no Nebula pack artwork.\nMIT code · Godot 4.5 Compatibility · AI-assisted code", 12))