☰
Coding Train《The Nature of Code》摩擦力的代码模拟:从动力学公式到 p5.js 实战
2026/10/14 20:39:32 网站建设 项目流程
  • 教程
  • 文档
  • 示例工程

【免费下载链接】website-archive

Archive of the Coding Train website (first version)

项目地址:https://gitcode.com/gh_mirrors/we/website-archive
点击查看免费下载

导读

本指南以 Coding Train 开源仓库中《The Nature of Code》第 2 章「力(Forces)」系列的摩擦力专题文档为核心,讲解如何把物理中的滑动摩擦力(kinetic friction)公式翻译成可运行的 p5.js 模拟代码。你将掌握:摩擦力如何作为一类普通力并入已有的applyForce()力驱动框架、如何用向量运算表达摩擦力的方向与大小、以及摩擦系数mu和质量mass对模拟效果的实际影响。读完本文,你可以直接复制仓库中的 Mover 实现,把它扩展进自己的重力、风力和碰撞模拟中。

摩擦力专题在课程体系中的位置

本文对应的是仓库中两份同主题文档:2015 年的原始版 _more/archive/nature-of-code/2-Forces/2.4-Friction-Force.md(即关联文档)与 2020 年重制版 _learning/nature-of-code/2.3-friction.md。两份文档主题一致——模拟摩擦力,重制版视频编号为 2.3,属于「Nature of Code」系列(见 _learning/nature-of-code/index.md)。

在课程脉络上,它是「力」章节的承前启后之作:

  • 前置:2.1 用applyForce()引入「力」概念(2.1-simulating-forces.md);2.2 为 Mover 添加质量属性、实现F = ma(2.2-mass-acceleration.md);
  • 本篇:把真实物理公式——摩擦力——作为第三种力接入同一套框架;
  • 后续:2.4 继续把「阻力/空气阻力(Drag)」同样实现为公式力(2.4-drag.md)。

也就是说,摩擦力是「真实物理公式 → 模拟代码」这条教学主线上的第一个完整案例:前面章节用的是随手设定的虚构力数值("made-up" values,见原始版 2.2-Applying-a-Force.md),而从本篇开始,力的大小由物理公式计算得出。

摩擦力的物理公式:在代码中如何表达

要理解模拟代码,先看物理模型。滑动摩擦力(kinetic friction)的大小由下式给出:

F_friction = -μ × N × v̂

其中:

  • μ(mu):摩擦系数,无量纲,反映接触面粗糙程度;
  • N:法向力(normal force),即物体对接触面的正压力大小;
  • v̂:速度方向的单位向量;负号表示摩擦力方向始终与运动方向相反。

代码实现的关键是把公式拆成「方向」和「大小」两步:

  1. 方向:取当前速度向量的单位向量,取反——this.vel.copy().normalize().mult(-1);
  2. 大小:摩擦系数mu乘以法向力normal(本模拟中直接用质量this.mass近似)。

在物体与地面接触的简化场景(水平地面、无垂直方向运动)中,法向力约等于物体重量,即N ≈ mg;在本例代码中取normal = this.mass,相当于按单位重力简化处理。当物体弹起、与地面短暂分离时(diff < 1的接触判定会失效),摩擦力自然不生效,这与物理直觉一致。

完整源码解析:Mover 类的摩擦力实现

仓库中该专题的完整可运行源码位于 learning/nature-of-code/2.3_friction/P5,包含三个文件:index.html(页面骨架)、mover.js(Mover 类)、sketch.js(主程序)。对应 p5.js Web Editor 草稿为zcTpMCpA(见 2.3-friction.md 的 front matter)。

主程序 sketch.js:力的组装

let movers = []; let mu = 0.1; function setup() { createCanvas(400, 400); for (let i = 0; i < 10; i++) { movers[i] = new Mover(random(width), 200, random(1, 8)); } } function draw() { background(0); for (let mover of movers) { if (mouseIsPressed) { let wind = createVector(0.1, 0); mover.applyForce(wind); } let gravity = createVector(0, 0.2); let weight = p5.Vector.mult(gravity, mover.mass); mover.applyForce(weight); mover.friction(); mover.update(); mover.edges(); mover.show(); } }

关键点:

  • 全局摩擦系数mu = 0.1:一个偏小的值,让物体缓慢减速而不是骤然停止,便于观察;
  • 重力(weight):gravity向量(0, 0.2)乘以质量得到重量,再交给applyForce()——这是 2.2 章引入的质量处理方式;
  • 风力:按住鼠标时施加水平力(0.1, 0),模拟横向气流;
  • 每帧调用顺序:先施加外力,再调用mover.friction()计算并施加摩擦力,最后更新位置、处理边界、绘制。

Mover 类:摩擦力是「读速度、写加速度」的力

mover.js 中的核心是friction()方法:

class Mover { constructor(x, y, m) { this.pos = createVector(x, y); this.vel = createVector(0, 0); this.acc = createVector(0, 0); this.mass = m; this.r = sqrt(this.mass) * 10; } friction() { let diff = height - (this.pos.y + this.r); if (diff < 1) { // shorcut to friction! // this.vel.mult(0.95); // Direction of Friction let friction = this.vel.copy(); friction.normalize(); friction.mult(-1); // chained version // let friction = this.vel.copy().normalize().mult(-1); // Magnitude of Friction let normal = this.mass; friction.setMag(mu * normal); this.applyForce(friction); } } applyForce(force) { let f = p5.Vector.div(force, this.mass); this.acc.add(f); } edges() { if (this.pos.y >= height - this.r) { this.pos.y = height - this.r; this.vel.y *= -1; } if (this.pos.x >= width - this.r) { this.pos.x = width - this.r; this.vel.x *= -1; } else if (this.pos.x <= this.r) { this.pos.x = this.r; this.vel.x *= -1; } } update() { this.vel.add(this.acc); this.pos.add(this.vel); this.acc.set(0, 0); } show() { stroke(255); strokeWeight(2); fill(255, 100); ellipse(this.pos.x, this.pos.y, this.r * 2); } }

逐步拆解friction()的实现逻辑:

  1. 接触判定:diff = height - (this.pos.y + this.r)计算物体底边到画布底部的间隙,diff < 1表示物体贴着地面(含轻微穿透);
  2. 方向:this.vel.copy().normalize().mult(-1)取速度的单位向量并反向——摩擦力永远与运动方向相反。注释中还给出了链式写法let friction = this.vel.copy().normalize().mult(-1);,以及一个偷懒的简化替代this.vel.mult(0.95)(直接每帧把速度打 95 折,能模拟减速效果,但没有物理依据);
  3. 大小:friction.setMag(mu * normal),其中normal = this.mass——把法向力近似为质量;
  4. 施加:把构造好的摩擦力向量交给applyForce(),与重力、风力走同一条F = ma通道。

质量如何影响摩擦力效果

注意normal = this.mass使摩擦力大小与质量成正比,而applyForce()中F = ma又要除以质量,因此摩擦力导致的加速度与质量无关:a = F/m = μN/m = μ·m/m = μ。实际效果是,大质量物体与地面接触时表现与小质量物体一致的减速体验——这正是伽利略「落体与质量无关」思想在水平摩擦场景的再现。你可以通过修改setup()中random(1, 8)的质量范围或全局mu值,直观验证这一特性。

页面加载与运行方式

仓库源码通过 index.html 组织依赖:

<!DOCTYPE html> <html> <head> <script src="https://cdn.jsdelivr.net/npm/p5@1.4.1/lib/p5.min.js"></script> <meta charset="utf-8" /> </head> <body> <script src="mover.js"></script> <script src="sketch.js"></script> </body> </html>

运行要点:

  • p5.js 版本:从 CDN 引入p5@1.4.1,若本地调试请保持版本一致;
  • 加载顺序:mover.js(类定义)必须先于sketch.js(主程序)加载,否则new Mover()会报Mover is not defined;
  • 本地运行:直接用浏览器打开index.html即可(p5.js 绘制无需本地服务器);按住鼠标施加风力,松开后观察摩擦力让物体在弹跳中逐渐减速、最终静止于地面。

交互验证与扩展练习

从源码结构可以推断,该模拟的「可调旋钮」集中在三个位置,可用于教学实验:

参数/代码位置默认值调整效果
全局mu(sketch.js)0.1摩擦系数越大,物体减速越快;超过约0.5时物体会被「粘」在地面上难以横向移动
gravity向量(sketch.js)(0, 0.2)增大则落得更快、反弹更强,间接改变接触瞬间的速度与摩擦生效时长
random(1, 8)质量范围(sketch.js)1~8控制物体大小(半径r = sqrt(mass) * 10)与重力大小

值得尝试的扩展方向:

  • 把normal = this.mass改为normal = this.mass * 0.5,观察「减半法向力」后物体滑得更远的差异;
  • 参考本章 2.2 的F = ma框架,把摩擦力的setMag计算推广为更通用的「速度相关阻尼力」;
  • 对照后续的阻力篇(2.4-drag.md)体会「摩擦力与速度无关、阻力与速度有关」两种公式在实现上的差异——摩擦力大小恒定(由μN决定),而阻力大小随速度平方变化。

小结

摩擦力模拟是《The Nature of Code》中「真实公式驱动模拟」的典范案例:它的代码量极小,却完整覆盖了向量方向运算、单位向量、标量缩放、F = ma力叠加等核心技能。通过本仓库源码,你可以清楚地看到一条可复用的编码路径——任何公式力都可以拆成「方向向量 + 大小标量」,然后用setMag()组装并交给applyForce()。这条路径将贯穿整本书的引力、阻力、弹力等所有力的模拟。

  • 教程
  • 文档
  • 示例工程

【免费下载链接】website-archive

Archive of the Coding Train website (first version)

项目地址:https://gitcode.com/gh_mirrors/we/website-archive
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询