RandomMomentania

Godot Voxel Terrain Tutorial Part 2

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 Voxel Terrain Tutorial**

# Part overview

In this part, we'll add the a way to add and remove voxels to our voxel terrain system, along with a way to spawn spheres that will bounce around the terrain. On top of that, we'll make a FPS camera system for moving around the scene. Let's jump right in! # Making the Player Camera First, let's start by making a way to move the camera around the scene. Open up Main_Scene.tscn (res:// -> Main_Scene.tscn) if it is not already open. You'll find there is a node called Player_Camera. Go ahead and expand it to show its children, and then select it. If you want, go ahead and make the UI node visible. Let's go over it's node structure:
* Player_Camera : A Spatial node to hold all of the nodes needed for the player camera. * View_Camera : The Camera node that is the player's actual view. This is ultimately what renders the view the player sees. * UI : A Control node to hold all of the nodes needed for the UI. * Crosshair : A TextureRect node that holds the texture that will help show the center of the screen. * Voxel_Inventory : A HBoxContainer node to hold all of the buttons that will change the voxel the player can place. * Voxel_Button - Voxel_Button4 : Buttons that will change the voxel the player can place. * Select_Texture : A TexturedRect that helps show which of the buttons is the currently selected button. * Sprite : A Sprite node that shows a image of the voxel that the player will be using when the button is pressed. * FPS_Label : A label to show the FPS (frames per second) Godot is running at. Now that we looked at how everything for the player camera is setup, let's start writing the code that will allow the player camera to work. ## Making The Main Script Let's start with making the main script. This will control the camera, and will add/remove voxels in the world. Select the Player_Camera node and make a new script called Player_Camera.gd. Add the following to Player_Camera.gd:
Let's go over how this script works, starting with its class variables:

* NORMAL_SPEED : The speed the player moves at normally when moving.
* SHIFT_SPEED : The speed the player moves at when the shift key is held down.
* keyboard_control_down : A variable for tracking whether the Control key is pressed/down.
* view_cam : A variable for holding the Camera node.
* MOUSE_SENSITIVITY : A variable for defining how sensitive the mouse is. You may need to adjust this value based on the sensitivity of your mouse!
* MIN_MAX_ANGLE : The minimum and maximum angles the player can move the camera, on the x axis.
* do_raycast : A variable for tracking whether we need to send out a raycast to add/remove a voxel.
* mode_remove_voxel : A variable for determining whether we need to remove a voxel, or add a voxel. If this variable is true, we assume that we need to remove a voxel.
* path_to_voxel_world : A exported NodePath to the voxel world. We make this exported so we can set it to Voxel_World from the editor.
* voxel_world : A variable to hold the node that has Voxel_World.gd.
* current_voxel : The name of the currently selected voxel.
* physics_object_scene : A exported PackedScene to hold the physics object we will spawn when the control button is pressed.

This is quite a few class variables. Hopefully I explained what each one will do in a way that makes sense, but if you have any questions, feel free to ask in the comments section!

Now that we have looked at the class variables, let's take a look at the functions!

### Going Through _ready

All we are doing here is getting the View_Camera node and assigned it to view_cam, and getting the node path_to_voxel_world points to and assigning it to voxel_world.


### Going Through _process

First, we check to see if the right mouse button is pressed/down. If it is, we then check to see if the current mouse mode is *not* MOUSE_MODE_CAPTURED. If it is not, then we set the current mouse mode to MOUSE_MODE_CAPTURED. This is so the player can move around the scene FPS style while holding the right mouse button.

If the right mouse button is not pressed/down, then we check to see if the current mouse mode is *not* MOUSE_MODE_VISIBLE. If it is not, then we set the current mouse mode to MOUSE_MODE_VISIBLE.

________

Next we check to see if the current mouse mode is MOUSE_MODE_CAPTURED. We do this because we only want to process the camera movement code if the right mouse button is pressed/held down.

If it is, we then make a new variable called speed and set its value to NORMAL_SPEED. Then we check to see if the shift key is pressed/down, and if it is we set speed to SHIFT_SPEED.

Next we make another new variable called dir and assign it to a empty Vector3. dir will store the direction the player wants to move towards.

Then, based on which keys are pressed, we change the values in dir on the axis the key that is pressed should move towards. For example, if the W key or up arrow key is pressed, then we set dir.z to -1, so the player will move in the same direction the camera is facing.

!!! TIP: Tip
    In Godot, the Camera nodes faces the negative Z axis!

Once we have checked each of the keys and changed dir as needed, we then normalize dir by calling the normalize function.

Finally, we move the camera according to the direction(s) we need to go.

First, we move the player along the Z axis stored in the basis of the global transform of the camera. We multiply this by speed and delta so the movement will only be as fast as speed and will move at the same rate regardless of the frame rate. This will move the camera forward/backwards according to the direction the camera is facing.

Next, we move the player along the X axis stored in the basis of the global transform of the camera. We multiply this by speed and delta so the movement will only be as fast as speed and will move at the same rate regardless of the frame rate. This will move the camera left/right according to the direction the camera is facing.

Finally, we move the player along the global Y axis, and unlike the others, we always move straight up. We multiply this by speed and delta so the movement will only be as fast as speed and will move at the same rate regardless of the frame rate. This will move the camera up regardless of which direction the camera is facing.

________

The last thing we do is check to see if the control key is pressed.

If it is, we then check to see if keyboard_control_down is false. If it is, then that means the control key has just been pressed.

If the control key has just been pressed, we set keyboard_control_down to true, since it has now been pressed. We then make a new instance of whatever scene physics_object_scene points to. We then set the newly spawned scene's transform to the transform of the camera, and then we add it as a child of the parent of Player_Camera.

If the control key is not pressed, then we set keyboard_control_down to false, so we can detect when the control key is first pressed.


### Going Through _physics_process

First we check to see if do_raycast is equal to true. If it is, we then set do_raycast to false, so we only send out a single raycast at a time.

Then we get the space state and assign it to a variable called space_state, the origin of the ray and assign it to a variable called from, and the end point of the ray and assign it to a variable called to.

!!! NOTE: Note
    Check out the [Raycasting section of the Godot documentation](https://docs.godotengine.org/en/3.0/tutorials/physics/ray-casting.html) for more information on raycasting from code in Godot.

Next we get a result from a raycast using the to and from positions and assign the results of the raycast to a new variable called result. We add the camera as a collision exception so it does not get in the way.

We then check to see if the raycast hit anything by checking to see if raycast is not null. We do this with if (raycast), which will return true so long as raycast is not null.

If the raycast hit something and result is not null, then we check to see if we are adding a voxel, or removing one. We do this by checking to see if mode_remove_voxel is false. If it is, then we are adding a voxel, not removing one.

If we are adding a voxel, mode_remove_voxel is false, then we call set_world_voxel in voxel_world. We pass in the position where the raycast hit, position, and we add half of the normal so the position passed is sticking slightly out of the voxel that the raycast collided with. For the voxel we are placing, we convert current_voxel into a string using get_voxel_int_from_string in voxel_world.

If we are removing a voxel, mode_remove_voxel is true, then we call set_world_voxel in voxel_world. We pass in the position where the raycast hit, position, and then we subtract half of the normal so the position passed is sticking into the voxel that the raycast collided with. For the voxel we are placing, we pass null so the voxel at the raycast collision position is removed.

### Going Through _unhandled_input

!!! NOTE: Note
    _unhandled_input is only called when a input event is NOT captured or processed by other nodes. For example, this means that when you click on a UI/GUI node, _unhandled_input will not receive the event, while _input always receives input events regardless of whether they have already been processed.

First we check to see if the mouse mode is MOUSE_MODE_CAPTURED.

If it is, we then check to see if the event is a InputEventMouseMotion event. We do this because for a FPS view, we need to rotate the camera as the mouse moves.

If the event is a InputEventMouseMotion event, we rotate view_cam on the x axis relative to the vertical, y, motion of the mouse. We also rotate the Player_Camera spatial node on the y axis relative to the horizontal, x, motion of the mouse.

!!! TIP: Tip
    We multiply both of these rotations by MOUSE_SENSITIVITY and -1. MOUSE_SENSITIVITY changes how much rotation the mouse motion will apply to the camera, while the -1 inverts the axis. You may need to change one, or both, of these values according to both your mouse and your personal preferences.

Finally, we clamp the rotation on the x axis in view_cam so the camera cannot rotate upside down. To do this, we get the rotation_degrees vector from view_cam, and then use the clamp function to clamp the rotation on the x axis from -MIN_MAX_ANGLE to MIN_MAX_ANGLE. We then reapply the, now clamped, rotation to view_cam.

_______

If the mouse is not captured, the mouse mode is not MOUSE_MODE_CAPTURED, we check to see if the input event is a InputEventMouseButton event. A InputEventMouseButton event is called whenever the mouse has a button pressed or released.

If the event is a InputEventMouseButton event, then we check to see if the pressed variable in the event is true. pressed will only be true if a mouse button was just pressed. pressed becomes false when the button is held or released.

If the mouse button was just pressed, we then check to see if the left mouse button was pressed, BUTTON_LEFT, or the middle mouse button, BUTTON_MIDDLE.

If the left mouse button is pressed, we set do_raycast to true, and set mode_remove_voxel to false so a new voxel will be placed at the raycast position.

If the middle mouse button is pressed, we set do_raycast to true, and set mode_remove_voxel to true so the voxel at the raycast position will be removed.


## Final touches

Now that we have finished the player camera script, there is a couple more things we need to do before we can move on to the UI. In the scene inspector, select another node and then select Player_Camera again. You should find there are now two exposed Script Variables fields. In the Path To Voxel World field, assign it to Voxel_World, and in the Physics Object Scene field, assign it to the Rigid_Sphere.tscn (res:// -> Rigid_Sphere.tscn) scene.

!!! NOTE: Note
    Feel free to take a look at how Rigid_Sphere.tscn is setup if you want! It has a simple embedded script that will push the sphere forward on the negative z axis, and after a certain amount of time, 6 seconds by default, it will destroy itself.

With that done, go ahead and run the scene! You should find that you can add voxels by pressing the left mouse button, remove voxels with the middle mouse button, and if you hold down the right mouse button, you can move around the scene in a first person style. If you press the control key on your keyboard, you'll find you create a red sphere that bounces around the enviroment for awhile before destroying itself. # Making a FPS label Next, let's add a simple label that will show how many FPS (Frames Per Second) the game is running at. This is helpful for seeing how fast the game is running, and it is really simple and easy to add. Select the FPS_Label node, which is a child of the UI node in Player_Camera and make a new script. Personally, I have just embedded the script into the scene, but feel free to save it to a file if you want! If you want to embed a script into a scene file, just enable the Build-in Script toggle, like in the picture below.
Regardless of how you save the script, once the script editor is open, add the following code:
All this script is doing is getting the current FPS from the Engine class, converting it into a string, and then making the Label node's text display the FPS.

Now when you run the game, in the top left corner of the screen you will be able to see the FPS the game is currently running at! This is handy for quickly seeing if the game is running slow without having to have the Debugger tab in the Godot editor open.


# Adding The Voxel Inventory

The last thing we're going to do for this tutorial part is add a way to change the voxels that is placed when the game is running. We will be making a 'inventory' of sorts. It will not be a true inventory, as we cannot add/remove to it from run time, but it will give us a way to change voxels.

## Making The Main Inventory

First, let's work on the main script that will run the inventory. Select the Voxel_Inventory node, which is a child of the UI node in Player_Camera. Make a new script called Voxel_Inventory.gd, and save it in the UI_Assets folder if you want.

Once Voxel_Inventory.gd is open, add the following code:

Let's go over how this script works, starting with its class variables:

* path_to_player : A exported NodePath to the player camera. This is so we can change the voxel the Player_Camera node will place.
* player : A variable to hold a reference to the Player_Camera node.
* last_pressed_button : A variable to hold a reference to the last pressed button in the inventory. We need this so we can deselect it when a new button is pressed.

Next, let's go through the functions, starting with _ready.

### Going Through _ready

First we check to see if path_to_player is null. If it is, then we simply just return. If path_to_player is not null, then we get the node it points to and assign it to the player variable for later use.

### Going Through change_player_voxel

This function will change the player's voxel to the passed in voxel, new_voxel_name. This function also expects the button that was pressed to send itself as an argument, button, so we can deselect it when another button is pressed.

First, we check to see if the path_to_player variable is null. If it is, then we simply return.

Next, we set the player's current_voxel variable to the passed in voxel name, new_voxel_name.

Then we check to see if last_pressed_button is not equal to null. If it is not, then we call the deselect function in last_button_pressed so the button can do whatever it needs to do when it is not selected.

Finally, we assign last_pressed_button to the passed in button, button, and then we call it's select function so it can do whatever it needs to do when the button is selected.

## Making The Inventory Buttons

Now all that is left is making the buttons that will call change_player_voxel when they are pressed. Select one of the Voxel_Button nodes and make a new script called Voxel_Inventory_Button.gd. Save it in the UI_Assets folder if you want, and then add the following code:

Let's go through this script, starting with the class variables:

* voxel_name : A exported string to hold the name that this button will change the voxel to when it is pressed.
* start_selected : A exported boolean for setting whether this button should be selected already when the game is started.

Next, let's look at the functions, starting with _ready.

### Going Through _ready

First we connect the pressed signal from the Button node to the on_press function. This is the same process as if you connect signals in the Godot editor, but we're just doing in through code instead of through the Godot Editor.

Next, we check to see if start_selected is true. If it is, then we tell Voxel_Inventory that this button is/was selected. We then call the select function so the button can do whatever it needs to do when it is selected.

!!! NOTE: Note
    We are making a assumption that Voxel_Inventory will always be the parent node of the voxels buttons. For this tutorial, this is not a huge deal, but for bigger projects it may be advisable not to make assumptions on how a scene will be setup as thing can change as a game develops.

If start_selected is false, then we just call deselect, so the button can do whatever it needs to do when deselected.

### Going Through on_press

All we are doing here is calling the change_player_voxel function in Voxel_Inventory and passing in voxel_name as the name of the voxel we want to change to, and self as the button.

### Going Through select

All we are doing here is making the Select_Texture node visible, so there is a green boarder around the button.

### Going Through deselect

Like with select, all we are doing here is making the Select_Texture node invisible, so there is no longer a green boarder around the button.

# Final touches

Now there is only a couple things we need to do, and then the inventory is complete.

First, select the Voxel_Inventory node and assign the Path To Player script variable so that it points to Player_Camera.

Then select each of the Voxel_Button nodes, and set the Voxel Name and Start Selected properties to the proper values for each button. For the included starter asset project, I will be using the following values:

* Voxel_Button : Voxel Name -> Stone, Start Selected -> False
* Voxel_Button2 : Voxel Name -> Cobble, Start Selected -> True
* Voxel_Button3 : Voxel Name -> Dirt, Start Selected -> False
* Voxel_Button4 : Voxel Name -> Grass, Start Selected -> False

# Final Notes

Go ahead and a try running the project, and now when you click the inventory buttons on the bottom, you should find that it will change the voxels you can place when you click with the left mouse button! Now you have a working voxel terrain system that you can add and remove voxels from. Everything for the voxel terrain is created using the SurfaceTool. Hopefully this helps show how powerful the SurfaceTool can be, and now you have a cool Godot voxel terrain system you can use in your projects. There are still several things we could add to this system. If you are looking for ideas, here are some things you could try: * Make it where you can save/load the voxel world to a file. This would allow for sharing terrain across computers! * Using the vertex colors, you could add some simple ambient occlusion to the corners of voxels. * Add a navigation mesh and a navigation agent! In theory you would just need to use the collision mesh to generate a navigation mesh. * Convert this code to C++ and use GDNative for improved performance. Because we are using GDScript, which is fast enough for most cases but struggles a bit here, and the SurfaceTool, which is really intended to only be used on meshes that do not change very often, performance is not quite as good as it could be. By using GDNative and C++, you could probably speed things up considerably. * Add a first person character akin to the main character in MineCraft. * Add a 'real' inventory that can hold varying amounts and types of voxels. !!! 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 here](https://drive.google.com/open?id=1VHVtF6iFoPa24iHUK4t6yNiRfDh13vZ8)! You can also find the downloads for this tutorial series on the [Downloads page](https://randommomentania.com/freebie-downloads/). Please read LICENSE.html or LICENSE.pdf for details on attribution. **You must include attributions to use some/all of the assets provided**. See the LICENSE files for details. !!! TIP: Thanks
Thanks for going through this tutorial! If you like what we do, **please consider buying one of [our paid games](https://randommomentania.com/paid-games/) or [products](https://randommomentania.com/other-products/) to help support RandomMomentania!** Have a suggestion for a future tutorial? Let us know either in the comments section below, or by following the instructions on our [Contact and FAQ](https://randommomentania.com/contact/) page!
© RandomMomentania 2019

Leave a Reply

Your email address will not be published. Required fields are marked *