Typing text function to automatically put words on appropriate line

Started by Kinoko, Fri 07/10/2005 13:10:43

Previous topic - Next topic

Kinoko

Rather than dredge up one of the many threads on this topic, I'll start a new one for the sake of neatness.

Everyone here helped me with this TypeLine function that allows me to have text "type" out letter by letter. I've extremely grateful. This is only a small matter but I was wondering if anyone knows how I could modify this code so that, as the text is typing out, instead of typing a word at the end of a line then flipping it to the next line when the word becmes too big... instead of that, if somehow the word was known to be too big before it starts getting typed out and automatically starts the next line over.

Probably not possible but I thought I'd ask. I've been trying to think of how to do it by getting the text after/before a space read but... I can't figure out how it could be done.

Code: ags

function TypeLine (int message, int xpos, int vpos, int width, int delay, int wait) {
if (IsGamePaused()==0) {
	GetMessageText(message, line);
	
	StopMoving(EGO);
	SetTextWindowGUI(1);
	  textid = 0;
	  length = 0;  
	  int i=0; 
	  StrCopy(displayedline,"");
	  textid = CreateTextOverlay(xpos, 100, 400, 1, 1, displayedline);
	
		  //get string length, to know how long the loop must run
	  length=StrLen(line); //set string length
		 //start loop
	while((i<length) && (1||(WaitKey(delay)==0))) {
		 // pick character at position "i"and stick it at the end of string "displayed line"
	    StrFormat(displayedline, "%s%c", displayedline, StrGetCharAt(line,i));
		 //set textoverlay as displayedline
	    SetTextOverlay(textid,xpos,vpos,width,1,1,displayedline); 
	
		 // if a space is added to the string do not play a sound, else play a 'tick'
	    if (StrGetCharAt(line,i) == ' '){
	   }
	   else{
		PlaySound(1);
	    }
		   //increase the loop counter
	    i++;  
	   if (i==length) if (wait>0) WaitKey(wait);
	   else if (wait == -1) while (IsKeyPressed(keyx) == 0) Wait(1);
	   }
	 RemoveOverlay(textid);
	 Wait(5);
}
}

GarageGothic

Actually this shouldn't be too difficult. I won't try to write any code here, because then other people will spend most of the day trying to fix my mistakes. But the main concept would be to add a sub-routine to "read ahead" a whole word (until a space is found), pass it to a string andÃ,  decide if the word fits or should be on the next line, then print the single-word string character by character instead of the whole line as you are currently doing.

Basically just GetCharAt and if char is not a space, StrCat it into a single word string. If on the other hand it's space, start typing that string out.

Am I making any sense?

Edit: Um, sorry, I just now realize that you're using overlays. I've been working with RawPrint for too long I suppose. Well, the theory should work anyway.

RickJ

I took a shot at re-writting your function to work the way you describe. 

Before displaying characters it is first necessary to replace the last space charater before the max pixel width is exceeded with a line break character.  This is done by copying characters one by one to a buffer and then checking it's their pixel width.  When the width of the line buffer exceeds the overlay width the message buffer is searched backwards (i.e. towards the beginning) for the first space character.  That space character is replaced by a line break character.  The line buffer is then cleared and the whole process repeats until the end of the message buffer is reached.

Now the message buffer should have line breaks at the proper points so that the behaviour you describe never occurs.   You may want to conduct some tests to be sure that the hardcoded 1 pixel margin is large enough enough to account for the line break etc and so that spurious blank lines are not generated.   The message buffer can now be displayed one character at a time similar to the way it was done in your original script. 

I took the liberty of updating some more things to the new style scripting as well.  Anyway here is my approach to this.  I haven't compiled or tested it but it should give you an idea how to implement such a thing and probably close to being in working condition.

function TypeLine (int message, int xpos, int vpos, int width, int delay, int wait) {
   int    font=1;                           // set to font id for overlay
   int    b, i, ovr, msglen;                // declare local variables for use within 
   char   msg[200], buf[200];               // this function only
   Overlay *ovr;
   
   // If game is paused do nothing, otherwise display the message 
   // one character at a time.
   if (IsGamePaused()==0) {                  

      // Initialize some things
      GetMessageText(message, msg);         
      player.StopMoving();
      SetTextWindowGUI(1);
      ovr = Overlay.CreateTextual(xpos, 100, width+1, font, 1, "");

      // Get message and insert line breaks 
      i = 0;
      b = 0;
      msglen = StrLen(msg);                // Get number of characters in message
      while (i<msglen) {
         // Copy message to line buffer
         buf = msg;
         i++;
         b++;
         buf = 0;
         
         // When pixel width of line buffer is greater than the width of the 
         // overlay buffer is full and is displayed.
         if (width<GetTextWidth(buf,font)) {
            while ((msg!=' ')&&(i>=0)) {   // Backup to last space character
               i--;
            }
            msg='[';                        // Replace the space with a line break
            i++;
            b = 0;                           // Clear line buffer for next line  
         }
      }
      
      // Display Message
      i = 0;
      b = 0;
      while (i<msglen) {
         buf = msg;                      // Buildup display buffer char by char
         b++;
         i++;
         buf = 0;         
         ovr.SetText(width,font,1,text);
         if ((buf!=' ')&&([buf!='[') { // If not space or line break play sound
            PlaySound(1);         
         }
         // Wait for awhile before displaying next character
         if (wait>0) WaitKey(wait);
         else if (wait == -1) while (IsKeyPressed(keyx) == 0) Wait(1);
      }
      // Remove text from screen
      ovr.Remove()
      Wait(5);
   }
}


Kinoko, In case you or anyone else is interested here are a couple of tips on how I think you can improve your scripts.     
Quote
if (IsGamePaused()==0) {
:
int i=0;
Not sure what they say in academia but I prefer not to decalre variables in the bowels of conditional statements.  IMHO, it's error prone and makes it harder to read the script.

Quote
textid = 0;
:
textid = CreateTextOverlay(xpos, 100, 400, 1, 1, displayedline);
It is redundant to set a variable to zero and a few lines of code later set it to soemthing else.   Since it doesn't do anything you can eliminate it to make the script easier to read and understand.

Quote
textid = 0;
length = 0;
StrCopy(displayedline,"");
GetMessageText(message, line);
Since these should be declared within the function they are used.  Declaring them outside means that they are static and that other scripts have access to them.    It is usually to declare these kinds of variables inside the functions where they are used unless there is reason to remember the data after the function has finished executing. 

Quote
function TypeLine (int message, int xpos, int vpos, int width/b], int delay, int   
:
textid = CreateTextOverlay(xpos, 100, 400, 1, 1, displayedline);
You pass in a pixel width in the function and then use a hardcoded value of 400.  I'm not sure if this is an error or if this has some utility I missed?

=========================
Anyway good luck with this.  Let me know if have any trouble getting it to work.

Kinoko

Thanks for the help, guys!

I get an error at the line; overlay *ovr

"Variable is already defined"

RickJ

Like I said I didn't do any testing or compiling.   However, looking at my script the eror seems to be caused by this line. 

Remove ovr from here:  "int    b, i, ovr, msglen; "

It was left over from before upgrading to the new style overlay commands.

Cheers

Kinoko

Ah, thanks. I now get 'undefined symbol' for "text". I'm not sure what to replace it with (if that)

Gilbert

Try changing the line:
ovr.SetText(width,font,1,text);
to:
ovr.SetText(width,font,1,buf);

and see if it works

Kinoko

That worked, thanks.

Er... now I have a prse error in:          if ((buf!=' ') && ([buf!='[')) { // If not space or line break play sound

Guessing it doesn't like those [s

Gilbert

Remove that red [ :

if ((buf!=' ') && ([buf!='[')) { // If not space or line break play sound

If it still doesn't work, use:
if ((buf!=' ') && (buf!=91)) { // If not space or line break play sound

Unless I looked it wrong from the table, ASCII value of [ is 91.

Kinoko

Okay, thanks, got it going now.

It looks like the code is cutting the speech off at the first character though. It either writes only the first character or nothing at all (but the text box still appears)

RickJ

Here is a debugged version of the above script.   Let me know if it works for you.
function TypeLine (int message, int xpos, int vpos, int width, int delay, int wait) {
   int    font=1;                           // set to font id for overlay
   int    b, i, j, k, msglen;             // declare local variables for use within 
//   int    keyx=0;
   char   msg[200], buf[200];               // this function only
   Overlay *ovr;
   
   // If game is paused do nothing, otherwise display the message 
   // one character at a time.
   if (IsGamePaused()==0) {                  

      // Initialize some things
      GetMessageText(message, msg);         
      player.StopMoving();
      ovr = Overlay.CreateTextual(xpos, 100, width, font, 15, "");
      WaitMouseKey(50);

      // Get message and insert line breaks 
      i = 0;
      b = 0;
      j = 0;
      k = -1;
      msglen = StrLen(msg);                // Get number of characters in message
      while (i<msglen) {
         // Copy message to line buffer
         buf = msg;
         if (msg==' ') {               // Remember location of last space
            k = j;
            j = i;
         }
         i++;
         b++;
         buf = 0;
         
         // When pixel width of line buffer is greater than the width of the 
         // overlay buffer is full and is displayed.
         if ((width<GetTextWidth(buf,font)+10)&&(j!=k)) {   // j==k when word > width pixels
            k = j;
            i = j;
            msg='[';                        // Replace the space with a line break
            i++;
            b = 0;                           // Clear line buffer for next line  
         }
      }
      
      // Display Message
      i = 0;
      b = 0;
      while (i<msglen) {
         buf = msg;                      // Buildup display buffer char by char
         b++;
         i++;
         buf = 0;         
         ovr.SetText(width,font,15,buf);
         if ((buf!=' ')&&(buf!='[')) {   // If not space or line break play sound
            PlaySound(1);         
         }
         // Wait for awhile before displaying next character
         if (wait>0) WaitKey(wait);
         else if (wait == -1) while (IsKeyPressed(keyx) == 0) Wait(1);
      }
      // Remove text from screen
      Wait(15);
      ovr.Remove();
   }
}

Kinoko

Same thing, I'm afraid. The first letter appears, then the text box just sits there until I press the button (keyx), then the text box appears for about half a second completed and disappears.

RickJ

Hmm,  it worked fine for me.   I had the keyx variable defined and initialized to zero though.  I commented it out because I assumed you had it defined somewhere else.  I guess you could could try renaiming keyx in my script to keyy and uncommenting the declaration and see what happens.   I also specified a value for the wait parameter when I called TypeLine as well, so you can also try that. 

I didn't really take the time to understand how that bit of code was working, just assumed it worked the way you wanted it to work.
Quote
         // Wait for awhile before displaying next character
         if (wait>0) WaitKey(wait);
         else if (wait == -1) while (IsKeyPressed(keyx) == 0) Wait(1);


Kinoko

That bit just determines the speed of the "typing", so it waits the specified time (wait) after typing each character.

RickJ

The way I read it if a positive value is specified for wait then one character is printed  every "wait" game cycles.  If minus one is specified for the wait parameter then the time between characters is dependent on some kind of keyboard action.   Since I have no idea where or how the value of the variable keyx is determined I can't say more. 

When I tested my script keyx was defined within the bounds of the function and permantely set to zero.   I am not able to help any more from thje information you provided.  You could try putting some "Display" statements at various points in the script to see what it's doing.   The first one that comes to mind is to display the message buffer, msg, after the new line characters are inserted.   This will show you the modified message.   
     :
            b = 0;                           // Clear line buffer for next line  
         }
      }

      Display(msg);    // Add this line for debug purposes
      // Display Message
     :

If it looks ok then you can focus on the code that follows the "// Display Message" comment.

   

Kinoko

Sorry to only get back to this now. Had to put this problem aside for a bit to focus on other things.

Anyway, I put that line in and the message displayed fine. If I then press 'x' (as defined by keyx), it disappears and reappears for only a fraction of a second at the top of the screen.

However, if instead of pressing 'x', I press any other key, the test message disappears and another box appears with only the first letter of the message displayed. Then, I can only press 'x' again to get rid of it, and again, the full message appears at the top of the screen for only a moment.

'keyx' is defined at the start of my game as uh, they key 'x' (but the player can change this which is why I used a variable)

SSH

I think there has been some confusion about what you want, Kinoko. Should pressing x speed up the typing to display the whole message? If so, you want:

Code: ags

int count=wait;
while (count >= 0 && IsKeyPressed(keyx)==0) {
 Wait(1);
 count--;
}

12

Kinoko

No, not at all. I just want the text to be typed on the correct line it fits on. Currently, as it's being "typed out", when the word gets too long it jumps to the next line, which looks somewhat untidy.

'x' is simply the button that removes the text window. No other function.

SSH

OK, so pressing x just clears the text away. So why is the check for it inside the loop?


In other news, the Credits module by SSH is now available, with typing mode
12

SMF spam blocked by CleanTalk