- 船只向前行驶的驱动力
假设在水中没有摩擦阻力,船只有惯性,船只可以转弯,按下前进键时船只会在力的作用下使得自身的物理运动方向变化到自身的前方方向,从而向前行进。
上图中
V:船当前物理速度
V1,V2:V在两个方向上的分速度
Vn:船要达到的目标速度
假设
船的最大前进推进力为pushForce,船的最大速率只能是maxSpeed。
具体思想为:将V分解为V1和V2,利用V1,V2和Vn的关系,得出当前的船需要添加的恒力,下面总结出三种方案。
第一种:
制动力和推进力分离
1.先得出在船正方向的最大力Fn,这里可以抽象的认为这个力是一直添加在船的正方向的,就像火箭的推进引擎。
2.制动力,这是另外计算的,根据当前的船的速度,以及Fn,得出,然后和已经添加的Fn相加得出当前需要添加到恒力组件上的力(由于概念问题,这里Fn做了一次不必要的计算)
第二种:
驱动力即是制动力,也就是一次性计算
用最大正方向速度向量减去当前速度向量,得到和船的偏移向量刚好相反但是模相等的抵消向量。为了抵消偏移向量,让pushForce乘以 该抵消向量的单位向量,从而得到需要添加到恒力组件上的力
改良:为了防止抖动,求出需要的力向量后,让当前组件上的力渐变到求出的力。
第三种:
将当前速度方向分解为V1和V2,然后计算两个垂直方向上需要添加的力,最后又这两个力相加得到需要的力向量
可以看出,其实上面三种方案最后的结果都是相同的,只是为了功能上的需求,以及防止物理演算失真的原因,从而得到的三种不同的计算方案
方案2的unity代码如下
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player01Test : MonoBehaviour
{
public float rotateSpeed;//旋转速度
ConstantForce2D constantForce2D;//恒力组件
public float maxSpeed;//最大速度
Rigidbody2D rigid2D;
//推力参数
public float pushForce;//推力大小
//[HideInInspector]
//public float currentForce;//当前推力
// Start is called before the first frame update
void Start()
{
constantForce2D = GetComponent<ConstantForce2D>();
rigid2D = GetComponent<Rigidbody2D>();
}
private void FixedUpdate()
{
//旋转
//为利用扭矩力使得船往某个方向旋转
if (Input.GetKey(KeyCode.LeftArrow))//按下左方向键,船往顺时针旋转
{
//transform.Rotate(new Vector3(0, 0, rotateSpeed * Time.fixedDeltaTime));
if (rigid2D.angularVelocity >= 200)//如果旋转速度已经达到最大,那么撤销扭矩力
constantForce2D.torque = 0;
else //否则添加扭矩力
constantForce2D.torque = 600;
rigid2D.angularDrag = 0;
}
else if (Input.GetKey(KeyCode.RightArrow))//按下右方向键,船往逆时针旋转
{
//transform.Rotate(new Vector3(0, 0, -rotateSpeed * Time.fixedDeltaTime));
if (rigid2D.angularVelocity <= -200)
constantForce2D.torque = 0;
else
constantForce2D.torque = -600;
rigid2D.angularDrag = 0;
}
else
{
constantForce2D.torque = 0;
rigid2D.angularDrag = 50;
}
//推进
if (Input.GetKey(KeyCode.UpArrow))
{
Vector3 Vn= transform.up * maxSpeed;//船需要达到的目标速度
Vector3 V = rigid2D.velocity;//当前的速度(方向不一定是当前的船正方向)
Vector3 forceDir = (Vn - V).normalized;//获得力方向的单元向量
//添加力到船的恒力组件上
constantForce2D.force = forceDir * pushForce;
/*//这里为改进代码方式0,用来代替上面这段代码,即允许微小误差存在,主要是防止物理演算的失真抖动
if (forceDir.magnitude>Time.fixedDeltaTime*pushForce)
constantForce2D.force = forceDir * pushForce;
else
constantForce2D.force = Vector3.zero;
*/
}
else//如果没有添加任何力,那么让恒力组件为0
{
constantForce2D.force = Vector3.zero;
}
}
}
组件
- 船只转弯的驱动
为了方便理解这里进行类似例子引申:
以2d为例,一个小球在一个横杆上,小球唯一的移动方式是设置constant force组件的方向力,小球有惯性,没有摩擦力,如何让小球移动到杆上的任意一个点,并且在该点的速度刚好为0?
很显然,在物理上这个问题很难解决,因此解决的方案是如何让小球无限的接近目标点,并且速度为0。
问题的关键:
如何在力的 作用下到达目标点
如何在力的作用下变化到目标速度
(这里就可想象为到达目标点时,速度为0)
方案一:(太复杂,以后考虑)
设置减速半径,当运动到里目标点距离为减速半径的长度时进行减速操作。减速力为,减速力为偏移距离剧偏移半径的百分比乘以最大推力,方向为速度的反方向。偏移半径为,最大速度在最大力的反作用下减速到0,期间运动的距离刚好为减速半径(改进,在变力的情况下速度减到0,运动的距离为偏移半径)
方案二:(太简单太low)
进入减速半径后直接使用rigidbody上的迟滞力,将迟滞力放到最大
方案三:(明确简单,优先选择)
1.如果大于减速距离,那么用力驱使速度朝向目标方向移动,目标速度为最大速度
2.如果小于减速距离,那么用力驱使速度朝向目标方向,目标速度为迟滞速度(迟滞速度比最大速度小得多,便于操作调节运动的位置)
3.如果小于迟滞距离:如果当前速度大于迟滞速度,那么设置目标速度为0,此时如果速度小于迟滞速度,那么设置线性阻尼为最大(改进:如果速度方向与目标方向相反,把目标速度改为朝向目标点的迟滞速度);如果速度小于迟滞速度,那么设置线性阻尼为最大(改进:如果速度方向与目标方向相反,把目标速度改为朝向目标点的迟滞速度)。
从而使得角色最大程度的靠近目标点,并且速度为0.
//=============================================================================================================
//更新版,以unity2D飞行游戏为例
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//飞船的最大推进力,扭矩力,推进转弯速率比来自于引擎
public class Ship : MonoBehaviour
{
public float maxSpeed;
public float pushForce;
public float maxRotateSpeed;
public float torque;
public float rotateSpeedPercent;//转弯速率比,即当飞船不推进时最大转弯速率是maxRotateSpeed,如果正在开启引擎推进,那么速率是百分率乘以最大速率
public float currentRotatePercent;
public Transform weaponAnchor;//武器锚点,生成的武器成为锚点的子物体
//public Weapon weapon;
public Transform engineAnchor;
//public Engine engine;
Rigidbody2D rigid2D;
ConstantForce2D constantForce2D;
public Transform model;
// Start is called before the first frame update
void Start()
{
rigid2D = GetComponent<Rigidbody2D>();
constantForce2D = GetComponent<ConstantForce2D>();
}
private void Update()
{
//Vector3 rotate = new Vector3(0, transform.rotation.eulerAngles.z, 0);
int n = (int)(transform.rotation.eulerAngles.z / 90);
if (n == 0 || n == 2) model.transform.rotation = transform.rotation * Quaternion.Euler(0, transform.rotation.eulerAngles.z % 90*(n==0?-1:1), 0);
else model.transform.rotation = transform.rotation * Quaternion.Euler(0, (90-transform.rotation.eulerAngles.z % 90) * (n == 1 ? -1 : 1), 0);
Debug.Log(model.rotation.eulerAngles.z);
}
// Update is called once per frame
void FixedUpdate()
{
//引擎推进
if (Input.GetKey(KeyCode.UpArrow))
{
PushRigid();
//engine.EngineActive();
currentRotatePercent = 0.5f;// rotateSpeedPercent;//如果正在推进引擎,那么当前转弯速率比为默认转弯速率比
}
else
{
constantForce2D.force = Vector3.zero;
//engine.EngineUnActive();
currentRotatePercent = 1;//如果没有推进引擎,那么当前转弯速率比为1,即用最大转弯速率为目标速率旋转
}
//转弯
if (Input.GetKey(KeyCode.LeftArrow)) { rigid2D.angularDrag = 0; RotateRigid(1); }
else if (Input.GetKey(KeyCode.RightArrow)) { rigid2D.angularDrag = 0; RotateRigid(-1); }
else { RotateRigid(0); rigid2D.angularDrag = 20; }
//开火
//if (Input.GetKey(KeyCode.LeftControl) && weapon != null) weapon.Fire();
}
//施加推力
void PushRigid()
{
Vector3 velocityReal = transform.up * maxSpeed;
Vector3 velocityRigid = rigid2D.velocity;
Vector3 forceDir = (velocityReal - velocityRigid).normalized;
if ((velocityReal - velocityRigid).magnitude > Time.fixedDeltaTime * pushForce) {
constantForce2D.force = forceDir * pushForce;
}
else
constantForce2D.force = Vector3.zero;
}
//旋转,用最大速率与百分比相乘获得目标旋转速率
void RotateRigid(int dir)
{
RotateRigid(dir, currentRotatePercent * maxRotateSpeed);
}
//旋转,带有目标旋转速率
void RotateRigid(int dir,float rotateSpeed)//0停止旋转,1正转,-1反转,第二个参数为转弯速率
{
float tarAngularVelocity = dir * rotateSpeed;//最大目标速度current永远大于0
float dirAngular = tarAngularVelocity - rigid2D.angularVelocity;//获得速度差
float proportion = dirAngular / rotateSpeed;
proportion = Mathf.Clamp(proportion, -1, 1);
constantForce2D.torque = proportion * torque;
}
//这里是数据使得飞船部件的属性数据计算生效
/*
public void InitDatas()
{
if (engine)
{
pushForce = engine.pushForce;
torque = engine.torque;
rotateSpeedPercent = engine.rotatePercent;
}
}*/
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
}
}