更新记录
1.0.0(2026-07-19)
首发。3D 物理引擎——真实刚体碰撞 + 相机批量投影,掷骰子 / 抽奖动效 / 商品 3D 展示 / 轻量小游戏。
- 刚体:球 / 盒 / 胶囊 / 圆柱 / 凸包 + 静态盒(地面墙体);弹性、摩擦、密度、阻尼、锁轴、重力倍率、传感器、防穿透(ccd)逐体可调
- 关节:球铰(三自由度)/ 铰链(马达 + 角度限位)/ 滑轨(限位 + 马达)/ 焊接
- 查询:射线检测 / 点查询 / 包围盒查询(未命中给安全默认值,不抛错)
- 碰撞事件:
drainEvents()每帧取空开始/结束接触对 - 相机 + 批量投影:
setCamera配机位,projectBodies()把全部刚体一次投成屏幕像素点集(sx, sy, depth)——不引 3D 渲染引擎就能画 3D - 物理单位:米/秒/牛顿,y 轴向上,四元数姿态(xyzw 序)
- 三端:Android / iOS / H5 同一原生内核,42 个 API 三端全量可用
- 性能实测:千刚体单步 ~1.3ms、千刚体批量投影 <1ms(60fps 预算余量约 7 倍)
- 上限保护:刚体/关节各 10,000、凸包 4,096 点,超限抛可捕获错误不闪退
平台兼容性
uni-app x(5.14)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| √ | √ | 5.0 | 12 | × | × |
nex-physics3d 3D 物理引擎(掷骰子 · 真实碰撞 · 3D 展示)
真实 3D 碰撞搬进 uni-app——掷个骰子试试。原生内核负责刚体仿真与透视投影,把 3D 世界直接算成屏幕像素坐标吐给你,JS 只管拿画布/图片把点画出来——不需要引入任何 3D 渲染引擎。实测:千刚体世界单步 ~1.3ms、千刚体批量投影 <1ms,60fps 预算(16.6ms)余量约 7 倍。骰子 / 抽奖动效 / 商品 3D 展示 / 轻量小游戏,一个插件配齐。
特性
- 5 种刚体:球 / 盒 / 胶囊 / 圆柱 / 凸包(任意凸多面体),外加静态盒(地面/墙体);弹性、摩擦、密度、阻尼、锁轴、重力倍率、传感器、防穿透(
ccd)逐体可调 - 4 种关节:球铰(三自由度)/ 铰链(可带马达与角度限位)/ 滑轨(可带限位与马达)/ 焊接
- 查询三件套:射线检测(拾取/瞄准)、点查询、包围盒查询——未命中给安全默认值,不抛错
- 碰撞事件:开始/结束接触对,每帧
drainEvents()一次取空,做音效/震动/计分触发 - 独家:相机 + 批量投影——
setCamera配好机位,projectBodies()一次把全部刚体投成(sx, sy, depth)屏幕点集(含地面墙体);深度排序画远近,画布线框、贴图精灵随你 - 物理单位、真实手感:米/秒/牛顿,y 轴向上,默认重力 -9.81——参数即真实世界直觉
- 三端同源:Android / iOS / H5 同一原生内核,42 个 API 三端全量可用(H5 无阉割)
- 零权限、纯本地:无网络、无存储、无隐私权限,全部计算在端上完成
快速上手:掷骰子(完整可抄)
import { PhysicsWorld3d } from '@/uni_modules/nex-physics3d'
// H5 端内核异步初始化:建世界前等一次(App 端可跳过)
// #ifdef H5
import { ensureReady } from '@/uni_modules/nex-physics3d'
await ensureReady() // await 需在 async 上下文内(如 async onLoad);非 async 处改用 ensureReady().then(...)
// #endif
// 1) 建世界 + 地面 + 一颗骰子(单位:米;y 轴向上)
const w = new PhysicsWorld3d({ gravityY: -9.81 })
w.addStaticBox({ x: 0, y: -0.5, z: 0, hx: 50, hy: 0.5, hz: 50 }) // 地面(hx/hy/hz 是半边长)
const dice = w.addBox({
bodyType: 'dynamic', x: 0, y: 5, z: 0, hx: 0.5, hy: 0.5, hz: 0.5, // 1m 见方骰子从 5m 高落下
restitution: 0.35, friction: 0.6, ccd: true // 弹一弹 + 防高速穿透
})
// 2) 掷:给随机初速度 + 角速度(rand 自备:(min,max) 均匀随机)
w.setLinearVelocity(dice, rand(-2, 2), 0, rand(-2, 2))
w.setAngularVelocity(dice, rand(-10, 10), rand(-10, 10), rand(-10, 10))
// 3) 相机(配一次):视口像素尺寸 + 机位(眼/看向/视场角[度])
w.setViewport(375, 300)
w.setCamera(0, 6, 9, 0, 0, 0, 60) // 从 (0,6,9) 米俯看原点
// 4) 每帧:步进 → 批量投影 → 画;骰子睡着 = 落定
const timer = setInterval(() => {
w.step(16.7) // 推进 16.7ms
drawWireframe(w.projectBodies()) // 屏幕点集直接画(解析见下)
if (w.isSleeping(dice)) { clearInterval(timer); console.log('点数', diceValue()) }
}, 16)
// 5) 解析投影结果:[handle, 形状编码, 顶点数, (sx,sy,depth)×顶点数] 逐体排列
function drawWireframe(flat: number[]) {
let i = 0
while (i < flat.length) {
const kind = flat[i + 1]; const n = flat[i + 2]; i += 3
const pts: number[][] = []
for (let k = 0; k < n; k++) { pts.push([flat[i], flat[i + 1], flat[i + 2]]); i += 3 }
// kind:0=球(24 点) 1=盒(8 角) 2=胶囊(18) 3=圆柱(16) 4=凸包(原点数) 5=静态盒(8 角)
// 盒/静态盒:8 角连 12 条棱;球:3 个纬度环各 8 点连环……画法随你的视觉风格
}
}
// 6) 读点数:四元数判哪个面朝上(对面点数和恒为 7)
function diceValue(): number {
const t = w.getTransform(dice) // [h, x,y,z, qx,qy,qz,qw]
const qx = t[4], qy = t[5], qz = t[6], qw = t[7]
const faces: number[][] = [ // [局部法向 x,y,z, 该面点数]
[0, 1, 0, 2], [0, -1, 0, 5], [1, 0, 0, 3], [-1, 0, 0, 4], [0, 0, 1, 1], [0, 0, -1, 6],
]
let best = 1, bestDot = -2
for (const f of faces) {
const wy = rotatedY(qx, qy, qz, qw, f[0], f[1], f[2]) // 面法向转到世界系后的 y 分量
if (wy > bestDot) { bestDot = wy; best = f[3] as number }
}
return best // y 分量最大的面 = 朝上的面
}
function rotatedY(qx: number, qy: number, qz: number, qw: number, vx: number, vy: number, vz: number): number {
const tx = 2 * (qy * vz - qz * vy), ty = 2 * (qz * vx - qx * vz), tz = 2 * (qx * vy - qy * vx)
return vy + qw * ty + (qz * tx - qx * tz) // v' = v + qw·t + q×t,只要 y
}
// 7) 重掷:先 clear() 清场(复用同一个世界;clear 后重建也可,见 FAQ)
function reroll() {
w.clear() // 保留重力配置,清掉全部刚体/关节/事件
// 重新 addStaticBox + addBox + 设随机速度即可
}
多骰同掷:循环 addBox 存下多个 handle,逐个 isSleeping 全真即全部落定。示例工程里有完整的掷骰子游戏页(画布线框着色 + 重掷按钮)可整页照抄。
API 速览(42 个,全部在 PhysicsWorld3d 对象上)
| 分组 | API |
|---|---|
| 世界(7) | new PhysicsWorld3d(config) · step(dtMs) · transforms() · getTransform(h) · setGravity · bodyCount() · clear() |
| 刚体增删(8) | addBall · addBox · addCapsule · addCylinder · addConvexHull · addStaticBox · removeBody · bodyExists |
| 刚体操控(14) | setLinearVelocity/getLinearVelocity · setAngularVelocity/getAngularVelocity · applyImpulse · applyTorqueImpulse · applyForce · applyTorque · setTransform · setBodyType · wakeUp · isSleeping · setRestitution · setFriction |
| 关节(5) | addSphericalJoint · addRevoluteJoint · addPrismaticJoint · addFixedJoint · removeJoint |
| 查询(3) | castRay(命中 [handle,x,y,z,dist])· pointQuery(未命中 -1)· queryAabb |
| 事件(1) | drainEvents()([h1,h2,started]×M,取走即清空) |
| 相机投影(4) | setViewport · setCamera · projectPoint · projectBodies |
step() / transforms() / getTransform() 返回 8 宽位姿 [handle, x,y,z, qx,qy,qz,qw](位置米 + 四元数 xyzw 序);错误可 try/catch(参数非法 / handle 不存在 / 超上限,message 双端一致)。
常见问题
- 画面里什么都没有? 九成是没配相机:先
setViewport(宽px, 高px)再setCamera(...)(默认机位只是「能跑不崩」的占位,不对准你的场景);另查每帧是否真的在调step()。 - 怎么知道骰子停了? 建世界保持默认
allowSleep: true,逐帧isSleeping(handle);多骰全true即全部落定。落定后用getTransform的四元数判朝上面(上方完整代码可抄)。 - 重掷几十次后内存涨? 世界是显式生命周期,引擎内存不归 JS 垃圾回收管:重建前必须先
clear()释放旧世界;之后复用同一个世界、或 clear 后重新new PhysicsWorld3d皆可(两种写法都不泄漏——上方快速上手用前者,示例工程的掷骰子页用后者);页面卸载(onUnload)也记得clear()。 - 骰子偶尔穿过地面? 高速小物体开
ccd: true(示例已开);另注意step(dtMs)是单次步进,掉帧时别把 100ms 一口气传进去——切成多次step(16.7)碰撞质量更好。 - 3D 坐标怎么对到我的画布? 不用你换算:
projectBodies()直接吐屏幕像素(sx, sy, depth),按depth从大到小排序先画远的即可;单点换算用projectPoint(x,y,z)。 - 单位到底是什么? 全部真实物理单位:米、秒、米/秒、牛顿;y 轴向上、默认重力
(0, -9.81, 0)。骰子建 0.5~1 米见方手感最稳(纯像素思维建 100「米」的骰子会觉得「飘」——自由落体 100 米当然久)。 - 旋转四元数怎么用? 顺序固定
(qx,qy,qz,qw)(标量在最后),可直接喂 three.jsnew Quaternion(x,y,z,w)等主流库;只想判朝向抄上方rotatedY即可,不必引数学库。 - H5 端首次调用报「尚未初始化完成」? H5 内核是异步初始化:应用启动或页面
onLoad里await ensureReady()一次,再new PhysicsWorld3d。 - Android 打包报「找不到 jna」? 本插件含原生内核库,须制作自定义调试基座(HBuilderX 运行 → 制作自定义调试基座),标准基座跑不了。
- 能做布娃娃/吊桥吗? 能:球铰 + 铰链把多个胶囊/盒串起来即可(关节
body1/body2+ 各自局部锚点)。软体/布料/流体不支持(超出刚体范围)。
平台说明
| 端 | 说明 |
|---|---|
| Android | 原生内核库(arm64-v8a / armeabi-v7a / x86_64 全覆盖);需自定义基座运行 |
| iOS | 原生内核库(真机 + 模拟器);HBuilderX 打包勾选「支持 Swift」 |
| H5 | 同一原生内核在浏览器内直接运行(免安装、免服务端);首次调用前 await ensureReady(),此后 42 个 API 与 App 端完全一致 |
上限保护:刚体/关节各 10,000、凸包单次 4,096 点——超限抛可捕获错误,绝不静默截断或闪退。
技术支持 / 问题反馈:插件评论区或联系作者。

收藏人数:
购买普通授权版(
试用
赞赏(0)
下载 1178
赞赏 0
下载 12438998
赞赏 1934
赞赏
京公网安备:11010802035340号