What’s New In SpriteKit on iOS 10: A Look At Tile Maps

Hi,

according to the forum post from Apple you are not supposed to use the filename of the tileset - file but rather
the name of the tile set in this file.

If this is true they could have at least provide some log output or some other indication of error that something is wrong.
Silently returning from the call with a “nil” isn’t really enlightening.

I will try it out - didn’t find any time yet to do so.

Cheers,

Frank

PS: I will Beta 4 a try - still have Beta 2 and 3 here. Beta 3 didn’t really work with Message Extensions - really weird. No breakpoints working either and no debug output using debugPrint or SwiftyBeaver.

Yes that’s right - it’s the name of the SKTileSet inside the file.

Don’t lose Beta 3. You may want to go back to it for Scene Editing

Hi,

yes - it works now using the name of the tileset and not the filename. At least in Beta 4 it works this way,
haven’t tried out Beta 3 though.

Cheers,

Frank

Hi,
How can I move the car from tile to tile with 4 directions?
(I included a Joystick)

Hi @aposos - I don’t understand your question. You have a joystick?

I have create a SKNode (looks like a D-Pad) to control the car.
If touched up the car should move up but from one tile to the other tile.
And if we touch continuously up it has to move smoothly from one tile to the next tile continuously.
How can I handle this?

@aposos - In the touch methods, check that you are touching the SKNode, and you could use the car’s velocity to travel in the direction indicated by your joystick. The tutorial has a target location and the car goes in that direction and stops. You could make the car go in that direction without stopping until the joystick is pressed again.

Another little question for you-
I just attempted the tutorial using Xcode 8 (gm) (thought it would be a fun morning’s entertainment) and am having a strange issue. The only tile I have managed to paint is the water tile - neither the plain grass nor the 8-way grass will paint. Just in case, I dl’d the finished project, and tried again. Similar result, but I was able to paint with ducks and gas cans – still no grass. Fresh Xcode, fresh system. Am I more likely to be doing something wrong, or might there an uncaught bug in the tile map editor? I assume the former, because you would think not being able to paint tiles would be rather obvious. I don’t have time to try it now, but my first thought is to replace all the grass assets just because those are the ones not painting.

Hi @pietro_pi - Not you - it seems to be a bug in the GM.

However, when I added a third tile to that set, and then created a new Tile Map Node in the Scene editor, I was able to paint the grass. (But not the third tile)

It looks like the last tile in the set is not being painted.

I’m sorry you didn’t get your fun morning’s entertainment as expected :smiley:

@pietro_pi - the bug is still in the release of Xcode 8. But all you have to do is, after adding your tiles to the tile set, add a new Single Tile Group. Delete this tile group after creating it, and your painting should now work.

Hi, I am having real trouble with creating a physics body for my map. I am iterating through the rows and columns and finding the tiles and from the tiles, creating a PB based on them:

let pb = SKPhysicsBody(texture: text, size: tile.size)
physicsBodyArray.append(pb)

Then once my loop is finished, I am adding the array to the tile map:

tileMap.physicsBody = SKPhysicsBody(bodies: physicsBodyArray)

The problem is that this just creates a physics body in the center of the tileMap. I can’t seem to find a way to add the position of all of these physics bodies.

Please can you point me in the right direction?

Many Thanks!

@b69ca - I have had success doing this using

SKPhysicsBody(rectangleOf: tile.size, center: center)

I haven’t tried with the texture, and of course the texture option doesn’t have a center parameter. My guess is that a physics body with lots of these textures would be inefficient.

In my above code, you can get the center value from:

let center = tileMapName.centerOfTile(atColumn: column, row: row)

@caroline - I am playing around with a TileSet for a game i’m creating and am following the methods you used in the tutorial but I continue to not be able to access the userdata for the tile. Do you know of any reasons why this could be? I double checked that my nodes were all loaded, and that the userData is defined on the tilemap.

override func update(_ currentTime: TimeInterval) {
let position = player.position
let column = platform.tileColumnIndex(fromPosition: position)
let row = platform.tileRowIndex(fromPosition: position)

    let objectTile = platform.tileDefinition(atColumn: column, row: row)      
    if let _ = objectTile?.userData?.value(forKey: "platform"){
        print("platform tile")
    }
    
    if let _ = objectTile?.userData?.value(forKey: "jumping"){
        print("jumping tile")
    }
}

@jzt5061- I’m sorry - nothing leaps out at me here. Did you try restarting Xcode? Or recreating the Tile Set to make sure that the user data actually did get saved properly?

When you print out the values for objectTile and objectTile?.userData? are either of them nil?

@caroline After deleting the tile map and restarting xcode it seems to be partially working now. Only problem is I have 2 different tiles and it seems to only recognize one of them. I tried 4 or 5 times to recreate the tilemap to fix this but every time it either reads platform tile for every tile, or jumping tile for every tile. Do you think I would have to make different tilemaps for the different tiles?

if let _ = objectTile?.userData?.value(forKey: “platform”){
print(“platform tile”)
}

    if let _ = objectTile?.userData?.value(forKey: "jumping"){
        print("jumping tile")
    }

@jzt5061- I don’t know why it’s not working for you. I just updated the tutorial to the newest version of Xcode, and it all works great for me. The only thing is that I have to restart Xcode before painting with the tiles.

I’m using two different userDatas in the tutorial - the duck and the gascan.

But if you really can’t get it working, you could use different tile map nodes. If you have one tile map node for each of your tiles, you can get away without creating userData, and just check if the tile is nil.

@caroline I believe i figured out why it was always displaying the same tile. It is due to the nature of how i set up the GameScene. In my game my player and tilemap are a child of a foreground node. The player is on a fixed y position and the tileMap moves downward(dy: -500) to give the effect of scrolling. Since it is always registering my position as (0, 0) or anything along that first row(and i had the same tiles on the same row) it appeared as if it was never updating even though in the simulator it looked like my player was traveling over different tiles.

What would be the best way for me to fix this issue? I would need the tileMap to know which position my player is over currently, not just in relation to the foreground node. I thought to make the player a child of the tileMap and add an updward velocity to the player(dy: 500) but my player still falls down with the tilemap. Any thoughts?

Heres my code:

class GameScene: SKScene, SKPhysicsContactDelegate {
var worldNode = SKNode()
var bgNode = SKNode()
var fgNode = SKNode()
var cameraNode: SKNode!
var background: SKNode!
var player: Character1Node!
var platform: SKTileMapNode!
var previousTranslateX: CGFloat = 0.0
var xVelocity: CGFloat = 10

override func didMove(to view: SKView) {
    setupNodes()
    self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
    cameraNode.position = CGPoint(x: size.width/2, y: size.height/2)
    
    //Panning for dragging player
    let pan = UIPanGestureRecognizer(target: self, action: #selector(GameScene.dragPlayer(sender:)))
    self.view?.addGestureRecognizer(pan)
    
}

func dragPlayer(sender: UIPanGestureRecognizer) {
    
    //retrieve pan movement along the x-axis of the view since the gesture began
    let currentTranslateX = sender.translation(in: view!).x
    
    //calculate translation since last measurement
    let translateX = currentTranslateX - previousTranslateX
    
    //move shape
    let adjustment:CGFloat = 2.0
    player.parent!.position = CGPoint(x: player.parent!.position.x + (translateX*adjustment), y:player.parent!.position.y)

    
    //reset previous measuremnt
    if sender.state == .ended{
        previousTranslateX = 0
    } else {
        previousTranslateX = currentTranslateX
    }
}



func setupNodes(){
    
    //Connecting variables to scene
    worldNode = childNode(withName: "World")!
    bgNode = worldNode.childNode(withName: "Background")!
    background = bgNode.childNode(withName: "Overlay")!.copy() as! SKNode
    fgNode = worldNode.childNode(withName: "Foreground")!
    cameraNode = worldNode.childNode(withName: "Camera")!

    
    //Platform physics
    platform = fgNode.childNode(withName: "Level1Map") as! SKTileMapNode
    let bodySize = CGSize(width: 150, height: 190)
    platform.physicsBody = SKPhysicsBody(rectangleOf: bodySize)
    platform.physicsBody!.isDynamic = true
    platform.physicsBody!.affectedByGravity = false
    platform.physicsBody!.linearDamping = 0
    platform.physicsBody!.categoryBitMask = PhysicsCategory.Platform
    platform.physicsBody!.collisionBitMask = PhysicsCategory.None
    platform.physicsBody!.velocity = CGVector(dx: 0, dy: -500)
    
    enumerateChildNodes(withName: "//*", using: {node, _ in
        if let customNode = node as? CustomNodeEvents {
            customNode.didMoveToScene()
        }
    })
    
    player = platform.childNode(withName: "//character1") as! Character1Node
    
}

override func update(_ currentTime: TimeInterval) {
    
    let position = player.parent!.position
    
    //Platform Tiles
    let column = platform.tileColumnIndex(fromPosition: position)
    let row = platform.tileRowIndex(fromPosition: position)
    let objectTile = platform.tileDefinition(atColumn: column, row: row)
    //print(objectTile)
    
    if let _ = objectTile?.userData?.value(forKey: "tile2"){
        print("jumping tile")
    }
    
    if let _ = objectTile?.userData?.value(forKey: "tile1"){
        print("platform tile")
    }
    
    print(player.parent!.position)


}

override func didSimulatePhysics() {
    cameraNode.position = CGPoint(x: player.parent!.position.x, y: player.parent!.position.y)
    self.centerOnNode(node: cameraNode)
}

func centerOnNode(node: SKNode) {
    let cameraPositionInScene: CGPoint = node.scene!.convert(node.position, from: worldNode)
    node.parent!.run(SKAction.move(to: CGPoint(x:node.parent!.position.x - cameraPositionInScene.x, y:node.parent!.position.y - cameraPositionInScene.y), duration: 2.0))

}

@jzt5061- I notice you’re using a camera.

Why don’t you leave the tile map as static and have the player move? This is simpler to envisage.

But now the player will go off the screen. So you constrain the camera to follow the player, and the player will always appear to be in the center of the screen.

Then you can find out the tile from the player’s position.

The only node position you would change would be the player. The camera would have constraints and the tile map node would be stationary.

1 Like

@caroline that makes a lot more sense! Thanks for the advice. I know this might be asking too much but can you help me figure out how to have the camera follow the player but not go further than the edge of the scene? I’m currently using the below code to have the camera follow the player, but I can’t seem to figure out how to restrict the camera movement to the edge of the scene.

override func didSimulatePhysics() {
    cameraNode.position = CGPoint(x: player.parent!.position.x, y: player.parent!.position.y)
    self.centerOnNode(node: cameraNode)
}

func centerOnNode(node: SKNode) {
    let cameraPositionInScene: CGPoint = node.scene!.convert(node.position, from: worldNode)
    node.parent!.run(SKAction.move(to: CGPoint(x:node.parent!.position.x - cameraPositionInScene.x, y:node.parent!.position.y - cameraPositionInScene.y), duration: 2.0))
    
}

You can use SKConstraints for constraining the camera.

Neil’s answered this exact question here:
http://archive.raywenderlich.com/forums/viewtopic.php?f=54&t=24078#p98811

1 Like