Documentación de Godot Engine 4.0 - Primer juego 3D

Este artículo es el resultado de la traducción al inglés de Google Translate, y DrGraph agregó algunas correcciones sobre esta base. Página original en inglés:

Tu primer juego 3D — Documentación de Godot Engine (estable) en español

Tu primer juego en 3D¶

En esta serie de tutoriales paso a paso, crearás tu primer juego completo en 3D usando Godot. Al final de esta serie, tendrás un proyecto simple pero terminado como el gif animado a continuación.

El juego que escribiremos aquí es similar a tu primer juego en 2D , pero con una diferencia: ahora puedes saltar y tu objetivo es aplastar a los súbditos. De esta manera, puede reconocer los patrones que aprendió en el tutorial anterior y desarrollarlos con código y funcionalidad nuevos.

Aprenderás:

  • Las coordenadas 3D se manejan mediante el mecanismo de salto.

  • Utilice cuerpos cinemáticos para mover personajes 3D y detectar cuándo y cómo chocan.

  • Utilice la capa física y un grupo para detectar interacciones con entidades específicas.

  • Escriba un juego de procedimiento básico instanciando monstruos periódicamente.

  • Diseñe una animación de movimiento y cambie su velocidad en tiempo de ejecución.

  • Dibuja la interfaz de usuario en el juego 3D.

y más.

Este tutorial es para principiantes que han seguido la serie completa de Primeros pasos. [Iremos despacio al principio para elaborar] y seremos breves a medida que sigamos con pasos similares. Si es un programador experimentado, puede explorar el código fuente para ver la demostración completa aquí: Código fuente de Squash the Creep .

NOTA: Puedes seguir esta serie sin terminar la serie 2D. Sin embargo, si eres nuevo en el desarrollo de juegos, te recomendamos comenzar con 2D. El código de juego 3D siempre es más complejo, mientras que la serie 2D te dará una base más cómoda.

Preparamos algunos activos del juego para que podamos saltar directamente al código. Puedes descargarlos aquí: Squash the Creeps assets .

Comenzaremos haciendo un prototipo básico de los movimientos del jugador. Luego agregaremos monstruos que generaremos aleatoriamente alrededor de la pantalla. Después de eso, implementaremos las mecánicas de salto y aplastamiento antes de mejorar el juego con algunas animaciones agradables. Terminaremos con una pantalla de puntuación y reintento.

Configurando el área de juego¶

En la primera parte, configuraremos el área de juego. Comencemos importando los activos iniciales y configurando la escena del juego.

Hemos preparado un proyecto de Godot con los modelos 3D y los sonidos que usaremos en este tutorial, vinculado en la página de índice. Si aún no lo ha hecho, puede descargar el archivo aquí: Squash the Creeps assets .

Después de la descarga, extraiga el archivo .zip en su computadora. Abre el Administrador de Proyectos de Godot y haz clic en el botón Importar .

En la ventana emergente de importación, ingrese la ruta completa al directorio recién creado  squash_the_creeps_start/. Puede hacer clic en el botón de exploración a la derecha para abrir un explorador de archivos y navegar a project.godotlos archivos contenidos en la carpeta.

Haga clic en Importar y editar para abrir el proyecto en el editor.

El proyecto de inicio contiene un icono y dos carpetas: art/y fonts/. Allí encontrarás los elementos artísticos y la música que usaremos en el juego.

Hay dos modelos 3D, player.glbalgunos mob.glbmateriales pertenecientes a estos modelos y una pista de música.

Establecer área jugable¶

Crearemos nuestra escena principal con un nodo normal como raíz. En  el panel Escena  , haga clic en el botón Agregar nodo secundario representado por el ícono "+" en la esquina superior izquierda , luego haga doble clic en el Nodo . Asigne un nombre al nodo Main. Otra forma de cambiar el nombre de un nodo es hacer clic con el botón derecho en el nodo y seleccionar Cambiar nombre (o F2). Alternativamente, para agregar un nodo a la escena, puede presionar Ctrl+a (o Cmd+a en macOS).

Presione Ctrl+s (Cmd+s en macOS) para guardar la escena como main.tscn.

Comenzaremos agregando un piso para evitar que el personaje se caiga. Para crear colisionadores estáticos como pisos, paredes o techos, puede usar el nodo StaticBody3D . Requieren un nodo secundario CollisionShape3D para definir el área de colisión. Con los nodos seleccionados Main, agregue un  nodo StaticBody3D , luego un CollisionShape3D . Cambie el nombre de StaticBody3DGround a .

Su árbol de escena debería verse así

Aparecerá una señal de advertencia junto a CollisionShape3D porque aún no hemos definido su forma. Si hace clic en el icono, aparecerá una ventana emergente que le dará más información.

Para crear la forma, seleccione el nodo CollisionShape3D, vaya al Inspector  y haga clic en el campo <vacío> junto a la propiedad Forma . Cree un nuevo BoxShape3D .

La forma de caja es perfecta para suelos y paredes planas. Su grosor le permite bloquear de manera confiable objetos que se mueven rápidamente.

Aparece una estructura alámbrica de un cuadro en la ventana gráfica con tres puntos naranjas. Puede hacer clic y arrastrarlos para editar de forma interactiva la extensión de las formas. También podemos establecer dimensiones con precisión en el inspector. Haga clic en BoxShape3D para expandir el recurso. Establezca su tamaño en X 60, Y y 2Z.60

Las formas de colisión son invisibles. Necesitamos agregar un piso visual para acompañarlo. Seleccione este Groundnodo y agregue un MeshInstance3D como su nodo secundario.

En el Inspector, haga clic en el campo junto a Malla y cree un recurso BoxMesh  para crear un cuadro visible.

Una vez más, es demasiado pequeño por defecto. Haga clic en el icono de cuadro para expandir el recurso y establecer su tamaño en 60, 2y 60.

Debería ver una losa gris ancha que cubre la cuadrícula y los ejes azul y rojo en la ventana gráfica.

Vamos a mover el suelo hacia abajo para que podamos ver la malla del suelo. Seleccione  Groundel nodo, mantenga presionada la tecla Ctrl para activar el ajuste de cuadrícula, luego haga clic y arrastre hacia abajo el eje Y. Es la flecha verde en el dispositivo móvil.

Nota: Si no puede ver los manipuladores de objetos 3D que se muestran en la imagen de arriba, asegúrese de que el Modo de selección esté activo en la barra de herramientas sobre la vista .

Mueva los 1medidores de suelo hacia abajo para que haya una cuadrícula de editor visible. Una etiqueta en la esquina inferior izquierda de la ventana gráfica le indica cuánto se ha panoramizado el nodo.

Nota: Mover el nodo Ground hacia abajo moverá ambos nodos secundarios al mismo tiempo. Asegúrese de mover el nodo Ground , no MeshInstance3D o  CollisionShape3D .

En última instancia, Groundtransform.position.y debería ser -1

Agreguemos una luz direccional para que nuestra escena no sea completamente gris. Seleccione Main el nodo y agregue el nodo secundario DirectionalLight3D .

Necesitamos mover y rotar el nodo DirectionalLight3D . Mueva el manipulador hacia arriba haciendo clic y arrastrando su flecha verde, luego haga clic y arrastre el arco rojo para girarlo sobre el eje X hasta que se ilumine el suelo.

En el Inspector , active Shadow -> Enabled haciendo clic en la casilla de verificación .

En este punto, su proyecto debería verse así.

Este es nuestro [nuevo punto de partida]. En la siguiente parte, nos ocuparemos de la escena del jugador y el movimiento de la base.

Escena del jugador y acciones de entrada¶

En las próximas dos lecciones, diseñaremos la escena del jugador, registraremos acciones de entrada personalizadas y codificaremos el movimiento del jugador. Al final, tendrás un personaje jugable que puede moverse en ocho direcciones.

Cree una nueva escena yendo al menú Escena en la esquina superior izquierda y haciendo clic en Nueva escena .

Crear un nodo CharacterBody3D como nodo raíz

Nombra el CharacterBody3D como Player. El cuerpo del personaje se suma a las regiones y los cuerpos rígidos utilizados en el tutorial del juego 2D. Al igual que los Rigidbodies, pueden moverse y chocar con el entorno, pero en lugar de ser controlados por el motor de física, determinas su movimiento. Verá cómo usamos las capacidades únicas de los nodos al codificar la mecánica de saltar y apretar.

Referencia: para obtener más información sobre los diferentes tipos de nodos físicos, consulte  Introducción a la física .

Ahora crearemos una plataforma básica para el modelo 3D de nuestro personaje. Esto nos permitirá rotar el modelo más tarde a través del código mientras se reproduce la animación.

Agregue un nodo Node3DPlayer的 como un nodo secundario y asígnele un nombrePivot

Luego, en el panel del sistema de archivos, haga doble clic para expandir la carpeta y arrástrela y suéltela art/debajo del archivo .player.glb文件Pivot

Esto debería instanciar el modelo como Pivot.Puede cambiarle el nombre a Character.

Nota: Estos .glbarchivos contienen datos de escena 3D basados ​​en la especificación GLTF 2.0 de código abierto. Son una alternativa moderna y poderosa a los formatos propietarios como FBX, que también es compatible con Godot. Para generar estos archivos, diseñamos el modelo en Blender 3D y lo exportamos a GLTF.

Al igual que con los diversos nodos de física, necesitamos una forma de colisión para que nuestro personaje colisione con el entorno. Seleccione el Playernodo nuevamente y agregue un nodo secundario  CollisionShape3D . En la propiedad Shape del Inspector , agregue un nuevo SphereShape3D .

La estructura alámbrica de la esfera aparece debajo del personaje.

Esta será la forma que utilizará el motor de física para colisionar con el entorno, por lo que queremos que se ajuste mejor al modelo 3D. Alejar un poco arrastrando el punto naranja en la ventana gráfica. El radio de mi esfera es de unos 0.8metros.

Luego, mueva la forma hacia arriba para que su parte inferior se alinee aproximadamente con el plano de la cuadrícula.

Puede alternar la visibilidad del modelo haciendo clic en Pivoto en el icono del ojo junto al nodo.Character

Guardar la escena comoplayer.tscn

Con nuestros nodos listos, casi podemos comenzar a codificar. Pero primero, también necesitamos definir algunas acciones de entrada.

Crear una acción de entrada¶

Para mover el personaje, escucharemos la entrada del jugador, como presionar una tecla de flecha. En Godot, si bien podemos escribir todas las combinaciones de teclas en código, existe un poderoso sistema que te permite asignar etiquetas a grupos de teclas y botones. Esto simplifica nuestros scripts y los hace más legibles.

Este sistema es mapeo de entrada. Para acceder a su editor, ve al menú del proyecto y selecciona la configuración del proyecto .

En la parte superior, hay varias pestañas. Haga clic en el mapa de entrada【Mapa de entrada】 . Esta ventana te permite agregar nuevas acciones en la parte superior, son tus pestañas. En la parte inferior, puede vincular claves a estas acciones.

El proyecto Godot viene con algunas acciones predefinidas diseñadas para el diseño de la interfaz de usuario, podemos usarlas aquí. Pero estamos definiendo nuestro propio gamepad.

Nombraremos nuestras acciones move_leftmove_rightmove_forwardmove_back .jump

Para agregar una acción, escribe su nombre en la barra superior y presiona Enter.

Crea las siguientes cinco acciones:

Para vincular una tecla o botón a una acción, haga clic en el botón "+" a su derecha. Esto es move_leftpara Presione la tecla de flecha izquierda y haga clic en Aceptar .

También vincula la tecla A a move_left动作.

Ahora agreguemos soporte para el joystick izquierdo del gamepad. Haga clic en el botón "+" nuevamente, pero esta vez seleccione Selección manual -> Ejes Joypad .

Seleccione el eje X negativo del joystick izquierdo.

Deje otros valores por defecto y presione OK

Nota: si desea que su controlador tenga diferentes acciones de entrada, debe usar la opción de dispositivo en las opciones adicionales. El dispositivo 0 corresponde al primer gamepad conectado, el dispositivo 1 corresponde al segundo gamepad conectado, y así sucesivamente.

Haga lo mismo para otras operaciones de entrada. Por ejemplo, vincule la flecha derecha D y el eje positivo del joystick izquierdo a move_right. Después de vincular todas las teclas, su interfaz debería verse así.

Lo último que se establece es jumpla acción. Vincula la tecla Espacio y la tecla A del gamepad.

Su acción de entrada de salto debería verse así.

Esos son todos los movimientos que necesitamos en este juego. Puede usar este menú para etiquetar cualquier grupo de teclas y botones en su proyecto.

En la siguiente sección, escribiremos el código y probaremos el movimiento del jugador.

Mover jugador usando código¶

¡Hora de codificar! Usaremos la acción de entrada creada en la sección anterior para mover el personaje.

Haga clic con el botón derecho en el Playernodo y seleccione Adjuntar script para agregarle un nuevo script. En la ventana emergente, configure la plantilla para que esté vacía antes de presionar el botón Crear  .

Comencemos con las propiedades de la clase. Definiremos una velocidad de movimiento, una aceleración de caída que representa la gravedad y una velocidad que usaremos para mover al personaje.

extiende CharacterBody3D

# Qué tan rápido se mueve el jugador en metros por segundo. 
@export  var speed = 14
 # La aceleración hacia abajo en el aire, en metros por segundo al cuadrado. 
@exportar  var caída_aceleración = 75

var target_velocity = Vector3.ZERO

Estas son propiedades comunes de los objetos en movimiento. target_velocityes un vector 3D que combina velocidad y dirección. Aquí lo definimos como una propiedad porque queremos actualizar y reutilizar su valor en todos los marcos.

NOTA: Estos valores son muy diferentes a los códigos QR porque las distancias están en metros. En 2D, mil unidades (píxeles) pueden corresponder solo a la mitad del ancho de la pantalla, mientras que en 3D es un kilómetro.

Vamos a codificar el movimiento. Primero _physics_process()中calculamos el vector de dirección de entrada usando el objeto global Input.

func _physics_process(delta):
     # Creamos una variable local para almacenar la dirección de entrada. 
    var dirección = Vector3.ZERO

    # Verificamos cada entrada de movimiento y actualizamos la dirección en consecuencia. 
    si Input.is_action_pressed("move_right"):
        dirección.x += 1
    si Input.is_action_pressed("move_left"):
        dirección.x -= 1
    if Input.is_action_pressed("move_back"):
         # Observe cómo estamos trabajando con los ejes x y z del vector. 
        # En 3D, el plano XZ es el plano de tierra.
        dirección.z += 1
    si Input.is_action_pressed("move_forward"):
        dirección.z -= 1

Aquí, usaremos _physics_process() funciones virtuales para todos los cálculos. Al igual que en _process(), le permite actualizar nodos en cada cuadro, pero está diseñado específicamente para código relacionado con la física, como cinemática en movimiento o cuerpos rígidos.

参考:_process()Para obtener más información sobre la diferencia entre y _physics_process(), consulte Procesamiento inactivo y físico .

Comenzamos inicializando una variable directiona Vector3.ZERO.Luego, verificamos si el jugador ha presionado una o más entradas y actualizamos el vector y los componentes move_*en consecuencia . Estos corresponden a los ejes del plano de tierra.xz

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

Si el jugador presiona W y D al mismo tiempo, la longitud del vector será de aproximadamente 1.4. Pero si presiona una tecla, tendrá una longitud de 1. Queremos que el vector tenga la misma longitud, no que se mueva más rápido en diagonal. Para hacer esto, podemos llamar a sus normalize()métodos.

#función_física_proceso(delta): 
    #...

    si dirección != Vector3.ZERO:
        dirección = dirección.normalizado()
        $Pivot.look_at(posición + dirección, Vector3.ARRIBA)

Aquí, solo normalizamos el vector si la longitud de la dirección es mayor que cero, lo que significa que el jugador está presionando la tecla de flecha.

En este caso también obtenemos Pivotel nodo y llamamos a sus look_at()métodos. Este método obtiene una posición en el espacio para ver en coordenadas globales y hacia arriba. En este caso podemos usar Vector3.UPconstantes.

Nota: Las coordenadas locales de un nodo, por ejemplo position, son relativas a su nodo principal. Coordenadas globales, por ejemplo global_position, en relación con el eje principal del mundo, como puede verlo en la ventana gráfica.

En 3D, el atributo que contiene la posición del nodo es position. Al sumarle direction, obtenemos una Playerdistancia de 1 metro para mirar.

Luego, actualizamos la velocidad. Tenemos que calcular la velocidad de avance y la velocidad de caída por separado. [Asegúrese de prestar atención a la sangría] para que estas líneas estén _physics_process()dentro de la función, pero fuera de la condición que acabamos de escribir arriba.

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

    # Velocidad de tierra
    target_velocity.x = dirección.x * velocidad
    target_velocity.z = dirección.z * velocidad

    # Velocidad vertical 
    si  no está_en_el_piso(): # Si está en el aire, cae hacia el piso. Literalmente gravedad
        target_velocity.y = target_velocity.y - (fall_acceleration * delta)

    # Mover el personaje
    velocidad = velocidad_objetivo
    mover_y_deslizar()

CharacterBody3D.is_on_floor()La función devuelve verdadero si el cuerpo chocó contra el suelo durante este fotograma . Es por eso que solo Playerle aplicamos gravedad cuando está en el aire.

Para la velocidad vertical, restamos la aceleración de descenso por el tiempo delta por cuadro. Esta línea de código hará que nuestro personaje caiga en cada fotograma, siempre que no aterrice o choque con el suelo.

El motor de física solo puede detectar interacciones con paredes, pisos u otros objetos durante un cuadro determinado si se producen movimientos y colisiones. Usaremos esta propiedad más adelante para escribir código de salto.

En la última línea, llamamos a un poderoso método de CharacterBody3D.move_and_slide(),这la CharacterBody3Dclase que te permite mover tu personaje sin problemas. Si choca contra una pared en medio de un movimiento, el motor intenta suavizarla por ti. Utiliza el valor de velocidad inherente a CharacterBody3D

Ese es todo el código necesario para mover al personaje en el suelo.

Aquí está el Player.gdcódigo completo para referencia.

extiende CharacterBody3D

# Qué tan rápido se mueve el jugador en metros por segundo. 
@export  var speed = 14
 # La aceleración hacia abajo en el aire, en metros por segundo al cuadrado. 
@exportar  var caída_aceleración = 75

var target_velocity = Vector3.ZERO


func _physics_process(delta):
     var dirección = Vector3.ZERO

    si Input.is_action_pressed("move_right"):
        dirección.x += 1
    si Input.is_action_pressed("move_left"):
        dirección.x -= 1
    si Input.is_action_pressed("move_back"):
        dirección.z += 1
    si Input.is_action_pressed("move_forward"):
        dirección.z -= 1

    si dirección != Vector3.ZERO:
        dirección = dirección.normalizado()
        $Pivot.look_at(posición + dirección, Vector3.ARRIBA)

    # Velocidad de tierra
    target_velocity.x = dirección.x * velocidad
    target_velocity.z = dirección.z * velocidad

    # Velocidad vertical 
    si  no está_en_el_piso(): # Si está en el aire, cae hacia el piso. Literalmente gravedad
        target_velocity.y = target_velocity.y - (fall_acceleration * delta)

    # Mover el personaje
    velocidad = velocidad_objetivo
    mover_y_deslizar()

Probando las acciones de nuestro jugador¶

Pondremos a nuestro Player en Mainescena para probarlo. Para hacer esto, necesitamos crear una instancia del jugador, luego agregar la cámara. A diferencia de 2D, en 3D, si su ventana gráfica no tiene una cámara apuntando a algo, no verá nada.

Guarde Playerla escena y abra Mainla escena. Puede hacerlo haciendo clic en la pestaña Principal en la parte superior del editor .

Si cerró la escena antes, vaya al sistema de archivos y haga doble clic  main.tscnpara volver a abrirla.

Para crear una instancia Player, haga clic con el botón derecho en Mainel nodo y seleccione Escena secundaria de instancia .

En la ventana emergente, haga doble clic en player.tscn. El carácter debe aparecer en el centro de la ventana gráfica.

Añadir cámara¶

A continuación, agreguemos la cámara. Al igual que hicimos con Player 's Pivot , crearemos un equipo básico. Haga clic con el botón derecho en el Mainnodo nuevamente y seleccione  Agregar nodo secundario . Cree un nuevo Marker3D y asígnele el nombre CameraPivot. Seleccione y agréguele CameraPivotun nodo secundario Camera3D . Su árbol de escena debería verse así.

Cuando haya seleccionado una cámara , observe la casilla de verificación de vista previa que aparece en la esquina superior izquierda. Puede hacer clic en él para obtener una vista previa de la proyección de la cámara en el juego.

Usaremos Pivot para rotar la cámara como si estuviera en una grúa. Primero dividamos la vista 3D para que podamos navegar libremente por la escena y ver lo que ve la cámara.

En la barra de herramientas justo encima de las ventanas gráficas, haga clic en Ver y, a continuación, haga clic en 2 Ventanas gráficas . También puede presionar Ctrl + 2 (Cmd + 2 en macOS).

En la vista inferior, seleccione su Camera3D y abra la vista previa de la cámara haciendo clic en la casilla de verificación.

En la vista superior, mueve la 19unidad de cámara hacia arriba a lo largo del eje Z (eje azul).

Aquí es donde ocurre la magia. Seleccione CameraPivot y gire los grados alrededor del eje X -45(use el círculo rojo). Verás que la cámara se mueve como si estuviera colgada de una grúa.

Puede ejecutar la escena presionando F6 y presionando las teclas de flecha para mover el personaje.

Podemos ver un espacio vacío alrededor del personaje debido a la proyección en perspectiva. En este juego, utilizaremos una proyección ortográfica para encuadrar mejor el área del juego y facilitar al jugador la lectura de las distancias.

Seleccione Cámara nuevamente y en el Inspector , establezca Proyección en  Ortogonal y Tamaño en 19. El personaje ahora debería verse más plano y el suelo debería llenar el fondo.

Nota: Al usar una cámara ortográfica en Godot 4, la calidad de las sombras direccionales depende del valor Lejos de la cámara . Cuanto mayor sea el valor Lejos , más lejos podrá ver la cámara. Sin embargo, los valores lejanos más altos también reducen la calidad de las sombras, ya que la representación de las sombras debe cubrir una distancia mayor.

Si las sombras direccionales se ven demasiado borrosas después de cambiar a una cámara ortográfica, reduzca la propiedad  Lejos100 de la cámara a un valor más bajo, por ejemplo , no reduzca demasiado esta propiedad o los objetos distantes comenzarán a desaparecer .

¡Prueba tu escena y deberías poder moverte en las 8 direcciones sin fallar en el piso!

Finalmente, tenemos tanto el movimiento como la visión del jugador. A continuación, nos ocuparemos de los monstruos.

Diseñar escena MOB¶

En esta parte, codificarás al monstruo, al que llamaremos mafia. En la próxima lección, los generaremos aleatoriamente alrededor del área jugable.

Diseñemos el monstruo mismo en la nueva escena. La estructura del nodo será player.tscnsimilar a la escena.

Vuelva a crear una escena con el nodo CharacterBody3D como raíz. nombrarlo  Mob_ Agregue un nodo secundario Node3D , denominado como Pivot. y arrastre y suelte el archivo mob.glbdesde el sistema de archivos para Pivotagregar el modelo 3D del monstruo a la escena.

mobPuede cambiar el nombre del nodo recién creado a Character.

Necesitamos una forma de colisión para que nuestro cuerpo funcione. Haga clic con el botón derecho Moben el nodo raíz de la escena y haga clic en Agregar nodo secundario .

Agregue CollisionShape3D .

En el Inspector , asigne un BoxShape3D a la propiedad Shape .

Deberíamos cambiar su tamaño para que se ajuste mejor al modelo 3D. Puede hacerlo de forma interactiva haciendo clic y arrastrando el punto naranja.

La caja debe tocar el suelo y ser un poco más delgada que el modelo. La forma en que funciona el motor de física es que si la esfera del jugador toca una esquina de la caja, habrá una colisión. Si la caja es un poco más grande que el modelo 3D, es posible que mueras lejos del monstruo y el juego le dará al jugador una sensación de injusticia.

Tenga en cuenta que mi caja es más alta que el monstruo. No hay problema en este juego porque estamos mirando la escena desde arriba y usando una perspectiva fija. La forma de colisión no tiene que coincidir exactamente con el modelo. La sensación del juego debe dictar su forma y tamaño a medida que lo prueba.

Eliminar monstruos fuera de pantalla¶

Generaremos monstruos periódicamente a lo largo de los niveles del juego. Si no tenemos cuidado, su número podría crecer hasta el infinito, y no queremos eso. Cada instancia de criatura tiene un costo de memoria y procesamiento que no queremos pagar cuando la criatura está fuera de la pantalla.

Una vez que un monstruo está fuera de la pantalla, ya no lo necesitamos, por lo que debemos eliminarlo. Godot tiene un nodo VisibleOnScreenNotifier3D que detecta cuando un objeto sale de la pantalla, el cual usaremos para destruir nuestra mafia.

Nota: cuando constantemente crea instancias de un objeto, puede usar una técnica para evitar el costo de crear y destruir instancias todo el tiempo, llamada agrupación. Consiste en crear previamente una matriz de objetos y reutilizarlos una y otra vez.

No necesita preocuparse por esto cuando usa GDScript. La principal razón para usar pools es evitar la congelación en lenguajes de recolección de basura como C# o Lua. GDScript usa una técnica diferente para administrar la memoria, a saber, el conteo de referencias, y no tiene esa advertencia. Puede obtener más información al respecto aquí: Gestión de memoria .

Seleccione este Mobnodo y agregue un nodo secundario VisibleOnScreenNotifier3D . Aparece otro cuadro, esta vez rosa. El nodo emite una señal cuando la caja está completamente fuera de la pantalla.

Cambie su tamaño usando los puntos naranjas hasta que cubra todo el modelo 3D.

Codificar el movimiento de MOB¶

Implementemos el movimiento del monstruo. Haremos esto en dos pasos. Primero, escribiremos Mobun script en , definiendo una función que inicialice el monstruo. Luego, codificaremos main.tscnel mecanismo de generación aleatoria en la escena y llamaremos a la función desde allí.

Añada el script a Mob.

Aquí está el código móvil para empezar. Definimos dos propiedades min_speed y max_speedpara definir un rango de velocidad aleatorio, que usaremos más adelante CharacterBody3D.velocity.

extiende CharacterBody3D

# Velocidad mínima de la mafia en metros por segundo. 
@export  var min_speed = 10
 # Velocidad máxima de la mafia en metros por segundo. 
@exportar  var max_speed = 18


función_física_proceso (_delta):
    mover_y_deslizar()

Al igual que el jugador, movemos CharacterBody3D.move_and_slide()la mafia en cada fotograma llamando a una función. Esta vez, no actualizamos cada cuadro velocity, queremos que el monstruo se mueva a una velocidad constante y salga de la pantalla, incluso si choca con un obstáculo.

Necesitamos definir otra función para hacer el cálculo CharacterBody3D.velocityEsta función girará el monstruo hacia el jugador y aleatorizará su ángulo de movimiento y velocidad.

Esta función tomará la posición de generación de la mafia start_positionplayer_positioncomo su parámetro.

Colocamos el monstruo en start_positiony usamos métodos look_at_from_position()para girarlo hacia el jugador y aleatorizar el ángulo girándolo aleatoriamente alrededor del eje Y en un ángulo. El siguiente código genera un valor aleatorio entre randf_range()radianes -PI/4y radianes .PI/4

# Esta función se llamará desde la escena principal. 
func initialize(start_position, player_position):
     # Posicionamos al mob colocándolo en start_position 
    # y lo rotamos hacia player_position, para que mire al jugador.
    mirar_desde_la_posición(posición_de_inicio, posición_del_jugador, Vector3.ARRIBA)
    # Gira este mob aleatoriamente dentro de un rango de -90 y +90 grados, 
    # para que no se mueva directamente hacia el jugador.
    rotar_y(randf_rango(-PI / 4, PI / 4))

Obtuvimos una posición aleatoria, ahora necesitamos una random_speedfunción randi_range().será útil ya que proporciona un valor int aleatorio que usaremos min_speed与max_speedrandom_speedSolo un número entero que usamos para multiplicar nuestro CharacterBody3D.velocity.Después de aplicar random_speed, hacemos el vector de velocidad Vector3 CharacterBody3D.velocity向que gira el jugador.

func initialize(start_position, player_position):
     # ...

    # Calculamos una velocidad aleatoria (entero) 
    var random_speed = randi_range(min_speed, max_speed)
     # Calculamos una velocidad de avance que representa la velocidad.
    velocidad = Vector3.ADELANTE * velocidad_aleatoria
    # Luego rotamos el vector de velocidad basado en la rotación Y de la mafia 
    # para movernos en la dirección en la que mira la mafia.
    velocidad = velocidad.rotada(Vector3.ARRIBA, rotación.y)

Saliendo de la pantalla¶

Todavía tenemos que destruir las turbas cuando salen de la pantalla. Para ello, conectamos la señal del nodo VisibleOnScreenNotifier3D al .screen_exitedMob

Haga clic en la pestaña 3D en la parte superior del editor para volver a la vista 3D. También puede presionar Ctrl + F2 (Alt + 2 en macOS).

Seleccione el nodo VisibleOnScreenNotifier3D , luego navegue hasta el panel de nodos en el lado derecho de la interfaz. Haga doble clic en screen_exited()la señal.

Conecte la señal aMob

Esto lo traerá de regreso al editor de scripts y agregará una nueva función para Ud  _on_visible_on_screen_notifier_3d_screen_exited(). Llame al método dentro de esa función queue_free() . Esta función destruye la instancia a la que se llamó.

función _on_visible_on_screen_notifier_3d_screen_exited():
    cola_libre()

¡Nuestro monstruo está listo para entrar en el juego! En la siguiente sección, generarás monstruos en el nivel del juego.

Aquí está el Mob.gdguión completo para referencia.

extiende CharacterBody3D

# Velocidad mínima de la mafia en metros por segundo. 
@export  var min_speed = 10
 # Velocidad máxima de la mafia en metros por segundo. 
@exportar  var max_speed = 18

función_física_proceso (_delta):
    mover_y_deslizar()

# Esta función se llamará desde la escena principal. 
func initialize(start_position, player_position):
     # Posicionamos al mob colocándolo en start_position 
    # y lo rotamos hacia player_position, para que mire al jugador.
    mirar_desde_la_posición(posición_de_inicio, posición_del_jugador, Vector3.ARRIBA)
    # Gira este mob aleatoriamente dentro de un rango de -90 y +90 grados, 
    # para que no se mueva directamente hacia el jugador.
    rotar_y(randf_rango(-PI / 4, PI / 4))

    # Calculamos una velocidad aleatoria (entero) 
    var random_speed = randi_range(min_speed, max_speed)
     # Calculamos una velocidad de avance que representa la velocidad.
    velocidad = Vector3.ADELANTE * velocidad_aleatoria
    # Luego rotamos el vector de velocidad basado en la rotación Y de la mafia 
    # para movernos en la dirección en la que mira la mafia.
    velocidad = velocidad.rotada(Vector3.ARRIBA, rotación.y)

función _on_visible_on_screen_notifier_3d_screen_exited():
    cola_libre()

Generar monstruos¶

En esta parte, generaremos monstruos aleatoriamente a lo largo de un camino. Al final, verás monstruos vagando por el tablero de juego.

main.tscnHaga doble clic para abrir Mainla escena en el sistema de archivos .

Antes de dibujar el camino, queremos cambiar la resolución del juego. Nuestro juego tiene un tamaño de ventana predeterminado 1152x648. Vamos a configurarlo como 720x540,una bonita caja pequeña.

Vaya a Proyecto -> Configuración del proyecto .

En el menú de la izquierda, navegue hacia abajo hasta Pantalla -> Ventana . A la derecha,  establezca el ancho en 720y la altura en 540.

Crear ruta de compilación¶

Tal como lo hizo en el tutorial del juego 2D, diseñará una ruta y usará un  nodo PathFollow3D para muestrear posiciones aleatorias en ella.

Pero en 3D, dibujar caminos es un poco más complicado. Queremos que se ajuste a la vista del juego para que los monstruos aparezcan fuera de la pantalla. Pero si dibujamos un camino, no lo veremos desde la vista previa de la cámara.

Para encontrar los límites de la vista, podemos usar alguna cuadrícula de marcadores de posición. Su ventana gráfica aún debe estar dividida en dos, con la vista previa de la cámara en la parte inferior. Si este no es el caso, presione Ctrl + 2 (Cmd + 2 en macOS) para dividir la vista en dos. Seleccione el nodo Camera3D y haga clic en la casilla de verificación de vista previa en la ventana gráfica inferior .

Agregar un cilindro de marcador de posición¶

Agreguemos la cuadrícula de marcador de posición. Agregue un nuevo Node3D como  Mainhijo del nodo y asígnele el nombre Cylinders. Usaremos esto para agrupar los cilindros. Seleccione Cylindersy agregue el nodo secundario MeshInstance3D

En el Inspector , asigne CylinderMesh a la propiedad Mesh .

Use el menú en la esquina superior izquierda de la ventana gráfica para establecer la ventana gráfica superior en una vista ortográfica superior. Alternativamente, puede presionar la tecla 7 en su teclado.

Las cuadrículas pueden distraer. Puede alternar esto yendo al menú Ver en la barra de herramientas y haciendo clic en Ver cuadrícula .

Ahora desea mover el cilindro a lo largo del plano del suelo, vea la vista previa de la cámara en la ventana de visualización inferior. Recomiendo usar el ajuste de cuadrícula para hacer esto. Puede alternarlo haciendo clic en el ícono del imán en la barra de herramientas o presionando Y.

Mueva el cilindro para que quede fuera de la vista de la cámara superior izquierda.

Crearemos copias de las mallas y las colocaremos alrededor del área de juego. Presione Ctrl + D (Cmd + D en macOS) para duplicar el nodo. También puede hacer clic con el botón derecho en un nodo en el panel de escena y seleccionar Duplicar . Mueva la copia hacia abajo en el eje Z azul hasta que esté justo fuera de la vista previa de la cámara.

Seleccione ambos cilindros presionando Mayús y haciendo clic en los cilindros no seleccionados y duplicándolos.

Muévalos a la derecha arrastrando el eje X rojo.

El blanco es un poco feo, ¿no? Hagamos que se destaquen dándoles un nuevo material.

En 3D, un material define las propiedades visuales de una superficie, como su color, la forma en que refleja la luz, etc. Podemos usarlos para cambiar el color de la cuadrícula.

Podemos actualizar los cuatro cilindros a la vez. Selecciona todas las instancias de malla en el panel de escena. Puede hacer esto haciendo clic en el primero, luego presionando Mayús y haciendo clic en el último.

En el Inspector , expanda la sección Material y asigne StandardMaterial3D a la ranura  0 .

Haga clic en el icono de la esfera para abrir el recurso material. Puede obtener una vista previa del material y una larga lista de secciones llenas de propiedades. Puedes usarlos para crear todo tipo de superficies, desde metal hasta roca o agua.

Expanda la sección Albedo .

Establezca el color en algo que contraste con el fondo, como un naranja brillante.

Ahora podemos usar el cilindro como guía. Haga clic en la flecha gris junto a ellos para colapsarlos en el panel de escena. En el futuro, también puede alternar la visibilidad de los cilindros haciendo clic en el ícono del ojo al lado de ellos.

Agregue el nodo secundario Path3D al Mainnodo. En la barra de herramientas, aparecen cuatro iconos. Haga clic en la herramienta Añadir punto , el icono con el signo "+" verde.

Puede pasar el cursor sobre cualquier icono para ver una información sobre herramientas que describe esa herramienta.

Haga clic en el centro de cada cilindro para crear un punto. Luego, haga clic en el icono Cerrar curva en la barra de herramientas para cerrar la ruta. Si algún punto está un poco fuera de lugar, puede hacer clic y arrastrarlo para cambiar su posición.

Tu camino debería verse así.

Para muestrear posiciones aleatorias en él, necesitamos un nodo PathFollow3D . Agregue un  PathFollow3D como Path3D. Cambie el nombre de estos dos nodos a SpawnPathy ,  respectivamente SpawnLocation. Es más descriptivo de lo que vamos a hacer con ellos.

Con esto, podemos escribir el código para el mecanismo de generación.

Monstruos generados aleatoriamente¶

Haga clic con el botón derecho en el Mainnodo y adjunte un nuevo script.

Comenzamos exportando una variable al Inspector para que podamos mob.tscn asignarla o cualquier otro monstruo.

extiende el nodo

@exportar  var mob_scene: Escena empaquetada

Queremos que las turbas se reproduzcan a intervalos regulares. Para hacer esto, necesitamos volver a la escena y agregar un temporizador. Sin embargo, antes de eso, debemos  mob.tscnasignar el archivo a mob_scenela propiedad anterior (¡de lo contrario, está vacío!)

Regrese a la pantalla 3D y seleccione Mainel nodo. Arrastre desde la base del sistema de archivos mob.tscnhasta la ranura Mob Scene en el Inspector .

Agregue un nuevo nodo Timer como elemento secundario de Main. nombrarlo MobTimer_

En el Inspector , configura su Tiempo de espera en 0.5segundos y activa  Autostart para que se inicie automáticamente cuando ejecutemos el juego.

El temporizador emite una señal timeoutcada vez que llega al final del tiempo de espera . De forma predeterminada, se reinician automáticamente y se señalizan en un bucle. Podemos conectarnos a esta señal desde el nodo maestro para generar monstruos 0.5cada segundo .

Con MobTimer aún seleccionado, vaya al puerto de nodos a la derecha y haga doble clic en la señal timeout.

Conéctelo al nodo maestro .

Esto lo llevará de regreso al script con una nueva  _on_mob_timer_timeout()función vacía.

Escribamos la lógica de generación de MOB. queremos:

  1. Cree una instancia de la escena MOB.

  2. Muestra en posiciones aleatorias a lo largo de la ruta generada.

  3. Obtener la posición del jugador.

  4. Llama al initialize()método de la criatura, pasándole la posición aleatoria y la posición del jugador.

  5. Agregue la criatura como hijo del nodo principal .

func _on_mob_timer_timeout():
     # Crea una nueva instancia de la escena Mob. 
    var mob = mob_scene.instantiate()

    # Elija una ubicación aleatoria en SpawnPath. 
    # Almacenamos la referencia al nodo SpawnLocation. 
    var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
     # Y dale un desplazamiento aleatorio.
    mob_spawn_ubicación.progress_ratio = randf()

    var player_position = $Jugador.posición
    mob.initialize(mob_spawn_ubicación.posición, posición_jugador)

    # Engendra la mafia agregándola a la escena principal.
    add_child(mafia)

Arriba, randf()se genera un valor aleatorio entre 0 y 1, que es lo que espera el nodo PathFollow : 0 es el inicio de la ruta y 1 es el final de la ruta. La ruta que establecemos se envuelve alrededor de la ventana de visualización de la cámara, por lo que cualquier valor aleatorio entre 0 y 1 progress_ratioes una posición aleatoria en el borde de la ventana de visualización.

main.gdAquí está el guión completo hasta ahora, como referencia.

extiende el nodo

@exportar  var mob_scene: Escena empaquetada


func _on_mob_timer_timeout():
     # Crea una nueva instancia de la escena Mob. 
    var mob = mob_scene.instantiate()

    # Elija una ubicación aleatoria en SpawnPath. 
    # Almacenamos la referencia al nodo SpawnLocation. 
    var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
     # Y dale un desplazamiento aleatorio.
    mob_spawn_ubicación.progress_ratio = randf()

    var player_position = $Jugador.posición
    mob.initialize(mob_spawn_ubicación.posición, posición_jugador)

    # Engendra la mafia agregándola a la escena principal.
    add_child(mafia)

Puede probar la escena presionando F6. Deberías ver al monstruo aparecer y moverse en línea recta.

Ahora chocan y se deslizan uno contra el otro cuando sus caminos se cruzan. Abordaremos este tema en la siguiente sección.

Monstruos que saltan y aprietan¶

En esta parte, agregaremos la capacidad de saltar y apretar al monstruo. En la próxima lección, haremos que el jugador muera cuando el monstruo toque el suelo.

Primero, tenemos que cambiar algunas configuraciones relacionadas con la interacción física. Entra en el mundo de la capa física.

Controlando las interacciones físicas¶

Las entidades físicas pueden acceder a dos propiedades complementarias: las capas [capas] y la máscara [máscara]. Una capa define en qué capa física se encuentra un objeto.

Las máscaras controlan las capas que el cuerpo escuchará y detectará. Esto afecta la detección de colisiones. Cuando desea que dos objetos interactúen, necesita que al menos un objeto tenga una máscara correspondiente al otro.

Si esto te confunde, no te preocupes, veremos tres ejemplos en un momento.

El punto importante es que puede usar capas y máscaras para filtrar interacciones físicas, controlar el rendimiento y eliminar la necesidad de condiciones adicionales en su código.

De forma predeterminada, todas las capas y máscaras de regiones y volúmenes físicos se establecen en 1. Eso significa que todos chocan entre sí.

Las capas físicas están representadas por números, pero podemos darles nombres para realizar un seguimiento de qué es qué.

Establecer nombre de capa¶

Démosle un nombre a la capa física. Vaya a Proyecto -> Configuración del proyecto .

En el menú de la izquierda, navegue hacia abajo hasta Nombres de capa -> Física 3D . Puede ver una lista de capas a la derecha, con un campo al lado de cada capa. Puede establecer sus nombres allí. Nombra las tres primeras capas jugador , enemigo y mundo respectivamente .

Ahora, podemos asignarlos a nuestros nodos físicos.

Asignación de capas y máscaras¶

En la escena principal , seleccione Groundel nodo. En el Inspector , expanda  la sección Colisión . Allí, puede tratar las capas y máscaras de sus nodos como una cuadrícula de botones.

El suelo es parte del mundo, por eso queremos que sea parte de la tercera capa. Haga clic en el botón iluminado para cerrar la primera capa y abrir la tercera capa. Luego, cierre la máscara haciendo clic en ella .

Como se mencionó anteriormente, la propiedad Mask permite que el nodo escuche las interacciones con otros objetos físicos, pero no la necesitamos para las colisiones. GroundNo necesita escuchar nada, solo está ahí para evitar que caigan mobs.

Tenga en cuenta que puede hacer clic en el botón "..." a la derecha de la propiedad para ver una lista de casillas de verificación con nombre.

El siguiente es Playery Mob. Ábralo haciendo doble clic en un archivo en el sistema de archivos player.tscn.

Selecciona el nodo Player y establece su Colisión -> Máscara en "enemigos" y "mundo". Puede dejar la propiedad Capa predeterminada sin cambios, ya que la primera capa es la capa "jugador".

Luego, haga doble clic para abrir la escena Mobmob.tscn y seleccione  Mobel nodo.

Establezca su Colisión -> Capa en "enemigos" y desactive su Colisión -> Máscara para que la máscara esté vacía.

Estos ajustes significan que los monstruos se moverán unos alrededor de otros. Si quieres que los monstruos choquen y se deslicen entre sí, activa la máscara de " enemigo".

Nota: las criaturas no necesitan enmascarar la capa "mundo", ya que solo se mueven en el plano XZ. No ponemos ninguna gravedad sobre ellos por diseño.

Saltar¶ _

El mecanismo de salto en sí requiere solo dos líneas de código. Abra el  script del reproductor. Necesitamos un valor para controlar la fuerza del salto y actualizar  _physics_process()para codificar el salto.

Después de la línea definida fall_acceleration, en la parte superior del script, agregue el archivo jump_impulse.

#... 
# Impulso vertical aplicado al personaje al saltar en metros por segundo. 
@exportar  var jump_impulse = 20

Dentro _physics_process(), agregue el siguiente código antes del bloque de código move_and_slide().

función_física_proceso (delta):
     #...

    # Saltando. 
    if is_on_floor() and Input.is_action_just_pressed("jump"):
        target_velocity.y = salto_impulso

    #...

¡Eso es todo lo que necesitas para saltar!

Este is_on_floor()método es una herramienta de esta clase CharacterBody3D. trueVuelve si el cuerpo choca con el suelo en este cuadro. Por eso aplicamos la gravedad al jugador : chocamos contra el suelo en lugar de flotar sobre él como un monstruo.

Si el personaje está en el suelo y el jugador presiona "saltar", inmediatamente le damos mucha velocidad vertical. En los juegos, realmente desea que los controles respondan y brinden un impulso de velocidad instantáneo como este, que no es práctico, pero se siente genial.

Tenga en cuenta que el eje Y es positivo hacia arriba. Esto es diferente de 2D, donde el eje Y es positivo hacia abajo.

Monstruos de calabaza¶

A continuación, agreguemos la mecánica de squash. Queremos que el personaje rebote sobre los monstruos y los mate al mismo tiempo.

Necesitamos detectar colisiones con monstruos y distinguirlas de las colisiones con el suelo. Para esto, podemos usar la función de etiquetado de grupos de Godot .

Abra la escena nuevamente mob.tscny seleccione el nodo Mob . Vaya al panel de nodos a la derecha para ver una lista de señales . El panel de nodos tiene dos pestañas: Señales , que ya usa, y Grupos  , que le permiten asignar etiquetas a los nodos .

Haga clic en él para revelar un campo donde puede escribir el nombre de la etiqueta. Ingrese "mafia" en el campo y haga clic en el botón Agregar .

Aparece un icono en el panel de escena que indica que el nodo pertenece al menos a un grupo.

Ahora podemos usar grupos en código para diferenciar entre colisiones con monstruos y colisiones con el suelo.

Mecanismo de compresión de codificación¶

Vuelva a la secuencia de comandos del reproductor para escribir el código de compresión y rebote.

En la parte superior del guión, necesitamos otra propiedad bounce_impulse: al aplastar a un enemigo, no necesariamente queremos que el personaje vuele tan alto como lo hace cuando salta.

# Impulso vertical aplicado al personaje al rebotar sobre una mafia en 
# metros por segundo. 
@exportación  var rebote_impulso = 16

Luego, después del bloque de código de salto_physics_process() que agregamos arriba , agregue el siguiente bucle. Cuando se usa  , Godot a veces mueve el cuerpo varias veces seguidas para suavizar el movimiento del personaje. Así que tenemos que pasar por todas las colisiones posibles.move_and_slide()

En cada iteración del ciclo, verificamos si hemos aterrizado en una mafia. Si es así, lo matamos y rebotamos.

Con este código, el ciclo no se ejecuta si no hay colisión en un cuadro dado.

función_física_proceso (delta):
    #...

   # Iterar a través de todas las colisiones que ocurrieron en este marco 
   para index in range(get_slide_collision_count()):
        # Obtenemos una de las colisiones con el jugador 
       var colision = get_slide_collision(index)

       # Si la colisión es con el suelo 
       if (collision.get_collider() == null):
            continuar

       # Si el colisionador es con un mob 
       if colision.get_collider().is_in_group("mob"):
            var mob = colision.get_collider()
            # comprobamos que lo estamos golpeando desde arriba. 
           if Vector3.UP.dot(collision.get_normal()) > 0.1:
                # Si es así, lo aplastamos y rebotamos.
               mafia.squash()
               target_velocity.y = rebote_impulso

Esas son muchas características nuevas. Aquí hay más información sobre ellos.

Las funciones get_slide_collision_count()y get_slide_collision()ambas provienen de la clase CharacterBody3D y son compatibles con  move_and_slide()相关.

get_slide_collision()Devuelve un  objeto KinematicCollision3D que contiene información sobre dónde y cómo ocurrió la colisión. Por ejemplo, usamos sus get_colliderpropiedades is_in_group()para verificar si chocamos con una "mafia" llamando: collision.get_collider().is_in_group("mob")

Nota: Este is_in_group()método está disponible en todos los Nodos .

Para verificar si aterrizamos en un monstruo, usamos el producto escalar vectorial: Vector3.UP.dot(collision.get_normal()) > 0.1. Una normal de colisión es un vector 3D perpendicular al plano en el que se produjo la colisión. El producto punto nos permite compararlo con la dirección hacia arriba.

Para un producto escalar, el ángulo entre los dos vectores es menor de 90 grados cuando el resultado es mayor que 0. Un valor por encima 0.1nos dice que estamos más o menos por encima del monstruo.

Estamos llamando a una función indefinida mob.squash(), por lo que debemos agregarla a la clase Mob.

Abra el script haciendo doble clic en él en el panel del sistema de archivos Mob.gd. En la parte superior del script, vamos a definir una squashednueva señal llamada . En la parte inferior, puede agregar la función de squash, donde podemos enviar una señal y destruir a la mafia.

# Emitido cuando el jugador salta sobre la mafia. 
señal aplastada

#...


func squash():
    aplastado.emit()
    cola_libre()

Usaremos esta señal para agregar puntos a la puntuación en la próxima lección.

Con él, deberías poder matar monstruos saltando sobre ellos. Puedes probar el juego presionando F5 y configurarlo main.tscncomo la escena principal del proyecto.

Sin embargo, el jugador aún no muere. Nos ocuparemos de eso en la siguiente sección.

Matar jugadores¶

Podemos matar enemigos saltando sobre ellos, pero el jugador aún no puede morir. Vamos a sacar esto del camino.

Queremos detectar ser golpeado por un enemigo de manera diferente a aplastarlo. Queremos que el jugador muera mientras se mueve por el suelo, pero no en el aire. Podemos usar matemáticas vectoriales para distinguir entre estos dos tipos de colisiones. Sin embargo, usaremos el nodo Area3D , que funciona con cuadros de colisión.

Hitbox con nodo Area¶

Vuelva a player.tscnla escena y agregue un nuevo nodo secundario Area3D . Nómbrelo  MobDetector Agregue un nodo CollisionShape3D como su hijo.

En el Inspector , dale una forma de Cilindro.

Este es un truco que puedes usar para hacer que las colisiones solo ocurran cuando el jugador está en el suelo o cerca de él. Puede bajar la altura del cilindro y moverlo hacia la parte superior del personaje. De esta forma, cuando el jugador salte, la forma será tan alta que los enemigos no podrán chocar con ella.

También quieres que el cilindro sea más ancho que la esfera. De esta manera, el jugador es golpeado antes de chocar y ser empujado hacia la parte superior del hitbox del monstruo.

Cuanto más ancho sea el cilindro, más fácil será matar al jugador.

A continuación , seleccione el MobDetectornodo de nuevo y desactive su propiedad Supervisable en el Inspector . Esto hace que el área sea indetectable para otros nodos físicos. La propiedad complementaria de Monitoreo le permite detectar colisiones. Luego, elimine Colisión -> Capa y configure la máscara en la capa "enemigos".

Emiten una señal cuando una zona detecta una colisión. Conectaremos uno al Playernodo. Seleccione MobDetectory vaya a la pestaña  Nodos del Inspector , haga doble clic en la señal y conéctela abody_enteredPlayer

MobDetector se activará cuando entre un nodo CharacterBody3DRigidBody3D . Dado que solo protege la capa física del "enemigo", solo detectará nodos. ​​​​​​​​body_enteredMob

Por el lado del código, vamos a hacer dos cosas: emitir una señal, que usaremos más tarde para terminar el juego y destruir al jugador. Podemos envolver estas operaciones en una die()función, lo que nos ayuda a poner etiquetas descriptivas en nuestro código.

# Emitido cuando el jugador fue golpeado por una turba. 
# Pon esto en la parte superior del script. 
golpe de señal


# Y esta función en la parte inferior. 
función morir():
    hit.emit()
    cola_libre()


func _on_mob_detector_body_entered(cuerpo):
    morir()

Vuelve a intentar el juego presionando F5. Si todo está configurado correctamente, el personaje debería morir cuando un enemigo golpea el colisionador. Tenga en cuenta que no Player, la siguiente línea

var player_position = $Jugador.posición

da un error porque no hay $Player!

También tenga en cuenta que los enemigos que chocan con el jugador y mueren dependen del  tamaño y la posición de Playerlas Mobformas del colisionador. Es posible que deba moverlos y cambiar su tamaño para obtener una sensación de juego compacta.

Fin del juego¶

Señales que podemos usar Playerpara hitterminar el juego. Todo lo que tenemos que hacer es conectarlo a Mainun nodo y dejar MobTimerde reaccionar.

Abra main.tscn, seleccione Playerel nodo y, en el  panel de nodos, conecte su hitseñal al Mainnodo.

Obtenga el temporizador en la función y deténgalo _on_player_hit().

func _on_player_hit():
    $MobTimer.stop()

Si prueba el juego ahora, las turbas dejarán de aparecer después de que muera y el resto desaparecerá de la pantalla.

Puedes darte crédito por hacer un prototipo de juego 3D completo, incluso si es un poco tosco.

A partir de ahí, agregaremos una puntuación, la opción de volver a intentar el juego y verás cómo se pueden usar animaciones minimalistas para que el juego se sienta más vivo.

Puntos de control del código¶

MainA continuación se muestra el script completo para el nodo , Moby Playercomo referencia. Puede usarlos para comparar y verificar su código.

desde main.gdel principio

extiende el nodo

@exportar  var mob_scene: Escena empaquetada


func _on_mob_timer_timeout():
     # Crea una nueva instancia de la escena Mob. 
    var mob = mob_scene.instantiate()

    # Elija una ubicación aleatoria en SpawnPath. 
    # Almacenamos la referencia al nodo SpawnLocation. 
    var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
     # Y dale un desplazamiento aleatorio.
    mob_spawn_ubicación.progress_ratio = randf()

    var player_position = $Jugador.posición
    mob.initialize(mob_spawn_ubicación.posición, posición_jugador)

    # Engendra la mafia agregándola a la escena principal.
    add_child(mafia)

func _on_player_hit():
    $MobTimer.stop()

El siguiente Mob.gdes

extiende CharacterBody3D

# Velocidad mínima de la mafia en metros por segundo. 
@export  var min_speed = 10
 # Velocidad máxima de la mafia en metros por segundo. 
@exportar  var max_speed = 18

# Emitido cuando el jugador saltó sobre la 
señal de la mafia aplastada

función_física_proceso (_delta):
    mover_y_deslizar()

# Esta función se llamará desde la escena principal. 
func initialize(start_position, player_position):
     # Posicionamos al mob colocándolo en start_position 
    # y lo rotamos hacia player_position, para que mire al jugador.
    mirar_desde_la_posición(posición_de_inicio, posición_del_jugador, Vector3.ARRIBA)
    # Gira este mob aleatoriamente dentro de un rango de -90 y +90 grados, 
    # para que no se mueva directamente hacia el jugador.
    rotar_y(randf_rango(-PI / 4, PI / 4))

    # Calculamos una velocidad aleatoria (entero) 
    var random_speed = randi_range(min_speed, max_speed)
     # Calculamos una velocidad de avance que representa la velocidad.
    velocidad = Vector3.ADELANTE * velocidad_aleatoria
    # Luego rotamos el vector de velocidad basado en la rotación Y de la mafia 
    # para movernos en la dirección en la que mira la mafia.
    velocidad = velocidad.rotada(Vector3.ARRIBA, rotación.y)

función _on_visible_on_screen_notifier_3d_screen_exited():
    cola_libre()

func squash():
    aplastado.emit()
    queue_free() # Destruye este nodo

Finalmente, el script más largo Player.gd:

extiende CharacterBody3D

golpe de señal

# Qué tan rápido se mueve el jugador en metros por segundo 
@export  var speed = 14
 # La aceleración hacia abajo mientras está en el aire, en metros por segundo al cuadrado. 
@export  var fall_acceleration = 75
 # Impulso vertical aplicado al personaje al saltar en metros por segundo. 
@export  var jump_impulse = 20
 # Impulso vertical aplicado al personaje al rebotar sobre una mafia 
# en metros por segundo. 
@exportación  var rebote_impulso = 16

var target_velocity = Vector3.ZERO


func _physics_process(delta):
     # Creamos una variable local para almacenar la dirección de entrada 
    var direction = Vector3.ZERO

    # Verificamos cada entrada de movimiento y actualizamos la dirección en consecuencia 
    si Input.is_action_pressed("move_right"):
        dirección.x = dirección.x + 1
    si Input.is_action_pressed("move_left"):
        dirección.x = dirección.x - 1
    if Input.is_action_pressed("move_back"):
         # Observe cómo estamos trabajando con los ejes x y z del vector. 
        # En 3D, el plano XZ es el plano de tierra.
        dirección.z = dirección.z + 1
    si Input.is_action_pressed("move_forward"):
        dirección.z = dirección.z - 1

    # Evita que la diagonal se mueva rápido 
    si la dirección es != Vector3.ZERO:
        dirección = dirección.normalizado()
        $Pivot.look_at(posición + dirección, Vector3.ARRIBA)

    # Velocidad de tierra
    target_velocity.x = dirección.x * velocidad
    target_velocity.z = dirección.z * velocidad

    # Velocidad vertical 
    si  no está_en_el_piso(): # Si está en el aire, cae hacia el piso. Literalmente gravedad
        target_velocity.y = target_velocity.y - (fall_acceleration * delta)

    # Saltando. 
    if is_on_floor() and Input.is_action_just_pressed("jump"):
        target_velocity.y = salto_impulso

    # Iterar a través de todas las colisiones que ocurrieron en este marco 
    # en C esto sería para (int i = 0; i < colisiones.Cuenta; i++) 
    para el índice en el rango (obtener_deslizamiento_recuento_colisiones()):
         # Obtenemos una de las colisiones con el jugador 
        var colisión = get_slide_colision(índice)

        # Si la colisión es con el suelo 
        if (collision.get_collider() == null):
             continuar

        # Si el colisionador es con un mob 
        if colision.get_collider().is_in_group("mob"):
             var mob = colision.get_collider()
             # comprobamos que lo estamos golpeando desde arriba. 
            if Vector3.UP.dot(collision.get_normal()) > 0.1:
                 # Si es así, lo aplastamos y rebotamos.
                mafia.squash()
                target_velocity.y = rebote_impulso

    # Mover el personaje
    velocidad = velocidad_objetivo
    mover_y_deslizar()

# Y esta función en la parte inferior. 
función morir():
    hit.emit()
    cola_libre()

func _on_mob_detector_body_entered(cuerpo):
    morir()

Nos vemos en la próxima lección, agregando puntajes y opciones de repetición.

Puntuación y repetición¶

En esta parte, agregaremos puntuación, reproducción de música y la capacidad de reiniciar el juego.

Tenemos que realizar un seguimiento del puntaje actual en una variable y mostrarlo en la pantalla usando una interfaz mínima. Usaremos etiquetas de texto para hacer esto.

En la escena principal, agregue Mainun nuevo nodo secundario Control y asígnele el nombre UserInterface. Ingresará automáticamente a la pantalla 2D donde podrá editar la interfaz de usuario (UI).

Agregue un nodo Etiqueta y asígnele un nombreScoreLabel

En el Inspector , establezca el texto de la Etiqueta como marcador de posición, como "Puntuación: 0".

Además, el texto predeterminado es blanco, al igual que el fondo de nuestro juego. Necesitamos cambiar su color para verlo en tiempo de ejecución.

Desplácese hacia abajo hasta Anulaciones de tema , expanda Colores  y habilite Color de fuente para colorear el texto en negro (en contraste con la escena 3D blanca)

Finalmente, haga clic y arrastre el texto en la ventana gráfica lejos de la esquina superior izquierda.

Este UserInterfacenodo nos permite agrupar la interfaz de usuario en una rama del árbol de escena y usar recursos de tema que se propagarán a todos sus elementos secundarios. Usaremos esto para establecer la fuente de nuestro juego.

Crear temas de interfaz de usuario¶

Seleccione el nodo de nuevo UserInterface. En el Inspector, cree un nuevo recurso de tema en Theme -> Theme .

Haz clic en él para abrir el editor de temas en el panel inferior. Le permite obtener una vista previa de cómo se verán todos los widgets de la interfaz de usuario integrados y los recursos de su tema.

De forma predeterminada, un tema tiene solo una propiedad, Fuente predeterminada .

Ver: puede agregar más propiedades a los recursos de temas para diseñar interfaces de usuario complejas, pero eso está más allá del alcance de esta serie. Para obtener más información sobre cómo crear y editar temas, consulte Introducción a las máscaras de GUI .

Esto requiere un archivo de fuentes, al igual que los archivos de fuentes en su computadora. Dos formatos de archivo de fuentes comunes son las fuentes TrueType (TTF) y las fuentes OpenType (OTF).

En el  panel de sistema de archivos, expanda fontsel directorio y haga clic en el archivo que incluimos en el proyecto Montserrat-Medium.ttfy arrástrelo  a la fuente predeterminada . El texto volverá a aparecer en la vista previa del tema.

El texto es un poco pequeño. Establezca el tamaño de fuente predeterminado en 22píxeles para aumentar el tamaño del texto.

Seguimiento de puntuaciones¶

A continuación, veamos las fracciones. Adjunte un nuevo script ScoreLabely defina scorevariables.

extiende la etiqueta

puntuación var = 0

Cada vez que aplastemos a un monstruo, la puntuación debería aumentar en 1. Podemos usar sus squashedseñales para saber cuándo está pasando algo. Sin embargo, debido a que instanciamos el mob desde el código, nuestro 通过editor no puede conectarle la señal del mob ScoreLabel.

En cambio, tenemos que hacer la conexión desde el código cada vez que se genera un monstruo.

Abre el guión main.gd. Si aún está abierto, puede hacer clic en su nombre en la columna izquierda del editor de secuencias de comandos.

Como alternativa, puede hacer doble clic en el archivo en el panel del sistema de archivosmain.gd .

En la parte inferior de la función _on_mob_timer_timeout(), agregue la siguiente línea:

func _on_mob_timer_timeout():
     #... 
    # Conectamos el mob a la etiqueta de puntaje para actualizar el puntaje al aplastar uno.
    mob.squashed.connect($UserInterface/ScoreLabel._on_mob_squashed.bind())

Lo que esta línea significa es que cuando la criatura emite una señal squashedScoreLabelel nodo recibirá la señal y llamará a la función _on_mob_squashed().

Regrese al ScoreLabel.gdscript para definir _on_mob_squashed() la función de devolución de llamada.

Allí incrementamos la puntuación y actualizamos el texto mostrado.

func _on_mob_squashed():
    puntuación += 1
    texto = "Puntuación: %s " % puntuación

La segunda línea usa el valor de la variable scoreen lugar del marcador de posición %s. Al usar esta función, Godot convertirá automáticamente el valor en una cadena literal, lo cual es conveniente para generar texto en etiquetas o usar funciones print().

Consulte: puede obtener más información sobre el formato de cadenas aquí: Cadenas de formato GDScript . En C#, considere usar la interpolación de cadenas con "$" .

Ahora puedes jugar y aplastar a algunos enemigos para ver cómo aumenta tu puntuación.

Nota: en juegos complejos, es posible que desee separar completamente la interfaz de usuario del mundo del juego. En ese caso, no realiza un seguimiento de la puntuación en la etiqueta. En su lugar, probablemente desee almacenarlo en un objeto dedicado separado. Pero cuando crea prototipos o cuando su proyecto es simple, es bueno mantener su código simple. La programación es siempre un acto de equilibrio.

Reproducir el juego¶

Ahora agregaremos la capacidad de volver a jugar después de morir. Cuando el jugador muere, mostraremos un mensaje en la pantalla y esperaremos la entrada.

De vuelta en main.tscnla escena, seleccione UserInterfaceel nodo, agregue un nodo secundario ColorRect y asígnele el nombre Retry. Este nodo rellena un rectángulo con un color uniforme y se utilizará como superposición para oscurecer la pantalla.

Para hacer que abarque toda la ventana gráfica, puede usar el menú Ajustes preestablecidos de anclaje en la barra de herramientas.

Ábralo y aplique el comando Full Rect .

No pasó nada. Bueno, casi nada, solo cuatro chinchetas verdes moviéndose hacia las esquinas del cuadro de selección.

Esto se debe a que los nodos de la interfaz de usuario (todos los nodos con un ícono verde) usan anclas y márgenes en relación con el cuadro delimitador de sus padres. Aquí, UserInterfaceel tamaño de los nodos es pequeño y Retryestá limitado por él.

Seleccione UserInterfacey aplique Anchor Preset -> Full Rect . El  Retrynodo ahora debería abarcar toda la ventana gráfica.

Cambiemos su color para oscurecer el área de juego. Seleccione Retryy en  el Inspector , establezca su Color en un color oscuro y transparente. Para hacer esto, en el selector de color, arrastre el control deslizante A hacia la izquierda. Controla el canal alfa del color, es decir, su opacidad/transparencia.

A continuación, agregue una etiqueta como elemento secundario Retryy asígnele el texto  "Presione Intro para volver a intentarlo". Para moverlo y anclarlo al centro de la pantalla, aplique Anchor Preset -> Center a él.

Opciones de reproducción de codificación¶

Ahora podemos usar código para Retrymostrar y ocultar nodos cuando el jugador muere y vuelve a jugar.

Abre el guión main.gd. Primero, queremos ocultar la superposición cuando comienza el juego. Agregue esta línea a _ready()la función.

función_listo ():
    $InterfazUsuario/Reintentar.hide()

Luego mostramos la superposición cuando el jugador es golpeado.

func _on_player_hit():
     #...
    $InterfazUsuario/Reintentar.show()

Finalmente, Retrydebemos escuchar la entrada del jugador cuando el nodo está visible y reiniciar el juego si presionan enter. Para esto, usamos la devolución de llamada integrada  _unhandled_input(), que se activa en cualquier entrada.

Si el jugador presiona una ui_acceptacción de entrada predefinida y Retryestá visible, recargaremos la escena actual.

func _unhandled_input(event):
     if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible:
         # Esto reinicia la escena actual.
        get_tree().reload_current_scene()

Esta función get_tree()nos da acceso al objeto SceneTree global , que nos permite recargar y reiniciar la escena actual.

Añadir música¶

Para agregar música que se reproduzca continuamente en segundo plano, usaremos otra función en Godot: la carga automática .

Para reproducir audio, todo lo que necesita hacer es agregar un nodo AudioStreamPlayer a su escena y adjuntarle un archivo de audio. Cuando comienza una escena, puede reproducirse automáticamente. Sin embargo, cuando recargas la escena, como si la volviéramos a reproducir, el nodo de audio se reinicia y la música comienza a sonar desde el principio.

Puede usar la función de carga automática para que Godot cargue automáticamente un nodo o una escena que no sea la actual cuando se inicia el juego. También puede usarlo para crear objetos accesibles globalmente.

Cree una nueva escena yendo al menú "Escenas" y haciendo clic en "Nueva escena" o usando el ícono +  al lado de la escena actualmente abierta .

Haga clic en el botón Otro nodo para crear un AudioStreamPlayer y cambiarle el nombre a  MusicPlayer.

art/Incluimos la banda sonora de la música en el catálogo, House In a Forest Loop.ogg,haga clic y arrástrela a la propiedad Stream en el Inspector . Además, active la reproducción automática para reproducir música automáticamente cuando comience el juego.

Guarda la escena como MusicPlayer.tscn.

Tenemos que registrarlo para la carga automática. Vaya al menú Proyecto -> Configuración del proyecto... y haga clic en la pestaña Carga automática .

En el campo Ruta , ingresa la ruta a la escena. Haga clic en el icono de la carpeta para abrir el explorador de archivos y haga doble clic MusicPlayer.tscnen Luego, haga clic en el botón Agregar a la derecha para registrar el nodo.

MusicPlayer.tscnAhora carga en cualquier escena que abras o juegues. Entonces, si ejecuta el juego ahora, la música se reproducirá automáticamente en cualquier escena.

Antes de concluir esta lección, echemos un vistazo rápido a cómo funciona. Cuando ejecutas el juego, tu base de escena  cambia para mostrarte dos pestañas:  Remoto y Local .

La pestaña Remoto le permite visualizar el árbol de nodos del juego en ejecución. Allí verá el nodo principal y todo lo que contiene la escena y la criatura instanciada en la parte inferior.

En la parte superior está el nodo de carga automática MusicPlayery el nodo raíz , que es la ventana gráfica de tu juego.

De eso se trata esta lección. En la siguiente parte, agregaremos una animación para que el juego se vea y se sienta mejor.

Aquí está el main.gdguión completo para referencia.

extiende el nodo

@exportar  var mob_scene: Escena empaquetada

función_listo ():
    $InterfazUsuario/Reintentar.hide()


func _on_mob_timer_timeout():
     # Crea una nueva instancia de la escena Mob. 
    var mob = mob_scene.instantiate()

    # Elija una ubicación aleatoria en SpawnPath. 
    # Almacenamos la referencia al nodo SpawnLocation. 
    var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
     # Y dale un desplazamiento aleatorio.
    mob_spawn_ubicación.progress_ratio = randf()

    var player_position = $Jugador.posición
    mob.initialize(mob_spawn_ubicación.posición, posición_jugador)

    # Engendra la mafia agregándola a la escena principal.
    add_child(mafia)

    # Conectamos la mafia a la etiqueta de puntaje para actualizar el puntaje al aplastar uno.
    mob.squashed.connect($UserInterface/ScoreLabel._on_mob_squashed.bind())

func _on_player_hit():
    $MobTimer.stop()
    $InterfazUsuario/Reintentar.show()

func _unhandled_input(event):
     if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible:
         # Esto reinicia la escena actual. 
        get_tree().reload_current_scene()

Animación de personajes¶

En esta lección final, usaremos las herramientas de animación integradas de Godot para hacer que nuestro personaje levite y aletee. Aprenderá a diseñar animaciones en el editor y usar código para darle vida a su juego.

Comenzaremos con una introducción al uso del editor de animación.

Usando el editor de animación¶

El motor viene con herramientas para crear animaciones en el editor. Luego puede usar el código para jugar y controlarlos en tiempo de ejecución.

Abra la escena del reproductor, seleccione Playerel nodo y agregue un nodo AnimationPlayer .

El muelle de animación aparece en el panel inferior.

Tiene una barra de herramientas y un menú desplegable de animación en la parte superior, un editor de pistas actualmente vacío en el medio y opciones de filtrado, ajuste y zoom en la parte inferior.

Vamos a crear una animación. Haga clic en Animación -> Nuevo .

Nombra la animación "flotador".

Una vez que haya creado una animación, aparecerá una línea de tiempo con números que representan el tiempo en segundos.

Queremos que la animación comience a reproducirse automáticamente cuando comience el juego. Además, debería hacer un bucle.

Puede hacerlo haciendo clic en el botón con el icono "A+" y la flecha de ciclo respectivamente en la barra de herramientas de animación.

También puede anclar el editor de animación haciendo clic en el icono de alfiler en la esquina superior derecha. Esto evita que se colapse cuando hace clic en la ventana gráfica y anula la selección del nodo.

Establezca la duración de la animación en 1.2segundos en la esquina superior derecha del panel.

Deberías ver que la cinta gris se ensancha un poco. Le muestra dónde comienza y termina la animación, y la línea azul vertical es su cursor de tiempo.

Puede hacer clic y arrastrar el control deslizante en la parte inferior derecha para acercar y alejar la línea de tiempo.

Animación flotante¶

Con los nodos del reproductor de animación, puede animar la mayoría de las propiedades en tantos nodos como desee. Tenga en cuenta el icono de llave junto a la propiedad en el Inspector. Puede hacer clic en cualquiera de ellos para crear pares de fotogramas clave, tiempo y valor para la propiedad correspondiente. Se inserta un fotograma clave en la línea de tiempo en la ubicación del cursor de tiempo.

Insertemos nuestra primera clave. Aquí animaremos la posición y rotación del nodo Character.

Seleccione Charactery expanda la sección Transformar en el Inspector . Haga clic en el icono de la llave junto a Posición y rotación .

Para este tutorial, simplemente cree las pistas RESET seleccionadas por defecto

Aparecen dos pistas en el editor con un icono de diamante para cada fotograma clave.

Puede hacer clic y arrastrar los diamantes para moverlos en el tiempo. Mueva la tecla de posición a 0.2segundos y la tecla de rotación a 0.1segundos.

Mueva el cursor de tiempo a segundos haciendo clic y arrastrando en la línea de tiempo gris 0.5.

En el Inspector , establezca el eje Y de Posición en metros y el eje X de Rotación en .0.658

Crear un fotograma clave para ambas propiedades

0.7 Ahora, mueva el fotograma clave de posición a segundos arrastrándolo en la línea de tiempo.

Nota: Una lección sobre los principios de la animación está más allá del alcance de este tutorial. Tenga en cuenta que no desea programar el tiempo y el espacio de manera uniforme. En cambio, los animadores usan tiempo e intervalos, dos principios básicos de animación. Desea compensar y contrastar los movimientos de sus personajes para que se sientan vivos.

Mueve el cursor de tiempo al final de la animación, en 1.2segundos. Establezca Posición Y en Aproximado 0.35y Rotación X en -9Grados. Vuelva a crear una clave para estas dos propiedades.

Puede obtener una vista previa del resultado haciendo clic en el botón de reproducción o presionando Mayús+D. Haga clic en el botón de detener S o presione para detener la reproducción.

Puede ver el motor interpolar entre fotogramas clave para producir una animación continua. Por ahora, sin embargo, la acción se siente muy robótica. Esto se debe a que la interpolación predeterminada es lineal, lo que genera transiciones constantes, a diferencia de cómo se mueven las criaturas en el mundo real.

Podemos usar curvas de aceleración para controlar la transición entre fotogramas clave.

Haga clic y arrastre las dos primeras claves en la línea de tiempo para seleccionarlas en el marco.

Puede editar las propiedades de ambas claves al mismo tiempo en el Inspector, donde puede ver una propiedad Easing .

Haga clic y arrastre la curva para tirar de ella hacia la izquierda. Esto lo aliviará, es decir, la transición es rápida inicialmente y se ralentiza a medida que el cursor de tiempo alcanza el siguiente fotograma clave.

Vuelva a reproducir la animación para ver la diferencia. La primera mitad ya debería sentirse un poco ágil.

Aplica desaceleración al segundo fotograma clave en la pista de rotación.

Haga lo contrario para el fotograma clave de la segunda posición, arrastrándolo hacia la derecha.

Tu animación debería verse así.

Nota: La animación actualiza las propiedades del nodo de animación en cada cuadro, sobrescribiendo el valor inicial. Si animamos el nodo Player directamente , nos impide moverlo en código. Aquí es donde el nodo Pivote resulta útil: incluso si animamos el Personaje , aún podemos mover y rotar el Pivote en el script y cambiar las capas encima de la animación.

¡Las criaturas de los jugadores ahora flotarán si juegas!

Si la criatura está demasiado cerca del suelo, puedes Pivotmoverte hacia arriba para contrarrestarla.

Controlar animaciones en código¶

Podemos usar código para controlar la reproducción de la animación en función de la entrada del jugador. Cambiemos la velocidad de la animación cuando el personaje se mueve.

Se abrió una secuencia de comandos al hacer clic en el icono de secuencia de comandos junto a ella Player.

En _physics_process(), después de verificar las filas del vector direction , agregue el siguiente código.

func _physics_process(delta):
     #... 
    if direction != Vector3.ZERO:
         #...
        $AnimationPlayer.speed_scale = 4
    más :
        $AnimationPlayer.speed_scale = 1

Este código hace que multipliquemos la velocidad de reproducción por cuando el jugador se mueve  4. Cuando se detienen, los restablecemos a la normalidad.

Mencionamos que Pivotla capa podría transformarse sobre la animación. Podemos usar la siguiente línea de código para hacer un arco de personaje mientras salta. Agrégalo al final _physics_process().

función_física_proceso (delta):
     #...
    $Pivote.rotación.x = PI / 6 * velocidad.y / salto_impulso

Criaturas animadas¶

Este es otro buen truco para las animaciones en Godot: mientras uses una estructura de nodos similar, puedes copiarlos en diferentes escenas.

Por ejemplo, tanto las escenas Mobcomo Playerlas escenas tienen Pivotun  Characternodo, por lo que podemos reutilizar animaciones entre ellas.

Abra la escena Player , seleccione el nodo AnimationPlayer y active la animación "flotante". A continuación, haga clic en Animación > Duplicar . Luego abra mob.tscn, cree un nodo secundario AnimationPlayer y selecciónelo. Haga clic en Animación > Pegar  y asegúrese de que el botón con el ícono "A+" (reproducción automática al cargar) y la flecha de bucle (bucle de animación) también estén activados en el editor de animación en el panel inferior. Eso es todo, todos los mobs ahora reproducirán la animación flotante.

Podemos cambiar la velocidad de reproducción según la criatura random_speed. Abra el script de Mob y agregue la siguiente línea al final de la función initialize().

func initialize(start_position, player_position):
     #...
    $AnimationPlayer.speed_scale = random_speed / min_speed

Con esto, ha codificado su primer juego completo en 3D.

¡felicidades !

En la siguiente sección, revisaremos rápidamente lo que ha aprendido y le daremos algunos enlaces para que continúe aprendiendo más. Pero por ahora, aquí están los completos Player.gdMob.gdpara que pueda comparar su código con ellos.

Aquí está el guión del jugador.

extiende CharacterBody3D

golpe de señal

# Qué tan rápido se mueve el jugador en metros por segundo. 
@export  var speed = 14
 # La aceleración hacia abajo en el aire, en metros por segundo al cuadrado. 
@export  var fall_acceleration = 75
 # Impulso vertical aplicado al personaje al saltar en metros por segundo. 
@export  var jump_impulse = 20
 # Impulso vertical aplicado al personaje al rebotar sobre una mafia 
# en metros por segundo. 
@exportación  var rebote_impulso = 16

var target_velocity = Vector3.ZERO


func _physics_process(delta):
     # Creamos una variable local para almacenar la dirección de entrada 
    var direction = Vector3.ZERO

    # Verificamos cada entrada de movimiento y actualizamos la dirección en consecuencia 
    si Input.is_action_pressed("move_right"):
        dirección.x = dirección.x + 1
    si Input.is_action_pressed("move_left"):
        dirección.x = dirección.x - 1
    if Input.is_action_pressed("move_back"):
         # Observe cómo estamos trabajando con los ejes x y z del vector. 
        # En 3D, el plano XZ es el plano de tierra.
        dirección.z = dirección.z + 1
    si Input.is_action_pressed("move_forward"):
        dirección.z = dirección.z - 1

    # Evita que el movimiento diagonal sea muy rápido 
    si dirección != Vector3.ZERO:
        dirección = dirección.normalizado()
        $Pivot.look_at(posición + dirección,Vector3.ARRIBA)
        $AnimationPlayer.speed_scale = 4
    más :
        $AnimationPlayer.speed_scale = 1

    # Velocidad de tierra
    target_velocity.x = dirección.x * velocidad
    target_velocity.z = dirección.z * velocidad

    # Velocidad vertical 
    si  no está_en_el_piso(): # Si está en el aire, cae hacia el piso
        target_velocity.y = target_velocity.y - (fall_acceleration * delta)

    # Saltando. 
    if is_on_floor() and Input.is_action_just_pressed("jump"):
        target_velocity.y = salto_impulso

    # Iterar a través de todas las colisiones que ocurrieron en este marco 
    # en C esto sería para (int i = 0; i < colisiones.Cuenta; i++) 
    para el índice en el rango (obtener_deslizamiento_recuento_colisiones()):
         # Obtenemos una de las colisiones con el jugador 
        var colisión = get_slide_colision(índice)

        # Si la colisión es con el suelo 
        if (collision.get_collider() == null):
             continuar

        # Si el colisionador es con un mob 
        if colision.get_collider().is_in_group("mob"):
             var mob = colision.get_collider()
             # comprobamos que lo estamos golpeando desde arriba. 
            if Vector3.UP.dot(collision.get_normal()) > 0.1:
                 # Si es así, lo aplastamos y rebotamos.
                mafia.squash()
                target_velocity.y = rebote_impulso

    # Mover el personaje
    velocidad = velocidad_objetivo
    mover_y_deslizar()

    $Pivote.rotación.x = PI / 6 * velocidad.y / salto_impulso

# Y esta función en la parte inferior. 
función morir():
    hit.emit()
    cola_libre()

func _on_mob_detector_body_entered(cuerpo):
    morir()

También hay un guión de The  Mob .

extiende CharacterBody3D

# Velocidad mínima de la mafia en metros por segundo. 
@export  var min_speed = 10
 # Velocidad máxima de la mafia en metros por segundo. 
@exportar  var max_speed = 18

# Emitido cuando el jugador saltó sobre la 
señal de la mafia aplastada

función_física_proceso (_delta):
    mover_y_deslizar()

# Esta función se llamará desde la escena principal. 
func initialize(start_position, player_position):
     # Posicionamos al mob colocándolo en start_position 
    # y lo rotamos hacia player_position, para que mire al jugador.
    mirar_desde_la_posición(posición_de_inicio, posición_del_jugador, Vector3.ARRIBA)
    # Gira este mob aleatoriamente dentro de un rango de -90 y +90 grados, 
    # para que no se mueva directamente hacia el jugador.
    rotar_y(randf_rango(-PI / 4, PI / 4))

    # Calculamos una velocidad aleatoria (entero) 
    var random_speed = randi_range(min_speed, max_speed)
     # Calculamos una velocidad de avance que representa la velocidad.
    velocidad = Vector3.ADELANTE * velocidad_aleatoria
    # Luego rotamos el vector de velocidad basado en la rotación Y de la mafia 
    # para movernos en la dirección en la que mira la mafia.
    velocidad = velocidad.rotada(Vector3.ARRIBA, rotación.y)

    $AnimationPlayer.speed_scale = random_speed / min_speed

función _on_visible_on_screen_notifier_3d_screen_exited():
    cola_libre()

func squash():
    aplastado.emit()
    queue_free() # Destruir este nodo

Yendo más lejos¶

Puedes estar tranquilo con el hecho de que has creado tu primer juego en 3D con Godot.

En esta serie, hemos cubierto una amplia gama de técnicas y características del editor. Espero que hayas visto lo intuitivo que es el sistema de escenas de Godot y hayas aprendido algunos trucos que puedes aplicar a tus proyectos.

Pero solo hemos arañado la superficie: Godot te ahorra tiempo creando juegos y mucho más. Puede aprender todo sobre esto navegando por la documentación.

¿Por dónde deberías empezar? A continuación, encontrará algunas páginas para comenzar a explorar y desarrollar lo que ha aprendido hasta ahora.

Pero antes de eso, aquí hay un enlace para descargar la versión completa del proyecto:  https://github.com/godotengine/godot-3d-dodge-the-creeps .

Explorar el manual¶

Siempre que tenga dudas o curiosidad acerca de una característica, el manual es su aliado. No contiene tutoriales sobre tipos de juegos o mecánicas específicas. En cambio, explica cómo funciona Godot en general. En él encontrarás información sobre 2D, 3D, física, renderizado y rendimiento.

Estas son las secciones que le sugerimos que explore a continuación:

  1. Lea la sección de secuencias de comandos para conocer las funciones básicas de programación que usará en cada proyecto.

  2. La sección 3D y física le enseñará más sobre la creación de juegos 3D en el motor .

  3. La entrada es otra entrada importante para cualquier proyecto de juego.

Puede comenzar con estos o, si lo prefiere, consulte el menú de la barra lateral a la izquierda y elija sus opciones.

Esperamos que hayas disfrutado de esta serie de tutoriales y esperamos ver lo que logras con Godot.

Supongo que te gusta

Origin blog.csdn.net/drgraph/article/details/130814519
Recomendado
Clasificación