Insérer une rupture de ligne en utilisant le SKLabelNode dans SpriteKit

question Simple sur la façon d'insérer un saut de ligne à l'aide de SKLabelNode classe dans SpriteKit. J'ai le code suivant mais il ne fonctionne pas -

 SKLabelNode *nerdText = [SKLabelNode labelNodeWithFontNamed:@"Times"];
    NSString *st1 = @"Test break";
    NSString *st2 = @"I want it to break";
    NSString *test = [NSString stringWithFormat:@"%@,r%@",st1,st2]; //Even tried n
    nerdText.text = test;
    nerdText.fontSize = 11;
    nerdText.fontColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
    nerdText.position = CGPointMake(150.0, 250.0);
    [self addChild:nerdText];

Aidez-moi!

29
demandé sur Arjun Busani 2013-10-04 14:27:18

15 réponses

Je ne pense pas que vous pouvez, voici un" hack "façon de le faire

SKNode *nerdText = [SKNode node]; 
SKLabelNode *a = [SKLabelNode labelNodeWithFontNamed:@"Arial"];
a.fontSize = 16;
a.fontColor = [SKColor yellowColor];
SKLabelNode *b = [SKLabelNode labelNodeWithFontNamed:@"Arial"];
b.fontSize = 16;
b.fontColor = [SKColor yellowColor];
NSString *st1 = @"Line 1";
NSString *st2 = @"Line 2";
b.position = CGPointMake(b.position.x, b.position.y - 20);
a.text = st1;
b.text = st2;
[nerdText addChild:a];
[nerdText addChild:b];
nerdText.position = CGPointMake(150.0, 250.0);
[self addChild:nerdText];
15
répondu DogCoffee 2013-10-04 12:02:30

j'ai eu le même problème. J'ai créé un drop-in de remplacement pour le SKLabelNode appelé DSMultilineLabelNode qui prend en charge word wrap, line breaks, etc. L'implémentation sous-jacente place la chaîne dans un contexte graphique et l'applique ensuite à une texture sur un SKSpriteNode.

il est disponible sur GitHub à:

https://github.com/downrightsimple/DSMultilineLabelNode

14
répondu Chris Allwein 2014-02-12 21:18:03
 static func multipleLineText(labelInPut: SKLabelNode) -> SKLabelNode {
        let subStrings:[String] = labelInPut.text!.componentsSeparatedByString("\n")
        var labelOutPut = SKLabelNode()
        var subStringNumber:Int = 0
        for subString in subStrings {
            let labelTemp = SKLabelNode(fontNamed: labelInPut.fontName)
            labelTemp.text = subString
            labelTemp.fontColor = labelInPut.fontColor
            labelTemp.fontSize = labelInPut.fontSize
            labelTemp.position = labelInPut.position
            labelTemp.horizontalAlignmentMode = labelInPut.horizontalAlignmentMode
            labelTemp.verticalAlignmentMode = labelInPut.verticalAlignmentMode
            let y:CGFloat = CGFloat(subStringNumber) * labelInPut.fontSize
            print("y is \(y)")
            if subStringNumber == 0 {
                labelOutPut = labelTemp
                subStringNumber++
            } else {
                labelTemp.position = CGPoint(x: 0, y: -y)
                labelOutPut.addChild(labelTemp)
                subStringNumber++
            }
        }
        return labelOutPut
    }
9
répondu Jerry Zhao 2015-10-11 16:40:05

voici un autre piratage de cinq minutes par votre serviteur. Il n'est pas trop mauvais.

+(SKSpriteNode*)spritenodecontaininglabelsFromStringcontainingnewlines:(NSString*)text fontname:(NSString*)fontname fontcolor:(NSColor*)colorFont fontsize:(const CGFloat)SIZEFONT verticalMargin:(const CGFloat)VERTICALMARGIN emptylineheight:(const CGFloat)EMPTYLINEHEIGHT {
    NSArray* strings = [text componentsSeparatedByString:@"\n"];
    //DLog(@"string count: %lu", (unsigned long)strings.count);

    NSColor* color = NSColor.clearColor;
#ifdef DEBUG
    color = [NSColor colorWithCalibratedRed:1 green:0 blue:0 alpha:0.5];
#endif
    SKSpriteNode* spritenode = [SKSpriteNode spriteNodeWithColor:color size:CGSizeMake(0, 0)];

    CGFloat totalheight = 0;
    CGFloat maxwidth = 0;

    NSMutableArray* labels = [NSMutableArray array];
    for (NSUInteger i = 0; i < strings.count; i++) {
        NSString* str = [strings objectAtIndex:i];
        const BOOL ISEMPTYLINE = [str isEqualToString:@""];

        if (!ISEMPTYLINE) {
            SKLabelNode* label = [SKLabelNode labelNodeWithFontNamed:fontname];
            label.text = str;
            label.fontColor = colorFont;
            label.fontSize = SIZEFONT;

            const CGSize SIZEOFLABEL = [label calculateAccumulatedFrame].size;
            if (SIZEOFLABEL.width > maxwidth)
                maxwidth = SIZEOFLABEL.width;
            totalheight += SIZEOFLABEL.height;
            [labels addObject:label];
        }
        else {
            totalheight += EMPTYLINEHEIGHT;
            [labels addObject:[NSNull null]];
        }
        if (i + 1 < strings.count)
            totalheight += VERTICALMARGIN;
    }
    spritenode.size = CGSizeMake(maxwidth, totalheight);

    //DLog(@"spritenode total size: %@", NSStringFromSize(spritenode.size));
    CGFloat y = spritenode.size.height * 0.5;
    const CGFloat X = 0;
    for (NSUInteger i = 0; i < strings.count; i++) {
        id obj = [labels objectAtIndex:i];
        if ([obj isKindOfClass:SKLabelNode.class]) {
            SKLabelNode* label = obj;
            label.verticalAlignmentMode = SKLabelVerticalAlignmentModeTop;
            label.position = ccp(X, y);
            [spritenode addChild:label];
            const CGSize SIZEOFLABEL = [label calculateAccumulatedFrame].size;
            y -= SIZEOFLABEL.height;
        }
        else {
            y -= EMPTYLINEHEIGHT;
        }
        if (i + 1 < labels.count)
            y -= VERTICALMARGIN;
    }

    return spritenode;
}

Btw vous aurez besoin de

static inline CGPoint ccp( CGFloat x, CGFloat y )
{
    return CGPointMake(x, y);
}
7
répondu Jonny 2013-10-18 09:27:39

ainsi après avoir fait un peu de recherche, j'ai appris que SkLabelNode N'était pas destiné à avoir des chaînes multilignes impliqués. Puisque la fonctionnalité est limitée avec SKLabelNode il est plus logique d'utiliser simplement un UILabel pour tenir la place de votre texte. Apprendre à intégrer les éléments de L'interface utilisateur en douceur dans sprite kit a rendu la vie beaucoup plus facile. Les éléments UI sont créés par programmation, et ajoutés à votre scène en utilisant

[self.view addsubview:(your UIelement)];

Donc tous les que vous avez à faire 1.Initialiser une instance de L'UIelement dans ce cas un UIlabel

UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(50, 50, 100, 100)];
label.backgroundColor = [UIColor whiteColor];
label.textColor = [UIColor blackColor];
label.text = @"helllllllllo";

2. Après que vous avez créé votre UIelement il suffit de l'ajouter à la vue en utilisant la méthode décrite ci-dessus

3.J'ai trouvé important de noter que les éléments de L'interface utilisateur et les éléments de SK n'interagissent pas de la même façon lorsqu'il s'agit du positionnement. Il y a quelques méthodes simples fournies telles que convertPointToView :

-(CGPoint)convertPointToView(CGPoint);

pour aider quand il s'agit de convertir des points. J'espère que aidé Bonne Chance!

7
répondu DanHabib 2015-10-26 21:58:22

j'ai écrit une solution pour Swift 3.

un projet de démonstration Xcode est disponible sur le projet open source GitHub: https://github.com/benmorrow/Multilined-SKLabelNode

voici l'extension SKLabelNode :

extension SKLabelNode {
  func multilined() -> SKLabelNode {
    let substrings: [String] = self.text!.components(separatedBy: "\n")
    return substrings.enumerated().reduce(SKLabelNode()) {
      let label = SKLabelNode(fontNamed: self.fontName)
      label.text = .element
      label.fontColor = self.fontColor
      label.fontSize = self.fontSize
      label.position = self.position
      label.horizontalAlignmentMode = self.horizontalAlignmentMode
      label.verticalAlignmentMode = self.verticalAlignmentMode
      let y = CGFloat(.offset - substrings.count / 2) * self.fontSize
      label.position = CGPoint(x: 0, y: -y)
      "151900920".addChild(label)
      return "151900920"
    }
  }
}

Voici comment vous l'utilisez:

let text = "hot dogs\ncold beer\nteam jerseys"
let singleLineMessage = SKLabelNode()
singleLineMessage.fontSize = min(size.width, size.height) /
  CGFloat(text.components(separatedBy: "\n").count) // Fill the screen
singleLineMessage.verticalAlignmentMode = .center // Keep the origin in the center
singleLineMessage.text = text
let message = singleLineMessage.multilined()
message.position = CGPoint(x: frame.midX, y: frame.midY)
message.zPosition = 1001  // On top of all other nodes
addChild(message)

voici à quoi ressemble l'application:

simulator screenshot multiline SKLabelNode

6
répondu Ben Morrow 2016-09-27 21:28:29

l'alternative est de créer une version bitmap du texte, puis d'utiliser l'image résultante avec un SKSpriteNode.

c'est plus facile que ça en a l'air.

un exemple, supposons que nous ayons une chaîne ou une chaîne attribuée et une variable CGSize avec la taille de la zone de texte résultante.

CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();

// Assuming size is in actual pixels. Multiply size by the retina scaling 
// factor if not.
CGContextRef context = CGBitmapContextCreate(NULL, (size_t)round(size.width), (size_t)round(size.height), 8, (size_t)round(size.width) * 4, rgbColorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedLast);

CGColorSpaceRelease(rgbColorSpace);

// Draw text, potentially flipping the coordinate system before 
// (depending on methods you use).
// Make sure that you draw the font twice as big for retina.
// E.g. [@"My text" drawInRect:rect withAttributes:attr];

// Once we have drawn the text, simply extract the image and
// Make a texture from it.

CGImageRef image = CGBitmapContextCreateImage(context);
SKTexture *texture = [SKTexture textureWithCGImage:image];
CGImageRelease(image);
CGContextRelease(context);

// Texture created, so make a sprite node to use it.
SKSpriteNode *node = [self node];
node.texture = texture;

// Set the node size to the size in non-retina pixels, so if size was with
// scale factor already multiplied in, then we would need to divide by the scale
// factor. 
node.size = size;
4
répondu Nuoji 2013-10-18 14:32:52

Ici juste pour apporter ma solution. Je me retrouve à vouloir la même chose-pour faire des multilignes de SKLabelNode à partir d'une longue chaîne. Le créer un par un et le positionner manuellement n'est pas pratique. Donc j'ai fait un moyen plus facile de faire le SKLabelNode multiligne. Cette méthode utilise SKLabelNodes (et ne pas capturer de texte dans l'image).

s'il vous Plaît voir ma solution si vous êtes intéressé: http://xcodenoobies.blogspot.com/2014/12/multiline-sklabelnode-hell-yes-please-xd.html

le résultat:

enter image description here

4
répondu GeneCode 2014-12-06 01:27:40

beaucoup de bonnes solutions ici, mais je n'en ai pas vu dans swift, donc nous y voilà. cette fonction va prendre une longue chaîne de caractères, et la briser où vous placez des caractères \n.

 func createMultiLineText(textToPrint:String, color:UIColor, fontSize:CGFloat, fontName:String, fontPosition:CGPoint, fontLineSpace:CGFloat)->SKNode{

    // create node to hold the text block
    var textBlock = SKNode()

    //create array to hold each line
    let textArr = textToPrint.componentsSeparatedByString("\n")

    // loop through each line and place it in an SKNode
    var lineNode: SKLabelNode
    for line: String in textArr {
        lineNode = SKLabelNode(fontNamed: fontName)
        lineNode.text = line
        lineNode.fontSize = fontSize
        lineNode.fontColor = color
        lineNode.fontName = fontName
        lineNode.position = CGPointMake(fontPosition.x,fontPosition.y - CGFloat(textBlock.children.count ) * fontSize + fontLineSpace)
        textBlock.addChild(lineNode)
    }

    // return the sknode with all of the text in it
    return textBlock
}
4
répondu FierceMonkey 2015-10-20 18:46:59

comme plusieurs autres, j'ai moi-même mis en place une solution à ce problème. Il s'agit d'une simple sous-classe SKLabelNode qui peut être utilisée en remplacement de la régulière SKLabelNode . Je trouve la sous-classification de la meilleure approche pour cette fonctionnalité que je l'utilise "partout" "tout le temps...

le tout est disponible à GitHub (pour toute personne intéressée) mais l'essentiel est comme suit: il sépare la chaîne et crée SKLabelNode régulière instances et annonces ces comme enfants du noeud. Ceci est fait chaque fois que setText: est invoqué:

- (void)setText:(NSString *)text{
    self.subNodes    = [self labelNodesFromText:text];
    [self removeAllChildren];
    for (SKLabelNode *childNode in self.subNodes) {
        [self addChild:childNode];
    }
    _text    = @""; // (synthesized in the implementation)
}

les sous-codes d'étiquette sont créés ici:

- (NSArray *)labelNodesFromText:(NSString *)text{
    NSArray *substrings    = [text componentsSeparatedByString:@"\n"];
    NSMutableArray *labelNodes    = [[NSMutableArray alloc] initWithCapacity:[substrings count]];

    NSUInteger labelNumber    = 0;
    for (NSString *substring in substrings) {
        SKLabelNode *labelNode    = [SKLabelNode labelNodeWithFontNamed:self.fontName];
        labelNode.text    = substring;
        labelNode.fontColor    = self.fontColor;
        labelNode.fontSize    = self.fontSize;
        labelNode.horizontalAlignmentMode    = self.horizontalAlignmentMode;
        labelNode.verticalAlignmentMode    = self.verticalAlignmentMode;
        CGFloat y    = self.position.y - (labelNumber * self.fontSize * kLineSpaceMultiplier); // kLineSpaceMultiplier is a float constant. 1.5 is the value I have chosen
        labelNode.position    = CGPointMake(self.position.x, y);
        labelNumber++;
        [labelNodes addObject:labelNode];
    }

    return [labelNodes copy];
}

comme vous l'avez peut-être remarqué, j'ai aussi une propriété subNodes (array). Ceci est utile ailleurs car l'implémentation complète permet également de changer n'importe laquelle des propriétés avec la syntaxe régulière SKLabelNode . (Texte, fontName, fontSize, alignement etc.)

2
répondu T. Benjamin Larsen 2014-04-18 10:01:53

si quelqu'un est intéressé, j'ai créé un meilleur SKLabelNode appelé SKLabelNodePlus qui a le soutien multi-ligne comme Chris Allwein, mais a aussi d'autres fonctionnalités que je trouve assez utile.

Check it out on GitHub:

https://github.com/MKargin0/SKLabelNodePlus

2
répondu MaxKargin 2015-08-24 12:01:36

Comme d'iOS 11/ macOS 10.13, SKLabelNode a une numberOfLines les biens qui se comporte d'une manière similaire à celle qui UILabel . Par défaut, il est défini à 1. Si vous le réglez à zéro, vous pouvez avoir un nombre illimité de lignes. Voir aussi lineBreakMode et preferredMaxLayoutWidth . J'ai pensé qu'il valait la peine de le souligner ici au cas où quelqu'un arrive à cette page avant qu'ils ne voient la documentation D'Apple. Si votre cible de construction minimale est iOS 11 / macOS 10.13, vous n'avez pas besoin des méthodes helper posté ci-dessus.

2
répondu OliverD 2018-06-26 20:19:29

à l'Aide https://github.com/downrightsimple/DSMultilineLabelNode et Comment écrire du texte sur l'image en Objective-C (iOS)? pour référence c'est ce que j'ai fait pour un moyen rapide et sale D'obtenir un SKNode d'emballage de texte (Xcode 7.1.1):

-(SKNode*)getWrappingTextNode:(NSString*)text maxWidth:(CGFloat)width {
    UIImage *img = [self drawText:text widthDimension:width];
    return [SKSpriteNode spriteNodeWithTexture:[SKTexture textureWithImage:img]];
}

-(UIImage*)drawText:(NSString*)text widthDimension:(CGFloat)width {
    NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
    paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
    paragraphStyle.alignment = NSTextAlignmentLeft; //or whatever alignment you want

    UIFont *font = [UIFont fontWithName:@"Verdana" size:22]; //or whatever font you want

    NSDictionary *att = @{NSFontAttributeName:font, NSParagraphStyleAttributeName: paragraphStyle};

    //using 800 here but make sure this height is greater than the potential height of the text (unless you want a max-height I guess but I did not test max-height)
    CGRect rect =  [text boundingRectWithSize:CGSizeMake(width, 800) options:NSStringDrawingUsesLineFragmentOrigin attributes:att context:nil];

    UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0f);

    [text drawInRect:rect withAttributes:att];

    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return newImage;
}
1
répondu salami 2017-05-23 12:34:29

Voici une fonction rapide et facile que j'ai écrite pour rendre la vie plus facile.

Étape 1) passer dans une chaîne, obtenir un SKSpriteNode.

Étape 2) Ajouter le noeud sprite à la scène.

    /******************************************************************************/
- (SKSpriteNode*) ConvertString: (NSString*) str
                   WithFontSize: (NSInteger) font_size
            ToParagraphWithSize: (CGSize) para_size
{
   SKSpriteNode* paragraph = [[SKSpriteNode alloc] initWithColor: [SKColor clearColor]
                                                            size: para_size];

   // Set the anchor point to the top left corner. This is where English
   // paragraphs usually start
   paragraph.anchorPoint = CGPointMake(0,1);

   // Create an array to hold multilple sub strings.  These sub strings will
   // become multiple SKLabels that will be added to the paragraph sprite node
   // created above
   NSMutableArray* str_arr = [[NSMutableArray alloc] init];

   // Lets separate words by a single space.
   NSArray* word_arr = [str componentsSeparatedByString:@" "];

   // 50% is an approximate character height to width ratio.  Change this
   // number to adjust the number of characters per line you would like.
   // Increase it if you have a lot of capitol W's
   float est_char_width = font_size * 0.50;
   NSInteger num_char_per_line = para_size.width / est_char_width;

   // For every word in the original string, make sure it fits on the line
   // then add it to the string array.
   NSString* temp_str = @"";
   for (NSString* word in word_arr)
   {
      if ((NSInteger)word.length <= num_char_per_line - (NSInteger)temp_str.length)
      {
         temp_str = [NSString stringWithFormat:@"%@ %@", temp_str, word];
      }
      else
      {
         [str_arr addObject: temp_str];
         temp_str = word;
      }
   }
   [str_arr addObject: temp_str];

   // For every sub string, create a label node and add it to the paragraph
   for (int i = 0; i < str_arr.count; i++)
   {
      NSString* sub_str = [str_arr objectAtIndex: i];
      SKLabelNode* label = [self CreateLabelWithText: sub_str];
      label.fontSize = 14;
      label.position = CGPointMake(0, -(i+1) * font_size);
      [paragraph addChild: label];
   }

   return paragraph;
}


/******************************************************************************/
- (SKLabelNode*) CreateLabelWithText: (NSString*) str
{
   enum alignment
   {
      CENTER,
      LEFT,
      RIGHT
   };

   SKLabelNode* label;
   label = [SKLabelNode labelNodeWithFontNamed:@"ChalkboardSE-Light"];
   label.name = @"label_name";
   label.text = str;
   label.zPosition = 1;
   label.horizontalAlignmentMode = LEFT;
   label.fontColor = [SKColor whiteColor];

   return label;
}
0
répondu Sean Mayes 2014-12-30 21:03:04

j'ai écrit une méthode utilitaire pour prendre une chaîne et la diviser en un tableau de chaînes avec une longueur maximale donnée. Il termine automatiquement chaque ligne avec un mot entier et supprime les espaces de tête. Espérons que cela aide quelqu'un!

- (NSArray*)linesFromString:(NSString*)string withMaxLineLength:(int)maxLineLength;
{
    NSMutableArray *lines = [NSMutableArray arrayWithCapacity:1];

    BOOL gotLine = NO;
    BOOL doneFormat = NO;
    BOOL endOfString = NO;
    int innerLoops = 0;
    int outerLoops = 0;
    int lineIndex = 0;
    int currentStringIndex = 0;
    int stringLength = (int)[string length];
    int rangeLength = maxLineLength;
    NSString *line;
    NSString *testChar;
    NSString *testChar2;

    while (!doneFormat) {
        outerLoops++;
        while (!gotLine) {
            endOfString = NO;
            innerLoops++;
            line = [string substringWithRange:NSMakeRange(currentStringIndex, rangeLength)];

            testChar = [line substringWithRange:NSMakeRange(0, 1)];
            if (currentStringIndex + rangeLength > [string length] - 1) {
                endOfString = YES;
            } else {
                testChar2 = [string substringWithRange:NSMakeRange(currentStringIndex + rangeLength, 1)];
            }

            //If the line starts with a space then advance 1 char and try again.
            if ([testChar isEqualToString:@" "]) {
                currentStringIndex++;
                // If we were at the end of the string then reduce the rangeLength as well.
                if (endOfString) {
                    rangeLength--;
                }

            // else, if this line ends at the end of a word (or the string) then it's good. ie next char in the string is a space.
            } else if ([testChar2 isEqualToString:@" "] || endOfString) {

                gotLine = YES;
                currentStringIndex += [line length];

            // else, make the line shorter by one character and try again
            } else if (rangeLength > 1){
                rangeLength--;

                // Otherwise the word takes up more than 1 line so use it all.
            } else {
                line = [string substringWithRange:NSMakeRange(currentStringIndex, maxLineLength)];
                currentStringIndex += [line length];
                gotLine = YES;
            }

            // Make sure we're not stuck in an endless loop
            if (innerLoops > 1000) {
                NSLog(@"Error: looped too long");
                break;
            }
        }

        // If we processed a line, and the line is not nil, add it to our array.
        if (gotLine && line) {
            [lines insertObject:line atIndex:lineIndex];
            lineIndex++;
        }

        // Reset variables
        rangeLength = maxLineLength;
        gotLine = NO;

        // If the current index is at the end of the string, then we're done.
        if (currentStringIndex >= stringLength) {
            doneFormat = YES;

        // If we have less than a full line left, then reduce the rangeLength to avoid throwing an exception
        } else if (stringLength - (currentStringIndex + rangeLength) < 0) {
            rangeLength = stringLength - currentStringIndex;
        }

        // Make sure we're not stuck in an endless loop
        if (outerLoops > 1000) {
            NSLog(@"Error: Outer-looped too long");
            break;
        }
    }
    return lines;
}

et puis je l'appelle et je crée quelques noeuds label pour ajouter à mon noeud de calque comme suit. J'ai aligné mes étiquettes de ligne en dessous et avec le bord gauche de button2, donc tout est aligné à gauche justifié.

CGFloat fontSize = 30.0f;
int lineCount;
NSString *description = [product localizedDescription];
NSString *line;
NSArray *lines = [self linesFromString:description withMaxLineLength:43];

if (lines) {

    lineCount = (int)[lines count];

    for (int i = 0; i < lineCount; i++) {

        line = [lines objectAtIndex:i];

        // Create a new label for each line and add it to my SKSpriteNode layer
        SKLabelNode *label = [SKLabelNode labelNodeWithFontNamed:@"Superclarendon-Black"];
        label.text = line;
        label.fontSize = fontSize;
        label.scale = 1.0f;
        label.name = @"lineLabel";
        label.fontColor = [UIColor blackColor];
        label.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeLeft;
        label.position = CGPointMake(button2.position.x - button2.size.width * 0.5f, button2.position.y - button2.size.height - i * fontSize * 1.1);
        [layer addChild:label];
    }
}
0
répondu Robert Wasmann 2016-05-12 21:10:28