mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-02-28 17:37:07 +08:00
feat: 智慧塔boss战第一阶段
This commit is contained in:
parent
3de944fc1b
commit
d9a398a8e9
@ -192,7 +192,21 @@ main.floors.tower7=
|
|||||||
[527,527,527,527,527,527,527,527,527,527,527,527,527,527,527]
|
[527,527,527,527,527,527,527,527,527,527,527,527,527,527,527]
|
||||||
],
|
],
|
||||||
"bgmap": [
|
"bgmap": [
|
||||||
|
[526,526,526,526,526,526,526,526,526,526,526,526,526,526,526],
|
||||||
|
[526,526,526,526,526,526,526,526,526,526,526,526,526,526,526],
|
||||||
|
[526,526,526,526,526,526,526,526,526,526,526,526,526,526,526],
|
||||||
|
[526,526,526,526,526,526,526,526,526,526,526,526,526,526,526],
|
||||||
|
[526,526,526,526,526,526,526,526,526,526,526,526,526,526,526],
|
||||||
|
[526,526,526,526,526,526,526,526,526,526,526,526,526,526,526],
|
||||||
|
[526,526,526,526,526,526,526,526,526,526,526,526,526,526,526],
|
||||||
|
[526,526,526,526,526,526,526,526,526,526,526,526,526,526,526],
|
||||||
|
[526,526,526,526,526,526,526,526,526,526,526,526,526,526,526],
|
||||||
|
[526,526,526,526,526,526,526,526,526,526,526,526,526,526,526],
|
||||||
|
[526,526,526,526,526,526,526,526,526,526,526,526,526,526,526],
|
||||||
|
[526,526,526,526,526,526,526,526,526,526,526,526,526,526,526],
|
||||||
|
[526,526,526,526,526,526,526,526,526,526,526,526,526,526,526],
|
||||||
|
[526,526,526,526,526,526,526,526,526,526,526,526,526,526,526],
|
||||||
|
[526,526,526,526,526,526,526,526,526,526,526,526,526,526,526]
|
||||||
],
|
],
|
||||||
"fgmap": [
|
"fgmap": [
|
||||||
|
|
||||||
|
4
src/game/state/interface.ts
Normal file
4
src/game/state/interface.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export interface IStateDamageable {
|
||||||
|
/** 生命值 */
|
||||||
|
hp: number;
|
||||||
|
}
|
@ -1,3 +1,7 @@
|
|||||||
|
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
||||||
|
import { RenderItem, RenderItemPosition } from '@/core/render/item';
|
||||||
|
import { Transform } from '@/core/render/transform';
|
||||||
|
import { IStateDamageable } from '@/game/state/interface';
|
||||||
import { Ticker } from 'mutate-animate';
|
import { Ticker } from 'mutate-animate';
|
||||||
|
|
||||||
export abstract class BarrageBoss {
|
export abstract class BarrageBoss {
|
||||||
@ -8,7 +12,14 @@ export abstract class BarrageBoss {
|
|||||||
/** 开始时刻 */
|
/** 开始时刻 */
|
||||||
private startTime: number = 0;
|
private startTime: number = 0;
|
||||||
/** 当前帧数 */
|
/** 当前帧数 */
|
||||||
private frame: number = 0;
|
frame: number = 0;
|
||||||
|
|
||||||
|
/** 这个boss战的主渲染元素,所有弹幕都会在此之上渲染 */
|
||||||
|
abstract readonly main: BossSprite;
|
||||||
|
/** 这个boss战中勇士的碰撞箱 */
|
||||||
|
abstract readonly hitbox: Hitbox.HitboxType;
|
||||||
|
/** 勇士的状态 */
|
||||||
|
abstract readonly state: IStateDamageable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* boss的ai,战斗开始后,每帧执行一次
|
* boss的ai,战斗开始后,每帧执行一次
|
||||||
@ -19,9 +30,19 @@ export abstract class BarrageBoss {
|
|||||||
|
|
||||||
private tick = () => {
|
private tick = () => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
this.ai(now - this.startTime, this.frame++);
|
this.ai(now - this.startTime, this.frame);
|
||||||
|
this.frame++;
|
||||||
this.projectiles.forEach(v => {
|
this.projectiles.forEach(v => {
|
||||||
v.ai(this, now - v.startTime, v.frame++);
|
const time = now - v.startTime;
|
||||||
|
v.time = time;
|
||||||
|
v.ai(this, time, v.frame);
|
||||||
|
v.frame++;
|
||||||
|
if (time > 60_000) {
|
||||||
|
this.destroyProjectile(v);
|
||||||
|
}
|
||||||
|
if (v.isIntersect(this.hitbox)) {
|
||||||
|
v.doDamage(this.state);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -59,48 +80,159 @@ export abstract class BarrageBoss {
|
|||||||
* @param x 弹幕的横坐标
|
* @param x 弹幕的横坐标
|
||||||
* @param y 弹幕的纵坐标
|
* @param y 弹幕的纵坐标
|
||||||
*/
|
*/
|
||||||
createProjectile(
|
createProjectile<T extends Projectile>(
|
||||||
Proj: new (boss: BarrageBoss) => Projectile,
|
Proj: new (boss: this) => T,
|
||||||
x: number,
|
x: number,
|
||||||
y: number
|
y: number
|
||||||
) {
|
): T {
|
||||||
const projectile = new Proj(this);
|
const projectile = new Proj(this);
|
||||||
projectile.setPosition(x, y);
|
projectile.setPosition(x, y);
|
||||||
return projectile;
|
return projectile;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class Projectile {
|
export abstract class BossSprite<
|
||||||
|
T extends BarrageBoss = BarrageBoss
|
||||||
|
> extends RenderItem {
|
||||||
|
/** 这个sprite所属的boss */
|
||||||
|
readonly boss: T;
|
||||||
|
|
||||||
|
constructor(type: RenderItemPosition, boss: T) {
|
||||||
|
super(type, false);
|
||||||
|
this.boss = boss;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在内置渲染函数执行前渲染内容,返回false会阻止内置渲染函数执行
|
||||||
|
* @param canvas 渲染至的画布
|
||||||
|
* @param transform 渲染时的变换矩阵
|
||||||
|
*/
|
||||||
|
protected abstract preDraw(
|
||||||
|
canvas: MotaOffscreenCanvas2D,
|
||||||
|
transform: Transform
|
||||||
|
): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在内置渲染函数执行后渲染内容,如果preDraw返回false,也会执行本函数
|
||||||
|
* @param canvas 渲染至的画布
|
||||||
|
* @param transform 渲染时的变换矩阵
|
||||||
|
*/
|
||||||
|
protected abstract postDraw(
|
||||||
|
canvas: MotaOffscreenCanvas2D,
|
||||||
|
transform: Transform
|
||||||
|
): void;
|
||||||
|
|
||||||
|
protected render(
|
||||||
|
canvas: MotaOffscreenCanvas2D,
|
||||||
|
transform: Transform
|
||||||
|
): void {
|
||||||
|
const pre = this.preDraw(canvas, transform);
|
||||||
|
if (!pre) {
|
||||||
|
this.postDraw(canvas, transform);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.renderProjectiles(canvas, transform);
|
||||||
|
this.postDraw(canvas, transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染所有弹幕
|
||||||
|
* @param canvas 渲染至的画布
|
||||||
|
* @param transform 渲染时的变换矩阵
|
||||||
|
*/
|
||||||
|
protected renderProjectiles(
|
||||||
|
canvas: MotaOffscreenCanvas2D,
|
||||||
|
transform: Transform
|
||||||
|
) {
|
||||||
|
this.boss.projectiles.forEach(v => {
|
||||||
|
v.render(canvas, transform);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class Projectile<T extends BarrageBoss = BarrageBoss> {
|
||||||
/** 这个弹幕从属的boss */
|
/** 这个弹幕从属的boss */
|
||||||
boss: BarrageBoss;
|
boss: T;
|
||||||
x: number = 0;
|
/** 这个弹幕的伤害 */
|
||||||
y: number = 0;
|
abstract damage: number;
|
||||||
|
|
||||||
|
private _x: number = 0;
|
||||||
|
get x(): number {
|
||||||
|
return this._x;
|
||||||
|
}
|
||||||
|
set x(v: number) {
|
||||||
|
this._x = v;
|
||||||
|
this.updateHitbox(v, this._y);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _y: number = 0;
|
||||||
|
get y(): number {
|
||||||
|
return this._y;
|
||||||
|
}
|
||||||
|
set y(v: number) {
|
||||||
|
this._y = v;
|
||||||
|
this.updateHitbox(this._x, v);
|
||||||
|
}
|
||||||
|
|
||||||
/** 弹幕的生成时刻 */
|
/** 弹幕的生成时刻 */
|
||||||
startTime: number = Date.now();
|
startTime: number = Date.now();
|
||||||
/** 弹幕当前帧数 */
|
/** 弹幕当前帧数 */
|
||||||
frame: number = 0;
|
frame: number = 0;
|
||||||
|
/** 当前弹幕持续时长 */
|
||||||
|
time: number = 0;
|
||||||
|
|
||||||
constructor(boss: BarrageBoss) {
|
/** 这个弹幕的碰撞箱 */
|
||||||
|
abstract hitbox: Hitbox.HitboxType;
|
||||||
|
|
||||||
|
constructor(boss: T) {
|
||||||
this.boss = boss;
|
this.boss = boss;
|
||||||
boss.projectiles.add(this);
|
boss.projectiles.add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断一个碰撞箱是否与本弹幕的碰撞箱有交叉。
|
||||||
|
* 此判断应该具有对称性,如果用A检测B发生碰撞,那么用B检测A也应该发生碰撞。
|
||||||
|
* @param hitbox 要检测的碰撞箱
|
||||||
|
*/
|
||||||
|
abstract isIntersect(hitbox: Hitbox.HitboxType): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当弹幕的横纵坐标改变时,更新碰撞箱
|
||||||
|
* @param x 弹幕的横坐标
|
||||||
|
* @param y 弹幕的纵坐标
|
||||||
|
*/
|
||||||
|
abstract updateHitbox(x: number, y: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对一个目标造成伤害
|
||||||
|
* @param target 伤害目标
|
||||||
|
* @returns 是否成功对目标造成伤害
|
||||||
|
*/
|
||||||
|
abstract doDamage(target: IStateDamageable): boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置这个弹幕的位置
|
* 设置这个弹幕的位置
|
||||||
*/
|
*/
|
||||||
setPosition(x: number, y: number) {
|
setPosition(x: number, y: number) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
|
this.updateHitbox(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 这个弹幕的ai,每帧执行一次,直至被销毁
|
* 这个弹幕的ai,每帧执行一次,直至被销毁,在1分钟后会强制被摧毁
|
||||||
* @param boss 从属的boss
|
* @param boss 从属的boss
|
||||||
* @param time 从弹幕生成开始算起至现在经过了多长时间
|
* @param time 从弹幕生成开始算起至现在经过了多长时间
|
||||||
* @param frame 从弹幕生成开始算起至现在经过了多少帧,即当前是第几帧
|
* @param frame 从弹幕生成开始算起至现在经过了多少帧,即当前是第几帧
|
||||||
*/
|
*/
|
||||||
abstract ai(boss: BarrageBoss, time: number, frame: number): void;
|
abstract ai(boss: T, time: number, frame: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 这个弹幕的渲染函数,原则上一个boss的弹幕应该全部画在同一层,而且渲染前画布不进行矩阵变换
|
||||||
|
* @param canvas 渲染至的画布
|
||||||
|
* @param transform 渲染时的变换矩阵
|
||||||
|
*/
|
||||||
|
abstract render(canvas: MotaOffscreenCanvas2D, transform: Transform): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 摧毁这个弹幕
|
* 摧毁这个弹幕
|
||||||
@ -111,18 +243,15 @@ export abstract class Projectile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export namespace Hitbox {
|
export namespace Hitbox {
|
||||||
export class Line {
|
export type HitboxType = Line | Rect | Circle;
|
||||||
x1: number;
|
|
||||||
y1: number;
|
|
||||||
x2: number;
|
|
||||||
y2: number;
|
|
||||||
|
|
||||||
constructor(x1: number, y1: number, x2: number, y2: number) {
|
export class Line {
|
||||||
this.x1 = x1;
|
constructor(
|
||||||
this.x2 = x2;
|
public x1: number,
|
||||||
this.y1 = y1;
|
public y1: number,
|
||||||
this.y2 = y2;
|
public x2: number,
|
||||||
}
|
public y2: number
|
||||||
|
) {}
|
||||||
|
|
||||||
setPoint1(x: number, y: number) {
|
setPoint1(x: number, y: number) {
|
||||||
this.x1 = x;
|
this.x1 = x;
|
||||||
@ -136,15 +265,11 @@ export namespace Hitbox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Circle {
|
export class Circle {
|
||||||
x: number;
|
constructor(
|
||||||
y: number;
|
public x: number,
|
||||||
radius: number;
|
public y: number,
|
||||||
|
public radius: number
|
||||||
constructor(x: number, y: number, radius: number) {
|
) {}
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.radius = radius;
|
|
||||||
}
|
|
||||||
|
|
||||||
setRadius(radius: number) {
|
setRadius(radius: number) {
|
||||||
this.radius = radius;
|
this.radius = radius;
|
||||||
@ -157,17 +282,12 @@ export namespace Hitbox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Rect {
|
export class Rect {
|
||||||
x: number;
|
constructor(
|
||||||
y: number;
|
public x: number,
|
||||||
w: number;
|
public y: number,
|
||||||
h: number;
|
public w: number,
|
||||||
|
public h: number
|
||||||
constructor(x: number, y: number, w: number, h: number) {
|
) {}
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.w = w;
|
|
||||||
this.h = h;
|
|
||||||
}
|
|
||||||
|
|
||||||
setPosition(x: number, y: number) {
|
setPosition(x: number, y: number) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
|
@ -1,12 +1,34 @@
|
|||||||
import { Shader } from '@/core/render/shader';
|
import { Shader } from '@/core/render/shader';
|
||||||
import { PointEffect } from '../fx/pointShader';
|
import { PointEffect } from '../fx/pointShader';
|
||||||
import { BarrageBoss } from './barrage';
|
import { BarrageBoss, BossSprite, Hitbox } from './barrage';
|
||||||
import { MotaRenderer } from '@/core/render/render';
|
import { MotaRenderer } from '@/core/render/render';
|
||||||
import { LayerGroup } from '@/core/render/preset/layer';
|
import { LayerGroup } from '@/core/render/preset/layer';
|
||||||
import { RenderItem } from '@/core/render/item';
|
import { RenderItem } from '@/core/render/item';
|
||||||
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
||||||
import { Transform } from '@/core/render/transform';
|
import { Transform } from '@/core/render/transform';
|
||||||
import { Animation, hyper, power, sleep, Transition } from 'mutate-animate';
|
import {
|
||||||
|
Animation,
|
||||||
|
hyper,
|
||||||
|
power,
|
||||||
|
sleep,
|
||||||
|
TimingFn,
|
||||||
|
Transition
|
||||||
|
} from 'mutate-animate';
|
||||||
|
import { Container } from '@/core/render/container';
|
||||||
|
import {
|
||||||
|
ArrowProjectile,
|
||||||
|
PortalProjectile,
|
||||||
|
ProjectileDirection
|
||||||
|
} from './towerBossProjectile';
|
||||||
|
import { IStateDamageable } from '@/game/state/interface';
|
||||||
|
|
||||||
|
Mota.require('var', 'loading').once('coreInit', () => {
|
||||||
|
const shader = new Shader();
|
||||||
|
shader.size(480, 480);
|
||||||
|
shader.setHD(true);
|
||||||
|
TowerBoss.shader = shader;
|
||||||
|
TowerBoss.effect.create(shader, 40);
|
||||||
|
});
|
||||||
|
|
||||||
const enum TowerBossStage {
|
const enum TowerBossStage {
|
||||||
/** 开场白阶段 */
|
/** 开场白阶段 */
|
||||||
@ -19,6 +41,8 @@ const enum TowerBossStage {
|
|||||||
Stage3,
|
Stage3,
|
||||||
Dialogue3,
|
Dialogue3,
|
||||||
Stage4,
|
Stage4,
|
||||||
|
Stage5,
|
||||||
|
Stage6,
|
||||||
|
|
||||||
End
|
End
|
||||||
}
|
}
|
||||||
@ -29,22 +53,40 @@ const enum HealthBarStatus {
|
|||||||
End
|
End
|
||||||
}
|
}
|
||||||
|
|
||||||
Mota.require('var', 'loading').once('coreInit', () => {
|
interface TowerBossAttack {
|
||||||
const shader = new Shader();
|
x: number;
|
||||||
shader.size(480, 480);
|
y: number;
|
||||||
shader.setHD(true);
|
damage: number;
|
||||||
TowerBoss.shader = shader;
|
/** 生成时刻 */
|
||||||
TowerBoss.effect.create(shader, 40);
|
spwan: number;
|
||||||
});
|
/** 持续时长 */
|
||||||
|
last: number;
|
||||||
|
}
|
||||||
|
|
||||||
class TowerBoss extends BarrageBoss {
|
interface AttackCircleRenderable {
|
||||||
|
cx: number;
|
||||||
|
cy: number;
|
||||||
|
alpha: number;
|
||||||
|
lineOffset: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TowerBoss extends BarrageBoss {
|
||||||
static effect: PointEffect = new PointEffect();
|
static effect: PointEffect = new PointEffect();
|
||||||
static shader: Shader;
|
static shader: Shader;
|
||||||
|
|
||||||
/** boss战阶段 */
|
/** boss战阶段 */
|
||||||
stage: TowerBossStage = TowerBossStage.Prologue;
|
stage: TowerBossStage = TowerBossStage.Prologue;
|
||||||
|
/** 当前boss血量 */
|
||||||
|
hp: number = 10000;
|
||||||
|
/** 当前时刻 */
|
||||||
|
time: number = 0;
|
||||||
|
|
||||||
private hp: number = 10000;
|
readonly hitbox: Hitbox.Rect;
|
||||||
|
readonly state: IStateDamageable;
|
||||||
|
readonly main: BossEffect;
|
||||||
|
|
||||||
|
/** 攻击位点 */
|
||||||
|
private attackLoc: Set<TowerBossAttack> = new Set();
|
||||||
|
|
||||||
/** 血条显示元素 */
|
/** 血条显示元素 */
|
||||||
private healthBar: HealthBar;
|
private healthBar: HealthBar;
|
||||||
@ -52,20 +94,398 @@ class TowerBoss extends BarrageBoss {
|
|||||||
private word: Word;
|
private word: Word;
|
||||||
/** 楼层渲染元素 */
|
/** 楼层渲染元素 */
|
||||||
private group: LayerGroup;
|
private group: LayerGroup;
|
||||||
|
/** 楼层渲染容器 */
|
||||||
|
private mapDraw: Container;
|
||||||
|
|
||||||
|
/** 每个阶段的进度,具体定义参考 ai 函数开头 */
|
||||||
|
private stageProgress: number = 0;
|
||||||
|
/** 当前阶段的开始时刻 */
|
||||||
|
private stageStartTime: number = 0;
|
||||||
|
/** 每一阶段的攻击boss次数 */
|
||||||
|
private attackTime: number = 0;
|
||||||
|
/** 攻击boss的红圈间隔时长 */
|
||||||
|
private attackInterval: number = 7000;
|
||||||
|
private attackIn: TimingFn = hyper('sin', 'out');
|
||||||
|
private attackOut: TimingFn = hyper('sin', 'in');
|
||||||
|
|
||||||
|
/** 使用技能1 智慧之矢 的次数 */
|
||||||
|
private skill1Time: number = 0;
|
||||||
|
/** 使用技能2 随机传送 的次数 */
|
||||||
|
private skill2Time: number = 0;
|
||||||
|
/** 使用技能3 冰锥 的次数 */
|
||||||
|
private skill3Time: number = 0;
|
||||||
|
/** 技能1的释放间隔 */
|
||||||
|
private skill1Interval: number = 10000;
|
||||||
|
/** 技能2的释放间隔 */
|
||||||
|
private skill2Interval: number = 7000;
|
||||||
|
/** 技能3的释放间隔 */
|
||||||
|
private skill3Interval: number = 13000;
|
||||||
|
|
||||||
|
/** 使用技能4 随机闪电 的次数 */
|
||||||
|
private skill4Time: number = 0;
|
||||||
|
/** 使用技能5 球形闪电 的次数 */
|
||||||
|
private skill5Time: number = 0;
|
||||||
|
/** 技能4的释放间隔 */
|
||||||
|
private skill4Interval: number = 4000;
|
||||||
|
/** 技能5的释放间隔 */
|
||||||
|
private skill5Interval: number = 12000;
|
||||||
|
|
||||||
|
/** 使用技能6 炸弹 的次数 */
|
||||||
|
private skill6Time: number = 0;
|
||||||
|
/** 使用技能7 连锁闪电 的次数 */
|
||||||
|
private skill7Time: number = 0;
|
||||||
|
/** 技能6的释放间隔 */
|
||||||
|
private skill6Interval: number = 500;
|
||||||
|
/** 技能7的释放间隔 */
|
||||||
|
private skill7Interval: number = 10000;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.healthBar = new HealthBar('absolute');
|
this.healthBar = new HealthBar('absolute');
|
||||||
this.word = new Word('absolute');
|
this.word = new Word('absolute');
|
||||||
|
this.main = new BossEffect('absolute', this);
|
||||||
const render = MotaRenderer.get('render-main')!;
|
const render = MotaRenderer.get('render-main')!;
|
||||||
this.group = render.getElementById('layer-main') as LayerGroup;
|
this.group = render.getElementById('layer-main') as LayerGroup;
|
||||||
|
this.mapDraw = render.getElementById('map-draw') as Container;
|
||||||
|
|
||||||
this.healthBar.init();
|
this.healthBar.init();
|
||||||
this.word.init();
|
this.word.init();
|
||||||
|
this.main.init();
|
||||||
|
|
||||||
|
this.healthBar.append(this.group);
|
||||||
|
this.word.append(this.group);
|
||||||
|
this.main.append(this.group);
|
||||||
|
|
||||||
|
const { x, y } = core.status.hero.loc;
|
||||||
|
const cell = 32;
|
||||||
|
this.hitbox = new Hitbox.Rect(x * cell + 4, y * cell + 16, 24, 32);
|
||||||
|
this.state = core.status.hero;
|
||||||
}
|
}
|
||||||
|
|
||||||
ai(time: number, frame: number): void {}
|
override start() {
|
||||||
|
super.start();
|
||||||
|
this.group.remove();
|
||||||
|
this.group.append(TowerBoss.shader);
|
||||||
|
TowerBoss.shader.append(this.mapDraw);
|
||||||
|
|
||||||
|
ArrowProjectile.init();
|
||||||
|
PortalProjectile.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
override end() {
|
||||||
|
super.end();
|
||||||
|
TowerBoss.shader.remove();
|
||||||
|
this.group.append(this.mapDraw);
|
||||||
|
this.healthBar.remove();
|
||||||
|
this.word.remove();
|
||||||
|
this.main.remove();
|
||||||
|
|
||||||
|
ArrowProjectile.end();
|
||||||
|
PortalProjectile.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于全局检测,例如受伤、攻击boss等
|
||||||
|
*/
|
||||||
|
check(time: number) {
|
||||||
|
this.checkLose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkLose() {
|
||||||
|
if (core.status.hero.hp < 0) {
|
||||||
|
core.lose();
|
||||||
|
core.updateStatusBar();
|
||||||
|
this.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 攻击boss
|
||||||
|
* @param damage 造成的伤害
|
||||||
|
*/
|
||||||
|
attackBoss(damage: number) {
|
||||||
|
this.hp -= damage;
|
||||||
|
this.healthBar.set(this.hp);
|
||||||
|
// 先用drawAnimate凑活一下,等下个版本提供更好的 api
|
||||||
|
if (this.stage === TowerBossStage.Stage4) {
|
||||||
|
core.drawAnimate('hand', 7, 2);
|
||||||
|
} else if (this.stage === TowerBossStage.Stage5) {
|
||||||
|
core.drawAnimate('hand', 7, 3);
|
||||||
|
} else if (this.stage === TowerBossStage.Stage6) {
|
||||||
|
core.drawAnimate('hand', 7, 4);
|
||||||
|
} else {
|
||||||
|
core.drawAnimate('hand', 7, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加攻击boss的圆圈
|
||||||
|
* @param last 持续时长
|
||||||
|
* @param damage 造成的伤害
|
||||||
|
*/
|
||||||
|
addAttackCircle(last: number, damage: number) {
|
||||||
|
let nx = 0;
|
||||||
|
let ny = 0;
|
||||||
|
if (this.stage === TowerBossStage.Stage4) {
|
||||||
|
nx = Math.floor(Math.random() * 11 + 2);
|
||||||
|
ny = Math.floor(Math.random() * 11 + 2);
|
||||||
|
} else if (this.stage === TowerBossStage.Stage5) {
|
||||||
|
nx = Math.floor(Math.random() * 9 + 3);
|
||||||
|
ny = Math.floor(Math.random() * 9 + 3);
|
||||||
|
} else if (this.stage === TowerBossStage.Stage6) {
|
||||||
|
nx = Math.floor(Math.random() * 7 + 4);
|
||||||
|
ny = Math.floor(Math.random() * 7 + 4);
|
||||||
|
} else {
|
||||||
|
nx = Math.floor(Math.random() * 13 + 1);
|
||||||
|
ny = Math.floor(Math.random() * 13 + 1);
|
||||||
|
}
|
||||||
|
const obj: TowerBossAttack = {
|
||||||
|
x: nx,
|
||||||
|
y: ny,
|
||||||
|
spwan: this.time,
|
||||||
|
damage,
|
||||||
|
last
|
||||||
|
};
|
||||||
|
this.attackLoc.add(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAttackCircleRenderable(): AttackCircleRenderable[] {
|
||||||
|
return [...this.attackLoc].map(v => {
|
||||||
|
const progress = (this.time - v.spwan) / v.last;
|
||||||
|
let alpha = 1;
|
||||||
|
let offset = 0;
|
||||||
|
if (progress < 0.1) {
|
||||||
|
alpha = progress * 10;
|
||||||
|
offset = 32 * this.attackIn(10 * (0.1 - progress));
|
||||||
|
} else if (progress > 0.9) {
|
||||||
|
alpha = 10 * (1 - progress);
|
||||||
|
offset = 32 * this.attackOut(10 * (progress - 0.9));
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
cx: v.x * 32,
|
||||||
|
cy: v.y * 32,
|
||||||
|
alpha,
|
||||||
|
lineOffset: offset
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderAttack() {
|
||||||
|
const renderable = this.getAttackCircleRenderable();
|
||||||
|
this.main.setAttackCircle(renderable);
|
||||||
|
}
|
||||||
|
|
||||||
|
ai(time: number, frame: number): void {
|
||||||
|
this.time = time;
|
||||||
|
const fixedTime = time - this.stageStartTime;
|
||||||
|
this.main.update();
|
||||||
|
this.renderAttack();
|
||||||
|
this.check(time);
|
||||||
|
switch (this.stage) {
|
||||||
|
case TowerBossStage.Prologue:
|
||||||
|
this.aiPrologue(fixedTime, frame);
|
||||||
|
break;
|
||||||
|
case TowerBossStage.Stage1:
|
||||||
|
this.aiStage1(fixedTime, frame);
|
||||||
|
break;
|
||||||
|
case TowerBossStage.Dialogue1:
|
||||||
|
this.aiDialogue1(fixedTime, frame);
|
||||||
|
break;
|
||||||
|
case TowerBossStage.Stage2:
|
||||||
|
this.aiStage2(fixedTime, frame);
|
||||||
|
break;
|
||||||
|
case TowerBossStage.Dialogue2:
|
||||||
|
this.aiDialogue2(fixedTime, frame);
|
||||||
|
break;
|
||||||
|
case TowerBossStage.Stage3:
|
||||||
|
this.aiStage3(fixedTime, frame);
|
||||||
|
break;
|
||||||
|
case TowerBossStage.Dialogue3:
|
||||||
|
this.aiDialogue3(fixedTime, frame);
|
||||||
|
break;
|
||||||
|
case TowerBossStage.Stage4:
|
||||||
|
this.aiStage4(fixedTime, frame);
|
||||||
|
break;
|
||||||
|
case TowerBossStage.Stage5:
|
||||||
|
this.aiStage5(fixedTime, frame);
|
||||||
|
break;
|
||||||
|
case TowerBossStage.Stage6:
|
||||||
|
this.aiStage6(fixedTime, frame);
|
||||||
|
break;
|
||||||
|
case TowerBossStage.End:
|
||||||
|
this.aiEnd(fixedTime, frame);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换boss阶段
|
||||||
|
* @param stage 切换至的阶段
|
||||||
|
* @param time 在当前阶段经过的时间
|
||||||
|
*/
|
||||||
|
private changeStage(stage: TowerBossStage, time: number) {
|
||||||
|
this.stage = stage;
|
||||||
|
this.stageStartTime += time;
|
||||||
|
this.stageProgress = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private aiPrologue(time: number, frame: number) {
|
||||||
|
// stageProgress:
|
||||||
|
// 0: 开始; 1: 开始血条动画
|
||||||
|
|
||||||
|
this.healthBar.showStart();
|
||||||
|
this.stageProgress = 1;
|
||||||
|
|
||||||
|
if (time > 1500) {
|
||||||
|
this.changeStage(TowerBossStage.Stage1, time);
|
||||||
|
this.attackTime = 2;
|
||||||
|
this.skill1Time = 1;
|
||||||
|
this.skill2Time = 1;
|
||||||
|
this.skill3Time = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async releaseSkill1() {
|
||||||
|
const locs = new Set<number>();
|
||||||
|
const count = Math.ceil(Math.random() * 8) + 4;
|
||||||
|
let i = 0;
|
||||||
|
while (i < count) {
|
||||||
|
const dir = Math.floor(Math.random() * 2);
|
||||||
|
const pos = Math.floor(Math.random() * 13 + 1);
|
||||||
|
const loc = pos + dir * 13;
|
||||||
|
if (!locs.has(loc)) continue;
|
||||||
|
i++;
|
||||||
|
locs.add(loc);
|
||||||
|
const proj = this.createProjectile(ArrowProjectile, 0, 0);
|
||||||
|
proj.setData(dir);
|
||||||
|
if (dir === ProjectileDirection.Horizontal) {
|
||||||
|
proj.setPosition(480 - 32, pos * 32 + 32);
|
||||||
|
} else {
|
||||||
|
proj.setPosition(pos * 32 + 32, 480 - 32);
|
||||||
|
}
|
||||||
|
await sleep(200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseSkill2() {
|
||||||
|
const x = Math.floor(Math.random() * 13 + 1);
|
||||||
|
const y = Math.floor(Math.random() * 13 + 1);
|
||||||
|
const proj = this.createProjectile(PortalProjectile, 0, 0);
|
||||||
|
proj.setTarget(x, y);
|
||||||
|
proj.createEffect(TowerBoss.effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
async releaseSkill3() {}
|
||||||
|
|
||||||
|
private aiStage1(time: number, frame: number) {
|
||||||
|
// stageProgress:
|
||||||
|
// 0: 开始; 1,2,3,4: 对应对话
|
||||||
|
|
||||||
|
const skill1Release = this.skill1Time * this.skill1Interval;
|
||||||
|
const skill2Release = this.skill2Time * this.skill2Interval;
|
||||||
|
const skill3Release = this.skill3Time * this.skill3Interval;
|
||||||
|
const attack = this.attackTime * this.attackInterval;
|
||||||
|
|
||||||
|
if (time > skill1Release) {
|
||||||
|
this.releaseSkill1();
|
||||||
|
this.skill1Time++;
|
||||||
|
}
|
||||||
|
if (time > skill2Release) {
|
||||||
|
this.releaseSkill2();
|
||||||
|
this.skill2Time++;
|
||||||
|
}
|
||||||
|
if (time > skill3Release) {
|
||||||
|
this.releaseSkill3();
|
||||||
|
this.skill3Time++;
|
||||||
|
}
|
||||||
|
if (time > attack) {
|
||||||
|
this.addAttackCircle(3000, 500);
|
||||||
|
this.attackTime++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hp <= 7000) {
|
||||||
|
this.changeStage(TowerBossStage.Dialogue1, time);
|
||||||
|
this.attackTime = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private aiDialogue1(time: number, frame: number) {}
|
||||||
|
|
||||||
|
private aiStage2(time: number, frame: number) {}
|
||||||
|
|
||||||
|
private aiDialogue2(time: number, frame: number) {}
|
||||||
|
|
||||||
|
private aiStage3(time: number, frame: number) {}
|
||||||
|
|
||||||
|
private aiDialogue3(time: number, frame: number) {}
|
||||||
|
|
||||||
|
private aiStage4(time: number, frame: number) {}
|
||||||
|
|
||||||
|
private aiStage5(time: number, frame: number) {}
|
||||||
|
|
||||||
|
private aiStage6(time: number, frame: number) {}
|
||||||
|
|
||||||
|
private aiEnd(time: number, frame: number) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BossEffect extends BossSprite<TowerBoss> {
|
||||||
|
private attackCircle: AttackCircleRenderable[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
this.size(480, 480);
|
||||||
|
this.setHD(true);
|
||||||
|
this.setZIndex(80);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置攻击boss圆圈的渲染信息
|
||||||
|
*/
|
||||||
|
setAttackCircle(renderable: AttackCircleRenderable[]) {
|
||||||
|
this.attackCircle = renderable;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected preDraw(
|
||||||
|
canvas: MotaOffscreenCanvas2D,
|
||||||
|
transform: Transform
|
||||||
|
): boolean {
|
||||||
|
this.renderAttackCircle(canvas);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected postDraw(
|
||||||
|
canvas: MotaOffscreenCanvas2D,
|
||||||
|
transform: Transform
|
||||||
|
): void {}
|
||||||
|
|
||||||
|
private renderAttackCircle(canvas: MotaOffscreenCanvas2D) {
|
||||||
|
const ctx = canvas.ctx;
|
||||||
|
ctx.strokeStyle = '#ffe229';
|
||||||
|
ctx.fillStyle = '#ffe229';
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
this.attackCircle.forEach(({ cx, cy, lineOffset, alpha }) => {
|
||||||
|
ctx.globalAlpha = alpha;
|
||||||
|
ctx.beginPath();
|
||||||
|
const offset = lineOffset + 8;
|
||||||
|
ctx.arc(cx, cy, 2, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(cx, cy, offset, 0, Math.PI * 2);
|
||||||
|
ctx.moveTo(cx + offset, cy);
|
||||||
|
ctx.lineTo(cx + offset + 16, cy);
|
||||||
|
ctx.moveTo(cx, cy + offset);
|
||||||
|
ctx.lineTo(cx, cy + offset + 16);
|
||||||
|
ctx.moveTo(cx - offset, cy);
|
||||||
|
ctx.lineTo(cx - offset - 16, cy);
|
||||||
|
ctx.moveTo(cx, cy - offset);
|
||||||
|
ctx.lineTo(cx, cy - offset - 16);
|
||||||
|
ctx.stroke();
|
||||||
|
});
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TextRenderable {
|
interface TextRenderable {
|
||||||
@ -80,8 +500,6 @@ class Word extends RenderItem {
|
|||||||
|
|
||||||
/** 当前正在显示的文字 */
|
/** 当前正在显示的文字 */
|
||||||
private showing: string = '';
|
private showing: string = '';
|
||||||
/** 是否已经显示完毕 */
|
|
||||||
private showEnd: boolean = true;
|
|
||||||
/** 文字显示时间间隔 */
|
/** 文字显示时间间隔 */
|
||||||
private showInterval: number = 100;
|
private showInterval: number = 100;
|
||||||
/** 文字显示的虚化时长 */
|
/** 文字显示的虚化时长 */
|
||||||
@ -128,7 +546,6 @@ class Word extends RenderItem {
|
|||||||
* @param text 要显示的文字
|
* @param text 要显示的文字
|
||||||
*/
|
*/
|
||||||
showText(text: string) {
|
showText(text: string) {
|
||||||
this.showEnd = false;
|
|
||||||
this.showStartTime = Date.now();
|
this.showStartTime = Date.now();
|
||||||
this.showing = text;
|
this.showing = text;
|
||||||
}
|
}
|
||||||
|
295
src/plugin/boss/towerBossProjectile.ts
Normal file
295
src/plugin/boss/towerBossProjectile.ts
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
import { hyper, power, TimingFn } from 'mutate-animate';
|
||||||
|
import { Hitbox, Projectile } from './barrage';
|
||||||
|
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
||||||
|
import { Transform } from '@/core/render/transform';
|
||||||
|
import type { TowerBoss } from './towerBoss';
|
||||||
|
import { IStateDamageable } from '@/game/state/interface';
|
||||||
|
import { PointEffect, PointEffectType } from '../fx/pointShader';
|
||||||
|
import { isNil } from 'lodash-es';
|
||||||
|
|
||||||
|
export const enum ProjectileDirection {
|
||||||
|
Vertical,
|
||||||
|
Horizontal
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ArrowProjectile extends Projectile<TowerBoss> {
|
||||||
|
static easing?: TimingFn;
|
||||||
|
static dangerEasing?: TimingFn;
|
||||||
|
|
||||||
|
static horizontal: MotaOffscreenCanvas2D | null = null;
|
||||||
|
static vertical: MotaOffscreenCanvas2D | null = null;
|
||||||
|
|
||||||
|
hitbox: Hitbox.Rect = new Hitbox.Rect(0, 0, 102, 32);
|
||||||
|
damage: number = 1000;
|
||||||
|
|
||||||
|
/** 弹幕的方向 */
|
||||||
|
direction: ProjectileDirection = ProjectileDirection.Horizontal;
|
||||||
|
|
||||||
|
private damaged: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* boss战开始时初始化
|
||||||
|
*/
|
||||||
|
static init() {
|
||||||
|
this.easing = power(2, 'in');
|
||||||
|
this.dangerEasing = power(3, 'out');
|
||||||
|
this.horizontal = new MotaOffscreenCanvas2D();
|
||||||
|
this.vertical = new MotaOffscreenCanvas2D();
|
||||||
|
const hor = this.horizontal;
|
||||||
|
hor.size(480 - 64, 32);
|
||||||
|
hor.setHD(true);
|
||||||
|
const ctxHor = hor.ctx;
|
||||||
|
ctxHor.fillStyle = '#f00';
|
||||||
|
ctxHor.globalAlpha = 0.6;
|
||||||
|
for (let i = 0; i < 13; i++) {
|
||||||
|
ctxHor.fillRect(i * 32 + 2, 2, 28, 28);
|
||||||
|
}
|
||||||
|
const ver = this.vertical;
|
||||||
|
ver.size(480 - 64, 32);
|
||||||
|
ver.setHD(true);
|
||||||
|
const ctxVer = ver.ctx;
|
||||||
|
ctxVer.fillStyle = '#f00';
|
||||||
|
ctxVer.globalAlpha = 0.6;
|
||||||
|
for (let i = 0; i < 13; i++) {
|
||||||
|
ctxVer.fillRect(2, i * 32 + 2, 28, 28);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* boss战结束后清理
|
||||||
|
*/
|
||||||
|
static end() {
|
||||||
|
this.easing = void 0;
|
||||||
|
this.dangerEasing = void 0;
|
||||||
|
this.horizontal?.clear();
|
||||||
|
this.horizontal = null;
|
||||||
|
this.vertical?.clear();
|
||||||
|
this.vertical = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置弹幕的数据
|
||||||
|
* @param direction 弹幕的方向
|
||||||
|
*/
|
||||||
|
setData(direction: ProjectileDirection) {
|
||||||
|
this.direction = direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
isIntersect(hitbox: Hitbox.HitboxType): boolean {
|
||||||
|
if (hitbox instanceof Hitbox.Rect) {
|
||||||
|
return Hitbox.checkRectRect(hitbox, this.hitbox);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHitbox(x: number, y: number): void {
|
||||||
|
this.hitbox.setPosition(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
doDamage(target: IStateDamageable): boolean {
|
||||||
|
if (this.damaged) return false;
|
||||||
|
target.hp -= this.damage;
|
||||||
|
this.damaged = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ai(boss: TowerBoss, time: number, frame: number): void {
|
||||||
|
if (time > 3000) {
|
||||||
|
const progress = (time - 3000) / 2000;
|
||||||
|
const res = ArrowProjectile.easing!(progress);
|
||||||
|
const dx = res * 640;
|
||||||
|
const x = 480 - 32 - dx;
|
||||||
|
if (this.direction === ProjectileDirection.Horizontal) {
|
||||||
|
this.setPosition(this.x, x);
|
||||||
|
} else {
|
||||||
|
this.setPosition(x, this.y);
|
||||||
|
}
|
||||||
|
} else if (time > 5000) {
|
||||||
|
this.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render(canvas: MotaOffscreenCanvas2D, transform: Transform): void {
|
||||||
|
const ctx = canvas.ctx;
|
||||||
|
|
||||||
|
if (this.time < 3000) {
|
||||||
|
let begin = 1;
|
||||||
|
if (this.time < 2000) {
|
||||||
|
begin = ArrowProjectile.dangerEasing!(this.time / 2000);
|
||||||
|
}
|
||||||
|
ctx.beginPath();
|
||||||
|
const len = begin * 13 * 32;
|
||||||
|
const x1 = 480 - 32 - len;
|
||||||
|
|
||||||
|
if (this.direction === ProjectileDirection.Horizontal) {
|
||||||
|
const canvas = ArrowProjectile.horizontal!.canvas;
|
||||||
|
ctx.drawImage(canvas, x1, this.y, len, 32);
|
||||||
|
} else {
|
||||||
|
const canvas = ArrowProjectile.vertical!.canvas;
|
||||||
|
ctx.drawImage(canvas, this.y, x1, 32, len);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const len = Math.max(this.y - 32, 0);
|
||||||
|
if (this.direction === ProjectileDirection.Horizontal) {
|
||||||
|
const canvas = ArrowProjectile.horizontal!.canvas;
|
||||||
|
ctx.drawImage(canvas, 32, this.y, len, 32);
|
||||||
|
} else {
|
||||||
|
const canvas = ArrowProjectile.vertical!.canvas;
|
||||||
|
ctx.drawImage(canvas, this.y, 32, 32, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const img = core.material.images.images['arrow.png'];
|
||||||
|
ctx.drawImage(img, this.x, this.y, 102, 32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PortalProjectile extends Projectile<TowerBoss> {
|
||||||
|
static easing?: TimingFn;
|
||||||
|
|
||||||
|
damage: number = 0;
|
||||||
|
hitbox: Hitbox.Circle = new Hitbox.Circle(0, 0, 0);
|
||||||
|
|
||||||
|
/** 传送目标位置 */
|
||||||
|
private tx: number = 0;
|
||||||
|
/** 传送目标位置 */
|
||||||
|
private ty: number = 0;
|
||||||
|
/** 是否已经传送过 */
|
||||||
|
private transfered: boolean = false;
|
||||||
|
|
||||||
|
private effect?: PointEffect;
|
||||||
|
private effectId?: number;
|
||||||
|
|
||||||
|
static init() {
|
||||||
|
this.easing = hyper('sin', 'out');
|
||||||
|
}
|
||||||
|
|
||||||
|
static end() {
|
||||||
|
this.easing = void 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
createEffect(effect: PointEffect) {
|
||||||
|
this.effect = effect;
|
||||||
|
const id = effect.addEffect(
|
||||||
|
PointEffectType.CircleWarpTangetial,
|
||||||
|
Date.now(),
|
||||||
|
4000,
|
||||||
|
[this.tx * 32, this.ty * 32, 12, 20]
|
||||||
|
);
|
||||||
|
this.effectId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置传送目标位置
|
||||||
|
*/
|
||||||
|
setTarget(x: number, y: number) {
|
||||||
|
this.tx = x;
|
||||||
|
this.ty = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
isIntersect(hitbox: Hitbox.HitboxType): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHitbox(x: number, y: number): void {
|
||||||
|
this.hitbox.setCenter(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
doDamage(target: IStateDamageable): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ai(boss: TowerBoss, time: number, frame: number): void {
|
||||||
|
if (!this.transfered && time > 2000) {
|
||||||
|
this.transfered = true;
|
||||||
|
core.setHeroLoc('x', this.tx);
|
||||||
|
core.setHeroLoc('y', this.ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time > 4000) {
|
||||||
|
this.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render(canvas: MotaOffscreenCanvas2D, transform: Transform): void {
|
||||||
|
const effect = this.effect;
|
||||||
|
const id = this.effectId;
|
||||||
|
if (!effect || isNil(id)) return;
|
||||||
|
const time = this.time;
|
||||||
|
const max = Math.PI * 8;
|
||||||
|
if (time < 2000) {
|
||||||
|
const progress = PortalProjectile.easing!(time / 2000);
|
||||||
|
effect.setEffect(id, void 0, [0, max * progress, 0, 0]);
|
||||||
|
} else {
|
||||||
|
const progress = PortalProjectile.easing!((time - 2000) / 2000);
|
||||||
|
effect.setEffect(id, void 0, [max * progress, max, 0, 0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IceProjectile extends Projectile<TowerBoss> {
|
||||||
|
damage: number = 5000;
|
||||||
|
hitbox: Hitbox.Rect = new Hitbox.Rect(0, 0, 32, 32);
|
||||||
|
|
||||||
|
private damaged: boolean = false;
|
||||||
|
private animated: boolean = false;
|
||||||
|
private converted: boolean = false;
|
||||||
|
|
||||||
|
private bx: number = 0;
|
||||||
|
private by: number = 0;
|
||||||
|
|
||||||
|
setPos(x: number, y: number) {
|
||||||
|
this.bx = x;
|
||||||
|
this.by = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
isIntersect(hitbox: Hitbox.HitboxType): boolean {
|
||||||
|
if (this.damaged) return false;
|
||||||
|
if (this.time < 2000) return false;
|
||||||
|
if (hitbox instanceof Hitbox.Rect) {
|
||||||
|
return Hitbox.checkRectRect(hitbox, this.hitbox);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHitbox(x: number, y: number): void {
|
||||||
|
this.hitbox.setPosition(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
doDamage(target: IStateDamageable): boolean {
|
||||||
|
if (!this.damaged) return false;
|
||||||
|
target.hp -= this.damage;
|
||||||
|
this.damaged = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ai(boss: TowerBoss, time: number, frame: number): void {
|
||||||
|
if (!this.converted && time > 2000) {
|
||||||
|
this.converted = true;
|
||||||
|
core.setBgFgBlock('bg', 167, this.bx, this.by);
|
||||||
|
}
|
||||||
|
if (time > 4000) {
|
||||||
|
core.setBgFgBlock('bg', 526, this.bx, this.by);
|
||||||
|
this.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render(canvas: MotaOffscreenCanvas2D, transform: Transform): void {
|
||||||
|
const ctx = canvas.ctx;
|
||||||
|
if (this.time < 2000) {
|
||||||
|
const fill = ctx.fillStyle;
|
||||||
|
const alpha = ctx.globalAlpha;
|
||||||
|
ctx.fillStyle = 'rgb(150,150,255)';
|
||||||
|
ctx.globalAlpha = 0.6;
|
||||||
|
ctx.fillRect(this.x + 2, this.y + 2, 28, 28);
|
||||||
|
ctx.fillStyle = fill;
|
||||||
|
ctx.globalAlpha = alpha;
|
||||||
|
} else {
|
||||||
|
if (!this.animated) {
|
||||||
|
this.animated = true;
|
||||||
|
core.drawAnimate('ice', this.bx, this.by);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user