Is there a way to use Character.SayBackground in a specific X and Y coordinates?

Started by Gal Shemesh, Tue 22/08/2023 11:26:27

Previous topic - Next topic

Gal Shemesh

Hi everyone,

Is there a way to show the Character.SayBackground function at a specific X and Y position?

I have a sound clip of a few characters saying something together, and I use Character.SayBackground in order to present the said text above each of the characters' heads, which works great. However, one of the characters has a huge gap of transparent area in his graphics (not something that I can change now as it will affect the entire game). So I just wish to show his text in a non blocking way a little lower, in relation to his y position.

Thanks
Gal Shemesh,
goldeng

Khris

Can you be more specific about the gap? Why can't you temporarily use a view without the gap? Or remove it altogether?

Snarky

The easiest thing is going to be making an invisible character to act as the speaker, which you can position as needed.

Crimson Wizard

Character.SayBackground creates a textual overlay in a position based on character's position.
The direct equivalent is Overlay.CreateTextual. This may be used to write custom speech too.

The benefits of using another character with SayBackground are that it calculates the position according to the same rule, and has its own internal timer for removing the overlay.
The benefit of using Overlay.CreateTextual is that you don't need dummy character(s). Note that unlike blocking speech, where you may use 1 dummy character always, for non blocking speech you need as many dummy characters as many simultaneous background speeches are there.

Gal Shemesh

EDIT:
Quote from: Khris on Tue 22/08/2023 11:48:21Can you be more specific about the gap? Why can't you temporarily use a view without the gap? Or remove it altogether?
I didn't want to begin modifying too much in the game's code, especially in the sprirtes, as it's not a game of mine but a friend's and has a very old code, and any change I may make could make something to behave unintendedly somewhere else in the game, such as 'jumpy' animations because some sprites may have a gap while ones that I may had fixed won't.

Thanks for the info, CW.
On the internal timer topic of the SayBackground overlay - how does it work? I mean, I tried using it in another place in the game to make a character speak in the background, writing his cues line by line and expected that there will be an automatic timer that removes each message after a period of time, but all I got was only the last message in the sequence showing up...
Gal Shemesh,
goldeng

Crimson Wizard

Quote from: Gal Shemesh on Tue 22/08/2023 14:17:55On the internal timer topic of the SayBackground overlay - how does it work? I mean, I tried using it in another place in the game to make a character speak in the background, writing his cues line by line and expected that there will be an automatic timer that removes each message after a period of time, but all I got was only the last message in the sequence showing up...

Because SayBackground is not a blocking action, the script continues further and all SayBackgrounds execute before screen can be redrawn once. Since only one speech may be displayed for same character at the same time, each next command was overriding the previous one.
If you need a sequence of non-blocking actions, that work in parallel to the rest of the game, the solution is to remember current "state" and test for this state in repeatedly-execute function(s).
In case of SayBackground, it returns an Overlay pointer, which you may store in a variable and test for Overlay.Valid to know when it's removed, so that you could continue with the next SayBackground.
The alternative is to use your own timer.


Gal Shemesh

Quote from: Crimson Wizard on Tue 22/08/2023 14:43:10In case of SayBackground, it returns an Overlay pointer, which you may store in a variable and test for Overlay.Valid to know when it's removed, so that you could continue with the next SayBackground.
That's an interesting one - can you kindly explain how do I save the Overlay pointer of SayBackground and check for it using the Overlay.Valid for triggering the next speech line?

I saw in the manual the 'Overlay.CreateTextual' example, though it doesn't speak on the specific SayBackground function. I made a custom SayBackground function called 'SayBackgroundNew' and it looks like this:

Code: ags
void SayBackgroundNew(this Character*, int cue, String text)
{
  String cueString = String.Format("&%d", cue);
  {
    if (Speech.VoiceMode == eSpeechVoiceAndText && IsSpeechVoxAvailable())
    {
      Game.PlayVoiceClip(this, cue);
      this.SayBackground(text);
    }
    else if (Speech.VoiceMode == eSpeechVoiceOnly && IsSpeechVoxAvailable())
    {
      this.Say(cueString);
    }      
    else if (Speech.VoiceMode == eSpeechTextOnly)
    {
      this.SayBackground(text);
    }
  }
}

And I call for it in the 'room_RepExec()' like this:

Code: ags
player.SayBackgroundNew(1, "first speech cue text");
player.SayBackgroundNew(2, "second speech cue text");

Thanks
Gal Shemesh,
goldeng

Crimson Wizard

There are two general approaches to waiting for something to finish in AGS:

1. A (simulated) blocking approach where you run a loop with Wait(1) inside, checking for some condition.
2. A non-blocking approach, where you start something, save in a global variable, and then checking this in repeatedly execute.

The most primitive blocking approach looks like this:
Code: ags
Overlay* over = player.SayBackground(text);
while (over.Valid)
{
   Wait(1);
}

A non-blocking approach requires you to save the thing you are checking in a global variable when you start an action, and check that variable in a rep exec.

But if there are several actions that may be run in sequence, or in parallel, then you would need to schedule following actions in an array. In more complex cases you'd need to have a struct with multiple data inside, or array of structs.

You would also need to store an index of a sequence state, because you must somehow tell which next action to run after the previous was completed.

This altogether may lead to necessity of designing a system, which stores and handles all this data...

For some relatively basic example, this is running consecutive saybackgrounds (one at a time):
Code: ags
// global variables declared somewhere
Overlay* BgSpeech; // current bg overlay
String BgLines[100]; // store text here
int TotalBgLines; // total scheduled background lines
int BgSpeechIndex; // next line to play


// Schedule a bg line
function AddBgSpeech(String text)
{
    BgLines[TotalBgLines] = text;
    TotalBgLines++;
}

// Reset all bg lines
function ClearBgSpeech()
{
    for (int i = 0; i < TotalBgLines; i++)
    {
       BgLines[i] = null; // clear strings from memory
    }
    TotalBgLines = 0;
    BgSpeechIndex = 0;
}

function RunNextBgSpeech()
{
    if (BgSpeechIndex >= TotalBgLines)
    {
        return; // nothing was scheduled or completed all lines
    }

    if (BgSpeech != null && BgSpeech.Valid)
    {
        return; // previous bg speech is still visible
    }

    // Choose which speech to run next
    BgSpeech = player.SayBackground(BgLines[BgSpeechIndex]);
    BgSpeechIndex++; // increment speech index for the next time

    // Check if completed all scheduled lines
    if (BgSpeechIndex == TotalBgLines)
    {
        BgSpeechIndex = 0;
        TotalBgLines = 0;
    }
}

function repeatedly_execute()
{
    // In rep exec update Bg Speech state
    RunNextBgSpeech();
}

function TriggerBgLines_RunAtSomePointInGame()
{
    AddBgSpeech("first speech cue text");
    AddBgSpeech("second speech cue text");
}

Gal Shemesh

Thank you so much @Crimson Wizard for the detailed information! I will take your example and spend some good time studying it, as there are many aspects in it that I wasn't aware about or ever used yet.
Gal Shemesh,
goldeng

Cassiebsg

Just a side note, cutting the top of a sprite won't affect your character at all, as characters are always calculated and positioned according to the center middle bottom-center of the sprite. Meaning the character won't jump "all over the place", as long as you don't cut any of the bottom, you can even reduce the sides, but then remember to cut equal amounts of each side, so that the center remains exactly the same.

And if you worried, just keep a backup of the original sprites, and if something does go wrong, just reload the old sprites.
Seems the easiest way to solve your problem, or just use a dummy. I like keeping things simple. :) Then again, you can view it as: learning new coding skills.

Edit: See bellow.
There are those who believe that life here began out there...

Gal Shemesh

Thanks @Cassiebsg - but doesn't AGS calculate and position according to the 'bottom-center' of the sprites boundaris (its feet)? I mean, if it calculates from the center as you say, wouldn't the center point change if the overall boundaries size of the character changes? See picture below.



I'm aware of problems like this from other game engines, where if you change the boundaries of a sprite from the Left or Top then it affects its 'hotspot' - causing its center point to change and the character to jump around when its now 'non-gap' sprite that you changed plays in a sequence with other animations that still have a gap in them. While if you change its boundaries from the Right or Bottom then the hotspot remains in place; a trick I used back when I was working on other game engines that gives you control over the hotspot position in a given sprite/frame, was to flip the image to the other side than the side I wish to change the boundaries from, to make the size change, and then to re-flip the sprite back, which kept its hotspot in place. But I didn't see a way to manually modify sprites/frames hotspot in AGS...

Anyway, I've successfully finished working on this game I was working on by now, along with successfully fixing all its scripting issues coming from the old AGS 2.72 - it was a 'making captions' project for a game that didn't had captions in the first place, only speech. But this note of yours is something to take into account if I encounter with such thing while working on other AGS projects that aren't mine in the future.
Gal Shemesh,
goldeng

Cassiebsg

Yes, I meant bottom-center, not middle-center. Otherwise all that I wrote afterwards makes no sense... ;D
Post edited above.

As for how to change the sprite, you can right-click the sprite in question and choose edit sprite. This will open the sprite on your chosen image editor (you need to set it up in the AGS editor settings), cut the top, and close the image editor. Repeat for any other sprite you may need to do this.
Alternatively you can export the sprites to a folder, and then edit them from there, and reimport after they're nicely cut. It's also possible yo cut them at import (never used this feature myself). There's an auto-cut empty areas of the sprite as well, but I think that one will cut all empty around the sprite, which may screw bottom and center position (probably only good for non-animating sprites).
There are those who believe that life here began out there...

Gal Shemesh

Not on the same matter I originally posted, but someone may reach this post when searching for SayBackground along with X and Y coordinates. Which I was looking for on another game that I'm working on where I wanted to position the speech text at the bottom center of the screen. So if anyone is interested, here's the details and the solution:

My game has live footage cut-scenes animations, which I imported into AGS as sprites, set them in Views and then in room objects. I'm using if statements to check for specific frames in the animations where I wish to play speech sound and to present the text at the bottom center of the screen.

Originally, I tried using .SayAt function for positioning the text where I want it to be, but it's a blocking function which sometimes caused another message that coded very close to the previous one to not appear. So I moved to use the .SayBackground function instead. But this one doesn't have the 'At' property like '.Say' has, and also requires to play the speech file separately. Besides, it also required to place the characters at the bottom center of the screen where I wanted the text to be shown and to make their transparancy 100. This had its own issues, such as frequently line breaks that looks wrong for bottom-center text, and also that sometimes the text was disappeared before the speech voice is finished playing, since as mentioned above the SayBackground is purely for showing text and you have to play the audio separately.

Eventually, I ended up using a custom .Say function which plays the speech clip, and also pass the text string to a GUI label, which I positioned exactly where I want it to be. So if anyone is interesed, here's how it works:

1. I made a GUI with a label that I positioned where I want the speech text to appear, and set it with the appropriate Speech font.

2. I made a custom 'say' function in my Global Script header (can't thank enough for @Khris for helping me with this one) called '.SayBottom', which goes like this:

Code: ags
void SayBottom(this Character*, int cue, String text)
{
  Game.PlayVoiceClip(this, cue);
  lblSpeech.TextColor = this.SpeechColor;
  lblSpeech.Text = text;
}

* I use this mainly in live-acting cut-scenes where the actual character are not present in the room. So I don't need any speech animation.

And under 'repeatedly_execute_always()' in the Global Script I wrote this to actually draw the text on the screen:

Code: ags
if (System.AudioChannels[0].IsPlaying) { // speech is playing on channel[0] by default
  if (lblSpeech.Text.Length < 500) // 500 is the Width of my label in pixels
  {
    lblSpeech.Y = 376; // this is the position I like the text to show for 2 lines of text
  }
  else
  {
    lblSpeech.Y = 356; // this is the position I like the text to show for 1 line of text
  }
}
else if (!System.AudioChannels[0].IsPlaying) { // this wipes any text from the label if no speech is playing on channel[0]
  lblSpeech.Text = "";
  }
}

By using this method in an actual playable room, this can be used for making the character speak (where the speech animation is not required) while the character does other stuff, or even walk around, as this is a none-blocking function.

Of course, this is for a case where you have speech in your game - if you're only making your game and don't have speech yet then this should be modified accordingly.
Gal Shemesh,
goldeng

SMF spam blocked by CleanTalk