利用Animation Curve实现列表的曲线滚动

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u010832643/article/details/79166748

0x00 需求的诞生
在上一个版本的开发过程中,美术同事提出了按照《悬空城》的活动列表的形式制作列表的滚动。由于UGUI先有的scroll View 并不支持这种形式,只能自己动手了。
这里写图片描述
左侧的活动列表滚动是沿着曲线滚动的。

0x01 问题的分析
难点有两个:
1.如何构造出列表的曲线,其实曲线就是其中的每个元素所在的x坐标不同,从纵向的角度看构成了一条连续的曲线。
2.何时刷新列表中每个元素的位置。

0x02 解决问题
首先,可以想到利用Animation Curve来构造对x坐标的映射,传入的值显然应该是列表中的元素距离某个pivot(列表控件的中点)的距离。
其次,何时刷新可以利用射线检测来判断,当pivot向下检测到的元素发生变化时刷新。


于是方案1出现了,核心部分为处理射线检测的部分:

    private GameObject GetRayCastGo()
    {
        Vector2 pos = sourceCamera.WorldToScreenPoint(middlePivot.transform.position);
        Ray ray = sourceCamera.ScreenPointToRay(pos);
        RaycastHit[] hits = Physics.RaycastAll(ray, 100f, (1 << LayerMask.NameToLayer("UI")));
        if (hits.Length > 0)
        {
            for (int i = 0; i < hits.Length; i++)
            {
                GameObject go = hits[i].collider.gameObject;
                if (lastGo == go) continue;
                lastGo = go;
                needRefresh = true;
                return go;
            }
        }
        return null;
    }

上述的方案记得要给每个button上增加box collider
这里写图片描述

0x03 优化方案
提交了上述方案后,得到的反馈是效果与《悬空城》有区别,手感体验不好。我后来又仔细体验了《悬空城》中的活动列表。直观的感受手感确实与《悬空城》有很大差距,分析了其中原因:
1.滚动不平滑,方案1中的元素位置刷新由于透过pivot向屏幕内进行射线检测,发现元素不同时才会重新计算x方向上的偏移量,导致滑动有明显的段落感;
2.方案1中元素x方向的偏移是线性的,《悬空城》中列表明显沿着一条弧线滑动,应调整animation curve;
调整后的Animation Curve:
这里写图片描述
3.射线检测毕竟开销太大了,为列表中的每个元素都增加box collider实在是不得已为止。

优化后的完整代码如下:

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

public class AnimationCurveHelper : MonoBehaviour
{
    public AnimationCurve positionCurve;

    private List<GameObject> goList = new List<GameObject>();   // 缓存gameobject列表
    private List<Vector2> goPos = new List<Vector2>();          // 缓存gameobject原始坐标

    private float midPosY = 0f;             // 中点的Y坐标
    private bool isInit = false;            // 是否初始化
    private bool debugMode = false;         // 是否开启调试log

    private void Start()
    {
        SetAnimationCurveData(transform.gameObject);
    }
    private void CustomizedDebug(string input)
    {
        if (debugMode)
        {
            Debug.Log(input);
        }
    }

    private void Update()
    {
        UpdategoList();
    }

    // 外部接口,本例中从Start中调用
    public void SetAnimationCurveData(GameObject midObj)
    {
        if (isInit) return;

        midPosY = midObj.transform.position.y;
        goList.Clear();
        for (int i = 0; i < transform.childCount; ++i)
        {
            goList.Add(transform.GetChild(i).gameObject);
        }

        InitPositionY();
        InitOriginalPos();
        isInit = true;
    }

    public void OpendebugMode(bool setting)
    {
        debugMode = setting;
    }

    private void InitOriginalPos()
    {
        goPos.Clear();
        for (int i = 0; i < goList.Count; ++i)
        {
            goPos.Add(goList[i].transform.position);
        }
    }

    private void InitPositionY()
    {
        RectTransform rectTrans = goList[0].transform.GetComponent<RectTransform>();        // 本例中排列的gameobject高度统一,取第一个gameobject的高度
        float height = rectTrans.rect.height;
        Vector3 vec3;
        for (int i = 0; i < goList.Count; ++i)
        {
            vec3 = goList[i].transform.position;
            goList[i].transform.position = new Vector2(vec3.x, vec3.y - i * (height / 100 - 0.3f));     // 偏移量,根据实际显示效果调整
        }
    }

    void UpdategoList()
    {
        for (int i = 0; i < goList.Count; ++i)
        {
            Transform trans = goList[i].transform;
            float value = Mathf.Abs(trans.position.y - midPosY);  
            float x = GetPositionX(value);
            trans.position = new Vector3(goPos[i].x + x - 0.6f, trans.position.y);     // 偏移量,根据实际显示效果调整

            CustomizedDebug("UpdategoList: " + value + ", trans.position.y:" + trans.position.y + ", trans.position.y: " + trans.position.y + ", midPosY: " + midPosY);
        }
    }

    float GetPositionX(float value)
    {
        float lerpIndex = Mathf.Lerp(1, 0, value / 5);                      // 5是经验数值,约为显示区域显示数量 / 2
        float x = positionCurve.Evaluate(lerpIndex * lerpIndex * 0.5f);     // 利用animation curve进行映射
        CustomizedDebug("GetPositionX lerpIndex: " + lerpIndex + ", x: " + x);
        return x;
    }
}

最终效果:
这里写图片描述
是不是如德芙一般顺滑?哈哈,赶在最后的deadline打入正式版本,即将在这次测试中与玩家见面了,期待测试取得好成绩。

0x04 参考资料
https://docs.unity3d.com/Manual/animeditor-AnimationCurves.html
https://docs.unity3d.com/Manual/EditingCurves.html

展开阅读全文

没有更多推荐了,返回首页