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;
![](/qrcode.jpg)
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;
}
}
最终效果:
0x04 参考资料
https://docs.unity3d.com/Manual/animeditor-AnimationCurves.html
https://docs.unity3d.com/Manual/EditingCurves.html
转载链接: