Unity中如何终止嵌套协程?解决推箱游戏的移动终止需求
俯视解谜推箱游戏:同时终止角色与箱子移动协程的可行方案
问题描述
我正在开发一款俯视视角解谜游戏,采用网格移动机制。已实现角色移动及推箱逻辑,但希望当箱子后方存在障碍物时,同时终止角色和箱子的移动协程。目前编写了PlayerMove和Move两个协程函数,尝试用异常终止协程但无效,请问有什么可行方法?
角色移动协程原代码
private IEnumerator PlayerMove(Vector2 direction) { // check for wall collisions if (Physics2D.Raycast(playerTransform.position, direction, raycastDistance, wallLayer).collider is not null) { Animate(Animations.CantMove); yield break; } // check for box collisions else if (Physics2D.Raycast(playerTransform.position, direction, raycastDistance, box.boxLayer).collider is not null) { if () } // changing fields isMoving = true; isPlayersTurn = false; // actual moving float elapsedTime = 0f; origPos = playerTransform.position; targetPos = origPos + direction; while (elapsedTime < timeToMove) { playerTransform.position = Vector2.Lerp(origPos, targetPos, (elapsedTime / timeToMove)); elapsedTime += Time.deltaTime; yield return null; } playerTransform.position = targetPos; // changing fields isMoving = false; isBotTurn = true; }
箱子移动协程原代码
public IEnumerator Move(Vector2 direction, float raycastDistance, LayerMask wallLayer, float timeToMove) { // check for wall collisions if (Physics2D.Raycast(transform.position, direction, raycastDistance, wallLayer).collider is not null) { //do something } // actual moving float elapsedTime = 0f; Vector2 origPos, targetPos; origPos = transform.position; targetPos = origPos + direction; while (elapsedTime < timeToMove) { transform.position = Vector2.Lerp(origPos, targetPos, (elapsedTime / timeToMove)); elapsedTime += Time.deltaTime; yield return null; } transform.position = targetPos; }
解决方案
Unity协程不推荐用异常终止,最稳妥的方式是布尔标记+手动协程引用管理,以下是具体实现:
核心思路
- 给角色和箱子类添加终止标记,用于实时检测是否需要停止移动
- 启动箱子协程时保存引用,方便后续手动终止
- 移动前预检查箱子后方障碍物,移动过程中实时检测终止标记
- 终止后重置角色和箱子的位置,保证网格对齐
修改后的代码实现
Player类修改
private bool _shouldStopMove = false; private Coroutine _currentBoxCoroutine; private IEnumerator PlayerMove(Vector2 direction) { _shouldStopMove = false; _currentBoxCoroutine = null; // 检测墙碰撞 if (Physics2D.Raycast(playerTransform.position, direction, raycastDistance, wallLayer).collider != null) { Animate(Animations.CantMove); yield break; } // 检测箱子碰撞 RaycastHit2D boxHit = Physics2D.Raycast(playerTransform.position, direction, raycastDistance, box.boxLayer); if (boxHit.collider != null) { Box targetBox = boxHit.collider.GetComponent<Box>(); if (targetBox != null) { // 预检查箱子后方是否有障碍物,有则直接终止角色移动 if (targetBox.CheckObstacleBehind(direction, raycastDistance, wallLayer)) { Animate(Animations.CantMove); yield break; } // 启动箱子协程并保存引用 _currentBoxCoroutine = StartCoroutine(targetBox.Move(direction, raycastDistance, wallLayer, timeToMove)); } } isMoving = true; isPlayersTurn = false; float elapsedTime = 0f; Vector2 origPos = playerTransform.position; Vector2 targetPos = origPos + direction; // 移动循环中加入终止标记检测 while (elapsedTime < timeToMove && !_shouldStopMove) { playerTransform.position = Vector2.Lerp(origPos, targetPos, elapsedTime / timeToMove); elapsedTime += Time.deltaTime; yield return null; } // 若因终止标记退出,重置角色位置并停止箱子协程 if (_shouldStopMove) { playerTransform.position = origPos; if (_currentBoxCoroutine != null) { StopCoroutine(_currentBoxCoroutine); box.transform.position = box.OrigPos; } } else { playerTransform.position = targetPos; } isMoving = false; isBotTurn = true; } // 公开方法供箱子触发终止 public void TriggerMoveStop() { _shouldStopMove = true; }
Box类修改
private bool _shouldStopMove = false; public Vector2 OrigPos { get; private set; } // 预检查后方障碍物的方法 public bool CheckObstacleBehind(Vector2 direction, float raycastDistance, LayerMask wallLayer) { return Physics2D.Raycast(transform.position, direction, raycastDistance, wallLayer).collider != null; } public IEnumerator Move(Vector2 direction, float raycastDistance, LayerMask wallLayer, float timeToMove) { _shouldStopMove = false; OrigPos = transform.position; // 移动中检测到障碍物,触发角色终止 if (CheckObstacleBehind(direction, raycastDistance, wallLayer)) { Player player = FindObjectOfType<Player>(); player?.TriggerMoveStop(); yield break; } float elapsedTime = 0f; Vector2 targetPos = OrigPos + direction; // 箱子移动循环加入终止标记检测 while (elapsedTime < timeToMove && !_shouldStopMove) { transform.position = Vector2.Lerp(OrigPos, targetPos, elapsedTime / timeToMove); elapsedTime += Time.deltaTime; yield return null; } // 终止时重置箱子位置 transform.position = _shouldStopMove ? OrigPos : targetPos; }
关键说明
- 避免用异常终止协程:Unity协程的设计逻辑不支持可靠的异常终止,布尔标记是官方推荐的控制方式
- 协程引用必须保存:只有持有
Coroutine对象,才能调用StopCoroutine手动终止 - 预检查+实时检查结合:移动前的预检查避免无效移动,实时检查应对动态障碍物等突发情况
- 位置重置必须执行:终止后将角色和箱子放回移动起点,保证网格移动的准确性
内容的提问来源于stack exchange,提问作者Fase Play
相关产品推荐
相关产品推荐

