其实理论上这个AI应该不难,
- 我们有击球点,假设一个落点,中间是抛物线,
- 再加入一个第三点就能解出抛物线方程。因为一定要经过球网,所以假设球网上方有一个点。
- 有了轨迹方程,就可以计算球的出发速度,
- 这个速度是碰撞后的矢量,碰撞前的速度矢量我们也有,
- 那就是如何控制球拍发生碰撞才能让球按照计划移动。
- 根据(可能是)高中物理,完全弹性碰撞的速度计算,m1*v1+m2*v2=m1*v1'+m2*v2',可以计算出碰撞前的球拍速度。
- 参考https://baike.baidu.com/item/%E5%AE%8C%E5%85%A8%E5%BC%B9%E6%80%A7%E7%A2%B0%E6%92%9E/1306748?fr=aladdin
- 有了碰撞前的球拍速度,下面分析碰撞前的球拍角度。球和平面的碰撞应该和光的反射类似,我们有入射方向和出射方向,可以求出法线方向,然后得到平面方向。
- 最后按照计算出的速度和角度给球拍赋值,前面计算出的速度是标量,方向就沿着法线方向移动吧。(ps:大学物理挂科,大学物理实验挂科),补充:又查了一些资料,据说速度按矢量计算就可以得到矢量速度,这样就应该按照速度方向调整球拍方向。
- 编码测试效果两边都用AI,结果发现全部白费,计划是每次接球落点完全一致,实际神仙打架,虽然大多数球被接到了,但是落点完全没有规律。
- 要么继续检查上述方法中的错误,要么改变策略,用机器学习训练个AI吧。
下面是数学和物理学的公式。考虑侧视图,二维视角,z轴水平,y轴竖直,球桌中点是(0,0)点
抛物线方程,y=a*z*z+b*z+c
球网上方一点(0,0.25),c=0.25
落地点(z1,0),击球点(z2,y2)
float b = ((y2 - c) * z1 * z1 - (y1 - c) * z2 * z2) / (z1 * z2 * (z1 - z2));
float a = ((y1 - c) * z2 - (y2 - c) * z1) / (z1 * z2 * (z1 - z2));
以上,得到抛物线方程,下面计算发球速度
先找到抛物线最高点,可以算出最高点到击球点高度h1,时间t1,最高点到落地点高度h2,时间t2
float z3 = -b / (2 * a);
// float y3 = (4*a*c-b*b)/(4*a);
float y3 = a * z3 * z3 + b * z3 + c;
float h1 = y3 - y2;
float h2 = y3 - 0;
float g = 9.8f;
float t1 = Mathf.Sqrt(2 * h1 / g);
float t2 = Mathf.Sqrt(2 * h2 / g);
float vz = (z1 - z2) / (t2 + t1);
float vy = g * t1;
float vx = (x1 - x2) / (t2 + t1);
x轴只是匀速直线运动,至此三轴发球速度得到。ps:unity有模拟空气阻力,影响不大,本文忽略掉
private Vector3 calcLine(float x1,float y1,float z1,float x2,float y2,float z2,float c)
{
//c=0.25,x1=-1.37,y1=0,x2,y2
//b=((y2-c)*x1*x1-(y1-c)*x2*x2)/(x1*x2*(x1-x2))
//a=((y1-c)*x2-(y2-c)*x1)/(x1*x2*(x1-x2)
Vector3 ret;
float b = ((y2 - c) * z1 * z1 - (y1 - c) * z2 * z2) / (z1 * z2 * (z1 - z2));
float a = ((y1 - c) * z2 - (y2 - c) * z1) / (z1 * z2 * (z1 - z2));
float k = a * z2;
if (k < 0)//先上升再下降
{
float z3 = -b / (2 * a);
// float y3 = (4*a*c-b*b)/(4*a);
float y3 = a * z3 * z3 + b * z3 + c;
float h1 = y3 - y2;
float h2 = y3 - 0;
float g = 9.8f;
float t1 = Mathf.Sqrt(2 * h1 / g);
float t2 = Mathf.Sqrt(2 * h2 / g);
float vx = (x1 - x2) / (t2 + t1);
float vz = (z1 - z2) / (t2 + t1);
float vy = g * t1;
ret = new Vector3(vx, vy, vz);
top.transform.position = new Vector3(0, y3, z3);
}
else//直接下降
{
float h2 = y2;
float g = 9.8f;
float t1 = 0;
float t2 = Mathf.Sqrt(2 * h2 / g);
float vx = (x1 - x2) / (t2 + t1);
float vz = (z1 - z2) / (t2 + t1);
float vy = 0;
ret = new Vector3(vx, vy, vz);
}
for (int i = 0; i < 100; i++)
{
float z = i * (z1 - z2) / 100 + z2;
float y = a * z * z + b * z + c;
float x = i * (x1 - x2) / 100 + x2;
line.SetPosition(i, new Vector3(x, y, z));
}
return ret;
}
下面是球拍速度计算方法
能量守恒方程:0.5*m1*v1*v1+0.5*m2*v2*v2=0.5*m1*v1'+0.5*m2*v2'*v2'
动量守恒方程:m1*v1+m2*v2=m1*v1'+m2*v2'
求解之后:v2'=(2*m1*v1+(m2-m1)*v2)/(m1+m2)
v2'是碰撞后球的速度,v1'是碰撞后球拍的速度,v1是碰撞前球拍的速度,v2是碰撞前球的速度。
现在已知v2,v2',求v1,对上面公式转换就好
v1=((m1+m2)*v2'-(m2-m1)*v2)/(2*m1)
把所有的速度当作矢量计算就可以得到矢量的v1,然后用这个v1控制AI球拍就可以了。
下面是球拍方向
myracket.GetComponent<Rigidbody>().MoveRotation(Quaternion.LookRotation(v1, Vector3.up));
myracket.GetComponent<Rigidbody>().velocity = v1;
最后,封装成函数,方便两边调用
AI3(racket, g1, aim2.transform.position, zero.transform.position,1);
AI3(racket2, g1, aim3.transform.position, zero2.transform.position, -1);
public void AI3(GameObject myracket, GameObject ball, Vector3 aim, Vector3 zero,int direction)
{
Vector3 v = ball.transform.position;
if (v.z * direction > zero.z* direction - 0.5 )
{
if (v.z * direction < zero.z * direction - 0.2)
myracket.GetComponent<Rigidbody>().MovePosition(new Vector3(v.x, v.y, zero.z));
//Vector3 inV = racket.transform.position - g1.transform.position;
//Vector3 outV = racket.transform.position - aim2.transform.position;
//Vector3 faxian = Vector3.Lerp(inV, outV, 0.5f);
//Debug.Log(faxian);
//racket.GetComponent<Rigidbody>().MoveRotation(Quaternion.LookRotation(new Vector3(faxian.x, 0, faxian.z), Vector3.up));
float x2 = myracket.transform.position.x;
float y2 = myracket.transform.position.y;
float z2 = myracket.transform.position.z;
float x1 = aim.x;
float y1 = aim.y;
float z1 = aim.z;
//float c = UnityEngine.Random.Range(2.0f,5.0f);
float c = height;
Vector3 v22 = calcLine(x1, y1, z1, x2, y2, z2, c);
Vector3 v2 = g1.GetComponent<Rigidbody>().velocity;
Vector3 v1 = calcSpeed(0.2f, 0.0025f, v2, v22);
//Vector3 faxian = (-v2+ v22)*0.5f;
//Debug.Log(""+ v2+v22+faxian);
myracket.GetComponent<Rigidbody>().MoveRotation(Quaternion.LookRotation(v1, Vector3.up));
myracket.GetComponent<Rigidbody>().velocity = v1;
}
else
{
myracket.GetComponent<Rigidbody>().MovePosition(new Vector3(v.x, v.y, zero.z));
myracket.GetComponent<Rigidbody>().MoveRotation(Quaternion.LookRotation(Vector3.back, Vector3.up));
myracket.GetComponent<Rigidbody>().velocity =Vector3.zero;
}
}
至少可以达到神仙打架的效果,数学方面以我的能力是够呛了,后面用机器学习试试吧