Compare commits

...

5 Commits

Author SHA1 Message Date
cblech 78471268db Show interaction popup (<E> Label) 2024-12-20 20:34:10 +01:00
cblech 84fcec8227 Added basic interaction functionality 2024-12-20 16:28:02 +01:00
cblech dc322d8943 Fixed camera 2024-12-20 14:52:24 +01:00
cblech 4ce0367f15 Added 3D Controller 2024-12-02 18:50:26 +01:00
cblech 421a90ec17 Added camera preview addon 2024-12-02 18:49:36 +01:00
26 changed files with 1593 additions and 23 deletions
@@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M4 11V4h7" fill="none" stroke="#fff" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" stroke-opacity=".6"/></svg>

After

Width:  |  Height:  |  Size: 221 B

@@ -0,0 +1,37 @@
[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/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
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,37 @@
[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/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=2.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
+1
View File
@@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m4 1v1l1 1v3h6v-3l1-1v-1zm1 6-2 3h10l-2-3zm2 4v2l1 2 1-2v-2z" fill="#e0e0e0"/></svg>

After

Width:  |  Height:  |  Size: 177 B

@@ -0,0 +1,37 @@
[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/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=2.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
@@ -0,0 +1,7 @@
[plugin]
name="Little Camera Preview"
description="Shows a picture-in-picture preview of the selected 2D or 3D camera"
author="Anthony Cossins"
version="1.3"
script="plugin.gd"
+87
View File
@@ -0,0 +1,87 @@
@tool
extends EditorPlugin
const preview_scene = preload("res://addons/anthonyec.camera_preview/preview.tscn")
var preview: CameraPreview
var current_main_screen_name: String
func _enter_tree() -> void:
main_screen_changed.connect(_on_main_screen_changed)
EditorInterface.get_selection().selection_changed.connect(_on_editor_selection_changed)
# Initialise preview panel and add to main screen.
preview = preview_scene.instantiate() as CameraPreview
preview.request_hide()
var main_screen = EditorInterface.get_editor_main_screen()
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 = 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()
+404
View File
@@ -0,0 +1,404 @@
@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 seperate 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 viewport_ratio: float = 1
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(30, 30) * editor_scale
var margin_size: float = 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 = initial_mouse_position - get_global_mouse_position()
var resized_size = 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 = get_global_mouse_position()
var offset = 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 = 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():
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(panel.global_position, panel.size)
panel_hover_rect = panel_hover_rect.grow(40)
var mouse_position = 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 = get_project_window_size()
var ratio = 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(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 = 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 = 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 = get_project_window_ratio()
var editor_viewport_size = get_editor_viewport_size()
var max_bounds = Vector2(
editor_viewport_size.x * 0.6,
editor_viewport_size.y * 0.8
)
var clamped_size = 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 = 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(ProjectSettings.get_setting("display/window/size/viewport_width"))
var window_height = 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 = get_project_window_size()
return project_window_size.y / project_window_size.x
func get_editor_viewport_size() -> Vector2:
var fallback_size = 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 = EditorInterface.get_editor_viewport_3d(0)
var editor_viewport_container = editor_sub_viewport_3d.get_parent().get_parent().get_parent()
# Early return incase editor tree structure has changed.
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,200 @@
[gd_scene load_steps=8 format=3 uid="uid://xybmfvufjuv"]
[ext_resource type="Script" 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_hchdq"]
viewport_path = NodePath("Panel/SubViewport")
[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 = -908.889
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_hchdq")
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
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
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"]
@@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/Roboto-Bold.ttf-a0c3395776dbc11ee676c5f1ea9c0
Rendering=null Rendering=null
antialiasing=1 antialiasing=1
generate_mipmaps=false generate_mipmaps=false
disable_embedded_bitmaps=true
multichannel_signed_distance_field=false multichannel_signed_distance_field=false
msdf_pixel_range=8 msdf_pixel_range=8
msdf_size=48 msdf_size=48
@@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/Roboto-Italic.ttf-844485a0171d6031f98f4829003
Rendering=null Rendering=null
antialiasing=1 antialiasing=1
generate_mipmaps=false generate_mipmaps=false
disable_embedded_bitmaps=true
multichannel_signed_distance_field=false multichannel_signed_distance_field=false
msdf_pixel_range=8 msdf_pixel_range=8
msdf_size=48 msdf_size=48
@@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/Roboto-Regular.ttf-d9ce0640effe9e93230b445b37
Rendering=null Rendering=null
antialiasing=1 antialiasing=1
generate_mipmaps=false generate_mipmaps=false
disable_embedded_bitmaps=true
multichannel_signed_distance_field=false multichannel_signed_distance_field=false
msdf_pixel_range=8 msdf_pixel_range=8
msdf_size=48 msdf_size=48
+19
View File
@@ -0,0 +1,19 @@
{
"@path": "res://addons/dialogic/Resources/character.gd",
"@subpath": NodePath(""),
"_translation_id": "16",
"color": Color(1, 1, 1, 1),
"custom_info": {
"sound_mood_default": "",
"sound_moods": {},
"style": ""
},
"default_portrait": "",
"description": "",
"display_name": "Semi Cat",
"mirror": false,
"nicknames": [""],
"offset": Vector2(0, 0),
"portraits": {},
"scale": 1.0
}
+1
View File
@@ -0,0 +1 @@
semi_cat: Meow! #id:15
@@ -1,3 +1,5 @@
keys,en,de keys,en,de
Character/13/name,Defaulty the Default Character,Standardy der Standard Charakter Character/13/name,Defaulty the Default Character,Standardy der Standard Charakter
Character/13/nicknames,Defaulty,Standardy Character/13/nicknames,Defaulty,Standardy
Character/16/name,Semi Cat,Semi Katze
Character/16/nicknames,,
1 keys en de
2 Character/13/name Defaulty the Default Character Standardy der Standard Charakter
3 Character/13/nicknames Defaulty Standardy
4 Character/16/name Semi Cat Semi Katze
5 Character/16/nicknames
@@ -0,0 +1,2 @@
keys,en,de
Text/15/text,Meow!,Miau!
1 keys en de
2 Text/15/text Meow! Miau!
@@ -0,0 +1,17 @@
[remap]
importer="csv_translation"
type="Translation"
uid="uid://dd4q6v071u4kp"
[deps]
files=["res://dialog/translations/dialogic_semi_cat_translation.en.translation", "res://dialog/translations/dialogic_semi_cat_translation.de.translation"]
source_file="res://dialog/translations/dialogic_semi_cat_translation.csv"
dest_files=["res://dialog/translations/dialogic_semi_cat_translation.en.translation", "res://dialog/translations/dialogic_semi_cat_translation.de.translation"]
[params]
compress=true
delimiter=0
+30 -13
View File
@@ -11,8 +11,8 @@ config_version=5
[application] [application]
config/name="Babushka" config/name="Babushka"
run/main_scene="res://scenes/2DTestingGround.tscn" run/main_scene="res://scenes/mockups/abandoned_street_3d.tscn"
config/features=PackedStringArray("4.2", "Forward Plus") config/features=PackedStringArray("4.3", "Forward Plus")
config/icon="res://icon.svg" config/icon="res://icon.svg"
[autoload] [autoload]
@@ -22,9 +22,11 @@ Dialogic="*res://addons/dialogic/Core/DialogicGameHandler.gd"
[dialogic] [dialogic]
directories/dch_directory={ directories/dch_directory={
"defaulty_the_default_character": "res://dialog/defaulty_the_default_character.dch" "defaulty_the_default_character": "res://dialog/defaulty_the_default_character.dch",
"semi_cat": "res://dialog/semi_cat.dch"
} }
directories/dtl_directory={ directories/dtl_directory={
"semi_cat": "res://dialog/semi_cat.dtl",
"test_time_line": "res://dialog/test_time_line.dtl" "test_time_line": "res://dialog/test_time_line.dtl"
} }
variables={} variables={}
@@ -59,7 +61,7 @@ translation/add_separator=false
translation/intern/save_mode=0 translation/intern/save_mode=0
translation/intern/file_mode=1 translation/intern/file_mode=1
translation/intern/translation_folder="res://dialog/translations" translation/intern/translation_folder="res://dialog/translations"
translation/id_counter=20 translation/id_counter=22
translation/locales=["de", "en"] translation/locales=["de", "en"]
text/autopauses={} text/autopauses={}
@@ -73,40 +75,55 @@ project/assembly_name="Babushka"
[editor_plugins] [editor_plugins]
enabled=PackedStringArray("res://addons/dialogic/plugin.cfg") enabled=PackedStringArray("res://addons/anthonyec.camera_preview/plugin.cfg", "res://addons/dialogic/plugin.cfg")
[input] [input]
move_left={ move_left={
"deadzone": 0.5, "deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"echo":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
] ]
} }
move_right={ move_right={
"deadzone": 0.5, "deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"echo":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
] ]
} }
move_up={ move_up={
"deadzone": 0.5, "deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"echo":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
] ]
} }
move_down={ move_down={
"deadzone": 0.5, "deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"echo":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
] ]
} }
dialogic_default_action={ dialogic_default_action={
"deadzone": 0.5, "deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194309,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194309,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null) , Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":32,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":32,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":88,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":88,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":0,"pressure":0.0,"pressed":false,"script":null) , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":0,"pressure":0.0,"pressed":false,"script":null)
] ]
} }
click={
"deadzone": 0.5,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null)
]
}
interact={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
]
}
[internationalization] [internationalization]
locale/translations=PackedStringArray("res://dialog/translations/dialogic_character_translations.en.translation", "res://dialog/translations/dialogic_test_time_line_translation.de.translation", "res://dialog/translations/dialogic_test_time_line_translation.en.translation", "res://dialog/translations/dialogic_character_translations.de.translation") locale/translations=PackedStringArray("res://dialog/translations/dialogic_character_translations.en.translation", "res://dialog/translations/dialogic_test_time_line_translation.de.translation", "res://dialog/translations/dialogic_test_time_line_translation.en.translation", "res://dialog/translations/dialogic_character_translations.de.translation", "res://dialog/translations/dialogic_semi_cat_translation.de.translation", "res://dialog/translations/dialogic_semi_cat_translation.en.translation")
[layer_names]
3d_physics/layer_1="General"
3d_physics/layer_5="Player"
File diff suppressed because it is too large Load Diff
+38
View File
@@ -0,0 +1,38 @@
[gd_scene load_steps=5 format=3 uid="uid://de2fn2m114eaw"]
[ext_resource type="Texture2D" uid="uid://cvn2p215jq2am" path="res://graphics/testingground/concerned.png" id="1_5jpx2"]
[ext_resource type="Script" path="res://scripts/player_3d.gd" id="1_08wm4"]
[ext_resource type="Script" path="res://scripts/camera_pivot.gd" id="3_drcdp"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1vdrh"]
radius = 0.0478421
height = 0.234557
[node name="Player3d" type="Node3D"]
[node name="CharacterBody3D" type="CharacterBody3D" parent="."]
collision_layer = 16
collision_mask = 17
script = ExtResource("1_08wm4")
SPEED = 0.5
[node name="Sprite" type="Sprite3D" parent="CharacterBody3D"]
transform = Transform3D(0.04, 0, 0, 0, 0.04, 0, 0, 0, 0.04, 0, 0.110813, 0)
billboard = 1
shaded = true
texture = ExtResource("1_5jpx2")
[node name="CollisionShape3D" type="CollisionShape3D" parent="CharacterBody3D"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.119886, 0)
shape = SubResource("CapsuleShape3D_1vdrh")
[node name="CameraPivot" type="Node3D" parent="CharacterBody3D"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.159723, 0)
script = ExtResource("3_drcdp")
[node name="SubPivot" type="Node3D" parent="CharacterBody3D/CameraPivot"]
transform = Transform3D(1, 0, 0, 0, 0.901878, 0.431991, 0, -0.431991, 0.901878, 0, 0, 0)
[node name="Camera3D" type="Camera3D" parent="CharacterBody3D/CameraPivot/SubPivot"]
transform = Transform3D(1, 0, 0, 0, 0.897258, 0.441506, 0, -0.441506, 0.897258, 0, 0.788471, 1.52789)
fov = 24.0844
+28
View File
@@ -0,0 +1,28 @@
[gd_scene load_steps=3 format=3 uid="uid://ob04y3syvo0e"]
[ext_resource type="Script" path="res://scripts/interaction_area.gd" id="1_26v5g"]
[sub_resource type="SphereShape3D" id="SphereShape3D_i8sim"]
radius = 1.0
[node name="InteractionArea" type="Node3D"]
script = ExtResource("1_26v5g")
[node name="Area3D" type="Area3D" parent="."]
collision_mask = 16
[node name="CollisionShape3D" type="CollisionShape3D" parent="Area3D"]
shape = SubResource("SphereShape3D_i8sim")
[node name="Label3D" type="Label3D" parent="."]
sorting_offset = 100.0
pixel_size = 0.001
billboard = 1
no_depth_test = true
fixed_size = true
text = "<E>"
font_size = 98
outline_size = 22
[connection signal="body_entered" from="Area3D" to="Label3D" method="show"]
[connection signal="body_exited" from="Area3D" to="Label3D" method="hide"]
+22
View File
@@ -0,0 +1,22 @@
extends Node3D
@export var canPitch: bool = false
@export var canYaw: bool = false
@export var rotateSpeed: float = 0.003
@onready var sub_pivot: Node3D = $SubPivot
func _ready() -> void:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
func _input(event):
if event.is_action_pressed("click"):
if Input.mouse_mode == Input.MOUSE_MODE_VISIBLE:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
if event.is_action_pressed("ui_cancel"):
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
if event is InputEventMouseMotion:
if Input.mouse_mode != Input.MOUSE_MODE_CAPTURED: return
if canYaw: sub_pivot.rotate_x(event.relative.y * -rotateSpeed)
if canPitch: rotate_y(event.relative.x * -rotateSpeed)
+5
View File
@@ -0,0 +1,5 @@
extends Node
class_name DialogicStarter
func open(timeline: String):
Dialogic.start(timeline)
+16
View File
@@ -0,0 +1,16 @@
extends Node3D
@onready var area_3d: Area3D = $Area3D
@onready var label_3d: Label3D = $Label3D
signal interacted
func _process(delta: float) -> void:
if area_3d.has_overlapping_bodies():
label_3d.show()
else:
label_3d.hide()
func _input(event: InputEvent) -> void:
if event.is_action("interact") && event.is_pressed() && area_3d.has_overlapping_bodies():
interacted.emit()
+30
View File
@@ -0,0 +1,30 @@
extends CharacterBody3D
@export var SPEED = 1.0
@onready var camera_3d: Camera3D = $CameraPivot/SubPivot/Camera3D
func _physics_process(delta: float) -> void:
# Add the gravity.
if not is_on_floor():
velocity += get_gravity() * delta
# Handle jump.
#if Input.is_action_just_pressed("ui_accept") and is_on_floor():
# velocity.y = JUMP_VELOCITY
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
var input_dir := Input.get_vector("move_left", "move_right", "move_up", "move_down")
input_dir = input_dir.rotated(-camera_3d.global_rotation.y)
var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
if direction:
velocity.x = direction.x * SPEED
velocity.z = direction.z * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
velocity.z = move_toward(velocity.z, 0, SPEED)
move_and_slide()