Unity中游戏卡牌滚动效果,EnhanceScrollview(适用于NGUI,UGUI)

最近项目中的活动面板要做来回滚动卡牌预览效果,感觉自己来写的话,也能写,但是可能会比较耗时,看到Github上有开源的项目,于是就借用了,Github的资源地址是:https://github.com/tinyantstudio/EnhancedScrollView,感谢作者tinyantstudio的分享。

本篇博客旨在告诉大家如何利用这个插件。

插件的核心在于工程中的6个脚本,以下是六个脚本的源码:

DragEnhanceView.cs

using UnityEngine;
using System.Collections;

public class DragEnhanceView : MonoBehaviour
{
    private EnhanceScrollView enhanceScrollView;
    public void SetScrollView(EnhanceScrollView view)
    {
        enhanceScrollView = view;
    }

    void OnEnhanceViewDrag(Vector2 delta)
    {
        if (enhanceScrollView != null)
            enhanceScrollView.OnDragEnhanceViewMove(delta);
    }

    void OnEnhaneViewDragEnd()
    {
        if (enhanceScrollView != null)
            enhanceScrollView.OnDragEnhanceViewEnd();
    }
}

EnhanceScrollViewDragController.cs

using UnityEngine;
using System.Collections;

public class EnhanceScrollViewDragController : MonoBehaviour
{
    private Vector2 lastPosition = Vector2.zero;
    private Vector2 cachedPosition = Vector2.zero;
    private GameObject dragTarget;

    private Camera targetCamera;
    private int rayCastMask = 0;
    private bool dragStart = false;

    public void SetTargetCameraAndMask(Camera camera, int mask)
    {
        this.targetCamera = camera;
        this.rayCastMask = mask;
    }

    void Update()
    {
        if (this.targetCamera == null)
            return;
#if UNITY_EDITOR
        ProcessMouseInput();
#elif UNITY_IOS || UNITY_ANDROID
        ProcessTouchInput();
#endif
    }

    /// 
    /// Process Mouse Input
    /// 
    private void ProcessMouseInput()
    {
        if (Input.GetMouseButtonDown(0))
        {
            if (targetCamera == null)
                return;
            dragTarget = RayCast(this.targetCamera, Input.mousePosition);
            lastPosition.x = Input.mousePosition.x;
            lastPosition.y = Input.mousePosition.y;
        }
        if (Input.GetMouseButton(0))
        {
            if (dragTarget == null)
                return;
            cachedPosition.x = Input.mousePosition.x;
            cachedPosition.y = Input.mousePosition.y;
            Vector2 delta = cachedPosition - lastPosition;
            if (!dragStart && delta.sqrMagnitude != 0f)
                dragStart = true;

            if (dragStart)
            {
                // Notify target
                dragTarget.SendMessage("OnEnhanceViewDrag", delta, SendMessageOptions.DontRequireReceiver);
            }
            lastPosition = cachedPosition;
        }

        if (Input.GetMouseButtonUp(0))
        {
            if (dragTarget != null && dragStart)
            {
                dragTarget.SendMessage("OnEnhaneViewDragEnd", SendMessageOptions.DontRequireReceiver);
            }
            dragTarget = null;
            dragStart = false;
        }
    }

    /// 
    /// Process Touch input
    /// 
    private void ProcessTouchInput()
    {
        if (Input.touchCount > 0)
        {
            Touch touch = Input.GetTouch(0);
            if (touch.phase == TouchPhase.Began)
            {
                if (targetCamera == null)
                    return;
                dragTarget = RayCast(this.targetCamera, Input.mousePosition);
            }
            else if (touch.phase == TouchPhase.Moved)
            {
                if (dragTarget == null)
                    return;
                if (!dragStart && touch.deltaPosition.sqrMagnitude != 0f)
                {
                    dragStart = true;
                }
                if (dragStart)
                {
                    // Notify target
                    dragTarget.SendMessage("OnEnhanceViewDrag", touch.deltaPosition, SendMessageOptions.DontRequireReceiver);
                }
            }
            else if (touch.phase == TouchPhase.Ended)
            {
                if (dragTarget != null && dragStart)
                {
                    dragTarget.SendMessage("OnEnhaneViewDragEnd", SendMessageOptions.DontRequireReceiver);
                }
                dragTarget = null;
                dragStart = false;
            }
        }
    }

    public GameObject RayCast(Camera cam, Vector3 inPos)
    {
        Vector3 pos = cam.ScreenToViewportPoint(inPos);
        if (float.IsNaN(pos.x) || float.IsNaN(pos.y))
            return null;
        if (pos.x < 0f || pos.x > 1f || pos.y < 0f || pos.y > 1f) return null;

        Ray ray = cam.ScreenPointToRay(inPos);
        float dis = 100f;
        RaycastHit[] hits = Physics.RaycastAll(ray, dis, rayCastMask);
        if (hits.Length > 0)
        {
            for (int i = 0; i < hits.Length; i++)
            {
                GameObject go = hits[i].collider.gameObject;
                DragEnhanceView dragView = go.GetComponent();
                if (dragView == null)
                    continue;
                else
                {
                    // just return current hover object our drag target
                    return go;
                }
            }
        }
        return null;
    }
}

EnhanceItem.cs

using UnityEngine;
using System.Collections;

public class EnhanceItem : MonoBehaviour
{
    // Start index
    private int curveOffSetIndex = 0;
    public int CurveOffSetIndex
    {
        get { return this.curveOffSetIndex; }
        set { this.curveOffSetIndex = value; }
    }

    // Runtime real index(Be calculated in runtime)
    private int curRealIndex = 0;
    public int RealIndex
    {
        get { return this.curRealIndex; }
        set { this.curRealIndex = value; }
    }

    // Curve center offset 
    private float dCurveCenterOffset = 0.0f;
    public float CenterOffSet
    {
        get { return this.dCurveCenterOffset; }
        set { dCurveCenterOffset = value; }
    }
    private Transform mTrs;

    void Awake()
    {
        mTrs = this.transform;
        OnAwake();
    }

    void Start()
    {
        OnStart();
    }

    // Update Item's status
    // 1. position
    // 2. scale
    // 3. "depth" is 2D or z Position in 3D to set the front and back item
    public void UpdateScrollViewItems(
        float xValue,
        float depthCurveValue,
        int depthFactor,
        float itemCount,
        float yValue,
        float scaleValue)
    {
        Vector3 targetPos = Vector3.one;
        Vector3 targetScale = Vector3.one;
        // position
        targetPos.x = xValue;
        targetPos.y = yValue;
        mTrs.localPosition = targetPos;

        // Set the "depth" of item
        // targetPos.z = depthValue;
        SetItemDepth(depthCurveValue, depthFactor, itemCount);
        // scale
        targetScale.x = targetScale.y = scaleValue;
        mTrs.localScale = targetScale;
    }

    protected virtual void OnClickEnhanceItem()
    {
        EnhanceScrollView.GetInstance.SetHorizontalTargetItemIndex(this);
    }

    protected virtual void OnStart()
    {
    }

    protected virtual void OnAwake()
    {
    }

    protected virtual void SetItemDepth(float depthCurveValue, int depthFactor, float itemCount)
    {
    }

    // Set the item center state
    public virtual void SetSelectState(bool isCenter)
    {
    }
}

EnhanceScrollView.cs

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

public class EnhanceScrollView : MonoBehaviour
{
    public enum InputSystemType
    {
        NGUIAndWorldInput, // use EnhanceScrollViewDragController.cs to get the input(keyboard and touch)
        UGUIInput,         // use UDragEnhanceView for each item to get drag event
    }

    // Input system type(NGUI or 3d world, UGUI)
    public InputSystemType inputType = InputSystemType.NGUIAndWorldInput;
    // Control the item's scale curve
    public AnimationCurve scaleCurve;
    // Control the position curve
    public AnimationCurve positionCurve;
    // Control the "depth"'s curve(In 3d version just the Z value, in 2D UI you can use the depth(NGUI))
    // NOTE:
    // 1. In NGUI set the widget's depth may cause performance problem
    // 2. If you use 3D UI just set the Item's Z position
    public AnimationCurve depthCurve = new AnimationCurve(new Keyframe(0, 0), new Keyframe(0.5f, 1), new Keyframe(1, 0));
    // The start center index
    [Tooltip("The Start center index")]
    public int startCenterIndex = 0;
    // Offset width between item
    public float cellWidth = 10f;
    private float totalHorizontalWidth = 500.0f;
    // vertical fixed position value 
    public float yFixedPositionValue = 46.0f;

    // Lerp duration
    public float lerpDuration = 0.2f;
    private float mCurrentDuration = 0.0f;
    private int mCenterIndex = 0;
    public bool enableLerpTween = true;

    // center and preCentered item
    private EnhanceItem curCenterItem;
    private EnhanceItem preCenterItem;

    // if we can change the target item
    private bool canChangeItem = true;
    private float dFactor = 0.2f;

    // originHorizontalValue Lerp to horizontalTargetValue
    private float originHorizontalValue = 0.1f;
    public float curHorizontalValue = 0.5f;

    // "depth" factor (2d widget depth or 3d Z value)
    private int depthFactor = 5;

    // Drag enhance scroll view
    [Tooltip("Camera for drag ray cast")]
    public Camera sourceCamera;
    private EnhanceScrollViewDragController dragController;

    public void EnableDrag(bool isEnabled)
    {
        if (isEnabled)
        {
            if (inputType == InputSystemType.NGUIAndWorldInput)
            {
                if (sourceCamera == null)
                {
                    Debug.LogError("## Source Camera for drag scroll view is null ##");
                    return;
                }

                if (dragController == null)
                    dragController = gameObject.AddComponent();
                dragController.enabled = true;
                // set the camera and mask
                dragController.SetTargetCameraAndMask(sourceCamera, (1 << LayerMask.NameToLayer("UI")));
            }
        }
        else
        {
            if (dragController != null)
                dragController.enabled = false;
        }
    }

    // targets enhance item in scroll view
    public List listEnhanceItems;
    // sort to get right index
    private List listSortedItems = new List();

    private static EnhanceScrollView instance;
    public static EnhanceScrollView GetInstance
    {
        get { return instance; }
    }

    void Awake()
    {
        instance = this;
    }

    void Start()
    {
        canChangeItem = true;
        int count = listEnhanceItems.Count;
        dFactor = (Mathf.RoundToInt((1f / count) * 10000f)) * 0.0001f;
        mCenterIndex = count / 2;
        if (count % 2 == 0)
            mCenterIndex = count / 2 - 1;
        int index = 0;
        for (int i = count - 1; i >= 0; i--)
        {
            listEnhanceItems[i].CurveOffSetIndex = i;
            listEnhanceItems[i].CenterOffSet = dFactor * (mCenterIndex - index);
            listEnhanceItems[i].SetSelectState(false);
            GameObject obj = listEnhanceItems[i].gameObject;

            if (inputType == InputSystemType.NGUIAndWorldInput)
            {
                DragEnhanceView script = obj.GetComponent();
                if (script != null)
                    script.SetScrollView(this);
            }
            else
            {
                UDragEnhanceView script = obj.GetComponent();
                if (script != null)
                    script.SetScrollView(this);
            }
            index++;
        }

        // set the center item with startCenterIndex
        if (startCenterIndex < 0 || startCenterIndex >= count)
        {
            Debug.LogError("## startCenterIndex < 0 || startCenterIndex >= listEnhanceItems.Count  out of index ##");
            startCenterIndex = mCenterIndex;
        }

        // sorted items
        listSortedItems = new List(listEnhanceItems.ToArray());
        totalHorizontalWidth = cellWidth * count;
        curCenterItem = listEnhanceItems[startCenterIndex];
        curHorizontalValue = 0.5f - curCenterItem.CenterOffSet;
        LerpTweenToTarget(0f, curHorizontalValue, false);

        // 
        // enable the drag actions
        // 
        EnableDrag(true);
    }

    private void LerpTweenToTarget(float originValue, float targetValue, bool needTween = false)
    {
        if (!needTween)
        {
            SortEnhanceItem();
            originHorizontalValue = targetValue;
            UpdateEnhanceScrollView(targetValue);
            this.OnTweenOver();
        }
        else
        {
            originHorizontalValue = originValue;
            curHorizontalValue = targetValue;
            mCurrentDuration = 0.0f;
        }
        enableLerpTween = needTween;
    }

    public void DisableLerpTween()
    {
        this.enableLerpTween = false;
    }

    /// 
    /// Update EnhanceItem state with curve fTime value
    /// 
    public void UpdateEnhanceScrollView(float fValue)
    {
        for (int i = 0; i < listEnhanceItems.Count; i++)
        {
            EnhanceItem itemScript = listEnhanceItems[i];
            float xValue = GetXPosValue(fValue, itemScript.CenterOffSet);
            float scaleValue = GetScaleValue(fValue, itemScript.CenterOffSet);
            float depthCurveValue = depthCurve.Evaluate(fValue + itemScript.CenterOffSet);
            itemScript.UpdateScrollViewItems(xValue, depthCurveValue, depthFactor, listEnhanceItems.Count, yFixedPositionValue, scaleValue);
        }
    }

    void Update()
    {
        if (enableLerpTween)
            TweenViewToTarget();
    }

    private void TweenViewToTarget()
    {
        mCurrentDuration += Time.deltaTime;
        if (mCurrentDuration > lerpDuration)
            mCurrentDuration = lerpDuration;

        float percent = mCurrentDuration / lerpDuration;
        float value = Mathf.Lerp(originHorizontalValue, curHorizontalValue, percent);
        UpdateEnhanceScrollView(value);
        if (mCurrentDuration >= lerpDuration)
        {
            canChangeItem = true;
            enableLerpTween = false;
            OnTweenOver();
        }
    }

    private void OnTweenOver()
    {
        if (preCenterItem != null)
            preCenterItem.SetSelectState(false);
        if (curCenterItem != null)
            curCenterItem.SetSelectState(true);
    }

    // Get the evaluate value to set item's scale
    private float GetScaleValue(float sliderValue, float added)
    {
        float scaleValue = scaleCurve.Evaluate(sliderValue + added);
        return scaleValue;
    }

    // Get the X value set the Item's position
    private float GetXPosValue(float sliderValue, float added)
    {
        float evaluateValue = positionCurve.Evaluate(sliderValue + added) * totalHorizontalWidth;
        return evaluateValue;
    }

    private int GetMoveCurveFactorCount(EnhanceItem preCenterItem, EnhanceItem newCenterItem)
    {
        SortEnhanceItem();
        int factorCount = Mathf.Abs(newCenterItem.RealIndex) - Mathf.Abs(preCenterItem.RealIndex);
        return Mathf.Abs(factorCount);
    }

    // sort item with X so we can know how much distance we need to move the timeLine(curve time line)
    static public int SortPosition(EnhanceItem a, EnhanceItem b) { return a.transform.localPosition.x.CompareTo(b.transform.localPosition.x); }
    private void SortEnhanceItem()
    {
        listSortedItems.Sort(SortPosition);
        for (int i = listSortedItems.Count - 1; i >= 0; i--)
            listSortedItems[i].RealIndex = i;
    }

    public void SetHorizontalTargetItemIndex(EnhanceItem selectItem)
    {
        if (!canChangeItem)
            return;

        if (curCenterItem == selectItem)
            return;

        canChangeItem = false;
        preCenterItem = curCenterItem;
        curCenterItem = selectItem;

        // calculate the direction of moving
        float centerXValue = positionCurve.Evaluate(0.5f) * totalHorizontalWidth;
        bool isRight = false;
        if (selectItem.transform.localPosition.x > centerXValue)
            isRight = true;

        // calculate the offset * dFactor
        int moveIndexCount = GetMoveCurveFactorCount(preCenterItem, selectItem);
        float dvalue = 0.0f;
        if (isRight)
        {
            dvalue = -dFactor * moveIndexCount;
        }
        else
        {
            dvalue = dFactor * moveIndexCount;
        }
        float originValue = curHorizontalValue;
        LerpTweenToTarget(originValue, curHorizontalValue + dvalue, true);
    }

    // Click the right button to select the next item.
    public void OnBtnRightClick()
    {
        if (!canChangeItem)
            return;
        int targetIndex = curCenterItem.CurveOffSetIndex + 1;
        if (targetIndex > listEnhanceItems.Count - 1)
            targetIndex = 0;
        SetHorizontalTargetItemIndex(listEnhanceItems[targetIndex]);
    }

    // Click the left button the select next next item.
    public void OnBtnLeftClick()
    {
        if (!canChangeItem)
            return;
        int targetIndex = curCenterItem.CurveOffSetIndex - 1;
        if (targetIndex < 0)
            targetIndex = listEnhanceItems.Count - 1;
        SetHorizontalTargetItemIndex(listEnhanceItems[targetIndex]);
    }

    public float factor = 0.001f;
    // On Drag Move
    public void OnDragEnhanceViewMove(Vector2 delta)
    {
        // In developing
        if (Mathf.Abs(delta.x) > 0.0f)
        {
            curHorizontalValue += delta.x * factor;
            LerpTweenToTarget(0.0f, curHorizontalValue, false);
        }
    }

    // On Drag End
    public void OnDragEnhanceViewEnd()
    {
        // find closed item to be centered
        int closestIndex = 0;
        float value = (curHorizontalValue - (int)curHorizontalValue);
        float min = float.MaxValue;
        float tmp = 0.5f * (curHorizontalValue < 0 ? -1 : 1);
        for (int i = 0; i < listEnhanceItems.Count; i++)
        {
            float dis = Mathf.Abs(Mathf.Abs(value) - Mathf.Abs((tmp - listEnhanceItems[i].CenterOffSet)));
            if (dis < min)
            {
                closestIndex = i;
                min = dis;
            }
        }
        originHorizontalValue = curHorizontalValue;
        float target = ((int)curHorizontalValue + (tmp - listEnhanceItems[closestIndex].CenterOffSet));
        preCenterItem = curCenterItem;
        curCenterItem = listEnhanceItems[closestIndex];
        LerpTweenToTarget(originHorizontalValue, target, true);
        canChangeItem = false;
    }
}

NGUIEnhanceItem.cs

using UnityEngine;
using System.Collections;

/// 
/// NGUI Enhance item example
/// 
public class NGUIEnhanceItem : EnhanceItem
{
    private UITexture mTexture;

    protected override void OnAwake()
    {
        this.mTexture = GetComponent();
        UIEventListener.Get(this.gameObject).onClick = OnClickNGUIItem;
    }

    private void OnClickNGUIItem(GameObject obj)
    {
        this.OnClickEnhanceItem();
    }

    // Set the item "depth" 2d or 3d
    protected override void SetItemDepth(float depthCurveValue, int depthFactor, float itemCount)
    {
        if (mTexture.depth != (int)Mathf.Abs(depthCurveValue * depthFactor))
            mTexture.depth = (int)Mathf.Abs(depthCurveValue * depthFactor);
    }

    // Item is centered
    public override void SetSelectState(bool isCenter)
    {
        if (mTexture == null)
            mTexture = this.GetComponent();
        if (mTexture != null)
            mTexture.color = isCenter ? Color.white : Color.gray;
    }

    protected override void OnClickEnhanceItem()
    {
        // item was clicked
        base.OnClickEnhanceItem();
    }
}

UGUIEnhanceItem.cs

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.EventSystems;

public class UGUIEnhanceItem : EnhanceItem
{
    private Button uButton;
    private Image image;

    protected override void OnStart()
    {
        image = GetComponent();
        uButton = GetComponent

导入以上6个脚本以后,我们开始制作效果,先从NGUI开始,我们先在场景中,随便添加一个背景,然后,我们在UIRoot下面添加一个空物体,取名Scrollview,添加EnhanceScrollView.cs脚本,然后制作六个Texture作为Scrollview的子物体,添加DragEnhanceView.cs脚本,NGUIEnhanceItem.cs脚本,BoxCollider组件。接着,我们在六个图片下方添加两个Button,作为左右切换卡牌的按钮,在点击事件中,拖入Scrollview,分别添加OnBtnLeftClick,OnBtnRightClick方法。做完以上操作以后,场景大概是这样:

Unity中游戏卡牌滚动效果,EnhanceScrollview(适用于NGUI,UGUI)_第1张图片

接着,我们选中Scrollview,调整脚本参数:

Unity中游戏卡牌滚动效果,EnhanceScrollview(适用于NGUI,UGUI)_第2张图片

ScaleCurve图像参数,设置为如下,左右循环都为pingpong:

Unity中游戏卡牌滚动效果,EnhanceScrollview(适用于NGUI,UGUI)_第3张图片

PositionCurve图像参数如下,左右循环都为loop:

Unity中游戏卡牌滚动效果,EnhanceScrollview(适用于NGUI,UGUI)_第4张图片

DepthCurve图像参数如下,左右循环都为loop:

Unity中游戏卡牌滚动效果,EnhanceScrollview(适用于NGUI,UGUI)_第5张图片

然后把Scrollview的子物体都拖到ListEnhanceItems这个公开数组下:

Unity中游戏卡牌滚动效果,EnhanceScrollview(适用于NGUI,UGUI)_第6张图片

这样,我们就把配置工作都做好了,运行游戏:

Unity中游戏卡牌滚动效果,EnhanceScrollview(适用于NGUI,UGUI)_第7张图片

可以看到,效果还不错,左右滑动或者点击切换按钮,就能实现切换卡牌的功能。

接着,我们看一下UGUI的实现,UGUI的UI布局基本和NGUI保持一致,所不同的是Scrollview的子物体添加的脚本不一样,所需要的脚本及组件如下图所示:

Unity中游戏卡牌滚动效果,EnhanceScrollview(适用于NGUI,UGUI)_第8张图片

然后,还有需要注意的一点是,在Scrollview上的参数配置上,我们需要把InputType这个属性调整为UGUI Input

曲线设置和子物体数组设置和NGUI一样,这里就不再重复了,配置完这些操作以后,运行,UGUI也能实现一样的卡牌滚动效果:

Unity中游戏卡牌滚动效果,EnhanceScrollview(适用于NGUI,UGUI)_第9张图片

以上,感谢Github。

你可能感兴趣的:(Unity)