586 lines
18 KiB
JavaScript
586 lines
18 KiB
JavaScript
/**
|
|
* 文件功能:聊天室 99A 主战坦克重装入场特效
|
|
*
|
|
* 使用全屏透明 Canvas 绘制中国 99A 主战坦克横穿屏幕、履带滚动、
|
|
* 长炮管炮击、楔形复合装甲、侧裙、尘土冲击波与重装入场 HUD。
|
|
*/
|
|
|
|
const Type99AEffect = (() => {
|
|
const DURATION = 8200;
|
|
const ARMOR = "#5f6f3a";
|
|
const DARK_ARMOR = "#1f2a1d";
|
|
const CAMO = "#7c6a36";
|
|
const DUST = "#fde68a";
|
|
const FIRE = "#f97316";
|
|
|
|
/**
|
|
* 缓出曲线,让坦克进场有重量感。
|
|
*
|
|
* @param {number} t 0 到 1 的进度
|
|
* @returns {number}
|
|
*/
|
|
function easeOutCubic(t) {
|
|
return 1 - Math.pow(1 - t, 3);
|
|
}
|
|
|
|
/**
|
|
* 缓入缓出曲线,用于 HUD 和冲击波。
|
|
*
|
|
* @param {number} t 0 到 1 的进度
|
|
* @returns {number}
|
|
*/
|
|
function easeInOutSine(t) {
|
|
return -(Math.cos(Math.PI * t) - 1) / 2;
|
|
}
|
|
|
|
/**
|
|
* 创建地面尘土粒子。
|
|
*
|
|
* @param {number} w 画布宽度
|
|
* @param {number} h 画布高度
|
|
* @returns {Array<Record<string, number>>}
|
|
*/
|
|
function createDust(w, h) {
|
|
return Array.from({ length: 90 }, () => ({
|
|
x: Math.random() * w,
|
|
y: h * (0.66 + Math.random() * 0.18),
|
|
speed: 1.6 + Math.random() * 4.8,
|
|
size: 2 + Math.random() * 8,
|
|
alpha: 0.12 + Math.random() * 0.34,
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* 绘制战场式地面背景。
|
|
*
|
|
* @param {CanvasRenderingContext2D} ctx Canvas 上下文
|
|
* @param {number} w 画布宽度
|
|
* @param {number} h 画布高度
|
|
* @param {number} progress 播放进度
|
|
*/
|
|
function drawBackdrop(ctx, w, h, progress) {
|
|
const fade = Math.min(1, progress / 0.16) * Math.min(1, (1 - progress) / 0.12);
|
|
const gradient = ctx.createRadialGradient(w * 0.5, h * 0.62, 0, w * 0.5, h * 0.62, Math.max(w, h) * 0.76);
|
|
gradient.addColorStop(0, `rgba(41,37,36,${0.42 * fade})`);
|
|
gradient.addColorStop(0.55, `rgba(63,98,18,${0.18 * fade})`);
|
|
gradient.addColorStop(1, "rgba(0,0,0,0)");
|
|
|
|
ctx.save();
|
|
ctx.fillStyle = gradient;
|
|
ctx.fillRect(0, 0, w, h);
|
|
ctx.globalCompositeOperation = "lighter";
|
|
|
|
for (let i = 0; i < 8; i++) {
|
|
const y = h * (0.7 + i * 0.028);
|
|
ctx.strokeStyle = `rgba(253,230,138,${0.1 * fade})`;
|
|
ctx.lineWidth = 2;
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, y + Math.sin(progress * 12 + i) * 4);
|
|
ctx.lineTo(w, y + Math.cos(progress * 9 + i) * 4);
|
|
ctx.stroke();
|
|
}
|
|
|
|
ctx.restore();
|
|
}
|
|
|
|
/**
|
|
* 绘制履带带起的尘土。
|
|
*
|
|
* @param {CanvasRenderingContext2D} ctx Canvas 上下文
|
|
* @param {Array<Record<string, number>>} dust 尘土粒子
|
|
* @param {number} w 画布宽度
|
|
* @param {number} progress 播放进度
|
|
*/
|
|
function drawDust(ctx, dust, w, progress) {
|
|
const fade = Math.min(1, progress / 0.18) * Math.min(1, (1 - progress) / 0.12);
|
|
|
|
ctx.save();
|
|
ctx.globalCompositeOperation = "lighter";
|
|
dust.forEach((particle, index) => {
|
|
const travel = (progress * (620 + particle.speed * 80) + index * 47) % (w + 420);
|
|
const x = w + 210 - travel;
|
|
ctx.globalAlpha = particle.alpha * fade;
|
|
ctx.fillStyle = DUST;
|
|
ctx.shadowColor = DUST;
|
|
ctx.shadowBlur = particle.size * 1.4;
|
|
ctx.beginPath();
|
|
ctx.ellipse(x, particle.y, particle.size * 1.8, particle.size * 0.7, 0, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
});
|
|
ctx.restore();
|
|
}
|
|
|
|
/**
|
|
* 绘制炮击冲击波。
|
|
*
|
|
* @param {CanvasRenderingContext2D} ctx Canvas 上下文
|
|
* @param {number} w 画布宽度
|
|
* @param {number} h 画布高度
|
|
* @param {number} progress 播放进度
|
|
*/
|
|
function drawShockwave(ctx, w, h, progress) {
|
|
const shot = Math.max(0, Math.min(1, (progress - 0.5) / 0.2));
|
|
if (shot <= 0 || shot >= 1) {
|
|
return;
|
|
}
|
|
|
|
const alpha = Math.sin(shot * Math.PI);
|
|
ctx.save();
|
|
ctx.globalCompositeOperation = "lighter";
|
|
ctx.strokeStyle = `rgba(253,230,138,${0.38 * alpha})`;
|
|
ctx.lineWidth = 5;
|
|
ctx.beginPath();
|
|
ctx.ellipse(w * 0.48, h * 0.68, w * (0.08 + shot * 0.38), h * (0.03 + shot * 0.08), 0, 0, Math.PI * 2);
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
}
|
|
|
|
/**
|
|
* 绘制 99A 主炮炮口火焰。
|
|
*
|
|
* @param {CanvasRenderingContext2D} ctx Canvas 上下文
|
|
* @param {number} progress 播放进度
|
|
*/
|
|
function drawMuzzleFlash(ctx, progress) {
|
|
const flash = Math.max(0, 1 - Math.abs(progress - 0.5) / 0.045);
|
|
if (flash <= 0) {
|
|
return;
|
|
}
|
|
|
|
ctx.save();
|
|
ctx.globalCompositeOperation = "lighter";
|
|
ctx.shadowColor = FIRE;
|
|
ctx.shadowBlur = 28;
|
|
ctx.fillStyle = `rgba(249,115,22,${0.78 * flash})`;
|
|
ctx.beginPath();
|
|
ctx.moveTo(408, -118);
|
|
ctx.lineTo(528, -156);
|
|
ctx.lineTo(468, -112);
|
|
ctx.lineTo(532, -76);
|
|
ctx.lineTo(408, -100);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
ctx.fillStyle = `rgba(255,255,255,${0.74 * flash})`;
|
|
ctx.beginPath();
|
|
ctx.moveTo(414, -114);
|
|
ctx.lineTo(486, -132);
|
|
ctx.lineTo(452, -108);
|
|
ctx.lineTo(492, -92);
|
|
ctx.lineTo(414, -102);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
ctx.restore();
|
|
}
|
|
|
|
/**
|
|
* 绘制 99A 主战坦克主体。
|
|
*
|
|
* @param {CanvasRenderingContext2D} ctx Canvas 上下文
|
|
* @param {number} x 坦克中心 x
|
|
* @param {number} y 坦克中心 y
|
|
* @param {number} scale 缩放比例
|
|
* @param {number} progress 播放进度
|
|
*/
|
|
function drawTank(ctx, x, y, scale, progress) {
|
|
ctx.save();
|
|
ctx.translate(x, y);
|
|
ctx.scale(scale, scale);
|
|
|
|
ctx.save();
|
|
ctx.shadowColor = "rgba(0,0,0,0.72)";
|
|
ctx.shadowBlur = 16;
|
|
ctx.fillStyle = "rgba(0,0,0,0.45)";
|
|
ctx.beginPath();
|
|
ctx.ellipse(0, 54, 250, 22, 0, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.restore();
|
|
|
|
drawMuzzleFlash(ctx, progress);
|
|
|
|
// 99A 履带底盘:右侧前导轮更大,模拟参考图的右前方视角。
|
|
const track = ctx.createLinearGradient(-246, 24, 246, 94);
|
|
track.addColorStop(0, "#0a0a0a");
|
|
track.addColorStop(0.42, DARK_ARMOR);
|
|
track.addColorStop(1, "#030712");
|
|
ctx.fillStyle = track;
|
|
ctx.beginPath();
|
|
ctx.moveTo(-250, 32);
|
|
ctx.lineTo(-206, 10);
|
|
ctx.lineTo(184, 12);
|
|
ctx.lineTo(252, 38);
|
|
ctx.lineTo(218, 94);
|
|
ctx.lineTo(-226, 96);
|
|
ctx.lineTo(-270, 70);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
ctx.fillStyle = "rgba(15,23,42,0.74)";
|
|
ctx.beginPath();
|
|
ctx.moveTo(-236, 22);
|
|
ctx.lineTo(180, 23);
|
|
ctx.lineTo(236, 43);
|
|
ctx.lineTo(204, 62);
|
|
ctx.lineTo(-218, 58);
|
|
ctx.lineTo(-256, 42);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
for (let i = 0; i < 7; i++) {
|
|
const wheelX = -180 + i * 58;
|
|
const wheelY = 62 + (i > 4 ? 2 : 0);
|
|
drawRoadWheel(ctx, wheelX, wheelY, progress + i * 0.05);
|
|
}
|
|
|
|
ctx.strokeStyle = "rgba(253,230,138,0.14)";
|
|
ctx.lineWidth = 4;
|
|
ctx.beginPath();
|
|
ctx.moveTo(-218, 82);
|
|
ctx.lineTo(212, 82);
|
|
ctx.stroke();
|
|
|
|
// 99A 车体:右侧为前装甲,首上装甲呈明显楔形下压。
|
|
const hull = ctx.createLinearGradient(-238, -50, 250, 36);
|
|
hull.addColorStop(0, "#42512b");
|
|
hull.addColorStop(0.38, ARMOR);
|
|
hull.addColorStop(0.66, CAMO);
|
|
hull.addColorStop(1, "#253018");
|
|
ctx.fillStyle = hull;
|
|
ctx.beginPath();
|
|
ctx.moveTo(-238, 24);
|
|
ctx.lineTo(-212, -30);
|
|
ctx.lineTo(86, -52);
|
|
ctx.lineTo(226, -24);
|
|
ctx.lineTo(252, 14);
|
|
ctx.lineTo(202, 38);
|
|
ctx.lineTo(-216, 36);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
ctx.fillStyle = "rgba(15,23,42,0.34)";
|
|
ctx.beginPath();
|
|
ctx.moveTo(68, -44);
|
|
ctx.lineTo(226, -22);
|
|
ctx.lineTo(248, 9);
|
|
ctx.lineTo(138, 18);
|
|
ctx.lineTo(86, -5);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// 侧裙装甲模块和数码迷彩块,增强 99A 识别度。
|
|
for (let i = 0; i < 8; i++) {
|
|
const panelX = -206 + i * 50;
|
|
ctx.fillStyle = i % 2 === 0 ? "rgba(54,83,20,0.86)" : "rgba(120,113,55,0.82)";
|
|
ctx.fillRect(panelX, -2, 42, 24);
|
|
ctx.strokeStyle = "rgba(15,23,42,0.34)";
|
|
ctx.lineWidth = 1.4;
|
|
ctx.strokeRect(panelX, -2, 42, 24);
|
|
}
|
|
|
|
[
|
|
[-186, -24, 34, 18, "#7f8f57"],
|
|
[-118, -34, 42, 20, "#b9855a"],
|
|
[-32, -39, 48, 22, "#344329"],
|
|
[52, -46, 44, 20, "#8a9b61"],
|
|
[118, -30, 38, 19, "#a36f52"],
|
|
[182, -14, 46, 21, "#415329"],
|
|
[-220, 2, 30, 20, "#27351f"],
|
|
[-72, 4, 36, 18, "#718246"],
|
|
[22, 2, 42, 20, "#ac7654"],
|
|
].forEach(([px, py, pw, ph, color]) => {
|
|
ctx.fillStyle = color;
|
|
ctx.fillRect(px, py, pw, ph);
|
|
});
|
|
|
|
// 参考图里的大号车号、国旗和前车灯。
|
|
ctx.fillStyle = "rgba(255,255,255,0.9)";
|
|
ctx.font = "900 31px serif";
|
|
ctx.fillText("807", -216, -12);
|
|
ctx.fillStyle = "rgba(220,38,38,0.95)";
|
|
ctx.fillRect(-128, -31, 34, 22);
|
|
ctx.fillStyle = "rgba(253,224,71,0.95)";
|
|
ctx.font = "900 12px serif";
|
|
ctx.fillText("★", -122, -16);
|
|
ctx.fillStyle = "rgba(254,242,242,0.88)";
|
|
roundRect(ctx, 178, -17, 18, 9, 5);
|
|
ctx.fill();
|
|
roundRect(ctx, 214, -8, 18, 9, 5);
|
|
ctx.fill();
|
|
|
|
// 低矮楔形炮塔和 125mm 长炮管:炮管朝右并略微上扬。
|
|
const turret = ctx.createLinearGradient(-128, -108, 156, -28);
|
|
turret.addColorStop(0, "#66734a");
|
|
turret.addColorStop(0.46, "#87905d");
|
|
turret.addColorStop(1, "#24301d");
|
|
ctx.fillStyle = turret;
|
|
ctx.beginPath();
|
|
ctx.moveTo(-132, -36);
|
|
ctx.lineTo(-82, -96);
|
|
ctx.lineTo(86, -108);
|
|
ctx.lineTo(158, -72);
|
|
ctx.lineTo(112, -32);
|
|
ctx.lineTo(-118, -20);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
[
|
|
[-98, -76, 42, 22, "#314222"],
|
|
[-28, -90, 50, 22, "#a36f52"],
|
|
[46, -92, 48, 20, "#73844d"],
|
|
[98, -66, 34, 20, "#2f3f24"],
|
|
].forEach(([px, py, pw, ph, color]) => {
|
|
ctx.fillStyle = color;
|
|
ctx.fillRect(px, py, pw, ph);
|
|
});
|
|
|
|
ctx.save();
|
|
ctx.strokeStyle = "rgba(17,24,39,0.96)";
|
|
ctx.lineWidth = 18;
|
|
ctx.lineCap = "round";
|
|
ctx.beginPath();
|
|
ctx.moveTo(106, -70);
|
|
ctx.lineTo(410, -120);
|
|
ctx.stroke();
|
|
ctx.strokeStyle = "#64748b";
|
|
ctx.lineWidth = 6;
|
|
ctx.beginPath();
|
|
ctx.moveTo(114, -75);
|
|
ctx.lineTo(404, -123);
|
|
ctx.stroke();
|
|
ctx.fillStyle = "#1f2937";
|
|
roundRect(ctx, 392, -132, 30, 24, 8);
|
|
ctx.fill();
|
|
ctx.fillStyle = "#40513a";
|
|
roundRect(ctx, 250, -107, 24, 20, 5);
|
|
ctx.fill();
|
|
roundRect(ctx, 322, -120, 24, 20, 5);
|
|
ctx.fill();
|
|
ctx.restore();
|
|
|
|
ctx.fillStyle = "rgba(17,24,39,0.95)";
|
|
roundRect(ctx, 86, -80, 62, 24, 8);
|
|
ctx.fill();
|
|
|
|
// 炮塔前装甲、烟幕弹发射器和车长机枪。
|
|
ctx.fillStyle = "rgba(15,23,42,0.5)";
|
|
ctx.beginPath();
|
|
ctx.moveTo(42, -48);
|
|
ctx.lineTo(96, -78);
|
|
ctx.lineTo(154, -66);
|
|
ctx.lineTo(112, -36);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
for (let i = 0; i < 4; i++) {
|
|
ctx.fillStyle = "rgba(15,23,42,0.9)";
|
|
roundRect(ctx, -124 + i * 13, -58 + (i % 2) * 10, 10, 18, 4);
|
|
ctx.fill();
|
|
}
|
|
|
|
ctx.strokeStyle = "rgba(15,23,42,0.9)";
|
|
ctx.lineWidth = 3;
|
|
ctx.beginPath();
|
|
ctx.moveTo(-24, -102);
|
|
ctx.lineTo(-18, -132);
|
|
ctx.lineTo(40, -138);
|
|
ctx.stroke();
|
|
|
|
ctx.fillStyle = "rgba(15,23,42,0.84)";
|
|
roundRect(ctx, -32, -108, 58, 14, 7);
|
|
ctx.fill();
|
|
|
|
// 装甲高光。
|
|
ctx.strokeStyle = "rgba(226,232,240,0.28)";
|
|
ctx.lineWidth = 2;
|
|
ctx.beginPath();
|
|
ctx.moveTo(-184, -22);
|
|
ctx.lineTo(92, -40);
|
|
ctx.moveTo(-142, 8);
|
|
ctx.lineTo(172, -2);
|
|
ctx.moveTo(92, -82);
|
|
ctx.lineTo(144, -66);
|
|
ctx.stroke();
|
|
|
|
ctx.restore();
|
|
}
|
|
|
|
/**
|
|
* 绘制坦克负重轮。
|
|
*
|
|
* @param {CanvasRenderingContext2D} ctx Canvas 上下文
|
|
* @param {number} x 中心 x
|
|
* @param {number} y 中心 y
|
|
* @param {number} progress 播放进度
|
|
*/
|
|
function drawRoadWheel(ctx, x, y, progress) {
|
|
ctx.save();
|
|
ctx.translate(x, y);
|
|
ctx.rotate(progress * 30);
|
|
ctx.fillStyle = "#111827";
|
|
ctx.beginPath();
|
|
ctx.arc(0, 0, 21, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.strokeStyle = "#475569";
|
|
ctx.lineWidth = 3;
|
|
ctx.stroke();
|
|
ctx.strokeStyle = "#94a3b8";
|
|
ctx.lineWidth = 2;
|
|
for (let i = 0; i < 6; i++) {
|
|
ctx.rotate(Math.PI / 3);
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, 0);
|
|
ctx.lineTo(16, 0);
|
|
ctx.stroke();
|
|
}
|
|
ctx.fillStyle = "#cbd5e1";
|
|
ctx.beginPath();
|
|
ctx.arc(0, 0, 5, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.restore();
|
|
}
|
|
|
|
/**
|
|
* 绘制 99A 重装入场 HUD 字幕。
|
|
*
|
|
* @param {CanvasRenderingContext2D} ctx Canvas 上下文
|
|
* @param {number} w 画布宽度
|
|
* @param {number} h 画布高度
|
|
* @param {number} progress 播放进度
|
|
* @param {string} title 入场标题
|
|
* @param {string} userInfo 用户身份信息
|
|
*/
|
|
function drawHud(ctx, w, h, progress, title, userInfo) {
|
|
const enter = Math.min(1, Math.max(0, (progress - 0.14) / 0.2));
|
|
const leave = Math.min(1, Math.max(0, (1 - progress) / 0.16));
|
|
const alpha = easeInOutSine(enter) * leave;
|
|
const y = h * 0.17 - (1 - enter) * 24;
|
|
|
|
ctx.save();
|
|
ctx.globalAlpha = alpha;
|
|
ctx.textAlign = "center";
|
|
ctx.shadowColor = "rgba(253,230,138,0.95)";
|
|
ctx.shadowBlur = 22;
|
|
ctx.fillStyle = "rgba(28,25,23,0.66)";
|
|
ctx.strokeStyle = "rgba(253,230,138,0.72)";
|
|
ctx.lineWidth = 2;
|
|
roundRect(ctx, w * 0.5 - 340, y - 56, 680, 120, 18);
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
|
|
ctx.fillStyle = "#fef3c7";
|
|
ctx.font = "700 16px serif";
|
|
ctx.fillText("ZTZ-99A ARMORED FORCE", w * 0.5, y - 24);
|
|
ctx.fillStyle = "#fde68a";
|
|
ctx.font = "700 18px serif";
|
|
ctx.fillText(userInfo, w * 0.5, y + 8, 620);
|
|
ctx.fillStyle = "#ffffff";
|
|
ctx.font = "900 34px serif";
|
|
ctx.fillText(title, w * 0.5, y + 45, 620);
|
|
ctx.restore();
|
|
}
|
|
|
|
/**
|
|
* 绘制圆角矩形路径。
|
|
*
|
|
* @param {CanvasRenderingContext2D} ctx Canvas 上下文
|
|
* @param {number} x 左上角 x
|
|
* @param {number} y 左上角 y
|
|
* @param {number} w 宽度
|
|
* @param {number} h 高度
|
|
* @param {number} r 圆角半径
|
|
*/
|
|
function roundRect(ctx, x, y, w, h, r) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(x + r, y);
|
|
ctx.lineTo(x + w - r, y);
|
|
ctx.quadraticCurveTo(x + w, y, x + w, y + r);
|
|
ctx.lineTo(x + w, y + h - r);
|
|
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
|
|
ctx.lineTo(x + r, y + h);
|
|
ctx.quadraticCurveTo(x, y + h, x, y + h - r);
|
|
ctx.lineTo(x, y + r);
|
|
ctx.quadraticCurveTo(x, y, x + r, y);
|
|
ctx.closePath();
|
|
}
|
|
|
|
/**
|
|
* 启动 99A 主战坦克重装入场特效。
|
|
*
|
|
* @param {HTMLCanvasElement} canvas 全屏特效画布
|
|
* @param {Function} onEnd 结束回调
|
|
* @param {object} options 特效附加参数
|
|
* @returns {{cancel: Function}}
|
|
*/
|
|
function start(canvas, onEnd, options = {}) {
|
|
const ctx = canvas.getContext("2d");
|
|
const w = canvas.width;
|
|
const h = canvas.height;
|
|
const dust = createDust(w, h);
|
|
const title = String(options.effect_title || "99A主战坦克 重装入场").trim() || "99A主战坦克 重装入场";
|
|
const userInfo = String(options.effect_user_info || "").trim();
|
|
const startTime = performance.now();
|
|
let animId = null;
|
|
let finished = false;
|
|
|
|
/**
|
|
* 统一结束动画,手动取消时只清理不回调。
|
|
*
|
|
* @param {boolean} canceled 是否为手动取消
|
|
*/
|
|
function finish(canceled) {
|
|
if (finished) {
|
|
return;
|
|
}
|
|
|
|
finished = true;
|
|
if (animId) {
|
|
cancelAnimationFrame(animId);
|
|
}
|
|
ctx.clearRect(0, 0, w, h);
|
|
if (!canceled) {
|
|
onEnd();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 逐帧绘制坦克入场动画。
|
|
*
|
|
* @param {number} now 当前高精度时间
|
|
*/
|
|
function animate(now) {
|
|
const elapsed = now - startTime;
|
|
const progress = Math.min(1, elapsed / DURATION);
|
|
const entry = easeOutCubic(Math.min(1, progress / 0.64));
|
|
const exit = easeInOutSine(Math.max(0, (progress - 0.76) / 0.24));
|
|
const tankX = -w * 0.24 + entry * w * 0.92 + exit * w * 0.62;
|
|
const tankY = h * 0.66 + Math.sin(progress * 24) * 2.5;
|
|
const scale = Math.min(1.12, Math.max(0.68, w / 1180));
|
|
|
|
ctx.clearRect(0, 0, w, h);
|
|
drawBackdrop(ctx, w, h, progress);
|
|
drawDust(ctx, dust, w, progress);
|
|
drawShockwave(ctx, w, h, progress);
|
|
drawTank(ctx, tankX, tankY, scale, progress);
|
|
drawHud(ctx, w, h, progress, title, userInfo);
|
|
|
|
if (progress < 1) {
|
|
animId = requestAnimationFrame(animate);
|
|
} else {
|
|
finish(false);
|
|
}
|
|
}
|
|
|
|
animId = requestAnimationFrame(animate);
|
|
|
|
return {
|
|
cancel() {
|
|
finish(true);
|
|
},
|
|
};
|
|
}
|
|
|
|
return { start };
|
|
})();
|
|
|
|
window.Type99AEffect = Type99AEffect;
|