popStar 又叫 消灭星星。 闲着想了下算法。 点击星星格子 会出现星星的描边,就是多边形格子的描边 最终 效果类似下图.
中心思想,贴出核心代码
--[[ 假如 一个格子 上方的格子 是无效格子 意味着这个格子的上面的这条边 是多边形的轮廓边 同理 根据四个方向的无效点 就可以获取 四个方向的轮廓边 最终将所有无效边 连接起来就是多边形的轮廓 ]] --[[ step1: @param 单个格子 @return1 返回 在map内 上下左右的有效格子 @return2 返回 在map内 无效方向。用来获取多边形的轮廓 step2: 1、获取所有点 2、根据点之间的距离 分组 获取 任意点a 如果 找不到 a 相邻 格子间距 的点就说明是轮廓点 如果能找到 偶数个点 就丢弃 说明可以合并 如果奇数个点 就保留 说明是转折点 ]]
处理点击的方法
--@param 点击位置 function GameLayer:handleTouch( pos_tbl_ ) local pos_x = pos_tbl_.x local pos_y = pos_tbl_.y -- 根据点击的点 获取所属行列 x y local x,y = getXYForPosition({pos_x,pos_y}) print(tostring(x) .. " === handleTouch === " .. tostring(y)) if x <= 0 or x > MAP_WIDTH then print("Touch x not in map " .. tostring(x)) return end if y <= 0 or y > MAP_HEIGHT then print("Touch y not in map " .. tostring(y)) return end -- 根据xy 坐标获取到对应的点 local target_node = self:getPointWidgetByPos({x, y}) if target_node then -- 根据格子节点 获取 挨着的所有相同颜色的点 local points = self:getAdJoinPoints({x, y}, target_node.color) if #points <= 1 then print("只有一个单元结点,不会标记或删除") return end -- 根据所有 同色格子 获取所有格子的边的向量集合 local edges = self:getBorderPointsByInvalidDirPoints(points) -- 合并所有同方向 首尾相接的向量 local ret = self:combineEdges(edges) -- 将所有向量的边 重新转换成点 local border_points = self:convertEdges2Position(ret) -- 将所有点连线 画出来 self:drawNodesBorder(border_points, cc.c4f(0.5, 0, 0.5, 1), self._main_panel) else print("not find target_node index = " .. tostring(index)) end end
通过dfs 获取相邻同色节点
--[[ @func 用来判断当前点是否有效 就是合法的格子 因为后面格子会消除 所以宽度高度都是不定的 维护一个二维的array @param1 当前点 @param2 当前map所有列的array 每个array中 对应列的格子的集合 ]] local function isPointValid( temp_pos_tbl_, map_height_tbl_ ) -- 当前列的最高值 local cur_line_height = map_height_tbl_[temp_pos_tbl_[1]] local cur_line_count = #(map_height_tbl_) return (temp_pos_tbl_[1] >= 1 and temp_pos_tbl_[1] <= cur_line_count) and (temp_pos_tbl_[2] >= 1 and temp_pos_tbl_[2] <= cur_line_height) end --[[ dfs 根据单个格子位置 获取所有同色格子的集合 ]] function GameLayer:getAdJoinPoints( pos_tbl_, color_ ) -- 获取一个点 相对轮廓来说 有效格子集合和无效格子集合 local function getNearByPoint( temp_pos_tbl_ ) local temp_x = temp_pos_tbl_[1] local temp_y = temp_pos_tbl_[2] local temp_ret = {} -- 方向向量 local dir_points = { {0,1}, {0,-1}, {-1,0}, {1,0} } local invalid_dir_points = {} for i=1,#dir_points do local dir_p = dir_points[i] local p = {temp_x + dir_p[1], temp_y + dir_p[2], dir_p} if isPointValid(p, self._map_height_tbl) then table.insert(temp_ret, p) else table.insert(invalid_dir_points, dir_p) end end return temp_ret, invalid_dir_points end -- 已经处理过的列表 local handled_key_map = {} -- 预处理 队列的key map 格子的查重 避免一直遍历 local pre_point_key_map = {} -- 将要处理的列表 local pre_point_list = {} --[[ 起始点 -> 获取邻接有效点 ->放入预处理队列 遍历预处理队列 -> 处理 -> 处理完成 -> 获取当前结点的邻接有效点 如果当前结点在预处理队列中 则跳过 不在 则加入预处理队列。 广度优先 ]] local ret_tbl = {}
-- 起始点 local t_p = pos_tbl_ repeat -- 放入处理过的格子map local handled_key = getKeyByPos(t_p) handled_key_map[handled_key] = 1 -- 获取邻接的有效格子集合 和 无效格子集合 local nearby_points, invalid_dir_points = getNearByPoint(t_p) -- 遍历有效格子集合 for i,v in ipairs(nearby_points) do local temp_pre_key = getKeyByPos(v) if not pre_point_key_map[temp_pre_key] and not handled_key_map[temp_pre_key] then -- 比较有效格子的颜色 颜色相同的点就是想要的 local node = self:getPointWidgetByPos(v) if node.color == color_ then table.insert(pre_point_list, {v[1], v[2]}) --标记这个点已经处理过 pre_point_key_map[temp_pre_key] = 1 v[3] = {} end end end -- 将无效方向的点 放入结果集中 在下一步需要用 table.insert(t_p, invalid_dir_points) table.insert(ret_tbl, t_p)
t_p = table.remove(pre_point_list, 1) until (not t_p) return ret_tbl end
获取轮廓边向量
--[[ @param1: points_tbl_ = { { x, y, { { 1, 0 }, { -1, 0 }, ... } }, ... } 这个方法是用来 将无效点转换成 边的向量 最终将向量连接起来变成多边形的轮廓 .. {0,1},{1,1} .. {0,0},{1,0} 四个格子组成一个田字 左下格子的右上顶点 定义为{0,0}点 左上格子的右下顶点 定义为{0,1}点 右上格子的左下顶点 定义为{1,1}点 右下格子的左上顶点 定义为{1,0}点 将这个属性放到 边的顶点的第三个位置 组成点 {x,y,dir_p} 下一步用来方便边的合并 ]] function GameLayer:getBorderPointsByInvalidDirPoints( points_tbl_ ) -- local dir_points = { {0,1}, {0,-1}, {-1,0}, {1,0} } -- 返回一个 向量 { {x1,y1, 额外属性dir_p}, ... } -- 规定方向向量都是顺时针转 按照顺时针组成一个多边形的轮廓 local function getEdgeByXYAndDirP( x_, y_, dir_p_ ) -- 方向向量{0,1}即相对本格子上边的格子为无效格子 则对应的轮廓边为 上面横着的从左到右 if dir_p_[1] == 0 then if dir_p_[2] == 1 then return {{x_, y_ + 1, {1,0}}, {x_+1, y_+1, {0,0}}} else -- 方向向量{0,0}即相对本格子下边的格子为无效格子 则对应的轮廓边为 下面横着的从右到左 return {{x_+1, y_, {0,1}}, {x_, y_, {1,1}}} end elseif dir_p_[2] == 0 then if dir_p_[1] == 1 then -- 同理 竖着的 右边缘 从上到下 return {{x_+1, y_+1, {0,0}}, {x_+1, y_, {0,1}}} else-- 同理 竖着的 左边缘 从下到上 return {{x_, y_, {1,1}}, {x_, y_+1, {1,0}}} end end end -- 先将格子根据坐标排序 这样获取的边会更集中 local points_tbl = points_tbl_ table.sort(points_tbl, function ( v1_, v2_ ) if v1_[1] == v2_[1] then return v1_[2] > v2_[2] end return v1_[1] > v2_[1] end) local direct_edges_tbl = {} for i,v in ipairs(points_tbl_) do local dir_p_tbl = v[3] for j,vv in ipairs(dir_p_tbl) do local edge = getEdgeByXYAndDirP(v[1], v[2], vv) table.insert(direct_edges_tbl, edge) end end return direct_edges_tbl end
合并边
--[[ 根据交点 把边串起来 @param1 direct_edges_tbl = { {{x1_, y1_}, {x2_, y2_}}, {{x2_, y2_}, {x3_, y3_}}, ... } ]] function GameLayer:combineEdges( direct_edges_tbl_ ) -- 将对角线上的两个点 合并成 一个点 针对品字形的左肩这种情况 local function getPdir( p1_, p2_ ) local dir_1 = p1_[3] local dir_2 = p2_[3] -- 两个点x,y存在相同 就直接return 否则返回两个点的延长线交点 if dir_1[1] ~= dir_2[1] and dir_1[2] ~= dir_2[2] then else return nil end -- 点1的x摩2取反 点2的y摩2取反 local x = (dir_1[2] + 1) % 2 local y = (dir_2[1] + 1) % 2 local ret_dir = {x, y} return ret_dir end --核心就是操作 这个深拷贝出来的边的集合 将边连起来 local copy_direct_edges_tbl = clone(direct_edges_tbl_) local function getSamePointsEdge(edge_) local t_p = edge_[2] for i=1, #copy_direct_edges_tbl do local edge = copy_direct_edges_tbl[i] local temp_p1 = edge[1] local temp_p2 = edge[2] local point_value = t_p[3] if t_p[1] == temp_p1[1] and t_p[2] == temp_p1[2] then edge = table.remove(copy_direct_edges_tbl, i) local t = getPdir(t_p, temp_p1) if t then t_p[3] = t temp_p1[3] = t end return {edge[1], edge[2]} end if t_p[1] == temp_p2[1] and t_p[2] == temp_p2[2] then edge = table.remove(copy_direct_edges_tbl, i) local t = getPdir(edge_[1], temp_p1, t_p[3]) return {edge[2], edge[1], t} end end end -- 从一条边的 起始点p1开始走,从当前边edge的另一个点p2到 copy_direct_edges_tbl 边的集合里面去找另一条边的相同点 local cur_edge = table.remove(copy_direct_edges_tbl, 1) local cur_p = cur_edge[1] local order_edges = {v} for i,v in ipairs(direct_edges_tbl_) do local edge = getSamePointsEdge(cur_edge) if edge then table.insert(order_edges, edge) cur_edge = edge else --最后一条边 会找不到下一个 print("getSamePointsEdge not find node ===") end end -- 起始点和尾节点要连起来 这里有点问题 遇到不完全闭合的图形会出现连接位置问题 (问题不大,再优化啦) if cur_p and cur_edge then local t = getPdir(cur_edge[2], cur_p) if t then cur_edge[2][3] = t cur_p[3] = t end end --合并同一方向上的 边 首尾相接的合并 local function mergeTwoEdge( edge1_, edge2_ ) local t_p1 = edge1_[1] local t_p2 = edge1_[2] local t_p3 = edge2_[1] local t_p4 = edge2_[2] local point_value1 = t_p1[3] local point_value2 = t_p4[3] local x = t_p1[1] if x == t_p2[1] and x == t_p3[1] and x == t_p4[1] then if point_value1[1] == point_value2[1] or point_value1[2] == point_value2[2] then return true, {t_p1, t_p4} end end local y = t_p1[2] if y == t_p2[2] and y == t_p3[2] and y == t_p4[2] then -- return true, {t_p1, t_p4} if point_value1[1] == point_value2[1] or point_value1[2] == point_value2[2] then return true, {t_p1, t_p4} end end return false end -- 合并同一条直线上的点 local ret_edges = {} local cur_edge = nil for i,v in ipairs(order_edges) do if cur_edge then local flag, edge = mergeTwoEdge(cur_edge, v) if flag then cur_edge = edge else table.insert(ret_edges, cur_edge) cur_edge = v end else cur_edge = v end end return ret_edges end
把合并后的边还原成点
--[[ 把边还原成点 ]] function GameLayer:convertEdges2Position( order_edges_ ) local max_x, max_y = 0, 0 --根据第三位属性 计算是否添加 格子间距 local function convertIndexPoint2Pos( p_ ) local pos = getXY2Position(p_) local x = pos[1] local y = pos[2] local dir_x = p_[3][1] local dir_y = p_[3][2] if dir_x == 0 then x = x - MAP_CELL_SEP_WIDTH end if dir_y == 0 then y = y - MAP_CELL_SEP_WIDTH end return {x = x, y = y} end --把点从边上拆出来 local points = {} for i,edge in ipairs(order_edges_) do if i == 1 then local p = edge[1] table.insert(points, p) end local p = edge[2] table.insert(points, p) end local ret_points = {} for i,v in ipairs(points) do table.insert(ret_points, convertIndexPoint2Pos(v)) end return ret_points end
完啦!
最终的结果就是如上图 代码写的有点啰嗦,但是几个月之后,自己还能看懂 还不错哈, 有问题请指教咯
在这里记录下,其实根据这个思想 是可以做多边形的描边算法的. 比如三角形组合起来做描边算法...