Árboles de comportamiento para proyectos Unity3D. Escrito con un enfoque basado en código para maximizar la mantenibilidad en grandes proyectos con el patrón de constructor. Inspirado en el árbol de comportamiento con fluidez.
Características
Apoyo
Únase a la comunidad de Discord si tiene preguntas o necesita ayuda.
Vea las próximas características y el progreso del desarrollo en el tablero de Trello.
Al crear árboles, necesitará almacenarlos en una variable para almacenar correctamente todos los datos necesarios.
using UnityEngine ;
using CleverCrow . Fluid . BTs . Tasks ;
using CleverCrow . Fluid . BTs . Trees ;
public class MyCustomAi : MonoBehaviour {
[ SerializeField ]
private BehaviorTree _tree ;
private void Awake ( ) {
_tree = new BehaviorTreeBuilder ( gameObject )
. Sequence ( )
. Condition ( " Custom Condition " , ( ) => {
return true ;
} )
. Do ( " Custom Action " , ( ) => {
return TaskStatus . Success ;
} )
. End ( )
. Build ( ) ;
}
private void Update ( ) {
// Update our tree every frame
_tree . Tick ( ) ;
}
}
Dependiendo de lo que regrese para un estado de tarea, sucederán diferentes cosas.
tree.Tick()
reiniciará el árbol si no hay otros nodos para ejecutartree.Tick()
. El árbol rastrea una referencia del puntero y solo se puede limpiar si tree.Reset()
se llama. Mientras su variable de almacenamiento de árboles esté configurada como public
o tenga un atributo SerializeField
. Podrás imprimir una visualización de tu árbol mientras el juego se ejecuta en el editor. Tenga en cuenta que no puede ver los árboles mientras el juego no se está ejecutando. Como el árbol tiene que ser construido para visualizar.
Puede agregar de forma segura un nuevo código a sus árboles de comportamiento con varias líneas. Permitiéndole personalizar BTS mientras admite futuras actualizaciones de versión.
using UnityEngine ;
using CleverCrow . Fluid . BTs . Tasks ;
using CleverCrow . Fluid . BTs . Tasks . Actions ;
using CleverCrow . Fluid . BTs . Trees ;
public class CustomAction : ActionBase {
protected override TaskStatus OnUpdate ( ) {
Debug . Log ( Owner . name ) ;
return TaskStatus . Success ;
}
}
public static class BehaviorTreeBuilderExtensions {
public static BehaviorTreeBuilder CustomAction ( this BehaviorTreeBuilder builder , string name = " My Action " ) {
return builder . AddNode ( new CustomAction { Name = name } ) ;
}
}
public class ExampleUsage : MonoBehaviour {
public void Awake ( ) {
var bt = new BehaviorTreeBuilder ( gameObject )
. Sequence ( )
. CustomAction ( )
. End ( ) ;
}
}
El árbol de comportamiento de fluidos se usa a través del Administrador de paquetes de Unity. Para usarlo, necesitará agregar las siguientes líneas a su archivo Packages/manifest.json
. Después de eso, podrá controlar visualmente qué versión específica del árbol de comportamiento de fluidos que está utilizando desde la ventana del administrador de paquetes en Unity. Esto debe hacerse para que su editor de unidades pueda conectarse al registro de paquetes de NPM.
{
"scopedRegistries" : [
{
"name" : " NPM " ,
"url" : " https://registry.npmjs.org " ,
"scopes" : [
" com.fluid "
]
}
],
"dependencies" : {
"com.fluid.behavior-tree" : " 2.2.0 "
}
}
Los archivos de versiones específicas y notas de lanzamiento están disponibles en la página de versiones.
Es posible que desee ver el proyecto Capture the Flag Ejemplo para un ejemplo de trabajo de cómo se puede usar el árbol de comportamiento de fluidos en su proyecto. Demuestra el uso en tiempo real con unidades que intentan capturar la bandera mientras toman potenciadores para tratar de ganar la ventaja.
Es posible que desee ver el proyecto Capture the Flag Ejemplo para un ejemplo de trabajo de cómo se puede usar el árbol de comportamiento de fluidos en su proyecto. Demuestra el uso en tiempo real con unidades que intentan capturar la bandera mientras toman potenciadores para tratar de ganar la ventaja.
El árbol de comportamiento de fluidos viene con una sólida biblioteca de acciones, condiciones, compuestos y otros nodos prefabricados para ayudar a acelerar su proceso de desarrollo.
Puede crear una acción genérica sobre la marcha. Si se encuentra reutilizando las mismas acciones, es posible que desee investigar la sección sobre cómo escribir sus propias acciones personalizadas.
. Sequence ( )
. Do ( " Custom Action " , ( ) => {
return TaskStatus . Success ;
} )
. End ( )
Omita una serie de garrapatas en el árbol de comportamiento.
. Sequence ( )
// Wait for 1 tick on the tree before continuing
. Wait ( 1 )
. Do ( MyAction )
. End ( )
Espera hasta que el número aprobado de segundos haya expirado en deltaTime
.
. Sequence ( )
. WaitTime ( 2.5f )
. Do ( MyAction )
. End ( )
Puede crear una condición genérica sobre la marcha. Si se encuentra reutilizando las mismas acciones, es posible que desee investigar la sección sobre cómo escribir sus propias condiciones personalizadas.
. Sequence ( )
. Condition ( " Custom Condtion " , ( ) => {
return true ;
} )
. Do ( MyAction )
. End ( )
Evalúe aleatoriamente un nodo como verdadero o falso en función de la oportunidad aprobada.
. Sequence ( )
// 50% chance this will return success
. RandomChance ( 1 , 2 )
. Do ( MyAction )
. End ( )
Ejecuta cada nodo infantil en orden y espera que un estado de éxito marque el siguiente nodo. Si se devuelve la falla , la secuencia dejará de ejecutar nodos infantiles y devolver la falla al padre.
Tenga en cuenta que es importante que cada compuesto sea seguido por una declaración .End()
. Esto asegura que sus nodos estén bien anidados cuando se construye el árbol.
. Sequence ( )
. Do ( ( ) => { return TaskStatus . Success ; } )
. Do ( ( ) => { return TaskStatus . Success ; } )
// All tasks after this will not run and the sequence will exit
. Do ( ( ) => { return TaskStatus . Failure ; } )
. Do ( ( ) => { return TaskStatus . Success ; } )
. End ( )
Ejecuta cada nodo infantil hasta que se devuelve el éxito .
. Selector ( )
// Runs but fails
. Do ( ( ) => { return TaskStatus . Failure ; } )
// Will stop here since the node returns success
. Do ( ( ) => { return TaskStatus . Success ; } )
// Does not run
. Do ( ( ) => { return TaskStatus . Success ; } )
. End ( )
Selecciona aleatoriamente un nodo infantil con un algoritmo de shuffle. Mira hasta que se devuelve Success
o cada nodo falla. Baraja cada vez que el árbol inicialmente comienza a ejecutarlo.
. SelectorRandom ( )
. Do ( ( ) => { return TaskStatus . Failure ; } )
. Do ( ( ) => { return TaskStatus . Success ; } )
. Do ( ( ) => { return TaskStatus . Failure ; } )
. End ( )
Ejecuta todos los nodos infantiles al mismo tiempo hasta que todos devuelven el éxito . Salga y detiene todos los nodos en ejecución si alguno de ellos devuelve la falla .
. Parallel ( )
// Both of these tasks will run every frame
. Do ( ( ) => { return TaskStatus . Continue ; } )
. Do ( ( ) => { return TaskStatus . Continue ; } )
. End ( )
Los decoradores son elementos principales que envuelven cualquier nodo para cambiar el valor de retorno (o ejecutar lógica especial). Son extremadamente poderosos y un gran cumplido para acciones, condiciones y compuestos.
Puede envolver cualquier nodo con su propio código decorador personalizado. Esto le permite personalizar la funcionalidad reutilizable.
Nota : Debe llamar manualmente Update()
en el nodo infantil o no disparará. Además, cada decorador debe ser seguido por una declaración .End()
. De lo contrario, el árbol no se construirá correctamente.
. Sequence ( )
. Decorator ( " Return Success " , child => {
child . Update ( ) ;
return TaskStatus . Success ;
} )
. Do ( ( ) => { return TaskStatus . Failure ; } )
. End ( )
. Do ( ( ) => { return TaskStatus . Success ; } )
. End ( )
Invierta el estado devuelto del nodo infantil si es TaskStatus.Success
o TaskStatus.Failure
. No cambia TaskStatus.Continue
.
. Sequence ( )
. Inverter ( )
. Do ( ( ) => { return TaskStatus . Success ; } )
. End ( )
. End ( )
Return TaskStatus.Success
Si el niño devuelve TaskStatus.Failure
. No cambia TaskStatus.Continue
.
. Sequence ( )
. ReturnSuccess ( )
. Do ( ( ) => { return TaskStatus . Failure ; } )
. End ( )
. End ( )
Return TaskStatus.Failure
Si el niño devuelve TaskStatus.Success
. No cambia TaskStatus.Continue
.
. Sequence ( )
. ReturnFailure ( )
. Do ( ( ) => { return TaskStatus . Success ; } )
. End ( )
. End ( )
Devuelve TaskStatus.Continue
independientemente del estado que regrese el niño. Este decorador (y todas las tareas descendientes) se puede interrumpir llamando BehaviorTree.Reset()
.
. Sequence ( )
. RepeatForever ( )
. Do ( ( ) => { return TaskStatus . Success ; } )
. End ( )
. End ( )
Devuelve TaskStatus.Failure
Si el niño devuelve TaskStatus.Failure
, de lo contrario devuelve TaskStatus.Continue
.
. Sequence ( )
. RepeatUntilFailure ( )
. Do ( ( ) => { return TaskStatus . Success ; } )
. End ( )
. End ( )
Return TaskStatus.Success
Si el niño devuelve TaskStatus.Success
, de lo contrario devuelve TaskStatus.Continue
.
. Sequence ( )
. RepeatUntilSuccess ( )
. Do ( ( ) => { return TaskStatus . Success ; } )
. End ( )
. End ( )
Los árboles se pueden combinar con solo unas pocas líneas de código. Esto le permite crear árboles de comportamiento inyectables que agrupan diferentes nodos para una funcionalidad compleja, como buscar o atacar.
Tenga en cuenta que los árboles empalmados requieren un árbol recién construido para la inyección, ya que los nodos solo se copian profundamente en .Build()
.
using CleverCrow . Fluid . BTs . Trees ;
using CleverCrow . Fluid . BTs . Tasks ;
using UnityEngine ;
public class MyCustomAi : MonoBehaviour {
private BehaviorTree _tree ;
private void Awake ( ) {
var injectTree = new BehaviorTreeBuilder ( gameObject )
. Sequence ( )
. Do ( " Custom Action " , ( ) => {
return TaskStatus . Success ;
} )
. End ( ) ;
_tree = new BehaviorTreeBuilder ( gameObject )
. Sequence ( )
. Splice ( injectTree . Build ( ) )
. Do ( " Custom Action " , ( ) => {
return TaskStatus . Success ;
} )
. End ( )
. Build ( ) ;
}
private void Update ( ) {
// Update our tree every frame
_tree . Tick ( ) ;
}
}
Lo que hace que el árbol de comportamiento fluido sea tan poderoso es la capacidad de escribir sus propios nodos y agregarlos al constructor sin editar ninguna fuente. Incluso puede crear paquetes de unidad que agregan una nueva funcionalidad de constructor. Por ejemplo, podemos escribir un nuevo método de constructor de árboles como este que establece el objetivo de su sistema de IA con solo unas pocas líneas de código.
var tree = new BehaviorTreeBuilder ( gameObject )
. Sequence ( )
. AgentDestination ( " Find Enemy " , target )
. Do ( ( ) => {
// Activate chase enemy code
return TaskStatus . Success ;
} )
. End ( )
. Build ( ) ;
Debe tomar alrededor de 3 minutos para crear su primera acción personalizada e implementarla. Primero cree una nueva acción.
using CleverCrow . Fluid . BTs . Tasks ;
using CleverCrow . Fluid . BTs . Tasks . Actions ;
using UnityEngine ;
using UnityEngine . AI ;
public class AgentDestination : ActionBase {
private NavMeshAgent _agent ;
public Transform target ;
protected override void OnInit ( ) {
_agent = Owner . GetComponent < NavMeshAgent > ( ) ;
}
protected override TaskStatus OnUpdate ( ) {
_agent . SetDestination ( target . position ) ;
return TaskStatus . Success ;
}
}
A continuación, necesitamos extender el guión BehaviorTreeBuilder
con nuestra nueva acción de WatingDestination. Para obtener más información sobre las extensiones de clase C#, consulte los documentos oficiales.
using CleverCrow . Fluid . BTs . Trees ;
public static class BehaviorTreeBuilderExtensions {
public static BehaviorTreeBuilder AgentDestination ( this BehaviorTreeBuilder builder , string name , Transform target ) {
return builder . AddNode ( new AgentDestination {
Name = name ,
target = target ,
} ) ;
}
}
¡Y has terminado! Ahora ha creado una acción personalizada y un comportamiento de comportamiento extensible que está a prueba de futuro para nuevas versiones. Los siguientes ejemplos serán más de lo mismo. Pero cada uno cubre un tipo de nodo diferente.
Puede crear sus propias acciones personalizadas con la siguiente plantilla. Esto es útil para agrupar el código que está utilizando constantemente.
using UnityEngine ;
using CleverCrow . Fluid . BTs . Tasks ;
using CleverCrow . Fluid . BTs . Tasks . Actions ;
public class CustomAction : ActionBase {
// Triggers only the first time this node is run (great for caching data)
protected override void OnInit ( ) {
}
// Triggers every time this node starts running. Does not trigger if TaskStatus.Continue was last returned by this node
protected override void OnStart ( ) {
}
// Triggers every time `Tick()` is called on the tree and this node is run
protected override TaskStatus OnUpdate ( ) {
// Points to the GameObject of whoever owns the behavior tree
Debug . Log ( Owner . name ) ;
return TaskStatus . Success ;
}
// Triggers whenever this node exits after running
protected override void OnExit ( ) {
}
}
Agregue su nuevo nodo a una extensión.
using CleverCrow . Fluid . BTs . Trees ;
public static class BehaviorTreeBuilderExtensions {
public static BehaviorTreeBuilder CustomAction ( this BehaviorTreeBuilder builder , string name = " My Action " ) {
return builder . AddNode ( new CustomAction {
Name = name ,
} ) ;
}
}
Se pueden agregar condiciones personalizadas con la siguiente plantilla de ejemplo. Querrá usarlos para verificaciones como la vista, si la IA puede moverse a una ubicación y otras tareas que requieren una verificación compleja.
using UnityEngine ;
using CleverCrow . Fluid . BTs . Tasks ;
public class CustomCondition : ConditionBase {
// Triggers only the first time this node is run (great for caching data)
protected override void OnInit ( ) {
}
// Triggers every time this node starts running. Does not trigger if TaskStatus.Continue was last returned by this node
protected override void OnStart ( ) {
}
// Triggers every time `Tick()` is called on the tree and this node is run
protected override bool OnUpdate ( ) {
// Points to the GameObject of whoever owns the behavior tree
Debug . Log ( Owner . name ) ;
return true ;
}
// Triggers whenever this node exits after running
protected override void OnExit ( ) {
}
}
Agregue la nueva condición a su generador de árboles de comportamiento con el siguiente fragmento.
using CleverCrow . Fluid . BTs . Trees ;
public static class BehaviorTreeBuilderExtensions {
public static BehaviorTreeBuilder CustomCondition ( this BehaviorTreeBuilder builder , string name = " My Condition " ) {
return builder . AddNode ( new CustomCondition {
Name = name ,
} ) ;
}
}
El árbol de comportamiento de fluidos no se limita a solo acciones y condiciones personalizadas. Puede crear nuevos tipos compuestos con una API bastante simple. Aquí hay un ejemplo de una secuencia básica.
using CleverCrow . Fluid . BTs . TaskParents . Composites ;
using CleverCrow . Fluid . BTs . Tasks ;
public class CustomSequence : CompositeBase {
protected override TaskStatus OnUpdate ( ) {
for ( var i = ChildIndex ; i < Children . Count ; i ++ ) {
var child = Children [ ChildIndex ] ;
var status = child . Update ( ) ;
if ( status != TaskStatus . Success ) {
return status ;
}
ChildIndex ++ ;
}
return TaskStatus . Success ;
}
}
Agregar compuestos personalizados a su árbol de comportamiento es tan simple como agregar acciones. Solo toma una línea de código.
using CleverCrow . Fluid . BTs . Trees ;
public static class BehaviorTreeBuilderExtensions {
public static BehaviorTreeBuilder CustomSequence ( this BehaviorTreeBuilder builder , string name = " My Sequence " ) {
return builder . ParentTask < CustomSequence > ( name ) ;
}
}
Los decoradores también se pueden escribir a medida para reducir el código repetitivo.
using CleverCrow . Fluid . BTs . Decorators ;
using CleverCrow . Fluid . BTs . Tasks ;
public class CustomInverter : DecoratorBase {
protected override TaskStatus OnUpdate ( ) {
if ( Child == null ) {
return TaskStatus . Success ;
}
var childStatus = Child . Update ( ) ;
var status = childStatus ;
switch ( childStatus ) {
case TaskStatus . Success :
status = TaskStatus . Failure ;
break ;
case TaskStatus . Failure :
status = TaskStatus . Success ;
break ;
}
return status ;
}
}
Implementar decoradores es similar a los compuestos. Si necesita establecer argumentos en el compuesto, querrá tomar un botín en el método BehaviorTreeBuilder.AddNodeWithPointer()
.
using CleverCrow . Fluid . BTs . Trees ;
public static class BehaviorTreeBuilderExtensions {
public static BehaviorTreeBuilder CustomInverter ( this BehaviorTreeBuilder builder , string name = " My Inverter " ) {
// See BehaviorTreeBuilder.AddNodeWithPointer() if you need to set custom composite data from arguments
return builder . ParentTask < CustomInverter > ( name ) ;
}
}
Si está utilizando un formateador automático, probablemente destrozará su formato de código con la sintaxis del constructor. Para evitar esto, puede desactivar el formato como en JetBrains Rider. Si necesita un IDE específico, no debería ser demasiado difícil buscar en Google el comentario de desactivación de formato específico que necesita.
// @formatter:off
_tree = new BehaviorTreeBuilder ( gameObject )
. Sequence ( )
. Condition ( " Custom Condition " , ( ) => {
return true ;
} )
. Do ( " Custom Action " , ( ) => {
return TaskStatus . Success ;
} )
. End ( )
. Build ( ) ;
// @formatter:on
Para acceder a las compilaciones nocturnas de develop
que son amigables con el administrador de paquetes, necesitará editar manualmente sus Packages/manifest.json
así.
{
"dependencies" : {
"com.fluid.behavior-tree" : " https://github.com/ashblue/fluid-behavior-tree.git#nightly "
}
}
Tenga en cuenta que para obtener una construcción nocturna más nueva, debe eliminar esta línea y cualquier datos de bloqueo relacionados en el manifiesto, dejar que la Unidad reconstruya y luego vuelva a agregarlo. A medida que Unity bloquea el hash de confirmación para las URL de GIT como paquetes.
Si desea ejecutar para ejecutar el entorno de desarrollo, necesitará instalar Node.js. Luego ejecute lo siguiente desde la raíz una vez.
npm install
Si desea crear una npm run build
desde la raíz y llenará la carpeta dist
.
Todas las confirmaciones deben hacerse utilizando Commitizen (que se instala automáticamente al ejecutar npm install
). Los compromisos se compilan automáticamente a los números de versión en el lanzamiento, por lo que esto es muy importante. Los PRS que no tienen compromisos basados en comitantes serán rechazados.
Para hacer que un cometominar el tipo de siguiente en una terminal desde la raíz
npm run commit
Consulte el documento de pautas de contribución para obtener más información.
Gracias a estas maravillosas personas (Key Emoji):
Ceniza azul | Jesse Talavera-Greenberg | Productucciones de PureSalt ? | Martin Duvergey ? | pila ? | Piotr Jastrzebski | Sounghoo |
Tnthomas ? | Ownez | angstr0m ? | Izzy ? | Jeremyvansnick |
Este proyecto sigue la especificación de todos los contribuyentes. ¡Contribuciones de cualquier tipo bienvenido!
Gracias a estas maravillosas personas (Key Emoji):
Este proyecto sigue la especificación de todos los contribuyentes. ¡Contribuciones de cualquier tipo bienvenido!