Initial commit.
@@ -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
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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"]
|
||||
@@ -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.
|
||||
@@ -0,0 +1,163 @@
|
||||

|
||||
|
||||
# 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/)
|
||||
|
||||
[](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)
|
||||
```
|
||||
|
||||

|
||||
|
||||
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)
|
||||
```
|
||||
|
||||

|
||||
|
||||
> [!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`).
|
||||
|
||||

|
||||
|
||||
## 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
|
||||

|
||||
|
||||
`DD3DDemo.tscn` in play mode
|
||||

|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
@@ -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))
|
||||
@@ -0,0 +1 @@
|
||||
uid://cjifa1oa5wtn4
|
||||
@@ -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"]
|
||||
@@ -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"
|
||||
@@ -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.
|
||||
@@ -0,0 +1 @@
|
||||
uid://dcxwqe5yhnqon
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||