Godot Lesson 11: Heart Pickups


This adds enemy drops and heart pickups.


  • Lessons 1-10



Repository: https://github.com/los-alamos-steam-lab/godot-tutorial/tree/11-heart-pickups

Pickup Code

Major Changes from the Video:

  • Created a class name for easier inheritance.

class_name pickup
extends Area2D

export(bool) var disappears = false

# Called when the node enters the scene tree for the first time.
func _ready():
    connect("body_entered", self, "body_entered")
    connect("area_entered", self, "area_entered")

func area_entered(area):
    var area_parent = area.get_parent()
    if area_parent.name == "sword":

func body_entered(body):

Key Code

extends pickup

func body_entered(body):
    # I replace body.get(keys) with body.keys because I want this
    # to fail if the player does not have a keys variable
    # I also made MAXKEYS a player constant to make it easier to change
    if body.name == "player" && body.keys < body.MAXKEYS:
        # Pickup the key and then delete it.
        body.keys += 1

Heart Code

extends pickup

func body_entered(body):
    # I replace body.get(health) with body.health because I want this
    # to fail if the player does not have a keys variable
    if body.name == "player" && body.health < body.MAXHEALTH:
        # Pickup the heart and then delete it.
        body.health += 1

Entity Code

class_name entity
extends KinematicBody2D

# we put this here instead of autoloading it
# nothing wrong with autoload, but I prefer things in the code
var dir = directions.new()

var SPEED = 0
var TYPE = "ENEMY"
var DAMAGE = null

var movedir = Vector2.ZERO
var knockdir = Vector2.ZERO
var spritedir = "down"

var hitstun = 0
var health = MAXHEALTH
var texture_default = null
var texture_hurt = null

func _ready():
    # keep the enemies frozen until they enter the camera scene
    if TYPE == "ENEMY":
        # set the collision mask to collide with the
        # walls in the camera scene
    texture_default         = $Sprite.texture
    # make the hurt texture the same name and path as the default texture
    # but replace .png with _hurt.png
    texture_hurt    = load($Sprite.texture.get_path().replace(".png", "_hurt.png"))

func movement_loop():
    var motion

    # if you aren't in hitstun then move normally
    # otherwise get knocked back
    if hitstun == 0:
        motion = movedir.normalized() * SPEED
        motion = knockdir.normalized() * 125

    # move_and_slide takes care of collisions and has you slide
    # along walls that are blocking your path
    move_and_slide(motion, Vector2.ZERO)

func spritedir_loop():
    match movedir:
            spritedir = "left"
            spritedir = "right"
            spritedir = "up"
            spritedir = "down"

# This changes our player animation.  "animation" is a string
# of the sort "idle", "push", or "walk"
func anim_switch(animation):
    var newanim = str(animation, spritedir)
    if $anim.current_animation != newanim:

func damage_loop():
    # If you're in hitstun countdown the timer
    health = min(health, MAXHEALTH)
    if hitstun > 0:
        hitstun -= 1
        $Sprite.texture = texture_hurt
        $Sprite.texture = texture_default

        # if the enemy should be dead
        if TYPE == "ENEMY" && health <= 0:
            # create the death animation, put it where the enemy was and destroy the enemy
            var drop = randi() % 4
            if drop == 0:

    # for any area that is overlapping the entity's hitbox
    for area in $hitbox.get_overlapping_areas():
        # Body is the area's parent - a weapon or an entity
        var body = area.get_parent()
        # if the entity isn't already hit, and the body gives damage,
        # and the body is a different type that the entity
        if hitstun == 0 and body.get("DAMAGE") != null and body.get("TYPE") != TYPE:
            # decrease health by the body's damage
            health -= body.get("DAMAGE")
            # Set the hitstun timer
            hitstun = 10
            # set knockdir to the opposite of the entity approached
            # the body from
            knockdir = global_transform.origin - body.global_transform.origin

# Accepts an actual item scene not the name of the scene
func use_item(item):
    # create an instance of the item
    var newitem = item.instance()

    # add it to the group with item name and the id of its parent
    newitem.add_to_group(str(newitem.get_name(), self))

    # make it a child of the entity

    # if there are already too many items of that type on the screen, delete it
    if get_tree().get_nodes_in_group(str(newitem.get_name(), self)).size() > newitem.maxamount:

func instance_scene(scene):
    var new_scene = scene.instance()
    new_scene.global_position = global_position