Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
quick:program [2019/05/13 13:49] rodrigobravoquick:program [2023/11/16 19:49] (current) – external edit 127.0.0.1
Line 1: Line 1:
 +<markdown>
 # Programmers Quick Start Guide # Programmers Quick Start Guide
  
Line 16: Line 17:
  
  
-{{ :images:program:StartingPointBehaviors.png }}+![](:images:program:StartingPointBehaviors.png)
  
 If you play that scene, you may have noticed that if the `Enemy` reaches the player, he keeps joined to him and slowly orbitating. This is not, surely enough, a nice behavior for a frightening enemy. We would want our enemy to stop moving and shoot the player when the distance between them reaches a threshold. We will do that adding a new child behavior to the priority selector but we need the `Shoot` action in the first place. If you play that scene, you may have noticed that if the `Enemy` reaches the player, he keeps joined to him and slowly orbitating. This is not, surely enough, a nice behavior for a frightening enemy. We would want our enemy to stop moving and shoot the player when the distance between them reaches a threshold. We will do that adding a new child behavior to the priority selector but we need the `Shoot` action in the first place.
Line 44: Line 45:
 - Open it into your preferred editor and substitute the code: - Open it into your preferred editor and substitute the code:
  
-<code csharp>+```csharp
     using UnityEngine;     using UnityEngine;
  
Line 90: Line 91:
          
     } // class ShootOnce     } // class ShootOnce
-</code>+```
  
 Some things to note: Some things to note:
Line 104: Line 105:
 - Create a new node in the canvas for the `ShootOnce` action. Note the action parameters in the inspector and, specifically, the default value for the `velocity` parameter. - Create a new node in the canvas for the `ShootOnce` action. Note the action parameters in the inspector and, specifically, the default value for the `velocity` parameter.
  
-{{ :images:program:FirstActionInTheCollection.png }}+![]( :images:program:FirstActionInTheCollection.png )
  
 - We want the shooting parameters to be available in the inspector of those game objects that use the behavior. Select _blackboard_ for all the three input parameters, and create a new field for each one. Use the default names except for `velocity`, where something like `bulletVelocity` is preferred to avoid confusion. - We want the shooting parameters to be available in the inspector of those game objects that use the behavior. Select _blackboard_ for all the three input parameters, and create a new field for each one. Use the default names except for `velocity`, where something like `bulletVelocity` is preferred to avoid confusion.
 - The `velocity` default value has vanished. Keep in mind that it is only used when the parameter is set _using a constant_ in the editor. When using a blackboard parameter, the default value specified in the code attribute is discarded. Fortunately, we can still provide a default value in the behavior. In the `Blackboard` tab see the blackboard parameters just created, and since `bulletVelocity` has a primitive type, you can provide the 30 again. - The `velocity` default value has vanished. Keep in mind that it is only used when the parameter is set _using a constant_ in the editor. When using a blackboard parameter, the default value specified in the code attribute is discarded. Fortunately, we can still provide a default value in the behavior. In the `Blackboard` tab see the blackboard parameters just created, and since `bulletVelocity` has a primitive type, you can provide the 30 again.
  
-{{ :images:program:ShootOnceBlackboardParameters.png }}+![]( :images:program:ShootOnceBlackboardParameters.png )
  
 - Connect the priority selector and the `ShootOnce` action so that shooting is the first child to be considered (higher priority). - Connect the priority selector and the `ShootOnce` action so that shooting is the first child to be considered (higher priority).
 - Right click on the default guard `AlwaysTrue` of the `ShootOnce` node, select `Replace with ...` and choose `Perception/IsTargetClose` from the condition list. Configure the condition with the `Player` as `target`, and 7 as `closeDistance`. As you might remember, the condition for the `MoveToGameObject` action (pursue the player) is exactly the same but the `closeDistance` is set to 15. - Right click on the default guard `AlwaysTrue` of the `ShootOnce` node, select `Replace with ...` and choose `Perception/IsTargetClose` from the condition list. Configure the condition with the `Player` as `target`, and 7 as `closeDistance`. As you might remember, the condition for the `MoveToGameObject` action (pursue the player) is exactly the same but the `closeDistance` is set to 15.
  
-{{ :images:program:BBUsingShootOnce.png }}+![]( :images:program:BBUsingShootOnce.png )
  
 - Close the editor. We will now create the bullet prefab that will be cloned each time the enemy shoots: - Close the editor. We will now create the bullet prefab that will be cloned each time the enemy shoots:
Line 122: Line 123:
     - Drag and drop the `Bullet` into the Project panel so a new prefab is created based on it, and remove the game object.     - Drag and drop the `Bullet` into the Project panel so a new prefab is created based on it, and remove the game object.
  
-<code csharp>+```csharp
         void Start() {         void Start() {
             Destroy(gameObject, 2);             Destroy(gameObject, 2);
         }         }
-</code>+```
  
  
Line 150: Line 151:
 - Add the code for automatically setting the `shootPoint` parameter in the `OnUpdate` method: - Add the code for automatically setting the `shootPoint` parameter in the `OnUpdate` method:
  
-<code csharp>+```csharp
     public override TaskStatus OnUpdate()     public override TaskStatus OnUpdate()
     {     {
Line 166: Line 167:
         [ ... ]         [ ... ]
     } // OnUpdate     } // OnUpdate
-</code>+```
  
  
Line 180: Line 181:
 Although our action just shoots a bullet, if you have played the scene you will have noticed that the enemy shoots a high-frequency stream of bullets. Although our action just shoots a bullet, if you have played the scene you will have noticed that the enemy shoots a high-frequency stream of bullets.
  
-{{ :images:program:ShootOnceFinalyBT.png }}+![]( :images:program:ShootOnceFinalyBT.png )
  
 Our `ShootOnce` action ends immediately after instantiating the bullet. That causes _the end of the parent_ priority selector. Fortunately, the root node is a `Repeat` that relaunches the behavior again in the next game loop. Usually, the condition that determines whether the player is near enough to be shot will still be true and the enemy shoots again, repeating the complete cycle. The bullet stream becomes maximum, because the enemy is shooting as fast as possible. Our `ShootOnce` action ends immediately after instantiating the bullet. That causes _the end of the parent_ priority selector. Fortunately, the root node is a `Repeat` that relaunches the behavior again in the next game loop. Usually, the condition that determines whether the player is near enough to be shot will still be true and the enemy shoots again, repeating the complete cycle. The bullet stream becomes maximum, because the enemy is shooting as fast as possible.
Line 192: Line 193:
 - The `OnUpdate()` method should start checking the delay. Usually it will increase the `elapsed` field and exit. Eventually, it will need to instantiate a new bullet, invoking the `ShootOnce::OnUpdate()` parent method. The important point here is the method _must_ return, in both cases, `TaskStatus.RUNNING` so the execution engine will know that the action has not finished and must be invoked in the next game cycle. Our `Shoot` becomes a _latent action_. - The `OnUpdate()` method should start checking the delay. Usually it will increase the `elapsed` field and exit. Eventually, it will need to instantiate a new bullet, invoking the `ShootOnce::OnUpdate()` parent method. The important point here is the method _must_ return, in both cases, `TaskStatus.RUNNING` so the execution engine will know that the action has not finished and must be invoked in the next game cycle. Our `Shoot` becomes a _latent action_.
  
-<code csharp>+```csharp
     using Pada1.BBCore;           // Code attributes     using Pada1.BBCore;           // Code attributes
     using Pada1.BBCore.Tasks;     // TaskStatus     using Pada1.BBCore.Tasks;     // TaskStatus
Line 226: Line 227:
  
     } // class Shoot     } // class Shoot
-</code> +```
  
 - Open the editor and change the `EnemyBehavior` so it uses the new `Shoot` action. - Open the editor and change the `EnemyBehavior` so it uses the new `Shoot` action.
Line 235: Line 235:
 Returning `RUNNING` could look familiar if you know the concept of _coroutine_. A coroutine is similar to a function that has the ability to pause itself and return the execution back to Unity. When restarted, _Unity coroutines_ will continue its execution where they left off; note that the `RUNNING` state in Behavior Bricks works differently because the `OnUpdate()` method is restarted from scratch. If actions were programmed using coroutines, our `Shoot` action would have been: Returning `RUNNING` could look familiar if you know the concept of _coroutine_. A coroutine is similar to a function that has the ability to pause itself and return the execution back to Unity. When restarted, _Unity coroutines_ will continue its execution where they left off; note that the `RUNNING` state in Behavior Bricks works differently because the `OnUpdate()` method is restarted from scratch. If actions were programmed using coroutines, our `Shoot` action would have been:
  
-<code csharp>+```csharp
 IEnumerator OnUpdateAsCoroutine() { IEnumerator OnUpdateAsCoroutine() {
     while(true) {     while(true) {
Line 249: Line 249:
     } // infinite loop     } // infinite loop
 } }
-</code>+```
  
  
Line 265: Line 265:
 As far as our `ShootOnce` action, we would use the `OnStart()` method for seeking the shooting point instead of doing it each `OnUpdate()` invocation: As far as our `ShootOnce` action, we would use the `OnStart()` method for seeking the shooting point instead of doing it each `OnUpdate()` invocation:
  
-<code csharp>+```csharp
 // Be careful! This is the ShootOnce action, not Shoot! // Be careful! This is the ShootOnce action, not Shoot!
  
Line 292: Line 292:
     [ ... ]     [ ... ]
 } // OnUpdate } // OnUpdate
-</code>+```
  
  
Line 307: Line 307:
 An action that never ends but just keep the behavior doing nothing could be implemented with an `OnUpdate()` method returning `RUNNING`. But that would be a waste of resources because the behavior will received its CPU slice each game cycle. A better approach is returning `SUSPEND`. The behavior will continue "alive" with no end, but it will not consume CPU at all. Note that this is also better than a behavior with a `Repeat` node with the built-in `SuccessAction` as only child: An action that never ends but just keep the behavior doing nothing could be implemented with an `OnUpdate()` method returning `RUNNING`. But that would be a waste of resources because the behavior will received its CPU slice each game cycle. A better approach is returning `SUSPEND`. The behavior will continue "alive" with no end, but it will not consume CPU at all. Note that this is also better than a behavior with a `Repeat` node with the built-in `SuccessAction` as only child:
  
-<code csharp>+```csharp
 using UnityEngine; using UnityEngine;
  
Line 327: Line 327:
  
 } // class SleepForever } // class SleepForever
-</code>+```
  
- 
-<!-- 
- 
-La idea inicial era hacer Shoot bien, metiendo una corutina que hiciera un sleep y luego llamara al Sleep. De hecho lo hice, vamos, y funciono, aunque habia que hacer el pino para que la corutina la lanzara el BehaviorExecutor (StartCoroutine esta en MonoBehavior, que no somos). No queria hacer una accion que heredara de MonoBehaviorAction porque suponia meter el componente en el objeto y mas lio. El siguiente paso que queria dar tras eso era cancelar la corrutina en el OnAbort. De hecho, tener una corutina que llamara al resume() cada delay segundos, en lugar de una corutina que esperara <delay> segundos, llamara al resume() y terminara, y que luego en el OnUpdate() se planificara otra corrutina. Asi aprovechaba para contar el OnStart(), OnAbort(), etcetera. Pero para eso se necesitaba usar StartCoroutine(<string>, <object>), de mono behavior, que busca en "la clase actual" un metodo llamado <string> y le pasa <object> como parametro, lanzando todo en una corrutina. Eso ya no se puede hacer con el BehaviorExecutor desde la accion, porque el ejecutor no es quien tiene el metodo... total, que lo deje a medias (el codigo esta a medias en 04_DoneShoot_Failed.cs) y replantee el tutorial. Esto es lo que habia empezado a escribir :-) 
- 
-No me gusta como ha quedado porque no se cuenta el ciclo de vida del OnStart, OnAbort, etcetera, pero bueno. Desde luego asi queda mas facil... 
- 
-We can take advantage of this feature for our `Shoot` action. Specifically, we will change the `OnUpdate()` method so, when called, a bullet is always instantiated. After that, a _Unity coroutine_ is launched and ends returning `TaskStatus.SUSPENDED` to the execution engine. The key point here is that the coroutine will sleep for `delay` seconds (the parameter will be `float` now) and, when restarted, it will ask the execution engine to `resume()` the action. No polling is needed because the coroutine will be, efficiently, slept by Unity. 
- 
-http://answers.unity3d.com/questions/161084/coroutine-without-monobehaviour.html 
- 
- 
---> 
  
 ## Conditions: perceiving the environment ## Conditions: perceiving the environment
Line 349: Line 336:
 - Select the `Directional light` game object and add it a new C# script called `DayNightCycle`: - Select the `Directional light` game object and add it a new C# script called `DayNightCycle`:
  
-<code csharp>+```csharp
     using UnityEngine;     using UnityEngine;
          
Line 379: Line 366:
         }         }
     } // class DayNightCycle     } // class DayNightCycle
-</code>+```
  
  
Line 386: Line 373:
 - Select the `Directional light` again, and label it with a new tag called `MainLight`. The condition will look for the light by that tag. Please note that a real game will had a better thought out implementation, using some kind of game state manager, for example. But, for shortness sake, we will get along with this one. - Select the `Directional light` again, and label it with a new tag called `MainLight`. The condition will look for the light by that tag. Please note that a real game will had a better thought out implementation, using some kind of game state manager, for example. But, for shortness sake, we will get along with this one.
  
-{{ :images:program:AddTag.png }}+![]( :images:program:AddTag.png )
  
 In Behavior Bricks, conditions are implemented creating a new subclass of `ConditionBase` of the `Pada1.BBCore.Framework` package. It is essentially equal to the known `BasePrimitiveAction` with the exception of some key points: In Behavior Bricks, conditions are implemented creating a new subclass of `ConditionBase` of the `Pada1.BBCore.Framework` package. It is essentially equal to the known `BasePrimitiveAction` with the exception of some key points:
Line 402: Line 389:
 - Open it into your preferred editor and substitute the code: - Open it into your preferred editor and substitute the code:
  
-<code csharp>+```csharp
     using UnityEngine;     using UnityEngine;
  
Line 427: Line 414:
         }         }
     } // class IsNightCondition     } // class IsNightCondition
-</code>+```
  
  
Line 434: Line 421:
 - Set the condition for this new branch to `IsNightCondition`. As soon as night arrives, the condition will be true and the `SleepForever` action, with higher priority, will be chosen. - Set the condition for this new branch to `IsNightCondition`. As soon as night arrives, the condition will be true and the `SleepForever` action, with higher priority, will be chosen.
  
-{{ :images:program:FinalEnemyBehavior.png }}+![]( :images:program:FinalEnemyBehavior.png )
  
 - Play the scene. Note that the enemy stops whatever was doing when the light is nearly off. - Play the scene. Note that the enemy stops whatever was doing when the light is nearly off.
Line 449: Line 436:
 But when a condition is used in a priority selector or a guard decorator, something unpleasant occurs: the execution engine could need to continually call to its `Check()` method to monitor its state. That will happen with higher priority conditions that are currently false, or with the first condition that is currently true. In our example, imagine the light is on (it is daytime), and the enemy is near enough to the player, so it is shooting at him. The state is summarized in the figure, where the `Shoot` action is highlighted as the current action. But when a condition is used in a priority selector or a guard decorator, something unpleasant occurs: the execution engine could need to continually call to its `Check()` method to monitor its state. That will happen with higher priority conditions that are currently false, or with the first condition that is currently true. In our example, imagine the light is on (it is daytime), and the enemy is near enough to the player, so it is shooting at him. The state is summarized in the figure, where the `Shoot` action is highlighted as the current action.
  
-{{ :images:program:PriroityExecutionExample.png }}+![]( :images:program:PriroityExecutionExample.png )
  
 In this situation, the execution state must guarantee _each cycle_ that the night has not yet fallen (`A` condition is still false) _and_ the player is still near enough to be shot (`B` condition is still true). As far as we currently know, that requires the execution engine to invoke `Check()` in both conditions. In general, if a priority selector is executing its n-th child, then _n_ conditions must be checked. When night has fallen, our `SleepForever` does not require CPU at all; but the `IsNightCondition` is checked every frame in order to know if the sleeping `SleepForever` should be still used. In this situation, the execution state must guarantee _each cycle_ that the night has not yet fallen (`A` condition is still false) _and_ the player is still near enough to be shot (`B` condition is still true). As far as we currently know, that requires the execution engine to invoke `Check()` in both conditions. In general, if a priority selector is executing its n-th child, then _n_ conditions must be checked. When night has fallen, our `SleepForever` does not require CPU at all; but the `IsNightCondition` is checked every frame in order to know if the sleeping `SleepForever` should be still used.
Line 461: Line 448:
 - Add a new event to the `DayNightCycle.cs` script: - Add a new event to the `DayNightCycle.cs` script:
  
-<code csharp>+```csharp
     // Event raised when sun rises or sets.     // Event raised when sun rises or sets.
     public event System.EventHandler OnChanged;     public event System.EventHandler OnChanged;
-</code>+```
  
  
 - Change the `Update` method so the event is triggered accordingly. For simplicity, we are not using the `EventArgs` parameter. A more elaborated implementation could inform whether night has just fallen, or a new day has come: - Change the `Update` method so the event is triggered accordingly. For simplicity, we are not using the `EventArgs` parameter. A more elaborated implementation could inform whether night has just fallen, or a new day has come:
  
-<code csharp>+```csharp
     void Update()     void Update()
     {     {
Line 482: Line 469:
         GetComponent<Light>().color = Color.Lerp(nightColor, dayColor, lightIntensity);         GetComponent<Light>().color = Color.Lerp(nightColor, dayColor, lightIntensity);
     }     }
-</code>+```
  
  
 Now, any object can receive notifications from the light using: Now, any object can receive notifications from the light using:
  
-<code csharp>+```csharp
     theLight.OnChanged += <myMethod>;     theLight.OnChanged += <myMethod>;
-</code>+```
  
  
Line 500: Line 487:
 By default, both methods return `RUNNING` when the result has not changed, denoting that nothing has changed. For clarifying, the default implementation in `ConditionBase` of both methods is, in essence: By default, both methods return `RUNNING` when the result has not changed, denoting that nothing has changed. For clarifying, the default implementation in `ConditionBase` of both methods is, in essence:
  
-<code csharp>+```csharp
 public virtual Tasks.TaskStatus MonitorFailWhenFalse() public virtual Tasks.TaskStatus MonitorFailWhenFalse()
 { {
Line 516: Line 503:
         return Tasks.TaskStatus.COMPLETED;         return Tasks.TaskStatus.COMPLETED;
 } }
-</code>+```
  
  
Line 525: Line 512:
 With all this in mind, a better implementation of our `IsNight` condition, using the event we have just added, becomes: With all this in mind, a better implementation of our `IsNight` condition, using the event we have just added, becomes:
  
-<code csharp>+```csharp
 using UnityEngine; using UnityEngine;
  
Line 620: Line 607:
  
 } // class IsNightCondition } // class IsNightCondition
-</code> +```
  
 `MonitorCompleteWhenTrue()` and `MonitorFailWhenFalse()` are symmetric, so we only detail here the first one. Imagine the execution engine starts the priority selector. It calls `Check()` and notices that the result is false (the sun is in the sky). It analyzes the subsequent children and decides which sub-behavior execute (that is not important for our current analysis). In the next cycles, it will not call `Check()` directly, but `MonitorCompleteWhenTrue()`. Our overridden implementation checks the light just once and, if it is still "on", it registers itself in the light event. The light is supposed to trigger the event when its state changes (becomes "off"), so we return `SUSPEND` to the execution engine, freeing valuable resources. `MonitorCompleteWhenTrue()` and `MonitorFailWhenFalse()` are symmetric, so we only detail here the first one. Imagine the execution engine starts the priority selector. It calls `Check()` and notices that the result is false (the sun is in the sky). It analyzes the subsequent children and decides which sub-behavior execute (that is not important for our current analysis). In the next cycles, it will not call `Check()` directly, but `MonitorCompleteWhenTrue()`. Our overridden implementation checks the light just once and, if it is still "on", it registers itself in the light event. The light is supposed to trigger the event when its state changes (becomes "off"), so we return `SUSPEND` to the execution engine, freeing valuable resources.
Line 635: Line 621:
 To solve this issue, the execution engine will call a new condition method, `OnAbort()`, equivalent to that namesake method in `Action`. Regarding `IsNight` condition, all this means that we should implement that method and unregister from any event we could have registered previously: To solve this issue, the execution engine will call a new condition method, `OnAbort()`, equivalent to that namesake method in `Action`. Regarding `IsNight` condition, all this means that we should implement that method and unregister from any event we could have registered previously:
  
-<code csharp>+```csharp
 public override void OnAbort() public override void OnAbort()
 { {
Line 645: Line 631:
     base.OnAbort();     base.OnAbort();
 } }
-</code>+```
  
 ## What's next? ## What's next?
Line 653: Line 639:
 Now, enjoy your newly acquired skills. But don't forget the motto: "two weeks programming can save you from one hour in the library". Before creating your own action or condition, browse the Collection! Behavior Bricks provides a rich built-in set of action and conditions, ready-to-use. Use them at will! Now, enjoy your newly acquired skills. But don't forget the motto: "two weeks programming can save you from one hour in the library". Before creating your own action or condition, browse the Collection! Behavior Bricks provides a rich built-in set of action and conditions, ready-to-use. Use them at will!
  
-<!-- 
- 
- 
-El comportamiento PursueAndWander esta abortando el MoveToPosition todo el rato cuando te sigue? Vamos, diria que no, pero no lo se :-? 
- 
-En el comportamiento devolvemos COMPLETED o RUNNING si no hay shootPoint. Devolver FAILED y asi se introduce? Hacerlo con numero de balas? :-m :-m Eso exigiria poder cambiarla en ejecucion... y no se puede :-? 
- 
-Cosas con las que seguir que NO requieren programacion: 
- 
-  - Si se ejecuta, el tio se pone a disparar como un loco. Si en ese momento el jugador se para y se retira un poco, las balas le pasaran silvando sin tocarle. El enemigo NO rota para encarar al player una vez que se ha parado: anyadir un paralel que tenga como hijo al shoot, y un segundo nodo que sea un "LookAt" al player para que le encare. 
-  - Como la accion de disparar construye una bala y termina, que esta pasando aqui?: 
-    - La accion de disparar termina. 
-    - Como ha terminado con exito un hijo del priority, el priority termina con exito. 
-    - Como el hijo ha terminado, el "Repeat" repite y reejecuta el priority. 
-    - Como el jugador esta aun a la vista, el priority elige su primer hijo. 
-    - Goto arriba. 
-  - Y esto ocurre tan rapido como sea posible (se replanifica en el siguiente game loop tras la finalizacion del ShootOnce). 
-  - Para mejorarlo, se puede anyadir un retraso directamente en el BT, usando una secuencia de disparo - WaitNUpdates (o WaitForSeconds). 
- 
- 
-Para la ProgrammersQuickStartGuide no sirven porque no aportan nada (salvo el Paralel, quiza, que no se cuenta en la Quick Start Guide, pero bueno). 
- 
- --> 
- 
-<!-- 
- 
-Siguientes pasos de la PQSG: 
- 
-- Acciones latentes 
-  - [ Se asume que no se ha hecho el BT con el WaitNUpdates ]: esto dispara como un condenado, queremos meter retraso. Podriamos meterlo con secuencias y acciones del BT, pero para ejemplificar mas posibilidades del API de BB lo haremos por codigo. Ampliaremos la accion para que tenga un parametro nuevo que nos diga cuanto esperar entre shoot's. En realidad... crearemos una nueva a partir del codigo de esta y la llamamos "Shoot" (y no ShootOnce"). 
-  - Anyadimos un parametro nuevo "delay" en la clase. 
-  - Anyadimos un atributo privado nuevo (sin atributo InParam, no es visible hacia fuera) con un contador. 
-  - Cambiamos el OnUpdate para que dispare, solo cuando el contador llegue a 0. En ese momento, se reestablece a `delay` y se repite el ciclo. 
-  - Ya nunca mas devolvemos COMPLETED, porque no acabamos: queremos que nos sigan llamando. 
-  - Si nos llaman todo el rato, tener la comprobacion de si se ha establecido el shootPoint en cada ciclo es tonteria. Metodo OnStart() para hacerlo ahi. 
-  - Aviso a navegantes: los atributos de las acciones _desaparecen_ entre ejecuciones _diferentes_: si la accion termina y se relanza, se hara con una instancia nueva. 
- 
- 
- 
-Y ahora la creamos. Las acciones deben heredar de BasePrimitiveAction (cambiar respecto al de David), e implementar el metodo OnUpdate(). El metodo sera llamado por el motor de ejecucion durante el tiempo que la accion este en marcha. Hay que decir si queremos seguir, o no. Exito o fracaso? Cuidado: no guardes cosas en la clase que quieras conservar entre ejecuciones, que se hacen news en cada paso. No tenemos el go que ejecuta el comportamiento. Si lo queremos, heredamos de GOAction. 
- 
-Poner al principio que las capturas son de UnityPro, y de MonoEditor? (no se si se necesitara alguna), pero tu mismo. Conocimientos previos de programacion? 
- 
- 
-Pada1.BBCore.Tasks.TaskStatus 
-  Todos salvo ABORTED y NONE. 
- 
- 
-Parametros de acciones/condiciones: 
-  - Pueden sr propiedades 
-  - Atributos: 
-    - InParam(<nombre> 
-        [InParam("Text", DefaultValue = "Mensaje por defecto")] 
-        [InParam("Count", typeof(int))] 
-        [Help("Message to be written")] 
- 
- 
-Idea: 
- 
-- Accion basica: shoot que hay ya, pero sin el Destroy de la bala (que se destruya ella misma). 
-  - Reflexion sobre el arbol completo: hemos terminado con COMPLETED la accion, el arbol termina, y se reinicia todo porque tenemos el loop. 
-- Accion latente: Podriamos hacer que el disparar no terminara nunca: disparar 1 de cada N. En realidad podriamos hacerlo con un subcomportamiento aprovechando el waitN (existe, no?) con un loop y una secuencia, pero bueno. Introducir el RUNNING. Contador de numero de loops, y configurable. 
-- Ciclo de vida de una accion: el ejemplo anterior usa el numero de loops... un tanto incierto en tiempo (update()) o poco intuitivo (si pasamos a fixedUpdate()). Medir el tiempo de verdad. Hay que coger el tick inicial... explicar onStart(). Hay un onEnd() que se podrian explicar aunque no se usen. 
-- Temporizador para SUSPEND? :-m :-m No se como se hace. 
-- Mirar si las condiciones usadas en el enemigo de detectar la cercania del player y demas se suspenden... :-m Igual podria usarse esto :-m :-m 
-- Creating conditions. Vamos al Player. Queremos que haga un sonido cuando le de una bala. Mmm.... loop + secuencia + (condicion, accion)? Otro modo "mejor" es loop + guard + accion (como se programaria esta accion? Supongo que con onTrigger y el flag...). NO. Mejor uno nuevo que haga el Click and go, pero si le dan, pare el Go que hubiera y reproduzca el sonido. Asi es un priority directamente. Si no, queda chungo porque si se pone loop + guard + accion luego cuando se use el monitor en la implementacion de la accion nos da lo mismo porque el priority termina siempre y se reejecuta el loop y empieza otra vez... necesitariamos una accion "sumidero" con condicion AlwaysTrue y que se suspenda sin hacer nada... necesitamos esto de todas formas en la otra? Mmmm... 
-- Latent conditions: lo anterior es una mierda porque cuesta mucho en ejecucion al llamar al Check todo el tiempo. Condiciones suspendidas (solo para guard / priorities!!). 
- 
- 
-Se queda pendiente el onAbort, onLatentStart y cosas asi, pero bueno. 
- 
- 
-Para el lint: detectar "el patron" loop + secuencia + (condicion, accion) y recomendar usar Guard. 
  
-TODO: tras acabarlo, cambiar el proyecto final para usar los nombres "Done". Pongo MyActions/... de momento para las capturas, pero luego hay que cambiarlas. La lata es que el cambio de nombre en el codigo hace reventar el editor de maneras raras...+</markdown>
  
--->