mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-01-19 12:49:25 +08:00
feat: 智慧塔弹幕战第二阶段
This commit is contained in:
parent
d9a398a8e9
commit
b5b435d133
@ -17,8 +17,11 @@ import {
|
|||||||
import { Container } from '@/core/render/container';
|
import { Container } from '@/core/render/container';
|
||||||
import {
|
import {
|
||||||
ArrowProjectile,
|
ArrowProjectile,
|
||||||
|
IceProjectile,
|
||||||
PortalProjectile,
|
PortalProjectile,
|
||||||
ProjectileDirection
|
ProjectileDirection,
|
||||||
|
ThunderBallProjectile,
|
||||||
|
ThunderProjectile
|
||||||
} from './towerBossProjectile';
|
} from './towerBossProjectile';
|
||||||
import { IStateDamageable } from '@/game/state/interface';
|
import { IStateDamageable } from '@/game/state/interface';
|
||||||
|
|
||||||
@ -39,10 +42,8 @@ const enum TowerBossStage {
|
|||||||
Stage2,
|
Stage2,
|
||||||
Dialogue2,
|
Dialogue2,
|
||||||
Stage3,
|
Stage3,
|
||||||
Dialogue3,
|
|
||||||
Stage4,
|
Stage4,
|
||||||
Stage5,
|
Stage5,
|
||||||
Stage6,
|
|
||||||
|
|
||||||
End
|
End
|
||||||
}
|
}
|
||||||
@ -171,6 +172,7 @@ export class TowerBoss extends BarrageBoss {
|
|||||||
|
|
||||||
ArrowProjectile.init();
|
ArrowProjectile.init();
|
||||||
PortalProjectile.init();
|
PortalProjectile.init();
|
||||||
|
ThunderProjectile.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
override end() {
|
override end() {
|
||||||
@ -183,6 +185,7 @@ export class TowerBoss extends BarrageBoss {
|
|||||||
|
|
||||||
ArrowProjectile.end();
|
ArrowProjectile.end();
|
||||||
PortalProjectile.end();
|
PortalProjectile.end();
|
||||||
|
ThunderProjectile.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -208,11 +211,11 @@ export class TowerBoss extends BarrageBoss {
|
|||||||
this.hp -= damage;
|
this.hp -= damage;
|
||||||
this.healthBar.set(this.hp);
|
this.healthBar.set(this.hp);
|
||||||
// 先用drawAnimate凑活一下,等下个版本提供更好的 api
|
// 先用drawAnimate凑活一下,等下个版本提供更好的 api
|
||||||
if (this.stage === TowerBossStage.Stage4) {
|
if (this.stage === TowerBossStage.Stage3) {
|
||||||
core.drawAnimate('hand', 7, 2);
|
core.drawAnimate('hand', 7, 2);
|
||||||
} else if (this.stage === TowerBossStage.Stage5) {
|
} else if (this.stage === TowerBossStage.Stage4) {
|
||||||
core.drawAnimate('hand', 7, 3);
|
core.drawAnimate('hand', 7, 3);
|
||||||
} else if (this.stage === TowerBossStage.Stage6) {
|
} else if (this.stage === TowerBossStage.Stage5) {
|
||||||
core.drawAnimate('hand', 7, 4);
|
core.drawAnimate('hand', 7, 4);
|
||||||
} else {
|
} else {
|
||||||
core.drawAnimate('hand', 7, 1);
|
core.drawAnimate('hand', 7, 1);
|
||||||
@ -227,13 +230,13 @@ export class TowerBoss extends BarrageBoss {
|
|||||||
addAttackCircle(last: number, damage: number) {
|
addAttackCircle(last: number, damage: number) {
|
||||||
let nx = 0;
|
let nx = 0;
|
||||||
let ny = 0;
|
let ny = 0;
|
||||||
if (this.stage === TowerBossStage.Stage4) {
|
if (this.stage === TowerBossStage.Stage3) {
|
||||||
nx = Math.floor(Math.random() * 11 + 2);
|
nx = Math.floor(Math.random() * 11 + 2);
|
||||||
ny = Math.floor(Math.random() * 11 + 2);
|
ny = Math.floor(Math.random() * 11 + 2);
|
||||||
} else if (this.stage === TowerBossStage.Stage5) {
|
} else if (this.stage === TowerBossStage.Stage4) {
|
||||||
nx = Math.floor(Math.random() * 9 + 3);
|
nx = Math.floor(Math.random() * 9 + 3);
|
||||||
ny = Math.floor(Math.random() * 9 + 3);
|
ny = Math.floor(Math.random() * 9 + 3);
|
||||||
} else if (this.stage === TowerBossStage.Stage6) {
|
} else if (this.stage === TowerBossStage.Stage5) {
|
||||||
nx = Math.floor(Math.random() * 7 + 4);
|
nx = Math.floor(Math.random() * 7 + 4);
|
||||||
ny = Math.floor(Math.random() * 7 + 4);
|
ny = Math.floor(Math.random() * 7 + 4);
|
||||||
} else {
|
} else {
|
||||||
@ -301,18 +304,12 @@ export class TowerBoss extends BarrageBoss {
|
|||||||
case TowerBossStage.Stage3:
|
case TowerBossStage.Stage3:
|
||||||
this.aiStage3(fixedTime, frame);
|
this.aiStage3(fixedTime, frame);
|
||||||
break;
|
break;
|
||||||
case TowerBossStage.Dialogue3:
|
|
||||||
this.aiDialogue3(fixedTime, frame);
|
|
||||||
break;
|
|
||||||
case TowerBossStage.Stage4:
|
case TowerBossStage.Stage4:
|
||||||
this.aiStage4(fixedTime, frame);
|
this.aiStage4(fixedTime, frame);
|
||||||
break;
|
break;
|
||||||
case TowerBossStage.Stage5:
|
case TowerBossStage.Stage5:
|
||||||
this.aiStage5(fixedTime, frame);
|
this.aiStage5(fixedTime, frame);
|
||||||
break;
|
break;
|
||||||
case TowerBossStage.Stage6:
|
|
||||||
this.aiStage6(fixedTime, frame);
|
|
||||||
break;
|
|
||||||
case TowerBossStage.End:
|
case TowerBossStage.End:
|
||||||
this.aiEnd(fixedTime, frame);
|
this.aiEnd(fixedTime, frame);
|
||||||
break;
|
break;
|
||||||
@ -376,7 +373,20 @@ export class TowerBoss extends BarrageBoss {
|
|||||||
proj.createEffect(TowerBoss.effect);
|
proj.createEffect(TowerBoss.effect);
|
||||||
}
|
}
|
||||||
|
|
||||||
async releaseSkill3() {}
|
async releaseSkill3() {
|
||||||
|
const count = Math.floor(Math.random() * 100);
|
||||||
|
const used = new Set<number>();
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const x = Math.floor(Math.random() * 13 + 1);
|
||||||
|
const y = Math.floor(Math.random() * 13 + 1);
|
||||||
|
const index = x + y * 13;
|
||||||
|
if (used.has(index)) continue;
|
||||||
|
used.add(index);
|
||||||
|
const proj = this.createProjectile(IceProjectile, x * 32, y * 32);
|
||||||
|
proj.setPos(x, y);
|
||||||
|
await sleep(20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private aiStage1(time: number, frame: number) {
|
private aiStage1(time: number, frame: number) {
|
||||||
// stageProgress:
|
// stageProgress:
|
||||||
@ -410,22 +420,119 @@ export class TowerBoss extends BarrageBoss {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private aiDialogue1(time: number, frame: number) {}
|
private aiDialogue1(time: number, frame: number) {
|
||||||
|
this.changeStage(TowerBossStage.Stage2, time);
|
||||||
|
this.attackTime = 3;
|
||||||
|
this.skill4Time = 5;
|
||||||
|
this.skill5Time = 3;
|
||||||
|
}
|
||||||
|
|
||||||
private aiStage2(time: number, frame: number) {}
|
releaseSkill4() {
|
||||||
|
const x = Math.floor(Math.random() * 11 + 2);
|
||||||
|
const y = Math.floor(Math.random() * 11 + 2);
|
||||||
|
const power = Math.floor(Math.random() * 6 + 1);
|
||||||
|
const proj = this.createProjectile(ThunderProjectile, 0, 0);
|
||||||
|
proj.setData(x, y, power);
|
||||||
|
proj.createEffect(TowerBoss.effect);
|
||||||
|
}
|
||||||
|
|
||||||
private aiDialogue2(time: number, frame: number) {}
|
async releaseSkill5() {
|
||||||
|
const count = Math.floor(Math.random() * 12 + 6);
|
||||||
|
const used = new Set<number>();
|
||||||
|
let i = 0;
|
||||||
|
while (i < count) {
|
||||||
|
const x = Math.floor(Math.random() * 13 + 1);
|
||||||
|
const y = Math.floor(Math.random() * 13 + 1);
|
||||||
|
const index = x + y * 13;
|
||||||
|
if (used.has(index)) continue;
|
||||||
|
i++;
|
||||||
|
used.add(index);
|
||||||
|
const px = x * 32 + 16;
|
||||||
|
const py = y * 32 + 16;
|
||||||
|
const proj1 = this.createProjectile(ThunderBallProjectile, 0, 0);
|
||||||
|
const proj2 = this.createProjectile(ThunderBallProjectile, 0, 0);
|
||||||
|
const proj3 = this.createProjectile(ThunderBallProjectile, 0, 0);
|
||||||
|
const proj4 = this.createProjectile(ThunderBallProjectile, 0, 0);
|
||||||
|
proj1.setData(ProjectileDirection.BottomToTop, x, y);
|
||||||
|
proj2.setData(ProjectileDirection.LeftToRight, x, y);
|
||||||
|
proj3.setData(ProjectileDirection.RightToLeft, x, y);
|
||||||
|
proj4.setData(ProjectileDirection.TopToBottom, x, y);
|
||||||
|
proj1.setPosition(px, py);
|
||||||
|
proj2.setPosition(px, py);
|
||||||
|
proj3.setPosition(px, py);
|
||||||
|
proj4.setPosition(px, py);
|
||||||
|
await sleep(200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private aiStage2(time: number, frame: number) {
|
||||||
|
const skill4Release = this.skill4Time * this.skill4Interval;
|
||||||
|
const skill5Release = this.skill5Time * this.skill5Interval;
|
||||||
|
const attack = this.attackTime * this.attackInterval;
|
||||||
|
|
||||||
|
if (time > skill4Release) {
|
||||||
|
this.releaseSkill4();
|
||||||
|
this.skill4Time++;
|
||||||
|
}
|
||||||
|
if (time > skill5Release) {
|
||||||
|
this.releaseSkill5();
|
||||||
|
this.skill5Time++;
|
||||||
|
}
|
||||||
|
if (time > attack) {
|
||||||
|
this.addAttackCircle(3000, 500);
|
||||||
|
this.attackTime++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hp <= 3500) {
|
||||||
|
this.changeStage(TowerBossStage.Dialogue2, time);
|
||||||
|
this.attackTime = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 压缩地形,将地形向内压缩一格
|
||||||
|
*/
|
||||||
|
terrainClose(n: number) {
|
||||||
|
for (let nx = n - 1; nx < 15 - n + 1; nx++) {
|
||||||
|
core.removeBlock(nx, n - 1);
|
||||||
|
core.removeBlock(nx, 15 - n + 1);
|
||||||
|
core.setBgFgBlock('bg', 0, nx, n - 1);
|
||||||
|
core.setBgFgBlock('bg', 0, nx, 15 - n + 1);
|
||||||
|
}
|
||||||
|
for (let ny = n; ny < 15 - n; ny++) {
|
||||||
|
core.removeBlock(n - 1, ny);
|
||||||
|
core.removeBlock(15 - n + 1, ny);
|
||||||
|
core.setBgFgBlock('bg', 0, n - 1, ny);
|
||||||
|
core.setBgFgBlock('bg', 0, 15 - n + 1, ny);
|
||||||
|
}
|
||||||
|
for (let nx = n; nx < 15 - n; nx++) {
|
||||||
|
core.setBlock(527, nx, n);
|
||||||
|
core.setBlock(527, nx, 15 - n);
|
||||||
|
}
|
||||||
|
for (let ny = n + 1; ny < 15 - n - 1; ny++) {
|
||||||
|
core.setBlock(527, n, ny);
|
||||||
|
core.setBlock(527, 15 - n, ny);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private aiDialogue2(time: number, frame: number) {
|
||||||
|
this.changeStage(TowerBossStage.Stage3, time);
|
||||||
|
this.attackTime = 3;
|
||||||
|
this.terrainClose(1);
|
||||||
|
this.skill6Time = 30;
|
||||||
|
this.skill7Time = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseSkill6() {}
|
||||||
|
|
||||||
|
releaseSkill7() {}
|
||||||
|
|
||||||
private aiStage3(time: number, frame: number) {}
|
private aiStage3(time: number, frame: number) {}
|
||||||
|
|
||||||
private aiDialogue3(time: number, frame: number) {}
|
|
||||||
|
|
||||||
private aiStage4(time: number, frame: number) {}
|
private aiStage4(time: number, frame: number) {}
|
||||||
|
|
||||||
private aiStage5(time: number, frame: number) {}
|
private aiStage5(time: number, frame: number) {}
|
||||||
|
|
||||||
private aiStage6(time: number, frame: number) {}
|
|
||||||
|
|
||||||
private aiEnd(time: number, frame: number) {}
|
private aiEnd(time: number, frame: number) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,12 @@ import { isNil } from 'lodash-es';
|
|||||||
|
|
||||||
export const enum ProjectileDirection {
|
export const enum ProjectileDirection {
|
||||||
Vertical,
|
Vertical,
|
||||||
Horizontal
|
Horizontal,
|
||||||
|
|
||||||
|
LeftToRight,
|
||||||
|
RightToLeft,
|
||||||
|
TopToBottom,
|
||||||
|
BottomToTop
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ArrowProjectile extends Projectile<TowerBoss> {
|
export class ArrowProjectile extends Projectile<TowerBoss> {
|
||||||
@ -38,6 +43,7 @@ export class ArrowProjectile extends Projectile<TowerBoss> {
|
|||||||
const hor = this.horizontal;
|
const hor = this.horizontal;
|
||||||
hor.size(480 - 64, 32);
|
hor.size(480 - 64, 32);
|
||||||
hor.setHD(true);
|
hor.setHD(true);
|
||||||
|
hor.withGameScale(true);
|
||||||
const ctxHor = hor.ctx;
|
const ctxHor = hor.ctx;
|
||||||
ctxHor.fillStyle = '#f00';
|
ctxHor.fillStyle = '#f00';
|
||||||
ctxHor.globalAlpha = 0.6;
|
ctxHor.globalAlpha = 0.6;
|
||||||
@ -47,6 +53,7 @@ export class ArrowProjectile extends Projectile<TowerBoss> {
|
|||||||
const ver = this.vertical;
|
const ver = this.vertical;
|
||||||
ver.size(480 - 64, 32);
|
ver.size(480 - 64, 32);
|
||||||
ver.setHD(true);
|
ver.setHD(true);
|
||||||
|
ver.withGameScale(true);
|
||||||
const ctxVer = ver.ctx;
|
const ctxVer = ver.ctx;
|
||||||
ctxVer.fillStyle = '#f00';
|
ctxVer.fillStyle = '#f00';
|
||||||
ctxVer.globalAlpha = 0.6;
|
ctxVer.globalAlpha = 0.6;
|
||||||
@ -118,25 +125,24 @@ export class ArrowProjectile extends Projectile<TowerBoss> {
|
|||||||
if (this.time < 2000) {
|
if (this.time < 2000) {
|
||||||
begin = ArrowProjectile.dangerEasing!(this.time / 2000);
|
begin = ArrowProjectile.dangerEasing!(this.time / 2000);
|
||||||
}
|
}
|
||||||
ctx.beginPath();
|
|
||||||
const len = begin * 13 * 32;
|
const len = begin * 13 * 32;
|
||||||
const x1 = 480 - 32 - len;
|
const x1 = 480 - 32 - len;
|
||||||
|
|
||||||
if (this.direction === ProjectileDirection.Horizontal) {
|
if (this.direction === ProjectileDirection.Horizontal) {
|
||||||
const canvas = ArrowProjectile.horizontal!.canvas;
|
const canvas = ArrowProjectile.horizontal!.canvas;
|
||||||
ctx.drawImage(canvas, x1, this.y, len, 32);
|
ctx.drawImage(canvas, x1, 0, len, 32, x1, this.y, len, 32);
|
||||||
} else {
|
} else {
|
||||||
const canvas = ArrowProjectile.vertical!.canvas;
|
const canvas = ArrowProjectile.vertical!.canvas;
|
||||||
ctx.drawImage(canvas, this.y, x1, 32, len);
|
ctx.drawImage(canvas, 0, x1, 32, len, this.y, x1, 32, len);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const len = Math.max(this.y - 32, 0);
|
const len = Math.max(this.y - 32, 0);
|
||||||
if (this.direction === ProjectileDirection.Horizontal) {
|
if (this.direction === ProjectileDirection.Horizontal) {
|
||||||
const canvas = ArrowProjectile.horizontal!.canvas;
|
const canvas = ArrowProjectile.horizontal!.canvas;
|
||||||
ctx.drawImage(canvas, 32, this.y, len, 32);
|
ctx.drawImage(canvas, 32, 0, len, 32, 32, this.y, len, 32);
|
||||||
} else {
|
} else {
|
||||||
const canvas = ArrowProjectile.vertical!.canvas;
|
const canvas = ArrowProjectile.vertical!.canvas;
|
||||||
ctx.drawImage(canvas, this.y, 32, 32, len);
|
ctx.drawImage(canvas, 0, 32, 32, len, this.y, 32, 32, len);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const img = core.material.images.images['arrow.png'];
|
const img = core.material.images.images['arrow.png'];
|
||||||
@ -232,15 +238,21 @@ export class IceProjectile extends Projectile<TowerBoss> {
|
|||||||
hitbox: Hitbox.Rect = new Hitbox.Rect(0, 0, 32, 32);
|
hitbox: Hitbox.Rect = new Hitbox.Rect(0, 0, 32, 32);
|
||||||
|
|
||||||
private damaged: boolean = false;
|
private damaged: boolean = false;
|
||||||
|
/** 是否已经播放冰冻动画 */
|
||||||
private animated: boolean = false;
|
private animated: boolean = false;
|
||||||
|
/** 是否已经转换成滑冰图块 */
|
||||||
private converted: boolean = false;
|
private converted: boolean = false;
|
||||||
|
|
||||||
private bx: number = 0;
|
private bx: number = 0;
|
||||||
private by: number = 0;
|
private by: number = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置这个寒冰弹幕的攻击位置
|
||||||
|
*/
|
||||||
setPos(x: number, y: number) {
|
setPos(x: number, y: number) {
|
||||||
this.bx = x;
|
this.bx = x;
|
||||||
this.by = y;
|
this.by = y;
|
||||||
|
this.updateHitbox(x * 32, y * 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
isIntersect(hitbox: Hitbox.HitboxType): boolean {
|
isIntersect(hitbox: Hitbox.HitboxType): boolean {
|
||||||
@ -293,3 +305,405 @@ export class IceProjectile extends Projectile<TowerBoss> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ThunderProjectile extends Projectile<TowerBoss> {
|
||||||
|
/** 闪电缓存画布 */
|
||||||
|
static cache: MotaOffscreenCanvas2D | null = null;
|
||||||
|
|
||||||
|
damage: number = 0;
|
||||||
|
hitbox: Hitbox.Rect = new Hitbox.Rect(0, 0, 96, 96);
|
||||||
|
|
||||||
|
private bx: number = 0;
|
||||||
|
private by: number = 0;
|
||||||
|
/** 闪电的强度 */
|
||||||
|
private power: number = 0;
|
||||||
|
private damaged: boolean = false;
|
||||||
|
private cached: boolean = false;
|
||||||
|
|
||||||
|
private effect?: PointEffect;
|
||||||
|
private effectId?: number;
|
||||||
|
|
||||||
|
static init() {
|
||||||
|
this.cache = new MotaOffscreenCanvas2D();
|
||||||
|
this.cache.setHD(true);
|
||||||
|
this.cache.withGameScale(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static end() {
|
||||||
|
this.cache?.clear();
|
||||||
|
this.cache = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建着色器特效
|
||||||
|
*/
|
||||||
|
createEffect(effect: PointEffect) {
|
||||||
|
this.effect = effect;
|
||||||
|
this.effectId = effect.addEffect(
|
||||||
|
PointEffectType.CircleBrightness,
|
||||||
|
Date.now() + 1000,
|
||||||
|
400,
|
||||||
|
[this.bx * 32 + 32, this.by * 32 + 32, 128, 32]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置闪电的信息
|
||||||
|
*/
|
||||||
|
setData(x: number, y: number, power: number) {
|
||||||
|
this.bx = x;
|
||||||
|
this.by = y;
|
||||||
|
this.power = power;
|
||||||
|
this.damage = power * 3000;
|
||||||
|
this.updateHitbox(x * 32 - 32, y * 32 - 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
isIntersect(hitbox: Hitbox.HitboxType): boolean {
|
||||||
|
if (this.damaged) return false;
|
||||||
|
if (this.time < 1000) 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;
|
||||||
|
this.damaged = true;
|
||||||
|
target.hp -= this.damage;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ai(boss: TowerBoss, time: number, frame: number): void {
|
||||||
|
if (time > 2000) {
|
||||||
|
this.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render(canvas: MotaOffscreenCanvas2D, transform: Transform): void {
|
||||||
|
const ctx = canvas.ctx;
|
||||||
|
if (this.time < 1000) {
|
||||||
|
const before = ctx.fillStyle;
|
||||||
|
ctx.fillStyle = '#fff';
|
||||||
|
for (let dx = -1; dx < 2; dx++) {
|
||||||
|
for (let dy = -1; dy < 2; dy++) {
|
||||||
|
const x = (this.bx + dx) * 32 + 2;
|
||||||
|
const y = (this.by + dy) * 32 + 2;
|
||||||
|
ctx.fillRect(x, y, 28, 28);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.fillStyle = before;
|
||||||
|
} else {
|
||||||
|
if (!this.cached) this.cacheThunder();
|
||||||
|
if (!ThunderProjectile.cache) return;
|
||||||
|
const x = this.bx * 32;
|
||||||
|
const before = ctx.globalAlpha;
|
||||||
|
const progress = (this.time - 1000) / 1000;
|
||||||
|
if (progress < 0.4) {
|
||||||
|
const effect = this.effect;
|
||||||
|
const id = this.effectId;
|
||||||
|
if (!effect || isNil(id)) return;
|
||||||
|
const effectRatio = ArrowProjectile.dangerEasing!(
|
||||||
|
progress * 2.5
|
||||||
|
);
|
||||||
|
effect.setEffect(id, void 0, [effectRatio, 0, 0, 0]);
|
||||||
|
}
|
||||||
|
ctx.globalAlpha = 1 - progress;
|
||||||
|
ctx.drawImage(ThunderProjectile.cache.canvas, x - 60, 0);
|
||||||
|
ctx.globalAlpha = before;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private cacheThunder() {
|
||||||
|
const cache = ThunderProjectile.cache;
|
||||||
|
if (!cache) return;
|
||||||
|
const bottom = this.by * 32 + 32;
|
||||||
|
cache.size(120, bottom);
|
||||||
|
const ctx = cache.ctx;
|
||||||
|
ctx.beginPath();
|
||||||
|
for (let i = 0; i < this.power; i++) {
|
||||||
|
let x = this.bx * 32;
|
||||||
|
let y = this.by * 32;
|
||||||
|
ctx.moveTo(x, y);
|
||||||
|
while (y > 0) {
|
||||||
|
x += Math.floor(Math.random() * 30 - 15);
|
||||||
|
y -= Math.floor(Math.random() * 80);
|
||||||
|
ctx.lineTo(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.shadowBlur = 3;
|
||||||
|
ctx.shadowColor = '#62c8f4';
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.globalAlpha = 0.6;
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ThunderBallProjectile extends Projectile<TowerBoss> {
|
||||||
|
static dangerEasing?: TimingFn;
|
||||||
|
|
||||||
|
static horizontal: MotaOffscreenCanvas2D | null = null;
|
||||||
|
static vertical: MotaOffscreenCanvas2D | null = null;
|
||||||
|
|
||||||
|
damage: number = 3000;
|
||||||
|
hitbox: Hitbox.Rect = new Hitbox.Rect(0, 0, 16, 16);
|
||||||
|
|
||||||
|
private direction: ProjectileDirection = ProjectileDirection.BottomToTop;
|
||||||
|
private cx: number = 0;
|
||||||
|
private cy: number = 0;
|
||||||
|
private damaged: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* boss战开始时初始化
|
||||||
|
*/
|
||||||
|
static init() {
|
||||||
|
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);
|
||||||
|
hor.withGameScale(true);
|
||||||
|
const ctxHor = hor.ctx;
|
||||||
|
ctxHor.fillStyle = '#fff';
|
||||||
|
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);
|
||||||
|
ver.withGameScale(true);
|
||||||
|
const ctxVer = ver.ctx;
|
||||||
|
ctxVer.fillStyle = '#fff';
|
||||||
|
ctxVer.globalAlpha = 0.6;
|
||||||
|
for (let i = 0; i < 13; i++) {
|
||||||
|
ctxVer.fillRect(2, i * 32 + 2, 28, 28);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* boss战结束后清理
|
||||||
|
*/
|
||||||
|
static end() {
|
||||||
|
this.dangerEasing = void 0;
|
||||||
|
this.horizontal?.clear();
|
||||||
|
this.horizontal = null;
|
||||||
|
this.vertical?.clear();
|
||||||
|
this.vertical = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setData(direction: ProjectileDirection, cx: number, cy: number) {
|
||||||
|
this.cx = cx;
|
||||||
|
this.cy = cy;
|
||||||
|
this.direction = direction;
|
||||||
|
this.setPosition(cx * 32 + 16, cy * 32 + 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
isIntersect(hitbox: Hitbox.HitboxType): boolean {
|
||||||
|
if (this.damaged) return false;
|
||||||
|
if (this.time < 3000) return false;
|
||||||
|
if (hitbox instanceof Hitbox.Rect) {
|
||||||
|
return Hitbox.checkRectRect(this.hitbox, hitbox);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHitbox(x: number, y: number): void {
|
||||||
|
this.hitbox.setPosition(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
doDamage(target: IStateDamageable): boolean {
|
||||||
|
if (this.damaged) return false;
|
||||||
|
this.damaged = true;
|
||||||
|
target.hp -= this.damage;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ai(boss: TowerBoss, time: number, frame: number): void {
|
||||||
|
if (time > 3000) {
|
||||||
|
const dt = time - 3000;
|
||||||
|
const dis = dt * 0.2;
|
||||||
|
const cx = this.cx * 32 + 16;
|
||||||
|
const cy = this.cy * 32 + 16;
|
||||||
|
|
||||||
|
switch (this.direction) {
|
||||||
|
case ProjectileDirection.BottomToTop:
|
||||||
|
this.setPosition(cx, cy - dis);
|
||||||
|
break;
|
||||||
|
case ProjectileDirection.LeftToRight:
|
||||||
|
this.setPosition(cx + dis, cy);
|
||||||
|
break;
|
||||||
|
case ProjectileDirection.RightToLeft:
|
||||||
|
this.setPosition(cx - dis, cy);
|
||||||
|
break;
|
||||||
|
case ProjectileDirection.TopToBottom:
|
||||||
|
this.setPosition(cx, cy + dis);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.x < -16 || this.x > 496 || this.y < -16 || this.y > 496) {
|
||||||
|
this.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render(canvas: MotaOffscreenCanvas2D, transform: Transform): void {
|
||||||
|
const ctx = canvas.ctx;
|
||||||
|
const cx = this.cx * 32 + 16;
|
||||||
|
const cy = this.cy * 32 + 16;
|
||||||
|
let left = 0;
|
||||||
|
let right = 0;
|
||||||
|
let top = 0;
|
||||||
|
let bottom = 0;
|
||||||
|
if (this.time < 3000) {
|
||||||
|
let begin = 1;
|
||||||
|
if (this.time < 2000) {
|
||||||
|
begin = ArrowProjectile.dangerEasing!(this.time / 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (this.direction) {
|
||||||
|
case ProjectileDirection.BottomToTop: {
|
||||||
|
const height = (cy - 48) * begin;
|
||||||
|
left = cx - 16;
|
||||||
|
right = cx + 16;
|
||||||
|
bottom = cy + 16;
|
||||||
|
top = cy - height - 16;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ProjectileDirection.LeftToRight: {
|
||||||
|
const width = (432 - cx) * begin;
|
||||||
|
left = cx - 16;
|
||||||
|
right = cx + 16 + width;
|
||||||
|
bottom = cy + 16;
|
||||||
|
top = cy - 16;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ProjectileDirection.RightToLeft: {
|
||||||
|
const width = (cx - 48) * begin;
|
||||||
|
left = cx - width - 16;
|
||||||
|
right = cx + 16;
|
||||||
|
bottom = cy + 16;
|
||||||
|
top = cy - 16;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ProjectileDirection.TopToBottom: {
|
||||||
|
const height = (432 - cy) * begin;
|
||||||
|
left = cx - 16;
|
||||||
|
right = cx + 16;
|
||||||
|
bottom = cy + 16;
|
||||||
|
top = cy + 16 + height;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (this.direction) {
|
||||||
|
case ProjectileDirection.BottomToTop: {
|
||||||
|
left = cx - 16;
|
||||||
|
right = cx + 16;
|
||||||
|
bottom = this.y;
|
||||||
|
top = 32;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ProjectileDirection.LeftToRight: {
|
||||||
|
left = this.x;
|
||||||
|
right = 448;
|
||||||
|
bottom = cy + 16;
|
||||||
|
top = cy - 16;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ProjectileDirection.RightToLeft: {
|
||||||
|
left = 32;
|
||||||
|
right = this.x;
|
||||||
|
bottom = cy + 16;
|
||||||
|
top = cy - 16;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ProjectileDirection.TopToBottom: {
|
||||||
|
left = cx - 16;
|
||||||
|
right = cx + 16;
|
||||||
|
bottom = 448;
|
||||||
|
top = this.y;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const w = right - left;
|
||||||
|
const h = bottom - top;
|
||||||
|
const hor = ThunderBallProjectile.horizontal!.canvas;
|
||||||
|
const ver = ThunderBallProjectile.vertical!.canvas;
|
||||||
|
switch (this.direction) {
|
||||||
|
case ProjectileDirection.BottomToTop:
|
||||||
|
case ProjectileDirection.TopToBottom: {
|
||||||
|
ctx.drawImage(hor, 0, top, 32, h, left, top, w, h);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ProjectileDirection.LeftToRight:
|
||||||
|
case ProjectileDirection.RightToLeft: {
|
||||||
|
ctx.drawImage(ver, left, 0, w, 32, left, top, w, h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.fillStyle = '#fff';
|
||||||
|
ctx.globalAlpha = 0.9;
|
||||||
|
ctx.beginPath();
|
||||||
|
const radius = 9 + Math.floor(Math.random() * 8 - 4);
|
||||||
|
ctx.arc(this.x, this.y, radius, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BoomProjectile extends Projectile<TowerBoss> {
|
||||||
|
damage: number = 3000;
|
||||||
|
hitbox: Hitbox.Rect = new Hitbox.Rect(0, 0, 32, 32);
|
||||||
|
|
||||||
|
isIntersect(hitbox: Hitbox.HitboxType): boolean {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHitbox(x: number, y: number): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
|
doDamage(target: IStateDamageable): boolean {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
|
ai(boss: TowerBoss, time: number, frame: number): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
|
render(canvas: MotaOffscreenCanvas2D, transform: Transform): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ChainProjectile extends Projectile<TowerBoss> {
|
||||||
|
damage: number = 4000;
|
||||||
|
hitbox: Hitbox.Line = new Hitbox.Line(0, 0, 0, 0);
|
||||||
|
|
||||||
|
isIntersect(hitbox: Hitbox.HitboxType): boolean {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHitbox(x: number, y: number): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
|
doDamage(target: IStateDamageable): boolean {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
|
ai(boss: TowerBoss, time: number, frame: number): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
|
render(canvas: MotaOffscreenCanvas2D, transform: Transform): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -43,7 +43,7 @@ export const enum PointEffectType {
|
|||||||
*/
|
*/
|
||||||
CircleHue,
|
CircleHue,
|
||||||
/**
|
/**
|
||||||
* 圆形扭曲特效,注意此特效会导致在此之前的所有非扭曲类特效失效,在添加时,系统会自动排序以保证特效正常显示\
|
* 圆形扭曲特效\
|
||||||
* 参数分别为:\
|
* 参数分别为:\
|
||||||
* `data1: x, y, maxRaius, waveRadius` | 中心横坐标,中心纵坐标,波纹最大传播距离,波纹环的半径\
|
* `data1: x, y, maxRaius, waveRadius` | 中心横坐标,中心纵坐标,波纹最大传播距离,波纹环的半径\
|
||||||
* `data2: amplitude, attenuation, linear, tangential` \
|
* `data2: amplitude, attenuation, linear, tangential` \
|
||||||
@ -63,7 +63,14 @@ export const enum PointEffectType {
|
|||||||
* (1表示扭曲了相位角的程度,例如Math.PI的相位,幅度为1,表示旋转整整一圈)\
|
* (1表示扭曲了相位角的程度,例如Math.PI的相位,幅度为1,表示旋转整整一圈)\
|
||||||
* `data2: startPhase, endPhase, _, _` 起始位置相位(靠近波纹中心的位置),终止位置相位(远离波纹中心的位置),空,空
|
* `data2: startPhase, endPhase, _, _` 起始位置相位(靠近波纹中心的位置),终止位置相位(远离波纹中心的位置),空,空
|
||||||
*/
|
*/
|
||||||
CircleWarpTangetial
|
CircleWarpTangetial,
|
||||||
|
/**
|
||||||
|
* 圆形亮度特效,可与任何特效叠加\
|
||||||
|
* 参数分别为:\
|
||||||
|
* `data1: x, y, radius, decay` | 中心横坐标,中心纵坐标,半径,衰减开始半径\
|
||||||
|
* `data2: ratio, _, _, _` | 亮度(0表示不变,1表示2倍亮度),空,空,空
|
||||||
|
*/
|
||||||
|
CircleBrightness
|
||||||
}
|
}
|
||||||
|
|
||||||
type EffectData = [x0: number, x1: number, x2: number, x3: number];
|
type EffectData = [x0: number, x1: number, x2: number, x3: number];
|
||||||
@ -568,6 +575,13 @@ void main() {
|
|||||||
color = vec4(color.rgb - grayed * ratio, 1.0);
|
color = vec4(color.rgb - grayed * ratio, 1.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 亮度,data1: x y radius decay;data2: ratio _ _ _
|
||||||
|
else if (effectType == ${PointEffectType.CircleBrightness}) {
|
||||||
|
float ratio = data2.x * calCircleDecay(data1) + 1.0;
|
||||||
|
if (ratio > 0.0) {
|
||||||
|
color.rgb *= ratio;
|
||||||
|
}
|
||||||
|
}
|
||||||
// 对比度,data1: x y radius decay;data2: ratio _ _ _
|
// 对比度,data1: x y radius decay;data2: ratio _ _ _
|
||||||
else if (effectType == ${PointEffectType.CircleContrast}) {
|
else if (effectType == ${PointEffectType.CircleContrast}) {
|
||||||
float ratio = data2.x * calCircleDecay(data1) + 1.0;
|
float ratio = data2.x * calCircleDecay(data1) + 1.0;
|
||||||
|
Loading…
Reference in New Issue
Block a user