游戲開發(fā)中的緩動(dòng)動(dòng)畫是提升游戲UI體驗(yàn)的重要因素之一,比如對(duì)話框彈出和關(guān)閉、按鈕動(dòng)效出現(xiàn)于消失、道具飛入背包等,可以使用LayaAir引擎提供的Tween緩動(dòng)類與Ease類實(shí)現(xiàn)。LayaAir引擎的Tween類與Ease類結(jié)合使用,能基本滿足游戲開發(fā)的緩動(dòng)效果。
- Tween緩動(dòng)類用來實(shí)現(xiàn)目標(biāo)對(duì)象屬性的緩動(dòng),比如目標(biāo)對(duì)象的x軸或y軸的緩動(dòng)距離等目標(biāo)值的設(shè)置,以及緩動(dòng)開始、停止、清理等設(shè)置。
- Ease類用來定義大量的緩動(dòng)函數(shù)以便實(shí)現(xiàn)Tween動(dòng)畫的具體緩動(dòng)效果
Tween
| 結(jié)構(gòu) | 描述 |
|---|---|
| Package | laya.utils.Tween |
| Class | Laya.Tween |
例如:水平來回移動(dòng)圖片

class Test {
constructor() {
Laya.init(Laya.Browser.clientWidth, Laya.Browser.clientHeight, Laya.WebGL);
this.initStage();
this.run();
}
initStage(){
Laya.stage.alignH = Laya.Stage.ALIGN_CENTER;
Laya.stage.alignV = Laya.Stage.ALIGN_MIDDLE;
Laya.stage.scaleMode = Laya.Stage.SCALE_SHOWALL;
Laya.stage.bgColor = "#000000";
}
run(){
this.drawLine();
this.createSprite("res/image/bomb.png");
this.tweenTo();
}
tweenTo(){
//從起始點(diǎn)移動(dòng)到中心點(diǎn)
//Laya.Tween.to(this.sprite, {x:Laya.stage.width>>1}, 3000);
//從中心點(diǎn)移動(dòng)到起始點(diǎn)
this.sprite.x = 0;
this.sprite.y = Laya.stage.height>>1;
Laya.Tween.from(this.sprite, {x:Laya.stage.width>>1, y:Laya.stage.height>>1}, 3000);
}
drawLine(){
let fromX = Laya.stage.width >> 1;
let fromY = 0;
let toX = Laya.stage.width >> 1;
let toY = Laya.stage.height;
let lineColor = "#111111";
let lineWidth = 1;
Laya.stage.graphics.drawLine(fromX, fromY, toX, toY, lineColor, lineWidth);
fromX = 0;
fromY = Laya.stage.height >> 1;
toX = Laya.stage.width;
toY = Laya.stage.height >> 1;
Laya.stage.graphics.drawLine(fromX, fromY, toX, toY, lineColor, lineWidth);
}
createSprite(skin){
this.sprite = new Laya.Sprite();
this.sprite.loadImage(skin, Laya.Handler.create(this, function(){
let texture = Laya.loader.getRes(skin);
this.sprite.pivot(texture.width>>1, texture.height>>1);
this.sprite.pos(texture.width>>1, Laya.stage.height>>1);
}));
Laya.stage.addChild(this.sprite);
return this.sprite;
}
}
//啟動(dòng)
new Test();
| 屬性 | 描述 |
|---|---|
repeat:number = 1 |
重播次數(shù),默認(rèn)為1,為0則表示無限循環(huán)播放。 |
update:Handler |
更新回調(diào),緩動(dòng)數(shù)值發(fā)生變化時(shí),回調(diào)變化的值。 |
| 存取器 | 描述 |
|---|---|
set progress(v:number):void |
設(shè)置當(dāng)前執(zhí)行比例 |
| 方法 | 描述 |
|---|---|
clear():void |
停止并清理當(dāng)前緩動(dòng) |
complete():void |
立即結(jié)束緩動(dòng)并到終點(diǎn) |
from():Tween |
從props屬性緩動(dòng)到當(dāng)前狀態(tài) |
pause():void |
暫停緩動(dòng),可以通過resume和restart重新開始。 |
recover():void |
回收到對(duì)象池 |
restart():void |
重新開始暫停的緩動(dòng) |
resume():void |
恢復(fù)暫停的緩動(dòng) |
setStartTime(startTime:number):void |
設(shè)置開始時(shí)間 |
to():Tween |
緩動(dòng)對(duì)象的props屬性到目標(biāo)值 |
靜態(tài)屬性
緩動(dòng)類Tween提供了較多的方法,常用的是from()和to()方法,這兩個(gè)方法的參數(shù)設(shè)置完全相同,但效果有所不同,from()是從緩動(dòng)目標(biāo)點(diǎn)向初始位置產(chǎn)生運(yùn)動(dòng),即從緩動(dòng)目標(biāo)位置來看,to()是從初始位置向緩動(dòng)目標(biāo)位置產(chǎn)生運(yùn)動(dòng),即到緩動(dòng)目標(biāo)位置去。
//從`props`屬性緩動(dòng)到當(dāng)前狀態(tài)
Laya.Tween.from(
target:any,
props:any,
duration:number,
ease?:Function,
complete?:Handler,
delay?:number,
coverBefore?:boolean,
autoRecover?:boolean
):Tween
// 緩動(dòng)對(duì)象的props屬性到目標(biāo)值
Laya.Tween.to(
target:any,
props:any,
duration:number,
ease?:Function,
complete?:Handler,
delay?:number,
coverBefore?:boolean,
autoRecover?:boolean
):Tween
| 參數(shù) | 描述 |
|---|---|
| target:any | 目標(biāo)對(duì)象,即將更改屬性值的對(duì)象。 |
| props:any | 目標(biāo)對(duì)象變化的屬性值 |
| duration:number | 耗費(fèi)的毫秒時(shí)間 |
| ease:Function = null | 緩動(dòng)類型,默認(rèn)為勻速運(yùn)動(dòng)。 |
| complete:Handler = null | 結(jié)束回調(diào)函數(shù) |
| delay:number = 0 | 延遲執(zhí)行毫秒時(shí)間 |
| coverBefore:boolean = false | 是否覆蓋之前的緩動(dòng) |
| autoRecover:boolean = true | 是否自動(dòng)回收,默認(rèn)為true表示緩動(dòng)結(jié)束后自動(dòng)回收到對(duì)象池。 |
props屬性
props是目標(biāo)對(duì)象改變產(chǎn)生緩動(dòng)效果的屬性,可以影響緩動(dòng)效果的運(yùn)動(dòng)軌跡。目標(biāo)對(duì)象的公共屬性都可以進(jìn)行設(shè)置,比如常見的坐標(biāo)位置、透明度、旋轉(zhuǎn)、軸心點(diǎn)、寬高等其它屬性。
ease 緩動(dòng)函數(shù)
Ease類定義了大量的緩動(dòng)函數(shù),以便實(shí)現(xiàn)Tween動(dòng)畫的具體緩動(dòng)效果,即Tween緩動(dòng)動(dòng)畫的具體動(dòng)畫類型由Ease進(jìn)行指定。
| 緩動(dòng)函數(shù) | 描述 |
|---|---|
Laya.Ease.backIn |
開始時(shí)往后運(yùn)動(dòng)然后反向朝目標(biāo)移動(dòng) |
Laya.Ease.backInOut |
開始運(yùn)動(dòng)時(shí)是向后跟蹤,再倒轉(zhuǎn)方向并朝目標(biāo)移動(dòng),稍微過沖目標(biāo)再次倒轉(zhuǎn)方向,回來朝目標(biāo)移動(dòng)。 |
Laya.Ease.backOut |
開始運(yùn)動(dòng)時(shí)朝目標(biāo)移動(dòng),稍微過沖再倒轉(zhuǎn)方向回來朝著目標(biāo)。 |
Laya.Ease.bounceIn |
以零速率開始運(yùn)動(dòng),執(zhí)行時(shí)加速。類似球向地板跌落又彈起到逐漸減小的回彈運(yùn)動(dòng)。 |
例如:逐字緩動(dòng)掉落效果

class Test {
constructor() {
Laya.init(Laya.Browser.clientWidth, Laya.Browser.clientHeight, Laya.WebGL);
this.initStage();
this.run();
}
initStage(){
Laya.stage.alignH = Laya.Stage.ALIGN_CENTER;
Laya.stage.alignV = Laya.Stage.ALIGN_MIDDLE;
Laya.stage.scaleMode = Laya.Stage.SCALE_SHOWALL;
Laya.stage.bgColor = "#000000";
}
run(){
//文字區(qū)域?qū)挾? let width = 400;
//分解字節(jié)串為字符并為其創(chuàng)建文本對(duì)象
let str = "JunChow";
let len = str.length;
for(let i=0; i<len; i++){
//為單個(gè)字符創(chuàng)建文本
let txt = this.createText(str.charAt(i));
//設(shè)置單個(gè)字符的坐標(biāo)位置
let x = (Laya.stage.width - width >> 1) + (width / len * i);
txt.x = x;
//增加動(dòng)效
let y = Laya.stage.height >> 1;
Laya.Tween.to(txt, {y:y}, 1000, Laya.Ease.elasticOut, null, i*1000);
}
}
createText(char){
let txt = new Laya.Text();
txt.text = char;
txt.font = "Impact";
txt.fontSize = 100;
txt.color = "#FFFFFF";
Laya.stage.addChild(txt);
return txt;
}
}
//啟動(dòng)
new Test();
例如:加載圖片并設(shè)置緩動(dòng)效果

class Test {
constructor() {
Laya.init(Laya.Browser.clientWidth, Laya.Browser.clientHeight, Laya.WebGL);
this.initStage();
this.run();
}
initStage(){
Laya.stage.alignH = Laya.Stage.ALIGN_CENTER;
Laya.stage.alignV = Laya.Stage.ALIGN_MIDDLE;
Laya.stage.scaleMode = Laya.Stage.SCALE_SHOWALL;
Laya.stage.bgColor = "#000000";
}
run(){
//加載圖片資源
let url = "res/image/npc.png";
Laya.loader.load(url, Laya.Handler.create(this, function(url){
//創(chuàng)建并加載圖片到舞臺(tái)顯示
let img = new Laya.Image();
img.loadImage(url);
Laya.stage.addChild(img);
//緩動(dòng)對(duì)象的props屬性到目標(biāo)值
Laya.Tween.to(
//緩動(dòng)對(duì)象
img,
//變化屬性目標(biāo)值
{x:Laya.stage.width - img.texture.width>>1, y:Laya.stage.height-img.texture.height>>1},
//花費(fèi)的毫秒時(shí)間
3000,
//緩動(dòng)類型
Laya.Ease.elasticOut,
//結(jié)束后回調(diào)函數(shù)
Laya.Handler.create(this, function(){
//創(chuàng)建文本
let txt = new Laya.Text();
txt.font = "Impact";
txt.fontSize = 80;
txt.color = "#FFFF00";
txt.stroke = 1;
txt.strokeColor = "#EEEEEE";
txt.text = "Game Start";
txt.pos(Laya.stage.width-txt.width>>1, Laya.stage.height-txt.height>>1);
Laya.stage.addChild(txt);
}),
//延遲執(zhí)行毫秒時(shí)間
1000
)
},[url]));
}
}
//啟動(dòng)
new Test();
封裝類庫
使用TypeScript封裝動(dòng)畫效果類庫
export default class Effect{
private static _instance:Effect;
public static getInstance():Effect{
if(!this._instance){
this._instance = new Effect();
}
return this._instance;
}
private _constructor(){
}
/**
* 類似蘋果系統(tǒng)上圖標(biāo)上下抖動(dòng)的效果
* @param target {Sprite} 抖動(dòng)對(duì)象
* @param initY {number} 抖動(dòng)對(duì)象初始Y軸坐標(biāo)位置
* @param callback {Function} 抖動(dòng)完畢后回調(diào)函數(shù)
* @param thisObj {any} 回調(diào)函數(shù)this對(duì)象
*/
shake(target, initY, callback, thisObj){
//抖動(dòng)頻率[時(shí)間, 移動(dòng)距離]
let data = [[20, 300], [15, 300], [10, 300], [5, 300]];
let index = 0;
//執(zhí)行抖動(dòng)
toShake();
function toShake(){
if(index >= data.length){
callback && callback.apply(thisObj, []);
}else{
let prop = {"y":initY - data[index][0]};
let duration = data[index][1];
let ease = null;
Laya.Tween.to(target, prop, duration, ease, Laya.Handler.create(null, ()=>{
prop = {"y": initY};
Laya.Tween.to(target, prop, duration, ease, Laya.Handler.create(null, ()=>{
++index;
toShake();
}));
}));
}
}
}
/**
* 向上移動(dòng)淡出效果
* @param target {Sprite} 淡出對(duì)象
* @param duration {number} 淡出時(shí)長(zhǎng)單位毫秒
* @param ease {Function} 淡出函數(shù)
* @param callback {Function} 淡出完成回調(diào)函數(shù)
* @param thisObj {any} 回調(diào)函數(shù)this對(duì)象
* @param args {Array} 回調(diào)傳參
*/
flowout(target, duration=500, ease=null, callback=null, thisObj=null, args=null){
let props = {y:target.y-150, alpha:0};
if(callback){
Laya.Tween.to(target, props, duration, ease, Laya.Handler.create(thisObj, callback, args));
}else{
Laya.Tween.to(target, props, duration, ease, Laya.Handler.create(target, target.removeSelf, args));
}
}
/**
* 文本數(shù)字增減漸變效果
* @param begin {number} 開始的數(shù)值
* @param end {number} 漸變到的數(shù)值
* @param callback {Function} 淡出完成回調(diào)函數(shù)
* @param thisObj {any} 回調(diào)函數(shù)的this對(duì)象
*/
flownum(begin, end, callback, thisObj=null){
let diff = Math.abs(end - begin);
if(diff <= 0){
return;
}
let cur = begin;
let per = diff / (end - begin);
let delay = 30;
let timer = new Laya.Timer();
timer.loop(delay, this, onLoop);
function onLoop(){
cur += per;
--diff;
if(diff < 0){
timer.clearAll(this);
timer = null;
}else{
callback && callback.apply(thisObj, [cur]);
}
}
}
/**
* 閃爍
* @param target {Sprite} 目標(biāo)節(jié)點(diǎn)對(duì)象
* @param duration {number} 閃爍頻率
*/
flicker(target, duration=700){
target.alpha = 1;
let props = {"alpha": 0};
let ease = null;
Laya.Tween.to(target, props, duration, ease, Laya.Handler.create(null, function(){
props = {"alpha": 1};
Laya.Tween.to(target, props, duration, ease, Laya.Handler.create(this, this.flicker, [target]));
}.bind(this)));
}
/**
* 停止動(dòng)畫 節(jié)點(diǎn)歸位
* 停止動(dòng)畫后容器位置初始化到原始位置,避免可能出現(xiàn)位置改變引起的bug。
* @param target {Sprite} 目標(biāo)節(jié)點(diǎn)
* @param x {number} X軸坐標(biāo)位置
* @param y {number} Y軸坐標(biāo)位置
*/
clear(target, x=null, y=null){
Laya.Tween.clearAll(target);
if(x!==null && y!==null){
target.pos(x, y);
}
}
/**
* 點(diǎn)擊放大縮小
* @param target {Sprite} 目標(biāo)節(jié)點(diǎn)
* @param fix 若中心點(diǎn)不是錨點(diǎn)則需修改位置
*/
clickCubicInOut(target, fix=true){
if(!target){
return;
}
target.off(Laya.Event.MOUSE_DOWN, this, this.onCubicInOut);
target.on(Laya.Event.MOUSE_DOWN, this, this.onCubicInOut, [target, fix]);
}
clearCubicInOut(target){
if(!target){
return;
}
target.off(Laya.Event.MOUSE_DOWN, this, this.onCubicInOut);
}
onCubicInOut(target, fix){
if(target._aniButtonEffect){
return;
}
target._aniButtonEffect = true;
let x, y;
if(fix){
x = target.x;
y = target.y;
}else{
x = target.x - ((target.width * 0.1) >> 1);
y = target.y - ((target.height * 0.1) >> 1);
}
let scaleX = target.scaleX;
let scaleY = target.scaleY;
let props = {x:x, y:y, scaleX:scaleX * 1.1, scaleY:scaleY * 1.1};
let duration = 100;
let ease = null;
Laya.Tween.to(target, props, duration, ease, Laya.Handler.create(this, ()=>{
props.scaleX = scaleX;
props.scaleY = scaleY;
Laya.Tween.to(target, props, duration ,ease, Laya.Handler.create(this, ()=>{
if(!target.destroyed){
target._aniButtonEffect = false;
}
}));
}));
}
}