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

Unity中角色背景移动时金币DOTween动画目标位置偏移问题求助

问题描述

我正在开发一款主角以恒定速度持续移动并收集金币的游戏。收集金币时,通过DOTween动画将金币移动到屏幕上显示固定金币总数的文本位置。代码中通过Vector3 targetPosition = target.position获取目标位置,但由于角色和背景持续向前移动,金币最终会移动到目标位置后方。尝试过FixedUpdate与Update结合的解决方案,但没有效果。

当前代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using DG.Tweening;
using System.Collections.Generic;

public class CoinCounter : MonoBehaviour
{
    [Header("UI reference")]
    [SerializeField] TMP_Text coinUIText;
    [SerializeField] GameObject animatedCoinPrefab;
    [SerializeField] Transform target;

    [Space]
    [Header("Available coins : (coins to pool )")]
    [SerializeField] int maxCoins;
    Queue<GameObject> coinsQueue = new Queue<GameObject>();

    [Space]
    [Header("Animation settings ")]
    [SerializeField] [Range(0.5f,0.9f)] float minAnimDuration;
    [SerializeField] [Range(0.9f,2f)] float maxAnimDuration;

    [SerializeField] Ease easeType;

    Vector3 targetPosition;

    private int _c = 0;

    public int Coins
    {
        get{return _c;}
        set{
            _c = value;
            //update UI text whenever "Coins" variable is changeds
            coinUIText.text = Coins.ToString();
        }
    }

    void FixedUpdate()
    {
        targetPosition = target.position;
        //prepare pool
        PrepareCoins();
    }

    void PrepareCoins()
    {
        GameObject coin;
        for(int i = 0; i<maxCoins; i++)
        {
            coin = Instantiate(animatedCoinPrefab);
            coin.transform.parent = transform;
            coin.SetActive(false);
            coinsQueue.Enqueue(coin);
        }
        
    }

    void Animate(Vector3 collectedCoinPosition, int amount)
    {
        for(int i = 0; i<amount; i++)
        {
            //check if there's coins in the pool
            if(coinsQueue.Count > 0)
            {
                //extract a coin from the pool
                GameObject coin = coinsQueue.Dequeue();
                coin.SetActive(true);

                //move coin to the collected coin pos
                coin.transform.position = collectedCoinPosition;

                //animate coin to target position
                float duration = Random.Range(minAnimDuration,maxAnimDuration);
                coin.transform.DOMove(targetPosition,duration).SetEase(easeType).OnComplete(()=>
                {
                    //execute whenever coin reach target position
                    coin.SetActive(false);
                    coinsQueue.Enqueue(coin);

                    Coins++;
                });

            }
        }
    }
    public void AddCoins(Vector3 collectedCoinPosition, int amount)
    {
        Animate(collectedCoinPosition,amount);
    }

    // Update is called once per frame
    void OnTriggerEnter2D(Collider2D other)
    {
       
    }
}

解决方案

1. 转换坐标系统:使用UI画布的局部坐标

目标是屏幕上固定的UI文本,不能用世界坐标作为动画终点。需要将金币的世界位置转换为UI画布的局部坐标,再让动画目标指向这个UI坐标。

2. 修复对象池初始化逻辑

原代码在FixedUpdate中反复调用PrepareCoins,会无限生成金币对象导致性能问题,应该只在Start方法中初始化一次对象池。

3. 避免动态更新目标位置

UI元素在屏幕上的位置固定,不需要在FixedUpdate中实时更新targetPosition,直接获取一次UI的局部坐标即可。


修改后的代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using DG.Tweening;

public class CoinCounter : MonoBehaviour
{
    [Header("UI reference")]
    [SerializeField] TMP_Text coinUIText;
    [SerializeField] GameObject animatedCoinPrefab;
    [SerializeField] RectTransform targetUIRect; // 改用RectTransform获取UI坐标
    [SerializeField] Canvas canvas; // 引用UI画布

    [Space]
    [Header("Available coins : (coins to pool )")]
    [SerializeField] int maxCoins;
    Queue<GameObject> coinsQueue = new Queue<GameObject>();

    [Space]
    [Header("Animation settings ")]
    [SerializeField] [Range(0.5f,0.9f)] float minAnimDuration;
    [SerializeField] [Range(0.9f,2f)] float maxAnimDuration;

    [SerializeField] Ease easeType;

    private int _c = 0;

    public int Coins
    {
        get{return _c;}
        set{
            _c = value;
            coinUIText.text = Coins.ToString();
        }
    }

    void Start()
    {
        // 只在初始化时创建对象池
        PrepareCoins();
    }

    void PrepareCoins()
    {
        for(int i = 0; i<maxCoins; i++)
        {
            GameObject coin = Instantiate(animatedCoinPrefab);
            // 将动画金币设为画布子物体,使用UI坐标
            coin.transform.SetParent(canvas.transform, false);
            coin.SetActive(false);
            coinsQueue.Enqueue(coin);
        }
    }

    void Animate(Vector3 collectedCoinWorldPosition, int amount)
    {
        // 将金币的世界坐标转换为UI画布的局部坐标
        Vector2 targetUIPosition = RectTransformUtility.WorldToScreenPoint(Camera.main, targetUIRect.position);
        RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, targetUIPosition, canvas.worldCamera, out targetUIPosition);

        for(int i = 0; i<amount; i++)
        {
            if(coinsQueue.Count > 0)
            {
                GameObject coin = coinsQueue.Dequeue();
                coin.SetActive(true);

                // 将收集到的金币世界坐标转成UI坐标
                Vector2 coinUIPosition;
                RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, 
                    Camera.main.WorldToScreenPoint(collectedCoinWorldPosition), 
                    canvas.worldCamera, out coinUIPosition);
                
                coin.GetComponent<RectTransform>().anchoredPosition = coinUIPosition;

                // 动画到UI目标位置
                float duration = Random.Range(minAnimDuration,maxAnimDuration);
                coin.GetComponent<RectTransform>().DOAnchorPos(targetUIPosition, duration)
                    .SetEase(easeType)
                    .OnComplete(()=>
                    {
                        coin.SetActive(false);
                        coinsQueue.Enqueue(coin);
                        Coins++;
                    });
            }
        }
    }

    public void AddCoins(Vector3 collectedCoinPosition, int amount)
    {
        Animate(collectedCoinPosition, amount);
    }

    void OnTriggerEnter2D(Collider2D other)
    {
        // 这里可以添加检测金币的逻辑,调用AddCoins
    }
}

关键修改说明

  • 将动画金币改为UI元素的子物体,使用RectTransform的锚点坐标进行动画,避免世界移动影响
  • 使用RectTransformUtility将世界坐标和屏幕坐标转换为UI画布的局部坐标,确保动画终点始终是屏幕上的固定位置
  • 修复对象池初始化逻辑,只在Start中执行一次,避免内存泄漏
  • 用DOAnchorPos替代DOMove,适配UI元素的坐标系统

内容的提问来源于stack exchange,提问作者hakanmes

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 15:12:07