Skip to content

continuous tweens#301

Open
nearnshaw wants to merge 2 commits into
mainfrom
285-continuous-tweens
Open

continuous tweens#301
nearnshaw wants to merge 2 commits into
mainfrom
285-continuous-tweens

Conversation

@nearnshaw

Copy link
Copy Markdown
Member

No description provided.

@nearnshaw
nearnshaw requested a review from a team as a code owner July 31, 2025 19:26
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 31, 2025

Copy link
Copy Markdown

Deploying adr with  Cloudflare Pages  Cloudflare Pages

Latest commit: 1fe6aa0
Status: ✅  Deploy successful!
Preview URL: https://76cfe6fe.adr-cvq.pages.dev
Branch Preview URL: https://285-continuous-tweens.adr-cvq.pages.dev

View logs

Comment on lines +23 to +27
We will introduce three new continuous tween modes to the Tween component:

- `RotateContinuous`
- `MoveContinuous`
- `TextureMoveContinuous`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’d like to start a conversation about an alternative approach—similar to how Unity’s DOTween works.

The idea is to introduce the following new modes to the Tween component:

message MoveIncrement {
  decentraland.common.Vector3 value = 1;
}

message RotateIncrement {
  decentraland.common.Quaternion value = 1;
}

message ScaleIncrement {
  decentraland.common.Vector3 value = 1;
}

message TextureMoveIncrement {
  decentraland.common.Vector2 value = 1;
  optional TextureMovementType movement_type = 2; // default = TextureMovementType.TMT_OFFSET
}

In addition, we could introduce a new loop mode for the tween sequence: TextureLoop.INCREMENTAL.

Here’s how it would work:

Tween.create(entity, {
  mode: Tween.Mode.MoveIncrement({ value: Vector3.create(10, 10, 10), duration: 2.0 })
})

This would move the entity by 10 units along all axes over 2 seconds—executed once.

If used in a sequence with different loop modes:

  • RESTART: It moves the entity by 10 units, then immediately resets (subtracts the same amount), and starts over.
  • YOYO: It moves the entity by 10 units, then reverses by the same amount (like a ping-pong).
  • INCREMENTAL (new): It moves the entity by 10 units, and when it ends, it repeats the same increment again.

So, using RESTART and YOYO, you can achieve the same results as with the existing non-incremental modes (START/END), but instead of moving between fixed points, the effect is achieved through value addition and subtraction.

This would still work with the existing duration and easing_function parameters, and I believe it would be clear and intuitive for creators to use.

What do you think?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @kuruk-mm
I think this is a good addition, I see it being useful
It doesn't fix the original problem that I tried to solve with this ADR, which is to prevent glitches like these:

spinning.carpincho.1.mov
Screen.Recording.2025-07-31.at.11.01.52.AM.mov

Unless we also move the interpretation of the TweenSequence to the engine, which is something that we should do eventually, but at least short term I don't think we have the bandwidth for it in the Foundation.

Both approaches could coexist I think, we don't need to pick just one or the other.

@kuruk-mm kuruk-mm Aug 7, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ADR should not address that issue. It is focused on introducing new functionality (incremental/continuous tweens).

To resolve the sequence issue, we should push TweenSequence into the engine.

Signed-off-by: Nicolas Earnshaw <nearnshaw@decentraland.org>
---
layout: adr
adr: 285
title: Continuous tweens

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By definition a tween is an interpolation, and naming a tween "continuous tween" is in conflict with "linear tweens", besides not reflecting the true nature of what the rest of the component does.

This ADR suits best a brand new component that may be called "Animation", "TransformAnimation" or similar as the semantics are different from a tween(not lerping/interpolating) and closer to CSS animations.

https://en.m.wikipedia.org/wiki/Inbetweening

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pravusjif @nearnshaw JIC you didn't see this

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @menduz
sorry yes, we saw it
Yes it's technically true that a continuous tween is an oxymoron
But on the other hand, we felt it was a lot more discoverable to have this functionality in the Tween component. Writing Tween. and seeing all the options is way better than having to remember that a different component name exists for when you want this other behavior.
So we prioritized ease of use over technical correctness

The feature is already developed and in production BTW
https://docs.decentraland.org/creator/development-guide/sdk7/move-entities/#constant-rotation

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nearnshaw the link 404

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hukasu

hukasu commented Jan 20, 2026

Copy link
Copy Markdown

For RotateContinuous and MoveContinuous should they compound on top of the inicial rotation/translation or should the value completely replace the rotate/translation?

@hukasu

hukasu commented Jan 21, 2026

Copy link
Copy Markdown

@nearnshaw could RotateContinuous have its field changed from Quaternion direction to Vector3 axis? using Quartenion open the possibility for shear transformations, is it desirable?

@hukasu

hukasu commented Jan 21, 2026

Copy link
Copy Markdown

for testing purposes i am editing the test10-continuous-tweens.ts of https://github.com/dclexplorer/mobile-scene-test

it already has a bunch of cubes with RotateContinuous, i edited them to use Quaternion.fromAngleAxis and i am creating another cube by their sides with what i think should be an equivalent TweenSequence

  const rotateY = engine.addEntity()
  Transform.create(rotateY, {
    position: Vector3.create(continuousTweenBaseX - 20, 2, ctRow1Z),
    scale: Vector3.create(1, 1, 1)
  })
  MeshRenderer.setBox(rotateY)
  Material.setPbrMaterial(rotateY, { albedoColor: Color4.create(0.8, 0.3, 0.3, 1) })
  addOrientationMarkers(rotateY, 1)
  Tween.setRotateContinuous(rotateY, Quaternion.fromAngleAxis(1, Vector3.create(0, 1, 0)), rotationSpeed)
  createLabel('Y', Vector3.create(continuousTweenBaseX - 20, 4, ctRow1Z), 0.9)

  const rotateYReference = engine.addEntity()
  Transform.create(rotateYReference, {
    position: Vector3.create(continuousTweenBaseX - 20, 2, ctRow1Z - 4),
    scale: Vector3.create(1, 1, 1)
  })
  MeshRenderer.setBox(rotateYReference)
  Material.setPbrMaterial(rotateYReference, { albedoColor: Color4.create(0.8, 0.3, 0.3, 1) })
  addOrientationMarkers(rotateYReference, 1)
  Tween.create(rotateYReference, {
    mode: Tween.Mode.Rotate({
      start: Quaternion.fromAngleAxis(0, Vector3.create(0, 1, 0)),
      end: Quaternion.fromAngleAxis(90, Vector3.create(0, 1, 0))
    }),
    duration: 2000,
    easingFunction: EasingFunction.EF_LINEAR
  })
  TweenSequence.create(rotateYReference, {
    loop: TweenLoop.TL_RESTART,
    sequence: [
      {
        mode: Tween.Mode.Rotate({
          start: Quaternion.fromAngleAxis(90, Vector3.create(0, 1, 0)),
          end: Quaternion.fromAngleAxis(180, Vector3.create(0, 1, 0))
        }),
        duration: 2000,
        easingFunction: EasingFunction.EF_LINEAR
      },
      {
        mode: Tween.Mode.Rotate({
          start: Quaternion.fromAngleAxis(180, Vector3.create(0, 1, 0)),
          end: Quaternion.fromAngleAxis(270, Vector3.create(0, 1, 0))
        }),
        duration: 2000,
        easingFunction: EasingFunction.EF_LINEAR
      },
      {
        mode: Tween.Mode.Rotate({
          start: Quaternion.fromAngleAxis(270, Vector3.create(0, 1, 0)),
          end: Quaternion.fromAngleAxis(360, Vector3.create(0, 1, 0))
        }),
        duration: 2000,
        easingFunction: EasingFunction.EF_LINEAR
      }
    ]
  })
  createLabel('Y', Vector3.create(continuousTweenBaseX - 20, 4, ctRow1Z - 4), 0.9)

is that TweenSequence supposed to be equivalent to the Tween.setRotateContinuous?

@robtfm

robtfm commented Jun 17, 2026

Copy link
Copy Markdown

we (bevy team) notice a divergence between unity implementation and adr for continuous tweens with duration. the ADR says that easing should be applied in that circumstance, but unity does not apply any easing on continuous tweens, with or without duration.

we think unity's approach is more intuitive than the ADR spec, so we implemented the same way.

@hukasu

hukasu commented Jun 17, 2026

Copy link
Copy Markdown

i mean, having a way to have describe a continuous tween that allows for easing would be useful, like a fan starting from resting and spinning up

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants