DynamicAppleSpawner.lua 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. ----------------------------------------------------------------------------------------------------
  2. --
  3. -- Copyright (c) Contributors to the Open 3D Engine Project.
  4. -- For complete copyright and license terms please see the LICENSE at the root of this distribution.
  5. --
  6. -- SPDX-License-Identifier: Apache-2.0 OR MIT
  7. --
  8. --
  9. --
  10. ----------------------------------------------------------------------------------------------------
  11. local DynamicAppleSpawner = {
  12. Properties = {
  13. Debug = true,
  14. DebugColliders = false,
  15. DestAppleTreeTag = "AppleTree",
  16. FollowTag = "FollowTarget",
  17. ApplesPrefab = { default = SpawnableScriptAssetRef(), description = "Prefab to spawn" },
  18. NumPrefabsToSpawn = 6,
  19. TranslationThreshold = { default = 1, description = "distance in meters follow target must move before we update" },
  20. DetectionRadius = 4,
  21. GroundHeight = 0
  22. },
  23. FollowTagHandler = {}
  24. }
  25. function DynamicAppleSpawner:OnActivate()
  26. self.numPrefabsToSpawn = 0 -- don't spawn prefabs until a follow target exists
  27. self.numPrefabsSpawned = 0
  28. self.closestAppleTrees = {}
  29. self.followTargets = {}
  30. self.freeAppleGroups = {}
  31. self.spawningPrefabs = false
  32. local physicsSystem = GetPhysicsSystem()
  33. local sceneHandle = physicsSystem:GetSceneHandle(DefaultPhysicsSceneName)
  34. self.scene = physicsSystem:GetScene(sceneHandle)
  35. self.appleTreeTag = Crc32(self.Properties.DestAppleTreeTag)
  36. self.FollowTagHandler.component = self
  37. self.FollowTagHandler.handler = TagGlobalNotificationBus.Connect(self.FollowTagHandler, Crc32(self.Properties.FollowTag))
  38. self.spawnableMediator = SpawnableScriptMediator()
  39. self.ticket = self.spawnableMediator:CreateSpawnTicket(self.Properties.ApplesPrefab)
  40. self.spawnableNotificationsBusHandler = SpawnableScriptNotificationsBus.Connect(self, self.ticket:GetId())
  41. self.tickHandler = TickBus.Connect(self, 0)
  42. end
  43. function DynamicAppleSpawner:PrintApplePositions(entityId)
  44. -- print out locations of every apple for scripting (Blender Python) purposes
  45. local children = TransformBus.Event.GetChildren(entityId)
  46. for i = 1, #children do
  47. local childId = children[i]
  48. local pos = TransformBus.Event.GetLocalTranslation(childId)
  49. Debug.Log("(" .. tostring(pos.x) .. "," .. tostring(pos.y) .. "," .. tostring(pos.z) .. "),")
  50. end
  51. end
  52. function DynamicAppleSpawner:OnSpawn(spawnTicket, entityList)
  53. -- first entity should be the root
  54. table.insert(self.freeAppleGroups, entityList[1])
  55. local entityName = GameEntityContextRequestBus.Broadcast.GetEntityName(entityList[1])
  56. Debug.Log("$5 Spawned " .. tostring(entityName))
  57. if self.numPrefabsSpawned >= self.numPrefabsToSpawn then
  58. self.spawningPrefabs = false
  59. end
  60. -- mark all follow targets to update in case they are waiting on groups
  61. for k, followTarget in pairs(self.followTargets) do
  62. followTarget.update = true
  63. end
  64. end
  65. function DynamicAppleSpawner:AddFollowTarget(entityId)
  66. -- add a follow target that we will spawn apples around
  67. self.followTargets[tostring(entityId)] = {
  68. position = Vector3(0, 0, 0),
  69. update = false,
  70. handler = nil,
  71. OnTransformChanged = function(followTarget, localTM, worldTM)
  72. local translation = worldTM:GetTranslation()
  73. translation.z = self.Properties.GroundHeight
  74. -- only set this target to update if we moved over the threshold distance
  75. if followTarget.update == false and
  76. followTarget.position:GetDistanceSq(translation) > self.Properties.TranslationThreshold then
  77. followTarget.update = true
  78. followTarget.position = translation
  79. followTarget.position.z = self.Properties.GroundHeight
  80. end
  81. end
  82. }
  83. self.followTargets[tostring(entityId)].handler = TransformNotificationBus.Connect(self.followTargets[tostring(entityId)], entityId)
  84. self.numPrefabsToSpawn = self.numPrefabsToSpawn + self.Properties.NumPrefabsToSpawn
  85. local entityName = GameEntityContextRequestBus.Broadcast.GetEntityName(entityId)
  86. Debug.Log("$5 Follow target added " .. tostring(entityName))
  87. end
  88. function DynamicAppleSpawner.FollowTagHandler:OnEntityTagAdded(entityId)
  89. self.component:AddFollowTarget(entityId)
  90. end
  91. function DynamicAppleSpawner:SetDescendantVisibility(entityId, visible)
  92. local descendants = TransformBus.Event.GetAllDescendants(entityId)
  93. for i = 1, #descendants do
  94. local descendantEntityId = descendants[i]
  95. RenderMeshComponentRequestBus.Event.SetVisibility(descendantEntityId, visible)
  96. end
  97. end
  98. function DynamicAppleSpawner:SetPhysicsEnabled(entityId, enabled)
  99. -- change the physics settings on all descendents
  100. -- currently not used because does not work reliably. colliders do not appear
  101. -- in correct position after movement
  102. local descendants = TransformBus.Event.GetAllDescendants(entityId)
  103. if enabled then
  104. for i = 1, #descendants do
  105. local descendantEntityId = descendants[i]
  106. SimulatedBodyComponentRequestBus.Event.DisablePhysics(descendantEntityId)
  107. end
  108. else
  109. for i = 1, #descendants do
  110. local descendantEntityId = descendants[i]
  111. SimulatedBodyComponentRequestBus.Event.EnablePhysics(descendantEntityId)
  112. end
  113. end
  114. end
  115. local function getAppleTreeIndex(appleTrees, entityId)
  116. for k, tree in ipairs(appleTrees) do
  117. if tree.entityId == entityId then
  118. return k
  119. end
  120. end
  121. return -1
  122. end
  123. function DynamicAppleSpawner:HaveFollowTargetsToUpdate()
  124. for k, followTarget in pairs(self.followTargets) do
  125. if followTarget.update then
  126. return true
  127. end
  128. end
  129. return false
  130. end
  131. function DynamicAppleSpawner:SetFollowTargetsUpdateStatus(update)
  132. for k, followTarget in pairs(self.followTargets) do
  133. followTarget.update = update
  134. end
  135. end
  136. function DynamicAppleSpawner:GetTreesNearFollowTargets()
  137. local treesNearFollowTargets = {}
  138. for k, followTarget in pairs(self.followTargets) do
  139. local tm = Transform.CreateTranslation(followTarget.position)
  140. -- use a small box height to avoid intersecting apples if possible
  141. -- otherwise need to filter out apples
  142. local dimensions = Vector3(self.Properties.DetectionRadius, self.Properties.DetectionRadius, 0.1)
  143. local overlapRequest = CreateBoxOverlapRequest(dimensions, tm)
  144. local hits = self.scene:QueryScene(overlapRequest)
  145. if hits.HitArray:Size() > 0 then
  146. for i = 1, #hits.HitArray do
  147. local entityId = hits.HitArray[i].EntityId
  148. if TagComponentRequestBus.Event.HasTag(entityId, self.appleTreeTag) then
  149. treesNearFollowTargets[tostring(entityId)] = entityId
  150. --Debug.Log("Tree found near target " .. tostring(entityId))
  151. end
  152. end
  153. end
  154. end
  155. return treesNearFollowTargets
  156. end
  157. function DynamicAppleSpawner:RemoveTreesOutsideFollowTargetRange(treesNearFollowTargets)
  158. for k, tree in ipairs(self.closestAppleTrees) do
  159. if treesNearFollowTargets[tostring(tree.entityId)] == nil then
  160. if self.Properties.Debug then
  161. Debug.Log("Removing apple group entity " .. tostring(tree.applesEntityId) .. " tree no longer near target " .. tostring(tree.entityId))
  162. end
  163. -- reveal the static mesh
  164. RenderMeshComponentRequestBus.Event.SetVisibility(tree.entityId, true)
  165. -- remove this tree and free the apple group to be re-used
  166. if tree.applesEntityId ~= -1 then
  167. table.insert(self.freeAppleGroups, tree.applesEntityId)
  168. TransformBus.Event.SetWorldTranslation(tree.applesEntityId, Vector3(0, 0, -10 * k))
  169. end
  170. end
  171. end
  172. end
  173. function DynamicAppleSpawner:ReUseAppleGroup(applesEntityId, treeEntityId)
  174. if self.Properties.Debug then
  175. Debug.Log("Claiming apple group " .. tostring(applesEntityId))
  176. end
  177. local treeTM = TransformBus.Event.GetWorldTM(treeEntityId)
  178. if treeTM ~= nil then
  179. TransformBus.Event.SetWorldTM(applesEntityId, treeTM)
  180. end
  181. -- make sure all apples are visible
  182. self:SetDescendantVisibility(applesEntityId, true)
  183. -- hide static render meshes
  184. RenderMeshComponentRequestBus.Event.SetVisibility(treeEntityId, false)
  185. table.remove(self.freeAppleGroups)
  186. end
  187. function DynamicAppleSpawner:OnTick(delaTime, scriptTime)
  188. local needMorePrefabs = false
  189. if self:HaveFollowTargetsToUpdate() then
  190. local treesNearFollowTargets = self:GetTreesNearFollowTargets()
  191. self:RemoveTreesOutsideFollowTargetRange(treesNearFollowTargets)
  192. self:SetFollowTargetsUpdateStatus(false)
  193. local closestTrees = {}
  194. -- re-use apple groups or spawn new ones
  195. for k, treeEntityId in pairs(treesNearFollowTargets) do
  196. local index = getAppleTreeIndex(self.closestAppleTrees, treeEntityId)
  197. if index ~= -1 and self.closestAppleTrees[index].applesEntityId ~= -1 then
  198. -- tree is already being tracked
  199. table.insert(closestTrees, self.closestAppleTrees[index])
  200. else
  201. -- tree has the required tag
  202. local applesEntityId = -1
  203. if #self.freeAppleGroups > 0 then
  204. applesEntityId = self.freeAppleGroups[#self.freeAppleGroups]
  205. if applesEntityId == nil or type(applesEntityId) == "number" then
  206. Debug.Log("Non EntityId found in freeAppleGroups " ..
  207. tostring(applesEntityId) .. " contains " .. #self.freeAppleGroups .. " items")
  208. needMorePrefabs = true
  209. else
  210. self:ReUseAppleGroup(applesEntityId, treeEntityId)
  211. table.insert(closestTrees, { entityId = treeEntityId, applesEntityId = applesEntityId })
  212. end
  213. else
  214. if self.Properties.Debug then
  215. Debug.Log("$4 Couldn't find free apple tree group in free pool of size " .. tostring(#self.freeAppleGroups))
  216. end
  217. needMorePrefabs = true
  218. end
  219. end
  220. end
  221. if self.Properties.Debug then
  222. for k, tree in ipairs(closestTrees) do
  223. DebugDrawRequestBus.Broadcast.DrawSphereOnEntity(tree.entityId, 0.5, Color(1.0, 0.0, 0.0), 1.0)
  224. end
  225. end
  226. self.closestAppleTrees = closestTrees
  227. end
  228. if needMorePrefabs then
  229. self:SetFollowTargetsUpdateStatus(true)
  230. end
  231. -- spawn additional apple group prefabs on demand
  232. if needMorePrefabs and self.spawningPrefabs == false then
  233. self.numPrefabsToSpawn = self.numPrefabsToSpawn + 1
  234. end
  235. -- spawn one apple group per frame
  236. if self.numPrefabsSpawned < self.numPrefabsToSpawn then
  237. self:SpawnAppleGroup()
  238. end
  239. end
  240. function DynamicAppleSpawner:SpawnAppleGroup()
  241. self.numPrefabsSpawned = self.numPrefabsSpawned + 1
  242. local tm = TransformBus.Event.GetWorldTM(self.entityId)
  243. local translation = tm:GetTranslation()
  244. -- move the prefabs down so they don't collide with eachother (more reliable than disabling/enabling physics)
  245. translation.z = translation.z - self.numPrefabsSpawned * 10
  246. local rotation = tm:GetRotation():GetEulerRadians()
  247. Debug.Log("$7 Spawning apple group " .. tostring(self.numPrefabsSpawned))
  248. self.spawningPrefabs = true
  249. self.spawnableMediator:SpawnAndParentAndTransform(self.ticket, self.entityId, translation, rotation, 1.0)
  250. end
  251. function DynamicAppleSpawner:OnDeactivate()
  252. if self.tickHandler ~= nil then
  253. self.tickHandler:Disconnect()
  254. self.tickHandler = nil
  255. end
  256. self.spawnableNotificationsBusHandler:Disconnect()
  257. self.FollowTagHandler.handler:Disconnect()
  258. self.FollowTagHandler.handler = nil
  259. for k, followTarget in pairs(self.followTargets) do
  260. followTarget.handler:Disconnect()
  261. end
  262. end
  263. return DynamicAppleSpawner