15

I'm not finding any support for dropshadow or outline of a font in Sprite Kit. For the dropshadow, I'm guessing I could create a second SKLabelNode and offset it behind the other.

Is there some way that I could utilize a SKEffectNode for the outline or dropshadow ? Possibly make the SKLabelNode a child of the SKEffectNode ?

update :

I was able to get a decent dropshadow using a second SKLabelNode behind the first, that is black and offset. Still interested in other potential options, but that seems to work well.

prototypical
  • 6,731
  • 3
  • 24
  • 34
  • 1
    Drop shadowing and outlining aren't that different from glowing. See [my answer to your other question about that](http://stackoverflow.com/a/19237349/957768) and you might be able to tweak the technique to get the effect you want. – rickster Oct 08 '13 at 01:01
  • Yes, it's exactly what came to mind. But I want the outline to be sharper, so I'll have to see what I can come up with. – prototypical Oct 08 '13 at 01:15
  • A blurred copy of an image makes a good glow effect. To turn that fuzzy glow into a solid outline, just boost the alpha of the blurred image -- try the `CIColorMatrix` filter with an `inputAVector` parameter of something like `[0 0 0 10]`. – rickster Oct 08 '13 at 16:50
  • Just occurred to me that the node count has a outlined label with a gradient fill. – DogCoffee Nov 03 '13 at 06:40

10 Answers10

18

This is most likely what you are doing, but it works and is simple.

- (SKLabelNode *) makeDropShadowString:(NSString *) myString
{
    int offSetX = 3;
    int offSetY = 3;

    SKLabelNode *completedString = [SKLabelNode labelNodeWithFontNamed:@"Arial"];
    completedString.fontSize = 30.0f;
    completedString.fontColor = [SKColor yellowColor];
    completedString.text = myString;


    SKLabelNode *dropShadow = [SKLabelNode labelNodeWithFontNamed:@"Arial"];
    dropShadow.fontSize = 30.0f;
    dropShadow.fontColor = [SKColor blackColor];
    dropShadow.text = myString;
    dropShadow.zPosition = completedString.zPosition - 1;
    dropShadow.position = CGPointMake(dropShadow.position.x - offSetX, dropShadow.position.y - offSetY);

    [completedString addChild:dropShadow];

    return completedString;
}

Will try and make outline one as well... but I have a feeling it'll be more tricky... there must be a way to use bitmap fonts .. ??? bmGlyph ...

DogCoffee
  • 19,820
  • 10
  • 87
  • 120
  • I did figure out the drop shadow, just as you have it here. The outline I still have not figured a way to do. I am thinking bitmap fonts as well, but still equally interested in how to do it without. I'm sure this will be a very common question for people getting started with Sprite Kit. – prototypical Oct 07 '13 at 22:32
  • sounds like they are coming http://stackoverflow.com/questions/18954144/sprite-kit-and-bitmap-fonts – DogCoffee Oct 07 '13 at 23:38
  • Yeah, I saw that comment earlier. I have to believe that we'll be seeing more features soon. I haven't looked too close at the SpriteKit code for hints of what that might be. – prototypical Oct 07 '13 at 23:41
  • I think for the initial release, it's pretty impressive. SpriteKit does most things that are supported rather well and it's easy to work with. I'm optimistic that things like this will get handled soon, but were not as critical. Hate the idea of doing a workaround like with the UILabel. – prototypical Oct 07 '13 at 23:45
  • I agree that would be very helpful. Was messing around with the particle emitter integration in xcode. Hope we see more of that kind of thing in the future. – prototypical Oct 07 '13 at 23:47
  • have you seen a decent tutorial on SKEffects, cannot find much on this. I find them hard to use. – DogCoffee Oct 07 '13 at 23:48
  • SKNode properties position and zPosition are relative to the parent. Since the shadow label is the child of the original label, you want the position code to look like dropShadow.zPosition = -1; dropShadow.position = CGPointMake(-offSetX, -offSetY); – Sky Feb 02 '15 at 20:57
7

Since iOS 11 SKLabelNode has attributedText property. You need to specify strokeColor, strokeWidth and don't forget font and foregroundColor.

kelin
  • 11,323
  • 6
  • 67
  • 104
4

I've been able to achieve a somewhat acceptable "outline" by not only using the "2 SKLabelNode" dropshadow techniques described here, but also by adding three more "drop shadows".

In other words, once you have the bottom dropshadow in place, add three more. One offset toward the top, one offset toward the right, and one offset toward the left — so I have four "drop shadows" underneath, with the main label on top for a total of five SKLabelNodes just to create this outline effect.

In my app, I have to animate this text, so I took it one step further and created a bitmap texture from these five labels and created a single SKSpriteNode from this texture, which allowed me to then delete the five original label nodes, and animate the bitmap version.

It might also be worth noting that by creating a texture from the label nodes, it resulted in a blurry texture, which I fixed by doubling the size of the label nodes before creating the texture, then reducing the size of the generated texture by 50%.

I am attaching an image to show you the result. It may not be the most elegant approach, but it seems to work for my particular situation. Hope this helps!

enter image description here

John R.
  • 177
  • 11
4

Further to Kelin's excellent point, here is the Swift 4 code for it:

var label: SKLabelNode!
let attStr: NSMutableAttributedString = NSMutableAttributedString(string: "MyDefaultText")
let myFont: UIFont = UIFont(name: "FontName", size: 7)!

attStr.mutableString.setString("MyText")
attStr.addAttribute(.font, value: myFont, range: NSMakeRange(0, attStr.length))
attStr.addAttribute(.foregroundColor, value: UIColor.white, range: NSMakeRange(0, attStr.length))
attStr.addAttribute(.strokeColor, value: UIColor.red, range: NSMakeRange(0, attStr.length))
attStr.addAttribute(.strokeWidth, value: -3.0, range: NSMakeRange(0, attStr.length))

label.attributedText = attStr

PS: To get the stroke and the fill at the same time, you need to apply a negative value to the .strokeWidth which means the stroke heads outwards. A positive value for the .strokeWidth will send the stroke inwards and cause your fill colour (.foregroundColor) to disappear!

AW101
  • 1,620
  • 14
  • 15
Geoff H
  • 3,107
  • 1
  • 28
  • 53
3

I suggest MKOutlinedLabelNode.

Example:

let textNode = MKOutlinedLabelNode(fontNamed: "Helvetica", fontSize: 32)
textNode.borderColor = UIColor.blackColor()
textNode.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.Center
textNode.fontColor = UIColor.blueColor()
textNode.outlinedText = "Test"

enter image description here

You can also check out ASAttributedLabelNode.

twodayslate
  • 2,803
  • 3
  • 27
  • 43
1

This worked for me:

http://battleofbrothers.com/sirryan/outline-text-in-spritekit

Here's the code I used in conjunction with ASAttributedLabelNode (some of it's specific to me like the font name/size/fillcolor/outlinecolor but of course just use your own):

func outlinedCenteredString(string : String, size: CGFloat) -> NSAttributedString
{
    var myMutableString : NSMutableAttributedString
    var font =  UIFont(name: "Super Mario 256", size: size)!
    var alignment : CTTextAlignment = CTTextAlignment.TextAlignmentCenter
    let alignmentSetting = [CTParagraphStyleSetting(spec: .Alignment, valueSize: Int(sizeofValue(alignment)), value: &alignment)]
    var paragraphRef = CTParagraphStyleCreate(alignmentSetting, 1)

    let textFontAttributes = [
        NSFontAttributeName : font,
        // Note: SKColor.whiteColor().CGColor breaks this
        NSForegroundColorAttributeName: UIColor.yellowColor(),
        NSStrokeColorAttributeName: UIColor.blackColor(),
        // Note: Use negative value here if you want foreground color to show
        NSStrokeWidthAttributeName:-3
        //,NSParagraphStyleAttributeName: paragraphRef
    ]

    myMutableString = NSMutableAttributedString(string: string, attributes: textFontAttributes as [NSObject : AnyObject])

    let para = NSMutableParagraphStyle()
    para.headIndent = 00
    para.firstLineHeadIndent = 00
    para.tailIndent = 0
    para.lineBreakMode = .ByWordWrapping
    para.alignment = .Center
    para.paragraphSpacing = 0
    myMutableString.addAttribute(
        NSParagraphStyleAttributeName,
        value:para, range:NSMakeRange(0,1))
    return myMutableString
}

along with the following to use this function:

    let hintSize = CGFloat(80.0)
    let hintLabel = ASAttributedLabelNode(size:CGSizeMake(playableRect.size.width*0.9, hintSize))

    hintLabel.attributedString = outlinedCenteredString("Touch the Rope to Release the Wood", size: hintSize)

    hintLabel.position =
        CGPointMake(
            size.width/2.0,
            ((size.height - playableRect.size.height)/2.0) + hintSize/2.0
    )

    hintLabel.zPosition = kHintZPosition
    addChild(hintLabel)
Doug Voss
  • 1,012
  • 1
  • 14
  • 11
  • Good addition. I've done this kind of solution on occasion as well since then. At the time, was hoping there was a pure SpriteKit solution. Maybe in the future ? – prototypical Jul 09 '15 at 23:49
0

With respect to outlining :

Short answer: You've got your work cut out for you.
Long answer: You've really got your work cut out for you.

We have been doing this on a project and the basic approach has been to create a UILabel and render it to a texture that goes into a sprite.
To get the outline onto the UILabel, you can use this: How do I make UILabel display outlined text? and to render that into a UIImage (that you can make a texture with [SKTexture textureWithImage:img]), use this : How to create an image from UILabel? This comes with a fist-full of problems, chief among them being slow, greedy rendering which was largely solved by pre-rendering to file whenever possible for static text.

I don't think you'll be able to pull this off with an effect node because of the precision that fonts tend to require. In retrospect, I would think twice before going down this road.

Community
  • 1
  • 1
Kardasis
  • 901
  • 12
  • 20
  • I was able to get a decent dropshadow now with two SKLabelNodes, one of them offset and black. With the SKEffectNode, you can use the shouldRasterize option to cache it. – prototypical Oct 06 '13 at 18:51
  • Sorry, I was pretty much talking about outlining. I'll edit the answer to reflect as much. Why not just have two SKLabels to make a drop shadow like you said. – Kardasis Oct 06 '13 at 18:55
  • Yes, as I said, I've now tried that out and it works for the dropshadow. I was looking for options that I'd maybe not thought of or weren't aware of for outlining and dropshadowing. – prototypical Oct 06 '13 at 20:55
  • I'm not clear on how your answer provides a option for an outline. Converting to an image doesn't give me an outline. Or am I missing something here ? Does UILabel have dropshadow/outline support ? – prototypical Oct 06 '13 at 20:57
  • Ok, I'll look into that. Going to leave this question open for a bit longer in hopes of other options. I managed a satisfactory dropshadow using SpriteKit and would like to avoid using the UILabel solution if possible. – prototypical Oct 07 '13 at 23:18
  • Just a note: avoid this if you want the solution to work for all platforms supported by Sprite Kit. – Jonny Oct 29 '13 at 01:24
0

Use OOP! I've implemented this as a new component, a descendant of SKNode, which contains two SKLabelNodes, one for text itself and another for its shadow:

final class TgSKLabelWithShadow: SKNode
{
    private var lblText: SKLabelNode!
    private var lblShadow: SKLabelNode!

    var text: String?
        {     get { return lblText.text }
        set { lblText.text   = newValue
              lblShadow.text = newValue   } }

    var fontColor: UIColor?
        { get { return lblText.fontColor     }
          set { lblText.fontColor = newValue } }
    // define additional getters/setters, if necessary.


    init(     position: CGPoint,
              alignment: SKLabelHorizontalAlignmentMode,
              text: String,
              fontName: String,
              fontSize: CGFloat,
              fontColor: UIColor,
              shadowColor: UIColor,
              shadowOffSet: CGPoint)
    {
        super.init()
        self.position = position

        lblShadow = SKLabelNode(text: text)
        lblShadow.fontName  = fontName
        lblShadow.fontColor = shadowColor
        lblShadow.fontSize  = fontSize
        lblShadow.horizontalAlignmentMode = alignment
        lblShadow.position = shadowOffSet
        self.addChild(lblShadow)  // add shadow first

        lblText = SKLabelNode(text: text)
        lblText.fontName  = fontName
        lblText.fontColor = fontColor
        lblText.fontSize  = fontSize
        lblText.horizontalAlignmentMode = alignment
        self.addChild(lblText)  // on top of shadow
    }

    required init?(coder aDecoder: NSCoder)
    {
        fatalError("TgSKLabelWithShadow - init(coder:) has not been implemented")
    }
}

Instantiate and add to parent (another node or the scene) like so:

   ndInfo2 = TgSKLabelWithShadow(
        position: CGPoint(x: size.width/2, y:   size.height - 170),
        alignment : .Center,
        text: "ndInfo2: ???",
        fontName: "Avenir",
        fontSize: 40,
        fontColor: colorText,
        shadowColor: UIColor.blueColor(),
        shadowOffSet: CGPoint(x:1, y:-1))
    addChild(ndInfo2)

An outline? Well, nothing prevents you to nest yet another SKLabelNode to establish an outline. (one could calculate it to be slightly bigger than the label.) Note that the order of adding child labels is relevant.

This code has been tested and is in active use in an Apple TV app that I am currently building. Kind Regards, Ted.

Ted van Gaalen
  • 1,159
  • 8
  • 5
0

I created a class inheriting from SKSpriteNode where it creates necessary SKLabelNode and 4 shadow labels according to the parameters passed to constructor.

You need to call the update() method when you changed any public property. (BorderSize more than 6-7 looks funny.)

import Foundation
import SpriteKit

class LFOutlinedLabel : SKSpriteNode {

    private let skewX : [CGFloat] = [-1, 1, 1,-1]
    private let skewY : [CGFloat] = [-1,-1, 1, 1]

    private var label : SKLabelNode = SKLabelNode()
    private var shadows : [SKLabelNode] = []

    public var borderOpacity : CGFloat = 1
    public var borderSize: CGFloat = 1
    public var borderColor: UIColor = UIColor.black
    public var text : String = "?"
    public var fontName : String = Fonts.OptimaExtraBlack.rawValue
    public var fontColor: UIColor = UIColor.white
    public var fontSize : CGFloat = 40

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        //self.setup()
    }

    override init(texture: SKTexture!, color: UIColor, size: CGSize) {
        super.init(texture: texture, color: color, size: size)
        //self.setup()
    }

    convenience init(size: CGSize, font: String, fSize: CGFloat, fColor: UIColor, bSize: CGFloat, bColor: UIColor, bOpacity: CGFloat)
    {
        self.init(texture: nil, color: UIColor.clear, size: size)
        self.fontName = font
        self.fontSize = fSize
        self.fontColor = fColor
        self.borderSize = bSize
        self.borderColor = bColor
        self.borderOpacity = bOpacity
        self.setup()
    }

    // create shadow labels
    private func setup() {
        if shadows.count == 0 {
            let width = self.size.width / 2
            let height = self.size.height / 2
            for j in 0...3 {
                let shadow = SKLabelNode(text: self.text)
                addChild(shadow)
                shadow.verticalAlignmentMode = .center
                shadow.horizontalAlignmentMode = .center
                shadow.zPosition = 999
                shadow.position = CGPoint(x: width + (skewX[j] * borderSize) , y: height + (skewY[j] * borderSize))
                shadow.text = self.text
                shadow.fontSize = self.fontSize
                shadow.fontName = self.fontName
                shadow.fontColor = borderColor
                shadows.append(shadow)
            }
            let label = SKLabelNode(text: self.text)
            addChild(label)
            label.verticalAlignmentMode = .center
            label.horizontalAlignmentMode = .center
            label.zPosition = 1000
            label.position = CGPoint(x: width , y: height )
            label.text = self.text
            label.fontSize = self.fontSize
            label.fontName = self.fontName
            label.fontColor = fontColor
            self.label = label
        }
    }

    public func update(){
        let width = self.size.width / 2
        let height = self.size.height / 2

        self.label.fontSize = fontSize
        self.label.fontName = fontName
        self.label.fontColor = fontColor
        self.label.verticalAlignmentMode = .center
        self.label.horizontalAlignmentMode = .center
        self.label.text = text
        self.label.position = CGPoint(x: width  , y: height )

        for i in 0...3 {
            shadows[i].verticalAlignmentMode = .center
            shadows[i].horizontalAlignmentMode = .center
            shadows[i].fontColor = borderColor
            shadows[i].fontSize = fontSize
            shadows[i].alpha = borderOpacity
            shadows[i].fontName = fontName
            shadows[i].text = text
            shadows[i].position = CGPoint(x: width + (skewX[i] * borderSize) , y: height + (skewY[i] * borderSize) )
        }
    }
}

the recent code can be found at https://gist.github.com/detaybey/214b23731a3b4d0344ce58643795f4b7

detay
  • 1,082
  • 11
  • 19
0

Erica Sadun once created a drop shadow based on SKEffectNode here

I recently had to create a Swift4 version of her ShadowLabelNode: https://gist.github.com/Bersaelor/d6ea241278665173485e8aabafbe9047

Bersaelor
  • 2,517
  • 34
  • 58