123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- ----------------------------------------------------------------------------------------------------
- --
- -- Copyright (c) Contributors to the Open 3D Engine Project.
- -- For complete copyright and license terms please see the LICENSE at the root of this distribution.
- --
- -- SPDX-License-Identifier: Apache-2.0 OR MIT
- --
- --
- --
- ----------------------------------------------------------------------------------------------------
- local DynamicAppleSpawner = {
- Properties = {
- Debug = true,
- DebugColliders = false,
- DestAppleTreeTag = "AppleTree",
- FollowTag = "FollowTarget",
- ApplesPrefab = { default = SpawnableScriptAssetRef(), description = "Prefab to spawn" },
- NumPrefabsToSpawn = 6,
- TranslationThreshold = { default = 1, description = "distance in meters follow target must move before we update" },
- DetectionRadius = 4,
- GroundHeight = 0
- },
- FollowTagHandler = {}
- }
- function DynamicAppleSpawner:OnActivate()
- self.numPrefabsToSpawn = 0 -- don't spawn prefabs until a follow target exists
- self.numPrefabsSpawned = 0
- self.closestAppleTrees = {}
- self.followTargets = {}
- self.freeAppleGroups = {}
- self.spawningPrefabs = false
- local physicsSystem = GetPhysicsSystem()
- local sceneHandle = physicsSystem:GetSceneHandle(DefaultPhysicsSceneName)
- self.scene = physicsSystem:GetScene(sceneHandle)
- self.appleTreeTag = Crc32(self.Properties.DestAppleTreeTag)
- self.FollowTagHandler.component = self
- self.FollowTagHandler.handler = TagGlobalNotificationBus.Connect(self.FollowTagHandler, Crc32(self.Properties.FollowTag))
- self.spawnableMediator = SpawnableScriptMediator()
- self.ticket = self.spawnableMediator:CreateSpawnTicket(self.Properties.ApplesPrefab)
- self.spawnableNotificationsBusHandler = SpawnableScriptNotificationsBus.Connect(self, self.ticket:GetId())
- self.tickHandler = TickBus.Connect(self, 0)
- end
- function DynamicAppleSpawner:PrintApplePositions(entityId)
- -- print out locations of every apple for scripting (Blender Python) purposes
- local children = TransformBus.Event.GetChildren(entityId)
- for i = 1, #children do
- local childId = children[i]
- local pos = TransformBus.Event.GetLocalTranslation(childId)
- Debug.Log("(" .. tostring(pos.x) .. "," .. tostring(pos.y) .. "," .. tostring(pos.z) .. "),")
- end
- end
- function DynamicAppleSpawner:OnSpawn(spawnTicket, entityList)
- -- first entity should be the root
- table.insert(self.freeAppleGroups, entityList[1])
- local entityName = GameEntityContextRequestBus.Broadcast.GetEntityName(entityList[1])
- Debug.Log("$5 Spawned " .. tostring(entityName))
- if self.numPrefabsSpawned >= self.numPrefabsToSpawn then
- self.spawningPrefabs = false
- end
- -- mark all follow targets to update in case they are waiting on groups
- for k, followTarget in pairs(self.followTargets) do
- followTarget.update = true
- end
- end
- function DynamicAppleSpawner:AddFollowTarget(entityId)
- -- add a follow target that we will spawn apples around
- self.followTargets[tostring(entityId)] = {
- position = Vector3(0, 0, 0),
- update = false,
- handler = nil,
- OnTransformChanged = function(followTarget, localTM, worldTM)
- local translation = worldTM:GetTranslation()
- translation.z = self.Properties.GroundHeight
- -- only set this target to update if we moved over the threshold distance
- if followTarget.update == false and
- followTarget.position:GetDistanceSq(translation) > self.Properties.TranslationThreshold then
- followTarget.update = true
- followTarget.position = translation
- followTarget.position.z = self.Properties.GroundHeight
- end
- end
- }
- self.followTargets[tostring(entityId)].handler = TransformNotificationBus.Connect(self.followTargets[tostring(entityId)], entityId)
- self.numPrefabsToSpawn = self.numPrefabsToSpawn + self.Properties.NumPrefabsToSpawn
- local entityName = GameEntityContextRequestBus.Broadcast.GetEntityName(entityId)
- Debug.Log("$5 Follow target added " .. tostring(entityName))
- end
- function DynamicAppleSpawner.FollowTagHandler:OnEntityTagAdded(entityId)
- self.component:AddFollowTarget(entityId)
- end
- function DynamicAppleSpawner:SetDescendantVisibility(entityId, visible)
- local descendants = TransformBus.Event.GetAllDescendants(entityId)
- for i = 1, #descendants do
- local descendantEntityId = descendants[i]
- RenderMeshComponentRequestBus.Event.SetVisibility(descendantEntityId, visible)
- end
- end
- function DynamicAppleSpawner:SetPhysicsEnabled(entityId, enabled)
- -- change the physics settings on all descendents
- -- currently not used because does not work reliably. colliders do not appear
- -- in correct position after movement
- local descendants = TransformBus.Event.GetAllDescendants(entityId)
- if enabled then
- for i = 1, #descendants do
- local descendantEntityId = descendants[i]
- SimulatedBodyComponentRequestBus.Event.DisablePhysics(descendantEntityId)
- end
- else
- for i = 1, #descendants do
- local descendantEntityId = descendants[i]
- SimulatedBodyComponentRequestBus.Event.EnablePhysics(descendantEntityId)
- end
- end
- end
- local function getAppleTreeIndex(appleTrees, entityId)
- for k, tree in ipairs(appleTrees) do
- if tree.entityId == entityId then
- return k
- end
- end
- return -1
- end
- function DynamicAppleSpawner:HaveFollowTargetsToUpdate()
- for k, followTarget in pairs(self.followTargets) do
- if followTarget.update then
- return true
- end
- end
- return false
- end
- function DynamicAppleSpawner:SetFollowTargetsUpdateStatus(update)
- for k, followTarget in pairs(self.followTargets) do
- followTarget.update = update
- end
- end
- function DynamicAppleSpawner:GetTreesNearFollowTargets()
- local treesNearFollowTargets = {}
- for k, followTarget in pairs(self.followTargets) do
- local tm = Transform.CreateTranslation(followTarget.position)
- -- use a small box height to avoid intersecting apples if possible
- -- otherwise need to filter out apples
- local dimensions = Vector3(self.Properties.DetectionRadius, self.Properties.DetectionRadius, 0.1)
- local overlapRequest = CreateBoxOverlapRequest(dimensions, tm)
- local hits = self.scene:QueryScene(overlapRequest)
- if hits.HitArray:Size() > 0 then
- for i = 1, #hits.HitArray do
- local entityId = hits.HitArray[i].EntityId
- if TagComponentRequestBus.Event.HasTag(entityId, self.appleTreeTag) then
- treesNearFollowTargets[tostring(entityId)] = entityId
- --Debug.Log("Tree found near target " .. tostring(entityId))
- end
- end
- end
- end
- return treesNearFollowTargets
- end
- function DynamicAppleSpawner:RemoveTreesOutsideFollowTargetRange(treesNearFollowTargets)
- for k, tree in ipairs(self.closestAppleTrees) do
- if treesNearFollowTargets[tostring(tree.entityId)] == nil then
- if self.Properties.Debug then
- Debug.Log("Removing apple group entity " .. tostring(tree.applesEntityId) .. " tree no longer near target " .. tostring(tree.entityId))
- end
- -- reveal the static mesh
- RenderMeshComponentRequestBus.Event.SetVisibility(tree.entityId, true)
- -- remove this tree and free the apple group to be re-used
- if tree.applesEntityId ~= -1 then
- table.insert(self.freeAppleGroups, tree.applesEntityId)
- TransformBus.Event.SetWorldTranslation(tree.applesEntityId, Vector3(0, 0, -10 * k))
- end
- end
- end
- end
- function DynamicAppleSpawner:ReUseAppleGroup(applesEntityId, treeEntityId)
- if self.Properties.Debug then
- Debug.Log("Claiming apple group " .. tostring(applesEntityId))
- end
- local treeTM = TransformBus.Event.GetWorldTM(treeEntityId)
- if treeTM ~= nil then
- TransformBus.Event.SetWorldTM(applesEntityId, treeTM)
- end
- -- make sure all apples are visible
- self:SetDescendantVisibility(applesEntityId, true)
- -- hide static render meshes
- RenderMeshComponentRequestBus.Event.SetVisibility(treeEntityId, false)
- table.remove(self.freeAppleGroups)
- end
- function DynamicAppleSpawner:OnTick(delaTime, scriptTime)
- local needMorePrefabs = false
- if self:HaveFollowTargetsToUpdate() then
- local treesNearFollowTargets = self:GetTreesNearFollowTargets()
- self:RemoveTreesOutsideFollowTargetRange(treesNearFollowTargets)
- self:SetFollowTargetsUpdateStatus(false)
-
- local closestTrees = {}
- -- re-use apple groups or spawn new ones
- for k, treeEntityId in pairs(treesNearFollowTargets) do
- local index = getAppleTreeIndex(self.closestAppleTrees, treeEntityId)
- if index ~= -1 and self.closestAppleTrees[index].applesEntityId ~= -1 then
- -- tree is already being tracked
- table.insert(closestTrees, self.closestAppleTrees[index])
- else
- -- tree has the required tag
- local applesEntityId = -1
- if #self.freeAppleGroups > 0 then
- applesEntityId = self.freeAppleGroups[#self.freeAppleGroups]
- if applesEntityId == nil or type(applesEntityId) == "number" then
- Debug.Log("Non EntityId found in freeAppleGroups " ..
- tostring(applesEntityId) .. " contains " .. #self.freeAppleGroups .. " items")
- needMorePrefabs = true
- else
- self:ReUseAppleGroup(applesEntityId, treeEntityId)
- table.insert(closestTrees, { entityId = treeEntityId, applesEntityId = applesEntityId })
- end
- else
- if self.Properties.Debug then
- Debug.Log("$4 Couldn't find free apple tree group in free pool of size " .. tostring(#self.freeAppleGroups))
- end
- needMorePrefabs = true
- end
- end
- end
- if self.Properties.Debug then
- for k, tree in ipairs(closestTrees) do
- DebugDrawRequestBus.Broadcast.DrawSphereOnEntity(tree.entityId, 0.5, Color(1.0, 0.0, 0.0), 1.0)
- end
- end
- self.closestAppleTrees = closestTrees
- end
- if needMorePrefabs then
- self:SetFollowTargetsUpdateStatus(true)
- end
- -- spawn additional apple group prefabs on demand
- if needMorePrefabs and self.spawningPrefabs == false then
- self.numPrefabsToSpawn = self.numPrefabsToSpawn + 1
- end
- -- spawn one apple group per frame
- if self.numPrefabsSpawned < self.numPrefabsToSpawn then
- self:SpawnAppleGroup()
- end
- end
- function DynamicAppleSpawner:SpawnAppleGroup()
- self.numPrefabsSpawned = self.numPrefabsSpawned + 1
- local tm = TransformBus.Event.GetWorldTM(self.entityId)
- local translation = tm:GetTranslation()
- -- move the prefabs down so they don't collide with eachother (more reliable than disabling/enabling physics)
- translation.z = translation.z - self.numPrefabsSpawned * 10
- local rotation = tm:GetRotation():GetEulerRadians()
- Debug.Log("$7 Spawning apple group " .. tostring(self.numPrefabsSpawned))
- self.spawningPrefabs = true
- self.spawnableMediator:SpawnAndParentAndTransform(self.ticket, self.entityId, translation, rotation, 1.0)
- end
- function DynamicAppleSpawner:OnDeactivate()
- if self.tickHandler ~= nil then
- self.tickHandler:Disconnect()
- self.tickHandler = nil
- end
- self.spawnableNotificationsBusHandler:Disconnect()
- self.FollowTagHandler.handler:Disconnect()
- self.FollowTagHandler.handler = nil
- for k, followTarget in pairs(self.followTargets) do
- followTarget.handler:Disconnect()
- end
- end
- return DynamicAppleSpawner
|