godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.13k stars 93 forks source link

Allow to control the Camera 3D in "Preview" mode #3297

Open golddotasksquestions opened 3 years ago

golddotasksquestions commented 3 years ago

Describe the project you are working on

Applies to any 3D project.

Describe the problem or limitation you are having in your project

Currently when enabling "preview" in the Editor viewport when a Camera node is selected, we are able to "see through the lens" of that camera.

However we cannot edit any objects in the viewport anymore and we can't translate or rotate the camera. The common recommendation given is to split the screen, and use one viewport to "look through the lens" and another to edit objects and move the camera around.

This has several massive disadvantages though:

This feature improvement has been requested a number of times over the years: https://github.com/godotengine/godot/issues/9550 https://github.com/godotengine/godot/issues/9135 https://github.com/godotengine/godot/issues/20248

Describe the feature / enhancement and how it helps to overcome the problem or limitation

It would allow me to generally control, place and position Camera nodes more easily and finally create cinematic content in Godot.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

When a Camera "Preview" is enabled, and this viewport is the active one (in case there are multiple), the User has the same level of control to move and rotate a Camera node, as they have with the default Editor viewport camera.

Controls also should be identical with one exception:

While currently the Editor viewport camera is focused on a point (F) and uses the middle mouse button to circle around this point, the "Preview" Camera should stay at it's translation and only rotate on the x and y axis, when the middle mouse button is pressed. This feels like you would hold the camera in your hands and pan it around.

Example GDScript:

func _input(event):
    if event is InputEventMouseMotion:
        if Input.is_action_pressed("MMB"):
            camera.rotation_degrees.y -= clamp(event.relative.x * 0.5, -5.0, 5.0)
            camera.rotation_degrees.x -= clamp(event.relative.y * 0.5, -5.0, 5.0)

Rightclick+AWDS and mouse wheel should be the same as the Editor viewport camera.

If this enhancement will not be used often, can it be worked around with a few lines of script?

It's easy to write scripts that will allow this behavior while the project is running, but simple tool scripts don't work with inputs, and EditorPlugins are on a completely different level of complexity and challenge. The "Preview" property also does not seem to be exposed anywhere.

Is there a reason why this should be core and not an add-on in the asset library?

It about an existing core feature which usefulness is halved without this improvement.

Calinou commented 3 years ago

Related to https://github.com/godotengine/godot-proposals/issues/1017.

I would require holding an additional modifier to move the camera when the 3D preview is enabled, as the risk of accidentally moving the Camera3D node is quite high otherwise. Maybe we should add an Unlock button that you need to click first?

golddotasksquestions commented 3 years ago

@Calinou I don't think a modifier would improve anything. The middle mouse button is not commonly used in the Editor unless you want to rotate the camera pivot. For the freelook AWDS controlls, you already have to keep right mouse button pressed, which is also not a common thing.

It's exactly the same usecase as with the default Editor Camera. Intuitively I expect the default Editor Camera controls to work in the "Preview" viewport mode, but they do not.

If there would be any modifier key, it would only make this feature harder to discover any less user friendly.

Razoric480 commented 3 years ago

I'd make any movement that affects a preview camera to be un-doable instead, as it's changing an object's properties, which is different from changing the main camera view.

dodgyville commented 3 years ago

This is my most requested feature in godot by far. At the risk of over-complicating it, please consider implementing this in a way that allows node types other than Camera to be included. The same mechanic would allow precise placement for spotlight and position3D nodes. However if it is only doable for Camera that is still a great improvement.

golddotasksquestions commented 3 years ago

@dodgyville Totally agree! I'm not sure it these features would not need separate proposals though. Anyways, I'm linking to your issue here as well, as the behaviour would indeed be very similar if not identical: https://github.com/godotengine/godot/issues/12083

golddotasksquestions commented 3 years ago

Good news! I found a workaround!

After years of unsuccessful trying, i have yesterday given up on creating an EditorPlugin for this. I have long given up on tool script, as I could not get any Inputs to work with them. _input(event) does not work, neither does _unhandled_input(event) , _gui_input(event), or Input.is_action_pressed("action") ...

Turns out what does work is Input.is_mouse_button_pressed(BUTTON_MIDDLE), who knew! It's also hard to figure out because it only seems to work in _process(). I wish these things would be documented. It it where, this would have saved me literally weeks of trail and error and frustration with tool scripts.

Anyways, here is the workaround for this issue:

Add your Camera to a group, then add a Node2D and attach this script:

tool
extends Node2D

var prev_pos = Vector2.ZERO
var camera = null

func _init():
    camera = get_tree().get_nodes_in_group("camera")[0]

func _process(delta):
    if Input.is_mouse_button_pressed(BUTTON_MIDDLE):
        var relativ_vec = get_global_mouse_position() - prev_pos
        camera.rotation_degrees.y -= clamp(relativ_vec.x * 0.5, -5.0, 5.0)
        camera.rotation_degrees.x -= clamp(relativ_vec.y * 0.5, -5.0, 5.0)
        prev_pos = get_global_mouse_position()

Now if I knew how to run this tool script only when above a Viewport that has "Preview" enabled ....

Razoric480 commented 3 years ago

@golddotasksquestions

func _connect_preview_checkbox(interface: EditorInterface) -> void:
    preview_checkbox = _find_by_type_name(editor_viewport, "CheckBox")

    if not preview_checkbox.is_connected("pressed", self, "_on_Preview_pressed"):
        preview_checkbox.connect("pressed", self, "_on_Preview_pressed")

func _find_by_type_name(parent: Node, type_name: String) -> Node:
    for child in parent.get_children():
        if child.get_class() == type_name:
            return child
        else:
            var result: Node = _find_by_type_name(child, type_name)
            if result:
                return result
    return null

To get an EditorPlugin to provide input events, see here: https://docs.godotengine.org/en/stable/classes/class_editorplugin.html#class-editorplugin-method-forward-canvas-gui-input You need to implement handles() and forward_canvas_gui_input(event).

golddotasksquestions commented 3 years ago

@Razoric480 Is this meant to work in a Node2D tool script, or does this only work in an EditorPlugin? How would I get the editor viewport? Even Zylann seems to be troubled with this question.

Razoric480 commented 3 years ago

No, it's for editor plugin.

EricEzaM commented 3 years ago

See UE4 "Pilot" feature for reference in another engine.

Schroedi commented 2 years ago

Coming from UE4, this was something I immediately missed.

Before I found this proposal, I made an add-on for the Add-on jam that allows you to control the selected camera and have a preview in the inspector: https://itch.io/jam/godot-addons-jam-1/rate/1379019

Screen Shot 2022-01-31 at 11 55 07

Another feature I did not know about is the option to set an object's transform to the current view transform: https://github.com/godotengine/godot-proposals/issues/1017#issuecomment-640053270

ARez2 commented 1 year ago

I just wanted to write a proposal very similar to this. My motivation was seeing how it is done inside Blender. Here is a video of that:

https://github.com/godotengine/godot-proposals/assets/75789249/5e900361-0d9d-44c2-bed2-a4196ff52464

As you can see in the video, the way to "activate" this mode inside Blender is first checking the Box "Camera to View" inside the "View" menu on the right of the Viewport, then going into the preview mode of the Camera (pressing NUM_0).

In Godot this could work by adding another checkbox to the Viewport Menu (same menu where "Align transform with view" is) and if checked, wait for a Camera3D to be used as Preview and if there is one, use the "Align transform with view" logic, as defined in

https://github.com/godotengine/godot/blob/cb7730c5b263967660077eb3bccda9ad52741bba/editor/plugins/node_3d_editor_plugin.cpp#L3264

to constantly set the current Camera3D transform to the viewport camera transform.

Maybe it would be a good idea to separate the logic (see the Link above) into a function so that it can be called from where the "Camera to View" mode requires it.

marciodrosa commented 1 year ago

Hi there, I'm starting to work on a (future) PR to implement this.

Regarding what @dodgyville said, it's actually easier to 'pilot' other nodes like spotlights than cameras. I will explain that in a moment.

First, let me show my approach. When one (and only one) node is selected, it can be used in 'pilot' mode by selecting this new menu option: Captura de tela 2023-10-03 111508

Then, a button will appear in the viewport to stop the pilot mode (let me know if you think the texts are ok; I'm not an English native speaker): Captura de tela 2023-10-03 111612

If none (or more than one) node is selected, the menu option is shown as this (disabled):

Captura de tela 2023-10-03 111424

So, when the user enters the pilot mode, the camera is positioned to the same transform as the selected node. When the user moves the editor's camera, the node's transform is synchronized with it. I'm currently implementing the undo/redo operations, which is quite easy on 'free look mode' (because the code already have a funcion that is called when the movement is started and when it is finished, which are the moments the undo/redo actions are commited), but not easy to do when using any other form of navigation. I think it is possible to implement, but it will require a few refactorings in order to not break as soon as another developer does any change in this already big and hard to mantain class.

I'm also need to implement some cases where the 'pilot mode' should be dismissed, like when the user changes from perspective to an orthogonal view.

I will talk about the approach to cameras in the next comment.

marciodrosa commented 1 year ago

So, regarding cameras:

In my opinion, 'preview' and 'pilot' a camera should be two separated things - which, however, can be done at the same time. It may seem weird, but it's kind the way Unreal does too (you can pilot a camera with or without previewing other camera's properties, like FOV). And we can make a specific UI for cameras. The button "Stop pilloting" will never appear if the camera is in preview mode. Instead, I'm thinking in something like this: Captura de tela 2023-10-03 113353

So, the questions:

Why would a user want to preview the camera without pilot it? To avoid unexpected movements when the user forgets it is in preview mode. As an Unreal Editor user, I did it a lot... it seems important, in my opinion.

Why would a user want to pilot a camera without preview it? Ok, this is more a technical limitation. There are a few different configurations between the camera node the user has in the scene and the editor's camera (which, for example, is configured to show gizmos and other things). This is why you can't, currently, move the camera in preview mode AND also can't select other nodes by clicking over it in the viewport while in 'preview mode'. So, if you want to pilot your camera, but want the same habilities you have with the editor's camera, you need to pilot it without preview it.

Is this the best approach? Maybe not, but I think it may be improved in the future. Currently, this is the only simple solution that I can see, and it seems way better than what we currently have.