Home

Awesome

Learning Godot (game engine)

Just one of the things I'm learning. https://github.com/hchiam/learning

Godot in 100 Seconds by Fireship.io

https://github.com/godotengine/godot

templates: https://github.com/godotengine/godot-demo-projects (and their demos)

intro tutorials:

best practices manual:

animation tips from DevWorm: https://youtu.be/XbDh2GAshBA?feature=shared

some helpful Godot plugins: https://youtu.be/bKNmsae5zXk?feature=shared

multiplayer godot repo from Battery Acid Dev

miscellaneous notes

For Ctrl+F convenience to remind myself of things:

more example GDScripts:

extends Sprite2D

# instance member variables:
var speed = 400
var angular_speed = PI # godot defaults rad angles

# Called when the node enters the scene tree for the first time.
func _ready():
  print('Hello World!')

# Called every frame. 'delta' is the elapsedf time since the previous frame.
func _process(delta): # use _physics_process for more consistent/smoother physics timing
  var change = angular_speed * delta
  rotation += change # rotation is a built-in property of Sprite2D
  var velocity = Vector2.UP.rotated(rotation) * speed
  position += velocity * delta # rotation is a built-in property of Sprite2D
  # note: you can set a constant rotation on a RigidBody2D in the Inspector panel with: Angular > Velocity

use func _unhandled_input(event): to handle any input:

func _unhandled_input(event):
  if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible:
    get_tree().reload_current_scene() # restart SceneTree = restart game
    # get_tree() gets the global singleton SceneTree, which is what holds the root viewport that in turn holds all root scenes/nodes

use func _process(delta):/func _physics_process(delta): with things like Input.is_action_pressed("ui_left"):

extends Sprite2D
var speed = 400
func _process(delta): # use _physics_process for more consistent/smoother physics timing
  var direction = 0
  if Input.is_action_pressed("ui_left"): # arrows on keyboard or D-pad
    direction = -1
  if Input.is_action_pressed("ui_right"):
    direction = 1
  if Input.is_action_pressed("ui_up"): # not elif, so you can move diagonally
    pass
  if Input.is_action_pressed("ui_down"):
    pass
  position += Vector2.RIGHT * direction * speed * delta
var speed = 400
var screen_size

func _ready():
  screen_size = get_viewport_rect().size

func _process(delta): # use _physics_process for more consistent/smoother physics timing
  var velocity = Vector2.ZERO
  if Input.is_action_pressed(&"ui_right"): # use a StringName &"..." for faster comparison than a regular String "..."
    velocity.x += 1
  if Input.is_action_pressed(&"ui_left"):
    velocity.x -= 1
  if Input.is_action_pressed(&"ui_down"):
    velocity.y += 1
  if Input.is_action_pressed(&"ui_up"):
    velocity.y -= 1
  if velocity.length() > 0:
    velocity = velocity.normalized() * speed # so diagonal is same speed as orthogonal
    $AnimatedSprite2D.play() # $AnimatedSprite2D is shorthand for getting children with get_node('AnimatedSprite2D')
  else:
    $AnimatedSprite2D.stop()
  
  position += velocity * delta
  
  # keep in screen:
  position = position.clamp(Vector2.ZERO, screen_size)
# to rotate sprite "up" animation in 8 directions:
$AnimatedSprite2D.animation = &"up"
if velocity.x < 0 and velocity.y < 0:
  rotation = - PI * 1/4
elif velocity.x == 0 and velocity.y < 0:
  rotation = 0
elif velocity.x > 0 and velocity.y < 0:
  rotation = PI * 1/4
elif velocity.x < 0 and velocity.y == 0:
  rotation = - PI * 1/2
elif velocity.x > 0 and velocity.y == 0:
  rotation = PI * 1/2
elif velocity.x < 0 and velocity.y > 0:
  rotation = - PI * 3/4
elif velocity.x == 0 and velocity.y > 0:
  rotation = PI
elif velocity.x > 0 and velocity.y > 0:
  rotation = PI * 3/4
signal hit

func _on_body_entered(body):
  hit.emit()
  # must defer setting value of physics properties in a physics callback (to avoid error if while processing a collision):
  $CollisionShape2D.set_deferred("disabled", true)
your_array.pick_random() # instead of your_array[randi() % your_array.size()]
queue_free() # vs free()
# Main.gd:

# so you can use the Inspector panel to select a node, to let you use it in this code as a property/variable mob_scene:
@export var mob_scene: PackedScene

# so you can then create instances of that node whenever you want:
var mob = mob_scene.instantiate() # like inside of func _on_mob_timer_timeout():

# and set random location along a PathFollow2D: (setup: PathFollow inside Path; Path = path, PathFollow = location on path):
var mob_spawn_location = $MobPath/MobSpawnLocation
# or var mob_spawn_location = get_node(^"MobPath/MobSpawnLocation")
mob_spawn_location.progress = randi() # or randf()
mob.position = mob_spawn_location.position
# ...

add_child(mob) # to add the mob instance to the Main scene (run this line in Main.gd)
var random_between_inclusive = randf_range(-PI / 4, PI / 4)
$MessageTimer.start()
await $MessageTimer.timeout
# after waiting, then can do something else
# basically "sleep" or "delay": create a one-shot timer for 1.0 second and await for it to finish:
await get_tree().create_timer(1.0).timeout # instead of using a Timer node

Debugging tips from DevWorm: https://www.youtube.com/watch?v=PB6YPnRAyjE

3D:

extends CharacterBody3D

signal hit

@export var speed = 14 # meters per second
@export var jump_impulse = 20 # big = unrealistic but responsive feels good
@export var bounce_impulse = 16
@export var fall_acceleration = 75 # i.e. gravity


func _physics_process(delta): # better than _process(delta) for physics
  var direction = Vector3.ZERO
  if Input.is_action_pressed("move_right"):
    direction.x += 1
  if Input.is_action_pressed("move_left"):
    direction.x -= 1
  if Input.is_action_pressed("move_back"):
    direction.z += 1
  if Input.is_action_pressed("move_forward"):
    direction.z -= 1

  if direction != Vector3.ZERO:
    direction = direction.normalized()
    basis = Basis.looking_at(direction) # use built-in basis property to rotate the player
    $AnimationPlayer.speed_scale = 4 # speed up animation
  else:
    $AnimationPlayer.speed_scale = 1

  velocity.x = direction.x * speed # left/right
  velocity.z = direction.z * speed # forward/back

  # velocity.y + jumping up:
  if is_on_floor() and Input.is_action_just_pressed("jump"):
    velocity.y += jump_impulse

  # velocity.y - falling down:
  velocity.y -= fall_acceleration * delta

  move_and_slide() # to smooth out physics motion

  # Here, we check if we landed on top of a mob and if so, we kill it and bounce.
  # With move_and_slide(), Godot makes the body move sometimes multiple times in a row to
  # smooth out the character's motion. So we have to loop over all collisions that may have
  # happened.
  # If there are no "slides" this frame, the loop below won't run.
  for index in range(get_slide_collision_count()): # check all collisions over move_and_slide():
    var collision = get_slide_collision(index)
    if collision.get_collider().is_in_group("mob"): # if hit mob:
      var mob = collision.get_collider()
      if Vector3.UP.dot(collision.get_normal()) > 0.1: # if the collision happened roughly above the mob:
        mob.squash() # call the custom squash() function of that mob instance's Mob.gd script
        velocity.y = bounce_impulse
        break # escape the for-loop to avoid over-counting squashing 1 mob

  # this makes the character follow a nice arc when jumping:
  rotation.x = PI / 6 * velocity.y / jump_impulse


func die():
  hit.emit() # emit custom signal
  queue_free() # clear this node from memory


func _on_MobDetector_body_entered(_body):
  die()
# Player.gd:

# faster:
$AnimationPlayer.speed_scale = 4
# or normal speed:
$AnimationPlayer.speed_scale = 1

# ...
$Pivot.rotation.x = PI / 6 * velocity.y / jump_impulse
# to get viewport size:
# DON'T use this:
get_viewport().size # gave me incorrect values after screen resize (e.g. fullscreen)
# USE this:
get_viewport().get_visible_rect().size # e.g. inside the GDScript of a Node
# or when available:
get_viewport_rect().size # e.g. inside the GDScript of a CanvasItem or a RigidBody2D