在Xcode 8中给SKTileMapNode的瓦片添加物理效果

7

我正在学习Swift,作为一个项目,我正在制作一个基于瓷砖的2D游戏,类似于超级马里奥,我的角色将在瓷砖上行走和跳跃。

Xcode和Sprite Kit的最新版本可以直接在Xcode中创建Tile Map地图。

在介绍新版Xcode和Sprite kit时,演示者展示了一个类似于我正在制作的游戏。

https://developer.apple.com/videos/play/wwdc2016/610/ (约20分钟左右)。

他提到给Tiles用户数据属性,我也这样做了,在代码中我们搜索所有具有该用户数据的瓷砖,并为它们分配一些物理属性,以便角色可以与其碰撞或交互(在我的情况下,我的角色不会落入或穿过瓷砖)。

因此,基本上的想法是给这些瓷砖添加物理Body,但这不能使用SKphysicsBody来完成。因此必须有另一种方法,由于我对Swift还不熟悉,所以我可能缺失了它。

如果有人知道这个问题,请帮忙解答,非常感谢。

如果问题不清楚,请告诉我,因为我也是Stack Overflow的新手。

4个回答

6

苹果员工Bobjt在这里表示,“正确的方法”是将用户数据添加到SKTileDefinition对象中,并使用该数据来识别和添加物理体到您的瓷砖。

因此,您可以通过编程或编辑器向瓷砖定义添加用户数据值,例如:

enter image description here

然后在代码中,您需要检查每个瓷砖定义,以查看它是否具有用户数据值。如果是这样,那么您需要计算瓷砖的位置,并在新节点上添加物理体,将其作为子节点附加到瓦片图中。以下是此操作的代码,Bobjt称之为“正确的方法”:

self.tileMap = self.childNode(withName: "Tile Map") as? SKTileMapNode  
guard let tileMap = self.tileMap else { fatalError("Missing tile map for the level") }  

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 {  
        let tileDefinition = tileMap.tileDefinition(atColumn: col, row: row)  
        let isEdgeTile = tileDefinition?.userData?["edgeTile"] as? Bool  
        if (isEdgeTile ?? false) {  
            let x = CGFloat(col) * tileSize.width - halfWidth  
            let y = CGFloat(row) * tileSize.height - halfHeight  
            let rect = CGRect(x: 0, y: 0, width: tileSize.width, height: tileSize.height)  
            let tileNode = SKShapeNode(rect: rect)  
            tileNode.position = CGPoint(x: x, y: y)  
            tileNode.physicsBody = SKPhysicsBody.init(rectangleOf: tileSize, center: CGPoint(x: tileSize.width / 2.0, y: tileSize.height / 2.0))  
            tileNode.physicsBody?.isDynamic = false  
            tileNode.physicsBody?.collisionBitMask = playerCollisionMask | wallCollisionMask  
            tileNode.physicsBody?.categoryBitMask = wallCollisionMask  
            tileMap.addChild(tileNode)  
        }  
    }  
}  

个人认为这种方法太繁琐了。我将尝试在我的游戏中采用不同的方法,如果有效果,我会在这里发布。我真正希望的是,苹果能增强瓦片地图API,使我们可以直接向单个瓦片添加物理体。也许在此过程中,他们可以优化引擎,使单个瓦片上的物理体自动合并形成更大、更优化的形状,以提高系统性能。
更新:我向苹果提出了request,希望能得到一些好处for whatever good might come of it

干得好!!!非常棒的研究,恭喜你从苹果内部人士那里获取了信息。我无法感谢你足够了。游戏工具包思维和设计方向缺乏透明度,这是令人难以置信的令人反感的。同样令人反感的是,缺乏有意义的努力来修复错误,并为这些事物的过程和范例带来任何连贯性、易用性和优雅性。对于一家拥有如此强大的力量和资源的公司来说,它们应该是一个不断令人尴尬的来源。 - Confused
祝你在为SK这个混乱的系统制定更多解决方案的道路上,能够成功克服各种困难和挑战。 - Confused
@Confused 感谢你的赞美和祝福!:) 如果我找到更好的解决方案,我一定会发布的。 - peacetype

2

或者,您可以对要赋予SKPhysicsBody的图块应用线扫描算法。

您可以按照以下四个步骤进行操作:

  1. 迭代SKTileMap中图块的位置。

  2. 找到相邻的图块。

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

    • 一个向下和向左的角落坐标
    • 一个向上和向右的角落坐标
  4. 绘制正方形,并继续处理下一组图块,直到用完所有图块坐标。

无可视化效果的屏幕截图以供比较

无可视化效果的屏幕截图显示物理体

查看我在类似帖子中如何实现此操作的答案。

“Original Answer”翻译成“最初的回答”。


2
“我不确定是否已经有一种可靠的方法来做到这一点...但是以下是两种尝试将物理应用于平铺地图的方式。”
“选项1:对于地图上每个位置的每个瓷砖,都应用SKNodes,并根据该瓷砖的内容和状态适当地应用physicsbody。”
“选项2:使用每个瓷砖的位置信息向SKTileMapNode添加一个physicsbodies数组,并相应地定位每个物体。”
“我想象中的是一个重力向下的马里奥风格平台游戏,需要为地面添加这种类型的物理体:”

enter image description here

从苹果WWDC中提取的有关瓷砖和物理的文字记录:
“你会注意到我在这里与瓷砖碰撞了。为了实现这一点,我们利用了可以放置在每个瓷砖上的自定义用户数据。在这里,我会向您展示我们的瓷砖集。选择其中一个变体,您会发现我们有一些用户数据在这里。我只设置了一个名为edgeTile的布尔值,并将其设置为1。因此,在代码中,我遍历我们平台演示中的瓦片地图,并寻找所有这些边缘瓦片。每当我找到一个时,我就创建一些物理数据,以允许玩家与之碰撞。由于它只是在我们的瓦片地图中,比如说我想越过这面大墙。当我运行游戏时,你会发现我的角色无法跳得高到越过它。他真的很想,因为那边的红色按钮看起来非常诱人。我真的想按下它。所以,既然我们只是从我们的瓦片和用户数据生成我们的物理数据,那么我们所能做的就是去这里擦除这些瓦片,再次构建并运行我们的游戏。瓦片现在消失了,我们现在可以通过那里移动。我们什么也没有改变,也没有改变代码。我们只是使用从瓦片地图中获取的数据来设置我们的瓷砖。就是这么简单。”
听起来很简单,但需要比我聪明得多的头脑才能弄清楚正在做什么以及是如何做的。

我打算在这里进行评论而不是回答。给单个瓷砖添加物理体并没有太多意义,你最终会添加更多不必要的物体,从而严重损害引擎。在这个例子中,您需要检查11个物体,其中4个由于其位置而是浪费的。我相信演讲者(我还没有看到)试图说的是使用用户数据在需要时构建自己的物理体,有点像这个人正在做的事情。https://forums.developer.apple.com/thread/50043 - Knight0fDragon
是的,如果是这种情况,一个人的身体就是所有人的身体。 - Knight0fDragon
1
很不可思议的是,作为SKTileMapNode的一部分,苹果没有设计和制作出这种物理处理机制。我认为。 - Confused
非常类似于它 - Knight0fDragon
1
聊得不错,伙计们 :) 有人来自苹果工作人员终于在@Knight0fDragon发布的链接中发表了意见。他们指出某些代码是“正确的方法”,所以我在这里发布了它。目前看来,通过搜索瓦片的用户数据将物理体添加到瓦片似乎是最好的选择。 - peacetype
显示剩余78条评论

1
我花了两天时间尝试不同的想法,但是没有比我在我的另一个回答中发布的更好的方法。如上所述,这是一位苹果员工推荐的方式,据我所知,这是让SpriteKit自动添加物理体到所有瓷砖的最有效的方法。我已经测试过它,它是可行的。(虽然我仍在期待苹果能够提供一种简单的方法来将物理体放在瓷砖上)。
但是还有其他考虑因素。如果由于场景中存在过多的物理体而导致性能问题,您可能希望尝试以下这些其他方法之一。但是,它们都比上述方法更耗时。唯一可以证明使用这些更费力的方法的原因是,如果由于性能问题需要减少场景中的物理体数量。否则,我认为上面提到的“自动”方法是我们最好的选择。
所以我不会在这里详细介绍,因为我认为自动选项是最好的。这些只是备用方法的想法,如果您的游戏需要非常节约系统资源。
备选方案#1-使用较少的物理体
在编辑器中创建您的平铺地图。然后继续在编辑器中工作,将颜色精灵(SKSpriteNodes)拖到需要物理体的地图部分上。为需要物理体的区域塑造节点,以制作最大的矩形。这对于像墙壁、地板、天花板、平台、板条箱等大而平坦的表面效果最佳。虽然这很繁琐,但最终你在模拟中使用的物理体比使用上述自动方法要少得多。
备选方案#2-不使用物理体
这个想法可能需要更多的工作,但是您可以潜在地避免完全使用物理体。首先,在编辑器中创建您的平铺地图。分析您的地图,以确定哪些平铺标记了障碍,玩家不应越过这些障碍。为该类型的平铺分配用户数据标识符。您需要不同类别的标识符来表示不同类型的障碍,并且您还可能需要设计您的艺术品以适应这种方法。
一旦确定了障碍瓷砖,编写代码检查玩家角色当前所处的瓷砖的用户数据值,并相应地限制角色的移动。例如,如果玩家进入标记上边界的标题,则移动代码将不允许玩家向上移动角色。同样,如果玩家进入标记最左边界的瓷砖,则移动代码将不允许玩家向左移动。

我认为苹果不会很快在瓷砖中添加物理效果。瓷砖的目的不是高度优化并保持前台功能的能量吗? - Moose
你关于苹果的看法可能是正确的,但我们可以抱有希望 :) 我只是在推测,但似乎添加物理体可以以不增加太多开销的方式完成。我知道SKPhysicsBody已经相当优化了。 - peacetype

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接