Godot Lesson 11: Heart Pickups

Summary

This adds enemy drops and heart pickups.

Prerequisites

  • Lessons 1-10

Video

Code

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":
        body_entered(area_parent.get_parent())

func body_entered(body):
    pass

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
        queue_free()

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
        queue_free()

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()

# "CONSTANTS"
var SPEED = 0
var TYPE = "ENEMY"
var DAMAGE = null
var MAXHEALTH = 1

# MOVEMENT
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_physics_process(false)
        # set the collision mask to collide with the
        # walls in the camera scene
        set_collision_mask_bit(1,1)
    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
    else:
        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:
        Vector2.LEFT:
            spritedir = "left"
        Vector2.RIGHT:
            spritedir = "right"
        Vector2.UP:
            spritedir = "up"
        Vector2.DOWN:
            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:
        $anim.play(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
    else:
        $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:
                instance_scene(preload("res://pickups/heart.tscn"))
            instance_scene(preload("res://enemies/enemy_death.tscn"))
            queue_free()

    # 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
    add_child(newitem)

    # 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:
        newitem.queue_free()

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