views:

341

answers:

2

I have a little 2D tile-based OpenGL ES game on the iPhone in which a dialogue box will appear on the screen when talking to characters in the game. I want to know how to display the text (bitmap font) gradually, not the entire string at once but, how they do it in the Zelda games, letter by letter with the little command-prompt looking underscore leading the text...Does anyone know what I am talking about?

P.S. - I currently use a method called -drawStringAtPoint (AngelCode bitmap-font library) to get my strings on the screen via bitmap fonts. But I cannot figure out how to do multiple lines or display the text little by little...

//From AngelCodeFont.m

// Changed 07/05/09 to add kerning
- (void)drawStringAt:(CGPoint)point text:(NSString*)text {

    // TODO: Add error if string is too long using NSASSERT
    //NSAssert(1>0, @"WARNING: Text to be rendered is too long");

    // Reset the number of quads which are going to be drawn
    int currentQuad = 0;

    // Enable those states necessary to draw with textures and allow blending
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_BLEND);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glEnableClientState(GL_VERTEX_ARRAY);

    // Setup how the text is to be blended
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    // Bind to the texture which was generated for the spritesheet image used for this font.  We only
    // need to bind once before the drawing as all characters are on the same texture.
    if([[image texture] name] != [_director currentlyBoundTexture]) {
     [_director setCurrentlyBoundTexture:[[image texture] name]];
     glBindTexture(GL_TEXTURE_2D, [[image texture] name]);
    } 

    // Set up the previous character and kerning amount vars
    unichar previousChar = -1;
    int kerningAmount = 0;

    // Loop through all the characters in the text
    for(int i=0; i<[text length]; i++) {

     // Grab the unicode value of the current character
     unichar charID = [text characterAtIndex:i];

     // Look up the kerning information for the previous char and this current char
     kerningAmount = [self kerningAmountForFirst:previousChar second:charID];

     // Move x based on the kerning info
     point.x += kerningAmount * scale;

     // Only render the current character if it is going to be visible otherwise move the variables such as currentQuad and point.x
     // as normal but don't render the character which should save some cycles
     if(point.x > 0 - ([charsArray[charID] width] * scale) || point.x < [[UIScreen mainScreen] bounds].size.width || point.y > 0 - ([charsArray[charID] height] * scale) || point.y < [[UIScreen mainScreen] bounds].size.height) {

      // Using the current x and y, calculate the correct position of the character using the x and y offsets for each character.
      // This will cause the characters to all sit on the line correctly with tails below the line etc.
      CGPoint newPoint = CGPointMake(point.x, 
                point.y - ([charsArray[charID] yOffset] + [charsArray[charID] height]) * [charsArray[charID] scale]);

      // Create a point into the bitmap font spritesheet using the coords read from the control file for this character
      CGPoint pointOffset = CGPointMake([charsArray[charID] x], [charsArray[charID] y]);

      // Calculate the texture coordinates and quad vertices for the current character
      [[charsArray[charID] image] calculateTexCoordsAtOffset:pointOffset subImageWidth:[charsArray[charID] width] subImageHeight:[charsArray[charID] height]];
      [[charsArray[charID] image] calculateVerticesAtPoint:newPoint subImageWidth:[charsArray[charID] width] subImageHeight:[charsArray[charID] height] centerOfImage:NO];

      // Place the calculated texture coordinates and quad vertices into the arrays we will use when drawing our string
      texCoords[currentQuad] = *[[charsArray[charID] image] textureCoordinates];
      vertices[currentQuad] = *[[charsArray[charID] image] vertices];

      // Increment the Quad count
      currentQuad++;
     }

     // Move x based on the amount to advance for the current char
     point.x += [charsArray[charID] xAdvance] * scale;

     // Store the character just processed as the previous char for looking up any kerning info
     previousChar = charID;
    }

    // Now that we have calculated all the quads and textures for the string we are drawing we can draw them all
    glVertexPointer(2, GL_FLOAT, 0, vertices);
    glTexCoordPointer(2, GL_FLOAT, 0, texCoords);
    glColor4f(colourFilter.red, colourFilter.green, colourFilter.blue, colourFilter.alpha * [_director globalAlpha]);
    glDrawElements(GL_TRIANGLES, currentQuad*6, GL_UNSIGNED_SHORT, indices);
    glDisable(GL_TEXTURE_2D);
    glDisable(GL_BLEND);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_VERTEX_ARRAY);
}
+5  A: 

Just draw the text several times, each time including one more character in the string.

In pseudo-code:

n = string length
for i=0 to n-1 {
    draw_text(substring from 0 to i + the underscore character)
    wait a couple of milliseconds
}
draw_text(entire string without the underscore character)

And obviously, waiting "a couple of milliseconds" will naturally make everything stop, so this has to happen either in a separate thread, or as a tick in a game loop.

csl
This however won't work in a standard game loop, I think Krypes' answer works better for that.
ReaperUnreal
This is pseudo-code, and illustrates the _general_ idea. Krypes' answer is good, but that one has a problem with the time stepping -- imagine what will happen if the framerate suddenly drops. Then the text will appear in a jiggery fashion. :-)
csl
+3  A: 

doing for i = 0 -> n -1 { draw text } means nothing else can happen while you render the text. Animate the typing text effect like you would anything else, incrementing animation state using the tickcount between draws for framerate independent animation speed.

struct FancyText
{
  char *text;
  double cps; // chars per second
  double len = 0;
}

void render(CGpoint* point, FancyText *fancy, double delta)
{
  fancy->index += delta * cps;

  drawStringAt(point, fancy->text, floor(index));
}

void drawStringAt(CGpoint *point, char *text)
{
  drawStringAt(point, text, strlen(text));
}

void drawStringAt(CGPoint *point, char *text, int len)
{
  // your drawstring code
  // using for i = 0 -> len - 1 instead of strlen(text)
}
Krypes
what does delta do? I see it used alot in game code.
RexOnRoids
delta == the difference between two values; mostly used for the time between two ticks. Ex: 1 min 30 sec. ... time pass... 1 min 31 sec. => delta: 1 sec.
Jodi
Check out "fix your timestep": http://gafferongames.com/game-physics/fix-your-timestep/
csl