import { DisplayAssetMetaData, GameConfig, GameFactory, GameObjectData, GameScene, GameSceneOpts, } from '@gedit/render-engine'; import { Action, Transition, TransitionType } from '@gedit/runtime-types'; import { codegen } from '../common/codegen'; import { Plugin, PluginPureHook } from '../plugin'; import { MAIN_SCRIPT_ID, ScriptReference } from '../script'; import { URI } from '@gedit/utils'; import { ContextStore } from '../common'; import { ResourceOuput } from './resource-plugin'; import { AssetsType } from '@gedit/assets-2d/lib/common/assets-schema'; import { AssetsMetaData } from '@gedit/assets-2d/lib/common/assets-service'; import { FontFamily } from '@gedit/assets-font'; import { AppConfigData } from '@gedit/app-config/lib/common'; import { OutputType } from './output-plugin'; import stringify from 'json-stringify-pretty-compact'; const symbolRegex = /^Symbol\(([a-zA-Z0-9\-\.]*)\)/; interface GameApi { factory: (engine: string) => GameFactory; RichcodeRunner: (factory: GameFactory) => any; } function transtionTransform(transitions: Transition[]): string { const codes: string[] = []; const toCode = (content: string, i: number) => `\n function _trans${i}_(ctx, api) {\n${content}\n }\n`; transitions.forEach((transtion, i) => { switch (transtion.type) { case TransitionType.gotoScene: codes.push( toCode(` return api.goto('${transtion.to}', { ctx });`, i) ); break; case TransitionType.dispatch: codes.push( toCode(` return engine.dispatch('${transtion.to}', ctx, api);`, i) ); break; } }); return codes.join('\n'); } export const RuntimeCompileOpts = Symbol('RuntimeOpts'); export interface RuntimeCompileOpts { root: URI; appConfig: AppConfigData; element?: string; toBrowserUrl?: ( meta: DisplayAssetMetaData, fileName?: string, info?: string ) => string; getMetaData?( uri: string, type: AssetsType ): Promise; title: string; getLibs: (nodes: GameObjectData[], config: GameConfig) => string[]; autoRun?: boolean | undefined; apiHost?: string; isBackend?: boolean; // 从后端调用 headerInserts?: string[]; bodyInserts?: string[]; babelTransform?: boolean; // 转换代码 vconsole?: boolean; type?: OutputType; renderLifeCycle?: { onInitBegin?: (ctx: RenderPrinter) => void; onInitEnd?: (ctx: RenderPrinter) => void; onGameScenesBegin?: (ctx: RenderPrinter) => void; onGameScenesEnd?: (ctx: RenderPrinter) => void; onGameEventsBegin?: (ctx: RenderPrinter) => void; onGameEventsEnd?: (ctx: RenderPrinter) => void; onGameStartBegin?: (ctx: RenderPrinter) => void; onGameStartEnd?: (ctx: RenderPrinter) => void; }; } export class RenderPrinter { readonly hook: PluginPureHook; readonly api: codegen.Printer; readonly factory: codegen.Printer; readonly scenePen: codegen.Printer; static engine = 'engine'; static runner = 'runner'; static jsApi = 'api'; private onPrint(str: string): void { this.hook.println(str); } constructor(hook: PluginPureHook) { this.hook = hook; this.api = new codegen.Printer(); this.factory = new codegen.Printer(); this.scenePen = new codegen.Printer(); } onGameInit(opts: RuntimeCompileOpts): void { if (opts.renderLifeCycle && opts.renderLifeCycle.onInitBegin) { opts.renderLifeCycle.onInitBegin(this); } const resources = this.hook.getValue(ContextStore.resource); const { api, hook, factory } = this; const { element, engine, config, type } = hook.ctx; // javascript // hook.print(`const ${RenderPrinter.jsApi} = `); // factory.ref(RenderPrinter.engine).JS_API(); // this.onPrint(api.flush()); const configData = { ...config, tags: config.tags.map((t: any) => ({ id: t.id, name: t.name })), collisionGroups: config.collisionGroups.map(t => ({ id: t.id, name: t.name, })), }; this.onPrint(`var configData = ${JSON.stringify(configData)};`); this.onPrint(`var resources = ${JSON.stringify(resources.resources)};`); // 提取所有数据; const script = hook.getScript(MAIN_SCRIPT_ID); const { imports } = script; const allActions = script.content.actions.actions; const componentData: { id: string; content: GameObjectData; opts: GameSceneOpts; }[] = []; const sceneData: { id: string; content: GameObjectData; opts: GameSceneOpts; }[] = []; imports.forEach(({ scriptId, defaultName }) => { const sceneScript = hook.getScript(scriptId); const sceneId = (symbolRegex.exec(scriptId.toString()) || [])[1] || ''; const { components } = sceneScript.content; if (components.length > 0) { const sceneOpts: GameSceneOpts = { name: sceneScript.name!, actions: allActions .filter(a => a.nodeId === sceneId) .map(a => ({ id: a.actionId, name: a.name, type: a.type })), }; if (defaultName && defaultName.match('component')) { components.forEach(component => { componentData.push({ id: component.id, content: component, opts: sceneOpts, }); }); } else { components.forEach(component => { sceneData.push({ id: component.id, content: component, opts: sceneOpts, }); }); } } }); this.onPrint(`var componentData = ${JSON.stringify(componentData)};`); this.onPrint(`var sceneData = ${JSON.stringify(sceneData)};`); if (type === OutputType.react) { this.onPrint('const { RENDER_ENGINE } = window;'); this.onPrint('const gameFunc = () => {'); } hook.print(`var ${RenderPrinter.engine} = `); api.ref('RENDER_ENGINE').factory(engine); this.onPrint(api.flush()); // 重发 log // hook.print(`if (window.RICHCODE_RUNNER) var ${RenderPrinter.runner} = `); // api.ref('window.RICHCODE_RUNNER', true).RichcodeRunner(codegen.identifier(RenderPrinter.engine)); this.onPrint(api.flush()); if (opts.apiHost && opts.apiHost) { this.onPrint( `engine.registerAPI(api => ({ ...api, request: (url, data, method) => api.request('${opts.apiHost}' + url, data, method)}))` ); } this.onPrint(` var world = engine.world("${element}", configData); engine.resources = resources; `); // factory.ref(RenderPrinter.engine).world(element, 'configData', resources.resources); factory.ref(RenderPrinter.engine).director(); this.onPrint(factory.flush()); if (opts.renderLifeCycle && opts.renderLifeCycle.onInitEnd) { opts.renderLifeCycle.onInitEnd(this); } } onGameScenes(opts: RuntimeCompileOpts): void { if (opts.renderLifeCycle && opts.renderLifeCycle.onGameScenesBegin) { opts.renderLifeCycle.onGameScenesBegin(this); } const { hook /* factory, scenePen */ } = this; const { type } = hook.ctx; const resources = hook.getValue(ContextStore.resource); const script = hook.getScript(MAIN_SCRIPT_ID); // const { imports } = script; const mainSceneId = script.content.start.sceneId; const loadingSceneId = script.content.loading.sceneId; // const loadingType = hook.ctx.config.loadingType || LoadingType.DEFAULT; /* const allActions = script.content.actions.actions; imports.forEach(({ scriptId, defaultName }) => { const sceneScript = hook.getScript(scriptId); const sceneId = (symbolRegex.exec(scriptId.toString()) || [])[1] || ''; const { components } = sceneScript.content; if (components.length > 0) { const sceneOpts: GameSceneOpts = { name: sceneScript.name!, actions: allActions .filter(a => a.nodeId === sceneId) .map(a => ({id: a.actionId, name: a.name, type: a.type})), }; // 组件模式 if (defaultName && defaultName.match('component')) { factory.ref(RenderPrinter.engine).component(sceneId, { register(register: any): string { components.forEach(component => { (scenePen.ref(register) as any).save!(component); }); return scenePen.flush(); } }, sceneOpts); } else { factory.ref(RenderPrinter.engine).scene(sceneId, { onStart(scene: GameScene<{}>): string { components.forEach(component => { // setTexture(component, getTexture(component, hook.ctx.config).replace(/^imageGroup/, 'image')); scenePen.ref(scene).createAuto!(component); }); // loading场景在开始后加载其它所有资源 if (loadingSceneId && sceneId === loadingSceneId) { scenePen.ref(scene).loadResources!([], true); // 开始时候要手动触发加载素材, 这里会异步触发 scenePen.ref(scene).startLoad(mainSceneId); } return scenePen.flush(); }, onPreload(scene: GameScene<{}>): string { // 如果没加载页,分页加载, 加载页的资源先加载 if (!loadingSceneId || sceneId === loadingSceneId) { scenePen.ref(scene).loadResources!(resources.sceneResourceIndexs[sceneId]); if (sceneId === loadingSceneId) { // 告知 scene 是否加载页 scenePen.ref(scene).setIsLoadScene?.(); } } // 第一个场景加载所有资源 // switch (loadingType) { // case GameConfig.LoadingType.DEFAULT: // if (sceneId === mainSceneId) scenePen.ref(scene).loadResources!(resources.sceneResourceIndexs[sceneId]); // break; // case GameConfig.LoadingType.FIRST_SCENE_LOAD_ALL: // if (sceneId === mainSceneId) scenePen.ref(scene).loadResources!([], true); // break; // case GameConfig.LoadingType.SCENE_EACH_LOAD: // const resourceIndexs = resources.sceneResourceIndexs[sceneId]; // if (resourceIndexs) { // scenePen.ref(scene).loadResources!(resourceIndexs); // } // break; // } return scenePen.flush(); }, }, sceneOpts); } } }); */ // this.onPrint(factory.flush()); this.onPrint(` function componentRegister(cData) { cData.forEach(component => { engine.component(component.id, { register(register) { register.save(component.content); } }, component.opts); }); }`); this.onPrint(`var mainSceneId = '${mainSceneId}'; var loadingSceneId = '${loadingSceneId}'; var sceneResourceIndexs = ${stringify(resources.sceneResourceIndexs)}; function sceneRegister(sData) { sData.forEach(function (s) { engine.scene(s.id, s.content, { onPreload: function (scene) { if (!loadingSceneId || s.id === loadingSceneId) { if (s.id === loadingSceneId) { scene.setIsLoadScene(); } scene.loadResources(sceneResourceIndexs[s.id]); } }, onStart: function (scene) { scene.createAuto(); if (loadingSceneId && s.id === loadingSceneId) { scene.loadResources([], true); scene.startLoad(mainSceneId); } }, }, s.opts); }) } `); if (type !== OutputType.react) { this.onPrint(` componentRegister(componentData); sceneRegister(sceneData); `); } if (opts.renderLifeCycle && opts.renderLifeCycle.onGameScenesEnd) { opts.renderLifeCycle.onGameScenesEnd(this); } } onGameEvents(opts: RuntimeCompileOpts): void { if (opts.renderLifeCycle && opts.renderLifeCycle.onGameEventsBegin) { opts.renderLifeCycle.onGameEventsBegin(this); } const { hook, factory } = this; const script = hook.getScript(MAIN_SCRIPT_ID); const { actions } = script.content; const transtions = actions.transitions; actions.actions.forEach(s => { let dep: ScriptReference | undefined; const codeId = Action.toCodeId(s); try { dep = hook.getScript(codeId); } catch (e) {} const code: string = dep && dep.code ? dep.code : ''; let transtionCode: string = ''; const transitionsInjected = transtions.filter(t => t.from === codeId); // 注入跳转逻辑, 这里采用promise串联执行 if (transitionsInjected && transitionsInjected.length > 0) { transtionCode = transtionTransform(transitionsInjected); } // javascript if (code || transtionCode) { let endCode = ''; if (code && !transtionCode) { endCode = code.match(/main\s*\(/) ? '\n return main;' : ''; } else if (!code && transtionCode) { endCode = `\n return [${transitionsInjected .map((c, i) => `_trans${i}_`) .join(', ')}]`; } else { endCode = `\n return [main, ${transitionsInjected .map((c, i) => `_trans${i}_`) .join(', ')}]`; } factory .ref(RenderPrinter.engine) .script( codeId, codegen.identifier( `() => {\n ${code}${transtionCode}${endCode} \n}` ) ); } }); this.onPrint(factory.flush()); if (opts.renderLifeCycle && opts.renderLifeCycle.onGameEventsEnd) { opts.renderLifeCycle.onGameEventsEnd(this); } } onGameStart(opts: RuntimeCompileOpts, fontData: FontFamily[]): void { if (opts.renderLifeCycle && opts.renderLifeCycle.onGameStartBegin) { opts.renderLifeCycle.onGameStartBegin(this); } const { hook, factory } = this; // const { element } = hook.ctx; const script = hook.getScript(MAIN_SCRIPT_ID); const startScene = script.content.loading.sceneId || script.content.start.sceneId; this.onPrint(factory.flush()); const { type } = hook.ctx; if (type === OutputType.html) { this.onPrint(`var start = '${startScene}';`); } this.onPrint(`function GEditGameStart(s = '${startScene}') {`); /* factory.ref(RenderPrinter.engine).start(startScene); this.onPrint(factory.flush()); this.onPrint('}'); */ if (opts.autoRun === undefined || opts.autoRun) { if (fontData && fontData.length) { this.onPrint(` engine.loadFontFamilies(${JSON.stringify(fontData)}).then(function (){ engine.start(s); });`); } else { this.onPrint('engine.start(s);'); } } this.onPrint('}'); if (opts.renderLifeCycle && opts.renderLifeCycle.onGameStartEnd) { opts.renderLifeCycle.onGameStartEnd(this); } if (type === OutputType.react) { this.onPrint(` return { GEditGameStart, world, engine, componentRegister, sceneRegister, sceneResourceIndexs } }; export { gameFunc, sceneData, componentData, configData, resources, }`); } else { this.onPrint('GEditGameStart(start);'); } } } // TODO: print code 还需要在灵活一点 export function renderPlugin( opts: RuntimeCompileOpts, fontData: FontFamily[] ): Plugin { return { onPrint(): void { const printer = new RenderPrinter(this); // 初始化游戏 printer.onGameInit(opts); // 渲染游戏场景 printer.onGameScenes(opts); // 编排游戏事件 printer.onGameEvents(opts); // 启动游戏 printer.onGameStart(opts, fontData); }, }; }