A straight forward script for random footsteps?

Started by WatchDaToast, Tue 28/04/2020 14:24:57

Previous topic - Next topic

WatchDaToast

I have been struggling with this for quite a while now and searched the forums for solutions. I found a few old threads, but I still can't really wrap my head around how it works. :(

I tried to get random footsteps for the main player by writing a global function that creates a random value (between 0 and 7) and for every different value, I assign a different sound effect to some frames of the players walking view (when he takes a step). Inside a room script, I checked if the player was on a certain frame in his walking view (I checked that using repeatedly_execute_always) und if yes, called the function. It worked somewhat.
It seemed as if AGS needed to catch up with the scripts everytime the function was first called. The main character walked around soundless, until he took around 3 or 4 steps. Then I started hearing
the footsteps. Sometimes the sounds stopped for a moment, especially if I switched to a different Loop while walking (when I switched from walking left to walking up).
Aside from these issues, sometimes the same footstep sound was randomly played one after the other, which I want to avoid, since it sounds a bit strange. That`s the reason why I want these random footsteps in the first place. But somehow my brain is too burned out right now to come up with a clean script that avoids the repeating of sounds.

I am sure such a script can be pulled off without any of these things happening, and I would be super thankful if someone here could help me understand how such a thing could work properly. :-D

Snarky

#1
(Just to avoid having two people working in parallel, letting you know I'll throw something together.)

OK, here's some untested code. Because many walkcycles have different numbers of frames in the front/back loop than in the side loops, the code allows you to define the frame numbers that should be set for those loops separately.

(Edit: Updated with improved random selection of footsteps)

Code: ags
// How many different footstep AudioClips we have
#define FOOTSTEP_AUDIO_COUNT 8

// Which frame numbers the footstep sounds should be linked to in the side walkcycles (set to actual values)
#define FOOTSTEP_FRAME_1_SIDE 5
#define FOOTSTEP_FRAME_2_SIDE 9

// Which frame numbers the footstep sounds should be linked to in the front and back walkcycles (set to actual values)
#define FOOTSTEP_FRAME_1_FB 4
#define FOOTSTEP_FRAME_2_FB 7

// An array where we're storing the audio clips for the footsteps
AudioClip* footsteps[FOOTSTEP_AUDIO_COUNT];
// The footsteps[] indexes for the first and second footstep
int footstepClip1, footstepClip2;

// The player animation frame shown in the previous game cycle
int lastPlayerFrame;

// Convenience function: minimum of two ints
int minInt(int a, int b)
{
  if(a<b) return a;
  return b;
}

// Initialize the footsteps array (replace values with actual clips)
void initFootstepAudio()
{
  footsteps[0] = aFootstep0;
  footsteps[1] = aFootstep1;
  // TODO: Initialize each entry
  footsteps[7] = aFootstep7;
}

// Convenience function to update the linked audio for a particular animation frame in a character's walkcycle
void SetFootstepAudio(this Character*, int loop, int frame, AudioClip* clip)
{
  if(Game.GetLoopCountForView(this.NormalView) > loop && Game.GetFrameCountForLoop(this.NormalView, loop) > frame)
  {
    ViewFrame* vf = Game.GetViewFrame(int this.NormalView, int loop, int frame);
    vf.LinkedAudio = clip;
  }
  else Display("Tried to set footstep sound for invalid walkcycle frame: %s, view %d, loop %d, frame %d", this.Name, this.NormalView, loop, frame);
}

void updateFootstepAudio()
{
  if(player.View == player.NormalView && player.Moving) // Walking
  {
    if(player.Frame != lastPlayerFrame && player.Frame == 1) // Switched to frame 1 (start of walkcycle)
    {
      // Choose two different random footstep clips
      footstepClip1 = Random(FOOTSTEP_AUDIO_COUNT - 2);
      if(footstepClip1 >= footstepClip2)
        footstepClip1++;
      footstepClip2 = Random(FOOTSTEP_AUDIO_COUNT - 2);
      if(footstepClip2 >= footstepClip1)
        footstepClip2++;

      // For all walkcycle loops in this view...
      for(int i=0; i<minInt(Game.GetLoopCountForView(player.NormalView), eDirectionUpLeft+1); i++)
      {
        // Set the footstep clips to the random values
        if(i == eDirectionDown || i == eDirectionUp) // Front or back loop
        {
          player.SetFootstepAudio(i, FOOTSTEP_FRAME_1_FB, footsteps[footstepClip1]);
          player.SetFootstepAudio(i, FOOTSTEP_FRAME_2_FB, footsteps[footstepClip2]);
        }
        else
        {
          player.SetFootstepAudio(i, FOOTSTEP_FRAME_1_SIDE, footsteps[footstepClip1]);
          player.SetFootstepAudio(i, FOOTSTEP_FRAME_2_SIDE, footsteps[footstepClip2]);
        }
      }
    }
    lastPlayerFrame = player.Frame;
  }
  else // Not walking
    lastPlayerFrame = -1;
}

function game_start()
{
  initFootstepAudio();
}

function repeatedly_execute_always()
{
  updateFootstepAudio();
}

Snarky

#2
And to take a closer look at each of your problems…

To ensure that the footsteps are set from the start of the walk animation, we set the update to happen on frame 1 (the first frame of the walk animation). We only update the footstep audio when the character is actually walking, and only one time per animation loop (no matter how long that frame is displayed):

Code: ags
  if(player.View == player.NormalView && player.Moving) // Walking
  {
    if(player.Frame != lastPlayerFrame && player.Frame == 1) // Switched to frame 1 (start of walkcycle)
    {
      // ...
    }
    lastPlayerFrame = player.Frame;
  }
  else // Not walking
    lastPlayerFrame == -1;


(The else ensures that the condition holds whenever we go from not walking to walking.)

To avoid having the footsteps go missing when the character changes walking direction, we always update all the walking directions, not just the direction we're currently facing:

Code: ags
      // For all walkcycle loops in this view...
      for(int i=0; i<minInt(Game.GetLoopCountForView(player.NormalView), eDirectionUpLeft+1); i++)
      {
        // ...
      }


To avoid having the same audio clip for both footsteps, we keep trying until we get two different random values:

Code: ags
      // Choose two different random footstep clips
      int footstepClip1 = Random(FOOTSTEP_AUDIO_COUNT - 1);
      int footstepClip2 = Random(FOOTSTEP_AUDIO_COUNT - 1);
      while(footstepClip2 == footstepClip1)
        footstepClip2 = Random(FOOTSTEP_AUDIO_COUNT - 1);


Actually, that's a pretty crude way of doing that. Here's a better one:

Code: ags
      // Choose two different random footstep clips
      int footstepClip1 = Random(FOOTSTEP_AUDIO_COUNT - 1);
      int footstepClip2 = Random(FOOTSTEP_AUDIO_COUNT - 2);
      if(footstepClip2 >= footstepClip1)
        footstepClip2++;


Edit: It just occurred to me that using this code, the first footstep of this animation loop could match the last footstep of the previous loop. To avoid that, let's try this:

Code: ags
// These variables need to be persistent now
int footstepClip1, footstepClip2;
// ...

{
  // ...
      // Choose two different random footstep clips, (new value of footstepClip1 should not match old value of footstepClip2)
      footstepClip1 = Random(FOOTSTEP_AUDIO_COUNT - 2);
      if(footstepClip1 >= footstepClip2)
        footstepClip1++;
      footstepClip2 = Random(FOOTSTEP_AUDIO_COUNT - 2);
      if(footstepClip2 >= footstepClip1)
        footstepClip2++;
      // ...
}


A side effect of this change is that the clip used for the very first step in the game will never be footsteps[0], but I don't think we care about that.

.M.M.

Is the script neccessary for the desired effect? The first thing that I found myself wondering about was whether it's not an overcomplicated solution. Given you have 8 different sounds, you could just assign them to sprites however you like: sure, there would always be the same sound for the one exact sprite, but I doubt any player would notice it as it would still sound varied.
Now that you have the script it's probably not an issue anymore. :) But it could be another way to reach the goal.

Snarky

You mean extend the walkcycle animations to to something like 4 full loops (8 steps), with different sounds set on each footfall?

I think that would quickly become pretty noticeable, because it would always start with the same sequence of sounds whenever the player starts walking.

WatchDaToast

#5
Wow! Thanks SO much for showing me how to do it in such an elaborate way! (laugh)
Gonna try to recreate this in AGS now!

Edit:

This is amazing! It works like a charm! :-D
I made some alterations, would you take a look if this is right?

I removed the else section you added, so I can occasionally switch the main characters view for other movements like swimming, without having that Display(".."); option pop up.
Shouldn`t create any issues, or does it?
Code: ags
// Convenience function to update the linked audio for a particular animation frame in a character's walkcycle
void SetFootstepAudio(this Character*, int Footsteploop, int Footstepframe, AudioClip* clip)
{
  if(Game.GetLoopCountForView(this.NormalView) > Footsteploop && Game.GetFrameCountForLoop(this.NormalView, Footsteploop) > Footstepframe)
  {
    ViewFrame* vf = Game.GetViewFrame(this.NormalView, int Footsteploop, int Footstepframe); //////int koennte vor This noch sein
    vf.LinkedAudio = clip;
  }

}


To change the footstep sounds depending on rooms or screen locations, I did this:
Code: ags
        // Set the footstep clips to the random values
        if(i == eDirectionDown || i == eDirectionUp) // Front or back loop
        {
          if (WalkOnGrass == true) { ////Footsteps on grass
          player.SetFootstepAudio(i, FOOTSTEP_FRAME_1_FB, footstepsGrass[footstepClip1]);
          player.SetFootstepAudio(i, FOOTSTEP_FRAME_2_FB, footstepsGrass[footstepClip2]);
          }
         else if (WalkOnWood == true) { ////Footsteps on wood
          player.SetFootstepAudio(i, FOOTSTEP_FRAME_1_FB, footstepsWood[footstepClip1]);
          player.SetFootstepAudio(i, FOOTSTEP_FRAME_2_FB, footstepsWood[footstepClip2]);
          }
         else if (WalkOnSand == true) { ////Footsteps on sand
          player.SetFootstepAudio(i, FOOTSTEP_FRAME_1_FB, footstepsSand[footstepClip1]);
          player.SetFootstepAudio(i, FOOTSTEP_FRAME_2_FB, footstepsSand[footstepClip2]);
          }
         else if (WalkOnStone == true) { ////Footsteps on stone
          player.SetFootstepAudio(i, FOOTSTEP_FRAME_1_FB, footstepsStone[footstepClip1]);
          player.SetFootstepAudio(i, FOOTSTEP_FRAME_2_FB, footstepsStone[footstepClip2]);
          }
          else {   /////keine Footsteps
          player.SetFootstepAudio(i, FOOTSTEP_FRAME_1_FB, aFinnNone);
          player.SetFootstepAudio(i, FOOTSTEP_FRAME_2_FB, aFinnNone);
          }
        }
        else
        {
          if (WalkOnGrass == true) { ////Footsteps on grass
          player.SetFootstepAudio(i, FOOTSTEP_FRAME_1_SIDE, footstepsGrass[footstepClip1]);
          player.SetFootstepAudio(i, FOOTSTEP_FRAME_2_SIDE, footstepsGrass[footstepClip2]);
           }
         else if (WalkOnWood == true) { ////Footsteps on wood
          player.SetFootstepAudio(i, FOOTSTEP_FRAME_1_SIDE, footstepsWood[footstepClip1]);
          player.SetFootstepAudio(i, FOOTSTEP_FRAME_2_SIDE, footstepsWood[footstepClip2]);
           }
         else if (WalkOnSand == true) { ////Footsteps on sand
          player.SetFootstepAudio(i, FOOTSTEP_FRAME_1_SIDE, footstepsSand[footstepClip1]);
          player.SetFootstepAudio(i, FOOTSTEP_FRAME_2_SIDE, footstepsSand[footstepClip2]);
          }
         else if (WalkOnStone == true) { ////Footsteps on stone
          player.SetFootstepAudio(i, FOOTSTEP_FRAME_1_SIDE, footstepsStone[footstepClip1]);
          player.SetFootstepAudio(i, FOOTSTEP_FRAME_2_SIDE, footstepsStone[footstepClip2]);
          }
          else {    /////keine Footsteps
          player.SetFootstepAudio(i, FOOTSTEP_FRAME_1_SIDE, aFinnNone);
          player.SetFootstepAudio(i, FOOTSTEP_FRAME_2_SIDE, aFinnNone); 
          }
        }


Just wanted to ask if this approach would create any bugs in the long run. :)

Edit 2:
Also, since your script that checks the first frame of the walking animation did not work for me, since the players first footstep sound already occurs on the first frame, I made this little (maybe a bit stupid) alteration:
Code: ags
  if(player.View == player.NormalView && player.Moving) // Walking
  {
    if((player.Frame == 0) || (player.Frame == 1) || (player.Frame == 5)) // Switched to frame 1 (start of walkcycle)
    {
      // Choose two different random footstep clips
      footstepClip1 = Random(FOOTSTEP_AUDIO_COUNT - 2);
      if(footstepClip1 >= footstepClip2)
        footstepClip1++;
      footstepClip2 = Random(FOOTSTEP_AUDIO_COUNT - 2);
      if(footstepClip2 >= footstepClip1)
        footstepClip2++;

This way, the footsteps immediately play on the first frame when walkin and change depending on the ground the player walks on.
I guess its` not too wise to have a script constantly check the players frame 0 when he is standing...

Snarky

Glad it worked for you! Untested code is always a bit of a gamble.

Quote from: WatchDaToast on Tue 05/05/2020 11:59:31
I made some alterations, would you take a look if this is right?

Sure.  :)

Quote from: WatchDaToast on Tue 05/05/2020 11:59:31
I removed the else section you added, so I can occasionally switch the main characters view for other movements like swimming, without having that Display(".."); option pop up.
Shouldn`t create any issues, or does it?

The Display() popping up is an indication that something is wrong elsewhere: if everything is working properly, it should never happen. So while taking it out shouldn't cause any problems in itself, I wouldn't recommend it because it just means you could be ignoring some other bug.

Did you test it? If you switch the main view to a swimming view I think the code I had should just work without any need for change. (Hmm... unless you go straight from walking to swimming without stopping at all, in which case it might take an animation loop for the sounds to trigger. I'm not sure that's even possible to do, though.)

The only "problem" I can see is that I've set up the frame number that the sounds are linked to as constants, which might not make sense if you can have completely different "walk" animations (with perhaps a different number of frames) for swimming, etc.

Quote from: WatchDaToast on Tue 05/05/2020 11:59:31
To change the footstep sounds depending on rooms or screen locations, I did this: [...]

Just wanted to ask if this approach would create any bugs in the long run. :)

No, this looks fine to me.  :)

Quote from: WatchDaToast on Tue 05/05/2020 11:59:31
Also, since your script that checks the first frame of the walking animation did not work for me, since the players first footstep sound already occurs on the first frame

Could you elaborate about "did not work"? Was it that the first footstep sound didn't play? Or all the footstep sounds for the whole first animation loop? Or what? (Also, does the problem only apply to the first time you walk after starting the game, or every time you start walking?)

Quote from: WatchDaToast on Tue 05/05/2020 11:59:31
I made this little (maybe a bit stupid) alteration:
Code: ags
  if(player.View == player.NormalView && player.Moving) // Walking
  {
    if((player.Frame == 0) || (player.Frame == 1) || (player.Frame == 5)) // Switched to frame 1 (start of walkcycle)
    {
      // Choose two different random footstep clips
      footstepClip1 = Random(FOOTSTEP_AUDIO_COUNT - 2);
      if(footstepClip1 >= footstepClip2)
        footstepClip1++;
      footstepClip2 = Random(FOOTSTEP_AUDIO_COUNT - 2);
      if(footstepClip2 >= footstepClip1)
        footstepClip2++;

This way, the footsteps immediately play on the first frame when walkin and change depending on the ground the player walks on.
I guess its` not too wise to have a script constantly check the players frame 0 when he is standing...

Hmm… This doesn't look right to me.

First, walk animations skip frame 0 (the standing frame), so it should never be the case that the character is both walking and player.Frame == 0. (Except perhaps if the character is turning before starting the walk? Which in that case might mean that the behavior will be different depending on whether the character first has to turn, or can start walking directly.)

Second, I don't see how checking frame 5 will help, if the problem is with the first animation loop: it shouldn't make a difference.

Third, you've taken out the player.Frame != lastPlayerFrame condition. The point of this condition is to only run the footstep assignment code once per animation loop, in the first game loop in which the frame is displayed â€" that is, the game loop in which the frame number is different from what it was last game loop â€" rather than again and again for as long as we're showing the frame, which is pretty wasteful. (Why calculate and assign a random footstep sound only to overwrite it with some other random sound before it even has a chance to play?)

The thing is, I don't really see how any of these three changes would fix any shortcoming with the code as-was, so I'd really like to get a better understanding of what's going on. I'm pretty sure there must be a better solution.

WatchDaToast

You are right, I may have created some weird stuff with the last script. Strangely, it works perfectly so far. :P
The only problem I encountered with the original script was that the first step sound, which already appears on frame 1, could not be heard. I assumed that was because the script checked if the player
was walking on frame 1, when the sound should have been played, so it was already too late.
It all started working perfectly when I checked on frame 0, which should not be able to be checked because the player is not walking.

So far, I put lastPlayerframe in there again, in hopes of making this messer a bit nicer:
Code: ags
  if(player.View == player.NormalView && player.Moving) // Walking
  {
    if((player.Frame != lastPlayerFrame && player.Frame == 0) || (player.Frame == 10) || (player.Frame == 5)) // Switched to frame 1 (start of walkcycle)
    {
      // Choose two different random footstep clips
      footstepClip1 = Random(FOOTSTEP_AUDIO_COUNT - 2);
      if(footstepClip1 >= footstepClip2)
        footstepClip1++;
      footstepClip2 = Random(FOOTSTEP_AUDIO_COUNT - 2);
      if(footstepClip2 >= footstepClip1)
        footstepClip2++;
 


Maybe it`s because I still struggle with understanding more complex scripts like this, but I added another check on frame 5 and 10 (the frames directly before the next step). So far I assume that this assures that, when the player is switching from one
walkable surface to another (which changes the footstep sound), the script checks the sounds that shall be played shortly before the next step. Frame 10 is the last frame of the walkable animation before it starts on frame 1 again, so I thought I make the script check there to assure that the first frame of the walkable animation has the correct sounds played.

Sorry if I hurt your brain with these things, I guess I may have gotten a bit wrong how this script should work... :-D

SMF spam blocked by CleanTalk