Learn 3D Graphics

Game Development with Godot + Blender

Learn the basics of creating a 3D game in the Godot game engine with assets created in Blender.

Godot Workspace, Concepts & Projects

Godot is an open source Game Engine designed by game developers for game developers. It is free to use - no matter how much money you make from your games. No accounts or other apps are needed!

Godot is already installed on the lab's computers, so there's no special setup necessary. Intallation is really easy though, if you choose to get Godot at home.

Godot uses it's own scripting language - called GDScript by default. This is an object based programming language - again created by game developers for game developers. While there is a learning curve (especially if you are new to programming), it's about as easy as it gets due to the high level of integration with Godot.

It is possible to use other common coding languages such as C#, but it requires additional setup.

  1. The very first time opening Godot, it will ask if you want to load a project from the asset library, since there are no available projects - choose "Cancel" Image is missing
  2. On the top left, click on "New Project" Image is missing
  3. Select a location to save the project:
    1. Click on Browse, and choose an easy to find directory - ie. Documents
    2. Click on "Create Folder" and make a folder to contain any games you make (the project name will automatically update to the folder name)
    3. Click on "Choose Current Folder"
  4. Choose a Renderer (this can be changed at any time later on, but may require changes to scenes)
    • Forward+ - for Desktop, great for complex scenes & advanced graphics though slow for simple scenes (the tutorial is based upon this Renderer)
    • Mobile - for Desktop/Mobile, great for simple scenes but has more graphics limitations & is not as strong for complex scenes
    • Compatibility - for Desktop/Mobile/Web, best for simple scenes and old/low-end devices, worst for advanced graphics
  5. Click on "Create & Edit"

Scene Tree

The Scene Tree (top left), displays all the assets in the specific scene in a "node" format. Nodes are the smallest building blocks that make up the scene.

The tree shows the parent/child relationship between different nodes. Making a change to a parent always impacts the child. At the same time, children can be adjusted independently from the parent.

New nodes can be created/added/removed from the Scene Tree

File System

The File System (bottom left) gives access to all of the assets/files for the project.

The Inspector

The Inspector (right side) shows the properties for the active node. The Node tab behind it also gives access to signals and groups.

Viewport

The Viewport has mulitple modes: 2D View, 3D View, Script Editor, and an Asset Library. Most often we will go back and forth between the 3D View, where we can see the virtual world of our game and the Script Editor where we will code different behaviors for our assets.

Bottom Panel

  1. In the top menu, go to "Editor" → "Editor Settings" Image is missing
  2. In the "General" tab, under "FileSystem", got to "Import"
  3. Next to "Blender 3 Path", type: /Applications/Blender.app/Contents/MacOS/ and then press return/enter Image is missing
  4. Choose "Save & Restart" at the bottom left

GDScript is the default scripting language in Godot. Here's some basic vocabulary and syntax information to get started. More specifics will follow later.

  • Class — a category or collection of related parts/objects that act as a blueprint to help create your own version in the related category
    • Extending the class gives the script access to methods, functions, and properties from that class or other classes inherited by the class
  • Add # in front of a line to write comments:
    • For organization/clarity
    • For reminders of what the code does or how it works
    • To temporarily disable lines of code while troubleshooting
  • Methods - default procedures/actions
  • Properties - attributes
  • Functions - lists of directions, each with a specific and unique name/identifier:
    • Functions begin with:
      
      # Functions might be built-in or might be completely custom
      # Parameters are temporary variables(changeable, custom data) specific to a function that pass information to that function
      # If there are multiple parameters, they are listed with commas between (param1, param2)
      # Some functions have no parameters and the () will be left empty
      
      func function_name(parameter):
      
      
    • Use the tab key to indent any lines inside the function:
      
      # Indenting shows the hierarchy - or what is part of the same block of code
      
      func function_name(parameter):
      	some code telling the function what to do will go here...
      	any code at this same indent level is part of the function...
      
      
  • Variables are used to set changeable data in a script:
    • Variables can be named anything, but should use _ instead of a space when using a name wth multiple words
    • Global variables are written outside of functions and can be used by any following function
    • Local variables are written inside a function and can only be used by that function
    • Variables have a variety of types:
      • string - text (ie. string : 'some name')
      • float - decimal numbers (ie. float : '1.0')
      • int - whole numbers (ie. int : '1')
      • str - converts numbers to a string
    • Variables are written as:
      • var variable_name = value (this syntax provides no hints if the wrong type is used elsewhere in the code)
      • var variable_name : variable_type = value (if the wrong type is used with this variable elsewhere in the code, this gives a hint about what is wrong)
      • var variable_name : = value (Godot guesses the type based on the value and provides hints like above when the wrong type is used)
    • Adding @export in front of the variable makes it public - so that the variable can be seen and changed in Godot's Inspector for easy testing purposes

Models from Blender can be added to Godot to use as parts of the game. Any animated actions (even shape key animations) that have pushed to the NLA Editor, will be available to use in Godot with the help of a little scripting.

Depending on the specific materials, they might might automatically be applied on the Godot end, or there might be additional setup. Some developers prefer to work with materials directly in Godot while others prefer to bake the Blender materials into image textures.

The standard format for models in Godot is glb/glTF. We'll actually just use the .blend file itself (it gets converted to glb automatically by Godot) so that any saved changes on the Blender side will automatically update in Godot.

  1. Create (and rig if necessary) an object for the player like normal
  2. Create any animations as different actions (with the Action Editor mode of the Dope Sheet for normal keyframes or the Shape Key Editor mode for shape keys) - ie. run, jump, idle or etc. & push them down to the NLA editor (Non-Linear Animation)
  3. Preparing Materials for Export:
    1. The Principled BSDF Shader will automatically be applied to the object in Godot - no additional changes are needed (this is currently the only material that works this way)
    2. For any other materials, they need to be "baked" into an image texture:
      1. Create a U/V map of the mesh (if one hasn't been created already)
        1. In Edit mode, select the entire mesh with "A"
        2. Press "U" and choose one of the unwrap optons (Smart UV Project is recommended)
        3. In the "UV Editing" workspace, adjust the map
      2. Create an image to bake the material onto:
        1. In the "UV Editing" workspace, click on "+ New" at the top of the "UV Editor" panel
        2. Give the image a name and choose "OK"
        3. Save the image using the "Image" menu → "Save" at the top of the UV Editor (save the image directly into the Assets/Materials folder for the game project)
      3. "Connect" the image to each material (this step can get much more complicated depending on the complexity of the material — there are some great video tutorials if you need a better result):
        1. In the "Shading" workspace, add an image texture node to the active material (Shift+A → Texture → Image Texture)
        2. Place the node to the side (not directly connected to the material nodes)
        3. Open the blank, saved image Image is missing
        4. In the "Materials" tab of the properties panel, switch to the next material & repeat the previous steps so that the empty saved image is to the side of each material's nodes Image is missing
        5. Repeat for any additional materials
      4. Bake the materials onto the image:
        1. In the properties panel, switch to the "Render" tab of the properties panel, set the render engine to "Cycles"
        2. Optionally, under "Light Paths", check "Fast GI Approximation" to use ambient lighting Image is missing
        3. The AO distance can be increased as needed
        4. Still in the "Render" tab, expand the "Bake" subsection and click on "Bake" Image is missing
      5. Switch the material(s) to use that baked image texture rather than the original color(s)
    3. Save the file in the game folder:
      1. In the File menu, choose "Save" — or "Save As" if the file has already been saved
      2. In the Save dialog, select a location:
        1. Use the sidebar of the Save dialog to navigate to your game folder
        2. Once the game directory is open, click on the new folder icon at the top of the dialog to create an "Art" folder
        3. Choose that new directory after it is created
      3. Choose a name - ie. player.blend
      4. Click on "Save Blender File"
  1. In the empty scene, click on "+ Other Node" in the Scene Tree
  2. Search for "character" and choose "CharacterBody3D" (this node is great for players as it can be setup to move/collide and you have control over the movement)
  3. Add the ability to rotate properly with future animations:
    1. Right-click on the node and choose "Add Child Node" from the context menu
    2. Search for "Charcter" and choose "Node3D"
    3. Double-click on the Node3D node to rename it "Pivot" (it is helpful to name things as you are going to make the function more clear)
  4. Import the player model by itself:
    1. In the FileSystem, expand the "Art" directory
    2. Double-click on the player.blend file to access the Import dialog
    3. Use the Scene tab to select the different assets in file
    4. On the right sidebar, check "Skip Import" for any models/assets that aren't part of the player — ie. references, lights, camera or etc.
    5. Adjust other settings if needed
    6. Click on "Reimport"
  5. Add the player model as a child of the Pivot node:
    1. Drag the player.blend file from the FileSystem panel over "Pivot" in the SceneTree and release to make the player model Pivot's child
    2. Alternately, create a placeholder object such as a cylinder to use temporarily as a player object (New Child Node → search for CSGCylinder3D ) and then replace it later in the process. It is easy to delete the placeholder and then add in the actual player model in the same place. In that case, the exact player size/position and the collision shape that gets added in the next step, may need adjustment
  6. Notice the warning icon to the right of the CharacterBody3D node - whenever you see this, it lets you know that something is missing or incorrect in the node setup - clicking on it brings up a hint about how to fix the problem - in this case, the node needs a collider
  7. Fix the warning by setting up a collision shape:
    1. Right-click on the CharacterBody3D node and choose "Add Child Node" from the context menu
    2. Search for "collision"
    3. Choose "CollisionShape3D" and click "Create"
    4. Notice another warning - in this case, the collision shape needs a shape set
    5. In the "Inspector", next to shape, click on empty then set a shape that makes sense for your model - "Capsule" is popular for players
    6. Correct the size/position of the shape to fit the player:
      1. Tap on the name of the shape (ie. CapsuleShape3D) to access/change the size settings (or just click and drag on the red circles of the collision shape in the 3D window)
      2. Use the Transform → Position settings (or use the triangular handles of the Transform widget) to adjust the location as needed
  8. Move the Player node to rest on top of the grid:
    1. In the Scene panel, select the Player node
    2. In the 3D window, change the view to look at the front or side by clicking on either X or Z in the View widget
    3. Use the Transform → Position settings (or use the triangular green handle of the Transform widget) to adjust the Y location as needed
    4. Keeping the Collision shape in particular just above the grid will prevent the player from falling through the floor/ground later on

A great feature in Godot is the ability to easily choose the input for each game. There can be multiple types of input setup for the same action.

  1. In the Project menu, go to "Project Settings"
  2. Click on the "Input Map" tab
  3. Create new input actions for "move_forward", "move_back", "turn_left", "turn_right", and "jump":
    (the names themselves could be anything, but generally it's helpful to be descriptive of the action and not include any spaces — whatever the names are, they'll be used in the movement script later on):
    1. For each action, in the "Add New Action" field, type the name of the action & then press return/enter
    2. Link each action name to specific inputs by clicking on the + icon to the far right of the input name and then:
      1. For keyboard inputs, press the key that should match the command (ie. W for move_forward) and then choose "OK"
      2. To set a joypad input, click on the + button, expand "Joypad Axes" or "Joypad Button", click on an axis or button option, and then choose "OK" (ie. Joypad Axis 1 for move_forward)
    3. Repeat as needed for additional keyboard inputs (ie. move_forward might have W, the up arrow, and Joypad Axis 1 all set as inputs
    4. At minimum, set:
      1. Forwards/backwards movement: up/down arrow keys and W/S
      2. Turning left/right: left/right arrow keys and A/D
      3. Jump: space

This script sets up basic tank controls (where forwards/backwards input controls forwards/backwards movement and left/right input controls the direction the player faces).

  1. Create the Script file:
    1. In the Scene panel, right-click on the Player node and choose "Attach Script"
    2. Click on the folder icon to set the script location:
      1. Off to the right, click on the new folder icon
      2. Name the new folder "Scripts" & then click on "OK"
    3. Choose "Open" to create the script in the chosen location
  2. Looking in the Script window, there should be a nearly empty script with the line:
    
    # extend gives access to any properties, methods, or functions that are available to the class or inherited by the class
    # CharacterBody3D is the class of the CharacterBody3D node
    extends CharacterBody3D
    
    
  3. Add public global variables for the movement speed and turning speed:
    
    # Global variables with type hinting - since these are written with decimals, the type is float
    # The variables could be named anything, but need to be written the same way wherever the variable is used
    # These can be accessed by any functions that follow & due to @export are visible in the Inspector
    @export var move_speed := 14.0
    @export var turn_speed := 4.0
    
    
  4. Add a global variable to describe velocity (speed+direction)
    
    # Vector3 is a default class which gives coordinates for the X, Y, and Z axi
    # This sets the initial velocity value to 0 for each axi
    var target_velocity := Vector3.ZERO
    
    
  5. Add the start of a movement function:
    
    # _physics_process is a default function for working with physics
    # Using delta keeps speeds consistent across different devices or operating systems
    func _physics_process(delta):
    
    
  6. Add local variables for movement and turn direction inside the function:
    
    # Use the tab key to indent blocks of code to show the hierarchy
    # Since this variable is indented, it is inside the physics function, making it local
    # This will represent the direction for all of Vector3's axi, with a starting value of 0
    	var move_direction = Vector3.ZERO
    # This sets another local variable that will represent the direction of the player rotation
    	var turn_direction = 0
    
    
  7. Link the input to the direction variables:
    
    # This automatically sets a positive value (1.0) for the first input and a negative value (-1.0) for the second input
    # The input names must be the same as the ones set in the Input Map
    # Later this will help determine the direction of movement or rotation for each key/input
    # get_action_strength is a method of the Input class
    # This only changes the Z vector for the movement direction
    	move_direction.z = Input.get_action_strength("move_back") - Input.get_action_strength("move_forward")
    	turn_direction = Input.get_action_strength("turn_left") - Input.get_action_strength("turn_right")
    
    
  8. Make the player turn along the Y axis (this is the vertical direction):
    
    # rotation_degrees is a default property of Vector3
    # The product of the multiplication operation gets added to the object's current rotation
    # Clockwise vs. counter-clockwise is determined by the positive/negative of turn_direction
    # turn_speed determines how quickly the rotation happens
    	target_velocity = move_direction.z * move_speed
    
    
  9. Set the target velocity:
    
    # This changes the value of the target_velocity variable to the product of the multiplication operation
    # The backwards/forwards direction is determined by the positive/negative of move_direction
    # globel_transform and basis are properties of the class Node3D and work together to share the rotation of an object
    # without them, the object can't move and rotate at the same time; speed should be obvious ;)
    	target_velocity = move_direction.z * global_tranform.basis.z * move_speed
    #This sets the velocity (a default property of the Vector3 class) to match the value for target_velocity
    	velocity = target_velocity
    
    
  10. Make velocity move the player:
    
    # This allows velocity to actually move our player - and allows for collisions with other CharacterBody3D or RigidBody objects
    # This is a default method of CharacterBody3D
    	move_and_slide()
    
    

We can't test the script until our player has been instantiated in a scene for our level — we need a surface to move along. This will happen in the next tab for the "Main" scene/level. At that point, you can preview the game and make adjustments to the speed variables as needed.

This adds a basic jump to the player movement script above. Some of the functionality relies upon that script.

  1. Add global, public variables for the jump strength & amount of gravity
    
    # These should be added towards the top of the script, after the variables for speed
    @export var jump_strength := 60.0
    @export var gravity := 100.0
    
    
  2. Set vertical gravity:
    
    # This should come directly after target_velocity = move_direction.z * global_transform.basis.z * move_speed
    # The gravity variable is multiplied by delta and then subtracted from the Y target_velocity variable
    # The result is the Y velocity decreases as if a force is pushing down on the object - just like gravity
    # Including the delta parameter makes sure the jump will be consistent across different OS or frame rates
    target_velocity.y -= gravity * delta
    
    
  3. Directly afterwards, set the jump input to change the Y velocity:
    
    # This should come before velocity = target_velocity
    # If statements make code run based upon conditions being met
    # is_on_floor is a method of CharacterBody3D and is_action_just_pressed is a method of the Input class
    # In this case, if the player is colliding with the floor/ground and the jump input is pressed, then the Y velocity changes to the jump_strength variable
    # Notice the additional indentation before the change to target_velocity.y — this determines what is part of the if statement
    
    	if is_on_floor and Input.is_action_just_pressed("jump"):
    		target_velocity.y = jump_strength
    
    

Again, we can't test the script until our player has been instantiated in a scene for our level. At that point, you may want to adjust the jump_strength or gravity values.

Setting Up the Main Scene

Adding the Player Scene as an Instance

Adding Obstacles or Hazards

The process of creating/exporting a game app is called building. There are some settings that should be set before building the playable game app.