Thursday, 11 May 2017

Gitting Gud At Godot - Part 5 - Basic Physics and Collision

Watch the video version of this tutorial here: https://www.youtube.com/watch?v=1Z77N4Gomtk

In this tutorial, I'm going to be explaining the topic of collision. However, before we can get into collision, I'm going to need to show you some physics bodies and how they work.


First, we're going to need to make some adjustments to our project file. Let's delete the player node by selecting it, pressing the delete key(or right clicking and selecting "Delete Node(s)") and selecting "Yes".

Next, let's add a KinematicBody2D to our project. Though this is the first PhysicsBody we're using, it definitely won't be the last. Make sure it's a child of our tree root, and call it "player" just like our previous Sprite node.

Now, we can add another Sprite node as a child of this one. Make sure our new player node is selected, and add a Sprite node. Give it the Godot mascot texture(icon.png) as we did in Part 2, but don't give it a script. We're going to be giving the script to our KinematicBody2D node.



You might notice that if you try to overwrite the old player.gd script, it'll cause you to load that script instead. This is fine, but if you do that then you will have to replace the entire file contents with:

extends KinematicBody2D

func _ready():
    pass

Otherwise, it'll throw an exception since our KinematicBody2D does not extend Sprite.



One more thing to add is a CollisionShape2D. As I'm sure you can guess, this is the part that handles collision. It only works when it's a child of a PhysicsBody, but it'll give you a handy warning should you try anyway.

Add the CollisionShape2D as a child of our "player" node, and look down to the Inspector tab. In our "Shape" property, we can select a number of shapes to use. For now, let's go with the standard RectangleShape2D. In the scene view, zoom in on the Godot mascot and you should notice a blue rectangle with two orange dots on either side. Drag these so that the blue rectangle covers the entire Sprite.

In case the blue rectangle is bugging you, you can select the eye icon next to the CollisionShape2D node in the scene tab to hide it.

Another thing to note is that all children of our new player node should have their position property set to (0, 0) in the Inspector tab, since this position is relative to the parent.

Now that we have all this set up, let's move our "player" node(not the Sprite!) somewhere into the visible game window and run our project. With any luck, it appears exactly the same as it did in the last tutorial, minus the movement controls.

Jeez, great job Alex, you taught us to do literally nothing! 

That's where you're wrong, uninformed yet indignant reader. This appears the same since we have effectively the same Sprite object, but we have made some large changes in the internal logic of our game.

Let's get ourselves back up to where we were in Part 4 with the functioning movement system. KinematicBody2Ds don't have a set_pos() function, so we're going to have to change our approach slightly. Instead of set_pos(), we can use the slightly nice "move()" function.

move() takes in a Vector2 argument, where the X and Y are coordinates relative to the KinematicBody2D we're moving. In short, this means that doing move(Vector2(1, 0)) would move our KinematicBody2D by 1 unit in the X direction.

I won't cover the same input processes as in Part 4, but feel free to refer back to it if you need to. Here is our player.gd file in its entirety:

extends KinematicBody2D

var SPEED = 150

func _ready():
    set_process(true)

func _process(delta):
    var move_vector = Vector2(0, 0)
    if(Input.is_action_pressed("move_right")):
        move_vector.x = SPEED*delta
    elif(Input.is_action_pressed("move_left")):
        move_vector.x = -SPEED*delta
    if(Input.is_action_pressed("move_up")):
        move_vector.y = -SPEED*delta
    if(Input.is_action_pressed("move_down")):
        move_vector.y = SPEED*delta
    move(move_vector)

You've probably noticed here that I'm using SPEED*delta instead of a numerical value for the amount that I want to move the player. As explained in Part 3, delta can be used to ensure a consistent speed despite variable framerates or processing speeds. This is exactly what I'm doing here. I've defined the variable "SPEED" at the top(it is global and other nodes can access it but we'll deal with that later) and then I've varied the amount that the player will move by a factor of delta.

Okay, so now let's add a wall. Since it's too much effort for me and probably for you too at this stage to draw up a beautiful wall texture, I'm going to re-use the Godot mascot icon. Having said that, if you want to use a beautiful wall texture, be my guest.

Create a new KinematicBody2D as a child of Node. Then, give it a Sprite object and load your desired texture. If you're like me and you want to use the Godot mascot again, it might help to stretch out the Sprite a bunch using the orange dots when you have the Sprite node selected in order to make it at least representative of a wall.

Next, give your KinematicBody2D a child of type CollisionShape2D and adjust it to fit the Sprite.


And that's all you need! Now you can launch your game, move your protagonist directly into the wall and watch as they are valiantly prevented from moving through it. No code required! Well, not for the collision bit, anyway.

I'd encourage you to experiment with RigidBody2D too, since this one responds to physics in a more classical way. It bounces, it rotates, and it is affected by gravity. All of this can be tweaked under the "Inspector" tab.

You're almost ready to make a real 2D sidescroller, we only have a few topics left! How exciting! However, don't expect that the tutorials are going to stop then- it's going to get intense.

Thanks for reading! Stay tuned for Part 6 on the topic of intersections and instancing!

Part 4: http://alexhoratiogamedev.blogspot.com/2017/05/gitting-gud-at-godot-part-4-input.html 

Part 6: http://alexhoratiogamedev.blogspot.com/2017/05/gitting-gud-at-godot-part-6.html

5 comments :

  1. I've been wanting to get into Godot for a while now, but none of the available tutorials I found explained things all that well. I'm really liking your tutorials and how simple it makes things to understand.

    ReplyDelete
  2. (Kinematicbody2D does actually have a set_pos() function, however to move with the physics engine working it needs to be moved with move())

    ReplyDelete
    Replies
    1. Damn, you're absolutely right. Thanks for the correction!!

      Delete
  3. It seems the godot updates mean we have to do move_and_collide instead of move in the code. There are two possible functions, move_and_collide or move_and_slide. move_and_collide works. Move and slide doesn't!

    ReplyDelete
  4. Unknown, you are making this process easier.

    In order to move the Player (in the scene, to relocate it to the bottom right, and not after hitting play), I had to click on the eye next to Sprite and CollisionShape2D to only select the Player object. After I moved the Player object, I clicked in the same place for the Sprite and CollisionShape2D to make them viewable again and they had moved with the Player.

    This had good information about the KinematicBody2D: http://docs.godotengine.org/en/stable/tutorials/physics/using_kinematic_body_2d.html

    For instance, for move and collide, the parameter is a Vector2 velocity vector multiplied by delta.
    Move and slide requires you do not multiply by delta. This actually works in the code if you do not multiply by delta.

    ex.
    func _process(delta):
    var move_vector = Vector2(0,0)

    if(Input.is_action_pressed("ui_right")):
    move_vector.x=SPEED

    move_and_slide(move_vector)

    The main difference between the two is the type of response you want when a collision occurs, but I think move_and_collide follows the tutorial a bit better, so that's what I'm using in this case.

    Remember to give the CollisionShape2D object a rectangle shape, I kept forgetting.

    extends KinematicBody2D

    var SPEED=150

    func _ready():
    set_process(true)

    func _process(delta):
    var move_vector = Vector2(0,0)

    if(Input.is_action_pressed("ui_right")):
    move_vector.x=SPEED*delta
    elif(Input.is_action_pressed("ui_left")):
    move_vector.x = -SPEED*delta
    if(Input.is_action_pressed("ui_up")):
    move_vector.y = -SPEED*delta
    if(Input.is_action_pressed("ui_down")):
    move_vector.y = SPEED*delta

    move_and_collide(move_vector)

    ReplyDelete