0

I'm working on a 2-d Platformer style game. I'm very new to IOS and Swift development. I'm attempting to use a button (a different node) to move my character across the screen from left to right. It works fine until I reach the halfway point then it speeds up dramatically and letting go of the touch of the button doesn't always stop it. Sometimes it requires another touch. Also the background doesn't appear to keep up with the player. Once the player hits mid screen the background should shift as the player continues moving.

I've pieced what I've done together from multiple tutorials SO examples but I'm stuck on this point.

class StoryModeScene: SKScene, SKPhysicsContactDelegate {

var tileMap = JSTileMap(named: "legend1Level1.tmx")
var tileSize:CGSize!
var xPointsToMovePerSecond:CGFloat = 0
var rightMoveButton = SKSpriteNode(imageNamed: "right-move")
var leftMoveButton = SKSpriteNode(imageNamed: "left-move")
var jumpButton = SKSpriteNode(imageNamed: "a-button")
var fireButton = SKSpriteNode(imageNamed: "b-button")
var forwardMarch:Bool = false
var mightAsWellJump:Bool = false
var onGround:Bool = true

//CREATE THE PLAYER ATLAS FOR ANIMATION
let playerAtlas = SKTextureAtlas(named:"legend1")
var playerSprites = Array<Any>()
var player = SKSpriteNode(imageNamed: "legend1")
var repeatActionPlayer = SKAction()

override func didMove(to view: SKView) {
    /* Setup your scene here */
    setupScene()
    addPlayer()

    //PREPARE TO ANIMATE THE PLAYER AND REPEAT THE ANIMATION FOREVER
    let animatedPlayer = SKAction.animate(with: self.playerSprites as! [SKTexture], timePerFrame: 0.1)
    self.repeatActionPlayer = SKAction.repeatForever(animatedPlayer)

    leftMoveButton.position.x = 64
    leftMoveButton.position.y = 64
    leftMoveButton.name = "moveLeft"
    addChild(leftMoveButton)

    rightMoveButton.position.x = 124
    rightMoveButton.position.y = 64
    rightMoveButton.name = "moveRight"
    addChild(rightMoveButton)

    jumpButton.position.x = 771
    jumpButton.position.y = 64
    jumpButton.name = "jumpButton"
    addChild(jumpButton)

    fireButton.position.x = 836
    fireButton.position.y = 64
    fireButton.name = "fireButton"
    addChild(fireButton)

}

override func update(_ currentTime: TimeInterval) {
    if (forwardMarch) {
        //let moveAction = SKAction.moveBy(x: 3, y: 0, duration: 1)
        //let repeatForEver = SKAction.repeatForever(moveAction)
        //let seq = SKAction.sequence([moveAction, repeatForEver])

        //run the action on your ship
        //player.run(seq)
        player.position.x = player.position.x + 3
        setViewpointCenter(player.position)
    }

    if (mightAsWellJump) {
        let jumpForce = CGPoint(x: 0.0, y: 310.0)
        let jumpCutoff: Float = 150.0

        if mightAsWellJump && onGround {
            player.physicsBody!.velocity = CGVector(dx: player.physicsBody!.velocity.dx + jumpForce.x, dy: player.physicsBody!.velocity.dy + jumpForce.y)
            onGround = false
        } else if !mightAsWellJump && player.physicsBody!.velocity.dy > CGFloat(jumpCutoff) {
            player.physicsBody!.velocity = CGVector(dx: player.physicsBody!.velocity.dx, dy: CGFloat(jumpCutoff))
        }


        player.position = CGPoint(x: player.position.x, y: player.position.y + 5);
    }
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    for touch in (touches) {
        let positionInScene = touch.location(in: self)
        let touchedNode = self.atPoint(positionInScene)
        if let name = touchedNode.name {
            if name == "jumpButton" {
                mightAsWellJump = true
                player.texture = SKTexture(imageNamed: "legend1_jump")
            }
            if name == "moveRight" {
                forwardMarch = true
                self.player.run(repeatActionPlayer)
            }
        }
    }
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    let touch = touches.first!
    if rightMoveButton.contains(touch.location(in: self)) {
        forwardMarch = false
        player.removeAllActions()
        player.texture = SKTexture(imageNamed: "legend1")
    }
    if jumpButton.contains(touch.location(in: self)) {
        mightAsWellJump = false
        player.removeAllActions()
        player.texture = SKTexture(imageNamed: "legend1")
    }
}

func setViewpointCenter(_ position: CGPoint) {
    var x = max(position.x, size.width / 2)
    var y = max(position.y, size.height / 2)
    x = min(x, (tileMap!.mapSize.width * tileMap!.tileSize.width) - size.width / 2)
    y = min(y, (tileMap!.mapSize.height * tileMap!.tileSize.height) - size.height / 2)
    let actualPosition = CGPoint(x: CGFloat(x), y: CGFloat(y))
    let centerOfView = CGPoint(x: size.width / 2, y: size.height / 2)
    let viewPoint = CGPoint(x: (centerOfView.x - actualPosition.x) * 3, y: centerOfView.y - actualPosition.y)
    tileMap!.position = viewPoint

}


func setupScene() {
    playerSprites.append(playerAtlas.textureNamed("legend1_0"))
    playerSprites.append(playerAtlas.textureNamed("legend1_1"))
    playerSprites.append(playerAtlas.textureNamed("legend1_2"))
    playerSprites.append(playerAtlas.textureNamed("legend1_3"))
    playerSprites.append(playerAtlas.textureNamed("legend1_4"))
    playerSprites.append(playerAtlas.textureNamed("legend1_5"))

    backgroundColor = UIColor(red: 165.0/255.0, green: 216.0/255.0, blue: 255.0/255.0, alpha: 1.0)

    physicsWorld.gravity = CGVector(dx: 0, dy: -9.8)

    anchorPoint = CGPoint(x: 0, y: 0)
    position = CGPoint(x: 0, y: 0)

    let point = tileMap!.calculateAccumulatedFrame()
    print (point)
    tileMap!.position = CGPoint(x: 0, y: 0)
    addChild(tileMap!)

    addFloor()
}

func addFloor() {
    for a in 0..<Int(tileMap!.mapSize.width) {
        for b in 0..<Int(tileMap!.mapSize.height) {
            let layerInfo:TMXLayerInfo = tileMap!.layers.firstObject as! TMXLayerInfo
            let point = CGPoint(x: a, y: b)
            let walls = tileMap!.layerNamed("Walls")
            let wallInfo:TMXLayerInfo = walls!.layerInfo
            let wallGIDs = wallInfo.layer.tileGid(at: wallInfo.layer.point(forCoord: point))

            if wallGIDs > 0 {
                //print (wallGIDs)
                //let node = walls
                let node = wallInfo.layer.tile(atCoord: point)
                node!.physicsBody = SKPhysicsBody(rectangleOf: node!.size)
                node!.physicsBody?.isDynamic = false
            }
        }
    }
}

func addPlayer() {
    tileSize = tileMap?.tileSize
    player.position = CGPoint(x: tileSize.width + player.size.width/2, y: tileSize.height + player.size.height*8)
    let rect = CGRect(origin: CGPoint(x: 0,y :0), size: CGSize(width: 50, height: 95))
    player.physicsBody = SKPhysicsBody(rectangleOf: rect.size)
    player.physicsBody!.velocity = CGVector(dx: 0.0, dy: 0.0)
    player.physicsBody!.isDynamic = true
    player.physicsBody!.restitution = 0
    player.physicsBody!.allowsRotation = false
    player.physicsBody!.friction = 1.0
    addChild(player)
}

}

Holding the rightMoveButton should move at a consistent pace to the right. As the player gets to mid screen the view point of the background should shift until it reaches the end of the background at which point the player can move off screen and complete the level. Releasing the button should allow the player to stop.

Muttface
  • 301
  • 4
  • 13

2 Answers2

1

You can move the scene as the character moves by creating an SKCameraNode. You then can choose when to move the camera to create the right affect. Make sure to set the camera as your game’s camera. You can learn about cameras here. As for the speeding up, I assume it has something to do with your physicsBody. If you’re interested about player movement, you could look here or here. There are many other great videos and sites online, which you could find by searching “SpriteKit player movement.”

Eli Front
  • 695
  • 1
  • 8
  • 28
  • Thank you for pointing me in the right direction with the SKCamera node. That was the answer to my problem. Once I get it completely sorted I'll post my code for anyone who runs into a similar question. The speeding up was actually an optical illusion because I had sped up my background moving because I thought it was an issue. – Muttface Oct 24 '19 at 04:30
0

Eli Front's answer pointed me in the right direction. I still have some issues with this code but the answer to my question was using SKCamera node as Eli pointed out. I just wanted to post the code that works so if anyone has a similar question there's a code example as far as camera moving with the player. The speed issue was essentially an optical illusion with the background moving too fast.

Note that the controls are children of the camera. That is so they move with the camera as well otherwise as your node moves the controls would move off screen.

import UIKit
import SpriteKit
import GameKit

class StoryModeScene: SKScene, SKPhysicsContactDelegate {

    var tileMap = JSTileMap(named: "legend1Level1.tmx")
    var tileSize:CGSize!
    var xPointsToMovePerSecond:CGFloat = 0
    var rightMoveButton = SKSpriteNode(imageNamed: "right-move")
    var leftMoveButton = SKSpriteNode(imageNamed: "left-move")
    var jumpButton = SKSpriteNode(imageNamed: "a-button")
    var fireButton = SKSpriteNode(imageNamed: "b-button")
    var forwardMarch:Bool = false
    var mightAsWellJump:Bool = false
    var onGround:Bool = true

    //CREATE THE Player ATLAS FOR ANIMATION
    let playerAtlas = SKTextureAtlas(named:"legend1")
    var playerSprites = Array<Any>()
    var player = SKSpriteNode(imageNamed: "legend1")
    var repeatActionPlayer = SKAction()
    let cam = SKCameraNode()

    var previousUpdateTime: TimeInterval = 0

    override func didMove(to view: SKView) {
        // SETUP CAMERA
        self.camera = cam
        scene?.addChild(cam)
        cam.position.x = 448
        cam.position.y = 212

        setupScene()
        addPlayer()

        //PREPARE TO ANIMATE THE PLAYER AND REPEAT THE ANIMATION FOREVER
        let animatedPlayer = SKAction.animate(with: self.playerSprites as! [SKTexture], timePerFrame: 0.1)
        self.repeatActionPlayer = SKAction.repeatForever(animatedPlayer)

        // SETUP CONTROLS
        leftMoveButton.position.x = -338
        leftMoveButton.position.y = -112
        leftMoveButton.name = "moveLeft"
        leftMoveButton.zPosition = 5
        cam.addChild(leftMoveButton)

        rightMoveButton.position.x = -278
        rightMoveButton.position.y = -112
        rightMoveButton.name = "moveRight"
        cam.addChild(rightMoveButton)

        jumpButton.position.x = 278
        jumpButton.position.y = -112
        jumpButton.name = "jumpButton"
        jumpButton.zPosition = 5
        cam.addChild(jumpButton)

        fireButton.position.x = 338
        fireButton.position.y = -112
        fireButton.name = "fireButton"
        cam.addChild(fireButton)

    }

    override func update(_ currentTime: TimeInterval) {

        if (forwardMarch) {
            if player.position.x > 448 && player.position.x < 1800 {
                cam.position.x = player.position.x
            } else if player.position.x >= 1800 {
                cam.position.x = 1800
            }
            setViewpointCenter(player.position)
            player.position.x = player.position.x + 3
        }

        if (mightAsWellJump) {
            let jumpForce = CGPoint(x: 0.0, y: 310.0)
            let jumpCutoff: Float = 150.0

            if mightAsWellJump && onGround {
                player.physicsBody!.velocity = CGVector(dx: player.physicsBody!.velocity.dx + jumpForce.x, dy: player.physicsBody!.velocity.dy + jumpForce.y)
                onGround = false
            } else if !mightAsWellJump && player.physicsBody!.velocity.dy > CGFloat(jumpCutoff) {
                player.physicsBody!.velocity = CGVector(dx: player.physicsBody!.velocity.dx, dy: CGFloat(jumpCutoff))
            }

            player.position = CGPoint(x: player.position.x, y: player.position.y + 5);
        }
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        for touch in (touches) {
            let positionInScene = touch.location(in: self)
            let touchedNode = self.atPoint(positionInScene)
            if let name = touchedNode.name {
                if name == "jumpButton" {
                    mightAsWellJump = true
                    player.texture = SKTexture(imageNamed: "legend1_jump")
                }
                if name == "moveRight" {
                    player.physicsBody!.velocity.dy = 0.0
                    forwardMarch = true
                    self.player.run(repeatActionPlayer)
                }
            }
        }
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        for touch in (touches) {
            let positionInScene = touch.location(in: self)
            let touchedNode = self.atPoint(positionInScene)
            if let name = touchedNode.name {
                if name == "jumpButton" {
                    mightAsWellJump = false
                    player.removeAllActions()
                    player.texture = SKTexture(imageNamed: "legend1")
                    player.physicsBody!.velocity = CGVector(dx: 0.0, dy: 0.0)
                }
                if name == "moveRight" {
                    player.removeAllActions()
                    player.texture = SKTexture(imageNamed: "legend1")
                    forwardMarch = false
                }
            }
        }
    }

    func setViewpointCenter(_ position: CGPoint) {
        var x = max(position.x, size.width / 2)
        var y = max(position.y, size.height / 2)
        x = min(x, (tileMap!.mapSize.width * tileMap!.tileSize.width) - size.width / 2)
        y = min(y, (tileMap!.mapSize.height * tileMap!.tileSize.height) - size.height / 2)
        let actualPosition = CGPoint(x: CGFloat(x), y: CGFloat(y))
        let centerOfView = CGPoint(x: size.width / 2, y: size.height / 2)
        let viewPoint = CGPoint(x: centerOfView.x - actualPosition.x, y: centerOfView.y - actualPosition.y)
        if (actualPosition.x > 1800) {
            tileMap!.position = tileMap!.position
        } else {
            tileMap!.position = viewPoint
        }
    }

    func setupScene() {
        // ADD PLAYER SPRITES @TODO MAKE THIS DYNAMIC
        playerSprites.append(playerAtlas.textureNamed("legend1_0"))
        playerSprites.append(playerAtlas.textureNamed("legend1_1"))
        playerSprites.append(playerAtlas.textureNamed("legend1_2"))
        playerSprites.append(playerAtlas.textureNamed("legend1_3"))
        playerSprites.append(playerAtlas.textureNamed("legend1_4"))
        playerSprites.append(playerAtlas.textureNamed("legend1_5"))

        backgroundColor = UIColor(red: 165.0/255.0, green: 216.0/255.0, blue: 255.0/255.0, alpha: 1.0)
        anchorPoint = CGPoint(x: 0, y: 0)
        position = CGPoint(x: 0, y: 0)

        // let point = tileMap!.calculateAccumulatedFrame()
        tileMap!.position = CGPoint(x: 0, y: 0)
        addChild(tileMap!)

        addFloor()
    }

    func addFloor() {
        for a in 0..<Int(tileMap!.mapSize.width) {
            for b in 0..<Int(tileMap!.mapSize.height) {
                // let layerInfo:TMXLayerInfo = tileMap!.layers.firstObject as! TMXLayerInfo
                let point = CGPoint(x: a, y: b)
                let walls = tileMap!.layerNamed("Walls")
                let wallInfo:TMXLayerInfo = walls!.layerInfo
                let wallGIDs = wallInfo.layer.tileGid(at: wallInfo.layer.point(forCoord: point))

                if wallGIDs > 0 {
                    let node = wallInfo.layer.tile(atCoord: point)
                    node!.physicsBody = SKPhysicsBody(rectangleOf: node!.size)
                    node!.physicsBody?.isDynamic = false
                }
            }
        }
    }

    func addPlayer() {
        tileSize = tileMap?.tileSize
        player.position = CGPoint(x: tileSize.width + player.size.width/2, y: tileSize.height + player.size.height*8)
        let rect = CGRect(origin: CGPoint(x: 0,y :0), size: CGSize(width: 50, height: 95))
        player.physicsBody = SKPhysicsBody(rectangleOf: rect.size)
        player.physicsBody!.velocity = CGVector(dx: 0.0, dy: 0.0)
        player.physicsBody!.isDynamic = true
        player.physicsBody!.restitution = 0
        player.physicsBody!.allowsRotation = false
        player.physicsBody!.friction = 1.0
        player.physicsBody!.mass = 1.0
        addChild(player)
    }

}

As I mentioned I still have some issues like the player node floating/flying when I jump/run at the same time. The 1st jump is the correct height and all subsequent jumps are smaller etc but I'm working on figuring those out.

Muttface
  • 301
  • 4
  • 13
  • As a small note I'm basing much of my work off this series: https://www.raywenderlich.com/2554-sprite-kit-tutorial-how-to-make-a-platform-game-like-super-mario-brothers-part-1 and some of it off of this SO question/answer: https://stackoverflow.com/questions/25995131/how-to-use-jstilemap-in-swift The Ray Wenderlich tutorial is old and in Objective-C which makes it difficult but I'm slowly figuring it out. – Muttface Oct 24 '19 at 14:05