Search code examples
swift3sprite-kitskspritenode

Animating individual characters in a SKLabelNode


Is there a more efficient way to animate text shivering with typewriting all in one sklabelnode? I'm trying to achieve the effect in some games like undertale where the words appear type writer style while they are shivering at the same time.

So far I've only been able to achieve it but with such luck:

class TextEffectScene: SKScene {

    var typeWriterLabel : SKLabelNode?
    var shiveringText_L : SKLabelNode?
    var shiveringText_O : SKLabelNode?
    var shiveringText_S : SKLabelNode?
    var shiveringText_E : SKLabelNode?
    var shiveringText_R : SKLabelNode?

    var button : SKSpriteNode?


    override func sceneDidLoad() {
        button = self.childNode(withName: "//button") as? SKSpriteNode

        self.scaleMode = .aspectFill //Very important for ensuring that the screen sizes do not change after transitioning to other scenes

        typeWriterLabel = self.childNode(withName: "//typeWriterLabel") as? SKLabelNode
        shiveringText_L = self.childNode(withName: "//L") as? SKLabelNode
        shiveringText_O = self.childNode(withName: "//O") as? SKLabelNode
        shiveringText_S = self.childNode(withName: "//S") as? SKLabelNode
        shiveringText_E = self.childNode(withName: "//E") as? SKLabelNode
        shiveringText_R = self.childNode(withName: "//R") as? SKLabelNode
    }



    // Type writer style animation

    override func didMove(to view: SKView) {
        fireTyping()
        shiveringText_L?.run(SKAction.repeatForever(SKAction.init(named: "shivering")!))
        shiveringText_O?.run(SKAction.repeatForever(SKAction.init(named: "shivering2")!))
        shiveringText_S?.run(SKAction.repeatForever(SKAction.init(named: "shivering3")!))
        shiveringText_E?.run(SKAction.repeatForever(SKAction.init(named: "shivering4")!))
        shiveringText_R?.run(SKAction.repeatForever(SKAction.init(named: "shivering5")!))
    }


    let myText = Array("You just lost the game :)".characters)
    var myCounter = 0
    var timer:Timer?

    func fireTyping(){
        typeWriterLabel?.text = ""
        timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(TextEffectScene.typeLetter), userInfo: nil, repeats: true)
    }


    func typeLetter(){
        if myCounter < myText.count {
            typeWriterLabel?.text = (typeWriterLabel?.text!)! + String(myText[myCounter])
            //let randomInterval = Double((arc4random_uniform(8)+1))/20 Random typing speed

            timer?.invalidate()
            timer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(TextEffectScene.typeLetter), userInfo: nil, repeats: false)
        } else {
            timer?.invalidate() // stop the timer
        }
        myCounter += 1
    }


    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first

        if let location = touch?.location(in: self) {
            if (button?.contains(location))! {
                print("doggoSceneLoaded")
                let transition = SKTransition.fade(withDuration: 0.5)
                let newScene = SKScene(fileNamed: "GameScene") as! GameScene
                self.view?.presentScene(newScene, transition: transition)
            }

        }


    }



}

enter image description here

As you can see, I had to animate each individual label node in a word "loser".

To create this effect:

enter image description here


Solution

  • something i have a lot of experience with... There is no way to do this properly outside of what you are already doing. My solution (for a text game) was to use NSAttributedString alongside CoreAnimation which allows you to have crazy good animations over UILabels... Then adding the UILabels in over top of SpriteKit.

    I was working on a better SKLabel subclass, but ultimately gave up on it after I realized that there was no way to get the kerning right without a lot more work.

    It is possible to use an SKSpriteNode and have a view as a texture, then you would just update the texture every frame, but this requires even more timing / resources.

    The best way to do this is in the SK Editor how you have been doing it. If you need a lot of animated text, then you need to use UIKit and NSAttributedString alongside CoreAnimation for fancy things.

    This is a huge, massive oversight IMO and is a considerable drawback to SpriteKit. SKLabelNode SUCKS.