Programando el movimiento del jugador

It's time to code! We're going to use the input actions we created in the last part to move the character.

Haz clic derecho en el nodo Player y selecciona Añadir Script para agregar un nuevo script. En la ventana emergente, establece la Plantilla a Empty antes de hacer clic en el botón Crear.

image0

Vamos a empezar con las propiedades de la clase. Vamos a definir una rapidez de movimento, una aceleración que representa la gravedad, y una velocidad que usaremos para mover el jugador.

extends KinematicBody

# How fast the player moves in meters per second.
export var speed = 14
# The downward acceleration when in the air, in meters per second squared.
export var fall_acceleration = 75

var velocity = Vector3.ZERO

Estas son propiedades comunes para un cuerpo en movimiento. La velocidad es un vector 3D combinando una velocidad con una dirección. Aquí, la definimos como una propiedad debido a que queremos actualizar y reusar su valor a través de los fotogramas.

Nota

Los valores son bastante diferentes del código 2D porque las distancias están en metros. En 2D mil unidades (píxeles) pueden corresponder a sólo mitad del ancho de tu pantalla, pero en 3D, es un kilómetro.

Ahora codifiquemos el movimiento. Comenzamos por calcular el vector de dirección de entrada utilizando el objeto Input global, en _physics_process().

func _physics_process(delta):
    # We create a local variable to store the input direction.
    var direction = Vector3.ZERO

    # We check for each move input and update the direction accordingly.
    if Input.is_action_pressed("move_right"):
        direction.x += 1
    if Input.is_action_pressed("move_left"):
        direction.x -= 1
    if Input.is_action_pressed("move_back"):
        # Notice how we are working with the vector's x and z axes.
        # In 3D, the XZ plane is the ground plane.
        direction.z += 1
    if Input.is_action_pressed("move_forward"):
        direction.z -= 1

Aquí, haremos todos los cálculos usando la función virtual _physics_process(). Como _process(), permite que actualices el nodo cada cuadro, pero está específicamente diseñado para código relacionado a la física, como mover un cuerpo cinemático o rígido.

Ver también

Para aprender más sobre la diferencia entre _process() y _physics_process(), véase Idle and Physics Processing.

Comenzamos por inicializar una variable direction a Vector3.ZERO. Luego, verificamos si el jugador está presionando una o más de las entradas move_* y actualizamos las componentes x y z como corresponda. Éstas atañan a los ejes del avión en el suelo.

Estas cuatro condiciones nos dan ocho posibilidades y ocho posibles direcciones.

En el caso que el jugador presione, por ejemplo W y D a la vez, el vector tendrá un largo de aproximadamente 1.4. Pero si presiona una sola tecla, tendrá el largo de 1. Queremos que el largo del vector sea consistente. Para ello, podemos llamar el método normalize().

#func _physics_process(delta):
    #...

    if direction != Vector3.ZERO:
        direction = direction.normalized()
        $Pivot.look_at(translation + direction, Vector3.UP)

Aquí, sólo normalizamos el vector si la dirección es mayor a cero, lo que significa que el jugador presiona una tecla de dirección.

En este caso, también obtenemos el nodo Pivot y llamamos a su método look_at(). Éste toma una posición en el espacio en coordenadas globales a la que apuntar, y la dirección. En este caso, podemos usar la constante Vector3.UP.

Nota

Las coordenadas locales de un nodo, como translation, son relativas a su padre. En cambio, las coordenadas globales son relativas a los ejes principales del mundo que se ven en el viewport.

En 3D, la propiedad que contiene la posición de un nodo es translation. Sumándole la direction, obtenemos una posición a la que apuntar que está a un metro del Player.

Then, we update the velocity. We have to calculate the ground velocity and the fall speed separately. Be sure to go back one tab so the lines are inside the _physics_process() function but outside the condition we just wrote.

func _physics_process(delta):
    #...
    if direction != Vector3.ZERO:
        #...

    # Ground velocity
    velocity.x = direction.x * speed
    velocity.z = direction.z * speed
    # Vertical velocity
    velocity.y -= fall_acceleration * delta
    # Moving the character
    velocity = move_and_slide(velocity, Vector3.UP)

For the vertical velocity, we subtract the fall acceleration multiplied by the delta time every frame. Notice the use of the -= operator, which is a shorthand for variable = variable - ....

This line of code will cause our character to fall in every frame. This may seem strange if it's already on the floor. But we have to do this for the character to collide with the ground every frame.

The physics engine can only detect interactions with walls, the floor, or other bodies during a given frame if movement and collisions happen. We will use this property later to code the jump.

On the last line, we call KinematicBody.move_and_slide(). It's a powerful method of the KinematicBody class that allows you to move a character smoothly. If it hits a wall midway through a motion, the engine will try to smooth it out for you.

La función toma dos parámetros: velocidad y el dirección por arriba. Se mueve el jugador y devuelve el velocidad restante despues de aplicando colisiones. Cuando llegue al piso o pared, la función reducirá o reajustará la rapidez en el dirección apropriada. En este caso, manteniendo el valor devuelto por el función previene el personaje contra acumulando impulso vertical, que podría seguir incrementando en otros casos hasta que el personaje mueva a través del terreno.

Y esto es todo el código necesario para mover el personaje en el piso.

Here is the complete Player.gd code for reference.

extends KinematicBody

# How fast the player moves in meters per second.
export var speed = 14
# The downward acceleration when in the air, in meters per second squared.
export var fall_acceleration = 75

var velocity = Vector3.ZERO


func _physics_process(delta):
    var direction = Vector3.ZERO

    if Input.is_action_pressed("move_right"):
        direction.x += 1
    if Input.is_action_pressed("move_left"):
        direction.x -= 1
    if Input.is_action_pressed("move_back"):
        direction.z += 1
    if Input.is_action_pressed("move_forward"):
        direction.z -= 1

    if direction != Vector3.ZERO:
        direction = direction.normalized()
        $Pivot.look_at(translation + direction, Vector3.UP)

    velocity.x = direction.x * speed
    velocity.z = direction.z * speed
    velocity.y -= fall_acceleration * delta
    velocity = move_and_slide(velocity, Vector3.UP)

Testing our player's movement

We're going to put our player in the Main scene to test it. To do so, we need to instantiate the player and then add a camera. Unlike in 2D, in 3D, you won't see anything if your viewport doesn't have a camera pointing at something.

Save your Player scene and open the Main scene. You can click on the Main tab at the top of the editor to do so.

image1

If you closed the scene before, head to the FileSystem dock and double-click Main.tscn to re-open it.

To instantiate the Player, right-click on the Main node and select Instance Child Scene.

image2

In the popup, double-click Player.tscn. The character should appear in the center of the viewport.

Adding a camera

Let's add the camera next. Like we did with our Player's Pivot, we're going to create a basic rig. Right-click on the Main node again and select Add Child Node this time. Create a new Position3D, name it CameraPivot, and add a Camera node as a child of it. Your scene tree should look like this.

image3

Notice the Preview checkbox that appears in the top-left when you have the Camera selected. You can click it to preview the in-game camera projection.

image4

We're going to use the Pivot to rotate the camera as if it was on a crane. Let's first split the 3D view to be able to freely navigate the scene and see what the camera sees.

In the toolbar right above the viewport, click on View, then 2 Viewports. You can also press Ctrl + 2 (Cmd + 2 on macOS).

image5

On the bottom view, select the Camera and turn on camera preview by clicking the checkbox.

image6

In the top view, move the camera about 19 units on the Z axis (the blue one).

image7

Here's where the magic happens. Select the CameraPivot and rotate it 45 degrees around the X axis (using the red circle). You'll see the camera move as if it was attached to a crane.

image8

You can run the scene by pressing F6 and press the arrow keys to move the character.

image9

We can see some empty space around the character due to the perspective projection. In this game, we're going to use an orthographic projection instead to better frame the gameplay area and make it easier for the player to read distances.

Select the Camera again and in the Inspector, set the Projection to Orthogonal and the Size to 19. The character should now look flatter and the ground should fill the background.

image10

With that, we have both player movement and the view in place. Next, we will work on the monsters.