Unity中如何实现GameObject平滑移动以填补销毁后的间隙?
嘿,我之前做类似的票务系统时也踩过这个坑!Lerp和SmoothDamp本身没问题,大概率是你用的方式不对——咱们来把这个平滑滑动的功能给搞定~
平滑填补票务间隙的正确实现思路
核心需求很明确:某张票务销毁后,下方的所有票务要平滑向下滑动填补空隙,而不是瞬间跳变。先聊聊你可能踩的坑,再给你具体的实现方案。
为什么Lerp/SmoothDamp会出异常?
你遇到的奇怪效果,基本逃不开这几个原因:
- 只调用了一次插值方法,没有在
Update()/FixedUpdate()里持续更新位置,自然会直接跳转到目标点 - 没结合
Time.deltaTime,导致滑动速度和帧率绑定,高帧率下飞一样快,低帧率下慢得离谱 - 目标位置计算错误,比如硬写了固定值,没动态获取上一张票务的底部位置
具体实现步骤
1. 先做个票务管理器统一管控
建议写个TicketManager脚本,专门维护所有活跃的票务列表,这样销毁票务时能快速定位需要移动的对象:
public class TicketManager : MonoBehaviour { public static TicketManager Instance; public List<Ticket> activeTickets = new List<Ticket>(); private void Awake() { Instance = this; } // 新票务生成时加入列表并立即定位 public void AddTicket(Ticket ticket) { activeTickets.Add(ticket); UpdateTicketPositionsImmediate(); } // 票务销毁时触发位置更新 public void RemoveTicket(Ticket ticket) { activeTickets.Remove(ticket); StartCoroutine(SmoothUpdateTicketPositions()); } }
2. 给票务加平滑滑动逻辑
在Ticket脚本里实现移动逻辑,确保每一帧都更新位置:
public class Ticket : MonoBehaviour { public float slideSpeed = 6f; // 滑动速度,自己调着看 private Vector3 targetPos; private bool isSliding = false; public void SetTargetPosition(Vector3 target) { targetPos = target; isSliding = true; } private void Update() { if (!isSliding) return; // 用Lerp结合Time.deltaTime实现平滑移动 transform.position = Vector3.Lerp(transform.position, targetPos, slideSpeed * Time.deltaTime); // 距离足够近时强制归位,避免抖动 if (Vector3.Distance(transform.position, targetPos) < 0.01f) { transform.position = targetPos; isSliding = false; } } // 票务完成后调用这个方法销毁 public void FinishTicket() { TicketManager.Instance.RemoveTicket(this); Destroy(gameObject); } }
3. 计算每个票务的目标位置
回到TicketManager,写个协程来计算所有票务应该移动到的位置:
private IEnumerator SmoothUpdateTicketPositions() { List<Vector3> targetPositions = new List<Vector3>(); // 这里替换成你的顶部初始Y坐标,比如UGUI的话可以用RectTransform的锚点位置 float currentY = Screen.height - 100f; // 单张票务的高度,UGUI用rectTransform.sizeDelta.y获取 float ticketHeight = GetComponent<RectTransform>().sizeDelta.y; // 从上到下排列票务,确保没有间隙 foreach (var ticket in activeTickets.OrderByDescending(t => t.transform.position.y)) { targetPositions.Add(new Vector3(ticket.transform.position.x, currentY, ticket.transform.position.z)); currentY -= ticketHeight; } // 给每个票务设置目标位置,触发滑动 for (int i = 0; i < activeTickets.Count; i++) { activeTickets[i].SetTargetPosition(targetPositions[i]); } yield return null; } // 生成票务时立即定位,避免初始间隙 private void UpdateTicketPositionsImmediate() { float currentY = Screen.height - 100f; float ticketHeight = GetComponent<RectTransform>().sizeDelta.y; foreach (var ticket in activeTickets.OrderByDescending(t => t.transform.position.y)) { ticket.transform.position = new Vector3(ticket.transform.position.x, currentY, ticket.transform.position.z); currentY -= ticketHeight; } }
偷懒省心的替代方案:用DOTween
如果不想自己写插值逻辑,直接用Unity Asset Store免费的DOTween插件,一行代码就能搞定平滑移动,还自带缓动效果:
把Ticket里的SetTargetPosition改成这样:
public void SetTargetPosition(Vector3 target) { transform.DOMove(target, 0.3f); // 0.3秒滑到目标位置,时长自己调 }
额外提醒
- 如果是UGUI的
RectTransform,别用transform.position,改用rectTransform.anchoredPosition来计算,适配不同分辨率 - 可以加个缓动曲线,比如用
Mathf.SmoothStep替代Lerp,让滑动“先慢、中快、后慢”,视觉更自然 - 一定要在销毁票务前通知管理器更新位置,别等销毁完再处理,避免列表索引错误
内容的提问来源于stack exchange,提问作者Matt
相关产品推荐
相关产品推荐

