What would be the best approach for outlining or dropshadowing a font?

喜你入骨 提交于 2019-12-13 11:33:13

问题


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.


回答1:


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 ...




回答2:


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




回答3:


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!




回答4:


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"

You can also check out ASAttributedLabelNode.




回答5:


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)



回答6:


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!




回答7:


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.




回答8:


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.




回答9:


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




回答10:


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



来源:https://stackoverflow.com/questions/19211827/what-would-be-the-best-approach-for-outlining-or-dropshadowing-a-font

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!