Adventure Game Studio

AGS Support => Beginners' Technical Questions => Topic started by: Nixxon on Mon 02/11/2015 23:10:02

Title: Background Speech (tried modules)
Post by: Nixxon on Mon 02/11/2015 23:10:02
Hey all,

Just a quickie. I'm trying to get a character to talk to himself in the background (with audio), I've been using the forum search function over the last week or so and ultimately downloaded a couple of modules that didn't work (they may have been out of date). I only need this to occur during one room of my game, so the easiest I can get away with coding this the better.

It doesn't need to be randomized though it would be nice if it looped.

The code would be roughly as follows (I tried this in the room repeat execute, with little luck).

Code (ags) Select
cChar.SayBackground("&1 I am randomly talking to myself");
Wait(600, NoEblock);
cChar.SayBackground("&2 Again I speak to myself");
Wait(600, NoEblock);
cChar.SayBackground("&3 Talkie Talkie Poo Poo");
Wait(600, NoEblock);
cChar.SayBackground("&4 Yawn... Running out of things to say");
Wait(600, NoEblock);
cChar.SayBackground("&5 Weenis!");
Wait(600, NoEblock);
Title: Re: Background Speech (tried modules)
Post by: Kumpel on Mon 02/11/2015 23:25:47
I think a combination of the BgSpeech module in this particular room and some if-prompt in the rep_exec_always of the global script could work. Just let him say the stuff you want with the module and let the game check, if he says one particular phrase number via BgSpeech.GetCurrentPlayingIndex();, to play a particular audiofile.

This is the module:
http://www.adventuregamestudio.co.uk/forums/index.php?topic=48086.msg636452160#msg636452160

Cheers
Kumpel
Title: Re: Background Speech (tried modules)
Post by: Nixxon on Mon 02/11/2015 23:41:17
Thanks Kumpel,

That's one of the Modules' I used. While the the functions explain in that thread are very easy to understand. To actually "make it go!" aren't... at least for me.

Do you write the actual dialog in the module script? or in the room? After installing the module, the BgSpeech.Add worked. but Wasn't sure how to "call it"? If that makes sense.
Title: Re: Background Speech (tried modules)
Post by: Khris on Mon 02/11/2015 23:45:04
It won't work like that; you can do this:

int cChar_speechTimer = 0;

function repeatedly_execute_always() {
  cChar_speechTimer++;
  if (cChar_speechTimer % 600 == 0) {
    int which = cChar_speechTimer / 600;
    String s;
    if (which == 1) s = "&1 I am randomly talking to myself";
    if (which == 2) s = "&2 Again I speak to myself";
    // etc
    cChar.SayBackground(s);
  }
  if (cChar_speechTimer == 3000) cChar_speechTimer = 0;
}
Title: Re: Background Speech (tried modules)
Post by: Nixxon on Mon 02/11/2015 23:49:08
Thank Khris,

So this just goes into the room repeat exec?
Title: Re: Background Speech (tried modules)
Post by: Khris on Mon 02/11/2015 23:53:08
No, this goes in the room script as-is, outside any functions.
It's a room variable and the repeatedly_execute_always() function, which, if existent, is called by AGS without having to link it.

(I always use the correct indentation; even if a function could go inside another function, lines 1, 3 and 14 aren't indented. When I post stuff that goes inside a function, I make sure to a) mention the functon and b) indent the code so it can be used as-is, too.)
Title: Re: Background Speech (tried modules)
Post by: Nixxon on Tue 03/11/2015 19:47:27
Seems to work great!

At one point the character dies and is removed from screen. I'm thinking I should use a global variable in conjunction with this? So that it doesn't run if the character is absent.

Also. the audio "&1" etc. doesn't seem to be working?
Title: Re: Background Speech (tried modules)
Post by: Khris on Tue 03/11/2015 21:12:14
Yeah, Character.SayBackground doesn't support voice speech. You could play the samples manually:
    String s;
    AudioClip *vs;
    if (which == 1) { s = "&1 I am randomly talking to myself"; vs = aNpcBg1; }
    if (which == 2) { s = "&2 Again I speak to myself";         vs = aNpcBg2; }
    // etc
    vs.Play();
    cChar.SayBackground(s);


To fix the other problem, just check if the character is still present (between line 3 and 4 of the previous snippet):
  if (cChar.Room != player.Room) return; // do nothing
Title: Re: Background Speech (tried modules)
Post by: Nixxon on Tue 03/11/2015 21:52:25
Quoteif (cChar.Room != player.Room) return; // do nothing

Worked brilliantly.

Bit of trouble with the manual clips. It seems to play the last one for each line (aPickuppebble) Is there a way to have a unique clip for each line of dialog?

Code (ags) Select
// room script file
int cPedoMan_speechTimer = 0;

function repeatedly_execute_always() {
  cPedoMan_speechTimer++;
  if (cPedoMan_speechTimer % 600 == 0) {
    int which = cPedoMan_speechTimer / 600;
    String s;
    AudioClip *vs;
      if (cPedoMan.Room != player.Room) return; // do nothing
  if (which == 1) s = "&1 I am randomly talking to myself"; vs = aPEDO7;
    if (which == 2) s = "&2 Again I speak to myself"; vs = aStonethrow;
    if (which == 3) s = "&3 I am randomly talking to myself"; vs = aMonstereat;
    if (which == 4) s = "&4 Again I speak to myself"; vs = aAopentrunk;
    if (which == 5) s = "&5 I am randomly talking to myself"; vs = aPickuppebble; // plays this every time.
   vs.Play();
   cPedoMan.SayBackground(s);
  }
  if (cPedoMan_speechTimer == 3000) cPedoMan_speechTimer = 0;
}
Title: Re: Background Speech (tried modules)
Post by: Snarky on Tue 03/11/2015 23:16:51
The { } brackets that Khris put in weren't just for show. They create a block, so that the if-condition covers both "lines" (separated by semi-colons). What you have is structurally identical to this â€" I've just reformatted for clarity:

Code (ags) Select
// room script file
int cPedoMan_speechTimer = 0;

function repeatedly_execute_always() {
  cPedoMan_speechTimer++;
  if (cPedoMan_speechTimer % 600 == 0) {
    int which = cPedoMan_speechTimer / 600;
    String s;
    AudioClip *vs;

    if (cPedoMan.Room != player.Room)
      return; // do nothing

    if (which == 1)
      s = "&1 I am randomly talking to myself";

    vs = aPEDO7;
    if (which == 2)
      s = "&2 Again I speak to myself";

    vs = aStonethrow;
    if (which == 3)
      s = "&3 I am randomly talking to myself";

    vs = aMonstereat;
    if (which == 4)
      s = "&4 Again I speak to myself";

    vs = aAopentrunk;
    if (which == 5)
      s = "&5 I am randomly talking to myself";

    vs = aPickuppebble; // plays this every time.

    vs.Play();
    cPedoMan.SayBackground(s);
  }
  if (cPedoMan_speechTimer == 3000)
    cPedoMan_speechTimer = 0;
}


It should now be obvious why it's always aPickuppebble that plays: that's what you set vs to right before you play it!

This is another good example of how important it is to format and indent your code consistently. Khris is an experienced coder and uses these one-line "abbreviations" (multiple statements on one line) that can be handy in some cases, but if you're not already familiar with code formatting, I think you should just stick to the simple form; that way you won't make mistakes like this.
Title: Re: Background Speech (tried modules)
Post by: Khris on Wed 04/11/2015 00:13:40
I was debating whether to take this shortcut, and I shouldn't have... :-D

    String s;
    AudioClip *vs;
    if (which == 1) {
      s = "I am randomly talking to myself";
      vs = aPEDO7;
    }
    if (which == 2) {
      s = "Again I speak to myself";
      vs = aStonethrow;
    }
    // etc
    vs.Play();
    cChar.SayBackground(s);
Title: Re: Background Speech (tried modules)
Post by: Nixxon on Wed 04/11/2015 08:11:36
Poop, I've tried both suggestions, and it's still playing the sound file from the last if with all dialogs. (aAopentrunk from line 32).

See code below, what am I missing?? Really sorry about this.

Code (ags) Select
// room script file
int cPedoMan_speechTimer = 0;

function repeatedly_execute_always() {
  cPedoMan_speechTimer++;
  if (cPedoMan_speechTimer % 600 == 0) {
    int which = cPedoMan_speechTimer / 600;
    String s;
    AudioClip *vs;

    if (cPedoMan.Room != player.Room)
      return; // do nothing

    if (which == 1) {
      s = "&1 DIALOG 1";
      vs = aPEDO7;
    }
    if (which == 2) {
      s = "&2 DIALOG 2";
      vs = aStonethrow;
    }
    if (which == 3) {
      s = "&3 DIALOG 3";
      vs = aMonstereat;
    }
    if (which == 4) {
      s = "&4 DIALOG 4";
      vs = aPickuppebble;
    }
    if (which == 5)
      s = "&5 DIALOG 5";
      vs = aAopentrunk;
 
    vs.Play();
    cPedoMan.SayBackground(s);
  }
  if (cPedoMan_speechTimer == 3000)
    cPedoMan_speechTimer = 0;
}

Title: Re: Background Speech (tried modules)
Post by: Scavenger on Wed 04/11/2015 08:31:07
This:

    if (which == 5)
      s = "&5 DIALOG 5";
      vs = aAopentrunk;


needs to be:

if (which == 5)
    {
        s = "&5 DIALOG 5";
        vs = aAopentrunk;
    }


You're still missing the brackets on the code - make sure to double check them, otherwise the if statement will only apply to the first function run after it, and always run the one after that.
Title: Re: Background Speech (tried modules)
Post by: Nixxon on Wed 04/11/2015 09:04:21
Absolutely amazing. You're all noble Jedi. Works a treat now.
Title: Re: {SOLVED} Background Speech (tried modules)
Post by: Snarky on Wed 04/11/2015 12:18:54
Quote from: Scavenger on Wed 04/11/2015 08:31:07
This:

    if (which == 5)
      s = "&5 DIALOG 5";
      vs = aAopentrunk;


needs to be:

if (which == 5)
    {
        s = "&5 DIALOG 5";
        vs = aAopentrunk;
    }


You're still missing the brackets on the code - make sure to double check them, otherwise the if statement will only apply to the first function run after it, and always run the one after that.

Yes, except you've got the indentation wrong. It should be:

    if (which == 5)
    {
      s = "&5 DIALOG 5";
      vs = aAopentrunk;
    }


See? The brackets are in line with the if-condition they belong to, and the block inside the brackets are indented one level (two spaces, by AGS default).

Glad it's working, Nixxon! I would also refactor this code a bit, to make it simpler and more future-proof:

Code (ags) Select
// room script file
int cPedoMan_speechTimer = 0;

// A helper function to simplify the job of background speech with voice
void SayBackgroundClip(this Character*, String message, AudioClip* clip)
{
  this.SayBackground(message);
  if(clip != null)
    clip.Play();
}

function repeatedly_execute_always() {
  cPedoMan_speechTimer++;
  if (cPedoMan_speechTimer % 600 == 0) {
    int which = cPedoMan_speechTimer / 600;
    if (cPedoMan.Room != player.Room)
      return; // do nothing

    if (which == 1)
      cPedoMan.SayBackgroundClip("&1 DIALOG 1", aPEDO7);
    else if (which == 2)
      cPedoMan.SayBackgroundClip("&2 DIALOG 2", aStonethrow);
    else if (which == 3)
      cPedoMan.SayBackgroundClip("&3 DIALOG 3", aMonstereat);
    else if (which == 4)
      cPedoMan.SayBackgroundClip("&4 DIALOG 4", aPickuppebble);
    else if (which == 5)
      cPedoMan.SayBackgroundClip("&5 DIALOG 5", aOpentrunk);
    else
      cPedoMan_speechTimer = 0; // Loop around
  }
}


This way you can add more lines very easily, without worrying about keeping track of the relationship between which and cPedoMan_speechTimer (a likely source of bugs, if you add a line but forget to update the 3000 value). And you can even use it to do background speech with voice for any other characters.
Title: Re: Background Speech (tried modules)
Post by: Nixxon on Wed 22/06/2016 11:43:01
Hey Guys,

I'm back on this one.  :sealed:

I decided to try Snarky's code instead as I wanted to add additional code to each line (such as set character animations etc.).

Initially i received the error -
Failed to save room room3.crm; details below
room3.asc(11): Error (line 11): '.SayBackgroundClip' is not a public member of 'Character'. Are you sure you spelt it correctly (remember, capital letters are important)?

I then tried to remove the 'BackgroundClip' and simply have 'cPedoMan.Say'

The game luunches this way, however when in that room the game crashes with the following error -
A blocking function was call from within a non-blocking event such as repeatedly_execute_always

Any ideas? Below is the code -

Code (ags) Select
// room script file
int cPedoMan_speechTimer = 0;

function repeatedly_execute_always() {
  cPedoMan_speechTimer++;
  if (cPedoMan_speechTimer % 600 == 0) {
    int which = cPedoMan_speechTimer / 600;
    if (cPedoMan.Room != player.Room)
      return; // do nothing

    if (which == 1)
      cPedoMan.Say("&1 DIALOG 1", aPEDO7);
    else if (which == 2)
      cPedoMan.Say("&2 DIALOG 2", aStonethrow);
    else if (which == 3)
      cPedoMan.Say("&3 DIALOG 3", aMonstereat);
    else if (which == 4)
      cPedoMan.Say("&4 DIALOG 4", aPickuppebble);
    else if (which == 5)
      cPedoMan.Say("&5 DIALOG 5", aPEDO7);
    else
      cPedoMan_speechTimer = 0; // Loop around
  }
}



EDIT - COMPLETELY MISSED THIS BIT. Duh

Code (ags) Select
// A helper function to simplify the job of background speech with voice
void SayBackgroundClip(this Character*, String message, AudioClip* clip)
{
  this.SayBackground(message);
  if(clip != null)
    clip.Play();
}
Title: Re: Background Speech (tried modules)
Post by: Nixxon on Wed 22/06/2016 11:46:03
BAH, never mind, I just missed a huge chunk of your code.

Clinically stupid. Sorry.
Title: Re: Background Speech (tried modules)
Post by: Nixxon on Wed 22/06/2016 11:51:58
Spoke to soon.

How would I go about adding additional scripts to each (which ==)

For instance -

if (which == 1)
      cPedoMan.SayBackgroundClip("&1 DIALOG 1", aPEDO7);
      set a character view etc.
    else if (which == 2)
      cPedoMan.SayBackgroundClip("&2 DIALOG 2", aStonethrow);
      play another line of dialog
    else if (which == 3)
      cPedoMan.SayBackgroundClip("&3 DIALOG 3", aMonstereat);
      something something etc.
    else if (which == 4)
      cPedoMan.SayBackgroundClip("&4 DIALOG 4", aPickuppebble);
something something etc.
    else if (which == 5)
      cPedoMan.SayBackgroundClip("&5 DIALOG 5", aPEDO7);
something something etc.
    else
      cPedoMan_speechTimer = 0; // Loop around
  }
Title: Re: Background Speech (tried modules)
Post by: Snarky on Wed 22/06/2016 12:45:32
Just wrap it in more curly brackets, like so:

Code (ags) Select
function repeatedly_execute_always() {
  cPedoMan_speechTimer++;
  if (cPedoMan_speechTimer % 600 == 0) {
    int which = cPedoMan_speechTimer / 600;
    if (cPedoMan.Room != player.Room)
      return; // do nothing

    if (which == 1) {
      cPedoMan.SayBackgroundClip("&1 DIALOG 1", aPEDO7);
      cPedoMan.NormalView = vPEDO9; // Or whatever
    }
    else if (which == 2) {
      cPedoMan.SayBackgroundClip("&2 DIALOG 2", aStonethrow);
      cPedoMan.SayBackgroundClip("&0 DIALOG X", aWhatever);  // Or whatever else
    }
    // ... other cases ...
    else
      cPedoMan_speechTimer = 0; // Loop around
  }
}
Title: Re: Background Speech (tried modules)
Post by: Nixxon on Thu 23/06/2016 00:29:41
Thanks Snarky,

A bit of an issue. It appears to be jumping directly to the last line of code between the brackets -

Code (ags) Select
function repeatedly_execute_always() {
  cPedoMan_speechTimer++;
  if (cPedoMan_speechTimer % 600 == 0) {
    int which = cPedoMan_speechTimer / 600;
    if (cPedoMan.Room != player.Room)
      return; // do nothing

    if (which == 1) {
      cPedoMan.SayBackgroundClip("&1 Blah Blah 1", aPEDO7); //SKIPS THIS LINE
      cPedoMan.SayBackgroundClip("&1 Blah Blah 2", aPEDO7); //GOES STRAIGHT TO THIS LINE FIRST
    }
    else if (which == 2) {
      cPedoMan.SayBackgroundClip("&2 DIALOG 2", aStonethrow);
    }
    else if (which == 3) {
      cPedoMan.SayBackgroundClip("&3 DIALOG 3", aMonstereat);
    }
    else if (which == 4) {
      cPedoMan.SayBackgroundClip("&4 DIALOG 4", aPickuppebble);
    }
    else if (which == 5) {
      cPedoMan.SayBackgroundClip("&5 DIALOG 5", aPEDO7);
    }
    else
      cPedoMan_speechTimer = 0; // Loop around
  }
}
Title: Re: Background Speech (tried modules)
Post by: Snarky on Thu 23/06/2016 08:37:26
Oh right, that's because SayBackgroundClip() doesn't wait until the character has stopped saying the first thing, so the second thing immediately overrides it.

As written, the code structure doesn't really allow for sequences of actions within each case. You can have multiple things that all happen together (e.g. saying a line and changing a view), but not saying a line, waiting until it's finished, and then doing something else (i.e. queuing commands).

It's possible to code around this, but I should point out that all of this is essentially what the BgSpeech module is for. The only thing it is missing is playing voice speech, and it would probably be easier to add that to the module than to hack together the queuing of commands. Or get in touch with Phemar and ask him to extend the module for you.

However, as a quick hack to make this more complicated thing work, the code in repeatedly_execute_always() will have to change:


int pedoState; // This is the old "which" variable

function repeatedly_execute_always() {
  cPedoMan_speechTimer++;
  bool stateSwitched = (cPedoMan_speechTimer % 600 == 0);
  if(stateSwitched)
    pedoState++;
  if (cPedoMan.Room != player.Room)
    return; // do nothing

  if (pedoState == 1 && stateSwitched)
    cPedoMan.SayBackgroundClip("&1 DIALOG 1", aPEDO7);
  else if (pedoState == 2) {
    if(stateSwitched)
      cPedoMan.SayBackgroundClip("&2 DIALOG 2", aStonethrow);
    else if(cPedoMan_speechTimer == 1400)    // Hardcoding the timing - ugly!
      cPedoMan.SayBackgroundClip("&0 DIALOG X", aWhatever);
  }
  else if (pedoState == 3 && stateSwitched)
    cPedoMan.SayBackgroundClip("&3 DIALOG 3", aMonstereat);
  else if (pedoState == 4 && stateSwitched)
    cPedoMan.SayBackgroundClip("&4 DIALOG 4", aPickuppebble);
  else if (pedoState == 5 && stateSwitched)
    cPedoMan.SayBackgroundClip("&5 DIALOG 5", aOpentrunk);
  else if (stateSwitched) {
      cPedoMan_speechTimer = 0; // Loop around - set to -1 if you don't want a double pause when it loops around
      pedoState = 0;
  }
}


Here the pedoState==2 demonstrates how to change the conditions to queue multiple commands or blocks of code. Obviously in this case it's very important that the whole sequence doesn't take more than 600 game cycles, since at that point it will be interrupted by the next state.
Title: Re: Background Speech (tried modules)
Post by: Nixxon on Tue 28/06/2016 04:00:35
Thanks Snarky,

Fortunately it doesn't matter too much as all i really wanted to do was set off a change view script. So all good :) i will save your for future reference.

I do have one more question though.

Is it possible to have two separate timers in one room?

I want to have another event running on a timer similar to the cPedoMan_speechTimer++;

Can i have another function in the function repeatedly_execute_always() {

Thanks in advance.
Title: Re: Background Speech (tried modules)
Post by: Snarky on Tue 28/06/2016 07:23:59
Well, first of all, what you have here isn't really a timer. It's just a counter. AGS also has a built-in timer type (you set them off and then they count down to 0 on their own, and you can check whether they've finished), though you don't really want one for this task.

But sure, you can have as many of these counters as you want, and multiple (though limited) built-in timers.

As for the second question, you can certainly call a function from repeatedly_execute_always(). You can't declare a function inside a function, because that doesn't really make sense.

One way to think about a function is that it's just a shortcut for a whole chunk of code: instead of having to do write a hundred lines every time you want to do something, you wrap it up as a function, and then you can just call that function to run those hundred lines.

So what you can do in order to keep things neat is this:

Code (ags) Select

void updatePedoMan() {
  // All the related code you had in repeatedly_execute_always()
}

void doSomethingElse() {
  // Some other background event
}

function repeatedly_execute_always() {
  updatePedoMan();
  doSomethingElse();
}