Unity移动端:如何实现流畅的滑动冲刺移动效果?
移动端Unity角色冲刺移动优化方案
问题现状
开发移动端Unity项目时,需要实现以下机制:玩家滑动屏幕后,将滑动方向转为最近的轴向向量(如70度对应(0,1),35度对应(1,0)),角色向该方向流畅冲刺到目标位置。当前实现仅能瞬移,使用Vector2.Lerp时卡顿、响应延迟,尝试Vector2.MovePosition()、rb.AddForce()等方法效果均不理想。
现有核心移动代码:
if (trigger) { fractionOfWayThere += 0.1f; transform.position=Vector2.Lerp(transform.position, finish, fractionOfWayThere); isMoving = true; }
核心问题分析
- Lerp用法错误:直接在Update中累加
fractionOfWayThere并以当前位置作为插值起点,会导致移动速度随帧率波动,且插值逻辑不符合"平滑冲刺"的预期(正确的Lerp应该用固定的起始位置和基于时间的进度)。 - 帧率依赖:用固定增量
0.1f更新进度,不同设备帧率不同会导致移动速度不一致,出现卡顿或过快。 - 位置判断精度问题:直接用
(Vector2)transform.position == finish判断到达,由于浮点精度误差,可能永远无法触发停止逻辑。 - 移动逻辑与输入逻辑耦合:所有逻辑都放在Update中,物理相关的移动未使用FixedUpdate,导致移动不流畅。
优化实现思路与代码修改
1. 修正Lerp插值逻辑
将冲刺的起始位置缓存,使用基于时间的进度值,保证移动速度稳定:
- 新增变量
startMovePos存储冲刺开始时的位置 - 用
Time.deltaTime计算进度,确保不同帧率下速度一致
2. 优化位置到达判断
用距离阈值代替直接相等判断,避免浮点精度问题:
if (Vector2.Distance(transform.position, finish) < 0.01f) { // 到达目标位置的逻辑 }
3. 分离输入与移动逻辑
输入处理放在Update中,物理移动放在FixedUpdate中(如果使用刚体),或者在Update中用时间增量保证平滑。
4. 简化方向向量转换逻辑
原有的三个if判断可以合并为更简洁的逻辑:
float absX = Mathf.Abs(destination.x); float absY = Mathf.Abs(destination.y); if (absX > absY) { destination = new Vector2(Mathf.Sign(destination.x), 0); } else if (absY > absX) { destination = new Vector2(0, Mathf.Sign(destination.y)); } else { // 对角线滑动时优先X方向,可按需调整 destination = new Vector2(Mathf.Sign(destination.x), 0); }
(替代原有的Funkcija方法,直接用Mathf.Sign获取方向)
5. 完整优化后代码示例
private Vector2 startMovePos; private Vector2 finish; private float moveDuration = 0.2f; // 冲刺持续时间,可调整 private float moveTimer; private Camera cam; private Rigidbody2D rb; // 如果使用刚体,记得在Awake中赋值 private bool isMoving; private SpriteRenderer sr; private Animator animator; private LayerMask spike; private void Awake() { cam = Camera.main; rb = GetComponent<Rigidbody2D>(); sr = GetComponent<SpriteRenderer>(); animator = GetComponent<Animator>(); } private void Update() { if (GetComponent<OnDeath>().isDead) return; // 输入处理 if (Input.touchCount > 0) { Touch touch = Input.GetTouch(0); Vector2 startPos = Vector2.zero; if (touch.phase == TouchPhase.Began) { startPos = cam.ScreenToWorldPoint(touch.position); } else if (touch.phase == TouchPhase.Ended) { Vector2 endPos = cam.ScreenToWorldPoint(touch.position); Vector2 destination = endPos - startPos; // 转换为轴向方向 float absX = Mathf.Abs(destination.x); float absY = Mathf.Abs(destination.y); if (absX > absY) { destination = new Vector2(Mathf.Sign(destination.x), 0); } else if (absY > absX) { destination = new Vector2(0, Mathf.Sign(destination.y)); } else { destination = new Vector2(Mathf.Sign(destination.x), 0); } finish = (Vector2)transform.position + destination.normalized; // 检测目标位置是否合法(原代码逻辑:避开spike层) if (Physics2D.OverlapCircle(finish, 0.1f, spike) == null) { // 开始冲刺 startMovePos = transform.position; moveTimer = 0; isMoving = true; animator.SetBool("isMoving", true); // 翻转角色 sr.flipX = finish.x < transform.position.x; } else { finish = transform.position; } } } // 非刚体模式下的移动处理 if (isMoving) { moveTimer += Time.deltaTime; float progress = Mathf.Clamp01(moveTimer / moveDuration); // 使用Lerp从起始位置到目标位置,保证平滑 transform.position = Vector2.Lerp(startMovePos, finish, progress); if (progress >= 1f) { isMoving = false; animator.SetBool("isMoving", false); transform.position = finish; // 确保精确到达目标 } } } // 如果使用刚体,改用FixedUpdate处理移动 /* private void FixedUpdate() { if (!isMoving) return; moveTimer += Time.fixedDeltaTime; float progress = Mathf.Clamp01(moveTimer / moveDuration); Vector2 targetPos = Vector2.Lerp(startMovePos, finish, progress); rb.MovePosition(targetPos); if (progress >= 1f) { isMoving = false; animator.SetBool("isMoving", false); rb.MovePosition(finish); } } */
额外建议
- 调整冲刺持续时间:
moveDuration控制冲刺的快慢,根据手感调整0.1~0.3秒左右。 - 加入缓动效果:可以用
Mathf.SmoothStep或动画曲线代替线性Lerp,让冲刺有加速减速的效果,手感更自然:float progress = Mathf.SmoothStep(0, 1, moveTimer / moveDuration); - 禁用输入锁定:冲刺过程中可以锁定输入,避免重复触发移动,提升手感。
内容的提问来源于stack exchange,提问作者Ilija Lazović
相关产品推荐
相关产品推荐

