需求
上周开发U3D的时候,遇到一个需求
场景中,有一个玩家,为了训练玩家,需要让玩家绕着给定物体一周
看似简单的需求,细想一下却没那么容易
首先拆分下具体需求与环境:
- 玩家进入绕物体的初始位置不确定
- 玩家绕物体的方向不确定(顺时针or逆时针)
- 玩家绕物体的路径理论上是无数条
- 玩家具有完全控制(也就是说玩家可能先顺时针转半圈然后逆时针转半圈)
- 必须包容上述所有条件精确的对玩家进行判断
所以想要实现还是有那么一点点难度
起初的想法是获取玩家的路径,然后判断是否为类圆
想了一会果断pass掉
因为获取玩家路径实在太浪费性能了,上述需求中说过,玩家路径几乎是无数条,
当然指定路径除外,况且玩家行走如龟速或者路径复杂怎么办,
比如某个调皮的玩家走两步退一步,秧歌式走法就很头疼了
你也不能说明他没绕着走一圈。
思路
pass掉上边的方法后,想到了角度
我如果能判断他的角度就行了
事实是可以的,我获取到玩家位置和物体位置,
用反三角函数就可以获得他们相对于坐标轴的角度
得到了角度,我只要判断玩家和物体完成了360°的旋转就能判断是否绕物体一周
但是中间还有一些特殊细节:
- 360°判断不可能是连续的,不然是无穷大,所以必须有一个捕捉精度
- 由于0°和360°是重叠的,这里会有一些处理
- 正因为第二条,所以如果玩家初始角度在0到L或360-L到360之间(L为捕捉精度),他的判断会有所不同,所以这两个特殊范围得特殊处理
基本上就上面三条问题
我直接忽略掉y轴,只关心平面。
流程图:
WPS画流程图比Visio方便,但功能不强大,所以流程图稍显复杂
下面的C#代码有几处和流程图不一样,但不影响整个算法
U3D C#代码
private float currentAngle; // 当前触发角度
private float precisionAngle = 36.0f; // 触发精度
private int progressAngle = 0; // 完成进度
private int fullProgress; // 总进度
if (Mathf.Abs(progressAngle) >= fullProgress)
{
// 这里是判断是否完成进度,若完成执行下面函数
Node1Finish();
}
//定义当前角度和偏移角度
float nowAngle = GetAngle(), offsetAngle;
if (currentAngle < precisionAngle
&& nowAngle >= (360.0f - (2 * precisionAngle) + currentAngle))
{
offsetAngle = currentAngle - 360.0f + nowAngle;
}
else if (currentAngle > (360.0f - precisionAngle)
&& nowAngle < ((2 * precisionAngle) - 360.0f + currentAngle))
{
offsetAngle = 360.0f - currentAngle + nowAngle;
}
else
{
offsetAngle = nowAngle - currentAngle;
}
if (Mathf.Abs(offsetAngle) < precisionAngle
|| Mathf.Abs(offsetAngle) > 2 * precisionAngle)
{
return;
}
if (offsetAngle > 0.0f)
{
++progressAngle;
currentAngle += precisionAngle;
if (currentAngle >= 360.0f)
{
currentAngle -= 360.0f;
}
}
else
{
--progressAngle;
currentAngle -= precisionAngle;
if (currentAngle < 0)
{
currentAngle = 360.0f + currentAngle;
}
}
这里的代码要放在Update或Update间接调用的函数内,
Fixed也可,只要是实时执行的就行。
执行前,应该先初始化当前触发角度,它应该是人物要进行绕物体时的角度