如何通过协程修改Unity中bg对象旋转角并兼容按键控制?
解决方案:Unity中同时支持按键与协程独立控制对象旋转并同步目标位置
核心问题分析
- Update中每次检测
_testFlag都会启动新协程,导致多协程冲突 - 目标位置计算依赖动态更新的
_currentTargetPos,旋转叠加逻辑混乱 - 按键与协程直接操作旋转值,导致两者控制逻辑互相干扰
可行解决方案
1. 统一目标旋转值,分离控制与执行
将bg的旋转目标值单独维护,按键和协程仅修改这个目标值,在Update中统一平滑过渡到目标旋转,保证两种控制方式互不干扰。
2. 保存目标对象的初始相对偏移
不再动态更新_currentTargetPos,而是保存_target相对于bg的初始本地偏移量,无论bg如何旋转,都能通过旋转矩阵计算正确的目标位置。
3. 防止协程重复启动
添加协程运行标志,确保同一时间只有一个协程执行旋转控制。
修改后的完整代码
using System.Collections; using UnityEngine; public class InputController : MonoBehaviour { [SerializeField] private Transform bg; // 需在Inspector中赋值 [SerializeField] private Transform _target; [SerializeField] private bool _testFlag; // 在编辑器中修改触发测试 private Vector3 _initialLocalOffset; // _target相对于bg的初始本地偏移 private float _targetRotationZ; // bg的目标Z轴旋转值 private bool _isCoroutineRunning; // 协程运行标志,避免重复启动 void Start() { // 保存初始相对偏移:bg本地空间下_target的位置 _initialLocalOffset = bg.InverseTransformPoint(_target.position); // 初始化目标旋转为当前bg的旋转角度 _targetRotationZ = bg.rotation.eulerAngles.z; } void Update() { // 按键控制:修改目标旋转值 if (Input.GetKey(KeyCode.D)) { _targetRotationZ -= 60f * Time.deltaTime; } else if (Input.GetKey(KeyCode.A)) { _targetRotationZ += 60f * Time.deltaTime; } // 平滑过渡到目标旋转 bg.rotation = Quaternion.Lerp(bg.rotation, Quaternion.Euler(0, 0, _targetRotationZ), 10f * Time.deltaTime); // 更新_target的位置 UpdateTargetPosition(); // 触发测试协程,确保仅启动一次 if (_testFlag && !_isCoroutineRunning) { StartCoroutine(TestRotateToTargetAngle()); _testFlag = false; // 立即重置标志,避免重复触发 } } /// <summary> /// 根据bg当前旋转计算_target的正确位置 /// </summary> private void UpdateTargetPosition() { // 将初始本地偏移通过bg的旋转转换为世界空间位置 Vector3 worldPosition = bg.TransformPoint(_initialLocalOffset); _target.position = worldPosition; } /// <summary> /// 协程控制bg旋转到指定角度,与按键控制独立生效 /// </summary> private IEnumerator TestRotateToTargetAngle() { _isCoroutineRunning = true; float startRotation = _targetRotationZ; float targetAngle = 20f; // 目标旋转角度 float timer = 0f; float rotationDuration = 2f; while (timer < rotationDuration) { timer += Time.deltaTime; float t = Mathf.Clamp01(timer / rotationDuration); // 使用LerpAngle处理角度环绕(如350°到10°的平滑过渡) _targetRotationZ = Mathf.LerpAngle(startRotation, targetAngle, t); yield return null; } // 确保最终旋转准确设置到目标角度 _targetRotationZ = targetAngle; _isCoroutineRunning = false; } }
关键说明
- 旋转控制分离:按键和协程仅修改
_targetRotationZ,Update中统一执行旋转过渡,避免直接操作bg旋转导致的冲突。 - 相对偏移保存:基于初始相对位置计算目标位置,不会出现旋转后位置偏移累积的问题。
- 协程防重复:
_isCoroutineRunning标志避免多协程同时修改旋转值,保证控制逻辑稳定。
内容的提问来源于stack exchange,提问作者Operator
相关产品推荐
相关产品推荐

