Tween Example
This article will focus on some common uses and interfaces in Cosos Creator Tween System.
Construct Tween
Tween can be constructed either by the tween
method or by using new Tween<T>(target: T)
.
Note: 'tween' is a tool method provided by the engine and is not a member of 'Tween' class, please note the distinction. For this, please refer to the interface description: Tween Interface.
Chain API
Most action-related interfaces return either this
or a new Tween
object, so it's easy to use chain calls to combine:
tween()
.target(this.node)
.to(1.0, { position: new Vec3(0, 10, 0) })
.by(1.0, { position: new Vec3(0, -10, 0) })
.delay(1.0)
.by(1.0, { position: new Vec3(0, -10, 0) })
.start()
to, by
Here is a demonstration of how to use a to
type of tween to bind the position information of a node and offset its position by 10 units along the Y-axis.
let tweenDuration : number = 1.0; // Duration of tween
tween(this.node.position)
.to( tweenDuration, new Vec3(0, 10, 0), { // The interface 'to' represents the absolute value of the node
onUpdate : (target:Vec3, ratio:number)=>{ // Implement ITweenOption's onUpdate callback to accept the current tweening progress
this.node.position = target; // Assign the position of the node to the result calculated by the tween system
}
}).start(); // Call the start method to enable tween
Returning Property Target Values through Functions
TIP
Supported since v3.8.4
tween(this.node).to(1, { angle: ()=>90 ).start();
The above code is equivalent to:
tween(this.node).to(1, { angle: 90 ).start();
Custom Interpolation Functions
TIP
Supported since v3.8.4
- For a specific property of the current action
// Simultaneously animate the 'angle' and 'position' properties on node, but only define a custom interpolation function for the 'angle' property
tween(this.node).to(1, {
angle: {
value: 90,
progress(start: number, end: number, current: number, ratio: number): number {
return lerp(start, end, ratio);
},
},
position: v3(90, 90, 90),
}
).start();
- For all properties of the current action
To handle custom interpolation functions for both the 'angle' and 'position' properties associated with the 'to' action's node, consider setting the 'opt.progress' parameter.
tween(this.node).to(1, { angle: 90, position: v3(90, 90, 90) }, {
progress(start: number, end: number, current: number, ratio: number): number {
return lerp(start, end, ratio);
},
}).start();
Custom Easing Functions
TIP
Supported since v3.8.4
- For a specific property of the current action
// 1. Use a built-in easing function
tween(this.node).to(1, {
angle: {
value: 90,
easing: 'backIn',
},
position: v3(90, 90, 90),
}).start();
// Or
// 2. Use a custom easing function
tween(this.node).to(1, {
angle: {
value: 90,
easing: (k: number): number => {
if (k === 1) {
return 1;
}
const s = 1.70158;
return k * k * ((s + 1) * k - s);
},
},
position: v3(90, 90, 90),
}).start();
- For all properties of the current action
To handle custom easing functions for both the 'angle' and 'position' properties associated with the 'to' action's node, consider setting the 'opt.easing' parameter.
// 1. Use a built-in easing function
tween(this.node).to(1, { angle: 90, position: v3(90, 90, 90) }, {
easing: 'backIn',
}).start();
// Or
// 2. Use a custom easing function
tween(this.node).to(1, { angle: 90, position: v3(90, 90, 90) }, {
easing: (k: number): number => {
if (k === 1) {
return 1;
}
const s = 1.70158;
return k * k * ((s + 1) * k - s);
},
}).start();
Easing Strings
TIP
Supported since v3.8.4
Consider defining a class:
class StringTarget {
string = '';
}
Example 1 (Integer Strings)
Using the to
interface, animate the string
property from '0' to '100' over one second:
const t = new StringTarget();
t.string = '0';
// Here, the type of the `string` value can be either a number or a string that can be converted to a number
tween(t).to(1, { string: 100 }).start();
tween(t).to(1, { string: '100' }).start();
Example 2 (Floating-Point Strings)
const t = new StringTarget();
t.string = '10';
// Animate from 10 to 110, keeping two decimal places throughout the animation
// For example: '10.00' -> '43.33' -> '76.67' -> '110.00'
tween(t).to(1, { string: { value: 110, toFixed: 2 } }).start(); // Note: `value` represents the target value, `toFixed` specifies the number of decimal places
Example 3 (Custom Handling of Multiple String Properties)
const o = {
gold: "¥0.00",
exp: '1000/1000',
lv: 'Lv.100',
attack: '100 points',
health: '10.00',
};
const tweenFormat = {
currency(value: number): TTweenCustomProperty<string> {
return {
value: `¥${value}`,
progress(start: number, end: number, current: string, ratio: number): string { // Custom progress function
return `¥${lerp(start, end, ratio).toFixed(2)}`; // Keep two decimal places
},
convert(v: string): number { // Custom conversion callback to convert string to number
return Number(v.slice(1)); // The string starts with '¥' which isn't part of the animation, so slice it off and convert the remaining part to a number
},
};
},
health(value: number): TTweenCustomProperty<string> {
// `health` is a floating-point number with two decimal places, so no need for a custom progress function; use the built-in progress function
return {
value: `${value}`,
toFixed: 2, // Specify two decimal places; without this, the result would be an integer string
};
},
exp(value: number): TTweenCustomProperty<string> {
return {
value: () => `${value}/1000`,
progress(start: number, end: number, current: string, ratio: number): string {
return `${lerp(start, end, ratio).toFixed(0)}/1000`;
},
convert(v: string): number {
return Number(v.slice(0, v.indexOf('/')));
},
// `exp` is an integer string, so no need to specify the `toFixed` parameter
};
},
lv(value: number): TTweenCustomProperty<string> {
return {
value: `Lv.${value}`,
progress(start: number, end: number, current: string, ratio: number): string {
return `Lv.${lerp(start, end, ratio).toFixed(0)}`;
},
convert(v: string): number {
return Number(v.slice(v.indexOf('.') + 1));
},
};
},
};
tween(o).to(1, {
gold: tweenFormat.currency(100),
health: tweenFormat.health(1),
exp: tweenFormat.exp(0),
lv: tweenFormat.lv(0),
}).start();
Custom Easing for Arbitrary Object Types
class MyProp {
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
public static lerp (a: MyProp, b: MyProp, out: MyProp, t: number): MyProp {
const x = a.x;
const y = a.y;
out.x = x + t * (b.x - x);
out.y = y + t * (b.y - y);
return out;
}
public static add (a: MyProp, b: MyProp): MyProp {
const out = new MyProp();
out.x = a.x + b.x;
out.y = a.y + b.y;
return out;
}
public static sub (a: MyProp, b: MyProp): MyProp {
const out = new MyProp();
out.x = a.x - b.x;
out.y = a.y - b.y;
return out;
}
clone(): MyProp {
return new MyProp(this.x, this.y);
}
equals (other: MyProp, epsilon = EPSILON): boolean {
return (
Math.abs(this.x - other.x) <= epsilon * Math.max(1.0, Math.abs(this.x), Math.abs(other.x))
&& Math.abs(this.y - other.y) <= epsilon * Math.max(1.0, Math.abs(this.y), Math.abs(other.y))
);
}
x = 0;
y = 0;
}
class MyObject {
angle = 0;
str = '';
private _myProp = new MyProp();
set myProp(v) {
this._myProp.x = v.x;
this._myProp.y = v.y;
}
get myProp() {
return this._myProp;
}
}
const o = new MyObject();
o.myProp.x = 1;
o.myProp.y = 1;
tween(o)
.by(1, { myProp: {
value: new MyProp(100, 100), // Target value
progress: MyProp.lerp, // Provide custom easing function for the object
clone: v => v.clone(), // Provide clone function
add: MyProp.add, // If using the `by` action, provide the `add` method
sub: MyProp.sub, // If using the `by` action with reverse, provide the `sub` method along with `add`
legacyProgress: false, // Set to `false` to use the new progress callback with object parameters, e.g., `MyProp.lerp`, instead of the default number-based progress callback
} }).id(123)
.reverse(123)
.start();
Binding Different Objects
There are more scenarios where Node
is used as a binding target in development, and the code example is as follows.
let quat : Quat = new Quat();
Quat.fromEuler(quat, 0, 90, 0);
tween(this.node)
.to(tweenDuration, {
position: new Vec3(0, 10, 0), // Bind position
scale: new Vec3(1.2, 3, 1), // Bind scale
rotation:quat } // Bind rotation
)
.start(); // Call the start method to enable tween
In fact the tween can be bound to any object, the code example is as follows:
class BindTarget{
color : Color
}
let sprite : Sprite = this.node.getComponent(Sprite) ;
let bindTarget : BindTarget = new BindTarget();
bindTarget.color = Color.BLACK;
tween(bindTarget)
.by( 1.0, { color: Color.RED }, {
onUpdate(tar:BindTarget){
sprite.color = tar.color; // Set the sprite to the color inside the 'BindTarget'
}
})
.start()
Multiple Actions
In general, a tween can consist of one or more actions, and Tween
maintains a data structure consisting of multiple actions to manage all actions within the current tween.
The following code demonstrates moving the object's position 10 units along the Y-axis and then 10 units along the -Y-axis.
let tweenDuration : number = 1.0;
tween(this.node.position)
.to( tweenDuration, new Vec3(0, 10, 0), {
onUpdate : (target:Vec3, ratio:number)=>{
this.node.position = target;
}
})
.to( tweenDuration, new Vec3(0, -10, 0), {
onUpdate : (target:Vec3, ratio:number)=>{
this.node.position = target;
}
}) // At this point the number of actions in the tween is 2
Multiple tweens can also be organized using the union
, sequence
, and parallel
interfaces. By creating some fixed tweens in advance and using union
, sequence
, parallel
to combine them, you can reduce the amount of code written.
Union
The union
method combines all current actions into one, with the following code example:
let tweenDuration : number = 1.0;
tween(this.node)
.to(tweenDuration, { position:new Vec3(0, 10, 0) }) // Here the node is the target of the tween
.to(tweenDuration, { position:new Vec3(0, -10, 0) }) // At this point the number of actions in the Tween is 2
.union() // The above two tweens will be combined into one, and the number of actions in the Tween will be 1
.start(); // Call the start method to enable tween
TIP
Supported from v3.8.4: union(fromId)
The union(fromId)
method merges actions from a specified identifier to the current action into a single sequence action. It is often used in conjunction with id
, repeat
, and repeatForever
. The sample code is as follows:
const node = new Node();
tween(node)
.to(1, { scale: new Vec3(10, 10, 10) })
.by(1, { position: new Vec3(200, 0, 0) }).id(123) // Mark the by action as 123
.delay(1) // Delay for one second
.reverse(123) // Reverse the action marked as 123, i.e., the previous 'by' action
.union(123) // Merge actions starting from the action marked as 123
.repeat(3) // Repeat the merged actions 3 times
.start();
Sequence
The sequence
function will transform the incoming tween into a queue form and add it to the current tween, with the following code example:
let tweenDuration: number = 1.0;
let t1 = tween(this.node)
.to(tweenDuration, { position: new Vec3(0, 10, 0) })
let t2 = tween(this.node)
.to(tweenDuration, { position: new Vec3(0, -10, 0) })
tween(this.node).sequence(t1, t2).start(); // Add t1 and t2 tweens to the new tween queue
You can achieve the same effect using the then interface as well:
let tweenDuration: number = 1.0;
let t1 = tween(this.node)
.to(tweenDuration, { position: new Vec3(0, 10, 0) });
let t2 = tween(this.node)
.to(tweenDuration, { position: new Vec3(0, -10, 0) });
tween(this.node).then(t1).then(t2).start(); // First add tween t1 to the queue, then add tween t2 to the queue
Parallel
The parallel
function will convert the incoming tween into parallel form and add it to the current tween, with the following code example:
let tweenDuration: number = 1.0;
let t1 = tween(this.node)
.to(tweenDuration, { position: new Vec3(0, 10, 0) })
let t2 = tween(this.node)
.to(tweenDuration, { position: new Vec3(0, -10, 0) })
tween(this.node).parallel(t1, t2).start(); // Convert t1 and t2 to parallel tweens and add the current tween
Then
The then
interface allows a new tween to be passed in and that tween integrated and added to the current tween's action, with the following code example.
let tweenAfter = tween(this.node)
.to(1.0, { position: new Vec3(0, -10, 0) })
tween(this.node)
.by(1.0, { position: new Vec3(0, 10, 0) })
.then(tweenAfter)
.start();
Delay
The delay
interface adds a delay to the current action after it.
Note that in the following code example, different delay
positions can cause completely different results.
After a delay of 1 second, start the movement and perform it twice in a row:
tslet tweenDuration: number = 1.0; tween(this.node) .delay(1.0) .to(tweenDuration, { position: new Vec3(0, 10, 0) }) .to(tweenDuration, { position: new Vec3(0, -10, 0) }) .start()
After the first movement, there is a 1 second delay before the second movement is performed.
tslet tweenDuration: number = 1.0; tween(this.node) .to(tweenDuration, { position: new Vec3(0, 10, 0) }) .delay(1.0) .to(tweenDuration, { position: new Vec3(0, -10, 0) }) .start()
Repeat
The repeat
interface can add a repeat count to the tween, if the embedTween
parameter is empty, the last action of the current tween will be used as parameter.
This means that if the current tween consists of more than one tween, only the last one will be repeated, note the following example:
let tweenDuration: number = 1.0;
tween(this.node)
.to(tweenDuration, { position: new Vec3(0, 10, 0) })
.by(tweenDuration, { position: new Vec3(0, -10, 0) })
.repeat(3) // Note that the 'by' tween is repeated 3 times here
.start()
If the second parameter embedTween
is not empty, the embedded tween will be repeated, with the following code example:
let tweenDuration: number = 1.0;
let embedTween = tween(this.node)
.by(tweenDuration, { position: new Vec3(0, -10, 0) })
tween(this.node)
.to(tweenDuration, { position: new Vec3(0, 10, 0) })
.repeat(3, embedTween) // Repeat the 'embedTween'
.start()
The usage of repeatForever
interface is similar to repeat
, but it becomes permanently repeated.
Action Identifier
TIP
Supported since v3.8.4
The id
interface is used to assign a numerical identifier to the preceding action. Ensure that identifiers are unique, as the tween system will directly use the first action found during internal searches.
It is frequently used in conjunction with union(fromId)
, reverse(id)
, and reverse(tween, id)
. Here's an example code snippet:
tween(this.node)
.by(1, { position: new Vec3(100, 0, 0) }).id(123) // Assign identifier 123 to the by action
.delay(1) // Delay for one second
.reverse(123) // Reverse the action identified by 123, and add the reversed action to the current action queue
.start();
Reversing Actions
TIP
Supported since v3.8.4
The reverse
interface has three overloaded implementations:
// Returns a "newly created" tween instance that reverses all actions in the current tween.
reverse (): Tween<T>;
/**
* Reverses a specific action identified within the current tween.
* @param id Identifier of the action to reverse within the current tween.
* @return Returns the instance itself for chaining.
*/
reverse (id: number): Tween<T>;
/**
* Reverses a specific action identified within another tween.
* @param otherTween Another tween instance to search for the action by identifier.
* @param id Identifier of the action to reverse.
* @return Returns the instance itself for chaining.
*/
reverse<U extends object = any> (otherTween: Tween<U>, id?: number): Tween<T>;
reverse()
Example
Reverse all actions in the current tween and return a newly created tween instance:
const t1 = tween(this.node)
.by(1, { position: new Vec3(100, 0, 0) }) // Node's x coordinate +100 relative to current position
.by(1, { scale: new Vec3(2, 2, 2) }); // Node's overall scale +2
const t2 = t1.reverse(); // Reverse entire tween t1 and store it as a new tween instance t2
// Equivalent to:
// const t2 = tween(this.node)
// .by(1, { scale: new Vec3(-2, -2, -2) })
// .by(1, { position: new Vec3(-100, 0, 0) });
tween(this.node)
.to(1, { position: new Vec3(200, 0, 0) })
.then(t2)
.start();
reverse(id)
Example
Reverse a specific action identified within the current tween:
tween(this.node)
.to(1, { scale: new Vec3(10, 10, 10) })
.by(1, { position: new Vec3(200, 0, 0) }).id(123) // Assign identifier 123 to the by action
.delay(1)
.reverse(123) // Reverse the action identified by 123 within the current tween
.start();
reverse(otherTween, id?)
Example
- Without passing an
id
, reverse the entire tweenotherTween
and integrate all actions into a single Sequence action, then add this action to the current tween's action queue. Achieves the same effect as the example in reverse() Example:
const t = tween(this.node)
.by(1, { position: new Vec3(100, 0, 0) }) // Node's x coordinate +100 relative to current position
.by(1, { scale: new Vec3(2, 2, 2) }); // Node's overall scale +2
tween(this.node)
.to(1, { position: new Vec3(200, 0, 0) })
.reverse(t) // Reverse the entire tween t and add it as an action
.start();
- When passing an
id
, reverse a specific action identified withinotherTween
and add this action to the current tween's action queue:
const t = tween(node)
.parallel(
tween(node).sequence(
tween(node).by(1, { position: new Vec3(100, 0, 0) }).id(123), // Assign identifier 123 to the by action
tween(node).to(1, { position: new Vec3(1000, 0, 0) }),
),
tween(node).delay(1),
);
tween(node)
.to(1, { position: new Vec3(200, 0, 0) })
.reverse(t, 123) // Reverse the action identified by 123 within tween t
.start();
Node-related Tween
The node-related methods only work if target
is Node
.
Visibility
The show
and hide
interfaces control the display and hiding of the bound nodes, in the following example the nodes are hidden and displayed after a 1 second delay.
tween(this.node)
.hide()
.delay(1.0)
.show()
.start();
RemoveSelf
This method generates a delete node action that removes the incoming node from within the scene tree.
In the following example, the node will be removed from the scene after a delay of 1 second.
tween(this.node)
.delay(1.0)
.removeSelf()
.start()
Call
The call
interface allows a callback action to be added to the tween, which is useful when dealing with certain asynchronous logic, as in the following example:
tween(this.node)
.to(1.0, { position: new Vec3(0, 10, 0)})
// This method will be called when the 'to' action is completed
.call( ()=>{
console.log("call");
})
.start()
Set
The properties of the target can be set via set
. The following example will set the node at [0, 100, 0] after a delay of 1 second.
tween(this.node)
.delay(1.0)
.set({ position: new Vec3(0, 100, 0) })
.start();
It is also possible to set several different properties at the same time, with the following code example:
tween(this.node)
// Set the position, scale and rotation of the node at the same time
.set({ position: new Vec3(0, 100, 0), scale: new Vec3(2, 2, 2), rotation: Quat.IDENTITY } )
.start();
Clone
The clone
method copies the current tween to the target parameter. Note that the source tween and the current tween should be of the same type when copying, i.e. the T
in new Tween<T>(target: T)
needs to be of the same type. The code example is as follows.
const srcTween = tween(this.node).delay(1.0).by(1.0, { position: new Vec3(0, 10, 0) })
// Copy 'srcTween' to a node named Cone
srcTween.clone(find("Cone")).start();
TIP
Starting from v3.8.4, it is supported to clone tweens without passing the target
parameter, meaning the cloned tween directly uses the original target.
Example:
const srcTween = tween(this.node).delay(1.0).by(1.0, { position: new Vec3(0, 10, 0) });
const clonedTween = srcTween.clone(); // clonedTween's target is also the this.node object
Specifying Different Target Objects for Sub-tweens in sequence
, parallel
, then
Interfaces
TIP
Supported from v3.8.4
If you want to handle actions like position
, contentSize
, and color
simultaneously in a tween chain, with their targets being different—node, UITransform component on the node, and Sprite component on the node respectively, you can achieve this using the following code:
tween(node)
.parallel(
tween(node).to(1, { position: new Vec3(100, 100, 0) }),
tween(node.getComponent(UITransform) as UITransform).to(1, { contentSize: size(100, 100) }),
tween(node.getComponent(Sprite) as Sprite).to(1, { color: color(100, 100, 100, 100) }),
)
.start();
The sequence
and then
interfaces work in a similar manner.
Stopping Tweens
stop
Instance method used to stop a specified tween.
const t = tween(node)
.by(1, { position: v3(90, 90, 90) })
.start(); // Start the tween
// ......
t.stop(); // Stop the tween
stopAll
Static method to stop all tweens.
Tween.stopAll();
stopAllByTag
Static method to stop all tweens with a specified tag.
- Without target parameter
const MY_TWEEN_TAG = 123;
const node1 = new Node();
const node2 = new Node();
const t1 = tween(node1)
.tag(MY_TWEEN_TAG)
.by(1, { position: new Vec3(100, 0, 0) })
.start();
const t2 = tween(node2)
.tag(MY_TWEEN_TAG)
.by(1, { scale: new Vec3(2, 2, 2) })
.start();
Tween.stopAllByTag(MY_TWEEN_TAG); // Both t1 and t2 have the tag MY_TWEEN_TAG, so both will be stopped
- With target parameter
const MY_TWEEN_TAG = 123;
const node1 = new Node();
const node2 = new Node();
const t1 = tween(node1)
.tag(MY_TWEEN_TAG)
.by(1, { position: new Vec3(100, 0, 0) })
.start();
const t2 = tween(node2)
.tag(MY_TWEEN_TAG)
.by(1, { scale: new Vec3(2, 2, 2) })
.start();
Tween.stopAllByTag(MY_TWEEN_TAG, node1); // Both t1 and t2 have the tag MY_TWEEN_TAG, but only t1 will be stopped since its target is node1, t2 will continue running as its target is node2
stopAllByTarget
Static method to stop all tween instances associated with a specified target object.
const node1 = new Node();
const node2 = new Node();
const t1 = tween(node1)
.by(1, { position: new Vec3(100, 0, 0) })
.start();
const t2 = tween(node2)
.by(1, { scale: new Vec3(2, 2, 2) })
.start();
const t3 = tween(node1)
.by(1, { angle: 90 })
.start();
Tween.stopAllByTarget(node1); // t1 and t3 are associated with node1, so they will be stopped, t2 will continue running
Pausing/Resuming Tweens
TIP
Supported from v3.8.4
Manual pause and resume:
const t = tween(this.node)
.by(1, { position: new Vec3(90, 0, 0) }).id(123)
.reverse(123)
.union()
.repeatForever()
.start(); // Start the tween
// ......
t.pause(); // Pause the tween t
// ......
t.resume(); // Resume the tween t
IMPORTANT
From v3.8.4, if the tween target is of type Node, the tween will automatically pause and resume based on the Node's active state.
Scaling Tween Time
TIP
Supported from v3.8.4
tween(this.node)
.to(1, { position: new Vec3(100, 100, 100) })
.timeScale(0.5)
.start();
In the above example, timeScale
is set to 0.5. The to
action's duration is 1, so the actual total duration of the tween execution will be duration / timeScale = 1 / 0.5 = 2 seconds
.
Getting the Total Duration of a Tween
TIP
Supported from v3.8.4
const t = tween(this.node)
.to(1, { position: new Vec3(100, 100, 100) })
.to(1, { scale: new Vec3(2, 2, 2) })
.start();
console.log(t.duration); // Outputs 2
Custom Actions (constant duration)
TIP
Supported from v3.8.4
The update
interface is used to add a custom action with constant duration.
Its interface declaration is as follows:
export type TweenUpdateCallback<T extends object, Args extends any[]> = (target: T, ratio: number, ...args: Args) => void;
/**
* Add a custom action with constant duration.
* @param duration The tween time in seconds.
* @param cb The callback of the current action.
* @param args The arguments passed to the callback function.
* @return The instance itself for easier chaining.
*/
update<Args extends any[]> (duration: number, cb: TTweenUpdateCallback<T, Args>, ...args: Args): Tween<T> { ... }
Example code:
let done = false;
tween(this.node)
.delay(1)
.by(1, { position: v3(90, 90, 90) })
.update(1, (target: Node, ratio: number, a: number, b: boolean, c: string, d: { 'world': () => number }): void =>{
// ......
// target, a, b, c, d are all parameters passed to the update callback, n parameters can be specified
// target is this.node
// a is 123
// b is true
// c is 'hello'
// d is { 'world': (): number => 456 }
}, 123, true, 'hello', { 'world': (): number => 456 })
.repeat(2)
.call(() => { done = true; })
.start();
Custom Action (Indeterminate Duration)
TIP
Supported from v3.8.4
The updateUntil
interface is used to add a custom action with an indeterminate duration.
Its interface declaration is as follows:
export type TweenUpdateUntilCallback<T extends object, Args extends any[]> = (target: T, dt: number, ...args: Args) => boolean;
/**
* Adds a custom action with an indeterminate duration. If the callback function returns `true`, it indicates that the current action has ended.
* @param cb The action callback function. If the callback function returns `true`, it indicates that the current action has ended.
* @param args The arguments to pass to the action callback function.
* @return The instance itself to allow for chained calls.
*/
updateUntil<Args extends any[]> (cb: TweenUpdateUntilCallback<T, Args>, ...args: Args): Tween<T> { ... }
The following example code is used to track a dynamic object. When the object is approached, the node scales up, resets its position, and repeats the process.
import { _decorator, Component, Node, tween, v3, Vec3 } from 'cc';
const { ccclass, property } = _decorator;
const positionTmp = new Vec3();
const positionTmp2 = new Vec3();
@ccclass('TweenTest')
export class TweenTest extends Component {
@property(Node)
targetNode: Node | null = null;
start() {
tween(this.node)
.updateUntil((curNode: Node, dt: number)=>{
const d = Vec3.copy(positionTmp2, this.targetNode!.position).subtract(curNode.position);
const length = d.length();
if (length < 10) {
return true; // The `updateUntil` process ends when the distance between the current node and the target node is less than 10
}
const newPos = Vec3.copy(positionTmp, curNode.position).add(d.normalize().multiplyScalar(length / 10 * dt * 10));
curNode.setPosition(newPos);
return false; // Returning `false` indicates that the `updateUntil` process should continue
})
.by(0.25, { scale: v3(1, 1, 0) }, { easing: 'cubicInOut' }).id(1)
.reverse(1)
.call((curNode?: Node)=>{
const newPos = v3((Math.random() - 0.5) * 400, (Math.random() - 0.5) * 400, 0);
if (newPos.y < 100 && newPos.y > 0) newPos.y = 100;
if (newPos.y > -100 && newPos.y < 0) newPos.y = -100;
if (newPos.x < 100 && newPos.x > 0) newPos.x = 100;
if (newPos.x > -100 && newPos.x < 0) newPos.x = -100;
curNode?.setPosition(newPos);
})
.union()
.repeatForever()
.start();
tween(this.node)
.by(1, { angle: 360 })
.repeatForever()
.start();
}
}
Result:
Starting Tween from a Specific Time
TIP
Supported from v3.8.4
The start
interface can receive a startTime
parameter, in seconds, to start the tween from a specific time. All tweens before this time will be executed immediately.
const t = tween(this.node)
.to(1, { position: new Vec3(100, 100, 100) })
.call(() => {})
.to(1, { scale: new Vec3(2, 2, 2) })
.start(1); // Start from the 1st second
In the above example, two to
actions are created with a total duration of 2 seconds. Since the tween starts from the 1st second, the position of this.node
will be immediately set to (100, 100, 100), and the callback of call
will be invoked immediately. Then, over the next 1 second, the scale animation will enlarge to 2.
Destruction
Automatic Destruction
TIP
Supported from v3.8.4
When the target of tween is Node
, it will listen to its destruction event for automatic destruction of tween, and the call to target
method will also update the listener automatically.
Manual Destruction
Most tweens destroy themselves after the last action, but tweens that are not destroyed properly, such as repeatForever
, will remain in memory after switching scenes. You need to call the destroy interface manually to destroy it.
To stop and destroy the tween, the following methods are available.
member
stop
function to destroy the tween, with the following code example.tslet t = tween(this.node.position) .to( 1.0, new Vec3(0, 10, 0), { onUpdate : (target:Vec3, ratio:number)=>{ this.node.position = target; } }) t.stop();
Use the static functions
stopAll
,stopAllByTag
andstopAllByTarget
to destroy all or specific tweens, with the following code example.tsTween.stopAll() // Destroy all tweens Tween.stopAllByTag(0); // Destroy all tweens with 0 as the tag Tween.stopAllByTarget(this.node); // Destroy all tweens on this node
Note: In versions prior to v3.8.4, remember to stop the corresponding tweens when switching scenes. From v3.8.4, the engine will handle this automatically.