1.13的预览版可以通过记分板的数据修改实体的NBT
我立刻想到了可以由此制作一个任意实体的碰撞物理引擎~
此处的都要用 NoGravity:1 屏蔽原版的重力 (甚至让Motion时刻为零 不过没有必要)效果图:
附件的datapack就是这个效果!
先执行function bounce:_init
再用命令方块超频执行function bounce:main
右键萝卜钓竿就可以发射弹弹苹果了!
一、运动分析
1. 首先我们来分析一下一个实体的运动过程
我们知道,速度是位置的变化率,加速度是速度的变化率
即
- v=dx/dt
- a=dv/dt
复制代码
此时的dt在mc里就是1tick
所以,位置的变化量x等于速度乘以1tick,速度的变化量等于加速度乘以1tick
此时速度的单位是block/tick,加速度单位为block/tick^2
2. 接下来分析一下实体与方块发生碰撞的情况
先来看平面中的情况
一个小球碰撞平面可以这样来看:
入射 出射 \ / \ / \ / \/ ------------- 碰撞平面 |
对入射时速度进行分解: -----> Vx | | \|/ Vy |
对出射时速度进行分解: /|\ -Vy | | -----> Vx |
可见发生反弹时垂直于反弹面的速度发生了反向。
将这个结论推广到三维空间中时也是成立的。
3. 再看两个实体碰撞的情况:
假设两个实体都是球体(简化模型)
把速度分解到碰撞方向上
在碰撞方向上,满足动量定理。即
- m1*V1x+m2*V2x=m1*V1x'+m2*V2x'
复制代码
且两小球碰撞满足能量守恒,即
- 1/2 m1*V1^2+1/2 m2*V2^2=1/2 m1*V1'^2+1/2 m2*V2'^2
复制代码
联立即可解得V1x'、V2x'
二、下面就把上面的分析放到MC里来实现吧!
1. 准备工作
建立以下九个记分板
- scoreboard objectives add px dummy x坐标
- scoreboard objectives add py dummy y坐标
- scoreboard objectives add pz dummy z坐标
- scoreboard objectives add vx dummy x速度
- scoreboard objectives add vy dummy y速度
- scoreboard objectives add vz dummy z速度
- scoreboard objectives add ax dummy x加速度
- scoreboard objectives add ay dummy y加速度
- scoreboard objectives add az dummy z加速度
复制代码
用记分板记录实体的位置、速度和加速度
建立两个常数记分板和临时记分板来备用
- scoreboard objectives add temp dummy 临时1
- scoreboard objectives add temp2 dummy 临时2
- scoreboard objectives add const1 dummy 常数1
- scoreboard objectives add const2 dummy 常数2
复制代码
2. 对实体的初始化
首先生成一个实体
- summon minecraft:pig 8 10 8 {Tags:["new_ball"],NoGravity:1,NoAI:1,Health:1}
复制代码
再给它赋予一个速度
- scoreboard players set @e[tag=new_ball] vx 5000
- scoreboard players set @e[tag=new_ball] vy 5000
- scoreboard players set @e[tag=new_ball] vz 5000
复制代码
设置加速度。这里只有y方向有加速度,也就是重力加速度
最后如果想改变重力,只要修改这几个记分板就可以了!
- scoreboard players set @e[tag=new_ball] ax 0
- scoreboard players set @e[tag=new_ball] ay -200
- scoreboard players set @e[tag=new_ball] az 0
复制代码
设置一些常数,后面会用到
- scoreboard players set @e[tag=new_ball] const1 -1
复制代码
3. 开始运动!
建立一个函数名叫fly,其执行者就是要移动的实体
以下是fly.mcfunction的内容:
首先获取实体位置
- execute as @s store result score @s px run data get entity @s Pos[0] 100000
- execute as @s store result score @s py run data get entity @s Pos[1] 100000
- execute as @s store result score @s pz run data get entity @s Pos[2] 100000
复制代码
接着使速度变化量也就是加速度加到速度上
- scoreboard players operation @s vx += @s ax
- scoreboard players operation @s vy += @s ay
- scoreboard players operation @s vz += @s az
复制代码
接着,如果目前的位置加**置变化量也就是下一tick的位置有一个方块,那么我们就将速度反向,达到碰撞反弹的效果
把检测分为xyz三个方向来检测,这里用到了很多1.13的特性
首先生成一个实体来帮助检测
- summon minecraft:area_effect_cloud ~ ~ ~ {Tags:["temp_mark"]}
复制代码
接着移动实体的位置到下一tick的位置
- #判断有无方块-temp=px
- execute as @s store result score @s temp run scoreboard players get @s px
- #判断有无方块-temp加上vx
- scoreboard players operation @s temp += @s vx
- #判断有无方块-x移动临时实体
- execute as @s store result entity @e[tag=temp_mark,limit=1] Pos[0] double 0.00001 run scoreboard players get @s temp
复制代码
如果临时实体的位置处有方块,那么就反向vx
- execute at @e[tag=temp_mark,limit=1] unless block ~ ~ ~ air run scoreboard players operation @s vx *= @s const1
复制代码
此处const1的值为-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的循环就完成了!
- #移动
- #p加上v
- scoreboard players operation @s px += @s vx
- scoreboard players operation @s py += @s vy
- scoreboard players operation @s pz += @s vz
- #p存入nbt
- execute as @s store result entity @s Pos[0] double 0.00001 run scoreboard players get @s px
- execute as @s store result entity @s Pos[1] double 0.00001 run scoreboard players get @s py
- execute as @s store result entity @s Pos[2] double 0.00001 run scoreboard players get @s pz
- #删除临时实体
- kill @e[tag=temp_mark]
复制代码
三、继续优化
1. 能量损失
以上的碰撞都是完全弹性碰撞,如果想要变成有能量损失的碰撞,只需要这样:
首先新建两个记分板用来保存竖直方向和水平方向能量损失的系数
- scoreboard objectives add energy_lost_n dummy 碰撞法向能量损失
- scoreboard objectives add energy_lost_t dummy 碰撞切向能量损失
复制代码
实体初始化时新增修改一些常数:
- #设置常数
- scoreboard players set @e[tag=new_ball] energy_lost_n -95
- scoreboard players set @e[tag=new_ball] energy_lost_t 95
- scoreboard players set @e[tag=new_ball] const1 100
- scoreboard players set @e[tag=new_ball] const2 100000
复制代码
然后将碰撞后修改速度的命令
- execute at @e[tag=temp_mark,limit=1] unless block ~ ~ ~ air run scoreboard players operation @s vx *= @s const1
复制代码
改成执行一个函数
- execute at @e[tag=temp_mark,limit=1] unless block ~ ~ ~ air run function bounce:energy_lost/x
复制代码
energy_lost/x.mcfunction的内容如下:
- scoreboard players operation @s vx *= @s energy_lost_n
- scoreboard players operation @s vx /= @s const1
- scoreboard players operation @s vy *= @s energy_lost_t
- scoreboard players operation @s vy /= @s const1
- scoreboard players operation @s vz *= @s energy_lost_t
- scoreboard players operation @s vz /= @s const1
复制代码
修改三个方向的速度即可
注意到垂直于碰撞方向的常数是负数,意为速度反向
2. 停止运动
经过以上修改,实体一定会停在地面。
但是实际上,实体会在地面不断小幅度弹跳,一旦进入这个阶段,其实就是停在地面了。
这时候要判断出这种情况并让它不再弹跳
在energy_lost/y.mcfunction中加入以下代码:
表示如果竖直速度已经非常小了,就执行energy_lost/on_ground这个函数
- execute if score @s vy matches -999..999 run function bounce:energy_lost/on_ground
复制代码
energy_lost/on_ground.mcfunction的内容如下:
首先让实体的位置贴在地面上,再让竖直的速度和加速度为零。
- #修改py向下取整
- scoreboard players operation @s py /= @s const2
- scoreboard players operation @s py *= @s const2
- scoreboard players add @s py 1
- #设置速度为0
- scoreboard players set @s vy 0
- scoreboard players set @s ay 0
复制代码
经过以上的操作,基本的实体碰撞方块的物理引擎就写好啦~