Arbres de comportement pour les projets Unity3d. Écrit avec une approche basée sur le code pour maximiser la maintenabilité sur les grands projets avec le modèle du constructeur. Inspiré par l'arbre de comportement fluide.
Caractéristiques
Soutien
Rejoignez la communauté Discord si vous avez des questions ou si vous avez besoin d'aide.
Voir les fonctionnalités à venir et les progrès du développement sur le conseil d'administration de Trello.
Lors de la création d'arbres, vous devrez les stocker dans une variable pour mettre correctement en cache toutes les données nécessaires.
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 ( ) ;
}
}
Selon ce que vous retournez pour un statut de tâche, différentes choses se produiront.
tree.Tick()
redémarrera l'arbre si aucun autre nœud à fonctionnertree.Tick()
est appelé. Une référence de pointeur est suivie par l'arbre et ne peut être effacée que si tree.Reset()
est appelée. Tant que votre variable de stockage d'arborescence est définie en public
ou dispose d'un attribut SerializeField
. Vous pourrez imprimer une visualisation de votre arbre pendant que le jeu fonctionne dans l'éditeur. Notez que vous ne pouvez pas voir les arbres pendant que le jeu ne fonctionne pas. Comme l'arbre doit être construit pour être visualisé.
Vous pouvez ajouter en toute sécurité un nouveau code à vos arbres de comportement avec plusieurs lignes. Vous permettant de personnaliser BTS tout en prenant en charge les futures mises à niveau de version.
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 ( ) ;
}
}
L'arbre de comportement de fluide est utilisé par le gestionnaire de packages d'Unity. Afin de l'utiliser, vous devrez ajouter les lignes suivantes à votre fichier Packages/manifest.json
. Après cela, vous pourrez contrôler visuellement la version spécifique de l'arborescence du comportement de fluide que vous utilisez dans la fenêtre du gestionnaire de package dans Unity. Cela doit être fait afin que votre éditeur Unity puisse se connecter au registre des packages de NPM.
{
"scopedRegistries" : [
{
"name" : " NPM " ,
"url" : " https://registry.npmjs.org " ,
"scopes" : [
" com.fluid "
]
}
],
"dependencies" : {
"com.fluid.behavior-tree" : " 2.2.0 "
}
}
Des archives de versions et de notes de publication spécifiques sont disponibles sur la page des versions.
Vous voudrez peut-être consulter le projet Capture the Flag Example pour un exemple de travail de la façon dont l'arbre de comportement fluide peut être utilisé dans votre projet. Il démontre une utilisation en temps réel avec des unités qui tentent de capturer le drapeau tout en saisissant des puissances pour essayer de gagner le dessus.
Vous voudrez peut-être consulter le projet Capture the Flag Example pour un exemple de travail de la façon dont l'arbre de comportement fluide peut être utilisé dans votre projet. Il démontre une utilisation en temps réel avec des unités qui tentent de capturer le drapeau tout en saisissant des puissances pour essayer de gagner le dessus.
Fluid Behavior Tree est livré avec une bibliothèque robuste d'actions, de conditions, de composites et d'autres nœuds préfabriqués pour accélérer votre processus de développement.
Vous pouvez créer une action générique à la volée. Si vous vous retrouvez à réutiliser les mêmes actions, vous voudrez peut-être examiner la section sur la rédaction de vos propres actions personnalisées.
. Sequence ( )
. Do ( " Custom Action " , ( ) => {
return TaskStatus . Success ;
} )
. End ( )
Ignorez un certain nombre de tiques sur l'arbre du comportement.
. Sequence ( )
// Wait for 1 tick on the tree before continuing
. Wait ( 1 )
. Do ( MyAction )
. End ( )
Attend jusqu'à ce que le nombre passé de secondes ait expiré en deltaTime
.
. Sequence ( )
. WaitTime ( 2.5f )
. Do ( MyAction )
. End ( )
Vous pouvez créer une condition générique à la volée. Si vous vous retrouvez à réutiliser les mêmes actions, vous voudrez peut-être examiner la section sur la rédaction de vos propres conditions personnalisées.
. Sequence ( )
. Condition ( " Custom Condtion " , ( ) => {
return true ;
} )
. Do ( MyAction )
. End ( )
Évaluez au hasard un nœud comme vrai ou faux en fonction de la chance passée.
. Sequence ( )
// 50% chance this will return success
. RandomChance ( 1 , 2 )
. Do ( MyAction )
. End ( )
Exécute chaque nœud enfant dans l'ordre et s'attend à ce qu'un statut de réussite coche le nœud suivant. Si l'échec est renvoyé, la séquence cessera d'exécuter les nœuds enfants et renvoie l'échec au parent.
Remarque Il est important que chaque composite soit suivi d'une déclaration .End()
. Cela garantit que vos nœuds sont correctement imbriqués lorsque l'arbre est construit.
. 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 ( )
Exécute chaque nœud enfant jusqu'à ce que le succès soit renvoyé.
. 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 ( )
Sélectionne au hasard un nœud enfant avec un algorithme Shuffle. Regarde jusqu'à ce que Success
soit renvoyé ou que chaque nœud échoue. Les mélanges chaque fois que l'arbre commence initialement à l'exécuter.
. SelectorRandom ( )
. Do ( ( ) => { return TaskStatus . Failure ; } )
. Do ( ( ) => { return TaskStatus . Success ; } )
. Do ( ( ) => { return TaskStatus . Failure ; } )
. End ( )
Exécute tous les nœuds enfants en même temps jusqu'à ce qu'ils reviennent tous. Sort et arrête tous les nœuds d'exécution si l'un d'eux renvoie l'échec .
. Parallel ( )
// Both of these tasks will run every frame
. Do ( ( ) => { return TaskStatus . Continue ; } )
. Do ( ( ) => { return TaskStatus . Continue ; } )
. End ( )
Les décorateurs sont des éléments parents qui enveloppent n'importe quel nœud pour modifier la valeur de retour (ou exécuter une logique spéciale). Ils sont extrêmement puissants et un grand compliment aux actions, aux conditions et aux composites.
Vous pouvez envelopper n'importe quel nœud avec votre propre code décorateur personnalisé. Cela vous permet de personnaliser les fonctionnalités réutilisables.
Remarque : vous devez appeler manuellement Update()
sur le nœud enfant ou il ne tirera pas. Chaque décorateur doit également être suivi d'une déclaration .End()
. Sinon, l'arbre ne s'appuiera pas correctement.
. Sequence ( )
. Decorator ( " Return Success " , child => {
child . Update ( ) ;
return TaskStatus . Success ;
} )
. Do ( ( ) => { return TaskStatus . Failure ; } )
. End ( )
. Do ( ( ) => { return TaskStatus . Success ; } )
. End ( )
Inverser l'état renvoyé du nœud enfant s'il s'agit de TaskStatus.Success
ou TaskStatus.Failure
. Ne change pas TaskStatus.Continue
.
. Sequence ( )
. Inverter ( )
. Do ( ( ) => { return TaskStatus . Success ; } )
. End ( )
. End ( )
Retour TaskStatus.Success
Si l'enfant renvoie TaskStatus.Failure
. Ne change pas TaskStatus.Continue
.
. Sequence ( )
. ReturnSuccess ( )
. Do ( ( ) => { return TaskStatus . Failure ; } )
. End ( )
. End ( )
Retour TaskStatus.Failure
Si l'enfant renvoie TaskStatus.Success
. Ne change pas TaskStatus.Continue
.
. Sequence ( )
. ReturnFailure ( )
. Do ( ( ) => { return TaskStatus . Success ; } )
. End ( )
. End ( )
Return TaskStatus.Continue
quel que soit le statut que l'enfant renvoie. Ce décorateur (et toutes les tâches descendants) peut être interrompu en appelant BehaviorTree.Reset()
.
. Sequence ( )
. RepeatForever ( )
. Do ( ( ) => { return TaskStatus . Success ; } )
. End ( )
. End ( )
Return TaskStatus.Failure
Si l'enfant renvoie TaskStatus.Failure
, sinon il renvoie TaskStatus.Continue
.
. Sequence ( )
. RepeatUntilFailure ( )
. Do ( ( ) => { return TaskStatus . Success ; } )
. End ( )
. End ( )
Return TaskStatus.Success
Si l'enfant renvoie TaskStatus.Success
, sinon il renvoie TaskStatus.Continue
.
. Sequence ( )
. RepeatUntilSuccess ( )
. Do ( ( ) => { return TaskStatus . Success ; } )
. End ( )
. End ( )
Les arbres peuvent être combinés avec seulement quelques lignes de code. Cela vous permet de créer des arbres de comportement injectables qui regroupent différents nœuds pour des fonctionnalités complexes telles que la recherche ou l'attaque.
Soyez averti que les arbres épissés nécessitent un arbre nouvellement construit pour l'injection, car les nœuds ne sont copiés que sur .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 ( ) ;
}
}
Ce qui rend l'arbre de comportement fluide si puissant, c'est la possibilité d'écrire vos propres nœuds et de les ajouter au constructeur sans modifier aucune source. Vous pouvez même créer des packages Unity qui ajoutent de nouvelles fonctionnalités de constructeur. Par exemple, nous pouvons écrire une nouvelle méthode d'arborescence comme celle-ci qui définit la cible de votre système d'IA avec seulement quelques lignes de code.
var tree = new BehaviorTreeBuilder ( gameObject )
. Sequence ( )
. AgentDestination ( " Find Enemy " , target )
. Do ( ( ) => {
// Activate chase enemy code
return TaskStatus . Success ;
} )
. End ( )
. Build ( ) ;
Cela devrait prendre environ 3 minutes pour créer votre première action personnalisée et l'implémenter. Créez d'abord une nouvelle action.
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 ;
}
}
Ensuite, nous devons étendre le script BehaviorTreeBuilder
avec notre nouvelle action AgentDestination. Pour plus d'informations sur les extensions de classe C #, consultez les documents officiels.
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 ,
} ) ;
}
}
Et vous avez terminé! Vous avez maintenant créé une action personnalisée et un constructeur d'arborescence de comportement extensible qui est futur pour les nouvelles versions. Les exemples suivants seront plus ou moins les mêmes. Mais chacun couvre un type de nœud différent.
Vous pouvez créer vos propres actions personnalisées avec le modèle suivant. Ceci est utile pour regrouper le code que vous utilisez constamment.
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 ( ) {
}
}
Ajoutez votre nouveau nœud à une extension.
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 ,
} ) ;
}
}
Des conditions personnalisées peuvent être ajoutées avec l'exemple suivant. Vous voudrez les utiliser pour des chèques tels que la vue, si l'IA peut se déplacer vers un emplacement et d'autres tâches qui nécessitent un contrôle complexe.
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 ( ) {
}
}
Ajoutez la nouvelle condition à votre constructeur d'arborescence de comportement avec l'extrait suivant.
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 ,
} ) ;
}
}
L'arbre de comportement fluide ne se limite pas aux actions et aux conditions personnalisées. Vous pouvez créer de nouveaux types composites avec une API assez simple. Voici un exemple de séquence de base.
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 ;
}
}
L'ajout de composites personnalisés à votre arbre de comportement est tout aussi simple que d'ajouter des actions. Prend juste une ligne de code.
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 ) ;
}
}
Les décorateurs peuvent également être écrits sur mesure pour réduire le code répétitif.
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 ;
}
}
La mise en œuvre des décorateurs est similaire aux composites. Si vous devez définir des arguments sur le composite, vous voudrez prendre un butin sur la méthode 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 vous utilisez un formateur automatique, il gênera probablement votre formatage de code avec la syntaxe du générateur. Pour éviter cela, vous pouvez désactiver la mise en forme comme dans JetBrains Rider. Si vous avez besoin d'un IDE spécifique, il ne devrait pas être trop difficile de rechercher sur Google le formatage spécifique désactiver les commentaires dont vous avez besoin.
// @formatter:off
_tree = new BehaviorTreeBuilder ( gameObject )
. Sequence ( )
. Condition ( " Custom Condition " , ( ) => {
return true ;
} )
. Do ( " Custom Action " , ( ) => {
return TaskStatus . Success ;
} )
. End ( )
. Build ( ) ;
// @formatter:on
Pour accéder à des versions nocturnes de develop
qui sont des gestionnaires de packages conviviaux, vous devrez modifier manuellement vos Packages/manifest.json
en tant que tel.
{
"dependencies" : {
"com.fluid.behavior-tree" : " https://github.com/ashblue/fluid-behavior-tree.git#nightly "
}
}
Notez que pour obtenir une construction nocturne plus récente, vous devez supprimer cette ligne et toutes les données de verrouillage associées dans le manifeste, laissez Unity Rebuild, puis ajoutez-la. Comme Unity verrouille le hachage de validation des URL GIT comme packages.
Si vous souhaitez exécuter pour exécuter l'environnement de développement, vous devrez installer Node.js. Ensuite, exécutez la suite de la racine une fois.
npm install
Si vous souhaitez créer une génération npm run build
à partir de la racine et qu'il remplira le dossier dist
.
Tous les engagements doivent être effectués à l'aide de Commizen (qui est automatiquement installé lors de l'exécution npm install
). Les engagements sont automatiquement compilés aux numéros de version lors de la version, c'est donc très important. Les PR qui n'ont pas de commits basés sur des commissations seront rejetés.
Pour faire un type de validation ce qui suit en un terminal de la racine
npm run commit
Veuillez consulter le document des directives contributives pour plus d'informations.
Merci à ces gens merveilleux (clé emoji):
Frêne bleu | Jesse Talavera-Greenberg | Purealtproductions ? | Martin Duvergey ? | pile d'appel ? | Piotr Jastrzebski | Sounghoo |
Tnthomes ? | Propriétaire | Angstr0m ? | Izzy ? | Jérémyvansnick |
Ce projet suit les spécifications de tous les contributeurs. Contributions de toute nature bienvenue!
Merci à ces gens merveilleux (clé emoji):
Ce projet suit les spécifications de tous les contributeurs. Contributions de toute nature bienvenue!