All Questions

Community

P
Lalin Paranawithana
posted to: Bumpers (solution)

Usage Of Collision Masks for StompDetector in Player Scene

In this video, you separated the functionality of the EnemyDetector by putting a part of it into another Area2D node called StompDetector. EnemyDetector handles killing off the player when the enemy's body enters that area. StompDetector is for letting the player stomp. However, you set its collision mask to only detect the bumpers layer. How is it going to stomp on enemies, then?

  • P
    Lalin Paranawithana replied

    Never mind. I figured it out. However, why did you move the enemy's StompDetector to the bumper layer instead of just making the player's StompDetector scan for the enemy as well?

  • Nathan Lovato replied

    This decision stems from a principle of object-oriented programming: we strive to encapsulate the logic for each object into its own code, and avoid accessing data or methods from unrelated objects.

    Here, you can think of the player as one entity and the enemy as another, unrelated entity. Ideally, we'd like the player's code (its gdscript code + all the nodes in the player scene) to only handle the player's logic, like jumping, bumping, moving, and its own death. And the enemy to do the same.

    In this beginner-level course, doing this introduces some complexity, and you'll see there's a bonus lesson that uses a few lines of code to remove the need for those kill areas.

    The principle is often valuable when your games grow in size, because having one entity kill another creates a hard dependency between the two in code and make them harder to change later. Concretely, here, it'd be any line in Player.gd that'd access the enemy node, for example:

    enemy.die()

    The dependency is due to the fact that is the reference to enemy is incorrect (they already died somehow right before this code was executed) or you decide to rename the die() function, you can introduce a bug in Player.gd.

    Hopefully, that gives you a sense for why we chose to show you how to add that kind of separation. Then, in your game projects, you'll want to thoughtfully choose between adding that dependency and keeping the code short or using extra nodes and code patterns to remove dependencies between nodes.

    You'll have to take those decisions really often when coding, it's always a balancing act.

  • P
    Lalin Paranawithana replied

    The dependency is due to the fact that is the reference to enemy is incorrect (they already died somehow right before this code was executed)

    This part wasn't really clear to me. Could you please explain it again?

  • Nathan Lovato replied

    Sure!

    In code, when one object accesses another directly and it's not a child or a component of it, we say there's a dependency between them. It's a kind of relationship we often try to avoid because it means that deleting an object from memory could cause a bug in another object.

    In this case, if in Player.gd you have a line of code that accesses an instance of Enemy, if that enemy gets freed by another script, the line enemy.die() will cause an error in your code.

    It's unlikely to happen in this simple platformer project because we only access the enemy when a collision happens, and the code's simple, but at GDQuest we've had cases in more complex projects where that kind of bug happened, and so we had to change the code in something like this.

    func _on_Area2D_body_entered(body):
        if body != null and body is Enemy:
            body.die()
  • P
    Lalin Paranawithana replied

    I see. Thanks! I really appreciate your help. Keep up the good work!