You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SpriteKit中如何限制节点仅沿网格线移动?5x5网格场景技术问询

SpriteKit实现节点沿网格线移动方案

SpriteKit没有原生的网格移动约束机制,需要手动处理触摸事件和位置限制来实现需求,以下是具体实现步骤:

1. 定义网格参数与辅助方法

首先确定网格的单元格大小、行列数,实现网格坐标与场景坐标的转换方法,同时限制节点只能在网格范围内移动。

2. 初始化网格与节点

  • 可选绘制网格线用于调试;
  • 随机将节点放置在网格交点上。

3. 处理触摸事件

通过触摸生命周期方法(touchesBegan/touchesMoved/touchesEnded)控制节点移动:

  • touchesBegan:记录节点初始所在的网格位置;
  • touchesMoved:限制节点只能沿水平或垂直方向移动(根据触摸偏移量判断优先方向);
  • touchesEnded:将节点吸附到最近的同列或同行网格交点,确保符合移动规则。

完整代码示例

import SpriteKit

class GameScene: SKScene {
    // 网格单元格大小
    let cellSize = CGSize(width: 100, height: 100)
    // 5x5网格,交点为6x6
    let gridRows = 5
    let gridColumns = 5
    
    var playerNode: SKSpriteNode!
    // 记录触摸开始时节点的网格位置
    private var initialGridPosition: (row: Int, column: Int)?
    
    override func didMove(to view: SKView) {
        setupGridLines()
        spawnPlayerNode()
    }
    
    // 绘制网格线(调试用,可移除)
    private func setupGridLines() {
        // 水平线
        for row in 0...gridRows {
            let y = CGFloat(row) * cellSize.height
            let line = SKShapeNode(rect: CGRect(x: 0, y: y, width: frame.width, height: 1))
            line.strokeColor = .lightGray
            addChild(line)
        }
        // 垂直线
        for column in 0...gridColumns {
            let x = CGFloat(column) * cellSize.width
            let line = SKShapeNode(rect: CGRect(x: x, y: 0, width: 1, height: frame.height))
            line.strokeColor = .lightGray
            addChild(line)
        }
    }
    
    // 随机生成节点到网格交点
    private func spawnPlayerNode() {
        playerNode = SKSpriteNode(color: .systemRed, size: CGSize(width: 40, height: 40))
        let randomRow = Int.random(in: 0...gridRows)
        let randomColumn = Int.random(in: 0...gridColumns)
        playerNode.position = convertGridToScene(row: randomRow, column: randomColumn)
        addChild(playerNode)
    }
    
    // 网格坐标转场景坐标
    private func convertGridToScene(row: Int, column: Int) -> CGPoint {
        let x = CGFloat(column) * cellSize.width
        let y = CGFloat(row) * cellSize.height
        return CGPoint(x: x, y: y)
    }
    
    // 场景坐标转最近的网格坐标
    private func convertSceneToGrid(_ point: CGPoint) -> (row: Int, column: Int) {
        let column = Int(round(point.x / cellSize.width))
        let row = Int(round(point.y / cellSize.height))
        // 限制在网格范围内
        let clampedColumn = max(0, min(column, gridColumns))
        let clampedRow = max(0, min(row, gridRows))
        return (clampedRow, clampedColumn)
    }
    
    // 触摸开始:记录初始网格位置
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        let touchPos = touch.location(in: self)
        if playerNode.contains(touchPos) {
            initialGridPosition = convertSceneToGrid(playerNode.position)
        }
    }
    
    // 触摸移动:限制水平/垂直方向移动
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first, let initialGridPos = initialGridPosition else { return }
        let touchPos = touch.location(in: self)
        
        let horizontalDelta = abs(touchPos.x - playerNode.position.x)
        let verticalDelta = abs(touchPos.y - playerNode.position.y)
        
        var newPos = playerNode.position
        if horizontalDelta > verticalDelta {
            // 优先水平移动,保持y坐标为初始网格行的y值
            newPos.y = convertGridToScene(row: initialGridPos.row, column: 0).y
            newPos.x = touchPos.x
        } else {
            // 优先垂直移动,保持x坐标为初始网格列的x值
            newPos.x = convertGridToScene(row: 0, column: initialGridPos.column).x
            newPos.y = touchPos.y
        }
        
        // 限制在网格边界内
        newPos.x = max(0, min(newPos.x, CGFloat(gridColumns) * cellSize.width))
        newPos.y = max(0, min(newPos.y, CGFloat(gridRows) * cellSize.height))
        
        playerNode.position = newPos
    }
    
    // 触摸结束:吸附到合法网格交点
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let initialGridPos = initialGridPosition else { return }
        let nearestGridPos = convertSceneToGrid(playerNode.position)
        
        var finalGridPos: (row: Int, column: Int)
        // 确保只能移动到同列或同行的网格点
        if nearestGridPos.column == initialGridPos.column {
            finalGridPos = (nearestGridPos.row, initialGridPos.column)
        } else if nearestGridPos.row == initialGridPos.row {
            finalGridPos = (initialGridPos.row, nearestGridPos.column)
        } else {
            // 若拖动方向斜向,回到初始位置
            finalGridPos = initialGridPos
        }
        
        playerNode.position = convertGridToScene(row: finalGridPos.row, column: finalGridPos.column)
        initialGridPosition = nil
    }
    
    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        touchesEnded(touches, with: event)
    }
}

补充说明

  • 如果不需要拖动过程中的实时方向限制,也可以简化为touchesEnded时直接将节点吸附到最近的网格交点,但需额外判断是否为水平/垂直方向的移动;
  • 网格线绘制是可选的,用于开发调试,上线前可以移除;
  • 可以根据需求调整单元格大小、节点样式等参数。

内容的提问来源于stack exchange,提问作者BokyobT

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 17:37:14