[node name="RichTextLabel" type="RichTextLabel" parent="MainPanel/HSplitContainer/BasicControls/VBoxContainer/TabContainer/Tab 4/MarginContainer"]
layout_mode = 2
focus_mode = 2
bbcode_enabled = true
text = "[center]RichTextLabel: [color=#f88]Tab 4[/color] is selected.[/center]"
context_menu_enabled = true
selection_enabled = true

[node name="Tab 5" type="Control" parent="MainPanel/HSplitContainer/BasicControls/VBoxContainer/TabContainer"]
visible = false
layout_mode = 2

[node name="MarginContainer" type="MarginContainer" parent="MainPanel/HSplitContainer/BasicControls/VBoxContainer/TabContainer/Tab 5"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 30
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 30

[node name="RichTextLabel" type="RichTextLabel" parent="MainPanel/HSplitContainer/BasicControls/VBoxContainer/TabContainer/Tab 5/MarginContainer"]
layout_mode = 2
focus_mode = 2
bbcode_enabled = true
text = "[center]RichTextLabel: [color=#f8f]Tab 5[/color] is selected.[/center]"
context_menu_enabled = true
selection_enabled = true

[node name="Tab 6" type="Control" parent="MainPanel/HSplitContainer/BasicControls/VBoxContainer/TabContainer"]
visible = false
layout_mode = 2

[node name="MarginContainer" type="MarginContainer" parent="MainPanel/HSplitContainer/BasicControls/VBoxContainer/TabContainer/Tab 6"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 30
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 30

[node name="RichTextLabel" type="RichTextLabel" parent="MainPanel/HSplitContainer/BasicControls/VBoxContainer/TabContainer/Tab 6/MarginContainer"]
layout_mode = 2
focus_mode = 2
bbcode_enabled = true
text = "[center]RichTextLabel: [color=#b8f]Tab 6[/color] is selected.[/center]"
context_menu_enabled = true
selection_enabled = true

[node name="Tab 7" type="Control" parent="MainPanel/HSplitContainer/BasicControls/VBoxContainer/TabContainer"]
visible = false
layout_mode = 2

[node name="MarginContainer" type="MarginContainer" parent="MainPanel/HSplitContainer/BasicControls/VBoxContainer/TabContainer/Tab 7"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 30
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 30

[node name="RichTextLabel" type="RichTextLabel" parent="MainPanel/HSplitContainer/BasicControls/VBoxContainer/TabContainer/Tab 7/MarginContainer"]
layout_mode = 2
focus_mode = 2
bbcode_enabled = true
text = "[center]RichTextLabel: [color=#fb6]Tab 7[/color] is selected.[/center]"
context_menu_enabled = true
selection_enabled = true

[node name="VSplitContainer" type="VSplitContainer" parent="MainPanel/HSplitContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
theme_override_constants/separation = 0
theme_override_constants/minimum_grab_thickness = 24

[node name="Numbers" type="PanelContainer" parent="MainPanel/HSplitContainer/VSplitContainer"]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_bl4wp")

[node name="VBoxContainer" type="VBoxContainer" parent="MainPanel/HSplitContainer/VSplitContainer/Numbers"]
layout_mode = 2

[node name="Title" type="Label" parent="MainPanel/HSplitContainer/VSplitContainer/Numbers/VBoxContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 24
text = "Numbers"
horizontal_alignment = 1

[node name="SpinBox" type="SpinBox" parent="MainPanel/HSplitContainer/VSplitContainer/Numbers/VBoxContainer"]
custom_minimum_size = Vector2(255, 0)
layout_mode = 2
size_flags_horizontal = 0
prefix = "SpinBox"

[node name="HSliderContainer" type="HBoxContainer" parent="MainPanel/HSplitContainer/VSplitContainer/Numbers/VBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 10

[node name="HSlider" type="HSlider" parent="MainPanel/HSplitContainer/VSplitContainer/Numbers/VBoxContainer/HSliderContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 0.5
value = 50.0

[node name="Label" type="Label" parent="MainPanel/HSplitContainer/VSplitContainer/Numbers/VBoxContainer/HSliderContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "HSlider"

[node name="ProgressBarContainer" type="HBoxContainer" parent="MainPanel/HSplitContainer/VSplitContainer/Numbers/VBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 10

[node name="ProgressBar" type="ProgressBar" parent="MainPanel/HSplitContainer/VSplitContainer/Numbers/VBoxContainer/ProgressBarContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 0.5
value = 50.0

[node name="Label" type="Label" parent="MainPanel/HSplitContainer/VSplitContainer/Numbers/VBoxContainer/ProgressBarContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "ProgressBar"

[node name="HSeparatorContainer" type="HBoxContainer" parent="MainPanel/HSplitContainer/VSplitContainer/Numbers/VBoxContainer"]
layout_mode = 2

[node name="HSeparatorLeft" type="HSeparator" parent="MainPanel/HSplitContainer/VSplitContainer/Numbers/VBoxContainer/HSeparatorContainer"]
layout_mode = 2
size_flags_horizontal = 3

[node name="Label" type="Label" parent="MainPanel/HSplitContainer/VSplitContainer/Numbers/VBoxContainer/HSeparatorContainer"]
layout_mode = 2
text = "HSeparator"

[node name="HSeparatorRight" type="HSeparator" parent="MainPanel/HSplitContainer/VSplitContainer/Numbers/VBoxContainer/HSeparatorContainer"]
layout_mode = 2
size_flags_horizontal = 3

[node name="TextureProgressContainer" type="HBoxContainer" parent="MainPanel/HSplitContainer/VSplitContainer/Numbers/VBoxContainer"]
layout_mode = 2

[node name="Control" type="Control" parent="MainPanel/HSplitContainer/VSplitContainer/Numbers/VBoxContainer/TextureProgressContainer"]
custom_minimum_size = Vector2(64, 64)
layout_mode = 2

[node name="TextureProgressBar" type="TextureProgressBar" parent="MainPanel/HSplitContainer/VSplitContainer/Numbers/VBoxContainer/TextureProgressContainer/Control"]
layout_mode = 0
offset_right = 128.0
offset_bottom = 128.0
scale = Vector2(0.5, 0.5)
value = 67.0
fill_mode = 4
texture_progress = ExtResource("1_8tycj")

[node name="Label" type="Label" parent="MainPanel/HSplitContainer/VSplitContainer/Numbers/VBoxContainer/TextureProgressContainer"]
layout_mode = 2
text = "TextureProgressBar"

[node name="Lists" type="PanelContainer" parent="MainPanel/HSplitContainer/VSplitContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
size_flags_stretch_ratio = 2.5
theme_override_styles/panel = SubResource("StyleBoxFlat_bl4wp")

[node name="VBoxContainer" type="VBoxContainer" parent="MainPanel/HSplitContainer/VSplitContainer/Lists"]
layout_mode = 2

[node name="Title" type="Label" parent="MainPanel/HSplitContainer/VSplitContainer/Lists/VBoxContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 24
text = "Lists"
horizontal_alignment = 1

[node name="HBoxContainer" type="HBoxContainer" parent="MainPanel/HSplitContainer/VSplitContainer/Lists/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
theme_override_constants/separation = 10

[node name="VBoxContainer" type="VBoxContainer" parent="MainPanel/HSplitContainer/VSplitContainer/Lists/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3


extends ColorPickerButton

# Returns the data to pass from an object when you click and drag away from
# this object. Also calls `set_drag_preview()` to show the mouse dragging
# something so the user knows that the operation is working.
func _get_drag_data(_at_position: Vector2) -> Color:
	# Use another colorpicker as drag preview.
	var cpb := ColorPickerButton.new()
	cpb.color = color
	cpb.size = Vector2(80.0, 50.0)

	# Allows us to center the color picker on the mouse.
	var preview := Control.new()
	preview.add_child(cpb)
	cpb.position = -0.5 * cpb.size

	# Sets what the user will see they are dragging.
	set_drag_preview(preview)

	# Return color as drag data.
	return color

# Returns a boolean by examining the data being dragged to see if it's valid
# to drop here.
func _can_drop_data(_at_position: Vector2, data: Variant) -> bool:
	return typeof(data) == TYPE_COLOR

# Takes the data being dragged and processes it. In this case, we are
# assigning a new color to the target color picker button.
func _drop_data(_at_position: Vector2, data: Variant) -> void:
	color = data


# The root Control node ("Main") and AspectRatioContainer nodes are the most important
# pieces of this demo.
# Both nodes have their Layout set to Full Rect
# (with their rect spread across the whole viewport, and Anchor set to Full Rect).
extends Control

var base_window_size := Vector2(
		ProjectSettings.get_setting("display/window/size/viewport_width"),
		ProjectSettings.get_setting("display/window/size/viewport_height")
)

# These defaults match this demo's project settings. Adjust as needed if adapting this
# in your own project.
var stretch_mode := Window.CONTENT_SCALE_MODE_CANVAS_ITEMS
var stretch_aspect := Window.CONTENT_SCALE_ASPECT_EXPAND

var scale_factor := 1.0
var gui_aspect_ratio := -1.0
var gui_margin := 0.0

@onready var panel: Panel = $Panel
@onready var arc: AspectRatioContainer = $Panel/AspectRatioContainer

func _ready() -> void:
	# The `resized` signal will be emitted when the window size changes, as the root Control node
	# is resized whenever the window size changes. This is because the root Control node
	# uses a Full Rect anchor, so its size will always be equal to the window size.
	resized.connect(_on_resized)
	update_container.call_deferred()

func update_container() -> void:
	# The code within this function needs to be run deferred to work around an issue with containers
	# having a 1-frame delay with updates.
	# Otherwise, `panel.size` returns a value of the previous frame, which results in incorrect
	# sizing of the inner AspectRatioContainer when using the Fit to Window setting.
	for _i in 2:
		if is_equal_approx(gui_aspect_ratio, -1.0):
			# Fit to Window. Tell the AspectRatioContainer to use the same aspect ratio as the window,
			# making the AspectRatioContainer not have any visible effect.
			arc.ratio = panel.size.aspect()
			# Apply GUI offset on the AspectRatioContainer's parent (Panel).
			# This also makes the GUI offset apply on controls located outside the AspectRatioContainer
			# (such as the inner side label in this demo).
			panel.offset_top = gui_margin
			panel.offset_bottom = -gui_margin
		else:
			# Constrained aspect ratio.
			arc.ratio = min(panel.size.aspect(), gui_aspect_ratio)
			# Adjust top and bottom offsets relative to the aspect ratio when it's constrained.
			# This ensures that GUI offset settings behave exactly as if the window had the
			# original aspect ratio size.
			panel.offset_top = gui_margin / gui_aspect_ratio
			panel.offset_bottom = -gui_margin / gui_aspect_ratio

		panel.offset_left = gui_margin
		panel.offset_right = -gui_margin

func _on_gui_aspect_ratio_item_selected(index: int) -> void:
	match index:
		0:  # Fit to Window
			gui_aspect_ratio = -1.0
		1:  # 5:4
			gui_aspect_ratio = 5.0 / 4.0
		2:  # 4:3
			gui_aspect_ratio = 4.0 / 3.0
		3:  # 3:2
			gui_aspect_ratio = 3.0 / 2.0
		4:  # 16:10
			gui_aspect_ratio = 16.0 / 10.0
		5:  # 16:9
			gui_aspect_ratio = 16.0 / 9.0
		6:  # 21:9
			gui_aspect_ratio = 21.0 / 9.0

	update_container.call_deferred()

func _on_resized() -> void:
	update_container.call_deferred()

func _on_gui_margin_drag_ended(_value_changed: bool) -> void:
	gui_margin = $"Panel/AspectRatioContainer/Panel/CenterContainer/Options/GUIMargin/HSlider".value
	$"Panel/AspectRatioContainer/Panel/CenterContainer/Options/GUIMargin/Value".text = str(gui_margin)
	update_container.call_deferred()

func _on_window_base_size_item_selected(index: int) -> void:
	match index:
		0:  # 648×648 (1:1)
			base_window_size = Vector2(648, 648)
		1:  # 640×480 (4:3)
			base_window_size = Vector2(640, 480)
		2:  # 720×480 (3:2)
			base_window_size = Vector2(720, 480)
		3:  # 800×600 (4:3)
			base_window_size = Vector2(800, 600)
		4:  # 1152×648 (16:9)
			base_window_size = Vector2(1152, 648)
		5:  # 1280×720 (16:9)
			base_window_size = Vector2(1280, 720)
		6:  # 1280×800 (16:10)
			base_window_size = Vector2(1280, 800)
		7:  # 1680×720 (21:9)
			base_window_size = Vector2(1680, 720)

	get_viewport().content_scale_size = base_window_size
	update_container.call_deferred()

func _on_window_stretch_mode_item_selected(index: int) -> void:
	stretch_mode = index as Window.ContentScaleMode
	get_viewport().content_scale_mode = stretch_mode

	# Disable irrelevant options when the stretch mode is Disabled.
	$"Panel/AspectRatioContainer/Panel/CenterContainer/Options/WindowBaseSize/OptionButton".disabled = stretch_mode == Window.CONTENT_SCALE_MODE_DISABLED
	$"Panel/AspectRatioContainer/Panel/CenterContainer/Options/WindowStretchAspect/OptionButton".disabled = stretch_mode == Window.CONTENT_SCALE_MODE_DISABLED

func _on_window_stretch_aspect_item_selected(index: int) -> void:
	stretch_aspect = index as Window.ContentScaleAspect
	get_viewport().content_scale_aspect = stretch_aspect

func _on_window_scale_factor_drag_ended(_value_changed: bool) -> void:
	scale_factor = $"Panel/AspectRatioContainer/Panel/CenterContainer/Options/WindowScaleFactor/HSlider".value
	$"Panel/AspectRatioContainer/Panel/CenterContainer/Options/WindowScaleFactor/Value".text = "%d%%" % (scale_factor * 100)
	get_viewport().content_scale_factor = scale_factor

func _on_window_stretch_scale_mode_item_selected(index: int) -> void:
	get_viewport().content_scale_stretch = index


# Multiple Resolutions and Aspect Ratios

**Note:** This demo is intended to showcase what Godot can do in terms of
supporting multiple resolutions and aspect ratios. As such, this demo very
full-featured but it's also fairly complex to understand.

If you're in a hurry and want to implement *decent* support for multiple
resolutions and aspect ratios in your game, see [Multiple resolutions crash
course](#multiple-resolutions-crash-course).

___

This project demonstrates how to set up a project to handle screens of multiple
resolutions and aspect ratios.

This demo allows you to adjust the window's base resolution, stretch mode,
stretch aspect, and scale factor (internally known as "stretch shrink"). This
lets you see what happens when adjusting those properties. Make sure to resize
the project window in any direction to see the difference with the various
stretch mode and stretch aspect settings.

The GUI can be made to fit the window or constrained to a specific aspect ratio
from a list of common aspect ratios. On ultrawide aspect ratios, this can be
used to prevent HUD elements from being too spread apart, which can harm the
gameplay experience. For non-essential HUD elements, specific controls can be
made to ignore this aspect ratio constraint when it makes sense (e.g. a list of
players on the side of the screen).

Additionally, a GUI margin setting is provided to better handle TVs with an
overscan area to prevent GUI elements from being cut off. This can also improve
the gameplay experience on large monitors by bringing HUD elements closer to the
center of the screen.

A DynamicFont with multichannel signed distance field (MSDF) rendering is also used.
This allows for crisp font rendering at any resolution, without having to re-rasterize
the font when the font size changes. This makes changing the various settings in this
demo faster, especially when large font sizes are used as a result of the GUI scale factor
setting being increased.

Note that by default, Godot uses font oversampling for traditional rasterized
DynamicFonts. This means MSDF fonts are *not* required to have crisp fonts at
higher-than-default screen resolutions.

Language: GDScript

Renderer: Compatibility

Check out this demo on the asset library: https://godotengine.org/asset-library/asset/2771

## Technical notes

The demo works with the following project settings:

- `canvas_items` stretch mode (this was called `2d` in Godot 3.x).
  Recommended for most non-pixel art games.
- `expand` stretch aspect (allows support for multiple aspect ratios without
  distortion or black bars).
- Using a base window size with a 1:1 aspect ratio (`648×648` in this demo).
  This prevents GUI elements from automatically shrinking, even in portrait
  mode.
  - With this setting, to prevent the GUI from breaking at narrow aspect ratios,
    the GUI must be designed to work with a 1:1 aspect ratio. This is not
    feasible in most complex games, so a base window size with a wider aspect
    ratio (such as 4:3 or 16:10) can be used instead. The wider the aspect
    ratio, the easier design becomes, but the GUI will automatically become
    smaller at narrow aspect ratios unless the user overrides its scale with the
    stretch shrink setting. Many devices such as the Steam Deck and MacBooks
    feature 16:10 displays, so it's recommended to use a 16:10 resolution or
    narrower as a base window size to ensure a good gameplay experience out of
    the box on those devices.
- Using a window size override with a 16:9 aspect ratio (`1152×648` in this demo).
  This way, the project starts in a 16:9 window even if the base window size has
  a 1:1 aspect ratio.
  - The test window height matches the width and height of the base window size,
    so GUI elements are still at the same size.

## Multiple resolutions crash course

**Not everything in this demo is critical to all games.** For gamejam projects or mobile games, most of this can be skipped.
See the [Common use case scenarios](https://docs.godotengine.org/en/stable/tutorials/rendering/multiple_resolutions.html#common-use-case-scenarios)
section in the Multiple resolutions documentation.

With the simpler setup described in the above documentation, there are a few
limitations compared to this demo:

- The HUD will shrink when the aspect ratio becomes narrower than the base
  window size. As such, it's recommended to use a base window size with a 16:10
  aspect ratio to prevent the HUD from shrinking on Steam Deck and MacBooks.
- Players will not be able to define a margin, which can be problematic when
  playing on a TV (as overscan can obstruct some HUD elements). This can be
  worked around by ensuring the entire HUD always has a small margin around it.
  This can be done by increasing the Margin properties on all sides on the root
  Control node by 10-30 pixels or so.

If you're releasing a full-fledged game on a desktop platform such as Steam,
consider implementing full support as this demo suggests. Your players will
thank you :slightly_smiling_face:

## Screenshots

![Screenshot](screenshots/multiple_resolutions.png)


; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
;   [section] ; section goes between []
;   param=value ; assign values to parameters

config_version=5

[application]

config/name="Multiple Resolutions and Aspect Ratios"
config/description="This project demonstrates how to set up a project to handle screens of
multiple resolutions and aspect ratios.

The GUI can be made to fit the window or constrained to a specific
aspect ratio from a list of common aspect ratios. On ultrawide aspect ratios,
this can be used to prevent HUD elements from being too spread apart,
which can harm the gameplay experience.

For non-essential HUD elements, specific controls can be made to
ignore this aspect ratio constraint when it makes sense
(e.g. a list of players on the side of the screen).

Additionally, a GUI margin setting is provided to better handle TVs
with an overscan area to prevent GUI elements from being cut off.
This can also improve the gameplay experience on large monitors
by bringing HUD elements closer to the center of the screen."
config/tags=PackedStringArray("accessibility", "best_practices", "demo", "gui", "official")
run/main_scene="res://main.tscn"
config/features=PackedStringArray("4.2")
run/low_processor_mode=true
config/icon="res://icon.webp"

[debug]

gdscript/warnings/untyped_declaration=1

[display]

window/size/viewport_width=648
window/size/window_width_override=1152
window/size/window_height_override=648
window/stretch/mode="canvas_items"
window/stretch/aspect="expand"
window/handheld/orientation="sensor"

[gui]

theme/default_font_multichannel_signed_distance_field=true

[rendering]

renderer/rendering_method="gl_compatibility"
renderer/rendering_method.mobile="gl_compatibility"
environment/defaults/default_clear_color=Color(0.133333, 0.133333, 0.2, 1)


[gd_scene load_steps=4 format=2]

[ext_resource path="res://ball.png" type="Texture2D" id=1]
[ext_resource path="res://logic/Ball.cs" type="Script" id=2]

[sub_resource type="CircleShape2D" id=1]
radius = 4.65663

[node name="Ball" type="Area2D"]
script = ExtResource( 2 )

[node name="Sprite2D" type="Sprite2D" parent="."]
texture = ExtResource( 1 )

[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource( 1 )


[gd_scene load_steps=12 format=3 uid="uid://bufr8nynnqrj"]

[ext_resource type="Script" path="res://Logic/Paddle.cs" id="1"]
[ext_resource type="Texture2D" uid="uid://cgbjbhglgxf0k" path="res://paddle.png" id="2"]
[ext_resource type="Script" path="res://Logic/Ball.cs" id="4"]
[ext_resource type="Texture2D" uid="uid://13ht46tyr4ae" path="res://ball.png" id="5"]
[ext_resource type="Texture2D" uid="uid://cim6es185kfto" path="res://separator.png" id="6"]
[ext_resource type="Script" path="res://Logic/Wall.cs" id="7"]
[ext_resource type="Script" path="res://Logic/CeilingFloor.cs" id="8"]

[sub_resource type="RectangleShape2D" id="1"]
size = Vector2(4, 16)

[sub_resource type="RectangleShape2D" id="2"]
size = Vector2(4, 4)

[sub_resource type="RectangleShape2D" id="3"]
size = Vector2(10, 200)

[sub_resource type="RectangleShape2D" id="4"]
size = Vector2(320, 10)

[node name="Pong" type="Node2D"]

[node name="Background" type="ColorRect" parent="."]
offset_right = 640.0
offset_bottom = 400.0
color = Color(0.141176, 0.152941, 0.164706, 1)

[node name="Left" type="Area2D" parent="."]
modulate = Color(0, 1, 1, 1)
position = Vector2(67.6285, 192.594)
script = ExtResource("1")

[node name="Sprite2D" type="Sprite2D" parent="Left"]
texture = ExtResource("2")

[node name="Collision" type="CollisionShape2D" parent="Left"]
shape = SubResource("1")

[node name="Right" type="Area2D" parent="."]
modulate = Color(1, 0, 1, 1)
position = Vector2(563.815, 188.919)
script = ExtResource("1")

[node name="Sprite2D" type="Sprite2D" parent="Right"]
texture = ExtResource("2")

[node name="Collision" type="CollisionShape2D" parent="Right"]
shape = SubResource("1")

[node name="Ball" type="Area2D" parent="."]
position = Vector2(320.5, 191.124)
script = ExtResource("4")

[node name="Sprite2D" type="Sprite2D" parent="Ball"]
texture = ExtResource("5")

[node name="Collision" type="CollisionShape2D" parent="Ball"]
shape = SubResource("2")

[node name="Separator" type="Sprite2D" parent="."]
position = Vector2(320, 200)
texture = ExtResource("6")

[node name="Node2D" type="Node2D" parent="."]

[node name="LeftWall" type="Area2D" parent="."]
position = Vector2(-10, 200)
script = ExtResource("7")

[node name="Collision" type="CollisionShape2D" parent="LeftWall"]
shape = SubResource("3")

[node name="RightWall" type="Area2D" parent="."]
position = Vector2(650, 200)
script = ExtResource("7")

[node name="Collision" type="CollisionShape2D" parent="RightWall"]
shape = SubResource("3")

[node name="Ceiling" type="Area2D" parent="."]
position = Vector2(320, -10)
script = ExtResource("8")

[node name="Collision" type="CollisionShape2D" parent="Ceiling"]
shape = SubResource("4")

[node name="Floor" type="Area2D" parent="."]
position = Vector2(320, 410)
script = ExtResource("8")
_bounceDirection = -1

[node name="Collision" type="CollisionShape2D" parent="Floor"]
shape = SubResource("4")

[connection signal="area_entered" from="Left" to="Left" method="OnAreaEntered"]
[connection signal="area_entered" from="Right" to="Right" method="OnAreaEntered"]
[connection signal="area_entered" from="LeftWall" to="LeftWall" method="OnWallAreaEntered"]
[connection signal="area_entered" from="RightWall" to="RightWall" method="OnWallAreaEntered"]
[connection signal="area_entered" from="Ceiling" to="Ceiling" method="OnAreaEntered"]
[connection signal="area_entered" from="Floor" to="Floor" method="OnAreaEntered"]


@tool
extends Control

var zoom_level := 0
var is_panning = false
var pan_center: Vector2
var viewport_center: Vector2
var view_mode_index := 0

var editor_interface: EditorInterface # Set in node25d_plugin.gd
var moving = false

@onready var viewport_2d = $Viewport2D
@onready var viewport_overlay = $ViewportOverlay
@onready var view_mode_button_group: ButtonGroup = $"../TopBar/ViewModeButtons/45Degree".group
@onready var zoom_label: Label = $"../TopBar/Zoom/ZoomPercent"
@onready var gizmo_25d_scene = preload("res://addons/node25d-cs/main_screen/gizmo_25d.tscn")

func _ready():
	# Give Godot a chance to fully load the scene. Should take two frames.
	await get_tree().process_frame
	await get_tree().process_frame
	var edited_scene_root = get_tree().edited_scene_root
	if not edited_scene_root:
		# Godot hasn't finished loading yet, so try loading the plugin again.
		editor_interface.set_plugin_enabled("node25d", false)
		editor_interface.set_plugin_enabled("node25d", true)
		return
	# Alright, we're loaded up. Now check if we have a valid world and assign it.
	var world_2d = edited_scene_root.get_viewport().world_2d
	if world_2d == get_viewport().world_2d:
		return # This is the MainScreen25D scene opened in the editor!
	viewport_2d.world_2d = world_2d

func _process(delta):
	if not editor_interface: # Something's not right... bail!
		return

	# View mode polling.
	var view_mode_changed_this_frame = false
	var new_view_mode = view_mode_button_group.get_pressed_button().get_index()
	if view_mode_index != new_view_mode:
		view_mode_index = new_view_mode
		view_mode_changed_this_frame = true
		_recursive_change_view_mode(get_tree().edited_scene_root)

	# Zooming.
	if Input.is_mouse_button_pressed(MOUSE_BUTTON_WHEEL_UP):
		zoom_level += 1
	elif Input.is_mouse_button_pressed(MOUSE_BUTTON_WHEEL_DOWN):
		zoom_level -= 1
	var zoom = _get_zoom_amount()

	# SubViewport size.
	var size = get_global_rect().size
	viewport_2d.size = size

	# SubViewport transform.
	var viewport_trans = Transform2D.IDENTITY
	viewport_trans.x *= zoom
	viewport_trans.y *= zoom
	viewport_trans.origin = viewport_trans.basis_xform(viewport_center) + size / 2
	viewport_2d.canvas_transform = viewport_trans
	viewport_overlay.canvas_transform = viewport_trans

	# Delete unused gizmos.
	var selection = editor_interface.get_selection().get_selected_nodes()
	var overlay_children = viewport_overlay.get_children()
	for overlay_child in overlay_children:
		var contains = false
		for selected in selection:
			if selected == overlay_child.get("node25d") and not view_mode_changed_this_frame:
				contains = true
		if not contains:
			overlay_child.queue_free()

	# Add new gizmos.
	for selected in selection:
		if selected.has_method("Node25DReady"):
			var new = true
			for overlay_child in overlay_children:
				if selected == overlay_child.get("node25d"):
					new = false
			if new:
				var gizmo = gizmo_25d_scene.instantiate()
				viewport_overlay.add_child(gizmo)
				gizmo.set("node25d", selected)
				gizmo.call("Initialize")

# This only accepts input when the mouse is inside of the 2.5D viewport.
func _gui_input(event):
	if event is InputEventMouseButton:
		if event.is_pressed():
			if event.button_index == MOUSE_BUTTON_WHEEL_UP:
				zoom_level += 1
				accept_event()
			elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
				zoom_level -= 1
				accept_event()
			elif event.button_index == MOUSE_BUTTON_MIDDLE:
				is_panning = true
				pan_center = viewport_center - event.position
				accept_event()
			elif event.button_index == MOUSE_BUTTON_LEFT:
				var overlay_children = viewport_overlay.get_children()
				for overlay_child in overlay_children:
					overlay_child.set("wantsToMove", true)
				accept_event()
		elif event.button_index == MOUSE_BUTTON_MIDDLE:
			is_panning = false
			accept_event()
		elif event.button_index == MOUSE_BUTTON_LEFT:
			var overlay_children = viewport_overlay.get_children()
			for overlay_child in overlay_children:
				overlay_child.set("wantsToMove", false)
			accept_event()
	elif event is InputEventMouseMotion:
		if is_panning:
			viewport_center = pan_center + event.position
			accept_event()

func _recursive_change_view_mode(current_node):
	if current_node.has_method("set_view_mode"):
		current_node.set_view_mode(view_mode_index) # GDScript.
	if current_node.has_method("SetViewMode"):
		current_node.call("SetViewMode", view_mode_index) # C#.
	for child in current_node.get_children():
		_recursive_change_view_mode(child)

func _get_zoom_amount():
	var zoom_amount = pow(1.05476607648, zoom_level) # 13th root of 2.
	zoom_label.text = str(round(zoom_amount * 1000) / 10) + "%"
	return zoom_amount

func _on_ZoomOut_pressed():
	zoom_level -= 1

func _on_ZoomIn_pressed():
	zoom_level += 1

func _on_ZoomReset_pressed():
	zoom_level = 0


[gd_scene load_steps=7 format=2]

[ext_resource path="res://addons/node25d-cs/Node25D.cs" type="Script" id=1]
[ext_resource path="res://addons/node25d-cs/icons/node_25d_icon.png" type="Texture2D" id=2]
[ext_resource path="res://assets/player/PlayerMath25D.cs" type="Script" id=3]
[ext_resource path="res://assets/player/textures/stand.png" type="Texture2D" id=4]
[ext_resource path="res://assets/player/PlayerSprite.cs" type="Script" id=5]

[sub_resource type="BoxShape3D" id=1]
extents = Vector3(0.5, 1, 0.5)

[node name="Player25D" type="Node2D"]
position = Vector2(0, -226.274)
z_index = 100
script = ExtResource( 1 )
__meta__ = {
"_editor_icon": ExtResource( 2 )
}
spatialPosition = Vector3(0, 10, 0)

[node name="PlayerMath25D" type="CharacterBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 10, 0)
script = ExtResource( 3 )

[node name="CollisionShape3D" type="CollisionShape3D" parent="PlayerMath25D"]
shape = SubResource( 1 )
__meta__ = {
"_edit_lock_": true
}

[node name="PlayerSprite" type="Sprite2D" parent="."]
scale = Vector2(1, 0.75)
z_index = 1
texture = ExtResource( 4 )
vframes = 5
script = ExtResource( 5 )

[node name="PlayerCamera" type="Camera2D" parent="PlayerSprite"]
current = true


extends Game

func _ready() -> void:
	var player_2 := %Player2 as Player
	var viewport_1 := %Viewport1 as SubViewport
	var viewport_2 := %Viewport2 as SubViewport
	viewport_2.world_2d = viewport_1.world_2d
	player_2.camera.custom_viewport = viewport_2
	player_2.camera.make_current()


[gd_resource type="ShaderMaterial" load_steps=2 format=3 uid="uid://djwm5nol3d801"]

[sub_resource type="Shader" id="1"]
code = "// original wind shader from https://github.com/Maujoe/godot-simple-wind-shader-2d/tree/master/assets/maujoe.simple_wind_shader_2d
// original script modified by HungryProton so that the assets are moving differently : https://pastebin.com/VL3AfV8D
//
// speed - The speed of the wind movement.
// minStrength - The minimal strength of the wind movement.
// maxStrength - The maximal strength of the wind movement.
// strengthScale - Scalefactor for the wind strength.
// interval - The time between minimal and maximal strength changes.
// detail - The detail (number of waves) of the wind movement.
// distortion - The strength of geometry distortion.
// heightOffset - The height where the wind begins to move. By default 0.0.

shader_type canvas_item;
render_mode blend_mix;

// Wind settings.
uniform float speed = 1.0;
uniform float minStrength : hint_range(0.0, 1.0) = 0.05;
uniform float maxStrength : hint_range(0.0, 1.0) = 0.01;
uniform float strengthScale = 100.0;
uniform float interval = 3.5;
uniform float detail = 1.0;
uniform float distortion : hint_range(0.0, 1.0);
uniform float heightOffset : hint_range(0.0, 1.0);

// With the offset value, you can if you want different moves for each asset. Just put a random value (1, 2, 3) in the editor. Don't forget to mark the material as unique if you use this
uniform float offset = 0;

float getWind(vec2 vertex, vec2 uv, float time){
    float diff = pow(maxStrength - minStrength, 2.0);
    float strength = clamp(minStrength + diff + sin(time / interval) * diff, minStrength, maxStrength) * strengthScale;
    float wind = (sin(time) + cos(time * detail)) * strength * max(0.0, (1.0-uv.y) - heightOffset);

    return wind;
}

void vertex() {
    vec4 pos = MODEL_MATRIX * vec4(0.0, 0.0, 0.0, 1.0);
    float time = TIME * speed + offset;
    //float time = TIME * speed + pos.x * pos.y  ; not working when moving...
    VERTEX.x += getWind(VERTEX.xy, UV, time);
}"

[resource]
shader = SubResource("1")
shader_parameter/speed = 1.0
shader_parameter/minStrength = 0.05
shader_parameter/maxStrength = 0.01
shader_parameter/strengthScale = 100.0
shader_parameter/interval = 3.5
shader_parameter/detail = 1.0
shader_parameter/distortion = null
shader_parameter/heightOffset = null
shader_parameter/offset = 0.0


class_name Player
extends CharacterBody2D

signal coin_collected()

const WALK_SPEED = 300.0
const ACCELERATION_SPEED = WALK_SPEED * 6.0
const JUMP_VELOCITY = -725.0
## Maximum speed at which the player can fall.
const TERMINAL_VELOCITY = 700

## The player listens for input actions appended with this suffix.[br]
## Used to separate controls for multiple players in splitscreen.
@export var action_suffix := ""

var gravity: int = ProjectSettings.get("physics/2d/default_gravity")
@onready var platform_detector := $PlatformDetector as RayCast2D
@onready var animation_player := $AnimationPlayer as AnimationPlayer
@onready var shoot_timer := $ShootAnimation as Timer
@onready var sprite := $Sprite2D as Sprite2D
@onready var jump_sound := $Jump as AudioStreamPlayer2D
@onready var gun: Gun = sprite.get_node(^"Gun")
@onready var camera := $Camera as Camera2D
var _double_jump_charged := false

func _physics_process(delta: float) -> void:
	if is_on_floor():
		_double_jump_charged = true
	if Input.is_action_just_pressed("jump" + action_suffix):
		try_jump()
	elif Input.is_action_just_released("jump" + action_suffix) and velocity.y < 0.0:
		# The player let go of jump early, reduce vertical momentum.
		velocity.y *= 0.6
	# Fall.
	velocity.y = minf(TERMINAL_VELOCITY, velocity.y + gravity * delta)

	var direction := Input.get_axis("move_left" + action_suffix, "move_right" + action_suffix) * WALK_SPEED
	velocity.x = move_toward(velocity.x, direction, ACCELERATION_SPEED * delta)

	if not is_zero_approx(velocity.x):
		if velocity.x > 0.0:
			sprite.scale.x = 1.0
		else:
			sprite.scale.x = -1.0

	floor_stop_on_slope = not platform_detector.is_colliding()
	move_and_slide()

	var is_shooting := false
	if Input.is_action_just_pressed("shoot" + action_suffix):
		is_shooting = gun.shoot(sprite.scale.x)

	var animation := get_new_animation(is_shooting)
	if animation != animation_player.current_animation and shoot_timer.is_stopped():
		if is_shooting:
			shoot_timer.start()
		animation_player.play(animation)

func get_new_animation(is_shooting := false) -> String:
	var animation_new: String
	if is_on_floor():
		if absf(velocity.x) > 0.1:
			animation_new = "run"
		else:
			animation_new = "idle"
	else:
		if velocity.y > 0.0:
			animation_new = "falling"
		else:
			animation_new = "jumping"
	if is_shooting:
		animation_new += "_weapon"
	return animation_new

func try_jump() -> void:
	if is_on_floor():
		jump_sound.pitch_scale = 1.0
	elif _double_jump_charged:
		_double_jump_charged = false
		velocity.x *= 2.5
		jump_sound.pitch_scale = 1.5
	else:
		return
	velocity.y = JUMP_VELOCITY
	jump_sound.play()


extends Node

var tween: Tween
var sub_tween: Tween

@onready var icon: Sprite2D = %Icon
@onready var icon_start_position := icon.position

@onready var countdown_label: Label = %CountdownLabel
@onready var path: Path2D = $Path2D
@onready var progress: TextureProgressBar = %Progress

func _process(_delta: float) -> void:
	if not tween or not tween.is_running():
		return

	progress.value = tween.get_total_elapsed_time()

func start_animation() -> void:
	# Reset the icon to original state.
	reset()
	# Create the Tween. Also sets the initial animation speed.
	# All methods that modify Tween will return the Tween, so you can chain them.
	tween = create_tween().set_speed_scale(%SpeedSlider.value)

	# Sets the amount of loops. 1 loop = 1 animation cycle, so e.g. 2 loops will play animation twice.
	if %Infinite.button_pressed:
		tween.set_loops() # Called without arguments, the Tween will loop infinitely.
	else:
		tween.set_loops(%Loops.value)

	# Step 1

	if is_step_enabled("MoveTo", 1.0):
		# tween_*() methods return a Tweener object. Its methods can also be chained, but
		# it's stored in a variable here for readability (chained lines tend to be long).
		# Note the usage of ^"NodePath". A regular "String" is accepted too, but it's very slightly slower.
		var tweener := tween.tween_property(icon, ^"position", Vector2(400, 250), 1.0)
		tweener.set_ease(%Ease1.selected)
		tweener.set_trans(%Trans1.selected)

	# Step 2

	if is_step_enabled("ColorRed", 1.0):
		tween.tween_property(icon, ^"self_modulate", Color.RED, 1.0)

	# Step 3

	if is_step_enabled("MoveRight", 1.0):
		# as_relative() makes the value relative, so in this case it moves the icon
		# 200 pixels from the previous position.
		var tweener := tween.tween_property(icon, ^"position:x", 200.0, 1.0).as_relative()
		tweener.set_ease(%Ease3.selected)
		tweener.set_trans(%Trans3.selected)
	if is_step_enabled("Roll", 0.0):
		# parallel() makes the Tweener run in parallel to the previous one.
		var tweener := tween.parallel().tween_property(icon, ^"rotation", TAU, 1.0)
		tweener.set_ease(%Ease3.selected)
		tweener.set_trans(%Trans3.selected)

	# Step 4

	if is_step_enabled("MoveLeft", 1.0):
		tween.tween_property(icon, ^"position", Vector2.LEFT * 200, 1.0).as_relative()
	if is_step_enabled("Jump", 0.0):
		# Jump has 2 substeps, so to make it properly parallel, it can be done in a sub-Tween.
		# Here we are calling a lambda method that creates a sub-Tween.
		# Any number of Tweens can animate a single object in the same time.
		tween.parallel().tween_callback(func():
			# Note that transition is set on Tween, but ease is set on Tweener.
			# Values set on Tween will affect all Tweeners (as defaults) and values
			# on Tweeners can override them.
			sub_tween = create_tween().set_speed_scale(%SpeedSlider.value).set_trans(Tween.TRANS_SINE)
			sub_tween.tween_property(icon, ^"position:y", -150.0, 0.5).as_relative().set_ease(Tween.EASE_OUT)
			sub_tween.tween_property(icon, ^"position:y", 150.0, 0.5).as_relative().set_ease(Tween.EASE_IN)
		)

	# Step 5

	if is_step_enabled("Blink", 2.0):
		# Loops are handy when creating some animations.
		for i in 10:
			tween.tween_callback(icon.hide).set_delay(0.1)
			tween.tween_callback(icon.show).set_delay(0.1)

	# Step 6

	if is_step_enabled("Teleport", 0.5):
		# Tweening a value with 0 duration makes it change instantly.
		tween.tween_property(icon, ^"position", Vector2(325, 325), 0)
		tween.tween_interval(0.5)
		# Binds can be used for advanced callbacks.
		tween.tween_callback(icon.set_position.bind(Vector2(680, 215)))

	# Step 7

	if is_step_enabled("Curve", 3.5):
		# Method tweening is useful for animating values that can't be directly interpolated.
		# It can be used for remapping and some very advanced animations.
		# Here it's used for moving sprite along a path, using inline lambda function.
		var tweener := tween.tween_method(
				func(v: float) -> void:
					icon.position = path.position + path.curve.sample_baked(v), 0.0, path.curve.get_baked_length(), 3.0
		).set_delay(0.5)
		tweener.set_ease(%Ease7.selected)
		tweener.set_trans(%Trans7.selected)

	# Step 8

	if is_step_enabled("Wait", 2.0):
		# ...
		tween.tween_interval(2)

	# Step 9

	if is_step_enabled("Countdown", 3.0):
		tween.tween_callback(countdown_label.show)
		tween.tween_method(do_countdown, 4, 1, 3)
		tween.tween_callback(countdown_label.hide)

	# Step 10

	if is_step_enabled("Enlarge", 0.0):
		tween.tween_property(icon, ^"scale", Vector2.ONE * 5, 0.5).set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_OUT)
	if is_step_enabled("Vanish", 1.0):
		tween.parallel().tween_property(icon, ^"self_modulate:a", 0.0, 1.0)

	if %Loops.value > 1 or %Infinite.button_pressed:
		tween.tween_callback(icon.show)
		tween.tween_callback(icon.set_self_modulate.bind(Color.WHITE))

	# RESET step

	if %Reset.button_pressed:
		tween.tween_callback(reset.bind(true))

func do_countdown(number: int) -> void:
	countdown_label.text = str(number)

func reset(soft: bool = false) -> void:
	icon.position = icon_start_position
	icon.self_modulate = Color.WHITE
	icon.rotation = 0
	icon.scale = Vector2.ONE
	icon.show()
	countdown_label.hide()

	if soft:
		# Only reset properties.
		return

	if tween:
		tween.kill()
		tween = null

	if sub_tween:
		sub_tween.kill()
		sub_tween = null

	progress.max_value = 0

func is_step_enabled(step: String, expected_time: float) -> bool:
	var enabled: bool = get_node("%" + step).button_pressed
	if enabled:
		progress.max_value += expected_time

	return enabled

func pause_resume() -> void:
	if tween and tween.is_valid():
		if tween.is_running():
			tween.pause()
		else:
			tween.play()

	if sub_tween and sub_tween.is_valid():
		if sub_tween.is_running():
			sub_tween.pause()
		else:
			sub_tween.play()

func kill_tween() -> void:
	if tween:
		tween.kill()
	if sub_tween:
		sub_tween.kill()

func speed_changed(value: float) -> void:
	if tween:
		tween.set_speed_scale(value)
	if sub_tween:
		sub_tween.set_speed_scale(value)

	%SpeedLabel.text = str("x", value)

func infinite_toggled(button_pressed: bool) -> void:
	%Loops.editable = not button_pressed


[node name="Camera2D" type="Camera2D" parent="."]
offset = Vector2(265, 247)

[node name="Player" parent="." instance=ExtResource("3")]
position = Vector2(120, 456)

[node name="SecretDetector" type="Area2D" parent="."]
position = Vector2(280, 440)

[node name="CollisionShape2D" type="CollisionShape2D" parent="SecretDetector"]
shape = SubResource("RectangleShape2D_a2gec")

[connection signal="body_entered" from="SecretDetector" to="TileMap" method="_on_secret_detector_body_entered"]
[connection signal="body_exited" from="SecretDetector" to="TileMap" method="_on_secret_detector_body_exited"]


[gd_scene load_steps=4 format=2]

[ext_resource path="res://player/player.gd" type="Script" id=1]
[ext_resource path="res://player/player.png" type="Texture2D" id=2]

[sub_resource type="RectangleShape2D" id=1]
extents = Vector2(7, 7)

[node name="Player" type="CharacterBody2D"]
script = ExtResource( 1 )

[node name="Sprite2D" type="Sprite2D" parent="."]
texture = ExtResource( 2 )

[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2(-0.315559, 0.157784)
shape = SubResource( 1 )


extends CharacterBody2D

const WALK_FORCE = 600
const WALK_MAX_SPEED = 200
const STOP_FORCE = 1300
const JUMP_SPEED = 200

@onready var gravity: float = ProjectSettings.get_setting("physics/2d/default_gravity")

func _physics_process(delta: float) -> void:
	# Horizontal movement code. First, get the player's input.
	var walk := WALK_FORCE * (Input.get_axis(&"move_left", &"move_right"))
	# Slow down the player if they're not trying to move.
	if abs(walk) < WALK_FORCE * 0.2:
		# The velocity, slowed down a bit, and then reassigned.
		velocity.x = move_toward(velocity.x, 0, STOP_FORCE * delta)
	else:
		velocity.x += walk * delta
	# Clamp to the maximum horizontal movement speed.
	velocity.x = clamp(velocity.x, -WALK_MAX_SPEED, WALK_MAX_SPEED)

	# Vertical movement code. Apply gravity.
	velocity.y += gravity * delta

	# Move based on the velocity and snap to the ground.
	# TODO: This information should be set to the CharacterBody properties instead of arguments: snap, Vector2.DOWN, Vector2.UP
	# TODO: Rename velocity to linear_velocity in the rest of the script.
	move_and_slide()

	# Check for jumping. is_on_floor() must be called after movement code.
	if is_on_floor() and Input.is_action_just_pressed(&"jump"):
		velocity.y = -JUMP_SPEED


[gd_scene format=3 uid="uid://dv7rxhg55y3t6"]

[node name="StaticScene" type="Node2D"]

[node name="StaticBodyPolygon" type="StaticBody2D" parent="."]
position = Vector2(-7.85718, 399.596)

[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="StaticBodyPolygon"]
build_mode = 1
polygon = PackedVector2Array(16.3331, -129.432, 154.006, -20.0078, 292.354, 30.3943, 447.054, 33.9161, 584.899, -14.7955, 751.156, -15.5179, 894.098, -65.4518, 1000.73, -209.127, 1037.77, -398.823, 1029.92, 253.327, 6.2309, 261.185, 7.35339, -398.823)


[gd_scene load_steps=2 format=3 uid="uid://cx2q80okt25o1"]

[sub_resource type="RectangleShape2D" id="1"]
size = Vector2(1600, 100)

[node name="StaticSceneFlat" type="Node2D"]

[node name="StaticBodyPolygon" type="StaticBody2D" parent="."]
position = Vector2(512, 550)

[node name="CollisionShape2D" type="CollisionShape2D" parent="StaticBodyPolygon"]
shape = SubResource( "1" )


[gd_scene load_steps=2 format=3 uid="uid://gk0nqu0jrwm"]

[sub_resource type="RectangleShape2D" id="1"]
size = Vector2(40, 40)

[node name="StackBox" type="RigidDynamicBody2D"]
position = Vector2(-180, -20)

[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource( "1" )


extends Test

const BOX_SIZE = Vector2(40, 40)
const BOX_SPACE = Vector2(50, 50)

@export_range(1, 1000) var row_size := 100
@export_range(1, 1000) var column_size := 100

var _objects: Array[Node2D] = []

var _log_physics := false
var _log_physics_time := 0
var _log_physics_time_start := 0

func _ready() -> void:
	await start_timer(1.0).timeout
	if is_timer_canceled():
		return

	_log_physics_start()

	_create_objects()

	await wait_for_physics_ticks(5).wait_done
	_log_physics_stop()

	await start_timer(1.0).timeout
	if is_timer_canceled():
		return

	_log_physics_start()

	_add_objects()

	await wait_for_physics_ticks(5).wait_done
	_log_physics_stop()

	await start_timer(1.0).timeout
	if is_timer_canceled():
		return

	_log_physics_start()

	_move_objects()

	await wait_for_physics_ticks(5).wait_done
	_log_physics_stop()

	await start_timer(1.0).timeout
	if is_timer_canceled():
		return

	_log_physics_start()

	_remove_objects()

	await wait_for_physics_ticks(5).wait_done
	_log_physics_stop()

	await start_timer(1.0).timeout
	if is_timer_canceled():
		return

	Log.print_log("* Done.")

func _exit_tree() -> void:
	for object in _objects:
		object.free()

func _physics_process(delta: float) -> void:
	super._physics_process(delta)

	if _log_physics:
		var time := Time.get_ticks_usec()
		var time_delta := time - _log_physics_time
		var time_total := time - _log_physics_time_start
		_log_physics_time = time
		Log.print_log("  Physics Tick: %.3f ms (total = %.3f ms)" % [0.001 * time_delta, 0.001 * time_total])

func _log_physics_start() -> void:
	_log_physics = true
	_log_physics_time_start = Time.get_ticks_usec()
	_log_physics_time = _log_physics_time_start

func _log_physics_stop() -> void:
	_log_physics = false

func _create_objects() -> void:
	_objects.clear()

	Log.print_log("* Creating objects...")
	var timer := Time.get_ticks_usec()

	var pos_x := -0.5 * (row_size - 1) * BOX_SPACE.x

	for row in row_size:
		var pos_y := -0.5 * (column_size - 1) * BOX_SPACE.y

		for column in column_size:
			# Create a new object and shape every time to avoid the overhead of connecting many bodies to the same shape.
			var box := create_rigidbody_box(BOX_SIZE)
			box.gravity_scale = 0.0
			box.position = Vector2(pos_x, pos_y)
			_objects.push_back(box)

			pos_y += BOX_SPACE.y

		pos_x += BOX_SPACE.x

	timer = Time.get_ticks_usec() - timer
	Log.print_log("  Create Time: %.3f ms" % (0.001 * timer))

func _add_objects() -> void:
	var root_node: Node2D = $Objects

	Log.print_log("* Adding objects...")
	var timer := Time.get_ticks_usec()

	for object in _objects:
		root_node.add_child(object)

	timer = Time.get_ticks_usec() - timer
	Log.print_log("  Add Time: %.3f ms" % (0.001 * timer))

func _move_objects() -> void:
	Log.print_log("* Moving objects...")
	var timer := Time.get_ticks_usec()

	for object in _objects:
		object.position += BOX_SPACE

	timer = Time.get_ticks_usec() - timer
	Log.print_log("  Move Time: %.3f ms" % (0.001 * timer))

func _remove_objects() -> void:
	var root_node: Node2D = $Objects

	Log.print_log("* Removing objects...")
	var timer := Time.get_ticks_usec()

	# Remove objects in reversed order to avoid the overhead of changing children index in parent.
	var object_count := _objects.size()
	for object_index in object_count:
		root_node.remove_child(_objects[object_count - object_index - 1])

	timer = Time.get_ticks_usec() - timer
	Log.print_log("  Remove Time: %.3f ms" % (0.001 * timer))


[gd_scene load_steps=7 format=3 uid="uid://dwka2imbe5gcs"]

[ext_resource type="Texture2D" uid="uid://c5duuyhbmd0m4" path="res://assets/texture/godot-head.png" id="1"]
[ext_resource type="Script" path="res://tests/functional/test_raycasting.gd" id="2"]
[ext_resource type="PackedScene" uid="uid://blh3twy74kbkv" path="res://tests/test_options.tscn" id="2_q8u1v"]

[sub_resource type="RectangleShape2D" id="1"]
size = Vector2(80, 120)

[sub_resource type="CircleShape2D" id="2"]
radius = 60.0

[sub_resource type="CapsuleShape2D" id="3"]
radius = 30.0
height = 110.0

[node name="Test" type="Node2D"]
script = ExtResource( "2" )

[node name="Options" parent="." instance=ExtResource( "2_q8u1v" )]
offset_right = 134.0
offset_bottom = 135.719
theme_override_font_sizes/font_size = 16

[node name="Shapes" type="Node2D" parent="."]
z_index = -1
z_as_relative = false

[node name="RigidBodyRectangle" type="RigidDynamicBody2D" parent="Shapes"]
position = Vector2(114.877, 248.76)
freeze = true

[node name="CollisionShape2D" type="CollisionShape2D" parent="Shapes/RigidBodyRectangle"]
rotation = -1.19206
scale = Vector2(1.2, 1.2)
shape = SubResource( "1" )

[node name="RigidBodySphere" type="RigidDynamicBody2D" parent="Shapes"]
position = Vector2(314.894, 257.658)
freeze = true

[node name="CollisionShape2D" type="CollisionShape2D" parent="Shapes/RigidBodySphere"]
shape = SubResource( "2" )

[node name="RigidBodyCapsule" type="RigidDynamicBody2D" parent="Shapes"]
position = Vector2(465.629, 261.204)
freeze = true

[node name="CollisionShape2D" type="CollisionShape2D" parent="Shapes/RigidBodyCapsule"]
rotation = -0.202458
scale = Vector2(1.2, 1.2)
shape = SubResource( "3" )

[node name="RigidBodyConvexPolygon" type="RigidDynamicBody2D" parent="Shapes"]
position = Vector2(613.385, 252.771)
freeze = true

[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="Shapes/RigidBodyConvexPolygon"]
polygon = PackedVector2Array(10.7, -54.5, 28.3596, -49.4067, 47.6282, -34.3806, 57.9717, -20.9447, 50.9869, 35.2694, 38.8, 47.5, 15.9852, 54.3613, -14.9507, 54.1845, -36.5, 48.1, -50.4828, 36.33, -58.0115, -20.515, -46.9473, -34.7342, -26.0876, -50.1138, -11.4152, -54.5332)

[node name="GodotIcon" type="Sprite2D" parent="Shapes/RigidBodyConvexPolygon"]
modulate = Color(1, 1, 1, 0.392157)
texture = ExtResource( "1" )

[node name="RigidBodyConcavePolygon" type="RigidDynamicBody2D" parent="Shapes"]
position = Vector2(771.159, 252.771)
freeze = true

[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="Shapes/RigidBodyConcavePolygon"]
polygon = PackedVector2Array(-5.93512, -43.2195, 6.44476, -42.9695, 11.127, -54.3941, 26.9528, -49.4309, 26.2037, -36.508, 37.5346, -28.1737, 47.6282, -34.3806, 58.0427, -20.9631, 51.113, -10.2876, 50.9869, 35.2694, 38.8, 47.5, 15.9852, 54.3613, -14.9507, 54.1845, -36.5, 48.1, -50.4828, 36.33, -51.3668, -9.98545, -57.8889, -20.5885, -46.9473, -34.7342, -37.4014, -28.547, -26.0876, -37.0323, -26.9862, -49.15, -11.4152, -54.5332)

[node name="GodotIcon" type="Sprite2D" parent="Shapes/RigidBodyConcavePolygon"]
modulate = Color(1, 1, 1, 0.392157)
texture = ExtResource( "1" )

[node name="RigidBodyConcaveSegments" type="RigidDynamicBody2D" parent="Shapes"]
position = Vector2(930.097, 252.771)
freeze = true

[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="Shapes/RigidBodyConcaveSegments"]
build_mode = 1
polygon = PackedVector2Array(-5.93512, -43.2195, 6.44476, -42.9695, 11.127, -54.3941, 26.9528, -49.4309, 26.2037, -36.508, 37.5346, -28.1737, 47.6282, -34.3806, 58.0427, -20.9631, 51.113, -10.2876, 50.9869, 35.2694, 38.8, 47.5, 15.9852, 54.3613, -14.9507, 54.1845, -36.5, 48.1, -50.4828, 36.33, -51.3668, -9.98545, -57.8889, -20.5885, -46.9473, -34.7342, -37.4014, -28.547, -26.0876, -37.0323, -26.9862, -49.15, -11.4152, -54.5332)

[node name="GodotIcon" type="Sprite2D" parent="Shapes/RigidBodyConcaveSegments"]
modulate = Color(1, 1, 1, 0.392157)
texture = ExtResource( "1" )


[gd_scene load_steps=12 format=3 uid="uid://bxx2ftwccdlft"]

[ext_resource type="Script" path="res://tests/functional/test_character_pixels.gd" id="1"]
[ext_resource type="Script" path="res://utils/rigidbody_controller.gd" id="2"]
[ext_resource type="PackedScene" uid="uid://blh3twy74kbkv" path="res://tests/test_options.tscn" id="3"]
[ext_resource type="PackedScene" uid="uid://cx2q80okt25o1" path="res://tests/static_scene_flat.tscn" id="4"]
[ext_resource type="Script" path="res://utils/characterbody_controller.gd" id="7"]

[sub_resource type="RectangleShape2D" id="3"]
size = Vector2(6, 9.8)

[sub_resource type="RectangleShape2D" id="RectangleShape2D_scs3g"]
size = Vector2(6, 4.8)

[sub_resource type="SeparationRayShape2D" id="SeparationRayShape2D_vby12"]
length = 7.0

[sub_resource type="PhysicsMaterial" id="1"]
friction = 0.0

[sub_resource type="RectangleShape2D" id="2"]
size = Vector2(6, 10)

[sub_resource type="RectangleShape2D" id="6"]
size = Vector2(20, 4)

[node name="Test" type="Node2D"]
script = ExtResource( "1" )
_motion_speed = 30.0
_gravity_force = 2.0
_jump_force = 50.0
_snap_distance = 1.0

[node name="ViewportContainer" type="SubViewportContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
offset_right = 1024.0
offset_bottom = 600.0
size_flags_horizontal = 3
size_flags_vertical = 3
stretch = true
__meta__ = {
"_edit_use_anchors_": false
}

[node name="Viewport" type="SubViewport" parent="ViewportContainer"]
handle_input_locally = false
size = Vector2i(1024, 600)
size_2d_override = Vector2i(128, 75)
size_2d_override_stretch = true
render_target_update_mode = 4

[node name="StaticSceneFlat" parent="ViewportContainer/Viewport" instance=ExtResource( "4" )]
position = Vector2(0, -450)

[node name="CharacterBody2D" type="CharacterBody2D" parent="ViewportContainer/Viewport"]
position = Vector2(30, 40)
collision_mask = 2147483649
script = ExtResource( "7" )

[node name="CollisionShape2D" type="CollisionShape2D" parent="ViewportContainer/Viewport/CharacterBody2D"]
shape = SubResource( "3" )

[node name="CharacterBodyRay2D" type="CharacterBody2D" parent="ViewportContainer/Viewport"]
position = Vector2(30, 40)
collision_mask = 2147483649
script = ExtResource( "7" )

[node name="CollisionShape2D" type="CollisionShape2D" parent="ViewportContainer/Viewport/CharacterBodyRay2D"]
position = Vector2(0, -2.5)
shape = SubResource( "RectangleShape2D_scs3g" )

[node name="CollisionShapeRay2D" type="CollisionShape2D" parent="ViewportContainer/Viewport/CharacterBodyRay2D"]
position = Vector2(0, -2)
shape = SubResource( "SeparationRayShape2D_vby12" )

[node name="RigidDynamicBody2D" type="RigidDynamicBody2D" parent="ViewportContainer/Viewport"]
position = Vector2(30, 40)
collision_mask = 2147483649
physics_material_override = SubResource( "1" )
contacts_reported = 4
contact_monitor = true
lock_rotation = true
script = ExtResource( "2" )

[node name="CollisionShape2D" type="CollisionShape2D" parent="ViewportContainer/Viewport/RigidDynamicBody2D"]
shape = SubResource( "2" )

[node name="RigidBodyRay2D" type="RigidDynamicBody2D" parent="ViewportContainer/Viewport"]
position = Vector2(30, 40)
collision_mask = 2147483649
physics_material_override = SubResource( "1" )
contacts_reported = 4
contact_monitor = true
lock_rotation = true
script = ExtResource( "2" )

[node name="CollisionShape2D" type="CollisionShape2D" parent="ViewportContainer/Viewport/RigidBodyRay2D"]
position = Vector2(0, -2.5)
shape = SubResource( "RectangleShape2D_scs3g" )

[node name="CollisionShapeRay2D" type="CollisionShape2D" parent="ViewportContainer/Viewport/RigidBodyRay2D"]
position = Vector2(0, -2)
shape = SubResource( "SeparationRayShape2D_vby12" )

[node name="Wall1" type="StaticBody2D" parent="ViewportContainer/Viewport"]
position = Vector2(20, 40)

[node name="CollisionShape2D" type="CollisionShape2D" parent="SubViewportContainer/SubViewport/Wall1"]
rotation = 1.5708
shape = SubResource( "6" )

[node name="Wall2" type="StaticBody2D" parent="ViewportContainer/Viewport"]
position = Vector2(122, 40)

[node name="CollisionShape2D" type="CollisionShape2D" parent="SubViewportContainer/SubViewport/Wall2"]
rotation = 1.5708
shape = SubResource( "6" )

[node name="Platform1" type="StaticBody2D" parent="ViewportContainer/Viewport"]
position = Vector2(50, 44)

[node name="CollisionShape2D" type="CollisionShape2D" parent="ViewportContainer/Viewport/Platform1"]
shape = SubResource( "6" )
one_way_collision = true

[node name="Platform2" type="StaticBody2D" parent="ViewportContainer/Viewport"]
position = Vector2(80, 38)

[node name="CollisionShape2D" type="CollisionShape2D" parent="ViewportContainer/Viewport/Platform2"]
shape = SubResource( "6" )

[node name="Slope" type="StaticBody2D" parent="ViewportContainer/Viewport"]
position = Vector2(85, 36)

[node name="CollisionShape2D" type="CollisionPolygon2D" parent="ViewportContainer/Viewport/Slope"]
polygon = PackedVector2Array(0, 0, 6, 0, 22, 16, 16, 16)

[node name="LabelTestType" type="Label" parent="."]
offset_left = 14.0
offset_top = 79.0
offset_right = 145.0
offset_bottom = 93.0
text = "Testing: "
__meta__ = {
"_edit_use_anchors_": false
}

[node name="Options" parent="." instance=ExtResource( "3" )]

[node name="LabelFloor" type="Label" parent="."]
offset_left = 14.0
offset_top = 237.929
offset_right = 145.0
offset_bottom = 251.929
text = "ON FLOOR"
__meta__ = {
"_edit_use_anchors_": false
}

[node name="LabelControls" type="Label" parent="."]
offset_left = 14.0
offset_top = 263.291
offset_right = 145.0
offset_bottom = 294.291
text = "LEFT/RIGHT - MOVE
UP - JUMP"
__meta__ = {
"_edit_use_anchors_": false
}


extends TestCharacter

const OPTION_TEST_CASE_ALL = "Test Cases/TEST ALL (0)"
const OPTION_TEST_CASE_JUMP_ONE_WAY_RIGID = "Test Cases/Jump through one-way tiles (Rigid Body)"
const OPTION_TEST_CASE_JUMP_ONE_WAY_CHARACTER = "Test Cases/Jump through one-way tiles (Character Body)"
const OPTION_TEST_CASE_JUMP_ONE_WAY_CORNER_RIGID = "Test Cases/Jump through one-way corner (Rigid Body)"
const OPTION_TEST_CASE_JUMP_ONE_WAY_CORNER_CHARACTER = "Test Cases/Jump through one-way corner (Character Body)"
const OPTION_TEST_CASE_FALL_ONE_WAY_CHARACTER = "Test Cases/Fall and pushed on one-way tiles (Character Body)"

var _test_jump_one_way := false
var _test_jump_one_way_corner := false
var _test_fall_one_way := false

var _extra_body: PhysicsBody2D = null

var _failed_reason := ""

func _ready() -> void:
	super._ready()

	options.add_menu_item(OPTION_TEST_CASE_ALL)
	options.add_menu_item(OPTION_TEST_CASE_JUMP_ONE_WAY_RIGID)
	options.add_menu_item(OPTION_TEST_CASE_JUMP_ONE_WAY_CHARACTER)
	options.add_menu_item(OPTION_TEST_CASE_JUMP_ONE_WAY_CORNER_RIGID)
	options.add_menu_item(OPTION_TEST_CASE_JUMP_ONE_WAY_CORNER_CHARACTER)
	options.add_menu_item(OPTION_TEST_CASE_FALL_ONE_WAY_CHARACTER)

func _input(event: InputEvent) -> void:
	super._input(event)

	if event is InputEventKey and not event.pressed:
		if event.keycode == KEY_0:
			await _on_option_selected(OPTION_TEST_CASE_ALL)

func _on_option_selected(option: String) -> void:
	match option:
		OPTION_TEST_CASE_ALL:
			await _test_all()
		OPTION_TEST_CASE_JUMP_ONE_WAY_RIGID:
			await _start_test_case(option)
			return
		OPTION_TEST_CASE_JUMP_ONE_WAY_CHARACTER:
			await _start_test_case(option)
			return
		OPTION_TEST_CASE_JUMP_ONE_WAY_CORNER_RIGID:
			await _start_test_case(option)
			return
		OPTION_TEST_CASE_JUMP_ONE_WAY_CORNER_CHARACTER:
			await _start_test_case(option)
			return
		OPTION_TEST_CASE_FALL_ONE_WAY_CHARACTER:
			await _start_test_case(option)
			return

	super._on_option_selected(option)

func _start_test_case(option: String) -> void:
	Log.print_log("* Starting " + option)

	match option:
		OPTION_TEST_CASE_JUMP_ONE_WAY_RIGID:
			_body_type = BodyType.RIGID_BODY
			_test_jump_one_way_corner = false
			await _start_jump_one_way()
		OPTION_TEST_CASE_JUMP_ONE_WAY_CHARACTER:
			_body_type = BodyType.CHARACTER_BODY
			_test_jump_one_way_corner = false
			await _start_jump_one_way()
		OPTION_TEST_CASE_JUMP_ONE_WAY_CORNER_RIGID:
			_body_type = BodyType.RIGID_BODY
			_test_jump_one_way_corner = true
			await _start_jump_one_way()
		OPTION_TEST_CASE_JUMP_ONE_WAY_CORNER_CHARACTER:
			_body_type = BodyType.CHARACTER_BODY
			_test_jump_one_way_corner = true
			await _start_jump_one_way()
		OPTION_TEST_CASE_FALL_ONE_WAY_CHARACTER:
			_body_type = BodyType.CHARACTER_BODY
			await _start_fall_one_way()
		_:
			Log.print_error("Invalid test case.")

func _test_all() -> void:
	Log.print_log("* TESTING ALL...")

	# RigidBody tests.
	await _start_test_case(OPTION_TEST_CASE_JUMP_ONE_WAY_RIGID)
	if is_timer_canceled():
		return

	await _start_test_case(OPTION_TEST_CASE_JUMP_ONE_WAY_CORNER_RIGID)
	if is_timer_canceled():
		return

	# CharacterBody tests.
	await _start_test_case(OPTION_TEST_CASE_JUMP_ONE_WAY_CHARACTER)
	if is_timer_canceled():
		return

	await _start_test_case(OPTION_TEST_CASE_JUMP_ONE_WAY_CORNER_CHARACTER)
	if is_timer_canceled():
		return

	await _start_test_case(OPTION_TEST_CASE_FALL_ONE_WAY_CHARACTER)
	if is_timer_canceled():
		return

	Log.print_log("* Done.")

func _set_result(test_passed: bool) -> void:
	var result := ""
	if test_passed:
		result = "PASSED"
	else:
		result = "FAILED"

	if not test_passed and not _failed_reason.is_empty():
		result += _failed_reason
	else:
		result += "."

	Log.print_log("Test %s" % result)

func _start_test() -> void:
	if _extra_body:
		_body_parent.remove_child(_extra_body)
		_extra_body.queue_free()
		_extra_body = null

	super._start_test()

	if _test_jump_one_way:
		_test_jump_one_way = false
		_moving_body._initial_velocity = Vector2(600, -1000)

		if _test_jump_one_way_corner:
			_moving_body.position.x = 147.0

		$JumpTargetArea2D.visible = true
		$JumpTargetArea2D/CollisionShape2D.disabled = false

	if _test_fall_one_way:
		_test_fall_one_way = false

		_moving_body.position.y = 350.0
		_moving_body._gravity_force = 100.0
		_moving_body._motion_speed = 0.0
		_moving_body._jump_force = 0.0

		_extra_body = _moving_body.duplicate()
		_extra_body._gravity_force = 100.0
		_extra_body._motion_speed = 0.0
		_extra_body._jump_force = 0.0
		_extra_body.position -= Vector2(0.0, 200.0)
		_body_parent.add_child(_extra_body)

		$FallTargetArea2D.visible = true
		$FallTargetArea2D/CollisionShape2D.disabled = false

func _start_jump_one_way() -> void:
	_test_jump_one_way = true
	_start_test()

	await start_timer(1.5).timeout
	if is_timer_canceled():
		return

	_finalize_jump_one_way()

func _start_fall_one_way() -> void:
	_test_fall_one_way = true
	_start_test()

	await start_timer(1.0).timeout
	if is_timer_canceled():
		return

	_finalize_fall_one_way()

func _finalize_jump_one_way() -> void:
	var passed := true
	if not $JumpTargetArea2D.overlaps_body(_moving_body):
		passed = false
		_failed_reason = ": the body wasn't able to jump all the way through."

	_set_result(passed)

	$JumpTargetArea2D.visible = false
	$JumpTargetArea2D/CollisionShape2D.disabled = true

func _finalize_fall_one_way() -> void:
	var passed := true
	if $FallTargetArea2D.overlaps_body(_moving_body):
		passed = false
		_failed_reason = ": the body was pushed through the one-way collision."

	_set_result(passed)

	$FallTargetArea2D.visible = false
	$FallTargetArea2D/CollisionShape2D.disabled = true


extends TestCharacter

const OPTION_TEST_CASE_ALL = "Test Cases/TEST ALL (0)"
const OPTION_TEST_CASE_DETECT_FLOOR_NO_SNAP = "Test Cases/Floor detection (Character Body)"
const OPTION_TEST_CASE_DETECT_FLOOR_MOTION_CHANGES = "Test Cases/Floor detection with motion changes (Character Body)"

const MOTION_CHANGES_DIR = Vector2(1.0, 1.0)
const MOTION_CHANGES_SPEEDS: Array[float] = [0.5, 1.0, 2.0, 5.0, 10.0, 20.0, 50.0]

var _test_floor_detection := false
var _test_motion_changes := false
var _floor_detected := false
var _floor_lost := false

var _failed_reason := ""

func _ready() -> void:
	super._ready()

	options.add_menu_item(OPTION_TEST_CASE_ALL)
	options.add_menu_item(OPTION_TEST_CASE_DETECT_FLOOR_NO_SNAP)
	options.add_menu_item(OPTION_TEST_CASE_DETECT_FLOOR_MOTION_CHANGES)

func _physics_process(delta: float) -> void:
	super._physics_process(delta)

	if _moving_body:
		if _moving_body.is_on_floor():
			_floor_detected = true
		elif _floor_detected:
			_floor_lost = true
			if _test_motion_changes:
				Log.print_log("Floor lost.")

		if _test_motion_changes:
			var speed_count := MOTION_CHANGES_SPEEDS.size()
			var speed_index := randi() % speed_count
			var speed := MOTION_CHANGES_SPEEDS[speed_index]
			var velocity := speed * MOTION_CHANGES_DIR
			_moving_body._constant_velocity = velocity
			#Log.print_log("Velocity: %s" % velocity)

func _input(event: InputEvent) -> void:
	super._input(event)

	if event is InputEventKey and not event.pressed:
		if event.keycode == KEY_0:
			await _on_option_selected(OPTION_TEST_CASE_ALL)

func _on_option_selected(option: String) -> void:
	match option:
		OPTION_TEST_CASE_ALL:
			await _test_all()
		OPTION_TEST_CASE_DETECT_FLOOR_NO_SNAP:
			await _start_test_case(option)
			return
		OPTION_TEST_CASE_DETECT_FLOOR_MOTION_CHANGES:
			await _start_test_case(option)
			return

	super._on_option_selected(option)

func _start_test_case(option: String) -> void:
	Log.print_log("* Starting " + option)

	match option:
		OPTION_TEST_CASE_DETECT_FLOOR_NO_SNAP:
			_test_floor_detection = true
			_test_motion_changes = false
			_use_snap = false
			_body_type = BodyType.CHARACTER_BODY
			_start_test()

			await start_timer(1.0).timeout
			if is_timer_canceled():
				return

			_set_result(not _floor_lost)
		OPTION_TEST_CASE_DETECT_FLOOR_MOTION_CHANGES:
			_test_floor_detection = true
			_test_motion_changes = true
			_use_snap = false
			_body_type = BodyType.CHARACTER_BODY
			_start_test()

			await start_timer(4.0).timeout
			if is_timer_canceled():
				_test_motion_changes = false
				return

			_test_motion_changes = false
			_moving_body._constant_velocity = Vector2.ZERO

			_set_result(not _floor_lost)
		_:
			Log.print_error("Invalid test case.")

func _test_all() -> void:
	Log.print_log("* TESTING ALL...")

	# Test floor detection with no snapping.
	await _start_test_case(OPTION_TEST_CASE_DETECT_FLOOR_NO_SNAP)
	if is_timer_canceled():
		return

	# Test floor detection with no snapping.
	# In this test case, motion alternates different speeds.
	await _start_test_case(OPTION_TEST_CASE_DETECT_FLOOR_MOTION_CHANGES)
	if is_timer_canceled():
		return

	Log.print_log("* Done.")

func _set_result(test_passed: bool) -> void:
	var result := ""
	if test_passed:
		result = "PASSED"
	else:
		result = "FAILED"

	if not test_passed and not _failed_reason.is_empty():
		result += _failed_reason
	else:
		result += "."

	Log.print_log("Test %s" % result)

func _start_test() -> void:
	super._start_test()

	_failed_reason = ""

	_floor_detected = false
	_floor_lost = false

	if _test_floor_detection:
		_failed_reason = ": floor was not detected consistently."
		if _test_motion_changes:
			# Always use the same seed for reproducible results.
			seed(123456789)
			_moving_body._gravity_force = 0.0
			_moving_body._motion_speed = 0.0
			_moving_body._jump_force = 0.0
		else:
			_moving_body._initial_velocity = Vector2(30, 0)
		_test_floor_detection = false
	else:
		_test_motion_changes = false


[gd_scene load_steps=15 format=3 uid="uid://bqmku5ewlo6j5"]

[ext_resource type="Script" path="res://tests/functional/test_character.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://blh3twy74kbkv" path="res://tests/test_options.tscn" id="3"]
[ext_resource type="Script" path="res://utils/slider.gd" id="3_cd5g0"]
[ext_resource type="PackedScene" uid="uid://cx2q80okt25o1" path="res://tests/static_scene_flat.tscn" id="4"]
[ext_resource type="Script" path="res://utils/label_slider_value.gd" id="4_eoplu"]
[ext_resource type="Script" path="res://utils/rigidbody_controller.gd" id="6"]
[ext_resource type="Script" path="res://utils/characterbody_controller.gd" id="7"]

[sub_resource type="CapsuleShape2D" id="3"]
radius = 15.0
height = 96.0

[sub_resource type="CircleShape2D" id="CircleShape2D_llvur"]
radius = 32.0

[sub_resource type="SeparationRayShape2D" id="RayShape2D_3lv1w"]
length = 64.0

[sub_resource type="PhysicsMaterial" id="1"]
friction = 0.0

[sub_resource type="CapsuleShape2D" id="2"]
radius = 15.0
height = 96.0

[sub_resource type="CircleShape2D" id="CircleShape2D_dr08f"]
radius = 32.0

[sub_resource type="SeparationRayShape2D" id="RayShape2D_w83f0"]
length = 64.0

[node name="Test" type="Node2D"]
script = ExtResource("1")
_snap_distance = 32.0
_floor_max_angle = 60.0

[node name="LabelTestType" type="Label" parent="."]
offset_left = 14.0
offset_top = 79.0
offset_right = 145.0
offset_bottom = 93.0
text = "Testing: "

[node name="Options" parent="." instance=ExtResource("3")]

[node name="LabelFloor" type="Label" parent="."]
offset_left = 14.0
offset_top = 237.929
offset_right = 145.0
offset_bottom = 251.929
text = "ON FLOOR"

[node name="LabelControls" type="Label" parent="."]
offset_left = 14.0
offset_top = 263.291
offset_right = 145.0
offset_bottom = 294.291
text = "LEFT/RIGHT - MOVE
UP - JUMP"

[node name="FloorMaxAngle" type="HBoxContainer" parent="."]
offset_left = 14.0
offset_top = 160.0
offset_right = 476.0
offset_bottom = 186.0
theme_override_constants/separation = 20
alignment = 2

[node name="Label" type="Label" parent="FloorMaxAngle"]
layout_mode = 2
offset_right = 123.0
offset_bottom = 26.0
text = "Floor Max angle"

[node name="HSlider" type="HSlider" parent="FloorMaxAngle"]
layout_mode = 2
offset_left = 143.0
offset_right = 419.0
offset_bottom = 16.0
size_flags_horizontal = 3
max_value = 180.0
script = ExtResource("3_cd5g0")
snap_step = 5.0

[node name="LabelValue" type="Label" parent="FloorMaxAngle"]
layout_mode = 2
offset_left = 439.0
offset_right = 462.0
offset_bottom = 26.0
text = "0.0"
script = ExtResource("4_eoplu")

[node name="CharacterBody2D" type="CharacterBody2D" parent="."]
position = Vector2(100, 450)
collision_mask = 2147483649
script = ExtResource("7")

[node name="CollisionShape2D" type="CollisionShape2D" parent="CharacterBody2D"]
shape = SubResource("3")

[node name="CharacterBodyRay2D" type="CharacterBody2D" parent="."]
position = Vector2(100, 450)
collision_mask = 2147483649
script = ExtResource("7")

[node name="CollisionShape2D" type="CollisionShape2D" parent="CharacterBodyRay2D"]
position = Vector2(0, -16)
shape = SubResource("CircleShape2D_llvur")

[node name="CollisionShapeRay2D" type="CollisionShape2D" parent="CharacterBodyRay2D"]
position = Vector2(0, -16)
shape = SubResource("RayShape2D_3lv1w")

[node name="RigidDynamicBody2D" type="RigidBody2D" parent="."]
position = Vector2(100, 450)
collision_mask = 2147483649
physics_material_override = SubResource("1")
contact_monitor = true
lock_rotation = true
script = ExtResource("6")

[node name="CollisionShape2D" type="CollisionShape2D" parent="RigidDynamicBody2D"]
shape = SubResource("2")

[node name="RigidBodyRay2D" type="RigidBody2D" parent="."]
position = Vector2(100, 450)
collision_mask = 2147483649
physics_material_override = SubResource("1")
contact_monitor = true
lock_rotation = true
script = ExtResource("6")

[node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBodyRay2D"]
position = Vector2(-7.62939e-06, -16)
shape = SubResource("CircleShape2D_dr08f")

[node name="CollisionShapeRay2D" type="CollisionShape2D" parent="RigidBodyRay2D"]
position = Vector2(-7.62939e-06, -16)
shape = SubResource("RayShape2D_w83f0")

[node name="StaticSceneFlat" parent="." instance=ExtResource("4")]
position = Vector2(0, 12)

[node name="StaticBody2D" type="StaticBody2D" parent="."]

[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="StaticBody2D"]
polygon = PackedVector2Array(171.04, 529.248, 379.275, 294.316, 506.084, 429.135, 648.26, 322.058, 868.746, 322.058, 985.282, 36.6919, 1242.91, 531.917)

[connection signal="value_changed" from="FloorMaxAngle/HSlider" to="." method="_update_floor_max_angle"]


extends RigidBody2D

var _initial_velocity := Vector2.ZERO
var _constant_velocity := Vector2.ZERO
var _motion_speed := 400.0
var _gravity_force := 50.0
var _jump_force := 1000.0
var _velocity := Vector2.ZERO
var _floor_max_angle := 45.0
var _on_floor := false
var _jumping := false
var _keep_velocity := false

func _ready() -> void:
	gravity_scale = 0.0

func _physics_process(_delta: float) -> void:
	if _initial_velocity != Vector2.ZERO:
		_velocity = _initial_velocity
		_initial_velocity = Vector2.ZERO
		_keep_velocity = true
	elif _constant_velocity != Vector2.ZERO:
		_velocity = _constant_velocity
	elif not _keep_velocity:
		_velocity.x = 0.0

	# Handle horizontal controls.
	if Input.is_action_pressed(&"character_left"):
		if position.x > 0.0:
			_velocity.x = -_motion_speed
			_keep_velocity = false
			_constant_velocity = Vector2.ZERO
	elif Input.is_action_pressed(&"character_right"):
		if position.x < 1024.0:
			_velocity.x = _motion_speed
			_keep_velocity = false
			_constant_velocity = Vector2.ZERO

	# Handle jump controls and gravity.
	if is_on_floor():
		if not _jumping and Input.is_action_just_pressed(&"character_jump"):
			# Start jumping.
			_jumping = true
			_velocity.y = -_jump_force
		elif not _jumping:
			# Reset gravity.
			_velocity.y = 0.0
	else:
		_velocity.y += _gravity_force
		_jumping = false

	linear_velocity = _velocity

func _integrate_forces(state: PhysicsDirectBodyState2D) -> void:
	_on_floor = false

	var contacts := state.get_contact_count()
	for i in contacts:
		var normal := state.get_contact_local_normal(i)

		# Detect floor.
		if acos(normal.dot(Vector2.UP)) <= deg_to_rad(_floor_max_angle) + 0.01:
			_on_floor = true

		# Detect ceiling.
		if acos(normal.dot(-Vector2.UP)) <= deg_to_rad(_floor_max_angle) + 0.01:
			_jumping = false
			_velocity.y = 0.0

func is_on_floor() -> bool:
	return _on_floor


extends Node

enum PhysicsEngine {
	GODOT_PHYSICS,
	OTHER,
}

var _engine: PhysicsEngine = PhysicsEngine.OTHER

func _enter_tree() -> void:
	process_mode = Node.PROCESS_MODE_ALWAYS

	# Always enable visible collision shapes on startup
	# (same as the Debug > Visible Collision Shapes option).
	get_tree().debug_collisions_hint = true

	var engine_string:= String(ProjectSettings.get_setting("physics/2d/physics_engine"))
	match engine_string:
		"DEFAULT":
			_engine = PhysicsEngine.GODOT_PHYSICS
		"GodotPhysics2D":
			_engine = PhysicsEngine.GODOT_PHYSICS
		_:
			_engine = PhysicsEngine.OTHER

func _process(_delta: float) -> void:
	if Input.is_action_just_pressed(&"toggle_full_screen"):
		if DisplayServer.window_get_mode() == DisplayServer.WINDOW_MODE_FULLSCREEN:
			DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
		else:
			DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)

	if Input.is_action_just_pressed(&"toggle_debug_collision"):
		var debug_collision_enabled := not _is_debug_collision_enabled()
		_set_debug_collision_enabled(debug_collision_enabled)
		if debug_collision_enabled:
			Log.print_log("Debug Collision ON")
		else:
			Log.print_log("Debug Collision OFF")

	if Input.is_action_just_pressed(&"toggle_pause"):
		get_tree().paused = not get_tree().paused

	if Input.is_action_just_pressed(&"exit"):
		get_tree().quit()

func get_physics_engine() -> PhysicsEngine:
	return _engine

func _set_debug_collision_enabled(enabled: bool) -> void:
	get_tree().debug_collisions_hint = enabled

func _is_debug_collision_enabled() -> bool:
	return get_tree().debug_collisions_hint


class_name Player
extends CharacterBody2D

# Keep this in sync with the AnimationTree's state names.
const States = {
	IDLE = "idle",
	WALK = "walk",
	RUN = "run",
	FLY = "fly",
	FALL = "fall",
}

const WALK_SPEED = 200.0
const ACCELERATION_SPEED = WALK_SPEED * 6.0
const JUMP_VELOCITY = -400.0
## Maximum speed at which the player can fall.
const TERMINAL_VELOCITY = 400

var falling_slow := false
var falling_fast := false
var no_move_horizontal_time := 0.0

@onready var gravity := float(ProjectSettings.get_setting("physics/2d/default_gravity"))
@onready var sprite: Node2D = $Sprite2D
@onready var sprite_scale := sprite.scale.x

func _ready() -> void:
	$AnimationTree.active = true

func _physics_process(delta: float) -> void:
	var is_jumping := false
	if Input.is_action_just_pressed("jump"):
		is_jumping = try_jump()
	elif Input.is_action_just_released("jump") and velocity.y < 0.0:
		# The player let go of jump early, reduce vertical momentum.
		velocity.y *= 0.6
	# Fall.
	velocity.y = minf(TERMINAL_VELOCITY, velocity.y + gravity * delta)

	var direction := Input.get_axis("move_left", "move_right") * WALK_SPEED
	velocity.x = move_toward(velocity.x, direction, ACCELERATION_SPEED * delta)

	if no_move_horizontal_time > 0.0:
		# After doing a hard fall, don't move for a short time.
		velocity.x = 0.0
		no_move_horizontal_time -= delta

	if not is_zero_approx(velocity.x):
		if velocity.x > 0.0:
			sprite.scale.x = 1.0 * sprite_scale
		else:
			sprite.scale.x = -1.0 * sprite_scale

	move_and_slide()

	# After applying our motion, update our animation to match.

	# Calculate falling speed for animation purposes.
	if velocity.y >= TERMINAL_VELOCITY:
		falling_fast = true
		falling_slow = false
	elif velocity.y > 300:
		falling_slow = true

	if is_jumping:
		$AnimationTree["parameters/jump/request"] = AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE

	if is_on_floor():
		# Most animations change when we run, land, or take off.
		if falling_fast:
			$AnimationTree["parameters/land_hard/request"] = AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE
			no_move_horizontal_time = 0.4
		elif falling_slow:
			$AnimationTree["parameters/land/request"] = AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE

		if abs(velocity.x) > 50:
			$AnimationTree["parameters/state/transition_request"] = States.RUN
			$AnimationTree["parameters/run_timescale/scale"] = abs(velocity.x) / 60
		elif velocity.x:
			$AnimationTree["parameters/state/transition_request"] = States.WALK
			$AnimationTree["parameters/walk_timescale/scale"] = abs(velocity.x) / 12
		else:
			$AnimationTree["parameters/state/transition_request"] = States.IDLE

		falling_fast = false
		falling_slow = false
	else:
		if velocity.y > 0:
			$AnimationTree["parameters/state/transition_request"] = States.FALL
		else:
			$AnimationTree["parameters/state/transition_request"] = States.FLY

func try_jump() -> bool:
	if is_on_floor():
		velocity.y = JUMP_VELOCITY
		return true

	return false


[gd_resource type="ShaderMaterial" load_steps=2 format=3 uid="uid://blst65bnoqyam"]

[sub_resource type="Shader" id="1"]
code = "// original wind shader from https://github.com/Maujoe/godot-simple-wind-shader-2d/tree/master/assets/maujoe.simple_wind_shader_2d
// original script modified by HungryProton so that the assets are moving differently : https://pastebin.com/VL3AfV8D
//
// speed - The speed of the wind movement.
// minStrength - The minimal strength of the wind movement.
// maxStrength - The maximal strength of the wind movement.
// strengthScale - Scalefactor for the wind strength.
// interval - The time between minimal and maximal strength changes.
// detail - The detail (number of waves) of the wind movement.
// distortion - The strength of geometry distortion.
// heightOffset - The height where the wind begins to move. By default 0.0.

shader_type canvas_item;
render_mode blend_mix;

// Wind settings.
uniform float speed = 1.0;
uniform float minStrength : hint_range(0.0, 1.0) = 0.05;
uniform float maxStrength : hint_range(0.0, 1.0) = 0.01;
uniform float strengthScale = 100.0;
uniform float interval = 3.5;
uniform float detail = 1.0;
uniform float distortion : hint_range(0.0, 1.0);
uniform float heightOffset : hint_range(0.0, 1.0);

// With the offset value, you can if you want different moves for each asset. Just put a random value (1, 2, 3) in the editor. Don't forget to mark the material as unique if you use this
uniform float offset = 0;

float getWind(vec2 vertex, vec2 uv, float time){
    float diff = pow(maxStrength - minStrength, 2.0);
    float strength = clamp(minStrength + diff + sin(time / interval) * diff, minStrength, maxStrength) * strengthScale;
    float wind = (sin(time) + cos(time * detail)) * strength * max(0.0, (1.0-uv.y) - heightOffset);

    return wind;
}

void vertex() {
    vec4 pos = MODEL_MATRIX * vec4(0.0, 0.0, 0.0, 1.0);
    float time = TIME * speed + offset;
    //float time = TIME * speed + pos.x * pos.y  ; not working when moving...
    VERTEX.x += getWind(VERTEX.xy, UV, time);
}"

[resource]
shader = SubResource("1")
shader_parameter/speed = 1.0
shader_parameter/minStrength = 0.05
shader_parameter/maxStrength = 0.01
shader_parameter/strengthScale = 100.0
shader_parameter/interval = 3.5
shader_parameter/detail = 1.0
shader_parameter/distortion = null
shader_parameter/heightOffset = null
shader_parameter/offset = 0.0


extends Control

var dialogue_node: Node = null

func _ready() -> void:
	visible = false

func show_dialogue(player: Pawn, dialogue: Node) -> void:
	visible = true
	$Button.grab_focus()
	dialogue_node = dialogue

	for c in dialogue.get_signal_connection_list("dialogue_started"):
		if player == c.callable.get_object():
			dialogue_node.start_dialogue()
			$Name.text = "[center]" + dialogue_node.dialogue_name + "[/center]"
			$Text.text = dialogue_node.dialogue_text
			return

	dialogue_node.dialogue_started.connect(player.set_active.bind(false))
	dialogue_node.dialogue_finished.connect(player.set_active.bind(true))
	dialogue_node.dialogue_finished.connect(hide)
	dialogue_node.dialogue_finished.connect(_on_dialogue_finished.bind(player))
	dialogue_node.start_dialogue()
	$Name.text = "[center]" + dialogue_node.dialogue_name + "[/center]"
	$Text.text = dialogue_node.dialogue_text

func _on_Button_button_up() -> void:
	dialogue_node.next_dialogue()
	$Name.text = "[center]" + dialogue_node.dialogue_name + "[/center]"
	$Text.text = dialogue_node.dialogue_text

func _on_dialogue_finished(player: Pawn) -> void:
	dialogue_node.dialogue_started.disconnect(player.set_active)
	dialogue_node.dialogue_finished.disconnect(player.set_active)
	dialogue_node.dialogue_finished.disconnect(hide)
	dialogue_node.dialogue_finished.disconnect(_on_dialogue_finished)


extends CharacterBody2D

var movement_speed := 200.0

@onready var navigation_agent: NavigationAgent2D = $NavigationAgent2D

func _ready() -> void:
	# These values need to be adjusted for the actor's speed
	# and the navigation layout.
	navigation_agent.path_desired_distance = 2.0
	navigation_agent.target_desired_distance = 2.0
	navigation_agent.debug_enabled = true

# The "click" event is a custom input action defined in
# Project > Project Settings > Input Map tab.
func _unhandled_input(event: InputEvent) -> void:
	if not event.is_action_pressed("click"):
		return

	set_movement_target(get_global_mouse_position())

func set_movement_target(movement_target: Vector2) -> void:
	navigation_agent.target_position = movement_target

func _physics_process(_delta: float) -> void:
	if navigation_agent.is_navigation_finished():
		return

	var current_agent_position: Vector2 = global_position
	var next_path_position: Vector2 = navigation_agent.get_next_path_position()

	velocity = current_agent_position.direction_to(next_path_position) * movement_speed
	move_and_slide()


[gd_scene load_steps=12 format=3 uid="uid://jlfslyfbhid5"]

[ext_resource type="Script" path="res://logic/paddle.gd" id="1"]
[ext_resource type="Texture2D" uid="uid://dvrb8efddt0aa" path="res://paddle.png" id="2"]
[ext_resource type="Script" path="res://logic/ball.gd" id="4"]
[ext_resource type="Texture2D" uid="uid://clowikjgl4yq1" path="res://ball.png" id="5"]
[ext_resource type="Texture2D" uid="uid://drgpdyyomyqpl" path="res://separator.png" id="6"]
[ext_resource type="Script" path="res://logic/wall.gd" id="7"]
[ext_resource type="Script" path="res://logic/ceiling_floor.gd" id="8"]

[sub_resource type="RectangleShape2D" id="1"]
size = Vector2(8, 32)

[sub_resource type="RectangleShape2D" id="2"]
size = Vector2(8, 8)

[sub_resource type="RectangleShape2D" id="3"]
size = Vector2(20, 400)

[sub_resource type="RectangleShape2D" id="4"]
size = Vector2(640, 20)

[node name="Pong" type="Node2D"]

[node name="Background" type="ColorRect" parent="."]
offset_right = 640.0
offset_bottom = 400.0
color = Color(0.141176, 0.152941, 0.164706, 1)

[node name="Left" type="Area2D" parent="."]
modulate = Color(0, 1, 1, 1)
position = Vector2(67.6285, 192.594)
script = ExtResource("1")

[node name="Sprite2D" type="Sprite2D" parent="Left"]
texture = ExtResource("2")

[node name="Collision" type="CollisionShape2D" parent="Left"]
shape = SubResource("1")

[node name="Right" type="Area2D" parent="."]
modulate = Color(1, 0, 1, 1)
position = Vector2(563.815, 188.919)
script = ExtResource("1")

[node name="Sprite2D" type="Sprite2D" parent="Right"]
texture = ExtResource("2")

[node name="Collision" type="CollisionShape2D" parent="Right"]
shape = SubResource("1")

[node name="Ball" type="Area2D" parent="."]
position = Vector2(320.5, 191.124)
script = ExtResource("4")

[node name="Sprite2D" type="Sprite2D" parent="Ball"]
texture = ExtResource("5")

[node name="Collision" type="CollisionShape2D" parent="Ball"]
shape = SubResource("2")

[node name="Separator" type="Sprite2D" parent="."]
position = Vector2(320, 200)
texture = ExtResource("6")

[node name="LeftWall" type="Area2D" parent="."]
position = Vector2(-10, 200)
script = ExtResource("7")

[node name="Collision" type="CollisionShape2D" parent="LeftWall"]
shape = SubResource("3")

[node name="RightWall" type="Area2D" parent="."]
position = Vector2(650, 200)
script = ExtResource("7")

[node name="Collision" type="CollisionShape2D" parent="RightWall"]
shape = SubResource("3")

[node name="Ceiling" type="Area2D" parent="."]
position = Vector2(320, -10)
script = ExtResource("8")

[node name="Collision" type="CollisionShape2D" parent="Ceiling"]
shape = SubResource("4")

[node name="Floor" type="Area2D" parent="."]
position = Vector2(320, 410)
script = ExtResource("8")
_bounce_direction = -1

[node name="Collision" type="CollisionShape2D" parent="Floor"]
shape = SubResource("4")

[node name="Camera2D" type="Camera2D" parent="."]
offset = Vector2(320, 200)

[connection signal="area_entered" from="Left" to="Left" method="_on_area_entered"]
[connection signal="area_entered" from="Right" to="Right" method="_on_area_entered"]
[connection signal="area_entered" from="LeftWall" to="LeftWall" method="_on_wall_area_entered"]
[connection signal="area_entered" from="RightWall" to="RightWall" method="_on_wall_area_entered"]
[connection signal="area_entered" from="Ceiling" to="Ceiling" method="_on_area_entered"]
[connection signal="area_entered" from="Floor" to="Floor" method="_on_area_entered"]


[gd_scene format=3 uid="uid://bywptem1jb35a"]

[node name="Explanations" type="RichTextLabel"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 10.0
offset_top = -370.0
offset_right = -10.0
offset_bottom = -730.0
size_flags_vertical = 4
mouse_filter = 2
bbcode_enabled = true
text = "This example shows how to apply the State programming pattern in GDscript, including Hierarchical States, and a pushdown automaton.

States are common in games. You can use the pattern to:

1. Separate each behavior and transitions between behaviors, thus make scripts shorter and easier to manage
2. Respect the Single Responsibility Principle. Each State object represents one action
3. Improve your code's structure. Look at the scene tree and FileSystem tab: without looking at the code, you'll know what the Player can or cannot do.

You can read more about States in the excellent Game Programming Patterns ebook."


extends Node2D

var bullet := preload("Bullet.tscn")

func _unhandled_input(event: InputEvent) -> void:
	if event.is_action_pressed("fire"):
		fire()

func fire() -> void:
	if not $CooldownTimer.is_stopped():
		return

	$CooldownTimer.start()
	var new_bullet := bullet.instantiate()
	add_child(new_bullet)
	new_bullet.position = global_position
	new_bullet.direction = owner.look_direction


extends Label

var start_position := Vector2()

func _ready() -> void:
	start_position = position

func _physics_process(_delta: float) -> void:
	position = $"../BodyPivot".position + start_position

func _on_StateMachine_state_changed(current_state: Node) -> void:
	text = String(current_state.name)


shader_type canvas_item;

uniform sampler2D screen_texture : hint_screen_texture, filter_linear_mipmap;
uniform float amount: hint_range(0.0, 5.0);

void fragment() {
	COLOR.rgb = textureLod(screen_texture, SCREEN_UV, amount).rgb;
}


[node name="MovingPlatform1" type="CharacterBody2D" parent="."]
position = Vector2(268.651, 152)

[node name="Collision" type="CollisionShape2D" parent="MovingPlatform1"]
shape = SubResource("3")

[node name="Sprite2D" type="Sprite2D" parent="MovingPlatform1"]
modulate = Color(0.4, 2, 0.8, 1)
texture = ExtResource("2")

[node name="AnimationPlayer" type="AnimationPlayer" parent="MovingPlatform1"]
callback_mode_process = 0
libraries = {
"": SubResource("AnimationLibrary_2v3oa")
}
autoplay = "leftright"

[node name="MovingPlatform2" type="CharacterBody2D" parent="."]
position = Vector2(88.3493, 152)

[node name="Collision" type="CollisionShape2D" parent="MovingPlatform2"]
shape = SubResource("3")

[node name="Sprite2D" type="Sprite2D" parent="MovingPlatform2"]
modulate = Color(0.4, 2, 0.8, 1)
texture = ExtResource("2")

[node name="AnimationPlayer" type="AnimationPlayer" parent="MovingPlatform2"]
callback_mode_process = 0
libraries = {
"": SubResource("AnimationLibrary_j555p")
}
autoplay = "updown"

[node name="Princess" type="Area2D" parent="."]
position = Vector2(97, 72)
script = ExtResource("7")

[node name="Collision" type="CollisionShape2D" parent="Princess"]
shape = SubResource("7")

[node name="Sprite2D" type="Sprite2D" parent="Princess"]
texture = ExtResource("4")

[node name="WinText" type="Label" parent="."]
visible = false
offset_left = 114.0
offset_top = 30.0
offset_right = 299.0
offset_bottom = 82.0
size_flags_horizontal = 2
size_flags_vertical = 0
text = "Thank You Cubio!
You Saved The Princess!"

[node name="OneWay1" type="CharacterBody2D" parent="."]
position = Vector2(440, 308)

[node name="Sprite2D" type="Sprite2D" parent="OneWay1"]
scale = Vector2(1, 0.3)
texture = ExtResource("2")

[node name="CollisionShape2D" type="CollisionShape2D" parent="OneWay1"]
shape = SubResource("8")
one_way_collision = true

[node name="OneWay2" type="CharacterBody2D" parent="."]
position = Vector2(456, 308)

[node name="Sprite2D" type="Sprite2D" parent="OneWay2"]
scale = Vector2(1, 0.3)
texture = ExtResource("2")

[node name="CollisionShape2D" type="CollisionShape2D" parent="OneWay2"]
shape = SubResource("8")
one_way_collision = true

[node name="OneWay3" type="CharacterBody2D" parent="."]
position = Vector2(472, 308)

[node name="Sprite2D" type="Sprite2D" parent="OneWay3"]
scale = Vector2(1, 0.3)
texture = ExtResource("2")

[node name="CollisionShape2D" type="CollisionShape2D" parent="OneWay3"]
shape = SubResource("8")
one_way_collision = true

[node name="OneWay4" type="CharacterBody2D" parent="."]
position = Vector2(487, 308)

[node name="Sprite2D" type="Sprite2D" parent="OneWay4"]
scale = Vector2(1, 0.3)
texture = ExtResource("2")

[node name="CollisionShape2D" type="CollisionShape2D" parent="OneWay4"]
shape = SubResource("8")
one_way_collision = true

[node name="Circle" type="CharacterBody2D" parent="."]
position = Vector2(241.169, 304.126)
rotation = -0.0790058

[node name="Sprite2D" type="Sprite2D" parent="Circle"]
modulate = Color(0.4, 2, 0.8, 1)
texture = ExtResource("5")

[node name="CollisionShape2D" type="CollisionShape2D" parent="Circle"]
shape = SubResource("9")

[node name="AnimationPlayer" type="AnimationPlayer" parent="Circle"]
libraries = {
"": SubResource("AnimationLibrary_gijtf")
}
autoplay = "turn"

[node name="BoxSprite" type="Sprite2D" parent="Circle"]
modulate = Color(0.4, 2, 0.8, 1)
position = Vector2(-3.05176e-05, -37.4108)
texture = ExtResource("2")

[node name="Box" type="CollisionShape2D" parent="Circle"]
position = Vector2(-0.440125, -37.0904)
shape = SubResource("11")

[node name="Platform" type="StaticBody2D" parent="."]
position = Vector2(257, 393)
rotation = -0.428054
metadata/_edit_group_ = true

[node name="Sprite2D" type="Sprite2D" parent="Platform"]
texture = ExtResource("6")

[node name="CollisionShape2D" type="CollisionShape2D" parent="Platform"]
shape = SubResource("12")

[node name="Platform1" type="StaticBody2D" parent="."]
position = Vector2(369, 393)
rotation = 0.471239
metadata/_edit_group_ = true

[node name="Sprite2D" type="Sprite2D" parent="Platform1"]
texture = ExtResource("6")

[node name="CollisionShape2D" type="CollisionShape2D" parent="Platform1"]
shape = SubResource("12")

[node name="Camera2D" type="Camera2D" parent="."]
offset = Vector2(265, 247)

[node name="Player" parent="." instance=ExtResource("3")]
position = Vector2(233.06, 223.436)

[connection signal="body_entered" from="Princess" to="Princess" method="_on_body_entered"]


extends Node

func _on_body_entered(body: Node2D) -> void:
	if body.name == "Player":
		$"../WinText".show()


[gd_scene load_steps=4 format=2]

[ext_resource path="res://player/player.gd" type="Script" id=1]
[ext_resource path="res://player/player.png" type="Texture2D" id=2]

[sub_resource type="RectangleShape2D" id=1]
extents = Vector2(7, 7)

[node name="Player" type="CharacterBody2D"]
script = ExtResource( 1 )

[node name="Sprite2D" type="Sprite2D" parent="."]
texture = ExtResource( 2 )

[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2(-0.315559, 0.157784)
shape = SubResource( 1 )


extends CharacterBody2D

const WALK_FORCE = 600
const WALK_MAX_SPEED = 200
const STOP_FORCE = 1300
const JUMP_SPEED = 200

@onready var gravity := float(ProjectSettings.get_setting("physics/2d/default_gravity"))

func _physics_process(delta: float) -> void:
	# Horizontal movement code. First, get the player's input.
	var walk := WALK_FORCE * (Input.get_axis(&"move_left", &"move_right"))
	# Slow down the player if they're not trying to move.
	if abs(walk) < WALK_FORCE * 0.2:
		# The velocity, slowed down a bit, and then reassigned.
		velocity.x = move_toward(velocity.x, 0, STOP_FORCE * delta)
	else:
		velocity.x += walk * delta
	# Clamp to the maximum horizontal movement speed.
	velocity.x = clamp(velocity.x, -WALK_MAX_SPEED, WALK_MAX_SPEED)

	# Vertical movement code. Apply gravity.
	velocity.y += gravity * delta

	# Move based on the velocity and snap to the ground.
	# TODO: This information should be set to the CharacterBody properties instead of arguments: snap, Vector2.DOWN, Vector2.UP
	# TODO: Rename velocity to linear_velocity in the rest of the script.
	move_and_slide()

	# Check for jumping. is_on_floor() must be called after movement code.
	if is_on_floor() and Input.is_action_just_pressed(&"jump"):
		velocity.y = -JUMP_SPEED


extends Area2D

signal hit

@export var speed = 400 # How fast the player will move (pixels/sec).
var screen_size # Size of the game window.

func _ready():
	screen_size = get_viewport_rect().size
	hide()

func _process(delta):
	var velocity = Vector2.ZERO # The player's movement vector.
	if Input.is_action_pressed(&"move_right"):
		velocity.x += 1
	if Input.is_action_pressed(&"move_left"):
		velocity.x -= 1
	if Input.is_action_pressed(&"move_down"):
		velocity.y += 1
	if Input.is_action_pressed(&"move_up"):
		velocity.y -= 1

	if velocity.length() > 0:
		velocity = velocity.normalized() * speed
		$AnimatedSprite2D.play()
	else:
		$AnimatedSprite2D.stop()

	position += velocity * delta
	position = position.clamp(Vector2.ZERO, screen_size)

	if velocity.x != 0:
		$AnimatedSprite2D.animation = &"right"
		$AnimatedSprite2D.flip_v = false
		$Trail.rotation = 0
		$AnimatedSprite2D.flip_h = velocity.x < 0
	elif velocity.y != 0:
		$AnimatedSprite2D.animation = &"up"
		rotation = PI if velocity.y > 0 else 0

func start(pos):
	position = pos
	rotation = 0
	show()
	$CollisionShape2D.disabled = false

func _on_body_entered(_body):
	hide() # Player disappears after being hit.
	hit.emit()
	# Must be deferred as we can't change physics properties on a physics callback.
	$CollisionShape2D.set_deferred(&"disabled", true)


[node name="TopLight12" type="Polygon2D" parent="Ambient"]
light_mask = 512
material = SubResource("22")
position = Vector2(1042, -63)
scale = Vector2(0.296875, 1)
color = Color(1, 0.584314, 0, 0.341176)
texture = ExtResource("13")
polygon = PackedVector2Array(414.316, 138, -124.632, 59, 154.947, 16, 683.79, 92)
uv = PackedVector2Array(-30.3157, -2, 272.842, -2, 273, 299, -37.0526, 297)

[node name="CanvasModulate" type="CanvasModulate" parent="."]
show_behind_parent = true
light_mask = 0
color = Color(0.466667, 0.635294, 0.92549, 1)

[node name="Crows" type="Node2D" parent="."]

[node name="CrowSleep" type="AnimatedSprite2D" parent="Crows"]
material = SubResource("41")
position = Vector2(445, -129)
sprite_frames = SubResource("40")
animation = &"sleep"
autoplay = "sleep"
frame = 1

[node name="CrowSleep2" type="AnimatedSprite2D" parent="Crows"]
material = SubResource("42")
position = Vector2(108, 481)
sprite_frames = SubResource("40")
animation = &"sleep"
autoplay = "sleep"
frame = 1
flip_h = true

[node name="CrowLook" type="AnimatedSprite2D" parent="Crows"]
position = Vector2(-168, -145)
sprite_frames = SubResource("91")
animation = &"looking"
autoplay = "looking"
frame = 7
flip_h = true

[node name="CrowLook2" type="AnimatedSprite2D" parent="Crows"]
position = Vector2(1782, 35)
sprite_frames = SubResource("91")
animation = &"looking"
autoplay = "looking"
frame = 7
flip_h = true

[node name="CrowLook3" type="AnimatedSprite2D" parent="Crows"]
position = Vector2(1024, 78)
sprite_frames = SubResource("91")
animation = &"looking"
autoplay = "looking"
frame = 7