Create Your First Godot 4 Game

Lesson 6: Creating a collectible

Video Notes

Let’s give our raptor some treats! In this lesson, we’ll cover creating a collectible bug that our dino can grab to score some points.

The final code can be found here: https://github.com/quiver-dev/raptor-run.

Corrections

In Godot 4 Beta 16 or higher, the AnimatedSprite2D’s Playing checkbox has been removed from the Inspector. It has been replaced by the Autoplay button, which can be found in SpriteFrames panel next to the trashcan icon.

Make sure to turn off looping on the collected animation, otherwise the animation_finished signal will never be fired for the collectible and it will never be freed.

Improvements

With newer versions of Godot 4, you can simplify the platform selection with the following:

var random_platform = available_platforms.pick_random()

  • Anoniempje
    Nov. 7, 2022 at 7:55 p.m.

    As of godot 4 beta 4 there is a random pick from array function.
    At 15m55s, line 42 and 43 can now be:

    	var random_platform = available_platforms.pick_random()
    	var new_platform = random_platform.instantiate()
    
    • amit
      Nov. 8, 2022 at 12:14 a.m.

      Nice find! A small, but welcome addition to GDScript.


  • strawfervor
    Dec. 15, 2022 at 9:31 p.m.

    This could be very nice tutorial, but it’s way too fast. It is impossible to follow you in this course, because you are pasting things, so it’s very frustraiting to stop or rewind it all the times to catch you up. In this lesson you forgot to mention about line of code with collect_sound varible (9 minute).

    • amit
      Dec. 16, 2022 at 7:01 p.m.

      Thanks for the feedback. It’s difficult to find the pacing that suits everyone, but I agree we should avoid copy and pasting multiple lines and that’s something we’ll address in future tutorials. Speaking for myself, I find myself pausing the video even if I can keep up with the writing the code, since it often takes a moment for the idea to process.

      I believe the collect_sound variable is initialized at 8:30 in the video. Is that what you mean?


  • Malcx
    Feb. 12, 2023 at 12:31 p.m.

    Why do we set the label text on every frame rather than just when the score changes?

    And would it not be more performant to create the available_platforms array once rather than every spawn call?

    • amit
      Feb. 24, 2023 at 7:13 p.m.

      Both valid points. I think the best way to handle updating the score label would be using a setter function on the score variable, which we don’t cover in this tutorial. And available_platforms could certainly be initialized once.


  • ManoShu
    Oct. 20, 2023 at 2:02 a.m.

    A thing that could be done a little bit better is to put a clamp on the collectible_pitch variable, as, in the case of the game spawning multiple platforms with “row” collectibles, the sound becomes jarring.

    After the collectible_pitch += 0.1 line, add:

    collectible_pitch = clamp(collectible_pitch, 1.0, 2.0)

    • ManoShu
      Oct. 20, 2023 at 2:08 a.m.

      The increase on the collectible_pitch can also be reduced to have a similar effect while not using a high pitch value:

      collectible_pitch += 0.05 # or other lower values
      
    • amit
      Oct. 20, 2023 at 3:10 p.m.

      Great idea! This would be especially useful if you’re designing custom platforms with even more collectibles.


  • RedHoodJT
    March 15, 2024 at 12:07 a.m.

    For some reason when I run the game my animation for picking up the collectible runs in a loop over and over again and the last collectible becomes hidden behind the platform but I am still able to collect it. Here is my code for the collectible:

    extends Area2D
    
    @export var value = 10
    
    @onready var game = $"/root/World"
    @onready var sprite = $AnimatedSprite2D
    
    # Called when the node enters the scene tree for the first time.
    func _ready():
    	self.body_entered.connect(_on_body_entered)
    	
    func _on_body_entered(body):
    	if body.is_in_group("player"):
    		game.add_score(value)
    		sprite.play("collected")
    		sprite.animation_finished.connect(_on_animation_finished)
    		
    func _on_animation_finished():
    	queue_free()
    	
    func _process(delta):
    	pass
    

    I have gone back and looked at the code in the lecture to what I have but I didn’t notice anything different. I tried deleting the animation and recreating it but that didn’t seem to do anything. I feel like I missed a step in the code that removes it from the scene.


  • RedHoodJT
    March 15, 2024 at 12:44 a.m.

    Never mind. I forgot to disable looping.


  • cswalker
    May 16, 2024 at 7:51 p.m.

    Collision is not triggering for me on Godot Engine v4.2.2.stable.flathub [15073afe3]

    Player - CharacterBody2D is in a group named player and is on Collision Layer 2. Everything looks setup correctly as everything up till this point works as the guide states.

    Collectible - Area2D has AnimatedSprite2D & CollisionShape2D (including looping off in the animation frames for the collected animation). It is also on Collision Layer 2.

    I put some print statements to help identify in the Output where something isn’t working. On play, the first print statement in func _ready() prints collectible ready in the output stream. However, the second print statement in func _on_body_entered(body) never prints body entered. So, this leads me to believe that the self.body_entered.connect(_on_body_entered) statement never triggers.

    I first thought it a problem with body_entered but the documents state it can be a PhysicsBody2D which CharacterBody2D is inherited by. I also, thought that this might only trigger on the first frame since it is in the _ready() function, but this seem not to be the case either. Or if it is, I don’t know where it should go.

    NOTE: My game variable points to World_Node2D instead of World, because I named it that to help me with my learning.

    @export var value = 10
    
    @onready var game = $"/root/World_Node2D"
    @onready var sprite = $AnimatedSprite2D
    
    # Called when the node enters the scene tree for the first time.
    func _ready():
        print("collectible ready")
        self.body_entered.connect(_on_body_entered)
        
    func _on_body_entered(body):
        print("body entered")
        if body.is_in_group("player"):
            game.add_score(value)
            sprite.play("collected")
            sprite.animation_finished.connect(_on_animation_finished)
            
    func _on_animation_finished():
        queue_free()
    # Called every frame. 'delta' is the elapsed time since the previous frame.
    func _process(delta):
        pass
    
    • amit
      May 17, 2024 at 3:32 p.m.

      Can you post your code somewhere and send a link? I can take a look at it once you do.

      • cswalker
        May 19, 2024 at 6:22 p.m.

        Thanks for offering to help. I found it was the collision mask after reading some docs. I’m trying to scan through now to find if i missed it on the player or the collectible it the guide, but not finding the mask portion.

        • Psych0Dad
          Sept. 14, 2024 at 12:18 p.m.

          I had the same issue. my problem was, that my CollisionShape2D node was a child of AnimatedSprite2D and not Collectible.


  • euvisio
    Dec. 24, 2024 at 10:14 a.m.

    In Godot 4.3 under Node Groups, there are Scene Groups and Global Groups. Should our Player be made part of the Global or Scene Group?

Want to get into the mix? Sign in or register to comment.

Next lesson