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)