Browse Source

Improve usability of Camera2D

Lazy-Rabbit-2001 5 months ago
parent
commit
5ca70d88a7

+ 3 - 0
doc/classes/Camera2D.xml

@@ -142,6 +142,9 @@
 		<member name="limit_bottom" type="int" setter="set_limit" getter="get_limit" default="10000000">
 			Bottom scroll limit in pixels. The camera stops moving when reaching this value, but [member offset] can push the view past the limit.
 		</member>
+		<member name="limit_enabled" type="bool" setter="set_limit_enabled" getter="is_limit_enabled" default="true">
+			If [code]true[/code], the limits will be enabled. Disabling this will allow the camera to focus anywhere, when the four [code]limit_*[/code] properties will not work.
+		</member>
 		<member name="limit_left" type="int" setter="set_limit" getter="get_limit" default="-10000000">
 			Left scroll limit in pixels. The camera stops moving when reaching this value, but [member offset] can push the view past the limit.
 		</member>

+ 167 - 0
editor/plugins/camera_2d_editor_plugin.cpp

@@ -0,0 +1,167 @@
+/**************************************************************************/
+/*  camera_2d_editor_plugin.cpp                                           */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* 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.                 */
+/**************************************************************************/
+
+#include "camera_2d_editor_plugin.h"
+
+#include "canvas_item_editor_plugin.h"
+#include "core/config/project_settings.h"
+#include "editor/editor_node.h"
+#include "editor/editor_undo_redo_manager.h"
+#include "editor/themes/editor_scale.h"
+#include "scene/2d/camera_2d.h"
+#include "scene/gui/label.h"
+#include "scene/gui/menu_button.h"
+
+void Camera2DEditor::edit(Camera2D *p_camera) {
+	if (p_camera == selected_camera) {
+		return;
+	}
+	selected_camera = p_camera;
+}
+
+void Camera2DEditor::_menu_option(int p_option) {
+	switch (p_option) {
+		case MENU_SNAP_LIMITS_TO_VIEWPORT: {
+			EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
+			Rect2 prev_rect = selected_camera->get_limit_rect();
+			ur->create_action(TTR("Snap the Limits to the Viewport"), UndoRedo::MERGE_DISABLE, selected_camera);
+			ur->add_do_method(this, "_snap_limits_to_viewport");
+			ur->add_do_reference(selected_camera);
+			ur->add_undo_method(this, "_undo_snap_limits_to_viewport", prev_rect);
+			ur->commit_action();
+		} break;
+	}
+}
+
+void Camera2DEditor::_snap_limits_to_viewport() {
+	selected_camera->set_limit(SIDE_LEFT, 0);
+	selected_camera->set_limit(SIDE_TOP, 0);
+	selected_camera->set_limit(SIDE_RIGHT, GLOBAL_GET("display/window/size/viewport_width"));
+	selected_camera->set_limit(SIDE_BOTTOM, GLOBAL_GET("display/window/size/viewport_height"));
+}
+
+void Camera2DEditor::_undo_snap_limits_to_viewport(const Rect2 &p_prev_rect) {
+	Point2 end = p_prev_rect.get_end();
+	selected_camera->set_limit(SIDE_LEFT, p_prev_rect.position.x);
+	selected_camera->set_limit(SIDE_TOP, p_prev_rect.position.y);
+	selected_camera->set_limit(SIDE_RIGHT, end.x);
+	selected_camera->set_limit(SIDE_BOTTOM, end.y);
+}
+
+void Camera2DEditor::_notification(int p_what) {
+	switch (p_what) {
+		case NOTIFICATION_THEME_CHANGED: {
+			options->set_button_icon(get_editor_theme_icon(SNAME("Camera2D")));
+		} break;
+	}
+}
+
+void Camera2DEditor::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("_snap_limits_to_viewport"), &Camera2DEditor::_snap_limits_to_viewport);
+	ClassDB::bind_method(D_METHOD("_undo_snap_limits_to_viewport", "prev_rect"), &Camera2DEditor::_undo_snap_limits_to_viewport);
+}
+
+Camera2DEditor::Camera2DEditor() {
+	options = memnew(MenuButton);
+
+	CanvasItemEditor::get_singleton()->add_control_to_menu_panel(options);
+
+	options->set_text(TTRC("Camera2D"));
+
+	options->get_popup()->add_item(TTRC("Snap the Limits to the Viewport"), MENU_SNAP_LIMITS_TO_VIEWPORT);
+	options->set_switch_on_hover(true);
+
+	options->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &Camera2DEditor::_menu_option));
+
+#ifdef TOOLS_ENABLED
+	add_user_signal(MethodInfo("_editor_theme_changed"));
+#endif
+}
+
+void Camera2DEditorPlugin::_update_approach_text_visibility() {
+	if (camera_2d_editor->selected_camera == nullptr) {
+		return;
+	}
+	approach_to_move_rect->set_visible(camera_2d_editor->selected_camera->is_limit_enabled());
+}
+
+void Camera2DEditorPlugin::_editor_theme_changed() {
+	approach_to_move_rect->remove_theme_color_override(SceneStringName(font_color));
+	approach_to_move_rect->add_theme_color_override(SceneStringName(font_color), Color(0.6f, 0.6f, 0.6f, 1));
+	approach_to_move_rect->add_theme_color_override("font_shadow_color", Color(0.2f, 0.2f, 0.2f, 1));
+	approach_to_move_rect->add_theme_constant_override("shadow_outline_size", 1 * EDSCALE);
+	approach_to_move_rect->add_theme_constant_override("line_spacing", 0);
+}
+
+void Camera2DEditorPlugin::edit(Object *p_object) {
+	Callable update_text = callable_mp(this, &Camera2DEditorPlugin::_update_approach_text_visibility);
+	StringName update_signal = SNAME("_camera_limit_enabled_updated");
+
+	Camera2D *prev_cam = camera_2d_editor->selected_camera;
+	if (prev_cam != nullptr && prev_cam->is_connected(update_signal, update_text)) {
+		prev_cam->disconnect(update_signal, update_text);
+	}
+	Camera2D *cam = Object::cast_to<Camera2D>(p_object);
+	if (cam != nullptr) {
+		camera_2d_editor->edit(cam);
+		_update_approach_text_visibility();
+		if (!cam->is_connected(update_signal, update_text)) {
+			cam->connect(update_signal, update_text);
+		}
+	}
+}
+
+bool Camera2DEditorPlugin::handles(Object *p_object) const {
+	return p_object->is_class("Camera2D");
+}
+
+void Camera2DEditorPlugin::make_visible(bool p_visible) {
+	if (p_visible) {
+		camera_2d_editor->options->show();
+		approach_to_move_rect->show();
+	} else {
+		camera_2d_editor->options->hide();
+		approach_to_move_rect->hide();
+	}
+}
+
+Camera2DEditorPlugin::Camera2DEditorPlugin() {
+	camera_2d_editor = memnew(Camera2DEditor);
+	EditorNode::get_singleton()->get_gui_base()->add_child(camera_2d_editor);
+	camera_2d_editor->connect(SNAME("_editor_theme_changed"), callable_mp(this, &Camera2DEditorPlugin::_editor_theme_changed));
+
+	approach_to_move_rect = memnew(Label);
+	approach_to_move_rect->set_text(TTRC("In Move Mode: \nHold Ctrl + left mouse button to move the limit rectangle.\nHold left mouse button to move the camera only."));
+	approach_to_move_rect->hide();
+	_editor_theme_changed();
+	CanvasItemEditor::get_singleton()->get_controls_container()->add_child(approach_to_move_rect);
+
+	make_visible(false);
+}

+ 80 - 0
editor/plugins/camera_2d_editor_plugin.h

@@ -0,0 +1,80 @@
+/**************************************************************************/
+/*  camera_2d_editor_plugin.h                                             */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* 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.                 */
+/**************************************************************************/
+
+#pragma once
+
+#include "editor/plugins/editor_plugin.h"
+
+class Camera2D;
+class Label;
+class MenuButton;
+
+class Camera2DEditor : public Control {
+	GDCLASS(Camera2DEditor, Control);
+
+	enum Menu {
+		MENU_SNAP_LIMITS_TO_VIEWPORT,
+	};
+
+	Camera2D *selected_camera = nullptr;
+
+	friend class Camera2DEditorPlugin;
+	MenuButton *options = nullptr;
+
+	void _menu_option(int p_option);
+	void _snap_limits_to_viewport();
+	void _undo_snap_limits_to_viewport(const Rect2 &p_prev_rect);
+
+protected:
+	static void _bind_methods();
+	void _notification(int p_what);
+
+public:
+	void edit(Camera2D *p_camera);
+	Camera2DEditor();
+};
+
+class Camera2DEditorPlugin : public EditorPlugin {
+	GDCLASS(Camera2DEditorPlugin, EditorPlugin);
+
+	Camera2DEditor *camera_2d_editor = nullptr;
+
+	Label *approach_to_move_rect = nullptr;
+
+	void _editor_theme_changed();
+	void _update_approach_text_visibility();
+
+public:
+	virtual void edit(Object *p_object) override;
+	virtual bool handles(Object *p_object) const override;
+	virtual void make_visible(bool p_visible) override;
+
+	Camera2DEditorPlugin();
+};

+ 2 - 0
editor/register_editor_types.cpp

@@ -73,6 +73,7 @@
 #include "editor/plugins/audio_stream_randomizer_editor_plugin.h"
 #include "editor/plugins/bit_map_editor_plugin.h"
 #include "editor/plugins/bone_map_editor_plugin.h"
+#include "editor/plugins/camera_2d_editor_plugin.h"
 #include "editor/plugins/camera_3d_editor_plugin.h"
 #include "editor/plugins/cast_2d_editor_plugin.h"
 #include "editor/plugins/collision_polygon_2d_editor_plugin.h"
@@ -252,6 +253,7 @@ void register_editor_types() {
 	EditorPlugins::add_by_type<VoxelGIEditorPlugin>();
 
 	// 2D
+	EditorPlugins::add_by_type<Camera2DEditorPlugin>();
 	EditorPlugins::add_by_type<CollisionPolygon2DEditorPlugin>();
 	EditorPlugins::add_by_type<CollisionShape2DEditorPlugin>();
 	EditorPlugins::add_by_type<CPUParticles2DEditorPlugin>();

+ 100 - 5
scene/2d/camera_2d.cpp

@@ -31,8 +31,61 @@
 #include "camera_2d.h"
 
 #include "core/config/project_settings.h"
+#include "core/input/input.h"
 #include "scene/main/viewport.h"
 
+#ifdef TOOLS_ENABLED
+Dictionary Camera2D::_edit_get_state() const {
+	Dictionary state = Node2D::_edit_get_state();
+	state["limit_rect"] = get_limit_rect();
+	return state;
+}
+
+void Camera2D::_edit_set_state(const Dictionary &p_state) {
+	if (p_state.has("limit_rect")) {
+		_set_limit_rect(p_state["limit_rect"]);
+	}
+	Node2D::_edit_set_state(p_state);
+}
+
+void Camera2D::_edit_set_position(const Point2 &p_position) {
+	if (_is_dragging_limit_rect()) {
+		Rect2 rect = get_limit_rect();
+		rect.position = p_position;
+		_set_limit_rect(rect);
+	} else {
+		Node2D::_edit_set_position(p_position);
+	}
+}
+
+Point2 Camera2D::_edit_get_position() const {
+	return _is_dragging_limit_rect() ? get_limit_rect().position : Node2D::_edit_get_position();
+}
+
+void Camera2D::_edit_set_rect(const Rect2 &p_rect) {
+	ERR_FAIL_COND(limit_enabled && !_edit_use_rect());
+	Rect2 rect = p_rect;
+	Vector2 scl = get_global_scale().abs();
+	rect.size *= scl;
+	rect.position = (rect.position + get_global_position()) * scl;
+	_set_limit_rect(rect);
+}
+#endif // TOOLS_ENABLED
+
+#ifdef DEBUG_ENABLED
+Rect2 Camera2D::_edit_get_rect() const {
+	Rect2 rect = get_limit_rect();
+	Vector2 scl = get_global_scale().abs();
+	rect.size /= scl;
+	rect.position = (rect.position - get_global_position()) / scl;
+	return rect;
+}
+
+bool Camera2D::_edit_use_rect() const {
+	return limit_enabled;
+}
+#endif // DEBUG_ENABLED
+
 bool Camera2D::_is_editing_in_editor() const {
 #ifdef TOOLS_ENABLED
 	return is_part_of_edited_scene();
@@ -75,6 +128,10 @@ void Camera2D::_update_scroll() {
 }
 
 #ifdef TOOLS_ENABLED
+bool Camera2D::_is_dragging_limit_rect() const {
+	return _edit_use_rect() && Input::get_singleton()->is_key_pressed(Key::CTRL);
+}
+
 void Camera2D::_project_settings_changed() {
 	if (screen_drawing_enabled) {
 		queue_redraw();
@@ -169,7 +226,7 @@ Transform2D Camera2D::get_camera_transform() {
 		Point2 screen_offset = (anchor_mode == ANCHOR_MODE_DRAG_CENTER ? (screen_size * 0.5 * zoom_scale) : Point2());
 		Rect2 screen_rect(-screen_offset + camera_pos, screen_size * zoom_scale);
 
-		if (limit_smoothing_enabled) {
+		if (limit_enabled && limit_smoothing_enabled) {
 			if (screen_rect.position.x < limit[SIDE_LEFT]) {
 				camera_pos.x -= screen_rect.position.x - limit[SIDE_LEFT];
 			}
@@ -222,7 +279,7 @@ Transform2D Camera2D::get_camera_transform() {
 
 	Rect2 screen_rect(-screen_offset + ret_camera_pos, screen_size * zoom_scale);
 
-	if (!position_smoothing_enabled || !limit_smoothing_enabled) {
+	if (limit_enabled && (!position_smoothing_enabled || !limit_smoothing_enabled)) {
 		if (screen_rect.position.x < limit[SIDE_LEFT]) {
 			screen_rect.position.x = limit[SIDE_LEFT];
 		}
@@ -398,8 +455,7 @@ void Camera2D::_notification(int p_what) {
 				}
 			}
 
-			if (limit_drawing_enabled) {
-				Color limit_drawing_color(1, 1, 0.25, 0.63);
+			if (limit_enabled && limit_drawing_enabled) {
 				real_t limit_drawing_width = -1;
 				if (is_current()) {
 					limit_drawing_width = 3;
@@ -415,7 +471,7 @@ void Camera2D::_notification(int p_what) {
 				};
 
 				for (int i = 0; i < 4; i++) {
-					draw_line(limit_points[i], limit_points[(i + 1) % 4], limit_drawing_color, limit_drawing_width);
+					draw_line(limit_points[i], limit_points[(i + 1) % 4], Color(1, 1, 0.25, 0.63), limit_drawing_width);
 				}
 			}
 
@@ -484,6 +540,22 @@ bool Camera2D::is_ignoring_rotation() const {
 	return ignore_rotation;
 }
 
+void Camera2D::set_limit_enabled(bool p_limit_enabled) {
+	if (p_limit_enabled == limit_enabled) {
+		return;
+	}
+	limit_enabled = p_limit_enabled;
+	_update_scroll();
+#ifdef TOOLS_ENABLED
+	emit_signal("_camera_limit_enabled_updated"); // Used for Camera2DEditorPlugin
+#endif
+	notify_property_list_changed();
+}
+
+bool Camera2D::is_limit_enabled() const {
+	return limit_enabled;
+}
+
 void Camera2D::set_process_callback(Camera2DProcessCallback p_mode) {
 	if (process_callback == p_mode) {
 		return;
@@ -548,6 +620,18 @@ void Camera2D::_update_process_internal_for_smoothing() {
 	set_process_internal(enable);
 }
 
+void Camera2D::_set_limit_rect(const Rect2 &p_limit_rect) {
+	Point2 limit_rect_end = p_limit_rect.get_end();
+	set_limit(SIDE_LEFT, p_limit_rect.position.x);
+	set_limit(SIDE_TOP, p_limit_rect.position.y);
+	set_limit(SIDE_RIGHT, limit_rect_end.x);
+	set_limit(SIDE_BOTTOM, limit_rect_end.y);
+}
+
+Rect2 Camera2D::get_limit_rect() const {
+	return Rect2(limit[SIDE_LEFT], limit[SIDE_TOP], limit[SIDE_RIGHT] - limit[SIDE_LEFT], limit[SIDE_BOTTOM] - limit[SIDE_TOP]);
+}
+
 void Camera2D::make_current() {
 	ERR_FAIL_COND(!enabled || !is_inside_tree());
 	get_tree()->call_group(group_name, "_make_current", this);
@@ -817,6 +901,9 @@ bool Camera2D::is_margin_drawing_enabled() const {
 }
 
 void Camera2D::_validate_property(PropertyInfo &p_property) const {
+	if (!limit_enabled && (p_property.name == "limit_smoothed" || p_property.name == "limit_left" || p_property.name == "limit_top" || p_property.name == "limit_right" || p_property.name == "limit_bottom")) {
+		p_property.usage = PROPERTY_USAGE_NO_EDITOR;
+	}
 	if (!position_smoothing_enabled && p_property.name == "position_smoothing_speed") {
 		p_property.usage = PROPERTY_USAGE_NO_EDITOR;
 	}
@@ -847,6 +934,9 @@ void Camera2D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("is_current"), &Camera2D::is_current);
 	ClassDB::bind_method(D_METHOD("_make_current"), &Camera2D::_make_current);
 
+	ClassDB::bind_method(D_METHOD("set_limit_enabled", "limit_enabled"), &Camera2D::set_limit_enabled);
+	ClassDB::bind_method(D_METHOD("is_limit_enabled"), &Camera2D::is_limit_enabled);
+
 	ClassDB::bind_method(D_METHOD("set_limit", "margin", "limit"), &Camera2D::set_limit);
 	ClassDB::bind_method(D_METHOD("get_limit", "margin"), &Camera2D::get_limit);
 
@@ -913,6 +1003,7 @@ void Camera2D::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "process_callback", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_process_callback", "get_process_callback");
 
 	ADD_GROUP("Limit", "limit_");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "limit_enabled"), "set_limit_enabled", "is_limit_enabled");
 	ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_left", PROPERTY_HINT_NONE, "suffix:px"), "set_limit", "get_limit", SIDE_LEFT);
 	ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_top", PROPERTY_HINT_NONE, "suffix:px"), "set_limit", "get_limit", SIDE_TOP);
 	ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_right", PROPERTY_HINT_NONE, "suffix:px"), "set_limit", "get_limit", SIDE_RIGHT);
@@ -961,4 +1052,8 @@ Camera2D::Camera2D() {
 
 	set_notify_transform(true);
 	set_hide_clip_children(true);
+
+#ifdef TOOLS_ENABLED
+	add_user_signal(MethodInfo("_camera_limit_enabled_updated")); // Camera2DEditorPlugin listens to this
+#endif
 }

+ 26 - 0
scene/2d/camera_2d.h

@@ -72,6 +72,7 @@ protected:
 	real_t rotation_smoothing_speed = 5.0;
 	bool rotation_smoothing_enabled = false;
 
+	bool limit_enabled = true;
 	int limit[4];
 	bool limit_smoothing_enabled = false;
 
@@ -87,7 +88,9 @@ protected:
 	bool _is_editing_in_editor() const;
 	void _update_process_callback();
 	void _update_scroll();
+
 #ifdef TOOLS_ENABLED
+	bool _is_dragging_limit_rect() const;
 	void _project_settings_changed();
 #endif
 
@@ -98,6 +101,8 @@ protected:
 
 	void _update_process_internal_for_smoothing();
 
+	void _set_limit_rect(const Rect2 &p_limit_rect);
+
 	bool screen_drawing_enabled = true;
 	bool limit_drawing_enabled = false;
 	bool margin_drawing_enabled = false;
@@ -122,6 +127,24 @@ protected:
 	void _validate_property(PropertyInfo &p_property) const;
 
 public:
+#ifdef TOOLS_ENABLED
+	virtual Dictionary _edit_get_state() const override;
+	virtual void _edit_set_state(const Dictionary &p_state) override;
+
+	virtual void _edit_set_position(const Point2 &p_position) override;
+	virtual Point2 _edit_get_position() const override;
+
+	virtual void _edit_set_rect(const Rect2 &p_rect) override;
+	virtual Size2 _edit_get_minimum_size() const override { return Size2(); }
+#endif // TOOLS_ENABLED
+
+#ifdef DEBUG_ENABLED
+	virtual Rect2 _edit_get_rect() const override;
+	virtual bool _edit_use_rect() const override;
+#endif // DEBUG_ENABLED
+
+	Rect2 get_limit_rect() const;
+
 	void set_offset(const Vector2 &p_offset);
 	Vector2 get_offset() const;
 
@@ -131,6 +154,9 @@ public:
 	void set_ignore_rotation(bool p_ignore);
 	bool is_ignoring_rotation() const;
 
+	void set_limit_enabled(bool p_limit_enabled);
+	bool is_limit_enabled() const;
+
 	void set_limit(Side p_side, int p_limit);
 	int get_limit(Side p_side) const;