Godot Runtime 3D Gizmo Tutorial Part 1
Pre-tutorial note
This tutorial uses Markdeep! Markdeep renders to HTML locally on your web browser and this may take a second, so please be patient! π
**Godot Runtime 3D Gizmo Tutorial Part 1** # Godot Runtime 3D Gizmo Tutorial OverviewIn this tutorial series, we are going to be making a set of gizmos you can use in 3D, at run time in Godot. We will not be tackling 2D gizmos in this tutorial series, though if you are interested let me know in the comments below! I'd love to make a 2D gizmo tutorial series as well if there is interest. !!! NOTE: Note While this tutorial can be completed by beginners, it is recommended to have some Godot experience before tackling this tutorial as it is fairly complex. This tutorial was made using **Godot 3.0.6!** ## What is gizmo? If you are wondering what a gizmo is, I'll do my best to explain what we will be making. We will be making a set of objects, gizmos, that then the mouse interacts with them, they will perform a certain function. For example, in the picture above there is a yellow square with some colored handles coming out of it. This is the translation gizmo (like the math function). By clicking and dragging on one of the colored handles, you can move the object around on that single axis. For example, clicking and dragging on the green handle will move the object up and down. Another example is clicking and dragging on the yellow cube in the center, which will move the object relative to the camera's view. The idea is that by using multiple gizmos, you can position, rotate, and scale the object until it is in the correct position, with the correct rotation, and the correct scale. ## Why would we want a gizmo? You may be thinking, why would we want 3D gizmos during runtime, especially since we can already position, rotate, and scale nodes in Godot using Godot's built in gizmos. There are several use cases where having gizmos that work in runtime could be very useful. Here are a few ideas: * The reason I got inspired to make this project to begin with is the need in a level editor I am making. Levels in the game are stored in a custom file format and are saved/loaded at runtime, so I needed a gizmo that would allow me to manipulate objects within my level at runtime. * You could add 3D gizmos in a debug mode or sorts so you can easily manipulate objects for testing purposes. For example, you may want to test whether a AI agent can navigate its way out of a maze from any point. By implementing a 3D gizmo, you can move the agent to the position you want within the maze without having to restart the project, go into the scene, move the object, and then launch the game again. * If you want to learn how to place objects from one viewport into another. This could be useful to know for all sorts of projects, not just 3D gizmos. Another reason you may want to make 3D gizmos that work during runtime is simply to know how to make 3D gizmos. I found writing the 3D gizmos incredibly interesting and it made me realize how complicated something as simple seeming as 3D gizmos can be! A added bonus is that we will also cover how to work with multiple viewports, learning how to move/rotate/scale objects relative to the camera, and how to make a free look camera that can select objects in the scene! !!! NOTE: Note if you are wondering why we don't just reuse Godot's gizmos during runtime, there are a few reasons: * For the project I'm making that needs gizmos, this was not a feasible option. * We can learn a lot by making our own version, and as a bonus, if we need to change something we can easily make adjustments. * I have no idea how hard it would be to reuse Godot's gizmos and I do not want to edit the C++ source code or use GDNative for this tutorial. So, let's jump right in and get this tutorial going! # Getting Setup First we need to get everything setup. I have included a starter project that has a prebuilt scene we will be using in this tutorial. Go ahead and [download it HERE](https://drive.google.com/open?id=1LF6RGZYm70FvCw6e9BC2_dH6MVI40O1p) and extract it somewhere on your computer. Open the project up in Godot and let's take a look at what is included. _____ Before we start poking around the scene itself, let's take a look at the project settings. !!! NOTE: Note: If you are wondering why everything looks a tad smaller, it is because I'm using a borrowed Mac computer, and I do not have Godot setup quite right just yet! Hopefully in the future I'll be able to fix it, but for this tutorial the pictures will all be a tad smaller than normal. Sorry! Once you have the project settings open, navigate to the input map tab and scroll down to the bottom. You should find that there are a few input actions already setup and ready. These input actions are what we will be using to move the camera around the scene. Feel free to change them if you want/need. Other than that, the rest of the project settings are the same as the defaults Godot creates. _________ Let's take a look at the included scene and how it is setup next. You may have noticed some of the nodes in the above picture are not fully expanded. This is on purpose and we'll look at these nodes in more details as we use them. First though, let's go over the majority of the nodes in the scene: * Example_Scene
: The root node of the scene. *Normal_Objects
A scene to hold all of the normal objects in the scene. In this case, 'normal' are just nodes that are nodes we will not be directly using in this tutorial. Instead, these nodes are the nodes we will be moving with the gizmos. *Floor
: A StaticBody node that will act as the floor for this example scene. * MeshInstance : A MeshInstance node that holds the mesh used for the floor. *CollisionShape
: A CollisionShape node that holds the collision shape surrounding the floor. *Wall
: A StaticBody node that will act as a wall in this example scene. * MeshInstance : A MeshInstance node that holds the mesh used for the wall. *CollisionShape
: A CollisionShape node that holds the collision shape surrounding the wall. *Wall2
: Another StaticBody node that will act as a wall. The node structure is exactly the same asWall
. *RigidBody_Box
: A RigidBody node that is a simple red box. * MeshInstance : A MeshInstance node that holds the mesh used for the RigidBody. *CollisionShape
: A CollisionShape node that holds the collision shape surrounding the RigidBody. *RigidBody_Prism
: A RigidBody node that is a simple green prism. Outside of the different mesh, material, and collision shape, everything is more or less the same asRigidBody_Box
. *RigidBody_Cylinder
: A RigidBody node that is a simple blue cylinder. Outside of the different mesh, material, and collision shape, everything is more or less the same asRigidBody_Box
. *DirectionalLight
: A DirectionalLight node to help illuminate the scene. * WorldEnvironment : A WorldEnvironment node to add tweak the visuals slightly to make them look a little better. Feel free to delete this node if you want/need, as it is completely unnecessary and is only used for visuals. *Editor_Controller
: A Spatial node to hold all of the nodes we will need for our 3D gizmo editor of sorts. *Editor_UI
: A Control node to hold all of the UI we will need for our editor. *Editor_Viewport
: A Viewport node that we will use to render objects on top of the current scene. This is especially useful for the 3D gizmos, as we want to make them render on top of whatever object is selected. *Editor_Camera_Controller
: A Spatial node that holds all of the nodes we will need to make the camera that will navigate through the scene. As you can see, this is quite a few nodes and we haven't even seen them all yet! Thankfully all of the nodes under theNormal_Objects
node we will not be using at all in this tutorial expect at runtime, and we will not need to do anything to those nodes to make them work during runtime. # Creating The Camera Let's work on the editor camera first, as from there we can continually build on top of it as we go through the tutorial series. But before we start working on anything, let's quickly write all of the code we will need in theEditor_Controller
node, as it is really small, straightforward, and we'll need it for the editor camera. ## Making The Editor Controller Select theEditor_Controller
node and assign a new script calledEditor_Controller.gd
. Save the file where ever you want, I'll just be placing it in theres://
folder, and then add the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
extends Spatial signal on_editor_mode_change(); export (String, "SELECT", "TRANSLATE", "ROTATE", "SCALE") var editor_mode; var is_in_freelook_mode = false; func change_editor_mode(new_mode): editor_mode = new_mode; emit_signal("on_editor_mode_change"); func _input(event): get_node("Editor_Viewport").input(event); |
Let's go through this script, starting with the class variables. For this tutorial, we will be defining **class variables as variables outside of any/all functions**. With that out of the way, let's take a look: *on_editor_mode_change
: Technically this is not a variable but instead is a custom signal that we will be emitting when the UI has changed the editor mode. We'll not be using this until we get to the editor UI. *editor_mode
: A variable to hold the current mode the editor is in, which we will be storing in a string. * If you are wondering, we are exporting the variable so we can see its value in the remote debugger, not so it can be set from the editor, which is an added bonus. * Also, we could make this a integer, a enumerator, or something else, but for simplicity we will be using a string. So long as we are consistent with how we seteditor_mode
, using a string should work fine. *is_in_freelook_mode
: A variable to hold whether the camera is in free look mode. This is we we know whether the camera is moving and so we need to ignore mouse clicks due to the camera's movement. The only variable we really need of this bunch for the player camera isis_in_freelook_mode
. We needis_in_freelook_mode
so we the camera only moves in free look mode and not all the time. Let's quickly go through the functions too, since they are small and easy to quickly blaze through. ### Going Throughchange_editor_mode
First we assigneditor_mode
to the new passed in editor mode,new_mode
. Finally, we emit theon_editor_mode_change
signal using theemit_signal
function. We do not need to pass any arguments along with the signal, so that's all we need to do. ### Going Through_input
All we are doing here is passing whatever input event we have to theEditor_Viewport
node. We need to do this because in Godot Viewport nodes do not receive input events on their own, they have to have input events passed to them. So, all we are doing is for each and every event theEditor_Controller
node receives, we just pass it straight to theEditor_Viewport
node using theinput
function, so all of the children nodes inEditor_Viewport
will receive input events. !!! TIP: Tip If you are wondering why Viewport nodes do not receive input events by default is for a very good reason, though for this project it kinda gets in the way, unfortunately. Viewport nodes do not receive input events by default is because a input event is tailored for the root viewport, the root node of the entire Godot game. For example, this makes things like the mouse by positioned according to the root viewport. Now for our project, this is not a issue because we are going to make theEditor_Viewport
have the exact same size as the root viewport, so in our case all of the elements will line up perfectly with the root viewport. However, if you are doing something like in the [Godot Viewport demos](https://github.com/godotengine/godot-demo-projects/tree/master/viewport), you have Viewports with differing sizes and positions. In this case, you will need to do additional processing so the input events are positioned correctly within the differently sized and positioned Viewport nodes, as they do not have the same size and/or position as the root viewport. However, for this tutorial, theEditor_Viewport
node will have the exact same size and position, so all we need to do is just pass the input events along. ## Making The Editor Camera Controller Now that we have written all of the code we will need in theEditor_Controller
node, we can move on to making the editor camera itself. This code will move the camera through 3D space so we can see what we are doing, and it will also select physics objects within the scene so we can use the 3D gizmos to edit their position, rotation, and/or scale. First, let's take take a look at how the nodes inEditor_Camera_Controller
are setup* Editor_Camera_Controller
: A Spatial node to hold the Camera node that will render the scene. We will be moving this node through 3D space to move the camera, and we will also be rotating this node on the Y axis (left and right) when the mouse moves in free look mode. *View_Camera
: The Camera node that will be rendering the scene. We will be rotating the Camera on the X axis (up and down) when the mouse moves in free look mode. !!! NOTE: note Remember, Camera nodes in Godot render face the negative Z axis. You can check this by clicking a camera node and having the Godot editor set toLocal Space Mode
. You can enable local space mode by pressing a little cube icon in the top right row of buttons in the center scene view.As you can see, we will only need a couple nodes for the camera system we will use to look around the scene. Let's write the code needed for the camera system next! Select Editor_Camera_Controller
and assign a new script calledEditor_Camera_Controller.gd
. Save it where ever you want, I'll just be placing it inres://
, and then add the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
extends Spatial var editor_controller; signal physics_object_selected(object); const NORMAL_COLLISION_LAYER = 1; const CONTROL_SPEED = 2; const MOVE_SPEED = 10; const SHIFT_SPEED = 20; const MOUSE_SENSITIVTY = 0.05; var view_camera = null; const CAMERA_MAX_ROTATION_ANGLE = 70; var send_raycast = false; func _ready(): editor_controller = get_parent(); view_camera = get_node("View_Camera"); func _process(delta): if (Input.is_mouse_button_pressed(BUTTON_RIGHT)): if (Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED): Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED); editor_controller.is_in_freelook_mode = true; else: if (Input.get_mouse_mode() != Input.MOUSE_MODE_VISIBLE): Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE); editor_controller.is_in_freelook_mode = false; if (editor_controller.is_in_freelook_mode == true): process_movement(delta); func process_movement(delta): var movement_vector = Vector3(0, 0, 0); var movement_speed = MOVE_SPEED; if (Input.is_action_pressed("editor_move_forward") == true): movement_vector.z = 1; elif (Input.is_action_pressed("editor_move_backward") == true): movement_vector.z -= 1; if (Input.is_action_pressed("editor_move_right") == true): movement_vector.x = 1; elif (Input.is_action_pressed("editor_move_left") == true): movement_vector.x = -1; if (Input.is_key_pressed(KEY_SHIFT)): movement_speed = SHIFT_SPEED; elif (Input.is_key_pressed(KEY_CONTROL)): movement_speed = CONTROL_SPEED; global_transform.origin += -view_camera.global_transform.basis.z * movement_vector.z * movement_speed * delta; global_transform.origin += view_camera.global_transform.basis.x * movement_vector.x * movement_speed * delta; func _unhandled_input(event): if (editor_controller.is_in_freelook_mode == true): if (event is InputEventMouseMotion): var camera_rotation = view_camera.rotation_degrees; camera_rotation.x = clamp(camera_rotation.x + (-event.relative.y * MOUSE_SENSITIVTY), -CAMERA_MAX_ROTATION_ANGLE, CAMERA_MAX_ROTATION_ANGLE); rotation_degrees.y += -event.relative.x * MOUSE_SENSITIVTY; view_camera.rotation_degrees = camera_rotation; else: if (event is InputEventMouseButton): if (event.button_index == BUTTON_LEFT and event.pressed == true): if (editor_controller.editor_mode == "SELECT"): send_raycast = true; func _physics_process(delta): if (send_raycast == true): send_raycast = false; var selected_node = null; var space_state = get_world().direct_space_state; var raycast_from = view_camera.project_ray_origin(get_tree().root.get_mouse_position()) var raycast_to = raycast_from + view_camera.project_ray_normal(get_tree().root.get_mouse_position()) * 100; var result = space_state.intersect_ray(raycast_from, raycast_to, [self], NORMAL_COLLISION_LAYER); if (result.size() > 0): selected_node = result.collider; emit_signal("physics_object_selected", selected_node); |
This is quite a bit of code, but we'll go through it piece by piece. First, let's go over the class variables: *editor_controller
: A variable to hold the Editor_Controller node. *physics_object_selected
: A custom signal that we will emit when a new physics object has been selected by the camera. The camera can select the following node types: StaticBody, RigidBody, and KinematicBody nodes. *NORMAL_COLLISION_LAYER
: A variable to hold the collision layer that all of the 'normal' nodes we want to be able to select are on. Anything on this layer will be able to be selected. Check out the tip below for how to calculate this number! *CONTROL_SPEED
: A variable to hold the speed the camera moves at when the control key is held down. Generally in most programs, when the control key is held down the camera will move slower. *MOVE_SPEED
: A variable to hold the speed the camera moves at normally. *SHIFT_SPEED
: A variable to hold the speed the camera moves at when the shift key is held down. Generally in most programs, when the shift key is held down the camera will move faster. *MOUSE_SENSITIVITY
: A variable to store how sensitive the mouse is. You may need to adjust this variable based on the sensitivity of your mouse. *view_camera
: A variable to hold the camera that will render the scene. We need this to get directions based on the camera's rotation. *CAMERA_MAX_ROTATION_ANGLE
: A variable to define how far the camera can move on the X axis when in free look mode. We need this to ensure the camera cannot rotate soo far that it flips upside down. *send_raycast
: A variable for storing whether the camera needs to send out a raycast on the next_physics_process
call. !!! TIP: Tip Check out [this answer on the Godot QA](https://godotengine.org/qa/17896/collision-layer-and-masks-in-gdscript) site for how to convert a physics layer to a integer! If you have gone through some of my other tutorials with free looking cameras, then most of these variables will look similar. There is only a few variables specific to the 3D gizmo part of this tutorial series:editor_controller
,physics_object_selected
, andNORMAL_COLLISION_LAYER
. All of the other variables will be used almost exclusively for moving the camera. In fact, most of this code you can reuse in other projects if you want a simple, relatively compact 3D free look camera. !!! NOTE: Note If you are wondering, by free look I must mean a FPS like camera where you can look around the scene with the mouse, and move around the scene with the WASD and arrow keys. Let's look at the functions next, starting with_ready
: ### Going Through_ready
First we get theEditor_Controller
node and assign it to theeditor_controller
variable. !!! WARNING: Warning We are usingget_parent
because in this tutorial, theEditor_Controller
node is the parent node in this scene. However, this is assuming thatEditor_Controller
will always be the parent node, which depending on your project, this may or may not be a safe assumption. Then we get theView_Camera
node and assign it to theview_camera
variable. ### Going Through_process
First we check to see if the right mouse button is pressed or held. We do this by checking to see ifInput.is_mouse_button_pressed(BUTTON_RIGHT)
is equal totrue
. If the right mouse button is pressed/held, then we check to see if the current mouse mode (Input.get_mouse_mode
) is not equal toMOUSE_MODE_CAPTURED
. If it is, then we set the mouse mode toMOUSE_MODE_CAPTURED
using theInput.set_mouse_mode
function. Then we setis_in_freelook_mode
ineditor_controller
totrue
so we can move the camera around when the right mouse button is pressed/held. If the right mouse button is NOT pressed/held, then we check to see if the current mouse mode is not equal toMOUSE_MODE_VISIBLE
. If it is, then we set the mouse mode toMOUSE_MODE_VISIBLE
using theInput.set_mouse_mode
function. Then we setis_in_freelook_mode
ineditor_controller
tofalse
so we can no longer move the camera around, as the right mouse button has been released. !!! NOTE: Note This will make the mouse captured when the right mouse button is pressed, but when the right mouse button is released then the mouse will be released. The last thing we do is check to see ifis_in_freelook_mode
ineditor_controller
istrue
. If it is, then call theprocess_movement
function so the camera can move around the scene. ### Going Throughprocess_movement
First we make two new variables.movement_vector
will store the direction the player intends to go, whilemovement_speed
will store the speed the camera will be moving at. We then check to see if theeditor_move_forward
action is pressed/down using theis_action_pressed
function inInput
. If the action is pressed/held, then we setmovement_vector.z
to-1
. If theeditor_move_backwards
action is pressed/held instead, then we setmovement_vector.z
to1
instead. !!! NOTE: Note Remember how I said Camera nodes face the negative Z axis? That is why we are settingmovement_vector.z
to-1
when the player wants to move forward, and1
when the player wants to move backwards. Then we do similar checks foreditor_move_right
andeditor_move_left
. Ifeditor_move_right
is pressed/held we setmovement_vector.x
to1
, while ifeditor_move_left
is pressed/held we setmovement_vector.x
to-1
. Next we check to see if the shift key is pressed/held down by checking to see ifis_key_pressed(KEY_SHIFT)
returnstrue
. If the shift key is pressed/held, then we setmovement_speed
toSHIFT_SPEED
. Likewise, ifis_key_pressed(KEY_CONTROL)
returnstrue
, then we setmove_speed
toCONTROL_SPEED
instead. Finally, we need to move everything in the direction the player intends to go. To do this, we first add the direction the camera is facing on the negative Z axis (where the camera is looking ->-view_camera.global_transform.basis.z
) multiplied bymovement_vector.z
,delta
, andspeed
to the camera's global position,global_transform.origin
. This will move the camera forwards/backwards according to where the camera is facing. Then we add the direction the camera is facing on the X axis (the right of the camera ->view_camera.global_transform.basis.x
) multiplied bymovement_vector.x
,delta
andmove_speed
to the camera's global position,global_transform.origin
. This will move the camera left/right according to where the camera is facing. ### Going Throughunhandled_input
First we check to see ifis_in_freelook_mode
ineditor_controller
istrue
. If it is, we then check to see if the inputevent
is aInputEventMouseMotion
event. AInputEventMouseMotion
event only happens when the mouse moves on the screen. If theevent
is aInputEventMouseMotion
event, then we we need to rotate the camera based on the mouse movement. First, we get the camera's current rotation and store in a new variable calledcamera_rotation
. We than add the mouse motion (event.relative
) multiplied byMOUSE_SENSITIVITY
to the X axis ofcamera_rotation
. To ensure the rotation on the X axis cannot go so far that the camera flips upside down, we use theClamp
function and pass in-CAMERA_MAX_ROTATION_ANGLE
as the minimum value, andCAMERA_MAX_ROTATION_ANGLE
as the maximum value. This keepscamera_rotation.x
within the range set inCAMERA_MAX_ROTATION_ANGLE
. Next we rotate theEditor_Camera_Controller
node on the Y axis by adding-event.relative.x
multiplied byMOUSE_SENSITIVITY
to the Y axis inrotation_degrees
. Finally, we apply the rotation on the X axis,current_rotation
toview_camera
. ______ Ifis_in_freelook_mode
ineditor_controller
is NOTtrue
, then we check to see if the inputevent
isInputEventMouseButton
.InputEventMouseButton
event only happens with one of the mouse buttons is pressed, released, or held. Ifevent
is aInputEventMouseButton
event, we then check to see if the button is the left mouse button by checking to see ifbutton_index
equalsBUTTON_LEFT
, and we check ifevent.pressed
istrue
, meaning the left mouse button was just pressed. Then we check if theeditor_mode
ineditor_controller
isSELECT
. If it is, then we setsend_raycast
totrue
. ### _physics_process First we check to see ifsend_raycast
istrue
. If it is, we setsend_raycast
to false so we only send out a single raycast per mouse click. Then we make a new variable calledselected_node
and assign its default value tonull
. Next we get the direct space state from the physics world usingget_world().direct_space_state
. Then we get the starting and ending position the raycast we want to create. !!! NOTE: Note See the [page on ray-casting](https://docs.godotengine.org/en/3.0/tutorials/physics/ray-casting.html) on the Godot documentation for more information! We then send out a raycast using theintersect_ray
function inspace_state
and store the results in a new variable calledresult
. Note how we are passing inNORMAL_COLLISION_LAYER
in so the raycast will only collide with objects on that layer! We then check to see if something is stored withinresult
by checking to see if there is at least a single element stored within the dictionary. If there is at least something stored within result, then we setselected_node
to the collider the raycast collided with,result.collider
. Finally, we emit thephysics_object_selected
signal and passselected_node
along with the signal. This will make that when no nodes are found by the raycastnull
will be emitted with the signal, while when the raycast collides with a node then the node the raycast collided with will be passed along. # Creating the UI Now that we have the camera setup, let's really quickly get the UI setup before we end this part. First, let's take a look at how the UI in the scene is setup:* Editor_UI
: The main Control node that holds all of the UI nodes we will need for the editor. See the note below for a important detail! *Gizmo_Viewport
: A TextureRect to display the results of theGizmo_Viewport
node. We will be using this in the next part to display the contents within the gizmo viewport! *Gizmo_Selection
: A Panel node to act as a backdrop for the buttons that will change the editor mode. *Button_Select
: A button that, when pressed, will change the editor mode toSELECT
. *Button_Translate
: A button that, when pressed, will change the editor mode toTRANSLATE
. Side note: *Translate in this case means the math term for moving a object along a axis.* *Button_Rotate
: A button that, when pressed, will change the editor mode toROTATE
. *Button_Scale
: A button that, when pressed, will change the editor mode toSCALE
. *TextureRect
: A TextureRect to hold the texture for that button. All of the buttons have this node, and the texture is just to show what the button does. !!! NOTE: Note Because we want the mouse to be able to interact with the buttons and other elements in the scene, we have theMouse filter
property inEditor_UI
set toignore
so it will ignore mouse events and allow them to pass through! Now that we have seen how the UI is setup in the scene, let's quickly write the code for it. SelectEditor_UI
and make a new script calledEditor_UI.gd
. Save it where ever you want, I'll be usingres://
, and then add the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
extends Control var editor_controller; func _ready(): editor_controller = get_parent(); get_node("Gizmo_Selection/HBoxContainer/Button_Select").connect("pressed", self, "on_mode_button_pressed", ["SELECT"]); get_node("Gizmo_Selection/HBoxContainer/Button_Translate").connect("pressed", self, "on_mode_button_pressed", ["TRANSLATE"]); get_node("Gizmo_Selection/HBoxContainer/Button_Rotate").connect("pressed", self, "on_mode_button_pressed", ["ROTATE"]); get_node("Gizmo_Selection/HBoxContainer/Button_Scale").connect("pressed", self, "on_mode_button_pressed", ["SCALE"]); func on_mode_button_pressed(new_mode): editor_controller.change_editor_mode(new_mode); |
Let's quickly go through this script, starting with the single class variable: *editor_controller
: A variable to hold theEditor_Controller
node. ## Going Through_ready
First we get the parent node ofEditor_UI
, which we assume will beEditor_Controller
and assign it to theeditor_controller
variable. As before, this assumption may or may not work for your project. Then we get each of the four buttons and connect theirpressed
signals to theon_mode_button_pressed
function. We pass in a additional argument, which is the mode that the button will change the editor to. ## Going Throughon_mode_button_pressed
All we are doing here is callingchange_editor_mode
and passing innew_mode
as the argument soeditor_mode
is changed to the passed in mode. # Final NotesWith all that done, we are ready to make the 3D gizmos in the next part of this tutorial series! But for now, go ahead and try running the project. You should find that if you hold the right mouse button down, you can look around using the mouse and move around using the WASD and arrow keys. Right now the UI does not seem to be doing anything, but if you run the remote debugger and look at Editor_Controller
after pushing one of the buttons, you should find that theeditor_mode
variable has changed according to which button you have pressed!In the next part, we'll start working on the 3D gizmos! **You can find Part 2 right here:** ***Coming soon!*** !!! WARNING: Warning If you ever get lost, be sure to read over the code again! If you have any questions, feel free to ask in the comments below! You can access the [finished project for this part here](https://drive.google.com/open?id=1gHZEhkqe2kghDJznJx5UTb44LMw3hXS1)! Please read LICENSE.html
orLICENSE.pdf
for details on attribution. **You must include attributions to use some/all of the assets provided**. See theLICENSE
files for details.Β© RandomMomentania 2019
Great tutorial, thanks!
And the second code block looks like rendered as the HTML text, but through the explanation I can make understand it!
Thanks εεΊζ!
The second code block was written incorrectly in the MarkDeep file so it rendered as HTML text, like you guessed. I must have missed it when I was writing the tutorial… π
It is fixed now, though I’m glad you were able to understand it regardless through the explanation. Thanks for letting me know about the issue so I could fix it!
Thanks again! π