Use timer or interval for procedure calls

Hi!
I want to display a string, not at once but like a typewriter, so character by character.
I have the following procedure that works okay but it displays the whole string because there is no delay between the typing.
How can I get this to work by using a Timer so the procedure will be called every 2 seconds to add a new character of the string?

procedure TViewMain.DisplayStringByChar(TextToDisplay: string);
var
  i: Integer;
begin
  TextLabel.Caption := '';

  for i := 1 to Length(TextToDisplay) do
  begin
    TextLabel.Caption := TextLabel.Caption + TextToDisplay[i];
  end;

end;

TCastleLabel has a special property MaxDisplayChars Castle Game Engine: CastleControls: Class TCastleLabel

This way making a typing label is as simple as (pseudocode, it may need some debugging before works):

type
  TTypingLabel = class(TCastleLabel)
  strict private
    DisplayChars: Integer;
    TypingTimer: TTimerResult;
  public
    procedure Update(const SecondsPassed: Single); override;
    constructor Create(AOwner: TComponent); override;
  end;

constructor TTypingLabel .Create(AOwner: TComponent); override;
begin
  inherited;
  TypingTimer := Timer;
end;

procedure TTypingLabel .Update(const SecondsPassed: Single); override;
const
  TypingSpeed = 40; // symbols per second
begin
  inherited;
  DisplayChars := TypingSpeed * TypingTimer.Elapsed;
end;

Of course you can hack a “regular” TCastleLabel to work in the same way but IMHO it’s more convenient to create an own override.

A bit more “complete” (and debugged) version: EugeneLoza / Typing Label · GitLab you can also see the example there.

Note that you don’t need a special timer (TypingTimer) in @eugeneloza example, at least in such simple case :slight_smile: Instead you get the SecondsPassed in Update. So something like this may be easier:

type
  TTypingLabel = class(TCastleLabel)
  strict private
    // float type, increased each frame
    DisplayChars: TFloatTime;
  public
    procedure Update(const SecondsPassed: Single); override;
  end;

procedure TTypingLabel .Update(const SecondsPassed: Single); override;
const
  TypingSpeed = 40; // symbols per second
begin
  inherited;
  DisplayChars := TypingSpeed * SecondsPassed;
  MaxDisplayChars := Trunc(DisplayChars);
end;

Alternatively, you can use regular TCastleLabel (advantage: you can easily place it at design-time, and no need CGE “custom components”) and modify it from outside in your view. Like

type
  TViewMain = ...
  published
    MyLabel: TCastleLabel;
  strict private
    // float type, increased each frame
    DisplayChars: TFloatTime;
    ....
  public
    procedure Update(const SecondsPassed: Single; var HandleInput: Boolean); override;
  end;

procedure TViewMain.Update(const SecondsPassed: Single; var HandleInput: Boolean);
const
  TypingSpeed = 40; // symbols per second
begin
  inherited;
  DisplayChars := TypingSpeed * SecondsPassed;
  MyLabel.MaxDisplayChars := Trunc(DisplayChars);
end;

Note: Above code was untested, but it will hopefully be enough to get you started :slight_smile:

Thanks, both @eugeneloza and @michalis
:slight_smile:

I did not get it to work, maybe because of the other stuff I already had put in the label and using Trunc was way too fast, the string was gone in a flash.
Then I remembered an old topic where eugeneloza gave me a tip about timed events and disappearing label after showing text.
I used this and expanded it and with a little additional help by ChatGPT I finally got this:

TAdvancedLabel = class(TCastleLabel)
  public
    Timeout: Single;
    DisplayCharacters: Integer;
    CurrentIndex: Integer;
   
    procedure Update(const SecondsPassed: Single;
      var HandleInput: boolean); override;
  end;  

 TViewMain = class(TCastleView)
  published  
   TextLabel: TAdvancedlabel; 
 private
   message: String;
end;
       

procedure TAdvancedLabel.Update(const SecondsPassed: Single;
      var HandleInput: boolean);
begin
  Timeout -= SecondsPassed;
  if CurrentIndex < Length(ViewMain.Message) then
  begin
    Inc(CurrentIndex);
    ViewMain.TextLabel.Caption := Copy(ViewMain.Message, 1, CurrentIndex);
  end;

  if Timeout < 0 then
  begin
    Text.Clear;
    CurrentIndex:= 1;
  end;
end;     

And now the text is scrolling from first to last character allright and also disappears after a few seconds.
I am now expanding this with an up and down moving textbar as long as there is a “conversation”.
When it is finished I will put it here in a Youtube video.
:slight_smile:

1 Like