mirror of
				https://github.com/unanmed/HumanBreak.git
				synced 2025-10-31 12:12:58 +08:00 
			
		
		
		
	feat: 传送门渲染
This commit is contained in:
		
							parent
							
								
									d0ff2f1dc5
								
							
						
					
					
						commit
						292e17ed53
					
				| @ -39,24 +39,24 @@ let ctx: CanvasRenderingContext2D; | ||||
| let particleSetting: MotaSettingItem; | ||||
| 
 | ||||
| let lastTime = 0; | ||||
| Mota.require('var', 'loading').once('coreInit', () => { | ||||
|     canvas = MotaCanvas2D.for('@portal'); | ||||
|     ctx = canvas.ctx; | ||||
|     canvas.mount(); | ||||
|     canvas.css(`z-index: 51`); | ||||
|     canvas.withGameScale(true); | ||||
|     canvas.pos(0, 0); | ||||
|     canvas.size(480, 480); | ||||
|     canvas.on('resize', () => { | ||||
|         canvas.css(`z-index: 51`); | ||||
|     }); | ||||
|     particleSetting = mainSetting.getSetting('fx.portalParticle')!; | ||||
|     ticker.add(tickPortal); | ||||
| }); | ||||
| // Mota.require('var', 'loading').once('coreInit', () => {
 | ||||
| //     canvas = MotaCanvas2D.for('@portal');
 | ||||
| //     ctx = canvas.ctx;
 | ||||
| //     canvas.mount();
 | ||||
| //     canvas.css(`z-index: 51`);
 | ||||
| //     canvas.withGameScale(true);
 | ||||
| //     canvas.pos(0, 0);
 | ||||
| //     canvas.size(480, 480);
 | ||||
| //     canvas.on('resize', () => {
 | ||||
| //         canvas.css(`z-index: 51`);
 | ||||
| //     });
 | ||||
| //     particleSetting = mainSetting.getSetting('fx.portalParticle')!;
 | ||||
| //     ticker.add(tickPortal);
 | ||||
| // });
 | ||||
| 
 | ||||
| Mota.require('var', 'hook').on('changingFloor', id => { | ||||
|     drawPortals(id); | ||||
| }); | ||||
| // Mota.require('var', 'hook').on('changingFloor', id => {
 | ||||
| //     drawPortals(id);
 | ||||
| // });
 | ||||
| 
 | ||||
| let needDraw = false; | ||||
| function tickPortal(time: number) { | ||||
|  | ||||
| @ -7,6 +7,7 @@ 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'; | ||||
| 
 | ||||
| let main: MotaRenderer; | ||||
| 
 | ||||
| @ -33,9 +34,11 @@ Mota.require('var', 'loading').once('loaded', () => { | ||||
|     const shadow = new LayerShadowExtends(); | ||||
|     const filter = new LayerGroupFilter(); | ||||
|     const animate = new LayerGroupAnimate(); | ||||
|     const portal = new LayerGroupPortal(); | ||||
|     layer.extends(damage); | ||||
|     layer.extends(detail); | ||||
|     layer.extends(filter); | ||||
|     layer.extends(portal); | ||||
|     layer.getLayer('event')?.extends(hero); | ||||
|     layer.getLayer('event')?.extends(door); | ||||
|     layer.getLayer('event')?.extends(shadow); | ||||
|  | ||||
| @ -16,7 +16,7 @@ export class Sprite<E extends ESpriteEvent = ESpriteEvent> extends RenderItem< | ||||
| 
 | ||||
|     /** | ||||
|      * 创建一个精灵,可以自由在上面渲染内容 | ||||
|      * @param type 渲染模式,absolute表示绝对位置,static表示跟随摄像机移动,只对顶层元素有效 | ||||
|      * @param type 渲染模式,absolute表示绝对位置,不会跟随自身的Transform改变 | ||||
|      * @param cache 是否启用缓存机制 | ||||
|      */ | ||||
|     constructor(type: RenderItemPosition = 'static', cache: boolean = true) { | ||||
|  | ||||
| @ -81,7 +81,7 @@ export namespace BluePalace { | ||||
| 
 | ||||
|     // ---------- 传送门部分
 | ||||
| 
 | ||||
|     interface Portal { | ||||
|     export interface Portal { | ||||
|         fx: number; | ||||
|         fy: number; | ||||
|         dir: Dir; | ||||
|  | ||||
							
								
								
									
										296
									
								
								src/plugin/fx/portal.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										296
									
								
								src/plugin/fx/portal.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,296 @@ | ||||
| import { logger } from '@/core/common/logger'; | ||||
| import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d'; | ||||
| import { mainSetting, MotaSettingItem } from '@/core/main/setting'; | ||||
| import { LayerGroupFloorBinder } from '@/core/render/preset/floor'; | ||||
| import { | ||||
|     ILayerGroupRenderExtends, | ||||
|     LayerGroup | ||||
| } from '@/core/render/preset/layer'; | ||||
| import { Sprite } from '@/core/render/sprite'; | ||||
| import type { BluePalace } from '@/game/mechanism/misc'; | ||||
| 
 | ||||
| /** 最大粒子数 */ | ||||
| const MAX_PARTICLES = 10; | ||||
| /** 粒子持续时长 */ | ||||
| const PARTICLE_LAST = 2000; | ||||
| /** 粒子产生间隔 */ | ||||
| const PARTICLE_INTERVAL = PARTICLE_LAST / MAX_PARTICLES; | ||||
| 
 | ||||
| export class LayerGroupPortal implements ILayerGroupRenderExtends { | ||||
|     id: string = 'portal'; | ||||
| 
 | ||||
|     group!: LayerGroup; | ||||
|     binder!: LayerGroupFloorBinder; | ||||
|     portal!: Portal; | ||||
| 
 | ||||
|     private onFloorChange = (floor: FloorIds) => { | ||||
|         const data = Mota.require('module', 'Mechanism').BluePalace.portals; | ||||
|         this.portal.cellSize = this.group.cellSize; | ||||
|         this.portal.setData(data[floor] ?? []); | ||||
|     }; | ||||
| 
 | ||||
|     private listen() { | ||||
|         this.binder.on('floorChange', this.onFloorChange); | ||||
|     } | ||||
| 
 | ||||
|     awake(group: LayerGroup): void { | ||||
|         this.group = group; | ||||
|         const ex = group.getExtends('floor-binder'); | ||||
|         if (ex instanceof LayerGroupFloorBinder) { | ||||
|             this.binder = ex; | ||||
|             this.portal = new Portal(); | ||||
|             this.portal.setHD(true); | ||||
|             this.portal.size(group.width, group.height); | ||||
|             group.getLayer('event')?.appendChild(this.portal); | ||||
|             this.listen(); | ||||
|         } else { | ||||
|             logger.error( | ||||
|                 1301, | ||||
|                 `Portal extends need 'floor-binder' extends as dependency.` | ||||
|             ); | ||||
|             group.removeExtends('portal'); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     onDestroy(group: LayerGroup): void { | ||||
|         this.binder.off('floorChange', this.onFloorChange); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| interface DrawingPortal { | ||||
|     color: string; | ||||
|     x: number; | ||||
|     y: number; | ||||
|     particles: Set<PortalParticle>; | ||||
|     /** v表示竖向,h表示横向 */ | ||||
|     type: 'v' | 'h'; | ||||
|     /** 上一次新增粒子的时间 */ | ||||
|     lastParticle: number; | ||||
| } | ||||
| 
 | ||||
| interface PortalParticle { | ||||
|     fx: number; | ||||
|     fy: number; | ||||
|     totalTime: number; | ||||
|     time: number; | ||||
|     tx: number; | ||||
|     ty: number; | ||||
|     r: number; | ||||
| } | ||||
| 
 | ||||
| export class Portal extends Sprite { | ||||
|     static colors: string[] = ['#0f0', '#ff0', '#0ff', '#fff', '#f0f']; | ||||
| 
 | ||||
|     cellSize: number = 32; | ||||
|     /** 当前的渲染数据 */ | ||||
|     private renderData: BluePalace.Portal[] = []; | ||||
|     /** 渲染内容 */ | ||||
|     private renderable: Set<DrawingPortal> = new Set(); | ||||
| 
 | ||||
|     /** 粒子开关设置 */ | ||||
|     private particleSetting: MotaSettingItem; | ||||
|     /** 上一帧时刻 */ | ||||
|     private lastTime: number = 0; | ||||
| 
 | ||||
|     private delegation: number; | ||||
| 
 | ||||
|     constructor() { | ||||
|         super('static', false); | ||||
| 
 | ||||
|         this.particleSetting = mainSetting.getSetting('fx.portalParticle')!; | ||||
| 
 | ||||
|         this.delegation = this.delegateTicker(() => { | ||||
|             if (this.particleSetting.value) this.update(this); | ||||
|         }); | ||||
| 
 | ||||
|         this.setRenderFn((canvas, transform) => { | ||||
|             // console.time();
 | ||||
|             this.renderPortal(canvas); | ||||
|             // console.timeEnd();
 | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 设置渲染内容 | ||||
|      */ | ||||
|     setData(data: BluePalace.Portal[]) { | ||||
|         this.renderData = data; | ||||
|         this.generateRenderable(); | ||||
|         this.update(this); | ||||
|     } | ||||
| 
 | ||||
|     private generateRenderable() { | ||||
|         this.renderable.clear(); | ||||
|         if (this.renderData.length === 0) return; | ||||
|         const colorLength = Portal.colors.length; | ||||
|         const cell = this.cellSize; | ||||
|         this.renderData.forEach((v, i) => { | ||||
|             const c = Portal.colors[i % colorLength]; | ||||
|             const { fx, fy, tx, ty, dir, toDir } = v; | ||||
| 
 | ||||
|             let x1 = fx * cell; | ||||
|             let y1 = fy * cell; | ||||
|             let x2 = tx * cell; | ||||
|             let y2 = ty * cell; | ||||
| 
 | ||||
|             if (dir === 'down') y1 += cell; | ||||
|             else if (dir === 'right') x1 += cell; | ||||
| 
 | ||||
|             if (toDir === 'down') y2 += cell; | ||||
|             else if (toDir === 'right') x2 += cell; | ||||
| 
 | ||||
|             this.renderable.add({ | ||||
|                 x: x1, | ||||
|                 y: y1, | ||||
|                 type: dir === 'left' || dir === 'right' ? 'v' : 'h', | ||||
|                 color: c, | ||||
|                 particles: new Set(), | ||||
|                 lastParticle: Date.now() | ||||
|             }); | ||||
| 
 | ||||
|             this.renderable.add({ | ||||
|                 x: x2, | ||||
|                 y: y2, | ||||
|                 type: toDir === 'left' || toDir === 'right' ? 'v' : 'h', | ||||
|                 color: c, | ||||
|                 particles: new Set(), | ||||
|                 lastParticle: Date.now() | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private renderPortal(canvas: MotaOffscreenCanvas2D) { | ||||
|         const { ctx } = canvas; | ||||
| 
 | ||||
|         const p = this.particleSetting.value; | ||||
|         ctx.lineCap = 'round'; | ||||
|         ctx.lineWidth = 3; | ||||
|         ctx.shadowOffsetX = 0; | ||||
|         ctx.shadowOffsetY = 0; | ||||
|         if (p) { | ||||
|             ctx.shadowBlur = 8; | ||||
|         } else { | ||||
|             ctx.shadowBlur = 0; | ||||
|         } | ||||
| 
 | ||||
|         const time = Date.now(); | ||||
|         const dt = time - this.lastTime; | ||||
|         this.lastTime = time; | ||||
|         this.renderable.forEach(v => { | ||||
|             const { color, x, y, type, lastParticle, particles } = v; | ||||
| 
 | ||||
|             ctx.strokeStyle = color; | ||||
|             ctx.fillStyle = color; | ||||
|             ctx.globalAlpha = 1; | ||||
|             ctx.shadowColor = color; | ||||
|             if (type === 'v') { | ||||
|                 ctx.beginPath(); | ||||
|                 ctx.moveTo(x, y - 14); | ||||
|                 ctx.lineTo(x, y + 30); | ||||
|                 ctx.stroke(); | ||||
|             } else { | ||||
|                 ctx.beginPath(); | ||||
|                 ctx.moveTo(x + 2, y); | ||||
|                 ctx.lineTo(x + 30, y); | ||||
|                 ctx.stroke(); | ||||
|             } | ||||
| 
 | ||||
|             if (!p) return; | ||||
| 
 | ||||
|             const needDelete = new Set<PortalParticle>(); | ||||
|             particles.forEach(v => { | ||||
|                 const { fx, fy, tx, ty, time: t, totalTime, r } = v; | ||||
|                 const progress = t / totalTime; | ||||
|                 const nx = (tx - fx) * progress + fx; | ||||
|                 const ny = (ty - fy) * progress + fy; | ||||
|                 v.time += dt; | ||||
| 
 | ||||
|                 if (progress > 1) { | ||||
|                     needDelete.add(v); | ||||
|                     return; | ||||
|                 } else if (progress > 0.75) { | ||||
|                     ctx.globalAlpha = (1 - progress) * 4; | ||||
|                 } else if (progress < 0.25) { | ||||
|                     ctx.globalAlpha = progress * 4; | ||||
|                 } else { | ||||
|                     ctx.globalAlpha = 1; | ||||
|                 } | ||||
| 
 | ||||
|                 ctx.beginPath(); | ||||
|                 ctx.arc(nx, ny, r, 0, Math.PI * 2); | ||||
|                 ctx.closePath(); | ||||
|                 ctx.fill(); | ||||
|             }); | ||||
|             needDelete.forEach(v => { | ||||
|                 particles.delete(v); | ||||
|             }); | ||||
|             if ( | ||||
|                 time - lastParticle >= PARTICLE_INTERVAL && | ||||
|                 particles.size < MAX_PARTICLES | ||||
|             ) { | ||||
|                 // 添加新粒子
 | ||||
|                 const direction = Math.random(); | ||||
|                 const k = Math.random() / 2 - 0.3; | ||||
|                 const verticle = Math.floor(Math.random() * 8 + 8); | ||||
|                 const r = Math.random() * 2; | ||||
|                 v.lastParticle = time; | ||||
|                 if (direction > 0.5) { | ||||
|                     // 左边 | 上边
 | ||||
|                     if (type === 'h') { | ||||
|                         const fx = Math.floor(Math.random() * 24 + x + 4); | ||||
|                         particles.add({ | ||||
|                             fx: fx, | ||||
|                             fy: y - 1, | ||||
|                             tx: verticle * k + fx + 4, | ||||
|                             ty: -verticle + y - 1, | ||||
|                             r: r, | ||||
|                             time: 0, | ||||
|                             totalTime: PARTICLE_LAST | ||||
|                         }); | ||||
|                     } else { | ||||
|                         const fy = Math.floor(Math.random() * 44 + y - 14); | ||||
|                         particles.add({ | ||||
|                             fy: fy, | ||||
|                             fx: x - 1, | ||||
|                             ty: verticle * k + fy + 4, | ||||
|                             tx: -verticle + x - 1, | ||||
|                             r: r, | ||||
|                             time: 0, | ||||
|                             totalTime: PARTICLE_LAST | ||||
|                         }); | ||||
|                     } | ||||
|                 } else { | ||||
|                     // 右边 | 下边
 | ||||
|                     if (type === 'h') { | ||||
|                         const fx = Math.floor(Math.random() * 24 + x + 4); | ||||
|                         particles.add({ | ||||
|                             fx: fx, | ||||
|                             fy: y + 1, | ||||
|                             tx: verticle * k + fx + 4, | ||||
|                             ty: verticle + y - 1, | ||||
|                             r: r, | ||||
|                             time: 0, | ||||
|                             totalTime: PARTICLE_LAST | ||||
|                         }); | ||||
|                     } else { | ||||
|                         const fy = Math.floor(Math.random() * 44 + y - 14); | ||||
|                         particles.add({ | ||||
|                             fy: fy, | ||||
|                             fx: x + 1, | ||||
|                             ty: verticle * k + fy + 4, | ||||
|                             tx: verticle + x + 1, | ||||
|                             r: r, | ||||
|                             time: 0, | ||||
|                             totalTime: PARTICLE_LAST | ||||
|                         }); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     destroy(): void { | ||||
|         super.destroy(); | ||||
|         this.removeTicker(this.delegation); | ||||
|     } | ||||
| } | ||||
| @ -448,23 +448,6 @@ export function init() { | ||||
|             adapters.animate?.global('drawHeroAnimate', name).then(() => { | ||||
|                 callback?.(); | ||||
|             }); | ||||
| 
 | ||||
|             // 开始绘制
 | ||||
|             // var animate = core.material.animates[name];
 | ||||
|             // animate.se = animate.se || {};
 | ||||
|             // if (typeof animate.se == 'string') animate.se = { 1: animate.se };
 | ||||
| 
 | ||||
|             // var id = setTimeout(null);
 | ||||
|             // core.status.animateObjs.push({
 | ||||
|             //     name: name,
 | ||||
|             //     id: id,
 | ||||
|             //     animate: animate,
 | ||||
|             //     hero: true,
 | ||||
|             //     index: 0,
 | ||||
|             //     callback: callback
 | ||||
|             // });
 | ||||
| 
 | ||||
|             // return id;
 | ||||
|         }; | ||||
|     }); | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user