Godot is an astonishingly feature packed development framework for creating games and graphical simulations. It can do fancy 3d modelling and physics as well as 2d and GUI elements. I am particularly impressed that the GUI of Godot itself is actually created with its own technology (presumably you could make a game that has an in-game creation feature using Godot). It is free, open source, Linux friendly, and pretty much completely wholesome.

Resources

Installation

Godot’s installation is nothing short of a miracle. You go to their web site, you get a compressed file (alas zipped) and uncompress it.

The download is astonishingly less than 20MB! It is a single 44MB executable at full size (at release of Godot3 in January 2018). This is shockingly compact and easy to deal with but it continues to impress: to run it, just execute the file and it’s running!

A good place to start is by clicking "AssetLib" which brings up all the official demos into the GUI of the editor. You can select one and download it and start playing with it. It’s actually a very clever way to teach the system.

Vim and Command Line

It looks like you can just edit the files in the project directories. The trick is how to synchronize them. You can just click on the file name in the editor to refresh. Sort of. I’d like to find a way to just not have it do any auto-syncing and do it manually.

How to use the command line to work with Godot.

Syntax highlighting was ok by default as a conf file. Install a proper syntax highlighting by doing this.

mkdir ~/.vim/syntax ; cd ~/.vim/syntax
wget https://raw.githubusercontent.com/quabug/vim-gdscript/master/syntax/gdscript.vim

Maybe add this to your ~/.vimrc.

au BufRead,BufNewFile *.gd  set filetype=gdscript

Tabs

The editor likes to use tab indentation, but it’s easy to fix.

Editor→ Editor Settings → General → Text Editor → Indent → Type

Change from evil "Tabs" to "Spaces".

Editing

  • RMB - aims camera angle from its current position

  • LMB - selects

  • [S]-MMB - pan view (translate), reposition view in display or, as I like to think of it, "shift" the view (same in Blender)

  • [C]-MMB - scale view, zoom/move view/camera closer, vertical mouse axis only (similar in Blender)

  • [S]-F1 - Contextual help for highlighted name in script editor.

  • [Ctl+k] - Comment out lines of code in the editor.

Here is the official documentations complete list of editor shortcuts.

To change these go to Editor → Editor Settings → Shortcuts.

Assets

When exporting from Blender use the glTF (.glb/gltf) format.

You can use the gltf but it makes Godot happier to convert it to a mesh resource. This is basically the same thing you select in the Mesh field of the MeshInstance3D node. The process is nicely explained here. The basic idea is to open the gltf file as its own scene (double click on it). The object you’re trying to import will then have a mesh instance and that will be the parent of a mesh resource. By exporting this mesh resource to its own (Godot) .res file, it can be made available to the Mesh selection field (drag it into place).

Ontology

Note that clicking on any function while holding [Ctl] will summon the official documentation reference for it.

A "Scene" is a tree of "Nodes".

A Node may have these kinds of features.

  • Name

  • Properties

  • Callback

  • Extendable - You can make the actual functionality different

The concept of delta comes up a lot and is used to manage the game loop to ensure consistent timing on different hardware. In a detailed sense it is the amount of wall clock time that has elapsed since the last frame. I’m not sure if it’s kept in system ticks or float seconds or milliseconds, but it should be convertible. And it generally doesn’t matter. By multiplying effects by delta you can smooth the effect with respect to time.

Physics

  • Static body - Objects that do not move under physics simulation. Use for environmental elements such as walls, floors, platforms, and other fixed objects.

  • Rigid body - Code does control position explicitly. Instead, apply forces (gravity, add_force(), apply_impulse(), etc) and the result will update the location sensibly. This can impart gravitational forces and provide computed collision results.

  • CharacterBody - This was formerly KinematicBody. It is used for characters controlled by the player and/or that need to move with some autonomy to the physics.

  • CollisionObject2D

    • PhysicsBody2D

      1. RididBody2D - Realistic simulated physics (gravity, etc)

    • Rigid mode - Moves in response to physics forces.

    • Static mode - Behave like a static body (won’t move)

    • Character mode - similar to Rigid but no rotation

    • Kinematic - Use code for moving like KinematicBody2D.

      1. KinematicBody2D - Arcade, moves but no normal physics necessarily

    • Area2D - Detects object overlapping

Can set gravity to 0 in "Physics 2d" in Property Settings for 2d top down simulations (think Asteroids).

apply_impulse() is more abrupt than add_force()

Example of how to insert physics nodes.

Scene tab → + → Spatial → CollisionObject → PhysicsBody → RigidBody

Area2D - Sprite - add your artwork - CollisionObject2D - (don’t drag collision shapes from outside box, use blue inner box handles).

KinematicBody2D - Arcade, moves but no normal physics necessarily Use move(Vector2 real_vec) method to move it to return "remaining motion" left after a hit. Allows for a n.reflect(remaning) or a n.slide(remaining). - Sprite - CollisionShape2D

How to directly do metaphysical things with a physics object like a PhysicsBody2D. For example, you may have a space ship that reappears on the opposite side of the screen when it goes off the screen (i.e. torus). Usually the outcome of where the ship is positioned is calculated solely by the physics engine. But here’s how to reach in and make adjustments for such a situation.

# Taken from: https://www.youtube.com/watch?v=xsAyx2r1bQU
func _integrate_forces(state):
    set_applied_force(thrust.rotated(rotation))
    set_applied_torque(rotation_dir*spin_thrust)
    var xform= state.get_transform()
    if xform.origin.x > screensize.x:
        xform.origin.x= 0
    if xform.origin.x < 0:
        xform.origin.x= screensize.x
    if xform.origin.y > screensize.y:
        xform.origin.y= 0
    if xform.origin.y < 0:
        xform.origin.y= screensize.y
    state.set_transform(xform)

Also custom_integrator() looks interesting too to make your own physics systems.

set_fixed_process(true) - Force physics to 1/60 second (or whatever).

Tween

A Tween node is just a way to interpolate a continuous state transition function. Here is a nice display of what that means: http://easings.net/

bool interpolate_property(object,property,initialvalue,endingvalue)

A Basic 3D Sample Project

Simple Floor System

I found it very confusing how to implement the most basic rudimentary thing one will almost certainly need 99.9% of the time: a simple floor. Here is a technique that works extracted from this fine video.

From a new project you can see in the Scene tab that there is a special (because I think it only occurs at startup) section called Create Root Node. If you click the 3D Scene button, that will get things started sensibly and, as it says, give you a Node3D root node. You’ll see a gizmo in the workspace area which is associated with this node. You can double click on the root node in the Scene content list (or F2) and rename it to something like "Level1" or "FloorDemo" or whatever.

It might be nice to add a Camera3D node as a child of the root node. Adding nodes is [C+a] or ou can use the little plus icon at the top left of the scene tab. Search for the Camera3D node in the hierarchy under Node3D or just type it to search. It might be good to move the camera away from the center of the scene and aim it back to the center.

Now it’s time to add the node for the floor (to the root node, not the camera node of course). In the hierarchy you’ll look at Node3DCollisionObject3DPhysicsBody3DStaticBody3D. And you can just search for StaticBody3D too, but it’s good to know where it really sits in the hierarchy. Go ahead and name this one Floor. This will add the node but it will have a little yellow warning icon next to it saying that it is incomplete and you should add a CollisionShape3D or something similar if you want anything sensible to happen.

To deal with this error more nodes must be added. Start by adding Node3DVisualInstance3DGeometryInstance3DMeshInstance3D. This will put a gizmo in for this node but no actual geometry shows up. To add geometry go over to the Inspector on the right and at the top there will be a field for Mesh and its value will be <empty>. Click that empty and select New BoxMesh from the pull down. Click on the picture of the cube in the Inspector to see its properties and adjust them to make the floor flatter and wider. Go ahead and make the size something like 10,0.5,10 (remember that Godot uses annoying Minecraft coordinates and Y is up). That’s the geometry taken care of, but it is not clearing the warning.

To really make this floor work, you also need to add another node as a child of the Floor node. Search for Node3dCollisionShape3D. Again, this CollisionShape3D doesn’t know what shape it is supposed to have and produces a warning saying so. You need to go to the Shape field in the inspector and pick New BoxShape3D. This produces a 2m cube which you need to modify by clicking on the shape field icon and putting in the same size values as before (10,0.5,10). You can check this works by clicking the visibility eye icon next to the MeshInstance3D node and seeing that the new collision shape is the same size as the floor’s geometry box.

At this stage it can be wise to group this floor node with its children. To do this, select the Floor node and then press [Ctl+g] (or look for the weird icon to the right of the lock icon above the 3d viewport). When this is done that weird icon appears next to the node in the scene list and when you select any component of the floor, it all goes along for the operation.

If you Play the scene as is, it will be very dark. This is because it lacks a light. You can go to the root node of the scene and add something like a Node3DVisualInstance3DLight3DDirectionalLight3d. This can be adjusted so its location and direction are correct. Go ahead and turn on the Shadow setting too.

Now we have a perfectly good floor with lighting and camera in a scene. What happens to that floor may not exactly be the floor and may want to be broken up into another scene. So if some object falls on this floor, it’s possible that this object will be used in some other scene where it falls on a different surface. To make a test object to interact with the floor, create a new scene. This is done with the plus icon next to the scene tab.

With the new blank scene, don’t bother with the "Create Root Node" menu; instead click the plus to add a specific node. The node to use for the falling test object is Node3DCollisionObject3DPhysicsBody3DRigidBody3D. The difference between that and a StaticBody3d is that rigid bodies move but static bodies don’t. Note however that static bodies do factor into the physics calculations — this is why they’re perfect for floors or other things that will see more dynamic objects bouncing off them. Rigid body objects have gravity and can be pushed by other objects.

It might be good to start by renaming this node to Block Just like the floor our test block will lack both a geometrical shape so that we can see it and it will also lack a collision boundary. Add both of these as was done for the floor. You can (probably) find MeshInstance3D and CollisionShape3D in the "Recent" pane of the add menu. Click on the Mesh/Shape fields in the Inspector to add New BoxMesh or New BoxShape3d as appropriate.

Great. Now there’s a floor scene and test block scene - how do they interact. Select the FloorDemo scene and instead of the plus icon (or [Ctl-a]) you want to use the link (chain) icon. With the root node of the FloorDemo scene selected, link a Block scene. It should appear and be editable. Note that it doesn’t need to be grouped because the handles are moving the whole scene when they are embedded like this, not just the top level node. Go ahead and move the block up so it can be poised to fall when played. Then play the scene ([F6]). If all went well, the block should fall in view of the camera and hit the floor and come to a sensible stop.

To get multiple blocks, rename the Block node to Block1. Now when you Dupiclate ([Ctl+d]), the new ones will have their number incremented. It is sensible to put all of these numbered block nodes into a generic Node3D which can be renamed Blocks and act as a tidy organizing container.

Simple Character

The "player" will be created in a new scene. Instead of a scene default add a Node3DCollisionObject3DPhysicsBody3DCharacterBody3D node. Rename it something sensible like Player. Just like the floor and blocks, you now need to fill out this node’s sub-nodes with Node3DVisualInstance3DGeometryInstance3DMeshInstance3D (with a New BoxMesh shape) and Node3dCollisionShape3D (with a New BoxShape3D shape). Note that at this point the Recent pane in the Create New Node menu comes in handy here if you’ve already done some other similar setup.

To do anything sensible, the root node in this player scene will need a script. With the Player selected in the Player scene, click the script icon to create a script for this node. Since this node is a CharacterBody3D, its script has the possibility to use a template of commonly used functions for this type of node — in this case a Basic Movement template. This basic template is actually reasonably competent and you can run the main scene and the player movement should now be controllable with the arrow keys and the space jumps.

User Input

Generally to ascertain if the user has pressed a key you can check for the following in its _process() method.

func _process(delta):
    if Input.is_action_pressed("ui_left"):
        rotate_y(deg_to_rad(-2))
    elif Input.is_action_pressed("ui_right"):
        rotate_y(deg_to_rad(2))
Input.is_action_just_pressed
Input.is_action_just_released
Input.is_anything_pressed
Input.is_joy_button_pressed

Useful Functions/Methods

  • queue_free() - Frees this object from memory, a.k.a. delete it.

  • rotate_y - Rotates a 3d object vertically in radians. Of course there are x and z variants too.

GDScript

  • # Comments as usual

  • $ = alias for get_node(). For example, you can refer to $AnimatedSprite/Monster.local_coords. Also has_node().

  • func = Python’s def, though don’t use self in the definition (although self may be available in the functions). Functions aren’t proper types and can’t be stored in variables, etc. static func myspecialfn(x,y): can be used to make sure that x and y are not inherited from elsewhere.

  • Native vector types Vector2(x,y) and Vector3(x,y,z)

  • Single or double quotes like Python.

  • Dictionaries and lists (perhaps called arrays) seem just like Python. e.g. var moves={"right":Vector2(1,0),"left":Vector2(-1,0)} Also mydict.keys() works.

  • null is a type that can not be assigned values. But it can be assigned to variables of any type. For example, this is ok.

    var my_float: float = null  # Explicitly initialize using null.
  • Other types bool, int, float, String, Rect2, Transform2D (transform matrix type), Plane, Quat (quaternion), AABB (axis aligned bounding box), Basis (3x3 matrix for 3d transforms), Transform (the 3d version), Color, NodePath, RID (resource id), Object (base class for user created types)

  • enum MOB_BAD, MOB_GOOD=10, MOB_UGLY is the same as const MOB_BAD=0, const MOB_GOOD=10, and const MOB_UGLY=11.

  • const e= 2.71828182845904523536

  • Boolean keywords are true and false, not the Python capitalized ones.

  • Random numbers

    • randi() - random integer, best used with a modolo.

    • rand_range(minval,maxval)

  • print("Shows up in the console.")

  • clamp(x,min,max) - set x to be x or min or max if x is lower or higher than min and max respectively.

  • class MyClass and then instantiate it with var mc= MyClass.new() and use with something like print(mc.mymember); also, extends MyClass for inheritance

  • Keywords that seem mostly identical to Python: if, elif, `else, for, while, break, continue, pass, return, range

  • match is like switch in other languages; note that break is default, use continue to fall through

    match mycoin:
        true:
            print("Heads!")
        false:
            print("Tails!")
  • ~ Bitwise not, << bitshifting, [&,|] bitwise [and,or]

  • || or or boolean or. Same with and.

  • 0xCECE Hex numbers

  • """multi line string""" Like Python.

  • @"NodePath" - I’d like a better example of this

Any variable you want exposed in the editor use

export var speed= 55
  • Vector2(x,y)

Some typical expected functions for node scripts (classes).

  • func _ready() - Called every time the node is added to the scene. Often contains the line set_fixed_process(true)

  • func _process(delta) - Called every frame where node is relevant.

delta is how much time has elapsed since last frame; used for frame rate independence.

randomize() - set random seed with something really random. Or something specific?

If you have a "scene" that describes how some game element works, you can instantiate many objects of that class. Here a parent "Node" (just a simple plain Node type node) spawns 10 animated sprites.

extends Node
onready var sprite= preload("res://Sprite.tscn")
func _ready():
    for i in range(10):
        var s=sprite.instance()
        add_child(s)

UI

It seems common to start by defining your own input map entries. Start your new project then go to Project → Project Settings → Input Map tab, and enter move_up, move_left etc in the Action box. Then go to the plus on each and Add Key to add a binding (A,S,D,W, or better, k,h,j,l etc).

I think the predefined ones (e.g. ui_up, ui_right, etc) are there to support the UI (menus, etc) keybindings.

Here’s a typical control scheme for mapping arrow keys (or WASD if it’s remapped) to do the sensible thing.

func get_input():
    thrust= Vector()
    if Input.is_action_pressed("ui_up"):
        thrust= Vector2(engine_thrust,0)
    if Input.is_action_pressed("ui_down"):
        thrust= Vector2(-engine_thrust,0)
    if Input.is_action_pressed("ui_right"):
        rotation_dir += 1
    if Input.is_action_pressed("ui_right"):
        rotation_dir -= 1

Here’s another possibly better way to do the same thing.

export  var speed=100
var vel= Vector2()
func _read():
    set_fixed_process(true)
    set_pos(Vector(500,300)
func _fixed_process(delta):
    var input= Vector2(0,0)
    input.x= Input.is_action_pressed("ui_right") - Input.is_action_pressed("ui_left")
    input.y= Input.is_action_pressed("ui_down") - Input.is_action_pressed("ui_up")
    vel= input.normalized() * speed
    set_pos(get_pos() + vel * delta)

To have a complex UI display in-game you can use lots of different container nodes. This video does a very good job introducing that concept.

Nodes

Node2D is a good choice for a root node in a 2d project.

StaticBody2D is a good parent wrapper of a Sprite node for things like backgrounds.

Quit

A normal thing to want to do, especially during development, is to politely quit your game. I defined the escape button using the same menu as previously shown and then added this.

if Input.is_action_just_pressed("quit_button"):
    get_tree().quit()

Signals

_on_some_action - Godot naming convention for responding to signals.

In the item’s script.
signal item_grabbed # Alerts interested funcs that an item was grabbed.

func on_item_area_enter(area): # Run if item collision occurs.
    emit_signal("item_grabbed") # Alert other nodes.
    queue_free() # Makes this object disappear from entire process.
In the main node’s script.
func spawn_item():
    var g= item.instance()
    item_container.add_child(g)
    g.connect("item_grabbed",self,"_on_item_grabbed")
func _on_item_grabbed():
    itemcount+=1

Performance

Need to audit the frame rate? That and many other interesting performance metrics can be studied with the get_monitor function described here.

C++

Of course you can use C++ to strong arm the engine itself. http://docs.godotengine.org/en/latest/development/cpp/ Quite an advanced topic. It would definitely require a lot of knowledge about the engine in general.