Connect Physicsbodies on TileMap in SpriteKit
I recommend applying a line sweep algorithm to merge the tiles together.
You can do this in four steps;
Iterate through the position of the tiles in your SKTileMap.
Find the tiles that are adjacent to one another.
For each group of adjacent tiles, collect:
- a down-left corner coordinate and
- an up-right corner coordinate.
Draw a square, and move on to the next group of tiles until you run out of tile coordinates.
The first step: creating an array containing all of your position nodes.
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()
}
The second and third step: finding adjacent tiles, collecting the two corner coordinates, and adding them to an array:
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
}
The fourth step: drawing a rectangle and moving on to the next shape:
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
}
}
Apply these steps correctly, and you should see that your tiles are merged together in large squares; like so:
Screenshot without visuals for comparison
Screenshot without visuals showing the physicsbodies
I had a lot of fun solving this problem. If I have helped you, let me know.
I only recently started coding and am looking for new challenges. Please reach out to me if you have challenges or projects I could possibly contribute to.
SpriteKit physics: How to make a player sprite follow the (sloped) ground
This has more to do with your game logic instead of your map properties. You need to have several "states" for your player. For example, if your player is idle you can set the CGVector to 0,0. This will stop the player from moving in any direction.
To give you some examples on movement. Let's say you want to make your object move right:
// move node's physics body to the right while leaving vertical movement as is
// 160 is just an example
myNode.physicsBody.velocity = CGVectorMake(160, self.physicsBody.velocity.dy);
// do not allow right movement to exceed 160
if(myNode.physicsBody.velocity.dx > 160)
myNode.physicsBody.velocity = CGVectorMake(160, self.physicsBody.velocity.dy);
To move left you inverse the dx value:
myNode.physicsBody.velocity = CGVectorMake(-160, self.physicsBody.velocity.dy);
if(myNode.physicsBody.velocity.dx < -160)
myNode.physicsBody.velocity = CGVectorMake(-160, self.physicsBody.velocity.dy);
Allow myNode to be affected by gravity and it should remain in contact with the ground as it moves down a slope.
Related Topics
iOS 14 Widget Detect System Theme Change
Sandbox Entitlement to Script Itunes via Nsapplescript
Call Function on App Termination in Swift
Protocol Variable Implementing Another Protocol
Swift-Animate Cashapelayer Stroke Color
Swift: Draw a Semi-Sphere in Mkmapview
Automatic Select a Date in Datepicker in Swift Language
Cllocationmanager and Tvos - Requestwheninuseauthorization() Not Prompting
In App Purchase in Skscene Not Working
Remove Cell When Button Pressed Inside Cell Customtableviewcell
How to Cut a Hole in a Sprite Image or Texture to Show What Is Behind It Using Spritekit in Swift
iOS Coredata Compatible with Both iOS 9 and iOS 10
Adding Data to a Specific UId in Firebase
Avoid Equatable and Hashable Boilerplate, Swift 4.2
Swift 3/iOS 10/Todayextension - Userdefaults Always Returns Nil