0
votes

I have made a game where the basic idea is that a player sprite has to either collide with or avoid falling enemy sprites depending on the letter assigned to the enemy. The problem is that when my player makes contact with a sprite, the surrounding enemies skip forward significantly. This makes the game too hard to play because there isn't enough time for the player to move out of the way of oncoming enemy sprites. This happens on the device and the simulator.

To see what I mean, look at this brief video. You can see the lag at 00:06 and 00:013: https://www.dropbox.com/s/8pp66baxc9uhy26/SimulatorScreenSnapz002.mov?dl=0

Here is my code for player and enemy contact:

class GameplaySceneClass: SKScene, SKPhysicsContactDelegate {

private var player:Player?

private var center = CGFloat()

private var canMove = false, moveLeft = false

private var itemController = ItemController()

private var scoreLabel: SKLabelNode?

private var wordLabel: SKLabelNode?

private var score = 0

private var theWord = ""

private var vowelPressed = false

let correct = SoundSFX("correct.wav")

let wrong = SoundSFX("nomatch.wav")

let explosion = SoundSFX("explosion.wav")

var arrayOfStrings: [String]?

var theCheckedWord = ""

var currentCount = 0

var itemsArray = [String]()

var partialExists = false

var characterScore = 0

var onePointLetters = [12, 14, 18, 19, 20]

var twoPointLetters = [4, 7]

var threePointLetters = [2, 3, 13, 16]

var fourPointLetters = [6, 8, 22, 23, 25]

var fivePointLetters = [11]

var eightPointLetters = [10, 24]

var tenPointLetters = [17, 26]

var letter = 0

var scoreArray = [Int]()

var matchingTerms = [String]()

var gravity:CGFloat = -0.35

var result = CGSize()

let synth = AVSpeechSynthesizer()
var myUtterance = AVSpeechUtterance(string:"")



override func didMove(to view: SKView) {

    SoundEngine.shared.backgroundMusicVolume = 1.0

    SoundEngine.shared.playBackgroundMusic("Jungle Audio-1.m4a", loop: true)

    initializeGame()



}

override func update(_ currentTime: TimeInterval) {
    managePlayer()
}


override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

    for touch in touches {

        let location = touch.location (in: self)


        if atPoint(location).name == "LetterA" {

            print ("Letter A pressed")
            vowelPressed = true
            theWord = theWord + "A"
            wordLabel?.text = theWord
            if theWord.characters.count >= 3 {
                checkWord()

            }
             vowelPressed = false

        }

        else if atPoint(location).name == "LetterE" {

        print ("Letter E pressed")
            vowelPressed = true
            theWord = theWord + "E"
            wordLabel?.text = theWord
            if theWord.characters.count >= 3 {
                checkWord()

            }
            vowelPressed = false

    }

        else if atPoint(location).name == "LetterI" {

            print ("Letter I pressed")
            vowelPressed = true
            theWord = theWord + "I"
            wordLabel?.text = theWord
            if theWord.characters.count >= 3 {
                checkWord()

            }

            vowelPressed = false


        }

        else if atPoint(location).name == "LetterO" {

            print ("Letter O pressed")
            vowelPressed = true
            theWord = theWord + "O"
            wordLabel?.text = theWord
            if theWord.characters.count >= 3 {
            checkWord()

            }

            vowelPressed = false

        }

        else if atPoint(location).name == "LetterU" {

            print ("Letter U pressed")
            vowelPressed = true
            theWord = theWord + "U"
            wordLabel?.text = theWord
            if theWord.characters.count >= 3 {
                checkWord()

            }

            vowelPressed = false


        }

        else if atPoint(location).name == "Pause"  {

            showPauseAlert()


        }



        else if atPoint(location).name == "delete" {

            theWord = ""
            theCheckedWord = ""
            wordLabel?.text = theWord

        }





        else {


        if location.x > center && !vowelPressed {

            moveLeft = false
        }

        else if location.x < center && !vowelPressed {

            moveLeft = true
        }



    canMove = true

        }
    }
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    canMove = false
}

func didBegin(_ contact: SKPhysicsContact) {

    if !gamePaused {

    var firstBody = SKPhysicsBody()
    var secondBody = SKPhysicsBody()

    if contact.bodyA.node?.name == "Player" {

        firstBody = contact.bodyA
        secondBody = contact.bodyB

    }

        else {

            firstBody = contact.bodyB
            secondBody = contact.bodyA

        }

    if firstBody.node?.name == "Player" && secondBody.node?.name == "Tile" {


        letter = secondBody.node?.userData?.value(forKey:"key") as! Int
        print ("The value of letter is \(letter)")
        var letterCode = String(describing: letter)
        var letterValue = Int(letterCode)
        var finalLetterValue = letterValue!+64
        var ASCIICode = Character(UnicodeScalar(finalLetterValue)!)
        var letterString = String(describing:ASCIICode)
        theWord = theWord + letterString
        print (theWord)

        if onePointLetters.contains(letter) {

            characterScore += 1



        }

        else if twoPointLetters.contains(letter) {

            characterScore += 2


        }

        else if threePointLetters.contains(letter) {

            characterScore += 3


        }

        else if fourPointLetters.contains(letter) {

            characterScore += 4
                        }

        else if fivePointLetters.contains(letter) {

            characterScore += 5


        }


        else if eightPointLetters.contains(letter) {

            characterScore += 8

        }

        else if tenPointLetters.contains(letter) {

            characterScore += 10

        }

        wordLabel?.text = theWord
        theCheckedWord = theWord.lowercased()
        print ("The checked word is \(theCheckedWord)")


        checkWord()

        secondBody.node?.removeFromParent()

    }


    if firstBody.node?.name == "Player" && secondBody.node?.name == "Bomb" {

        explosion.play()
        firstBody.node?.removeFromParent()
        secondBody.node?.removeFromParent()
        theWord = ""
        score = 0
        scoreLabel?.text = String(score)
        var delayTimer = SKAction.wait(forDuration:1)
        run (delayTimer)
        restartGame()

    }


}

}



private func initializeGame() {





    do {
        // This solution assumes  you've got the file in your bundle
        if let path = Bundle.main.path(forResource: "en", ofType: "txt"){
            let data = try String(contentsOfFile:path, encoding: String.Encoding.utf8)
            arrayOfStrings = data.components(separatedBy: "\n")

        }
    } catch let err as NSError {
        // do something with Error
        print(err)
    }

    physicsWorld.contactDelegate = self
    physicsWorld.gravity = CGVector(dx:0,dy:gravity)





    player = childNode(withName: "Player") as? Player!
    player?.initializePlayer()
    player?.position = CGPoint(x:0, y: -420)
    scoreLabel = childNode(withName: "ScoreLabel") as? SKLabelNode!
    scoreLabel?.text = String(score)
    wordLabel = childNode(withName:"WordLabel") as? SKLabelNode!
    wordLabel?.text = ""
    center = self.frame.size.width/self.frame.size.height



    var spawnBlocks = SKAction.repeatForever(SKAction.sequence([SKAction.wait(forDuration:1),SKAction.run(spawnItems)]))



    var removeBlocks = SKAction.repeatForever(SKAction.sequence([SKAction.wait(forDuration:1),SKAction.run(removeItems)]))


    if gamePaused == true {

        self.scene?.isPaused = true

    }

    self.run(spawnBlocks)


    self.run(removeBlocks)


}

private func checkWord() {

    theCheckedWord = theWord.lowercased()


    if (arrayOfStrings?.contains(theCheckedWord))! {

        print ("Yes! \(theCheckedWord) is a word")





        correct.play()
        print ("The current value of score is \(score)")
        score += characterScore
        print ("Current gravity setting is \(gravity)")
        scoreLabel?.text = String(score)
        characterScore = 0

        matchingTerms = (arrayOfStrings?.filter({$0.hasPrefix(theCheckedWord)

        }))!


        if matchingTerms.count == 1 {


            characterScore = 0
            print ("Current gravity setting is \(gravity)")
            theWord = ""
            theCheckedWord = ""
            wordLabel?.text = ""


        }

    }



    else if !(arrayOfStrings?.contains(theCheckedWord))! {



        matchingTerms = (arrayOfStrings?.filter({$0.hasPrefix(theCheckedWord)

        }))!



        if matchingTerms.count == 0 {

            wrong.play()

            characterScore = 0

            if score >= 5 {

                score -= 5

            }



            theWord = ""
            theCheckedWord = ""
            wordLabel?.text = ""

        }
    }



}

Here is my code for the enemies:

import SpriteKit

struct ColliderType {

static let PLAYER: UInt32 = 0
static let TILE_AND_BOMB: UInt32 = 1;

}


public var bounds = UIScreen.main.bounds
public var width = bounds.size.width
public var height = bounds.size.height


class ItemController {



private var minX = CGFloat(-(width/2)), maxX = CGFloat(width/2)

private var vowelNumbers = [1, 5, 9, 15, 21]

func spawnItems() -> SKSpriteNode {
    let item: SKSpriteNode?

    if Int(randomBetweenNumbers(firstNum: 0, secondNum: 10)) > 7 {
        item = SKSpriteNode(imageNamed: "Bomb")
        item!.name = "Bomb"
        item!.setScale(0.6)
        item!.physicsBody = SKPhysicsBody(circleOfRadius:          item!.size.height / 2)
        print ("The value of width is \(width)")

    } else {
        var num = Int(randomBetweenNumbers(firstNum: 1, secondNum: 26))

        if vowelNumbers.contains(num) {

            num = num + 1
        }

        item = SKSpriteNode(imageNamed: "Tile \(num)")
        item?.userData = NSMutableDictionary()
        item!.name = "Tile"
        item!.userData!.setValue(num, forKey:"key")
        item!.zRotation = 0
        item!.setScale(0.9)
        item!.physicsBody = SKPhysicsBody(circleOfRadius:   item!.size.height / 2)
        if gamePaused == true {

            item!.isPaused = true

        }

        else {

            item!.isPaused = false

        }
    }

    item!.physicsBody?.categoryBitMask = ColliderType.TILE_AND_BOMB;
    item!.physicsBody?.isDynamic = true

    item!.zPosition = 3
    item!.anchorPoint = CGPoint(x: 0.5, y: 0.5)

    item!.position.x = randomBetweenNumbers(firstNum: minX, secondNum: maxX)
    item!.position.y = 600

    return item!
}



func randomBetweenNumbers(firstNum: CGFloat, secondNum: CGFloat) -> CGFloat {
    return CGFloat(arc4random()) / CGFloat(UINT32_MAX) * abs(firstNum - secondNum) + min(firstNum, secondNum);
}

}

And here is my code for the player:

import SpriteKit

class Player: SKSpriteNode {

private var minX = CGFloat(-300), maxX = CGFloat(300)

func initializePlayer() {

    name = "Player"

    physicsBody = SKPhysicsBody(circleOfRadius: size.height/2)
    physicsBody?.affectedByGravity = false
    physicsBody?.isDynamic = false
    physicsBody?.categoryBitMask = ColliderType.PLAYER
    physicsBody?.contactTestBitMask = ColliderType.TILE_AND_BOMB


  }

 func move(left:Bool){

    if left {
    position.x -= 15

        if position.x < minX {

            position.x = minX
        }

  }
  else {

    position.x += 15

        if position.x > maxX {

            position.x = maxX
        }


     }

   }

}
2
Is that also happening on the device?BadgerBadger
How many strings are in your arrayOfStrings?nathangitter
Also that... your checkWords method and the whole way you check for letters could be a lot more efficient if you used a subclass of skspritenode and added a letter property or something like that.BadgerBadger
arrayOfStrings.count is 274921. Thank you for your answer, I'll try a subclass as you suggest.user3140521
@user3140521 If you have 20-30 fps on a device for something this simple, then you should really change something in your code. First thing to keep in mind is that you should use atlases. Second, on your SKView, do skView.showsDrawsCount = true to see how many draw calls are required for your scene to be rendered (and post that number here).Whirlwind

2 Answers

0
votes

Consider calling the checkWord() method from an SKAction:

let checkWordsAction = SKAction.run() {
    [weak self] in
    self?.checkWord()
}
self.run(checkWordsAction)

That way, if your array is too big, the code will not get interrupted because you are walking it to check for your word.

Also I'm not sure of what you are trying to do with the wait action at the end of the didBegin(contact:) method, when you are colliding with bombs. If you are trying to wait for 1 second before restarting the game, then keep in mind that calling the wait action the way you do will not pause your code. Actually it will simply put the scene on idle for 1 second and restart the game in the background. If that is the intended effect, it's ok, but you might be better using the run(_:completion:) method instead and call restartGame() from the completion closure:

let waitAction = SKAction.wait(1)
self.run(waitAction) { [weak self] in
    self?.restartGame()
}

Hope this helps!

0
votes

In didBegin(_ contact: SKPhysicsContact), you create two physics bodies in:

var firstBody = SKPhysicsBody()
var secondBody = SKPhysicsBody()

Maybe the lag is created because you create two new physics bodies. What if you don't create the physics bodies, but just create references to existing ones? I haven't tested this, but maybe try:

let firstBody: SKPhysicsBody
let secondBody: SKPhysicsBody

if contact.bodyA.node?.name == "Player" {

    firstBody = contact.bodyA
    secondBody = contact.bodyB
}

else {
    firstBody = contact.bodyB
    secondBody = contact.bodyA
}