diff --git a/tutorials/physics/files/using_kinematic2d.zip b/tutorials/physics/files/using_kinematic2d.zip new file mode 100644 index 000000000..541b862cf Binary files /dev/null and b/tutorials/physics/files/using_kinematic2d.zip differ diff --git a/tutorials/physics/img/k2d_bullet_bounce.gif b/tutorials/physics/img/k2d_bullet_bounce.gif new file mode 100644 index 000000000..246cbcc4f Binary files /dev/null and b/tutorials/physics/img/k2d_bullet_bounce.gif differ diff --git a/tutorials/physics/img/k2d_compare.gif b/tutorials/physics/img/k2d_compare.gif new file mode 100644 index 000000000..95b5a4466 Binary files /dev/null and b/tutorials/physics/img/k2d_compare.gif differ diff --git a/tutorials/physics/img/k2d_platform.gif b/tutorials/physics/img/k2d_platform.gif new file mode 100644 index 000000000..f78f95496 Binary files /dev/null and b/tutorials/physics/img/k2d_platform.gif differ diff --git a/tutorials/physics/index.rst b/tutorials/physics/index.rst index d62560f96..2b9f50879 100644 --- a/tutorials/physics/index.rst +++ b/tutorials/physics/index.rst @@ -7,5 +7,6 @@ Physics physics_introduction rigid_body + using_kinematic_body_2d ray-casting kinematic_character_2d diff --git a/tutorials/physics/using_kinematic_body_2d.rst b/tutorials/physics/using_kinematic_body_2d.rst new file mode 100644 index 000000000..25d0ee998 --- /dev/null +++ b/tutorials/physics/using_kinematic_body_2d.rst @@ -0,0 +1,359 @@ +.. _doc_using_kinematic_body_2d: + +Using KinematicBody2D +===================== + +Introduction +------------ + +Godot offers a number of collision objects to provide both collision detection +and response. Trying to decide which one to use for your project can be confusing. +You can avoid problems and simplify development if you understand how each each +works and what their pros and cons are. In this tutorial, we'll look at the +:ref:`KinematicBody2D ` node and show some examples +of how it can be used. + +.. note:: This document assumes you're familiar with Godot's various physics + bodies. Please read :ref:`doc_physics_introduction` first. + +What is a kinematic body? +------------------------- + +``KinematicBody2D`` is for implementing bodies that are to be controlled via code. +They detect collisions with other bodies when moving, but are not affected by +engine physics properties like gravity or friction. While this means that you +have to write some code to create their behavior, it also means you have more +precise control over how they move and react. + +.. tip:: A `KinematicBody2D` can be affected by gravity and other forces, + but you must calculate the movement in code. The physics engine will + not move a `KinematicBody2D`. + +Movement and Collision +---------------------- + +When moving a ``KinematicBody2D``, you should not set its ``position`` property +directly. Instead, you use the ``move_and_collide()`` or ``move_and_slide()`` methods. +These methods move the body along a given vector and will instantly stop if +a collision is detected with another body. After a KinematicBody2D has collided, +any *collision response* must be coded manually. + +.. warning:: Kinematic body movement should only be done in the ``_physics_process()`` callback. + +The two movement methods serve different purposes, and later in this tutorial you'll +see examples of how they work. + +``move_and_collide`` +~~~~~~~~~~~~~~~~~~~~ + +This method takes one parameter: a :ref:`Vector2 ` indicating the body's +relative movement. Typically, this is your velocity vector multiplied by the +frame timestep (``delta``). If the engine detects a collision anywhere along +this vector, the body will immediately stop moving. If this happens, the +method will return a :ref:`KinematicCollision2D ` object. + +``KinematicCollision2D`` is an object containing data about the collision +and the colliding object. Using this data you can calculate your collision +response. + +``move_and_slide`` +~~~~~~~~~~~~~~~~~~ + +The ``move_and_slide()`` method is intended to simplify the collision +response in the common case where you want one body to slide along the other. +This is especially useful in platformers or top-down games, for example. + +.. tip:: ``move_and_slide()`` automatically calculates frame-based movement + using ``delta``. Do *not* multiply your velocity vector by ``delta`` + before passing it to ``move_and_slide()``. + +In addition to the velocity vector, ``move_and_slide()`` takes a number of +other parameters allowing you to customize the slide behavior: + +- ``floor_normal`` - *default value:* ``Vector2( 0, 0 )`` + + This parameter allows you to define what surfaces the engine should consider + to be the floor. Setting this lets you use the ``is_on_floor()``, ``is_on_wall()``, + and ``is_on_ceiling()`` methods to detect what type of surface the body is + in contact with. The default value means that all surfaces are considered walls. + +- ``slope_stop_min_velocity`` - *default value:* ``5`` + + This is the minimum velocity when standing on a slope. This prevents a body + from sliding down a slope when standing still. + +- ``max_bounces`` - *default value:* ``4`` + + This is the maximum number of collisions before the body stops moving. Setting + this too low may prevent movement entirely. + +- ``floor_max_angle`` - *default value:* ``0.785398`` (in radians, equivalent to ``45`` degrees) + + This is the maximum angle before a surface is no longer considered a "floor". + +Which movement method to use? +----------------------------- + +A common question from new Godot users is: "How do you decide which movement +function to use?" Often the response is to use ``move_and_slide()`` because +it's "simpler", but this is not necessarily the case. One way to think of it +is that ``move_and_slide()`` is a special case, and ``move_and_collide()`` +is more general. For example, the following two code snippets result in +the same collision response: + +.. image:: img/k2d_compare.gif + +.. tabs:: + .. code-tab:: gdscript GDScript + + # using move_and_collide + var collision = move_and_collide(velocity * delta) + if collision: + velocity = velocity.slide(collision.normal) + + # using move_and_slide + velocity = move_and_slide(velocity) + + .. code-tab:: csharp + + Coming soon + +Anything you do with ``move_and_slide()`` can also be done with ``move_and_collide()``, +it just might take a little more code. However, as we'll see in the examples below, +there are cases where ``move_and_slide()`` doesn't provide the response you want. + +Examples +-------- + +To see these examples in action, download the sample project: +:download:`using_kinematic2d.zip `. + +Movement and walls +~~~~~~~~~~~~~~~~~~ + +If you've downloaded the sample project, this example is in the "BasicMovement.tscn" scene. + +For this example, Add a ``KinematicBody2D`` with two children: a ``Sprite`` and a +``CollisionShape2D``. Use the Godot "icon.png" as the Sprite's texture (drag it +from the Filesystem dock to the *Texture* property of the ``Sprite``). In the +``CollisionShape2D``'s *Shape* property, select "New RectangleShape2D" and +size the rectangle to fit over the sprite image. + +.. note:: See :ref:`doc_2d_movement` for examples of implementing 2D movement schemes. + +Attach a script to the KinematicBody2D and add the following code: + +.. tabs:: + .. code-tab:: gdscript GDScript + + extends KinematicBody2D + + var speed = 250 + var velocity = Vector2() + + func get_input(): + # Detect up/down/left/right keystate and only move when pressed + velocity = Vector2() + if Input.is_action_pressed('ui_right'): + velocity.x += 1 + if Input.is_action_pressed('ui_left'): + velocity.x -= 1 + if Input.is_action_pressed('ui_down'): + velocity.y += 1 + if Input.is_action_pressed('ui_up'): + velocity.y -= 1 + velocity = velocity.normalized() * speed + + func _physics_process(delta): + get_input() + move_and_collide(velocity * delta) + + .. code-tab:: csharp + + Coming soon + + +Run this scene and you'll see that ``move_and_collide()`` works as expected, moving +the body along the velocity vector. Now let's see what happens when you add +some obstacles. Add a :ref:`StaticBody2D ` with a +rectangular collision shape. For visibility, you can use a sprite, a +Polygon2D, or just turn on "Visible Collision Shapes" from the "Debug" menu. + +Run the scene again and try moving into the obstacle. You'll see that the ``KinematicBody2D`` +can't penetrate the obstacle. However, try moving into the obstacle at an angle and +you'll find that the obstacle acts like glue - it feels like the body gets stuck. + +This happens because there is no *collision response*. ``move_and_collide()`` just stops +the body's movement when a collision occurs. We need to code whatever response we +want from the collision. + +Try changing the function to ``move_and_slide(velocity)`` and running again. +Note that we removed ``delta`` from the velocity calculation. + +``move_and_slide()`` provides a default collision response of sliding the body along the +collision object. This is useful for a great many game types, and may be all you need +to get the behavior you want. + +Bouncing/reflecting +~~~~~~~~~~~~~~~~~~~ + +What if you don't want a sliding collision response? For this example ("BounceandCollide.tscn" +in the sample project), we have a character shooting bullets and we want the bullets to +bounce off the walls. + +This example uses three scenes. The main scene contains the Player and Walls. +The Bullet and Wall are separate scenes so that they can be instanced. + +The Player is controlled by the `w` and `s` keys for forward and back. Aiming +uses the mouse pointer. Here is the code for the Player, using ``move_and_slide()``: + +.. tabs:: + .. code-tab:: gdscript GDScript + + extends KinematicBody2D + + var Bullet = preload("res://Bullet.tscn") + var speed = 200 + var velocity = Vector2() + + func get_input(): + # add these actions in Project Settings -> Input Map + velocity = Vector2() + if Input.is_action_pressed('backward'): + velocity = Vector2(-speed/3, 0).rotated(rotation) + if Input.is_action_pressed('forward'): + velocity = Vector2(speed, 0).rotated(rotation) + if Input.is_action_just_pressed('mouse_click'): + shoot() + + func shoot(): + # "Muzzle" is a Position2D placed at the barrel of the gun + var b = Bullet.instance() + b.start($Muzzle.global_position, rotation) + get_parent().add_child(b) + + func _physics_process(delta): + get_input() + var dir = get_global_mouse_position() - global_position + # Don't move if too close to the mouse pointer + if dir.length() > 5: + rotation = dir.angle() + velocity = move_and_slide(velocity) + + .. code-tab:: csharp + + Coming soon + + +And the code for the Bullet: + +.. tabs:: + .. code-tab:: gdscript GDScript + + extends KinematicBody2D + + var speed = 750 + var velocity = Vector2() + + func start(pos, dir): + rotation = dir + position = pos + velocity = Vector2(speed, 0).rotated(rotation) + + func _physics_process(delta): + var collision = move_and_collide(velocity * delta) + if collision: + velocity = velocity.bounce(collision.normal) + if collision.collider.has_method("hit"): + collision.collider.hit() + + func _on_VisibilityNotifier2D_screen_exited(): + queue_free() + + .. code-tab:: csharp + + Coming soon + + +The action happens in ``_physics_process()``. After using ``move_and_collide()`` if a +collision occurs, a ``KinematicCollision2D`` object is returned (otherwise, the return +is ``Nil``). + +If there is a returned collision, we use the ``normal`` of the collision to reflect +the bullet's ``velocity`` with the ``Vector2.bounce()`` method. + +If the colliding object (``collider``) has a ``hit`` method, +we also call it. In the example project, we've added a flashing color effect to +the Wall to demonstrate this. + +.. image:: img/k2d_bullet_bounce.gif + +Platformer movement +~~~~~~~~~~~~~~~~~~~ + +Let's try one more popular example: the 2D platformer. ``move_and_slide()`` +is ideal for quickly getting a functional character controller up and running. +If you've downloaded the sample project, you can find this in "Platformer.tscn". + +For this example, we'll assume you have a level made of ``StaticBody2D`` objects. +They can be any shape and size. In the sample project, we're using +:ref:`Polygon2D ` to create the platform shapes. + +Here's the code for the player body: + + +.. tabs:: + .. code-tab:: gdscript GDScript + + extends KinematicBody2D + + export (int) var run_speed = 100 + export (int) var jump_speed = -400 + export (int) var gravity = 1200 + + var velocity = Vector2() + var jumping = false + + func get_input(): + velocity.x = 0 + var right = Input.is_action_pressed('ui_right') + var left = Input.is_action_pressed('ui_left') + var jump = Input.is_action_just_pressed('ui_select') + + if jump and is_on_floor(): + jumping = true + velocity.y = jump_speed + if right: + velocity.x += run_speed + if left: + velocity.x -= run_speed + + func _physics_process(delta): + get_input() + velocity.y += gravity * delta + if jumping and is_on_floor(): + jumping = false + velocity = move_and_slide(velocity, Vector2(0, -1)) + + .. code-tab:: csharp + + Coming soon + +.. image:: img/k2d_platform.gif + +When using ``move_and_slide()`` the function returns a vector representing the +movement that remained after the slide collision occurred. Setting that value back +to the character's ``velocity`` allows us to smoothly move up and down slopes. Try +removing ``velocity =`` and see what happens if you don't do this. + +Also note that we've added ``Vector2(0, -1)`` as the floor normal. This is a vector +pointing straight upward. This means that if the character collides with an object +that has this normal, it will be considered a floor. + +Using the floor normal allows us to make jumping work, using ``is_on_floor()``. This +function will only return ``true`` after a ``move_and_slide()`` collision where the +colliding body's normal is within 45 degrees of the given floor vector (this can +be adjusted by setting ``floor_max_angle``). + +This also allows you to implement other features like wall jumps using ``is_on_wall()``, +for example.