skynovel
Version:
webgl novelgame framework
365 lines (328 loc) • 13.5 kB
text/typescript
/* ***** BEGIN LICENSE BLOCK *****
Copyright (c) 2018-2020 Famibee (famibee.blog38.fc2.com)
This software is released under the MIT License.
http://opensource.org/licenses/mit-license.php
** ***** END LICENSE BLOCK ***** */
import {CmnLib, IEvtMng} from './CmnLib';
import {ITwInf, CmnTween} from './CmnTween';
import {IHTag, IVariable, IMain, HArg} from './CmnInterface';
import {Application} from 'pixi.js';
import {SysBase} from './SysBase';
import {Config} from './Config';
const Tween = require('@tweenjs/tween.js').default;
import {Loader, LoaderResource} from 'pixi.js';
export class FrameMng {
constructor(private readonly cfg: Config, hTag: IHTag, private readonly appPixi: Application, private readonly val: IVariable, private readonly main: IMain, private readonly sys: SysBase, private readonly hTwInf: {[name: string]: ITwInf}) {
// HTMLフレーム
hTag.add_frame = o=> this.add_frame(o); // フレーム追加
hTag.let_frame = o=> this.let_frame(o); // フレーム変数を取得
hTag.set_frame = o=> this.set_frame(o); // フレーム変数に設定
hTag.frame = o=> this.frame(o); // フレームに設定
hTag.tsy_frame = o=> this.tsy_frame(o); // フレームをトゥイーン開始
}
private evtMng : IEvtMng;
setEvtMng(evtMng: IEvtMng) {this.evtMng = evtMng;}
private hIfrm : {[name: string]: HTMLIFrameElement} = Object.create(null);
destroy() {
for (const n in this.hIfrm) {
const f = this.hIfrm[n];
f.parentElement!.removeChild(f);
}
this.hIfrm = Object.create(null);
}
// HTMLフレーム
// フレーム追加
private add_frame(hArg: HArg) {
const id = hArg.id;
if (! id) throw 'idは必須です';
const src = hArg.src;
if (! src) throw 'srcは必須です';
const frmnm = `const.sn.frm.${id}`;
if (this.val.getVal(`tmp:${frmnm}`)) throw `frame【${id}】はすでにあります`;
const a = CmnLib.argChk_Num(hArg, 'alpha', 1);
const sx = CmnLib.argChk_Num(hArg, 'scale_x', 1);
const sy = CmnLib.argChk_Num(hArg, 'scale_y', 1);
const r = CmnLib.argChk_Num(hArg, 'rotate', 0);
const v = CmnLib.argChk_Boolean(hArg, 'visible', true);
const b_color = hArg.b_color ?` background-color: ${hArg.b_color};` :'';
const rct = this.rect(hArg);
const scale = this.sys.reso4frame *CmnLib.cvsScale;
this.appPixi.view.insertAdjacentHTML('beforebegin', `<iframe id="${id
}" sandbox="allow-scripts allow-same-origin" style="z-index: 1; opacity: ${a}; position: absolute; left:${
this.sys.ofsLeft4frm +rct.x *scale
}px; top: ${
this.sys.ofsTop4frm +rct.y *scale
}px; border: 0px; overflow: hidden; display: ${v ?'inline' :'none'
};${b_color}" width="${rct.width *scale}" height="${rct.height *scale
}" transform: scale(${sx}, ${sy}) rotate(${r}deg);></iframe>`);
const ifrm = document.getElementById(id) as HTMLIFrameElement;
this.hIfrm[id] = ifrm;
const win: Window = ifrm.contentWindow!;
ifrm.onload = ()=> {
// 組み込み変数
this.val.setVal_Nochk('tmp', frmnm, true);
this.val.setVal_Nochk('tmp', frmnm +'.alpha', a);
this.val.setVal_Nochk('tmp', frmnm +'.x', rct.x);
this.val.setVal_Nochk('tmp', frmnm +'.y', rct.y);
this.val.setVal_Nochk('tmp', frmnm +'.scale_x', sx);
this.val.setVal_Nochk('tmp', frmnm +'.scale_y', sy);
this.val.setVal_Nochk('tmp', frmnm +'.rotate', r);
this.val.setVal_Nochk('tmp', frmnm +'.width', rct.width);
this.val.setVal_Nochk('tmp', frmnm +'.height', rct.height);
this.val.setVal_Nochk('tmp', frmnm +'.visible', v);
this.evtMng.resvFlameEvent(win);
const setImg: Function = (win as any).sn_repRes;
if (this.sys.crypto && setImg) setImg((i: HTMLImageElement)=> {
const src = (i.dataset.src ?? '').replace(/(.+\/|\..+)/g, '');
const oUrl = this.hEncImgOUrl[src];
if (oUrl) {i.src = oUrl; return}
const aImg = this.hAEncImg[src];
if (aImg) {aImg.push(i); return}
this.hAEncImg[src] = [i];
const url = this.cfg.searchPath(src, Config.EXT_SPRITE);
(new Loader()).add(src, url, {xhrType: 'arraybuffer'})
.pre((res: LoaderResource, next: Function)=> res.load(()=> {
this.sys.pre(res.extension, res.data)
.then(r=> {
if (res.extension != 'bin') {next(); return;}
res.data = r;
if (res.data instanceof HTMLImageElement) {
res.type = LoaderResource.TYPE.IMAGE;
this.hEncImgOUrl[src] = res.data.src;
}
next();
})
.catch(e=> this.main.errScript(`Graphic ロード失敗です fn:${res.name} ${e}`, false));
}))
.load((_ldr: any, hRes: any)=> {
for (const src in hRes) {
const oUrl = hRes[src].data.src;
this.hAEncImg[src].map(v=> v.src = oUrl);
delete this.hAEncImg[src];
// URL.revokeObjectURL(oUrl); // 画面遷移で毎回再生成するので
}
});
});
this.main.resume();
};
const url = this.cfg.searchPath(src, Config.EXT_HTML);
if (! this.sys.crypto) {ifrm.src = url; return true}
(new Loader())
.add(src, url, {xhrType: LoaderResource.XHR_RESPONSE_TYPE.TEXT})
.pre((res: LoaderResource, next: Function)=> res.load(()=> {
this.sys.pre(res.extension, res.data)
.then(r=> {res.data = r; next();})
.catch(e=> this.main.errScript(`[add_frame]Html ロード失敗です src:${res.name} ${e}`, false));
}))
.load((_ldr, hRes)=> {
const base = url.slice(0, url.lastIndexOf('/') +1);
const htm = String(hRes[src]?.data).replace(
/\s(?:src|href)=(["'])(\S+)\1/g,
(v, p1, p2)=> (p2.slice(0, 3) == '../')
? v.replace('../', this.sys.cur)
: v.replace(p1, p1 + base)
);
const blob = new Blob([htm], {type: 'text/html'});
ifrm.src = URL.createObjectURL(blob);
});
return true;
}
private hAEncImg : {[name: string]: HTMLImageElement[]} = Object.create(null);
private hEncImgOUrl : {[name: string]: string} = Object.create(null);
private rect(hArg: HArg): DOMRect {
const a = {...hArg};
const re = this.sys.resolution;
return new DOMRect(
CmnLib.argChk_Num(a, 'x', 0) *re,
CmnLib.argChk_Num(a, 'y', 0) *re,
CmnLib.argChk_Num(a, 'width', CmnLib.stageW) *re,
CmnLib.argChk_Num(a, 'height', CmnLib.stageH) *re,
);
}
cvsResize() { // TODO: フォントサイズはどう変更すべきか
const scale = this.sys.reso4frame *CmnLib.cvsScale;
for (const n in this.hIfrm) {
const f = this.hIfrm[n];
const x = Number(this.val.getVal(`const.sn.frm.${n}.x`));
const y = Number(this.val.getVal(`const.sn.frm.${n}.y`));
const w = Number(this.val.getVal(`const.sn.frm.${n}.width`));
const h = Number(this.val.getVal(`const.sn.frm.${n}.height`));
f.style.left = this.sys.ofsLeft4frm +x *scale +'px';
f.style.top = this.sys.ofsTop4frm +y *scale +'px';
f.width = String(w *scale);
f.height = String(h *scale);
}
}
// フレーム変数を取得
private let_frame(hArg: HArg) {
const id = hArg.id;
if (! id) throw 'idは必須です';
const frmnm = `const.sn.frm.${id}`;
if (! this.val.getVal(`tmp:${frmnm}`)) throw `frame【${id}】が読み込まれていません`;
const var_name = hArg.var_name;
if (! var_name) throw 'var_nameは必須です';
const ifrm = document.getElementById(id) as HTMLIFrameElement;
const win: Window = ifrm.contentWindow!;
if (! win.hasOwnProperty(var_name)) throw `frame【${id}】に変数/関数【${var_name}】がありません。変数は var付きにして下さい`;
const v = (win as any)[var_name];
// var変数 / 関数実行の戻り値 -> 組み込み変数
this.val.setVal_Nochk(
'tmp',
frmnm +'.'+ var_name,
CmnLib.argChk_Boolean(hArg, 'function', false) ?v() :v
);
return false;
}
// フレーム変数に設定
private set_frame(hArg: HArg) {
const id = hArg.id;
if (! id) throw 'idは必須です';
const frmnm = `const.sn.frm.${id}`;
if (! this.val.getVal(`tmp:${frmnm}`)) throw `frame【${id}】が読み込まれていません`;
const var_name = hArg.var_name;
if (! var_name) throw 'var_nameは必須です';
const text = hArg.text;
if (! text) throw 'textは必須です';
// -> 組み込み変数
this.val.setVal_Nochk('tmp', frmnm +'.'+ var_name, text);
// -> var変数に設定
const ifrm = document.getElementById(id) as HTMLIFrameElement;
const win: any = ifrm.contentWindow!;
win[var_name] = text;
return false;
}
// フレームに設定
private frame(hArg: HArg) {
const id = hArg.id;
if (! id) throw 'idは必須です';
const frmnm = `const.sn.frm.${id}`;
if (! this.val.getVal(`tmp:${frmnm}`)) throw `frame【${id}】が読み込まれていません`;
const ifrm = document.getElementById(id) as HTMLIFrameElement;
if ('alpha' in hArg) {
const a = String(hArg.alpha);
ifrm.style.opacity = a;
this.val.setVal_Nochk('tmp', frmnm +'.alpha', a);
}
const rct = this.rect(hArg);
const scale = this.sys.reso4frame *CmnLib.cvsScale;
if ('x' in hArg || 'y' in hArg) {
ifrm.style.left = this.sys.ofsLeft4frm +rct.x *scale +'px';
ifrm.style.top = this.sys.ofsTop4frm +rct.y *scale +'px';
this.val.setVal_Nochk('tmp', frmnm +'.x', rct.x);
this.val.setVal_Nochk('tmp', frmnm +'.y', rct.y);
}
if ('scale_x' in hArg || 'scale_y' in hArg || 'rotate' in hArg) {
const sx = CmnLib.argChk_Num(hArg, 'scale_x', 1);
const sy = CmnLib.argChk_Num(hArg, 'scale_y', 1);
const r = CmnLib.argChk_Num(hArg, 'rotate', 0);
ifrm.style.transform = `scale(${sx}, ${sy}) rotate(${r}deg)`;
this.val.setVal_Nochk('tmp', frmnm +'.scale_x', sx);
this.val.setVal_Nochk('tmp', frmnm +'.scale_y', sy);
this.val.setVal_Nochk('tmp', frmnm +'.rotate', r);
}
if ('width' in hArg) {
ifrm.width = String(rct.width *scale);
this.val.setVal_Nochk('tmp', frmnm +'.width', rct.width);
}
if ('height' in hArg) {
ifrm.height = String(rct.height *scale);
this.val.setVal_Nochk('tmp', frmnm +'.height', rct.height);
}
if ('visible' in hArg) {
const v = CmnLib.argChk_Boolean(hArg, 'visible', true);
ifrm.style.display = v ?'inline' :'none';
this.val.setVal_Nochk('tmp', frmnm +'.visible', v);
}
if ('b_color' in hArg) {
ifrm.style.backgroundColor = hArg.b_color!;
}
return false;
}
// フレームをトゥイーン開始
private tsy_frame(hArg: HArg) {
const id = hArg.id;
if (! id) throw 'idは必須です';
const frmnm = `const.sn.frm.${id}`;
if (! this.val.getVal(`tmp:${frmnm}`, 0)) throw `frame【${id}】が読み込まれていません`;
const ease = CmnTween.ease(hArg.ease);
const ifrm = document.getElementById(id) as HTMLIFrameElement;
const hNow: any = {};
const hTo: any = {};
const repeat = CmnLib.argChk_Num(hArg, 'repeat', 1);
let fncA = ()=> {};
if ('alpha' in hArg) {
hNow.a = ifrm.style.opacity;
hTo.a = CmnLib.argChk_Num(hArg, 'alpha', 0);
fncA = ()=> {
ifrm.style.opacity = hNow.a;
this.val.setVal_Nochk('tmp', 'alpha', hNow.a);
}
}
let fncXYSR = ()=> {};
const rct = this.rect(hArg);
const scale = this.sys.reso4frame *CmnLib.cvsScale;
if ('x' in hArg || 'y' in hArg
|| 'scale_x' in hArg || 'scale_y' in hArg || 'rotate' in hArg) {
hNow.x = Number(this.val.getVal(`tmp:${frmnm}.x`));
hNow.y = Number(this.val.getVal(`tmp:${frmnm}.y`));
hNow.sx = Number(this.val.getVal(`tmp:${frmnm}.scale_x`));
hNow.sy = Number(this.val.getVal(`tmp:${frmnm}.scale_y`));
hNow.r = Number(this.val.getVal(`tmp:${frmnm}.rotate`));
hTo.x = rct.x;
hTo.y = rct.y;
hTo.sx = CmnLib.argChk_Num(hArg, 'scale_x', 1);
hTo.sy = CmnLib.argChk_Num(hArg, 'scale_y', 1);
hTo.r = CmnLib.argChk_Num(hArg, 'rotate', 0);
fncXYSR = ()=> {
ifrm.style.left = this.sys.ofsLeft4frm +hNow.x *scale +'px';
ifrm.style.top = this.sys.ofsTop4frm +hNow.y *scale +'px';
ifrm.style.transform = `scale(${hNow.sx}, ${hNow.sy}) rotate(${hNow.r}deg)`;
this.val.setVal_Nochk('tmp', frmnm +'.x', hNow.x);
this.val.setVal_Nochk('tmp', frmnm +'.y', hNow.y);
this.val.setVal_Nochk('tmp', frmnm +'.scale_x', hNow.sx);
this.val.setVal_Nochk('tmp', frmnm +'.scale_y', hNow.sy);
this.val.setVal_Nochk('tmp', frmnm +'.rotate', hNow.r);
};
}
let fncW = ()=> {};
if ('width' in hArg) {
hNow.w = this.val.getVal(`tmp:${frmnm}.width`);
hTo.w = rct.width;
fncW = ()=> {
ifrm.width = hNow.w *scale +'px';
this.val.setVal_Nochk('tmp', frmnm +'.width', hNow.w);
};
}
let fncH = ()=> {};
if ('height' in hArg) {
hNow.h = this.val.getVal(`tmp:${frmnm}.height`);
hTo.h = rct.height;
fncH = ()=> {
ifrm.height = hNow.h *scale +'px';
this.val.setVal_Nochk('tmp', frmnm +'.height', hNow.h);
};
}
this.appPixi.stage.interactive = false;
const tw_nm = `frm\n${hArg.id}`;
const tw = new Tween.Tween(hNow)
.to(hTo, CmnLib.argChk_Num(hArg, 'time', NaN)
* (Boolean(this.val.getVal('tmp:sn.skip.enabled')) ?0 :1))
.delay(CmnLib.argChk_Num(hArg, 'delay', 0))
.easing(ease)
.repeat(repeat == 0 ?Infinity :(repeat -1)) // 一度リピート→計二回なので
.yoyo(CmnLib.argChk_Boolean(hArg, 'yoyo', false))
.onUpdate(()=> {fncA(); fncXYSR(); fncW(); fncH();})
.onComplete(()=> {
this.appPixi.stage.interactive = true;
const twInf = this.hTwInf[tw_nm];
if (! twInf) return;
delete this.hTwInf[tw_nm];
this.evtMng.popLocalEvts(); // [wait_tsy]したのにキャンセルされなかった場合向け
if (twInf.resume) this.main.resume();
if (twInf.onComplete) twInf.onComplete();
})
.start();
this.hTwInf[tw_nm] = {tw: tw, resume: false, onComplete: ()=> {}}
return false;
}
}