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嘛,但是如果大家想要在项目中使用的话,就需要更加拟真和复杂的算法,本文只是带大家大概的了解一下它的实现方式。

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

相关推荐
小孙姐29 分钟前
3——VUE侦听器和计算属性
前端·javascript·vue.js
好奇的菜鸟1 小时前
typeof 和 as 关键字
javascript·typescript
龙虎榜小红牛系统2 小时前
Wordcloud也能生成一个,带html的词云图文件吗??
python·html·wordcloud
珹洺2 小时前
从 HTML 到 CSS:开启网页样式之旅(三)—— CSS 三大特性与 CSS 常用属性
前端·javascript·css·网络·html·tensorflow·html5
T^T尚7 小时前
uniapp H5上传图片前压缩
前端·javascript·uni-app
出逃日志7 小时前
JS的DOM操作和事件监听综合练习 (具备三种功能的轮播图案例)
开发语言·前端·javascript
XIE3928 小时前
如何开发一个脚手架
前端·javascript·git·npm·node.js·github
GISer_Jing8 小时前
React渲染相关内容——渲染流程API、Fragment、渲染相关底层API
javascript·react.js·ecmascript
山猪打不过家猪8 小时前
React(五)——useContecxt/Reducer/useCallback/useRef/React.memo/useMemo
前端·javascript·react.js
前端青山8 小时前
React事件处理机制详解
开发语言·前端·javascript·react.js