canvas游戏开发中用纯esm分离粒子系统和物理引擎,核心是通过静态导入导出实现职责解耦:粒子系统专注生成、更新、渲染数据,物理引擎纯函数式处理状态更新,主循环通过函数参数传递数据流,确保无全局污染、可树摇、易测试。

在Canvas游戏开发中用纯ESM结构分离粒子系统和物理引擎,核心是利用ES6模块的静态导入导出机制实现职责解耦,同时保证运行时无全局污染、可树摇、易测试。关键不在“能不能用”,而在于如何设计模块边界与数据流。
粒子系统模块化:只暴露行为接口,不持有状态
粒子系统不应自己管理canvas上下文或帧循环,而是专注生成、更新、渲染粒子数据。用类封装但导出工厂函数,避免new调用污染模块作用域:
// particles.js
export class Particle {
constructor(x, y, vx, vy, life) {
this.x = x; this.y = y;
this.vx = vx; this.vy = vy;
this.life = life;
}
}
<p>export function createParticleSystem() {
const particles = [];</p><p>return {
spawn(x, y, count = 1) {
for (let i = 0; i 20,
y + (Math.random() - 0.5) 20,
(Math.random() - 0.5) <em> 4,
(Math.random() - 0.5) </em> 4,
60 + Math.random() * 40
));
}
},</p><pre class="brush:php;toolbar:false;">update(delta) {
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.vx * delta;
p.y += p.vy * delta;
p.life -= delta;
if (p.life <p>};
}</p>物理引擎模块化:纯函数式 + 状态不可变更新
物理引擎负责碰撞检测、力计算、速度积分,但不直接操作粒子实例。它接收粒子数据数组,返回新数组(或就地更新并返回引用),便于与粒子系统解耦:
// physics.js
export function applyGravity(particles, gravity = 0.2) {
return particles.map(p => ({
...p,
vy: p.vy + gravity
}));
}
<p>export function bounceOffBounds(particles, width, height, damping = 0.8) {
return particles.map(p => {
let nx = p.x, ny = p.y, nvx = p.vx, nvy = p.vy;</p><pre class="brush:php;toolbar:false;">if (p.x = width) {
nx = p.x = height) {
ny = p.y <p>});
}</p><p>// 可组合的物理管道
export function runPhysics(particles, config) {
let result = [...particles];
if (config.gravity) result = applyGravity(result, config.gravity);
if (config.bounds) result = bounceOffBounds(result, ...config.bounds);
return result;
}</p>主游戏循环中组合使用:ESM驱动数据流,非继承或单例
入口文件(如main.js)用顶层await加载模块,用requestAnimationFrame驱动,把物理更新和粒子更新串成明确的数据流水线:
// main.js
import { createParticleSystem } from './particles.js';
import { runPhysics } from './physics.js';
<p>const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const particleSystem = createParticleSystem();</p><p>// 模块间通信仅通过函数参数和返回值
let particles = [];</p><p>function gameLoop(timestamp) {
const delta = Math.min(16, timestamp - (window.lastTime || timestamp));
window.lastTime = timestamp;</p><p>// 1. 用户输入或事件触发粒子生成
if (Math.random() </p><p>// 2. 获取当前粒子快照,交给物理引擎处理
particles = runPhysics(particles, {
gravity: 0.3,
bounds: [canvas.width, canvas.height]
});</p><p>// 3. 粒子系统接管渲染(也可让粒子系统自己维护内部数组)
particleSystem.render(ctx);</p><p>requestAnimationFrame(gameLoop);
}</p><p>requestAnimationFrame(gameLoop);</p>构建与部署注意点:确保浏览器原生支持
纯ESM项目需服务端支持.mjs后缀或正确Content-Type,开发时推荐用vite或esbuild dev server。生产环境若需兼容旧浏览器,应配合rollup或webpack打包为IIFE,但模块结构仍保持ESM源码组织——分离不是为了打包,而是为了可读性与可替换性:
- HTML中必须用
<script type="module"></script>引入入口 - 所有路径需带扩展名(
./physics.js不能写成./physics) - 避免动态
import()混用,除非做按需加载特效模块 - 物理参数可抽到
config.js单独导出,方便A/B测试或调试面板注入
不复杂但容易忽略:粒子与物理的分离本质是数据流向控制——物理输出新状态,粒子消费该状态。ESM只是让这种契约显式化、可验证、可独立测试。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











