对于前端开发者而言,游戏开发与常规页面开发的核心区别,在于“交互的真实感”。很多新手入门游戏开发时,容易陷入“重视觉、轻物理”的误区,花大量时间打磨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点,帮你快速落地应用:
-
简化优先:前端游戏物理无需复刻现实,提取核心规律(如重力=垂直匀加速、碰撞=边界重叠),用最简单的代码实现“符合直觉”的效果,兼顾性能与体验。
-
复用性:将算法封装为工具类或方法(如碰撞检测、缓动插值),可在不同游戏中直接复用,减少重复开发,提升效率。
-
适配为王:结合帧率适配、碰撞分层、边界约束等优化算法,确保游戏在不同设备上流畅运行,同时避免交互生硬、物体穿透等问题。
无论是新手入门,还是资深开发者查漏补缺,掌握这20种算法,都能彻底摆脱“伪物理”交互困境,打造出丝滑、真实的前端游戏体验。后续可根据具体游戏场景,灵活组合算法(如“抛物线运动+圆形碰撞”实现弓箭射击、“重力+弹簧约束”实现跳跃缓冲),解锁更多复杂物理效果。