Fake physic – obstacle local avoidance

移動系統更新為假物理後, 衍生出很多違反物理的Bug, 例如

“可以無視牆壁直接跑進牆”.

而且 Unity3D 的 Raycast, 如果在原始點的時候就已經和物件重疊, 該次的 Raycast result 中將會被忽略
(已確認是官方的設計,並不會改)

所以如何解決掉在物件前面嘗試進行移動的路線修正方式, 就成了最迫切的課題.

為了解決這問題, 我的方案是減小 capsule 的 radius ,預留一個 padding 值, 然後在產生 collision 結果後重新加回去.
避免在下一次物理更新時發生上面 overlap 的問題.

更新進行上限的 2 次 Raycast 的組合

  • 前方向檢查
    • 前方不通行, 以牆身normal為準
      取樣牆面的左/右, 並進行碰撞檢查
  • 如以上的修改沒有進行, 檢查該區域有否重疊

黃線為前方障礙(大概只能在第一次接觸時看到)
紅線是避障AI的規避路線.

implement the feature for my fake physic reaction dealing with obstacle ahead. – use raycast + overlap – since raycast cannot detect the collision object at very begin sometime the avatar will not locate the wall in front of it. solution :

process 2 times raycast checking in fixed update.

  • check obstacle based on movement ahead
    1. if blocking, try to project the current direction on wall, and try to override the movement is it’s possible.
  • if ALL checking above aren’t doing anything, check if we WILL overlap any obstacle on target position, when that happen override position.

Debug vision :

the yellow line is the origin direction input by player.(only saw on first hit)

the red line is the slip away path then choose by the avoid obstacle logic.d

 

/// <summary>Vector3 = position, w = weight of this position.</summary>
private Vector3 AvoidObstacleOnPath(Vector3 referencePos)
{
	Vector3 currentPos = m_Avatar.self.transform.position;
	if (referencePos == currentPos)
		return currentPos;

	Debug.Assert(mode == eMode.Master, "Mode - " + mode.ToString("F") + "AvoidObstacleOnPath() should only called in " + eMode.Master.ToString("F") + " mode.");
	Vector3 targetPos = referencePos;
	float delta = Time.fixedDeltaTime;
	Vector3 direction = referencePos - currentPos;
	float distance = Mathf.Max(float.Epsilon, direction.magnitude);
	direction.Normalize();
#if RADIUS_PADDING
	const float s_RadiusPadding = 0.01f;
	float radius = m_Avatar.self.capsule.radius - s_RadiusPadding;
#else
	float radius = m_Avatar.self.capsule.radius;
#endif
	bool posOverrideFlag = false;

	// Physics 1
	// Block detection along the path.
	Vector3[] p = CapsuleCastData.GetCapsuleColliderPoints(m_Avatar.self.capsule, currentPos);
	if (m_Raycast.CapsuleCast(p[0], p[1], radius, direction, distance, m_AvoidCollision, m_QueryTriggerInteraction))
	{
		posOverrideFlag = true;
		// by default, we use that point to replace the current result.

#if RADIUS_PADDING
		// Hotfix : always have padding for next raycast,
		// Bug : Raycast overlap at begin will be ignore, result in NO BLOCKING!
		targetPos = currentPos + direction * (m_Raycast.hitResult.distance - s_RadiusPadding);
#else
		fixedPos = currentPos + direction * m_Raycast.hitResult.distance;
#endif
		if (m_Debug)
			Debug.DrawLine(currentPos, targetPos, Color.yellow, 3f);

		// Physics 2
		// Blocking detected, see if we can slip away based on current direction.
		Vector3 hitNormal = m_Raycast.hitResult.normal.normalized;
		float dot = Vector3.Dot(direction, -hitNormal);
		if (dot >= 0f)
		{
			// try slip away, instead of stuck there.
			Vector3 projectNormal = Vector3.ProjectOnPlane(direction, hitNormal).normalized;
			float halfDistance = Mathf.Max(s_MinSlipDistance, distance * 0.5f);
					
			/// detect obstacle, based on hit result <see cref="targetPos"/> is point stick on obstacle.
			p = CapsuleCastData.GetCapsuleColliderPoints(m_Avatar.self.capsule, targetPos);
			if (!m_Raycast.CapsuleCast(p[0], p[1], radius, projectNormal, halfDistance))
			{
				// path clear.
				targetPos = targetPos + projectNormal * halfDistance;
				if (m_Debug)
					Debug.DrawLine(currentPos, targetPos, Color.red, 3f);
			}
		}
		// else stuck in P1 result.
	}

	// Physic 3
	// final fail safe, if above checking not run at all (Raycast + overlap at begin)
	if (!posOverrideFlag)
	{
		Collider obj = IsOverlap(targetPos);
		if (obj != null)
		{
			if (m_Debug)
				Debug.LogWarning("AvoidObstacleOnPath fail, we overlap \'" + obj.name + "\' ahead, clamp mover position.", this);
			return Vector3.Lerp(m_AvatarBody.position, m_Rigidbody.position, 0.5f);
		}
	}
	return targetPos;
}

private Collider IsOverlap(Vector3 targetPos)
{
	Collider[] objs = new Collider[5];
	Vector3[] tp = CapsuleCastData.GetCapsuleColliderPoints(m_Avatar.self.capsule, targetPos);
	float smallerRadius = Mathf.Max(float.Epsilon, m_Avatar.self.capsule.radius - 0.001f);
	int overlapCnt = m_Capsule.OverlapNonAlloc(tp[0], tp[1], smallerRadius, ref objs, m_AvoidCollision, m_QueryTriggerInteraction);

	// skip all avatar's collider. but report any others.
	for (int i = 0; i < overlapCnt; i++)
	{
		if (!objs[i].transform.IsChildOf(m_AvatarBody.transform))
			return objs[i]; 
	}
	return null;
}

 

 

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *

*