You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 06:27:56