Look up “Godot Tutorials” on YouTube for the basic tutorial series.
In a world of Inheritance, we might have:
Player Object
Enemy Object
With Inheritance, we can create a parent Entity that has:
This works until we run into something like a ‘Tree’
We can have:
Player
Enemy
Tree
Keep in mind your game will run at say 60 Frames Per Second so don’t run big for loops, complex calculations in your script lifecycles.
While(userNotQuitOrCrash): updatePhysics() Scene.updateScenePicture() # Update images you’re shown passToNodeScripts(userInput) performanceUpdate() # Most game engines will handle this for you userNotQuitOrCrash = updateAppState()
Look at the MainLoop.cpp
and Main.cpp
file for details on this game loop.
Top main classes are ‘Nodes’ and ‘Resources’
Node classes are functionality and behavior.
Example Nodes
In the FileSystem (bottom left default), you can create a new ‘Resource’ class. A resource is saved to the disk (file system).
Example Resources
Our game objects go through a life cycle of:
Lifecycle details:
enter_tree()
notification in top
to bottom orderready()
notification is called when a node and all its children are inside the active scence_input()
, _process()
, _physics_process()
are called if usedexit scene
notification in bottom to top order
So all the children will exist scene before the root node can exitVirtual Methods allow subclasses of the base class type that has the virtual method to
override the method. If there is virtual
, Godot will handle virtual method calls if they are
overridden. For example, a Node class has the following methods:
Node Methods
_enter_tree()
virtual_exit_tree()
virtual_get_configuration_warning()
virtual_input(InputEvent event)
virtual_physics_process(float delta)
virtual_process(float delta)
virtual_ready()
virtual_unhandled_input(InputEvent event)
virtualWe can override these following virtual methods
_enter_tree()
The _enter_tree()
method is always called when a scene (gameobject) enters the SceneTree.
The notification is Top-to-Bottom order.
How _enter_tree()
plays:
root
Parent
Child1
GrandChild1
Child2
the order will be:
Before our scene is loaded, it’ll be an inactive Node to an active Node.
E.g.
SceneTree
|
Root Viewport
|
Inactive: Node -> Active: Node (`_enter_tree()` is when a Node becomes Active)
When do you need to use _enter_tree()
?
_enter_tree()
is very specialized; prioritize _ready()
over _enter_tree()
when setting initial property values_ready()
method_ready()
function can be used by any class that inherits from the Node class_ready()
function is called last (i.e. calls lowest children first)_ready()
method is called only once_ready()
method againExample
Root
Child1
GrandChild1
GreatGrandChild1
AnotherGrandChild1
Child2
GrandChild2
will call:
GreatGrandChild1 _ready() method is called
GrandChild1 _ready() method is called
AnotherGrandChild1 _ready() method is called
Child1 _ready() method is called
GrandChild2 _ready() method is called
Child2 _ready() method is called
Root _ready() method is called
Does the Root have children? If so, go to Child. If not, did the _ready()
method get called?
Note: Just because a node has been deleted/set to inactive (no longer there in the SceneTree), it doesn’t mean it’s been removed from memory.
When to use?
_process()
Delta Time (elapsed time) is a concepted used by programmers in relation to hardware and network responsiveness. In Godot, delta time is a float value. For game programming, we refer to the elapsed time between the last frame that was drawn and the current frame that is drawn.
Issues between different computer speeds:
F1 -> delta time -> F2 -> delta time -> F3 -> delta time -> F4
Say we have a game running 4FPS and one with 60FPS.
We have a framerate dependent calculation
We can have a framerate independent by using delta time
Godot gives you two virtual methods to handle delta time:
_process(delta):
pass
_physics_process(delta):
pass
_process(delta)
methodWhen to use:
When not to use:
_process(delta)
run slower than _physics_process(delta)
Never remove vsync
(default on); if you do, then our _process(delta)
method is called as quickly as possible
and we will not be frame independent anymore.
_process_physics(delta)
methodWhen to use:
When not to use:
_physics_process(delta)
_process(delta)
_process(delta)
, switch back to _physics_process(delta)
Player Input includes:
The easiest way for Godot to handle keyboard input is through the Application’s “Input Map Tab”
4 common input virtual methods from Node Class:
func _input(event):
func _gui_input(event):
func _unhandled_input(event):
func_unhandled_key_input(event):
How does this fit in?
User Input -> OS -> Scene Tree -> Root ViewPort ->
1. Input
2. Control Node (GUI Input)
3. Unhandled Input
Input works Bottom to Top order with by first looking to see if the _input(event)
method was called on any of the
nodes, then bottom to top again on _gui_input(event)
, then _unhandled_input(event)
, etc.
Just keep in mind you have to exit the InputEvent otherwise the input event will be passed along.
Control < CanvasItem < Node < Object
func _unhandled_input(InputEvent event):
You don’t want player input at higher levels (e.g. _input
, _gui_input
) because you want priority to go to
say “Pausing the Game”. You don’t want a player to shoot their gun while the game is paused.
func_unhandled_key_input(event):
This will only get called if a key press was/is detected (mouse movement does not activate this function). Consider using this for input dealing with key presses.
CollisionObject._input_event()
self.get_tree().get_root().set_input_as_handled()
InputEventWithModifiers
(keyboard, mice, trackpad)Example code:
func _input(event):
if event is InputEventKey:
if event.scancode == KEY_RIGHT:
print("Right key pressed")
else:
print(event)
_exit_tree()
All code logic inside the _exit_tree()
virtual method gets called when the node is removed from the scene tree.
Code:
func _exit_tree():
# recommended below - doesn't do anything until you add or remove nodes
# self.queue_free()
pass
# Notice it takes no arguments and returns back no value (void)
The easiest thing to do as a beginner is to delete the node from memory after it has left the scene tree.
Recommended to use queue_free()
inside the _exit_tree()
method for all Nodes because memory management is complex.
Best not to have memory leaks and just delete everything that leaves the scene tree.
Node.queue_free()
- queues a node for deletion at the end of the current frame.
When deleted, all children nodes are deleted. This method makes sure it is safe to delete the node and is safer
than using Object.free()
. This is the preferred method.Object.free()
- deletes the object from memory. Any pre-existing reference to the freed object will become invalid
with no built in safety measures.Remember that removing an Object/Node from the Scene Tree does not remove the Node from Memory.
You need to use Node.queue_free()
or Object.free()
.
When a node is removed from the scene it is an Orphaned Node, but not deleted from Memory.
add_child(<node_object>):
pass
remove_child(<node_object>):
pass
You can use a technique called Object Pooling for handling memory performance, which is more performant than Node.queue_free()
.
The theory is you have an Active node (e.g. a sprite node) that is on a Scene, then we create a new variable in another node
and point this new variable to the old node. We then remove our Active node from Scene and can restore from the variable.
This is a more advanced technique (again, beginners should just use Node.queue_free()
for now).
A Pivot Point (aka Offset) is the reference point (a pair value in an x,y coordinate system) for positioning, rotating, and scaling a game object. A pivot point is where the transformation portion of our Node object gets affected at. We want our pivot point in the center of our images, which makes boundary calculations easy to do.
We can create a complete image using Skeletal Animation/Rigging instead of a Spritesheet.
Resolution is pixels in each direction (width x height) with an Aspect Ratio of 16:9 (x 120), the ratio of width to its height
For game programming, there are two different window values we can retrieve:
Godot offers a global OS
singleton class
1. Player Device Screen/Resolution
# getter method only
screen = OS.get_screen_size()
# returns a Vector2(float x, float y)
2. Game View Resolution
# setter
vector2 = Vector2(float x, float y)
OS.set_window_size(vector2)
# Getter returns Vectors2
game = OS.get_window_size()
Game Window offset is top left
vector2 = Vector2(float x, float y)
OS.set_window_position(vector2)
position = OS.get_window_position()
# getter method
OS.is_window_resizable() # returns boolean
# setter method (if you don't want user to resize
OS.set_window_resizable(false)
Full Screen or Borderless Window
OS.set_window_fullscreen(true)
OS.set_borderless_window(true) # unable to move
If you develop for mobile, pick landscape (Godot default) OR portrait.
SCREEN_ORIENTATION_LANDSCAPE = 0
SCREEN_ORIENTATION_PORTRAIT = 1
Godot offers us a way through project settings to handle built-in scaling for us under
Project Setting > Display > Window > Stretch (One Size Fits All Solution)
Recommended Starting Settings:
For mobile game dev, would recommend a mix of one size fits all method in addition to scaling for individual images
Before applying scaling, pick a Static Game Window Size Width during Development (e.g. 1920 x 1080). Images need to have a width and height that can fit your game without scaling
Calculations:
New Resolution / Original Resolution = Conversion New Ratio = Conversion * Original Ratio
e.g. Resolution: 1920 x 1080 = Image Ratio 1:1 Resolution: 100 x 100 = Image Ratio 0.052: 0.052 for X aspect change (100/1920 = 0.052), 0.092: 0.092 for Y aspect change (100/1080 = 0.092)
CanvasItem Class(Visibility)
set_visible(bool)
- show image onto the game screenis_visible() -> bool
- check if image is visible or nothide()
- shortcut method to hide imageshow()
- shortcut method to show imageProperties:
set_offset(Vector2)
get_offset()
-> Vector2set_centered(bool)
is_offset()
-> boolset_texture(Texture)
get_texture()
-> TextureStreamTexture
< Texture (inherits from Texture)Methods:
get_size()
-> Vector2get_height()
-> intget_width()
-> intget_data()
-> Image (very important)Methods:
get_size()
-> Vector2get_height()
-> intget_width()
-> intYou do not want to edit and save images directly through the Godot editor (use an image editing software)
resize(int width, int height, Interpolation = 1)
shrink_x2()
-> voidexpand_x2_hq2x()
-> voidsave_png("save file path")
setup
E.g.
extends Sprite
func _enter_tree():
myCustomCode()
func _physics_process(_delta):
myCustomCode()
func myCustomCode() -> void:
pass
CollisionShape2D
is the class Godot uses in order to define a space in which the collision algorithm can work with in
detecting when Game Objects intersect/collide.CollisionObject2D
class.The first property Shape
tells us what kind of Shape this collision object is, e.g. RectangleShape2D
.
The simpler the shape, the more performant your game will be (Simpler Shapes > Polygons)
If multiple CollisionShape2D objects are “ON”, Godot uses the UNION of all of those objects
Allows you to pick:
ConvexPolygonShape2D
- Godot assumes you are using a convex polygon; simple polygon in which no line segment between two points
goes outside the polygon. Interior angles are less than 180 degrees. Godot decomposes the shape into as many convex
polygons as possible to make sure all collision checks are against a convex polygon. These are faster to check.ConcavePolygonShape2D
- Godot assumes you are using a concave polygon; a polygon where one or more interior angles is
greater than 180 degrees. Do not use this for RigidBody2D
.Easier to work with, especially if your polygon changes dynamically over time
More expensive, but allows more detailed shapes (i.e. more Vertices/edges).
Basically the same as CollisionShape2D
with two extra properties:
build_mode
polygon
PoolVector2Array
(vector specifically made to hold Vector2D, optimized for memory)
PoolVector2Array
)PhysicsBody2D
is the base class for all objects that are affected by physics in 2D space*Body2D
NodesGodot gives us the options of:
StaticBody2D
RigidBody2D
KinimaticBody2D
StaticBody2D
RigidBody2D
RigidBody2D
when you want to have physics forces applied to your game object (e.g. Gravity, friction)RigidBody2D
is not meant to be used for user controlled game objects (e.g. your Main Player Character)KinimaticBody2D
*Body2D
NodesMethods:
move_and_collide(Vector2)
- when you would like your game object to stop moving after a collision has been
internally detected.move_and_slide(Vector2)
- it stops your game object from moving in the direction of a ‘blocked’ path (slides)
If you move southwest (diagonal) and there happens to be a floor, your game object will just move West (at a reduced speed)Two categories:
True Random Numbers are hard; uses a measurement/algorithm system
Pseudorandom is given to us by a RandomNumberGenerator
Class with a default seed
RandomNumberGenerator.randomize()
, which is a time-based seedint randi()
returns 0 - 4294967295int randi_range(int from, int to)
Can have count up timers (e.g. speed run), count down timers (e.g. pass a level in X time), refresh timer for skills,
new monsters spawning. We can write with custom code or use a Timer
node.
Use raw code when you want to do something other than counting down.
Example basic raw code for a timer
var timeCounter: float = 0.0
var timeLeft: int = 10
func _physics_process(delta) -> void:
timeCounter += delta
if( int(timeCounter) == 1):
timeLeft -= 1
timeCounter = 0
print(timeLeft)
Timer
nodeNode
classProperties:
autostart
# default is falsepaused
# default is falseone_shot
# default is false, loops the timerwait_time
# set to 1.0Methods:
start(float time_sec = -1)
stop()
connect("timeout", self, "methodName")
by using Timer Node SignalsHow to put it together:
timeout()
signal methodstart(float time)
method
By default, will restart againCode Example:
extends Timer
func _read() -> void:
wait_time = 3.0
connect("timeout", self, "printHello")
start()
func printHello():
print("Hello World")
CanvasItem
ClassCanvasItem
Class is the Base class of anything 2D.Methods
_draw()
virtual method - You have to define drawing methods inside the draw()
virtual method or nothing will be drawnupdate()
method - queues the canvas drawing for update. Use this when changing drawning positions/colors/etchide()
- makes your canvas item invisibleshow()
- unhides your canvas itemmodulate
- a Color
class data type that can apply Color to your texture (applies to your children too)self_modulate
- only applies Color to parentHow do I draw things on the Screen with CanvasItem?
draw_line(Vector2 from, Vector2 to, Color color, float width=1.0, bool antialiased=false)
draw_multiline(PoolVector2Array points, Color color, float width=1.0, bool antialiased=false)
with a var name = PoolVector2Array([Vector2(0,0), ...])
draw_circle(Vector2 position, float radius, Color color)
draw_rect(Rect2 rect, Color color, bool filled=true, float width=1.0, bool antialiased=false)
with a var name = Rect2(float x, float y, float width, float height)
draw_primitive(PoolVector2Array points, PoolColorArray colors, PoolVector2Array uvs, Texture texture=null,
float width=1.0, Texture normal_map=null) # for dots (1 point), lines (2), triangles (3), quads (4), uv (Texture coordinates for each vertex)
draw_string(Font font, Vector2 position, String text, Color modulate=Color(1,1,1,1), int clip_w=-1)
for textCamera2D
< Node2D
< CanvasItem
< Node
< Object
Inheritance
Node2D
: position propertyNode
: access to Script Lifecyclescurrent
property - default is false)Current
property to true and the camera will follow the PlayerMethods
make_current()
-> void, makes the current Camera2D Node the active camera in the SceneTreealign()
if you experience camera lag, to align the camera to the tracked nodeBasics:
Anchor
- Starting Position/Origin: it must starts its position somewhere along the edges of your parent Control Node
Margin
- how many pixels away from anchor in terms of the X axis and Y axis
Grow Direction
- which direction does it grow? Horizontal, Vertical
End
(Grow Away from the Origin)Begin
(Grow towards the Origin)Both
(Growth Away and towards the Origin)Rect
Methods:
get_viewport().size.x
# width of viewportget_viewport().size.y
# height of viewportExample of changing Scenes: Splash Screen -> Main Menu -> Game -> Credits
You can change scenes 3 ways:
CanvasItem
has a hide()
)These will delete the current scene immediately when called.
SceneTree.change_scene()
SceneTree.change_scene_to()
get_tree()
methodchange_scene()
Code:
get_tree().change_scene(String path)
var sceneTwo:String="res://Scene2.tscn"
get_tree().change_scene(sceneTwo)
change_scene_to()
Code:
get_tree().change_scene_to(PackedScene packed_scene)
var sceneTwo = preload("res://Scene2.tscn")
get_tree().change_scene_to(sceneTwo)
If changing a scene runs into an error, you’ll run into these Error Enum values:
Buttons are needed to have a pressable item so we can create:
UI can have the following states:
Button
NodeButton
< BaseButton
< Control
< CanvasItem
< Node
< Object
Comes with 5 states:
If we attach a script, we can get the following methods and properties:
_pressed()
virtual_is_hovered()
Disabled
(Boolean)Pressed
(Boolean)The Toggle Mode
lets you toggle between staying pressed or popping back to a default state
button_down()
button_up()
pressed()
TextureButton
NodeGreat for amazing looking buttons
TextureButton
< BaseButton
< Control
< CanvasItem
< Node
< Object
5 states:
AutoLoad
, Enable
.Code:
Say you have a Singleton with:
#PlayerStats.gd
var level = 1
var player_name = "Will"
#You can access this globally through other scripts with
PlayerStats.player_name
Steps:
How to save:
myFile.store_line(to_json(saveData))
Paths:
user://file.txt
~/Library/Application Support/Godot/app_userdata/<Project Name>
%APPDATA%\Godot\
~/.local/share/godot/
You can print out the methods and properties
print(tile_map_layer.get_class())
print(tile_map_layer.get_property_list())
print(tile_map_layer.get_method_list())print(tile_map_layer.get_class())
print(tile_map_layer.get_property_list())