diff --git a/Chapter09/Assets/BehaviorBricks.meta b/Chapter09/Assets/BehaviorBricks.meta
deleted file mode 100644
index 05f611f..0000000
--- a/Chapter09/Assets/BehaviorBricks.meta
+++ /dev/null
@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: d5519bf1599a35a47842931052d9be3b
-folderAsset: yes
-DefaultImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/Chapter09/Assets/Behaviors/DoneAbortableClickAndGo.asset b/Chapter09/Assets/Behaviors/DoneAbortableClickAndGo.asset
deleted file mode 100644
index 417ccba..0000000
--- a/Chapter09/Assets/Behaviors/DoneAbortableClickAndGo.asset
+++ /dev/null
@@ -1,121 +0,0 @@
-%YAML 1.1
-%TAG !u! tag:unity3d.com,2011:
---- !u!114 &11400000
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInternal: {fileID: 0}
- m_GameObject: {fileID: 0}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 34a7c8ca992f915438a96c2077353778, type: 3}
- m_Name: DoneAbortableClickAndGo
- m_EditorClassIdentifier:
- brickName: Assets/Behaviors/DoneAbortableClickAndGo.asset
- xml: "\uFEFF\r\n\r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n
- \ \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n
- \ \r\n \r\n \r\n
- \ \r\n \r\n
- \ \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n
- \ \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n
- \ <_rootList>\r\n \r\n \r\n
- \ \r\n \r\n \r\n
- \ \r\n \r\n \r\n
- \ \r\n \r\n <_nodes>\r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <_data>\r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n <_data>\r\n
- \ \r\n
- \ \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n <_data>\r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n <_data>\r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n <_data>\r\n
- \ \r\n
- \ \r\n \r\n
- \ \r\n \r\n \r\n
- \ \r\n \r\n <_data>\r\n \r\n \r\n
- \ \r\n \r\n
- \ \r\n \r\n \r\n \r\n
- \ \r\n \r\n
- \ \r\n \r\n \r\n \r\n
- \ \r\n
- \ \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n <_data>\r\n
- \ \r\n
- \ \r\n \r\n \r\n \r\n
- \ \r\n <_data>\r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n <_rootList />\r\n
- \ <_nodes>\r\n \r\n \r\n \r\n
- \ \r\n <_rootList />\r\n
- \ <_nodes>\r\n \r\n \r\n \r\n
- \ \r\n"
- zoom: 1
- subslist: []
- _guid: 617eb351eb6e2a64798c9b6c3423d876
diff --git a/Chapter09/Assets/Behaviors/DoneAbortableClickAndGo.asset.meta b/Chapter09/Assets/Behaviors/DoneAbortableClickAndGo.asset.meta
deleted file mode 100644
index 9c4ed78..0000000
--- a/Chapter09/Assets/Behaviors/DoneAbortableClickAndGo.asset.meta
+++ /dev/null
@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: 617eb351eb6e2a64798c9b6c3423d876
-NativeFormatImporter:
- externalObjects: {}
- mainObjectFileID: 0
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/Chapter09/Assets/Behaviors/EnemyTree.asset b/Chapter09/Assets/Behaviors/EnemyTree.asset
deleted file mode 100644
index 1b222c0..0000000
--- a/Chapter09/Assets/Behaviors/EnemyTree.asset
+++ /dev/null
@@ -1,144 +0,0 @@
-%YAML 1.1
-%TAG !u! tag:unity3d.com,2011:
---- !u!114 &11400000
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInternal: {fileID: 0}
- m_GameObject: {fileID: 0}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 34a7c8ca992f915438a96c2077353778, type: 3}
- m_Name: EnemyTree
- m_EditorClassIdentifier:
- brickName: Assets/Behaviors/EnemyTree.asset
- xml: "\uFEFF\r\n\r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n
- \ \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n
- \ \r\n \r\n
- \ \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n
- \ \r\n <_guid>23a4bbec9d68e58448b2f39637bbb586\r\n
- \ \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ <_rootList>\r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n
- \ \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <_nodes>\r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n <_data>\r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n <_data>\r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n <_data>\r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n <_data>\r\n \r\n
- \ \r\n \r\n \r\n
- \ \r\n \r\n \r\n <_data>\r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n <_data>\r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n <_data>\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n <_data>\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <_data>\r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n
- \ <_data>\r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n"
- zoom: 1
- subslist:
- - {fileID: 11400000, guid: 23a4bbec9d68e58448b2f39637bbb586, type: 2}
- _guid: f46677e0463f8e044a1b93d3c66d65e2
diff --git a/Chapter09/Assets/Behaviors/EnemyTree.asset.meta b/Chapter09/Assets/Behaviors/EnemyTree.asset.meta
deleted file mode 100644
index 80a0a4d..0000000
--- a/Chapter09/Assets/Behaviors/EnemyTree.asset.meta
+++ /dev/null
@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: f46677e0463f8e044a1b93d3c66d65e2
-NativeFormatImporter:
- externalObjects: {}
- mainObjectFileID: 11400000
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/Chapter09/Assets/Behaviors/Wander.asset b/Chapter09/Assets/Behaviors/Wander.asset
deleted file mode 100644
index d248bcc..0000000
--- a/Chapter09/Assets/Behaviors/Wander.asset
+++ /dev/null
@@ -1,71 +0,0 @@
-%YAML 1.1
-%TAG !u! tag:unity3d.com,2011:
---- !u!114 &11400000
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInternal: {fileID: 0}
- m_GameObject: {fileID: 0}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 34a7c8ca992f915438a96c2077353778, type: 3}
- m_Name: Wander
- m_EditorClassIdentifier:
- brickName: Assets/Behaviors/Wander.asset
- xml: "\uFEFF\r\n\r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n
- \ \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n \r\n \r\n
- \ \r\n <_rootList>\r\n \r\n
- \ \r\n \r\n \r\n
- \ <_nodes>\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n <_data>\r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n <_data>\r\n \r\n \r\n
- \ \r\n \r\n
- \ \r\n \r\n \r\n \r\n
- \ <_data>\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <_data>\r\n \r\n \r\n \r\n \r\n \r\n \r\n
- \ \r\n \r\n"
- zoom: 1
- subslist: []
- _guid: 23a4bbec9d68e58448b2f39637bbb586
diff --git a/Chapter09/Assets/Behaviors/Wander.asset.meta b/Chapter09/Assets/Behaviors/Wander.asset.meta
deleted file mode 100644
index 9f43c69..0000000
--- a/Chapter09/Assets/Behaviors/Wander.asset.meta
+++ /dev/null
@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: 23a4bbec9d68e58448b2f39637bbb586
-NativeFormatImporter:
- externalObjects: {}
- mainObjectFileID: 0
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/Chapter09/Assets/Scenes/BehaviorTreeDemo.unity b/Chapter09/Assets/Scenes/BehaviorTreeDemo.unity
index 233012d..b91437f 100644
--- a/Chapter09/Assets/Scenes/BehaviorTreeDemo.unity
+++ b/Chapter09/Assets/Scenes/BehaviorTreeDemo.unity
@@ -137,6 +137,7 @@ GameObject:
- component: {fileID: 99947097}
- component: {fileID: 99947102}
- component: {fileID: 99947101}
+ - component: {fileID: 99947103}
m_Layer: 0
m_Name: Player
m_TagString: Untagged
@@ -231,66 +232,13 @@ MonoBehaviour:
m_GameObject: {fileID: 99947096}
m_Enabled: 1
m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 422fb6956201c7944ae2113a1a53b0b8, type: 3}
+ m_Script: {fileID: 11500000, guid: b2c3d4e5f67890abcdef12345678901a, type: 3}
m_Name:
m_EditorClassIdentifier:
- maxTasksPerTick: 500
- behavior: {fileID: 11400000, guid: 617eb351eb6e2a64798c9b6c3423d876, type: 2}
- paused: 0
- restartWhenFinished: 0
- blackboard:
- intParams:
- intParamsNames: []
- intParamsLostDefaultValue:
- boolParams:
- boolParamsNames: []
- boolParamsLostDefaultValue:
- floatParams: []
- floatParamsNames: []
- floatParamsLostDefaultValue:
- stringParams: []
- stringParamsNames: []
- stringParamsLostDefaultValue:
- colorParams: []
- colorParamsNames: []
- colorParamsLostDefaultValue:
- objectParams:
- - {fileID: 799500328}
- objectParamsNames:
- - camera
- objectParamsLostDefaultValue:
- layerMaskParams:
- - serializedVersion: 2
- m_Bits: 4294967295
- layerMaskParamsNames:
- - mask
- layerMaskParamsLostDefaultValue:
- enumParamsNames: []
- enumParamsLostDefaultValue:
- vector2Params: []
- vector2ParamsNames: []
- vector2ParamsLostDefaultValue:
- vector3Params: []
- vector3ParamsNames: []
- vector3ParamsLostDefaultValue:
- vector4Params: []
- vector4ParamsNames: []
- vector4ParamsLostDefaultValue:
- rectParams: []
- rectParamsNames: []
- rectParamsLostDefaultValue:
- animationCurveParams: []
- animationCurveParamsNames: []
- animationCurveParamsLostDefaultValue:
- boundsParams: []
- boundsParamsNames: []
- boundsParamsLostDefaultValue:
- gradientParams: []
- gradientParamsNames: []
- gradientParamsLostDefaultValue:
- quaternionParams: []
- quaternionParamsNames: []
- quaternionParamsLostDefaultValue:
+ cameraRef: {fileID: 799500328}
+ layerMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
--- !u!195 &99947102
NavMeshAgent:
m_ObjectHideFlags: 0
@@ -313,6 +261,25 @@ NavMeshAgent:
m_BaseOffset: 0.5
m_WalkableMask: 4294967295
m_ObstacleAvoidanceType: 4
+--- !u!114 &99947103
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 99947096}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: d4e5f67890abcdef12345678901b2c3d, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ cameraRef: {fileID: 799500328}
+ shootPoint: {fileID: 0}
+ bullet: {fileID: 100000, guid: c960b0a8d3f789c4ea257bb6a16ab2a2, type: 3}
+ bulletVelocity: 30
+ layerMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
--- !u!1 &159678351
GameObject:
m_ObjectHideFlags: 0
@@ -422,69 +389,17 @@ MonoBehaviour:
m_GameObject: {fileID: 159678351}
m_Enabled: 1
m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 422fb6956201c7944ae2113a1a53b0b8, type: 3}
+ m_Script: {fileID: 11500000, guid: a1b2c3d4e5f67890abcdef1234567890, type: 3}
m_Name:
m_EditorClassIdentifier:
- maxTasksPerTick: 500
- behavior: {fileID: 11400000, guid: f46677e0463f8e044a1b93d3c66d65e2, type: 2}
- paused: 0
- restartWhenFinished: 0
- blackboard:
- intParams:
- intParamsNames: []
- intParamsLostDefaultValue:
- boolParams:
- boolParamsNames: []
- boolParamsLostDefaultValue:
- floatParams: []
- floatParamsNames: []
- floatParamsLostDefaultValue:
- stringParams: []
- stringParamsNames: []
- stringParamsLostDefaultValue:
- colorParams: []
- colorParamsNames: []
- colorParamsLostDefaultValue:
- objectParams:
- - {fileID: 99947096}
- - {fileID: 2016349709}
- - {fileID: 1378570865}
- - {fileID: 100000, guid: c960b0a8d3f789c4ea257bb6a16ab2a2, type: 3}
- objectParamsNames:
- - player
- - floor
- - shootPoint
- - bullet
- objectParamsLostDefaultValue:
- layerMaskParams: []
- layerMaskParamsNames: []
- layerMaskParamsLostDefaultValue:
- enumParamsNames: []
- enumParamsLostDefaultValue:
- vector2Params: []
- vector2ParamsNames: []
- vector2ParamsLostDefaultValue:
- vector3Params: []
- vector3ParamsNames: []
- vector3ParamsLostDefaultValue:
- vector4Params: []
- vector4ParamsNames: []
- vector4ParamsLostDefaultValue:
- rectParams: []
- rectParamsNames: []
- rectParamsLostDefaultValue:
- animationCurveParams: []
- animationCurveParamsNames: []
- animationCurveParamsLostDefaultValue:
- boundsParams: []
- boundsParamsNames: []
- boundsParamsLostDefaultValue:
- gradientParams: []
- gradientParamsNames: []
- gradientParamsLostDefaultValue:
- quaternionParams: []
- quaternionParamsNames: []
- quaternionParamsLostDefaultValue:
+ player: {fileID: 99947096}
+ wanderArea: {fileID: 2016349709}
+ shootPoint: {fileID: 1378570865}
+ bullet: {fileID: 100000, guid: c960b0a8d3f789c4ea257bb6a16ab2a2, type: 3}
+ shootDistance: 7
+ chaseDistance: 20
+ shootDelay: 1
+ bulletVelocity: 30
--- !u!195 &159678357
NavMeshAgent:
m_ObjectHideFlags: 0
diff --git a/Chapter09/Assets/Scripts/EnemyAI.cs b/Chapter09/Assets/Scripts/EnemyAI.cs
new file mode 100644
index 0000000..1e0963b
--- /dev/null
+++ b/Chapter09/Assets/Scripts/EnemyAI.cs
@@ -0,0 +1,169 @@
+using UnityEngine;
+using UnityEngine.AI;
+
+///
+/// EnemyAI implements the enemy behavior tree logic directly in C#.
+/// It uses a priority-selector pattern (the core of a behavior tree) evaluated each frame:
+///
+/// - If it is night, the enemy sleeps (does nothing).
+/// - If the player is within , the enemy shoots.
+/// - If the player is within , the enemy chases the player.
+/// - Otherwise, the enemy wanders randomly across the navigation mesh.
+///
+/// This script replaces the BehaviorBricks component and works with Unity 6.
+/// For the Unity Native Behavior visual-graph approach, see the custom node scripts
+/// (IsNightCondition, ShootAction, ShootOnceAction, SleepForeverAction) and the
+/// EnemyTree.behavior asset that must be created in the Unity Behavior editor.
+///
+public class EnemyAI : MonoBehaviour
+{
+ [Header("Scene References")]
+ /// The player GameObject that the enemy reacts to.
+ public GameObject player;
+
+ /// The floor/area GameObject used as the wander bounds.
+ public GameObject wanderArea;
+
+ ///
+ /// Transform marking the bullet spawn position and forward direction.
+ /// If not assigned, the script searches for a child named "shootPoint".
+ ///
+ public Transform shootPoint;
+
+ /// The bullet prefab to instantiate when shooting.
+ public GameObject bullet;
+
+ [Header("Behavior Settings")]
+ /// Distance at which the enemy switches from chasing to shooting.
+ public float shootDistance = 7f;
+
+ /// Distance at which the enemy starts chasing the player.
+ public float chaseDistance = 20f;
+
+ /// Seconds between shots.
+ public float shootDelay = 1f;
+
+ /// Launch speed of the bullet in units per second.
+ public float bulletVelocity = 30f;
+
+ private NavMeshAgent agent;
+ private DayNightCycle dayNightCycle;
+ private float shootTimer;
+ private bool isWandering;
+
+ void Start()
+ {
+ agent = GetComponent();
+
+ // Find the DayNightCycle component on the "MainLight" tagged object.
+ GameObject lightGO = GameObject.FindGameObjectWithTag("MainLight");
+ if (lightGO != null)
+ dayNightCycle = lightGO.GetComponent();
+
+ // Find shoot point in children if not assigned.
+ if (shootPoint == null)
+ shootPoint = transform.Find("shootPoint");
+
+ shootTimer = shootDelay;
+ }
+
+ void Update()
+ {
+ // Priority 1: Sleep during night time.
+ if (dayNightCycle != null && dayNightCycle.IsNight)
+ {
+ agent.isStopped = true;
+ return;
+ }
+
+ agent.isStopped = false;
+
+ if (player == null)
+ {
+ Wander();
+ return;
+ }
+
+ float distToPlayer = Vector3.Distance(transform.position, player.transform.position);
+
+ // Priority 2: Shoot when close enough.
+ if (distToPlayer <= shootDistance)
+ {
+ agent.ResetPath();
+ ShootAtPlayer();
+ return;
+ }
+
+ // Priority 3: Chase the player when within range.
+ if (distToPlayer <= chaseDistance)
+ {
+ isWandering = false;
+ agent.SetDestination(player.transform.position);
+ return;
+ }
+
+ // Priority 4: Wander randomly.
+ Wander();
+ }
+
+ private void ShootAtPlayer()
+ {
+ if (shootPoint == null || bullet == null)
+ return;
+
+ // Rotate to face the player on the horizontal plane.
+ Vector3 direction = (player.transform.position - transform.position).normalized;
+ transform.rotation = Quaternion.LookRotation(new Vector3(direction.x, 0f, direction.z));
+
+ shootTimer -= Time.deltaTime;
+ if (shootTimer > 0f)
+ return;
+
+ shootTimer = shootDelay;
+
+ GameObject newBullet = Instantiate(
+ bullet, shootPoint.position,
+ shootPoint.rotation * bullet.transform.rotation);
+
+ Rigidbody rb = newBullet.GetComponent();
+ if (rb == null)
+ rb = newBullet.AddComponent();
+
+ rb.linearVelocity = bulletVelocity * shootPoint.forward;
+ }
+
+ private void Wander()
+ {
+ // Pick a new destination when we arrive or haven't started yet.
+ if (!isWandering || !agent.hasPath || agent.remainingDistance < 0.5f)
+ {
+ Vector3 target = GetRandomNavMeshPosition();
+ agent.SetDestination(target);
+ isWandering = true;
+ }
+ }
+
+ private Vector3 GetRandomNavMeshPosition()
+ {
+ Vector3 center = transform.position;
+ float radius = 10f;
+
+ if (wanderArea != null)
+ {
+ center = wanderArea.transform.position;
+ // Use half the floor's scale as the wander extent.
+ Vector3 scale = wanderArea.transform.localScale;
+ radius = Mathf.Min(scale.x, scale.z) * 0.5f;
+ }
+
+ Vector3 randomPoint = center + new Vector3(
+ Random.Range(-radius, radius),
+ 0f,
+ Random.Range(-radius, radius));
+
+ if (NavMesh.SamplePosition(randomPoint, out NavMeshHit hit, radius * 2f, NavMesh.AllAreas))
+ return hit.position;
+
+ return transform.position;
+ }
+}
diff --git a/Chapter09/Assets/Scripts/EnemyAI.cs.meta b/Chapter09/Assets/Scripts/EnemyAI.cs.meta
new file mode 100644
index 0000000..4dfdbb3
--- /dev/null
+++ b/Chapter09/Assets/Scripts/EnemyAI.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a1b2c3d4e5f67890abcdef1234567890
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Chapter09/Assets/Scripts/IsNight.cs b/Chapter09/Assets/Scripts/IsNight.cs
index da7e4a9..5bd1090 100644
--- a/Chapter09/Assets/Scripts/IsNight.cs
+++ b/Chapter09/Assets/Scripts/IsNight.cs
@@ -1,135 +1,30 @@
-using Pada1.BBCore; // Code attributes
-using Pada1.BBCore.Framework; // ConditionBase
-using Pada1.BBCore.Tasks; // TaskStatus
+using System;
+using Unity.Behavior;
+using Unity.Properties;
using UnityEngine;
///
-/// DoneIsNightCondition is a condittion inherited from ConditionBase and Checks whether it is night.
-/// It searches for the first light labeled with the 'MainLight' tag, and looks for its DayNightCycle script, returning the
-/// informed state. If no light is found, false is returned.
+/// IsNightCondition is a Unity Behavior condition node that checks whether it is night.
+/// It searches for the first GameObject tagged "MainLight" and reads the DayNightCycle
+/// component to determine the current time of day.
+/// If no light is found, the condition returns false.
///
-///
-[Condition("Chapter09/IsNight")]
-[Help("Checks whether it is night.")]
-public class IsNightCondition : ConditionBase {
- ///
- /// Method Checks if there is DoneDayNightCycle component.
- ///
- /// True if is night in DoneDayNightCycle component.
- public override bool Check() {
- return SearchLight() && light.IsNight;
- }
-
- ///
- /// Method invoked by the execution engine when the condition is used in a priority selector and its last value was false.
- ///
- /// It must return COMPLETED when the value
- /// becomes true. In other case, it can return RUNNING if the method should be
- /// invoked again in the next game cycle, or SUSPEND if we will be notified of the
- /// change through any other mechanism.
-
-
- // Method invoked by the execution engine when the condition is used in a priority
- // selector and its last value was false. It must return COMPLETED when the value
- // becomes true. In other case, it can return RUNNING if the method should be
- // invoked again in the next game cycle, or SUSPEND if we will be notified of the
- // change through any other mechanism.
- public override TaskStatus MonitorCompleteWhenTrue() {
- if (Check()) {
- // Light is off. It's night right now.
- return TaskStatus.COMPLETED;
- }
- // Light does not exist, or is "on". We must register ourselves in the
- // light event so we will be notified when the sun sets. In the mean time,
- // we do not need to be called anymore.
- if (light != null) {
- light.OnChanged += OnSunset;
- }
- return TaskStatus.SUSPENDED;
- // We will never awake if light does not exist.
-
- }
-
-
- ///
- ///Similar to MonitorCompleteWhenTrue, but used when the last condition value was
- /// true and the execution engine is checking that it has not become false.
- ///
- /// It must return FAILED when the value
- /// becomes false. In other case, it can return RUNNING if the method should be
- /// invoked again in the next game cycle, or SUSPEND if we will be notified of the
- /// change through any other mechanism.
-
- // Similar to MonitorCompleteWhenTrue, but used when the last condition value was
- // true and the execution engine is checking that it has not become false.
- public override TaskStatus MonitorFailWhenFalse() {
- if (!Check()) {
- // Light does not exist, or is "on" (daylight). Condition is false.
- return TaskStatus.FAILED;
- }
- // Light exists, and is "off" (night). We suspend ourselves
- // until sunrise (when the condition will become false).
- light.OnChanged += OnSunrise;
- return TaskStatus.SUSPENDED;
- }
-
-
-
- ///
- /// Method attached to the light event that will be called when the light is "off"
- /// again. We remove ourselves from the event, and notify the execution engine
- /// that the new condition value is true (it is night again).
- ///
- ///
- ///
-
-
- // Method attached to the light event that will be called when the light is "off"
- // again. We remove ourselves from the event, and notify the execution engine
- // that the new condition value is true (it is night again).
- public void OnSunset(object sender, System.EventArgs night) {
- light.OnChanged -= OnSunset;
- EndMonitorWithSuccess();
- } // OnSunset
-
-
- ///
- /// Similar to OnSunset, but used when we are monitoring the sunrise.
- ///
- ///
- ///
- // Similar to OnSunset, but used when we are monitoring the sunrise.
- public void OnSunrise(object sender, System.EventArgs e) {
- light.OnChanged -= OnSunrise;
- EndMonitorWithFailure();
- } // OnSunrise
-
- /// Abort method of MoveToGameObject.
- /// DoneDayNightCycle component exits we remove ourselves from the event.
- public override void OnAbort() {
- if (SearchLight()) {
- light.OnChanged -= OnSunrise;
- light.OnChanged -= OnSunset;
- }
- base.OnAbort();
- } // OnAbort
-
- // Search the global light, and stores in the light field. It returns true
- // if the light was found.
- private bool SearchLight() {
- if (light != null) {
- return true;
- }
-
+[Serializable, GeneratePropertyBag]
+[NodeDescription(
+ name: "Is Night",
+ story: "It is currently night",
+ category: "Chapter09",
+ id: "chapter09-isnight-condition-v1")]
+public partial class IsNightCondition : Condition
+{
+ /// Checks whether it is currently night via the DayNightCycle component.
+ /// True if it is night; false otherwise.
+ public override bool IsTrue()
+ {
GameObject lightGO = GameObject.FindGameObjectWithTag("MainLight");
- if (lightGO == null) {
+ if (lightGO == null)
return false;
- }
-
- light = lightGO.GetComponent();
- return light != null;
- } // searchLight
-
- private DayNightCycle light;
-
-}
\ No newline at end of file
+ DayNightCycle cycle = lightGO.GetComponent();
+ return cycle != null && cycle.IsNight;
+ }
+}
diff --git a/Chapter09/Assets/Scripts/PlayerClickToMove.cs b/Chapter09/Assets/Scripts/PlayerClickToMove.cs
new file mode 100644
index 0000000..1e05254
--- /dev/null
+++ b/Chapter09/Assets/Scripts/PlayerClickToMove.cs
@@ -0,0 +1,39 @@
+using UnityEngine;
+using UnityEngine.AI;
+
+///
+/// PlayerClickToMove moves the player to the position indicated by a left mouse click.
+/// A new click immediately overrides any previous destination.
+/// This script replaces the BehaviorBricks "DoneAbortableClickAndGo" behavior and works with Unity 6.
+///
+public class PlayerClickToMove : MonoBehaviour
+{
+ ///
+ /// The camera used to project the mouse ray into the scene.
+ /// If not assigned, is used.
+ ///
+ public Camera cameraRef;
+
+ /// Layer mask controlling which surfaces accept click destinations.
+ public LayerMask layerMask = ~0;
+
+ private NavMeshAgent agent;
+
+ void Start()
+ {
+ agent = GetComponent();
+
+ if (cameraRef == null)
+ cameraRef = Camera.main;
+ }
+
+ void Update()
+ {
+ if (Input.GetMouseButtonDown(0) && cameraRef != null)
+ {
+ Ray ray = cameraRef.ScreenPointToRay(Input.mousePosition);
+ if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, layerMask))
+ agent.SetDestination(hit.point);
+ }
+ }
+}
diff --git a/Chapter09/Assets/Scripts/PlayerClickToMove.cs.meta b/Chapter09/Assets/Scripts/PlayerClickToMove.cs.meta
new file mode 100644
index 0000000..bd255ca
--- /dev/null
+++ b/Chapter09/Assets/Scripts/PlayerClickToMove.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b2c3d4e5f67890abcdef12345678901a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Chapter09/Assets/Scripts/PlayerShoot.cs b/Chapter09/Assets/Scripts/PlayerShoot.cs
new file mode 100644
index 0000000..595fe88
--- /dev/null
+++ b/Chapter09/Assets/Scripts/PlayerShoot.cs
@@ -0,0 +1,60 @@
+using UnityEngine;
+
+///
+/// Allows the player to shoot a bullet toward a right-clicked point on the ground.
+/// Left-click moves the player ();
+/// right-click rotates the player toward the cursor and fires one bullet.
+///
+public class PlayerShoot : MonoBehaviour
+{
+ ///
+ /// The camera used to project the mouse ray. Falls back to if not assigned.
+ ///
+ public Camera cameraRef;
+
+ ///
+ /// The transform used as the bullet spawn point and forward direction.
+ /// If not assigned the player's own transform is used.
+ ///
+ public Transform shootPoint;
+
+ /// The bullet prefab to instantiate.
+ public GameObject bullet;
+
+ /// Launch speed of the bullet in units per second.
+ public float bulletVelocity = 30f;
+
+ /// Layer mask controlling which surfaces accept shoot destinations.
+ public LayerMask layerMask = ~0;
+
+ void Start()
+ {
+ if (cameraRef == null)
+ cameraRef = Camera.main;
+ }
+
+ void Update()
+ {
+ if (!Input.GetMouseButtonDown(1) || cameraRef == null || bullet == null)
+ return;
+
+ Ray ray = cameraRef.ScreenPointToRay(Input.mousePosition);
+ if (!Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, layerMask))
+ return;
+
+ // Rotate the player to face the target on the horizontal plane.
+ Vector3 direction = (hit.point - transform.position).normalized;
+ direction.y = 0f;
+ if (direction != Vector3.zero)
+ transform.rotation = Quaternion.LookRotation(direction);
+
+ Transform spawnTf = shootPoint != null ? shootPoint : transform;
+ GameObject newBullet = Instantiate(bullet, spawnTf.position, spawnTf.rotation);
+
+ Rigidbody rb = newBullet.GetComponent();
+ if (rb == null)
+ rb = newBullet.AddComponent();
+
+ rb.linearVelocity = bulletVelocity * spawnTf.forward;
+ }
+}
diff --git a/Chapter09/Assets/Scripts/PlayerShoot.cs.meta b/Chapter09/Assets/Scripts/PlayerShoot.cs.meta
new file mode 100644
index 0000000..766e85e
--- /dev/null
+++ b/Chapter09/Assets/Scripts/PlayerShoot.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d4e5f67890abcdef12345678901b2c3d
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Chapter09/Assets/Scripts/Shoot.cs b/Chapter09/Assets/Scripts/Shoot.cs
index f485325..c750954 100644
--- a/Chapter09/Assets/Scripts/Shoot.cs
+++ b/Chapter09/Assets/Scripts/Shoot.cs
@@ -1,41 +1,81 @@
-using UnityEngine;
-
-using Pada1.BBCore; // Code attributes
-using Pada1.BBCore.Tasks; // TaskStatus
+using System;
+using Unity.Behavior;
+using Unity.Properties;
+using UnityEngine;
+using Action = Unity.Behavior.Action;
///
-/// DoneShootOnce is a action inherited from DoneShootOnce and Periodically clones a 'bullet' and
-/// shoots it throught the Forward axis with the specified velocity. This action never ends.
+/// ShootAction is a Unity Behavior action node that periodically fires a bullet prefab
+/// along the forward axis of the shoot point. This action never completes on its own.
///
-[Action("Chapter09/Shoot")]
-[Help("Periodically clones a 'bullet' and shoots it through the Forward axis " +
- "with the specified velocity. This action never ends.")]
-public class Shoot : ShootOnce {
- ///Input delay Parameter in seconds, 30 by default.
- // Define the input parameter delay, with the waited time between shoots.
- [InParam("delay", DefaultValue = 1.0f)]
- public float delay;
-
- // Time since the last shoot.
- private float elapsedTime = 0;
-
-
- /// Update method of DoneShoot.
- /// Return Running task.
- // Main class method, invoked by the execution engine.
- public override TaskStatus OnUpdate() {
- if (delay > 0) {
- elapsedTime += Time.deltaTime;
- if (elapsedTime >= delay) {
- elapsedTime = 0;
- return TaskStatus.RUNNING;
- }
-
- }
-
- base.OnUpdate();
- return TaskStatus.RUNNING;
+[Serializable, GeneratePropertyBag]
+[NodeDescription(
+ name: "Shoot Repeatedly",
+ story: "Shoot [Bullet] from [ShootPoint] every [Delay] seconds at [Velocity] units per second",
+ category: "Chapter09",
+ id: "chapter09-shoot-action-v1")]
+public partial class ShootAction : Action
+{
+ /// The transform marking the bullet spawn position and direction.
+ [SerializeReference] public BlackboardVariable ShootPoint;
+
+ /// The bullet prefab to instantiate.
+ [SerializeReference] public BlackboardVariable Bullet;
+
+ /// Seconds between shots. Defaults to 1 second.
+ [SerializeReference] public BlackboardVariable Delay;
+
+ /// The launch speed in units per second.
+ [SerializeReference] public BlackboardVariable Velocity;
+ private float elapsedTime;
+
+ /// Resets the shot timer so the first shot fires after one full delay.
+ protected override Status OnStart()
+ {
+ elapsedTime = 0f;
+ return Status.Running;
}
-}
\ No newline at end of file
+ ///
+ /// Advances the timer each frame and fires a bullet when the delay has elapsed.
+ /// Always returns to keep the action active indefinitely.
+ ///
+ protected override Status OnUpdate()
+ {
+ float delay = Delay?.Value ?? 1f;
+ elapsedTime += UnityEngine.Time.deltaTime;
+
+ if (elapsedTime < delay)
+ return Status.Running;
+
+ // Reset the timer before firing so the next shot is delayed by a full
+ // period regardless of how long FireBullet() takes (it's instant, but
+ // this ordering keeps the interval consistent). The first shot therefore
+ // fires after the initial delay, consistent with the original Shoot action.
+ elapsedTime = 0f;
+ FireBullet();
+ return Status.Running;
+ }
+
+ private void FireBullet()
+ {
+ Transform shootPt = ShootPoint?.Value;
+ GameObject bulletPrefab = Bullet?.Value;
+
+ if (shootPt == null || bulletPrefab == null)
+ return;
+
+ float vel = Velocity?.Value ?? 30f;
+
+ GameObject newBullet = UnityEngine.Object.Instantiate(
+ bulletPrefab, shootPt.position,
+ shootPt.rotation * bulletPrefab.transform.rotation);
+
+ Rigidbody rb = newBullet.GetComponent();
+ if (rb == null)
+ rb = newBullet.AddComponent();
+
+ rb.linearVelocity = vel * shootPt.forward;
+ }
+}
diff --git a/Chapter09/Assets/Scripts/ShootOnce.cs b/Chapter09/Assets/Scripts/ShootOnce.cs
index 622177d..9acf14c 100644
--- a/Chapter09/Assets/Scripts/ShootOnce.cs
+++ b/Chapter09/Assets/Scripts/ShootOnce.cs
@@ -1,74 +1,64 @@
-using UnityEngine;
-
-using Pada1.BBCore; // Code attributes
-using Pada1.BBCore.Tasks; // TaskStatus
-using BBUnity.Actions; // GOAction
+using System;
+using Unity.Behavior;
+using Unity.Properties;
+using UnityEngine;
+using Action = Unity.Behavior.Action;
///
-/// DoneShootOnce is a action inherited from GOAction and Clone a 'bullet' and shoots
-/// it throught the Forward axis with the specified velocity.
+/// ShootOnceAction is a Unity Behavior action node that instantiates a bullet prefab and
+/// shoots it along the forward axis of the shoot point with the specified velocity.
+/// The action completes after firing a single shot.
///
-[Action("Chapter09/ShootOnce")]
-[Help("Clone a 'bullet' and shoots it through the Forward axis with the " +
- "specified velocity.")]
-public class ShootOnce : GOAction {
+[Serializable, GeneratePropertyBag]
+[NodeDescription(
+ name: "Shoot Once",
+ story: "Shoot [Bullet] from [ShootPoint] at [Velocity] units per second",
+ category: "Chapter09",
+ id: "chapter09-shootonce-action-v1")]
+public partial class ShootOnceAction : Action
+{
+ /// The transform marking the bullet spawn position and direction.
+ [SerializeReference] public BlackboardVariable ShootPoint;
- ///Input shootPoint Parameter.
- // Define the input parameter "shootPoint".
- [InParam("shootPoint")]
- public Transform shootPoint;
+ /// The bullet prefab to instantiate.
+ [SerializeReference] public BlackboardVariable Bullet;
- ///Input bullet Parameter.
- // Define the input parameter "bullet" (the prefab to be cloned).
- [InParam("bullet")]
- public GameObject bullet;
+ /// The launch speed in units per second.
+ [SerializeReference] public BlackboardVariable Velocity;
- ///Input velocity Parameter, by deafult is 30f.
- // Define the input parameter velocity, and provide a default
- // value of 30.0 when used as CONSTANT in the editor.
- [InParam("velocity", DefaultValue = 30f)]
- public float velocity;
+ ///
+ /// Fires a single bullet on the first frame. Returns if
+ /// the shoot point or bullet prefab is missing; otherwise returns .
+ ///
+ protected override Status OnStart()
+ {
+ Transform shootPt = ShootPoint?.Value;
+ GameObject bulletPrefab = Bullet?.Value;
+ if (shootPt == null || bulletPrefab == null)
+ {
+ Debug.LogWarning("ShootOnceAction: ShootPoint or Bullet is not assigned.");
+ return Status.Failure;
+ }
- /// Initialization method of DoneShootOnce.
- /// If the shootPoint is not established, we look for the shooting point.
+ float vel = Velocity?.Value ?? 30f;
- // Initialization method. If not established, we look for the shooting point.
- public override void OnStart() {
- if (shootPoint == null) {
- shootPoint = gameObject.transform.Find("shootPoint");
- if (shootPoint == null) {
- Debug.LogWarning("Shoot point not specified. ShootOnce will not work " +
- "for " + gameObject.name);
- }
- }
- base.OnStart();
- } // OnStart
+ GameObject newBullet = UnityEngine.Object.Instantiate(
+ bulletPrefab, shootPt.position,
+ shootPt.rotation * bulletPrefab.transform.rotation);
+ Rigidbody rb = newBullet.GetComponent();
+ if (rb == null)
+ rb = newBullet.AddComponent();
- /// Update method of DoneShootOnce.
- /// Instantiate the bullet prefab, Search the RigitBody component in bullet instance. We add a rigitBody to bullet
- /// if doesn´t exist, and then we give it a velocity.
- /// Return FAILED if the shootPoint is null, and COMPLETE otherwise.
- // Main class method, invoked by the execution engine.
- public override TaskStatus OnUpdate() {
- if (shootPoint == null || bullet == null) {
- return TaskStatus.FAILED;
- }
- // Instantiate the bullet prefab.
- GameObject newBullet = Object.Instantiate(
- bullet, shootPoint.position,
- shootPoint.rotation * bullet.transform.rotation
- );
- // Give it a velocity
- if (newBullet.GetComponent() == null)
- // Safeguard test, altough the rigid body should be provided by the
- // prefab to set its weight.
- newBullet.AddComponent();
+ rb.linearVelocity = vel * shootPt.forward;
- newBullet.GetComponent().velocity = velocity * shootPoint.forward;
- // The action is completed. We must inform the execution engine.
- return TaskStatus.COMPLETED;
+ return Status.Success;
}
+ protected override Status OnUpdate()
+ {
+ return Status.Success;
+ }
}
+
diff --git a/Chapter09/Assets/Scripts/SleepForever.cs b/Chapter09/Assets/Scripts/SleepForever.cs
index 76f109d..1ba2919 100644
--- a/Chapter09/Assets/Scripts/SleepForever.cs
+++ b/Chapter09/Assets/Scripts/SleepForever.cs
@@ -1,25 +1,30 @@
-using Pada1.BBCore; // Code attributes
-using Pada1.BBCore.Framework; // BasePrimitiveAction
-using Pada1.BBCore.Tasks; // TaskStatus
+using System;
+using Unity.Behavior;
+using Unity.Properties;
+using UnityEngine;
+using Action = Unity.Behavior.Action;
///
-/// It is an action that inherits from a primitive base action that updates the status of the behavior to suspended, in this case
-/// is suspended by changing the brightness.
+/// SleepForeverAction is a Unity Behavior action node that never completes.
+/// It is used to keep the enemy idle (e.g., during night time) without consuming CPU.
///
-[Action("Chapter09/SleepForever")]
-[Help("Low-cost infinite action that never ends. It does not consume CPU at all.")]
-
-
-public class SleepForever : BasePrimitiveAction
+[Serializable, GeneratePropertyBag]
+[NodeDescription(
+ name: "Sleep Forever",
+ story: "Sleep forever (do nothing)",
+ category: "Chapter09",
+ id: "chapter09-sleepforever-action-v1")]
+public partial class SleepForeverAction : Action
{
-
- // Main class method, invoked by the execution engine.
- ///Method of onUpdate of SleepForever.
- ///Change the status of the task.
- ///Value of the status of the suspended task.
- public override TaskStatus OnUpdate()
+ ///
+ /// Keeps the node running indefinitely, effectively doing nothing.
+ /// Unity Native Behavior has no SUSPENDED equivalent;
+ /// is the correct value to keep a node alive without completing or failing.
+ ///
+ /// Always returns .
+ protected override Status OnUpdate()
{
- return TaskStatus.SUSPENDED;
+ return Status.Running;
}
-
}
+
diff --git a/Chapter09/Packages/manifest.json b/Chapter09/Packages/manifest.json
index 0e78d8c..1fed8ee 100644
--- a/Chapter09/Packages/manifest.json
+++ b/Chapter09/Packages/manifest.json
@@ -1,25 +1,15 @@
{
"dependencies": {
- "com.unity.2d.sprite": "1.0.0",
- "com.unity.2d.tilemap": "1.0.0",
- "com.unity.ads": "3.7.5",
- "com.unity.analytics": "3.7.1",
- "com.unity.collab-proxy": "1.15.4",
- "com.unity.ide.rider": "3.0.7",
- "com.unity.ide.visualstudio": "2.0.12",
- "com.unity.ide.vscode": "1.2.4",
- "com.unity.purchasing": "4.1.1",
- "com.unity.test-framework": "1.1.30",
- "com.unity.textmeshpro": "3.0.6",
- "com.unity.timeline": "1.7.0-pre.1",
- "com.unity.ugui": "1.0.0",
- "com.unity.xr.legacyinputhelpers": "2.1.8",
+ "com.unity.behavior": "1.0.7",
+ "com.unity.collab-proxy": "2.4.4",
+ "com.unity.ide.rider": "3.0.31",
+ "com.unity.ide.visualstudio": "2.0.22",
+ "com.unity.textmeshpro": "3.0.9",
+ "com.unity.ugui": "2.0.0",
"com.unity.modules.ai": "1.0.0",
- "com.unity.modules.androidjni": "1.0.0",
"com.unity.modules.animation": "1.0.0",
"com.unity.modules.assetbundle": "1.0.0",
"com.unity.modules.audio": "1.0.0",
- "com.unity.modules.cloth": "1.0.0",
"com.unity.modules.director": "1.0.0",
"com.unity.modules.imageconversion": "1.0.0",
"com.unity.modules.imgui": "1.0.0",
@@ -30,20 +20,11 @@
"com.unity.modules.screencapture": "1.0.0",
"com.unity.modules.terrain": "1.0.0",
"com.unity.modules.terrainphysics": "1.0.0",
- "com.unity.modules.tilemap": "1.0.0",
"com.unity.modules.ui": "1.0.0",
"com.unity.modules.uielements": "1.0.0",
- "com.unity.modules.umbra": "1.0.0",
- "com.unity.modules.unityanalytics": "1.0.0",
"com.unity.modules.unitywebrequest": "1.0.0",
- "com.unity.modules.unitywebrequestassetbundle": "1.0.0",
- "com.unity.modules.unitywebrequestaudio": "1.0.0",
- "com.unity.modules.unitywebrequesttexture": "1.0.0",
- "com.unity.modules.unitywebrequestwww": "1.0.0",
- "com.unity.modules.vehicles": "1.0.0",
"com.unity.modules.video": "1.0.0",
"com.unity.modules.vr": "1.0.0",
- "com.unity.modules.wind": "1.0.0",
"com.unity.modules.xr": "1.0.0"
}
}
diff --git a/Chapter09/Packages/packages-lock.json b/Chapter09/Packages/packages-lock.json
deleted file mode 100644
index 15e95a2..0000000
--- a/Chapter09/Packages/packages-lock.json
+++ /dev/null
@@ -1,421 +0,0 @@
-{
- "dependencies": {
- "com.unity.2d.sprite": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {}
- },
- "com.unity.2d.tilemap": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {}
- },
- "com.unity.ads": {
- "version": "3.7.5",
- "depth": 0,
- "source": "registry",
- "dependencies": {
- "com.unity.ugui": "1.0.0"
- },
- "url": "https://packages.unity.com"
- },
- "com.unity.analytics": {
- "version": "3.7.1",
- "depth": 0,
- "source": "registry",
- "dependencies": {
- "com.unity.services.analytics": "1.0.4",
- "com.unity.ugui": "1.0.0"
- },
- "url": "https://packages.unity.com"
- },
- "com.unity.collab-proxy": {
- "version": "1.15.4",
- "depth": 0,
- "source": "registry",
- "dependencies": {
- "com.unity.nuget.newtonsoft-json": "2.0.0",
- "com.unity.services.core": "1.0.1"
- },
- "url": "https://packages.unity.com"
- },
- "com.unity.ext.nunit": {
- "version": "1.0.6",
- "depth": 1,
- "source": "registry",
- "dependencies": {},
- "url": "https://packages.unity.com"
- },
- "com.unity.ide.rider": {
- "version": "3.0.7",
- "depth": 0,
- "source": "registry",
- "dependencies": {
- "com.unity.ext.nunit": "1.0.6"
- },
- "url": "https://packages.unity.com"
- },
- "com.unity.ide.visualstudio": {
- "version": "2.0.12",
- "depth": 0,
- "source": "registry",
- "dependencies": {
- "com.unity.test-framework": "1.1.9"
- },
- "url": "https://packages.unity.com"
- },
- "com.unity.ide.vscode": {
- "version": "1.2.4",
- "depth": 0,
- "source": "registry",
- "dependencies": {},
- "url": "https://packages.unity.com"
- },
- "com.unity.nuget.newtonsoft-json": {
- "version": "2.0.2",
- "depth": 1,
- "source": "registry",
- "dependencies": {},
- "url": "https://packages.unity.com"
- },
- "com.unity.purchasing": {
- "version": "4.1.1",
- "depth": 0,
- "source": "registry",
- "dependencies": {
- "com.unity.ugui": "1.0.0",
- "com.unity.modules.unityanalytics": "1.0.0",
- "com.unity.modules.unitywebrequest": "1.0.0",
- "com.unity.modules.jsonserialize": "1.0.0",
- "com.unity.modules.androidjni": "1.0.0",
- "com.unity.services.core": "1.0.1"
- },
- "url": "https://packages.unity.com"
- },
- "com.unity.services.analytics": {
- "version": "1.0.4",
- "depth": 1,
- "source": "registry",
- "dependencies": {
- "com.unity.services.core": "1.0.1"
- },
- "url": "https://packages.unity.com"
- },
- "com.unity.services.core": {
- "version": "1.0.1",
- "depth": 1,
- "source": "registry",
- "dependencies": {
- "com.unity.modules.unitywebrequest": "1.0.0"
- },
- "url": "https://packages.unity.com"
- },
- "com.unity.test-framework": {
- "version": "1.1.30",
- "depth": 0,
- "source": "registry",
- "dependencies": {
- "com.unity.ext.nunit": "1.0.6",
- "com.unity.modules.imgui": "1.0.0",
- "com.unity.modules.jsonserialize": "1.0.0"
- },
- "url": "https://packages.unity.com"
- },
- "com.unity.textmeshpro": {
- "version": "3.0.6",
- "depth": 0,
- "source": "registry",
- "dependencies": {
- "com.unity.ugui": "1.0.0"
- },
- "url": "https://packages.unity.com"
- },
- "com.unity.timeline": {
- "version": "1.7.0-pre.1",
- "depth": 0,
- "source": "registry",
- "dependencies": {
- "com.unity.modules.director": "1.0.0",
- "com.unity.modules.animation": "1.0.0",
- "com.unity.modules.audio": "1.0.0",
- "com.unity.modules.particlesystem": "1.0.0"
- },
- "url": "https://packages.unity.com"
- },
- "com.unity.ugui": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {
- "com.unity.modules.ui": "1.0.0",
- "com.unity.modules.imgui": "1.0.0"
- }
- },
- "com.unity.xr.legacyinputhelpers": {
- "version": "2.1.8",
- "depth": 0,
- "source": "registry",
- "dependencies": {
- "com.unity.modules.vr": "1.0.0",
- "com.unity.modules.xr": "1.0.0"
- },
- "url": "https://packages.unity.com"
- },
- "com.unity.modules.ai": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {}
- },
- "com.unity.modules.androidjni": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {}
- },
- "com.unity.modules.animation": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {}
- },
- "com.unity.modules.assetbundle": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {}
- },
- "com.unity.modules.audio": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {}
- },
- "com.unity.modules.cloth": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {
- "com.unity.modules.physics": "1.0.0"
- }
- },
- "com.unity.modules.director": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {
- "com.unity.modules.audio": "1.0.0",
- "com.unity.modules.animation": "1.0.0"
- }
- },
- "com.unity.modules.imageconversion": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {}
- },
- "com.unity.modules.imgui": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {}
- },
- "com.unity.modules.jsonserialize": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {}
- },
- "com.unity.modules.particlesystem": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {}
- },
- "com.unity.modules.physics": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {}
- },
- "com.unity.modules.physics2d": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {}
- },
- "com.unity.modules.screencapture": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {
- "com.unity.modules.imageconversion": "1.0.0"
- }
- },
- "com.unity.modules.subsystems": {
- "version": "1.0.0",
- "depth": 1,
- "source": "builtin",
- "dependencies": {
- "com.unity.modules.jsonserialize": "1.0.0"
- }
- },
- "com.unity.modules.terrain": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {}
- },
- "com.unity.modules.terrainphysics": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {
- "com.unity.modules.physics": "1.0.0",
- "com.unity.modules.terrain": "1.0.0"
- }
- },
- "com.unity.modules.tilemap": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {
- "com.unity.modules.physics2d": "1.0.0"
- }
- },
- "com.unity.modules.ui": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {}
- },
- "com.unity.modules.uielements": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {
- "com.unity.modules.ui": "1.0.0",
- "com.unity.modules.imgui": "1.0.0",
- "com.unity.modules.jsonserialize": "1.0.0",
- "com.unity.modules.uielementsnative": "1.0.0"
- }
- },
- "com.unity.modules.uielementsnative": {
- "version": "1.0.0",
- "depth": 1,
- "source": "builtin",
- "dependencies": {
- "com.unity.modules.ui": "1.0.0",
- "com.unity.modules.imgui": "1.0.0",
- "com.unity.modules.jsonserialize": "1.0.0"
- }
- },
- "com.unity.modules.umbra": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {}
- },
- "com.unity.modules.unityanalytics": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {
- "com.unity.modules.unitywebrequest": "1.0.0",
- "com.unity.modules.jsonserialize": "1.0.0"
- }
- },
- "com.unity.modules.unitywebrequest": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {}
- },
- "com.unity.modules.unitywebrequestassetbundle": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {
- "com.unity.modules.assetbundle": "1.0.0",
- "com.unity.modules.unitywebrequest": "1.0.0"
- }
- },
- "com.unity.modules.unitywebrequestaudio": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {
- "com.unity.modules.unitywebrequest": "1.0.0",
- "com.unity.modules.audio": "1.0.0"
- }
- },
- "com.unity.modules.unitywebrequesttexture": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {
- "com.unity.modules.unitywebrequest": "1.0.0",
- "com.unity.modules.imageconversion": "1.0.0"
- }
- },
- "com.unity.modules.unitywebrequestwww": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {
- "com.unity.modules.unitywebrequest": "1.0.0",
- "com.unity.modules.unitywebrequestassetbundle": "1.0.0",
- "com.unity.modules.unitywebrequestaudio": "1.0.0",
- "com.unity.modules.audio": "1.0.0",
- "com.unity.modules.assetbundle": "1.0.0",
- "com.unity.modules.imageconversion": "1.0.0"
- }
- },
- "com.unity.modules.vehicles": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {
- "com.unity.modules.physics": "1.0.0"
- }
- },
- "com.unity.modules.video": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {
- "com.unity.modules.audio": "1.0.0",
- "com.unity.modules.ui": "1.0.0",
- "com.unity.modules.unitywebrequest": "1.0.0"
- }
- },
- "com.unity.modules.vr": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {
- "com.unity.modules.jsonserialize": "1.0.0",
- "com.unity.modules.physics": "1.0.0",
- "com.unity.modules.xr": "1.0.0"
- }
- },
- "com.unity.modules.wind": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {}
- },
- "com.unity.modules.xr": {
- "version": "1.0.0",
- "depth": 0,
- "source": "builtin",
- "dependencies": {
- "com.unity.modules.physics": "1.0.0",
- "com.unity.modules.jsonserialize": "1.0.0",
- "com.unity.modules.subsystems": "1.0.0"
- }
- }
- }
-}
diff --git a/Chapter09/ProjectSettings/ProjectVersion.txt b/Chapter09/ProjectSettings/ProjectVersion.txt
index 2854d00..7a3bbe2 100644
--- a/Chapter09/ProjectSettings/ProjectVersion.txt
+++ b/Chapter09/ProjectSettings/ProjectVersion.txt
@@ -1,2 +1,2 @@
-m_EditorVersion: 2022.1.0b2
-m_EditorVersionWithRevision: 2022.1.0b2 (980041f98dd2)
+m_EditorVersion: 6000.0.36f1
+m_EditorVersionWithRevision: 6000.0.36f1 (70e14c8a4a94)
diff --git a/Chapter09/README.md b/Chapter09/README.md
new file mode 100644
index 0000000..6ace89d
--- /dev/null
+++ b/Chapter09/README.md
@@ -0,0 +1,88 @@
+# Chapter 09 – Behavior Trees
+
+This chapter demonstrates implementing **Behavior Trees** for game AI in Unity 6, using the
+[Unity Native Behavior package](https://docs.unity3d.com/Packages/com.unity.behavior@1.0)
+(`com.unity.behavior@1.0`).
+
+## Project Requirements
+
+| Requirement | Version |
+|---|---|
+| Unity Editor | 6000.0.x (Unity 6) |
+| `com.unity.behavior` | 1.0.7 |
+| `com.unity.modules.ai` | 1.0.0 |
+
+## Scene
+
+Open **Assets/Scenes/BehaviorTreeDemo** to see the demo.
+
+- **Player** – **Left-click** anywhere on the floor to move. **Right-click** anywhere to face that direction and fire a bullet (uses `PlayerClickToMove.cs` + `PlayerShoot.cs`).
+- **Enemy** – Follows a priority-selector behavior tree (uses `EnemyAI.cs`).
+- **Directional Light** – Drives the `DayNightCycle` component that toggles between day and night.
+
+> **NavMesh**: The baked `NavMesh.asset` is committed in `Assets/Scenes/BehaviorTreeDemo/`. Both the Player and the Enemy use a `NavMeshAgent` so click-to-move works without any extra baking step.
+
+## How the Behavior Tree Works
+
+The `EnemyAI` MonoBehaviour implements the following priority-selector tree directly in C#:
+
+```
+Repeat forever {
+ Priority Selector {
+ if IsNight → SleepForever (do nothing)
+ if distance < 7 → Shoot at player
+ if distance < 20 → Chase player
+ otherwise → Wander randomly
+ }
+}
+```
+
+## Custom Node Scripts (Unity Behavior visual graph)
+
+The following scripts define custom nodes for the **Unity Behavior** visual graph editor:
+
+| Script | Type | Description |
+|--------|------|-------------|
+| `IsNightCondition.cs` | `Condition` | Returns true during night time |
+| `SleepForeverAction.cs` | `Action` | Runs forever (idles) |
+| `ShootOnceAction.cs` | `Action` | Fires one bullet |
+| `ShootAction.cs` | `Action` | Fires bullets periodically (never ends) |
+
+### Creating the Visual Behavior Graph
+
+To recreate the enemy behavior as a **Unity Behavior graph asset** in the editor:
+
+1. Open the **Unity Behavior** window: **Window → AI → Behavior**.
+2. Create a new graph asset in `Assets/Behaviors/` and name it `EnemyTree`.
+3. Build the following tree using the nodes above:
+ - Add a **Repeat** decorator (loops = –1) as the root.
+ - Add a **Priority Selector** as its child.
+ - Add four branches to the selector, each with a guard condition and an action:
+ 1. Guard: **Is Night** → Action: **Sleep Forever**
+ 2. Guard: **Is Target Close** (distance = 7) → Action: **Shoot Repeatedly** (delay = 1 s, velocity = 30)
+ 3. Guard: **Is Target Close** (distance = 20) → Action: **Navigate To Location**
+ 4. Guard: **Always True** → Action (sub-graph): **Wander**
+4. Remove the `EnemyAI` MonoBehaviour from the Enemy GameObject.
+5. Add a **BehaviorGraphAgent** component to the Enemy and assign the graph asset.
+6. Populate the blackboard variables: `Player`, `WanderArea`, `ShootPoint`, `Bullet`.
+
+## Key API Changes from BehaviorBricks to Unity Native Behavior
+
+| BehaviorBricks (old) | Unity Native Behavior (new) |
+|---|---|
+| `Pada1.BBCore` / `BBUnity.Actions` | `Unity.Behavior` |
+| `ConditionBase` | `Condition` |
+| `GOAction` / `BasePrimitiveAction` | `Action` |
+| `TaskStatus.RUNNING` | `Status.Running` |
+| `TaskStatus.COMPLETED` | `Status.Success` |
+| `TaskStatus.FAILED` | `Status.Failure` |
+| `[Condition("path")]` | `[NodeDescription(name: "...", category: "...")]` |
+| `[InParam("name")]` | `[SerializeReference] public BlackboardVariable` |
+| `BrainComponent` (scene) | `BehaviorGraphAgent` (scene) |
+| `.asset` (BehaviorBricks XML) | `.behavior` (Unity Behavior graph) |
+
+## Unity 6 API Fixes
+
+- `Rigidbody.velocity` → `Rigidbody.linearVelocity` (deprecated in Unity 6)
+- Removed `com.unity.ads`, `com.unity.analytics`, `com.unity.purchasing` (deprecated services)
+- Removed `com.unity.xr.legacyinputhelpers` (replaced by XR Interaction Toolkit)