用命令写一个碰撞物理引擎

联机侠
用命令写一个碰撞物理引擎

1.13的预览版可以通过记分板的数据修改实体的NBT

我立刻想到了可以由此制作一个任意实体的碰撞物理引擎~
此处的都要用 NoGravity:1 屏蔽原版的重力 (甚至让Motion时刻为零 不过没有必要)效果图:

附件的datapack就是这个效果!
先执行function bounce:_init
再用命令方块超频执行function bounce:main
右键萝卜钓竿就可以发射弹弹苹果了!

一、运动分析
1. 首先我们来分析一下一个实体的运动过程

我们知道,速度是位置的变化率,加速度是速度的变化率

  1. v=dx/dt
  2. a=dv/dt

复制代码

此时的dt在mc里就是1tick
所以,位置的变化量x等于速度乘以1tick,速度的变化量等于加速度乘以1tick
此时速度的单位是block/tick,加速度单位为block/tick^2

2. 接下来分析一下实体与方块发生碰撞的情况
先来看平面中的情况
一个小球碰撞平面可以这样来看:

入射  出射
\      /
\    /
\  /
\/
------------- 碰撞平面
对入射时速度进行分解:
-----> Vx
|
|
\|/  Vy
对出射时速度进行分解:
/|\ -Vy
|
|
-----> Vx

可见发生反弹时垂直于反弹面的速度发生了反向。
将这个结论推广到三维空间中时也是成立的。

3. 再看两个实体碰撞的情况:
假设两个实体都是球体(简化模型)
 
把速度分解到碰撞方向上
在碰撞方向上,满足动量定理。即

  1. m1*V1x+m2*V2x=m1*V1x'+m2*V2x'

复制代码

且两小球碰撞满足能量守恒,即

  1. 1/2 m1*V1^2+1/2 m2*V2^2=1/2 m1*V1'^2+1/2 m2*V2'^2

复制代码

联立即可解得V1x'、V2x'

二、下面就把上面的分析放到MC里来实现吧!

1. 准备工作
建立以下九个记分板

  1. scoreboard objectives add px dummy x坐标
  2. scoreboard objectives add py dummy y坐标
  3. scoreboard objectives add pz dummy z坐标
  4. scoreboard objectives add vx dummy x速度
  5. scoreboard objectives add vy dummy y速度
  6. scoreboard objectives add vz dummy z速度
  7. scoreboard objectives add ax dummy x加速度
  8. scoreboard objectives add ay dummy y加速度
  9. scoreboard objectives add az dummy z加速度

复制代码

用记分板记录实体的位置、速度和加速度

建立两个常数记分板和临时记分板来备用

  1. scoreboard objectives add temp dummy 临时1
  2. scoreboard objectives add temp2 dummy 临时2
  3. scoreboard objectives add const1 dummy 常数1
  4. scoreboard objectives add const2 dummy 常数2

复制代码

2. 对实体的初始化
首先生成一个实体

  1. summon minecraft:pig 8 10 8 {Tags:["new_ball"],NoGravity:1,NoAI:1,Health:1}

复制代码

再给它赋予一个速度

  1. scoreboard players set @e[tag=new_ball] vx 5000
  2. scoreboard players set @e[tag=new_ball] vy 5000
  3. scoreboard players set @e[tag=new_ball] vz 5000

复制代码

设置加速度。这里只有y方向有加速度,也就是重力加速度
最后如果想改变重力,只要修改这几个记分板就可以了!

  1. scoreboard players set @e[tag=new_ball] ax 0
  2. scoreboard players set @e[tag=new_ball] ay -200
  3. scoreboard players set @e[tag=new_ball] az 0

复制代码

设置一些常数,后面会用到

  1. scoreboard players set @e[tag=new_ball] const1 -1

复制代码

3. 开始运动!
建立一个函数名叫fly,其执行者就是要移动的实体
以下是fly.mcfunction的内容:
首先获取实体位置

  1. execute as @s store result score @s px run data get entity @s Pos[0] 100000
  2. execute as @s store result score @s py run data get entity @s Pos[1] 100000
  3. execute as @s store result score @s pz run data get entity @s Pos[2] 100000

复制代码

接着使速度变化量也就是加速度加到速度上

  1. scoreboard players operation @s vx += @s ax
  2. scoreboard players operation @s vy += @s ay
  3. scoreboard players operation @s vz += @s az

复制代码

接着,如果目前的位置加**置变化量也就是下一tick的位置有一个方块,那么我们就将速度反向,达到碰撞反弹的效果
把检测分为xyz三个方向来检测,这里用到了很多1.13的特性
首先生成一个实体来帮助检测

  1. summon minecraft:area_effect_cloud ~ ~ ~ {Tags:["temp_mark"]}

复制代码

接着移动实体的位置到下一tick的位置

  1. #判断有无方块-temp=px
  2. execute as @s store result score @s temp run scoreboard players get @s px
  3. #判断有无方块-temp加上vx
  4. scoreboard players operation @s temp += @s vx
  5. #判断有无方块-x移动临时实体
  6. execute as @s store result entity @e[tag=temp_mark,limit=1] Pos[0] double 0.00001 run scoreboard players get @s temp

复制代码

如果临时实体的位置处有方块,那么就反向vx

  1. execute at @e[tag=temp_mark,limit=1] unless block ~ ~ ~ air run scoreboard players operation @s vx *= @s const1

复制代码

此处const1的值为-1
再将临时实体的位置还原

  1. execute as @s store result entity @e[tag=temp_mark,limit=1] Pos[0] double 0.00001 run scoreboard players get @s px

复制代码

同理,对y,z方向都做同样处理。

本来我们是要检测碰撞另外一个实体的,但是由于速度在碰撞方向上的分解需要进行向量的旋转等运算,需要计算正弦正切值,太麻烦我懒得搞233反正思路有了肯定是可以做出来的!正弦的计算用泰勒展开就好!

接下来把位置加上速度,再把记分板的值更新到实体上,一个tick的循环就完成了!

  1. #移动
  2. #p加上v
  3. scoreboard players operation @s px += @s vx
  4. scoreboard players operation @s py += @s vy
  5. scoreboard players operation @s pz += @s vz
  6. #p存入nbt
  7. execute as @s store result entity @s Pos[0] double 0.00001 run scoreboard players get @s px
  8. execute as @s store result entity @s Pos[1] double 0.00001 run scoreboard players get @s py
  9. execute as @s store result entity @s Pos[2] double 0.00001 run scoreboard players get @s pz
  10. #删除临时实体
  11. kill @e[tag=temp_mark]

复制代码

三、继续优化

1. 能量损失
以上的碰撞都是完全弹性碰撞,如果想要变成有能量损失的碰撞,只需要这样:
首先新建两个记分板用来保存竖直方向和水平方向能量损失的系数

  1. scoreboard objectives add energy_lost_n dummy 碰撞法向能量损失
  2. scoreboard objectives add energy_lost_t dummy 碰撞切向能量损失

复制代码

实体初始化时新增修改一些常数:

  1. #设置常数
  2. scoreboard players set @e[tag=new_ball] energy_lost_n -95
  3. scoreboard players set @e[tag=new_ball] energy_lost_t 95
  4. scoreboard players set @e[tag=new_ball] const1 100
  5. scoreboard players set @e[tag=new_ball] const2 100000

复制代码

然后将碰撞后修改速度的命令

  1. execute at @e[tag=temp_mark,limit=1] unless block ~ ~ ~ air run scoreboard players operation @s vx *= @s const1

复制代码

改成执行一个函数

  1. execute at @e[tag=temp_mark,limit=1] unless block ~ ~ ~ air run function bounce:energy_lost/x

复制代码

energy_lost/x.mcfunction的内容如下:

  1. scoreboard players operation @s vx *= @s energy_lost_n
  2. scoreboard players operation @s vx /= @s const1
  3. scoreboard players operation @s vy *= @s energy_lost_t
  4. scoreboard players operation @s vy /= @s const1
  5. scoreboard players operation @s vz *= @s energy_lost_t
  6. scoreboard players operation @s vz /= @s const1

复制代码

修改三个方向的速度即可
注意到垂直于碰撞方向的常数是负数,意为速度反向

2. 停止运动
经过以上修改,实体一定会停在地面。
但是实际上,实体会在地面不断小幅度弹跳,一旦进入这个阶段,其实就是停在地面了。
这时候要判断出这种情况并让它不再弹跳

在energy_lost/y.mcfunction中加入以下代码:
表示如果竖直速度已经非常小了,就执行energy_lost/on_ground这个函数

  1. execute if score @s vy matches -999..999 run function bounce:energy_lost/on_ground

复制代码

energy_lost/on_ground.mcfunction的内容如下:
首先让实体的位置贴在地面上,再让竖直的速度和加速度为零。

  1. #修改py向下取整
  2. scoreboard players operation @s py /= @s const2
  3. scoreboard players operation @s py *= @s const2
  4. scoreboard players add @s py 1
  5. #设置速度为0
  6. scoreboard players set @s vy 0
  7. scoreboard players set @s ay 0

复制代码

经过以上的操作,基本的实体碰撞方块的物理引擎就写好啦~

联机侠

发表评论

您必须 登录 才能发表留言!