在SpriteKit中连接TileMap上的Physicsbodies

时间:2017-12-05 02:16:57

标签: swift sprite-kit skphysicsbody sktilemapnode

我使用以下函数在SKTileMapNode上添加物理实体:

 static func addPhysicsBody(to tileMap: SKTileMapNode, and tileInfo: String){

    let tileSize = tileMap.tileSize
    let halfWidth = CGFloat(tileMap.numberOfColumns) / 2 * tileSize.width
    let halfHeight = CGFloat(tileMap.numberOfRows) / 2 * tileSize.height

    for row in 0..<tileMap.numberOfColumns{
        for column in 0..<tileMap.numberOfRows{
            let tileDefinition = tileMap.tileDefinition(atColumn: column, row: row)
            let isCorrectTile = tileDefinition?.userData?[tileInfo] as? Bool
            if isCorrectTile ?? false && tileInfo == "wall"{

                let x = CGFloat(column) * tileSize.width - halfWidth
                let y = CGFloat(row) * tileSize.height - halfHeight

                let tileNode = SKNode()
                tileNode.position = CGPoint(x: x, y: y)
                tileNode.physicsBody = SKPhysicsBody.init(rectangleOf: tileSize, center: CGPoint(x: tileSize.width / 2, y: tileSize.height / 2))
                tileNode.physicsBody!.isDynamic = false
                tileNode.physicsBody!.restitution = 0.0
                tileNode.physicsBody!.categoryBitMask = Constants.PhysicsCategories.wall
                tileNode.physicsBody!.collisionBitMask = Constants.PhysicsCategories.player | Constants.PhysicsCategories.npc | Constants.PhysicsCategories.enemy
                nodesForGraph.append(tileNode)
                tileMap.addChild(tileNode)
            }
        }
    }
}

但是,如果我使用它,我每个瓷砖都有一个物理实体。我想将物理实体连接到更大的实体以获得更好的性能。我知道这可以是init(bodies: [SKPhysicsBody])。但是我怎么能这样做呢? 我怎样才能找出哪个身体在另一个身体旁边分组呢? tileMap中的物理实体并不是彼此相邻的。有些是物理实体的大块,有些是单一的物理实体,旁边没有实体。所以我不能简单地将每个物理体放在一个阵列中并将它们分组 这是一张图片,展示了此刻的样子。

TileMap Physics

我希望解释清楚。如果没有,我会尝试更好地解释它。

之前有没有人这样做过,可以指出我正确的方向?我将不胜感激任何帮助。

编辑: 在我尝试之前:

static var bodies = [SKPhysicsBody]()
static func addPhysicsBody(to tileMap: SKTileMapNode, and tileInfo: String){

    let tileSize = tileMap.tileSize
    let halfWidth = CGFloat(tileMap.numberOfColumns) / 2 * tileSize.width
    let halfHeight = CGFloat(tileMap.numberOfRows) / 2 * tileSize.height

    for column in 0..<tileMap.numberOfColumns{
        for row in 0..<tileMap.numberOfRows{
            let tileDefinition = tileMap.tileDefinition(atColumn: column, row: row)
            let isCorrectTile = tileDefinition?.userData?[tileInfo] as? Bool
            if isCorrectTile ?? false && tileInfo == "wall"{

                let x = CGFloat(column) * tileSize.width - halfWidth
                let y = CGFloat(row) * tileSize.height - halfHeight

                let tileNode = SKNode()
                tileNode.position = CGPoint(x: x, y: y)
                tileNode.physicsBody = SKPhysicsBody.init(rectangleOf: tileSize, center: CGPoint(x: tileSize.width / 2, y: tileSize.height / 2))
                tileNode.physicsBody!.isDynamic = false
                tileNode.physicsBody!.restitution = 0.0
                tileNode.physicsBody!.categoryBitMask = Constants.PhysicsCategories.wall
                tileNode.physicsBody!.collisionBitMask = Constants.PhysicsCategories.player | Constants.PhysicsCategories.npc | Constants.PhysicsCategories.enemy
                //nodesForGraph.append(tileNode)
                bodies.append(tileNode.physicsBody!)
                tileMap.addChild(tileNode)
            }
        }
    }
    tileMap.physicsBody = SKPhysicsBody(bodies: bodies)
}

但是当我这样做时,物理实体完全搞砸了......

2 个答案:

答案 0 :(得分:0)

正如Knight0fDragon指出的那样,没有办法完全按照你的要求去做。不幸的是,SpriteKit中的磁贴地图还有很多不足之处。但是你可以尝试这种技术来减少物理体的数量。

想法#1 - 手动绘制您的物理实体

在编辑器中创建切片贴图。只需将瓷砖纹理绘制到地图上;不要给他们任何物理机构。然后继续在编辑器中工作,将Color Sprites(SKSpriteNodes)拖动到需要物理实体的地图部分上。对节点进行整形,以便为需要物理实体的区域创建最大的矩形。这最适用于大型平面表面,如墙壁,地板,天花板,平台,板条箱等。它很繁琐,但最终你的模拟中物理实体的数量远远少于自动将物体分配给所有瓷砖你在干嘛。

想法#2 - 不使用物理身体

这个想法可能需要更多的工作,但你可能完全避免使用物理机构。首先,在编辑器中创建切片贴图。分析您的地图以确定哪些标记标记了一个障碍,玩家不应该越过该障碍。将用户数据标识符分配给该类型的磁贴。对于不同类型的障碍,您需要不同类别的标识符,您可能还需要设计您的作品以适应这种方法。

一旦你的障碍图块得到充分识别,就编写代码来检查播放器精灵当前占用的图块的用户数据值,并相应地限制精灵的移动。例如,如果玩家输入标记上边界的标题,则您的移动代码将不允许玩家精灵向上移动。同样,如果玩家输入标记最左边界的图块,则您的移动代码不会让玩家向左移动。

您可以查看this related post我基本上建议相同的想法。不幸的是,SpriteKit的瓷砖地图没有解决这个问题的完美解决方案。

答案 1 :(得分:0)

我建议应用扫线算法将图块合并在一起。

您可以通过四个步骤进行操作;

  1. 遍历图块在SKTileMap中的位置。

  2. 找到彼此相邻的图块。

  3. 对于每组相邻的图块,收集:

    • 左下角坐标
    • 右上角坐标
  4. 绘制一个正方形,然后移至下一组图块,直到用完图块坐标为止。


第一步:创建一个包含所有位置节点的数组。

func tilephysics() {

    let tilesize = tileMap.tileSize
    let halfwidth = CGFloat(tileMap.numberOfColumns) / 2.0 * tilesize.width
    let halfheight =  CGFloat(tileMap.numberOfRows) / 2.0 * tilesize.height

    for col in 0 ..< tileMap.numberOfColumns {

        for row in 0 ..< tileMap.numberOfRows {

            if (tileMap.tileDefinition(atColumn: col, row: row)?.userData?.value(forKey: "ground") != nil) {

                let tileDef = tileMap.tileDefinition(atColumn: col, row: row)!
                let tile = SKSpriteNode()

                let x = round(CGFloat(col) * tilesize.width - halfwidth + (tilesize.width / 2))
                let y = round(CGFloat(row) * tilesize.height - halfheight + (tilesize.height / 2))

                tile.position = CGPoint(x: x, y: y)
                tile.size = CGSize(width: tileDef.size.width, height: tileDef.size.height)

                tileArray.append(tile)
                tilePositionArray.append(tile.position)
            }
        }
    }
    algorithm()
}

第二步和第三步::找到相邻的图块,收集两个角坐标,然后将它们添加到数组中:

var dir = [String]()
var pLoc = [CGPoint]()
var adT = [CGPoint]()

func algorithm(){

    let width = tileMap.tileSize.width
    let height = tileMap.tileSize.height
    let rWidth = 0.5 * width
    let rHeight = 0.5 * height

    var ti:Int = 0
    var ti2:Int = 0
    var id:Int = 0
    var dl:CGPoint = CGPoint(x: 0, y: 0)

    var tLE = [CGPoint]()
    var tRE = [CGPoint]()

    for t in tilePositionArray {

        if (ti-1 < 0) || (tilePositionArray[ti-1].y != tilePositionArray[ti].y - height) {

            dl = CGPoint(x: t.x - rWidth, y: t.y - rHeight)

        }

        if (ti+1 > tilePositionArray.count-1) {
            tLE.append(dl)

            tRE.append(CGPoint(x: t.x + rWidth, y: t.y + rHeight))

        } else if (tilePositionArray[ti+1].y != tilePositionArray[ti].y + height) {

            if let _ = tRE.first(where: {

                if $0 == CGPoint(x: t.x + rWidth - width, y: t.y + rHeight) {id = tRE.index(of: $0)!}

                return $0 == CGPoint(x: t.x + rWidth - width, y: t.y + rHeight)}) {

                if tLE[id].y == dl.y {

                    tRE[id] = CGPoint(x: t.x + rWidth, y: t.y + rHeight)

                } else {

                    tLE.append(dl)

                    tRE.append(CGPoint(x: t.x + rWidth, y: t.y + rHeight))

                }

            } else {

                tLE.append(dl)

                tRE.append(CGPoint(x: t.x + rWidth, y: t.y + rHeight))

            }

        }

        ti+=1

    }

第四步:绘制一个矩形并移至下一个形状:

for t in tLE {

        let size = CGSize(width: abs(t.x - tRE[ti2].x), height: abs(t.y - tRE[ti2].y))
        let loadnode = SKNode()

        loadnode.physicsBody = SKPhysicsBody(rectangleOf: size)
        loadnode.physicsBody?.isDynamic = false
        loadnode.physicsBody?.affectedByGravity = false
        loadnode.physicsBody?.restitution = 0

        loadnode.physicsBody?.categoryBitMask = 2

        loadnode.position.x = t.x + size.width / 2
        loadnode.position.y = t.y + size.height / 2

        scene.addChild(loadnode)

        ti2 += 1

    }
}

正确应用这些步骤,您应该看到您的图块已合并成大方块;像这样:

Screenshot without visuals for comparison

Screenshot without visuals showing the physicsbodies

我很高兴解决了这个问题。如果我帮助过您,请告诉我。 我只是最近才开始编码,并正在寻找新的挑战。如果您有任何挑战或可以为我做贡献的项目,请与我联系。