GitHub_Trending/ap/app-ideas游戏物理:碰撞检测与运动模拟
引言:游戏物理的核心价值
在游戏开发领域,物理模拟是创造沉浸式体验的关键技术。无论是简单的2D小游戏还是复杂的3D大作,精确的碰撞检测和真实的运动模拟都是游戏可玩性的基石。本文将深入探讨app-ideas项目中涉及的游戏物理实现,为您提供从理论到实践的完整指南。
您是否曾遇到过这些问题?
- 游戏物体碰撞时出现穿墙现象
- 物理运动看起来不自然、不真实
- 性能问题导致游戏卡顿
- 复杂的碰撞检测逻辑难以实现
通过本文,您将掌握:
- 基础物理概念与数学原理
- 多种碰撞检测算法实现
- 运动模拟的最佳实践
- 性能优化技巧
- 实际项目应用案例
物理基础概念
运动学基本公式
在游戏物理中,我们需要理解几个核心的运动学公式:
| 物理量 | 公式 | 描述 |
|---|---|---|
| 位移 | s = v₀t + ½at² | 物体在时间t内的位移 |
| 速度 | v = v₀ + at | 物体在时间t后的速度 |
| 加速度 | a = (v - v₀)/t | 物体的加速度 |
向量数学基础
class Vector2 {
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
// 向量加法
add(other) {
return new Vector2(this.x + other.x, this.y + other.y);
}
// 向量减法
subtract(other) {
return new Vector2(this.x - other.x, this.y - other.y);
}
// 标量乘法
multiply(scalar) {
return new Vector2(this.x * scalar, this.y * scalar);
}
// 向量点积
dot(other) {
return this.x * other.x + this.y * other.y;
}
// 向量长度
magnitude() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
// 单位向量
normalize() {
const mag = this.magnitude();
return mag > 0 ? new Vector2(this.x / mag, this.y / mag) : new Vector2();
}
}
碰撞检测算法
1. 轴对齐包围盒(AABB)碰撞检测
AABB是最简单且高效的碰撞检测方法,适用于矩形物体。
function checkAABBCollision(rect1, rect2) {
return rect1.x < rect2.x + rect2.width &&
rect1.x + rect1.width > rect2.x &&
rect1.y < rect2.y + rect2.height &&
rect1.y + rect1.height > rect2.y;
}
2. 圆形碰撞检测
function checkCircleCollision(circle1, circle2) {
const dx = circle1.x - circle2.x;
const dy = circle1.y - circle2.y;
const distance = Math.sqrt(dx * dx + dy * dy);
return distance < circle1.radius + circle2.radius;
}
3. 分离轴定理(SAT)复杂形状碰撞
function projectVertices(vertices, axis) {
let min = Infinity;
let max = -Infinity;
for (const vertex of vertices) {
const projection = vertex.x * axis.x + vertex.y * axis.y;
min = Math.min(min, projection);
max = Math.max(max, projection);
}
return { min, max };
}
function checkSATCollision(poly1, poly2) {
const axes = [];
// 获取所有边的法线作为分离轴
for (let i = 0; i < poly1.vertices.length; i++) {
const p1 = poly1.vertices[i];
const p2 = poly1.vertices[(i + 1) % poly1.vertices.length];
const edge = new Vector2(p2.x - p1.x, p2.y - p1.y);
const normal = new Vector2(-edge.y, edge.x).normalize();
axes.push(normal);
}
for (let i = 0; i < poly2.vertices.length; i++) {
const p1 = poly2.vertices[i];
const p2 = poly2.vertices[(i + 1) % poly2.vertices.length];
const edge = new Vector2(p2.x - p1.x, p2.y - p1.y);
const normal = new Vector2(-edge.y, edge.x).normalize();
axes.push(normal);
}
// 检查所有分离轴
for (const axis of axes) {
const proj1 = projectVertices(poly1.vertices, axis);
const proj2 = projectVertices(poly2.vertices, axis);
if (proj1.max < proj2.min || proj2.max < proj1.min) {
return false; // 存在分离轴,无碰撞
}
}
return true; // 所有轴都有重叠,发生碰撞
}
运动模拟实现
基于时间的运动模拟
class PhysicsObject {
constructor(position, velocity = new Vector2(), acceleration = new Vector2()) {
this.position = position;
this.velocity = velocity;
this.acceleration = acceleration;
this.mass = 1;
this.restitution = 0.8; // 弹性系数
this.friction = 0.98; // 摩擦系数
}
update(deltaTime) {
// 更新速度
this.velocity = this.velocity.add(this.acceleration.multiply(deltaTime));
// 应用摩擦力
this.velocity = this.velocity.multiply(this.friction);
// 更新位置
this.position = this.position.add(this.velocity.multiply(deltaTime));
// 重置加速度
this.acceleration = new Vector2();
}
applyForce(force) {
// F = ma → a = F/m
this.acceleration = this.acceleration.add(force.multiply(1 / this.mass));
}
}
碰撞响应处理
function resolveCollision(obj1, obj2, normal, depth) {
// 相对速度
const relativeVelocity = obj2.velocity.subtract(obj1.velocity);
const velocityAlongNormal = relativeVelocity.dot(normal);
// 如果物体正在分离,不处理碰撞
if (velocityAlongNormal > 0) return;
// 计算冲量标量
const restitution = Math.min(obj1.restitution, obj2.restitution);
let impulseScalar = -(1 + restitution) * velocityAlongNormal;
impulseScalar /= (1 / obj1.mass + 1 / obj2.mass);
// 应用冲量
const impulse = normal.multiply(impulseScalar);
obj1.velocity = obj1.velocity.subtract(impulse.multiply(1 / obj1.mass));
obj2.velocity = obj2.velocity.add(impulse.multiply(1 / obj2.mass));
// 位置校正(防止物体嵌入)
const percent = 0.8; // 通常使用80%的渗透深度
const slop = 0.01; // 允许的渗透容差
const correction = normal.multiply(
Math.max(depth - slop, 0) / (1 / obj1.mass + 1 / obj2.mass) * percent
);
obj1.position = obj1.position.subtract(correction.multiply(1 / obj1.mass));
obj2.position = obj2.position.add(correction.multiply(1 / obj2.mass));
}
性能优化策略
空间分割技术
class SpatialHashGrid {
constructor(cellSize, width, height) {
this.cellSize = cellSize;
this.grid = new Map();
this.width = width;
this.height = height;
}
getCellKey(x, y) {
const cellX = Math.floor(x / this.cellSize);
const cellY = Math.floor(y / this.cellSize);
return `${cellX},${cellY}`;
}
insert(object) {
const keys = this.getObjectCells(object);
for (const key of keys) {
if (!this.grid.has(key)) {
this.grid.set(key, new Set());
}
this.grid.get(key).add(object);
}
object.gridCells = keys;
}
getObjectCells(object) {
const cells = new Set();
const bounds = object.getBounds();
const minX = Math.floor(bounds.left / this.cellSize);
const maxX = Math.floor(bounds.right / this.cellSize);
const minY = Math.floor(bounds.top / this.cellSize);
const maxY = Math.floor(bounds.bottom / this.cellSize);
for (let x = minX; x <= maxX; x++) {
for (let y = minY; y <= maxY; y++) {
cells.add(`${x},${y}`);
}
}
return Array.from(cells);
}
getNearbyObjects(object) {
const nearby = new Set();
for (const key of object.gridCells) {
const cellObjects = this.grid.get(key);
if (cellObjects) {
for (const obj of cellObjects) {
if (obj !== object) {
nearby.add(obj);
}
}
}
}
return Array.from(nearby);
}
update(object) {
// 移除旧位置
for (const key of object.gridCells) {
const cell = this.grid.get(key);
if (cell) {
cell.delete(object);
}
}
// 插入新位置
this.insert(object);
}
}
碰撞检测优化流程
实际项目应用
Boole-Bot游戏中的物理实现
基于app-ideas中的Boole-Bot游戏项目,我们可以实现一个完整的物理引擎:
class BooleBotPhysics {
constructor(arenaWidth, arenaHeight) {
this.arenaWidth = arenaWidth;
this.arenaHeight = arenaHeight;
this.bots = [];
this.collisionPairs = [];
this.spatialGrid = new SpatialHashGrid(50, arenaWidth, arenaHeight);
}
addBot(bot) {
this.bots.push(bot);
this.spatialGrid.insert(bot);
}
update(deltaTime) {
// 更新所有机器人的位置
for (const bot of this.bots) {
if (bot.active) {
bot.update(deltaTime);
this.handleArenaBoundaries(bot);
this.spatialGrid.update(bot);
}
}
// 检测和处理碰撞
this.detectCollisions();
this.resolveCollisions();
}
handleArenaBoundaries(bot) {
// 边界碰撞检测和响应
const radius = bot.radius;
if (bot.position.x - radius < 0) {
bot.position.x = radius;
bot.velocity.x = -bot.velocity.x * bot.restitution;
} else if (bot.position.x + radius > this.arenaWidth) {
bot.position.x = this.arenaWidth - radius;
bot.velocity.x = -bot.velocity.x * bot.restitution;
}
if (bot.position.y - radius < 0) {
bot.position.y = radius;
bot.velocity.y = -bot.velocity.y * bot.restitution;
} else if (bot.position.y + radius > this.arenaHeight) {
bot.position.y = this.arenaHeight - radius;
bot.velocity.y = -bot.velocity.y * bot.restitution;
}
}
detectCollisions() {
this.collisionPairs = [];
for (const bot of this.bots) {
if (!bot.active) continue;
const nearbyBots = this.spatialGrid.getNearbyObjects(bot);
for (const otherBot of nearbyBots) {
if (bot !== otherBot && otherBot.active) {
const collision = this.checkCircleCollision(bot, otherBot);
if (collision.colliding) {
this.collisionPairs.push({
bot1: bot,
bot2: otherBot,
normal: collision.normal,
depth: collision.depth
});
}
}
}
}
}
resolveCollisions() {
for (const pair of this.collisionPairs) {
this.resolveCollision(pair.bot1, pair.bot2, pair.normal, pair.depth);
// 处理布尔逻辑碰撞结果
this.handleBooleanCollision(pair.bot1, pair.bot2);
}
}
handleBooleanCollision(bot1, bot2) {
// 基于布尔逻辑的碰撞处理
const result1 = this.applyBooleanOperation(bot1.operation, bot1.value, bot2.value);
const result2 = this.applyBooleanOperation(bot2.operation, bot2.value, bot1.value);
if (result1 === 0) bot1.deactivate();
if (result2 === 0) bot2.deactivate();
}
applyBooleanOperation(operation, value1, value2) {
switch (operation) {
case 'AND': return value1 && value2;
case 'OR': return value1 || value2;
case 'XOR': return (value1 && !value2) || (!value1 && value2);
case 'NOT': return !value1;
default: return value1;
}
}
}
高级主题:连续碰撞检测
对于高速移动的物体,离散碰撞检测可能失效,需要连续碰撞检测(CCD):
function continuousCollisionDetection(obj1, obj2, deltaTime) {
// 计算相对运动
const relativeVelocity = obj2.velocity.subtract(obj1.velocity);
const relativeSpeed = relativeVelocity.magnitude();
if (relativeSpeed === 0) return null;
// 使用射线投射进行连续检测
const direction = relativeVelocity.normalize();
const rayOrigin = obj1.position;
const rayLength = relativeSpeed * deltaTime;
// 执行射线与形状的相交测试
const intersection = rayCast(obj2, rayOrigin, direction, rayLength);
if (intersection) {
return {
time: intersection.time,
point: intersection.point,
normal: intersection.normal
};
}
return null;
}
function rayCast(object, origin, direction, maxDistance) {
// 实现射线与物体形状的相交测试
// 这里以圆形为例
if (object.shape === 'circle') {
const toCenter = object.position.subtract(origin);
const projection = toCenter.dot(direction);
const closestDistSq = toCenter.dot(toCenter) - projection * projection;
const radiusSq = object.radius * object.radius;
if (closestDistSq > radiusSq) return null;
const insideDist = Math.sqrt(radiusSq - closestDistSq);
let t = projection - insideDist;
if (t < 0) t = projection + insideDist;
if (t < 0 || t > maxDistance) return null;
const hitPoint = origin.add(direction.multiply(t));
const normal = hitPoint.subtract(object.position).normalize();
return { time: t / maxDistance, point: hitPoint, normal };
}
// 可以添加其他形状的射线检测
return null;
}
调试与性能分析
性能监控工具
class PhysicsProfiler {
constructor() {
this.stats = {
updateTime: 0,
collisionDetectionTime: 0,
collisionResolutionTime: 0,
totalObjects: 0,
collisionCount: 0
};
this.frameTimes = [];
}
startFrame() {
this.frameStart = performance.now();
}
endFrame() {
const frameTime = performance.now() - this.frameStart;
this.frameTimes.push(frameTime);
// 保持最近60帧的数据
if (this.frameTimes.length > 60) {
this.frameTimes.shift();
}
}
startCollisionDetection() {
this.collisionStart = performance.now();
}
endCollisionDetection(collisionCount) {
this.stats.collisionDetectionTime = performance.now() - this.collisionStart;
this.stats.collisionCount = collisionCount;
}
getAverageFrameTime() {
if (this.frameTimes.length === 0) return 0;
return this.frameTimes.reduce((sum, time) => sum + time, 0) / this.frameTimes.length;
}
getFPS() {
const avgFrameTime = this.getAverageFrameTime();
return avgFrameTime > 0 ? 1000 / avgFrameTime : 0;
}
logStats() {
console.log(`FPS: ${this.getFPS().toFixed(1)}`);
console.log(`碰撞检测时间: ${this.stats.collisionDetectionTime.toFixed(2)}ms`);
console.log(`碰撞次数: ${this.stats.collisionCount}`);
console.log(`物体数量: ${this.stats.totalObjects}`);
}
}
最佳实践总结
代码组织建议
性能优化清单
- 空间分割:始终使用空间分割技术(四叉树、空间哈希网格等)
- 分层检测:先进行宽相位检测,再进行窄相位检测
- 时间步长:使用固定的时间步长进行物理更新
- 对象池:重用物理对象避免内存分配
- 惰性计算:只在需要时计算复杂的物理量
- 简化形状:使用简单的碰撞形状近似复杂几何体
- 批量处理:批量处理类似的物理计算
常见问题解决方案
| 问题 | 解决方案 |
|---|---|
| 物体穿透 | 使用连续碰撞检测(CCD)或增加迭代次数 |
| 性能下降 | 优化空间分割,减少不必要的碰撞检测 |
| 运动不真实 | 调整物理参数(质量、弹性、摩擦) |
| 数值不稳定 | 使用更稳定的数值积分方法 |
结语
游戏物理是一个深奥而有趣的领域,从简单的碰撞检测到复杂的物理模拟,每一个细节都影响着游戏的最终体验。通过app-ideas项目中的实际案例,我们看到了如何将理论物理知识转化为可运行的代码。
记住,优秀的物理模拟不仅仅是技术的堆砌,更是艺术与科学的完美结合。在不断迭代和优化中,您将创造出既真实又令人满意的游戏物理体验。
开始您的物理编程之旅吧!从简单的AABB碰撞开始,逐步挑战更复杂的物理模拟,最终打造出属于您自己的物理引擎。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



