所以我正在撰寫代碼來模擬動物如何在地圖周圍吃食物和喝水,但我不斷收到錯誤訊息。我相信這是因為多種動物正試圖進入一個湖泊或一種水果,但我不知道如何避免這種情況發生。所以這是我的代碼,請幫忙(我知道我的編碼技能很差,但我不在乎)
public float hunger = 100;
public float speed;
public float thirst = 100;
// Start is called before the first frame update
void Start()
{
StartCoroutine(hungerdrop());
}
// Update is called once per frame
void Update()
{
if (hunger <= 20 && hunger < thirst)
{
gotoBush();
}
if (thirst <= 20 && thirst < hunger)
{
gotoLake();
}
if (hunger <= 0)
{
Destroy(gameObject);
}
}
IEnumerator hungerdrop()
{
yield return new WaitForSeconds(0.2f);
hunger -= 1;
thirst -= 2;
StartCoroutine(hungerdrop());
}
public void gotoBush()
{
GetClosestFruit();
}
public void gotoLake()
{
GetClosestLake();
}
GameObject GetClosestFruit()
{
GameObject tMin = null;
float minDist = Mathf.Infinity;
Vector3 currentPos = transform.position;
foreach (GameObject t in GameObject.FindGameObjectsWithTag("fruit"))
{
float dist = Vector3.Distance(t.transform.position, currentPos);
if (dist < minDist)
{
tMin = t;
minDist = dist;
}
}
if (tMin != null)
{
float step = speed * Time.deltaTime;
StartCoroutine(walkToBush());
IEnumerator walkToBush()
{
if (tMin != null)
{
while (transform.position != tMin.transform.position && tMin != null)
{
yield return new WaitForSeconds(0.1f);
transform.position = Vector3.MoveTowards(transform.position, tMin.transform.position, step);
}
hunger = 100;
StartCoroutine(bushEat());
}
IEnumerator bushEat()
{
yield return new WaitForSeconds(0.1f);
destroyBush();
}
}
void destroyBush()
{
Destroy(tMin);
}
}
return tMin;
}
GameObject GetClosestLake()
{
GameObject tMin = null;
float minDist = Mathf.Infinity;
Vector3 currentPos = transform.position;
foreach (GameObject t in GameObject.FindGameObjectsWithTag("lake"))
{
float dist = Vector3.Distance(t.transform.position, currentPos);
if (dist < minDist)
{
tMin = t;
minDist = dist;
}
}
if (tMin != null )
{
float step = speed * Time.deltaTime;
StartCoroutine(walkToBush());
IEnumerator walkToBush()
{
if (tMin != null)
{
while (transform.position != tMin.transform.position && tMin != null)
{
yield return new WaitForSeconds(0.1f);
if(tMin != null)
{
transform.position = Vector3.MoveTowards(transform.position, tMin.transform.position, step);
}
}
thirst = 100;
StartCoroutine(bushEat());
}
IEnumerator bushEat()
{
yield return new WaitForSeconds(0.1f);
destroyBush();
}
}
void destroyBush()
{
Destroy(tMin);
}
}
return tMin;
}
}
uj5u.com熱心網友回復:
第一:你應該在協程中使用 while 回圈而不是像這樣的遞回:
IEnumerator hungerdrop()
{
while(true)
{
hunger -= 1;
thirst -= 2;
yield return new WaitForSeconds(0.2f);
}
}
第二:要檢查已經被銷毀的游戲物件,您可以簡單地使用 if 陳述句,如:
if(gameObject != null)
{
//do something
}
uj5u.com熱心網友回復:
如果我理解正確,您有多個動物實體都在運行此組件。
我在這里看到兩個大問題:
- 您可能有并發協程,因為您在條件滿足的情況下每幀啟動一個新例程
true!這甚至可能意味著您試圖向兩個相反的方向移動 => 您永遠無法達到目標。 - 您的多只動物可能會瞄準相同的資源 => 當其中一只仍在朝它移動時,另一只可能已經吃掉并摧毀了它。
然后很可能有這個問題,您在獲得位置后檢查資源的活動狀態
while (transform.position != tMin.transform.position && tMin != null)
你應該在哪里檢查
while (tMin && transform.position != tMin.transform.position)
但是,while即使tMin不再存在,這仍然不會阻止執行之后的其余代碼!
我寧愿做的是
- 確保一次只運行一個協程
- 確保一次只有一只動物可以瞄準一個資源
因此,我寧愿將兩個實際組件附加到具有公共基類的資源上,而不是使用標簽:
public abstract class ConsumableResource : MonoBehaviour
{
public bool isOccupied;
}
然后這兩個進入相應的資源GameObject:
public class Fruit : ConsumableResource
{
// Doesn't have to do anything else .. but could
}
和
public class Lake : ConsumableResource
{
// Doesn't have to do anything else .. but could
// You could e.g. consider to add a counter so that multiple animals can target this at a time
// and/or add another counter so multiple animals can consume this resource before it is destroyed
}
然后我會做
public class Animal : MonoBehaviour
{
public float hunger = 100;
public float speed;
public float thirst = 100;
// the currently executed coroutine
private Coroutine _currentRoutine;
// the currently targeted resource instance
private ConsumableResource _currentTargetResource;
// Update is called once per frame
private void Update()
{
// first of all I would rather drop the hunger and thirst like this
// This now means hunger drops 5 units per second and thirst drops 10 units per second
// What you had before was a complex way for writing basically the same
hunger -= 5f * Time.deltaTime;
thirst -= 10f * Time.deltaTime;
if (hunger <= 0)
{
Destroy(gameObject);
}
// In order to no end up with multiple concurrent routines check if there is already a routine running first
if (_currentRoutine == null)
{
if (hunger <= 20 && hunger < thirst)
{
// start our resource routine targeting the type Fruit and if we succeed to run the entire routine to and
// then call WhenConsumedFruit afterwards to reset the hunger
_currentRoutine = StartCoroutine(GetResourceRoutine<Fruit>(WhenConsumedFruit));
}
// make your cases exclusive!
else if (thirst <= 20 && thirst < hunger)
{
_currentRoutine = StartCoroutine(GetResourceRoutine<Lake>(WhenConsumedLake));
}
}
}
private void WhenConsumedFruit()
{
hunger = 100;
}
private void WhenConsumedLake()
{
thirst = 100;
}
// Use a generic method to not repeat the same implementation
// This now can be used to get the closest of any inherited type from ConsumableResource
private T FindClosestResource<T>() where T : ConsumableResource
{
// First get all instances in the scene of given resource type
var allFruits = FindObjectsOfType<T>();
// filter out those that are already occupied by another animal instance
var onlyNotOccupiedFruits = allFruits.Where(fruit => !fruit.isOccupied);
// sort the remaining instances by distance
var sortedByDistance = onlyNotOccupiedFruits.OrderBy(fruit => (fruit.transform.position - transform.position).sqrMagnitude);
// take the first one (= closest) or null if there was no remaining instance
return sortedByDistance.FirstOrDefault();
}
// Again use the most generic base class so you can reuse the same code for any inherited type of ConsumableResource
IEnumerator MoveTowardsResource(ConsumableResource target)
{
while (transform.position != target.transform.position)
{
transform.position = Vector3.MoveTowards(transform.position, target.transform.position, speed * Time.deltaTime);
yield return null;
}
transform.position = target.transform.position;
}
// and again do his only once
IEnumerator ConsumeResource(ConsumableResource target)
{
yield return new WaitForSeconds(0.1f);
// You might want to consider to rather move this into the resource itself
// so it could extend the behavior as said e.g. using counters
// without the need that your animal class has to be aware of what exactly
// this means for the resource. Maybe it doesn't destroy it but only disable
// for some time so it can grow back => unlimmitted options ;)
Destroy(target.gameObject);
}
private IEnumerator GetResourceRoutine<T>(Action whenConsumed) where T : ConsumableResource
{
_currentTargetResource = FindClosestResource<T>();
// did we find a closest fruit?
if (!_currentTargetResource)
{
// if not terminate this routine and allow the next one to start
_currentRoutine = null;
yield break;
}
// set closest fruit occupied so no animal can take it anymore
_currentTargetResource.isOccupied = true;
// Move towards the closest fruit
yield return MoveTowardsResource(_currentTargetResource);
// "Eat" the fruit
yield return ConsumeResource(_currentTargetResource);
// when all is done without this object dying before simply invoked the passed callback action
whenConsumed?.Invoke();
// Allow the next routine to start
_currentRoutine = null;
}
private void OnDestroy()
{
// in case we die for whatever reason make sure to release the occupied resources if there are any
// so if you die on the way towards it at least from now on another animal can pick it again as target
if (_currentTargetResource) _currentTargetResource.isOccupied = false;
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/340633.html
下一篇:用條件統一多個音頻
