SapManager = SapManager or BaseClass() local SapManager = SapManager local Status = Status local Time = Time local ScreenWidth = ScreenWidth local ScreenHeight = ScreenHeight local table_insert = table.insert local table_sort = table.sort SapManager.CONST_X_SIZE = 3000 SapManager.CONST_Y_SIZE = 3000 SapManager.XSize = 3000 SapManager.YSize = 3000 SapManager.viewDistanceMin = -1000 --视野可见范围最小值 真实值*100 SapManager.viewDistanceMax = 6000 --视野可见范围最大值 真实值*100 SapManager.ObjType = { JumpPoint = 1, NPC = 2, Door = 3, Tile = 4, Effect = 5, Model = 6, MoveObj = 7, SpecialModel = 8,--特殊模型,只摆放在场景装饰用 CacheEffect = 9, } SapManager.OperateType = { CreateOBJ = 1, DestoryOBJ = 2, } function SapManager:__init() SapManager.Instance = self self.callback_func_list = {} self.objs_list = {} self.obj_state = {} self.last_update_time = 0 self.scene_mgr = SceneManager:getInstance() self.main_camera = MainCamera:getInstance() self.scene = Scene:getInstance() self.screen_delta = co.TableXY(100,100) self.point_list = {} self.update_frame = 0 self.check_create_far_frame = 0 self.check_create_obj_frame = 0 self.first_check = true self.main_role_pos = CoVector2.New(0,0) self.cur_model_pos = CoVector2.New(0,0) self.is_use_higher_check = false self.is_enabled = true self.last_create_effect_time = 0 self.open_model_height_check_mode = false self.ignore_list = {} self.create_list = {} self.deleta_list = {} self.camera_rect = {} self.show_bound_max_size = 0 -- if ApplicationPlatform ~= RuntimePlatform.IPhonePlayer then -- -- 检测模式 true为四叉树检测 -- self.change_check_mode = true -- end self.change_check_mode = false for i=1,9 do self.point_list[i] = Vector3(0,0,0) end --加载完NPC传送点后强制检测一次区域对象 local func = function() self.lastCamPos = nil end GlobalEventSystem:Bind(EventName.NPC_LOAD_FINISH,func) --小飞鞋传送上天后强制检测一次远景模型加载 local fly_func = function() self.force_check_far_model_state = true end GlobalEventSystem:Bind(SceneEventType.DO_FLYSHOE_EFFECT_FINISH_UP,fly_func) --小飞鞋落地后强制检测一次SAP清理旧位置对象 local fly_end_func = function() self.check_create_obj_frame = 1000 self.check_create_far_frame = 1000 self.force_check_far_model_state = true end GlobalEventSystem:Bind(SceneEventType.DO_FLYSHOE_EFFECT_FINISH_DOWN,fly_end_func) local scene_change_func = function() self.initTree = false end GlobalEventSystem:Bind(SceneEventType.SCENE_CHANGED, scene_change_func) end function SapManager:getInstance() if SapManager.Instance == nil then SapManager.New() end return SapManager.Instance end --[[ func结构 operate:操作类型 type:对象类型 data:自定义数据 ]] function SapManager:RegisterCallbackFunc( func ) table_insert(self.callback_func_list,func) end --[[ obj结构 id:对象索引 obj_type:对象类型 SapManager.ObjType pos:坐标 co.TableXY size:大小 co.TableXY ]] function SapManager:RegisterObj(obj) local id = obj.type .. "-" .. obj.id if not self.objs_list[id] then obj.size.z = obj.size.z or 1 obj.bound_offset = obj.bound_offset or co.TableXYZ(0,0,0) obj.bound_offset.z = obj.bound_offset.z or 0 obj.pos.z = obj.pos.z or 0 obj.rect = {center = obj.pos, x1 = obj.pos.x - obj.size.x/2 + obj.bound_offset.x, y1 = obj.pos.y - obj.size.y/2 + obj.bound_offset.y, z1 = obj.pos.z - obj.size.z/2 + obj.bound_offset.z, x2 = obj.pos.x + obj.size.x/2 + obj.bound_offset.x, y2 = obj.pos.y + obj.size.y/2 + obj.bound_offset.y, z2 = obj.pos.z + obj.size.z/2 + obj.bound_offset.z,} self.objs_list[id] = obj self.obj_state[id] = false if self.change_check_mode then self:InsertTree(obj) end else logWarn("SapManager:RegisterObj id is registered",id) end end function SapManager:UnRegisterObj(obj_type,obj_index) local id = obj_type .. "-" .. obj_index if self.objs_list[id] then if self.obj_state[id] then local obj = self.objs_list[id] for k,func in pairs(self.callback_func_list) do func(SapManager.OperateType.DestoryOBJ,obj.type,obj.id) end end self.objs_list[id] = nil self.obj_state[id] = nil if self.change_check_mode then self:ClearTreeObj(id) end end end function SapManager:IsInScreen(screen_pos) if screen_pos.x > 0 and screen_pos.x < ScreenWidth + self.screen_delta.x and screen_pos.y > 0 and screen_pos.y < ScreenHeight + self.screen_delta.y then return true else return false end end function SapManager:IsOutSide(val1) if val1 > SapManager.viewDistanceMax or val1 < SapManager.viewDistanceMin then return true else return false end end --使用更加精细的检测模式 function SapManager:SetUseCarefulCheckMode(bool, close_dis_check) self.is_use_higher_check = bool self.is_close_dis_check = close_dis_check end --[[ 判断条件:如果两个“包围盒的中心点的距离”大于“两个包围盒长度的总和的一半”,就是超出边界(画图更能理解) ]] function SapManager:InRect( r1, obj) local r2 = obj.rect local is_far = obj.is_far if self.is_use_higher_check then if obj.is_far == false and obj.type == SapManager.ObjType.Model then --if SystemMemoryLevel.Cur == SystemMemoryLevel.Hight then --高端机采用更精确算法 r2 = {x1 = obj.rect.x1,y1 = obj.rect.z1 , z1 = obj.rect.y1, x2 = obj.rect.x2,y2 = obj.rect.z2 , z2 = obj.rect.y2, center = obj.rect.center} is_far = true --end end end if is_far then if not obj.is_far then if self:InSmallRect(r1,r2) then return true end end self.cur_model_pos.x = obj.rect.center.x self.cur_model_pos.y = obj.rect.center.z local dis = CoVector2.distance(self.main_role_pos,self.cur_model_pos) if not self.is_close_dis_check and self:IsOutSide(dis)then return false end --判断条件:包围盒的任意一个顶点在屏幕内,就认为能看到 self.point_list[1].x,self.point_list[1].y,self.point_list[1].z = r2.x1,r2.y1,r2.z1 self.point_list[2].x,self.point_list[2].y,self.point_list[2].z = r2.x2,r2.y1,r2.z1 self.point_list[3].x,self.point_list[3].y,self.point_list[3].z = r2.x2,r2.y1,r2.z2 self.point_list[4].x,self.point_list[4].y,self.point_list[4].z = r2.x1,r2.y1,r2.z2 self.point_list[5].x,self.point_list[5].y,self.point_list[5].z = r2.x1,r2.y2,r2.z1 self.point_list[6].x,self.point_list[6].y,self.point_list[6].z = r2.x1,r2.y2,r2.z2 self.point_list[7].x,self.point_list[7].y,self.point_list[7].z = r2.x2,r2.y2,r2.z1 self.point_list[8].x,self.point_list[8].y,self.point_list[8].z = r2.x2,r2.y2,r2.z2 self.point_list[9].x,self.point_list[9].y,self.point_list[9].z = r2.center.x,r2.center.y,r2.center.z local screen_pos = 1 for i,v in ipairs(self.point_list) do screen_pos = self.main_camera:WorldToScreenPoint(v/MainCamera.PixelsToUnits) if self:IsInScreen(screen_pos) then return true end end return false end local state = self:InSmallRect(r1,r2) if state and self.open_model_height_check_mode and obj.type == SapManager.ObjType.Model then local offset = obj.pos.z - self.role_height if offset > 3000 or offset < -3000 then state = false end end return state end function SapManager:InSmallRect(r1,r2) local l1 = (r1.x1+r1.x2)*0.5-(r2.x1+r2.x2)*0.5 -- 2个包围盒的中心点的距离 local l2 = (r1.x2+r2.x2-r1.x1-r2.x1)*0.5 -- [(r1.x2 - r1.x1) + (r2.x2 - r2.x1)] * 0.5 两个包围盒长的总和的一半 local s1 = (r1.y1+r1.y2)*0.5-(r2.y1+r2.y2)*0.5 local s2 = (r1.y2+r2.y2-r1.y1-r2.y1)*0.5 if l1 < 0 then l1 = -l1 end if l2 < 0 then l2 = -l2 end if s1 < 0 then s1 = -s1 end if s2 < 0 then s2 = -s2 end if l1 < l2 and s1 < s2 then return true end return false end function SapManager:Update( cam_pos ) if not self.is_enabled then return end if self.show_bound_max_size > 0 then self:DebugDrawBound(cam_pos) end self.update_frame = self.update_frame + 1 self.check_create_far_frame = self.check_create_far_frame + 1 self.check_create_obj_frame = self.check_create_obj_frame + 1 if not self.scene:IsSceneLoaded() then self.first_check = true self.last_create_effect_time = 0 return end if cam_pos and self.lastCamPos then local x_offset = cam_pos.x - self.lastCamPos.x local y_offset = cam_pos.y - self.lastCamPos.y local small_offset = 100 if ( x_offset < small_offset and x_offset > -small_offset ) and ( y_offset < small_offset and y_offset > -small_offset ) then if not self.force_check_far_model_state and not self.wait_load_terrain_finish and not self.wait_load_particle_finish then return end end end if Time.time - self.last_update_time < 0.1 and not self.wait_load_terrain_finish and not self.wait_load_particle_finish then return end local main_role = self.scene:GetMainRole() if not main_role then return end self.main_role_pos.x = main_role.real_pos.x self.main_role_pos.y = main_role.real_pos.y self.last_update_time = Time.time if self.lastCamPos == nil then self.lastCamPos = {x = cam_pos.x, y = cam_pos.y} else self.lastCamPos.x = cam_pos.x self.lastCamPos.y = cam_pos.y end local scale = self.main_camera:GetCameraRatio() self.role_height = main_role.obj_pos_height * MainCamera.PixelsToUnits local low_offset = 1 if SystemMemoryLevel.Cur == SystemMemoryLevel.Low then low_offset = 0.8 if SystemRuntimePlatform.IsIphone() then low_offset = 0.6 end end --跳跃的时候摄像机变高了,会影响视野范围,这里增加一个参数做偏移 --0.1 这个参数如果最大跳跃高度改变了,可能需要调整 local jump_offset = 0 if main_role then jump_offset = (main_role.jump_height)/MainCamera.PixelsToUnits*0.15 end scale = scale + jump_offset local now_width = SapManager.XSize * scale * low_offset local now_height = SapManager.YSize * scale * low_offset self.camera_rect.x1 = cam_pos.x - now_width * 0.7 self.camera_rect.y1 = cam_pos.y - now_height * 0.3 self.camera_rect.x2 = cam_pos.x + now_width * 0.7 self.camera_rect.y2 = cam_pos.y + now_height * 1 if MapView.OpenSceneOperate then self.camera_rect.y1 = cam_pos.y - now_height * 1 end --远景对象一秒检测1次 local check_far = false if self.update_frame > 60 then check_far = true self.update_frame = 0 self.force_check_far_model_state = false end --已创建的远景对象5秒才检测是否要销毁 local check_create_far = false if self.check_create_far_frame > 300 then check_create_far = true self.check_create_far_frame = 0 end --已创建的场景对象3秒才检测是否要销毁 local check_create_obj = false if self.check_create_obj_frame > 180 or SystemRuntimePlatform.IsIphone() then check_create_obj = true self.check_create_obj_frame = 0 end --local cur_time = os.clock() if self.change_check_mode then if self.initTree then -- 初始化判断 self:CheckChild(self.objTree,check_far,check_create_far,check_create_obj) end else self:CheckObj(check_far,check_create_far,check_create_obj) end if self.deleta_list then for key,id in pairs(self.deleta_list) do local obj = self.objs_list[id] for k,func in pairs(self.callback_func_list) do if obj.type == SapManager.ObjType.CacheEffect then func(SapManager.OperateType.DestoryOBJ,obj.type,obj.data.obj.obj_name) else func(SapManager.OperateType.DestoryOBJ,obj.type,obj.id) end end self.deleta_list[key] = nil end end if self.create_list then local sort_func = function(a,b) local i = self.objs_list[a] local j = self.objs_list[b] local di = co.PointDistance(i.pos,main_role.real_pos) local dj = co.PointDistance(j.pos,main_role.real_pos) if di < dj then return true end end table_sort(self.create_list,sort_func) for key,id in ipairs(self.create_list) do local obj = self.objs_list[id] for k,func in pairs(self.callback_func_list) do func(SapManager.OperateType.CreateOBJ,obj.type,obj.data) end self.create_list[key] = nil end end self.first_check = false end -- 递归遍历树节点 function SapManager:CheckChild(parent,check_far,check_create_far,check_create_obj) for id,v in pairs(parent.obj) do local obj = self.objs_list[v] if not self.ignore_list[id] and (not obj.is_far or self.first_check or check_far) then if obj.is_far and self.obj_state[id] and not check_create_far then --已经加载的远景对象未到检测周期 else if self.obj_state[id] and not check_create_obj then --已经加载的场景对象未到检测周期 else if self:InRect(self.camera_rect,obj) then if not self.obj_state[id] then self.obj_state[id] = true table_insert(self.create_list,id) end else if self.obj_state[id] then self.obj_state[id] = false table_insert(self.deleta_list,id) end end end end end end for i,v in pairs(parent.childs) do if self:InSmallRect(self.camera_rect,v.rect) or parent.has_far==1 then self:CheckChild(v,check_far,check_create_far,check_create_obj) end end end -- 全遍历场景物体 function SapManager:CheckObj(check_far,check_create_far,check_create_obj) for id,obj in pairs(self.objs_list) do if not self.ignore_list[id] and (not obj.is_far or self.first_check or check_far) then if obj.is_far and self.obj_state[id] and not check_create_far then --已经加载的远景对象未到检测周期 else if self.obj_state[id] and not check_create_obj then --已经加载的场景对象未到检测周期 else if self:InRect(self.camera_rect,obj) then if not self.obj_state[id] then --限制场景特效创建间隔 local delay_state = false --[[ if obj.type == SapManager.ObjType.Effect then if cur_time - self.last_create_effect_time <= 0.5 then delay_state = true else self.last_create_effect_time = cur_time end end ]] if not delay_state then self.obj_state[id] = true if self.create_list == nil then self.create_list = {} end table_insert(self.create_list,id) end end else if self.obj_state[id] then self.obj_state[id] = false if self.deleta_list == nil then self.deleta_list = {} end table_insert(self.deleta_list,id) end end end end end end end function SapManager:SetEnabled(bool) self.is_enabled = bool end function SapManager:AddIgnoreObj(id) self.ignore_list[id]=true self.obj_state[id] = false local obj = self.objs_list[id] if obj then for k,func in pairs(self.callback_func_list) do func(SapManager.OperateType.DestoryOBJ,obj.type,obj.id) end end end function SapManager:RemoveIgnoreObj(id) self.ignore_list[id]=nil end function SapManager:ForceCheckFarModelLoad() self.force_check_far_model_state = true end function SapManager:WaitLoadTerrainFinish(state) self.wait_load_terrain_finish = state end function SapManager:WaitLoadParticleFinish(state) self.wait_load_particle_finish = state end function SapManager:OpenModelHeightCheckMode( state ) self.open_model_height_check_mode = state end ---------使用四叉树管理物体----------- function SapManager:InitTree() local map_size = MapView:getInstance():GetMapSize() local max_size = math.max(map_size.x, map_size.y) self.maxDepth = max_size > 22000 and 4 or 3 self.objTree = { depth = 0, obj = {}, childs= {}, rect = { x1 = 0, y1 = 0, x2 = max_size, y2 = max_size, }, } local scene_id = SceneManager.Instance:GetSceneId() -- 全加载地图 if scene_id and MapView.AllLoadScene[scene_id] then self.maxDepth = 0 end end -- 创建树节点 function SapManager:CreatTreeNode(parent, i, obj_rect) local rect = parent.rect local pos_x = (rect.x2 + rect.x1) * 0.5 local pos_y = (rect.y2 + rect.y1) * 0.5 local rect_size = (rect.x2 - pos_x) * 0.5 if i == 1 then pos = {x = pos_x + rect_size, y = pos_y + rect_size} elseif i == 2 then pos = {x = pos_x + rect_size, y = pos_y - rect_size} elseif i == 3 then pos = {x = pos_x - rect_size, y = pos_y - rect_size} else pos = {x = pos_x - rect_size, y = pos_y + rect_size} end local child_rect = { x1 = pos.x - rect_size, y1 = pos.y - rect_size, x2 = pos.x + rect_size, y2 = pos.y + rect_size, } -- 包含obj才创建节点 if not self:InBound(child_rect,obj_rect) then return end parent.childs[i] = { depth = parent.depth + 1, obj = {}, childs = {}, rect = child_rect, } return parent.childs[i] end -- obj插入树,obj被子节点包含时,添加到子节点,否则添加到父节点 function SapManager:InsertTree(obj) if not self.initTree then self:InitTree() self.initTree = true end local rect = obj.rect local id = obj.type .. "-" .. obj.id local is_far = obj.is_far and 1 or 0 local Insert Insert = function ( parent) -- 标志有无远物 parent.has_far = parent.has_far == 1 and 1 or is_far local inBound = false for i = 1,4 do local is_creat = false local child = parent.childs[i] if not child then -- 动态添加子节点 child = self:CreatTreeNode(parent, i,rect) is_creat = true end if child and (is_creat or self:InBound(child.rect,rect)) then if child.depth < self.maxDepth then Insert(child) else if child.obj[id] == nil then -- 只存id,减少消耗 child.obj[id] = id end end inBound = true break end end if not inBound and parent.obj[id] == nil then parent.obj[id] = id end end Insert(self.objTree) end -- r1 包含 r2 function SapManager:InBound(r1,r2) if r1.x1 <= r2.x1 and r1.y1 <= r2.y1 and r1.x2 >= r2.x2 and r1.y2 >= r2.y2 then return true end return false end function SapManager:ClearTreeObj( id ) local clearTree clearTree = function(parent) if parent.obj[id] then parent.obj[id] = nil return end parent.has_far = 0 for i,v in pairs(parent.childs) do clearTree(v) end end clearTree(self.objTree) end -- 编辑器窗口下显示检测区域 function SapManager:DebugDrawBound(cam_pos) if self.initTree and self.camera_rect.x1 and self.change_check_mode then self:DebugDrawBoundByRect(self.camera_rect,Color.red) local showChild showChild = function(parent) local len = TableSize(parent.obj) if len > 0 then for id,v in pairs(parent.obj) do local obj = self.objs_list[id] local r = obj.rect if obj and obj.size and obj.size.x < self.show_bound_max_size then if self:InRect(self.camera_rect,obj) then self:DebugDrawBoundByRect(r,Color.green) else self:DebugDrawBoundByRect(r,Color(1, 1, 1, 0.6)) end end end end --local color = self:GetColorByDepth(parent.depth) self:DebugDrawBoundByRect(parent.rect,color) if TableSize(parent.childs) == 0 then return end for i,v in pairs(parent.childs) do -- 进入区域才检测 if self:InSmallRect(self.camera_rect,v.rect) or parent.has_far==1 then showChild(v) end end end showChild(self.objTree) end -- 兼容遍历模式的区域显示 if not self.change_check_mode and self.camera_rect.x1 then self:DebugDrawBoundByRect(self.camera_rect,Color.red) for id,obj in pairs(self.objs_list) do if obj.size and obj.size.x < self.show_bound_max_size then local r = obj.rect if self:InRect(self.camera_rect,obj) then self:DebugDrawBoundByRect(r,Color.green) else self:DebugDrawBoundByRect(r,Color(1, 1, 1, 0.6)) end end end end end function SapManager:GetColorByDepth( depth ) --最底层蓝色 if depth == self.maxDepth then return Color.blue end end function SapManager:DebugDrawBoundByRect(r,color) color = color or Color.yellow r.z1 = r.z1 or -3000 r.z2 = r.z2 or 5000 if not self.point then self.point = {} for i=1,9 do self.point[i] = Vector3(0,0,0) end end self.point[1].x,self.point[1].z,self.point[1].y = r.x1/100,r.y1/100,r.z1/100 self.point[2].x,self.point[2].z,self.point[2].y = r.x2/100,r.y1/100,r.z1/100 self.point[3].x,self.point[3].z,self.point[3].y = r.x2/100,r.y1/100,r.z2/100 self.point[4].x,self.point[4].z,self.point[4].y = r.x1/100,r.y1/100,r.z2/100 self.point[5].x,self.point[5].z,self.point[5].y = r.x1/100,r.y2/100,r.z1/100 self.point[6].x,self.point[6].z,self.point[6].y = r.x1/100,r.y2/100,r.z2/100 self.point[7].x,self.point[7].z,self.point[7].y = r.x2/100,r.y2/100,r.z1/100 self.point[8].x,self.point[8].z,self.point[8].y = r.x2/100,r.y2/100,r.z2/100 UnityEngine.Debug.DrawLine(self.point[1], self.point[2],color); UnityEngine.Debug.DrawLine(self.point[2], self.point[3],color); UnityEngine.Debug.DrawLine(self.point[3], self.point[4],color); UnityEngine.Debug.DrawLine(self.point[4], self.point[1],color); UnityEngine.Debug.DrawLine(self.point[4], self.point[6],color); UnityEngine.Debug.DrawLine(self.point[3], self.point[8],color); UnityEngine.Debug.DrawLine(self.point[2], self.point[7],color); UnityEngine.Debug.DrawLine(self.point[1], self.point[5],color); UnityEngine.Debug.DrawLine(self.point[5], self.point[6],color); UnityEngine.Debug.DrawLine(self.point[6], self.point[8],color); UnityEngine.Debug.DrawLine(self.point[8], self.point[7],color); UnityEngine.Debug.DrawLine(self.point[7], self.point[5],color); end function SapManager:ChangeSize( size) self.show_bound_max_size = size or self.show_bound_max_size end