Tutorial (Godot Engine v3 - GDScript) - Collision Detection!

in utopian-io •  7 years ago  (edited)

Godot Engine Logo v3 (Competent).png Tutorial

...learn how to add Collision Detection!

What Will I Learn?

This tutorial builds on the last, which explained how to add Bullets.

In this article, you will learn how to add collisions to the game; allowing for the Invaders and the Player to kill or be killed!


Correct Behaviour.gif

Note: the recording method doesn't do justice to the smoothness of this

Assumptions

You will

  • Learn about Collisions (an overview)
  • Add a Collision Area to the Player Ship
  • Add a Collision Area to the Invader
  • Add a Collision Area to the Laser
  • Learn to connect the Signals!
  • Rename Area2D's for object checking
  • Learn to enable Debug option to see Collision Areas

Requirements

You must have installed Godot Engine v3.0.

All the code from this tutorial will be provided in a GitHub repository. I'll explain more about this, towards the end of the tutorial.


Collisions (an overview)

Collisions in Godot Engine are very simple to implement, but requires a lot of practice!

For our game, we are going to utilise the Area2D class, which is designed to be used for 2D collisions. With this, we are going to attach a CollisionShape2D.

The shape is defined as an area, not seen, which overlaps an object on the screen; i.e. a Sprite is a great candidate!

The shape can be one of many different shapes, i.e. Circle, Box, Concave, Convex, etc.


image.png

As the object (i.e. Sprite) moves, the Shape remains positioned to it. For example, in the above diagram, the Player Sprite has a triangular Collision Shape assigned to it. As the ship moves, the Shape follows, as it is attached to the child.

The Shape is NEVER seen; unless it is enabled in debug mode (see the debug section below).


image.png

When two objects with CollisionShape2D's overlap, a collision is detected and a signal is raised. These are hooked to code, which in turn, responds; such as destroy the Player ship because the Invader's shape (the red circle) enters the Players!

Consult the Godot Area2D documentation for a list and description of all the signals available; we're only going to use the 'area_entered'.

Add a Collision Area to the Player ship

Let's begin by adding a collision shape to the Player ship.

  • Open the Player Scene
  • Create an Area2D node as a child to the root Player sprite:
    image.png
    You'll note a Yellow triangle appears
    image.png

Hover over it and you will see a pop-up informing you that you need to add a Shape!

  • Create a CollisionShape2D as a child to the Area2D:
    image.png

A yellow triangle will show next to the new CollisionShape2D because we need to define the shape itself! Look in the inspector, when you select the CollisionShape2D. Its value will be a null shape
image.png

You need to click on this and select a New Convex Shape (look at the icon to the left of the option, it shows a box with a hat on it)
image.png

Click on the option again and the menu will list an Edit option, select that
image.png

Click on the Vector Points option, because we need to set our Vector points

(The little right arrow, as highlighted in yellow)
image.png

Now specify Array Size 3 with the values [(-6,7),(0,-7),(6,7)]
image.png

As can be seen in the diagram above, you set the Vector array as three points on a Triangle (it is odd that Godot doesn't have a built in a triangle shape in my opinion!). On the left, you can see what should be in your 2D view. The Shape will be seen over your Sprite, even though in the game it is hidden because it is ONLY used to detect collisions with other areas.

Congratulations, you've now added a Collision Area to your Player ship, let's move on and do the same with the Invader and the Laser bullet

Add a Collision Area to the Invader

  • Open the Invader Scene
  • Add an Area2D and CollisionShape2D.
  • This time, add a Capsule Shape to the _Invader:
    image.png

As can be seen, the Shape is smaller than the _Invader, therefore we need to resize it!

Note: you may wonder why I chose the Capsule type over a Circular one. We want the shape to fit as snuggly as possible. The Invader sprite is wide and short, thus, the Capsule works best for this sprite.

Before we change the size, please find the Node2D>Transform>Rotation Degree option of the CollisionShape2D and set it to 90 degrees, thereby placing the curved ends sideways:
image.png

Next, edit the Capsule Shape, setting it to a Radius of 28 and Height of 25 (I only found these by experimenting!)
image.png

The Shape should now be perfectly placed over your Invader Sprite, like so:
image.png

Add Collision Area to the Laser

  • Open the Laser Scene
  • Add an Area2D and CollisionShape2D.
  • Add a rectangle Shape to the Invader:
    image.png

As can be seen, the Shape is a lot wider than the Laser but shorter; therefore we need to resize it!
Edit the rectangle, setting it to x=5 and y=15:
image.png

We could have used a Capsule Shape, but the number of points in the shape determines performance! For our purposes, this really isn't relevant, but if we had hundreds of thousands, then it it might!

Try running the game now! What happens???

... Did you expect that?

Connecting the Signals!

Nothing happened! The game played as it did before.

For those with a good memory, I mentioned earlier that the collisions are detected and a signal is fired! Without us changing the behaviour of our actors to use the signals, nothing happens!

Let's add the first simple case.

  • Open the Player Scene
  • Open the Player script
  • Add the following line to the top of the ready function
$Area2D.connect("area_entered", self, "hit")

This locates the child Area2D and tells it to connect the signal to a function; as described in the parameters.

  1. The first parameter is the "area_entered" signal that is called by the Collision.
  2. The second parameter states where to find the third parameter, as a function; i.e. in the same (self) script.
  3. The name of the function to call

Thus, when any other Area2D enters the Players Area2D child area, an 'area_entered' signal is triggered which is wired to our hit function.

Which has to be added:

func hit(object):
        modulate.a = 0.2

This simply receives the object that was detected hitting the Area2D (not that we use it... yet!).

For now, we'll set the alpha of the sprite, thereby dimming it

We'll change this code shortly, but for now, this is the easiest way for me to demonstrate the process.

The code looks like this in the editor:

image.png

Try running it, but do not touch the Spacebar; if you do, you'll notice a bug!

Eventually, the Invader reaches the Player and when one touches, the ship becomes transparent:

alien to player collision.gif

Did you notice what happens when you press the Spacebar?

This is the type of issue that will often confuse beginners. It works, but not as expected!

press fire.gif

As soon as the Laser appears, its Shape collides with the Player ship, because it is launched by it! We need a mechanism to ignore the Laser!

Rename the Laser Area2D

Not the best solution, but one approach is to use the name of the object to determine whether it should be ignored or not.

I.E. The object passed to the hit function of the Player ship will be the Area2D that collided. If we rename the Laser's Area2D to something unique (rather the generic Area2D name), we can check for its name and ignore it!

  • Open the Lazer Scene
  • Rename the Area2D to LaserArea

image.png

Let's now modify the Player script hit function:

    func hit(object):
        if object.name != 'LaserArea':
            modulate.a = 0.2

The function will only change the alpha of the Sprite if something, other than the LaserArea hits it.

Try rerunning! You should find that you can fire at will! Allow the Alien to touch and you will see that they WILL kill the Player:

bullets ok.gif

Connect the Invader to a hit signal!

Let's do the same for the Invader as we did for the Player

  • Open the Invader Scene
  • Open the Invader script
  • Add the new ready and hit functions:
    func _ready():
    $Area2D.connect("area_entered", self, "hit")
func hit(object):
    queue_free()

The ready function connects the Invader child Area2D to this script and wires the 'area_entered' signal to the hit function
The hit function instructs Godot Engine to remove the Instance from the tree before the next frame (i.e. remove it from the screen and game)!

You can now dispatch those pesky Invaders:

destroying invaders.gif

... BUT, that was WAY too easy... Why???

Set the Laser signal processing!

The Laser bullet didn't stop when it hit the first Invader! It continues to wipe out anything else that it hits before going off the screen! This is bad but can be easily fixed. Can you think how???

Before adding the code to the Laser script, we need to think:

  • When the Laser is fired by the Player ship it will detect it
  • The Laser node should only be removed when it hits an Invader

Given this, we need to modify the name of the Area2D of the Invader, so that the Laser script can use its name (much like we did with the Player and Laser)

  • Open the Invader Scene
  • Rename the Area2D to InvaderArea:
    image.png
  • By changing the Area2D, you will need to open the Invader script and modify the ready method:
func _ready():
    $InvaderArea.connect("area_entered", self, "hit")

Previously, the connect was on the $Area2D, but this has now been renamed to $InvaderArea! See how making changes to the Nodes in the Scene Tree can affect your code!

  • Open the Laser script and add the following two new functions:
func _ready():
    $LaserArea.connect("area_entered", self, "hit")

func hit(object):
    if object.name == 'InvaderArea':
        queue_free()

The ready function finds the child LaserArea and connects its "area_entered" signal to the scripts hit function
The hit function checks whether the object that is touching is an InvaderArea, freeing itself from the tree and the game if true.

The behaviour is now correct:
Correct Behaviour.gif

Finally

That concludes this issue. My next article will address colour selections!

Don't forget to check out the game on itch.io. I'm publishing the game in advance of tutorials, so it is worth you checking it out periodically! You'll be able to guess what is coming up in my posts!

Please do comment and ask questions! I'm more than happy to interact with you.

Sample Project

I hope you've read through this Tutorial, as it will provide you with the hands-on skills that you simply can't learn from downloading the sample set of code.

However, for those wanting the code, please download from GitHub.

You should then Import the "Space Invaders (part 7)" folder into Godot Engine.

Other Tutorials

Beginners

Competent



Posted on Utopian.io - Rewarding Open Source Contributors

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

Thank you for the contribution. It has been approved.

Hi sp33dy, very interesting tutorial once again, but I think you accidentally pasted the tutorial twice!

You can contact us on Discord.
[utopian-moderator]

Thanks! I had problems with my laptop and had to reboot as the keyboard had stopped working. I pasted the content into notepad++ before so. Looks like it messed up the content. I've now fixed that and fixed the spelling mistake in the title!

Hey @sp33dy I am @utopian-io. I have just upvoted you!

Achievements

  • You have less than 500 followers. Just gave you a gift to help you succeed!
  • Seems like you contribute quite often. AMAZING!

Community-Driven Witness!

I am the first and only Steem Community-Driven Witness. Participate on Discord. Lets GROW TOGETHER!

mooncryption-utopian-witness-gif

Up-vote this comment to grow my power and help Open Source contributions like this one. Want to chat? Join me on Discord https://discord.gg/Pc8HG9x