animating a panel¶
drive a spring and a smooth damper to make a button that bounces when clicked
by the end of this guide you will have a button that springs upward and scales out when you click it, then settles back to rest on its own. you will wire a SmoothFloat and a SpringFloat through an AnimationSet, feed both into a PanelTransform, and learn the exact rebuild discipline that keeps the panel ticking while the motion runs and idle otherwise.
the core loop¶
goo has no implicit animation system. the pattern is always the same four steps:
- store a damper or spring as a field on your panel.
- mutate its target when input arrives and call
Rebuild(). - advance it in a
Tick(float dt)override and returntruewhile it is still moving. - sample
.CurrentinsideBuild()and feed it into the tree.
Tick is called every frame before the build gate. returning true requests a rebuild. returning false lets the panel idle with no further work until the next input event.
set up the fields¶
create Code/Demos/SpringButtonUI.cs. declare a SmoothFloat for scale and a SpringFloat for the bounce offset:
using Goo;
using Goo.Animation;
using Sandbox.UI;
using PanelTransform = Goo.PanelTransform; // disambiguate from Sandbox.UI.PanelTransform
namespace Sandbox;
public sealed class SpringButtonUI : GooPanel<Container>
{
SmoothFloat _scale = new( initial: 1f, smoothTime: 0.08f );
SpringFloat _bounce = new( initial: 0f, frequency: 6f, damping: 0.5f );
readonly AnimationSet _anims = new();
protected override void OnEnabled()
{
base.OnEnabled();
_anims.Clear();
_anims.Add( dt => _scale.Tick( dt ) );
_anims.Add( dt => _bounce.Tick( dt ) );
}
}
SmoothFloat approaches its target smoothly and tracks velocity, so rapid re-clicks snap it back without a glitch. SpringFloat can overshoot: a damping of 0.5 means the button bounces past its target before settling. frequency controls how fast the spring oscillates.
registering animators from OnEnabled (not per-frame) and calling _anims.Clear() first prevents double-registration on hotload or re-enable.
tick and build¶
add the Tick override and a Build that samples both animators:
protected override bool Tick( float dt ) => _anims.UpdateAll( dt );
protected override Container Build() => new Container
{
Width = 200f,
Height = 200f,
JustifyContent = Justify.Center,
AlignItems = Align.Center,
Children =
{
new Container
{
Width = 90f,
Height = 90f,
BorderRadius = 12,
BackgroundColor = Theme.AccentBlue,
JustifyContent = Justify.Center,
AlignItems = Align.Center,
Transform = PanelTransform.Scale( _scale.Current )
.TranslateY( Px.Of( -_bounce.Current * 30f ) ),
OnClick = _ => Poke(),
Children = { new Text( "click me" ) { FontColor = Color.White } },
},
},
};
_scale.Current and _bounce.Current are sampled fresh every time Build() runs. the transform chain is Scale first, then TranslateY: the button grows and lifts simultaneously. Px.Of wraps the float in the Length the engine expects for an absolute pixel offset.
wire the click¶
void Poke()
{
_scale.Target = _scale.Target > 1.01f ? 1f : 1.35f;
_bounce.Current = 1f;
_bounce.Target = 0f;
Rebuild();
}
toggling _scale.Target between 1 and 1.35 means a second click while still expanded springs back to normal size. setting _bounce.Current = 1f directly gives the spring an instant kick rather than a target change, which produces the pop feel. Rebuild() is called once here to kick off the first paint; after that Tick keeps returning true until both animators settle, and goo rebuilds each of those frames automatically.
press play, drop SpringButtonUI on a screen panel, and click. the button scales out and pops upward, bounces once or twice, then idles.
what just happened¶
the button is three layers:
- fields (
_scale,_bounce,_anims) hold all the motion state.Build()never mutates them. Tickadvances every animator every frame and signals whether a rebuild is needed. when both animators settle,Tickreturnsfalse, the panel goes idle, andBuild()stops running until the next click.Build()is a pure function of the current field values. the same field state always produces the same panel tree. that is what lets goo diff it cheaply.
the animations reference covers AnimationSet, tweens, Animator, and AgePhase - all follow the same advance-in-Tick, sample-in-Build posture.
see also¶
- animations - the full damper table,
AnimationSet,Tween,Animator,AgePhase, and the chaining-gotcha for|vs||. - panel transform - the
Scale,TranslateY, and the rest of the op families, plus thePx.OfandLengthargument forms. - build method - the
Tick/Rebuild()contract and why the rebuild gate matters for performance. - building a keystroke hud -
AgePhaseand the spawn-hold-expire pattern in a live overlay.