6

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

lws803
  • 907
  • 2
  • 10
  • 21
  • I would create a custom SKAction that simply modify the SKLabelNode text over time by adding one character at a time until it reaches the final string. – BadgerBadger May 04 '17 at 12:28
  • Or a subclass of SKLabelNode... – BadgerBadger May 04 '17 at 12:33
  • Thanks, subclassing might simplify it a great deal! Saw a similar method here. But is there really no way to get text wrap in sklabelnode tho. – lws803 May 04 '17 at 12:40
  • I still don't think each individual characters can be animated tho. Animating sklabelnode animates the entire label altogether. – lws803 May 04 '17 at 12:43
  • Well, if you want control over the individual characters, you could also subclass from SKNode and generate your SKLabelNodes from code. Then you can do whatever you want. Put every label in an array so you can reference to them easily and run SKActions on them. Since the SKLabelNode will calculate its size automatically, you can easily put them all side by side. Even spaces: I don't think it will be a big performance hit since you are not displaying pages of text. – BadgerBadger May 04 '17 at 13:41
  • Hmm would it be too much to ask if I could see an example? Sorry its pretty hard to digest, I'm still quite new to SpriteKit – lws803 May 04 '17 at 14:11
  • 1
    I'll try to put something together for you tonight! – BadgerBadger May 04 '17 at 14:42
  • Got very busy, sorry! I'll do that ASAP! – BadgerBadger May 05 '17 at 14:25
  • I just posted an answer with a link to a quick demo I just put together. It's not well commented, but i'll come back to that later if you want. – BadgerBadger May 12 '17 at 06:31

3 Answers3

3

For those who may be interested to Swift 4 I've realized a gitHub project around this special request called SKAdvancedLabelNode. You can find here all sources.

Usage:

// horizontal alignment : left
var advLabel = SKAdvancedLabelNode(fontNamed:"Optima-ExtraBlack")
advLabel.name = "advLabel"
advLabel.text = labelTxt
advLabel.fontSize = 20.0
advLabel.fontColor = .green
advLabel.horizontalAlignmentMode = .left
addChild(self.advLabel)
advLabel.position = CGPoint(x:frame.width / 2.5, y:frame.height*0.70)
advLabel.sequentiallyBouncingZoom(delay: 0.3,infinite: true)

Output:

enter image description here

Alessandro Ornano
  • 34,887
  • 11
  • 106
  • 133
2

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.

Fluidity
  • 3,985
  • 1
  • 13
  • 34
  • I couldn't agree less. I've tried overlaying UILabels on top of the gameViewController and it works so much better. At least it supports text wrapping which allows me to add narrative slow typing animations which stretches on to a few lines. – lws803 May 05 '17 at 16:20
  • I said to use UILabels :P @Wilson which is what I do in mine. – Fluidity May 05 '17 at 16:36
  • oops sorry I meant I couldn't agree more! Haha Thanks for the help! – lws803 May 05 '17 at 17:56
  • Go have a look at what I put together in my answer. I had to fiddle a little bit for the kerning, but all in all the main issues were the spaces. it looks fine to me – BadgerBadger May 12 '17 at 06:32
1

As I said in a comment, you can subclass from an SKNode and use it to generate your labels for each characters. You then store the labels in an array for future reference.

I've thrown something together quickly and it works pretty well. I had to play a little bit with positionning so it looks decent, because spaces were a bit too small. Also horizontal alignement of each label has to be .left or else, it will be all crooked.

Anyway, it'S super easy to use! Go give it a try!

Here is a link to the gist I just created.

https://gist.github.com/sonoblaise/e3e1c04b57940a37bb9e6d9929ccce27

BadgerBadger
  • 696
  • 3
  • 12
  • thanks for sharing, and its fine depending on certain words / fonts (i wrote similar to this), but kerning is unique to each font and is totally lost in this approach. – Fluidity May 12 '17 at 06:56
  • I'll have to try, but at first it was a complete mess until I set each label horizontalAlign to .left. – BadgerBadger May 12 '17 at 12:06
  • you would have to essentially build your own kerning tables then implement them, for every font. i came up with a solution to do this automatically via collisions, but it just wasnt worth it compared to the built in awesome of nsaatributedstring and coreanimation – Fluidity May 12 '17 at 13:52
  • Don't the fonts themselves have that info somewhere? We can probably retrieve that and use it to space the characters properly? – BadgerBadger May 12 '17 at 18:58
  • possibly you could find the tables. but still, when you make an individual node there is an "unknown value" of empty space added to the left and right of each individual letter when transformed into a node. Since we dont jave access to spritekit source, the best option is to not use LabelNode for text intensive options where the dev doesnt jave time to do kerning programmatically;) – Fluidity May 12 '17 at 20:09
  • I'm pretty sure there is an easy way ;) – BadgerBadger May 14 '17 at 03:06
  • keep trying my friend :) if you find something let me know. I spent two weeks on this in December. – Fluidity May 14 '17 at 17:03
  • Did you try working with NSFont? I just found out about it in the documentation and it looks like it can retrieve that kind of info. I'll try digging deeper into it when I have more time on my hands. – BadgerBadger May 16 '17 at 01:55