feat: 渲染系统接入vue渲染器

This commit is contained in:
unanmed 2024-11-27 11:02:11 +08:00
parent 4073db5b04
commit ac68d64f5f
30 changed files with 5699 additions and 3756 deletions

File diff suppressed because it is too large Load Diff

View File

@ -278,9 +278,9 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a = {
// 要存档的内容
var data = {
floorId: core.status.floorId,
hero: core.clone(core.status.hero, name => name !== 'chase'),
hero: core.status.hero,
hard: core.status.hard,
maps: core.clone(core.maps.saveMap()),
maps: core.maps.saveMap(),
route: core.encodeRoute(core.status.route, !fromAutosave),
values: values,
version: core.firstData.version,
@ -291,7 +291,7 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a = {
skill: HeroSkill.saveSkill()
};
return data;
return structuredClone(data);
},
loadData: function (data, callback) {
// 读档操作;从存储中读取了内容后的行为

View File

@ -134,6 +134,7 @@ export class MotaOffscreenCanvas2D extends EventEmitter<OffscreenCanvasEvent> {
this.canvas.remove();
this.ctx.reset();
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this._freezed = true;
MotaOffscreenCanvas2D.list.delete(this);
}

View File

@ -138,6 +138,10 @@ export class HeroKeyMover {
private onStepEnd = () => {
const con = this.controller;
if (!con) return;
if (core.status.lockControl) {
con.stop();
return;
}
if (!this.moving) {
con.stop();
return;

View File

@ -71,6 +71,7 @@ export class Container<E extends EContainerEvent = EContainerEvent>
removeChild(...child: RenderItem<any>[]): void {
let changed = false;
child.forEach(v => {
if (v.parent !== this) return;
const success = v.remove();
if (success) {
changed = true;

View File

@ -1,7 +1,7 @@
import EventEmitter from 'eventemitter3';
import { logger } from '../common/logger';
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
import { RenderItem, RenderItemPosition } from './item';
import { ERenderItemEvent, RenderItem, RenderItemPosition } from './item';
import { Transform } from './transform';
import { isWebGL2Supported } from '../fx/webgl';
@ -91,7 +91,11 @@ export type ProgramConstructor<T extends GL2Program> = new (
fs?: string
) => T;
export abstract class GL2 extends RenderItem {
export interface EGL2Event extends ERenderItemEvent {}
export abstract class GL2<E extends EGL2Event = EGL2Event> extends RenderItem<
EGL2Event | E
> {
/** 是否支持此组件 */
static readonly support: boolean = isWebGL2Supported();

View File

@ -1,81 +0,0 @@
import { FloorItemDetail } from '@/plugin/fx/itemDetail';
import { FloorDamageExtends } from './preset/damage';
import { LayerDoorAnimate } from './preset/floor';
import { HeroRenderer } from './preset/hero';
import { LayerGroup, FloorLayer } from './preset/layer';
import { MotaRenderer } from './render';
import { LayerShadowExtends } from '../fx/shadow';
import { LayerGroupFilter } from '@/plugin/fx/gameCanvas';
import { LayerGroupAnimate } from './preset/animate';
import { LayerGroupPortal } from '@/plugin/fx/portal';
import { LayerGroupHalo } from '@/plugin/fx/halo';
import { FloorViewport } from './preset/viewport';
import { Container } from './container';
import { PopText } from '@/plugin/fx/pop';
import { FloorChange } from '@/plugin/fallback';
import tips from '@/data/tips.json';
let main: MotaRenderer;
Mota.require('var', 'loading').once('coreInit', () => {
const render = new MotaRenderer();
main = render;
render.hide();
const mapDraw = new Container();
const layer = new LayerGroup();
const pop = new PopText('static');
const floorChange = new FloorChange('static');
mapDraw.id = 'map-draw';
layer.id = 'layer-main';
pop.id = 'pop-main';
floorChange.id = 'floor-change';
mapDraw.setHD(true);
mapDraw.setAntiAliasing(false);
mapDraw.size(core._PX_, core._PY_);
floorChange.size(480, 480);
floorChange.setHD(true);
floorChange.setZIndex(50);
floorChange.setTips(tips);
pop.setZIndex(80);
['bg', 'bg2', 'event', 'fg', 'fg2'].forEach(v => {
layer.addLayer(v as FloorLayer);
});
const damage = new FloorDamageExtends();
const hero = new HeroRenderer();
const detail = new FloorItemDetail();
const door = new LayerDoorAnimate();
const shadow = new LayerShadowExtends();
const filter = new LayerGroupFilter();
const animate = new LayerGroupAnimate();
const portal = new LayerGroupPortal();
const halo = new LayerGroupHalo();
const viewport = new FloorViewport();
layer.extends(damage);
layer.extends(detail);
layer.extends(filter);
layer.extends(portal);
layer.extends(halo);
layer.getLayer('event')?.extends(hero);
layer.getLayer('event')?.extends(door);
layer.getLayer('event')?.extends(shadow);
layer.extends(animate);
layer.extends(viewport);
render.appendChild(mapDraw);
mapDraw.appendChild(layer);
layer.appendChild(pop);
mapDraw.appendChild(floorChange);
console.log(render);
});
Mota.require('var', 'hook').on('reset', () => {
main.show();
});
Mota.require('var', 'hook').on('restart', () => {
main.hide();
});

145
src/core/render/index.tsx Normal file
View File

@ -0,0 +1,145 @@
import { FloorItemDetail } from '@/plugin/fx/itemDetail';
import { FloorDamageExtends } from './preset/damage';
import { LayerDoorAnimate } from './preset/floor';
import { HeroRenderer } from './preset/hero';
import { LayerGroup, FloorLayer } from './preset/layer';
import { MotaRenderer } from './render';
import { LayerShadowExtends } from '../fx/shadow';
import { LayerGroupFilter } from '@/plugin/fx/gameCanvas';
import { LayerGroupAnimate } from './preset/animate';
import { LayerGroupPortal } from '@/plugin/fx/portal';
import { LayerGroupHalo } from '@/plugin/fx/halo';
import { FloorViewport } from './preset/viewport';
import { Container } from './container';
import { PopText } from '@/plugin/fx/pop';
import { FloorChange } from '@/plugin/fallback';
import { onTick, render } from './renderer';
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
import { SpriteComponent } from './renderer/elements';
import { defineComponent, onActivated, onMounted, ref } from 'vue';
import { Ticker } from 'mutate-animate';
import { Sprite } from './sprite';
let main: MotaRenderer;
Mota.require('var', 'loading').once('coreInit', () => {
main = new MotaRenderer();
const Com = defineComponent(props => {
const group = ref<LayerGroup>();
return () => (
<container
id="map-draw"
hd
antiAliasing={false}
width={core._PX_}
height={core._PY_}
>
<layer-group
id="layer-main"
ref={group}
ex={[
new FloorDamageExtends(),
new FloorItemDetail(),
new LayerGroupFilter(),
new LayerGroupPortal(),
new LayerGroupHalo(),
new LayerGroupAnimate(),
new FloorViewport()
]}
>
<layer layer="bg" zIndex={10}></layer>
<layer layer="bg2" zIndex={20}></layer>
<layer
layer="event"
zIndex={30}
ex={[
new HeroRenderer(),
new LayerDoorAnimate(),
new LayerShadowExtends()
]}
></layer>
<layer layer="fg" zIndex={40}></layer>
<layer layer="fg2" zIndex={50}></layer>
<PopText id="pop-main" zIndex={80}></PopText>
</layer-group>
<FloorChange id="floor-change" zIndex={50}></FloorChange>
</container>
);
});
main.hide();
render(<Com></Com>, main);
// const mapDraw = new Container();
// const layer = new LayerGroup();
// const pop = new PopText('static');
// const floorChange = new FloorChange('static');
// mapDraw.id = 'map-draw';
// layer.id = 'layer-main';
// pop.id = 'pop-main';
// floorChange.id = 'floor-change';
// mapDraw.setHD(true);
// mapDraw.setAntiAliasing(false);
// mapDraw.size(core._PX_, core._PY_);
// floorChange.size(480, 480);
// floorChange.setHD(true);
// floorChange.setZIndex(50);
// floorChange.setTips(tips);
// pop.setZIndex(80);
// ['bg', 'bg2', 'event', 'fg', 'fg2'].forEach(v => {
// layer.addLayer(v as FloorLayer);
// });
// const damage = new FloorDamageExtends();
// const hero = new HeroRenderer();
// const detail = new FloorItemDetail();
// const door = new LayerDoorAnimate();
// const shadow = new LayerShadowExtends();
// const filter = new LayerGroupFilter();
// const animate = new LayerGroupAnimate();
// const portal = new LayerGroupPortal();
// const halo = new LayerGroupHalo();
// const viewport = new FloorViewport();
// layer.extends(damage);
// layer.extends(detail);
// layer.extends(filter);
// layer.extends(portal);
// layer.extends(halo);
// layer.getLayer('event')?.extends(hero);
// layer.getLayer('event')?.extends(door);
// layer.getLayer('event')?.extends(shadow);
// layer.extends(animate);
// layer.extends(viewport);
// main.appendChild(mapDraw);
// mapDraw.appendChild(layer);
// layer.appendChild(pop);
// mapDraw.appendChild(floorChange);
console.log(main);
});
Mota.require('var', 'hook').on('reset', () => {
main.show();
});
Mota.require('var', 'hook').on('restart', () => {
main.hide();
});
export * from './preset';
export * from './renderer';
export * from './adapter';
export * from './cache';
export * from './camera';
export * from './container';
export * from './gl2';
export * from './item';
export * from './render';
export * from './shader';
export * from './sprite';
export * from './transform';
export * from './utils';

View File

@ -4,6 +4,7 @@ import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
import { Ticker, TickerFn } from 'mutate-animate';
import { Transform } from './transform';
import { logger } from '../common/logger';
import { ElementNamespace, ComponentInternalInstance } from 'vue';
export type RenderFunction = (
canvas: MotaOffscreenCanvas2D,
@ -117,6 +118,24 @@ interface IRenderTickerSupport {
hasTicker(id: number): boolean;
}
interface IRenderVueSupport {
/**
* jsx, vue
* @param key
* @param prevValue
* @param nextValue
* @param namespace
* @param parentComponent
*/
patchProp(
key: string,
prevValue: any,
nextValue: any,
namespace?: ElementNamespace,
parentComponent?: ComponentInternalInstance | null
): void;
}
export interface ERenderItemEvent {
beforeUpdate: [item?: RenderItem];
afterUpdate: [item?: RenderItem];
@ -144,7 +163,9 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
IRenderAnchor,
IRenderConfig,
IRenderFrame,
IRenderTickerSupport
IRenderTickerSupport,
IRenderChildable,
IRenderVueSupport
{
/** 渲染的全局ticker */
static ticker: Ticker = new Ticker();
@ -194,16 +215,34 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
hidden: boolean = false;
/** 滤镜 */
filter: string = 'none';
/** 混合方式 */
composite: GlobalCompositeOperation = 'source-over';
/** 不透明度 */
alpha: number = 1;
private _parent?: RenderItem;
/** 当前元素的父元素 */
parent?: RenderItem & IRenderChildable;
get parent() {
return this._parent;
}
/** 当前元素是否为根元素 */
readonly isRoot: boolean = false;
protected needUpdate: boolean = false;
/** 该渲染元素的模型变换矩阵 */
transform: Transform = new Transform();
private _transform: Transform = new Transform();
/** 设置该渲染元素的模型变换矩阵 */
set transform(value: Transform) {
this._transform = value;
this.update();
}
/** 获取该渲染元素的模型变换矩阵 */
get transform() {
this.update();
return this._transform;
}
/** 该渲染元素的子元素 */
children: Set<RenderItem<ERenderItemEvent>> = new Set();
/** 渲染缓存信息 */
protected cache: MotaOffscreenCanvas2D = new MotaOffscreenCanvas2D();
@ -278,10 +317,13 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
const ax = -this.anchorX * this.width;
const ay = -this.anchorY * this.height;
canvas.ctx.save();
const ctx = canvas.ctx;
ctx.save();
canvas.setAntiAliasing(this.antiAliasing);
if (this.enableCache) canvas.ctx.filter = this.filter;
if (this.type === 'static') transformCanvas(canvas, tran);
ctx.globalAlpha = this.alpha;
ctx.globalCompositeOperation = this.composite;
if (this.enableCache) {
const { width, height, ctx } = this.cache;
if (this.cacheDirty) {
@ -319,6 +361,24 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
this.update(this);
}
/**
*
* @param composite
*/
setComposite(composite: GlobalCompositeOperation) {
this.composite = composite;
this.update();
}
/**
*
* @param alpha
*/
setAlpha(alpha: number) {
this.alpha = alpha;
this.update();
}
/**
* 使
*/
@ -426,10 +486,10 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
*
* @param parent
*/
append(parent: IRenderChildable & RenderItem) {
append(parent: RenderItem) {
this.remove();
parent.children.add(this);
this.parent = parent;
this._parent = parent;
parent.requestSort();
this.needUpdate = false;
this.update();
@ -447,7 +507,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
if (!this.parent) return false;
const parent = this.parent;
const success = parent.children.delete(this);
this.parent = void 0;
this._parent = void 0;
parent.requestSort();
parent.update();
if (!success) return false;
@ -455,6 +515,179 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
return true;
}
/**
* RenderItem继承类中复写它使
* @param child
*/
appendChild(...child: RenderItem<any>[]): void {
logger.warn(35);
}
/**
* RenderItem继承类中复写它使
* @param child
*/
removeChild(...child: RenderItem<any>[]): void {
logger.warn(36);
}
/**
* RenderItem继承类中复写它使
*/
requestSort(): void {
logger.warn(37);
}
/**
* prop是否是期望类型
* @param value
* @param expected
* @param key
*/
protected assertType(
value: any,
expected: string | (new () => any),
key: string
) {
if (typeof expected === 'string') {
const type = typeof value;
if (type !== expected) {
logger.warn(21, key, expected, type);
return false;
} else {
return true;
}
} else {
if (value instanceof expected) {
return true;
} else {
logger.warn(
21,
key,
expected.name,
value?.constructor?.name ?? typeof value
);
return false;
}
}
}
/**
* key
* @param key
* @returns
*/
protected parseEvent(key: string): string | false {
if (key.startsWith('on')) {
const code = key.charCodeAt(2);
if (code >= 65 && code <= 90) {
return key[2].toLowerCase() + key.slice(3);
}
}
return false;
}
patchProp(
key: string,
prevValue: any,
nextValue: any,
namespace?: ElementNamespace,
parentComponent?: ComponentInternalInstance | null
): void {
switch (key) {
case 'x': {
if (!this.assertType(nextValue, 'number', key)) return;
this.pos(nextValue, this.transform.y);
return;
}
case 'y': {
if (!this.assertType(nextValue, 'number', key)) return;
this.pos(this.transform.x, nextValue);
return;
}
case 'anchorX': {
if (!this.assertType(nextValue, 'number', key)) return;
this.setAnchor(nextValue, this.anchorY);
return;
}
case 'anchorY': {
if (!this.assertType(nextValue, 'number', key)) return;
this.setAnchor(this.anchorX, nextValue);
return;
}
case 'zIndex': {
if (!this.assertType(nextValue, 'number', key)) return;
this.setZIndex(nextValue);
return;
}
case 'width': {
if (!this.assertType(nextValue, 'number', key)) return;
this.size(nextValue, this.height);
return;
}
case 'height': {
if (!this.assertType(nextValue, 'number', key)) return;
this.size(this.width, nextValue);
return;
}
case 'filter': {
if (!this.assertType(nextValue, 'string', key)) return;
this.setFilter(this.filter);
return;
}
case 'hd': {
if (!this.assertType(nextValue, 'boolean', key)) return;
this.setHD(nextValue);
return;
}
case 'antiAliasing': {
if (!this.assertType(nextValue, 'boolean', key)) return;
this.setAntiAliasing(nextValue);
return;
}
case 'hidden': {
if (!this.assertType(nextValue, 'boolean', key)) return;
if (nextValue) this.hide();
else this.show();
return;
}
case 'transform': {
if (!this.assertType(nextValue, Transform, key)) return;
this.transform = nextValue;
this.update();
return;
}
case 'type': {
if (!this.assertType(nextValue, 'string', key)) return;
this.type = nextValue;
this.update();
return;
}
case 'id': {
if (!this.assertType(nextValue, 'string', key)) return;
this.id = nextValue;
return;
}
case 'alpha': {
if (!this.assertType(nextValue, 'number', key)) return;
this.setAlpha(nextValue);
return;
}
case 'composite': {
if (!this.assertType(nextValue, 'string', key)) return;
this.setComposite(nextValue);
return;
}
}
const ev = this.parseEvent(key);
if (ev) {
if (prevValue) {
this.off(ev as keyof ERenderItemEvent, prevValue);
}
this.on(ev as keyof ERenderItemEvent, nextValue);
}
}
/**
* 使
*/

View File

@ -4,14 +4,15 @@ import { Sprite } from '../sprite';
import { HeroRenderer } from './hero';
import { ILayerGroupRenderExtends, LayerGroup } from './layer';
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
import { transformCanvas } from '../item';
import { ERenderItemEvent, RenderItem, transformCanvas } from '../item';
import { Transform } from '../transform';
export class LayerGroupAnimate implements ILayerGroupRenderExtends {
static animateList: Set<LayerGroupAnimate> = new Set();
id: string = 'animate';
group!: LayerGroup;
hero!: HeroRenderer;
hero?: HeroRenderer;
animate!: Animate;
private animation: Set<AnimateData> = new Set();
@ -28,7 +29,8 @@ export class LayerGroupAnimate implements ILayerGroupRenderExtends {
}
private updatePosition(animate: AnimateData) {
if (!this.hero.renderable) return;
if (!this.checkHero()) return;
if (!this.hero?.renderable) return;
const { x, y } = this.hero.renderable;
const cell = this.group.cellSize;
const half = cell / 2;
@ -48,14 +50,23 @@ export class LayerGroupAnimate implements ILayerGroupRenderExtends {
};
private listen() {
this.hero.on('moveTick', this.onMoveTick);
if (this.checkHero()) {
this.hero!.on('moveTick', this.onMoveTick);
}
}
private checkHero() {
if (this.hero) return true;
const ex = this.group.getLayer('event')?.getExtends('floor-hero');
if (ex instanceof HeroRenderer) {
this.hero = ex;
return true;
}
return false;
}
awake(group: LayerGroup): void {
this.group = group;
const ex = group.getLayer('event')?.getExtends('floor-hero');
if (ex instanceof HeroRenderer) {
this.hero = ex;
this.animate = new Animate();
this.animate.size(group.width, group.height);
this.animate.setHD(true);
@ -63,16 +74,14 @@ export class LayerGroupAnimate implements ILayerGroupRenderExtends {
group.appendChild(this.animate);
LayerGroupAnimate.animateList.add(this);
this.listen();
} else {
logger.error(14);
group.removeExtends('animate');
}
}
onDestroy(group: LayerGroup): void {
this.hero.off('moveTick', this.onMoveTick);
if (this.checkHero()) {
this.hero!.off('moveTick', this.onMoveTick);
LayerGroupAnimate.animateList.delete(this);
}
}
}
interface AnimateData {
@ -89,7 +98,9 @@ interface AnimateData {
readonly absolute: boolean;
}
export class Animate extends Sprite {
export interface EAnimateEvent extends ERenderItemEvent {}
export class Animate extends RenderItem<EAnimateEvent> {
/** 绝对位置的动画 */
private absoluteAnimates: Set<AnimateData> = new Set();
/** 静态位置的动画 */
@ -102,18 +113,6 @@ export class Animate extends Sprite {
constructor() {
super('absolute', false, true);
this.setRenderFn((canvas, transform) => {
if (
this.absoluteAnimates.size === 0 &&
this.staticAnimates.size === 0
) {
return;
}
this.drawAnimates(this.absoluteAnimates, canvas);
transformCanvas(canvas, transform);
this.drawAnimates(this.staticAnimates, canvas);
});
this.delegation = this.delegateTicker(time => {
if (time - this.lastTime < 50) return;
this.lastTime = time;
@ -129,6 +128,21 @@ export class Animate extends Sprite {
adapter.add(this);
}
protected render(
canvas: MotaOffscreenCanvas2D,
transform: Transform
): void {
if (
this.absoluteAnimates.size === 0 &&
this.staticAnimates.size === 0
) {
return;
}
this.drawAnimates(this.absoluteAnimates, canvas);
transformCanvas(canvas, transform);
this.drawAnimates(this.staticAnimates, canvas);
}
private drawAnimates(
data: Set<AnimateData>,
canvas: MotaOffscreenCanvas2D

View File

@ -21,7 +21,7 @@ import type {
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
import { isNil } from 'lodash-es';
import { getDamageColor } from '@/plugin/utils';
import { RenderItem, transformCanvas } from '../item';
import { ERenderItemEvent, RenderItem, transformCanvas } from '../item';
import EventEmitter from 'eventemitter3';
import { Transform } from '../transform';
@ -123,7 +123,7 @@ export interface DamageRenderable {
strokeWidth?: number;
}
interface EDamageEvent extends ESpriteEvent {
export interface EDamageEvent extends ERenderItemEvent {
setMapSize: [width: number, height: number];
beforeDamageRender: [need: Set<number>, transform: Transform];
updateBlocks: [blocks: Set<number>];

View File

@ -0,0 +1,82 @@
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
import { RenderItem } from '../item';
import { Transform } from '../transform';
type DrawType = 'fill' | 'stroke';
interface IGraphicProperty {
/** 渲染方式,是描边还是填充 */
mode: DrawType;
}
export class Graphics extends RenderItem {
protected render(
canvas: MotaOffscreenCanvas2D,
transform: Transform
): void {}
}
export class Rect extends RenderItem implements IGraphicProperty {
mode: DrawType = 'fill';
rectX: number = 0;
rectY: number = 0;
rectWidth: number = 100;
rectHeight: number = 100;
protected render(
canvas: MotaOffscreenCanvas2D,
transform: Transform
): void {
const ctx = canvas.ctx;
ctx.rect(this.rectX, this.rectY, this.rectWidth, this.rectHeight);
}
setPos(x: number, y: number) {
this.rectX = x;
this.rectY = y;
this.update();
}
setSize(w: number, h: number) {
this.rectWidth = w;
this.rectHeight = h;
this.update();
}
}
export class Circle extends RenderItem implements IGraphicProperty {
mode: DrawType = 'fill';
protected render(
canvas: MotaOffscreenCanvas2D,
transform: Transform
): void {}
}
export class Ellipse extends RenderItem implements IGraphicProperty {
mode: DrawType = 'fill';
protected render(
canvas: MotaOffscreenCanvas2D,
transform: Transform
): void {}
}
export class Line extends RenderItem implements IGraphicProperty {
mode: DrawType = 'fill';
protected render(
canvas: MotaOffscreenCanvas2D,
transform: Transform
): void {}
}
export class Path extends RenderItem implements IGraphicProperty {
mode: DrawType = 'fill';
protected render(
canvas: MotaOffscreenCanvas2D,
transform: Transform
): void {}
}

View File

@ -0,0 +1,8 @@
export * from './animate';
export * from './block';
export * from './damage';
export * from './floor';
export * from './hero';
export * from './layer';
export * from './misc';
export * from './viewport';

View File

@ -1,8 +1,13 @@
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
import { Container } from '../container';
import { Container, EContainerEvent } from '../container';
import { Sprite } from '../sprite';
import { TimingFn } from 'mutate-animate';
import { IAnimateFrame, renderEmits, RenderItem } from '../item';
import {
ERenderItemEvent,
IAnimateFrame,
renderEmits,
RenderItem
} from '../item';
import { logger } from '@/core/common/logger';
import { RenderableData, texture } from '../cache';
import {
@ -14,6 +19,7 @@ import {
import { Transform } from '../transform';
import { LayerFloorBinder, LayerGroupFloorBinder } from './floor';
import { RenderAdapter } from '../adapter';
import { ElementNamespace, ComponentInternalInstance } from 'vue';
export interface ILayerGroupRenderExtends {
/** 拓展的唯一标识符 */
@ -95,7 +101,12 @@ const layerZIndex: Record<FloorLayer, number> = {
fg2: 50
};
export class LayerGroup extends Container implements IAnimateFrame {
export interface ELayerGroupEvent extends EContainerEvent {}
export class LayerGroup
extends Container<ELayerGroupEvent>
implements IAnimateFrame
{
/** 地图组列表 */
// static list: Set<LayerGroup> = new Set();
@ -115,7 +126,7 @@ export class LayerGroup extends Container implements IAnimateFrame {
camera: Transform = new Transform();
private needRender?: Set<number>;
private extend: Map<string, ILayerGroupRenderExtends> = new Map();
readonly extend: Map<string, ILayerGroupRenderExtends> = new Map();
constructor() {
super('static', true);
@ -212,7 +223,8 @@ export class LayerGroup extends Container implements IAnimateFrame {
*
* @param layer
*/
addLayer(layer: FloorLayer) {
addLayer(layer: FloorLayer | Layer) {
if (typeof layer === 'string') {
const l = new Layer();
l.layer = layer;
this.layers.set(layer, l);
@ -224,6 +236,15 @@ export class LayerGroup extends Container implements IAnimateFrame {
}
return l;
} else {
if (layer.layer) {
this.layers.set(layer.layer, layer);
for (const ex of this.extend.values()) {
ex.onLayerAdd?.(this, layer);
}
}
return layer;
}
}
/**
@ -516,7 +537,9 @@ export interface LayerMovingRenderable extends RenderableData {
alpha: number;
}
export class Layer extends Container {
export interface ELayerEvent extends EContainerEvent {}
export class Layer extends Container<ELayerEvent> {
// 一些会用到的常量
static readonly FRAME_0 = 1;
static readonly FRAME_1 = 2;
@ -1384,6 +1407,55 @@ export class Layer extends Container {
});
}
patchProp(
key: string,
prevValue: any,
nextValue: any,
namespace?: ElementNamespace,
parentComponent?: ComponentInternalInstance | null
): void {
switch (key) {
case 'layer':
if (!this.assertType(nextValue, 'string', key)) return;
const parent = this.parent;
if (parent instanceof LayerGroup) {
parent.removeLayer(this);
this.layer = nextValue;
parent.addLayer(this);
} else {
this.layer = nextValue;
}
this.update();
return;
}
}
private addToGroup(group: LayerGroup) {
if (this.layer) {
group.addLayer(this);
}
}
private removeFromGroup(group: LayerGroup) {
if (this.layer) {
group.removeLayer(this);
}
}
append(parent: RenderItem): void {
super.append(parent);
if (parent instanceof LayerGroup) {
this.addToGroup(parent);
}
}
remove(): boolean {
if (this.parent instanceof LayerGroup) {
this.removeFromGroup(this.parent);
}
return super.remove();
}
destroy(): void {
for (const ex of this.extend.values()) {
ex.onDestroy?.(this);

View File

@ -1,9 +1,14 @@
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
import { Sprite } from '../sprite';
import { ERenderItemEvent, RenderItem, RenderItemPosition } from '../item';
import { Transform } from '../transform';
import { ElementNamespace, ComponentInternalInstance } from 'vue';
type CanvasStyle = string | CanvasGradient | CanvasPattern;
export class Text extends Sprite {
export interface ETextEvent extends ERenderItemEvent {}
export class Text extends RenderItem<ETextEvent> {
text: string;
fillStyle?: CanvasStyle = '#fff';
@ -16,13 +21,18 @@ export class Text extends Sprite {
private static measureCanvas = new MotaOffscreenCanvas2D();
constructor(text: string = '') {
super('static', false);
constructor(text: string = '', type: RenderItemPosition = 'static') {
super(type, false);
this.text = text;
if (text.length > 0) this.calBox();
}
this.renderFn = ({ canvas, ctx }) => {
protected render(
canvas: MotaOffscreenCanvas2D,
transform: Transform
): void {
const ctx = canvas.ctx;
ctx.textBaseline = 'bottom';
ctx.fillStyle = this.fillStyle ?? 'transparent';
ctx.strokeStyle = this.strokeStyle ?? 'transparent';
@ -35,7 +45,6 @@ export class Text extends Sprite {
if (this.fillStyle) {
ctx.fillText(this.text, 0, this.descent);
}
};
}
/**
@ -96,6 +105,22 @@ export class Text extends Sprite {
this.descent = fontBoundingBoxAscent;
this.size(width, fontBoundingBoxAscent);
}
patchProp(
key: string,
prevValue: any,
nextValue: any,
namespace?: ElementNamespace,
parentComponent?: ComponentInternalInstance | null
): void {
switch (key) {
case 'font':
if (!this.assertType(nextValue, 'string', key)) return;
this.setFont(nextValue);
break;
}
super.patchProp(key, prevValue, nextValue, namespace, parentComponent);
}
}
export type SizedCanvasImageSource = Exclude<
@ -103,26 +128,47 @@ export type SizedCanvasImageSource = Exclude<
VideoFrame | SVGElement
>;
export class Image extends Sprite {
image: SizedCanvasImageSource;
export interface EImageEvent extends ERenderItemEvent {}
constructor(image: SizedCanvasImageSource) {
super();
export class Image extends RenderItem<EImageEvent> {
image: CanvasImageSource;
constructor(image: CanvasImageSource, type: RenderItemPosition = 'static') {
super(type);
this.image = image;
if (image instanceof VideoFrame || image instanceof SVGElement) {
this.size(200, 200);
} else {
this.size(image.width, image.height);
}
}
this.renderFn = ({ canvas, ctx }) => {
protected render(
canvas: MotaOffscreenCanvas2D,
transform: Transform
): void {
const ctx = canvas.ctx;
ctx.drawImage(this.image, 0, 0, canvas.width, canvas.height);
};
}
/**
*
* @param image
*/
setImage(image: SizedCanvasImageSource) {
setImage(image: CanvasImageSource) {
this.image = image;
this.size(image.width, image.height);
this.update(this);
this.update();
}
}
export class Comment extends RenderItem {
constructor(public text: string = '') {
super('static');
this.hide();
}
protected render(
canvas: MotaOffscreenCanvas2D,
transform: Transform
): void {}
}

View File

@ -9,8 +9,8 @@ export class FloorViewport implements ILayerGroupRenderExtends {
id: string = 'viewport';
group!: LayerGroup;
hero!: HeroRenderer;
binder!: LayerGroupFloorBinder;
hero?: HeroRenderer;
binder?: LayerGroupFloorBinder;
/** 是否启用视角控制拓展 */
enabled: boolean = true;
@ -91,12 +91,13 @@ export class FloorViewport implements ILayerGroupRenderExtends {
* @param y
*/
getBoundedPosition(x: number, y: number) {
if (!this.checkDependency()) return { x, y };
if (!this.boundX && !this.boundY) return { x, y };
const width = core._WIDTH_;
const height = core._HEIGHT_;
const minX = (width - 1) / 2;
const minY = (height - 1) / 2;
const floor = core.status.maps[this.binder.getFloor()];
const floor = core.status.maps[this.binder!.getFloor()];
const maxX = floor.width - minX - 1;
const maxY = floor.height - minY - 1;
@ -151,6 +152,7 @@ export class FloorViewport implements ILayerGroupRenderExtends {
}
private createMoveTransition() {
if (!this.checkDependency()) return;
let xTarget: number = 0;
let yTarget: number = 0;
let xStart: number = this.ox;
@ -159,7 +161,7 @@ export class FloorViewport implements ILayerGroupRenderExtends {
let yStartTime: number = Date.now();
let ending: boolean = false;
// 这个数等于 sinh(2)用这个数的话可以正好在刚开始移动的时候达到1的斜率效果会比较好
let transitionTime = this.hero.speed * 3.626860407847019;
let transitionTime = this.hero!.speed * 3.626860407847019;
const setTargetX = (x: number, time: number) => {
if (x === xTarget) return;
@ -175,7 +177,7 @@ export class FloorViewport implements ILayerGroupRenderExtends {
};
if (this.movingFramer) {
this.hero.off('moveTick', this.movingFramer);
this.hero!.off('moveTick', this.movingFramer);
}
this.movingFramer = () => {
if (this.inTransition) return;
@ -186,22 +188,22 @@ export class FloorViewport implements ILayerGroupRenderExtends {
ending = true;
}
if (!ending) {
const dir = this.hero.stepDir;
const dir = this.hero!.stepDir;
const { x, y } = core.utils.scan2[dir];
setTargetX(-x * this.maxOffset, now);
setTargetY(-y * this.maxOffset, now);
}
if (!this.hero.renderable) return;
if (!this.hero!.renderable) return;
const { x, y } = this.hero.renderable;
const { x, y } = this.hero!.renderable;
const { x: nx, y: ny } = this.getBoundedPosition(x, y);
this.nx = nx;
this.ny = ny;
if (ending) {
if (this.ox === xTarget && this.oy == yTarget) {
this.hero.off('moveTick', this.movingFramer);
this.hero!.off('moveTick', this.movingFramer);
return;
}
}
@ -228,7 +230,7 @@ export class FloorViewport implements ILayerGroupRenderExtends {
}
}
};
this.hero.on('moveTick', this.movingFramer);
this.hero!.on('moveTick', this.movingFramer);
}
/**
@ -299,8 +301,9 @@ export class FloorViewport implements ILayerGroupRenderExtends {
// this.createMoving();
}
awake(group: LayerGroup): void {
this.group = group;
private checkDependency() {
if (this.hero && this.binder) return true;
const group = this.group;
const ex1 = group.getLayer('event')?.getExtends('floor-hero');
const ex2 = group.getExtends('floor-binder');
if (
@ -309,12 +312,15 @@ export class FloorViewport implements ILayerGroupRenderExtends {
) {
this.hero = ex1;
this.binder = ex2;
return true;
}
return false;
}
awake(group: LayerGroup): void {
this.group = group;
this.create();
adapter.add(this);
} else {
logger.error(15);
group.removeExtends('viewport');
}
}
onDestroy(group: LayerGroup): void {

View File

@ -67,11 +67,12 @@ export class MotaRenderer extends Container {
}
}
private searchElement(ele: Container, id: string): RenderItem | null {
private searchElement(ele: RenderItem, id: string): RenderItem | null {
for (const child of ele.children) {
if (child.id === id) return child;
if (child instanceof Container) {
return this.searchElement(child, id);
else {
const ele = this.searchElement(child, id);
if (ele) return ele;
}
}
return null;

View File

@ -0,0 +1,119 @@
import {
ComponentOptionsMixin,
defineComponent,
DefineComponent,
h,
ReservedProps,
VNodeProps
} from 'vue';
import EventEmitter from 'eventemitter3';
import {
AnimateProps,
BaseProps,
CommentProps,
ContainerProps,
CustomProps,
DamageProps,
GL2Props,
ImageProps,
LayerGroupProps,
LayerProps,
ShaderProps,
SpriteProps,
TextProps
} from './props';
import { ERenderItemEvent, RenderItem } from '../item';
import { ESpriteEvent, Sprite } from '../sprite';
import { EContainerEvent } from '../container';
import { EGL2Event } from '../gl2';
import { EImageEvent, ETextEvent } from '../preset/misc';
import { ELayerEvent, ELayerGroupEvent } from '../preset/layer';
import { EAnimateEvent } from '../preset/animate';
import { EDamageEvent } from '../preset/damage';
import { EShaderEvent } from '../shader';
export type WrapEventEmitterEvents<T extends EventEmitter.ValidEventTypes> =
T extends string | symbol
? T
: {
[P in keyof T]: T[P] extends any[]
? (...args: T[P]) => void
: (...args: any[]) => void;
};
type MappingEvent<E extends ERenderItemEvent> = {
[P in keyof WrapEventEmitterEvents<E> as P extends string
? `on${Capitalize<P>}`
: never]?: WrapEventEmitterEvents<E>[P];
};
type _Define<P extends BaseProps, E extends ERenderItemEvent> = DefineComponent<
P,
{},
{},
{},
{},
ComponentOptionsMixin,
ComponentOptionsMixin,
WrapEventEmitterEvents<E>,
Exclude<keyof WrapEventEmitterEvents<E>, number | symbol>,
VNodeProps,
Readonly<P & MappingEvent<E>>
>;
type TagDefine<T extends object, E extends ERenderItemEvent> = T &
MappingEvent<E> &
ReservedProps;
export type RenderItemComponent = _Define<BaseProps, ERenderItemEvent>;
export type SpriteComponent = _Define<SpriteProps, ESpriteEvent>;
export type ContainerComponent = _Define<ContainerProps, EContainerEvent>;
export type GL2Component = _Define<GL2Props, EGL2Event>;
export type ShaderComponent = _Define<ShaderProps, EShaderEvent>;
export type TextComponent = _Define<TextProps, ETextEvent>;
export type ImageComponent = _Define<ImageProps, EImageEvent>;
export type CommentComponent = _Define<CommentProps, ERenderItemEvent>;
export type LayerGroupComponent = _Define<LayerGroupProps, ELayerGroupEvent>;
export type LayerComponent = _Define<LayerProps, ELayerEvent>;
export type AnimateComponent = _Define<AnimateProps, EAnimateEvent>;
export type DamageComponent = _Define<DamageProps, EDamageEvent>;
declare module 'vue/jsx-runtime' {
namespace JSX {
export interface IntrinsicElements {
sprite: TagDefine<SpriteProps, ESpriteEvent>;
container: TagDefine<ContainerProps, EContainerEvent>;
shader: TagDefine<ShaderProps, EShaderEvent>;
text: TagDefine<TextProps, ETextEvent>;
image: TagDefine<ImageProps, EImageEvent>;
comment: TagDefine<CommentProps, ERenderItemEvent>;
custom: TagDefine<CustomProps, ERenderItemEvent>;
layer: TagDefine<LayerProps, ELayerEvent>;
'layer-group': TagDefine<LayerGroupProps, ELayerGroupEvent>;
damage: TagDefine<DamageProps, EDamageEvent>;
animate: TagDefine<AnimateProps, EAnimateEvent>;
}
}
}
export interface InstancedElementProp {
item: RenderItem;
}
export function wrapInstancedComponent<
P extends BaseProps = BaseProps,
E extends ERenderItemEvent = ERenderItemEvent,
C extends RenderItem = RenderItem
>(onCreate: (props: P) => C): _Define<P, E> {
const Com = defineComponent((props, ctx) => {
return () => {
const p = {
...props,
...ctx.attrs,
_item: onCreate
};
return h('custom', p, ctx.slots);
};
});
return Com as _Define<P, E>;
}

View File

@ -0,0 +1,99 @@
import {
ComponentInternalInstance,
createRenderer,
ElementNamespace,
VNodeProps
} from 'vue';
import { ERenderItemEvent, RenderItem } from '../item';
import { tagMap } from './map';
import { logger } from '@/core/common/logger';
import { Comment, Text } from '../preset/misc';
export const { createApp, render } = createRenderer<RenderItem, RenderItem>({
patchProp: function (
el: RenderItem,
key: string,
prevValue: any,
nextValue: any,
namespace?: ElementNamespace,
parentComponent?: ComponentInternalInstance | null
): void {
el.patchProp(key, prevValue, nextValue, namespace, parentComponent);
},
insert: function (
el: RenderItem<ERenderItemEvent>,
parent: RenderItem,
anchor?: RenderItem<ERenderItemEvent> | null
): void {
parent.appendChild(el);
},
remove: function (el: RenderItem<ERenderItemEvent>): void {
el.remove();
el.destroy();
},
createElement: function (
type: string,
namespace?: ElementNamespace,
isCustomizedBuiltIn?: string,
vnodeProps?: (VNodeProps & { [key: string]: any }) | null
): RenderItem {
const onCreate = tagMap.get(type);
if (!onCreate) {
logger.error(20, type);
throw new Error(`Cannot create element '${type}'`);
}
return onCreate(namespace, isCustomizedBuiltIn, vnodeProps);
},
createText: function (text: string): RenderItem<ERenderItemEvent> {
logger.warn(38);
return new Text(text);
},
createComment: function (text: string): RenderItem<ERenderItemEvent> {
return new Comment(text);
},
setText: function (node: RenderItem<ERenderItemEvent>, text: string): void {
if (node instanceof Text) {
node.setText(text);
} else {
logger.warn(39);
}
},
setElementText: function (node: RenderItem, text: string): void {
if (node instanceof Text) {
node.setText(text);
} else {
logger.warn(39);
}
},
parentNode: function (
node: RenderItem<ERenderItemEvent>
): RenderItem | null {
return node.parent ?? null;
},
nextSibling: function (
node: RenderItem<ERenderItemEvent>
): RenderItem<ERenderItemEvent> | null {
if (!node.parent) {
return null;
} else {
const parent = node.parent;
const list = [...parent.children];
const index = list.indexOf(node);
return list[index] ?? null;
}
}
});
export * from './elements';
export * from './map';
export * from './props';
export * from './use';

View File

@ -0,0 +1,161 @@
import { logger } from '@/core/common/logger';
import { ERenderItemEvent, RenderItem } from '../item';
import { ElementNamespace, VNodeProps } from 'vue';
import { Container } from '../container';
import { MotaRenderer } from '../render';
import { Sprite } from '../sprite';
import { Comment, Image, Text } from '../preset/misc';
import { Shader } from '../shader';
import { Animate, Damage, EDamageEvent, Layer, LayerGroup } from '../preset';
type OnItemCreate<
E extends ERenderItemEvent = ERenderItemEvent,
T extends RenderItem<E> = RenderItem<E>
> = (
namespace?: ElementNamespace,
isCustomizedBuiltIn?: string,
vnodeProps?: (VNodeProps & { [key: string]: any }) | null
) => T;
class RenderTagMap {
private map: Map<string, OnItemCreate> = new Map();
/**
*
* @param tag
* @param ele
*/
register<E extends ERenderItemEvent, T extends RenderItem<E>>(
tag: string,
onCreate: OnItemCreate<E, T>
) {
if (this.map.has(tag)) {
logger.warn(34, tag);
}
this.map.set(tag, onCreate);
}
/**
*
* @param tag
*/
get<E extends ERenderItemEvent, T extends RenderItem<E>>(
tag: string
): OnItemCreate<E, T> | undefined {
return this.map.get(tag) as OnItemCreate<E, T>;
}
}
export const tagMap = new RenderTagMap();
// Default elements
tagMap.register('container', (_0, _1, props) => {
if (!props) return new Container();
else {
const {
type = 'static',
enableCache = true,
fallthrough = false
} = props;
return new Container(type, enableCache, fallthrough);
}
});
tagMap.register('mota-renderer', (_0, _1, props) => {
return new MotaRenderer(props?.id);
});
tagMap.register('sprite', (_0, _1, props) => {
if (!props) return new Sprite();
else {
const {
type = 'static',
enableCache = true,
fallthrough = false
} = props;
return new Sprite(type, enableCache, fallthrough);
}
});
tagMap.register('text', (_0, _1, props) => {
if (!props) return new Text();
else {
const { type = 'static', text = '' } = props;
return new Text(text, type);
}
});
tagMap.register('image', (_0, _1, props) => {
if (!props) return new Image(core.material.images.images['bg.jpg']);
else {
const {
image = core.material.images.images['bg.jpg'],
type = 'static'
} = props;
return new Image(image, type);
}
});
tagMap.register('comment', (_0, _1, props) => {
if (!props) return new Comment();
else {
const { text = '' } = props;
return new Comment(text);
}
});
tagMap.register('shader', (_0, _1, props) => {
if (!props) return new Shader();
else {
const { type = 'static' } = props;
return new Shader(type);
}
});
tagMap.register('custom', (_0, _1, props) => {
if (!props) {
logger.error(22);
throw new Error('Cannot create custom element.');
} else {
const item = props._item;
if (!item) {
logger.error(22);
throw new Error('Cannot create custom element.');
}
return item(props);
}
});
tagMap.register('layer', (_0, _1, props) => {
if (!props) return new Layer();
else {
const { ex } = props;
const l = new Layer();
if (ex) {
(ex as any[]).forEach(v => {
l.extends(v);
});
}
return l;
}
});
tagMap.register('layer-group', (_0, _1, props) => {
if (!props) return new LayerGroup();
else {
const { ex, layers } = props;
const l = new LayerGroup();
if (ex) {
(ex as any[]).forEach(v => {
l.extends(v);
});
}
if (layers) {
(layers as any[]).forEach(v => {
l.addLayer(v);
});
}
return l;
}
});
tagMap.register<EDamageEvent, Damage>('damage', (_0, _1, props) => {
return new Damage();
});
tagMap.register('animate', (_0, _1, props) => {
return new Animate();
});

View File

@ -0,0 +1,89 @@
import { RenderFunction, RenderItem, RenderItemPosition } from '../item';
import { Transform } from '../transform';
import {
FloorLayer,
ILayerGroupRenderExtends,
ILayerRenderExtends
} from '../preset/layer';
import type { EnemyCollection } from '@/game/enemy/damage';
export interface CustomProps {
_item: (props: BaseProps) => RenderItem;
}
export interface BaseProps {
x?: number;
y?: number;
anchorX?: number;
anchorY?: number;
zIndex?: number;
width?: number;
height?: number;
filter?: string;
hd?: boolean;
antiAliasing?: boolean;
hidden?: boolean;
transform?: Transform;
type?: RenderItemPosition;
id?: string;
alpha?: number;
composite?: GlobalCompositeOperation;
}
export interface SpriteProps extends BaseProps {
render?: RenderFunction;
}
export interface ContainerProps extends BaseProps {}
export interface GL2Props extends BaseProps {}
export interface ShaderProps extends BaseProps {}
export interface TextProps extends BaseProps {
text?: string;
fillStyle?: CanvasStyle;
strokeStyle?: CanvasStyle;
font?: string;
strokeWidth?: number;
}
export interface ImageProps extends BaseProps {
image: CanvasImageSource;
}
export interface CommentProps extends BaseProps {
text?: string;
}
export interface LayerGroupProps extends BaseProps {
cellSize?: number;
blockSize?: number;
floorId?: FloorIds;
bindThisFloor?: boolean;
camera?: Transform;
ex?: readonly ILayerGroupRenderExtends[];
layers?: readonly FloorLayer[];
}
export interface LayerProps extends BaseProps {
layer?: FloorLayer;
mapWidth?: number;
mapHeight?: number;
cellSize?: number;
background?: AllNumbers;
floorImage?: FloorAnimate[];
ex?: readonly ILayerRenderExtends[];
}
export interface AnimateProps extends BaseProps {}
export interface DamageProps extends BaseProps {
mapWidth?: number;
mapHeight?: number;
cellSize?: number;
enemy?: EnemyCollection;
font?: string;
strokeStyle?: string;
strokeWidth?: number;
}

View File

@ -0,0 +1,57 @@
import { gameKey, Hotkey } from '@/core/main/custom/hotkey';
import { Animation, Ticker, Transition } from 'mutate-animate';
import { onMounted, onUnmounted } from 'vue';
const ticker = new Ticker();
/**
*
* @param fn
*/
export function onTick(fn: (time: number) => void) {
onMounted(() => {
ticker.add(fn);
});
onUnmounted(() => {
ticker.remove(fn);
});
}
type AnimationUsing = [Animation];
type TransitionUsing = [Transition];
/**
*
*/
export function useAnimation(): AnimationUsing {
const ani = new Animation();
onUnmounted(() => {
ani.ticker.destroy();
});
return [ani];
}
/**
*
*/
export function useTransition(): TransitionUsing {
const tran = new Transition();
onUnmounted(() => {
tran.ticker.destroy();
});
return [tran];
}
type KeyUsing = [Hotkey, symbol];
/**
*
*/
export function useKey(): KeyUsing {
const sym = Symbol();
gameKey.use(sym);
onUnmounted(() => {
gameKey.dispose();
});
return [gameKey, sym];
}

View File

@ -1,7 +1,7 @@
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
import { ERenderItemEvent, RenderItem, RenderItemPosition } from './item';
import { Transform } from './transform';
import { GL2, GL2Program, IGL2ProgramPrefix } from './gl2';
import { EGL2Event, GL2, GL2Program, IGL2ProgramPrefix } from './gl2';
const SHADER_PREFIX: IGL2ProgramPrefix = {
VERTEX: /* glsl */ `#version 300 es
@ -33,7 +33,11 @@ void main() {
}
`;
export class Shader extends GL2 {
export interface EShaderEvent extends EGL2Event {}
export class Shader<E extends EShaderEvent = EShaderEvent> extends GL2<
EShaderEvent | E
> {
setHD(hd: boolean): void {
super.setHD(hd);
this.sizeGL(this.width, this.height);

View File

@ -6,6 +6,8 @@ import {
} from './item';
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
import { Transform } from './transform';
import { ElementNamespace, ComponentInternalInstance } from 'vue';
import { logger } from '../common/logger';
export interface ESpriteEvent extends ERenderItemEvent {}
@ -40,4 +42,24 @@ export class Sprite<
this.renderFn = fn;
this.update(this);
}
patchProp(
key: string,
prevValue: any,
nextValue: any,
namespace?: ElementNamespace,
parentComponent?: ComponentInternalInstance | null
): void {
super.patchProp(key, prevValue, nextValue, namespace, parentComponent);
const type = typeof nextValue;
switch (key) {
case 'render':
if (type !== 'function') {
logger.error(21, key, 'function', type);
return;
}
this.setRenderFn(nextValue);
break;
}
}
}

View File

@ -1,4 +1,4 @@
import { TimingFn } from 'mutate-animate';
import { Ticker, TimingFn } from 'mutate-animate';
import { RenderAdapter } from './adapter';
import { FloorViewport } from './preset/viewport';

View File

@ -13,12 +13,15 @@
"11": "Cache depth cannot larger than 31.",
"12": "Cannot move while status is not 'moving'. Call 'readyMove' first.",
"13": "Cannot compile $1 shader. Error info: $2",
"14": "Animate extension needs 'floor-hero' extension as dependency.",
"15": "Viewport extension needs 'floor-hero' extension as dependency.",
"14": "",
"15": "",
"16": "Cannot find log message for $1 code $2.",
"17": "Cannot use shader program for shader element that does not belong to it.",
"18": "Cannot delete shader program for shader element that does not belong to it.",
"19": "Cannot create MotaRenderer instance for nonexistent canvas.",
"20": "Cannot create render element for tag '$1', since there's no registration for it.",
"21": "Incorrect render prop type is delivered. key: '$1', expected type: '$2', delivered type: '$3'",
"22": "Incorrect props for custom tag. Please ensure you have delivered 'item' prop and other required props.",
"1101": "Shadow extension needs 'floor-hero' extension as dependency.",
"1201": "Floor-damage extension needs 'floor-binder' extension as dependency.",
"1301": "Portal extension need 'floor-binder' extension as dependency.",
@ -58,6 +61,12 @@
"31": "Cannot use indices since the indices instance is not belong to the program.",
"32": "Sub-image exceeds texture dimensions, auto adjusting size.",
"33": "Cannot modify MotaOffscreenCanvas2D that is freezed.",
"34": "Repeated render tag registration: '$1'.",
"35": "Cannot append child on plain render item, please ensure you have overrided 'appendChild' method in your own element.",
"36": "Cannot remove child on plain render item, please ensure you have overrided 'removeChild' method in your own element.",
"37": "Cannot execute 'requestSort' on plain render item, please ensure you have overrided 'requestSort' method in your own element.",
"38": "Using plain text in jsx is strongly not recommended, since you can hardly modify its attributes. Consider using Text element instead.",
"39": "Plain text is not supported outside Text element.",
"1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency.",
"1101": "Cannot add new effect to point effect instance, for there's no more reserve space for it. Please increase the max count of the instance."
}

View File

@ -1,14 +1,15 @@
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
import { wrapInstancedComponent } from '@/core/render';
import { RenderItem, RenderItemPosition } from '@/core/render/item';
import { Transform } from '@/core/render/transform';
// 渲染端的向后兼容用,会充当两个版本间过渡的作用
export class FloorChange extends RenderItem {
class Change extends RenderItem {
private tips: string[] = [];
/** 当前小贴士 */
private usingTip: string = '';
/** 透明度 */
private alpha: number = 0;
private backAlpha: number = 0;
private title: string = '';
constructor(type: RenderItemPosition) {
@ -45,10 +46,10 @@ export class FloorChange extends RenderItem {
const dt = Date.now() - start;
const progress = dt / time;
if (progress > 1) {
this.alpha = 1;
this.backAlpha = 1;
this.removeTicker(id);
} else {
this.alpha = progress;
this.backAlpha = progress;
}
this.update();
},
@ -71,9 +72,9 @@ export class FloorChange extends RenderItem {
const progress = dt / time;
if (progress > 1) {
this.removeTicker(id);
this.alpha = 0;
this.backAlpha = 0;
} else {
this.alpha = 1 - progress;
this.backAlpha = 1 - progress;
}
this.update();
},
@ -87,9 +88,9 @@ export class FloorChange extends RenderItem {
canvas: MotaOffscreenCanvas2D,
transform: Transform
): void {
if (this.alpha === 0) return;
if (this.backAlpha === 0) return;
const ctx = canvas.ctx;
ctx.globalAlpha = this.alpha;
ctx.globalAlpha = this.backAlpha;
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.textAlign = 'center';
@ -106,3 +107,5 @@ export class FloorChange extends RenderItem {
}
}
}
export const FloorChange = wrapInstancedComponent(() => new Change('static'));

View File

@ -1,4 +1,5 @@
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
import { wrapInstancedComponent } from '@/core/render';
import { RenderItem, RenderItemPosition } from '@/core/render/item';
import { Transform } from '@/core/render/transform';
import { TimingFn } from 'mutate-animate';
@ -18,7 +19,7 @@ function parabola(input: number): [number, number] {
return [x, x ** 2 / 20 - 3 * x];
}
export class PopText extends RenderItem {
class Pop extends RenderItem {
private popList: Set<PopData> = new Set();
private delegation: number = 0;
@ -94,3 +95,5 @@ export class PopText extends RenderItem {
this.removeTicker(this.delegation);
}
}
export const PopText = wrapInstancedComponent(() => new Pop('static'));

27
src/plugin/fx/shadow.ts Normal file
View File

@ -0,0 +1,27 @@
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
import { GL2, GL2Program } from '@/core/render/gl2';
import { Transform } from '@/core/render/transform';
const MAX_COUNT = 5;
export class ShadowEffect extends GL2 {
protected preDraw(
canvas: MotaOffscreenCanvas2D,
transform: Transform,
gl: WebGL2RenderingContext,
program: GL2Program
): boolean {
throw new Error('Method not implemented.');
}
protected postDraw(
canvas: MotaOffscreenCanvas2D,
transform: Transform,
gl: WebGL2RenderingContext,
program: GL2Program
): void {
throw new Error('Method not implemented.');
}
}
class ShadowProgam extends GL2Program {}

View File

@ -9,11 +9,22 @@ import postcssPresetEnv from 'postcss-preset-env';
const FSHOST = 'http://127.0.0.1:3000/';
const custom = [
'container', 'image', 'sprite', 'shader', 'text', 'comment', 'custom',
'layer', 'layer-group', 'animate', 'damage'
]
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
vuejsx(),
vue({
customElement: custom
}),
vuejsx({
isCustomElement: (tag) => {
return custom.includes(tag)
}
}),
legacy({
targets: ['defaults', 'not IE 11'],
polyfills: true,