Queue playAction() in UPBGE python mainloop

Hi!
I’m using UPBGE with a python mainloop as described here:

I started from the default cube and I created two actions on this cube in the Dope Sheet Action Editor:

  • ActionRotation: frame 1 with Z Euler Rotation = 0°, frame 30 with Z Euler Rotation = 45°
  • ActionScale: frame 1 with X Scale, Y Scale and Z Scale = 1, frame 30 with X Scale, Y Scale and Z Scale = 0.5

This is the code in main.py:

#! bpy
import bge
import time
import timeit

# active scene cube
scene = bge.logic.getCurrentScene()
# the object "Cube"
cube = scene.objects["Cube"]

keyboard = bge.logic.keyboard

LOOP = True
FPS = 60  # Frames per Second
last_time = timeit.default_timer()

bge.logic.setExitKey(bge.events.LEFTARROWKEY)

while LOOP:
    # mainloop
    # wait until next frame
    current_time = timeit.default_timer()
    # sleep time until next frame
    sleep_time = 1 / FPS - (current_time - last_time)
    if sleep_time > 0:
        time.sleep(sleep_time)
    last_time = current_time

    # change scene
    # read keyboard input
    k_events = bge.logic.KX_INPUT_ACTIVE
    # uparrow key pressed
    if keyboard.events[bge.events.UPARROWKEY] == k_events:
        cube.playAction("ActionRotation", 1, 30)
        cube.playAction("ActionScale", 1, 30)
    # downarrow key pressed
    if keyboard.events[bge.events.DOWNARROWKEY] == k_events:
        cube.playAction("ActionScale", 30, 1)
        cube.playAction("ActionRotation", 30, 1)
    # escape key pressed
    if keyboard.events[bge.events.ESCKEY] == k_events:
        # Stop mainloop -> exit program
        LOOP = False
    # render next frame
    bge.logic.NextFrame()

But it doesn’t work as I want.
When I press UPARROWKEY I see only the ActionScale action, when I press DOWNARROWKEY I see only the ActionRotation action.

What I want to do is to have a kind of queue where the actions are performed only when the previous ones are finished.
So if I press UPARROWKEY I will see the rotation and after the scale, if I press DOWNARROWKEY I will see the scale and after the rotation.
What is the best way to do this?

I tried to pass priority=0 and priority=1 to playAction(), but in this case the action with lower priority is not played after the other action.

Append the actions to a list on key press, if the action isn’t already there. Then if list isn’t empty, pop from it. Repeat when the last one finishes playing.

Here I did it with an action actuator which is more convenient.

def setActSlot(slot, name, start, end):
    slot.action     = name;
    slot.frameStart = start;
    slot.frameEnd   = end;

# -- --- -- ---

dict  = {"CubeAction": [0, 6], "CubeAction.001": [0, 6]};
queue = [(name, dict[name][0], dict[name][1]) for name in dict];

# -- --- -- ---

#entry point
def runQueue(cont):

    slot = cont.actuators["ActionSlot0"];

    if queue:
        
        if slot.frame == slot.frameEnd:
            setActSlot(slot, *queue.pop(0))
            cont.activate(slot);
    
# -- --- -- ---

Also when adding to the queue on a keypress you probably want to check that the action isn’t already queued. Maybe try like so,

action = None;

if some_keypress: action = (action0_name, action0_start, action0_end);
elif another_keypress: action = (action1_name, action1_start, action1_end);

if action:
    if action not in queue: queue.append(action);

The “action” tuple here being a list of arguments for the playback function, or the actuator setup function in my case.

Also note that I’m being lazy in the initial example by checking whether the current frame is the last, which can be pretty unreliable in some scenarios. If I remember correctly, loops and pingpong skip hitting the last frame at the last millisecond and just reset back to start/play in reverse. The playback function may do some of that too, I’m not sure. You should probably use getActionFrame and/or isPlayingAction to check whether playback has ended (or is about to end).

Thank you for your suggestion, it inspired me :grinning:
I read your code and I tried to do in this way:

  • declare a list outside the mainloop
actionsQueue = []
  • in the mainloop write something like this
    if JUST_ACTIVATED in keyboard.inputs[bge.events.AKEY].queue:
        actionsQueue.append((OBJECT, "ACTION_NAME", FRAME_START, FRAME_END))
  • in the end of the mainloop write this:
    if actionsQueue:
        if not actionsQueue[0][0].isPlayingAction():
            actionToDo = actionsQueue.pop(0)
            actionToDo[0].playAction(actionToDo[1], actionToDo[2], actionToDo[3])

It seems to work!