JavaScript 从零实现物理模拟

最近掌门人在写3D游戏,对于其中的物理效果很感兴趣,今天我将使用纯JavaScript来实现一个简易的物理模拟,其中包括碰撞检测与响应、摩擦力与空气阻力、以及物体的破坏效果。本文的目标是创建一个可以控制物体的环境,其中物体会按照物理定律移动,并且能够相互之间发生碰撞和反应。并且不会使用任何第三方库,从零实现。

碰撞检测

首先就是碰撞检测,在游戏中这是最基础的效果,下述栗子中将会实现检测两个矩形物体是否发生碰撞。

js 复制代码
function isColliding(rect1, rect2) {
  return (
    rect1.x < rect2.x + rect2.width &&
    rect1.x + rect1.width > rect2.x &&
    rect1.y < rect2.y + rect2.height &&
    rect1.height + rect1.y > rect2.y
  );
}

// 测试碰撞检测
const rect1 = { x: 5, y: 5, width: 50, height: 50 };
const rect2 = { x: 20, y: 20, width: 50, height: 50 };

console.log(isColliding(rect1, rect2)); // 输出:true

碰撞响应

正常情况下,当两个物体发生碰撞时,我们需要计算它们的碰撞响应。在这里,就简单地反转它们的速度来模拟弹性碰撞。

js 复制代码
function resolveCollision(obj1, obj2) {
  const tempVelocity = obj1.velocity;
  obj1.velocity = obj2.velocity;
  obj2.velocity = tempVelocity;
}

// 测试碰撞响应
let obj1 = { velocity: { x: 5, y: 0 } };
let obj2 = { velocity: { x: -3, y: 0 } };

if (isColliding(rect1, rect2)) {
  resolveCollision(obj1, obj2);
}

console.log(obj1.velocity, obj2.velocity); // 输出:{ x: -3, y: 0 } { x: 5, y: 0 }

3. 摩擦力

摩擦力会减缓物体在接触表面上的移动。在这个示例中,我们使用一个简单的摩擦系数来模拟摩擦力对速度的影响。

js 复制代码
function applyFriction(velocity, friction) {
  velocity.x *= friction;
  velocity.y *= friction;
}

// 测试摩擦力
let velocity = { x: 10, y: 0 };
const friction = 0.95; // 摩擦系数

applyFriction(velocity, friction);

console.log(velocity); // 输出:{ x: 9.5, y: 0 }

空气阻力

空气阻力会减缓物体在空中的移动。我们可以通过减小速度的百分比来模拟这种效果.

js 复制代码
function applyDrag(velocity, drag) {
  velocity.x *= (1 - drag);
  velocity.y *= (1 - drag);
}

// 测试空气阻力
let velocity = { x: 10, y: 0 };
const drag = 0.05; // 空气阻力系数

applyDrag(velocity, drag);

console.log(velocity); // 输出:{ x: 9.5, y: 0 }

物体破坏

首先当物体的速度超过一定阈值时,我们可以假设物体已经被破坏。这里我们定义一个简单的函数来判断物体是否应该被破坏,并在破坏发生时改变其状态。

js 复制代码
function checkAndApplyDamage(obj, threshold) {
  const speed = Math.sqrt(obj.velocity.x ** 2 + obj.velocity.y ** 2);
  if (speed > threshold) {
    obj.isDestroyed = true;
  }
}

// 测试物体破坏
let obj = { velocity: { x: 50, y: 0 }, isDestroyed: false };
const damageThreshold = 30; // 破坏阈值

checkAndApplyDamage(obj, damageThreshold);

console.log(obj.isDestroyed); // 输出:true

完整案例

俗话说光说不练假把式,所有的都是为最终的效果服务的,最后我们将上述的所有效果整合起来,实现一个简单的demo效果。

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"> 
    <title>物理模拟</title>
    <style> 
        canvas { 
            border: 1px solid black;
        } 
        #controls {
            margin-top: 10px;
        }
        .info {
            margin-right: 10px;
        }
    </style>
</head>
</body>
  <canvas id="simulation" width="500" height="300"></canvas>
  <div id="controls">
    <div class="info">
      <label for="friction">摩擦力:</label>
      <input type="range" id="friction" min="0" max="1" step="0.01" value="0.99">
      <span id="frictionValue">0.99</span>
    </div>
    <div class="info">
      <label for="drag">风阻:</label>
      <input type="range" id="drag" min="0" max="0.1" step="0.001" value="0.01">
      <span id="dragValue">0.01</span>
    </div>
    <div class="info">
      <label for="threshold">临界值:</label>
      <input type="range" id="threshold" min="0" max="500" step="1" value="25">
      <span id="thresholdValue">25</span>
    </div>
    <button id="restartBtn">重启模拟</button>
  </div>
  <script>
  document.addEventListener('DOMContentLoaded', function () {
  // 获取canvas和context
  const canvas = document.getElementById('simulation');
  const ctx = canvas.getContext('2d');

  // 定义物体构造函数
  function GameObject(x, y, width, height, velocity) {
    this.initialX = x;
    this.initialY = y;
    this.width = width;
    this.height = height;
    this.initialVelocity = { ...velocity };
    this.velocity = { ...velocity };
    this.isDestroyed = false;
  }

  // 添加方法到GameObject原型
  GameObject.prototype = {
    reset() {
      this.x = this.initialX;
      this.y = this.initialY;
      this.velocity = { ...this.initialVelocity };
      this.isDestroyed = false;
    },
    isColliding(other) {
      return (
        this.x < other.x + other.width &&
        this.x + this.width > other.x &&
        this.y < other.y + other.height &&
        this.height + this.y > other.y
      );
    },
    resolveCollision(other) {
      if (!this.isDestroyed && !other.isDestroyed) {
        console.log('打印碰撞前的速度:', Math.sqrt(this.velocity.x ** 2 + this.velocity.y ** 2), Math.sqrt(other.velocity.x ** 2 + other.velocity.y ** 2));
        const tempVelocity = this.velocity;
        this.velocity = other.velocity;
        other.velocity = tempVelocity;
        console.log('打印碰撞后的速度:', Math.sqrt(this.velocity.x ** 2 + this.velocity.y ** 2), Math.sqrt(other.velocity.x ** 2 + other.velocity.y ** 2));
      }
    },
    update(friction, drag, threshold) {
      if (this.isDestroyed) return;

      // 应用摩擦力和空气阻力
      this.velocity.x *= friction;
      this.velocity.y *= friction;
      this.velocity.x *= (1 - drag);
      this.velocity.y *= (1 - drag);

      // 更新位置
      this.x += this.velocity.x;
      this.y += this.velocity.y;

      // 检查破坏
      const speed = Math.sqrt(this.velocity.x ** 2 + this.velocity.y ** 2);
      if (speed > threshold) {
        this.isDestroyed = true;
      }
    },
    draw() {
      ctx.fillStyle = this.isDestroyed ? 'red' : 'blue';
      ctx.fillRect(this.x, this.y, this.width, this.height);
    }
  };

  // 实例化物体,确保设置了 x 和 y
  const obj1 = new GameObject(100, 100, 50, 50, { x: 5, y: 0 });
  const obj2 = new GameObject(300, 100, 50, 50, { x: -5, y: 0 });

  // 设置物理参数
  let friction = 0.99;
  let drag = 0.01;
  let destructionThreshold = 25;

  // 获取控件元素
  const frictionInput = document.getElementById('friction');
  const dragInput = document.getElementById('drag');
  const thresholdInput = document.getElementById('threshold');
  const frictionValueDisplay = document.getElementById('frictionValue');
  const dragValueDisplay = document.getElementById('dragValue');
  const thresholdValueDisplay = document.getElementById('thresholdValue');
  const restartBtn = document.getElementById('restartBtn');

  // 更新参数值的函数
  function updateParameters() {
    friction = parseFloat(frictionInput.value);
    drag = parseFloat(dragInput.value);
    destructionThreshold = parseFloat(thresholdInput.value);
  }

  // 监听滑动条的变化
  frictionInput.addEventListener('input', function () {
    frictionValueDisplay.textContent = this.value;
  });
  dragInput.addEventListener('input', function () {
    dragValueDisplay.textContent = this.value;
  });
  thresholdInput.addEventListener('input', function () {
    thresholdValueDisplay.textContent = this.value;
  });

  // 动画循环函数
  let animationFrameId;

  function animate() {
    animationFrameId = requestAnimationFrame(animate);

    // 清除画布
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // 更新和绘制物体
    obj1.update(friction, drag, destructionThreshold);
    obj2.update(friction, drag, destructionThreshold);
    obj1.draw();
    obj2.draw();

    // 碰撞检测和响应
    if (obj1.isColliding(obj2)) {
      obj1.resolveCollision(obj2);
    }
  }

  // 启动动画
  animate();

  // 重新开始动画的函数
  function restartAnimation() {
    // 取消当前的动画帧请求
    cancelAnimationFrame(animationFrameId);

    // 重置物体状态
    obj1.reset();
    obj2.reset();

    // 更新参数
    updateParameters();

    // 重新开始动画
    animate();
  }

  // 绑定重启按钮事件
  restartBtn.addEventListener('click', restartAnimation);

})
  </script>
<body>

总结

上述的案例我们实现了简单的物理模拟效果,只是给大家一个思路,毕竟万物皆可JS嘛,但是如果大家想要在项目中使用的话,就需要更加拟真和复杂的算法,本文只是带大家大概的了解一下它的实现方式。

大家快去试试吧,俗话说眼过千遍,不如手过一遍,说不定大家在实现的过程中,会找到更加方便简易的实现方法。

相关推荐
熊猫_豆豆2 小时前
一个模拟四轴飞行器在随机气流扰动下悬停飞行的交互式3D仿真网页,包含飞行器建模与PID控制算法
javascript·3d·html·四轴无人机模拟飞行
小贺儿开发2 小时前
一句话生成网页 + 自动化办公(OpenCode + DeepSeek-V4)
css·自动化·html·工具·代码·网页·deepseek
来恩10033 小时前
jQuery选择器
前端·javascript·jquery
前端繁华如梦3 小时前
树上挂苹果还是挂玻璃球?Three.js 程序化果实的完整实现指南
前端·javascript
CDwenhuohuo3 小时前
优惠券组件直接用 uview plus
前端·javascript·vue.js
川冰ICE4 小时前
TypeScript装饰器与元编程实战
前端·javascript·typescript
AI砖家4 小时前
Vue3组件传参大全,各种传参方式的对比
前端·javascript·vue.js
希望永不加班4 小时前
var局部变量类型推断的利弊
java·服务器·前端·javascript·html
threelab5 小时前
Three.js 3D 地图可视化 | 三维可视化 / AI 提示词
前端·javascript·人工智能·3d·着色器
失眠的咕噜5 小时前
PDA 安卓设备上传多张图片
android·前端·javascript