diff --git a/src/app/play/Unit.ts b/src/app/play/Unit.ts
index 4a84782db4c8f7a50a9e45a2bd7e50d7f32d2f0c..b78f3f2c65d4e7535cb12b7da1dff9bb746eb0d2 100644
--- a/src/app/play/Unit.ts
+++ b/src/app/play/Unit.ts
@@ -4,6 +4,7 @@ import TWEEN from '@tweenjs/tween.js';
 interface AirWindow  extends Window {
   air: any;
   curCtx: any;
+  curImages: any;
 }
 declare let window: AirWindow;
 
@@ -36,7 +37,6 @@ class Sprite {
 
 
 
-
 export class MySprite extends Sprite {
 
   _width = 0;
@@ -69,6 +69,16 @@ export class MySprite extends Sprite {
   img;
   _z = 0;
 
+  _showRect;
+
+
+  _bitmapFlag = false;
+  _offCanvas;
+  _offCtx;
+  isCircleStyle = false; // 切成圆形
+  parent;
+  _maskSpr;
+
 
   init(imgObj = null, anchorX: number = 0.5, anchorY: number = 0.5) {
 
@@ -79,7 +89,6 @@ export class MySprite extends Sprite {
       this.width = this.img.width;
       this.height = this.img.height;
 
-
     }
 
     this.anchorX = anchorX;
@@ -87,8 +96,11 @@ export class MySprite extends Sprite {
   }
 
 
-  setShadow(offX, offY, blur, color = 'rgba(0, 0, 0, 0.3)') {
+  setShowRect(rect) {
+    this._showRect = rect;
+  }
 
+  setShadow(offX, offY, blur, color = 'rgba(0, 0, 0, 0.3)') {
 
     this._shadowFlag = true;
     this._shadowColor = color;
@@ -102,6 +114,19 @@ export class MySprite extends Sprite {
     this._radius = r;
   }
 
+  setMaskSpr(spr) {
+    this._maskSpr = spr;
+    this._createOffCtx();
+  }
+
+  _createOffCtx() {
+    if (!this._offCtx) {
+      this._offCanvas = document.createElement('canvas'); // 创建一个新的canvas
+      this.width = this._offCanvas.width = this.width; // 创建一个正好包裹住一个粒子canvas
+      this.height = this._offCanvas.height = this.height;
+      this._offCtx = this._offCanvas.getContext('2d');
+    }
+  }
 
   update($event = null) {
     if (!this.visible && this.childDepandVisible) {
@@ -137,7 +162,22 @@ export class MySprite extends Sprite {
     this.ctx.transform(1, this.skewX, this.skewY, 1, 0, 0);
 
 
+  }
+
+  drawSelf() {
+
+    if (this._shadowFlag) {
 
+      this.ctx.shadowOffsetX = this._shadowOffsetX;
+      this.ctx.shadowOffsetY = this._shadowOffsetY;
+      this.ctx.shadowBlur = this._shadowBlur;
+      this.ctx.shadowColor = this._shadowColor;
+    } else {
+      this.ctx.shadowOffsetX = 0;
+      this.ctx.shadowOffsetY = 0;
+      this.ctx.shadowBlur = null;
+      this.ctx.shadowColor = null;
+    }
 
 
     if (this._radius) {
@@ -146,45 +186,68 @@ export class MySprite extends Sprite {
       const w = this.width;
       const h = this.height;
 
-      this.ctx.lineTo(-w / 2, h / 2);          // 创建水平线
-      this.ctx.arcTo(-w / 2, -h / 2, -w / 2 + r, -h / 2, r);
-      this.ctx.arcTo(w / 2, -h / 2, w / 2, -h / 2 + r, r);
-      this.ctx.arcTo(w / 2, h / 2, w / 2 - r, h / 2, r);
-      this.ctx.arcTo(-w / 2, h / 2, -w / 2, h / 2 - r, r);
+      this.ctx.beginPath()
+      this._roundRect(-w / 2, - h / 2, w, h, r * 1 || 0);
+      this.ctx.clip();
+    }
 
+    if (this.isCircleStyle) {
+      this.ctx.beginPath()
+      this.ctx.arc(0, 0, Math.max(this.width, this.height) / 2, 0, Math.PI * 2, false); // 圆形
       this.ctx.clip();
     }
 
-  }
 
-  drawSelf() {
 
+    if (this.img) {
 
+      if (this._showRect) {
+        const rect = this._showRect;
+        this.ctx.drawImage(this.img, rect.x, rect.y, rect.width, rect.height, this._offX + rect.x, this._offY + rect.y, rect.width, rect.height);
+      } else {
+        if (this._offCtx) {
+          this._offScreenRender();
+        } else {
+          this.ctx.drawImage(this.img, this._offX, this._offY);
+        }
+      }
+    }
+  }
 
-    if (this._shadowFlag) {
+  _offScreenRender() {
 
-      this.ctx.shadowOffsetX = this._shadowOffsetX;
-      this.ctx.shadowOffsetY = this._shadowOffsetY;
-      this.ctx.shadowBlur = this._shadowBlur;
-      this.ctx.shadowColor = this._shadowColor;
-    } else {
-      this.ctx.shadowOffsetX = 0;
-      this.ctx.shadowOffsetY = 0;
-      this.ctx.shadowBlur = null;
-      this.ctx.shadowColor = null;
-    }
+    this._offCtx.save();
 
+    this._offCtx.clearRect(0, 0, this.width, this.height);
 
-    if (this.img) {
-      this.ctx.drawImage(this.img, this._offX, this._offY);
+    this._offCtx.drawImage(this.img, 0, 0);
+    if (this._maskSpr) {
+      this._offCtx.globalCompositeOperation = 'destination-in';
+      this._offCtx.drawImage(this._maskSpr.img, this._maskSpr.x + this._maskSpr._offX - this._offX , this._maskSpr.y + this._maskSpr._offY - this._offY);
     }
 
+    this.ctx.drawImage(this._offCanvas, this._offX, this._offY);
 
+    this._offCtx.restore();
 
+  }
 
+  _roundRect(x, y, w, h, r) {
+    const min_size = Math.min(w, h);
+    if (r > min_size / 2) r = min_size / 2;
+    // 开始绘制
+    const ctx = this.ctx;
+    ctx.beginPath();
+    ctx.moveTo(x + r, y);
+    ctx.arcTo(x + w, y, x + w, y + h, r);
+    ctx.arcTo(x + w, y + h, x, y + h, r);
+    ctx.arcTo(x, y + h, x, y, r);
+    ctx.arcTo(x, y, x + w, y, r);
+    ctx.closePath();
   }
 
-  updateChildren() {
+
+    updateChildren() {
 
     if (this.children.length <= 0) { return; }
 
@@ -257,6 +320,13 @@ export class MySprite extends Sprite {
     }
   }
 
+
+  set bitmapFlag(v) {
+    this._bitmapFlag = v;
+  }
+  get bitmapFlag() {
+    return this._bitmapFlag;
+  }
   set alpha(v) {
     this._alpha = v;
     if (this.childDepandAlpha) {
@@ -305,7 +375,6 @@ export class MySprite extends Sprite {
 
   getBoundingBox() {
 
-
     const getParentData = (item) => {
 
       let px = item.x;
@@ -343,11 +412,6 @@ export class MySprite extends Sprite {
     const width = this.width * Math.abs(data.sx);
     const height = this.height * Math.abs(data.sy);
 
-    // const x = this.x + this._offX * Math.abs(this.scaleX);
-    // const y = this.y + this._offY * Math.abs(this.scaleY);
-    // const width = this.width * Math.abs(this.scaleX);
-    // const height = this.height * Math.abs(this.scaleY);
-
     return {x, y, width, height};
   }
 
@@ -758,7 +822,8 @@ export class RichTextOld extends Label {
 export class RichText extends Label {
 
 
-  disH =  30;
+  disH = 30;
+  offW = 10;
 
   constructor(ctx?: any) {
     super(ctx);
@@ -788,7 +853,7 @@ export class RichText extends Label {
     const chr = this.text.split(' ');
     let temp = '';
     const row = [];
-    const w = selfW - 80;
+    const w = selfW - this.offW * 2;
     const disH = (this.fontSize + this.disH) * this.scaleY;
 
 
@@ -964,28 +1029,28 @@ export class ShapeRectNew extends MySprite {
     ctx.save();
     ctx.beginPath(0);
     // 从右下角顺时针绘制,弧度从0到1/2PI
-    ctx.arc(width - radius, height - radius, radius, 0, Math.PI / 2);
+    ctx.arc(width - radius + this._offX, height - radius + this._offY, radius, 0, Math.PI / 2);
 
     // 矩形下边线
-    ctx.lineTo(radius, height);
+    ctx.lineTo(radius + this._offX, height + this._offY);
 
     // 左下角圆弧,弧度从1/2PI到PI
-    ctx.arc(radius, height - radius, radius, Math.PI / 2, Math.PI);
+    ctx.arc(radius + this._offX, height - radius + this._offY, radius, Math.PI / 2, Math.PI);
 
     // 矩形左边线
-    ctx.lineTo(0, radius);
+    ctx.lineTo(0 + this._offX, radius + this._offY);
 
     // 左上角圆弧,弧度从PI到3/2PI
-    ctx.arc(radius, radius, radius, Math.PI, Math.PI * 3 / 2);
+    ctx.arc(radius + this._offX, radius + this._offY, radius, Math.PI, Math.PI * 3 / 2);
 
     // 上边线
-    ctx.lineTo(width - radius, 0);
+    ctx.lineTo(width - radius + this._offX, 0 + this._offY);
 
     // 右上角圆弧
-    ctx.arc(width - radius, radius, radius, Math.PI * 3 / 2, Math.PI * 2);
+    ctx.arc(width - radius + this._offX, radius + this._offY, radius, Math.PI * 3 / 2, Math.PI * 2);
 
     // 右边线
-    ctx.lineTo(width, height - radius);
+    ctx.lineTo(width + this._offX, height - radius + this._offY);
     ctx.closePath();
 
     if (this.fill) {
@@ -1020,7 +1085,7 @@ export class MyAnimation extends MySprite {
 
   loop = false;
   playEndFunc;
-  delayPerUnit = 1;
+  delayPerUnit = 0.07;
 
   restartFlag = false;
   reverseFlag = false;
@@ -1109,8 +1174,9 @@ export class MyAnimation extends MySprite {
     this.frameArr[this.frameIndex].visible = true;
 
     if (this.playEndFunc) {
-      this.playEndFunc();
+      const func = this.playEndFunc;
       this.playEndFunc = null;
+      func();
     }
   }
 
@@ -1177,6 +1243,14 @@ export class MyAnimation extends MySprite {
 
 export function tweenChange(item, obj, time = 0.8, callBack = null, easing = null, update = null) {
 
+  const tween = createTween(item, obj, time, callBack, easing, update)
+  tween.start();
+
+  return tween;
+}
+
+export function createTween(item, obj, time = 0.8, callBack = null, easing = null, update = null) {
+
   const tween = new TWEEN.Tween(item).to(obj, time * 1000);
 
   if (callBack) {
@@ -1192,15 +1266,28 @@ export function tweenChange(item, obj, time = 0.8, callBack = null, easing = nul
       update(a, b);
     });
   }
-
-  tween.start();
   return tween;
 }
 
+export function tweenQueue(arr) {
+
+  const showOneTween = () => {
+    const tween = arr.shift();
+    if (arr.length > 0) {
+      tween.onComplete( () => {
+        showOneTween();
+      });
+    }
+    tween.start();
+  };
+
+  showOneTween();
+}
+
 
 
 
-export function rotateItem(item, rotation, time = 0.8, callBack = null, easing = null) {
+export function rotateItem(item, rotation, time = 0.8, callBack = null, easing = null, loop = false) {
 
   const tween = new TWEEN.Tween(item).to({ rotation }, time * 1000);
 
@@ -1208,7 +1295,14 @@ export function rotateItem(item, rotation, time = 0.8, callBack = null, easing =
     tween.onComplete(() => {
       callBack();
     });
+  } else if (loop) {
+    const speed = (rotation - item.rotation) / time;
+    tween.onComplete(() => {
+      item.rotation %= 360;
+      rotateItem(item, item.rotation + speed * time, time, null, easing, true);
+    });
   }
+
   if (easing) {
     tween.easing(easing);
   }
@@ -1491,7 +1585,7 @@ export function getPosDistance(sx, sy, ex, ey) {
   return len;
 }
 
-export function delayCall(callback, second) {
+export function delayCall(second, callback) {
   const tween = new TWEEN.Tween(this)
     .delay(second * 1000)
     .onComplete(() => {
@@ -1525,10 +1619,15 @@ export function formatTime(fmt, date) {
   return fmt;
 }
 
-export function getMinScale(item, maxLen) {
-  const sx = maxLen / item.width;
-  const sy = maxLen / item.height;
+export function getMinScale(item, maxW, maxH = null) {
+  if (!maxH) {
+    maxH = maxW;
+  }
+
+  const sx = maxW / item.width;
+  const sy = maxH / item.height;
   const minS = Math.min(sx, sy);
+
   return minS;
 }
 
@@ -1571,6 +1670,46 @@ export function jelly(item, time = 0.7) {
   run();
 }
 
+export function jellyPop(item, time) {
+
+  if (item.jellyTween) {
+    TWEEN.remove(item.jellyTween);
+  }
+
+  const t = time / 6;
+  const baseSX = item.scaleX;
+  const baseSY = item.scaleY;
+  let index = 0;
+
+  const run = () => {
+    if (index >= arr.length) {
+      item.jellyTween = null;
+      return;
+    }
+    const data = arr[index];
+    const t = tweenChange(item, {scaleX: data[0], scaleY: data[1]}, data[2], () => {
+      index ++;
+      run();
+    }, TWEEN.Easing.Sinusoidal.InOut);
+    item.jellyTween = t;
+  };
+
+
+  const arr = [
+    [baseSX * 1.3, baseSY * 1.3, t],
+    [baseSX * 0.85, baseSY * 0.85, t * 1],
+    [baseSX * 1.1, baseSY * 1.1, t * 1],
+    [baseSX * 0.95, baseSY * 0.95, t * 1],
+    [baseSX * 1.02, baseSY * 1.02, t * 1],
+    [baseSX * 1, baseSY * 1, t * 1],
+  ];
+
+
+  item.setScaleXY(0);
+  run();
+
+}
+
 
 
 export function showPopParticle(img, pos, parent, num = 20, minLen = 40, maxLen = 80, showTime = 0.4) {
@@ -1672,5 +1811,41 @@ export function shake(item, time = 0.5, callback = null, rate = 1) {
 
 }
 
+export function getMaxScale(item, maxW, maxH) {
+  const sx = maxW / item.width;
+  const sy = maxH / item.height;
+  const maxS = Math.max(sx, sy);
+  return maxS;
+}
+
+export function createSprite(key) {
+  const image = window.curImages;
+  const spr = new MySprite();
+  spr.init(image.get(key));
+  return spr;
+}
+
+export class MyVideo extends MySprite {
+
+  element;
+
+  initVideoElement(videoElement)  {
+
+    this.element = videoElement;
+    this.width = this.element.videoWidth;
+    this.height = this.element.videoHeight;
+
+    this.element.currentTime = 0.01;
+  }
+
+
+  drawSelf() {
+    super.drawSelf();
+
+    this.ctx.drawImage(this.element, 0, 0, this.width, this.height);
+  }
+}
+
+
 // --------------- custom class --------------------
 
diff --git a/src/app/play/play.component.ts b/src/app/play/play.component.ts
index f4b8a863d45aa9f51852483802422d4dfacce939..722f061f157a6447b2ffe05ba064b845a7fc5085 100644
--- a/src/app/play/play.component.ts
+++ b/src/app/play/play.component.ts
@@ -108,6 +108,16 @@ export class PlayComponent implements OnInit, OnDestroy {
   ngOnDestroy() {
     window['curCtx'] = null;
     window.cancelAnimationFrame(this.animationId);
+    this.cleanAudio();
+  }
+
+
+  cleanAudio() {
+    if (this.audioObj) {
+      for (const key in this.audioObj) {
+        this.audioObj[key].pause();
+      }
+    }
   }
 
 
@@ -141,6 +151,7 @@ export class PlayComponent implements OnInit, OnDestroy {
     this.canvas.nativeElement.height = this.canvasHeight;
 
     window['curCtx'] = this.ctx;
+    window['curImages'] = this.images;
   }
 
 
diff --git a/tslint.json b/tslint.json
index 4fcb4b1d413f773db7ea534e2c9d6d29436a819a..92190299d54a20a69f1f724335455dae4ce7a698 100644
--- a/tslint.json
+++ b/tslint.json
@@ -92,7 +92,8 @@
     "variable-name": false,
 
     "no-unused-expression": false,
-    "align": false
+    "align": false,
+    "no-string-literal": false
   },
   "rulesDirectory": [
     "codelyzer"