Enrich the go.animate function to unleash hidden potential

Hello all!

Please have a look at this feature request.

Basically, I suggest we add a new parameter to go.animate, an optional post processing function that would be called by the engine just prior to setting the new value of a property. The function would take the interpolated value computed by go.animate, do its thing, and return either a vector, quaternion, etc… the same types as what the “to” parameter can have. The resulting value would then be used by to update the property.

This frees us from using the update method in scripts for things such as moving an object along a circular motion for example. We would be able to use all the power of the go.animate method to our advantage (easing methods, delay, duration, etc…) but be free to update a property in any way we like.

I think that would not work well, as it would be a lot of extra calls between C and Lua (they’re not free).

I think you’re better off animating it yourself in update() to be honest.

3 Likes

I think @aglitchman has an example where he generates a custom vector using a Lua function and then use it instead of predefined easing functions (yes, it is possible to use vmath.vector() as Easing function, see API reference (go) ).

I have an example where you can play with it here: Easing functions – Alexey Gulev (custom easing input field)

1 Like

Yes! The live example - custom easing.

-- Adapted from https://easings.net/#easeOutBack
-- @param amp number increase this coefficient so that the amplitude of the swing is greater.
local function easing_outback(amp) 
    return function(k)
        local s = 1.70158 * amp
        k = k - 1
        return k * k * ((s + 1) * k + s) + 1
    end
end

local function build_easing(fn)
    local v = {}
    local points = 30
    for i = 1, points do
        table.insert(v, fn((i - 1) / (points - 1)))
    end
    return vmath.vector(v)
end

-- Generate custom easings
local OUTBACK1 = build_easing(easing_outback(1)) -- the same as go.EASING_OUTBACK
local OUTBACK2 = build_easing(easing_outback(2))
local OUTBACK4 = build_easing(easing_outback(4))

-- Usage example:
-- go.animate(".", "position.y", go.PLAYBACK_LOOP_PINGPONG, 100, OUTBACK4, 2)
6 Likes

@Mathias_Westerdahl yes I was afraid of this. But I thought the property was set by Lua code in the end. Maybe by inserting a function call there if Lua code is called anyway would not be a great deal. If C is setting the new value in the game object then I understand. Maybe we could have a go.fanimate function in pure Lua that would be less performant but include the post processing function as part of its signature. I don’t know. Perhaps C and Lua talk overhead would not be that penalizing too. We can measure! Food for thoughts!

@AGulev @aglitchman I don’t think this is what I am after - easing. I know we can use a table or map (which is just another word for function) as the easing function. What I look for is use any easing function with/in addition to the post processing function. This means that if my post processing function moves an object on a circular motion around in the screen, I would want any easing method applied to that so that, for example, the object would ease in (or out, or any of the many easing methods) on that circular path that I define. This decouples the easing from the “motion” code.

I am not sure I completely understand what @Bateleur is after. But maybe you can use go.animate to animate a custom property defined in a script (with go.property). Then in the update function you can read the value of this property and do whatever you want.

I don’t think the engine should call back the lua layer each time it updates a value for go.animate. If I understand correctly the go.animate function just “register” the animation and then the engine processes all such animations in a very efficient way in C.

2 Likes

The biggest issue for this would be adding ANY overhead is not worth it. Usefulness of the go process is that it is light and fast. This seems to a be a fringe edge case type situation of what go could be used for and with a better understanding, i.e. an example of what you are trying to achieve, there is most likely a better way for it to be done without adding overhead to every single go instance.

@Bateleur Do you have an example you could show? Maybe a clip of gameplay or even another tutorial from another engine? It is not quite obvious what you are trying to accomplish. The power of go is how light and efficient it is, adding more overhead doesn’t seem to “unlock” any potential but actually introduces complexity that hinders its usefulness.

@roccosaienz @blisteredMind ,

I might not have been the clearest. I’ll try to better explain what I have in mind in this post.

The feature request just popped into my mind as I was thinking about one of my project : a roulette game. Imagine the roulette wheel and the ball. Both are moving. The wheel rotates on itself and can be animated with a simple go.animate call where you change the angle/rotation property.

Now consider the ball. The ball makes a circular motion around the outer edge of the wheel. How can you animate it with go.animate? I don’t think you can. The equation of motion for the ball is essentially this: [radius * cos(2PI * t), radius * sin(2PI * t), 0] say for a ball moving in the x and y plane with a z value of 0. “t” is time here. @AGulev suggested easing as a way out of this problem. Easing is a different beast. It makes animation more pleasant to the eye, e.g. not so linear. It plays with acceleration and timing to do this. But it is not designed to provide the kind of motion we are after for the ball.

The key parameter in go.animate is the “to” parameter. It is fixed right now. If we could have a function called with the value the engine computes after interpolating and easing and its result fetched to the property value (the position of the ball in our roulette example) then we could place the ball anywhere we want and benefit from all the power of the go.animate function (delay, easing, duration…) with a single line of code to boot. In our example, we could have the ball move around the wheel and use some easing method so that it would accelerate and decelerate on its trajectory, a trajectory that the developer has full control of. The motion would be a circle, and it would not be linear - unless we choose linear easing of course!

BUT… all of this assumed - and I indicated it in my feature request on GitHub - that Lua was responsible to set the final value of the go at the end. We know C interpolates and “eases” the value. But I thought maybe it then passes the result to Lua which fetches the value into the property. If C does all the interpolation and easing work and fetches the property value 60 times per second as well then yes perhaps it’s too costly to implement the post processing function as I described. In fact, it’s probably C which fetches the value and no Lua code is called or used in the current implementation.

For sure I can use the update function to move the ball. It’s just that if I want easing I would have to code it myself. @roccosaienz suggested I animate the property and read it and update it myself. Would that work? Ideally it would be nice if we could animate custom properties. Say a Property object with a value of type 3D vector or whatever as opposed to a real property that is part of an object. Wait! Is this another feature request??? :sweat_smile: But if I animate the position of the ball directly and update the position in the update function, would that work? Or would there be conflicts of some sort between the C code and the Lua code updating the property on their own respective agenda?

Please let me know if I better explained what I had in mind!

If I was to do this in 3D, I would use the go function to rotate the wheel at constant speed and rotate the ball in the opposite direction. If you created the ball with origin offset to be at the edge of the wheel it would look like a roulette wheel does. I would then set a timer for when the ball should begin to fall and slow. At that point I would cancel the animation of the ball and enable physics and apply a force to keep it going around but start to fall. Placing triggers on the space would allow you to determine what space the ball landed. You could create an animation call back that keeps the wheels rotation going if the ball has not landed on a spot and once it had make the last animation slow down with the easing part of the wheel for the last rotation. You could probably do this all in the physics engine tho.

1 Like

But perhaps to explain what you might be looking for with the go function, you can apply more than one animation at time to a single game object. Such as applying the circle motion as constant loop and then applying an inward or outward motion in one shot animation.

1 Like

Ok so I’m working in 2D for this project. I put 3D vector as my position in the roulette wheel ball
example but it is actually meant as a 2D game. Here is a more complete set of equations:

local r ← radius(t) # t is time here
local noise ← [ noise_x(t), noise_y(t) ]
[ r * cos(2PI * t), r * sin(2PI * t) ] + noise

This is basically how I move the ball around the roulette wheel. So the radius is constant for the major part of the animation, then starts to decrease after a certain point, up to a certain minimum value. This captures the fact that the ball is being slowly pulled towards the middle of the roulette wheel. “t”, or the time, controls this behavior. It also controls the circular motion of the ball. “t” gradually decelerates down to zero when the ball starts being pulled inwards. Finally, when this is the case, we add some noise to simulate the ball bouncing around.

I wanted to simply use a function (the function above) in go.animate. For this to work, I would need to animate the position of the ball [t, t] from say [0, 0] to [10, 10] (that is, 10 loops). I wanted the engine to interpolate an “ease” [t, t] then pass it to my function that would compute the true (final) position as a function of “t” and then have the engine pass the final value to the position property of the ball go.

My issue with go.animate is that it is only interpolating values on a line, from whatever starting value the property has to the “to” parameter. We have no control in between.

That said, my suggestion probably wouldn’t work and I would need to code easing myself and compose it with my functions above in the update function.

@blisteredMind yes I think what you describe is kind of what I look for. The ball is subject to various forces. I just don’t see how to make it do a many full circles with go.animate. The simplest would simply to use the update function. Just for my information, instead of using timers can we use the “complete function” parameter and call go.animate from there? Also you don’t need to be random at all with the graphics/animation part. I coded a solution in react native where I simply randomly sample which slot will be “chosen” by the ball, and I sample also where that slot will be at the end of the animation (say north, or west or any other angle) and then I match all of this to a sound effect and it looks pretty good. I play an animation which is decided in advance! The trick is to match where the slot will be at the end with where the ball will be, with the sound effect! Unfortunately I don’t remember how to make react native sing anymore and I don’t want to do it too. I will redo in Defold! Also I could use go.animate by having the ball occupy just a fraction of a larger square sprite with the square width equal to the radius of the roulette wheel. The ball would appear at its place. Then animate rotation of the square go. That could work and I am not sure if this is what you alluded to. Most of the square sprite would be transparent, except for the ball. This is a solution I think.

You should add a go property for t and then animate that property. Put your function into the update or fixed_update of the script that uses the go property of t to calculate your position and set it to the game object. Select your easing and duration along with a callback function for what should happen when t is done.

You can also add a go property for the duration and be able to set variable amounts of time that this should happen if you wanted.

2 Likes

Those are good ideas!

I knew you could add a property to a go but didn’t know you could animate it - I thought only a specific set of known properties could be animated and not custom ones.

Duration cannot be that random because of the sound effect. I would need more than one sound effect (of a ball rolling around). But it’s a possibility for sure!

Also you can check the Tweener lua library which allows you to set a custom callback on each “tween” step - https://github.com/Insality/defold-tweener

5 Likes

You can use metamethods to kinda do what you want. You can create a table with defined metamethod __newindex() in which you can postprocess the current value. And then run the go.animate() on that table. I used it to animate score label counter where the number changing would slow down closer to the target score.

Hi! I will check this. Thanks!

Unsure if I understand. You mean we can animate a property of type table and by bypassing its setter method, do some postprocessing there?