问题
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