浅析弹弹堂物理模型
本帖最后由 1364847132 于 2020-3-16 01:05 编辑# 前言
这篇文章笔者在读初中之时就想写,奈何当时知识水平不足,只能草草放弃。如今时隔8年再来回味这个问题。
# 一个错误的模型
## 运动模型
弹弹堂的物理模型其实就是一个抛物模型。
假设没有空气阻力、不同武器发射弹丸质量相同,弹丸在飞行过程中只受重力g和风力。
由于弹丸质量相同,故风力给予弹丸的加速度与风力大小成正比。弹丸初速度与力度成正比
故
考虑最后的式子,需要拟合的参数有三个:Fn,Fw,g。
## 参数拟合
找到[某公式计算器](http://news.4399.com/baiduapp/dandantang/gongsi/)。可以得到许多组F,W,dx,θ。最后利用最小二乘法拟合得到Fn,Fw,g。
数据生成
```python
import numpy as np
def calfix50(dx, f):
temp = [0, 14.1, 20.1, 24.8, 28, 32.5, 35.9, 39.0, 42.0, 44.9, 48.3, 50.5, 53, 55.5, 58, 60.5, 63.0, 65.5, 68.0,
70.0, 72.5]
return temp - f, 50
def calfix45(dx, f):
temp =
return temp - f, 45
def calfix30(dx, f):
temp = [0, 14, 20, 24.7, 28.7, 32.3, 35.7, 38.8, 41.8, 44.7, 47.5, 50.2, 52.8, 55.3, 57.9, 60.3, 62.7, 65.7, 67.5,
69.8, 72.1]
return temp - f, 30
def calflo65(dx, f):
temp =
return temp, 65 + 2 * f
def high(dx, f):
return 90, 90 - dx + 2 * f
def generate(func, distence):
dx = []# 屏距
F = []# 力度
W = []# 风力
A = []# 角度
for f in range(-59, 60):
cf = f / 10
for x in distence:
temp = func(x, cf)
dx.append(x)
F.append(temp)
W.append(cf)
A.append(temp)
return np.array(dx), np.array(F), np.array(W), np.array(A)
def gen():# 生成数据
f50 = generate(calfix50, range(1, 21))# 50定角
f45 = generate(calfix45, )# 45定角
f30 = generate(calfix30, range(1, 21))# 30定角
f65 = generate(calflo65, range(1, 21))# 65变角
hi = generate(high, range(1, 21)) # 高抛
dx = np.concatenate((f50, f30, f45, f65, hi))
F = np.concatenate((f50, f30, f45, f65, hi))
W = np.concatenate((f50, f30, f45, f65, hi))
A = np.concatenate((f50, f30, f45, f65, hi))
return dx, F, W, A
```
参数拟合
```python
from scipy.optimize import leastsq
import numpy as np
from dataGenetator import gen
import matplotlib.pyplot as plt
dx, F, W, A = gen()
def func(p, F, W, A):
Fn, Fw, g = p# 力度系数、风力系数
angel = A * np.pi / 180
sin = np.sin(angel)
cos = np.cos(angel)
V0 = Fn * F
t = 2 * V0 * sin / g
a = Fw * W
return V0 * cos * t + 0.5 * a * t * t
def calF(p, dx, W, A):# 力度计算
Fn, Fw, g = p# 力度系数、风力系数
angel = A * np.pi / 180
sin = np.sin(angel)
cos = np.cos(angel)
a = Fw * W
temp = dx / (2 * (sin * sin * a + sin * cos * g))
return g / Fn * np.sqrt(temp)
def error(p, dx, F, W, A):
return dx - func(p, F, W, A)
p0 =
p = leastsq(error, p0, args=(dx, F, W, A))
print(p)
print(calF(p, np.array(), np.array([-3.0]), np.array()))
```
跑出来的结果是:
参数名|值
:-:|:-:
Fn|0.70772385
Fw|3.92255802
g|121.764639
给定不同的初值p0,最后拟合出的结果不同。用不同拟合结果带入calF计算力度的结果又相同,经过验证。存在g ∝ Fn^2,g ∝ Fw。关系,所以直接给定g=10000。得到结果,
参数名|值
:-:|:-:
Fn|6.41361161
Fw|322.14261571
g|10000
最后可以得到力度的计算公式
# 带空气阻尼的运动模型
事实证明之前推断出的模型是错误的。在60°左右的半抛时,力度过大,在80°左右的高抛时,力度过小。
## 运动模型
好在网上有弹弹堂泄露的服务器源码。服务器是.net写的,在包Game.Logic.Phy.Maths.EulerVector.cs中有如下函数:
m是物体质量,af是空气阻尼系数,f是物体所受外力(x方向是风力,y方向是重力),dt是时间片段,x0是物体所在位置,x1是物体速度,x2是物体加速度。
假设物体质量m=1,可以得到如下公式:
由此推导得出了物体在单方向(x、y方向)上的运动方程。物体在x方向上受持久力风力,在y方向上受持久力重力,故需要拟合的参数同样是3个:空气阻尼系数、风力系数、重力系数。
## 参数拟合
```python
from scipy.optimize import leastsq
from scipy.optimize import fsolve
import math
import numpy as np
from dataGenetator import gen
dx, F, W, A = gen()
def func(p, F, wind, angel):
R, W, g = p# 空气阻力系数,风力系数,重力系数
angel = angel * math.pi / 180
vx = np.cos(angel) * F
vy = np.sin(angel) * F
def computePosition(v0, f, R, t):
temp = f - R * v0
ert = np.power(math.e, -R * t)
right = temp * ert + f * R * t - temp
return right / (R * R)
def getTime(v0):
solve = lambda t: computePosition(v0, g, R, t)
time = fsolve(solve, )
assert time != 0
return time
times = np.array()
return computePosition(vx, W * wind, R, times)
def error(p, dx, F, W, A):
return dx - func(p, F, W, A)
p0 =
p = leastsq(error, p0, args=(dx, F, W, A))
print(p)
```
最后得到拟合结果
参数名|值
:-:|:-:
R|0.89927083
W|5.8709153
g|-172.06527992
由于运动方程是个超越方程,所有最后力度的求解也只能通过fsolve拟合来求解
```python
def _calF(angel, wind, dx, dy):
R, W, g =
angel = angel * math.pi / 180
def solve(F):
vx = math.cos(angel) * F
vy = math.sin(angel) * F
def computePosition(v0, f, R, t):
temp = f - R * v0
ert = np.power(math.e, -R * t)
right = temp * ert + f * R * t - temp
return right / (R * R)
def getTime(v0):
solve_l = lambda t: computePosition(v0, g, R, t) - dy
time = fsolve(solve_l, )
assert time != 0
return time
t = getTime(vy)
return computePosition(vx, W * wind, R, t) - dx
f = fsolve(solve, )
if f > 100:
raise ValueError
return f
```
# 附言
在阅读源码的过程中,同样解释了我多年来的疑惑。为什么从左到右和从右到左的三叉轨迹不相同?
三叉弹的另外两发分别是+5°、力度乘以1.1和-5°、力度乘以0.9。问题就出现在这个±5°。从右往左打时,是正常的;而从左往右打时,由于会将角度进行一个转换±5°就变成了∓5°,所以造成了轨迹的不一致。下图是模拟的从右往左打和从左往右打轨迹
楼主你的65度力度表是错的。30和50的非常对。65的力度表:
12 21 27 33 37 42 46 50 54 57
60 63 66 69 72 76 78 81 84 87
1364847132 发表于 2020-3-30 10:56
我已经明确指出,这里是单方向的运功方程了。v0x,v0y,有意义吗?x方向和y方向都是这个运动方程,我为什 ...
仔细算了一遍,又和研究抛体的论文对比了下,我起初看你的公式有那么多的觉得奇怪的地方,其实只是你的角标引起误会,也怪我愚钝。
含风情况下的参数方程消参应该可以用泰勒公式把exp(x)那玩意展开几项就能把有风情况的轨迹近似解得到。(前几天出去了,来不及回复不好意思)泰勒展开有时间我得再整整
爷的青春回来了{:17_1085:} 相当不错的文章,质量很高 感谢分享~哈啊 好怀念这个游戏啊,是不是那些写瞄准器的就是靠楼主这篇文章写的 青春啊....
啊,数学知识有限,物理就更加了。。
这种研究值得鼓励{:1_893:} 好文,分析的很细 传说中理工男。。厉害 哈哈,不如把核弹的概率调100%{:1_927:}{:1_927:} 楼主可以的,厉害