在 SpriteKit 中的 TileMap 上连接 Physicsbodies
Connect Physicsbodies on TileMap in SpriteKit
我使用以下函数将物理体附加到 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 中的物理实体并不都彼此相邻。有些是物理体的大块,有些是单个物理体,旁边没有任何物体。所以我不能简单地将每个物理体放在一个数组中并将它们分组。
这是一张图片,显示了它目前的样子。
希望解释清楚。如果没有,我将尝试更好地解释它。
有没有人以前做过这个并且可以指出正确的方向?我将不胜感激任何帮助。
编辑:
在我尝试之前:
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)
}
但是当我这样做的时候,物理体完全搞砸了..
正如 Knight0fDragon 指出的那样,无法完全按照您的要求进行操作。不幸的是,SpriteKit 中的瓦片地图还有很多不足之处。但是你可以试试这个技巧来减少物理体的数量。
想法 #1 - 手动绘制物理形体
在编辑器中创建您的瓷砖地图。只需将瓷砖纹理绘制到地图上;不要为它们分配任何物理物体。然后继续在编辑器中工作,将 Color Sprites (SKSpriteNodes
) 拖到需要物理 body 的地图部分。塑造节点,使需要物理体的区域尽可能成为最大的矩形。这最适合大型平坦表面,如墙壁、地板、天花板、平台、板条箱等。这很乏味,但与您自动将物体分配给所有瓷砖相比,您最终在模拟中使用的物理物体要少得多。
想法 #2 - 不使用物理物体
这个想法可能需要更多的工作,但您可以完全避免使用物理体。首先,在编辑器中创建您的瓦片地图。分析您的地图以确定哪些方块标记了障碍,玩家不应越过该障碍。为该类型的磁贴分配一个用户数据标识符。对于不同类型的障碍,您需要不同类别的标识符,您可能还需要设计您的艺术作品以适应这种方法。
一旦你的屏障瓦片被充分识别,编写代码来检查玩家精灵当前占据的瓦片的用户数据值,并相应地限制精灵的移动。例如,如果玩家输入的标题标记了上限,您的移动代码将不允许玩家精灵向上移动。同样,如果玩家进入标记最左边边界的方块,您的移动代码将不会让玩家向左移动。
您可以查看 this related post,其中我基本上提出了相同的想法。不幸的是,SpriteKit 的瓦片地图并没有完美解决这个问题。
我建议应用线扫描算法将图块合并在一起。
您可以分四步完成;
遍历您的 SKTileMap 中图块的位置。
找到彼此相邻的图块。
对于每组相邻的图块,收集:
- a down-left角坐标和
- 一个up-right角坐标.
画一个正方形,然后移动到下一组瓷砖,直到您 运行 超出瓷砖坐标。
第一步:创建一个包含所有位置节点的数组。
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 [=11=] == CGPoint(x: t.x + rWidth - width, y: t.y + rHeight) {id = tRE.index(of: [=11=])!}
return [=11=] == 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
解决这个问题让我很开心。如果我帮助了你,请告诉我。
我最近才开始编码,正在寻找新的挑战。如果您遇到挑战或我可能参与的项目,请与我联系。
我使用以下函数将物理体附加到 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 中的物理实体并不都彼此相邻。有些是物理体的大块,有些是单个物理体,旁边没有任何物体。所以我不能简单地将每个物理体放在一个数组中并将它们分组。
这是一张图片,显示了它目前的样子。
希望解释清楚。如果没有,我将尝试更好地解释它。
有没有人以前做过这个并且可以指出正确的方向?我将不胜感激任何帮助。
编辑: 在我尝试之前:
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)
}
但是当我这样做的时候,物理体完全搞砸了..
正如 Knight0fDragon 指出的那样,无法完全按照您的要求进行操作。不幸的是,SpriteKit 中的瓦片地图还有很多不足之处。但是你可以试试这个技巧来减少物理体的数量。
想法 #1 - 手动绘制物理形体
在编辑器中创建您的瓷砖地图。只需将瓷砖纹理绘制到地图上;不要为它们分配任何物理物体。然后继续在编辑器中工作,将 Color Sprites (SKSpriteNodes
) 拖到需要物理 body 的地图部分。塑造节点,使需要物理体的区域尽可能成为最大的矩形。这最适合大型平坦表面,如墙壁、地板、天花板、平台、板条箱等。这很乏味,但与您自动将物体分配给所有瓷砖相比,您最终在模拟中使用的物理物体要少得多。
想法 #2 - 不使用物理物体
这个想法可能需要更多的工作,但您可以完全避免使用物理体。首先,在编辑器中创建您的瓦片地图。分析您的地图以确定哪些方块标记了障碍,玩家不应越过该障碍。为该类型的磁贴分配一个用户数据标识符。对于不同类型的障碍,您需要不同类别的标识符,您可能还需要设计您的艺术作品以适应这种方法。
一旦你的屏障瓦片被充分识别,编写代码来检查玩家精灵当前占据的瓦片的用户数据值,并相应地限制精灵的移动。例如,如果玩家输入的标题标记了上限,您的移动代码将不允许玩家精灵向上移动。同样,如果玩家进入标记最左边边界的方块,您的移动代码将不会让玩家向左移动。
您可以查看 this related post,其中我基本上提出了相同的想法。不幸的是,SpriteKit 的瓦片地图并没有完美解决这个问题。
我建议应用线扫描算法将图块合并在一起。
您可以分四步完成;
遍历您的 SKTileMap 中图块的位置。
找到彼此相邻的图块。
对于每组相邻的图块,收集:
- a down-left角坐标和
- 一个up-right角坐标.
画一个正方形,然后移动到下一组瓷砖,直到您 运行 超出瓷砖坐标。
第一步:创建一个包含所有位置节点的数组。
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 [=11=] == CGPoint(x: t.x + rWidth - width, y: t.y + rHeight) {id = tRE.index(of: [=11=])!}
return [=11=] == 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
解决这个问题让我很开心。如果我帮助了你,请告诉我。 我最近才开始编码,正在寻找新的挑战。如果您遇到挑战或我可能参与的项目,请与我联系。