Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revisionLast revisionBoth sides next revision | ||
quick:program [2020/09/03 09:11] – pedro | quick:program [2020/09/03 09:36] – pedro | ||
---|---|---|---|
Line 17: | Line 17: | ||
- | {{ : | + | ![](: |
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 45: | Line 45: | ||
- Open it into your preferred editor and substitute the code: | - Open it into your preferred editor and substitute the code: | ||
- | < | + | ```csharp |
using UnityEngine; | using UnityEngine; | ||
Line 91: | Line 91: | ||
| | ||
} // class ShootOnce | } // class ShootOnce | ||
- | </ | + | ``` |
Some things to note: | Some things to note: | ||
Line 105: | Line 105: | ||
- Create a new node in the canvas for the `ShootOnce` action. Note the action parameters in the inspector and, specifically, | - Create a new node in the canvas for the `ShootOnce` action. Note the action parameters in the inspector and, specifically, | ||
- | {{ : | + | ![]( : |
- 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, | - 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, | ||
- | {{ : | + | ![]( : |
- 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/ | - Right click on the default guard `AlwaysTrue` of the `ShootOnce` node, select `Replace with ...` and choose `Perception/ | ||
- | {{ : | + | ![]( : |
- 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 123: | 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. | ||
- | < | + | ```csharp |
void Start() { | void Start() { | ||
Destroy(gameObject, | Destroy(gameObject, | ||
} | } | ||
- | </ | + | ``` |
Line 151: | 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: | ||
- | < | + | ```csharp |
public override TaskStatus OnUpdate() | public override TaskStatus OnUpdate() | ||
{ | { | ||
Line 167: | Line 167: | ||
[ ... ] | [ ... ] | ||
} // OnUpdate | } // OnUpdate | ||
- | </ | + | ``` |
Line 181: | 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. | ||
- | {{ : | + | ![]( : |
Our `ShootOnce` action ends immediately after instantiating the bullet. That causes _the end of the parent_ priority selector. Fortunately, | Our `ShootOnce` action ends immediately after instantiating the bullet. That causes _the end of the parent_ priority selector. Fortunately, | ||
Line 193: | 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:: | - 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:: | ||
- | < | + | ```csharp |
using Pada1.BBCore; | using Pada1.BBCore; | ||
using Pada1.BBCore.Tasks; | using Pada1.BBCore.Tasks; | ||
Line 227: | Line 227: | ||
} // class Shoot | } // class Shoot | ||
- | </ | + | ``` |
- 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 236: | 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: | ||
- | < | + | ```csharp |
IEnumerator OnUpdateAsCoroutine() { | IEnumerator OnUpdateAsCoroutine() { | ||
while(true) { | while(true) { | ||
Line 250: | Line 249: | ||
} // infinite loop | } // infinite loop | ||
} | } | ||
- | </ | + | ``` |
Line 266: | 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: | ||
- | < | + | ```csharp |
// Be careful! This is the ShootOnce action, not Shoot! | // Be careful! This is the ShootOnce action, not Shoot! | ||
Line 293: | Line 292: | ||
[ ... ] | [ ... ] | ||
} // OnUpdate | } // OnUpdate | ||
- | </ | + | ``` |
Line 308: | 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 " | 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 " | ||
- | < | + | ```csharp |
using UnityEngine; | using UnityEngine; | ||
Line 328: | Line 327: | ||
} // class SleepForever | } // class SleepForever | ||
- | </ | + | ``` |
Line 350: | 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`: | ||
- | < | + | ```csharp |
using UnityEngine; | using UnityEngine; | ||
| | ||
Line 380: | Line 379: | ||
} | } | ||
} // class DayNightCycle | } // class DayNightCycle | ||
- | </ | + | ``` |
Line 387: | 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, | - 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, | ||
- | {{ : | + | ![]( : |
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 403: | Line 402: | ||
- Open it into your preferred editor and substitute the code: | - Open it into your preferred editor and substitute the code: | ||
- | < | + | ```csharp |
using UnityEngine; | using UnityEngine; | ||
Line 428: | Line 427: | ||
} | } | ||
} // class IsNightCondition | } // class IsNightCondition | ||
- | </ | + | ``` |
Line 435: | 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. | ||
- | {{ : | + | ![]( : |
- 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 450: | 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. | ||
- | {{ : | + | ![]( : |
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 462: | Line 461: | ||
- Add a new event to the `DayNightCycle.cs` script: | - Add a new event to the `DayNightCycle.cs` script: | ||
- | < | + | ```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; | ||
- | </ | + | ``` |
- 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: | ||
- | < | + | ```csharp |
void Update() | void Update() | ||
{ | { | ||
Line 483: | Line 482: | ||
GetComponent< | GetComponent< | ||
} | } | ||
- | </ | + | ``` |
Now, any object can receive notifications from the light using: | Now, any object can receive notifications from the light using: | ||
- | < | + | ```csharp |
theLight.OnChanged += < | theLight.OnChanged += < | ||
- | </ | + | ``` |
Line 501: | 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: | ||
- | < | + | ```csharp |
public virtual Tasks.TaskStatus MonitorFailWhenFalse() | public virtual Tasks.TaskStatus MonitorFailWhenFalse() | ||
{ | { | ||
Line 517: | Line 516: | ||
return Tasks.TaskStatus.COMPLETED; | return Tasks.TaskStatus.COMPLETED; | ||
} | } | ||
- | </ | + | ``` |
Line 526: | 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: | ||
- | < | + | ```csharp |
using UnityEngine; | using UnityEngine; | ||
Line 621: | Line 620: | ||
} // class IsNightCondition | } // class IsNightCondition | ||
- | </ | + | ``` |
`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 " | `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 " | ||
Line 636: | Line 634: | ||
To solve this issue, the execution engine will call a new condition method, `OnAbort()`, | To solve this issue, the execution engine will call a new condition method, `OnAbort()`, | ||
- | < | + | ```csharp |
public override void OnAbort() | public override void OnAbort() | ||
{ | { | ||
Line 646: | Line 644: | ||
base.OnAbort(); | base.OnAbort(); | ||
} | } | ||
- | </ | + | ``` |
## What's next? | ## What's next? | ||
Line 732: | Line 730: | ||
--> | --> | ||
- | < | + | </markdown> |