I recently switched from Unity to Godot and I’m still adjusting. So far, the experience has been great, but I was wondering about one small yet very useful feature that many 3D applications have: the ability to center the editor camera around a point.
In Unity, I can simply middle-click anywhere in the scene, and the editor camera automatically centers on that spot. This lets me easily rotate around it, and any zoom or pan movements are adjusted relative to that point. Blender offers the same feature, and once you get used to it, it becomes indispensable.
So, I’m curious—does Godot have a similar function?
Not quite. I’m looking to center the camera around the clicked point, not around a node. To make it clearer, I’ll just show you.
In this Godot video, I’m trying to center the camera around the top of the tower with the small “antenna” so I can orbit around that point and inspect it. It is possible, but in my opinion the process is very cumbersome. (Sorry about external link, forum won’t let me upload files because I’m a new user)
I also recorded another video, this time in Unity, doing the same thing. In contrast, I can simply middle‑click on the top of the tower and the view is instantly centered around the clicked point. For demonstration, I clicked a few different spots to show how easy it is to center the camera around any point.
So my question is: does Godot have a similar feature? Since this was added to Blender and Unity, it has become essential for me because it greatly simplifies navigating the 3D inspector.
Here’s a draft plugin. You can try it out and see how it works for you.
The command is ALT + middle click. It works only on meshes and ground. Could be extended to colliders.
It ended up a little bit trickier than I expected. Mesh picking works via gizmos but gizmo functionality that allows on-demand ray hit testing is not exposed to scripting i.e. EditorNode3DGizmo::intersect_ray()
Because of this, the plugin needs to pluck triangular meshes from the gpu buffers and do the “manual” ray testing. This is currently done brute force every time the user alt clicks, for all mesh instances. Performance needs to be tested with mesh heavy scenes. Plenty of room for improvement here.
I also used a rather hacky method to execute the built in focusing. Again the control over camera cursor is not exposed to scripting so the only way to do this is tricking the editor to execute the menu callback. If someone knows a better way - please share.
Doing this as a native code extension or source code intervention would be quite a bit simpler as there is access to all of the needed functionality via just a few calls, but then the thing would need to be compiled per platform. So this may be a good enough compromise.
@tool
extends EditorPlugin
func _enable_plugin() -> void:
# enable forwarding of 3d viewport events even if no node is selected
set_input_event_forwarding_always_enabled()
func _forward_3d_gui_input(viewport_camera, event) -> int:
# do our thing on ALT + MMB
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_MIDDLE and event.is_pressed() and event.alt_pressed:
focus_cam_to_surface_point(event.position, viewport_camera)
return AFTER_GUI_INPUT_PASS
func focus_cam_to_surface_point(screen_position: Vector2, cam: Camera3D) -> void:
# get all mesh instances in the scene
var scene_root: Node = EditorInterface.get_edited_scene_root()
var mesh_instances: Array = scene_root.find_children("*", "MeshInstance3D", true, false)
# get pick ray in world space
var ray_origin: Vector3 = cam.project_ray_origin(screen_position)
var ray_direction: Vector3 = cam.project_ray_normal(screen_position)
# collect pick ray hitpoints on all meshes. SLOW! could use some optimization (e.g. cache the triangle meshes)
var hitpoints: Array[Vector3] = []
for instance: MeshInstance3D in mesh_instances:
# make trimesh
var trimesh: TriangleMesh = instance.mesh.generate_triangle_mesh()
# global to instance local matrix
var to_instance_space = instance.global_transform.affine_inverse()
# transform the pick ray into instance local space
var ray_origin_local: Vector3 = to_instance_space * ray_origin
var ray_direction_local: Vector3 = to_instance_space.basis * ray_direction
# intersect and append hitpoint if mesh was hit
var result: Dictionary = trimesh.intersect_ray(ray_origin_local, ray_direction_local)
if result:
hitpoints.append(instance.global_transform * result["position"])
# sort hitpoints by distance to camera
hitpoints.sort_custom(
func(a: Vector3, b: Vector3):
return cam.global_position.distance_squared_to(a) < cam.global_position.distance_squared_to(b)
)
# decide the target position: nearest hitpoint or ground if there are no hitpoints
var target_position: Vector3
if hitpoints.is_empty():
var plane_hit: Variant = Plane(Vector3.UP).intersects_ray(ray_origin, ray_direction)
if plane_hit:
target_position = plane_hit
else:
return # ray cannot hit ground, we're out of target options
else:
target_position = hitpoints[0]
# create a dummy target node
var dummy_target: Node3D = Node3D.new()
scene_root.add_child(dummy_target)
dummy_target.owner = scene_root # set the owner so we can see the node in the editor if something fails, just for debugging
dummy_target.global_position = target_position
# store the current selection and select the dummy target node
var editor_selection: EditorSelection = EditorInterface.get_selection()
var selected_nodes_before: Array = editor_selection.get_selected_nodes()
editor_selection.clear()
editor_selection.add_node(dummy_target)
# trigger editor's built in focus by faking the menu hotkey hit, happens in all 4 views
# (find a better way to do this)
for b in EditorInterface.get_editor_main_screen().find_children("*", "Button", true, false):
if b.text.contains("Perspective"):
var p: PopupMenu = b.find_children("*", "PopupMenu", false, false)[0]
var e: InputEventKey = InputEventKey.new()
e.keycode = KEY_F
e.pressed = true
p.activate_item_by_event(e)
# delete the dummy target node and restore the selection
dummy_target.queue_free()
for node in selected_nodes_before:
editor_selection.add_node(node)
This would definitely be neat to have built in. Maybe some of the higher-ups know if it’s already on the short term to do list, @athousandships?
If not, I might tidy it up a bit and upload to the asset lib.
I actually made a source code implementation and a proposal but turns out there already was a still open pull request from 6 months ago. The main point of contention is the control scheme. The simple ALT+MMB is apparently a no go because of recent addition of customizable camera navigation schemes that emulate other popular 3D programs (Maya, Modo etc). My suggestion was to use all 3 mod keys + mouse click so it doesn’t interfere with any of those classic nav schemes. Either that or a single hotkey.
All in all, the thing works superbly when integrated natively. Hopefully we’ll see it in the next release. If not, you can always compile my fork
I wish they just added it to the engine, too bad it conflicts with nav schemes.
Is there a way to make a request to the dev team to include this feature?
The pull request is already there. You can go upvote and comment on it. You can upvote my proposal as well, although it was closed. Doing so may increase the likelihood of lead developers paying more attention to the whole thing.