playable.js
Version:
A lightweight HTML5 game engine.
1,743 lines (1,721 loc) • 107 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
class Event {
constructor(type, data = null) {
this.type = null;
this.data = null;
this.target = null;
this.currentTarget = null;
this.$init(type, data);
}
$init(type, data = null) {
this.type = type;
this.data = data;
return this;
}
release() {
this.type = null;
this.data = null;
Event.recycle(this);
}
static create(type, data = null) {
let pool = this.$pool;
if (pool.length > 0) {
return pool.pop().$init(type, data);
}
else {
return new Event(type, data);
}
}
static recycle(e) {
this.$pool.push(e);
}
}
/** @event added */
Event.ADDED = 'added';
/** @event removed */
Event.REMOVED = 'removed';
/** @event addedToStage */
Event.ADDED_TO_STAGE = 'addedToStage';
/** @event removeFromStage */
Event.REMOVED_FROM_STAGE = 'removeFromStage';
/** @event activate */
Event.ACTIVATE = 'activate';
/** @event deactivate */
Event.DEACTIVATE = 'deactivate';
/** @event enterFrame */
Event.ENTER_FRAME = 'enterFrame';
/** @event tick */
Event.TICK = 'tick';
/** @event tickerPause */
Event.TICKER_PAUSE = 'tickerPause';
/** @event tickerResume */
Event.TICKER_RESUME = 'tickerResume';
/** @event viewportResize */
Event.VIEWPORT_RESIZE = 'viewportResize';
/** @event load */
Event.LOAD = 'load';
/** @event abort */
Event.ABORT = 'abort';
/** @event error */
Event.ERROR = 'error';
/** @event progress */
Event.PROGRESS = 'progress';
/** @event complete */
Event.COMPLETE = 'complete';
/** @event ended */
Event.ENDED = 'ended';
/** @event soundComplete */
Event.SOUND_COMPLETE = 'soundComplete';
Event.$pool = [];
var Event$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
Event: Event
});
class EventEmitter {
constructor() {
this.$events = {};
this.$emittingType = null;
this.$removedListeners = [];
}
on(type, listener) {
let listeners = this.$events[type] || [];
listeners.push(listener);
this.$events[type] = listeners;
return this;
}
off(type, listener) {
if (this.$emittingType === type && listener) {
this.$removedListeners.push(listener);
}
else {
let listeners = this.$events[type] || [];
if (listener) {
let index = listeners.indexOf(listener);
if (index >= 0) {
listeners.splice(index, 1);
}
}
else {
listeners.length = 0;
}
}
return this;
}
once(type, listener) {
let that = this;
let wrapper = function () {
listener.apply(this, arguments);
that.off(type, wrapper);
};
return this.on(type, wrapper);
}
emit(type, ...args) {
let event;
if (type instanceof Event) {
args = [type];
type.target = type.target || this;
type.currentTarget = this;
type = type.type;
}
let listeners = this.$events[type];
let hasListeners = listeners && listeners.length > 0;
let removedListeners = this.$removedListeners;
if (!event && hasListeners && args.length === 0) {
event = Event.create(type);
event.target = this;
event.currentTarget = this;
args.push(event);
}
if (hasListeners) {
this.$emittingType = type;
for (let listener of listeners) {
if (removedListeners.indexOf(listener) < 0) {
listener.apply(this, args);
}
}
this.$emittingType = null;
}
if (event) {
event.release();
}
for (let listener of removedListeners) {
this.off(type, listener);
}
removedListeners.length = 0;
return hasListeners;
}
hasEventListener(type) {
let listeners = this.$events[type];
return !!listeners && listeners.length > 0;
}
removeAllListeners() {
this.$events = {};
return this;
}
}
var EventEmitter$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
EventEmitter: EventEmitter
});
class Ticker extends EventEmitter {
constructor(stage) {
super();
this.$stage = null;
this.$fps = 0;
this.$deltaTime = 0;
this.$paused = true;
this.$shouldResume = false;
this.$timerIndex = 0;
this.$lastTimestamp = 0;
this.$tickHandle = null;
this.$stage = stage;
this.$timers = [];
this.$boundTick = this.$tick.bind(this);
this.$enterFrameCallbackList = [stage];
this.$start();
}
get fps() {
return this.$fps;
}
get deltaTime() {
return this.$deltaTime;
}
get paused() {
return this.$paused;
}
$start() {
let stage = this.$stage;
let prefixes = ['', 'o', 'ms', 'moz', 'webkit'];
for (let prefix of prefixes) {
let requestKey = prefix ? prefix + 'RequestAnimationFrame' : 'requestAnimationFrame';
let cancelKey = prefix ? prefix + 'CancelAnimationFrame' : 'cancelAnimationFrame';
let cancelRequestKey = prefix ? prefix + 'CancelRequestAnimationFrame' : 'cancelRequestAnimationFrame';
window.requestAnimationFrame = window.requestAnimationFrame || window[requestKey];
window.cancelAnimationFrame = window.cancelAnimationFrame || window[cancelKey] || window[cancelRequestKey];
}
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = function (callback) {
return window.setTimeout(callback, 1000 / 60);
};
window.cancelAnimationFrame = function (handle) {
window.clearTimeout(handle);
};
}
stage.on(Event.ACTIVATE, () => {
if (this.$shouldResume) {
this.resume();
this.$shouldResume = false;
}
});
stage.on(Event.DEACTIVATE, () => {
if (!this.$paused) {
this.pause();
this.$shouldResume = true;
}
});
if (stage.activated) {
this.$paused = false;
this.$tick();
}
else {
this.$shouldResume = true;
}
return this;
}
pause() {
if (!this.$paused) {
this.$paused = true;
this.$lastTimestamp = 0;
this.emit(Event.TICKER_PAUSE);
cancelAnimationFrame(this.$tickHandle);
}
return this;
}
resume() {
if (this.$paused) {
this.$paused = false;
this.$tick();
this.emit(Event.TICKER_RESUME);
}
return this;
}
setTimeout(handler, timeout = 0) {
let handle = ++this.$timerIndex;
this.$timers[handle] = { handler, timeout, resetTime: timeout };
return handle;
}
clearTimeout(handle) {
delete this.$timers[handle];
}
setInterval(handler, timeout) {
let handle = ++this.$timerIndex;
this.$timers[handle] = { handler, timeout, resetTime: timeout, interval: true };
return handle;
}
clearInterval(handle) {
delete this.$timers[handle];
}
registerEnterFrameCallback(layer) {
let list = this.$enterFrameCallbackList;
if (list.indexOf(layer) < 0) {
list.push(layer);
}
return this;
}
unregisterEnterFrameCallback(layer) {
let list = this.$enterFrameCallbackList;
let index = list.indexOf(layer);
if (index >= 0) {
list.splice(index, 1);
}
return this;
}
$tick() {
let now = Date.now();
let lastTimestamp = this.$lastTimestamp;
let deltaTime = lastTimestamp ? now - this.$lastTimestamp : 1000 / 60;
let enterFrameCallbackList = this.$enterFrameCallbackList;
this.$fps = Math.round(1000 / deltaTime);
this.$deltaTime = deltaTime;
this.$lastTimestamp = now;
this.$tickHandle = window.requestAnimationFrame(this.$boundTick);
this.$checkTimers(deltaTime);
let event = Event.create(Event.ENTER_FRAME, deltaTime);
for (let layer of enterFrameCallbackList) {
layer.emit(event);
}
event.release();
let tickEvent = Event.create(Event.TICK, deltaTime);
this.emit(tickEvent);
tickEvent.release();
}
$checkTimers(dt) {
let timers = this.$timers;
for (let key in timers) {
let timer = timers[key];
let restTime = timer.resetTime = timer.resetTime - dt;
if (restTime <= 0) {
timer.handler();
if (timer.interval) {
timer.resetTime += timer.timeout;
}
else {
this.clearTimeout(+key);
}
}
}
}
}
var Ticker$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
Ticker: Ticker
});
class Vector {
constructor(x, y) {
this.set(x, y);
}
get length() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
get angle() {
return Math.atan2(this.y, this.x);
}
set(x, y) {
this.x = x || 0;
this.y = y || 0;
return this;
}
add(x, y) {
if (x instanceof Vector) {
this.x += x.x;
this.y += x.y;
}
else {
this.x += x;
this.y += y;
}
return this;
}
subtract(x, y) {
if (x instanceof Vector) {
this.x -= x.x;
this.y -= x.y;
}
else {
this.x -= x;
this.y -= y;
}
return this;
}
dotProduct(x, y) {
if (x instanceof Vector) {
return this.x * x.x + this.y * x.y;
}
else {
return this.x * x + this.y * y;
}
}
normalize() {
let length = this.length;
this.x = this.x / length;
this.y = this.y / length;
return this;
}
negate() {
this.x *= -1;
this.y *= -1;
return this;
}
scale(x, y) {
this.x *= x;
this.y *= y === undefined ? x : y;
return this;
}
rotate(angle) {
let x = this.x;
let y = this.y;
this.x = x * Math.cos(angle) - y * Math.sin(angle);
this.y = x * Math.sin(angle) + y * Math.cos(angle);
return this;
}
transform(m) {
let x = this.x;
let y = this.y;
this.x = m.a * x + m.c * y + m.tx;
this.y = m.b * x + m.d * y + m.ty;
return this;
}
distance(v) {
return Math.sqrt((this.x - v.x) * (this.x - v.x) + (this.y - v.y) * (this.y - v.y));
}
equal(v) {
return this.x === v.x && this.y === v.y;
}
release() {
Vector.recycle(this);
return this;
}
static create(x, y) {
let pool = this.$pool;
if (pool.length > 0) {
return pool.pop().set(x, y);
}
else {
return new Vector(x, y);
}
}
static recycle(v) {
this.$pool.push(v);
}
}
Vector.$pool = [];
var Vector$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
Vector: Vector
});
/**
* ```
* {a b 0}
* (x, y, 1) * {c d 0} = (ax + cy + tx, bx + dy + ty, 1)
* {tx ty 1}
* ```
*/
class Matrix {
constructor(a, b, c, d, tx, ty) {
if (arguments.length > 0) {
this.set(a, b, c, d, tx, ty);
}
else {
this.identity();
}
}
set(a, b, c, d, tx, ty) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.tx = tx;
this.ty = ty;
return this;
}
identity() {
return this.set(1, 0, 0, 1, 0, 0);
}
invert() {
let a = this.a;
let b = this.b;
let c = this.c;
let d = this.d;
let tx = this.tx;
let ty = this.ty;
let n = a * d - c * b;
this.a = d / n;
this.b = -b / n;
this.c = -c / n;
this.d = a / n;
this.tx = (c * ty - d * tx) / n;
this.ty = (b * tx - a * ty) / n;
return this;
}
prepend(a, b, c, d, tx, ty) {
if (a instanceof Matrix) {
return this.prepend(a.a, a.b, a.c, a.d, a.tx, a.ty);
}
let a1 = this.a;
let b1 = this.b;
let c1 = this.c;
let d1 = this.d;
let tx1 = this.tx;
let ty1 = this.ty;
this.a = a * a1 + b * c1;
this.b = a * b1 + b * d1;
this.c = c * a1 + d * c1;
this.d = c * b1 + d * d1;
this.tx = tx * a1 + ty * c1 + tx1;
this.ty = tx * b1 + ty * d1 + ty1;
return this;
}
append(a, b, c, d, tx, ty) {
if (a instanceof Matrix) {
return this.append(a.a, a.b, a.c, a.d, a.tx, a.ty);
}
let a1 = this.a;
let b1 = this.b;
let c1 = this.c;
let d1 = this.d;
let tx1 = this.tx;
let ty1 = this.ty;
this.a = a * a1 + c * b1;
this.b = b * a1 + d * b1;
this.c = a * c1 + c * d1;
this.d = b * c1 + d * d1;
this.tx = a * tx1 + c * ty1 + tx;
this.ty = b * tx1 + d * ty1 + ty;
return this;
}
scale(x, y) {
return this.append(x, 0, 0, y === undefined ? x : y, 0, 0);
}
rotate(angle) {
let sin = Math.sin(angle);
let cos = Math.cos(angle);
return this.append(cos, sin, -sin, cos, 0, 0);
}
skew(skewX, skewY) {
return this.append(1, Math.tan(skewY), Math.tan(skewX), 1, 0, 0);
}
translate(x, y) {
if (x instanceof Vector) {
return this.append(1, 0, 0, 1, x.x, x.y);
}
return this.append(1, 0, 0, 1, x, y);
}
equal(m) {
return m instanceof Matrix &&
this.a === m.a && this.b === m.b &&
this.c === m.c && this.d === m.d &&
this.tx === m.tx && this.ty === m.ty;
}
release() {
Matrix.recycle(this);
}
static create(a, b, c, d, tx, ty) {
let m;
let pool = this.$pool;
if (pool.length > 0) {
m = pool.pop();
}
else {
m = new Matrix();
}
if (arguments.length) {
m.set(a, b, c, d, tx, ty);
}
else {
m.identity();
}
return m;
}
static recycle(m) {
this.$pool.push(m);
}
}
Matrix.$pool = [];
var Matrix$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
Matrix: Matrix
});
class Rectangle {
constructor(x, y, width, height) {
this.set(x, y, width, height);
}
get top() {
return this.y;
}
set top(top) {
this.height += this.y - top;
this.y = top;
}
get bottom() {
return this.y + this.height;
}
set bottom(bottom) {
this.height = bottom - this.y;
}
get left() {
return this.x;
}
set left(left) {
this.width += this.x - left;
this.x = left;
}
get right() {
return this.x + this.width;
}
set right(right) {
this.width = right - this.x;
}
get topLeft() {
return Vector.create(this.left, this.top);
}
set topLeft(v) {
this.top = v.y;
this.left = v.x;
}
get bottomRight() {
return Vector.create(this.right, this.bottom);
}
set bottomRight(v) {
this.bottom = v.y;
this.right = v.x;
}
set(x, y, width, height) {
this.x = x || 0;
this.y = y || 0;
this.width = width || 0;
this.height = height || 0;
return this;
}
contains(x, y) {
if (x instanceof Vector) {
return this.contains(x.x, x.y);
}
return x >= this.x && x <= this.x + this.width && y >= this.y && y <= this.y + this.height;
}
equal(r) {
return r instanceof Rectangle &&
r.x === this.x && r.y === this.y && r.width === this.width && r.height === this.height;
}
release() {
Rectangle.recycle(this);
}
static create(x, y, width, height) {
let pool = this.$pool;
if (pool.length > 0) {
return pool.pop().set(x, y, width, height);
}
else {
return new Rectangle(x, y, width, height);
}
}
static recycle(r) {
this.$pool.push(r);
}
}
Rectangle.$pool = [];
var Rectangle$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
Rectangle: Rectangle
});
class TouchEvent extends Event {
constructor(type) {
super(type);
this.$init(type);
}
$init(type) {
this.type = type;
this.targetX = 0;
this.targetY = 0;
this.localX = 0;
this.localY = 0;
this.stageX = 0;
this.stageY = 0;
this.identifier = 0;
this.target = null;
this.currentTarget = null;
this.cancelBubble = false;
return this;
}
stopPropagation() {
this.cancelBubble = true;
}
release() {
TouchEvent.recycle(this);
}
static create(type) {
let pool = this.$pool;
if (pool.length > 0) {
return pool.pop().$init(type);
}
else {
return new TouchEvent(type);
}
}
static recycle(e) {
this.$pool.push(e);
}
}
/** @event touchStart */
TouchEvent.TOUCH_START = 'touchStart';
/** @event touchMove */
TouchEvent.TOUCH_MOVE = 'touchMove';
/** @event touchEnd */
TouchEvent.TOUCH_END = 'touchEnd';
/** @event touchCancel */
TouchEvent.TOUCH_CANCEL = 'touchCancel';
/** @event touchTap */
TouchEvent.TOUCH_TAP = 'touchTap';
TouchEvent.$pool = [];
var TouchEvent$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
TouchEvent: TouchEvent
});
class Layer extends EventEmitter {
constructor() {
super();
this.name = '';
this.tag = '';
this.touchable = true;
this.$x = 0;
this.$y = 0;
this.$width = 0;
this.$height = 0;
this.$anchorX = 0;
this.$anchorY = 0;
this.$skewX = 0;
this.$skewY = 0;
this.$scaleX = 1;
this.$scaleY = 1;
this.$rotation = 0;
this.$alpha = 1;
this.$visible = true;
this.$smoothing = true;
this.$background = null;
this.$stage = null;
this.$parent = null;
this.$children = [];
this.$dirty = true;
this.$shouldEmitTap = true;
this.$touches = [];
this.$canvas = document.createElement('canvas');
this.$context = this.$canvas.getContext('2d');
}
get x() {
return this.$x;
}
set x(x) {
if (this.$x !== x) {
this.$x = x;
this.$markParentDirty();
}
}
get y() {
return this.$y;
}
set y(y) {
if (this.$y !== y) {
this.$y = y;
this.$markParentDirty();
}
}
get width() {
return this.$width ? this.$width : this.$canvas.width / Layer.pixelRatio;
}
set width(width) {
if (this.$width !== width) {
this.$width = width;
this.$resizeCanvas();
}
}
get height() {
return this.$height ? this.$height : this.$canvas.height / Layer.pixelRatio;
}
set height(height) {
if (this.$height !== height) {
this.$height = height;
this.$resizeCanvas();
}
}
get anchorX() {
return this.$anchorX;
}
set anchorX(anchorX) {
if (this.$anchorX !== anchorX) {
this.$anchorX = anchorX;
this.$resizeCanvas();
}
}
get anchorY() {
return this.$anchorY;
}
set anchorY(anchorY) {
if (this.$anchorY !== anchorY) {
this.$anchorY = anchorY;
this.$resizeCanvas();
}
}
get skewX() {
return this.$skewX;
}
set skewX(skewX) {
if (this.$skewX !== skewX) {
this.$skewX = skewX;
this.$markParentDirty();
}
}
get skewY() {
return this.$skewY;
}
set skewY(skewY) {
if (this.$skewY !== skewY) {
this.$skewY = skewY;
this.$markParentDirty();
}
}
get scaleX() {
return this.$scaleX;
}
set scaleX(scaleX) {
if (this.$scaleX !== scaleX) {
this.$scaleX = scaleX;
this.$markParentDirty();
}
}
get scaleY() {
return this.$scaleY;
}
set scaleY(scaleY) {
if (this.$scaleY !== scaleY) {
this.$scaleY = scaleY;
this.$markParentDirty();
}
}
get rotation() {
return this.$rotation;
}
set rotation(rotation) {
if (this.$rotation !== rotation) {
this.$rotation = rotation;
this.$markParentDirty();
}
}
get alpha() {
return this.$alpha;
}
set alpha(alpha) {
if (this.$alpha !== alpha) {
this.$alpha = alpha;
this.$markParentDirty();
}
}
get visible() {
return this.$visible;
}
set visible(visible) {
if (this.$visible !== visible) {
this.$visible = visible;
this.$markParentDirty();
}
}
get smoothing() {
return this.$smoothing;
}
set smoothing(smoothing) {
this.$smoothing = smoothing;
this.$resizeCanvas();
}
get background() {
return this.$background;
}
set background(background) {
if (this.$background !== background) {
this.$background = background;
this.$markDirty();
}
}
get stage() {
return this.$stage;
}
get parent() {
return this.$parent;
}
get numChildren() {
return this.$children.length;
}
get ticker() {
return this.$stage ? this.$stage.ticker : null;
}
get canvas() {
return this.$canvas;
}
addChild(child) {
return this.addChildAt(child, this.$children.length);
}
addChildAt(child, index) {
let children = this.$children;
if (child.$parent) {
child.$parent.removeChild(child);
}
if (index < 0 || index > children.length) {
index = children.length;
}
child.$emitAdded(this);
children.splice(index, 0, child);
this.$resizeCanvas();
return this;
}
replaceChild(oldChild, newChild) {
let index = this.getChildIndex(oldChild);
this.removeChildAt(index);
this.addChildAt(newChild, index);
return this;
}
getChildByName(name) {
let children = this.$children;
for (let child of children) {
if (child.name === name) {
return child;
}
}
return null;
}
getChildrenByTag(tag) {
let result = [];
let children = this.$children;
for (let child of children) {
if (child.tag === tag) {
result.push(child);
}
}
return result;
}
getChildAt(index) {
return this.$children[index] || null;
}
getChildIndex(child) {
return this.$children.indexOf(child);
}
hasChild(child) {
return this.getChildIndex(child) >= 0;
}
swapChildren(child1, child2) {
let index1 = this.getChildIndex(child1);
let index2 = this.getChildIndex(child2);
if (index1 >= 0 && index2 >= 0) {
this.swapChildrenAt(index1, index2);
}
return this;
}
swapChildrenAt(index1, index2) {
let child1 = this.$children[index1];
let child2 = this.$children[index2];
if (index1 !== index2 && child1 && child2) {
this.$children[index1] = child2;
this.$children[index2] = child1;
this.$markDirty();
}
return this;
}
setChildIndex(child, index) {
let children = this.$children;
let oldIndex = this.getChildIndex(child);
if (index < 0) {
index = 0;
}
else if (index > children.length) {
index = children.length;
}
if (oldIndex >= 0 && index > oldIndex) {
for (let i = oldIndex + 1; i <= index; ++i) {
children[i - 1] = children[i];
}
children[index] = child;
this.$markDirty();
}
else if (oldIndex >= 0 && index < oldIndex) {
for (let i = oldIndex - 1; i >= index; --i) {
children[i + 1] = children[i];
}
children[index] = child;
this.$markDirty();
}
return this;
}
removeChild(child) {
let index = this.getChildIndex(child);
return this.removeChildAt(index);
}
removeChildAt(index) {
let children = this.$children;
let child = children[index];
if (child) {
children.splice(index, 1);
child.$emitRemoved();
this.$resizeCanvas();
}
return this;
}
removeChildByName(name) {
let children = this.$children;
for (let i = 0, l = children.length; i < l; ++i) {
let child = children[i];
if (child.name === name) {
this.removeChildAt(i);
break;
}
}
return this;
}
removeChildrenByTag(tag) {
let children = this.$children;
for (let i = children.length - 1; i >= 0; --i) {
let child = children[i];
if (child.tag === tag) {
this.removeChildAt(i);
}
}
return this;
}
removeAllChildren() {
let children = this.$children;
for (let child of children) {
child.$emitRemoved();
}
this.$children.length = 0;
this.$resizeCanvas();
return this;
}
removeSelf() {
if (this.$parent) {
this.$parent.removeChild(this);
}
return this;
}
$markDirty(sizeDirty) {
if (sizeDirty) {
this.$resizeParentCanvas();
}
else if (!this.$dirty) {
this.$markParentDirty();
}
this.$dirty = true;
}
$markParentDirty() {
if (this.$parent) {
this.$parent.$markDirty();
}
}
$resizeCanvas() {
let width = this.$width;
let height = this.$height;
let canvas = this.$canvas;
let anchorX = this.$anchorX;
let anchorY = this.$anchorY;
let context = this.$context;
let smoothing = this.$smoothing;
let pixelRatio = Layer.pixelRatio;
if (width && height) {
canvas.width = width * pixelRatio;
canvas.height = height * pixelRatio;
}
else {
let bounds = this.$getContentBounds();
canvas.width = (width || bounds.right + anchorX) * pixelRatio;
canvas.height = (height || bounds.bottom + anchorY) * pixelRatio;
bounds.release();
}
if (context.imageSmoothingEnabled !== smoothing) {
context.imageSmoothingEnabled = smoothing;
}
this.$markDirty(true);
}
$resizeParentCanvas() {
if (this.$parent) {
this.$parent.$resizeCanvas();
}
}
$getTransform() {
let degToRad = Math.PI / 180;
let matrix = Matrix.create();
matrix.translate(-this.$anchorX, -this.$anchorY);
matrix.skew(this.skewX * degToRad, this.skewY * degToRad);
matrix.rotate(this.rotation * degToRad);
matrix.scale(this.scaleX, this.scaleY);
matrix.translate(this.x, this.y);
return matrix;
}
$getChildTransform(child) {
return child.$getTransform();
}
$getChildBounds(child) {
let width = child.width;
let height = child.height;
let bounds = Rectangle.create();
let matrix = this.$getChildTransform(child);
let topLeft = Vector.create(0, 0).transform(matrix);
let topRight = Vector.create(width, 0).transform(matrix);
let bottomLeft = Vector.create(0, height).transform(matrix);
let bottomRight = Vector.create(width, height).transform(matrix);
let minX = Math.min(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x);
let maxX = Math.max(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x);
let minY = Math.min(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y);
let maxY = Math.max(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y);
bounds.top = minY;
bounds.bottom = maxY;
bounds.left = minX;
bounds.right = maxX;
matrix.release();
topLeft.release();
topRight.release();
bottomLeft.release();
bottomRight.release();
return bounds;
}
$getContentBounds() {
let bounds;
let children = this.$children;
for (let child of children) {
if (child.$visible) {
let childBounds = this.$getChildBounds(child);
if (bounds) {
bounds.top = Math.min(bounds.top, childBounds.top);
bounds.bottom = Math.max(bounds.bottom, childBounds.bottom);
bounds.left = Math.min(bounds.left, childBounds.left);
bounds.right = Math.max(bounds.right, childBounds.right);
childBounds.release();
}
else {
bounds = childBounds;
}
}
}
bounds = bounds || Rectangle.create();
return bounds;
}
$emitTouchEvent(event, inside) {
let type = event.type;
let localX = event.localX;
let localY = event.localY;
let touches = this.$touches;
let identifier = event.identifier;
if (type === TouchEvent.TOUCH_START) {
this.$shouldEmitTap = true;
touches[identifier] = true;
}
else if (!touches[identifier]) {
return false;
}
else if (type === TouchEvent.TOUCH_TAP || type === TouchEvent.TOUCH_CANCEL) {
touches[identifier] = false;
}
if (type === TouchEvent.TOUCH_MOVE) {
this.$shouldEmitTap = false;
}
let children = this.$children;
for (let i = children.length - 1; i >= 0; --i) {
let child = children[i];
if (!child.$visible || !child.touchable) {
continue;
}
let matrix = this.$getChildTransform(child);
let localPos = Vector.create(localX, localY).transform(matrix.invert()).subtract(child.$anchorX, child.$anchorY);
let inside = child.$localHitTest(localPos);
localPos.release();
matrix.release();
if (inside || type !== TouchEvent.TOUCH_START) {
event.target = child;
event.localX = event.targetX = localPos.x;
event.localY = event.targetY = localPos.y;
if (child.$emitTouchEvent(event, inside)) {
break;
}
}
}
if (type === TouchEvent.TOUCH_TAP && (!inside || !this.$shouldEmitTap)) {
return true;
}
if (!event.cancelBubble) {
event.localX = localX;
event.localY = localY;
this.emit(event);
}
return true;
}
$emitAdded(parent) {
let stage = parent.$stage;
this.$parent = parent;
this.emit(Event.ADDED);
if (stage) {
this.$emitAddedToStage(stage);
}
}
$emitRemoved() {
let stage = this.$stage;
this.$parent = null;
this.emit(Event.REMOVED);
if (stage) {
this.$emitRemovedFromStage();
}
}
$emitAddedToStage(stage) {
let children = this.$children;
this.$stage = stage;
this.emit(Event.ADDED_TO_STAGE);
if (this.hasEventListener(Event.ENTER_FRAME)) {
stage.ticker.registerEnterFrameCallback(this);
}
for (let child of children) {
child.$emitAddedToStage(stage);
}
}
$emitRemovedFromStage() {
let stage = this.$stage;
let children = this.$children;
this.$stage = null;
this.emit(Event.REMOVED_FROM_STAGE);
if (this.hasEventListener(Event.ENTER_FRAME)) {
stage.ticker.unregisterEnterFrameCallback(this);
}
for (let child of children) {
child.$emitRemovedFromStage();
}
}
$localHitTest(vector) {
return vector.x >= -this.anchorX && vector.x <= this.width - this.anchorX && vector.y >= -this.anchorY && vector.y <= this.height - this.anchorY;
}
$isChildVisible(child) {
if (!child.visible || !child.alpha || !child.width || !child.height) {
return false;
}
let minX = -this.$anchorX;
let maxX = this.width + minX;
let minY = -this.$anchorY;
let maxY = this.height + minY;
let bounds = this.$getChildBounds(child);
let inside = bounds.left <= maxX && bounds.right >= minX && bounds.top <= maxY && bounds.bottom >= minY;
bounds.release();
return inside;
}
$drawChild(child) {
let ctx = this.$context;
let canvas = child.$canvas;
let width = child.width;
let height = child.height;
let pixelRatio = Layer.pixelRatio;
let matrix = this.$getChildTransform(child).scale(pixelRatio);
let drawCalls = child.$render();
let globalAlpha = ctx.globalAlpha;
if (globalAlpha !== child.alpha) {
ctx.globalAlpha = child.alpha;
}
if (matrix.b === 0 && matrix.c === 0) {
let tx = (matrix.tx + 0.5) | 0;
let ty = (matrix.ty + 0.5) | 0;
width = (width * matrix.a) + 0.5 | 0;
height = (height * matrix.d) + 0.5 | 0;
ctx.drawImage(canvas, tx, ty, width, height);
}
else {
ctx.save();
ctx.transform(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty);
ctx.drawImage(canvas, 0, 0, width, height);
ctx.restore();
}
if (globalAlpha !== child.alpha) {
ctx.globalAlpha = globalAlpha;
}
matrix.release();
return drawCalls + 1;
}
$render() {
if (!this.$dirty) {
return 0;
}
let drawCalls = 0;
let ctx = this.$context;
let canvas = this.$canvas;
let children = this.$children;
let canvasWidth = canvas.width;
let canvasHeight = canvas.height;
let anchorX = (this.$anchorX + 0.5) | 0;
let anchorY = (this.$anchorY + 0.5) | 0;
let background = this.$background;
let pixelRatio = Layer.pixelRatio;
ctx.globalAlpha = 1;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
if (background) {
ctx.fillStyle = background;
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
}
ctx.translate(anchorX * pixelRatio, anchorY * pixelRatio);
for (let child of children) {
if (this.$isChildVisible(child)) {
drawCalls += this.$drawChild(child);
}
}
this.$dirty = false;
return drawCalls;
}
on(type, listener) {
super.on(type, listener);
if (type === Event.ENTER_FRAME && this.ticker) {
this.ticker.registerEnterFrameCallback(this);
}
else if (type === Event.ADDED && this.$parent) {
let event = Event.create(type);
listener.call(this, event);
event.release();
}
else if (type === Event.ADDED_TO_STAGE && this.$stage) {
let event = Event.create(type);
listener.call(this, event);
event.release();
}
return this;
}
off(type, listener) {
super.off(type, listener);
if (type === Event.ENTER_FRAME && !this.hasEventListener(Event.ENTER_FRAME) && this.ticker) {
this.ticker.unregisterEnterFrameCallback(this);
}
return this;
}
}
Layer.pixelRatio = typeof window === 'undefined' ? 1 : window.devicePixelRatio || 1;
var Layer$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
Layer: Layer
});
class Ease {
static linear(t, b, c, d) {
return c * t / d + b;
}
static easeInQuad(t, b, c, d) {
return c * (t /= d) * t + b;
}
static easeOutQuad(t, b, c, d) {
return -c * (t /= d) * (t - 2) + b;
}
static easeInOutQuad(t, b, c, d) {
if ((t /= d / 2) < 1)
return c / 2 * t * t + b;
return -c / 2 * ((--t) * (t - 2) - 1) + b;
}
static easeInCubic(t, b, c, d) {
return c * (t /= d) * t * t + b;
}
static easeOutCubic(t, b, c, d) {
return c * ((t = t / d - 1) * t * t + 1) + b;
}
static easeInOutCubic(t, b, c, d) {
if ((t /= d / 2) < 1)
return c / 2 * t * t * t + b;
return c / 2 * ((t -= 2) * t * t + 2) + b;
}
static easeInQuart(t, b, c, d) {
return c * (t /= d) * t * t * t + b;
}
static easeOutQuart(t, b, c, d) {
return -c * ((t = t / d - 1) * t * t * t - 1) + b;
}
static easeInOutQuart(t, b, c, d) {
if ((t /= d / 2) < 1)
return c / 2 * t * t * t * t + b;
return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
}
static easeInQuint(t, b, c, d) {
return c * (t /= d) * t * t * t * t + b;
}
static easeOutQuint(t, b, c, d) {
return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
}
static easeInOutQuint(t, b, c, d) {
if ((t /= d / 2) < 1)
return c / 2 * t * t * t * t * t + b;
return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
}
static easeInSine(t, b, c, d) {
return (t === d) ? b + c : -c * Math.cos(t / d * (Math.PI / 2)) + c + b;
}
static easeOutSine(t, b, c, d) {
return c * Math.sin(t / d * (Math.PI / 2)) + b;
}
static easeInOutSine(t, b, c, d) {
return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;
}
static easeInExpo(t, b, c, d) {
return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b;
}
static easeOutExpo(t, b, c, d) {
return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
}
static easeInOutExpo(t, b, c, d) {
if (t === 0)
return b;
if (t === d)
return b + c;
if ((t /= d / 2) < 1)
return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b;
}
static easeInCirc(t, b, c, d) {
return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
}
static easeOutCirc(t, b, c, d) {
return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
}
static easeInOutCirc(t, b, c, d) {
if ((t /= d / 2) < 1)
return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b;
return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;
}
static easeInElastic(t, b, c, d) {
let s = 1.70158;
let p = 0;
let a = c;
if (t === 0)
return b;
if ((t /= d) === 1)
return b + c;
if (!p)
p = d * 0.3;
if (a < Math.abs(c)) {
a = c;
s = p / 4;
}
else {
s = p / (2 * Math.PI) * Math.asin(c / a);
}
return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
}
static easeOutElastic(t, b, c, d) {
let s = 1.70158;
let p = 0;
let a = c;
if (t === 0)
return b;
if ((t /= d) === 1)
return b + c;
if (!p)
p = d * 0.3;
if (a < Math.abs(c)) {
a = c;
s = p / 4;
}
else {
s = p / (2 * Math.PI) * Math.asin(c / a);
}
return a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b;
}
static easeInOutElastic(t, b, c, d) {
let s = 1.70158;
let p = 0;
let a = c;
if (t === 0)
return b;
if ((t /= d / 2) === 2)
return b + c;
if (!p)
p = d * (0.3 * 1.5);
if (a < Math.abs(c)) {
a = c;
s = p / 4;
}
else {
s = p / (2 * Math.PI) * Math.asin(c / a);
}
if (t < 1)
return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * 0.5 + c + b;
}
static easeInBack(t, b, c, d, s = 1.70158) {
return (t === d) ? b + c : c * (t /= d) * t * ((s + 1) * t - s) + b;
}
static easeOutBack(t, b, c, d, s = 1.70158) {
return (t === 0) ? b : c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
}
static easeInOutBack(t, b, c, d, s = 1.70158) {
if ((t /= d / 2) < 1)
return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
}
static easeInBounce(t, b, c, d) {
return c - Ease.easeOutBounce(d - t, 0, c, d) + b;
}
static easeOutBounce(t, b, c, d) {
if ((t /= d) < (1 / 2.75)) {
return c * (7.5625 * t * t) + b;
}
else if (t < (2 / 2.75)) {
return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b;
}
else if (t < (2.5 / 2.75)) {
return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b;
}
else {
return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b;
}
}
static easeInOutBounce(t, b, c, d) {
if (t < d / 2)
return Ease.easeInBounce(t * 2, 0, c, d) * 0.5 + b;
return Ease.easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b;
}
}
var Ease$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
Ease: Ease
});
class Tween extends EventEmitter {
constructor(target, option) {
super();
this.loop = false;
this.$target = null;
this.$paused = true;
this.$stopped = true;
this.$stepIndex = 0;
this.$stepPosition = 0;
this.$steps = [];
this.$stepProps = [];
this.$shouldSaveProps = true;
this.$target = target;
this.loop = option ? option.loop : false;
this.$boundOnEnterFrame = this.$onEnterFrame.bind(this);
}
get paused() {
return this.$paused;
}
get stopped() {
return this.$stopped;
}
set(props) {
this.$steps.push({
type: 'set',
props
});
return this;
}
to(props, duration, ease) {
this.$steps.push({
type: 'to',
duration,
props,
ease
});
return this;
}
wait(duration) {
this.$steps.push({
type: 'wait',
duration
});
return this;
}
call(callback) {
this.$steps.push({
type: 'call',
callback
});
return this;
}
play() {
if (this.$stopped) {
this.resume();
Tween.$tweens.push(this);
}
return this;
}
pause() {
this.$paused = true;
this.$target.off(Event.ENTER_FRAME, this.$boundOnEnterFrame);
return this;
}
resume() {
if (this.$paused) {
this.$paused = false;
this.$stopped = false;
this.$target.on(Event.ENTER_FRAME, this.$boundOnEnterFrame);
}
return this;
}
stop() {
this.pause();
this.$stopped = true;
let index = Tween.$tweens.indexOf(this);
if (index >= 0) {
Tween.$tweens.splice(index, 1);
}
return this;
}
$onEnterFrame(e) {
this.$nextFrame(e.data);
}
$nextFrame(dt) {
let loop = this.loop;
let steps = this.$steps;
let stepLength = this.$steps.length;
let stepIndex = this.$stepIndex;
let stepPosition = this.$stepPosition + dt;
let step = steps[stepIndex];
let type = step.type;
let duration = step.duration || 0;
let props = step.props;
let ease = step.ease || Ease.linear;
let callback = step.callback;
if (type === 'set') {
this.$setProps(props);
}
else if (type === 'to') {
this.$easeProps(stepIndex, props, stepPosition, duration, ease);
}
else if (type === 'call') {
callback.call(this.$target);
}
if (stepPosition < duration) {
this.$stepPosition = stepPosition;
}
else if (stepIndex + 1 < stepLength) {
this.$stepPosition = 0;
this.$stepIndex = stepIndex + 1;
this.$shouldSaveProps = true;
this.$setProps(props);
this.$nextFrame(stepPosition - duration);
}
else if (loop) {
this.$stepIndex = 0;
this.$stepPosition = 0;
this.$shouldSaveProps = true;
this.$setProps(props);
this.$nextFrame(stepPosition - duration);
}
else {
this.$stepIndex = 0;
this.$stepPosition = 0;
this.$shouldSaveProps = true;
this.$setProps(props);
this.pause();
}
}
$saveOriginalProps(stepIndex, props) {
let target = this.$target;
let stepProps = this.$stepProps;
let originalProps = stepProps[stepIndex] = stepProps[stepIndex] || {};
for (let key in props) {
originalProps[key] = target[key];
}
this.$shouldSaveProps = false;
}
$easeProps(stepIndex, props, position, duration, ease) {
if (this.$shouldSaveProps) {
this.$saveOriginalProps(stepIndex, props);
}
let target = this.$target;
let originalProps = this.$stepProps[stepIndex] || {};
if (position > duration) {
position = duration;
}
for (let key in props) {
let originalValue = originalProps[key];
let offsetValue = props[key] - originalValue;
target[key] = ease(position, originalValue, offsetValue, duration);
}
}
$setProps(props) {
let target = this.$target;
for (let key in props) {
target[key] = props[key];
}
}
static get(target, option) {
return new Tween(target, option);
}
static pauseTweens(target) {
let tweens = this.$tweens;
for (let tween of tweens) {
if (tween.$target === target) {
tween.pause();
}
}
}
static resumeTweens(target) {
let tweens = this.$tweens;
for (let tween of tweens) {
if (tween.$target === target) {
tween.resume();
}
}
}
static removeTweens(target) {
let tweens = this.$tweens;
for (let i = tweens.length - 1; i >= 0; --i) {
let tween = tweens[i];
if (tween.$target === target) {
tween.stop();
}
}
}
static removeAllTweens() {
let tweens = this.$tweens;
for (let i = tweens.length - 1; i >= 0; --i) {
let tween = tweens[i];
tween.stop();
}
}
}
Tween.$tweens = [];
var Tween$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
Tween: Tween
});
class Scroller extends Layer {
constructor() {
super();
this.$scrollTop = 0;
this.$scrollLeft = 0;
this.$scrollWidth = 0;
this.$scrollHeight = 0;
this.$touchingX = null;
this.$touchingY = null;
this.$touchingId = null;
this.$touchingTime = null;
this.$velocitiesX = [];
this.$velocitiesY = [];
this.$inertiaTween = null;
this.width = 200;
this.height = 200;
this.on(TouchEvent.TOUCH_START, this.$onTouchStart);
this.on(TouchEvent.TOUCH_MOVE, this.$onTouchMove);
this.on(TouchEvent.TOUCH_END, this.$onTouchEnd);
this.on(TouchEvent.TOUCH_CANCEL, this.$onTouchCancel);
}
get scrollTop() {
return this.$scrollTop;
}
set scrollTop(scrollTop) {
let bounds = this.$getContentBounds();
let maxScrollTop = this.$scrollHeight - this.$height;
scrollTop = Math.max(0, Math.min(scrollTop, maxScrollTop));
if (scrollTop !== this.$scrollTop) {
this.$scrollTop = scrollTop;
this.$markDirty();
}
bounds.release();
}
get scrollLeft() {
return this.$scrollLeft;
}
set scrollLeft(scrollLeft) {
let bounds = this.$getContentBounds();
let maxScrollLeft = this.$