Godot Engine 4.0 Documentación - Manual - Mejores Prácticas

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: Mejores prácticas — Documentación de Godot Engine (estable) en inglés

 

Introducción¶ _

Esta serie es una colección de mejores prácticas para ayudarte a usar Godot de manera eficiente.

Godot ofrece una gran flexibilidad para estructurar el código base de un proyecto y dividirlo en escenas. Cada enfoque tiene sus pros y sus contras, y puede ser difícil sopesarlos todos hasta que haya estado usando el motor el tiempo suficiente.

Siempre hay muchas formas de estructurar el código y resolver problemas específicos de programación. Es imposible cubrir todo aquí.

Es por eso que cada artículo comienza con un problema del mundo real. Desglosaremos cada uno de los problemas básicos, propondremos soluciones, analizaremos los pros y los contras de cada opción y destacaremos el mejor curso de acción para el problema en cuestión.

Deberías comenzar leyendo Aplicando principios orientados a objetos en Godot . Explica cómo los nodos y escenas de Godot se relacionan con clases y objetos en otros lenguajes de programación orientados a objetos. Le ayudará a entender el resto de esta serie.

Nota: Las mejores prácticas en Godot se basan en principios de diseño orientado a objetos. Utilizamos herramientas como el Principio de Responsabilidad Única y  la encapsulación .

Aplicando Principios Orientados a Objetos en Godot¶

El motor proporciona dos formas principales de crear objetos reutilizables: guiones y escenas. Ninguno de estos define técnicamente las clases bajo el capó.

Sin embargo, muchas de las mejores prácticas para usar Godot implican aplicar principios de programación orientados a objetos a los guiones y escenas que conforman tu juego. Por eso es útil entender cómo pensamos en ellos como clases.

Esta guía explica brevemente cómo funcionan los guiones y las escenas en el núcleo del motor para ayudarlo a comprender cómo funcionan bajo el capó.

Cómo funcionan los scripts en el motor¶

El motor proporciona clases integradas como Node . Puede ampliarlos para crear tipos derivados mediante scripts.

Estos scripts no son técnicamente clases. En cambio, son recursos que le indican al motor que realice una serie de inicializaciones en una de las clases integradas del motor.

La clase interna de Godot tiene un método para registrar los datos de la clase en ClassDB . Esta base de datos proporciona acceso en tiempo de ejecución a la información de la clase. ClassDBContiene información sobre la clase, como:

  • característica.

  • método.

  • constante.

  • Señal.

Esto ClassDBes lo que comprueba el objeto cuando hace cosas como acceder a propiedades o llamar a métodos. Comprueba los registros de la base de datos y los registros del tipo base del objeto para ver si el objeto admite la operación.

Adjuntar scripts a sus objetos es extensible ClassDB.

Nota: Incluso los scripts que no usan extendspalabras clave se heredan implícitamente de la  clase RefCounted base del motor . Por lo tanto, puede  extendscrear instancias de secuencias de comandos sin la palabra clave de código. Como se expanden RefCounted, no puede adjuntarlos a Node .

escena¶ _

Las escenas se comportan en muchos aspectos como clases, por lo que tiene sentido pensar en las escenas como clases. Las escenas son grupos de nodos reutilizables, instanciables y heredables. Crear una escena es similar a un script que crea nodos y los agrega como elementos secundarios add_child().

A menudo emparejamos escenas con nodos raíz programables usando nodos de escena. Por lo tanto, los scripts amplían la escena agregando comportamiento a través de código imperativo.

El contenido de la escena ayuda a definir:

  • Qué nodos puede usar el script

  • como estan organizados

  • como se inicializan

  • ¿Cuál es la conexión de señal entre ellos?

¿Por qué son importantes para la organización de la escena? Porque las instancias de escenas son objetos. Por lo tanto, muchos principios orientados a objetos que se aplican a la escritura de código también se aplican a escenarios: responsabilidad única, encapsulación, etc.

Una escena siempre es una extensión de un script adjunto a su nodo raíz , por lo que puede interpretarlo como parte de una clase.

La mayoría de las técnicas presentadas en esta serie de mejores prácticas se basan en este punto.

Organización de la escena¶

Este artículo cubre temas relacionados con la organización eficaz del contenido de la escena. ¿Qué nodos se deben utilizar? ¿Dónde deben colocarse? ¿Cómo deben interactuar?

Cómo construir relaciones de manera eficiente¶

Cuando los usuarios de Godot comienzan a crear sus propias escenas, a menudo se encuentran con las siguientes preguntas:

Crearon su primera escena y la llenaron de contenido, pero terminaron guardando ramas de su escena en escenas separadas porque comenzó a acumularse la persistente sensación de que debían mantener las cosas separadas. Sin embargo, luego notaron que las referencias duras en las que podían confiar antes ya no eran posibles. La reutilización de una escena en varios lugares crea problemas, ya que las rutas de los nodos no pueden encontrar sus objetivos y las conexiones de señal realizadas en el editor se rompen.

Para resolver estos problemas, las subescenas deben instanciarse sin información detallada sobre su entorno. Las personas deben poder confiar en que la subescena se creará sola sin elegir cómo la usan las personas.

Una de las cosas más importantes a considerar en OOP es mantener clases centralizadas y de un solo propósito que se acoplan libremente con el resto de la base de código . Esto mantiene pequeño el tamaño de los objetos (para facilitar el mantenimiento) y aumenta su reutilización.

Estas mejores prácticas de programación orientada a objetos tienen varias implicaciones para las mejores prácticas en la estructura de escenas y el uso de guiones .

Los escenarios deben diseñarse para que no tengan dependencias si es posible.  Es decir, las personas deben crear escenarios que mantengan todo lo que necesitan dentro de sí mismos.

Si una escena tiene que interactuar con un contexto externo, los desarrolladores experimentados recomiendan usar  la inyección de dependencia . Esta técnica implica que una API de alto nivel proporcione las dependencias de una API de bajo nivel. ¿Por qué quieres hacer esto? Porque las clases que dependen del entorno externo pueden desencadenar errores y comportamientos inesperados sin darse cuenta.

Para hacer esto, los datos deben estar expuestos y luego confiar en el contexto principal para inicializarlos:

  1. conectado a la señal. Muy seguro, pero solo debe usarse para el comportamiento de "respuesta", no para iniciarlo. Tenga en cuenta que los nombres de las señales suelen ser verbos en tiempo pasado, como "entered", "skill_activated" o "item_collected".

    # Parent 
    $Child.signal_name.connect(method_on_the_object) # Child 
    signal_name.emit() # Activa el comportamiento definido por los padres.
    
    
    
  2. Llame a un método. Se utiliza para iniciar el comportamiento.

    # Parent 
    $Child.method_name = "do" # Child, asumiendo que tiene la propiedad String 'method_name' y el método 'do'. 
    call(method_name) # Llamar al método definido por el padre (que el hijo debe poseer).
    
    
    
  3. Inicialice la propiedad Callable . Más seguro que los métodos porque no se requiere la propiedad de los métodos. Se utiliza para iniciar el comportamiento.

    # Parent 
    $Child.func_property = object_with_method.method_on_the_object # Child 
    func_property.call() # Llamar al método definido por el padre (puede venir de cualquier lugar).
    
    
    
  4. Inicialice un nodo u otra referencia de objeto.

    # Padre 
    $Niño.objetivo = self # Niño 
    print(objetivo) # Usar nodo definido por padre.
    
    
    
  5. Inicializar una ruta de nodo.

    # Parent 
    $Child.target_path = ".." # Child 
    get_node(target_path) # Usar NodePath definido por el padre.
    
    
    

Estas opciones ocultan los puntos de acceso para los nodos secundarios. Esto, a su vez, hace que el niño se acople débilmente a su entorno. Se puede reutilizar en otro contexto sin cambios adicionales en su API.

Nota: Aunque los ejemplos anteriores ilustran las relaciones padre-hijo, los mismos principios se aplican a todas las relaciones de objetos. Los nodos que son hermanos solo deben conocer su jerarquía, mientras que los ancestros median en su comunicación y referencias.

# Padre 
$Left.target = $Right.get_node("Receiver") # Left var target: Node
 func execute():
     # Hacer algo con 'target'. # Función derecha _init():
     var receptor = Receptor.nuevo() 
    add_child(receptor)






El mismo principio se aplica a los objetos que no son nodos y que mantienen dependencias con otros objetos. Cualquiera que sea el objeto que realmente posee estos objetos debe administrar la relación entre ellos.

Nota: se debe preferir mantener los datos internos (dentro de la escena), aunque depender de un contexto externo, incluso un contexto débilmente acoplado, aún significa que un nodo esperará que algo en su entorno sea verdadero. La filosofía de diseño del proyecto debería evitar que esto suceda. De lo contrario, la responsabilidad inherente del código obligará a los desarrolladores a usar la documentación para rastrear las relaciones de los objetos en una escala microscópica; esto también se conoce como el infierno del desarrollo. De forma predeterminada, escribir código que se base en documentación externa para usarlo de forma segura es propenso a errores.

Para evitar la creación y el mantenimiento de dicha documentación, convierta los nodos dependientes (los "hijos" anteriores) en implementaciones  _get_configuration_warning(). Al devolver una cadena no vacía de esto, el panel de escena generará un icono de alerta que tomará la cadena como información sobre herramientas. Cuando el nodo Area2D no define nodos secundarios  CollisionShape2D , este icono es el mismo que el icono que muestra el nodo Area2D y otros nodos. Luego, el editor autograba la escena a través del código de script. No es necesario copiar contenido a través del documento.

Una GUI como esta puede informar mejor a los usuarios del proyecto sobre información clave sobre los nodos. ¿Tiene dependencias externas? ¿Se cumplen estas dependencias? Otros programadores, especialmente diseñadores y escritores, necesitarán instrucciones claras en el mensaje que les indique cómo configurarlo.

Entonces, ¿por qué funcionan todos estos interruptores complicados? Bueno, porque las escenas funcionan mejor cuando se ejecutan individualmente. Trabajar de forma anónima con otros (con dependencias rígidas mínimas, es decir, acoplamiento flexible) es la siguiente mejor opción si no es posible trabajar solo. Inevitablemente, es posible que sea necesario realizar cambios en la clase, y si esos cambios hacen que interactúe con otras escenas de formas imprevistas, las cosas comienzan a fallar. El objetivo de toda esta indirección es evitar situaciones en las que cambiar una clase afectaría negativamente a otras clases que dependen de ella.

Los guiones y las escenas, como extensiones de la clase de motor, deben seguir todos los principios de programación orientada a objetos. Ejemplos incluyen...

Como resultado, los desarrolladores comienzan a trabajar en juegos solo para detenerse en las enormes posibilidades que tienen ante ellos. Probablemente saben lo que quieren hacer, qué tipo de sistema quieren, pero ¿dónde ponerlo todo ? Bueno, cómo uno hace su juego siempre depende de ellos. Un árbol de nodos se puede construir de infinitas maneras. Sin embargo, para aquellos que no estén seguros, esta guía útil puede proporcionarles una muestra de estructura decente para comenzar.

Los juegos siempre deben tener una especie de "punto de entrada", un lugar donde los desarrolladores puedan rastrear claramente dónde comienzan las cosas para que puedan seguir la lógica cuando continúen en otro lugar. Este lugar también sirve como una vista panorámica de todos los demás datos y lógica del programa. Para aplicaciones heredadas, esta sería la función "principal". En este caso será un nodo maestro.

  • Nodo "principal" (main.gd)

El script actuará main.gdcomo el controlador principal del juego.

Entonces uno tiene su "mundo" real del juego (mundo 2D o 3D). Esto puede ser un hijo de Main. Además, su juego requiere una GUI principal para administrar los diversos menús y widgets necesarios para el proyecto.

  • Nodo "principal" (main.gd)

    • Nodo2D/Nodo3D “Mundo” (game_world.gd)

    • Control "GUI" (gui.gd)

Al cambiar de nivel, los elementos secundarios del nodo "mundo" se pueden intercambiar. El cambio manual de escenas brinda a los usuarios un control total sobre cómo se transforma su mundo de juego.

El siguiente paso es considerar qué tipo de sistema de juego requiere el proyecto. Si uno tiene un sistema...

  1. Rastree todos sus datos internamente

  2. Debería ser accesible a nivel mundial

  3. debería existir en aislamiento

... y luego se debe crear un nodo "singleton" de carga automática .

notas

Para juegos más pequeños, una alternativa más simple con menos control es tener un singleton de "juego" que simplemente llama al método SceneTree.change_scene_to_file  () para intercambiar el contenido de la escena principal. Esta estructura más o menos mantiene el "mundo" como el nodo principal del juego.

Cualquier GUI también debe ser un singleton, ser una parte transitoria del "mundo" o agregarse manualmente como un elemento secundario directo de la raíz. De lo contrario, los nodos de la GUI también se eliminan durante las transiciones de escena.

Si un sistema modifica los datos de otros sistemas, esos sistemas deben definirse como sus propios guiones o escenas, no autocargados. Consulte  la documentación de carga automática frente a nodo normal  para obtener más información sobre por qué.

Cada subsistema del juego debería tener su propia sección en SceneTree. Las relaciones padre-hijo solo deben usarse cuando un nodo es un elemento válido de su padre. ¿Retirar al padre significa razonablemente que también se debe retirar al niño? Si no, entonces debería tener su propio lugar en la jerarquía como un hermano u otra relación.

notas

En algunos casos, es deseable que estos nodos separados también se coloquen entre sí. Para esto,  se pueden usar nodos RemoteTransform  /  RemoteTransform2D. Permitirán que el nodo de destino herede condicionalmente los elementos de transformación seleccionados del nodo Remoto*. Para asignar un NodePath , use uno de los siguientes:target 

  1. Un tercero confiable, posiblemente el nodo principal, media en la asignación.

  2. Un grupo que facilita la extracción de referencias a los nodos deseados (suponiendo que solo hay un objetivo).

¿Cuándo se debe hacer esto? Bueno, esto es subjetivo. Los dilemas surgen cuando los nodos tienen que moverse por el SceneTree para protegerse, cuando se debe realizar una microgestión. Por ejemplo...

  • Agregue un nodo "Jugador" a la "Sala".

  • La sala debe cambiarse, por lo que la sala actual debe eliminarse.

  • Los jugadores deben ser retenidos y/o movidos antes de que se pueda eliminar una sala.

    ¿La memoria es un problema?

    • De lo contrario, se pueden crear dos habitaciones, mover el reproductor y eliminar la anterior. ningún problema.

    Si es así, uno tendrá que...

    • Mueve al jugador a otra parte del árbol.

    • Eliminar habitación.

    • Crea una instancia y agrega una nueva habitación.

    • Agrega el jugador nuevamente.

El problema es que el jugador aquí es un "caso especial"; el desarrollador debe saber que necesita manejar al jugador de esta manera para el proyecto. Por lo tanto, la única manera de compartir de manera confiable esta información como equipo es documentándola . Sin embargo, es peligroso mantener los detalles de implementación en la documentación. Esta es una carga de mantenimiento que afecta la legibilidad del código e infla innecesariamente el contenido intelectual del proyecto.

En juegos más complejos con activos más grandes, podría ser una mejor idea mantener al jugador en otra parte del SceneTree por completo. Esto resulta en:

  1. Más consistencia.

  2. No hay "casos especiales" que deban documentarse y mantenerse en alguna parte.

  3. No hay posibilidad de error porque estos detalles no se consideran.

En cambio, si desea un nodo secundario que no herede la transformación del padre, tiene las siguientes opciones:

  1. Solución declarativa : poner un nodo entre ellos . Como nodos sin transiciones, los nodos no pasan dicha información a sus hijos.

  2. Solución imperativa : use las propiedades de los nodos CanvasItemNode3D . Esto hará que el nodo ignore sus transformaciones heredadas.top_level

notas

Si crea un juego en línea, tenga en cuenta qué nodos y sistemas de juego son relevantes para todos los jugadores y cuáles solo son relevantes para el servidor autorizado. Por ejemplo, no es necesario que todos los usuarios tengan una copia de la lógica "PlayerController" para cada jugador. En cambio, solo necesitan los suyos. Por lo tanto, mantenerlos en una rama diferente al "mundo" puede ayudar a simplificar la administración de las conexiones del juego, etc.

La clave para la organización de la escena es pensar en SceneTree en términos relacionales en lugar de espaciales. ¿Depende el nodo de la existencia de su nodo padre? Si no, entonces pueden prosperar en otro lugar por su cuenta. Si son dependientes, entonces podría decirse que deberían ser hijos de ese padre (y probablemente parte de la escena de ese padre si aún no lo son).

¿Significa esto que los propios nodos son componentes? de nada. El árbol de nodos de Godot forma una relación de agregación, no una relación de composición. Sin embargo, aunque todavía existe la flexibilidad de mover los nodos, sería bueno no requerir tales movimientos de forma predeterminada.

Cuándo usar escenas y guiones¶

Hemos cubierto las diferencias entre escenarios y guiones. Los scripts definen extensiones de clase de motor usando código imperativo y escenarios usando código declarativo.

Por lo tanto, cada sistema funciona de manera diferente. Una escena puede definir cómo se inicializa una clase extendida, pero no su comportamiento real. Las escenas a menudo se usan junto con scripts, donde las escenas declaran combinaciones de nodos y los scripts agregan comportamiento usando código imperativo.

Tipos anónimos¶

El contenido de la escena se puede definir completamente usando solo scripts . Esencialmente, esto es lo que hace el editor Godot, solo en el constructor C++ de sus objetos.

Sin embargo, elegir cuál usar puede ser un dilema. Crear una instancia de secuencia de comandos es lo mismo que crear una clase en el motor, mientras que el manejo de escenarios requiere cambiar la API:

const MyNode = preload("my_node.gd")
 const MyScene = preload("my_scene.tscn")
 var node = Node.new()
 var my_node = MyNode.new() # Llamada al mismo método 
var my_scene = MyScene.instantiate() # Llamada de método diferente 
var my_inherited_scene = MyScene.instantiate(PackedScene.GEN_EDIT_STATE_MAIN) # Crear escena heredando de MyScene

Además, los scripts se ejecutarán un poco más lentos que las escenas debido a las diferencias de velocidad entre el motor y el código del script. Cuanto más grande y complejo sea el nodo, más razones para construirlo en una escena.

Tipos con nombre¶

Los scripts se pueden registrar como nuevos tipos en el propio editor. Esto lo mostrará como un nuevo tipo en el cuadro de diálogo de creación de nodos o recursos con un icono opcional. De esta forma, la capacidad del usuario para usar scripts se simplifica enormemente. en lugar de tener que...

  1. Conozca los tipos básicos de scripts que quieren usar.

  2. Cree una instancia de este tipo base.

  3. Agregue el script al nodo.

Con los scripts registrados, el tipo de script se convierte en una opción de creación como otros nodos y recursos en el sistema. El cuadro de diálogo de creación incluso tiene una barra de búsqueda para buscar tipos por nombre.

Hay dos tipos de sistemas de registro:

  • tipo personalizado

    • Editor solamente. Los nombres de tipo no son accesibles en tiempo de ejecución.

    • No se admiten los tipos personalizados heredados.

    • Una herramienta de inicialización. Cree nodos usando scripts. Eso es todo.

    • El editor no conoce los tipos de scripts ni su relación con otros tipos de motor o scripts.

    • Permite al usuario definir iconos.

    • Funciona con todos los lenguajes de secuencias de comandos porque maneja los recursos de secuencias de comandos de forma abstracta.

    • Use EditorPlugin.add_custom_type para configurarlo.

  • clase de guión

    • Editor y tiempo de ejecución accesibles.

    • Visualización completa de las relaciones de herencia.

    • Los nodos se crean mediante scripts, pero los tipos también se pueden cambiar o ampliar desde el editor.

    • El editor es consciente de la relación de herencia entre las secuencias de comandos, las clases de secuencias de comandos y las clases de C++ del motor.

    • Permite al usuario definir iconos.

    • Los desarrolladores de motores tienen que agregar soporte manualmente para el idioma (exposición de nombres y accesibilidad en tiempo de ejecución).

    • Solo Godot 3.1+.

    • El editor escanea la carpeta del proyecto y registra los nombres expuestos para todos los lenguajes de secuencias de comandos. Cada lenguaje de secuencias de comandos debe implementar su propio soporte para exponer esta información.

Ambos métodos agregan el nombre al cuadro de diálogo de creación, pero la clase de secuencia de comandos en particular también permite al usuario acceder al nombre del tipo sin cargar el recurso de secuencia de comandos. Es posible crear instancias y acceder a constantes o métodos estáticos desde cualquier lugar.

Con estas características, uno podría desear que su tipo fuera un script sin escenarios, ya que confiere facilidad de uso al usuario. Aquellos que desarrollen complementos o creen herramientas internas para que las usen los diseñadores lo encontrarán más fácil de esta manera.

En el lado negativo, esto también significa que la programación imperativa debe usarse mucho.

Rendimiento de scripts y PackedScene¶

Un último aspecto a tener en cuenta a la hora de seleccionar escenas y guiones es la velocidad de ejecución.

A medida que aumenta el tamaño de los objetos, también aumenta el tamaño necesario para que los scripts los creen e inicialicen. La creación de una jerarquía de nodos demuestra esto. La lógica de cada nodo puede ser de cientos de líneas de código.

El siguiente ejemplo de código crea un nuevo nodo Node, cambia su nombre, le asigna un script, establece su futuro padre como su propietario para que se guarde en el disco con él y finalmente lo agrega como hijo del nodo Main:

# main.gd 
extiende Node func _init():
     var child = Node.new() 
    child.name = "Child" 
    child.script = preload("child.gd") 
    child.owner = self 
    add_child(child)


El código de script como este es mucho más lento que el código C++ en el lado del motor. Cada instrucción llama a la API de secuencias de comandos, lo que provoca múltiples "búsquedas" en el backend para encontrar la lógica a ejecutar.

Las escenas ayudan a evitar este problema de rendimiento. PackedScene es el tipo base del que hereda Scene y define recursos que crean objetos con datos serializados. El motor puede agrupar escenas en el backend y proporcionar un mejor rendimiento que las secuencias de comandos.

Conclusión¶ _

Al final, el mejor enfoque es considerar lo siguiente:

  • Si uno desea crear una herramienta básica que se reutilizará en varios proyectos diferentes y es probable que la usen personas de todos los niveles (incluidos aquellos que no se etiquetan a sí mismos como "programadores"), entonces probablemente sea posible Debería ser un script , posiblemente uno con un nombre/icono personalizado.

  • Si alguien desea crear un concepto específico para su juego, siempre debe ser una escena. Los escenarios son más fáciles de rastrear/editar que los scripts y brindan mayor seguridad.

  • Si alguien quiere nombrar la escena, aún puede hacerlo en 3.1 declarando la clase de script y dándole la escena como una constante. El script en realidad se convierte en un espacio de nombres:

    # game.gd 
    class_name Game # extends RefCounted, por lo que no aparecerá en el diálogo de creación de nodos 
    extends RefCounted const MyScene = preload("my_scene.tscn") # main.gd extends Node
     func _ready(): 
        add_child(Game. MiEscena.instantiate())
    
    
    
    
    

Carga automática vs nodos regulares¶

Godot proporciona la función de cargar automáticamente los nodos raíz del proyecto, lo que le permite acceder a ellos de forma global, y puede completar la función de singletons: Singletons (  Autoload) . Estos nodos cargados automáticamente no se liberan cuando cambia la escena desde el código usando SceneTree.change_scene_to_file .

En esta guía, aprenderá cuándo usar la carga automática y los trucos que puede usar para evitarlo.

Cortar problemas de audio¶

Otros motores pueden alentar el uso de la creación de clases de administrador, organizando grandes cantidades de funcionalidad en singletons en objetos accesibles globalmente. Godot proporciona muchas formas de evitar el estado global gracias a los árboles de nodos y las señales.

Por ejemplo, digamos que estamos construyendo un juego de plataformas y queremos recolectar monedas que reproducen efectos de sonido. Hay un nodo: AudioStreamPlayer . Pero si lo llamamos cuando ya está sonando AudioStreamPlayer, el nuevo sonido interrumpirá al primero.

Una solución es escribir una clase de administrador de sonido global y cargada automáticamente. Genera un conjunto de nodos AudioStreamPlayerpor los que pasa cada vez que llega una nueva solicitud de efecto de sonido. Suponiendo que llamemos a esa clase Sound, puede usarla desde cualquier parte de su proyecto llamándola Sound.play("coin_pickup.ogg"). Esto resuelve el problema a corto plazo, pero causa más problemas:

  1. Estado global : un objeto ahora es responsable de los datos de todos los objetos. Si esta clase  Soundtiene errores o no hay un AudioStreamPlayer disponible, todos los nodos que lo llaman pueden fallar.

  2. Acceso global : ahora que se puede Sound.play(sound_path) llamar a cualquier objeto desde cualquier lugar, ya no existe una manera fácil de encontrar el origen de un error.

  3. Asignación global de recursosAudioStreamPlayer : con un conjunto de nodos almacenados desde el principio, tiene muy poco y enfrenta errores, o demasiado y usa más memoria de la que necesita.

NOTA: El problema con el acceso global es que cualquier código en cualquier lugar podría pasar datos incorrectos para Soundla carga automática en nuestro ejemplo. Por lo tanto, el área a explorar para corregir errores abarca todo el proyecto.

Cuando guarda código en una escena, es posible que solo haya uno o dos guiones involucrados en el audio.

Compare esto con AudioStreamPlayermantener tantos nodos como sea posible dentro de cada escena, y todos estos problemas desaparecerán:

  1. Cada escena gestiona su propia información de estado. Si hay un problema con los datos, solo causará problemas en esa escena.

  2. Cada escena solo accede a su propio nodo. Ahora, si hay un error, es fácil encontrar qué nodo tiene la culpa.

  3. Cada escena asigna exactamente la cantidad de recursos que necesita.

Administrar funcionalidad o datos compartidos¶

Otra razón para usar la carga automática podría ser que desee reutilizar el mismo método o datos en varios escenarios.

En el caso de las funciones, puede usar la palabra clave class_nameNode en GDScript para crear un nuevo tipo que proporcione esa funcionalidad para una sola escena.

En el lado de los datos, puede:

  1. Cree un nuevo tipo de recurso para compartir datos.

  2. Almacene los datos en un objeto al que todos los nodos puedan acceder, por ejemplo, utilizando una propiedad ownerpara acceder al nodo raíz de la escena.

Cuándo deberías usar la carga automática¶

La carga automática de nodos puede simplificar su código en algunos casos:

  • Datos estáticos : si necesita datos exclusivos de una clase, como una base de datos, la carga automática es una gran herramienta. No hay una API de secuencias de comandos en Godot para crear y administrar datos estáticos.

  • Funciones estáticas : cree una biblioteca de funciones que simplemente devuelvan un valor.

  • Sistemas de amplio alcance : si un singleton administra su propia información sin invadir los datos de otros objetos, esta es una buena manera de crear sistemas que manejen una amplia gama de tareas. Por ejemplo, un sistema de búsqueda o diálogo.

Antes de Godot 3.1, otro uso era solo por conveniencia: las cargas automáticas tenían una variable global con un nombre generado en GDScript, lo que le permitía llamarlas desde cualquier archivo de script en su proyecto. Pero ahora, puede usar class_namepalabras clave para obtener el autocompletado de tipos a lo largo de su proyecto.

Nota: Autoload no es exactamente un singleton. No hay nada que le impida crear una instancia de una copia de un nodo de carga automática. Es solo una herramienta para hacer que los nodos se carguen automáticamente como elementos secundarios de la raíz del árbol de escenas, independientemente de la estructura de nodos del juego o de la escena que se esté ejecutando, por ejemplo, presionando F6.

De modo que puede  Soundobtener nodos autocargados llamando, por ejemplo, a named get_node("/root/Sound")autoload.

Cuándo y cómo evitar usar nodos para todo¶

Los nodos son baratos de producir, pero incluso ellos tienen sus limitaciones. Un proyecto puede tener decenas de miles de nodos haciendo cosas. Sin embargo, cuanto más complejo sea su comportamiento, más presión ejerce cada uno sobre el desempeño del proyecto.

Godot proporciona objetos más livianos para crear API utilizadas por los nodos. Es importante tener esto en cuenta como opciones al diseñar cómo desea que se estructure la funcionalidad de su proyecto.

  1. Objeto  : el objeto ligero definitivo, el objeto original debe utilizar la gestión de memoria manual. Habiendo dicho eso, no es demasiado difícil crear sus propias estructuras de datos personalizadas, e incluso la estructura del nodo es más ligera que la clase Node .

    • Ejemplo: ver los nodos del árbol . Admite la personalización avanzada de directorios con un número arbitrario de filas y columnas. Los datos que utiliza para generar la visualización son en realidad un árbol de objetos TreeItem .

    • Pros: Simplificar la API a un alcance más pequeño de objetos ayuda a mejorar su accesibilidad y reduce el tiempo de iteración. En lugar de usar toda la biblioteca de nodos, cree un conjunto simplificado de objetos a partir del cual los nodos puedan generar y administrar nodos secundarios apropiados.

    NOTA: Se debe tener cuidado al manipularlos. Los objetos se pueden almacenar en variables, pero estas referencias pueden dejar de ser válidas sin previo aviso. Por ejemplo, si el creador del objeto decide eliminarlo inexplicablemente, se activará un estado de error la próxima vez que se acceda a él.

  2. RefCounted : solo un poco más complicado que Object. Realizan un seguimiento de las referencias a sí mismos y solo eliminan la memoria cargada cuando no hay más referencias a ellos mismos. Estos son útiles en la mayoría de los casos en los que se requieren datos de una clase personalizada.

    • Ejemplo: Ver el objeto FileAccess . Funciona como un objeto normal, excepto que no necesita eliminarlo usted mismo.

    • Ventajas: Igual que el Objeto.

  3. Recursos : solo un poco más complejo que RefCounted. Tienen una capacidad innata para serializar/deserializar (es decir, guardar y cargar) propiedades de objetos a/desde archivos de recursos de Godot.

    • Ejemplo: Script, PackedScene (para archivos de escena) y otros tipos como cada clase AudioEffect . Cada uno de estos se puede guardar y cargar, por lo que se extienden desde los recursos.

    • Ventajas: Mucho se ha dicho  sobre las ventajas de Resource sobre los métodos tradicionales de almacenamiento de datos . Sin embargo, en el contexto del uso de Recursos sobre Nodos, su principal ventaja es la compatibilidad con Inspector. Si bien son casi tan livianos como Object/RefCounted, aún pueden mostrar y exportar propiedades en el inspector. Esto les permite tener un propósito similar al de los nodos secundarios en términos de disponibilidad, pero también mejora el rendimiento si planea tener muchos de estos recursos/nodos en su escena.

Interfaz de Godot¶

A menudo, uno necesita depender de los scripts de otros objetos para obtener propiedades. Hay dos partes en este proceso:

  1. Obtener una referencia a un objeto que pueda tener estas características.

  2. Acceder a datos o lógica desde un objeto.

El resto de este tutorial describe varias formas de hacer todo esto.

Obtener referencia de objeto¶

Como con todos los Objetos , la forma más básica de referirse a ellos es obtener una referencia a un objeto existente de otra instancia obtenida.

var obj = nodo.objeto # Acceso a la propiedad. 
var obj = node.get_object() # Acceso al método.

El mismo principio se aplica a los objetos RefCounted . Aunque los usuarios a menudo acceden a NodeResource de esta manera , hay medidas alternativas disponibles.

En lugar de acceder a propiedades o métodos, los recursos se pueden obtener cargando el acceso.

var preres = preload(ruta) # Cargar recurso durante la carga de escena 
var res = load(ruta) # Cargar recurso cuando el programa llega a la declaración 

# Tenga en cuenta que los usuarios cargan escenas y guiones, por convención, con 
nombres PascalCase # (como nombres de tipo), a menudo en constantes 
const MyScene : = preload("my_scene.tscn") as PackedScene # Carga estática 
const MyScript : = preload("my_script.gd") as Script # El valor de este tipo varía, es decir, es una variable, por lo que usa snake_case. 
export(Script) var script_type: Script # Si necesita un "export const var" (que no existe), use un condicional




# setter para un script de herramienta que verifica si se está ejecutando en el editor. 
herramienta # Debe colocarse en la parte superior del archivo. 

# Debe configurarse desde el editor, por defecto es nulo. 
export(Script) var const_script setget set_const_script
 func set_const_script(valor):
     if Engine.is_editor_hint(): 
        const_script = valor # Avisa a los usuarios si el valor no se ha establecido. func _get_configuration_warning():
     if not const_script:
         return "Debe inicializar la propiedad 'const_script'".
    volver ""



 

Tenga en cuenta lo siguiente:

  1. Un idioma puede cargar tales recursos de varias maneras.

  2. Al diseñar la forma en que los objetos acceden a los datos, no olvide que los recursos también se pueden pasar como referencias.

  3. Recuerde que cargar un recurso obtiene una instancia de recurso en caché mantenida por el motor. Para obtener un nuevo objeto, se debe copiar o utilizar una referencia existente  new().

Los nodos también tienen un punto de acceso alternativo: SceneTree.

extiende Nodo # Lento. func dynamic_lookup_with_dynamic_nodepath(): 
    print(get_node("Child")) # Más rápido. Solo GDScript. func dynamic_lookup_with_cached_nodepath(): 
    print($Child) # Más rápido. No se rompe si el nodo se mueve más tarde. # Tenga en cuenta que la anotación `@onready` es solo GDScript. # Otros lenguajes deben hacer... # var child # func _ready(): # child = get_node("Child") @onready var child = $Child
 func lookup_and_cache_for_future_access(): 
    print(child) # Delegar la asignación de referencia a una fuente externa .













 


# Con: necesidad de realizar una verificación de validación. 
# Pro: el nodo no hace requisitos de su estructura externa. 
# 'prop' puede provenir de cualquier parte. 
var prop
 func call_me_after_prop_is_initialized_by_parent():
     # Validar prop de una de tres maneras. 

    # Fallo sin notificación. 
    if  not prop:
         return 

    # Fail con un mensaje de error. 
    if  not prop: 
        printerr("'prop' no fue inicializado") return # Falla y termina. # Nota: Los scripts que se ejecutan desde una plantilla de exportación de lanzamiento no # ejecutan declaraciones `assert`. 
    afirmar (prop, "'prop' no se inicializó")
        

    
    
    

# Usa una carga automática. 
# Peligroso para nodos típicos, pero útil para verdaderos nodos singleton 
# que administran sus propios datos y no interfieren con otros objetos. 
func reference_a_global_autoloaded_variable(): 
    print(globals) 
    print(globals.prop) 
    print(globals.my_getter())

Acceder a datos o lógica desde objetos¶

La API de secuencias de comandos de Godot es DUCK. Esto significa que si un script realiza una operación, Godot no verificará que admita la operación de tipo. En su lugar, comprueba si el objeto implementa los métodos individuales.

Por ejemplo, la clase CanvasItem tiene una visible propiedad. Todas las propiedades expuestas a la API de secuencias de comandos son en realidad pares setter y getter vinculados a nombres. Si alguien intenta acceder  a CanvasItem.visible , Godot realiza las siguientes comprobaciones, en orden:

  • Si el objeto tiene un script adjunto, intentará establecer la propiedad a través del script. Esto le da a los scripts la oportunidad de anular las propiedades definidas en el objeto base anulando los métodos de establecimiento de la propiedad.

  • Si el script no tiene ese atributo, realiza una búsqueda de HashMap en ClassDB contra la clase CanvasItem y todos sus tipos heredados para encontrar el atributo "visible". Si lo encuentra, llama al definidor o captador vinculado. Para obtener más información sobre HashMap, consulte  la documentación de Preferencias de datos .

  • Si no lo encuentra, realiza una verificación explícita para ver si el usuario tenía la intención de acceder a un atributo "script" o "meta".

  • De lo contrario, busca /implementación en CanvasItem y sus tipos derivados _set(según el tipo de acceso). _getEstos métodos pueden realizar la lógica para dar la impresión de que el objeto tiene propiedades. Lo mismo es cierto para los métodos _get_property_list.

    • Tenga en cuenta que esto sucede incluso con nombres de símbolos ilegales, como el atributo "1/tile_name" de un TileSet . Esto se refiere al nombre del mosaico con ID 1, es decir  TileSet.tile_get_name(1).

Como resultado, este sistema tipo DUCK puede localizar propiedades en scripts, en la clase del objeto o en cualquier clase de la que herede el objeto, pero se limita a cosas que amplían el objeto.

Godot proporciona varias opciones para realizar comprobaciones de tiempo de ejecución en estos accesos:

  • Acceso a la propiedad del pato. Estas serán inspecciones de propiedad (mencionadas anteriormente). Si el objeto no admite la operación, la ejecución se detendrá.

    # Todos los objetos tienen métodos get, set y call wrapper tipo pato. 
    get_parent(). set ("visible", false) # Usando un símbolo de acceso, en lugar de una cadena en la llamada al método, # llamará implícitamente al método `set` que, a su vez, llama al método # setter vinculado a la propiedad a través de la búsqueda de propiedad # secuencia. 
    get_parent().visible = false # Tenga en cuenta que si uno define un _set y _get que describen la # existencia de una propiedad, pero la propiedad no se reconoce en ningún método _get_property_list #, entonces los métodos set() y get() funcionarán, pero el símbolo # acceso afirmará que no puede encontrar la propiedad.
    
    
    
    
    
    
    
    
    
    
    
  • comprobación del método. En el caso de CanvasItem.visible , se puede acceder a estos métodos set_visiblecomo is_visiblea cualquier otro método.

    var child = get_child(0) # Búsqueda dinámica. 
    child.call("set_visible", false) # Búsqueda dinámica basada en símbolos. # GDScript crea un alias de esto en un método de 'llamada' entre bastidores. 
    child.set_visible(false) # Búsqueda dinámica, comprueba primero la existencia del método. if child.has_method("set_visible"): 
        child.set_visible(false) # Comprobación de emisión, seguida de búsqueda dinámica. # Útil cuando realiza múltiples llamadas "seguras" sabiendo que la clase # las implementa todas. No hay necesidad de controles repetidos. # Complicado si uno ejecuta una verificación de conversión para un tipo definido por el usuario, ya que # fuerza más dependencias. si el niño es
    
    
    
    
    
    
    
    
    
    
    
    
    
    
     CanvasItem:
        child.set_visible(false) 
        child.show_on_top = true # Si uno no desea fallar estas comprobaciones sin notificar a los usuarios, # puede usar una aserción en su lugar. Estos desencadenarán errores de tiempo de ejecución # inmediatamente si no es cierto. 
    assert(child.has_method("set_visible")) 
    assert(child.is_in_group("offer")) 
    assert(child is CanvasItem) # También puede usar etiquetas de objetos para implicar una interfaz, es decir, asumir que # implementa ciertos métodos. # Hay dos tipos, los cuales solo existen para Nodos: Nombres y # Grupos. # Suponiendo... # Existe un objeto "Quest" y 1) que puede "completar" o "fallar" y
    
    
    
    
    
    
    
    
    
    
    
    
    # que tendrá texto disponible antes y después de cada estado... 
    
    # 1. Use un nombre. 
    var quest = $Quest 
    print(quest.text) 
    quest.complete() # or quest.fail() 
    print(quest.text) # contenido de texto nuevo implícito 
    
    # 2. Use un grupo. 
    for a_child in get_children():
         if a_child.is_in_group("quest"): 
            print(quest.text) 
            quest.complete() # o quest.fail() 
            print(quest.text) # contenido de texto nuevo implícito 
    
    # Tenga en cuenta que estos las interfaces son convenciones específicas del proyecto que 
    define el equipo # (¡lo que significa documentación! ¿Pero tal vez valga la pena?).
    # Cualquier script que se ajuste a la "interfaz" documentada del nombre o 
    # grupo puede reemplazarlo.
    
  • Subcontratar el acceso a Callable. Estos pueden ser útiles en situaciones donde se requiere la máxima libertad de dependencias. En este caso, confíe en el contexto externo para establecer el método.

# child.gd 
extends Node
 var fn = null func my_method():
     if fn: 
        fn.call() # parent.gd extends Node @onready var child = $Child func _ready(): 
    child.fn = print_me 
    child.my_method( ) func print_me(): 
    print(nombre)






 




Estas estrategias contribuyen al diseño flexible de Godot. Entre ellos, los usuarios disponen de una amplia gama de herramientas para satisfacer sus necesidades específicas.

Notificaciones de Godot¶

Cada objeto en Godot implementa un  método de _notificación . Su propósito es que el objeto responda a varias devoluciones de llamada a nivel de motor que pueden estar asociadas con él. Por ejemplo, si el motor le dice  a CanvasItem que "dibuje", llamará al  _notification(NOTIFICATION_DRAW).

Algunas de estas notificaciones, como la pintura, son útiles para anularlas en los scripts. Tanto es así que Godot expone una serie de funciones con funciones dedicadas:

  • _ready():NOTIFICACIÓN_LISTO

  • _enter_tree():NOTIFICACIÓN_ENTRAR_ÁRBOL

  • _exit_tree():NOTIFICACIÓN_ÁRBOL_SALIDA

  • _process(delta):NOTIFICACIÓN_PROCESO

  • _physics_process(delta):NOTIFICACIÓN_FÍSICA_PROCESO

  • _draw():NOTIFICACIÓN_SORTEO

Lo que los usuarios pueden no darse cuenta es que existen notificaciones para otros tipos además de Node, por ejemplo:

Muchas de las devoluciones de llamada que existen en Nodes no tienen ningún método dedicado, pero siguen siendo muy útiles.

Se puede acceder a  _notificationtodas estas notificaciones personalizadas desde métodos comunes.

NOTA: Los métodos marcados como "virtuales" en la documentación también están destinados a ser anulados por secuencias de comandos.

Un ejemplo clásico es el método _init en Object . Aunque no existe un  NOTIFICATION_*equivalente, el motor sigue llamando al método. La mayoría de los lenguajes (excepto C#) se basan en él como constructor.

Entonces, ¿cuándo se deben usar estas notificaciones o funciones virtuales?

_proceso y _física_proceso y * _entrada¶

_processSe utiliza cuando se requiere un tiempo delta dependiente de la velocidad de fotogramas entre fotogramas. Este es el lugar correcto si el código que actualiza los datos del objeto debe actualizarse con la mayor frecuencia posible. Las verificaciones de lógica de bucle y el almacenamiento en caché de datos generalmente se realizan aquí, pero todo se reduce a la frecuencia con la que se deben evaluar las actualizaciones. Si no necesitan ejecutar cada fotograma, otra opción es implementar un bucle de tiempo de espera de rendimiento de temporizador.

# Bucle infinito, pero solo se ejecuta cada vez que se activa el temporizador. 
# Permite operaciones recurrentes que no activan la lógica del script 
# cada cuadro (o incluso cada cuadro fijo). 
while true: 
    my_method() 
    $Timer.start() 
    yield($Timer, "timeout")

_physics_processSe utiliza cuando se desea un tiempo delta independiente de la velocidad de fotogramas entre fotogramas. Este es el lugar correcto si el código debe actualizarse de manera constante a lo largo del tiempo, sin importar qué tan rápido o lento avance el tiempo. Las operaciones de transformación de objetos y cinemática cíclica deben realizarse aquí.

Mientras sea posible, para obtener el mejor rendimiento, debe evitar la verificación de entrada durante estas devoluciones de llamada. _process_physics_processdisparará en cada oportunidad (no "descansan" por defecto). En cambio, *_inputla devolución de llamada solo se activará en los marcos en los que el motor realmente detecte la entrada.

La acción de entrada en la devolución de llamada de entrada se puede verificar de manera similar. Si se va a utilizar el tiempo delta, se puede obtener del método de tiempo delta asociado según sea necesario.

# Llamado a cada cuadro, incluso cuando el motor no detecta ninguna entrada. 
func _process(delta):
     if Input.is_action_just_pressed("ui_select"): 
        print(delta) # Llamado durante cada evento de entrada. func _unhandled_input(evento):
     coincide con event.get_class(): 
        "InputEventKey": if Input.is_action_just_pressed("ui_accept"): 
                print(get_process_delta_time())



            

_init e inicialización y exportación¶

Si el script inicializa su propio subárbol de nodos sin una escena, ese código debería ejecutarse aquí. Aquí también deberían ejecutarse otras propiedades o inicializaciones independientes de SceneTree. _readyEsto se activa antes de o _enter_tree, pero después de que se crea el script y se inicializan sus propiedades.

Se pueden producir tres tipos de asignaciones de propiedades durante la instanciación del script:

# "uno" es un "valor inicializado". Estos NO activan al colocador. 
# Si alguien establece el valor como "dos" desde el Inspector, este sería un 
# "valor exportado". Estos SÍ disparan al colocador. 
export(String) var test = "one" setget set_test func _init():
     # "tres" es un "valor de asignación inicial". # Estos NO activan el colocador, pero... 
    test = "tres" # Estos SÍ activan el colocador. Tenga en cuenta el prefijo `self`. 
    self.test = "tres" func set_test(valor): 
    prueba = valor 
    print("Configuración: ", prueba)


    
    


Al instanciar una escena, los valores de propiedad se establecen en el siguiente orden:

  1. Asignación de valor inicial: la creación de instancias asignará un valor de inicialización o un valor de asignación inicial. Las asignaciones de inicialización tienen prioridad sobre los valores de inicialización.

  2. Asignación de valor de exportación: si se instancia desde una escena en lugar de un guión, Godot asignará un valor de exportación para reemplazar el valor inicial definido en el guión.

Por lo tanto, crear instancias de guiones y escenas afectará tanto la inicialización como la cantidad de veces que el motor llama a los setters.

_ready y _enter_tree y NOTIFICATION_PARENTED¶

Al instanciar una escena conectada a la primera escena en ejecución, Godot instanciará los nodos en el árbol (hará llamadas _init) y construirá el árbol desde la raíz hacia abajo. Esto hace que _enter_treelas llamadas caigan en cascada por el árbol. Llamado por los nodos de hoja una vez que el árbol está completo _ready. Los nodos llamarán a este método una vez que todos los nodos secundarios hayan terminado de llamar a sus métodos. Esto provoca una cascada inversa de regreso a la raíz del árbol.

Al crear una instancia de un guión o una escena independiente, los nodos no se agregan al SceneTree en la creación, por lo que _enter_treeno se activan las devoluciones de llamada. En su lugar, simplemente _initllame al . _enter_treeOcurre y se llama cuando se agrega una escena al SceneTree _ready.

Si necesita desencadenar un comportamiento que ocurre como padre de otro nodo, ya sea que ocurra o no como parte de la escena principal/activa, puede usar las notificaciones PARENTED . Por ejemplo, aquí hay un fragmento que conecta el método de un nodo a una señal personalizada en el nodo principal sin fallar. Útil para nodos centrados en datos que pueden crearse en tiempo de ejecución.

extiende Nodo var parent_cache func connection_check():
     return parent_cache.has_user_signal("interactuó_con") func _notification(qué):
     coincide con qué: 
        NOTIFICATION_PARENTED: 
            parent_cache = get_parent() if connection_check(): 
                parent_cache.interacted_with.connect(_on_parent_interacted_with) 
        NOTIFICATION_UNPARENTED: if connection_check(): 
                parent_cache.interacted_with.disconnect(_on_parent_interacted_with) func 
    print("¡Estoy reaccionando a la interacción de mis padres!")






            
            

 _on_parent_interacted_with():

Preferencias de datos¶

¿Alguna vez se preguntó si la estructura de datos Y o Z debería usarse para resolver el problema X? Este artículo cubre una variedad de temas relacionados con estos dilemas.

Nota: Este artículo se refiere a la operación "[algo]-tiempo". Este término proviene de la notación Big O del análisis de algoritmos  .

Para resumir, describe el peor de los casos para la duración del tiempo de ejecución. En términos sencillos:

"A medida que aumenta el tamaño del dominio del problema, el tiempo que tarda en ejecutarse el algoritmo..."

  • Tiempo constante, O(1): "...no aumenta".

  • Tiempo logarítmico: "...aumenta a un ritmo lento".O(log n)

  • Tiempo lineal O(n): "...aumenta al mismo ritmo".

  • ETC.

Imagínese si tuviera que procesar 3 millones de puntos de datos en un marco. No es posible utilizar algoritmos de tiempo lineal para crear funciones, porque el tamaño de los datos aumenta el tiempo de ejecución mucho más allá del tiempo asignado. En cambio, la operación se puede manejar sin problemas utilizando algoritmos de tiempo constante.

En general, los desarrolladores quieren evitar en la medida de lo posible participar en operaciones de tiempo lineal. Sin embargo, si el tamaño de la operación de tiempo lineal se mantiene pequeño y no es necesario realizar la operación con mucha frecuencia, entonces puede ser aceptable. Equilibrar estos requisitos y elegir la estructura de datos/algoritmo correcta para el trabajo es parte de lo que hace que la habilidad de un programador sea valiosa.

Arrays vs Diccionarios vs Objetos¶

Godot almacena todas las variables en la API de secuencias de comandos en  la clase Variant . Variants puede almacenar estructuras de datos compatibles con Variant, como  Array and Dictionary y Objects .

Godot implementa Array como Vector<Variant>.El motor almacena los contenidos de la matriz en partes contiguas de la memoria, es decir, están uno al lado del otro en una fila.

Nota: Para quienes no estén familiarizados con C++, Vector es el nombre de un objeto de matriz en la biblioteca tradicional de C++. Es un tipo de "plantilla", lo que significa que sus registros solo pueden contener ciertos tipos (indicados entre paréntesis angulares). Entonces, por ejemplo,  PackedStringArray es similar a Vector<String>.

El almacenamiento de memoria contigua significa el siguiente rendimiento operativo:

  • Iteración: la más rápida. Ideal para bucles.

    • Op: todo lo que hace es incrementar un contador para obtener el siguiente registro.

  • Insertar, borrar, mover: depende de la posición. Generalmente más lento.

    • Op: Agregar/eliminar/mover contenido implica mover registros adyacentes (para hacer espacio/rellenar espacio).

    • Agregar/eliminar rápidamente desde el final .

    • Añadir/eliminar lentamente desde cualquier posición .

    • Agregar/quitar desde el frente es lo más lento.

    • Si realiza múltiples inserciones/retiros desde el frente, entonces...

      1. Invierta la matriz.

      2. Haga un bucle y realice los cambios de matriz al final .

      3. Invierta la matriz.

      Esto solo hará 2 copias de la matriz (aún en tiempo constante, pero más lento), mientras copia aproximadamente la mitad de la matriz, en promedio, N veces (tiempo lineal).

  • Get, Set: Más rápido por ubicación . Por ejemplo, puede solicitar los registros 0, 2, 10, etc., pero no puede especificar qué registros desea.

    • Op: 1 operación de suma desde el comienzo de la matriz hasta el índice deseado.

  • Búsqueda: más lenta. El índice/posición del valor de identidad.

    • Op: tiene que iterar a través de la matriz y comparar valores hasta que se encuentre una coincidencia.

      • El rendimiento también depende de si se requiere una búsqueda exhaustiva.

    • Una operación de búsqueda personalizada puede llegar a un tiempo logarítmico (relativamente rápido) si se mantiene en orden. Sin embargo, los usuarios legos no estarán contentos con esto. Hágalo reordenando la matriz después de cada edición y escribiendo un algoritmo de búsqueda consciente del orden.

Godot implementa Diccionario como una pequeña matriz (inicializada en 2^3 u 8 registros) donde el motor almacena pares clave-valor. Cuando una persona intenta acceder a un valor, le proporciona una clave. Luego codifica la clave, es decir, la convierte en un número. "Hash" se utiliza para calcular el índice en la matriz. Como matriz, OHM realiza una búsqueda rápida en una "tabla" de claves asignadas a valores. Cuando el HashMap se llena demasiado, aumenta a la siguiente potencia de 2 (es decir, 16 registros, luego 32, etc.) y reconstruye la estructura.OrderedHashMap<Variant, Variant>

Hashing se realiza para reducir la posibilidad de colisiones de claves. Si esto sucede, la tabla debe volver a calcular otro índice para obtener el valor teniendo en cuenta la posición anterior. En conjunto, esto da como resultado un acceso en tiempo constante a todos los registros a expensas de la memoria y algunas eficiencias operativas menores.

  1. Cada clave se codifica cualquier número de veces.

    • Hashing es un tiempo constante, por lo que incluso si un algoritmo tiene que hacerse más de una vez, siempre que la cantidad de hashes no dependa demasiado de la densidad de la tabla, las cosas seguirán siendo rápidas. Esto lleva a...

  2. Mantenga el tamaño de la tabla creciendo.

    • HashMaps mantiene intencionalmente los espacios de memoria no utilizada esparcidos por la tabla para reducir las colisiones de hash y mantener la velocidad de acceso. Es por eso que su tamaño sigue creciendo cuadráticamente a la potencia de 2.

Los diccionarios, como se podría decir, se especializan en tareas en las que las matrices no son buenas. Los detalles de su funcionamiento se describen a continuación:

  • Iteraciones: Rápido.

    • Op: El vector hash interno del mapa iterado. devuelve cada clave. Posteriormente, el usuario utiliza la tecla para saltar y volver al valor deseado.

  • Insertar, borrar, mover: más rápido.

    • Op: hash la clave dada. Realice 1 suma para encontrar el valor apropiado (inicio de matriz + desplazamiento). Los movimientos son dos de ellos (uno insertar, uno borrar). El mapa debe someterse a algún mantenimiento para mantener su funcionalidad:

      • Actualizar una lista ordenada de registros.

      • Determine si los requisitos de densidad de la mesa requieren capacidad de mesa extendida.

    • El diccionario recuerda el orden en que el usuario insertó sus claves. Esto le permite realizar iteraciones confiables.

  • Obtener, Establecer: Más rápido. Igual que la búsqueda por clave .

    • Op: Igual que Insertar/Borrar/Mover.

  • Búsqueda: más lenta. Clave que identifica un valor.

    • Op: tiene que iterar a través de registros y comparar valores hasta que se encuentre una coincidencia.

    • Tenga en cuenta que Godot no proporciona esta funcionalidad de fábrica (ya que no son adecuados para esta tarea).

Godot implementa objetos como contenedores de contenido de datos tontos pero dinámicos. Los objetos consultan las fuentes de datos al hacer preguntas. Por ejemplo, para responder a la pregunta "¿Tiene una propiedad llamada 'posición'?", podría preguntar su secuencia de comandos o ClassDB . Uno puede encontrar más información sobre qué son los objetos y cómo funcionan en el artículo Aplicando principios orientados a objetos en Godot .

El detalle importante aquí es la complejidad de la tarea del objeto. Cada vez que se ejecuta una de estas consultas de múltiples fuentes, ejecuta varios  bucles de iteración y búsquedas de HashMap. Además, las consultas son operaciones de tiempo lineales, según el tamaño de la jerarquía de herencia del objeto. Si la clase consultada por Object (su clase actual) no encuentra nada, la solicitud se difiere a la siguiente clase base, hasta llegar a la clase Object original. Si bien estas son operaciones rápidas de forma aislada, el hecho de que tenga que hacer tantas comprobaciones las hace más lentas que ambas alternativas para encontrar datos.

Nota: Cuando los desarrolladores mencionan lo lenta que es la API de secuencias de comandos, se refieren a esta cadena de consulta. Las operaciones de secuencias de comandos de la API inevitablemente toman más tiempo que el código compilado de C++ donde la aplicación sabe exactamente dónde buscar cualquier cosa. Deben encontrar la fuente de cualquier dato relevante antes de intentar acceder a él.

GDScript es lento porque cada operación que realiza pasa por este sistema.

C# puede manejar algunas cosas a mayor velocidad con un código de bytes más optimizado. Sin embargo, si una secuencia de comandos de C# llama al contenido de la clase de motor, o si la secuencia de comandos intenta acceder a contenido fuera de ella, pasará por esta canalización.

NativeScript C++ va un paso más allá y mantiene todo interno por defecto. Las llamadas a estructuras externas se realizarán a través de la API de secuencias de comandos. En NativeScript C++, registrar métodos para exponerlos a la API de secuencias de comandos es una tarea manual. Es en este punto que las clases externas que no son de C++ usarán la API para ubicarlas.

Entonces, suponiendo que uno se extiende desde la referencia para crear una estructura de datos como una matriz o un diccionario, ¿por qué elegir un objeto sobre las otras dos opciones?

  1. Control: con los objetos viene la capacidad de crear estructuras más complejas. Los datos se pueden abstraer jerárquicamente para garantizar que las API externas no cambien a medida que cambian las estructuras de datos internas. Además, los objetos pueden tener señales, lo que permite un comportamiento reactivo.

  2. Claridad: los objetos son fuentes confiables de datos cuando se trata de los datos que las clases de secuencias de comandos y motores definen para ellos. Es posible que una propiedad no contenga el valor que esperamos, pero no debe preocuparse por si la propiedad existe.

  3. Conveniencia: si uno ya tiene en mente una estructura de datos similar, extender desde una clase existente hace que la tarea de construir la estructura de datos sea mucho más fácil. Por el contrario, las matrices y los diccionarios no satisfacen todos los casos de uso que uno pueda tener.

Los objetos también brindan a los usuarios la oportunidad de crear estructuras de datos más especializadas. Con él, uno puede diseñar sus propias listas, árboles de búsqueda binarios, montones, árboles de distribución, gráficos, conjuntos disjuntos y cualquier otra opción.

"¿Por qué no usar Node para estructuras de árbol?", podría preguntarse uno. Bueno, la clase Node contiene cosas que no tienen nada que ver con estructuras de datos personalizadas. Por lo tanto, puede resultar útil crear sus propios tipos de nodos al crear estructuras de árbol.

extiende Object
 class_name TreeNode var _parent: TreeNode = null
 var _children: = [] setget func _notification(p_what):
     match p_what: 
        NOTIFICATION_PREDELETE: # Destructor. para un_niño en _niños: 
                un_niño.free()




            
            

A partir de aquí, las personas pueden crear sus propias estructuras con funciones específicas, limitadas solo por su imaginación.

Enumeraciones: int y cadena¶

La mayoría de los idiomas ofrecen opciones para los tipos enumerados. GDScript no es diferente, pero a diferencia de la mayoría de los otros lenguajes, permite números enteros o cadenas como valores de enumeración (este último solo cuando se usan exportpalabras clave en GDScript). La pregunta entonces es, "¿Cuál debo usar?"

La respuesta corta es, "cuál es más cómodo para ti". Esta es una característica específica de GDScript, no del script de Godot en general; estos lenguajes priorizan la usabilidad sobre el rendimiento.

En un nivel técnico, las comparaciones de enteros (tiempo constante) ocurrirán más rápido que las comparaciones de cadenas (tiempo lineal). Si desea mantener las convenciones de otros idiomas, debe usar números enteros.

 El principal problema con el uso de números enteros surge cuando se desea imprimir valores de enumeración . Como números enteros, intentar imprimir MY_ENUM se imprimirá  5o lo que sea, en lugar de algo como: "MyEnum"Para imprimir enumeraciones enteras, se debe escribir un diccionario para mapear el valor de cadena correspondiente de cada enumeración.

Si el propósito principal de usar enumeraciones es imprimir valores y desea agruparlos como conceptos relacionados, entonces tiene sentido usarlos como cadenas. De esta forma, no hay necesidad de una estructura de datos separada para realizar la impresión.

AnimatedTexture vs. AnimatedSprite2D vs. AnimationPlayer vs. AnimationTree

¿Cuándo se debe usar cada una de las clases de animación de Godot? La respuesta puede no ser clara de inmediato para los nuevos usuarios de Godot.

Las texturas animadas son texturas que el motor dibuja como bucles de animación en lugar de imágenes estáticas. Los usuarios pueden manipular...

  1. La velocidad (fps) a la que se mueve a través de cada parte de la textura.

  2. El número de regiones contenidas en la textura (marco).

El RenderingServer de Godot luego renderiza las regiones secuencialmente a una velocidad específica. La buena noticia es que esto no implica una lógica adicional por parte del motor. La mala noticia es que los usuarios tienen muy poco control.

Tenga en cuenta también que, a diferencia de los otros objetos de nodo discutidos aquí , AnimatedTexture es un recurso . Es posible crear un nodo Sprite2D que use una AnimatedTexture como su textura . O (algo que nadie más puede hacer) puede agregar AnimatedTextures como mosaicos en un TileSet e integrarlo con  un TileMap para muchos fondos animados automáticamente, todo renderizado en una sola llamada de sorteo por lotes.

El nodo AnimatedSprite2D combinado con  el activo SpriteFrames permite crear varias secuencias de animación a través de hojas de sprites, alternar entre animaciones y controlar su velocidad, compensación de área y orientación. Esto los hace ideales para controlar animaciones 2D basadas en fotogramas.

Si necesita desencadenar otros efectos relacionados con los cambios de animación (como crear efectos de partículas, llamar a funciones o manipular otros elementos periféricos además de las animaciones basadas en cuadros), debe usar el nodo AnimationPlayer con AnimatedSprite2D .

Si desea diseñar un sistema de animación 2D más complejo, por ejemplo, AnimationPlayer también es la herramienta que debe utilizar.

  1. Cortar animación: edita la transformación de un sprite en tiempo de ejecución.

  2. Animación de malla 2D: defina regiones y equipe un esqueleto para la textura de un sprite. Luego anime el esqueleto, estirando y doblando la textura de acuerdo con la relación entre los huesos.

  3. Una mezcla de lo anterior.

Si bien se requiere un AnimationPlayer para cada secuencia de animación individual diseñada para el juego, también es útil combinar animaciones para mezclarlas, es decir, lograr transiciones suaves entre ellas. También puede haber una jerarquía entre animaciones diseñadas para objetos. Aquí es donde brilla AnimationTree. Puede encontrar una guía detallada sobre el uso de AnimationTree aquí.

Preferencias lógicas¶

¿Alguna vez se preguntó si se debe usar la estrategia Y o la estrategia Z para resolver el problema X? Este artículo cubre una variedad de temas relacionados con estos dilemas.

Agregar nodos y cambiar propiedades: ¿qué viene primero?

Al inicializar un nodo desde un script en tiempo de ejecución, es posible que deba cambiar propiedades como el nombre o la ubicación del nodo. Un dilema común es ¿cuándo debería cambiar estos valores?

Es una buena práctica cambiar el valor en un nodo antes de agregarlo al árbol de la escena. Los setters de algunas propiedades tienen código que actualiza otros valores correspondientes, ¡y ese código puede ser lento! En la mayoría de los casos, este código no afectará el rendimiento de su juego, pero en casos de uso intensivo, como la generación de procedimientos, puede ralentizar su juego.

Por estas razones, siempre es una buena práctica establecer el valor inicial de un nodo antes de agregarlo al árbol de la escena.

Cargando y precargando¶

En GDScript, hay un  método de precarga global . Carga los recursos antes de tiempo para avanzar en las operaciones de "carga" y evitar cargar recursos en medio del código sensible al rendimiento.

Su contraparte, el método de carga, carga el recurso solo cuando se alcanza una declaración de carga. Es decir, cargará los recursos en el lugar, lo que puede causar ralentizaciones cuando ocurre en medio de procesos sensibles. Esta función también es un alias para ResourceLoader.load(ruta) accesible para todos los lenguajes de secuencias de comandos .load

Entonces, ¿cuándo ocurren exactamente la precarga y la carga, y cuándo se debe usar una de ellas? Veamos un ejemplo:

# my_buildings.gd 
extiende el nodo # Tenga en cuenta cómo los scripts/escenas constantes tienen un esquema de nombres diferente que # sus variantes de propiedad. # Este valor es una constante, por lo que se genera cuando se carga el objeto Script. # El script está precargando el valor. La ventaja aquí es que el editor # puede ofrecer autocompletado ya que debe ser una ruta estática. const BuildingScn = preload("res://building.tscn") # 1. La secuencia de comandos precarga el valor, por lo que se cargará como una dependencia # del archivo de secuencia de comandos 'my_buildings.gd'. Pero, debido a que se trata de una propiedad # en lugar de una constante, el objeto no copiará el recurso # PackedScene precargado en la propiedad hasta que la secuencia de comandos cree una instancia













# con .nuevo(). 
# 
# 2. El valor precargado es inaccesible desde el objeto Script solo. Como 
# tal, precargar el valor aquí en realidad no beneficia a nadie. 
# 
# 3. Debido a que el usuario exporta el valor, si esta secuencia de comandos se almacena en 
# un nodo en un archivo de escena, el código de instanciación de escena sobrescribirá el 
valor inicial # precargado de todos modos (desperdiciándolo). Por lo general, es mejor 
# proporcionar valores predeterminados nulos, vacíos o no válidos para las exportaciones. 
# 
# 4. Es cuando uno instancia este script por sí mismo con .new() que 
# uno cargará "office.tscn" en lugar del valor exportado. 
export(PackedScene) var a_building = preload("office.tscn")

# ¡UH oh! ¡Esto da como resultado un error! 
# Uno debe asignar valores constantes a las constantes. Debido a que `load` realiza una 
# búsqueda en tiempo de ejecución por su propia naturaleza, no se puede usar para inicializar una 
# constante. 
const OfficeScn = load("res://office.tscn") # ¡Se carga correctamente y solo cuando se crea una instancia del script! ¡Hurra! var office_scn = load("res://office.tscn")



La precarga permite que la secuencia de comandos maneje toda la carga cuando se carga la secuencia de comandos. La precarga es útil, pero hay momentos en los que no es deseable. Para diferenciar estas situaciones, considere lo siguiente:

  1. La carga previa de activos (especialmente escenas o guiones) puede causar más cargas de las esperadas si no hay forma de determinar cuándo se carga el guión. Esto puede causar tiempos de carga no intencionales de longitud variable además de la operación de carga del script original.

  2. No tiene sentido precargar el valor si algo más puede reemplazarlo (como la inicialización de exportación de una escena). Este no es un factor importante si planea crear siempre los scripts usted mismo.

  3. Si solo desea "importar" otro activo de clase (guión o escena), la mejor práctica suele ser usar constantes de precarga. Sin embargo, hay casos especiales en los que uno podría no querer hacer esto:

    1. Si la clase "importada" está sujeta a cambios, entonces debería ser una propiedad, exportinicializada con un o un load(tal vez ni siquiera inicializada hasta más tarde).

    2. Si su secuencia de comandos necesita muchas dependencias y no desea consumir tanta memoria, es posible que desee cargar y descargar varias dependencias en tiempo de ejecución a medida que cambia la situación. Si los recursos están precargados en constantes, la única forma de descargar esos recursos es descargar todo el script. Si son propiedades cargadas, puede configurarlas nully eliminar por completo todas las referencias al recurso (como un  tipo de extensión RefCounted , esto hará que el recurso se elimine de la memoria).

Grandes niveles: Estático vs. Dinámico¶

Si quieres crear un gran nivel, ¿qué situación es la más adecuada? ¿Deberían crear el nivel como un espacio estático? ¿O deberían cargar el nivel en partes y mover el contenido del mundo según sea necesario?

Bueno, la respuesta corta es "cuando el rendimiento lo exige". El dilema asociado con estas dos opciones es una antigua elección de programación: ¿una optimiza la velocidad de la memoria o al revés?

La respuesta ingenua es usar niveles estáticos que carguen todo a la vez. Sin embargo, dependiendo del proyecto, esto puede consumir mucha memoria. Desperdiciar la memoria RAM del usuario puede hacer que los programas se ejecuten lentamente o se bloqueen por completo con todas las demás cosas que la computadora está tratando de hacer al mismo tiempo.

Independientemente, las escenas más grandes deben dividirse en otras más pequeñas (para ayudar a la reutilización de los activos). Luego, los desarrolladores pueden diseñar un nodo para administrar la creación/carga y la eliminación/descarga de recursos y nodos en tiempo real. Los juegos con entornos grandes y variados o elementos generados por procedimientos a menudo implementarán estas estrategias para evitar desperdiciar memoria.

Por otro lado, los sistemas dinámicos son más complejos de codificar, es decir, usan más lógica de programación, lo que conduce a errores y oportunidades de errores. Si no tienen cuidado, pueden desarrollar un sistema que infla la deuda técnica de la aplicación.

Por lo tanto, su mejor apuesta es...

  1. Use niveles estáticos para juegos pequeños.

  2. Si alguien tiene el tiempo o los recursos para jugar un juego mediano o grande, cree una biblioteca o complemento para codificar la gestión de nodos y recursos. Si se mejora con el tiempo para mejorar la usabilidad y la estabilidad, puede convertirse en una herramienta confiable en todos los proyectos.

  3. Escribir código de lógica dinámica para un juego mediano/grande, porque uno tiene habilidades de codificación pero no tiene tiempo ni recursos para perfeccionar el código (el juego debe terminarse). Podría refactorizarse más tarde para subcontratar el código en un complemento.

Consulte la documentación Cambio manual de escenas para ver ejemplos de las diversas formas de intercambiar escenas en tiempo de ejecución  .

Organización del proyecto¶

Introducción¶ _

Dado que Godot no tiene restricciones en la estructura del proyecto o el uso del sistema de archivos, la organización de los archivos al aprender el motor puede parecer un desafío. El flujo de trabajo sugerido por este tutorial debería ser un buen punto de partida. También cubriremos cómo usar Godot para el control de versiones.

Organización¶ _

Godot está inherentemente basado en escenas y utiliza el sistema de archivos tal cual, sin metadatos ni base de datos de activos.

A diferencia de otros motores, muchos activos están contenidos dentro de la propia escena, por lo que la cantidad de archivos en el sistema de archivos es mucho menor.

Con esto en mente, el enfoque más común es agrupar activos lo más cerca posible de la escena; facilita el mantenimiento a medida que crece el proyecto.

Por ejemplo, las personas a menudo pueden colocar sus activos básicos (como imágenes de sprites, mallas de modelos 3D, materiales y música, etc.) en una carpeta. Luego pueden usar una carpeta separada para almacenar los niveles de compilación que los usan.

/project.godot 
/docs/.gdignore # Consulte "Ignorar carpetas específicas" a continuación 
/docs/learning.html 
/models/town/house/house.dae 
/models/town/house/window.png 
/models/town/house/ puerta.png 
/personajes/jugador/cubio.dae 
/personajes/jugador/cubio.png 
/personajes/enemigos/goblin/goblin.dae 
/personajes/enemigos/goblin/goblin.png 
/personajes/npcs/suzanne/suzanne.dae 
/ personajes/npcs/suzanne/suzanne.png 
/niveles/riverdale/riverdale.scn

Guía de moda¶

Para mantener la coherencia entre los proyectos, recomendamos seguir estas pautas:

  • Utilice snake_case para los nombres de carpetas y archivos (excepto para los scripts de C#). Esto evita problemas de distinción entre mayúsculas y minúsculas que pueden surgir después de exportar proyectos en Windows. Los scripts de C# son una excepción a esta regla, porque la convención es nombrarlos según los nombres de clase que deberían estar en PascalCase.

  • Utilice PascalCase para los nombres de los nodos , ya que coincide con el caso del nodo integrado.

  • En general, mantenga los recursos de terceros en la addons/carpeta de nivel superior, incluso si no son complementos del editor. Esto facilita el seguimiento de qué archivos son archivos de terceros. Hay algunas excepciones a esta regla; por ejemplo, si está utilizando activos de juegos de terceros para su personaje, tiene más sentido incluirlos en la misma carpeta que las escenas y guiones de su personaje.

Entrada¶ _

Las versiones de Godot anteriores a la 3.0 realizaban el proceso de importación desde archivos fuera del proyecto. Si bien esto es útil en proyectos más grandes, crea un dolor de cabeza organizativo para la mayoría de los desarrolladores.

Como resultado, los activos ahora se pueden importar de forma transparente desde la carpeta del proyecto.

Ignorar carpetas específicas¶

Para evitar que Godot importe archivos contenidos en una carpeta específica, cree un archivo vacío en esa carpeta llamado (requiere .gdignoreel encabezado). .Esto es útil para acelerar la importación inicial del proyecto.

notas

Para crear un archivo con un nombre que comience con un punto en Windows, puede usar un editor de texto como Notepad ++ o usar el siguiente comando en el símbolo del sistema:type nul > .gdignore

Una vez que se ignora una carpeta, los recursos de esa carpeta ya no se pueden usar load()ni preload()cargar el método. Ignorar una carpeta también la oculta automáticamente del sistema de archivos, lo que es útil para reducir el desorden.

Tenga en cuenta que .gdignorese ignorará el contenido del archivo, por lo que el archivo debe estar vacío. No .gitignoreadmite esquemas como lo hacen los archivos.

Sensibilidad a mayúsculas y minúsculas¶

Windows y las versiones recientes de macOS utilizan un sistema de archivos que distingue entre mayúsculas y minúsculas de forma predeterminada, mientras que las distribuciones de Linux utilizan un sistema de archivos que distingue entre mayúsculas y minúsculas de forma predeterminada. Esto puede causar problemas después de exportar el proyecto, ya que el sistema de archivos virtual PCK de Godot distingue entre mayúsculas y minúsculas. Para evitar esto, se recomienda ceñirse a  snake_casenombrar todos los archivos en su proyecto (generalmente con caracteres en minúsculas).

Nota: puede infringir esta regla cuando la guía de estilo indique lo contrario (como la guía de estilo de C#). Aún así, sé constante para evitar errores.

En Windows 10, para evitar errores relacionados con la distinción entre mayúsculas y minúsculas, también puede hacer que las carpetas de proyectos distingan entre mayúsculas y minúsculas. Después de habilitar la función Subsistema de Windows para Linux, ejecute el siguiente comando en una ventana de PowerShell:

# Para habilitar la distinción entre mayúsculas y minúsculas: 
fsutil file setcaseSensibleinfo <ruta a la carpeta del proyecto> enable # Para deshabilitar la distinción entre mayúsculas y minúsculas: 
fsutil file setcaseSensibleinfo <ruta a la carpeta del proyecto> disabled


Si no ha habilitado el subsistema de Windows para Linux, puede ingresar la siguiente línea en una ventana de PowerShell , ejecutar como administrador y luego reiniciar cuando se le solicite:

Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux

Sistema de control de versiones¶

Introducción¶ _

Godot pretende ser compatible con VCS y producir archivos en su mayoría legibles y combinables. Godot también admite sistemas de control de versiones en el editor. Sin embargo, VCS en el editor requiere un complemento para el VCS específico que está utilizando. VCS se puede configurar o desactivar en el editor en Proyecto > Control de versiones.

​​

Complementos oficiales de Git¶

El complemento oficial admite el uso de Git desde el editor. Puede encontrar la última versión aquí . La documentación sobre cómo usar el complemento de Git se puede encontrar aquí .

Archivos para excluir de VCS¶

Godot crea automáticamente algunos archivos y carpetas. Debe agregarlos a su VCS ignorar:

  • .godot/: Esta carpeta almacena varios datos de caché de proyectos. .godot/imported/Todos los archivos importados automáticamente por el motor de almacenamiento en función de sus activos de origen y sus indicadores de importación. .godot/editor/Contiene datos sobre el estado del editor, como los archivos de script abiertos actualmente y los nodos usados ​​recientemente.

  • *.translation: estos archivos son traducciones de importación binaria generadas a partir de archivos CSV.

  • export_presets.cfg: este archivo contiene todos los ajustes preestablecidos de exportación para el proyecto, incluida información confidencial, como las credenciales del almacén de claves de Android.

  • .mono/: esta carpeta almacena archivos mono generados automáticamente. Solo existe en proyectos que usan la versión Mono de Godot.

NOTA: Guarde este archivo .gitignore en la carpeta raíz de su proyecto para establecer automáticamente las exclusiones de archivos.

Usando Git en Windows¶

La mayoría de los clientes de Git para Windows están configurados core.autocrlfcon true.Esto puede hacer que los archivos se marquen innecesariamente como modificados por Git porque sus finales de línea se convierten automáticamente. Lo mejor es configurar esta opción para:

git config --global core.entrada autocrlf

Problemas conocidos¶

¡Cierre siempre el editorgit pull antes de ejecutar ! De lo contrario,  puede perder datos si sincroniza el archivo mientras el editor está abierto .

Supongo que te gusta

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