I am learning Swift, and as a project I am working on a tile based 2D game similar to super mario where my character will walk and jump on tiles.
The latest version of Xcode and Sprite Kit give the ability to create a Tile Map directly in Xcode.
In the presentation of the new Xcode and Sprite kit, the guy demonstrates a game similar to what i am working on.
https://developer.apple.com/videos/play/wwdc2016/610/ (around the 20th minute).
He mentions giving Tiles user data properties which i did, and in code we search through all the tiles which have that user data and give them some physics properties so that the character can collide or interact with them (in my case, my character not falling or walking through the tiles).
so basically, the idea is giving those tiles a physics Body, but this can't be done using SKphysicsBody. So there must be another way, and since i am new to Swift i am missing it.
if anyone knows this, i would very much appreciate the help.
If the question is unclear let me know because i am also new to stack overflow.
Apple staff member Bobjt says here that "the right approach" is to add user data to your SKTileDefinition
objects and use that to identify and add physics bodies to your tiles.
So you would add a user data value to a tile definition either programmatically or in the editor, like so:
Then in code you would check each tile definition to see if it has the user data value. If so, then you need to calculate the tile's position and add a physics body on a new node, parenting it to your tile map. Here is the code for this which Bobjt referred to as "the correct approach":
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)
}
}
}
Personally, I think this approach is too fussy. I'm going to try a different approach in my game and if it works I'll post it here. What I'd really like is for Apple to enhance the tile map API so that we can add physics bodies directly to individual tiles. Maybe in the process they could optimize the engine so that physics bodies on individual tiles would be automatically merged to form larger, more optimal shapes in order to improve system performance.
Update: I filed a request with Apple about this issue, for whatever good might come of it.
I'm not sure there's a surefire way to do this yet... but here's two ways to think about how to try apply physics to a tilemap.
Option 1: Apply SKNodes to each positions of each tile on your map, and apply an appropriate physicsbody to that SKNode based on the content and state of that tile.
Option 2: Use the position information of each tile to add an array of physicsbodies to the SKTileMapNode, and position each of them accordingly.
I'm imagining a gravity down, Mario style platformer, with this kind of terrain in need of physics bodies for the ground:
And you'll note that I'm colliding with the tiles here.
And I achieve this by leveraging custom user data that we can put on each of our tiles.
Here, I'll show you in our tile set.
Select one of the variants here.
And you can see that we have some user data over here.
And I just have a value called edgeTile which is a Boolean, and I set to 1.
So, in code, I'm going through the tile map in our platform demo here, and I'm looking for all of these edge tiles.
And whenever I find one, I create some physics data to allow the player to collide with it.
And since it is just in our tile map here, say I wanted to get over this large wall here.
When I run the game, you'll see that my guy can't actually jump high enough to get over it.
And he really wants to because that red button over there looks really tempting.
I really want to push that thing.
So, since we're just generating our physics data from our tiles and our user data, all we can do is just going here and erase these tiles and build and run our game again.
The tiles are now gone and we can now move through there.
And we didn't have to change code or anything.
We just used the data that we pulled from the tile map to set up our tile.
It's that simple.
Makes it sound very simple, but takes a far greater mind than mine to figure out what is being done, and how it's being done.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With