Initial commit.

This commit is contained in:
2026-09-19 10:06:28 -04:00
commit df09395018
653 changed files with 102225 additions and 0 deletions
@@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M4 11V4h7" fill="none" stroke="#fff" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" stroke-opacity=".6"/></svg>

After

Width:  |  Height:  |  Size: 221 B

@@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://btc01wc11tiid"
path="res://.godot/imported/GuiResizerTopLeft.svg-eb563f557424c74239e878a1213a5bf4.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/anthonyec.camera_preview/GuiResizerTopLeft.svg"
dest_files=["res://.godot/imported/GuiResizerTopLeft.svg-eb563f557424c74239e878a1213a5bf4.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=2.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
@@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M12 11V4h-7" fill="none" stroke="#fff" stroke-opacity=".6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>

After

Width:  |  Height:  |  Size: 223 B

@@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://04l05jxuyt7k"
path="res://.godot/imported/GuiResizerTopRight.svg-cc1dc8556d51357c5eb0b01d09d8f049.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/anthonyec.camera_preview/GuiResizerTopRight.svg"
dest_files=["res://.godot/imported/GuiResizerTopRight.svg-cc1dc8556d51357c5eb0b01d09d8f049.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=2.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
+1
View File
@@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m4 1v1l1 1v3h6v-3l1-1v-1zm1 6-2 3h10l-2-3zm2 4v2l1 2 1-2v-2z" fill="#e0e0e0"/></svg>

After

Width:  |  Height:  |  Size: 177 B

@@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://do6d60od41vmg"
path="res://.godot/imported/Pin.svg-83b09f5c00a829c5d8b136bf5bae65bc.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/anthonyec.camera_preview/Pin.svg"
dest_files=["res://.godot/imported/Pin.svg-83b09f5c00a829c5d8b136bf5bae65bc.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=2.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
@@ -0,0 +1,7 @@
[plugin]
name="Little Camera Preview"
description="Shows a picture-in-picture preview of the selected 2D or 3D camera"
author="Anthony Cossins"
version="1.3"
script="plugin.gd"
+87
View File
@@ -0,0 +1,87 @@
@tool
extends EditorPlugin
const preview_scene = preload("res://addons/anthonyec.camera_preview/preview.tscn")
var preview: CameraPreview
var current_main_screen_name: String
func _enter_tree() -> void:
main_screen_changed.connect(_on_main_screen_changed)
EditorInterface.get_selection().selection_changed.connect(_on_editor_selection_changed)
# Initialise preview panel and add to main screen.
preview = preview_scene.instantiate() as CameraPreview
preview.request_hide()
var main_screen: Control = EditorInterface.get_editor_main_screen() as Control
main_screen.add_child(preview)
func _exit_tree() -> void:
if preview:
preview.queue_free()
func _ready() -> void:
# TODO: Currently there is no API to get the main screen name without
# listening to the `EditorPlugin.main_screen_changed` signal:
# https://github.com/godotengine/godot-proposals/issues/2081
EditorInterface.set_main_screen_editor("Script")
EditorInterface.set_main_screen_editor("3D")
func _on_main_screen_changed(screen_name: String) -> void:
current_main_screen_name = screen_name
# TODO: Bit of a hack to prevent pinned staying between view changes on the same scene.
preview.unlink_camera()
_on_editor_selection_changed()
func _on_editor_selection_changed() -> void:
if not is_main_screen_viewport():
# This hides the preview "container" and not the preview itself, allowing
# any locked previews to remain visible once switching back to 3D tab.
preview.visible = false
return
preview.visible = true
var selected_nodes: Array[Node] = EditorInterface.get_selection().get_selected_nodes()
var selected_camera_3d: Camera3D = find_camera_3d_or_null(selected_nodes)
var selected_camera_2d: Camera2D = find_camera_2d_or_null(selected_nodes)
if selected_camera_3d and current_main_screen_name == "3D":
preview.link_with_camera_3d(selected_camera_3d)
preview.request_show()
elif selected_camera_2d and current_main_screen_name == "2D":
preview.link_with_camera_2d(selected_camera_2d)
preview.request_show()
else:
preview.request_hide()
func is_main_screen_viewport() -> bool:
return current_main_screen_name == "3D" or current_main_screen_name == "2D"
func find_camera_3d_or_null(nodes: Array[Node]) -> Camera3D:
var camera: Camera3D
for node in nodes:
if node is Camera3D:
camera = node as Camera3D
break
return camera
func find_camera_2d_or_null(nodes: Array[Node]) -> Camera2D:
var camera: Camera2D
for node in nodes:
if node is Camera2D:
camera = node as Camera2D
break
return camera
func _on_selected_camera_3d_tree_exiting() -> void:
preview.unlink_camera()
@@ -0,0 +1 @@
uid://dgq70e0pxlh61
+406
View File
@@ -0,0 +1,406 @@
@tool
class_name CameraPreview
extends Control
enum CameraType {
CAMERA_2D,
CAMERA_3D
}
enum PinnedPosition {
LEFT,
RIGHT,
}
enum InteractionState {
NONE,
RESIZE,
DRAG,
# Animation is split into 2 separate states so that the tween is only
# invoked once in the "start" state.
START_ANIMATE_INTO_PLACE,
ANIMATE_INTO_PLACE,
}
const margin_3d: Vector2 = Vector2(10, 10)
const margin_2d: Vector2 = Vector2(20, 15)
const panel_margin: float = 2
const min_panel_size: float = 250
@onready var panel: Panel = %Panel
@onready var placeholder: Panel = %Placeholder
@onready var preview_camera_3d: Camera3D = %Camera3D
@onready var preview_camera_2d: Camera2D = %Camera2D
@onready var sub_viewport: SubViewport = %SubViewport
@onready var sub_viewport_text_rect: TextureRect = %TextureRect
@onready var resize_left_handle: Button = %ResizeLeftHandle
@onready var resize_right_handle: Button = %ResizeRightHandle
@onready var lock_button: Button = %LockButton
@onready var gradient: TextureRect = %Gradient
@onready var viewport_margin_container: MarginContainer = %ViewportMarginContainer
@onready var overlay_margin_container: MarginContainer = %OverlayMarginContainer
@onready var overlay_container: Control = %OverlayContainer
var _camera_type: CameraType = CameraType.CAMERA_3D
var _pinned_position: PinnedPosition = PinnedPosition.RIGHT
var _editor_scale: float = EditorInterface.get_editor_scale()
var _is_locked: bool
var _show_controls: bool
var _selected_camera_3d: Camera3D
var _selected_camera_2d: Camera2D
var _state: InteractionState = InteractionState.NONE
var _initial_mouse_position: Vector2
var _initial_panel_size: Vector2
var _initial_panel_position: Vector2
func _ready() -> void:
# Set initial width.
panel.size.x = min_panel_size * _editor_scale
# Setting texture to viewport in code instead of directly in the editor
# because otherwise an error "Path to node is invalid: Panel/SubViewport"
# on first load. This is harmless but doesn't look great.
#
# This is a known issue:
# https://github.com/godotengine/godot/issues/27790#issuecomment-499740220
sub_viewport_text_rect.texture = sub_viewport.get_texture()
# From what I can tell there's something wrong with how an editor theme
# scales when used within a plugin. It seems to ignore the screen scale.
# For instance, a 30x30px button will appear tiny on a retina display.
#
# Someone else had the issue with no luck:
# https://forum.godotengine.org/t/how-to-scale-plugin-controls-to-look-the-same-in-4k-as-1080p/36151
#
# And seems Dialogic also scales buttons manually:
# https://github.com/dialogic-godot/dialogic/blob/master/addons/dialogic/Editor/Common/sidebar.gd#L25C6-L38
#
# Maybe I don't know the correct way to do it, so for now the workaround is
# to set the correct size in code using screen scale.
var button_size: Vector2 = Vector2(30, 30) * _editor_scale
var margin_size: int = int(panel_margin * _editor_scale)
resize_left_handle.size = button_size
resize_left_handle.pivot_offset = Vector2(0, 0) * _editor_scale
resize_right_handle.size = button_size
resize_right_handle.pivot_offset = Vector2(30, 30) * _editor_scale
lock_button.size = button_size
lock_button.pivot_offset = Vector2(0, 30) * _editor_scale
viewport_margin_container.add_theme_constant_override("margin_left", margin_size)
viewport_margin_container.add_theme_constant_override("margin_top", margin_size)
viewport_margin_container.add_theme_constant_override("margin_right", margin_size)
viewport_margin_container.add_theme_constant_override("margin_bottom", margin_size)
overlay_margin_container.add_theme_constant_override("margin_left", margin_size)
overlay_margin_container.add_theme_constant_override("margin_top", margin_size)
overlay_margin_container.add_theme_constant_override("margin_right", margin_size)
overlay_margin_container.add_theme_constant_override("margin_bottom", margin_size)
# Parent node overlay size is not available on first ready, need to wait a
# frame for it to be drawn.
await get_tree().process_frame
# Anchors are set in code because setting them in the editor UI doesn't take
# editor scale into account.
resize_left_handle.position = Vector2(0, 0)
resize_right_handle.set_anchors_preset(Control.PRESET_TOP_LEFT)
resize_right_handle.position = Vector2(overlay_container.size.x - button_size.x, 0)
resize_right_handle.set_anchors_preset(Control.PRESET_TOP_RIGHT)
lock_button.position = Vector2(0, overlay_container.size.y - button_size.y)
lock_button.set_anchors_preset(Control.PRESET_BOTTOM_LEFT)
func _process(_delta: float) -> void:
if not visible: return
match _state:
InteractionState.NONE:
panel.size = get_clamped_size(panel.size)
panel.position = get_pinned_position(_pinned_position)
InteractionState.RESIZE:
var delta_mouse_position: Vector2 = _initial_mouse_position - get_global_mouse_position()
var resized_size: Vector2 = panel.size
if _pinned_position == PinnedPosition.LEFT:
resized_size = _initial_panel_size - delta_mouse_position
if _pinned_position == PinnedPosition.RIGHT:
resized_size = _initial_panel_size + delta_mouse_position
panel.size = get_clamped_size(resized_size)
panel.position = get_pinned_position(_pinned_position)
InteractionState.DRAG:
placeholder.size = panel.size
var global_mouse_position: Vector2 = get_global_mouse_position()
var offset: Vector2 = _initial_mouse_position - _initial_panel_position
panel.global_position = global_mouse_position - offset
if global_mouse_position.x < global_position.x + size.x / 2:
_pinned_position = PinnedPosition.LEFT
else:
_pinned_position = PinnedPosition.RIGHT
placeholder.position = get_pinned_position(_pinned_position)
InteractionState.START_ANIMATE_INTO_PLACE:
var final_position: Vector2 = get_pinned_position(_pinned_position)
var tween: Tween = get_tree().create_tween()
tween.set_ease(Tween.EASE_OUT)
tween.set_trans(Tween.TRANS_CUBIC)
tween.tween_property(panel, "position", final_position, 0.3)
tween.finished.connect(func() -> void:
panel.position = final_position
_state = InteractionState.NONE
)
_state = InteractionState.ANIMATE_INTO_PLACE
# I couldn't get `mouse_entered` and `mouse_exited` events to work
# nicely, so I use rect method instead. Plus using this method it's easy to
# grow the hit area size.
var panel_hover_rect: Rect2 = Rect2(panel.global_position, panel.size)
panel_hover_rect = panel_hover_rect.grow(40)
var mouse_position: Vector2 = get_global_mouse_position()
_show_controls = _state != InteractionState.NONE or panel_hover_rect.has_point(mouse_position)
# UI visibility.
resize_left_handle.visible = _show_controls and _pinned_position == PinnedPosition.RIGHT
resize_right_handle.visible = _show_controls and _pinned_position == PinnedPosition.LEFT
lock_button.visible = _show_controls or _is_locked
placeholder.visible = _state == InteractionState.DRAG or _state == InteractionState.ANIMATE_INTO_PLACE
gradient.visible = _show_controls
# Sync camera settings.
if _camera_type == CameraType.CAMERA_3D and _selected_camera_3d:
sub_viewport.size = panel.size
# Sync position and rotation without using a `RemoteTransform` node
# because if you save a camera as a scene, the remote transform node will
# be stored within the scene. Also it's harder to keep the remote
# transform `remote_path` up-to-date with scene changes, which causes
# many errors.
preview_camera_3d.global_position = _selected_camera_3d.global_position
preview_camera_3d.global_rotation = _selected_camera_3d.global_rotation
preview_camera_3d.fov = _selected_camera_3d.fov
preview_camera_3d.projection = _selected_camera_3d.projection
preview_camera_3d.size = _selected_camera_3d.size
preview_camera_3d.cull_mask = _selected_camera_3d.cull_mask
preview_camera_3d.keep_aspect = _selected_camera_3d.keep_aspect
preview_camera_3d.near = _selected_camera_3d.near
preview_camera_3d.far = _selected_camera_3d.far
preview_camera_3d.h_offset = _selected_camera_3d.h_offset
preview_camera_3d.v_offset = _selected_camera_3d.v_offset
preview_camera_3d.attributes = _selected_camera_3d.attributes
preview_camera_3d.environment = _selected_camera_3d.environment
if _camera_type == CameraType.CAMERA_2D and _selected_camera_2d:
var project_window_size: Vector2 = get_project_window_size()
var ratio: float = project_window_size.x / panel.size.x
# TODO: Is there a better way to fix this?
# The camera border is visible sometimes due to pixel rounding.
# Subtract 1px from right and bottom to hide this.
var hide_camera_border_fix: Vector2 = Vector2(1, 1)
sub_viewport.size = panel.size
sub_viewport.size_2d_override = (panel.size - hide_camera_border_fix) * ratio
sub_viewport.size_2d_override_stretch = true
preview_camera_2d.global_position = _selected_camera_2d.global_position
preview_camera_2d.global_rotation = _selected_camera_2d.global_rotation
preview_camera_2d.offset = _selected_camera_2d.offset
preview_camera_2d.zoom = _selected_camera_2d.zoom
preview_camera_2d.ignore_rotation = _selected_camera_2d.ignore_rotation
preview_camera_2d.anchor_mode = _selected_camera_2d.anchor_mode
preview_camera_2d.limit_left = _selected_camera_2d.limit_left
preview_camera_2d.limit_right = _selected_camera_2d.limit_right
preview_camera_2d.limit_top = _selected_camera_2d.limit_top
preview_camera_2d.limit_bottom = _selected_camera_2d.limit_bottom
func link_with_camera_3d(camera_3d: Camera3D) -> void:
# TODO: Camera may not be ready since this method is called in `_enter_tree`
# in the plugin because of a workaround for:
# https://github.com/godotengine/godot-proposals/issues/2081
if not preview_camera_3d:
return request_hide()
var is_different_camera: bool = camera_3d != preview_camera_3d
# TODO: A bit messy.
if is_different_camera:
if preview_camera_3d.tree_exiting.is_connected(unlink_camera):
preview_camera_3d.tree_exiting.disconnect(unlink_camera)
if not camera_3d.tree_exiting.is_connected(unlink_camera):
camera_3d.tree_exiting.connect(unlink_camera)
sub_viewport.disable_3d = false
sub_viewport.world_3d = camera_3d.get_world_3d()
_selected_camera_3d = camera_3d
_camera_type = CameraType.CAMERA_3D
func link_with_camera_2d(camera_2d: Camera2D) -> void:
if not preview_camera_2d:
return request_hide()
var is_different_camera: bool = camera_2d != preview_camera_2d
# TODO: A bit messy.
if is_different_camera:
if preview_camera_2d.tree_exiting.is_connected(unlink_camera):
preview_camera_2d.tree_exiting.disconnect(unlink_camera)
if not camera_2d.tree_exiting.is_connected(unlink_camera):
camera_2d.tree_exiting.connect(unlink_camera)
sub_viewport.disable_3d = true
sub_viewport.world_2d = camera_2d.get_world_2d()
_selected_camera_2d = camera_2d
_camera_type = CameraType.CAMERA_2D
func unlink_camera() -> void:
if _selected_camera_3d:
_selected_camera_3d = null
if _selected_camera_2d:
_selected_camera_2d = null
_is_locked = false
lock_button.button_pressed = false
func request_hide() -> void:
if _is_locked: return
visible = false
func request_show() -> void:
visible = true
func get_pinned_position(pinned_position: PinnedPosition) -> Vector2:
var margin: Vector2 = margin_3d * _editor_scale
if _camera_type == CameraType.CAMERA_2D:
margin = margin_2d * _editor_scale
match pinned_position:
PinnedPosition.LEFT:
return Vector2.ZERO - Vector2(0, panel.size.y) - Vector2(-margin.x, margin.y)
PinnedPosition.RIGHT:
return size - panel.size - margin
_:
assert(false, "Unknown pinned position %s" % str(pinned_position))
return Vector2.ZERO
func get_clamped_size(desired_size: Vector2) -> Vector2:
var viewport_ratio: float = get_project_window_ratio()
var editor_viewport_size: Vector2 = get_editor_viewport_size()
var max_bounds: Vector2 = Vector2(
editor_viewport_size.x * 0.6,
editor_viewport_size.y * 0.8
)
var clamped_size: Vector2 = desired_size
# Apply aspect ratio.
clamped_size = Vector2(clamped_size.x, clamped_size.x * viewport_ratio)
# Clamp the max size while respecting the aspect ratio.
if clamped_size.y >= max_bounds.y:
clamped_size.x = max_bounds.y / viewport_ratio
clamped_size.y = max_bounds.y
if clamped_size.x >= max_bounds.x:
clamped_size.x = max_bounds.x
clamped_size.y = max_bounds.x * viewport_ratio
# Clamp the min size based on if it's portrait or landscape. Portrait min
# size should be based on it's height. Landscape min size is based on it's
# width instead. Applying min width to a portrait size would make it too big.
var is_portrait: bool = viewport_ratio > 1
if is_portrait and clamped_size.y <= min_panel_size * _editor_scale:
clamped_size.x = min_panel_size / viewport_ratio
clamped_size.y = min_panel_size
clamped_size = clamped_size * _editor_scale
if not is_portrait and clamped_size.x <= min_panel_size * _editor_scale:
clamped_size.x = min_panel_size
clamped_size.y = min_panel_size * viewport_ratio
clamped_size = clamped_size * _editor_scale
# Round down to avoid sub-pixel artifacts, mainly seen around the margins.
return clamped_size.floor()
func get_project_window_size() -> Vector2:
var window_width: float = float(ProjectSettings.get_setting("display/window/size/viewport_width"))
var window_height: float = float(ProjectSettings.get_setting("display/window/size/viewport_height"))
return Vector2(window_width, window_height)
func get_project_window_ratio() -> float:
var project_window_size: Vector2 = get_project_window_size()
return project_window_size.y / project_window_size.x
func get_editor_viewport_size() -> Vector2:
var fallback_size: Vector2 = EditorInterface.get_editor_main_screen().size
# There isn't an API for getting the viewport node. Instead it has to be
# found by checking the parent's parent of the subviewport and find
# the correct node based on name and class.
var editor_sub_viewport_3d: SubViewport = EditorInterface.get_editor_viewport_3d(0)
var editor_viewport_container: Control = editor_sub_viewport_3d.get_parent().get_parent().get_parent() as Control
# Early return in case editor tree structure has changed.
if not editor_viewport_container:
return fallback_size
if editor_viewport_container.get_class() != "Node3DEditorViewportContainer":
return fallback_size
return editor_viewport_container.size
func _on_resize_handle_button_down() -> void:
if _state != InteractionState.NONE: return
_state = InteractionState.RESIZE
_initial_mouse_position = get_global_mouse_position()
_initial_panel_size = panel.size
func _on_resize_handle_button_up() -> void:
_state = InteractionState.NONE
func _on_drag_handle_button_down() -> void:
if _state != InteractionState.NONE: return
_state = InteractionState.DRAG
_initial_mouse_position = get_global_mouse_position()
_initial_panel_position = panel.global_position
func _on_drag_handle_button_up() -> void:
if _state != InteractionState.DRAG: return
_state = InteractionState.START_ANIMATE_INTO_PLACE
func _on_lock_button_pressed() -> void:
_is_locked = !_is_locked
@@ -0,0 +1 @@
uid://56ntugxskwub
@@ -0,0 +1,201 @@
[gd_scene load_steps=8 format=3 uid="uid://xybmfvufjuv"]
[ext_resource type="Script" uid="uid://56ntugxskwub" path="res://addons/anthonyec.camera_preview/preview.gd" id="1_6b32r"]
[ext_resource type="Texture2D" uid="uid://do6d60od41vmg" path="res://addons/anthonyec.camera_preview/Pin.svg" id="2_p0pa8"]
[ext_resource type="Texture2D" uid="uid://btc01wc11tiid" path="res://addons/anthonyec.camera_preview/GuiResizerTopLeft.svg" id="2_t64ej"]
[ext_resource type="Texture2D" uid="uid://04l05jxuyt7k" path="res://addons/anthonyec.camera_preview/GuiResizerTopRight.svg" id="3_6yuab"]
[sub_resource type="ViewportTexture" id="ViewportTexture_m22c1"]
[sub_resource type="Gradient" id="Gradient_11p6r"]
offsets = PackedFloat32Array(0, 0.3, 0.6, 1)
colors = PackedColorArray(0, 0, 0, 0.235294, 0, 0, 0, 0.0784314, 0, 0, 0, 0.0784314, 0, 0, 0, 0.235294)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_4dkve"]
gradient = SubResource("Gradient_11p6r")
width = 256
height = 256
fill_to = Vector2(2.08165e-12, 1)
[node name="Preview" type="Control"]
z_index = 999
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_6b32r")
[node name="Placeholder" type="Panel" parent="."]
unique_name_in_owner = true
visible = false
modulate = Color(1, 1, 1, 0.705882)
layout_mode = 1
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -40.0
offset_top = -40.0
offset_right = 410.0
offset_bottom = 410.0
grow_horizontal = 0
grow_vertical = 0
[node name="Panel" type="Panel" parent="."]
unique_name_in_owner = true
clip_contents = true
layout_mode = 1
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -520.0
offset_top = -301.0
offset_right = -20.0
offset_bottom = -20.0
grow_horizontal = 0
grow_vertical = 0
pivot_offset = Vector2(450, 300)
[node name="SubViewport" type="SubViewport" parent="Panel"]
unique_name_in_owner = true
handle_input_locally = false
gui_disable_input = true
size_2d_override_stretch = true
[node name="Camera3D" type="Camera3D" parent="Panel/SubViewport"]
unique_name_in_owner = true
current = true
[node name="Camera2D" type="Camera2D" parent="Panel/SubViewport"]
unique_name_in_owner = true
ignore_rotation = false
[node name="ViewportMarginContainer" type="MarginContainer" parent="Panel"]
unique_name_in_owner = true
clip_contents = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
theme_override_constants/margin_left = 4
theme_override_constants/margin_top = 4
theme_override_constants/margin_right = 4
theme_override_constants/margin_bottom = 4
[node name="TextureRect" type="TextureRect" parent="Panel/ViewportMarginContainer"]
unique_name_in_owner = true
layout_mode = 2
texture = SubResource("ViewportTexture_m22c1")
expand_mode = 1
[node name="Gradient" type="TextureRect" parent="Panel"]
unique_name_in_owner = true
visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
texture = SubResource("GradientTexture2D_4dkve")
[node name="OverlayMarginContainer" type="MarginContainer" parent="Panel"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
theme_override_constants/margin_left = 4
theme_override_constants/margin_top = 4
theme_override_constants/margin_right = 4
theme_override_constants/margin_bottom = 4
[node name="OverlayContainer" type="Control" parent="Panel/OverlayMarginContainer"]
unique_name_in_owner = true
clip_contents = true
layout_mode = 2
mouse_filter = 2
[node name="DragHandle" type="Button" parent="Panel/OverlayMarginContainer/OverlayContainer"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
focus_mode = 0
flat = true
[node name="ResizeLeftHandle" type="Button" parent="Panel/OverlayMarginContainer/OverlayContainer"]
unique_name_in_owner = true
visible = false
layout_mode = 1
offset_right = 60.0
offset_bottom = 60.0
size_flags_horizontal = 0
size_flags_vertical = 0
mouse_default_cursor_shape = 12
icon = ExtResource("2_t64ej")
flat = true
icon_alignment = 1
expand_icon = true
[node name="ResizeRightHandle" type="Button" parent="Panel/OverlayMarginContainer/OverlayContainer"]
unique_name_in_owner = true
visible = false
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -60.0
offset_bottom = 60.0
grow_horizontal = 0
pivot_offset = Vector2(60, 60)
size_flags_horizontal = 8
size_flags_vertical = 0
mouse_default_cursor_shape = 11
icon = ExtResource("3_6yuab")
flat = true
icon_alignment = 1
expand_icon = true
[node name="LockButton" type="Button" parent="Panel/OverlayMarginContainer/OverlayContainer"]
unique_name_in_owner = true
visible = false
layout_mode = 1
anchors_preset = 2
anchor_top = 1.0
anchor_bottom = 1.0
offset_top = -60.0
offset_right = 60.0
grow_vertical = 0
pivot_offset = Vector2(0, 60)
size_flags_horizontal = 0
size_flags_vertical = 8
tooltip_text = "Always Show Preview"
toggle_mode = true
icon = ExtResource("2_p0pa8")
flat = true
icon_alignment = 1
expand_icon = true
[connection signal="button_down" from="Panel/OverlayMarginContainer/OverlayContainer/DragHandle" to="." method="_on_drag_handle_button_down"]
[connection signal="button_up" from="Panel/OverlayMarginContainer/OverlayContainer/DragHandle" to="." method="_on_drag_handle_button_up"]
[connection signal="renamed" from="Panel/OverlayMarginContainer/OverlayContainer/DragHandle" to="." method="_on_drag_handle_renamed"]
[connection signal="button_down" from="Panel/OverlayMarginContainer/OverlayContainer/ResizeLeftHandle" to="." method="_on_resize_handle_button_down"]
[connection signal="button_up" from="Panel/OverlayMarginContainer/OverlayContainer/ResizeLeftHandle" to="." method="_on_resize_handle_button_up"]
[connection signal="button_down" from="Panel/OverlayMarginContainer/OverlayContainer/ResizeRightHandle" to="." method="_on_resize_handle_button_down"]
[connection signal="button_up" from="Panel/OverlayMarginContainer/OverlayContainer/ResizeRightHandle" to="." method="_on_resize_handle_button_up"]
[connection signal="pressed" from="Panel/OverlayMarginContainer/OverlayContainer/LockButton" to="." method="_on_lock_button_pressed"]
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 DmitriySalnikov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the Software), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, andor sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+163
View File
@@ -0,0 +1,163 @@
![icon](/images/icon_3d_128.png)
# Debug drawing utility for Godot
This is an add-on for debug drawing in 3D and for some 2D overlays, which is written in `C++` and can be used with `GDScript`, `C++` or `C#`.
Based on my previous addon, which was developed [only for C#](https://github.com/DmitriySalnikov/godot_debug_draw_cs), and which was inspired by [Zylann's GDScript addon](https://github.com/Zylann/godot_debug_draw)
## [Documentation](https://dd3d.dmitriysalnikov.ru/docs/)
## [Godot 3 version](https://github.com/DmitriySalnikov/godot_debug_draw_3d/tree/godot_3)
## Support me
Your support adds motivation to develop my public projects.
<a href="https://boosty.to/dmitriysalnikov/donate"><img src="/docs/images/boosty.png" alt="Boosty" width=150px/></a>
<a href="#"><img src="/docs/images/USDT-TRC20.png" alt="USDT-TRC20" width=150px/></a>
<b>USDT-TRC20 TEw934PrsffHsAn5M63SoHYRuZo984EF6v</b>
## Features
3D:
* Arrow
* Billboard opaque square
* Box
* Camera Frustum
* Capsule
* Cylinder
* Gizmo
* Grid
* Line
* Line Path
* Line with Arrow
* Plane
* Points
* Position 3D (3 crossing axes)
* Sphere
* 3D Text
Overlay:
* Text (with grouping and coloring)
Precompiled for:
* Windows
* Linux (built on Ubuntu 22.04)
* macOS (10.15+)
* Android (5.0+)
* iOS
* Web (Firefox is supported by Godot 4.3+)
This addon supports working with several World3D and different Viewports. [More information](https://dd3d.dmitriysalnikov.ru/docs/?page=md_docs_2SubViewports.html).
There is also a no depth test mode and other settings that can be changed for each instance.
This addon supports double-precision builds, for more information, [see the documentation](https://dd3d.dmitriysalnikov.ru/docs/?page=md_docs_2DoublePrecision.html).
## [Interactive Web Demo](https://dd3d.dmitriysalnikov.ru/demo/)
[![screenshot_web](/images/screenshot_web.png)](https://dd3d.dmitriysalnikov.ru/demo/)
## Download
To download, use the [Godot Asset Library](https://godotengine.org/asset-library/asset/1766) or use one of the stable versions from the [GitHub Releases](https://github.com/DmitriySalnikov/godot_debug_draw_3d/releases) page.
For versions prior to `1.4.5`, just download one of the `source codes` in the assets. For newer versions, download `debug-draw-3d_[version].zip`.
### Installation
* Close editor
* Copy `addons/debug_draw_3d` to your `addons` folder, create it if the folder doesn't exist
* Launch editor
## Examples
More examples can be found in the `examples_dd3d/` folder.
Simple test:
```gdscript
func _process(delta: float) -> void:
var _time = Time.get_ticks_msec() / 1000.0
var box_pos = Vector3(0, sin(_time * 4), 0)
var line_begin = Vector3(-1, sin(_time * 4), 0)
var line_end = Vector3(1, cos(_time * 4), 0)
DebugDraw3D.draw_box(box_pos, Quaternion.IDENTITY, Vector3(1, 2, 1), Color(0, 1, 0))
DebugDraw3D.draw_line(line_begin, line_end, Color(1, 1, 0))
DebugDraw2D.set_text("Time", _time)
DebugDraw2D.set_text("Frames drawn", Engine.get_frames_drawn())
DebugDraw2D.set_text("FPS", Engine.get_frames_per_second())
DebugDraw2D.set_text("delta", delta)
```
![screenshot_1](/images/screenshot_1.png)
An example of using scoped configs:
```gdscript
@tool
extends Node3D
func _ready():
# Set the base scoped_config.
# Each frame will be reset to these scoped values.
DebugDraw3D.scoped_config().set_thickness(0.1).set_center_brightness(0.6)
func _process(delta):
# Draw using the base scoped config.
DebugDraw3D.draw_box(Vector3.ZERO, Quaternion.IDENTITY, Vector3.ONE * 2, Color.CORNFLOWER_BLUE)
if true:
# Create a scoped config that will exist until exiting this if.
var _s = DebugDraw3D.new_scoped_config().set_thickness(0).set_center_brightness(0.1)
# Draw with a thickness of 0
DebugDraw3D.draw_box(Vector3.ZERO, Quaternion.IDENTITY, Vector3.ONE, Color.RED)
# If necessary, the values inside this scope can be changed
# even before each call to draw_*.
_s.set_thickness(0.05)
DebugDraw3D.draw_box(Vector3(1,0,1), Quaternion.IDENTITY, Vector3.ONE * 1, Color.BLUE_VIOLET)
```
![screenshot_5](/images/screenshot_5.png)
> [!TIP]
>
> If you want to use a non-standard Viewport for rendering a 3d scene, then do not forget to specify it in the scoped config!
## API
This project has a separate [documentation](https://dd3d.dmitriysalnikov.ru/docs/) page.
Also, a list of all functions is available in the documentation inside the editor (see `DebugDraw3D` and `DebugDraw2D`).
![screenshot_4](/images/screenshot_4.png)
## Known issues and limitations
The text in the keys and values of a text group cannot contain multi-line strings.
The entire text overlay can only be placed in one corner.
[Frustum of Camera3D does not take into account the window size from ProjectSettings](https://github.com/godotengine/godot/issues/70362).
## Usage Analytics
This addon collects anonymous statistics on **editor** usage time. The data includes the library version, engine version, operating system, system architecture, and locale. No personally identifiable information is gathered.
Libraries without **editor** support do not collect statistics in any way. Also, previews running in the editor do not collect any usage statistics.
You can disable this in the editor settings at: `debug_draw_3d/settings/telemetry_state`.
## More screenshots
`DD3DDemo.tscn` in editor
![screenshot_2](/images/screenshot_2.png)
`DD3DDemo.tscn` in play mode
![screenshot_3](/images/screenshot_3.png)
@@ -0,0 +1,162 @@
[configuration]
entry_symbol = "debug_draw_3d_library_init"
compatibility_minimum = "4.4.1"
reloadable = false
[dependencies]
; example.x86_64 = { "relative or absolute path to the dependency" : "the path relative to the exported project", }
; -------------------------------------
; debug
macos = { }
windows.x86_64 = { }
linux.x86_64 = { }
linux.arm64 = { }
; by default godot is using threads
web.wasm32.nothreads = {}
web.wasm32 = {}
android.arm32 = { }
android.arm64 = { }
android.x86_32 = { }
android.x86_64 = { }
ios = {}
; -------------------------------------
; release no debug draw
macos.template_release = { }
windows.template_release.x86_64 = { }
linux.template_release.x86_64 = { }
linux.template_release.arm64 = { }
web.template_release.wasm32.nothreads = { }
web.template_release.wasm32 = { }
android.template_release.arm32 = { }
android.template_release.arm64 = { }
android.template_release.x86_32 = { }
android.template_release.x86_64 = { }
ios.template_release = {}
; -------------------------------------
; release forced debug draw
macos.template_release.forced_dd3d = { }
windows.template_release.x86_64.forced_dd3d = { }
linux.template_release.x86_64.forced_dd3d = { }
linux.template_release.arm64.forced_dd3d = { }
web.template_release.wasm32.nothreads.forced_dd3d = { }
web.template_release.wasm32.forced_dd3d = { }
ios.template_release.forced_dd3d = {}
[libraries]
; -------------------------------------
; debug
macos = "libs/libdd3d.macos.editor.universal.framework"
windows.x86_64 = "libs/libdd3d.windows.editor.x86_64.dll"
linux.x86_64 = "libs/libdd3d.linux.editor.x86_64.so"
linux.arm64 = "libs/libdd3d.linux.editor.arm64.so"
web.wasm32.nothreads = "libs/libdd3d.web.template_debug.wasm32.wasm"
web.wasm32 = "libs/libdd3d.web.template_debug.wasm32.threads.wasm"
android.arm32 = "libs/libdd3d.android.template_debug.arm32.so"
android.arm64 = "libs/libdd3d.android.template_debug.arm64.so"
android.x86_32 = "libs/libdd3d.android.template_debug.x86_32.so"
android.x86_64 = "libs/libdd3d.android.template_debug.x86_64.so"
ios = "libs/libdd3d.ios.template_debug.universal.dylib"
; -------------------------------------
; release no debug draw
macos.template_release = "libs/libdd3d.macos.template_release.universal.framework"
windows.template_release.x86_64 = "libs/libdd3d.windows.template_release.x86_64.dll"
linux.template_release.x86_64 = "libs/libdd3d.linux.template_release.x86_64.so"
linux.template_release.arm64 = "libs/libdd3d.linux.template_release.arm64.so"
web.template_release.wasm32.nothreads = "libs/libdd3d.web.template_release.wasm32.wasm"
web.template_release.wasm32 = "libs/libdd3d.web.template_release.wasm32.threads.wasm"
android.template_release.arm32 = "libs/libdd3d.android.template_release.arm32.so"
android.template_release.arm64 = "libs/libdd3d.android.template_release.arm64.so"
android.template_release.x86_32 = "libs/libdd3d.android.template_release.x86_32.so"
android.template_release.x86_64 = "libs/libdd3d.android.template_release.x86_64.so"
ios.template_release = "libs/libdd3d.ios.template_release.universal.dylib"
; -------------------------------------
; release forced debug draw
macos.template_release.forced_dd3d = "libs/libdd3d.macos.template_release.universal.enabled.framework"
windows.template_release.x86_64.forced_dd3d = "libs/libdd3d.windows.template_release.x86_64.enabled.dll"
linux.template_release.x86_64.forced_dd3d = "libs/libdd3d.linux.template_release.x86_64.enabled.so"
linux.template_release.arm64.forced_dd3d = "libs/libdd3d.linux.template_release.arm64.enabled.so"
web.template_release.wasm32.nothreads.forced_dd3d = "libs/libdd3d.web.template_release.wasm32.enabled.wasm"
web.template_release.wasm32.forced_dd3d = "libs/libdd3d.web.template_release.wasm32.threads.enabled.wasm"
ios.template_release.forced_dd3d = "libs/libdd3d.ios.template_release.universal.enabled.dylib"
; -------------------------------------
; DOUBLE PRECISION
; -------------------------------------
; -------------------------------------
; debug
macos.double = "libs/libdd3d.macos.editor.universal.double.framework"
windows.x86_64.double = "libs/libdd3d.windows.editor.x86_64.double.dll"
linux.x86_64.double = "libs/libdd3d.linux.editor.x86_64.double.so"
linux.arm64.double = "libs/libdd3d.linux.editor.arm64.double.so"
web.wasm32.nothreads.double = "libs/libdd3d.web.template_debug.wasm32.double.wasm"
web.wasm32.double = "libs/libdd3d.web.template_debug.wasm32.threads.double.wasm"
android.arm32.double = "libs/libdd3d.android.template_debug.arm32.double.so"
android.arm64.double = "libs/libdd3d.android.template_debug.arm64.double.so"
android.x86_32.double = "libs/libdd3d.android.template_debug.x86_32.double.so"
android.x86_64.double = "libs/libdd3d.android.template_debug.x86_64.double.so"
ios.double = "libs/libdd3d.ios.template_debug.universal.double.dylib"
; -------------------------------------
; release no debug draw
macos.template_release.double = "libs/libdd3d.macos.template_release.universal.double.framework"
windows.template_release.x86_64.double = "libs/libdd3d.windows.template_release.x86_64.double.dll"
linux.template_release.x86_64.double = "libs/libdd3d.linux.template_release.x86_64.double.so"
linux.template_release.arm64.double = "libs/libdd3d.linux.template_release.arm64.double.so"
web.template_release.wasm32.nothreads.double = "libs/libdd3d.web.template_release.wasm32.double.wasm"
web.template_release.wasm32.double = "libs/libdd3d.web.template_release.wasm32.threads.double.wasm"
android.template_release.arm32.double = "libs/libdd3d.android.template_release.arm32.double.so"
android.template_release.arm64.double = "libs/libdd3d.android.template_release.arm64.double.so"
android.template_release.x86_32.double = "libs/libdd3d.android.template_release.x86_32.double.so"
android.template_release.x86_64.double = "libs/libdd3d.android.template_release.x86_64.double.so"
ios.template_release.double = "libs/libdd3d.ios.template_release.universal.double.dylib"
; -------------------------------------
; release forced debug draw
macos.template_release.forced_dd3d.double = "libs/libdd3d.macos.template_release.universal.enabled.double.framework"
windows.template_release.x86_64.forced_dd3d.double = "libs/libdd3d.windows.template_release.x86_64.enabled.double.dll"
linux.template_release.x86_64.forced_dd3d.double = "libs/libdd3d.linux.template_release.x86_64.enabled.double.so"
linux.template_release.arm64.forced_dd3d.double = "libs/libdd3d.linux.template_release.arm64.enabled.double.so"
web.template_release.wasm32.nothreads.forced_dd3d.double = "libs/libdd3d.web.template_release.wasm32.enabled.double.wasm"
web.template_release.wasm32.forced_dd3d.double = "libs/libdd3d.web.template_release.wasm32.threads.enabled.double.wasm"
ios.template_release.forced_dd3d.double = "libs/libdd3d.ios.template_release.universal.enabled.double.dylib"
@@ -0,0 +1 @@
uid://svqaxfp5kyrl
View File
+3
View File
@@ -0,0 +1,3 @@
# Temporary editor files
~*.dll
~*.TMP
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>libdd3d.macos.editor.universal.dylib</string>
<key>CFBundleName</key>
<string>Debug Draw 3D</string>
<key>CFBundleDisplayName</key>
<string>Debug Draw 3D</string>
<key>CFBundleIdentifier</key>
<string>ru.dmitriysalnikov.dd3d</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright (c) Dmitriy Salnikov.</string>
<key>CFBundleVersion</key>
<string>1.7.3</string>
<key>CFBundleShortVersionString</key>
<string>1.7.3</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>DTPlatformName</key>
<string>macosx</string>
<key>LSMinimumSystemVersion</key>
<string>10.14</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
</dict>
</plist>
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>libdd3d.macos.template_release.universal.enabled.dylib</string>
<key>CFBundleName</key>
<string>Debug Draw 3D</string>
<key>CFBundleDisplayName</key>
<string>Debug Draw 3D</string>
<key>CFBundleIdentifier</key>
<string>ru.dmitriysalnikov.dd3d</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright (c) Dmitriy Salnikov.</string>
<key>CFBundleVersion</key>
<string>1.7.3</string>
<key>CFBundleShortVersionString</key>
<string>1.7.3</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>DTPlatformName</key>
<string>macosx</string>
<key>LSMinimumSystemVersion</key>
<string>10.14</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
</dict>
</plist>
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>libdd3d.macos.template_release.universal.dylib</string>
<key>CFBundleName</key>
<string>Debug Draw 3D</string>
<key>CFBundleDisplayName</key>
<string>Debug Draw 3D</string>
<key>CFBundleIdentifier</key>
<string>ru.dmitriysalnikov.dd3d</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright (c) Dmitriy Salnikov.</string>
<key>CFBundleVersion</key>
<string>1.7.3</string>
<key>CFBundleShortVersionString</key>
<string>1.7.3</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>DTPlatformName</key>
<string>macosx</string>
<key>LSMinimumSystemVersion</key>
<string>10.14</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
</dict>
</plist>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
# MIT License
Copyright © 2023-present Hugo Locurcio and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+653
View File
@@ -0,0 +1,653 @@
extends CanvasLayer
@export var fps: Label
@export var frame_time: Label
@export var frame_number: Label
@export var frame_stats: Label
@export var frame_history_total_avg: Label
@export var frame_history_total_min: Label
@export var frame_history_total_max: Label
@export var frame_history_total_last: Label
@export var frame_history_cpu_avg: Label
@export var frame_history_cpu_min: Label
@export var frame_history_cpu_max: Label
@export var frame_history_cpu_last: Label
@export var frame_history_gpu_avg: Label
@export var frame_history_gpu_min: Label
@export var frame_history_gpu_max: Label
@export var frame_history_gpu_last: Label
@export var fps_graph: Panel
@export var total_graph: Panel
@export var cpu_graph: Panel
@export var gpu_graph: Panel
@export var information: Label
@export var settings: Label
## The number of frames to keep in history for graph drawing and best/worst calculations.
## Currently, this also affects how FPS is measured.
const HISTORY_NUM_FRAMES = 150
var GRAPH_SIZE = Vector2(150, 25) # kept uppercase for less code change
const DEFAULT_GRAPH_SIZE = Vector2(150, 25)
const DEFAULT_FONT_SIZE = 12.0
const MIN_FONT_SIZE = 3.0
const MAX_FONT_SIZE = 72.0
const DEFAULT_FONT_OUTLINE_SIZE = 3.0
const DEFAULT_HEADER_WIDTH = 50.0
const GRAPH_MIN_FPS = 10
const GRAPH_MAX_FPS = 160
const GRAPH_MIN_FRAMETIME = 1.0 / GRAPH_MIN_FPS
const GRAPH_MAX_FRAMETIME = 1.0 / GRAPH_MAX_FPS
## Debug menu display style.
enum Style {
HIDDEN, ## Debug menu is hidden.
VISIBLE_COMPACT, ## Debug menu is visible, with only the FPS, FPS cap (if any) and time taken to render the last frame.
VISIBLE_DETAILED, ## Debug menu is visible with full information, including graphs.
MAX, ## Represents the size of the Style enum.
}
## The style to use when drawing the debug menu.
var style := Style.HIDDEN:
set(value):
style = value
match style:
Style.HIDDEN:
visible = false
Style.VISIBLE_COMPACT, Style.VISIBLE_DETAILED:
visible = true
frame_number.visible = style == Style.VISIBLE_DETAILED
frame_stats.visible = style == Style.VISIBLE_DETAILED
$DebugMenu/VBoxContainer/FrameTimeHistory.visible = style == Style.VISIBLE_DETAILED
$DebugMenu/VBoxContainer/FPSGraph.visible = style == Style.VISIBLE_DETAILED
$DebugMenu/VBoxContainer/TotalGraph.visible = style == Style.VISIBLE_DETAILED
$DebugMenu/VBoxContainer/CPUGraph.visible = style == Style.VISIBLE_DETAILED
$DebugMenu/VBoxContainer/GPUGraph.visible = style == Style.VISIBLE_DETAILED
information.visible = style == Style.VISIBLE_DETAILED
settings.visible = style == Style.VISIBLE_DETAILED
var _custom_font_size: int = DEFAULT_FONT_SIZE * 3 # 3x
## Debug menu display size.
enum Display_Size {
SIZE_3, ## 0.25x the default scale
SIZE_6, ## 0.5x the default scale
SIZE_12_DEFAULT, ## 1.0x, the default display size
SIZE_18, ## 1.5x the default scale
SIZE_24, ## 2.0x the default scale
SIZE_30, ## 2.5x the default scale
SIZE_CUSTOM, ## Initially 3.0x the default scale, but overwritten with set_font_size()
MAX, ## Represents the size of the Display_Size enum.
}
## The size to use when drawing the debug menu.
var display_size := Display_Size.SIZE_12_DEFAULT:
set(value):
display_size = value
match display_size:
Display_Size.SIZE_3: # 0.25x
_resize_overlay(DEFAULT_FONT_SIZE * 0.25,
0, # no outline, makes small font look better
DEFAULT_HEADER_WIDTH* 0.25)
Display_Size.SIZE_6: # 0.5x
_resize_overlay(DEFAULT_FONT_SIZE* 0.5,
DEFAULT_FONT_OUTLINE_SIZE * 0.5,
DEFAULT_HEADER_WIDTH* 0.5)
Display_Size.SIZE_12_DEFAULT: # 1.0x
_resize_overlay(DEFAULT_FONT_SIZE,
DEFAULT_FONT_OUTLINE_SIZE,
DEFAULT_HEADER_WIDTH)
Display_Size.SIZE_18: # 1.5x
_resize_overlay(DEFAULT_FONT_SIZE * 1.5,
DEFAULT_FONT_OUTLINE_SIZE * 1.5,
DEFAULT_HEADER_WIDTH * 1.5)
Display_Size.SIZE_24: # 2.0x
_resize_overlay(DEFAULT_FONT_SIZE * 2.0,
DEFAULT_FONT_OUTLINE_SIZE * 2.0,
DEFAULT_HEADER_WIDTH * 2.0)
Display_Size.SIZE_30: # 2.5x
_resize_overlay(DEFAULT_FONT_SIZE * 2.5,
DEFAULT_FONT_OUTLINE_SIZE * 2.5,
DEFAULT_HEADER_WIDTH * 2.5)
Display_Size.SIZE_CUSTOM: # initially is 3.0x, replaced when set_font_size() is called
var new_scale: float = float(_custom_font_size / DEFAULT_FONT_SIZE)
_resize_overlay(DEFAULT_FONT_SIZE * new_scale,
DEFAULT_FONT_OUTLINE_SIZE * new_scale,
DEFAULT_HEADER_WIDTH * new_scale)
# Value of `Time.get_ticks_usec()` on the previous frame.
var last_tick := 0
var thread := Thread.new()
## Returns the sum of all values of an array (use as a parameter to `Array.reduce()`).
var sum_func := func avg(accum: float, number: float) -> float: return accum + number
# History of the last `HISTORY_NUM_FRAMES` rendered frames.
var frame_history_total: Array[float] = []
var frame_history_cpu: Array[float] = []
var frame_history_gpu: Array[float] = []
var fps_history: Array[float] = [] # Only used for graphs.
var frametime_avg := GRAPH_MIN_FRAMETIME
var frametime_cpu_avg := GRAPH_MAX_FRAMETIME
var frametime_gpu_avg := GRAPH_MIN_FRAMETIME
var frames_per_second := float(GRAPH_MIN_FPS)
var frame_time_gradient := Gradient.new()
func _init() -> void:
# This must be done here instead of `_ready()` to avoid having `visibility_changed` be emitted immediately.
visible = false
if not InputMap.has_action("cycle_debug_menu"):
# Create default input action if no user-defined override exists.
# We can't do it in the editor plugin's activation code as it doesn't seem to work there.
InputMap.add_action("cycle_debug_menu")
var event := InputEventKey.new()
event.keycode = KEY_F3
InputMap.action_add_event("cycle_debug_menu", event)
if not InputMap.has_action("cycle_debug_menu_size"):
# Create default input action if no user-defined override exists.
# We can't do it in the editor plugin's activation code as it doesn't seem to work there.
InputMap.add_action("cycle_debug_menu_size")
var event := InputEventKey.new()
event.keycode = KEY_F4
InputMap.action_add_event("cycle_debug_menu_size", event)
if not InputMap.has_action("debug_menu_screenshot"):
# Create default input action if no user-defined override exists.
# We can't do it in the editor plugin's activation code as it doesn't seem to work there.
InputMap.add_action("debug_menu_screenshot")
var event := InputEventKey.new()
event.keycode = KEY_F5
InputMap.action_add_event("debug_menu_screenshot", event)
func _ready() -> void:
# start visibility from project settings
if ProjectSettings.has_setting("DebugMenu/settings/startup_visibility"):
style = ProjectSettings.get_setting("DebugMenu/settings/startup_visibility")
# font size from project settings
if ProjectSettings.has_setting("DebugMenu/settings/font_size"):
set_font_size(ProjectSettings.get_setting("DebugMenu/settings/font_size"))
fps_graph.draw.connect(_fps_graph_draw)
total_graph.draw.connect(_total_graph_draw)
cpu_graph.draw.connect(_cpu_graph_draw)
gpu_graph.draw.connect(_gpu_graph_draw)
fps_history.resize(HISTORY_NUM_FRAMES)
frame_history_total.resize(HISTORY_NUM_FRAMES)
frame_history_cpu.resize(HISTORY_NUM_FRAMES)
frame_history_gpu.resize(HISTORY_NUM_FRAMES)
# NOTE: Both FPS and frametimes are colored following FPS logic
# (red = 10 FPS, yellow = 60 FPS, green = 110 FPS, cyan = 160 FPS).
# This makes the color gradient non-linear.
# Colors are taken from <https://tailwindcolor.com/>.
frame_time_gradient.set_color(0, Color.from_rgba8(239, 68, 68)) # red-500
frame_time_gradient.set_color(1, Color.from_rgba8(56, 189, 248)) # light-blue-400
frame_time_gradient.add_point(0.3333, Color.from_rgba8(250, 204, 21)) # yellow-400
frame_time_gradient.add_point(0.6667, Color.from_rgba8(128, 226, 95)) # 50-50 mix of lime-400 and green-400
get_viewport().size_changed.connect(update_settings_label)
# Display loading text while information is being queried,
# in case the user toggles the full debug menu just after starting the project.
information.text = "Loading hardware information...\n\n "
settings.text = "Loading project information..."
thread.start(
func():
# Disable thread safety checks as they interfere with this add-on.
# This only affects this particular thread, not other thread instances in the project.
# See <https://github.com/godotengine/godot/pull/78000> for details.
# Use a Callable so that this can be ignored on Godot 4.0 without causing a script error
# (thread safety checks were added in Godot 4.1).
if Engine.get_version_info()["hex"] >= 0x040100:
Callable(Thread, "set_thread_safety_checks_enabled").call(false)
# Enable required time measurements to display CPU/GPU frame time information.
# These lines are time-consuming operations, so run them in a separate thread.
RenderingServer.viewport_set_measure_render_time(get_viewport().get_viewport_rid(), true)
update_information_label()
update_settings_label()
)
func _input(event: InputEvent) -> void:
if event.is_action_pressed("cycle_debug_menu"):
style = wrapi(style + 1, 0, Style.MAX) as Style
# Only cycle size/scale when DebugMenu is visible
elif event.is_action_pressed("cycle_debug_menu_size") and style != Style.HIDDEN:
display_size = wrapi(display_size + 1, 0, Display_Size.MAX) as Display_Size
# Only take screen shot if debug menu is displayed (maybe allow this anytime?)
elif event.is_action_pressed("debug_menu_screenshot") and style != Style.HIDDEN:
do_screenshot()
#region Scaling functions by Antz
func _resize_overlay(font_size_in: int, outline_size: int, header_width: float):
# change font size and outline for all labels
for l in get_tree().get_nodes_in_group("debug_menu_label"):
var label : Label = l as Label
label.add_theme_font_size_override("font_size", font_size_in)
# no outline for very small fonts sizes
if font_size_in < 6:
label.add_theme_constant_override("outline_size", 0)
else:
label.add_theme_constant_override("outline_size", outline_size)
# change header widths
for l in get_tree().get_nodes_in_group("debug_menu_header"):
var label : Label = l as Label
label.custom_minimum_size.x = header_width
# main FPS label size is 50% bigger
fps.add_theme_font_size_override("font_size", font_size_in*1.5)
# no outline for very small fonts sizes
if font_size_in < 6:
fps.add_theme_constant_override("outline_size", 0)
else:
fps.add_theme_constant_override("outline_size", outline_size*1.5)
var new_scale: float = font_size_in / DEFAULT_FONT_SIZE
# scale line spacing for multi line labels
settings.add_theme_constant_override("line_spacing", 3 * new_scale)
information.add_theme_constant_override("line_spacing", 3 * new_scale)
# graph re-size
GRAPH_SIZE = DEFAULT_GRAPH_SIZE * new_scale
for p in get_tree().get_nodes_in_group("debug_menu_graph"):
var panel: Panel = p as Panel
panel.custom_minimum_size = GRAPH_SIZE
# this adjusts the label on the left Y minimum size, so that the graphs are spaced
panel.get_parent().find_child("Title").custom_minimum_size.y = GRAPH_SIZE.y + (2.0 * new_scale)
## Set the font size of the Debug Menu overlay
## Sets display_size to Display_Size.SIZE_CUSTOM
## recalculates all GUI elements
func set_font_size(font_size_in: int):
if font_size_in < MIN_FONT_SIZE or font_size_in > MAX_FONT_SIZE:
printerr(str("Font size range for DebugMenu is [", MIN_FONT_SIZE," to ", MAX_FONT_SIZE ,"]"))
return
_custom_font_size = font_size_in
display_size = Display_Size.SIZE_CUSTOM # this triggers the set, where resize_overlay() takes place
#endregion Scaling functions
func _exit_tree() -> void:
if thread.is_started():
thread.wait_to_finish()
## Update hardware information label (this can change at runtime based on window
## size and graphics settings). This is only called when the window is resized.
## To update when graphics settings are changed, the function must be called manually
## using `DebugMenu.update_settings_label()`.
func update_settings_label() -> void:
settings.text = ""
if ProjectSettings.has_setting("application/config/version"):
settings.text += "Project Version: %s\n" % ProjectSettings.get_setting("application/config/version")
var rendering_method := str(ProjectSettings.get_setting_with_override("rendering/renderer/rendering_method"))
var rendering_method_string := rendering_method
match rendering_method:
"forward_plus":
rendering_method_string = "Forward+"
"mobile":
rendering_method_string = "Forward Mobile"
"gl_compatibility":
rendering_method_string = "Compatibility"
settings.text += "Rendering Method: %s\n" % rendering_method_string
var viewport := get_viewport()
# The size of the viewport rendering, which determines which resolution 3D is rendered at.
var viewport_render_size := Vector2i()
if viewport.content_scale_mode == Window.CONTENT_SCALE_MODE_VIEWPORT:
viewport_render_size = viewport.get_visible_rect().size
settings.text += "Viewport: %d×%d, Window: %d×%d\n" % [viewport.get_visible_rect().size.x, viewport.get_visible_rect().size.y, viewport.size.x, viewport.size.y]
else:
# Window size matches viewport size.
viewport_render_size = viewport.size
settings.text += "Viewport: %d×%d\n" % [viewport.size.x, viewport.size.y]
# Display 3D settings only if relevant.
if viewport.get_camera_3d():
var scaling_3d_mode_string := "(unknown)"
match viewport.scaling_3d_mode:
Viewport.SCALING_3D_MODE_BILINEAR:
scaling_3d_mode_string = "Bilinear"
Viewport.SCALING_3D_MODE_FSR:
scaling_3d_mode_string = "FSR 1.0"
Viewport.SCALING_3D_MODE_FSR2:
scaling_3d_mode_string = "FSR 2.2"
var antialiasing_3d_string := ""
if viewport.scaling_3d_mode == Viewport.SCALING_3D_MODE_FSR2:
# The FSR2 scaling mode includes its own temporal antialiasing implementation.
antialiasing_3d_string += (" + " if not antialiasing_3d_string.is_empty() else "") + "FSR 2.2"
if viewport.scaling_3d_mode != Viewport.SCALING_3D_MODE_FSR2 and viewport.use_taa:
# Godot's own TAA is ignored when using FSR2 scaling mode, as FSR2 provides its own TAA implementation.
antialiasing_3d_string += (" + " if not antialiasing_3d_string.is_empty() else "") + "TAA"
if viewport.msaa_3d >= Viewport.MSAA_2X:
antialiasing_3d_string += (" + " if not antialiasing_3d_string.is_empty() else "") + "%d× MSAA" % pow(2, viewport.msaa_3d)
if viewport.screen_space_aa == Viewport.SCREEN_SPACE_AA_FXAA:
antialiasing_3d_string += (" + " if not antialiasing_3d_string.is_empty() else "") + "FXAA"
settings.text += "3D scale (%s): %d%% = %d×%d" % [
scaling_3d_mode_string,
viewport.scaling_3d_scale * 100,
viewport_render_size.x * viewport.scaling_3d_scale,
viewport_render_size.y * viewport.scaling_3d_scale,
]
if not antialiasing_3d_string.is_empty():
settings.text += "\n3D Antialiasing: %s" % antialiasing_3d_string
var environment := viewport.get_camera_3d().get_world_3d().environment
if environment:
if environment.ssr_enabled:
settings.text += "\nSSR: %d Steps" % environment.ssr_max_steps
if environment.ssao_enabled:
settings.text += "\nSSAO: On"
if environment.ssil_enabled:
settings.text += "\nSSIL: On"
if environment.sdfgi_enabled:
settings.text += "\nSDFGI: %d Cascades" % environment.sdfgi_cascades
if environment.glow_enabled:
settings.text += "\nGlow: On"
if environment.volumetric_fog_enabled:
settings.text += "\nVolumetric Fog: On"
var antialiasing_2d_string := ""
if viewport.msaa_2d >= Viewport.MSAA_2X:
antialiasing_2d_string = "%d× MSAA" % pow(2, viewport.msaa_2d)
if not antialiasing_2d_string.is_empty():
settings.text += "\n2D Antialiasing: %s" % antialiasing_2d_string
## Update hardware/software information label (this never changes at runtime).
func update_information_label() -> void:
var adapter_string := ""
# Make "NVIDIA Corporation" and "NVIDIA" be considered identical (required when using OpenGL to avoid redundancy).
if RenderingServer.get_video_adapter_vendor().trim_suffix(" Corporation") in RenderingServer.get_video_adapter_name():
# Avoid repeating vendor name before adapter name.
# Trim redundant suffix sometimes reported by NVIDIA graphics cards when using OpenGL.
adapter_string = RenderingServer.get_video_adapter_name().trim_suffix("/PCIe/SSE2")
else:
adapter_string = RenderingServer.get_video_adapter_vendor() + " - " + RenderingServer.get_video_adapter_name().trim_suffix("/PCIe/SSE2")
# Graphics driver version information isn't always availble.
var driver_info := OS.get_video_adapter_driver_info()
var driver_info_string := ""
if driver_info.size() >= 2:
driver_info_string = driver_info[1]
else:
driver_info_string = "(unknown)"
var release_string := ""
if OS.has_feature("editor"):
# Editor build (implies `debug`).
release_string = "editor"
elif OS.has_feature("debug"):
# Debug export template build.
release_string = "debug"
else:
# Release export template build.
release_string = "release"
var rendering_method := str(ProjectSettings.get_setting_with_override("rendering/renderer/rendering_method"))
var rendering_driver := str(ProjectSettings.get_setting_with_override("rendering/rendering_device/driver"))
var graphics_api_string := rendering_driver
if rendering_method != "gl_compatibility":
if rendering_driver == "d3d12":
graphics_api_string = "Direct3D 12"
elif rendering_driver == "metal":
graphics_api_string = "Metal"
elif rendering_driver == "vulkan":
if OS.has_feature("macos") or OS.has_feature("ios"):
graphics_api_string = "Vulkan via MoltenVK"
else:
graphics_api_string = "Vulkan"
else:
if rendering_driver == "opengl3_angle":
graphics_api_string = "OpenGL via ANGLE"
elif OS.has_feature("mobile") or rendering_driver == "opengl3_es":
graphics_api_string = "OpenGL ES"
elif OS.has_feature("web"):
graphics_api_string = "WebGL"
elif rendering_driver == "opengl3":
graphics_api_string = "OpenGL"
information.text = (
"%s, %d threads\n" % [OS.get_processor_name().replace("(R)", "").replace("(TM)", ""), OS.get_processor_count()]
+ "%s %s (%s %s), %s %s\n" % [OS.get_name(), "64-bit" if OS.has_feature("64") else "32-bit", release_string, "double" if OS.has_feature("double") else "single", graphics_api_string, RenderingServer.get_video_adapter_api_version()]
+ "%s, %s" % [adapter_string, driver_info_string]
)
func _fps_graph_draw() -> void:
var fps_polyline := PackedVector2Array()
fps_polyline.resize(HISTORY_NUM_FRAMES)
for fps_index in fps_history.size():
fps_polyline[fps_index] = Vector2(
remap(fps_index, 0, fps_history.size(), 0, GRAPH_SIZE.x),
remap(clampf(fps_history[fps_index], GRAPH_MIN_FPS, GRAPH_MAX_FPS), GRAPH_MIN_FPS, GRAPH_MAX_FPS, GRAPH_SIZE.y, 0.0)
)
# Don't use antialiasing to speed up line drawing, but use a width that scales with
# viewport scale to keep the line easily readable on hiDPI displays.
fps_graph.draw_polyline(fps_polyline, frame_time_gradient.sample(remap(frames_per_second, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)), 1.0)
func _total_graph_draw() -> void:
var total_polyline := PackedVector2Array()
total_polyline.resize(HISTORY_NUM_FRAMES)
for total_index in frame_history_total.size():
total_polyline[total_index] = Vector2(
remap(total_index, 0, frame_history_total.size(), 0, GRAPH_SIZE.x),
remap(clampf(frame_history_total[total_index], GRAPH_MIN_FPS, GRAPH_MAX_FPS), GRAPH_MIN_FPS, GRAPH_MAX_FPS, GRAPH_SIZE.y, 0.0)
)
# Don't use antialiasing to speed up line drawing, but use a width that scales with
# viewport scale to keep the line easily readable on hiDPI displays.
total_graph.draw_polyline(total_polyline, frame_time_gradient.sample(remap(1000.0 / frametime_avg, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)), 1.0)
func _cpu_graph_draw() -> void:
var cpu_polyline := PackedVector2Array()
cpu_polyline.resize(HISTORY_NUM_FRAMES)
for cpu_index in frame_history_cpu.size():
cpu_polyline[cpu_index] = Vector2(
remap(cpu_index, 0, frame_history_cpu.size(), 0, GRAPH_SIZE.x),
remap(clampf(frame_history_cpu[cpu_index], GRAPH_MIN_FPS, GRAPH_MAX_FPS), GRAPH_MIN_FPS, GRAPH_MAX_FPS, GRAPH_SIZE.y, 0.0)
)
# Don't use antialiasing to speed up line drawing, but use a width that scales with
# viewport scale to keep the line easily readable on hiDPI displays.
cpu_graph.draw_polyline(cpu_polyline, frame_time_gradient.sample(remap(1000.0 / frametime_cpu_avg, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)), 1.0)
func _gpu_graph_draw() -> void:
var gpu_polyline := PackedVector2Array()
gpu_polyline.resize(HISTORY_NUM_FRAMES)
for gpu_index in frame_history_gpu.size():
gpu_polyline[gpu_index] = Vector2(
remap(gpu_index, 0, frame_history_gpu.size(), 0, GRAPH_SIZE.x),
remap(clampf(frame_history_gpu[gpu_index], GRAPH_MIN_FPS, GRAPH_MAX_FPS), GRAPH_MIN_FPS, GRAPH_MAX_FPS, GRAPH_SIZE.y, 0.0)
)
# Don't use antialiasing to speed up line drawing, but use a width that scales with
# viewport scale to keep the line easily readable on hiDPI displays.
gpu_graph.draw_polyline(gpu_polyline, frame_time_gradient.sample(remap(1000.0 / frametime_gpu_avg, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)), 1.0)
func _process(_delta: float) -> void:
if visible:
fps_graph.queue_redraw()
total_graph.queue_redraw()
cpu_graph.queue_redraw()
gpu_graph.queue_redraw()
# Difference between the last two rendered frames in milliseconds.
var frametime := (Time.get_ticks_usec() - last_tick) * 0.001
frame_history_total.push_back(frametime)
if frame_history_total.size() > HISTORY_NUM_FRAMES:
frame_history_total.pop_front()
# Frametimes are colored following FPS logic (red = 10 FPS, yellow = 60 FPS, green = 110 FPS, cyan = 160 FPS).
# This makes the color gradient non-linear.
frametime_avg = frame_history_total.reduce(sum_func) / frame_history_total.size()
frame_history_total_avg.text = str(frametime_avg).pad_decimals(2)
frame_history_total_avg.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_avg, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
var frametime_min: float = frame_history_total.min()
frame_history_total_min.text = str(frametime_min).pad_decimals(2)
frame_history_total_min.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_min, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
var frametime_max: float = frame_history_total.max()
frame_history_total_max.text = str(frametime_max).pad_decimals(2)
frame_history_total_max.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_max, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
frame_history_total_last.text = str(frametime).pad_decimals(2)
frame_history_total_last.modulate = frame_time_gradient.sample(remap(1000.0 / frametime, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
var viewport_rid := get_viewport().get_viewport_rid()
var frametime_cpu := RenderingServer.viewport_get_measured_render_time_cpu(viewport_rid) + RenderingServer.get_frame_setup_time_cpu()
frame_history_cpu.push_back(frametime_cpu)
if frame_history_cpu.size() > HISTORY_NUM_FRAMES:
frame_history_cpu.pop_front()
frametime_cpu_avg = frame_history_cpu.reduce(sum_func) / frame_history_cpu.size()
frame_history_cpu_avg.text = str(frametime_cpu_avg).pad_decimals(2)
frame_history_cpu_avg.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_cpu_avg, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
var frametime_cpu_min: float = frame_history_cpu.min()
frame_history_cpu_min.text = str(frametime_cpu_min).pad_decimals(2)
frame_history_cpu_min.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_cpu_min, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
var frametime_cpu_max: float = frame_history_cpu.max()
frame_history_cpu_max.text = str(frametime_cpu_max).pad_decimals(2)
frame_history_cpu_max.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_cpu_max, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
frame_history_cpu_last.text = str(frametime_cpu).pad_decimals(2)
frame_history_cpu_last.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_cpu, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
var frametime_gpu := RenderingServer.viewport_get_measured_render_time_gpu(viewport_rid)
frame_history_gpu.push_back(frametime_gpu)
if frame_history_gpu.size() > HISTORY_NUM_FRAMES:
frame_history_gpu.pop_front()
frametime_gpu_avg = frame_history_gpu.reduce(sum_func) / frame_history_gpu.size()
frame_history_gpu_avg.text = str(frametime_gpu_avg).pad_decimals(2)
frame_history_gpu_avg.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_gpu_avg, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
var frametime_gpu_min: float = frame_history_gpu.min()
frame_history_gpu_min.text = str(frametime_gpu_min).pad_decimals(2)
frame_history_gpu_min.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_gpu_min, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
var frametime_gpu_max: float = frame_history_gpu.max()
frame_history_gpu_max.text = str(frametime_gpu_max).pad_decimals(2)
frame_history_gpu_max.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_gpu_max, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
frame_history_gpu_last.text = str(frametime_gpu).pad_decimals(2)
frame_history_gpu_last.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_gpu, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
frames_per_second = 1000.0 / frametime_avg
fps_history.push_back(frames_per_second)
if fps_history.size() > HISTORY_NUM_FRAMES:
fps_history.pop_front()
fps.text = str(floor(frames_per_second)) + " FPS"
var frame_time_color := frame_time_gradient.sample(remap(frames_per_second, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
fps.modulate = frame_time_color
frame_time.text = str(frametime).pad_decimals(2) + " mspf"
frame_time.modulate = frame_time_color
var vsync_string := ""
match DisplayServer.window_get_vsync_mode():
DisplayServer.VSYNC_ENABLED:
vsync_string = "V-Sync"
DisplayServer.VSYNC_ADAPTIVE:
vsync_string = "Adaptive V-Sync"
DisplayServer.VSYNC_MAILBOX:
vsync_string = "Mailbox V-Sync"
if Engine.max_fps > 0 or OS.low_processor_usage_mode:
# Display FPS cap determined by `Engine.max_fps` or low-processor usage mode sleep duration
# (the lowest FPS cap is used).
var low_processor_max_fps := roundi(1000000.0 / OS.low_processor_usage_mode_sleep_usec)
var fps_cap := low_processor_max_fps
if Engine.max_fps > 0:
fps_cap = mini(Engine.max_fps, low_processor_max_fps)
frame_time.text += " (cap: " + str(fps_cap) + " FPS"
if not vsync_string.is_empty():
frame_time.text += " + " + vsync_string
frame_time.text += ")"
else:
if not vsync_string.is_empty():
frame_time.text += " (" + vsync_string + ")"
frame_number.text = "Frame: " + str(Engine.get_frames_drawn())
frame_stats.text = str(
&"Draw calls: %d" % Performance.get_monitor(Performance.RENDER_TOTAL_DRAW_CALLS_IN_FRAME),
&", Primitives: ", int(Performance.get_monitor(Performance.RENDER_TOTAL_PRIMITIVES_IN_FRAME)),
&", VRAM: %d" % snapped(Performance.get_monitor(Performance.RENDER_VIDEO_MEM_USED) * 0.000001, 1.0), &" MB")
last_tick = Time.get_ticks_usec()
## Captures a screenshot and saves it to the user folder in user://screenshots.
func do_screenshot() -> void:
await RenderingServer.frame_post_draw # Wait until frame is drawn
var image := get_viewport().get_texture().get_image()
# Fix image size for true pixel game, too small image output
if image.get_size().x != get_viewport().size.x or image.get_size().y != get_viewport().size.y:
image.resize(get_viewport().size.x, get_viewport().size.y, Image.INTERPOLATE_NEAREST)
if not image:
printerr("Failed to capture screen.")
return
# Ensure the screenshots directory exists
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path("user://screenshots"))
var path := "user://screenshots/screenshot_%s.png" % Time.get_datetime_string_from_system().replace(":", "_")
var err := image.save_png(ProjectSettings.globalize_path(path))
if err == OK:
print_rich("🖵 [color=yellow]Screenshot saved to:[/color] ", ProjectSettings.globalize_path(path))
OS.shell_show_in_file_manager(ProjectSettings.globalize_path(path))
else:
printerr("Failed to save screenshot:", err)
func _on_visibility_changed() -> void:
if visible:
# Reset graphs to prevent them from looking strange before `HISTORY_NUM_FRAMES` frames
# have been drawn.
var frametime_last := (Time.get_ticks_usec() - last_tick) * 0.001
fps_history.resize(HISTORY_NUM_FRAMES)
fps_history.fill(1000.0 / frametime_last)
frame_history_total.resize(HISTORY_NUM_FRAMES)
frame_history_total.fill(frametime_last)
frame_history_cpu.resize(HISTORY_NUM_FRAMES)
var viewport_rid := get_viewport().get_viewport_rid()
frame_history_cpu.fill(RenderingServer.viewport_get_measured_render_time_cpu(viewport_rid) + RenderingServer.get_frame_setup_time_cpu())
frame_history_gpu.resize(HISTORY_NUM_FRAMES)
frame_history_gpu.fill(RenderingServer.viewport_get_measured_render_time_gpu(viewport_rid))
+1
View File
@@ -0,0 +1 @@
uid://cjifa1oa5wtn4
+396
View File
@@ -0,0 +1,396 @@
[gd_scene format=3 uid="uid://cggqb75a8w8r"]
[ext_resource type="Script" uid="uid://cjifa1oa5wtn4" path="res://addons/debug_menu/debug_menu.gd" id="1_p440y"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ki0n8"]
bg_color = Color(0, 0, 0, 0.25098)
[node name="CanvasLayer" type="CanvasLayer" unique_id=414397422 node_paths=PackedStringArray("fps", "frame_time", "frame_number", "frame_stats", "frame_history_total_avg", "frame_history_total_min", "frame_history_total_max", "frame_history_total_last", "frame_history_cpu_avg", "frame_history_cpu_min", "frame_history_cpu_max", "frame_history_cpu_last", "frame_history_gpu_avg", "frame_history_gpu_min", "frame_history_gpu_max", "frame_history_gpu_last", "fps_graph", "total_graph", "cpu_graph", "gpu_graph", "information", "settings")]
process_mode = 3
layer = 128
script = ExtResource("1_p440y")
fps = NodePath("DebugMenu/VBoxContainer/FPS")
frame_time = NodePath("DebugMenu/VBoxContainer/FrameTime")
frame_number = NodePath("DebugMenu/VBoxContainer/FrameNumber")
frame_stats = NodePath("DebugMenu/VBoxContainer/FrameStats")
frame_history_total_avg = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/TotalAvg")
frame_history_total_min = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/TotalMin")
frame_history_total_max = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/TotalMax")
frame_history_total_last = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/TotalLast")
frame_history_cpu_avg = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/CPUAvg")
frame_history_cpu_min = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/CPUMin")
frame_history_cpu_max = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/CPUMax")
frame_history_cpu_last = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/CPULast")
frame_history_gpu_avg = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/GPUAvg")
frame_history_gpu_min = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/GPUMin")
frame_history_gpu_max = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/GPUMax")
frame_history_gpu_last = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/GPULast")
fps_graph = NodePath("DebugMenu/VBoxContainer/FPSGraph/Graph")
total_graph = NodePath("DebugMenu/VBoxContainer/TotalGraph/Graph")
cpu_graph = NodePath("DebugMenu/VBoxContainer/CPUGraph/Graph")
gpu_graph = NodePath("DebugMenu/VBoxContainer/GPUGraph/Graph")
information = NodePath("DebugMenu/VBoxContainer/Information")
settings = NodePath("DebugMenu/VBoxContainer/Settings")
[node name="DebugMenu" type="Control" parent="." unique_id=346480719]
custom_minimum_size = Vector2(400, 400)
layout_mode = 3
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -416.0
offset_top = 8.0
offset_right = -16.0
offset_bottom = 408.0
grow_horizontal = 0
size_flags_horizontal = 8
size_flags_vertical = 4
mouse_filter = 2
[node name="VBoxContainer" type="VBoxContainer" parent="DebugMenu" unique_id=947897244]
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -300.0
offset_bottom = 374.0
grow_horizontal = 0
mouse_filter = 2
theme_override_constants/separation = 0
[node name="FPS" type="Label" parent="DebugMenu/VBoxContainer" unique_id=997178716 groups=["debug_menu_label"]]
modulate = Color(0, 1, 0, 1)
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/line_spacing = 0
theme_override_constants/outline_size = 5
theme_override_font_sizes/font_size = 18
text = "60 FPS"
horizontal_alignment = 2
[node name="FrameTime" type="Label" parent="DebugMenu/VBoxContainer" unique_id=1892289036 groups=["debug_menu_label"]]
modulate = Color(0, 1, 0, 1)
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "16.67 mspf (cap: 123 FPS + Adaptive V-Sync)"
horizontal_alignment = 2
[node name="FrameNumber" type="Label" parent="DebugMenu/VBoxContainer" unique_id=1790704468 groups=["debug_menu_label"]]
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "Frame: 1234"
horizontal_alignment = 2
[node name="FrameStats" type="Label" parent="DebugMenu/VBoxContainer" unique_id=805680288 groups=["debug_menu_label"]]
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "Stats:"
horizontal_alignment = 2
[node name="FrameTimeHistory" type="GridContainer" parent="DebugMenu/VBoxContainer" unique_id=209708847]
layout_mode = 2
size_flags_horizontal = 8
mouse_filter = 2
theme_override_constants/h_separation = 0
theme_override_constants/v_separation = 0
columns = 5
[node name="Spacer" type="Control" parent="DebugMenu/VBoxContainer/FrameTimeHistory" unique_id=1653573840]
custom_minimum_size = Vector2(60, 0)
layout_mode = 2
mouse_filter = 2
[node name="AvgHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory" unique_id=1515213830 groups=["debug_menu_header", "debug_menu_label"]]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "Average"
horizontal_alignment = 2
[node name="MinHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory" unique_id=839075582 groups=["debug_menu_header", "debug_menu_label"]]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "Best"
horizontal_alignment = 2
[node name="MaxHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory" unique_id=2001207057 groups=["debug_menu_header", "debug_menu_label"]]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "Worst"
horizontal_alignment = 2
[node name="LastHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory" unique_id=145277658 groups=["debug_menu_header", "debug_menu_label"]]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "Last"
horizontal_alignment = 2
[node name="TotalHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory" unique_id=684135416 groups=["debug_menu_label"]]
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "Total:"
horizontal_alignment = 2
[node name="TotalAvg" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory" unique_id=1148307968 groups=["debug_menu_label"]]
modulate = Color(0, 1, 0, 1)
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "123.45"
horizontal_alignment = 2
[node name="TotalMin" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory" unique_id=807706427 groups=["debug_menu_label"]]
modulate = Color(0, 1, 0, 1)
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "123.45"
horizontal_alignment = 2
[node name="TotalMax" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory" unique_id=1552303690 groups=["debug_menu_label"]]
modulate = Color(0, 1, 0, 1)
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "123.45"
horizontal_alignment = 2
[node name="TotalLast" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory" unique_id=614568271 groups=["debug_menu_label"]]
modulate = Color(0, 1, 0, 1)
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "123.45"
horizontal_alignment = 2
[node name="CPUHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory" unique_id=180318023 groups=["debug_menu_label"]]
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "CPU:"
horizontal_alignment = 2
[node name="CPUAvg" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory" unique_id=1638494409 groups=["debug_menu_label"]]
modulate = Color(0, 1, 0, 1)
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "123.45"
horizontal_alignment = 2
[node name="CPUMin" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory" unique_id=1525286902 groups=["debug_menu_label"]]
modulate = Color(0, 1, 0, 1)
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "12.34"
horizontal_alignment = 2
[node name="CPUMax" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory" unique_id=1696498522 groups=["debug_menu_label"]]
modulate = Color(0, 1, 0, 1)
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "123.45"
horizontal_alignment = 2
[node name="CPULast" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory" unique_id=2040832325 groups=["debug_menu_label"]]
modulate = Color(0, 1, 0, 1)
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "123.45"
horizontal_alignment = 2
[node name="GPUHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory" unique_id=15556991 groups=["debug_menu_label"]]
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "GPU:"
horizontal_alignment = 2
[node name="GPUAvg" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory" unique_id=1755091907 groups=["debug_menu_label"]]
modulate = Color(0, 1, 0, 1)
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "123.45"
horizontal_alignment = 2
[node name="GPUMin" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory" unique_id=1225652947 groups=["debug_menu_label"]]
modulate = Color(0, 1, 0, 1)
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "1.23"
horizontal_alignment = 2
[node name="GPUMax" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory" unique_id=1647337652 groups=["debug_menu_label"]]
modulate = Color(0, 1, 0, 1)
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "123.45"
horizontal_alignment = 2
[node name="GPULast" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory" unique_id=900089497 groups=["debug_menu_label"]]
modulate = Color(0, 1, 0, 1)
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "123.45"
horizontal_alignment = 2
[node name="FPSGraph" type="HBoxContainer" parent="DebugMenu/VBoxContainer" unique_id=2129193079]
layout_mode = 2
mouse_filter = 2
alignment = 2
[node name="Title" type="Label" parent="DebugMenu/VBoxContainer/FPSGraph" unique_id=1484247768 groups=["debug_menu_label"]]
custom_minimum_size = Vector2(0, 27)
layout_mode = 2
size_flags_horizontal = 8
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "FPS: "
vertical_alignment = 1
[node name="Graph" type="Panel" parent="DebugMenu/VBoxContainer/FPSGraph" unique_id=1324384502 groups=["debug_menu_graph"]]
custom_minimum_size = Vector2(150, 25)
layout_mode = 2
size_flags_vertical = 0
mouse_filter = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_ki0n8")
[node name="TotalGraph" type="HBoxContainer" parent="DebugMenu/VBoxContainer" unique_id=1757012220]
layout_mode = 2
mouse_filter = 2
alignment = 2
[node name="Title" type="Label" parent="DebugMenu/VBoxContainer/TotalGraph" unique_id=1749839410 groups=["debug_menu_label"]]
custom_minimum_size = Vector2(0, 27)
layout_mode = 2
size_flags_horizontal = 8
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "Total: "
vertical_alignment = 1
[node name="Graph" type="Panel" parent="DebugMenu/VBoxContainer/TotalGraph" unique_id=1880596791 groups=["debug_menu_graph"]]
custom_minimum_size = Vector2(150, 25)
layout_mode = 2
size_flags_vertical = 0
mouse_filter = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_ki0n8")
[node name="CPUGraph" type="HBoxContainer" parent="DebugMenu/VBoxContainer" unique_id=705142964]
layout_mode = 2
mouse_filter = 2
alignment = 2
[node name="Title" type="Label" parent="DebugMenu/VBoxContainer/CPUGraph" unique_id=148522575 groups=["debug_menu_label"]]
custom_minimum_size = Vector2(0, 27)
layout_mode = 2
size_flags_horizontal = 8
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "CPU: "
vertical_alignment = 1
[node name="Graph" type="Panel" parent="DebugMenu/VBoxContainer/CPUGraph" unique_id=964269529 groups=["debug_menu_graph"]]
custom_minimum_size = Vector2(150, 25)
layout_mode = 2
size_flags_vertical = 0
mouse_filter = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_ki0n8")
[node name="GPUGraph" type="HBoxContainer" parent="DebugMenu/VBoxContainer" unique_id=796931287]
layout_mode = 2
mouse_filter = 2
alignment = 2
[node name="Title" type="Label" parent="DebugMenu/VBoxContainer/GPUGraph" unique_id=1665497332 groups=["debug_menu_label"]]
custom_minimum_size = Vector2(0, 27)
layout_mode = 2
size_flags_horizontal = 8
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "GPU: "
vertical_alignment = 1
[node name="Graph" type="Panel" parent="DebugMenu/VBoxContainer/GPUGraph" unique_id=168534966 groups=["debug_menu_graph"]]
custom_minimum_size = Vector2(150, 25)
layout_mode = 2
size_flags_vertical = 0
mouse_filter = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_ki0n8")
[node name="Information" type="Label" parent="DebugMenu/VBoxContainer" unique_id=811551518 groups=["debug_menu_label"]]
modulate = Color(1, 1, 1, 0.752941)
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "12th Gen Intel(R) Core(TM) i0-1234K
Windows 12 64-bit (double precision), Vulkan 1.2.34
NVIDIA GeForce RTX 1234, 123.45.67"
horizontal_alignment = 2
[node name="Settings" type="Label" parent="DebugMenu/VBoxContainer" unique_id=857946198 groups=["debug_menu_label"]]
modulate = Color(0.8, 0.84, 1, 0.752941)
layout_mode = 2
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 12
text = "Project Version: 1.2.3
Rendering Method: Forward+
Window: 1234×567, Viewport: 1234×567
3D Scale (FSR 1.0): 100% = 1234×567
3D Antialiasing: TAA + 2× MSAA + FXAA
SSR: 123 Steps
SSAO: On
SSIL: On
SDFGI: 1 Cascades
Glow: On
Volumetric Fog: On
2D Antialiasing: 2× MSAA"
horizontal_alignment = 2
[connection signal="visibility_changed" from="." to="." method="_on_visibility_changed"]
+7
View File
@@ -0,0 +1,7 @@
[plugin]
name="Antz's Debug Menu"
description="In-game debug menu displaying performance metrics and hardware information"
author="Calinou/Antz"
version="1.3.0"
script="plugin.gd"
+50
View File
@@ -0,0 +1,50 @@
@tool
extends EditorPlugin
func _enter_tree() -> void:
if not ProjectSettings.has_setting("autoload/DebugMenu"):
add_autoload_singleton("DebugMenu", "res://addons/debug_menu/debug_menu.tscn")
if not ProjectSettings.has_setting("application/config/version") or ProjectSettings.get_setting("application/config/version") == "":
ProjectSettings.set_setting("application/config/version", "1.0.0")
print('Debug Menu: Setting "application/config/version" was missing or empty and has been set to "1.0.0".')
ProjectSettings.add_property_info({
name = "application/config/version",
type = TYPE_STRING,
})
ProjectSettings.save()
# Add new Project Setting for font_size
if not ProjectSettings.has_setting("DebugMenu/settings/font_size"):
ProjectSettings.set_setting("DebugMenu/settings/font_size", 12)
var property_info = {
"name": "DebugMenu/settings/font_size",
"type": TYPE_INT,
"hint": PROPERTY_HINT_RANGE,
"hint_string" : "3,72"
}
ProjectSettings.add_property_info(property_info)
ProjectSettings.set_initial_value("DebugMenu/settings/font_size", 12)
ProjectSettings.save()
# Add new Project Setting for startup visibility
if not ProjectSettings.has_setting("DebugMenu/settings/startup_visibility"):
ProjectSettings.set_setting("DebugMenu/settings/startup_visibility", 0)
property_info = {
"name": "DebugMenu/settings/startup_visibility",
"type": TYPE_INT ,
"hint": PROPERTY_HINT_ENUM,
"hint_string" : "hidden,visible compact,visible detailed"
}
ProjectSettings.add_property_info(property_info)
ProjectSettings.set_initial_value("DebugMenu/settings/startup_visibility", 0)
ProjectSettings.save()
func _exit_tree() -> void:
remove_autoload_singleton("DebugMenu")
# Don't remove the project setting's value and input map action,
# as the plugin may be re-enabled in the future.
+1
View File
@@ -0,0 +1 @@
uid://dcxwqe5yhnqon
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Jan Thomä
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+22
View File
@@ -0,0 +1,22 @@
@tool
@icon("all_of_guard.svg")
## A composite guard that is satisfied when all of its guards are satisfied.
class_name AllOfGuard
extends Guard
## The guards that need to be satisified. When empty, returns true.
@export var guards:Array[Guard] = []
func is_satisfied(context_transition:Transition, context_state:StateChartState) -> bool:
for guard in guards:
if not guard.is_satisfied(context_transition, context_state):
return false
return true
func get_supported_trigger_types() -> int:
var supported_trigger_types:int = StateChart.TriggerType.NONE
for guard in guards:
supported_trigger_types |= guard.get_supported_trigger_types()
return supported_trigger_types
@@ -0,0 +1 @@
uid://bxprlw40wl0c3
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ef9f4c" d="M 8 1 C 6.6741569 1 5.4373689 1.3647032 4.3828125 2 L 2 2 C 1.4460018 2 1 2.4460018 1 3 L 1 8 C 1 11.877988 4.1220117 15 8 15 C 11.877988 15 15 11.877988 15 8 L 15 3 C 15 2.4460018 14.553997 2 14 2 L 11.617188 2 C 10.562631 1.3647032 9.3258431 1 8 1 z M 8 3 C 8.6661496 3 9.341059 3.1508527 9.9296875 3.5429688 C 10.518315 3.9350846 11 4.6664108 11 5.5 C 11 6.9485493 9.7463158 7.7230237 9.5 7.8652344 C 9.2154316 8.0295298 8.9412084 8.1318679 8.6484375 8.234375 C 8.6681764 8.2541435 8.6871907 8.2731281 8.7070312 8.2929688 C 8.9070123 8.4929497 9.2811078 8.799985 9.7128906 9.1347656 C 9.9017773 8.6062498 10 8.1127633 10 8 L 12 8 C 12 8.6954536 11.795285 9.5095092 11.384766 10.361328 C 12.044312 10.824409 12.554688 11.167969 12.554688 11.167969 L 11.445312 12.832031 C 11.445313 12.832031 10.925524 12.485639 10.251953 12.013672 C 9.6766003 12.579442 8.9294983 13 8 13 C 6.8333369 13 5.8637218 12.710781 5.1503906 12.175781 C 4.4370594 11.640784 4 10.833331 4 10 C 4 9.2388379 4.3300936 8.5580855 4.765625 8.0820312 C 4.9713648 7.857149 5.1975274 7.6693505 5.4316406 7.5058594 C 5.1796661 7.0623217 5 6.566579 5 6 C 5 5.1666691 5.3858355 4.4501019 5.9179688 3.9179688 C 6.450102 3.3858354 7.1666691 3 8 3 z M 8 5 C 7.8333339 5 7.5498958 5.1141669 7.3320312 5.3320312 C 7.1141669 5.5498958 7 5.8333339 7 6 C 7 6.1264072 7.0667919 6.3402314 7.234375 6.6171875 C 7.3875379 6.5601425 7.53671 6.5060056 7.6738281 6.4570312 C 8.0933833 6.3071794 8.4471687 6.1652679 8.5 6.1347656 C 8.8373346 5.9400052 9 5.958085 9 5.5 C 9 5.3320472 8.9816816 5.3145276 8.8203125 5.2070312 C 8.6589434 5.0995351 8.3338474 5 8 5 z M 6.6777344 9.0839844 C 6.4967446 9.2009263 6.3438607 9.3203258 6.2402344 9.4335938 C 6.0610842 9.6294125 6 9.7611651 6 10 C 6 10.166667 6.0629436 10.359219 6.3496094 10.574219 C 6.6362752 10.789219 7.1666691 11 8 11 C 8.202884 11 8.4004221 10.931957 8.5917969 10.804688 C 8.0988221 10.427669 7.6364251 10.050488 7.2929688 9.7070312 C 7.0901233 9.5041858 6.8823271 9.2968491 6.6777344 9.0839844 z "/></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ux4ia8xhhjrx"
path="res://.godot/imported/all_of_guard.svg-49642db22a4a20844b2d39e67c930c8b.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/godot_state_charts/all_of_guard.svg"
dest_files=["res://.godot/imported/all_of_guard.svg-49642db22a4a20844b2d39e67c930c8b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=false
@@ -0,0 +1,63 @@
@tool
@icon("animation_player_state.svg")
class_name AnimationPlayerState
extends AtomicState
## Animation player that this state will use.
@export_node_path("AnimationPlayer") var animation_player: NodePath:
set(value):
animation_player = value
update_configuration_warnings()
## The name of the animation that should be played when this state is entered.
## When this is empty, the name of this state will be used.
@export var animation_name: StringName = ""
## A custom blend time for the animation. The default value of -1.0 will use the
## default blend time of the animation player.
@export var custom_blend: float = -1.0
## A custom speed for the animation. Use negative values to play the animation
## backwards.
@export var custom_speed: float = 1.0
## Whether the animation should be played from the end.
@export var from_end: bool = false
var _animation_player: AnimationPlayer
func _ready() -> void:
if Engine.is_editor_hint():
return
super._ready()
_animation_player = get_node_or_null(animation_player)
if not is_instance_valid(_animation_player):
push_error("The animation player is invalid. This node will not work.")
func _state_enter(transition_target:StateChartState) -> void:
super._state_enter(transition_target)
if not is_instance_valid(_animation_player):
return
var target_animation := animation_name
if target_animation == "":
target_animation = get_name()
if _animation_player.current_animation == target_animation and _animation_player.is_playing():
return
_animation_player.play(target_animation, custom_blend, custom_speed, from_end)
func _get_configuration_warnings() -> PackedStringArray:
var warnings := super._get_configuration_warnings()
warnings.append("This node is deprecated and will be removed in a future version.")
if animation_player.is_empty():
warnings.append("No animation player is set.")
elif get_node_or_null(animation_player) == null:
warnings.append("The animation player path is invalid.")
return warnings
@@ -0,0 +1 @@
uid://b5vawfqxn7cyf
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ef9f4c" d="M 3 0 C 1.3380034 0 0 1.3380034 0 3 L 0 13 C 0 14.661996 1.3380034 16 3 16 L 13 16 C 14.661996 16 16 14.661996 16 13 L 16 3 C 16 1.3380034 14.661996 0 13 0 L 3 0 z M 3 1 L 13 1 C 14.107998 1 15 1.8920022 15 3 L 15 13 C 15 14.107998 14.107998 15 13 15 L 3 15 C 1.8920022 15 1 14.107998 1 13 L 1 3 C 1 1.8920022 1.8920022 1 3 1 z M 8 3 A 5 5 0 0 0 3 8 A 5 5 0 0 0 8 13 A 5 5 0 0 0 13 8 A 5 5 0 0 0 8 3 z M 6 5 L 11 8 L 6 11 L 6 5 z "/></svg>

After

Width:  |  Height:  |  Size: 526 B

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b3m20gsesp4i0"
path="res://.godot/imported/animation_player_state.svg-1acd03c414690dd7446458c5293935cb.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/godot_state_charts/animation_player_state.svg"
dest_files=["res://.godot/imported/animation_player_state.svg-1acd03c414690dd7446458c5293935cb.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=false
@@ -0,0 +1,63 @@
@tool
@icon("animation_tree_state.svg")
class_name AnimationTreeState
extends AtomicState
## Animation tree that this state will use.
@export_node_path("AnimationTree") var animation_tree:NodePath:
set(value):
animation_tree = value
update_configuration_warnings()
## The name of the state that should be activated in the animation tree
## when this state is entered. If this is empty, the name of this state
## will be used.
@export var state_name:StringName = ""
var _animation_tree_state_machine:AnimationNodeStateMachinePlayback
func _ready() -> void:
if Engine.is_editor_hint():
return
super._ready()
_animation_tree_state_machine = null
var the_tree := get_node_or_null(animation_tree)
if is_instance_valid(the_tree):
var state_machine = the_tree.get("parameters/playback")
if state_machine is AnimationNodeStateMachinePlayback:
_animation_tree_state_machine = state_machine
else:
push_error("The animation tree does not have a state machine as root node. This node will not work.")
else:
push_error("The animation tree is invalid. This node will not work.")
func _state_enter(transition_target:StateChartState) -> void:
super._state_enter(transition_target)
if not is_instance_valid(_animation_tree_state_machine):
return
var target_state := state_name
if target_state == "":
target_state = get_name()
# mirror this state to the animation tree
_animation_tree_state_machine.travel(target_state)
func _get_configuration_warnings() -> PackedStringArray:
var warnings := super._get_configuration_warnings()
warnings.append("This node is deprecated and will be removed in a future version.")
if animation_tree.is_empty():
warnings.append("No animation tree is set.")
elif get_node_or_null(animation_tree) == null:
warnings.append("The animation tree path is invalid.")
return warnings
@@ -0,0 +1 @@
uid://488xerv8d0xh
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ef9f4c" d="M 3 0 C 1.3380051 0 0 1.3380051 0 3 L 0 13 C 0 14.661994 1.3380051 16 3 16 L 13 16 C 14.661994 16 16 14.661994 16 13 L 16 3 C 16 1.3380051 14.661994 0 13 0 L 3 0 z M 3 1 L 13 1 C 14.107997 1 15 1.8920033 15 3 L 15 13 C 15 14.107997 14.107997 15 13 15 L 3 15 C 1.8920033 15 1 14.107997 1 13 L 1 3 C 1 1.8920033 1.8920033 1 3 1 z M 3 3 L 3 13 L 4 13 L 4 11 L 5 11 L 5 13 L 11 13 L 11 11 L 12 11 L 12 13 L 13 13 L 13 3 L 12 3 L 12 4 L 11 4 L 11 3 L 5 3 L 5 4 L 4 4 L 4 3 L 3 3 z M 4 5 L 5 5 L 5 7 L 4 7 L 4 5 z M 6 5 L 7 5 L 7 8 L 9 8 L 9 9 L 8 9 L 8 10 L 10 10 L 10 11.013672 L 7 11.013672 L 7 10 L 7 9 L 6 9 L 6 5 z M 11 5 L 12 5 L 12 7 L 11 7 L 11 5 z M 4 8 L 5 8 L 5 10 L 4 10 L 4 8 z M 11 8 L 12 8 L 12 10 L 11 10 L 11 8 z "/></svg>

After

Width:  |  Height:  |  Size: 821 B

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://3wqyduuj0fq"
path="res://.godot/imported/animation_tree_state.svg-b99077fc178cfa1e23b2c854c7735c4a.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/godot_state_charts/animation_tree_state.svg"
dest_files=["res://.godot/imported/animation_tree_state.svg-b99077fc178cfa1e23b2c854c7735c4a.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=false
+21
View File
@@ -0,0 +1,21 @@
@tool
@icon("any_of_guard.svg")
## A composite guard, that is satisfied if any of the guards are satisfied.
class_name AnyOfGuard
extends Guard
## The guards of which at least one must be satisfied. If empty, this guard is not satisfied.
@export var guards: Array[Guard] = []
func is_satisfied(context_transition:Transition, context_state:StateChartState) -> bool:
for guard in guards:
if guard.is_satisfied(context_transition, context_state):
return true
return false
func get_supported_trigger_types() -> int:
var supported_trigger_types:int = StateChart.TriggerType.NONE
for guard in guards:
supported_trigger_types |= guard.get_supported_trigger_types()
return supported_trigger_types
@@ -0,0 +1 @@
uid://duv2yyvlunsi8
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ef9f4c" d="M 8 1 C 6.6741569 1 5.4373689 1.3647032 4.3828125 2 L 2 2 C 1.4460018 2 1 2.4460018 1 3 L 1 8 C 1 11.877988 4.1220117 15 8 15 C 11.877988 15 15 11.877988 15 8 L 15 3 C 15 2.4460018 14.553997 2 14 2 L 11.617188 2 C 10.562631 1.3647032 9.3258431 1 8 1 z M 5 4 L 7 4 L 7 12 L 5 12 L 5 4 z M 9 4 L 11 4 L 11 12 L 9 12 L 9 4 z "/></svg>

After

Width:  |  Height:  |  Size: 418 B

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dbf5ogymlonu4"
path="res://.godot/imported/any_of_guard.svg-3b1aa026a997dbfebde2cc5993b5c820.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/godot_state_charts/any_of_guard.svg"
dest_files=["res://.godot/imported/any_of_guard.svg-3b1aa026a997dbfebde2cc5993b5c820.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=false
+25
View File
@@ -0,0 +1,25 @@
@tool
@icon("atomic_state.svg")
## This is a state that has no sub-states.
class_name AtomicState
extends StateChartState
func _handle_transition(transition:Transition, source:StateChartState) -> void:
# resolve the target state
var target := transition.resolve_target()
if target == null:
push_error("The target state '" + str(transition.to) + "' of the transition from '" + source.name + "' is not a state.")
return
# atomic states cannot transition, so we need to ask the parent
# ask the parent
get_parent()._handle_transition(transition, source)
func _get_configuration_warnings() -> PackedStringArray:
var warnings := super._get_configuration_warnings()
# check if we have any child nodes which are not transitions
for child in get_children():
if child is StateChartState:
warnings.append("Atomic states cannot have child states. These will be ignored.")
break
return warnings
@@ -0,0 +1 @@
uid://cytafq8i1y8qm
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ef9f4c" d="M 3 0 C 1.3380034 0 0 1.3380034 0 3 L 0 13 C 0 14.661996 1.3380034 16 3 16 L 13 16 C 14.661996 16 16 14.661996 16 13 L 16 3 C 16 1.3380034 14.661996 0 13 0 L 3 0 z M 3 1 L 13 1 C 14.107998 1 15 1.8920022 15 3 L 15 13 C 15 14.107998 14.107998 15 13 15 L 3 15 C 1.8920022 15 1 14.107998 1 13 L 1 3 C 1 1.8920022 1.8920022 1 3 1 z M 8 3 A 5 5 0 0 0 3 8 A 5 5 0 0 0 8 13 A 5 5 0 0 0 13 8 A 5 5 0 0 0 8 3 z "/></svg>

After

Width:  |  Height:  |  Size: 498 B

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c4ojtah20jtxc"
path="res://.godot/imported/atomic_state.svg-5ab16e5747cef5b5980c4bf84ef9b1af.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/godot_state_charts/atomic_state.svg"
dest_files=["res://.godot/imported/atomic_state.svg-5ab16e5747cef5b5980c4bf84ef9b1af.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=false
+268
View File
@@ -0,0 +1,268 @@
@tool
@icon("compound_state.svg")
## A compound state is a state that has multiple sub-states of which exactly one can
## be active at any given time.
class_name CompoundState
extends StateChartState
## Called when a child state is entered.
signal child_state_entered()
## Called when a child state is exited.
signal child_state_exited()
## The initial state which should be activated when this state is activated.
@export_node_path("StateChartState") var initial_state:NodePath:
get:
return initial_state
set(value):
initial_state = value
update_configuration_warnings()
## The currently active substate.
var _active_state:StateChartState = null
## The initial state
@onready var _initial_state:StateChartState = get_node_or_null(initial_state)
## The history states of this compound state.
var _history_states:Array[HistoryState] = []
## Whether any of the history states needs a deep history.
var _needs_deep_history:bool = false
func _init() -> void:
# subscribe to the child_entered_tree signal in edit mode so we can
# automatically set the initial state when a new sub-state is added
if Engine.is_editor_hint():
child_entered_tree.connect(
func(child:Node):
# when a child is added in the editor and the child is a state
# and we don't have an initial state yet, set the initial state
# to the newly added child
if child is StateChartState and initial_state.is_empty():
# the newly added node may have a random name now,
# so we need to defer the call to build a node path
# to the next frame, so the editor has time to rename
# the node to its final name
(func(): initial_state = get_path_to(child)).call_deferred()
)
func _state_init() -> void:
super._state_init()
# check if we have any history states
for child in get_children():
if child is HistoryState:
var child_as_history_state:HistoryState = child as HistoryState
_history_states.append(child_as_history_state)
# remember if any of the history states needs a deep history
_needs_deep_history = _needs_deep_history or child_as_history_state.deep
# initialize all substates. find all children of type State and call _state_init on them.
for child in get_children():
if child is StateChartState:
var child_as_state:StateChartState = child as StateChartState
child_as_state._state_init()
child_as_state.state_entered.connect(func(): child_state_entered.emit())
child_as_state.state_exited.connect(func(): child_state_exited.emit())
func _state_enter(transition_target:StateChartState) -> void:
super._state_enter(transition_target)
# activate the initial state _unless_ one of these are true
# - the transition target is a descendant of this state
# - we already have an active state because entering the state triggered an immediate transition to a child state
# - we are no longer active becasue entering the state triggered an immediate transition to some other state
var target_is_descendant := false
if transition_target != null and is_ancestor_of(transition_target):
target_is_descendant = true
if not target_is_descendant and not is_instance_valid(_active_state) and _state_active:
if _initial_state != null:
if _initial_state is HistoryState:
_restore_history_state(_initial_state)
else:
_active_state = _initial_state
_active_state._state_enter(null)
else:
push_error("No initial state set for state '" + name + "'.")
func _state_step() -> void:
super._state_step()
if _active_state != null:
_active_state._state_step()
func _state_save(saved_state:SavedState, child_levels:int = -1) -> void:
super._state_save(saved_state, child_levels)
# in addition save all history states, as they are never active and normally would not be saved
var parent := saved_state.get_substate_or_null(self)
if parent == null:
push_error("Probably a bug: The state of '" + name + "' was not saved.")
return
for history_state in _history_states:
history_state._state_save(parent, child_levels)
func _state_restore(saved_state:SavedState, child_levels:int = -1) -> void:
super._state_restore(saved_state, child_levels)
# in addition check if we are now active and if so determine the current active state
if active:
# find the currently active child
for child in get_children():
if child is StateChartState and child.active:
_active_state = child
break
func _state_exit() -> void:
# if we have any history states, we need to save the current active state
if _history_states.size() > 0:
var saved_state := SavedState.new()
# we save the entire hierarchy if any of the history states needs a deep history
# otherwise we only save this level. This way we can save memory and processing time
_state_save(saved_state, -1 if _needs_deep_history else 1)
# now save the saved state in all history states
for history_state in _history_states:
# when saving history it's ok when we save deep history in a history state that doesn't need it
# because at restore time we will use the state's deep flag to determine if we need to restore
# the entire hierarchy or just this level. This way we don't need multiple copies of the same
# state hierarchy.
history_state.history = saved_state
# deactivate the current state
if _active_state != null:
_active_state._state_exit()
_active_state = null
super._state_exit()
func _process_transitions(trigger_type:StateChart.TriggerType, event:StringName = "") -> bool:
if not active:
return false
# forward to the active state
if is_instance_valid(_active_state):
if _active_state._process_transitions(trigger_type, event):
# emit the event_received signal when the trigger type is event
if trigger_type == StateChart.TriggerType.EVENT:
self.event_received.emit(event)
return true
# if the event was not handled by the active state, we handle it here
# base class will also emit the event_received signal
return super._process_transitions(trigger_type, event)
func _handle_transition(transition:Transition, source:StateChartState) -> void:
# print("CompoundState._handle_transition: " + name + " from " + source.name + " to " + str(transition.to))
# resolve the target state
var target := transition.resolve_target()
if target == null:
push_error("The target state '" + str(transition.to) + "' of the transition from '" + source.name + "' is not a state.")
return
# the target state can be
# 0. this state. in this case exit this state and re-enter it. This can happen when
# a child state transfers to its parent state.
# 1. a direct child of this state. this is the easy case in which
# we will deactivate the current _active_state and activate the target
# 2. a descendant of this state. in this case we find the direct child which
# is the ancestor of the target state, activate it and then ask it to perform
# the transition.
# 3. no descendant of this state. in this case, we ask our parent state to
# perform the transition
if target == self:
# exit this state and re-enter it
_state_exit()
_state_enter(target)
return
if target in get_children():
# all good, now first deactivate the current state
if is_instance_valid(_active_state):
_active_state._state_exit()
# now check if the target is a history state, if this is the
# case, we need to restore the saved state
if target is HistoryState:
_restore_history_state(target)
return
# else, just activate the target state
_active_state = target
_active_state._state_enter(target)
return
if self.is_ancestor_of(target):
# find the child which is the ancestor of the new target.
for child in get_children():
if child is StateChartState and child.is_ancestor_of(target):
# found it.
# change active state if necessary
if _active_state != child:
if is_instance_valid(_active_state):
_active_state._state_exit()
_active_state = child
# give the transition target because we will send
# the transition to the child state right after we activate it.
# this avoids the child needlessly entering the initial state
_active_state._state_enter(target)
# ask child to handle the transition
child._handle_transition(transition, source)
return
return
# ask the parent
get_parent()._handle_transition(transition, source)
func _restore_history_state(target:HistoryState) -> void:
# print("Target is history state, restoring saved state.")
var saved_state := target.history
if saved_state != null:
# restore the saved state
_state_restore(saved_state, -1 if target.deep else 1)
return
# print("No history saved so far, activating default state.")
# if we don't have history, we just activate the default state
var default_state := target.get_node_or_null(target.default_state) as StateChartState
if is_instance_valid(default_state):
_active_state = default_state
_active_state._state_enter(null)
return
else:
push_error("The default state '" + str(target.default_state) + "' of the history state '" + target.name + "' cannot be found.")
return
func _get_configuration_warnings() -> PackedStringArray:
var warnings := super._get_configuration_warnings()
# count the amount of child states
var child_count := 0
for child in get_children():
if child is StateChartState:
child_count += 1
if child_count < 1:
warnings.append("Compound states should have at least one child state.")
elif child_count < 2:
warnings.append("Compound states with only one child state are not very useful. Consider adding more child states or removing this compound state.")
var the_initial_state := get_node_or_null(initial_state)
if not is_instance_valid(the_initial_state):
warnings.append("Initial state could not be resolved, is the path correct?")
elif the_initial_state.get_parent() != self:
warnings.append("Initial state must be a direct child of this compound state.")
return warnings
@@ -0,0 +1 @@
uid://jk2jm1g6q853
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ef9f4c" d="M 3 0 C 1.3380034 0 0 1.3380034 0 3 L 0 13 C 0 14.661996 1.3380034 16 3 16 L 13 16 C 14.661996 16 16 14.661996 16 13 L 16 3 C 16 1.3380034 14.661996 0 13 0 L 3 0 z M 3 1 L 13 1 C 14.107998 1 15 1.8920022 15 3 L 15 13 C 15 14.107998 14.107998 15 13 15 L 3 15 C 1.8920022 15 1 14.107998 1 13 L 1 3 C 1 1.8920022 1.8920022 1 3 1 z M 8 2.5 A 2.5 2.5 0 0 0 5.5 5 A 2.5 2.5 0 0 0 5.9941406 6.4804688 L 4.8476562 8.4726562 A 2.5454545 2.5454545 0 0 0 4.5449219 8.4550781 A 2.5454545 2.5454545 0 0 0 2 11 A 2.5454545 2.5454545 0 0 0 4.5449219 13.544922 A 2.5454545 2.5454545 0 0 0 6.8828125 12 L 9.1953125 12 A 2.5 2.5 0 0 0 11.5 13.544922 A 2.5 2.5 0 0 0 14 11.044922 A 2.5 2.5 0 0 0 11.5 8.5449219 A 2.5 2.5 0 0 0 11.236328 8.5644531 L 10.013672 6.46875 A 2.5 2.5 0 0 0 10.5 5 A 2.5 2.5 0 0 0 8 2.5 z M 8.2890625 7.4785156 L 9.5 9.5546875 A 2.5 2.5 0 0 0 9.2304688 10 L 6.8847656 10 A 2.5454545 2.5454545 0 0 0 6.5800781 9.4707031 L 7.7265625 7.4804688 A 2.5 2.5 0 0 0 8 7.5 A 2.5 2.5 0 0 0 8.2890625 7.4785156 z "/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bbudjoa3ds4qj"
path="res://.godot/imported/compound_state.svg-84780d78ec1f15e1cbb9d20f4df031a7.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/godot_state_charts/compound_state.svg"
dest_files=["res://.godot/imported/compound_state.svg-84780d78ec1f15e1cbb9d20f4df031a7.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=false
@@ -0,0 +1,66 @@
// ReSharper disable once CheckNamespace
namespace GodotStateCharts
{
using System;
using Godot;
/// <summary>
/// Wrapper around the compound state node.
/// </summary>
public class CompoundState : StateChartState
{
/// <summary>
/// Called when a child state is entered.
/// </summary>
public event Action ChildStateEntered
{
add => Wrapped.Connect(SignalName.ChildStateEntered, Callable.From(value));
remove => Wrapped.Disconnect(SignalName.ChildStateEntered, Callable.From(value));
}
/// <summary>
/// Called when a child state is exited.
/// </summary>
public event Action ChildStateExited
{
add => Wrapped.Connect(SignalName.ChildStateExited, Callable.From(value));
remove => Wrapped.Disconnect(SignalName.ChildStateExited, Callable.From(value));
}
private CompoundState(Node wrapped) : base(wrapped)
{
}
/// <summary>
/// Creates a wrapper object around the given node and verifies that the node
/// is actually a compound state. The wrapper object can then be used to interact
/// with the compound state chart from C#.
/// </summary>
/// <param name="state">the node that is the state</param>
/// <returns>a State wrapper.</returns>
/// <throws>ArgumentException if the node is not a state.</throws>
public new static CompoundState Of(Node state)
{
if (state.GetScript().As<Script>() is not GDScript gdScript ||
!gdScript.ResourcePath.EndsWith("compound_state.gd"))
{
throw new ArgumentException("Given node is not a compound state.");
}
return new CompoundState(state);
}
public new class SignalName : StateChartState.SignalName
{
/// <see cref="CompoundState.ChildStateEntered"/>
public static readonly StringName ChildStateEntered = "child_state_entered";
/// <see cref="CompoundState.ChildStateExited"/>
public static readonly StringName ChildStateExited = "child_state_exited";
}
}
}
@@ -0,0 +1 @@
uid://bqobe1g1eljyy
@@ -0,0 +1,49 @@
// ReSharper disable once CheckNamespace
namespace GodotStateCharts
{
using Godot;
/// <summary>
/// Base class for all wrapper classes. Provides some common functionality. Not to be used directly.
/// </summary>
public abstract class NodeWrapper
{
/// <summary>
/// The wrapped node.
/// </summary>
protected internal readonly Node Wrapped;
protected NodeWrapper(Node wrapped)
{
Wrapped = wrapped;
}
/// <summary>
/// Allows to connect to signals on the wrapped node.
/// </summary>
/// <param name="signal"></param>
/// <param name="method"></param>
/// <param name="flags"></param>
public Error Connect(StringName signal, Callable method, uint flags = 0u)
{
return Wrapped.Connect(signal, method, flags);
}
/// <summary>
/// Allows to call methods on the wrapped node deferred.
/// </summary>
public Variant CallDeferred(string method, params Variant[] args)
{
return Wrapped.CallDeferred(method, args);
}
/// <summary>
/// Allows to call methods on the wrapped node.
/// </summary>
public Variant Call(string method, params Variant[] args)
{
return Wrapped.Call(method, args);
}
}
}
@@ -0,0 +1 @@
uid://buhujyqck3bti
@@ -0,0 +1,39 @@
// ReSharper disable once CheckNamespace
namespace GodotStateCharts
{
using Godot;
/// <summary>
/// Base class for all wrapper classes for Godot Resource types. Provides common functionality. Not to be used directly.
/// </summary>
public abstract class ResourceWrapper
{
/// <summary>
/// The wrapped resource. Useful for you need to access the underlying resource directly,
/// e.g. for serialization.
/// </summary>
public readonly Resource Wrapped;
protected ResourceWrapper(Resource wrapped)
{
Wrapped = wrapped;
}
/// <summary>
/// Allows to call methods on the wrapped resource deferred.
/// </summary>
public Variant CallDeferred(string method, params Variant[] args)
{
return Wrapped.CallDeferred(method, args);
}
/// <summary>
/// Allows to call methods on the wrapped resource.
/// </summary>
public Variant Call(string method, params Variant[] args)
{
return Wrapped.Call(method, args);
}
}
}
@@ -0,0 +1 @@
uid://d5nx6pgk2tr7e
@@ -0,0 +1,89 @@
// ReSharper disable once CheckNamespace
namespace GodotStateCharts
{
using System;
using Godot;
/// <summary>
/// C# wrapper for the SerializedStateChart Godot resource.
/// </summary>
public class SerializedStateChart : ResourceWrapper
{
private SerializedStateChart(Resource wrapped) : base(wrapped) { }
public static SerializedStateChart Of(Resource resource)
{
if (resource.GetScript().As<Script>() is not GDScript gdScript
|| !gdScript.ResourcePath.EndsWith("serialized_state_chart.gd"))
{
throw new ArgumentException("Given resource is not a SerializedStateChart.");
}
return new SerializedStateChart(resource);
}
public int Version
{
get => Wrapped.Get("version").AsInt32();
set => Wrapped.Set("version", value);
}
public string Name
{
get => Wrapped.Get("name").AsString();
set => Wrapped.Set("name", value);
}
public Godot.Collections.Dictionary ExpressionProperties
{
get => Wrapped.Get("expression_properties").AsGodotDictionary();
set => Wrapped.Set("expression_properties", value);
}
public Godot.Collections.Array QueuedEvents
{
get => Wrapped.Get("queued_events").AsGodotArray();
set => Wrapped.Set("queued_events", value);
}
public bool PropertyChangePending
{
get => Wrapped.Get("property_change_pending").AsBool();
set => Wrapped.Set("property_change_pending", value);
}
public bool StateChangePending
{
get => Wrapped.Get("state_change_pending").AsBool();
set => Wrapped.Set("state_change_pending", value);
}
public bool LockedDown
{
get => Wrapped.Get("locked_down").AsBool();
set => Wrapped.Set("locked_down", value);
}
public Godot.Collections.Array QueuedTransitions
{
get => Wrapped.Get("queued_transitions").AsGodotArray();
set => Wrapped.Set("queued_transitions", value);
}
public bool TransitionsProcessingActive
{
get => Wrapped.Get("transitions_processing_active").AsBool();
set => Wrapped.Set("transitions_processing_active", value);
}
public SerializedStateChartState State
{
get
{
var stateRes = Wrapped.Get("state").As<Resource>();
return stateRes != null ? SerializedStateChartState.Of(stateRes) : null;
}
set => Wrapped.Set("state", value?.Wrapped);
}
}
}
@@ -0,0 +1 @@
uid://b5c6gy54ic6eq
@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using Godot;
namespace GodotStateCharts
{
/// <summary>
/// C# wrapper for the SerializedStateChartState Godot resource.
/// </summary>
public class SerializedStateChartState : ResourceWrapper
{
private SerializedStateChartState(Resource wrapped) : base(wrapped)
{
}
public static SerializedStateChartState Of(Resource resource)
{
if (resource.GetScript().As<Script>() is not GDScript gdScript
|| !gdScript.ResourcePath.EndsWith("serialized_state_chart_state.gd"))
{
throw new ArgumentException("Given resource is not a SerializedStateChartState.");
}
return new SerializedStateChartState(resource);
}
public string Name
{
get => Wrapped.Get("name").AsString();
set => Wrapped.Set("name", value);
}
public int StateType
{
get => Wrapped.Get("state_type").AsInt32();
set => Wrapped.Set("state_type", value);
}
public bool Active
{
get => Wrapped.Get("active").AsBool();
set => Wrapped.Set("active", value);
}
public string PendingTransitionName
{
get => Wrapped.Get("pending_transition_name").AsString();
set => Wrapped.Set("pending_transition_name", value);
}
public float PendingTransitionRemainingDelay
{
get => Wrapped.Get("pending_transition_remaining_delay").AsSingle();
set => Wrapped.Set("pending_transition_remaining_delay", value);
}
public float PendingTransitionInitialDelay
{
get => Wrapped.Get("pending_transition_initial_delay").AsSingle();
set => Wrapped.Set("pending_transition_initial_delay", value);
}
public List<SerializedStateChartState> Children
{
get
{
var wrappedChildren = Wrapped.Get("children").AsGodotArray();
var result = new List<SerializedStateChartState>();
// ReSharper disable once LoopCanBeConvertedToQuery
foreach (var item in wrappedChildren)
{
result.Add(Of(item.As<Resource>()));
}
return result;
}
set
{
var wrappedChildren = new Godot.Collections.Array();
foreach (var child in value)
{
wrappedChildren.Add(child.Wrapped);
}
Wrapped.Set("children", wrappedChildren);
}
}
}
}
@@ -0,0 +1 @@
uid://e4tnwhionqsn2
@@ -0,0 +1,129 @@
// ReSharper disable once CheckNamespace
namespace GodotStateCharts
{
using Godot;
using System;
/// <summary>
/// Wrapper around the GDScript state chart node. Allows interacting with the state chart.
/// </summary>
public class StateChart : NodeWrapper
{
/// <summary>
/// Emitted when the state chart receives an event. This will be
/// emitted no matter which state is currently active and can be
/// useful to trigger additional logic elsewhere in the game
/// without having to create a custom event bus. It is also used
/// by the state chart debugger. Note that this will emit the
/// events in the order in which they are processed, which may
/// be different from the order in which they were received. This is
/// because the state chart will always finish processing one event
/// fully before processing the next. If an event is received
/// while another is still processing, it will be enqueued.
/// </summary>
public event Action<StringName> EventReceived
{
add => Wrapped.Connect(SignalName.EventReceived, Callable.From(value));
remove => Wrapped.Disconnect(SignalName.EventReceived, Callable.From(value));
}
protected StateChart(Node wrapped) : base(wrapped)
{
}
/// <summary>
/// Creates a wrapper object around the given node and verifies that the node
/// is actually a state chart. The wrapper object can then be used to interact
/// with the state chart from C#.
/// </summary>
/// <param name="stateChart">the node that is the state chart</param>
/// <returns>a StateChart wrapper.</returns>
/// <throws>ArgumentException if the node is not a state chart.</throws>
public static StateChart Of(Node stateChart)
{
if (stateChart.GetScript().As<Script>() is not GDScript gdScript
|| !gdScript.ResourcePath.EndsWith("state_chart.gd"))
{
throw new ArgumentException("Given node is not a state chart.");
}
return new StateChart(stateChart);
}
/// <summary>
/// Sends an event to the state chart node.
/// </summary>
/// <param name="eventName">the name of the event to send</param>
public void SendEvent(string eventName)
{
Call(MethodName.SendEvent, eventName);
}
/// <summary>
/// Sets an expression property on the state chart node for later use with expression guards.
/// </summary>
/// <param name="name">the name of the property to set. This is case sensitive.</param>
/// <param name="value">the value to set the property to.</param>
public void SetExpressionProperty(string name, Variant value)
{
Call(MethodName.SetExpressionProperty, name, value);
}
/// <summary>
/// Returns the value of an expression property on the state chart node.
/// </summary>
/// <param name="name">the name of the proeprty to read. This is case sensitive. </param>
/// <param name="defaultValue">the default value to be returned if no such property exists</param>
/// <returns>the value of the property</returns>
public T GetExpressionProperty<[MustBeVariant]T>(string name, T defaultValue = default)
{
return Call(MethodName.GetExpressionProperty, name, Variant.From(defaultValue)).As<T>();
}
/// <summary>
/// Steps the state chart node. This will invoke all <code>state_stepped</code> signals on the
/// currently active states in the state charts. See the "Stepping Mode" section of the manual
/// for more details.
/// </summary>
public void Step()
{
Call(MethodName.Step);
}
public class SignalName : Node.SignalName
{
/// <see cref="StateChart.EventReceived"/>
///
/// </summary>
public static readonly StringName EventReceived = "event_received";
}
public class MethodName : Node.MethodName
{
/// <summary>
/// Sends an event to the state chart node.
/// </summary>
public static readonly StringName SendEvent = "send_event";
/// <summary>
/// Sets an expression property on the state chart node for later use with expression guards.
/// </summary>
public static readonly StringName SetExpressionProperty = "set_expression_property";
/// <summary>
/// Returns the value of an expression property on the state chart node.
/// </summary>
public static readonly StringName GetExpressionProperty = "get_expression_property";
/// <summary>
/// Steps the state chart node. This will invoke all <code>state_stepped</code> signals on the
/// currently active states in the state charts. See the "Stepping Mode" section of the manual
/// for more details.
/// </summary>
public static readonly StringName Step = "step";
}
}
}
@@ -0,0 +1 @@
uid://ctpynfb7pf165
@@ -0,0 +1,65 @@
// ReSharper disable once CheckNamespace
namespace GodotStateCharts
{
using Godot;
using System;
/// <summary>
/// Wrapper around the state chart debugger node.
/// </summary>
public class StateChartDebugger : NodeWrapper
{
private StateChartDebugger(Node wrapped) : base(wrapped) {}
/// <summary>
/// Creates a wrapper object around the given node and verifies that the node
/// is actually a state chart debugger. The wrapper object can then be used to interact
/// with the state chart debugger from C#.
/// </summary>
/// <param name="stateChartDebugger">the node that is the state chart debugger</param>
/// <returns>a StateChartDebugger wrapper.</returns>
/// <throws>ArgumentException if the node is not a state chart debugger.</throws>
public static StateChartDebugger Of(Node stateChartDebugger)
{
if (stateChartDebugger.GetScript().As<Script>() is not GDScript gdScript
|| !gdScript.ResourcePath.EndsWith("state_chart_debugger.gd"))
{
throw new ArgumentException("Given node is not a state chart debugger.");
}
return new StateChartDebugger(stateChartDebugger);
}
/// <summary>
/// Sets the node that the state chart debugger should debug.
/// </summary>
/// <param name="node">the the node that should be debugged. Can be a state chart or any
/// node above a state chart. The debugger will automatically pick the first state chart
/// node below the given one.</param>
public void DebugNode(Node node)
{
Call(MethodName.DebugNode, node);
}
/// <summary>
/// Adds a history entry to the history output.
/// </summary>
/// <param name="text">the text to add</param>
public void AddHistoryEntry(string text)
{
Call(MethodName.AddHistoryEntry, text);
}
public class MethodName : Node.MethodName
{
/// <summary>
/// Sets the node that the state chart debugger should debug.
/// </summary>
public static readonly string DebugNode = "debug_node";
/// <summary>
/// Adds a history entry to the history output.
/// </summary>
public static readonly string AddHistoryEntry = "add_history_entry";
}
}
}
@@ -0,0 +1 @@
uid://c6rxydyggksbf
@@ -0,0 +1,32 @@
namespace GodotStateCharts;
using Godot;
public class StateChartSerializer
{
private static readonly GodotObject Wrapped = GD.Load("res://addons/godot_state_charts/state_chart_serializer.gd");
/// <summary>
/// Serializes the given state chart and returns a serialized object that
/// can be stored as part of a saved game.
/// </summary>
/// <param name="stateChart">the state chart to serialize</param>
/// <returns>a resource containing the serialized state</returns>
public static SerializedStateChart Serialize(StateChart stateChart)
{
return SerializedStateChart.Of(Wrapped.Call("serialize", stateChart.Wrapped).As<Resource>());
}
/// <summary>
/// Deserializes the given serialized state chart into the given state chart. Returns a set of
/// error messages. If the serialized state chart was no longer compatible with the current state
/// chart, nothing will happen. The operation is successful when the returned array is empty.
/// </summary>
public static string[] Deserialize(SerializedStateChart serializedStateChart, StateChart stateChart)
{
var variant = Wrapped.Call("deserialize", serializedStateChart.Wrapped, stateChart.Wrapped);
return variant.AsStringArray();
}
}
@@ -0,0 +1 @@
uid://bawtcbamqpc0f

Some files were not shown because too many files have changed in this diff Show More