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).
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);
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
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.
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;
}
Thank Khris,
So this just goes into the room repeat exec?
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.)
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?
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
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?
// 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;
}
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:
// 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.
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);
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.
// 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;
}
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.
Absolutely amazing. You're all noble Jedi. Works a treat now.
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:
// 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.
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 -
// 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
// 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();
}
BAH, never mind, I just missed a huge chunk of your code.
Clinically stupid. Sorry.
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
}
Just wrap it in more curly brackets, like so:
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
}
}
Thanks Snarky,
A bit of an issue. It appears to be jumping directly to the last line of code between the brackets -
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
}
}
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.
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.
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:
void updatePedoMan() {
// All the related code you had in repeatedly_execute_always()
}
void doSomethingElse() {
// Some other background event
}
function repeatedly_execute_always() {
updatePedoMan();
doSomethingElse();
}