mirror of
				https://github.com/unanmed/HumanBreak.git
				synced 2025-11-04 07:02:58 +08:00 
			
		
		
		
	Compare commits
	
		
			2 Commits
		
	
	
		
			e99320c52d
			...
			4102893916
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 4102893916 | |||
| 44276183bb | 
							
								
								
									
										1
									
								
								src/core/system/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/core/system/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export * from './ui';
 | 
				
			||||||
							
								
								
									
										55
									
								
								src/core/system/ui/controller.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/core/system/ui/controller.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,55 @@
 | 
				
			|||||||
 | 
					import { Component, VNodeProps } from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IUIControllerConfig<Element, UI> {
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 将一个ui挂载至目标元素时的操作
 | 
				
			||||||
 | 
					     * @param element 要挂载至的目标元素
 | 
				
			||||||
 | 
					     * @param ui 要挂载的ui对象
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    insert(element: Element, ui: UI): void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 将一个ui从目标元素上移除时的操作
 | 
				
			||||||
 | 
					     * @param element 被移除ui的父元素
 | 
				
			||||||
 | 
					     * @param ui 要被移除的ui元素
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    remove(element: Element, ui: UI): void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 创建一个新UI
 | 
				
			||||||
 | 
					     * @param component UI组件
 | 
				
			||||||
 | 
					     * @param props UI传递的props
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    createUI(
 | 
				
			||||||
 | 
					        component: Component,
 | 
				
			||||||
 | 
					        props?: (VNodeProps & { [key: string]: any }) | null
 | 
				
			||||||
 | 
					    ): UI;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const enum OpenOption {
 | 
				
			||||||
 | 
					    Push,
 | 
				
			||||||
 | 
					    Unshift
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const enum CloseOption {
 | 
				
			||||||
 | 
					    Splice,
 | 
				
			||||||
 | 
					    Pop,
 | 
				
			||||||
 | 
					    Shift
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class UIController<Element, UI> {
 | 
				
			||||||
 | 
					    constructor(config: IUIControllerConfig<Element, UI>) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 设置当ui改变时控制器的行为
 | 
				
			||||||
 | 
					     * @param open 打开时的行为
 | 
				
			||||||
 | 
					     * @param close 关闭时的行为
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    setChangeMode(open: OpenOption, close: CloseOption) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 将这个UI控制器挂载至容器上
 | 
				
			||||||
 | 
					     * @param container 要挂载至的容器
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    mount(container: Element) {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								src/core/system/ui/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/core/system/ui/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export * from './controller';
 | 
				
			||||||
@ -17,8 +17,11 @@ export abstract class BarrageBoss extends EventEmitter<BarrageBossEvent> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /** 开始时刻 */
 | 
					    /** 开始时刻 */
 | 
				
			||||||
    private startTime: number = 0;
 | 
					    private startTime: number = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** 当前帧数 */
 | 
					    /** 当前帧数 */
 | 
				
			||||||
    frame: number = 0;
 | 
					    frame: number = 0;
 | 
				
			||||||
 | 
					    /** 上一帧的时刻 */
 | 
				
			||||||
 | 
					    lastTime: number = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** 这个boss战的主渲染元素,所有弹幕都会在此之上渲染 */
 | 
					    /** 这个boss战的主渲染元素,所有弹幕都会在此之上渲染 */
 | 
				
			||||||
    abstract readonly main: BossSprite;
 | 
					    abstract readonly main: BossSprite;
 | 
				
			||||||
@ -31,17 +34,19 @@ export abstract class BarrageBoss extends EventEmitter<BarrageBossEvent> {
 | 
				
			|||||||
     * boss的ai,战斗开始后,每帧执行一次
 | 
					     * boss的ai,战斗开始后,每帧执行一次
 | 
				
			||||||
     * @param time 从战斗开始算起至现在经过了多长时间
 | 
					     * @param time 从战斗开始算起至现在经过了多长时间
 | 
				
			||||||
     * @param frame 从战斗开始算起至现在经过了多少帧,即当前是第几帧
 | 
					     * @param frame 从战斗开始算起至现在经过了多少帧,即当前是第几帧
 | 
				
			||||||
 | 
					     * @param dt 本帧距上一帧多长时间,即上一帧持续了多长时间
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    abstract ai(time: number, frame: number): void;
 | 
					    abstract ai(time: number, frame: number, dt: number): void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private tick = () => {
 | 
					    private tick = () => {
 | 
				
			||||||
        const now = Date.now();
 | 
					        const now = Date.now();
 | 
				
			||||||
        this.ai(now - this.startTime, this.frame);
 | 
					        const dt = now - this.lastTime;
 | 
				
			||||||
 | 
					        this.ai(now - this.startTime, this.frame, dt);
 | 
				
			||||||
        this.frame++;
 | 
					        this.frame++;
 | 
				
			||||||
        this.projectiles.forEach(v => {
 | 
					        this.projectiles.forEach(v => {
 | 
				
			||||||
            const time = now - v.startTime;
 | 
					            const time = now - v.startTime;
 | 
				
			||||||
            v.time = time;
 | 
					            v.time = time;
 | 
				
			||||||
            v.ai(this, time, v.frame);
 | 
					            v.ai(this, time, v.frame, dt);
 | 
				
			||||||
            v.frame++;
 | 
					            v.frame++;
 | 
				
			||||||
            if (time > 60_000) {
 | 
					            if (time > 60_000) {
 | 
				
			||||||
                this.destroyProjectile(v);
 | 
					                this.destroyProjectile(v);
 | 
				
			||||||
@ -50,6 +55,7 @@ export abstract class BarrageBoss extends EventEmitter<BarrageBossEvent> {
 | 
				
			|||||||
                v.doDamage(this.state);
 | 
					                v.doDamage(this.state);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					        this.lastTime = now;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -230,8 +236,9 @@ export abstract class Projectile<T extends BarrageBoss = BarrageBoss> {
 | 
				
			|||||||
     * @param boss 从属的boss
 | 
					     * @param boss 从属的boss
 | 
				
			||||||
     * @param time 从弹幕生成开始算起至现在经过了多长时间
 | 
					     * @param time 从弹幕生成开始算起至现在经过了多长时间
 | 
				
			||||||
     * @param frame 从弹幕生成开始算起至现在经过了多少帧,即当前是第几帧
 | 
					     * @param frame 从弹幕生成开始算起至现在经过了多少帧,即当前是第几帧
 | 
				
			||||||
 | 
					     * @param dt 本帧距上一帧多长时间,即上一帧持续了多长时间
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    abstract ai(boss: T, time: number, frame: number): void;
 | 
					    abstract ai(boss: T, time: number, frame: number, dt: number): void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 这个弹幕的渲染函数,原则上一个boss的弹幕应该全部画在同一层,而且渲染前画布不进行矩阵变换
 | 
					     * 这个弹幕的渲染函数,原则上一个boss的弹幕应该全部画在同一层,而且渲染前画布不进行矩阵变换
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										131
									
								
								src/plugin/boss/palaceBoss.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								src/plugin/boss/palaceBoss.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,131 @@
 | 
				
			|||||||
 | 
					import { IStateDamageable } from '@/game/state/interface';
 | 
				
			||||||
 | 
					import { BarrageBoss, BossSprite, Hitbox } from './barrage';
 | 
				
			||||||
 | 
					import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    Container,
 | 
				
			||||||
 | 
					    HeroRenderer,
 | 
				
			||||||
 | 
					    LayerGroup,
 | 
				
			||||||
 | 
					    MotaRenderer,
 | 
				
			||||||
 | 
					    RenderItem,
 | 
				
			||||||
 | 
					    Shader,
 | 
				
			||||||
 | 
					    Transform
 | 
				
			||||||
 | 
					} from '@/core/render';
 | 
				
			||||||
 | 
					import { Pop } from '../fx/pop';
 | 
				
			||||||
 | 
					import { SplittableBall } from './palaceBossProjectile';
 | 
				
			||||||
 | 
					import { PointEffect } from '../fx/pointShader';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Mota.require('var', 'loading').once('coreInit', () => {
 | 
				
			||||||
 | 
					    const shader = new Shader();
 | 
				
			||||||
 | 
					    shader.size(480, 480);
 | 
				
			||||||
 | 
					    shader.setHD(true);
 | 
				
			||||||
 | 
					    shader.setZIndex(120);
 | 
				
			||||||
 | 
					    PalaceBoss.shader = shader;
 | 
				
			||||||
 | 
					    PalaceBoss.effect.create(shader, 40);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const enum BossStage {
 | 
				
			||||||
 | 
					    Prologue,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Stage1,
 | 
				
			||||||
 | 
					    Stage2,
 | 
				
			||||||
 | 
					    Stage3,
 | 
				
			||||||
 | 
					    Stage4,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    End
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class PalaceBoss extends BarrageBoss {
 | 
				
			||||||
 | 
					    static effect: PointEffect = new PointEffect();
 | 
				
			||||||
 | 
					    static shader: Shader;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    main: BossSprite<BarrageBoss>;
 | 
				
			||||||
 | 
					    hitbox: Hitbox.Circle;
 | 
				
			||||||
 | 
					    state: IStateDamageable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private stage: BossStage = BossStage.Prologue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** 用于展示傅里叶频谱的背景元素 */
 | 
				
			||||||
 | 
					    private back: SonicBack;
 | 
				
			||||||
 | 
					    /** 楼层渲染元素 */
 | 
				
			||||||
 | 
					    private group: LayerGroup;
 | 
				
			||||||
 | 
					    /** 楼层渲染容器 */
 | 
				
			||||||
 | 
					    private mapDraw: Container;
 | 
				
			||||||
 | 
					    /** 伤害弹出 */
 | 
				
			||||||
 | 
					    pop: Pop;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private heroHp: number = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor() {
 | 
				
			||||||
 | 
					        super();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const render = MotaRenderer.get('render-main')!;
 | 
				
			||||||
 | 
					        this.group = render.getElementById('layer-main') as LayerGroup;
 | 
				
			||||||
 | 
					        this.mapDraw = render.getElementById('map-draw') as Container;
 | 
				
			||||||
 | 
					        this.pop = render.getElementById('pop-main') as Pop;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.state = core.status.hero;
 | 
				
			||||||
 | 
					        this.main = new BossEffect('static', this);
 | 
				
			||||||
 | 
					        this.back = new SonicBack('static');
 | 
				
			||||||
 | 
					        const { x, y } = core.status.hero.loc;
 | 
				
			||||||
 | 
					        const cell = 32;
 | 
				
			||||||
 | 
					        this.hitbox = new Hitbox.Circle(x + cell / 2, y + cell / 2, cell / 3);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override start(): void {
 | 
				
			||||||
 | 
					        super.start();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        PalaceBoss.shader.append(this.mapDraw);
 | 
				
			||||||
 | 
					        this.main.append(this.group);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // const event = this.group.getLayer('event');
 | 
				
			||||||
 | 
					        // const hero = event?.getExtends('floor-hero') as HeroRenderer;
 | 
				
			||||||
 | 
					        // hero?.on('moveTick', this.moveTick);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        SplittableBall.init({});
 | 
				
			||||||
 | 
					        this.heroHp = core.status.hero.hp;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override end(): void {
 | 
				
			||||||
 | 
					        super.end();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        PalaceBoss.shader.remove();
 | 
				
			||||||
 | 
					        this.main.remove();
 | 
				
			||||||
 | 
					        this.back.remove();
 | 
				
			||||||
 | 
					        this.main.destroy();
 | 
				
			||||||
 | 
					        this.back.destroy();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // const event = this.group.getLayer('event');
 | 
				
			||||||
 | 
					        // const hero = event?.getExtends('floor-hero') as HeroRenderer;
 | 
				
			||||||
 | 
					        // hero?.off('moveTick', this.moveTick);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        SplittableBall.end();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        PalaceBoss.effect.end();
 | 
				
			||||||
 | 
					        core.status.hero.hp = this.heroHp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Mota.Plugin.require('replay_g').clip('choices:0');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ai(time: number, frame: number): void {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BossEffect extends BossSprite<PalaceBoss> {
 | 
				
			||||||
 | 
					    protected preDraw(
 | 
				
			||||||
 | 
					        canvas: MotaOffscreenCanvas2D,
 | 
				
			||||||
 | 
					        transform: Transform
 | 
				
			||||||
 | 
					    ): boolean {
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected postDraw(
 | 
				
			||||||
 | 
					        canvas: MotaOffscreenCanvas2D,
 | 
				
			||||||
 | 
					        transform: Transform
 | 
				
			||||||
 | 
					    ): void {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SonicBack extends RenderItem {
 | 
				
			||||||
 | 
					    protected render(
 | 
				
			||||||
 | 
					        canvas: MotaOffscreenCanvas2D,
 | 
				
			||||||
 | 
					        transform: Transform
 | 
				
			||||||
 | 
					    ): void {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										261
									
								
								src/plugin/boss/palaceBossProjectile.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										261
									
								
								src/plugin/boss/palaceBossProjectile.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,261 @@
 | 
				
			|||||||
 | 
					import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
 | 
				
			||||||
 | 
					import { Transform } from '@/core/render';
 | 
				
			||||||
 | 
					import { IStateDamageable } from '@/game/state/interface';
 | 
				
			||||||
 | 
					import { Hitbox, Projectile } from './barrage';
 | 
				
			||||||
 | 
					import type { PalaceBoss } from './palaceBoss';
 | 
				
			||||||
 | 
					import { clamp } from '../utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function popDamage(damage: number, boss: PalaceBoss, color: string) {
 | 
				
			||||||
 | 
					    const { x, y } = core.status.hero.loc;
 | 
				
			||||||
 | 
					    boss.pop.addPop(
 | 
				
			||||||
 | 
					        (-damage).toString(),
 | 
				
			||||||
 | 
					        1000,
 | 
				
			||||||
 | 
					        x * 32 + 16,
 | 
				
			||||||
 | 
					        y * 32 + 16,
 | 
				
			||||||
 | 
					        color
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ISplitData {
 | 
				
			||||||
 | 
					    split: boolean;
 | 
				
			||||||
 | 
					    /** 分裂时刻,以弹幕被创建时刻为基准 */
 | 
				
			||||||
 | 
					    time: number;
 | 
				
			||||||
 | 
					    /** 分裂起始角度,以该弹幕朝向方向为 0 */
 | 
				
			||||||
 | 
					    startAngle: number;
 | 
				
			||||||
 | 
					    /** 分裂终止角度,以该弹幕朝向方向为 0 */
 | 
				
			||||||
 | 
					    endAngle: number;
 | 
				
			||||||
 | 
					    /** 每秒加速度 */
 | 
				
			||||||
 | 
					    acc: number;
 | 
				
			||||||
 | 
					    /** 初始速度 */
 | 
				
			||||||
 | 
					    startVel: number;
 | 
				
			||||||
 | 
					    /** 终止速度 */
 | 
				
			||||||
 | 
					    endVel: number;
 | 
				
			||||||
 | 
					    /** 持续时长 */
 | 
				
			||||||
 | 
					    lastTime: number;
 | 
				
			||||||
 | 
					    /** 分裂数量 */
 | 
				
			||||||
 | 
					    count: number;
 | 
				
			||||||
 | 
					    /** 这个弹幕分裂产生的弹幕的分裂信息,不填则表示产生的弹幕不会分裂 */
 | 
				
			||||||
 | 
					    data?: ISplitData;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class SplittableBall extends Projectile<PalaceBoss> {
 | 
				
			||||||
 | 
					    damage: number = 10000;
 | 
				
			||||||
 | 
					    hitbox: Hitbox.Circle = new Hitbox.Circle(0, 0, 8);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static ball: Map<string, MotaOffscreenCanvas2D> = new Map();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private damaged: boolean = false;
 | 
				
			||||||
 | 
					    private splitData?: ISplitData;
 | 
				
			||||||
 | 
					    private last: number = 60_000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** 角度,水平向右为 0,顺时针旋转一圈为 Math.PI * 2 */
 | 
				
			||||||
 | 
					    private angle: number = 0;
 | 
				
			||||||
 | 
					    /** 每秒加速度 */
 | 
				
			||||||
 | 
					    private acc: number = 0;
 | 
				
			||||||
 | 
					    /** 初始速度,每秒多少像素 */
 | 
				
			||||||
 | 
					    private startVel: number = 0;
 | 
				
			||||||
 | 
					    /** 终止速度 */
 | 
				
			||||||
 | 
					    private endVel: number = 0;
 | 
				
			||||||
 | 
					    /** 弹幕颜色 */
 | 
				
			||||||
 | 
					    private color?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private startVelX: number = 0;
 | 
				
			||||||
 | 
					    private startVelY: number = 0;
 | 
				
			||||||
 | 
					    private endVelX: number = 0;
 | 
				
			||||||
 | 
					    private endVelY: number = 0;
 | 
				
			||||||
 | 
					    private vx: number = 0;
 | 
				
			||||||
 | 
					    private vy: number = 0;
 | 
				
			||||||
 | 
					    // 加速度
 | 
				
			||||||
 | 
					    private ax: number = 0;
 | 
				
			||||||
 | 
					    private ay: number = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static init(colors: Record<string, string[]>) {
 | 
				
			||||||
 | 
					        this.ball.clear();
 | 
				
			||||||
 | 
					        for (const [key, color] of Object.entries(colors)) {
 | 
				
			||||||
 | 
					            const canvas = new MotaOffscreenCanvas2D();
 | 
				
			||||||
 | 
					            canvas.size(32, 32);
 | 
				
			||||||
 | 
					            canvas.withGameScale(true);
 | 
				
			||||||
 | 
					            canvas.setHD(true);
 | 
				
			||||||
 | 
					            const ctx = canvas.ctx;
 | 
				
			||||||
 | 
					            const gradient = ctx.createRadialGradient(16, 16, 8, 16, 16, 16);
 | 
				
			||||||
 | 
					            const step = 1 / (color.length - 1);
 | 
				
			||||||
 | 
					            for (let i = 0; i < color.length; i++) {
 | 
				
			||||||
 | 
					                gradient.addColorStop(i * step, color[i]);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            ctx.fillStyle = gradient;
 | 
				
			||||||
 | 
					            ctx.arc(16, 16, 16, 0, Math.PI * 2);
 | 
				
			||||||
 | 
					            ctx.fill();
 | 
				
			||||||
 | 
					            canvas.freeze();
 | 
				
			||||||
 | 
					            this.ball.set(key, canvas);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static end() {
 | 
				
			||||||
 | 
					        this.ball.forEach(v => {
 | 
				
			||||||
 | 
					            v.clear();
 | 
				
			||||||
 | 
					            v.delete();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        this.ball.clear();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 设置持续时长
 | 
				
			||||||
 | 
					     * @param time 持续时长
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    setLastTime(time: number) {
 | 
				
			||||||
 | 
					        this.last = time;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 设置这个弹幕的分裂数据
 | 
				
			||||||
 | 
					     * @param data 分裂数据,不填表示该弹幕不会分裂
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    setSplitData(data?: ISplitData) {
 | 
				
			||||||
 | 
					        this.splitData = data;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 计算速度分量信息
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private calVel() {
 | 
				
			||||||
 | 
					        const sin = Math.sin(this.angle);
 | 
				
			||||||
 | 
					        const cos = Math.cos(this.angle);
 | 
				
			||||||
 | 
					        const vel = Math.hypot(this.vx, this.vy);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.startVelX = this.startVel * cos;
 | 
				
			||||||
 | 
					        this.startVelY = this.startVel * sin;
 | 
				
			||||||
 | 
					        this.endVelX = this.endVel * cos;
 | 
				
			||||||
 | 
					        this.endVelY = this.endVel * sin;
 | 
				
			||||||
 | 
					        this.ax = this.acc * cos;
 | 
				
			||||||
 | 
					        this.ay = this.acc * sin;
 | 
				
			||||||
 | 
					        this.vx = vel * cos;
 | 
				
			||||||
 | 
					        this.vy = vel * sin;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 设置弹幕速度朝向
 | 
				
			||||||
 | 
					     * @param angle 朝向
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    setAngle(angle: number) {
 | 
				
			||||||
 | 
					        this.angle = angle;
 | 
				
			||||||
 | 
					        this.calVel();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 设置速度
 | 
				
			||||||
 | 
					     * @param start 起始速度
 | 
				
			||||||
 | 
					     * @param end 终止速度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    setVel(start: number, end: number) {
 | 
				
			||||||
 | 
					        this.startVel = start;
 | 
				
			||||||
 | 
					        this.endVel = end;
 | 
				
			||||||
 | 
					        this.calVel();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 设置加速度
 | 
				
			||||||
 | 
					     * @param acc 加速度,每秒加速多少像素
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    setAcc(acc: number) {
 | 
				
			||||||
 | 
					        this.acc = acc;
 | 
				
			||||||
 | 
					        this.calVel();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 设置弹幕的颜色
 | 
				
			||||||
 | 
					     * @param color 颜色
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    setColor(color: string) {
 | 
				
			||||||
 | 
					        this.color = color;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    isIntersect(hitbox: Hitbox.HitboxType): boolean {
 | 
				
			||||||
 | 
					        if (hitbox instanceof Hitbox.Circle) {
 | 
				
			||||||
 | 
					            return Hitbox.checkCircleCircle(hitbox, this.hitbox);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    updateHitbox(x: number, y: number): void {
 | 
				
			||||||
 | 
					        this.hitbox.setCenter(x, y);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    doDamage(target: IStateDamageable): boolean {
 | 
				
			||||||
 | 
					        if (this.damaged) return false;
 | 
				
			||||||
 | 
					        target.hp -= this.damage;
 | 
				
			||||||
 | 
					        this.damaged = true;
 | 
				
			||||||
 | 
					        core.drawHeroAnimate('hand');
 | 
				
			||||||
 | 
					        popDamage(this.damage, this.boss, '#ff8180');
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private split(boss: PalaceBoss) {
 | 
				
			||||||
 | 
					        if (!this.splitData?.split) return;
 | 
				
			||||||
 | 
					        const {
 | 
				
			||||||
 | 
					            startAngle,
 | 
				
			||||||
 | 
					            endAngle,
 | 
				
			||||||
 | 
					            startVel,
 | 
				
			||||||
 | 
					            endVel,
 | 
				
			||||||
 | 
					            acc,
 | 
				
			||||||
 | 
					            lastTime,
 | 
				
			||||||
 | 
					            count,
 | 
				
			||||||
 | 
					            data
 | 
				
			||||||
 | 
					        } = this.splitData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const sa = this.angle + startAngle;
 | 
				
			||||||
 | 
					        const ea = this.angle + endAngle;
 | 
				
			||||||
 | 
					        const step = (ea - sa - 1) / count;
 | 
				
			||||||
 | 
					        const { x, y } = this.hitbox;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (let i = 0; i < count; i++) {
 | 
				
			||||||
 | 
					            const proj = boss.createProjectile(SplittableBall, x, y);
 | 
				
			||||||
 | 
					            proj.setAngle(sa + step * i);
 | 
				
			||||||
 | 
					            proj.setAcc(acc);
 | 
				
			||||||
 | 
					            proj.setVel(startVel, endVel);
 | 
				
			||||||
 | 
					            proj.setLastTime(lastTime);
 | 
				
			||||||
 | 
					            proj.setSplitData(data);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ai(boss: PalaceBoss, time: number, frame: number, dt: number): void {
 | 
				
			||||||
 | 
					        if (this.splitData?.split) {
 | 
				
			||||||
 | 
					            if (time > this.splitData.time) {
 | 
				
			||||||
 | 
					                this.split(boss);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (time > this.last) {
 | 
				
			||||||
 | 
					            this.destroy();
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const p = dt / 1000;
 | 
				
			||||||
 | 
					        this.vx += this.ax * p;
 | 
				
			||||||
 | 
					        this.vy += this.ay * p;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const sx = Math.sign(this.vx);
 | 
				
			||||||
 | 
					        const sy = Math.sign(this.vy);
 | 
				
			||||||
 | 
					        const cx = clamp(
 | 
				
			||||||
 | 
					            Math.abs(this.vx),
 | 
				
			||||||
 | 
					            Math.abs(this.startVelX),
 | 
				
			||||||
 | 
					            Math.abs(this.endVelX)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        const cy = clamp(
 | 
				
			||||||
 | 
					            Math.abs(this.vy),
 | 
				
			||||||
 | 
					            Math.abs(this.startVelY),
 | 
				
			||||||
 | 
					            Math.abs(this.endVelY)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        this.vx = cx * sx;
 | 
				
			||||||
 | 
					        this.vy = cy * sy;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const { x, y } = this.hitbox;
 | 
				
			||||||
 | 
					        this.setPosition(x + this.vx * p, y + this.vy * p);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    render(canvas: MotaOffscreenCanvas2D, transform: Transform): void {
 | 
				
			||||||
 | 
					        if (!this.color) return;
 | 
				
			||||||
 | 
					        const texture = SplittableBall.ball.get(this.color);
 | 
				
			||||||
 | 
					        if (!texture) return;
 | 
				
			||||||
 | 
					        const ctx = canvas.ctx;
 | 
				
			||||||
 | 
					        ctx.drawImage(texture.canvas, this.x - 16, this.y - 16, 32, 32);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -179,6 +179,8 @@ export class TowerBoss extends BarrageBoss {
 | 
				
			|||||||
        this.healthBar.remove();
 | 
					        this.healthBar.remove();
 | 
				
			||||||
        this.word.remove();
 | 
					        this.word.remove();
 | 
				
			||||||
        this.main.remove();
 | 
					        this.main.remove();
 | 
				
			||||||
 | 
					        this.main.destroy();
 | 
				
			||||||
 | 
					        this.healthBar.destroy();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const event = this.group.getLayer('event');
 | 
					        const event = this.group.getLayer('event');
 | 
				
			||||||
        const hero = event?.getExtends('floor-hero') as HeroRenderer;
 | 
					        const hero = event?.getExtends('floor-hero') as HeroRenderer;
 | 
				
			||||||
 | 
				
			|||||||
@ -517,3 +517,11 @@ export function calStringSize(str: string) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return size;
 | 
					    return size;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function clamp(num: number, start: number, end: number) {
 | 
				
			||||||
 | 
					    const s = Math.min(start, end);
 | 
				
			||||||
 | 
					    const e = Math.max(start, end);
 | 
				
			||||||
 | 
					    if (num < s) return s;
 | 
				
			||||||
 | 
					    else if (num > e) return e;
 | 
				
			||||||
 | 
					    return num;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user