【Unity-小组件】相机注视伸缩

该代码示例展示了如何在Unity中创建一个C#脚本,用于控制相机对游戏对象进行注释和收缩的效果。通过事件回调管理注释的开始和结束,使用动画曲线控制相机收缩动画,并在注视结束时添加缓动以平滑重置相机位置。此外,提供了注册和反注册监听事件的接口,便于在不同时机介入功能逻辑。
摘要由CSDN通过智能技术生成

组件功能

让相机在某一时刻对某个点进行注释和收缩的表现。


下面给出C#代码

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

#if UNITY_EDITOR
[ExecuteAlways]
#endif
public class UIControlShrinkCamera : MonoBehaviour
{
    #region 事件区

    /// <summary>
    /// 无参注释开始回调,优先级比有参的高,并且只在队列开始时调用
    /// </summary>
    private event Action _StartSeeAction;
    /// <summary>
    /// 有参注释开始回调,队列每次注释都调用
    /// </summary>
    private event Action<GameObject> _StartSeeEveryOneAction;
    /// <summary>
    /// 无参注释结束回调,优先级比有参的高,并且只在队列开始时调用
    /// </summary>
    private event Action _SeeEndAction;
    /// <summary>
    /// 有参注释结束回调,队列每次注释都调用
    /// </summary>
    private event Action<GameObject> _SeeEndEveryOneAction;

    #endregion

    #region 属性区

    

    #endregion

    #region 变量区

    [Space]
    [Header("注释英雄时的位置向量大小")]
    [Range(5,10)]
    public float heroSeePos;
    [SerializeField]
    [Header("受作用物体")]
    private Transform _targetTransform;

    /// <summary>
    /// 收缩曲线
    /// </summary>
    public AnimationCurve shrinkCurve;
    
    private Queue<GameObject> _heroTargetList = new();

    #endregion

    #region 函数区

    //--------------------Public 公有函数--------------------//

    public void SeeHero(GameObject targetTrans)
    {
        if (_heroTargetList.Count == 0)
        {
            _heroTargetList.Enqueue(targetTrans);
            _SeeHeroAnim();
        }
        else
        {
            _heroTargetList.Enqueue(targetTrans);
        }
    }

    /// <summary>
    /// 注册开始注视的监听
    /// </summary>
    /// <param name="starSeeAction"></param>
    public void RegisterStartSeeEvent(Action starSeeAction)
    {
        _StartSeeAction -= starSeeAction;
        _StartSeeAction += starSeeAction;
    }
    
    /// <summary>
    /// 注册开始注视的监听
    /// </summary>
    /// <param name="starSeeEveryOneAction"></param>
    public void RegisterStartSeeEveryOneEvent(Action<GameObject> starSeeEveryOneAction)
    {
        _StartSeeEveryOneAction -= starSeeEveryOneAction;
        _StartSeeEveryOneAction += starSeeEveryOneAction;
    }

    /// <summary>
    /// 注册注视结束的监听
    /// </summary>
    /// <param name="seeEndAction"></param>
    public void RegisterSeeEndEvent(Action seeEndAction)
    {
        _SeeEndAction -= seeEndAction;
        _SeeEndAction += seeEndAction;
    }
    
    /// <summary>
    /// 注册注视结束的监听
    /// </summary>
    /// <param name="seeEndEveryOneAction"></param>
    public void RegisterSeeEndEveryOneEvent(Action<GameObject> seeEndEveryOneAction)
    {
        _SeeEndEveryOneAction -= seeEndEveryOneAction;
        _SeeEndEveryOneAction += seeEndEveryOneAction;
    }
    
    /// <summary>
    /// 注销开始注视的监听
    /// </summary>
    /// <param name="starSeeAction"></param>
    public void UnRegisterStartSeeEvent(Action starSeeAction)
    {
        _StartSeeAction -= starSeeAction;
    }
    
    /// <summary>
    /// 注销开始注视的监听
    /// </summary>
    /// <param name="starSeeAction"></param>
    public void UnRegisterStartSeeEveryOneEvent(Action<GameObject> starSeeAction)
    {
        _StartSeeEveryOneAction -= starSeeAction;
    }

    /// <summary>
    /// 注销注视结束的监听
    /// </summary>
    /// <param name="seeEndEveryOneAction"></param>
    public void UnRegisterSeeEndEveryOneEvent(Action<GameObject> seeEndEveryOneAction)
    {
        _SeeEndEveryOneAction -= seeEndEveryOneAction;
    }
    
    /// <summary>
    /// 注销注视结束的监听
    /// </summary>
    /// <param name="seeEndAction"></param>
    public void UnRegisterSeeEndEvent(Action seeEndAction)
    {
        _SeeEndAction -= seeEndAction;
    }

    //--------------------Protect 保护函数--------------------//

#if UNITY_EDITOR
    /// <summary>
    /// 测试使用
    /// </summary>
    [SerializeField]
    private List<Transform> _testTarget = new List<Transform>();
    private void OnValidate()
    {
        if (_targetTransform == null)
        {
            _targetTransform = transform;
        }
    }
#endif

    //--------------------Private 私有函数--------------------//
    
    #region SeeHero

    private void _SeeHeroAnim()
    {
        if (_heroTargetList == null || _heroTargetList.Count <= 0)
        {
            return;
        }

        StartCoroutine(_SeeHeroAnimCo());
    }

    /// <summary>
    /// 开启队列协程
    /// </summary>
    /// <returns></returns>
    private IEnumerator _SeeHeroAnimCo()
    {
        if (_heroTargetList.Count <= 0)
        {
            yield break;
        }

        _StartSeeAction?.Invoke();
        Vector3 nowPosition = _targetTransform.position;
        Quaternion quaternion = _targetTransform.rotation;
        while (_heroTargetList.Count > 0)
        {
            // Vector3 targetTrans = _heroTargetList.Peek();
            GameObject role = _heroTargetList.Peek();
            if (role == null)
            {
                continue;
            }

            _StartSeeEveryOneAction?.Invoke(role);
            Vector3 targetTrans = role.transform.position;
            targetTrans.y += 1.8f;
            yield return _SeeTargetHeroAnimCo(targetTrans,_targetTransform);
            _SeeEndEveryOneAction?.Invoke(role);
            _heroTargetList.Dequeue();
        }
        
        _targetTransform.position = nowPosition;
        _targetTransform.rotation = quaternion;
        _SeeEndAction?.Invoke();
    }

    /// <summary>
    /// 挪动注视英雄目标
    /// </summary>
    /// <returns></returns>
    private IEnumerator _SeeTargetHeroAnimCo(Vector3 target,Transform oTrans)
    {
        if (oTrans == null)
        {
            yield break;
        }
        Vector3 oPos = oTrans.position;
        Quaternion oQuaternion = oTrans.rotation;
        //这里target不判空了,只有唯一个地方调用并且在调用处判空了
        Vector3 targetPosition = target;
        Vector3 targetPos = targetPosition + (oPos - targetPosition).normalized * heroSeePos;
        Vector3 moveVec = targetPos - oPos;
        
        float durTime = shrinkCurve.keys[shrinkCurve.length - 1].time;
        float passTime = 0f;
        
        _targetTransform.LookAt(target);

        float endTime = Time.realtimeSinceStartup + durTime;
        while (Time.realtimeSinceStartup < endTime)
        {
            float tempTimeScale = (Time.timeScale > 1 ? Time.timeScale : 1f);
            float curWaitTime = Time.fixedUnscaledDeltaTime / tempTimeScale;
            float value = shrinkCurve.Evaluate(passTime);
            _targetTransform.position = oPos + moveVec * value;
            passTime += curWaitTime * tempTimeScale;
            yield return new WaitForSecondsRealtime(curWaitTime);
        }
        
        _targetTransform.position = oPos;
        _targetTransform.rotation = oQuaternion;
        yield break;
    }

    #endregion

    #endregion
}

下面是组件效果

Alt


这里有个问题,相机在第一个和第二注视的时候会很快的跳转回原始位置,解决这个问题,在注视结束的时候做一个缓动重置位置来解决问题,下面是修改后的代码。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

#if UNITY_EDITOR
[ExecuteAlways]
#endif
public class UIControlShrinkCamera : MonoBehaviour
{
    #region 事件区

    /// <summary>
    /// 无参注释开始回调,优先级比有参的高,并且只在队列开始时调用
    /// </summary>
    private event Action _StartSeeAction;
    /// <summary>
    /// 有参注释开始回调,队列每次注释都调用
    /// </summary>
    private event Action<GameObject> _StartSeeEveryOneAction;
    /// <summary>
    /// 无参注释结束回调,优先级比有参的高,并且只在队列开始时调用
    /// </summary>
    private event Action _SeeEndAction;
    /// <summary>
    /// 有参注释结束回调,队列每次注释都调用
    /// </summary>
    private event Action<GameObject> _SeeEndEveryOneAction;

    #endregion

    #region 属性区

    

    #endregion

    #region 变量区

    [Space]
    [Header("注释英雄时的位置向量大小")]
    [Range(5,10)]
    public float heroSeePos;
    [SerializeField]
    [Header("受作用物体")]
    private Transform _targetTransform;
    [SerializeField]
    [Header("重置原始位置间隔时间")]
    [Range(0,1f)]
    private float _resetOriginalPosIntervalTime = 0.3f;

    /// <summary>
    /// 收缩曲线
    /// </summary>
    public AnimationCurve shrinkCurve;
    
    private Queue<GameObject> _heroTargetList = new();

    #endregion

    #region 函数区

    //--------------------Public 公有函数--------------------//

    public void SeeHero(GameObject targetTrans)
    {
        if (_heroTargetList.Count == 0)
        {
            _heroTargetList.Enqueue(targetTrans);
            _SeeHeroAnim();
        }
        else
        {
            _heroTargetList.Enqueue(targetTrans);
        }
    }

    /// <summary>
    /// 注册开始注视的监听
    /// </summary>
    /// <param name="starSeeAction"></param>
    public void RegisterStartSeeEvent(Action starSeeAction)
    {
        _StartSeeAction -= starSeeAction;
        _StartSeeAction += starSeeAction;
    }
    
    /// <summary>
    /// 注册开始注视的监听
    /// </summary>
    /// <param name="starSeeEveryOneAction"></param>
    public void RegisterStartSeeEveryOneEvent(Action<GameObject> starSeeEveryOneAction)
    {
        _StartSeeEveryOneAction -= starSeeEveryOneAction;
        _StartSeeEveryOneAction += starSeeEveryOneAction;
    }

    /// <summary>
    /// 注册注视结束的监听
    /// </summary>
    /// <param name="seeEndAction"></param>
    public void RegisterSeeEndEvent(Action seeEndAction)
    {
        _SeeEndAction -= seeEndAction;
        _SeeEndAction += seeEndAction;
    }
    
    /// <summary>
    /// 注册注视结束的监听
    /// </summary>
    /// <param name="seeEndEveryOneAction"></param>
    public void RegisterSeeEndEveryOneEvent(Action<GameObject> seeEndEveryOneAction)
    {
        _SeeEndEveryOneAction -= seeEndEveryOneAction;
        _SeeEndEveryOneAction += seeEndEveryOneAction;
    }
    
    /// <summary>
    /// 注销开始注视的监听
    /// </summary>
    /// <param name="starSeeAction"></param>
    public void UnRegisterStartSeeEvent(Action starSeeAction)
    {
        _StartSeeAction -= starSeeAction;
    }
    
    /// <summary>
    /// 注销开始注视的监听
    /// </summary>
    /// <param name="starSeeAction"></param>
    public void UnRegisterStartSeeEveryOneEvent(Action<GameObject> starSeeAction)
    {
        _StartSeeEveryOneAction -= starSeeAction;
    }

    /// <summary>
    /// 注销注视结束的监听
    /// </summary>
    /// <param name="seeEndEveryOneAction"></param>
    public void UnRegisterSeeEndEveryOneEvent(Action<GameObject> seeEndEveryOneAction)
    {
        _SeeEndEveryOneAction -= seeEndEveryOneAction;
    }
    
    /// <summary>
    /// 注销注视结束的监听
    /// </summary>
    /// <param name="seeEndAction"></param>
    public void UnRegisterSeeEndEvent(Action seeEndAction)
    {
        _SeeEndAction -= seeEndAction;
    }

    //--------------------Protect 保护函数--------------------//

#if UNITY_EDITOR
    /// <summary>
    /// 测试使用
    /// </summary>
    [SerializeField]
    private List<Transform> _testTarget = new List<Transform>();
    private void OnValidate()
    {
        if (_targetTransform == null)
        {
            _targetTransform = transform;
        }
    }
#endif

    //--------------------Private 私有函数--------------------//
    
    #region SeeHero

    private void _SeeHeroAnim()
    {
        if (_heroTargetList == null || _heroTargetList.Count <= 0)
        {
            return;
        }

        StartCoroutine(_SeeHeroAnimCo());
    }

    /// <summary>
    /// 开启队列协程
    /// </summary>
    /// <returns></returns>
    private IEnumerator _SeeHeroAnimCo()
    {
        if (_heroTargetList.Count <= 0)
        {
            yield break;
        }

        _StartSeeAction?.Invoke();
        Vector3 nowPosition = _targetTransform.position;
        Quaternion quaternion = _targetTransform.rotation;
        while (_heroTargetList.Count > 0)
        {
            // Vector3 targetTrans = _heroTargetList.Peek();
            GameObject role = _heroTargetList.Peek();
            if (role == null)
            {
                continue;
            }

            _StartSeeEveryOneAction?.Invoke(role);
            Vector3 targetTrans = role.transform.position;
            targetTrans.y += 1.8f;
            yield return _SeeTargetHeroAnimCo(targetTrans,_targetTransform);
            _SeeEndEveryOneAction?.Invoke(role);
            _heroTargetList.Dequeue();
        }
        
        _targetTransform.position = nowPosition;
        _targetTransform.rotation = quaternion;
        _SeeEndAction?.Invoke();
    }

    /// <summary>
    /// 挪动注视英雄目标
    /// </summary>
    /// <returns></returns>
    private IEnumerator _SeeTargetHeroAnimCo(Vector3 target,Transform oTrans)
    {
        if (oTrans == null)
        {
            yield break;
        }
        Vector3 oPos = oTrans.position;
        Quaternion oQuaternion = oTrans.rotation;
        //这里target不判空了,只有唯一个地方调用并且在调用处判空了
        Vector3 targetPosition = target;
        Vector3 targetPos = targetPosition + (oPos - targetPosition).normalized * heroSeePos;
        Vector3 moveVec = targetPos - oPos;
        
        float durTime = shrinkCurve.keys[shrinkCurve.length - 1].time;
        float passTime = 0f;
        
        _targetTransform.LookAt(target);

        float endTime = Time.realtimeSinceStartup + durTime;
        while (Time.realtimeSinceStartup < endTime)
        {
            float tempTimeScale = (Time.timeScale > 1 ? Time.timeScale : 1f);
            float curWaitTime = Time.fixedUnscaledDeltaTime / tempTimeScale;
            float value = shrinkCurve.Evaluate(passTime);
            _targetTransform.position = oPos + moveVec * value;
            passTime += curWaitTime * tempTimeScale;
            yield return new WaitForSecondsRealtime(curWaitTime);
        }

		//start 这里做一个缓动来解决突然重置的问题
        Vector3 nowPos = _targetTransform.position;
        Quaternion nowQuat = _targetTransform.rotation;
        float t = 0;
        while (t < _resetOriginalPosIntervalTime)
        {
            float lerpT = t / _resetOriginalPosIntervalTime;
            _targetTransform.position = Vector3.Lerp(nowPos, oPos, lerpT);
            _targetTransform.rotation = Quaternion.Lerp(nowQuat,oQuaternion,lerpT);
            t += Time.unscaledDeltaTime;
            yield return new WaitForSecondsRealtime(Time.unscaledDeltaTime);
        }
        //end
        
        _targetTransform.position = oPos;
        _targetTransform.rotation = oQuaternion;
        yield break;
    }

    #endregion

    #endregion
}

修改后的效果

请添加图片描述

对Inspector面板上几个参数进行一下解释

  • HeroSeePos ⇒ 相机在注视目标物体时和目标物体的距离值
  • TargetTransform ⇒ 受作用的物体,案例上受作用物体是相机,也可以设置其他物体进行收缩
  • ResetOriginalPosIntervalTime ⇒ 重置原始位置的缓动时间
  • ShrinkCurve ⇒ 物体收缩动画的曲线控制,可以看到有案例中的曲线有三段,第一段是从初始位置到目标位置,中间那段是停顿时间,最后的那段就是重置曲线,曲线可以根据自己的需要自己调整

对接口进行一下解释

外部调用收缩注视的接口只有一个 SeeHero接口

如果想在不同时机做一些特殊事情的话可以调用下面的几个接口进行注册回调

  • RegisterStartSeeEvent 无参注释开始回调,优先级比有参的高,并且只在队列开始时调用
  • RegisterStartSeeEveryOneEvent 有参注释开始回调,队列每次注释都调用
  • RegisterSeeEndEvent 无参注释结束回调,优先级比有参的高,并且只在队列开始时调用
  • RegisterSeeEndEveryOneEvent 有参注释结束回调,队列每次注释都调用

对应的反注册接口

  • UnRegisterStartSeeEvent
  • UnRegisterStartSeeEveryOneEvent
  • UnRegisterSeeEndEvent
  • UnRegisterSeeEndEveryOneEvent

如果有相同功能,希望可以帮到你。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值