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 15:49]
rodrigobravo
quick:program [2020/09/03 11:36] (current)
pedro
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>+```
  
  
Line 349: Line 349:
 - 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 379:
         }         }
     } // class DayNightCycle     } // class DayNightCycle
-</code>+```
  
  
Line 386: Line 386:
 - 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 402:
 - 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 427:
         }         }
     } // class IsNightCondition     } // class IsNightCondition
-</code>+```
  
  
Line 434: Line 434:
 - 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 449:
 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 461:
 - 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 482:
         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 500:
 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 516:
         return Tasks.TaskStatus.COMPLETED;         return Tasks.TaskStatus.COMPLETED;
 } }
-</code>+```
  
  
Line 525: Line 525:
 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 620:
  
 } // 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 634:
 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 644:
     base.OnAbort();     base.OnAbort();
 } }
-</code>+```
  
 ## What's next? ## What's next?
Line 730: Line 729:
  
 --> -->
 +
 +</markdown>