[SOLVED] Process on_key_press during blocking scripts?

Started by tor.brandt, Tue 08/11/2016 10:19:59

Previous topic - Next topic

tor.brandt

I'd like to make a PauseGame() function similar to the classical LucasArts games, such that I can pause anywhere in the game, including while characters are speaking, animations are run, cutscenes are running, etc. etc.

I have made an on_key_press PauseGame() function, and it's working fine, but I just accidentally discovered that (of course) it doesn't go through if a blocking script is running (e.g. if a character is speaking non-background-ingly).
I tried putting an IsKeyPressed() function in repeatedly_execute_always, but for that to work, of course, you would have to press and release the key pretty damn fast :smiley:

Is there a feasible way to make an OnKeyPress() function that somehow overrides the currently running blocking script (inherently, no more than one blocking script can run at any one time, right?)?
It would of course also have to somehow store that currently running blocking script, including the current "position" within that script, such that it could resume it at UnPauseGame().

Khris

Not possible, afaik.

While you wouldn't have to press and release the key really fast, the main problem is that you cannot jump back into a function like that. The only feasible way I could see is to detect the keypress in REA, use a while loop to wait for the key being released, then run another while loop containing Wait(1) until the key is pressed and released again. However, REA doesn't allow blocking events in the first place.

Unless a customizeable global Pause function is added to the engine, there's nothing you can do (except use non-blocking functions for every single action, which basically means you have to reimplement half the engine manually).

tor.brandt

@Khris:

Thanks for the reply!
Hmm, yeah that amount of work seems a bit out of proportion :smiley:

It seems odd though, to have a pause game function in AGS (being an engine built specifically for point and click adventure games) that cannot override blocking scripts. I mean, in a classical point and click game, if the game cannot be paused e.g. while a character is speaking, then why have the pause function at all?
The only time I use the pause function when playing the classical games is when I'm interrupted (e.g. when someone speaks to me IRL), such that I'm not able to keep up with what is happening in the game. Because, I mean, whenever there is not a character speaking, a cutscene running, etc., then a classical point and click game can easily be left alone without pausing, because nothing will happen until the player resumes the game.

On second thought, there are of course such things as timers, and I know people are making all kinds of different types of games in AGS, so it's probably just me that's only used to one specific type of game.
However, it would definitely be nice if an overriding pause function was added to the next version of AGS :smiley:

Crimson Wizard

#3
Quote from: tor.brandt on Tue 08/11/2016 11:07:16
It seems odd though, to have a pause game function in AGS (being an engine built specifically for point and click adventure games) that cannot override blocking scripts. I mean, in a classical point and click game, if the game cannot be paused e.g. while a character is speaking, then why have the pause function at all?

I think the built-in pause in AGS currently is kind of "character-protection" pause, which main function is to suspend dynamic events, for example when you open inventory or options menu, like in old Sierra games where player could die.

tor.brandt

Quote from: Crimson Wizard on Tue 08/11/2016 14:34:21

I think the built-in pause in AGS currently is kind of "character-protection" pause, which main function is to suspend dynamic events, for example when you open inventory or options menu, like in old Sierra games where player could die.


Ah yes, of course, that makes sense :smiley:

Riaise

You can use the code in this post by Radiant to create a custom paused state, rather than using the built in one.

Cassiebsg

Don't know about other systems, but in windows you can either click outside the game window, and that will efectively stop the game (Assuming it's not set in config to continue running) even mid sentence. If you playing full screen, Alt+Tab should do the same, by existing the game and putting it on pause in the task bar... ;)
That's what I normally do if I want to pause a game.
There are those who believe that life here began out there...

tor.brandt

Quote from: Riaise on Tue 08/11/2016 16:59:00
You can use the code in this post by Radiant to create a custom paused state, rather than using the built in one.

I'll take a look at that, thanks!


Quote from: Cassiebsg on Wed 09/11/2016 16:26:56
Don't know about other systems, but in windows you can either click outside the game window, and that will efectively stop the game (Assuming it's not set in config to continue running) even mid sentence. If you playing full screen, Alt+Tab should do the same, by existing the game and putting it on pause in the task bar... ;)
That's what I normally do if I want to pause a game.

Thanks, that's a good tip for me when I'm playing games, thanks!
What I'd like though is to put the function inside my game :smiley:

monkey0506

The following works perfectly:

Code: ags

eKeyCode pauseKey = eKeySpace;
bool pauseKeyHeld = false;

void TogglePause()
{
  if (IsGamePaused())
  {
    UnPauseGame();
    //lblPaused.Text = "Paused? false";
  }
  else
  {
    PauseGame();
    //lblPaused.Text = "Paused? true";
  }
}

function on_key_press(eKeyCode keycode)
{
  if (keycode == pauseKey)
  {
    TogglePause();
  }
  if (IsGamePaused())
  {
    return;
  }
  //if (keycode == 'A')
  //{
  //  player.Say("Hello World!");
  //}
}

function repeatedly_execute_always()
{
  if (!IsInterfaceEnabled())
  {
    if (IsKeyPressed(pauseKey))
    {
      if (!pauseKeyHeld)
      {
        pauseKeyHeld = true;
        TogglePause();
      }
    }
    else if (pauseKeyHeld)
    {
      pauseKeyHeld = false;
    }
  }
}


And by "works perfectly", I mean this suspends the game, even during character speech and other blocking events.

Edit: Actually, there is a discrepancy. AGS apparently calls on_key_press periodically, though not as often as repeatedly_execute_always, so if you hold down a key the non-blocking event will run again after a period of time, while this blocking event will not exhibit that behavior, only (un)pausing the game once per key press. It could be made (presumably) to match the non-blocking behavior, but IMO, the way I've implemented this blocking event gives a better experience.

Crimson Wizard

#9
@monkey0506, I think it is worth to mention that there is a potential issue with repeatedly_execute_always, and key_press, etc functions in custom script modules which do not check for IsGamePaused. You need to ensure that every instance of these functions, even from imported scripts, check pause state.

monkey0506

There are a lot of things which should respect the game's paused state, but there are just as many things which shouldn't. Whether or not a module's functions should respect the pause state depends entirely on the function that the module is providing, and module authors should bear the responsibility of making sure that they respect the pause state in a way which is logically consistent with the behavior they provide. The code snippet I provided here does respect the pause state. The KeyPressAlways module which I referenced in the other thread, is too abstract to force any particular behavior regarding the pause state, so it is up to the module user in that case to determine whether key presses should go before or after an "IsGamePaused" condition (which they would write in their own script, below the module).

I'm not sure what more beyond that you might have meant, but the engine's PauseGame and UnPauseGame methods do not, as you previously asserted, represent a "'character-protection' pause". With the proper logic in script modules and end-user developer scripts, these functions both work exactly as described by tor.brandt in the original post. The whole problem in this thread was one of bad logical conditions, not a bug or insufficient pause feature.

Crimson Wizard

#11
I finally realized my mistake. I did not quite remember details, so I was thinking that pause cannot stop game until it leaves the script function.

Now when I checked with the code, I see that AGS pause indeed suspends the blocking movement and similar actions. What it does not do, is that it does not literally suspend the script function at arbitrary point, but continues executing script commands until it either reaches the action that calls engine updates inside script (e.g. Wait, or blocking movement), or exits script function back to the engine.

monkey0506

Quote from: Crimson Wizard on Thu 10/11/2016 09:38:42What it does not do, is that it does not literally suspend the script function at arbitrary point, but continues executing script commands until it either reaches the action that calls engine updates inside script (e.g. Wait, or blocking movement), or exits script function back to the engine.

Hmm, yes, I guess in that case you're right. Calling PauseGame is not a blocking action, so it can't arbitrarily pause the execution of a script function like Wait does. If that was asked about, I apologize for overlooking that. I think it has often been asked about as a "non-blocking Wait" which would yield control of the game script for the rest of the game loop like Wait does, but without running in the blocking thread. Pausing the game doesn't do that. ;)

tor.brandt


tor.brandt

@monkey0506:
I tried implementing your pause function, and it works (i.e. it does indeed pause even during blocking functions), but I have to tap the pause key really fast, or else it toggles again (i.e. unpauses). If I hold down the pause key, it toggles repeatedly until I release the key.
Is this how it should work, or have I done something wrong?

monkey0506

Could you post the code you're using? Because the code I posted doesn't do that. (bit irritable just now, sorry if I'm being short with you)

tor.brandt

@monkey0506:
That's alright :smiley:

I used the exact code you posted above, except I didn't assign
Code: ags
eKeyCode pauseKey = eKeySpace;

but instead just replaced "pauseKey" with "eKeySpace" in the on_key_press function.

Could that make the difference, and if so, how?

monkey0506

To clarify, on_key_press is always executed repeatedly. Holding a key down will call on_key_press several times, but I don't know the rate offhand. Using on_key_press for this will cause your game to pause/unpause repeatedly.

The code in repeatedly_execute_always (from the code I posted) only executes if a blocking function is running and the pause key (eKeySpace) has been pressed (but not held). Holding down the pause key during a blocking function will only toggle the pause state once, not repeatedly.

This is a discrepancy between the behavior when a blocking script is running vs. when it isn't running.

If you want the on_key_press behavior to match the repeatedly_execute_always behavior, then you should use the KeyPressAlways module, which permits disabling the normal on_key_press function in favor of your custom on_key_press_always function (described in the module documentation).

If you want the repeatedly_execute_always behavior to match the on_key_press behavior, then you could add a variable to control how often the "held" state of the pause key resets.

Code: ags
eKeyCode pauseKey = eKeySpace; // this allows the key to be reassigned without having to change multiple lines in the script below
bool pauseKeyHeld = false; // whether the pause key has been held down from a previous game loop

// TODO: Adjust poll time as needed, e.g. GetGameSpeed() / 2 or GetGameSpeed() * 2
#define PAUSE_KEY_POLL_TIME GetGameSpeed()
int pauseKeyPoll = 0;
 
void TogglePause()
{
  if (IsGamePaused())
  {
    UnPauseGame();
  }
  else
  {
    PauseGame();
  }
}
 
function on_key_press(eKeyCode keycode) // standard on_key_press, NEVER called during blocking scripts
{
  if (keycode == pauseKey)
  {
    TogglePause();
  }
  if (IsGamePaused())
  {
    return;
  }
}
 
function repeatedly_execute_always() // called every game loop, during blocking AND non-blocking scripts
{
  if (!IsInterfaceEnabled()) // blocking script is running
  {
    if (!pauseKeyPoll) // poll timer expired, reset poll timer and "held" state
    {
      pauseKeyPoll = PAUSE_KEY_POLL_TIME;
      pauseKeyHeld = false;
    }
    else // poll timer not expired, update poll timer
    {
      pauseKeyPoll--;
    }
    if (IsKeyPressed(pauseKey)) // check raw state of pause key, key physically held down
    {
      if (!pauseKeyHeld) // if pause key was not previously held (last game loop) or poll timer expired
      {
        pauseKeyHeld = true; // set pause key as continuously held
        TogglePause(); // toggle pause state
      }
    }
    else if (pauseKeyHeld) // if pause key marked as held, but key has been physically released
    {
      pauseKeyHeld = false; // set pause key as not held
    }
  }
}

Crimson Wizard

#18
I wrote a KeyListener module in the past which is for knowing when the key was just pressed or just released, and how much time it was held down:
http://www.adventuregamestudio.co.uk/forums/index.php?topic=53226.0

but unless you are going to add more similar key controls to your game, that module may be too much to add for single case.

tor.brandt

@monkey0506:

I'm still new to scripting, so I don't understand in full how your code works, but just to be sure:
I tried putting your first code (from your post 10 Nov, 5.49) into my global script, and that resulted in the behaviour I described (i.e. if Space was held down, pause was toggled repeatedly, instead of just once). Here I'm not talking about how the script works, but about the actual experience when playing the game.

Is this how it should work, or did I do something wrong? I'm sorry if you already answered this question, but I didn't quite get your reply.


@Crimson Wizard:
Thanks, I'll take a look at your module!

SMF spam blocked by CleanTalk