React Native游戏拖拽Tile避免重叠问题求助
React Native 游戏Tile拖拽重叠问题排查与修复
我用React Native开发一款游戏,将一组Tile对象绝对定位渲染,吸附点是2行16列的网格。每个Tile有Width、height和coordinates属性。拖拽释放后,Tile偶尔会出现重叠,推测是把第二行(y>0)的元素拖到第一行(y===0)时触发,但不确定原因。
当Tile被移至已占用的吸附点时,连锁反应函数需要让连锁中的其他Tile左右移动或换行到行首/行尾,相关代码如下:
连锁反应处理函数
// Function to handle chain reaction const handleChainReaction = (newTiles, movedTile) => { let tilesToCheck = [movedTile]; while (tilesToCheck.length > 0) { let currentTile = tilesToCheck.shift(); let occupiedIndex; do { occupiedIndex = newTiles.findIndex( (t) => t.x === currentTile.x && t.y === currentTile.y && t.key !== currentTile.key ); if (occupiedIndex !== -1) { let displacedTile = { ...newTiles[occupiedIndex] }; displacedTile.x += TILE_WIDTH; // Move the tile to the right // Check for horizontal boundary if (displacedTile.x >= CONTAINER_WIDTH) { displacedTile.x = 0; displacedTile.y += TILE_HEIGHT; // Check for vertical boundary if (displacedTile.y >= TOTAL_HEIGHT) { displacedTile.y = 0; } } newTiles[occupiedIndex] = displacedTile; // Add the displaced tile to the check queue tilesToCheck.push(displacedTile); } } while (occupiedIndex !== -1); } return newTiles; };
PanResponder 代码
const panResponder = useMemo( () => PanResponder.create({ onStartShouldSetPanResponder: () => true, onPanResponderMove: (e, gestureState) => { const newX = initialPan.x + gestureState.dx; const newY = initialPan.y + gestureState.dy; pan.setValue({ x: newX, y: newY }); }, onPanResponderRelease: (e, gestureState) => { const finalX = initialPan.x + gestureState.dx; const finalY = initialPan.y + gestureState.dy; const closestSnapPoint = findClosestSnapPoint( finalX, finalY, snapPoints ); let newTiles = [...tiles]; // Clone the tiles array const movedTileIndex = newTiles.findIndex(t => t.key === tile.key); let movedTile = { ...newTiles[movedTileIndex], x: closestSnapPoint.x, y: closestSnapPoint.y }; newTiles[movedTileIndex] = movedTile; newTiles = handleChainReaction(newTiles, movedTile); checkGroups(newTiles); // Animate to the closest snap point Animated.spring(pan, { toValue: { x: closestSnapPoint.x, y: closestSnapPoint.y }, useNativeDriver: false, friction: 30, tension: 60, }).start(); // Update the initial position for the next drag initialPan.x = closestSnapPoint.x; initialPan.y = closestSnapPoint.y; }, }), [tiles] );
吸附点生成代码
for (let row = 0; row < 2; row++) { for (let col = 0; col < 16; col++) { const x = col * TILE_WIDTH; const y = row * TILE_HEIGHT; snaps.push({ x, y }); } }
Tile对象示例
{"color": "blue", "groupIndex": 0, "groupLength": 0, "isInGroup": false, "key": "n13-cblue", "label": 13, "number": 13, "x": 576.675, "y": 53.823}
问题根源分析
- 浮点精度误差:Tile的x/y值为浮点数,但位置判断用严格相等
===比较,微小的浮点误差会导致无法检测到位置重叠,最终出现Tile叠加。 - 换行逻辑不严谨:仅处理向右移动到行尾换行到下一行的场景,未考虑2行网格的循环边界逻辑,且没有处理反向挤压的情况。
- 连锁反应队列重复处理:
do-while循环会重复检查同一位置,可能导致Tile被多次移动,或遗漏新的位置冲突。
修复方案
1. 用网格坐标替代浮点值判断位置
通过将x/y转换为整数网格行列,避免浮点精度问题:
// 新增工具函数:转换为网格坐标 const getGridPos = (x, y) => ({ col: Math.round(x / TILE_WIDTH), row: Math.round(y / TILE_HEIGHT) }); // 修改handleChainReaction中的位置判断逻辑 occupiedIndex = newTiles.findIndex(t => { const currGrid = getGridPos(currentTile.x, currentTile.y); const tileGrid = getGridPos(t.x, t.y); return currGrid.col === tileGrid.col && currGrid.row === tileGrid.row && t.key !== currentTile.key; });
2. 完善连锁位移与换行逻辑
基于网格行列处理位移,确保2行网格的循环边界正确:
// 修改handleChainReaction中的位移代码 if (occupiedIndex !== -1) { let displacedTile = { ...newTiles[occupiedIndex] }; const displacedGrid = getGridPos(displacedTile.x, displacedTile.y); // 向右移动一格 displacedGrid.col += 1; // 超出列数则换行 if (displacedGrid.col >= 16) { displacedGrid.col = 0; displacedGrid.row += 1; // 超出行数则回到第一行 displacedGrid.row = displacedGrid.row >= 2 ? 0 : displacedGrid.row; } // 转换回实际坐标 displacedTile.x = displacedGrid.col * TILE_WIDTH; displacedTile.y = displacedGrid.row * TILE_HEIGHT; newTiles[occupiedIndex] = displacedTile; tilesToCheck.push(displacedTile); }
3. 优化连锁队列处理逻辑
移除do-while循环,改为单次检查后加入队列,避免重复处理同一位置:
while (tilesToCheck.length > 0) { let currentTile = tilesToCheck.shift(); const occupiedIndex = newTiles.findIndex(t => { const currGrid = getGridPos(currentTile.x, currentTile.y); const tileGrid = getGridPos(t.x, t.y); return currGrid.col === tileGrid.col && currGrid.row === tileGrid.row && t.key !== currentTile.key; }); if (occupiedIndex !== -1) { let displacedTile = { ...newTiles[occupiedIndex] }; const displacedGrid = getGridPos(displacedTile.x, displacedTile.y); displacedGrid.col += 1; if (displacedGrid.col >= 16) { displacedGrid.col = 0; displacedGrid.row += 1; displacedGrid.row = displacedGrid.row >= 2 ? 0 : displacedGrid.row; } displacedTile.x = displacedGrid.col * TILE_WIDTH; displacedTile.y = displacedGrid.row * TILE_HEIGHT; newTiles[occupiedIndex] = displacedTile; tilesToCheck.push(displacedTile); } }
4. 确保拖拽后的吸附坐标精确
在释放拖拽时,直接使用网格计算后的精确坐标,避免浮点误差:
// 修改onPanResponderRelease中的movedTile赋值 const closestSnapPoint = findClosestSnapPoint(finalX, finalY, snapPoints); const snapGrid = getGridPos(closestSnapPoint.x, closestSnapPoint.y); const preciseX = snapGrid.col * TILE_WIDTH; const preciseY = snapGrid.row * TILE_HEIGHT; let movedTile = { ...newTiles[movedTileIndex], x: preciseX, y: preciseY };
内容的提问来源于stack exchange,提问作者Abdul Sadik Yalcin
相关产品推荐
相关产品推荐

