使用unity开发游戏真是非常方便。研究飞行模拟也有一段时日,尝试过物理和数学模拟。从效果上来看,物理模拟较为真实一点。但是操作不好。数学模拟的话,虽然牺牲了飞行效果,操控是非常方便的。
所谓的数学模拟,就是位移模拟,通过定义起飞速度,加速度等,模拟飞机的飞行过程,包括转向,飞行坠落等。
来看一下飞机的飞行状态:在地面上,飞机达到起飞速度时,可以拉起飞机,否则,一直在地面上;在空中:当飞机低于起飞速度,下降。大于起飞速度则能保持在空中。飞机不可能倒着飞行的,所以,飞机的速度状态就有:起飞速度,正常速度,最大速度。通过输入改变当前速度,然后通过判断速度所对应的状态处理。
飞机的转向:飞机的转向,有y轴的转向,控制飞机的左右飞行。x的转向,控制飞机的升降。左右飞行时,飞机自身需要以z轴旋转,来模拟飞机转弯的效果。
飞机的失速:当飞机在空中的速度低于起飞速度时,飞机下落。当着地时,下落的速度为0.
整理一下思路:可以通过射线检测的方式获取距离地面的高度,判断飞机是在空中还是地面。通过输入值来调整currentSpeed,通过与飞机的offSpeed,normalSpeed,maxSpeed比较,来判断飞机所处的状态。通过四元数方法,调整飞机的角度,保持飞机的平衡和转向时的偏转效果。
主要使用到的方法:
移动:transform.Translate(vector, Space.World);
旋转:transform.Rotate(vector, Space.World);
转角:transform.rotation = Quaternion.RotateTowards(transform.rotation,rotation, speed);
高度:Physics.Raycast(ray, out hit,1<<0);height = hit.distance;
飞机的抽象控制方法:
public abstract void MoveLR(float speed);//左右移动 public abstract void RoteUD(float speed);//上下旋转 public abstract void MoveFB(float speed);//速度控制 public abstract void RoteLR(float speed);//左右旋转
public abstract Balance(Quaternion r, float speed);//转角
public abstract void Operational();//飞行状态
接下来就是飞行方法的实现:
public override void MoveFB(float speed)//速度控制 { IsRun = true;//主动控制打开 CurrentSpeed += speed*aircaft.Acc*Time.deltaTime;//加/减速 CurrentSpeed = Mathf.Clamp(CurrentSpeed, 0, aircaft.MaxSpeed);//控制速度在最大值范围内 } public override void MoveLR(float speed)//水平移动飞机,飞机的侧飞 { //左右移动 if ((IsSing) || IsOnGround) return;//如果在地面或者飞机处于特技状态 //IsLRB = false; Vector3 vector = body.right; vector.y = 0; Move(speed * vector * aircaft.MoveLRSpeed * Time.deltaTime * CurrentSpeed/aircaft.MoveFBSpeed);//侧飞 Balance(Quaternion.Euler(body.eulerAngles.x, body.eulerAngles.y, -aircaft.AxisLR * speed), aircaft.RoteLRSpeed * Time.deltaTime);/旋转机身,实现侧飞的效果 //print("MoveLR" + speed); } public override void Operational()//飞机的状态控制 { Altigraph();//测量高度 if (CurrentSpeed < aircaft.OffSpeed)//小于起飞速度 { //落下 if (!IsOnGround)//在空中 { Move(-Vector3.up * Time.deltaTime * 10 * (1 - CurrentSpeed / (aircaft.OffSpeed)));//失重下落 downSpeed = Mathf.Lerp(downSpeed, 0.1f, Time.deltaTime); //print("downSpeed" + downSpeed); RoteUD(downSpeed);//机身前倾实现下落效果 } if (!rigidbody) rigidbody = GetComponent<Rigidbody>(); rigidbody.useGravity = IsOnGround;//如果飞机在地面,启用重力,否则不使用重力 } else { downSpeed = 0; } Balance();//保持飞机的平衡 if (!IsRun) {//保持飞机以正常速度飞行 if (CurrentSpeed > aircaft.MoveFBSpeed) CurrentSpeed = Mathf.Lerp(CurrentSpeed, aircaft.MoveFBSpeed,Time.deltaTime); else if (CurrentSpeed > aircaft.OffSpeed && !IsOnGround) CurrentSpeed =Random.Range(aircaft.OffSpeed,aircaft.MoveFBSpeed); else if (IsOnGround && CurrentSpeed < aircaft.OffSpeed) { CurrentSpeed = Mathf.Lerp(CurrentSpeed,0,Time.deltaTime); } } Move(body.forward * CurrentSpeed * Time.deltaTime);//调用飞行方法 } public override void RoteLR(float speed)//飞机的转向 { //左右旋转 if ((IsSing) || IsOnGround) return; IsLRB = false; Rote(speed * Vector3.up * aircaft.RoteLRSpeed * Time.deltaTime * CurrentSpeed / aircaft.MoveFBSpeed); Balance(Quaternion.Euler(body.eulerAngles.x, body.eulerAngles.y,-aircaft.AxisLR * speed), aircaft.RoteLRSpeed * Time.deltaTime); //print("RoteLR" + speed); } public override void RoteUD(float speed)//飞机的转向 { //上下旋转 //速度和角度 if ((IsSing) || IsOnGround && CurrentSpeed < aircaft.MoveFBSpeed / 3.6f) return; if (CurrentSpeed < aircaft.MoveFBSpeed / 3.6f && speed<0) return; IsFBB = false; Balance(Quaternion.Euler(aircaft.AxisFB * speed, body.eulerAngles.y, body.eulerAngles.z), aircaft.RoteFBSpeed * Time.deltaTime * CurrentSpeed / aircaft.MoveFBSpeed); //print("RoteUD" + speed); } public override void Balance()//飞机的平衡方法,当无输入事件时,飞机自动平衡 { if (IsSing) return; if (IsLRB)//z轴平衡(左右) { Balance(Quaternion.Euler(body.eulerAngles.x, body.eulerAngles.y, 0), aircaft.RoteLRSpeed * Time.deltaTime/1.2f ); } if (IsFBB)//x轴平衡(上下) { Balance(Quaternion.Euler(0, body.eulerAngles.y, body.eulerAngles.z), aircaft.RoteFBSpeed * Time.deltaTime /1.3f); } IsLRB = true;//自动平衡打开 IsFBB = true;//自动平衡打开 }
都是非常简单的代码,关键是如何使用,以及逻辑的处理。
为了增加飞机飞行的效果,我还设计了飞机的特技飞行:90度转角的左右飞行,以及180度翻转飞行。当然也是使用了基础的公式完成的。左右90度特技飞行:飞机侧身90度,并大角度的转角转向飞机的左或右方。获取相对于飞机在世界坐标的左/右方向V,然后通过判断当前方向与V的角度,来判定是否完成特技飞行。180度转角飞行:飞机绕自身x轴180度旋转,然后恢复平衡。
因为特技飞行的完成需要一段时间,而这段时间是玩家不需要控制,系统完成后进入正常状态的,所以用到需要协程。
IEnumerator SLR(float speed) { //90度转角飞行 speed = (speed > 0 ? 1 : -1); Vector3 aim = body.right * (speed); aim.y = 0; while(Vector3.Dot(aim.normalized,body.forward.normalized)<0.99f){ Rote(speed * Vector3.up * aircaft.RoteLRSpeed * Time.deltaTime); Balance(Quaternion.Euler(body.eulerAngles.x, body.eulerAngles.y, -85 * (speed )), aircaft.RoteLRSpeed * Time.deltaTime*3.8f); Balance(Quaternion.Euler(0, body.eulerAngles.y, body.eulerAngles.z), aircaft.RoteFBSpeed * Time.deltaTime *1.8f); yield return new WaitForFixedUpdate(); } while ((body.eulerAngles.z > 15) && (body.eulerAngles.z < 180) || (body.eulerAngles.z < 345) && (body.eulerAngles.z > 270)) { Balance(Quaternion.Euler(0, body.eulerAngles.y, body.eulerAngles.z), aircaft.RoteFBSpeed * Time.deltaTime); Balance(Quaternion.Euler(body.eulerAngles.x, body.eulerAngles.y, 0), aircaft.RoteLRSpeed * Time.deltaTime * 3); yield return new WaitForFixedUpdate(); } IsSing = false; } public override void StuntUD(float axis) { if ((IsSing) || IsOnGround && CurrentSpeed < aircaft.MoveFBSpeed / 3.6f) return; if (!IsSing) { IsSing = true; StartCoroutine(SUD(axis)); } } IEnumerator SUD(float speed) { //180度翻转 speed = (speed > 0 ? 1 : -1); Vector3 aim = -body.forward ; aim.y = 0; while (Vector3.Dot(aim.normalized, body.forward.normalized) < 0.8f)//飞机翻转 { Vector3 v = body.right; v.y= 0; Rote(body.right * Time.deltaTime * -90 * speed); Move(-Vector3.up * speed * Time.deltaTime * 10 * (CurrentSpeed / (aircaft.OffSpeed))); //body.Rotate(Vector3.right * Time.deltaTime * -90,Space.Self); //Balance(Quaternion.Euler(body.eulerAngles.x, body.eulerAngles.y, 0), aircaft.RoteLRSpeed * Time.deltaTime*5); yield return new WaitForFixedUpdate(); } while ((body.eulerAngles.z > 15) && (body.eulerAngles.z < 180) || (body.eulerAngles.z < 345) && (body.eulerAngles.z >270))//翻转后飞机完成平衡 { Balance(Quaternion.Euler(0, body.eulerAngles.y, body.eulerAngles.z), aircaft.RoteFBSpeed * Time.deltaTime ); Balance(Quaternion.Euler(body.eulerAngles.x, body.eulerAngles.y, 0), aircaft.RoteLRSpeed * Time.deltaTime*3); yield return new WaitForFixedUpdate(); } IsSing = false; }
以上是飞机的飞行模拟思路以及基本算法实现。玩家操作的代码就是输入检测之类的,然后再调用一下飞行接口就行了,我就不提供了。
接下来的问题就是AI飞行了,由电脑逻辑控制的AI飞行。如上图,是由计算机控制的飞行。这个AI就一个核心方法,就是飞到目标点。要实现这个功能也很简单,就是计算一下目标点的距离,角度等。然后根据距离以及当前状态控制加减速度,通过角度控制转向。主要使用到Vector.Dot(v1,v2);,然后就是各种情况下的判断和飞行方法的调用。例如当目标在飞机的后方时,可以调用180度翻转,来锁定目标。
好,我们简单的分析一下如何去写这个AI,获取目标Point,计算距离差,高度差,角度差。通过高度差,控制飞机的升降,距离差控制速度,角度差控制转向。
嗯,下面就是实现的代码。
public void MoveToPoint(Vector3 point, float stopDistance) { Vector3 hPoint = point; hPoint.y = flight.body.position.y; hDistence = Vector3.Distance(hPoint, flight.body.position);//水平距离 distence = Vector3.Distance(point, flight.body.position);//距离 dHeight = point.y - flight.body.position.y;//高度差 vector = (point - flight.body.position).normalized; dUDAgle = Vector3.Dot(vector, flight.body.TransformDirection(Vector3.up).normalized); //获取目标点与当前位置的距离,高度差,判断目标相对于当前位置的前/后,左/右 dFBAgle = Vector3.Dot(vector, (flight.body.TransformDirection(Vector3.forward)).normalized);//判断目标点相对于当前位置的前后 //print(forward); dLRAgle = Vector3.Dot(vector, (flight.body.TransformDirection(Vector3.right)).normalized);//判断目标点相对于当前位置的左右 //获取这个信息后,接下来就是移动到目标点 //先判断什么?正前方到目标点的角度,如果这个角度在一定范围内,则可以进行移动 // float axisFB = 0; if (dFBAgle > objectAI.Precision) { //方向偏移在正常范围内 //根据距离判断是否加/减速度 // if (distence > objectAI.RunDistance * stopDistance || (flight.CurrentSpeed < flight.aircaft.OffSpeed && !IsLand)) { axisFB = Random.Range(0f, 1f); } else if (distence > objectAI.FreeDistance * stopDistance && !IsLand) { axisFB = Random.Range(-0.5f, 0.5f); } else if (distence < objectAI.SlowDistance * stopDistance && IsLand) { axisFB = Random.Range(-1f, 0f); } } else { //方向偏移不在正常范围 //判断距离是否达到停止距离 //判断高度是否在误差之内 //调整左右和前后,来 if (distence < stopDistance) { //到达目标点附近,但是角度偏差过大,判断目标的左右,进行旋转 axisFB = Random.Range(0.5f, 1f); flight.RoteLR(dLRAgle); } else { //if (Mathf.Abs(dLRAgle) < 0.05f) dLRAgle = 0; if (dFBAgle < -0.75f) { flight.StuntUD(flight.Height>100?-1:1); } else if (dFBAgle < -0.25f) { flight.StuntLR(dLRAgle); } flight.RoteLR(dLRAgle); if (!IsLand) { if (Mathf.Abs(dHeight) > 5) { flight.RoteUD(-dUDAgle); } if(flight.IsOnGround){ flight.RoteUD(-1); } } if(flight.CurrentSpeed<flight.aircaft.MoveFBSpeed) axisFB = Random.Range(0.5f, 1f); } } //flight.RoteLR(dLRAgle); if (Mathf.Abs(axisFB) > 0.01f) flight.MoveFB(axisFB); }
好了,今天的飞行模拟就介绍到这了。预告一下,下一篇文章阿亮将探讨导弹算法的实现(预计目标点位置,而非通过获取目标的当前位置)