Commit daadfab5 authored by rockyl's avatar rockyl

rebuild-lifecycle

parent b4f552ef
......@@ -4,7 +4,7 @@
import {cleanEntity, setupScene, } from "./interpreter";
import {addLoader, cacheRes, destroyRes, getAllResUuids, loadJson5} from "../assets-manager";
import {customConfig, getRoot, pause, Scene, start} from "../core";
import {Scene, } from "../core";
export * from './interpreter'
......
This diff is collapsed.
This diff is collapsed.
......@@ -21,11 +21,12 @@ export class Component extends HashObject {
*/
entity: Entity;
protected delayCallbacks = [];
private _firstUpdate: boolean;
//是否有效
protected _enabled: boolean = EngineConfig.componentEnabled;
private _firstUpdate: boolean = EngineConfig.componentEnabled;
/**
* 是否有效状态
*/
......@@ -39,9 +40,11 @@ export class Component extends HashObject {
if (this.entity && this.entity.isActive) {
if(!EngineConfig.editorMode){
if (this._enabled) {
this._firstUpdate = true;
this.onEnable();
} else {
this.onDisable();
this.onDestroy();
}
}
}
......@@ -86,7 +89,7 @@ export class Component extends HashObject {
* 当组件被唤醒时
*/
onAwake() {
this._firstUpdate = true;
}
/**
......@@ -104,6 +107,10 @@ export class Component extends HashObject {
}
$onUpdate(t) {
if (this._firstUpdate) {
this.$onAwake();
}
this.onUpdate(t);
if (!this._firstUpdate) {
......
......@@ -5,87 +5,8 @@
import HashObject from "./HashObject";
import {Component} from "./Component";
import {EngineConfig} from "../engine-config";
/**
* 节点遍历(先序遍历)
* @param target 目标节点
* @param hitChild 遇到子节点回调
* @param level 深度,默认全部遍历
* @param includeSelf 是否包括自身
* @param fullCallback 子节点遍历完后回调
* @param params 其他参数
*/
export function traverse(target: Entity, hitChild: (child: Entity, ...params) => boolean, level = -1, includeSelf = false, fullCallback?: (current: Entity) => void, ...params) {
let interrupt;
if (includeSelf) {
hitChild(target, ...params);
}
if (level !== 0) {
for (let child of target.children) {
if (hitChild(child, ...params)) {
interrupt = true;
continue;
}
if (child.children.length > 0) {
traverse(child, hitChild, level - 1, false, fullCallback, ...params);
}
}
}
fullCallback && fullCallback(target);
}
/**
* 节点遍历(后序遍历且倒序)
* @param target 目标节点
* @param hitChild 遇到子节点回调
* @param level 深度,默认全部遍历
* @param includeSelf 是否包括自身
* @param fullCallback 子节点遍历完后回调
* @param params 其他参数
*/
export function traversePostorder(target: Entity, hitChild: (child: Entity, ...params) => boolean, level = -1, includeSelf = false, fullCallback?: (current: Entity) => void, ...params) {
let interrupt;
if (level !== 0) {
for (let i = target.children.length - 1; i >= 0; i--) {
const child = target.children[i];
if (traversePostorder(child, hitChild, level - 1, false, fullCallback, ...params)) {
return true;
}
if (hitChild(child, ...params)) {
return true;
}
}
}
if (includeSelf) {
hitChild(target, ...params);
}
!interrupt && fullCallback && fullCallback(target);
}
/**
* 节点冒泡
* @param target 目标节点
* @param hitParent 遇到父节点回调
* @param includeSelf 是否包括自身
* @param params 其他参数
*/
export function bubbling(target: Entity, hitParent: (parent: Entity, ...params) => boolean, includeSelf = false, ...params) {
if (includeSelf) {
hitParent(target, ...params);
}
let entity = target;
while (entity = entity.parent) {
if (hitParent(entity, ...params)) {
break;
}
}
}
import ScillaEngine from "./ScillaEngine";
import {bubbling, traverse} from "./entity-utils";
/**
* 实体类
......@@ -114,6 +35,9 @@ export class Entity extends HashObject {
}
}
/**
* 获取唯一ID
*/
get uuid(): string {
return this._uuid;
}
......@@ -165,11 +89,10 @@ export class Entity extends HashObject {
_free() {
this._isFree = true;
let that = this;
traverse(this, function (child: Entity) {
/*if (child.isParentActive) {
that._invokeEnabledState(false);
}*/
if (child.isParentActive) {
child._invokeEnabledState(false);
}
child._free();
return false;
}, 1)
......@@ -181,11 +104,10 @@ export class Entity extends HashObject {
_restrict() {
this._isFree = false;
let that = this;
traverse(this, function (child: Entity) {
/*if (child.isParentActive) {
that._invokeEnabledState(true);
}*/
if (child.isParentActive) {
child._invokeEnabledState(true);
}
child._restrict();
return false;
}, 1)
......@@ -200,6 +122,18 @@ export class Entity extends HashObject {
return this._parent;
}
/**
* 获取根节点
*/
get root(): RootEntity{
let p:any = this;
while(p = p.parent){
if(p instanceof RootEntity){
return p;
}
}
}
/**
* 是否含有子节点
* @param child
......@@ -458,7 +392,7 @@ export class Entity extends HashObject {
onEnable() {
this.forEachComponent(comp => {
if (comp.enabled) {
return comp.$onAwake();
return comp.onEnable();
}
});
}
......@@ -469,7 +403,7 @@ export class Entity extends HashObject {
onDisable() {
this.forEachComponent(comp => {
if (comp.enabled) {
return comp.$onSleep();
return comp.onDisable();
}
});
}
......@@ -614,3 +548,25 @@ export class Entity extends HashObject {
}
};
}
/**
* 根实体类
*/
export class RootEntity extends Entity{
protected _engine: ScillaEngine;
constructor(engine: ScillaEngine){
super();
this.name = 'root';
this._engine = engine;
}
/**
* 获取引擎,只有活动的root节点才有这个属性
*/
get engine(){
return this._engine;
}
}
......@@ -12,7 +12,7 @@ function getHashCode() {
* 哈希对象
*/
export default class HashObject {
_hashCode: number;
private _hashCode: number;
constructor() {
this._hashCode = getHashCode();
......
/**
* Created by rockyl on 2018-12-03.
*/
import {Entity} from "./Entity";
import {loadResItems} from "../assets-manager";
/**
* 场景
*/
export class Scene {
name: string;
root: Entity;
resourceGroups: any = {
preload: [],
delay: [],
};
config: any;
/**
* 初始化
* @param config
*/
initByConfig(config: any){
this.config = config;
this.name = config.name;
const resourceGroups = config['resource-groups'];
for(let key in resourceGroups){
this.resourceGroups[key] = resourceGroups[key];
}
}
/**
* 加载资源组
* @param name
* @param progress
*/
async loadResGroup(name, progress?){
await loadResItems(this.resourceGroups[name], progress);
}
}
/**
* Created by rockyl on 2018/11/23.
*
* 引擎
*/
import {Entity, RootEntity} from "./Entity";
import {injectProp} from "../tools/utils";
import InteractContext from "./context/InteractContext";
import RenderContext, {ScaleMode} from "./context/RenderContext";
import './requestAnimationFrame';
import {AssetsManager} from "../assets-manager";
import {traverse, traversePostorder} from "./entity-utils";
/**
* 引擎类
*/
export default class ScillaEngine {
/**
* 默认配置
*/
readonly engineConfig: any = {
fps: 60,
designWidth: 750,
designHeight: 1334,
scaleMode: ScaleMode.FIXED_WIDTH,
touchEnabled: true,
};
/**
* 自定义参数
*/
readonly customConfig: any = {};
private _root: Entity;
private canvasElement: HTMLElement;
private _flush = 0;
private _currentFlush = 0;
private tsStart;
private tsLast;
private lastFPS = 0;
private _renderContext: RenderContext;
private _interactContext: InteractContext;
private _assetsManager: AssetsManager;
private nextTicks = [];
constructor() {
this._assetsManager = new AssetsManager();
}
/**
* 装配引擎
* @param _engineConfig
* @param _customConfig
*/
setup(_engineConfig?, _customConfig?) {
injectProp(this.engineConfig, _engineConfig);
injectProp(this.customConfig, _customConfig);
const {canvas, designWidth, designHeight, scaleMode, modifyCanvasSize, touchEnabled} = this.engineConfig;
this.canvasElement = typeof canvas == 'string' ? document.getElementById(canvas) : canvas;
this._interactContext = new InteractContext(this);
this._interactContext.setup({
canvas: this.canvasElement,
touchHandlers: {
onTouchBegin: this.onTouchBegin.bind(this),
onTouchMove: this.onTouchMove.bind(this),
onTouchEnd: this.onTouchEnd.bind(this),
},
touchEnabled,
});
this._renderContext = new RenderContext(this);
this._renderContext.setup({
canvas: this.canvasElement,
designWidth,
designHeight,
scaleMode,
modifyCanvasSize,
onUpdateScale: this.onUpdateScale.bind(this),
});
this._root = new RootEntity(this);
this._root._restrict();
}
/**
* 开始引擎
*/
start() {
this._root.enabled = true;
this.tsStart = Date.now();
this.startTick();
}
/**
* 暂停引擎
*/
pause() {
this._root.enabled = false;
this.stopTick();
}
/**
* 获取根Entity
*/
get root(): Entity {
return this._root;
}
/**
* 获取节点路径
* @param entity
*/
getEntityPath(entity?: Entity): string {
let path = '';
let current = entity || this._root;
while (current.parent) {
path = current.parent.children.indexOf(current) + (path.length > 0 ? '|' : '') + path;
current = current.parent;
}
return path;
}
/**
* 根据节点路径获取节点
* @param path
*/
getEntityByPath(path?: string): Entity {
let target = this._root;
if (path.length > 0) {
let arr = path.split('|');
for (let item of arr) {
target = target.children[item];
if (!target) {
target = null;
break;
}
}
}
return target;
}
/**
* 获取当前帧率
*/
getFPS() {
return this.lastFPS;
}
/**
* 获取渲染上下文
*/
get renderContext(): RenderContext {
return this._renderContext;
}
/**
* 获取交互上下文
*/
get interactContext(): InteractContext {
return this._interactContext;
}
/**
* 获取资源管理
*/
get assetsManager(): AssetsManager {
return this._assetsManager;
}
private onUpdateScale(scaleX, scaleY, rotation) {
this._interactContext.updateScale(scaleX, scaleY, rotation)
}
/**
* 开始时钟
*/
private startTick() {
this._flush = 60 / this.engineConfig.fps - 1 >> 0;
if (this._flush < 0) {
this._flush = 0;
}
requestAnimationFrame(this.flush);
}
/**
* 停止时钟
*/
private stopTick() {
}
/**
* 时钟触发
*/
private flush = (tsNow): void => {
if (this._flush == 0) {
this.onFrameTick(tsNow);
} else {
if (this._currentFlush == 0) {
this.onFrameTick(tsNow);
this._currentFlush = this._flush;
} else {
this._currentFlush--;
}
}
requestAnimationFrame(this.flush);
}
/**
* 下一帧执行
* @param func
* @param tickCount
*/
nextTick(func, tickCount = 1) {
this.nextTicks.push({func, tickCount});
}
private onFrameTick(tsNow) {
this._renderContext.clear();
const tsNow2 = Date.now();
this.lastFPS = Math.floor(1000 / (tsNow - this.tsLast));
this.tsLast = tsNow;
const ts = tsNow - this.tsStart;
traverse(this._root, function (child) {
if (!child.isFree && child.enabled) {
child.onUpdate(ts);
} else {
return true;
}
}, -1, true, function (current) {
current.afterUpdate();
});
//const tsPass = Date.now() - tsNow;
for (let i = 0, li = this.nextTicks.length; i < li; i++) {
const item = this.nextTicks[i];
item.tickCount--;
if (item.tickCount <= 0) {
item.func(ts);
this.nextTicks.splice(i, 1);
i--;
li--;
}
}
}
/**
* 代理出来的onTouchBegin方法
* @param event
*/
private onTouchBegin(event) {
traversePostorder(this._root, function (child) {
return child.onInteract(0, event);
})
}
/**
* 代理出来的onTouchMove方法
* @param event
*/
private onTouchMove(event) {
traversePostorder(this._root, function (child) {
return child.onInteract(1, event);
})
}
/**
* 代理出来的onTouchEnd方法
* @param event
*/
private onTouchEnd(event) {
traversePostorder(this._root, function (child) {
return child.onInteract(2, event);
})
}
/**
* 页面坐标转画布坐标
* @param pageX
* @param pageY
* @param identifier
* @param isLocalPos
*/
pagePosToCanvasPos(pageX, pageY, identifier?, isLocalPos: boolean = false) {
const {scaleX, scaleY, rotation} = this.renderContext;
let x = pageX, y = pageY;
let box = this.canvasElement.getBoundingClientRect();
if (!isLocalPos) {
let doc = document.documentElement;
let left = box.left + window.pageXOffset - doc.clientLeft;
let top = box.top + window.pageYOffset - doc.clientTop;
x = pageX - left;
y = pageY - top;
}
let newX = x;
let newY = y;
if (rotation === 90) {
newX = y;
newY = box.width - x;
} else if (rotation === -90) {
newX = box.height - y;
newY = x;
}
newX = newX / scaleX;
newY = newY / scaleY;
return {
x: Math.round(newX),
y: Math.round(newY),
identifier: identifier || 0,
};
}
/**
* 画布坐标转页面坐标
* @param x
* @param y
* @param isLocalPos
*/
canvasPosToPagePos(x, y, isLocalPos: boolean = true) {
const {scaleX, scaleY, rotation} = this.renderContext;
x = x * scaleX;
y = y * scaleY;
if (!isLocalPos) {
let box = this.canvasElement.getBoundingClientRect();
let doc = document.documentElement;
let left = box.left + window.pageXOffset - doc.clientLeft;
let top = box.top + window.pageYOffset - doc.clientTop;
x += left;
y += top;
}
return {
x, y,
}
}
}
......@@ -3,207 +3,173 @@
*
* 交互上下文
*/
let _canvas;
let _touchHandler;
let _scaleX, _scaleY, _rotation;
import ScillaEngine from "../ScillaEngine";
const ua = navigator.userAgent.toLowerCase();
const isMobile = (ua.indexOf('mobile') !== -1 || ua.indexOf('android') !== -1);
interface InteractContextOption{
/**
* 画布
*/
canvas:any;
/**
* 触摸句柄
*/
touchHandlers: any;
/**
* 触摸开关
*/
touchEnabled: boolean;
}
/**
* 装配上下文
* @param options
* 交互上下文
*/
export function setupContext(options:any = {}) {
const {canvas, touchHandler, touchEnabled} = options;
export default class InteractContext {
protected canvas;
protected touchHandlers;
protected scaleX;
protected scaleY;
protected rotation;
_touchHandler = touchHandler;
private engine;
_canvas = canvas;
if(touchEnabled){
addListeners();
constructor(engine: ScillaEngine) {
this.engine = engine;
}
}
/**
* 更新缩放模式
* @param scaleX
* @param scaleY
* @param rotation
*/
export function updateScaleMode(scaleX, scaleY, rotation) {
_scaleX = scaleX;
_scaleY = scaleY;
_rotation = rotation;
}
/**
* 装配上下文
* @param options
*/
setup(options: InteractContextOption = <InteractContextOption>{}) {
const {canvas, touchHandlers, touchEnabled} = options;
/**
* 适配鼠标事件
*/
function addListeners() {
if (window.navigator.msPointerEnabled) {
_canvas.addEventListener("MSPointerDown", (event) => {
event.identifier = event.pointerId;
onTouchBegin(event);
prevent(event);
}, false);
_canvas.addEventListener("MSPointerMove", (event) => {
event.identifier = event.pointerId;
onTouchMove(event);
prevent(event);
}, false);
_canvas.addEventListener("MSPointerUp", (event) => {
event.identifier = event.pointerId;
onTouchEnd(event);
prevent(event);
}, false);
}
else {
if (!isMobile) {
addMouseListener();
this.touchHandlers = touchHandlers;
this.canvas = canvas;
if (touchEnabled) {
this.addListeners();
}
addTouchListener();
}
}
/**
* 阻断页面拖动
* @param event
*/
function prevent(event) {
event.stopPropagation();
if (event["isScroll"] != true && !_canvas['userTyping']) {
event.preventDefault();
/**
* 更新缩放模式
* @param scaleX
* @param scaleY
* @param rotation
*/
updateScale(scaleX, scaleY, rotation) {
this.scaleX = scaleX;
this.scaleY = scaleY;
this.rotation = rotation;
}
}
/**
* 增加鼠标事件
*/
function addMouseListener() {
_canvas.addEventListener("mousedown", onTouchBegin);
_canvas.addEventListener("mousemove", onMouseMove);
_canvas.addEventListener("mouseup", onTouchEnd);
}
/**
* 增加触摸事件
*/
function addTouchListener() {
_canvas.addEventListener("touchstart", (event) => {
for(let touch of event.changedTouches){
onTouchBegin(touch);
/**
* 适配鼠标事件
*/
private addListeners() {
if (window.navigator.msPointerEnabled) {
this.canvas.addEventListener("MSPointerDown", (event) => {
event.identifier = event.pointerId;
this.onTouchBegin(event);
this.prevent(event);
}, false);
this.canvas.addEventListener("MSPointerMove", (event) => {
event.identifier = event.pointerId;
this.onTouchMove(event);
this.prevent(event);
}, false);
this.canvas.addEventListener("MSPointerUp", (event) => {
event.identifier = event.pointerId;
this.onTouchEnd(event);
this.prevent(event);
}, false);
} else {
if (!isMobile) {
this.addMouseListener();
}
this.addTouchListener();
}
prevent(event);
}, false);
_canvas.addEventListener("touchmove", (event) => {
for(let touch of event.changedTouches){
onTouchMove(touch);
}
prevent(event);
}, false);
_canvas.addEventListener("touchend", (event) => {
for(let touch of event.changedTouches){
onTouchEnd(touch);
}
prevent(event);
}, false);
_canvas.addEventListener("touchcancel", (event) => {
for(let touch of event.changedTouches){
onTouchEnd(touch);
}
prevent(event);
}, false);
}
function onTouchBegin(event) {
let location = getLocation(event);
_touchHandler.onTouchBegin(location);
}
function onMouseMove(event) {
if (event.buttons === 0) {
onTouchEnd(event);
} else {
onTouchMove(event);
}
}
function onTouchMove(event) {
let location = getLocation(event);
_touchHandler.onTouchMove(location);
/**
* 阻断页面拖动
* @param event
*/
private prevent(event) {
event.stopPropagation();
if (event["isScroll"] != true && !this.canvas['userTyping']) {
event.preventDefault();
}
}
}
/**
* 增加鼠标事件
*/
private addMouseListener() {
this.canvas.addEventListener("mousedown", this.onTouchBegin);
this.canvas.addEventListener("mousemove", this.onMouseMove);
this.canvas.addEventListener("mouseup", this.onTouchEnd);
}
function onTouchEnd(event) {
let location = getLocation(event);
_touchHandler.onTouchEnd(location);
}
/**
* 增加触摸事件
*/
private addTouchListener() {
this.canvas.addEventListener("touchstart", (event) => {
for (let touch of event.changedTouches) {
this.onTouchBegin(touch);
}
this.prevent(event);
}, false);
this.canvas.addEventListener("touchmove", (event) => {
for (let touch of event.changedTouches) {
this.onTouchMove(touch);
}
this.prevent(event);
}, false);
this.canvas.addEventListener("touchend", (event) => {
for (let touch of event.changedTouches) {
this.onTouchEnd(touch);
}
this.prevent(event);
}, false);
this.canvas.addEventListener("touchcancel", (event) => {
for (let touch of event.changedTouches) {
this.onTouchEnd(touch);
}
this.prevent(event);
}, false);
}
function getLocation(event){
return pagePosToCanvasPos(event.pageX, event.pageY, event.identifier)
}
private onTouchBegin(event) {
let location = this.getLocation(event);
this.touchHandlers.onTouchBegin(location);
}
/**
* 页面坐标转画布坐标
* @param pageX
* @param pageY
* @param identifier
* @param isLocalPos
*/
export function pagePosToCanvasPos(pageX, pageY, identifier?, isLocalPos:boolean = false) {
let x = pageX, y = pageY;
let box = _canvas.getBoundingClientRect();
if(!isLocalPos){
let doc = document.documentElement;
let left = box.left + window.pageXOffset - doc.clientLeft;
let top = box.top + window.pageYOffset - doc.clientTop;
x = pageX - left;
y = pageY - top;
private onMouseMove(event) {
if (event.buttons === 0) {
this.onTouchEnd(event);
} else {
this.onTouchMove(event);
}
}
let newX = x;
let newY = y;
private onTouchMove(event) {
let location = this.getLocation(event);
this.touchHandlers.onTouchMove(location);
if (_rotation === 90) {
newX = y;
newY = box.width - x;
}
else if (_rotation === -90) {
newX = box.height - y;
newY = x;
}
newX = newX / _scaleX;
newY = newY / _scaleY;
return {
x: Math.round(newX),
y: Math.round(newY),
identifier: identifier || 0,
};
}
/**
* 画布坐标转页面坐标
* @param x
* @param y
* @param isLocalPos
*/
export function canvasPosToPagePos(x, y, isLocalPos:boolean = true){
x = x * _scaleX;
y = y * _scaleY;
if(!isLocalPos){
let box = _canvas.getBoundingClientRect();
let doc = document.documentElement;
let left = box.left + window.pageXOffset - doc.clientLeft;
let top = box.top + window.pageYOffset - doc.clientTop;
x += left;
y += top;
private onTouchEnd(event) {
let location = this.getLocation(event);
this.touchHandlers.onTouchEnd(location);
}
return {
x, y,
private getLocation(event) {
return this.engine.pagePosToCanvasPos(event.pageX, event.pageY, event.identifier)
}
}
......@@ -4,9 +4,9 @@
* 渲染上下文
*/
import {updateScaleMode} from "./InteractContext";
import Bounds from "../../support/Bounds";
import {dirtyFieldTrigger} from "../../tools/decorators";
import ScillaEngine from "../ScillaEngine";
/**
* 缩放模式
......@@ -15,44 +15,100 @@ import {dirtyFieldTrigger} from "../../tools/decorators";
* FIXED_WIDTH: 宽度固定
* FIXED_HEIGHT: 高度固定
*/
export const ScaleMode = {
SHOW_ALL: 'showAll',
FIXED_WIDTH: 'fixedWidth',
FIXED_HEIGHT: 'fixedHeight',
NO_SCALE: 'noScale',
NO_FIXED: 'noFixed',
};
export enum ScaleMode {
SHOW_ALL = 'showAll',
FIXED_WIDTH = 'fixedWidth',
FIXED_HEIGHT = 'fixedHeight',
NO_SCALE = 'noScale',
NO_FIXED = 'noFixed',
}
interface RenderContextOption {
/**
* 画布实例
*/
canvas: any;
/**
* 设计宽度
*/
designWidth: number;
/**
* 设计高度
*/
designHeight: number;
/**
* 缩放模式
*/
scaleMode: ScaleMode;
/**
* 是否修改画布尺寸
*/
modifyCanvasSize?: boolean;
/**
* 是否自动调整舞台尺寸
*/
autoAdjustSize?: boolean;
/**
* 当缩放模式修改时
*/
onUpdateScale: Function;
}
/**
* 渲染上下文
*/
export default class RenderContext{
export default class RenderContext {
protected canvas;
protected context;
protected canvasContext;
protected stageWidth;
protected stageHeight;
protected scaleX;
protected scaleY;
protected rotation = 0;
protected stageWidth: number;
protected stageHeight: number;
protected dirtyFieldTriggerLock = false;
private _scaleX: number;
private _scaleY: number;
private _rotation: number = 0;
protected dirtyFieldTriggerLock: boolean = false;
private shortcutCanvas;
private onUpdateScale: Function;
private engine;
/**
* 设计宽度
*/
@dirtyFieldTrigger
designWidth;
/**
* 设计高度
*/
@dirtyFieldTrigger
designHeight;
/**
* 缩放模式
*/
@dirtyFieldTrigger
scaleMode;
scaleMode: ScaleMode;
/**
* 是否修改画布的尺寸
*/
@dirtyFieldTrigger
modifyCanvasSize;
/**
* 是否自动调整舞台尺寸
*/
@dirtyFieldTrigger
autoAdjustSize;
private onModify(value, key, oldValue){
if(!this.dirtyFieldTriggerLock){
constructor(engine: ScillaEngine) {
this.engine = engine;
}
//todo
private onModify(value, key, oldValue) {
if (!this.dirtyFieldTriggerLock) {
this.updateScaleModeSelf();
}
}
......@@ -61,11 +117,11 @@ export default class RenderContext{
* 装配上下文
* @param options
*/
setup(options: any = {}){
const {canvas, designWidth, designHeight, scaleMode = ScaleMode.SHOW_ALL, modifyCanvasSize = false, autoAdjustSize = false} = options;
setup(options: RenderContextOption = <RenderContextOption>{}) {
const {canvas, designWidth, designHeight, scaleMode = ScaleMode.SHOW_ALL, modifyCanvasSize = false, autoAdjustSize = false, onUpdateScale} = options;
this.canvas = canvas;
this.context = canvas.getContext('2d');
this.canvasContext = canvas.getContext('2d');
this.dirtyFieldTriggerLock = true;
......@@ -74,31 +130,53 @@ export default class RenderContext{
this.scaleMode = scaleMode;
this.modifyCanvasSize = modifyCanvasSize;
this.autoAdjustSize = autoAdjustSize;
this.onUpdateScale = onUpdateScale;
this.dirtyFieldTriggerLock = false;
this.updateScaleModeSelf();
}
/**
* 缩放x
*/
get scaleX(): number {
return this._scaleX;
}
/**
* 缩放y
*/
get scaleY(): number {
return this._scaleY;
}
/**
* 旋转
*/
get rotation(): number {
return this._rotation;
}
/**
* 清空渲染上下文
*/
clear() {
this.context.setTransform(1, 0, 0, 1, 0, 0);
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.canvasContext.setTransform(1, 0, 0, 1, 0, 0);
this.canvasContext.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
/**
* 获取渲染上下文
*/
getContext() {
return this.context;
get context() {
return this.canvasContext;
}
/**
* 获取舞台尺寸
*/
getStageSize() {
get stageSize() {
return {
width: this.stageWidth,
height: this.stageHeight,
......@@ -108,17 +186,17 @@ export default class RenderContext{
/**
* 获取舞台缩放
*/
getStageScale() {
get stageScale() {
return {
x: this.scaleX,
y: this.scaleY,
x: this._scaleX,
y: this._scaleY,
}
}
/**
* 获取舞台中心
*/
getStageCenter() {
get stageCenter() {
return {
x: this.stageWidth / 2,
y: this.stageHeight / 2,
......@@ -129,7 +207,7 @@ export default class RenderContext{
* 更新缩放模式
*/
private updateScaleModeSelf() {
let {canvas, designWidth, designHeight, scaleMode, rotation, modifyCanvasSize} = this;
let {canvas, designWidth, designHeight, scaleMode, _rotation, modifyCanvasSize} = this;
let parent = canvas.parentElement;
let containerWidth = parent.clientWidth;
......@@ -190,9 +268,9 @@ export default class RenderContext{
break;
case ScaleMode.FIXED_WIDTH:
width = dWidth;
if(modifyCanvasSize){
if (modifyCanvasSize) {
height = dHeight;
}else{
} else {
height = containerHeight / scaleX;
}
scaleY = scaleX;
......@@ -201,9 +279,9 @@ export default class RenderContext{
stageHeight = height;
break;
case ScaleMode.FIXED_HEIGHT:
if(modifyCanvasSize){
if (modifyCanvasSize) {
width = dWidth;
}else{
} else {
width = containerWidth / scaleY;
}
height = dHeight;
......@@ -235,12 +313,12 @@ export default class RenderContext{
break;
}
updateScaleMode(scaleX, scaleY, rotation);
this.onUpdateScale(scaleX, scaleY, _rotation);
this.stageWidth = stageWidth;
this.stageHeight = stageHeight;
this.scaleX = scaleX;
this.scaleY = scaleY;
this._scaleX = scaleX;
this._scaleY = scaleY;
canvas.width = width;
canvas.height = height;
......@@ -257,18 +335,18 @@ export default class RenderContext{
async shortcut(type: number = 0, params: ShortcutParams) {
let targetImg;
if(params.bounds || params.zoomToDom){
if (params.bounds || params.zoomToDom) {
const dataUrl = this.canvas.toDataURL('image/png');
const img = await dataUrlToImage(dataUrl);
targetImg = await this.shortcutWithSize(img, type, params.imgType, params.quality, params.bounds, params.zoomToDom ? this.scaleX : 1);
}else{
targetImg = await this.shortcutWithSize(img, type, params.imgType, params.quality, params.bounds, params.zoomToDom ? this._scaleX : 1);
} else {
targetImg = this.canvas.toDataURL(params.imgType, params.quality);
}
return targetImg;
}
private async shortcutWithSize(img, type, imgType, quality?, bounds?:Bounds, scale = 1) {
private async shortcutWithSize(img, type, imgType, quality?, bounds?: Bounds, scale = 1) {
let {shortcutCanvas, stageWidth, stageHeight} = this;
if (!shortcutCanvas) {
......@@ -282,8 +360,8 @@ export default class RenderContext{
const dh = sh * scale;
shortcutCanvas.width = dw;
shortcutCanvas.height = dh;
const context = shortcutCanvas.getContext('2d');
context.drawImage(img, sx, sy, sw, sh, 0, 0, dw, dh);
const canvasContext = shortcutCanvas.getContext('2d');
canvasContext.drawImage(img, sx, sy, sw, sh, 0, 0, dw, dh);
const dataUrl = shortcutCanvas.toDataURL('image/' + imgType, quality);
switch (type) {
......
/**
* Created by rockyl on 2019-04-22.
*/
import {Entity} from "./Entity";
/**
* 节点遍历(先序遍历)
* @param target 目标节点
* @param hitChild 遇到子节点回调
* @param level 深度,默认全部遍历
* @param includeSelf 是否包括自身
* @param fullCallback 子节点遍历完后回调
* @param params 其他参数
*/
export function traverse(target: Entity, hitChild: (child: Entity, ...params) => boolean, level = -1, includeSelf = false, fullCallback?: (current: Entity) => void, ...params) {
let interrupt;
if (includeSelf) {
hitChild(target, ...params);
}
if (level !== 0) {
for (let child of target.children) {
if (hitChild(child, ...params)) {
interrupt = true;
continue;
}
if (child.children.length > 0) {
traverse(child, hitChild, level - 1, false, fullCallback, ...params);
}
}
}
fullCallback && fullCallback(target);
}
/**
* 节点遍历(后序遍历且倒序)
* @param target 目标节点
* @param hitChild 遇到子节点回调
* @param level 深度,默认全部遍历
* @param includeSelf 是否包括自身
* @param fullCallback 子节点遍历完后回调
* @param params 其他参数
*/
export function traversePostorder(target: Entity, hitChild: (child: Entity, ...params) => boolean, level = -1, includeSelf = false, fullCallback?: (current: Entity) => void, ...params) {
let interrupt;
if (level !== 0) {
for (let i = target.children.length - 1; i >= 0; i--) {
const child = target.children[i];
if (traversePostorder(child, hitChild, level - 1, false, fullCallback, ...params)) {
return true;
}
if (hitChild(child, ...params)) {
return true;
}
}
}
if (includeSelf) {
hitChild(target, ...params);
}
!interrupt && fullCallback && fullCallback(target);
}
/**
* 节点冒泡
* @param target 目标节点
* @param hitParent 遇到父节点回调
* @param includeSelf 是否包括自身
* @param params 其他参数
*/
export function bubbling(target: Entity, hitParent: (parent: Entity, ...params) => boolean, includeSelf = false, ...params) {
if (includeSelf) {
hitParent(target, ...params);
}
let entity = target;
while (entity = entity.parent) {
if (hitParent(entity, ...params)) {
break;
}
}
}
......@@ -4,12 +4,12 @@
export {Component} from "./Component";
export {Entity} from './Entity'
export {Scene} from './Scene'
export {ScillaEvent} from './ScillaEvent'
export {createCanvas, ScaleMode} from './context/RenderContext';
export {pagePosToCanvasPos, canvasPosToPagePos} from './context/InteractContext';
export * from './manager'
export {default as ScillaEngine} from './ScillaEngine'
export {default as Texture, createTexture} from './Texture'
export * from './Sheet'
export * from './FrameAnimation'
export * from './interpreter'
/**
* Created by rockyl on 2018-12-03.
*
* 配置文件解释器
*/
import {Entity} from "../core/Entity";
import {ScillaEvent} from "../core/ScillaEvent";
import {EngineConfig} from "../engine-config";
let entityCache = {};
let entityCacheConfig;
const defMap = {};
let prefabID: number = 0;
let _getResProxy: Function;
/**
* 注册组件
* @param name
* @param def
*/
export function registerDef(name: string, def) {
defMap[name] = def;
def.__class__ = name;
}
function getEntityCacheConfig(config) {
return config['entity-cache'] ? config['entity-cache'].concat() : [];
}
/**
* 装配场景
* @param config
* @param root
* @param getResProxy
*/
export function setupScene(config: any, root: Entity, getResProxy: Function) {
_getResProxy = getResProxy;
entityCacheConfig = getEntityCacheConfig(config);
instantiateConfig(config, root);
entityCache = {};
}
/**
* 清空实体
* @param entity
*/
export function cleanEntity(entity: Entity) {
entity.removeAllComponents();
entity.removeChildren();
}
/**
* 装配预制体
* @param config
*/
export function instantiate(config: any): Entity {
let pid = ++prefabID;
entityCacheConfig = getEntityCacheConfig(config);
for (let i = 0, li = entityCacheConfig.length; i < li; i++) {
entityCacheConfig[i] = pid + '_' + entityCacheConfig[i];
}
const entity = instantiateConfig(config, null, pid);
entityCache = {};
return entity.children[0];
}
/**
* 装配树节点
* @param config
* @param root
* @param pid
*/
function instantiateConfig(config, root?: Entity, pid?:number): Entity {
let rootConfig = config.root;
const entity = setupEntity(rootConfig, root);
setupComponent(rootConfig, entity, true);
injectComponent(rootConfig, entity, true, pid);
return entity;
}
/**
* 装配实体
* @param config
* @param root
* @param pid
*/
function setupEntity(config, root?: Entity, pid?: number): Entity {
let entity: Entity = null;
if (config) {
let {name, uuid, children} = config;
if (pid !== undefined && uuid !== undefined) {
uuid = pid + '_' + uuid;
}
entity = root || new Entity(name, uuid);
if (entityCacheConfig.indexOf(uuid) >= 0) {
entityCache[uuid] = entity;
}
if (children) {
for (let i = 0, li = children.length; i < li; i++) {
let child = children[i];
const childEntity = setupEntity(child, null, pid);
entity.addChild(childEntity);
}
}
if (!root) {
entity.enabled = !config.disabled;
}
}
return entity;
}
/**
* 装配组件
* @param config
* @param root
* @param includeSelf
*/
function setupComponent(config, root: Entity, includeSelf: boolean = false) {
if (includeSelf) {
instantiateComponents(root, config);
}
if (config && config.children) {
for (let i = 0, li = root.children.length; i < li; i++) {
const child = config.children[i];
const entity = root.children[i];
instantiateComponents(entity, child);
setupComponent(child, entity);
}
}
}
/**
* 注入组件参数
* @param config
* @param root
* @param includeSelf
* @param pid
*/
function injectComponent(config, root: Entity, includeSelf = false, pid?: number) {
if (includeSelf) {
injectComponents(root, config, pid);
}
if (config && config.children) {
for (let i = 0, li = root.children.length; i < li; i++) {
const child = config.children[i];
const entity = root.children[i];
injectComponents(entity, child, pid);
injectComponent(child, entity, false, pid);
}
}
}
/**
* 实例化组件列表
* @param entity
* @param config
*/
function instantiateComponents(entity: Entity, config: any) {
if (config.components) {
for (const component of config.components) {
instantiateComponent(entity, component);
}
}
}
/**
* 注入组件列表参数
* @param entity
* @param config
* @param pid
*/
function injectComponents(entity: Entity, config: any, pid?: number) {
if (config.components) {
const components = entity.components;
for (let i = 0, li = config.components.length; i < li; i++) {
const component = config.components[i];
injectComponentProperties(components[i], component, pid);
}
}
}
/**
* 注入组件属性
* @param component
* @param config
* @param pid
*/
export function injectComponentProperties(component, config, pid?: number){
const {properties} = config;
if (properties) {
injectProperties(component, properties, pid);
}
}
/**
* 实例化组件
* @param entity
* @param config
*/
export function instantiateComponent(entity: Entity, config: any) {
const {script, } = config;
let def = getDefByName(script);
if (!def) {
return;
}
const instance: any = new def();
instance.enabled = !config.disabled;
entity.addComponent(instance);
return instance;
}
/**
* 根据名称获取定义
* @param name
* @param showWarn
*/
export function getDefByName(name: string, showWarn: boolean = true): any {
let def;
/*if (name.indexOf('/') >= 0) {//addition
name = name.substr(name.lastIndexOf('/') + 1);
}*/
def = defMap[name];
if (!def && showWarn) {
console.warn('missing def:', name);
return;
}
return def;
}
const skipKeys = ['_type_', '_constructor_'];
/**
* 属性注入
* @param node
* @param propertiesConfig
* @param pid
*/
function injectProperties(node, propertiesConfig, pid?: number) {
if (!node) {
console.warn('node is null.');
return;
}
for (const key in propertiesConfig) {
if (skipKeys.indexOf(key) >= 0) {
continue;
}
const propertyOfConfig: any = propertiesConfig[key];
let propertyOfInstance = node[key];
if (typeof propertyOfConfig === 'object') {
if (propertyOfInstance instanceof ScillaEvent) {
if(!EngineConfig.editorMode){
injectEvent(propertyOfInstance, propertyOfConfig, pid);
}
} else if (propertyOfConfig._type_ === 'raw') {
node[key] = propertyOfInstance = propertyOfConfig.data;
} else {
if (Array.isArray(propertyOfConfig) && !propertyOfInstance) {
node[key] = propertyOfInstance = []
}
node[key] = injectObject(propertyOfInstance, propertyOfConfig, pid);
}
} else {
injectBaseType(node, key, propertyOfConfig, pid);
}
}
}
function injectObject(propertyOfInstance, propertyOfConfig, pid?: number) {
if (propertyOfInstance === undefined) {
if (propertyOfConfig._type_) {
let def = getDefByName(propertyOfConfig._type_);
if (def) {
let constructorArgs = propertyOfConfig._constructor_;
if (constructorArgs && constructorArgs.length > 0) {
propertyOfInstance = def.constructor.apply(null, constructorArgs);
} else {
propertyOfInstance = new def();
}
}
}
}
if (propertyOfInstance) {
injectProperties(propertyOfInstance, propertyOfConfig, pid);
}
return propertyOfInstance;
}
function injectBaseType(node, key, propertyOfConfig, pid?: number) {
let propertyValue;
if (typeof propertyOfConfig === 'string') {
propertyValue = getLink(propertyOfConfig, pid);
} else {
propertyValue = propertyOfConfig;
}
let keyAvatar = key;
if(propertyValue instanceof Promise){
keyAvatar = 'async_' + keyAvatar;
}
node[keyAvatar] = propertyValue;
}
function injectEvent(event: ScillaEvent, config, pid?) {
for (const {entity: entityName, component: componentIndex, method: methodName, param} of config) {
if (entityName && componentIndex >= 0 && methodName) {
const entity = getLink(entityName, pid);
const component = entity.components[componentIndex];
const method = component[methodName];
if (method) {
if (param == undefined) {
event.addListener(method, component, 0);
} else {
event.addListener(method, component, 0, param);
}
}
}
}
}
function getLink(str: string, pid?: number) {
let result;
if (str.indexOf('res|') == 0) { //res uuid
const uuid = str.substr(4);
result = _getResProxy(uuid);
} else if (str.indexOf('entity|') == 0) { //entity uuid
const uuid = transPrefabUUID(str.substr(7), pid);
result = entityCache[uuid];
} else {
result = str;
}
return result;
}
function transPrefabUUID(uuid, pid: number) {
return pid ? pid + '_' + uuid : uuid;
}
/**
* Created by rockyl on 2018/11/23.
*/
import {Entity, traverse, traversePostorder} from "./Entity";
import {injectProp} from "../tools/utils";
import {setupContext as setupInteractContext} from "./context/InteractContext";
import RenderContext, {ScaleMode} from "./context/RenderContext";
import './requestAnimationFrame';
/**
* 默认配置
*/
export const engineConfig: any = {
fps: 60,
designWidth: 750,
designHeight: 1334,
scaleMode: ScaleMode.FIXED_WIDTH,
touchEnabled: true,
};
/**
* 自定义参数
*/
export const customConfig: any = {};
let root: Entity;
let _flush = 0, _currentFlush = 0;
let tsStart, tsLast;
let lastFPS = 0;
let renderContext, interactContext;
/**
* 装配引擎
* @param _engineConfig
* @param _customConfig
*/
export function setup(_engineConfig?, _customConfig?) {
injectProp(engineConfig, _engineConfig);
injectProp(customConfig, _customConfig);
const {canvas, designWidth, designHeight, scaleMode, modifyCanvasSize, touchEnabled} = engineConfig;
let canvasElement = typeof canvas == 'object' ? canvas : document.getElementById(canvas);
interactContext = setupInteractContext({
canvas: canvasElement,
touchHandler: {
onTouchBegin,
onTouchMove,
onTouchEnd,
},
touchEnabled,
});
renderContext = new RenderContext();
renderContext.setup({
canvas: canvasElement,
designWidth,
designHeight,
scaleMode,
modifyCanvasSize,
});
root = new Entity('root');
root._restrict();
}
/**
* 开始引擎
*/
export function start() {
root.enabled = true;
tsStart = Date.now();
startTick();
}
/**
* 暂停引擎
*/
export function pause() {
root.enabled = false;
stopTick();
}
/**
* 获取根Entity
*/
export function getRoot(): Entity {
return root;
}
/**
* 获取节点路径
* @param entity
*/
export function getEntityPath(entity?: Entity): string {
let path = '';
let current = entity || root;
while (current.parent) {
path = current.parent.children.indexOf(current) + (path.length > 0 ? '|' : '') + path;
current = current.parent;
}
return path;
}
/**
* 根据节点路径获取节点
* @param path
*/
export function getEntityByPath(path?: string): Entity {
let target = root;
if (path.length > 0) {
let arr = path.split('|');
for (let item of arr) {
target = target.children[item];
if (!target) {
target = null;
break;
}
}
}
return target;
}
/**
* 获取当前帧率
*/
export function getFPS() {
return lastFPS;
}
/**
* 获取渲染上下文
*/
export function getRenderContext(): RenderContext {
return renderContext;
}
/**
* 开始时钟
*/
function startTick() {
_flush = 60 / engineConfig.fps - 1 >> 0;
if (_flush < 0) {
_flush = 0;
}
requestAnimationFrame(flush);
}
/**
* 停止时钟
*/
function stopTick() {
}
let tsLast2;
/**
* 时钟触发
*/
function flush(tsNow): void {
if (_flush == 0) {
onFrameTick(tsNow);
} else {
if (_currentFlush == 0) {
onFrameTick(tsNow);
_currentFlush = _flush;
} else {
_currentFlush--;
}
}
requestAnimationFrame(flush);
}
const nextTicks = [];
/**
* 下一帧执行
* @param func
* @param tickCount
*/
export function nextTick(func, tickCount = 1) {
nextTicks.push({func, tickCount});
}
function onFrameTick(tsNow) {
renderContext.clear();
const tsNow2 = Date.now();
lastFPS = Math.floor(1000 / (tsNow - tsLast));
tsLast = tsNow;
tsLast2 = tsNow2;
const ts = tsNow - tsStart;
traverse(root, function (child) {
if (!child.isFree && child.enabled) {
child.onUpdate(ts);
} else {
return true;
}
}, -1, true, function (current) {
current.afterUpdate();
});
//const tsPass = Date.now() - tsNow;
for (let i = 0, li = nextTicks.length; i < li; i++) {
const item = nextTicks[i];
item.tickCount--;
if (item.tickCount <= 0) {
item.func(ts);
nextTicks.splice(i, 1);
i--;
li--;
}
}
}
/**
* 代理出来的onTouchBegin方法
* @param event
*/
function onTouchBegin(event) {
traversePostorder(root, function (child) {
return child.onInteract(0, event);
})
}
/**
* 代理出来的onTouchMove方法
* @param event
*/
function onTouchMove(event) {
traversePostorder(root, function (child) {
return child.onInteract(1, event);
})
}
/**
* 代理出来的onTouchEnd方法
* @param event
*/
function onTouchEnd(event) {
traversePostorder(root, function (child) {
return child.onInteract(2, event);
})
}
\ No newline at end of file
/**
* Created by rockyl on 2018-12-05.
*
* 引擎配置
*/
import {injectProp} from "./tools/utils";
/**
* 针对引擎的配置
*/
export const EngineConfig = {
lineHeightRatio: 1.2,
entityEnabled: true,
......@@ -15,6 +20,10 @@ export const EngineConfig = {
editorMode: false,
};
/**
* 注入配置
* @param _options
*/
export function modifyEngineConfig(_options) {
injectProp(EngineConfig, _options);
}
......@@ -3,7 +3,6 @@
*/
export * from './core'
export * from './editor'
export * from './assets-manager'
export * from './support'
export * from './tools'
......
......@@ -6,7 +6,7 @@
let all = {};
function getGroup(name){
function getGroup(name: string){
let group = all[name];
if(!group){
throw new Error('group ' + name + ' not registered.');
......@@ -15,11 +15,22 @@ function getGroup(name){
return group;
}
export function register(name, newFunc, initFunc){
/**
* 注册一个池
* @param name 池名称
* @param newFunc 实例化方法
* @param initFunc 初始化方法
*/
export function register(name: string, newFunc: Function, initFunc: Function){
all[name] = {name, newFunc, initFunc, pool: []};
}
export function get(name, ...params){
/**
* 获取一个对象
* @param name 池名称
* @param params 参数
*/
export function get(name: string, ...params){
let group = getGroup(name);
let {newFunc, initFunc, pool} = group;
......@@ -36,7 +47,12 @@ export function get(name, ...params){
return instance;
}
export function recycle(name, instance){
/**
* 回收一个对象
* @param name 池名称
* @param instance 实例
*/
export function recycle(name: string, instance){
let group = getGroup(name);
group.pool.push(instance);
......
/**
* Created by rockyl on 2018-12-07.
*/
import {dirtyFieldTrigger} from "../tools/decorators";
import HashObject from "../core/HashObject";
/**
* 尺寸
* 尺寸
*/
export default class Size {
export default class Size extends HashObject{
/**
* 宽度
*/
@dirtyFieldTrigger
width: number;
/**
* 高度
*/
@dirtyFieldTrigger
height: number;
onChange;
constructor(width = NaN, height = NaN) {
constructor(width: number = NaN, height: number = NaN, onChange?: Function) {
super();
this.onChange = onChange;
this.width = width;
this.height = height;
}
/**
* 置空
*/
setNaN(){
this.width = NaN;
this.height = NaN;
}
isEmpty(){
/**
* 是否空尺寸
*/
get isEmpty(){
return this.width === 0 && this.height === 0;
}
set(width?, height?) {
/**
* 设置宽高
* @param width
* @param height
*/
set(width?: number, height?: number) {
if (width !== undefined) {
this.width = width;
}
......@@ -38,10 +62,17 @@ export default class Size {
}
}
clone() {
/**
* 克隆一个尺寸实例
*/
clone(): Size {
return new Size(this.width, this.height);
}
/**
* 从一个尺寸实例拷贝
* @param target
*/
copyFrom(target) {
this.width = target.width;
this.height = target.height;
......
......@@ -17,26 +17,53 @@ enum STATUS {
DO_CALL,
}
/**
* 补间动画插件接口
*/
export interface ITweenPlugin {
/**
* 处理插值
* @param fromValue
* @param toValue
* @param ratio
* @param allowOutOfBounds
*/
resolveLerp(fromValue, toValue, ratio, allowOutOfBounds): any;
}
/**
* 补间动画参数
*/
export interface TweenOptions {
/**
* 循环次数 <0:无限循环,0:一次,>0:循环次数
* @default 0
*/
loop?: number;
/**
* 是否自动播放
* @default true
*/
autoPlay?: boolean;
/**
* 保存初始的字段名列表
*/
initFields?: string[];
/**
* 支持对象展开的字段名列表
*/
fields?: string[];
}
/**
* 补间动画
* @param host
* @param target
* @param override
* @param options
* @param plugins
* 创建补间动画
* @param host 拥有声明周期的组件
* @param target 目标对象
* @param override 是否覆盖该对象上的动画
* @param options 补间动画参数
* @param plugins 插件
*/
export function createTween(host: any, target: any, override = false, options?: TweenOptions, plugins = []) {
export function createTween(host: any, target: any, override = false, options?: TweenOptions, plugins: ITweenPlugin[] = []) {
if (override) {
killTweens(target);
}
......@@ -70,28 +97,31 @@ function addTween(target, tween: Tween) {
tweens.push(tween);
}
/**
* 补间动画类
*/
export class Tween extends HashObject {
host: any;
target: any;
loop: number;
queue = [];
protected host: any;
protected target: any;
protected loop: number;
protected queue = [];
loopCounting: number = 0;
step: number;
status: STATUS = STATUS.IDLE;
protected loopCounting: number = 0;
protected step: number;
protected status: STATUS = STATUS.IDLE;
t;
startTime;
protected t;
protected startTime;
plugins: ITweenPlugin[];
fields;
protected plugins: ITweenPlugin[];
protected fields;
autoPlay: boolean;
initProps: any;
fromProps: any;
toProps: any;
ease: Function;
duration: number;
protected autoPlay: boolean;
protected initProps: any;
protected fromProps: any;
protected toProps: any;
protected ease: Function;
protected duration: number;
constructor(host: any, target: any, options?: TweenOptions, plugins = []) {
super();
......@@ -109,7 +139,7 @@ export class Tween extends HashObject {
}
}
private resolveLerp(fromValue, toValue, ratio) {
protected resolveLerp(fromValue, toValue, ratio) {
let currentValue;
if (this.plugins.length > 0) {
for (let plugin of this.plugins) {
......@@ -127,7 +157,7 @@ export class Tween extends HashObject {
return currentValue;
}
onUpdate = (t) => {
protected onUpdate = (t) => {
this.t = t;
switch (this.status) {
case STATUS.DO_TO:
......@@ -172,7 +202,7 @@ export class Tween extends HashObject {
}
};
private getInitProps(fields) {
protected getInitProps(fields) {
const props = {};
for (let field of fields) {
if (field in this.target) {
......@@ -183,6 +213,10 @@ export class Tween extends HashObject {
return props;
}
/**
* 设置目标的属性
* @param props 属性对象
*/
set(props) {
this.queue.push({action: 'set', props});
if (this.autoPlay) {
......@@ -192,7 +226,13 @@ export class Tween extends HashObject {
return this;
}
to(props, duration?, ease?) {
/**
* 进行一次补间动画
* @param props 属性对象
* @param duration 耗时(ms)
* @param ease 缓动方法
*/
to(props, duration?:number, ease?: Function) {
this.queue.push({action: 'to', props, duration, ease});
if (this.autoPlay) {
this._start();
......@@ -201,6 +241,10 @@ export class Tween extends HashObject {
return this;
}
/**
* 等待一段时间
* @param duration 耗时(ms)
*/
wait(duration) {
this.queue.push({action: 'wait', duration});
if (this.autoPlay) {
......@@ -210,7 +254,13 @@ export class Tween extends HashObject {
return this;
}
call(func, thisObj?, params?) {
/**
* 执行一个方法
* @param func 方法体
* @param thisObj 闭包域
* @param params 参数
*/
call(func: Function, thisObj?, params?) {
this.queue.push({action: 'call', func, thisObj, params});
if (this.autoPlay) {
this._start();
......@@ -219,6 +269,12 @@ export class Tween extends HashObject {
return this;
}
/**
* 播放补间动画
* @param override 是否覆盖该对象上的动画
* @param delay 延迟执行时间(ms)
* @param resetLoopCounting 是否重置循环次数计数
*/
play(override = false, delay: number = 0, resetLoopCounting: boolean = true) {
if (override) {
killTweens(this.target);
......@@ -236,19 +292,22 @@ export class Tween extends HashObject {
this._start(resetLoopCounting);
};
/**
* 终止补间动画
*/
stop() {
this.status = STATUS.IDLE;
this.host.cancelOnNextTick(this.onUpdate);
}
_set(props) {
protected _set(props) {
this.status = STATUS.DO_SET;
injectProp(this.target, props);
this._doNextAction();
}
_to(props, duration, ease) {
protected _to(props, duration, ease) {
this.status = STATUS.DO_TO;
this.startTime = this.t;
......@@ -263,7 +322,7 @@ export class Tween extends HashObject {
//this.tween = annie.Tween.to(this.target, (duration / 1000) || 0, _props)
}
_wait(duration) {
protected _wait(duration) {
this.status = STATUS.DO_WAIT;
this.startTime = this.t;
......@@ -273,13 +332,13 @@ export class Tween extends HashObject {
}, duration)*/
}
_call(func, thisObj, params) {
protected _call(func, thisObj, params) {
this.status = STATUS.DO_CALL;
func.apply(thisObj, params);
this._doNextAction();
}
_start(resetLoopCounting: boolean = true) {
protected _start(resetLoopCounting: boolean = true) {
this.status = STATUS.PENDING;
if (resetLoopCounting) {
this.loopCounting = 0;
......@@ -289,12 +348,12 @@ export class Tween extends HashObject {
this.host.callOnNextTick(this.onUpdate, false);
}
_readyStart = (t) => {
protected _readyStart = (t) => {
this.t = t;
this._doStart();
};
_doStart() {
protected _doStart() {
if (this.status == STATUS.IDLE) {
return;
}
......@@ -307,7 +366,7 @@ export class Tween extends HashObject {
this._doNextAction();
};
_doNextAction = () => {
protected _doNextAction = () => {
if (this.step < this.queue.length) {
let action = this.queue[this.step++];
switch (action.action) {
......
......@@ -4,6 +4,7 @@
*/
import {get, recycle, register} from "./ObjectPool";
import HashObject from "../core/HashObject";
const name = 'Vector2D';
register(name, function () {
......@@ -32,16 +33,20 @@ export function releaseVector2D(target) {
/**
* 2D矢量
*/
export default class Vector2D {
_x: number;
_y: number;
onChange;
export default class Vector2D extends HashObject{
private _x: number;
private _y: number;
private onChange: Function;
/**
* 创建一个2D矢量
* @param x x分量
* @param y y分量
* @param onChange 当改变时触发
*/
constructor(x:number = 0, y:number = 0, onChange?: Function) {
super();
public static get zero(): Vector2D {
return zero;
}
constructor(x = 0, y = 0, onChange?) {
this.onChange = onChange;
this._x = 0;
......@@ -49,6 +54,9 @@ export default class Vector2D {
this.setXY(x, y);
}
/**
* x分量
*/
get x(): number {
return this._x;
}
......@@ -62,6 +70,9 @@ export default class Vector2D {
}
}
/**
* y分量
*/
get y(): number {
return this._y;
}
......@@ -75,23 +86,38 @@ export default class Vector2D {
}
}
setXY(x = 0, y = 0): Vector2D {
/**
* 设置分量
* @param x
* @param y
*/
setXY(x:number = 0, y:number = 0): Vector2D {
this.x = x;
this.y = y;
return this;
}
/**
* 从一个向量拷贝分量
* @param v2
*/
copyFrom(v2): Vector2D {
this.x = v2.x;
this.y = v2.y;
return this;
}
/**
* 克隆出一个向量
*/
clone(): Vector2D {
return new Vector2D(this.x, this.y);
}
/**
* 把向量置空
*/
zero(): Vector2D {
this.x = 0;
this.y = 0;
......@@ -99,10 +125,16 @@ export default class Vector2D {
return this;
}
/**
* 是不是一个0向量
*/
get isZero(): boolean {
return this.x == 0 && this.y == 0;
}
/**
* 单位化向量
*/
normalize(): Vector2D {
let len = this.length;
if (len == 0) {
......@@ -114,63 +146,109 @@ export default class Vector2D {
return this;
}
/**
* 是不是一个单位向量
*/
get isNormalized(): boolean {
return this.length == 1.0;
}
/**
* 截取向量长度
* @param max
*/
truncate(max): Vector2D {
this.length = Math.min(max, this.length);
return this;
}
/**
* 向量反向
*/
reverse(): Vector2D {
this.x = -this.x;
this.y = -this.y;
return this;
}
/**
* 获取点乘
* @param v2
*/
dotProd(v2): number {
return this.x * v2.x + this.y * v2.y;
}
/**
* 获取叉乘
* @param v2
*/
crossProd(v2): number {
return this.x * v2.y - this.y * v2.x;
}
/**
* 获取长度的平方
* @param v2
*/
distSQ(v2): number {
let dx = v2.x - this.x;
let dy = v2.y - this.y;
return dx * dx + dy * dy;
}
/**
* 获取两个向量的距离
* @param v2
*/
distance(v2): number {
return Math.sqrt(this.distSQ(v2));
}
/**
* 向量加法
* @param v2
*/
add(v2): Vector2D {
this.x += v2.x;
this.y += v2.y;
return this;
}
/**
* 向量减法
* @param v2
*/
subtract(v2): Vector2D {
this.x -= v2.x;
this.y -= v2.y;
return this;
}
multiply(value): Vector2D {
/**
* 向量乘于某个数
* @param value
*/
multiply(value:number): Vector2D {
this.x *= value;
this.y *= value;
return this;
}
divide(value): Vector2D {
/**
* 向量除于某个数
* @param value
*/
divide(value:number): Vector2D {
this.x /= value;
this.y /= value;
return this;
}
/**
* 向量角度
* @param value
*/
set angle(value: number) {
this.radian = value * Math.PI / 180;
}
......@@ -179,6 +257,10 @@ export default class Vector2D {
return this.radian * 180 / Math.PI;
}
/**
* 向量弧度
* @param value
*/
set radian(value: number) {
let len = this.length;
this.setXY(Math.cos(value) * len, Math.sin(value) * len);
......@@ -188,23 +270,37 @@ export default class Vector2D {
return Math.atan2(this.y, this.x);
}
/**
* 是否等于某个向量
* @param v2
*/
equals(v2): boolean {
return this.x == v2.x && this.y == v2.y;
}
/**
* 向量长度
* @param value
*/
get length(): number {
return Math.sqrt(this.lengthSQ);
}
set length(value: number) {
let a = this.radian;
this.setXY(Math.cos(a) * value, Math.sin(a) * value);
}
get length(): number {
return Math.sqrt(this.lengthSQ);
}
/**
* 获取向量长度的平方
*/
get lengthSQ(): number {
return this.x * this.x + this.y * this.y;
}
/**
* 获取向量斜率
*/
get slope(): number {
return this.y / this.x;
}
......@@ -217,5 +313,3 @@ export default class Vector2D {
return Math.acos(v1.dotProd(v2) / (v1.length * v2.length));
}
}
const zero = new Vector2D();
/**
* Created by rockyl on 2019-01-01.
*
* 时间相关常用方法
*/
import {format as stringFormat, supplement} from './utils'
......@@ -14,23 +16,50 @@ export function ts2Date(ts) {
return newDate;
}
/**
* 日期类转日期字符串
* @param date
* @param format
*/
export function dateToDateString(date:Date, format:string = '{0}/{1}/{2}'):string{
return stringFormat(format, date.getFullYear(), supplement(date.getMonth() + 1, 2), supplement(date.getDate(), 2))
}
/**
* 日期类转时间字符串
* @param date
* @param format
*/
export function dateToTimeString(date:Date, format:string = '{0}:{1}:{2}'):string{
return stringFormat(format, supplement(date.getHours(), 2), supplement(date.getMinutes(), 2), supplement(date.getSeconds(), 2))
}
/**
* 日期类转字符串
* @param date
* @param dayFormat
* @param timeFormat
*/
export function dateToString(date: Date, dayFormat, timeFormat){
return dateToDateString(date, dayFormat) + dateToTimeString(date, timeFormat);
}
/**
* 时间戳转时间字符串
* @param ts
* @param format
*/
export function tsToTimeString(ts, format:string = '{0}:{1}:{2}'):string{
let date = ts2Date(ts);
return stringFormat(format, supplement(date.getHours(), 2), supplement(date.getMinutes(), 2), supplement(date.getSeconds(), 2))
}
/**
* 秒转之间字符串
* @param second
* @param format
* @param placeZero
*/
export function secondFormat(second:number, format:string = '{2}:{1}:{0}', placeZero:boolean = true):string {
let ss:any = second % 60;
let mm:any = Math.floor(second / 60) % 60;
......
......@@ -4,12 +4,22 @@
* 常用工具
*/
/**
* 属性注入方法
* @param target 目标对象
* @param data 被注入对象
* @param callback 自定义注入方法
* @param ignoreMethod 是否忽略方法
* @param ignoreNull 是否忽略Null字段
*
* @return 是否有字段注入
*/
export function injectProp(target: any, data?: any, callback?: Function, ignoreMethod: boolean = true, ignoreNull: boolean = true): boolean {
if (!target || !data) {
return false;
}
let result = true;
let result = false;
for (let key in data) {
let value: any = data[key];
if ((!ignoreMethod || typeof value != 'function') && (!ignoreNull || value != null)) {
......@@ -18,11 +28,17 @@ export function injectProp(target: any, data?: any, callback?: Function, ignoreM
} else {
target[key] = value;
}
result = true;
}
}
return result;
}
/**
* 对象转搜索字符串
* @param obj
*/
export function objectStringify(obj) {
if (!obj) {
return '';
......@@ -34,16 +50,30 @@ export function objectStringify(obj) {
return arr.join('&');
}
/**
* 生成一个等待Promise
* @param duration
*/
export function waitPromise(duration): Promise<void> {
return new Promise(resolve => {
setTimeout(resolve, duration);
});
}
/**
* 可视化字符串
* @param formatStr
* @param params
*/
export function format(formatStr: string, ...params): string {
return formatApply(formatStr, params);
}
/**
* 格式化字符串(apply调用)
* @param formatStr
* @param params
*/
export function formatApply(formatStr: string, params: any[]): string {
let result: string = formatStr;
for (let i = 0, len = params.length; i < len; i++) {
......@@ -67,6 +97,11 @@ const zeros: Array<string> = [
"0000000000"
];
/**
* 补零
* @param value
* @param count
*/
export function supplement(value: number, count: number): string {
let index = count - value.toString().length - 1;
if (index < 0) {
......
......@@ -9,8 +9,7 @@
"lib": [
"es5",
"es6",
"dom",
"es2015.promise"
"dom"
]
},
"include": [
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment