前端游戏开发必备20种经典物理算法:从入门到实战,打造丝滑物理交互

15 阅读35分钟

对于前端开发者而言,游戏开发与常规页面开发的核心区别,在于“交互的真实感”。很多新手入门游戏开发时,容易陷入“重视觉、轻物理”的误区,花大量时间打磨UI,却忽略了底层物理逻辑——角色跳跃没有重力下坠感、物体碰撞缺乏合理反馈、粒子运动毫无规律,最终导致游戏体验生硬,难以留住玩家。

其实,前端游戏所需的物理算法,并非高校物理课本中的复杂推导,而是对现实物理规律的工程化简化与落地。无论是Canvas开发2D小游戏,还是用Three.js搭建3D场景,无论是原生开发还是基于Phaser、PixiJS框架开发,掌握经典物理算法,都是实现“丝滑交互”的关键。

本文完全原创,无任何抄袭,聚焦前端游戏开发高频需求,整理了20种经典物理算法,按“基础运动→受力分析→碰撞检测→约束控制→优化适配”五大维度分类,每个算法均包含「原理拆解+原创代码+场景落地」,摒弃冗余理论,聚焦实操,适合前端游戏新手快速上手,也适合资深开发者查漏补缺,帮你彻底摆脱“伪物理”交互困境。

一、开篇思考:前端游戏的物理算法,核心是“简化而非复刻”

很多前端开发者会困惑:现实世界的物理规律极其复杂,比如空气阻力会随速度变化、物体碰撞会产生形变、重力会随海拔改变,难道游戏中都要一一模拟?答案是否定的。

前端游戏物理算法的核心逻辑是“模拟真实,而非复刻真实”——我们只需提取现实物理规律的核心特征,用最简单的代码实现“符合玩家直觉”的效果,同时兼顾浏览器性能。毕竟,玩家追求的是“看起来真实”,而非“完全真实”。

举个直观的例子:现实中苹果下落会受空气阻力影响,速度趋于恒定,但游戏中我们只需用简单的匀加速算法模拟重力,就能让玩家感受到“自然下落”的效果;再比如,现实中两个物体碰撞会有能量损耗和形变,但游戏中只需用反弹算法+能量衰减系数,就能实现合理的碰撞反馈,既简单又高效。

无需担心自己的物理基础薄弱,本文所有算法都避开复杂公式推导,聚焦“前端可落地”,用JavaScript代码直观呈现,哪怕是零基础,也能跟着案例一步步实现。下面,我们正式开始20种经典物理算法的拆解,从基础到复杂,循序渐进。

二、基础运动类算法(4种,入门必备)

运动类算法是游戏物理的基石,核心解决“物体如何移动”的问题,重点描述位置、速度、加速度三者的关系,适配前端requestAnimationFrame帧更新逻辑(默认60FPS,每帧间隔约16.67ms),所有复杂物理效果,都基于这4种基础算法扩展而来。

1. 线性匀加速运动算法(经典基础)

原理:物体运动时,加速度始终保持恒定,速度随时间线性递增,位置随时间呈二次函数变化。前端开发中,我们无需考虑复杂的物理单位(如m/s²),只需将加速度简化为“像素/帧²”,适配屏幕渲染逻辑。

核心逻辑:每帧根据帧间隔时间(dt)更新速度,再根据速度更新位置,确保运动平滑,不受帧率波动影响。

原创代码实现(模拟雨滴下落,匀加速运动):

// 雨滴类,封装匀加速运动逻辑
class Raindrop {
  constructor() {
    this.x = Math.random() * window.innerWidth; // 随机x坐标
    this.y = -20; // 初始位置在屏幕上方
    this.speed = 0; // 初始速度(静止)
    this.acceleration = 0.3; // 匀加速度(模拟重力)
  }

  // 帧更新方法
  update(timestamp, lastTime) {
    // 计算帧间隔时间dt(秒),避免帧率影响运动速度
    const dt = (timestamp - lastTime) / 1000;
    // 更新速度:速度 = 当前速度 + 加速度 × 时间
    this.speed += this.acceleration * dt * 60; // ×60适配60FPS
    // 更新位置:位置 = 当前位置 + 速度 × 时间
    this.y += this.speed * dt;
    // 超出屏幕后重置
    if (this.y > window.innerHeight) {
      this.y = -20;
      this.x = Math.random() * window.innerWidth;
      this.speed = 0;
    }
  }

  // 渲染方法(简化)
  render(ctx) {
    ctx.fillStyle = 'rgba(138, 43, 226, 0.6)';
    ctx.beginPath();
    ctx.arc(this.x, this.y, 3, 0, Math.PI * 2);
    ctx.fill();
  }
}

// 初始化雨滴并启动帧更新
const raindrops = Array(50).fill().map(() => new Raindrop());
let lastTime = 0;
function gameLoop(timestamp) {
  const ctx = document.getElementById('canvas').getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  
  raindrops.forEach(raindrop => {
    raindrop.update(timestamp, lastTime);
    raindrop.render(ctx);
  });
  
  lastTime = timestamp;
  requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);

应用场景:雨滴下落、物体自由坠落、角色加速冲刺、子弹发射后的加速运动等。

2. 恒速运动算法(经典简化)

原理:匀加速运动的特殊情况,加速度为0,速度保持恒定,位置随时间均匀变化。核心是“速度不变,仅更新位置”,计算量最小,性能最优。

核心亮点:无需处理加速度变化,适合所有“匀速移动”场景,避免多余计算,提升游戏运行效率。

原创代码实现(模拟游戏背景匀速滚动):

// 背景滚动类
class Background {
  constructor() {
    this.x = 0; // 初始x坐标
    this.y = 0; // 初始y坐标
    this.speed = 30; // 匀速滚动速度(像素/秒)
    this.width = window.innerWidth;
    this.height = window.innerHeight;
  }

  update(timestamp, lastTime) {
    const dt = (timestamp - lastTime) / 1000;
    // 匀速更新x坐标(向左滚动)
    this.x -= this.speed * dt;
    // 滚动到底部后重置,实现无缝循环
    if (this.x <= -this.width) {
      this.x = 0;
    }
  }

  render(ctx) {
    // 绘制背景(简化,实际用背景图)
    ctx.fillStyle = '#87CEEB';
    ctx.fillRect(this.x, this.y, this.width, this.height);
    // 绘制第二个背景,实现无缝滚动
    ctx.fillRect(this.x + this.width, this.y, this.width, this.height);
  }
}

// 启动背景滚动
const background = new Background();
let lastTime = 0;
function gameLoop(timestamp) {
  const ctx = document.getElementById('canvas').getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  
  background.update(timestamp, lastTime);
  background.render(ctx);
  
  lastTime = timestamp;
  requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);

应用场景:游戏背景滚动、角色正常行走、子弹直线飞行、NPC匀速巡逻等。

3. 线性匀减速运动算法(经典核心)

原理:加速度与速度方向相反,速度随时间线性递减,直到速度为0(或达到最小速度),核心是“反向加速度抵消速度”,让物体实现“自然减速”效果。

核心注意点:需判断速度是否减至0,避免物体反向运动(根据游戏需求调整)。

原创代码实现(模拟汽车刹车减速):

// 汽车类,模拟刹车减速
class Car {
  constructor() {
    this.x = 50;
    this.y = 300;
    this.speed = 100; // 初始行驶速度(像素/秒)
    this.deceleration = 30; // 减速加速度(反向,像素/秒²)
    this.width = 60;
    this.height = 30;
  }

  update(timestamp, lastTime) {
    const dt = (timestamp - lastTime) / 1000;
    // 减速更新:速度 = 当前速度 - 减速加速度 × 时间
    this.speed = Math.max(0, this.speed - this.deceleration * dt);
    // 更新位置
    this.x += this.speed * dt;
  }

  render(ctx) {
    ctx.fillStyle = '#FF6347';
    ctx.fillRect(this.x, this.y, this.width, this.height);
  }
}

// 启动刹车模拟
const car = new Car();
let lastTime = 0;
function gameLoop(timestamp) {
  const ctx = document.getElementById('canvas').getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  
  car.update(timestamp, lastTime);
  car.render(ctx);
  
  lastTime = timestamp;
  requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);

应用场景:汽车刹车、角色停止移动、物体滑行减速、子弹飞行减速等。

4. 抛物线运动算法(经典高频)

原理:水平方向做匀速运动(加速度为0),垂直方向做匀加速运动(模拟重力),两者合成抛物线轨迹,核心是“分方向独立计算,再合并运动效果”。

核心亮点:适配所有抛射类场景,是前端游戏中最常用的运动算法之一,如投篮、抛石、弓箭射击等。

原创代码实现(模拟投篮运动,抛物线轨迹):

// 篮球类,模拟抛物线运动
class Basketball {
  constructor() {
    this.x = 100; // 初始位置(投篮点)
    this.y = 300;
    this.speedX = 80; // 水平匀速速度(像素/秒)
    this.speedY = -70; // 垂直初始速度(向上,负号表示方向)
    this.gravity = 0.25; // 垂直方向重力加速度
    this.radius = 15;
  }

  update(timestamp, lastTime) {
    const dt = (timestamp - lastTime) / 1000;
    // 水平方向:匀速运动,速度不变
    this.x += this.speedX * dt;
    // 垂直方向:匀加速运动,受重力影响
    this.speedY += this.gravity * dt * 60;
    this.y += this.speedY * dt;
    // 落地后反弹(简化)
    if (this.y >= 400) {
      this.speedY = -this.speedY * 0.6; // 反弹速度衰减60%
      this.y = 400;
    }
  }

  render(ctx) {
    ctx.fillStyle = '#FFD700';
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.strokeStyle = '#000';
    ctx.stroke();
  }
}

// 启动投篮模拟
const basketball = new Basketball();
let lastTime = 0;
function gameLoop(timestamp) {
  const ctx = document.getElementById('canvas').getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  // 绘制地面
  ctx.fillStyle = '#228B22';
  ctx.fillRect(0, 400, canvas.width, 20);
  
  basketball.update(timestamp, lastTime);
  basketball.render(ctx);
  
  lastTime = timestamp;
  requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);

应用场景:投篮游戏、弓箭射击、抛石砸物、愤怒的小鸟类抛射场景等。

三、受力分析类算法(4种,提升真实感)

受力分析类算法,核心解决“物体为什么会这样运动”的问题,将现实世界中的力(重力、摩擦力、推力等)转化为代码逻辑,作用于物体的加速度,进而改变运动状态。前端游戏中,无需模拟复杂的力的分解,只需简化为“影响加速度的系数”,即可实现真实的受力效果。

5. 重力算法(经典核心)

原理:地球重力的简化模拟,方向固定(2D场景中垂直向下,对应Canvas的y轴正方向),大小恒定,核心是“给物体垂直方向添加固定加速度”。

核心优化:可根据游戏场景调整重力大小(如太空场景重力减小,水下场景重力减弱),提升场景适配性。

原创代码实现(模拟角色跳跃+重力效果):

// 角色类,包含重力和跳跃逻辑
class Player {
  constructor() {
    this.x = 150;
    this.y = 300;
    this.width = 50;
    this.height = 80;
    this.speedX = 0;
    this.speedY = 0;
    this.gravity = 0.2; // 重力加速度
    this.jumpPower = -12; // 跳跃推力(向上)
    this.isOnGround = true; // 防止双重跳跃
  }

  // 跳跃触发
  jump() {
    if (this.isOnGround) {
      this.speedY = this.jumpPower;
      this.isOnGround = false;
    }
  }

  // 移动控制(左右方向)
  move(direction) {
    this.speedX = direction * 50; // 50为移动速度
  }

  update(timestamp, lastTime) {
    const dt = (timestamp - lastTime) / 1000;
    // 应用重力:垂直方向加速度更新速度
    this.speedY += this.gravity * dt * 60;
    // 水平方向移动
    this.x += this.speedX * dt;
    // 垂直方向移动
    this.y += this.speedY * dt;
    // 落地检测(简化)
    if (this.y >= 300) {
      this.y = 300;
      this.speedY = 0;
      this.isOnGround = true;
    }
    // 边界检测,防止角色超出屏幕
    this.x = Math.max(0, Math.min(this.x, window.innerWidth - this.width));
  }

  render(ctx) {
    ctx.fillStyle = '#4169E1';
    ctx.fillRect(this.x, this.y, this.width, this.height);
  }
}

// 初始化角色并绑定控制事件
const player = new Player();
document.addEventListener('keydown', (e) => {
  if (e.code === 'Space') player.jump();
  if (e.code === 'ArrowLeft') player.move(-1);
  if (e.code === 'ArrowRight') player.move(1);
});
document.addEventListener('keyup', (e) => {
  if (e.code === 'ArrowLeft' || e.code === 'ArrowRight') player.move(0);
});

// 启动游戏循环
let lastTime = 0;
function gameLoop(timestamp) {
  const ctx = document.getElementById('canvas').getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  // 绘制地面
  ctx.fillStyle = '#8B4513';
  ctx.fillRect(0, 380, canvas.width, 20);
  
  player.update(timestamp, lastTime);
  player.render(ctx);
  
  lastTime = timestamp;
  requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);

应用场景:所有包含“下落”“跳跃”的游戏,如超级马里奥、flappy bird、平台跳跃类游戏等。

6. 摩擦力算法(经典高频)

原理:阻碍物体运动的力,方向与物体运动方向相反,大小与速度成正比(或固定值),核心是“通过速度衰减系数,让物体逐渐减速”。

分类:静摩擦力(物体静止时)和动摩擦力(物体运动时),前端游戏中可简化为“动摩擦力”,用衰减系数实现。

原创代码实现(模拟滑块在地面滑行,受摩擦力减速):

// 滑块类,模拟摩擦力效果
class Slider {
  constructor() {
    this.x = 100;
    this.y = 200;
    this.width = 40;
    this.height = 20;
    this.speedX = 80; // 初始滑行速度
    this.friction = 0.96; // 摩擦力衰减系数(0~1,越小减速越快)
    this.gravity = 0.1;
    this.speedY = 0;
  }

  update(timestamp, lastTime) {
    const dt = (timestamp - lastTime) / 1000;
    // 应用重力
    this.speedY += this.gravity * dt * 60;
    this.y += this.speedY * dt;
    // 落地检测
    if (this.y >= 300) {
      this.y = 300;
      this.speedY = 0;
      // 落地后施加摩擦力,减速滑行
      this.speedX *= this.friction;
    }
    // 更新水平位置
    this.x += this.speedX * dt;
    // 速度趋近于0时,停止运动(避免无限微小速度)
    if (Math.abs(this.speedX) < 0.1) {
      this.speedX = 0;
    }
  }

  render(ctx) {
    ctx.fillStyle = '#9370DB';
    ctx.fillRect(this.x, this.y, this.width, this.height);
  }
}

// 启动滑块模拟
const slider = new Slider();
let lastTime = 0;
function gameLoop(timestamp) {
  const ctx = document.getElementById('canvas').getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  // 绘制地面
  ctx.fillStyle = '#8B4513';
  ctx.fillRect(0, 320, canvas.width, 20);
  
  slider.update(timestamp, lastTime);
  slider.render(ctx);
  
  lastTime = timestamp;
  requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);

应用场景:物体滑行、角色停止移动、汽车刹车、冰面滑行等需要“减速停止”的场景。

7. 推力算法(经典实用)

原理:瞬间或持续作用于物体的力,改变物体的加速度(持续推力)或速度(瞬间推力),核心是“给物体添加额外的速度或加速度”。

分类:瞬间推力(如跳跃、子弹发射)和持续推力(如推进器、风力),前端游戏中可根据场景选择对应方式。

原创代码实现(模拟火箭推进器,持续推力):

// 火箭类,模拟持续推力效果
class Rocket {
  constructor() {
    this.x = window.innerWidth / 2;
    this.y = window.innerHeight - 100;
    this.radius = 15;
    this.speedX = 0;
    this.speedY = 0;
    this.gravity = 0.15;
    this.thrustPower = 0.3; // 持续推力加速度
    this.isThrusting = false; // 是否开启推进器
  }

  // 开启/关闭推进器
  toggleThrust(flag) {
    this.isThrusting = flag;
  }

  update(timestamp, lastTime) {
    const dt = (timestamp - lastTime) / 1000;
    // 应用重力
    this.speedY += this.gravity * dt * 60;
    // 开启推进器时,施加持续推力(向上,抵消重力)
    if (this.isThrusting) {
      this.speedY -= this.thrustPower * dt * 60;
    }
    // 更新位置
    this.x += this.speedX * dt;
    this.y += this.speedY * dt;
    // 边界检测
    this.y = Math.max(this.radius, Math.min(this.y, window.innerHeight - this.radius));
  }

  render(ctx) {
    // 绘制火箭
    ctx.fillStyle = '#FF4500';
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
    ctx.fill();
    // 推进器火焰(开启时绘制)
    if (this.isThrusting) {
      ctx.fillStyle = '#FFD700';
      ctx.beginPath();
      ctx.moveTo(this.x, this.y + this.radius);
      ctx.lineTo(this.x - 8, this.y + this.radius + 15);
      ctx.lineTo(this.x + 8, this.y + this.radius + 15);
      ctx.fill();
    }
  }
}

// 初始化火箭并绑定控制事件
const rocket = new Rocket();
document.addEventListener('keydown', (e) => {
  if (e.code === 'Space') rocket.toggleThrust(true);
});
document.addEventListener('keyup', (e) => {
  if (e.code === 'Space') rocket.toggleThrust(false);
});

// 启动火箭模拟
let lastTime = 0;
function gameLoop(timestamp) {
  const ctx = document.getElementById('canvas').getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  
  rocket.update(timestamp, lastTime);
  rocket.render(ctx);
  
  lastTime = timestamp;
  requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);

应用场景:火箭推进、角色跳跃、子弹发射、风力推动、水流冲击等场景。

8. 力的合成算法(经典进阶)

原理:当物体同时受到多个力的作用(如斜面上的物体受重力、支持力、摩擦力),将每个力分解为x、y两个方向,分别计算合加速度,再合并运动效果,核心是“分方向计算,再合成”。

核心亮点:解决复杂受力场景,让物体运动更贴合现实,是进阶游戏开发的必备算法。

原创代码实现(模拟斜面上的滑块,受重力、支持力、摩擦力):

// 斜面滑块类,模拟多力合成
class SlopeSlider {
  constructor() {
    this.x = 100;
    this.y = 100;
    this.width = 40;
    this.height = 20;
    this.speedX = 0;
    this.speedY = 0;
    this.mass = 1; // 质量(简化计算)
    this.gravity = 0.2; // 重力加速度
    this.frictionCoeff = 0.1; // 摩擦系数
  }

  // 斜面参数(倾角30度,转化为弧度)
  get slopeAngle() {
    return Math.PI / 6;
  }

  update(timestamp, lastTime) {
    const dt = (timestamp - lastTime) / 1000;
    // 1. 分解重力(多力合成核心)
    const gravityForce = this.mass * this.gravity; // 重力大小
    // 沿斜面向下的分力(x方向)
    const forceX = gravityForce * Math.sin(this.slopeAngle);
    // 垂直斜面的分力(y方向,支持力的反作用力)
    const forceY = gravityForce * Math.cos(this.slopeAngle);
    
    // 2. 计算摩擦力(与运动方向相反)
    const frictionForce = forceY * this.frictionCoeff;
    const frictionX = -Math.sign(this.speedX) * frictionForce;
    
    // 3. 计算合加速度(合外力/质量)
    const accX = (forceX + frictionX) / this.mass;
    const accY = 0; // 垂直斜面方向无运动,加速度为0
    
    // 4. 更新速度和位置
    this.speedX += accX * dt * 60;
    this.x += this.speedX * dt;
    // 斜面位置修正(简化,确保滑块在斜面上)
    this.y = 100 + this.x * Math.tan(this.slopeAngle);
    
    // 速度衰减,避免无限加速
    if (Math.abs(this.speedX) < 0.1) {
      this.speedX = 0;
    }
  }

  render(ctx) {
    // 绘制斜面
    ctx.strokeStyle = '#8B4513';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.moveTo(50, 100);
    ctx.lineTo(500, 100 + 450 * Math.tan(Math.PI / 6));
    ctx.stroke();
    // 绘制滑块
    ctx.fillStyle = '#20B2AA';
    ctx.fillRect(this.x, this.y, this.width, this.height);
  }
}

// 启动斜面滑块模拟
const slopeSlider = new SlopeSlider();
let lastTime = 0;
function gameLoop(timestamp) {
  const ctx = document.getElementById('canvas').getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  
  slopeSlider.update(timestamp, lastTime);
  slopeSlider.render(ctx);
  
  lastTime = timestamp;
  requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);

应用场景:斜面上的物体滑动、斜坡赛车、山地场景中的角色移动等复杂受力场景。

四、碰撞检测类算法(5种,游戏交互核心)

碰撞检测是游戏物理交互的灵魂,核心解决“两个物体是否接触”的问题,前端游戏中,核心思路是“简化几何形状”——将复杂物体的碰撞范围简化为矩形、圆形等简单形状,兼顾检测精度与性能。以下5种算法,覆盖所有2D游戏碰撞场景。

9. AABB轴对齐矩形碰撞算法(经典高频)

原理:Axis-Aligned Bounding Box(轴对齐矩形碰撞),适用于不旋转的矩形物体,核心是“判断两个矩形的边界是否重叠”,计算最简单、性能最高,是前端游戏最常用的碰撞检测算法。

核心逻辑:两个矩形A、B,若A的右边界<B的左边界、A的左边界>B的右边界、A的下边界<B的上边界、A的上边界>B的下边界,四个条件有一个成立,则无碰撞;否则存在碰撞。

原创代码实现(角色与障碍物碰撞检测):

/**
 * AABB轴对齐矩形碰撞检测(原创实现)
 * @param {Object} rectA - 矩形A {x, y, width, height}(x,y为左上角坐标)
 * @param {Object} rectB - 矩形B
 * @returns {boolean} 是否发生碰撞
 */
function checkAABBCollision(rectA, rectB) {
  // 四个边界判断,有一个成立则无碰撞
  const noCollision = 
    rectA.x + rectA.width < rectB.x || // A在B左侧
    rectA.x > rectB.x + rectB.width || // A在B右侧
    rectA.y + rectA.height < rectB.y || // A在B上方
    rectA.y > rectB.y + rectB.height; // A在B下方
  return !noCollision;
}

// 示例:角色与障碍物碰撞检测
const player = { x: 100, y: 300, width: 50, height: 80 };
const obstacle = { x: 200, y: 300, width: 40, height: 40 };

// 游戏循环中检测碰撞
function gameLoop() {
  const ctx = document.getElementById('canvas').getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  
  // 检测碰撞
  const isCollide = checkAABBCollision(player, obstacle);
  // 碰撞后处理(停止移动)
  if (isCollide) {
    player.x = 100; // 重置位置,避免穿透
    console.log('角色与障碍物碰撞!');
  } else {
    player.x += 1; // 未碰撞则移动
  }
  
  // 渲染角色和障碍物
  ctx.fillStyle = '#4169E1';
  ctx.fillRect(player.x, player.y, player.width, player.height);
  ctx.fillStyle = '#FF0000';
  ctx.fillRect(obstacle.x, obstacle.y, obstacle.width, obstacle.height);
  
  requestAnimationFrame(gameLoop);
}
gameLoop();

应用场景:角色与障碍物碰撞、平台跳跃类游戏的平台检测、方块消除游戏、UI元素碰撞等。

10. 圆形碰撞检测算法(经典简洁)

原理:适用于圆形或近似圆形物体,核心是“判断两个圆形的圆心距离是否小于等于两个圆的半径之和”,对旋转不敏感,计算简洁。

性能优化:为避免开平方运算(影响性能),可直接比较“距离的平方”与“半径和的平方”。

原创代码实现(子弹与敌人碰撞检测):

/**
 * 圆形碰撞检测(原创优化版)
 * @param {Object} circleA - 圆形A {x, y, radius}(x,y为圆心坐标)
 * @param {Object} circleB - 圆形B
 * @returns {boolean} 是否发生碰撞
 */
function checkCircleCollision(circleA, circleB) {
  // 计算两圆心x、y方向差值
  const dx = circleB.x - circleA.x;
  const dy = circleB.y - circleA.y;
  // 计算距离的平方(避免开平方,提升性能)
  const distanceSquared = dx * dx + dy * dy;
  // 计算半径和的平方
  const radiusSum = circleA.radius + circleB.radius;
  const radiusSumSquared = radiusSum * radiusSum;
  // 距离平方 <= 半径和平方,即为碰撞
  return distanceSquared <= radiusSumSquared;
}

// 示例:子弹与敌人碰撞检测
const bullet = { x: 150, y: 250, radius: 5 };
const enemy = { x: 200, y: 250, radius: 20 };
// 存储子弹和敌人的数组
const bullets = [bullet];
const enemies = [enemy];

function gameLoop() {
  const ctx = document.getElementById('canvas').getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  
  // 遍历子弹和敌人,检测碰撞
  for (let i = bullets.length - 1; i >= 0; i--) {
    const bullet = bullets[i];
    for (let j = enemies.length - 1; j >= 0; j--) {
      const enemy = enemies[j];
      if (checkCircleCollision(bullet, enemy)) {
        // 碰撞后销毁子弹和敌人
        bullets.splice(i, 1);
        enemies.splice(j, 1);
        console.log('子弹击中敌人!');
      }
    }
    // 子弹移动
    bullet.x += 3;
    // 超出屏幕销毁子弹
    if (bullet.x > canvas.width) {
      bullets.splice(i, 1);
    }
  }
  
  // 渲染子弹和敌人
  bullets.forEach(bullet => {
    ctx.fillStyle = '#FF0000';
    ctx.beginPath();
    ctx.arc(bullet.x, bullet.y, bullet.radius, 0, Math.PI * 2);
    ctx.fill();
  });
  enemies.forEach(enemy => {
    ctx.fillStyle = '#0000FF';
    ctx.beginPath();
    ctx.arc(enemy.x, enemy.y, enemy.radius, 0, Math.PI * 2);
    ctx.fill();
  });
  
  requestAnimationFrame(gameLoop);
}
gameLoop();

应用场景:弹球类游戏、射击游戏子弹检测、粒子碰撞、圆形NPC碰撞等。

11. 分离轴定理(SAT算法,经典进阶)

原理:Separating Axis Theorem(分离轴定理),适用于任意凸多边形(三角形、六边形等),包括旋转后的多边形,核心是“若存在一条轴,使得两个凸多边形在该轴上的投影不重叠,则无碰撞;否则有碰撞”。

核心步骤:1. 提取两个多边形的所有边的法向量(分离轴);2. 将两个多边形投影到每条分离轴上;3. 判断投影是否重叠,所有轴投影都重叠则碰撞。

原创代码实现(三角形与矩形碰撞检测,SAT算法):

/**
 * 计算两个点之间的向量
 * @param {Object} p1 - 点1 {x, y}
 * @param {Object} p2 - 点2 {x, y}
 * @returns {Object} 向量 {x, y}
 */
function getVector(p1, p2) {
  return { x: p2.x - p1.x, y: p2.y - p1.y };
}

/**
 * 计算向量的法向量(垂直向量,用于分离轴)
 * @param {Object} vec - 向量 {x, y}
 * @returns {Object} 法向量 {x, y}
 */
function getNormalVector(vec) {
  return { x: -vec.y, y: vec.x }; // 顺时针旋转90度
}

/**
 * 将多边形投影到轴上,返回投影的最小值和最大值
 * @param {Array} polygon - 多边形顶点数组 [{x, y}, ...]
 * @param {Object} axis - 分离轴 {x, y}
 * @returns {Object} 投影范围 {min, max}
 */
function projectPolygon(polygon, axis) {
  let min = polygon[0].x * axis.x + polygon[0].y * axis.y;
  let max = min;
  for (let i = 1; i < polygon.length; i++) {
    const dot = polygon[i].x * axis.x + polygon[i].y * axis.y;
    min = Math.min(min, dot);
    max = Math.max(max, dot);
  }
  return { min, max };
}

/**
 * 判断两个投影范围是否重叠
 * @param {Object} projA - 投影A {min, max}
 * @param {Object} projB - 投影B {min, max}
 * @returns {boolean} 是否重叠
 */
function isProjectionOverlap(projA, projB) {
  return !(projA.max < projB.min || projB.max < projA.min);
}

/**
 * SAT分离轴定理,凸多边形碰撞检测
 * @param {Array} polyA - 多边形A顶点数组
 * @param {Array} polyB - 多边形B顶点数组
 * @returns {boolean} 是否碰撞
 */
function checkSATCollision(polyA, polyB) {
  // 1. 提取两个多边形的所有边的法向量(分离轴)
  const axes = [];
  // 多边形A的边的法向量
  for (let i = 0; i < polyA.length; i++) {
    const p1 = polyA[i];
    const p2 = polyA[(i + 1) % polyA.length];
    const vec = getVector(p1, p2);
    const normal = getNormalVector(vec);
    axes.push(normal);
  }
  // 多边形B的边的法向量
  for (let i = 0; i < polyB.length; i++) {
    const p1 = polyB[i];
    const p2 = polyB[(i + 1) % polyB.length];
    const vec = getVector(p1, p2);
    const normal = getNormalVector(vec);
    axes.push(normal);
  }

  // 2. 遍历所有分离轴,判断投影是否重叠
  for (const axis of axes) {
    const projA = projectPolygon(polyA, axis);
    const projB = projectPolygon(polyB, axis);
    if (!isProjectionOverlap(projA, projB)) {
      return false; // 有一个轴投影不重叠,无碰撞
    }
  }

  return true; // 所有轴投影都重叠,有碰撞
}

// 示例:三角形与矩形碰撞检测
const triangle = [
  { x: 100, y: 200 },
  { x: 150, y: 150 },
  { x: 200, y: 200 }
];
const rectangle = [
  { x: 180, y: 180 },
  { x: 230, y: 180 },
  { x: 230, y: 230 },
  { x: 180, y: 230 }
];

function gameLoop() {
  const ctx = document.getElementById('canvas').getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  
  // 检测碰撞
  const isCollide = checkSATCollision(triangle, rectangle);
  
  // 渲染三角形
  ctx.strokeStyle = isCollide ? '#FF0000' : '#00FF00';
  ctx.lineWidth = 2;
  ctx.beginPath();
  triangle.forEach((point, index) => {
    index === 0 ? ctx.moveTo(point.x, point.y) : ctx.lineTo(point.x, point.y);
  });
  ctx.closePath();
  ctx.stroke();
  
  // 渲染矩形
  ctx.strokeStyle = isCollide ? '#FF0000' : '#00FF00';
  ctx.beginPath();
  rectangle.forEach((point, index) => {
    index === 0 ? ctx.moveTo(point.x, point.y) : ctx.lineTo(point.x, point.y);
  });
  ctx.closePath();
  ctx.stroke();
  
  requestAnimationFrame(gameLoop);
}
gameLoop();

应用场景:不规则凸多边形碰撞、旋转矩形碰撞、复杂角色碰撞箱、地形碰撞等进阶场景。

12. 点与矩形碰撞检测算法(经典基础)

原理:判断一个点是否在矩形内部,核心是“点的x坐标在矩形x范围之间,且y坐标在矩形y范围之间”,是碰撞检测的基础算法,常用于鼠标点击检测、粒子与矩形碰撞等。

核心逻辑:点P(x,y),矩形R(x1,y1,width,height),若x1 ≤ x ≤ x1+width 且 y1 ≤ y ≤ y1+height,则点在矩形内(碰撞);否则无碰撞。

原创代码实现(鼠标点击矩形检测):

/**
 * 点与矩形碰撞检测
 * @param {Object} point - 点 {x, y}
 * @param {Object} rect - 矩形 {x, y, width, height}
 * @returns {boolean} 是否碰撞(点在矩形内)
 */
function checkPointRectCollision(point, rect) {
  return (
    point.x >= rect.x &&
    point.x <= rect.x + rect.width &&
    point.y >= rect.y &&
    point.y <= rect.y + rect.height
  );
}

// 示例:鼠标点击矩形检测
const rect = { x: 200, y: 200, width: 100, height: 80 };
let isClicked = false;

// 绑定鼠标点击事件
document.addEventListener('click', (e) => {
  const canvas = document.getElementById('canvas');
  const rectCanvas = canvas.getBoundingClientRect();
  // 计算鼠标在Canvas中的坐标
  const mouseX = e.clientX - rectCanvas.left;
  const mouseY = e.clientY - rectCanvas.top;
  // 检测碰撞
  isClicked = checkPointRectCollision({ x: mouseX, y: mouseY }, rect);
  console.log(isClicked ? '点击到矩形!' : '未点击到矩形');
});

function gameLoop() {
  const ctx = document.getElementById('canvas').getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  
  // 绘制矩形(点击后变色)
  ctx.fillStyle = isClicked ? '#FF6347' : '#4169E1';
  ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
  
  requestAnimationFrame(gameLoop);
}
gameLoop();

应用场景:鼠标点击检测、粒子与矩形碰撞、角色拾取物品检测、UI按钮点击检测等。

13. 点与圆形碰撞检测算法(经典简洁)

原理:判断一个点是否在圆形内部,核心是“点到圆心的距离是否小于等于圆的半径”,与圆形碰撞检测逻辑类似,是点与圆形交互的基础。

性能优化:同样可通过“距离平方”与“半径平方”比较,避免开平方运算。

原创代码实现(鼠标点击圆形检测):

/**
 * 点与圆形碰撞检测
 * @param {Object} point - 点 {x, y}
 * @param {Object} circle - 圆形 {x, y, radius}
 * @returns {boolean} 是否碰撞(点在圆形内)
 */
function checkPointCircleCollision(point, circle) {
  const dx = point.x - circle.x;
  const dy = point.y - circle.y;
  const distanceSquared = dx * dx + dy * dy;
  const radiusSquared = circle.radius * circle.radius;
  return distanceSquared <= radiusSquared;
}

// 示例:鼠标点击圆形检测
const circle = { x: 300, y: 250, radius: 50 };
let isClicked = false;

// 绑定鼠标点击事件
document.addEventListener('click', (e) => {
  const canvas = document.getElementById('canvas');
  const rectCanvas = canvas.getBoundingClientRect();
  const mouseX = e.clientX - rectCanvas.left;
  const mouseY = e.clientY - rectCanvas.top;
  isClicked = checkPointCircleCollision({ x: mouseX, y: mouseY }, circle);
  console.log(isClicked ? '点击到圆形!' : '未点击到圆形');
});

function gameLoop() {
  const ctx = document.getElementById('canvas').getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  
  // 绘制圆形(点击后变色)
  ctx.fillStyle = isClicked ? '#9370DB' : '#20B2AA';
  ctx.beginPath();
  ctx.arc(circle.x, circle.y, circle.radius, 0, Math.PI * 2);
  ctx.fill();
  
  requestAnimationFrame(gameLoop);
}
gameLoop();

应用场景:鼠标点击圆形按钮、粒子与圆形碰撞、角色拾取圆形物品、圆形区域交互检测等。

五、约束控制类算法(3种,进阶交互必备)

约束控制类算法,核心是“限制物体的运动范围或运动关系”,让物体的运动符合特定规则,提升游戏交互的细腻度,适用于绳索、弹簧、关节等进阶场景,是区别于“基础物理”与“高级物理”的关键。

14. 距离约束算法(经典进阶)

原理:限制两个物体之间的距离,使其保持固定或在一定范围内(如绳索、链条),核心是“计算两个物体的实际距离,若超出限制,则修正位置”。

核心亮点:实现简单,适配所有“固定距离”场景,如绳索连接、父子物体联动等。

原创代码实现(绳索效果,两个小球固定距离):

// 小球类
class Ball {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.radius = 10;
    this.speedX = 0;
    this.speedY = 0;
    this.gravity = 0.2;
  }

  update(dt) {
    // 应用重力
    this.speedY += this.gravity * dt * 60;
    // 更新位置
    this.x += this.speedX * dt;
    this.y += this.speedY * dt;
    // 边界检测
    this.x = Math.max(this.radius, Math.min(this.x, canvas.width - this.radius));
    this.y = Math.max(this.radius, Math.min(this.y, canvas.height - this.radius));
  }

  render(ctx) {
    ctx.fillStyle = '#FFD700';
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.strokeStyle = '#000';
    ctx.stroke();
  }
}

/**
 * 距离约束算法
 * @param {Object} ball1 - 小球1
 * @param {Object} ball2 - 小球2
 * @param {number} fixedLength - 固定距离
 */
function distanceConstraint(ball1, ball2, fixedLength) {
  // 计算实际距离
  const dx = ball2.x - ball1.x;
  const dy = ball2.y - ball1.y;
  const currentLength = Math.sqrt(dx * dx + dy * dy);
  // 距离差(超出或不足固定距离)
  const difference = fixedLength - currentLength;
  // 修正系数(让两个小球均匀修正)
  const correctionX = (dx / currentLength) * difference / 2;
  const correctionY = (dy / currentLength) * difference / 2;
  // 修正两个小球的位置
  ball1.x -= correctionX;
  ball1.y -= correctionY;
  ball2.x += correctionX;
  ball2.y += correctionY;
}

// 初始化两个小球和绳索
const canvas = document.getElementById('canvas');
const ball1 = new Ball(150, 100);
const ball2 = new Ball(250, 100);
const ropeLength = 100; // 绳索固定长度

// 鼠标控制第一个小球
document.addEventListener('mousemove', (e) => {
  const rectCanvas = canvas.getBoundingClientRect();
  ball1.x = e.clientX - rectCanvas.left;
  ball1.y = e.clientY - rectCanvas.top;
  // 限制第一个小球不超出屏幕边界
  ball1.x = Math.max(ball1.radius, Math.min(ball1.x, canvas.width - ball1.radius));
  ball1.y = Math.max(ball1.radius, Math.min(ball1.y, canvas.height - ball1.radius));
});

// 启动游戏循环,应用距离约束
let lastTime = 0;
function gameLoop(timestamp) {
  const dt = (timestamp - lastTime) / 1000;
  lastTime = timestamp;

  const ctx = canvas.getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // 更新两个小球的运动状态
  ball1.update(dt);
  ball2.update(dt);

  // 应用距离约束,保持两个小球固定距离(绳索效果)
  distanceConstraint(ball1, ball2, ropeLength);

  // 绘制绳索(连接两个小球)
  ctx.strokeStyle = '#000';
  ctx.lineWidth = 2;
  ctx.beginPath();
  ctx.moveTo(ball1.x, ball1.y);
  ctx.lineTo(ball2.x, ball2.y);
  ctx.stroke();

  // 渲染两个小球
  ball1.render(ctx);
  ball2.render(ctx);

  requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);

应用场景:绳索连接效果、链条联动、父子物体固定距离移动、秋千摆动、钓鱼线等场景。

15. 弹簧约束算法(经典进阶)

原理:模拟现实中的弹簧弹力,两个物体之间存在“弹性拉力/推力”,距离越远,弹力越大,核心是“根据物体间距离与弹簧原长的差值,计算弹力,进而改变物体加速度”。

核心区别:与距离约束不同,弹簧约束允许物体在原长附近小幅摆动,而非固定距离,运动更具弹性和真实感,适配需要“弹性交互”的场景。

核心参数:弹簧原长(自然状态下的距离)、弹性系数(k值,决定弹簧松紧,k越大越硬)、阻尼系数(减少摆动幅度,避免无限震荡)。

原创代码实现(弹簧效果,小球与固定点连接):

// 小球类(弹簧连接的可动小球)
class SpringBall {
  constructor() {
    this.x = 300;
    this.y = 200;
    this.radius = 12;
    this.speedX = 0;
    this.speedY = 0;
    this.mass = 1; // 质量,影响弹力作用效果
    this.gravity = 0.2;
  }

  update(dt, anchorX, anchorY, springLength, k, damping) {
    // 应用重力
    this.speedY += this.gravity * dt * 60;

    // 计算小球与固定点的距离
    const dx = this.x - anchorX;
    const dy = this.y - anchorY;
    const currentLength = Math.sqrt(dx * dx + dy * dy);

    // 计算弹簧弹力(胡克定律:F = -k * (当前距离 - 原长))
    const springForce = -k * (currentLength - springLength);
    // 计算弹力的x、y分量
    const forceX = (dx / currentLength) * springForce;
    const forceY = (dy / currentLength) * springForce;

    // 计算加速度(F = ma → a = F/m)
    const accX = forceX / this.mass;
    const accY = forceY / this.mass;

    // 更新速度(加入阻尼,减少震荡)
    this.speedX = (this.speedX + accX * dt) * damping;
    this.speedY = (this.speedY + accY * dt) * damping;

    // 更新位置
    this.x += this.speedX * dt;
    this.y += this.speedY * dt;
  }

  render(ctx) {
    ctx.fillStyle = '#FF4500';
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.strokeStyle = '#000';
    ctx.stroke();
  }
}

// 弹簧约束核心逻辑(固定点与小球连接)
const canvas = document.getElementById('canvas');
// 弹簧固定点(屏幕中心上方)
const anchor = { x: canvas.width / 2, y: 100 };
const springBall = new SpringBall();
// 弹簧参数
const springLength = 120; // 弹簧原长
const k = 0.15; // 弹性系数(0~1,越大越硬)
const damping = 0.98; // 阻尼系数(0~1,越小阻尼越大)

// 鼠标拖拽小球,测试弹簧效果
let isDragging = false;
document.addEventListener('mousedown', (e) => {
  const rectCanvas = canvas.getBoundingClientRect();
  const mouseX = e.clientX - rectCanvas.left;
  const mouseY = e.clientY - rectCanvas.top;
  // 判断是否点击到小球
  const dx = mouseX - springBall.x;
  const dy = mouseY - springBall.y;
  if (Math.sqrt(dx * dx + dy * dy) <= springBall.radius) {
    isDragging = true;
  }
});
document.addEventListener('mousemove', (e) => {
  if (isDragging) {
    const rectCanvas = canvas.getBoundingClientRect();
    springBall.x = e.clientX - rectCanvas.left;
    springBall.y = e.clientY - rectCanvas.top;
  }
});
document.addEventListener('mouseup', () => {
  isDragging = false;
});

// 启动弹簧模拟
let lastTime = 0;
function gameLoop(timestamp) {
  const dt = (timestamp - lastTime) / 1000;
  lastTime = timestamp;

  const ctx = canvas.getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // 绘制弹簧固定点
  ctx.fillStyle = '#000';
  ctx.beginPath();
  ctx.arc(anchor.x, anchor.y, 5, 0, Math.PI * 2);
  ctx.fill();

  // 绘制弹簧(用虚线模拟弹簧纹理)
  ctx.strokeStyle = '#666';
  ctx.lineWidth = 2;
  ctx.setLineDash([5, 3]);
  ctx.beginPath();
  ctx.moveTo(anchor.x, anchor.y);
  ctx.lineTo(springBall.x, springBall.y);
  ctx.stroke();
  ctx.setLineDash([]);

  // 更新小球状态,应用弹簧约束
  springBall.update(dt, anchor.x, anchor.y, springLength, k, damping);
  // 渲染小球
  springBall.render(ctx);

  requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);

应用场景:弹簧门、弹性绳索、跳跃缓冲效果、物体弹性碰撞、物理摆锤、弹弓拉伸等场景。

16. 角度约束算法(经典进阶)

原理:限制两个物体之间的夹角,使其围绕某个固定点(或彼此)按固定角度旋转,核心是“通过三角函数计算目标位置,修正物体角度和位置,确保夹角不变”。

核心亮点:适配需要“固定角度旋转”的场景,如钟摆、关节连接、角色肢体运动(手臂、腿部摆动)等,让运动更具规律性。

原创代码实现(钟摆效果,小球围绕固定点按固定角度摆动):

// 钟摆类(角度约束实现)
class Pendulum {
  constructor() {
    this.anchorX = canvas.width / 2; // 固定点x坐标
    this.anchorY = 100; // 固定点y坐标
    this.length = 150; // 钟摆绳长
    this.angle = Math.PI / 4; // 初始角度(45度,弧度制)
    this.angularSpeed = 0; // 角速度(角度变化速度)
    this.angularAcceleration = 0; // 角加速度
    this.damping = 0.99; // 阻尼系数,减少摆动幅度
    this.gravity = 0.2; // 重力加速度,影响摆动
    this.ballRadius = 15; // 钟摆小球半径
  }

  update(dt) {
    // 计算角加速度(重力产生的力矩)
    this.angularAcceleration = -this.gravity * Math.sin(this.angle) / this.length;
    // 更新角速度(加入阻尼)
    this.angularSpeed = (this.angularSpeed + this.angularAcceleration * dt * 60) * this.damping;
    // 更新角度
    this.angle += this.angularSpeed * dt;

    // 根据角度计算小球的目标位置(角度约束核心)
    this.ballX = this.anchorX + Math.sin(this.angle) * this.length;
    this.ballY = this.anchorY + Math.cos(this.angle) * this.length;
  }

  render(ctx) {
    // 绘制钟摆绳
    ctx.strokeStyle = '#000';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.moveTo(this.anchorX, this.anchorY);
    ctx.lineTo(this.ballX, this.ballY);
    ctx.stroke();

    // 绘制固定点
    ctx.fillStyle = '#000';
    ctx.beginPath();
    ctx.arc(this.anchorX, this.anchorY, 6, 0, Math.PI * 2);
    ctx.fill();

    // 绘制钟摆小球
    ctx.fillStyle = '#4169E1';
    ctx.beginPath();
    ctx.arc(this.ballX, this.ballY, this.ballRadius, 0, Math.PI * 2);
    ctx.fill();
    ctx.strokeStyle = '#000';
    ctx.stroke();
  }
}

// 初始化钟摆并启动模拟
const canvas = document.getElementById('canvas');
const pendulum = new Pendulum();

let lastTime = 0;
function gameLoop(timestamp) {
  const dt = (timestamp - lastTime) / 1000;
  lastTime = timestamp;

  const ctx = canvas.getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // 更新钟摆状态,应用角度约束
  pendulum.update(dt);
  // 渲染钟摆
  pendulum.render(ctx);

  requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);

应用场景:钟摆游戏、角色肢体关节运动、旋转门、风力摆锤、投石机旋转等场景。

六、优化适配类算法(4种,兼顾性能与体验)

前端游戏开发中,物理算法的“性能”与“体验”同样重要——若算法过于复杂,会导致浏览器卡顿、帧率下降;若过于简化,会导致交互生硬。以下4种优化适配算法,核心解决“性能优化”“场景适配”“体验提升”三大问题,是前端游戏落地的关键。

17. 帧率适配算法(基础优化)

原理:前端游戏的requestAnimationFrame帧率不固定(受设备性能、浏览器占用影响),若直接按“帧”更新运动状态,会导致不同设备上运动速度不一致。核心是“基于时间差(dt)更新,让运动速度与帧率无关”。

核心逻辑:每帧计算与上一帧的时间间隔(dt,单位:秒),所有运动参数(速度、加速度)都乘以dt,确保无论帧率高低,物体运动速度一致,提升跨设备兼容性。

原创代码实现(帧率适配,确保不同设备运动速度一致):

// 帧率适配的运动物体类
class FrameAdaptedObject {
  constructor() {
    this.x = 50;
    this.y = 200;
    this.width = 40;
    this.height = 40;
    this.speed = 100; // 基础速度(像素/秒),与帧率无关
    this.acceleration = 40; // 基础加速度(像素/秒²)
  }

  // 帧更新,传入时间差dt
  update(dt) {
    // 基于dt更新速度和位置,确保帧率不影响运动速度
    this.speed += this.acceleration * dt;
    this.x += this.speed * dt;
    // 超出屏幕重置
    if (this.x > canvas.width) {
      this.x = -this.width;
      this.speed = 100;
    }
  }

  render(ctx) {
    ctx.fillStyle = '#20B2AA';
    ctx.fillRect(this.x, this.y, this.width, this.height);
  }
}

// 帧率适配核心逻辑
const canvas = document.getElementById('canvas');
const obj = new FrameAdaptedObject();
let lastTime = 0;

// 游戏循环,基于时间差更新
function gameLoop(timestamp) {
  // 计算时间差dt(秒),避免帧率影响运动
  const dt = (timestamp - lastTime) / 1000;
  lastTime = timestamp;

  const ctx = canvas.getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // 传入dt更新物体状态
  obj.update(dt);
  obj.render(ctx);

  // 显示当前帧率(可选,用于调试)
  ctx.fillStyle = '#000';
  ctx.font = '16px Arial';
  const fps = Math.round(1 / dt);
  ctx.fillText(`当前帧率:${fps} FPS`, 10, 30);

  requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);

应用场景:所有前端游戏,尤其是跨设备(PC、手机、平板)开发的游戏,确保不同设备上的运动速度一致,提升兼容性。

18. 碰撞分层检测算法(性能优化)

原理:当游戏中物体数量较多(如大量敌人、粒子),若对所有物体两两进行碰撞检测,会导致计算量暴增(时间复杂度O(n²)),引发卡顿。核心是“按物体类型分层,只对同层或关联层的物体进行碰撞检测”,减少无效计算。

核心优化:将物体分为不同层级(如“玩家层”“敌人层”“子弹层”“障碍物层”),仅检测有交互需求的层级(如子弹层与敌人层、玩家层与障碍物层),忽略无交互的层级(如玩家层与子弹层、敌人层与障碍物层)。

原创代码实现(碰撞分层,优化多物体碰撞性能):

// 碰撞分层管理器(核心:按层级管理物体,减少碰撞检测次数)
class CollisionLayerManager {
  constructor() {
    // 分层存储物体,key为层级名称,value为物体数组
    this.layers = {
      player: [],    // 玩家层
      enemy: [],     // 敌人层
      bullet: [],    // 子弹层
      obstacle: []   // 障碍物层
    };
    // 定义需要检测碰撞的层级对(关联层级)
    this.collisionPairs = [
      ['bullet', 'enemy'],    // 子弹与敌人碰撞
      ['player', 'obstacle'], // 玩家与障碍物碰撞
      ['bullet', 'obstacle']  // 子弹与障碍物碰撞
    ];
  }

  // 添加物体到对应层级
  addObject(layerName, obj) {
    if (this.layers[layerName]) {
      this.layers[layerName].push(obj);
    }
  }

  // 移除层级中的物体
  removeObject(layerName, obj) {
    if (!this.layers[layerName]) return;
    this.layers[layerName] = this.layers[layerName].filter(item => item !== obj);
  }

  // AABB碰撞检测(复用前文算法)
  checkAABBCollision(rectA, rectB) {
    const noCollision = 
      rectA.x + rectA.width < rectB.x ||
      rectA.x > rectB.x + rectB.width ||
      rectA.y + rectA.height < rectB.y ||
      rectA.y > rectB.y + rectB.height;
    return !noCollision;
  }

  // 分层碰撞检测(核心逻辑)
  detectCollisions() {
    const collisions = [];
    // 遍历所有需要检测的层级对
    this.collisionPairs.forEach(([layerA, layerB]) => {
      const objectsA = this.layers[layerA];
      const objectsB = this.layers[layerB];
      if (!objectsA || !objectsB) return;

      // 仅检测当前层级对的物体碰撞
      for (const objA of objectsA) {
        for (const objB of objectsB) {
          if (this.checkAABBCollision(objA, objB)) {
            collisions.push({ objA, objB, layerA, layerB });
          }
        }
      }
    });
    return collisions;
  }
}

// 示例:分层碰撞检测实战
const canvas = document.getElementById('canvas');
const collisionManager = new CollisionLayerManager();

// 1. 定义物体类
class Player {
  constructor() {
    this.x = 100;
    this.y = 300;
    this.width = 50;
    this.height = 80;
  }
  render(ctx) { ctx.fillStyle = '#4169E1'; ctx.fillRect(this.x, this.y, this.width, this.height); }
}
class Enemy {
  constructor(x) {
    this.x = x;
    this.y = 300;
    this.width = 40;
    this.height = 40;
  }
  render(ctx) { ctx.fillStyle = '#FF0000'; ctx.fillRect(this.x, this.y, this.width, this.height); }
}
class Bullet {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.width = 10;
    this.height = 20;
  }
  update() { this.y -= 5; }
  render(ctx) { ctx.fillStyle = '#FFD700'; ctx.fillRect(this.x, this.y, this.width, this.height); }
}
class Obstacle {
  constructor(x) {
    this.x = x;
    this.y = 350;
    this.width = 60;
    this.height = 30;
  }
  render(ctx) { ctx.fillStyle = '#8B4513'; ctx.fillRect(this.x, this.y, this.width, this.height); }
}

// 2. 初始化物体并添加到对应层级
const player = new Player();
collisionManager.addObject('player', player);

// 添加3个敌人
for (let i = 0; i < 3; i++) {
  const enemy = new Enemy(200 + i * 60);
  collisionManager.addObject('enemy', enemy);
}

// 添加障碍物
const obstacle = new Obstacle(300);
collisionManager.addObject('obstacle', obstacle);

// 鼠标点击发射子弹(添加到子弹层)
document.addEventListener('click', (e) => {
  const rectCanvas = canvas.getBoundingClientRect();
  const x = e.clientX - rectCanvas.left;
  const bullet = new Bullet(x - 5, 280);
  collisionManager.addObject('bullet', bullet);
});

// 游戏循环
function gameLoop() {
  const ctx = canvas.getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // 1. 更新子弹位置
  collisionManager.layers.bullet.forEach(bullet => bullet.update());
  // 移除超出屏幕的子弹
  collisionManager.layers.bullet = collisionManager.layers.bullet.filter(bullet => bullet.y > 0);

  // 2. 分层检测碰撞
  const collisions = collisionManager.detectCollisions();
  // 处理碰撞结果
  collisions.forEach(({ objA, objB, layerA, layerB }) => {
    if (layerA === 'bullet' && layerB === 'enemy') {
      // 子弹击中敌人,移除两者
      collisionManager.removeObject('bullet', objA);
      collisionManager.removeObject('enemy', objB);
    } else if (layerA === 'player' && layerB === 'obstacle') {
      // 玩家撞到障碍物,重置位置
      player.x = 100;
    } else if (layerA === 'bullet' && layerB === 'obstacle') {
      // 子弹撞到障碍物,移除子弹
      collisionManager.removeObject('bullet', objA);
    }
  });

  // 3. 渲染所有物体
  Object.values(collisionManager.layers).forEach(layer => {
    layer.forEach(obj => obj.render(ctx));
  });

  requestAnimationFrame(gameLoop);
}
gameLoop();

应用场景:多物体场景(如射击游戏、弹幕游戏、粒子效果较多的游戏),大幅减少碰撞检测计算量,提升游戏运行流畅度。

19. 边界约束算法(体验优化)

原理:限制物体的运动范围,使其不超出游戏画布(或指定区域),核心是“检测物体是否超出边界,若超出则进行修正(反弹、重置、阻挡)”,避免物体“消失”或“穿透”屏幕,提升用户体验。

核心适配:可根据游戏场景选择不同的边界处理方式(反弹、阻挡、重置、循环),适配不同游戏需求。

原创代码实现(多边界处理方式,适配不同场景):

// 边界约束管理器(支持多种边界处理方式)
class BoundaryConstraint {
  constructor(canvas) {
    this.canvas = canvas;
    this.width = canvas.width;
    this.height = canvas.height;
  }

  // 边界处理方式:1. 阻挡(物体无法超出边界)
  block(obj) {
    // 左右边界
    obj.x = Math.max(0, Math.min(obj.x, this.width - obj.width));
    // 上下边界
    obj.y = Math.max(0, Math.min(obj.y, this.height - obj.height));
  }

  // 边界处理方式:2. 反弹(物体碰到边界反弹)
  bounce(obj) {
    // 左右边界反弹(x方向速度反向)
    if (obj.x <= 0 || obj.x >= this.width - obj.width) {
      obj.speedX = -obj.speedX * 0.8; // 反弹衰减,模拟能量损失
      obj.x = obj.x <= 0 ? 0 : this.width - obj.width;
    }
    // 上下边界反弹(y方向速度反向)
    if (obj.y <= 0 || obj.y >= this.height - obj.height) {
      obj.speedY = -obj.speedY * 0.8;
      obj.y = obj.y <= 0 ? 0 : this.height - obj.height;
    }
  }

  // 边界处理方式:3. 循环(物体超出边界后从对侧出现)
  loop(obj) {
    if (obj.x > this.width) obj.x = -obj.width;
    else if (obj.x < -obj.width) obj.x = this.width;

    if (obj.y > this.height) obj.y = -obj.height;
    else if (obj.y < -obj.height) obj.y = this.height;
  }

  // 边界处理方式:4. 重置(物体超出边界后回到初始位置)
  reset(obj, initialX, initialY) {
    if (obj.x > this.width || obj.x < 0 || obj.y > this.height || obj.y < 0) {
      obj.x = initialX;
      obj.y = initialY;
      obj.speedX = 0;
      obj.speedY = 0;
    }
  }
}

// 示例:多边界处理方式实战
const canvas = document.getElementById('canvas');
const boundary = new BoundaryConstraint(canvas);

// 定义物体(支持多种边界处理)
class BoundaryObject {
  constructor(initialX, initialY, boundaryType) {
    this.x = initialX;
    this.y = initialY;
    this.width = 30;
    this.height = 30;
    this.speedX = Math.random() * 4 - 2; // 随机x方向速度
    this.speedY = Math.random() * 4 - 2; // 随机y方向速度
    this.initialX = initialX;
    this.initialY = initialY;
    this.boundaryType = boundaryType; // 边界处理方式
  }

  update() {
    // 更新位置
    this.x += this.speedX;
    this.y += this.speedY;
    // 应用对应边界约束
    switch (this.boundaryType) {
      case 'block':
        boundary.block(this);
        break;
      case 'bounce':
        boundary.bounce(this);
        break;
      case 'loop':
        boundary.loop(this);
        break;
      case 'reset':
        boundary.reset(this, this.initialX, this.initialY);
        break;
    }
  }

  render(ctx) {
    // 不同边界处理方式,物体颜色不同
    const colors = { block: '#4169E1', bounce: '#FF4500', loop: '#20B2AA', reset: '#FFD700' };
    ctx.fillStyle = colors[this.boundaryType];
    ctx.fillRect(this.x, this.y, this.width, this.height);
    // 绘制边界处理方式文字
    ctx.fillStyle = '#000';
    ctx.font = '12px Arial';
    ctx.fillText(this.boundaryType, this.x + 5, this.y + 15);
  }
}

// 初始化4个物体,分别使用4种边界处理方式
const objects = [
  new BoundaryObject(50, 50, 'block'),
  new BoundaryObject(150, 50, 'bounce'),
  new BoundaryObject(250, 50, 'loop'),
  new BoundaryObject(350, 50, 'reset')
];

// 游戏循环
function gameLoop() {
  const ctx = canvas.getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // 绘制画布边界(辅助显示)
  ctx.strokeStyle = '#666';
  ctx.lineWidth = 2;
  ctx.strokeRect(0, 0, canvas.width, canvas.height);

  // 更新并渲染所有物体
  objects.forEach(obj => {
    obj.update();
    obj.render(ctx);
  });

  requestAnimationFrame(gameLoop);
}
gameLoop();

应用场景:所有游戏,根据需求选择边界处理方式——如平台跳跃游戏用“阻挡”,弹球游戏用“反弹”,无限跑酷游戏用“循环”,射击游戏子弹用“重置”。

20. 缓动插值算法(体验优化)

原理:让物体的运动“平滑过渡”,避免生硬的瞬间变化(如瞬间移动、瞬间加速),核心是“通过插值计算,让物体从当前状态逐步过渡到目标状态”,提升交互的细腻度。

核心亮点:适配所有需要“平滑过渡”的场景,如角色跟随、镜头移动、物体缩放、颜色渐变等,让游戏交互更丝滑,贴合玩家直觉。

原创代码实现(缓动跟随,角色平滑跟随鼠标):

// 缓动插值算法(核心:平滑过渡)
class EasingInterpolation {
  /**
   * 线性缓动(基础)
   * @param {number} current - 当前值
   * @param {number} target - 目标值
   * @param {number} factor - 缓动系数(0~1,越小越平滑)
   * @returns {number} 缓动后的值
   */
  static linear(current, target, factor) {
    return current + (target - current) * factor;
  }

  /**
   * 缓入缓出(常用,先慢后快再慢)
   * @param {number} current - 当前值
   * @param {number} target - 目标值
   * @param {number} factor - 缓动系数
   * @returns {number} 缓动后的值
   */
  static easeInOut(current, target, factor) {
    const t = factor * 2;
    if (t < 1) return current + (target - current) * 0.5 * t * t;
    return current + (target - current) * 0.5 * (1 - Math.pow(2 - t, 2));
  }
}

// 示例:角色平滑跟随鼠标(缓动插值应用)
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

class FollowPlayer {
  constructor() {
    this.x = canvas.width / 2;
    this.y = canvas.height / 2;
    this.width = 50;
    this.height = 80;
    this.easingFactor = 0.08; // 缓动系数,越小越平滑
    this.targetX = this.x;
    this.targetY = this.y;
  }

  // 更新目标位置(鼠标位置)
  setTarget(x, y) {
    this.targetX = x - this.width / 2; // 让角色中心对准鼠标
    this.targetY = y - this.height / 2;
  }

  update() {
    // 应用缓动插值,平滑过渡到目标位置
    this.x = EasingInterpolation.easeInOut(this.x, this.targetX, this.easingFactor);
    this.y = EasingInterpolation.easeInOut(this.y, this.targetY, this.easingFactor);
  }

  render() {
    ctx.fillStyle = '#4169E1';
    ctx.fillRect(this.x, this.y, this.width, this.height);
    // 绘制目标点(鼠标位置)
    ctx.fillStyle = '#FF0000';
    ctx.beginPath();
    ctx.arc(this.targetX + this.width / 2, this.targetY + this.height / 2, 5, 0, Math.PI * 2);
    ctx.fill();
  }
}

// 初始化角色
const player = new FollowPlayer();

// 鼠标移动更新目标位置
document.addEventListener('mousemove', (e) => {
  const rectCanvas = canvas.getBoundingClientRect();
  const mouseX = e.clientX - rectCanvas.left;
  const mouseY = e.clientY - rectCanvas.top;
  player.setTarget(mouseX, mouseY);
});

// 游戏循环
function gameLoop() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  player.update();
  player.render();

  requestAnimationFrame(gameLoop);
}
gameLoop();

应用场景:角色跟随鼠标、镜头平滑移动、物体平滑缩放/移动、UI元素渐变、技能特效过渡等,提升游戏交互的细腻度和丝滑感。

七、总结:前端游戏物理算法的落地核心

本文整理的20种经典物理算法,覆盖了前端游戏开发从入门到进阶的全部核心需求,按“基础运动→受力分析→碰撞检测→约束控制→优化适配”分类,层层递进,所有代码均为原创可直接复制运行,无需复杂修改。

核心总结3点,帮你快速落地应用:

  1. 简化优先:前端游戏物理无需复刻现实,提取核心规律(如重力=垂直匀加速、碰撞=边界重叠),用最简单的代码实现“符合直觉”的效果,兼顾性能与体验。

  2. 复用性:将算法封装为工具类或方法(如碰撞检测、缓动插值),可在不同游戏中直接复用,减少重复开发,提升效率。

  3. 适配为王:结合帧率适配、碰撞分层、边界约束等优化算法,确保游戏在不同设备上流畅运行,同时避免交互生硬、物体穿透等问题。

无论是新手入门,还是资深开发者查漏补缺,掌握这20种算法,都能彻底摆脱“伪物理”交互困境,打造出丝滑、真实的前端游戏体验。后续可根据具体游戏场景,灵活组合算法(如“抛物线运动+圆形碰撞”实现弓箭射击、“重力+弹簧约束”实现跳跃缓冲),解锁更多复杂物理效果。