Skip to content

07 Tweens

Dave Kimber edited this page Jun 11, 2023 · 4 revisions

For a quick demo, check out the tweens example game.

What is a Tween?

A tween is a description of a desired change over a period of time.

Currently tweens are limited to sprites, they allow you to modify an attribute of the sprite by specifying a target value (relative to the current value), how quickly the target value should be reached, and with what kind of progression curve (linear, quadratic, elastic etc. see Easing Functions). Configuration options are available for reversing and repeating tweens, as well at triggering code at different points as the tween progresses.

Creating a Tween

The quip.tweens/tween function requires a keyword (the field to modify) and a scalar value (the target). It returns a tween that can be attached to a sprite with the quip.tween/add-tween function.

;; A simple tween which will rotate a sprite from its current `:rotation` 180 degrees.
(def t (quip.tween/tween :rotation 180))

;; Adding the tween to the player sprite will start the tween
(quip.tween/add-tween player t)

⚠️ You must call the quip.tween/update-sprite-tweens function on the game state in your scene's update function:

(defn update-level-01
  [state]
  (-> state
      quip.sprite/update-scene-sprites
      ;; You must update tweens for them to work
      quip.tween/update-sprite-tweens))

You can set the number of frames that a tween should take to complete by specifying the :step-count. This defaults to 100, lower numbers will make the tween complete faster, but very low numbers won't be as smooth.

;; This tween will flip the sprite in 15 frames instead of 100
(def t (quip.tween/tween
        :rotation
        180
        :step-count 15))

Non-scalar Values

If the field you want to tween is not a scalar, numerical value (such as :pos or :vel which are vectors) you must supply an :update-fn function.

This function should take the current value of the field, and a delta d to modify it by, it should return the new value of the field.

;; We want to tween the x value of `:pos`
(def t (quip.tween/tween
        :pos
        30
        :update-fn (fn [[x y] d]
                     [(+ x d) y])))

For convenience, the quip.tween namespace provides the tween-x-fn and tween-y-fn functions which will tween the first and second elements of a vector respectively.

;; It's often easier to use the built in function
(def t (quip.tween/tween
        :pos
        30
        :update-fn quip.tween/tween-x-fn))

Easing Functions

By default a tween will use a linear easing function, meaning the rate of change to the value will be constant. You can specify other easing functions with the :easing-fn optional argument.

You can define you own easing function as a function of a single number x (between 0 and 1) which represents the progress of the tween. Your function should return a value between 0 and 1 which represents how much of the change to the value should have happened.

;; This easing function describes a quadratic curve from 0 to 1
(defn example-ease
  [x]
  (* x x))

It's rare that you need to define your own easing function. The quip.tween namespace makes several easing functions available for convenience. Representations of these functions can be seen at easings.net.

  • ease-sigmoid
  • ease-in-sine
  • ease-out-sine
  • ease-in-out-sine
  • ease-in-quad
  • ease-out-quad
  • ease-in-out-quad
  • ease-in-cubic
  • ease-out-cubic
  • ease-in-out-cubic
  • ease-in-quart
  • ease-out-quart
  • ease-in-out-quart
  • ease-in-quint
  • ease-out-quint
  • ease-in-out-quint
  • ease-in-expo
  • ease-out-expo
  • ease-in-out-expo
  • ease-in-circ
  • ease-out-circ
  • ease-in-out-circ
  • ease-in-back
  • ease-out-back
  • ease-in-out-back
  • ease-in-elastic
  • ease-out-elastic
  • ease-in-out-elastic
  • ease-in-bounce
  • ease-out-bounce
  • ease-in-out-bounce

Yoyos, Repetition and Completion

Yoyos

You can make a tween "yoyo" by setting the :yoyo? optional argument to true. This will make the tween reverse itself once it has gotten to the target value. If your tween is modifying a non-scalar field, you must also provide a :yoyo-update-fn which should do the opposite of your :update-fn, i.e. reduce the current value by the delta instead of increase it.

;; We want to tween the x value of `:pos` and yoyo back to the original value
(def t (quip.tween/tween
        :pos
        30
        :update-fn (fn [[x y] d]
                     [(+ x d) y])
        :yoyo? true
        :yoyo-update-fn (fn [[x y] d]
                          [(- x d) y])))

Once again, the quip.tween namespace provides built-in functions for yoyo updates on [x y] vectors, tween-x-yoyo-fn and tween-y-yoyo-fn.

;; It's often easier to use the built in functions
(def t (quip.tween/tween
        :pos
        30
        :update-fn quip.tween/tween-x-fn
        :yoyo? true
        :yoyo-update-fn quip.tween/tween-x-yoyo-fn))

You can also specify a function to modify the sprite when it has completed the initial tween and is about to start the yoyo. The :on-yoyo-fn optional argument should be a function that takes the current value of the sprite and returns the updated sprite.

(def t (quip.tween/tween
        :pos
        30
        :update-fn quip.tween/tween-x-fn
        :yoyo? true
        :yoyo-update-fn quip.tween/tween-x-yoyo-fn
        :on-yoyo-fn (fn [s]
                      ;; Turn the sprite around before returning
                      (flip-direction s))))

Repetition

You can specify a number of times to repeat the tween (including yoyo) with the :repeat-times optional argument. The value should be a positive integer, if you want a tween to repeat indefinitely you can simply use the ##Inf symbolic value for infinity.

;; Flip the sprite upside down, then back up, 10 times
(def t (quip.tween/tween
        :rotation
        180
        :yoyo? true
        :repeat-times 10))

You can also specify a function to modify the sprite when a repeat has completed with the :on-repeat-fn optional argument.

;; Flip the sprite upside down, then back up, forever
(def t (quip.tween/tween
        :rotation
        180
        :yoyo? true
        :repeat-times ##Inf
        :on-repeat-fn (fn [s]
                        (prn "I don't feel well")
                        s)))

Completion

Finally you can specify a function to modify the sprite when a tween has fully completed (including all yoyos and repetitions) with the :on-complete-fn optional argument.

⚠️ the :on-complete-fn function will never be called if :repeat-times id ##Inf.

;; Flip the sprite upside down, then back up, gain some xp for doing so
(def t (quip.tween/tween
        :rotation
        180
        :yoyo? true
        :on-complete-fn (fn [s]
                          (add-xp s 200))))