Logic vs. animation framerate inconsistent - how to time UV animation precisely?

I am very nearly done with my first client project based on UPGE 0.2.5 after a month’s long slog, but one niggle still remains. My “game” has multiple dashed lines consisting of a subdivided plane (really an applied array modifier on a plane), deformed by a curve, with two animated UV textures (procedurally generated - no actual bitmap textures used). By altering the alpha these create two effects; in the first instance of dashes moving along the curve, and in the second the dashed lines gradually being revealed, giving the impression of the line of dashes extending from one object to another.

This “reveal” is triggered by keypresses, and once a line is fully revealed other things happen (think of a fuse burning towards a bomb, which should then detonate). My application requires taking control of the mainloop, from which I can easily trigger the animation “action” that the material provides. The problem is that compared to the logic clock (i.e. bge.logic.NextFrame() ticks) the length of time it takes to play the animation varies; 100 animated frames != 100 logic frames, and this makes it impossible to accurately sync the next event.

I can think of two possible routes here: first, to take control of the “reveal” UV animation in the mainloop, and update it with each logic tick - secondly, to check in the mainloop whether the animation has reached its target frame before proceeding to the next step in my script. But I’m wondering if either is the right one, or if there is a better way to do it?

Here’s my ridiculously complicated material, which in combination with a separate material that is keyframed to fade from black to white (and back again) achieves both these animations (the “material” input is used for the reveal, and the “time” input to make the dashes move):

To reiterate, what I would like to do is move the “reveal” animation out of the material, away from the timeline, and into control of my main.py code, or alternatively to have some way of main.py precisely determining the state of an animation action.

This might be the answer:

with bricks you can use the action brick. With python you can check on wich frame the action is

obj = obj/armature playing the action
frame = obj.getActionFrame()

if frame == 100:
    #do something

#or

if frame > 99 and frame < 100:
    #do something
2 Likes

Thanks. Yeah, I just noticed that the action frame counter is fractional, so a straight comparison with integers won’t work - I just do int(step[“object”].getActionFrame()) and only increment my own frame counter on whole frames.

This seems to work. I append animations to a list when I trigger them, and only advance my frame counter when at least one of the active animations has completed a full “frame”. Once the animation reaches its frame count it’s removed from the list. I only really play one animation at a time so I’m not sure how this would work with multiple simultaneous animations, but the intention to handle this is there.

  if station.anim_queue:
    # lock current_frame to animation frame
    count = False
    for anim in station.anim_queue:
      # [object, action, frames, current]
      anim_frame = int(anim[0].getActionFrame())
      if anim[3] < anim_frame: 
        # at least one full frame, count it
        anim[3] = anim_frame
        count = True
      else:
        # fractional frame
        pass
      if anim[2] < anim[3]: 
        # frames left
        pass
      else:
        # animation completed
        # we could check anim[0].isPlayingAction() here
        # but that would fail with multiple animations
        # playing on the same object simultaneously 
        station.anim_queue.remove(anim)
    if count:
      station.current_frame += 1
  else:
    station.current_frame += 1

  bge.logic.NextFrame()

If ultimately you’re looking for framerate consistency, I’d go with the Action actuator as @Cotaks mentioned, as to this day logic-bricks are still relatively faster then Python thanks their source being hard-corded in C++.

You mean it plays animations faster? Or that querying object properties is faster? What is “faster”? My main.py is 417 lines and implements its own state machine and its own scene scripting language. It also displays video on a secondary monitor, listens to inputs on five GPIOs and controls three as outputs, talks to an LED display over RS485, a projector over RS232 and an LED controller over I2C. At this stage (client demo on Monday) it is not within human ability to rewrite everything to fit UPBGEs logic bricks, so unless there’s a quick change that would yield a noticeable increase in performance I’m just not in the mood. And performance is not really a problem - I see 25% CPU usage on a four core chip, which is constant regardless of what I’m doing in my Python - even an empty game loop will take 25% CPU. This jumps to 60% when also playing video, but still far from taxing. I’m not looking for performance but timing consistency.

Despite the fact that most of your project exceeds my competence nevertheless I could imagine that it could be an advantage to keyframe your “Time” (or “Add”) instead of relying on its generated value.

No… I mean it’s the most likely method to maintain a stable performance / consistency for playing animations(s) - besides GPU skinning, but that’s a totally different topic.

Faster as in terms of performance. The higher the performance, the smaller the margin of performance lag is likely to happen.

Consistency and performance are the same thing though. Again, higher performance performance gives you that extra layer of protection against performance lag, which in turns reduces the changes of inconsistencies happening.

So you mean if I do this:

slot = controller.actuators["ActionSlot0"];
slot.action     = "myAction";
slot.frameStart = 0;
slot.frameEnd   = 100;
controller.activate(slot);

I will see less CPU usage than if I do this:

some_object.playAction("myAction", 0, 100)

If so I would love to hear why - doesn’t rendering an animation use the same pipeline regardless of how it’s triggered?

A logic-brick actuator waits for the Python controller (or Python component) to call it. This means it is dependent on Python, and Python in turn is dependent on logic cycles, and as well depends on performance. This does not necessarily mean Python is in-sync with performance time (delta time), but rather that it affects the performance, which in turn also affects Python. It is basically a dependency circle.