update 0.6.5
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
using XericLibrary.Runtime.UIGraph;
|
||||
|
||||
namespace XericLibraryEditor.XericComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// UISplineCurveRenderer 的 Inspector:在默认字段上面加一条烘焙工具栏。
|
||||
/// 烘焙系数(采样数、触发时机)本来就是公开字段,这里只补"手动烘一次"和状态读数。
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(UISplineCurveRenderer))]
|
||||
public class UISplineCurveRendererEditor : Editor
|
||||
{
|
||||
private UISplineCurveRenderer script;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
script = (UISplineCurveRenderer)target;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DrawBakeBar();
|
||||
|
||||
serializedObject.Update();
|
||||
DrawDefaultInspector();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 烘焙状态条:读数 + 手动烘焙按钮。
|
||||
/// 烘焙结果落在 XericRectSpline 的序列化字段里,所以按完要标脏,
|
||||
/// 否则存不进场景,下次打开还得重烘。
|
||||
/// </summary>
|
||||
private void DrawBakeBar()
|
||||
{
|
||||
if (script == null)
|
||||
return;
|
||||
|
||||
var path = script.splinePath;
|
||||
var spline = path != null ? path.xericSpline : null;
|
||||
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
|
||||
EditorGUILayout.LabelField("弧长烘焙", EditorStyles.boldLabel);
|
||||
EditorGUILayout.LabelField("采样点",
|
||||
spline != null ? spline.BakeCount.ToString() : "未绑定样条");
|
||||
EditorGUILayout.LabelField("曲线长度",
|
||||
spline != null ? spline.SplineLength.ToString("0.###") : "-");
|
||||
EditorGUILayout.LabelField("已烘焙次数", script.BakeRunCount.ToString());
|
||||
EditorGUILayout.LabelField("校正状态",
|
||||
script.progressMapping == SplineProgressMapping.BakedDistance
|
||||
? (spline != null && spline.BakeCount >= script.bakeSubdivide
|
||||
? "按烘焙距离校正"
|
||||
: "烘焙数据不够,暂时退回参数映射")
|
||||
: "未启用(直接按参数)");
|
||||
|
||||
using (new EditorGUI.DisabledScope(spline == null))
|
||||
{
|
||||
if (GUILayout.Button("烘焙一次", GUILayout.Height(24)))
|
||||
{
|
||||
// XericRectSpline 是普通 [Serializable] 类,不是 Object,
|
||||
// 能登记撤销/标脏的只有承载它的那个路径组件
|
||||
Undo.RecordObject(path, "烘焙样条");
|
||||
script.BakeNow();
|
||||
EditorUtility.SetDirty(path);
|
||||
EditorUtility.SetDirty(script);
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
}
|
||||
|
||||
if (script.bakeTrigger == SplineBakeTrigger.Manual)
|
||||
EditorGUILayout.HelpBox(
|
||||
"触发时机是 Manual:几何怎么改都不会自动烘,进度贴不贴得准就看上面这个按钮按没按。",
|
||||
MessageType.Info);
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4cb828489da70e091cbdc53afe9881ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3dd3579dda7928297735f22b46528170
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,212 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
using XericLibrary.Runtime;
|
||||
using XericLibrary.Runtime.UIGraph;
|
||||
using XericLibrary.Runtime.XericComponent;
|
||||
|
||||
namespace XericLibraryEditor.Spline
|
||||
{
|
||||
/// <summary>
|
||||
/// 样条曲线路径的 Hierarchy 右键创建菜单。
|
||||
/// 此文件直接放在 Release/Editor/ 下由 Unity 编译,
|
||||
/// 不经过外部 DLL 管线,确保菜单立即生效。
|
||||
/// </summary>
|
||||
public static class SplinePathCreateMenuItems
|
||||
{
|
||||
private const string MenuRoot = "GameObject/Xeric Library/Spline/";
|
||||
|
||||
/// <summary>
|
||||
/// 创建带 XericRectSplinePath 的 UI 节点(二维样条)
|
||||
/// </summary>
|
||||
[MenuItem(MenuRoot + "2D Spline", false, 20)]
|
||||
private static void CreateRectSplinePath(MenuCommand menuCommand)
|
||||
{
|
||||
var go = CreateUiGameObject("2D Spline", new Vector2(300f, 150f));
|
||||
var path = go.AddComponent<XericRectSplinePath>();
|
||||
|
||||
// 给两个默认控制点,创建后场景里立刻有点可选中
|
||||
path.xericSpline.Add(MakeDefaultPoint(new Vector2(0.25f, 0.5f)));
|
||||
path.xericSpline.Add(MakeDefaultPoint(new Vector2(0.75f, 0.5f)));
|
||||
|
||||
GameObjectUtility.SetParentAndAlign(go, menuCommand.context as GameObject);
|
||||
Finish(go, "Create 2D Spline");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建带 XericSplinePath 的场景节点(三维样条)
|
||||
/// </summary>
|
||||
[MenuItem(MenuRoot + "3D Spline", false, 21)]
|
||||
private static void CreateSplinePath(MenuCommand menuCommand)
|
||||
{
|
||||
var go = new GameObject("3D Spline");
|
||||
var path = go.AddComponent<XericSplinePath>();
|
||||
|
||||
path.xericSpline.Add(new XericSpline.SplinePoint(Vector3.left));
|
||||
path.xericSpline.Add(new XericSpline.SplinePoint(Vector3.right));
|
||||
|
||||
GameObjectUtility.SetParentAndAlign(go, menuCommand.context as GameObject);
|
||||
Finish(go, "Create 3D Spline");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建由二维样条驱动的 UI 曲线渲染节点,并自动绑定同物体上的样条路径
|
||||
/// </summary>
|
||||
[MenuItem(MenuRoot + "Spline Curve Renderer", false, 22)]
|
||||
private static void CreateSplineCurveRenderer(MenuCommand menuCommand)
|
||||
{
|
||||
var go = CreateUiGameObject("Spline Curve Renderer", new Vector2(300f, 150f));
|
||||
var path = go.AddComponent<XericRectSplinePath>();
|
||||
|
||||
path.xericSpline.Add(MakeDefaultPoint(new Vector2(0.1f, 0.5f)));
|
||||
path.xericSpline.Add(MakeDefaultPoint(new Vector2(0.9f, 0.5f)));
|
||||
|
||||
var renderer = go.AddComponent<UISplineCurveRenderer>();
|
||||
renderer.splinePath = path;
|
||||
|
||||
GameObjectUtility.SetParentAndAlign(go, menuCommand.context as GameObject);
|
||||
Finish(go, "Create Spline Curve Renderer");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 建一条能直接跑起来的曲线进度条:底轨铺满 + 填充到 60%,两条面片共用同一条样条
|
||||
/// </summary>
|
||||
[MenuItem(MenuRoot + "Progress Bar Demo", false, 23)]
|
||||
private static void CreateProgressBarDemo(MenuCommand menuCommand)
|
||||
{
|
||||
var go = CreateUiGameObject("Spline Progress Bar", new Vector2(520f, 220f));
|
||||
var path = go.AddComponent<XericRectSplinePath>();
|
||||
|
||||
// S 形:弧长校正在这条形状上才看得出差别(匀速直线段上校正前后一模一样)
|
||||
AddDemoPoint(path, new Vector2(0.08f, 0.3f), new Vector2(0.28f, 0f));
|
||||
AddDemoPoint(path, new Vector2(0.5f, 0.78f), new Vector2(0.3f, 0f));
|
||||
AddDemoPoint(path, new Vector2(0.92f, 0.22f), new Vector2(0.26f, 0f));
|
||||
|
||||
var renderer = go.AddComponent<UISplineCurveRenderer>();
|
||||
renderer.splinePath = path;
|
||||
// 面片0=底轨铺满并压暗,面片1=填充到 60% 保持全亮
|
||||
renderer.headTailPairs = new[] { 1f, 0f, 0.6f, 0f };
|
||||
renderer.ribbonTints = new[]
|
||||
{
|
||||
new Color32(90, 90, 90, 255),
|
||||
new Color32(255, 255, 255, 255),
|
||||
};
|
||||
renderer.curveWidth = 14f;
|
||||
renderer.fillRibbonIndex = 1;
|
||||
go.AddComponent<UISplineProgressDriver>();
|
||||
ApplyProgressMaterial(renderer);
|
||||
|
||||
PlaceUnderCanvas(go, menuCommand);
|
||||
Finish(go, "Create Spline Progress Bar");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 换上真正消费 uv0.y 的材质,并配一张沿路径方向的手风琴条纹,这样一进来就能看见流动。
|
||||
/// 纹理是运行时生成的(不落盘、不进版本库),要长期用请自己把图拖到 progressTexture 上。
|
||||
/// </summary>
|
||||
private static void ApplyProgressMaterial(UISplineCurveRenderer renderer)
|
||||
{
|
||||
var shader = Shader.Find("XericLibrary/UIGraph/XericUICurveProgress");
|
||||
if (shader == null || !shader.isSupported)
|
||||
{
|
||||
// 必须写全:本文件在 XericLibraryEditor.Spline 里,裸 Debug 会被
|
||||
// 外层命名空间 XericLibraryEditor.Debug 抢走
|
||||
UnityEngine.Debug.LogWarning("[Spline Progress] 找不到 XericUICurveProgress 着色器,演示沿用默认材质(默认材质不读 uv0.y,也没有采贴图)");
|
||||
return;
|
||||
}
|
||||
|
||||
var texture = new Texture2D(4, 128, TextureFormat.RGBA32, false);
|
||||
texture.wrapMode = TextureWrapMode.Repeat;
|
||||
for (int y = 0; y < texture.height; y++)
|
||||
{
|
||||
// 沿 uv.y(也就是路径进度)方向的亮带 + 暗谷
|
||||
float wave = 0.5f + 0.5f * Mathf.Sin(y * Mathf.PI * 2f / texture.height * 3f);
|
||||
var color = new Color(1f, 1f, 1f, Mathf.Lerp(0.25f, 1f, wave));
|
||||
for (int x = 0; x < texture.width; x++)
|
||||
texture.SetPixel(x, y, color);
|
||||
}
|
||||
texture.Apply();
|
||||
|
||||
var material = new Material(shader);
|
||||
material.SetFloat("_Tile", 5f);
|
||||
material.SetFloat("_Scroll", 0.45f);
|
||||
material.SetFloat("_EdgeExp", 0.85f);
|
||||
material.SetFloat("_EdgeStepRange", 0.12f);
|
||||
|
||||
renderer.progressTexture = texture;
|
||||
renderer.material = material;
|
||||
}
|
||||
|
||||
// 工具方法
|
||||
|
||||
/// <summary>默认手柄偏移(锚点空间):手柄全零时贝塞尔段退化成直线,看不出这是一条曲线</summary>
|
||||
private const float DefaultHandleOffset = 0.1f;
|
||||
|
||||
/// <summary>
|
||||
/// 造一个带默认手柄的端点。与 2D 编辑器里插入端点用的是同一套默认值,
|
||||
/// 两边各自实现(编辑器在 DLL 里,这里要当场编译,不能等它重新构建)。
|
||||
/// </summary>
|
||||
private static XericRectSpline.RectSplinePoint MakeDefaultPoint(Vector2 anchor)
|
||||
{
|
||||
var point = new XericRectSpline.RectSplinePoint(anchor);
|
||||
// Local 是相对锚点的偏移;StartHandle/EndHandle 是叠在锚点上的绝对值
|
||||
point.StartHandleLocal = new Vector2(-DefaultHandleOffset, 0f);
|
||||
point.EndHandleLocal = new Vector2(DefaultHandleOffset, 0f);
|
||||
return point;
|
||||
}
|
||||
|
||||
private static void AddDemoPoint(XericRectSplinePath path, Vector2 anchor, Vector2 handleOffset)
|
||||
{
|
||||
var point = new XericRectSpline.RectSplinePoint(anchor);
|
||||
// Start/EndHandle 是"叠在锚点上的"绝对锚点值,不是偏移量
|
||||
point.EndHandle = anchor + handleOffset;
|
||||
point.StartHandle = anchor - handleOffset;
|
||||
path.xericSpline.Add(point);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Graphic 不在 Canvas 下就画不出来,演示物必须落到某个 Canvas 里;
|
||||
/// 场景里没有就现建一个(与 BlueprintMenuItems 同一套路)
|
||||
/// </summary>
|
||||
private static void PlaceUnderCanvas(GameObject go, MenuCommand menuCommand)
|
||||
{
|
||||
var parent = menuCommand.context as GameObject;
|
||||
if (parent != null && parent.GetComponentInParent<Canvas>() != null)
|
||||
{
|
||||
GameObjectUtility.SetParentAndAlign(go, parent);
|
||||
return;
|
||||
}
|
||||
|
||||
var canvas = Object.FindObjectOfType<Canvas>();
|
||||
if (canvas == null)
|
||||
{
|
||||
var canvasGo = new GameObject("Spline Demo Canvas",
|
||||
typeof(Canvas), typeof(UnityEngine.UI.CanvasScaler), typeof(UnityEngine.UI.GraphicRaycaster));
|
||||
canvasGo.GetComponent<Canvas>().renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
Undo.RegisterCreatedObjectUndo(canvasGo, "Create Canvas For Spline Demo");
|
||||
}
|
||||
|
||||
GameObjectUtility.SetParentAndAlign(go, canvas.gameObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建带 RectTransform 的 UI 节点;界面样条的计算范围就是这个矩形
|
||||
/// </summary>
|
||||
private static GameObject CreateUiGameObject(string name, Vector2 size)
|
||||
{
|
||||
var go = new GameObject(name, typeof(RectTransform));
|
||||
var rt = go.GetComponent<RectTransform>();
|
||||
rt.sizeDelta = size;
|
||||
rt.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
rt.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
rt.pivot = new Vector2(0.5f, 0.5f);
|
||||
return go;
|
||||
}
|
||||
|
||||
private static void Finish(GameObject go, string undoName)
|
||||
{
|
||||
Undo.RegisterCreatedObjectUndo(go, undoName);
|
||||
Selection.activeObject = go;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 543e1ea7a6089fd6a835ee1d69cd721c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -7,4 +7,4 @@ ScriptedImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {instanceID: 0}
|
||||
script: {fileID: 11500000, guid: 625f186215c104763be7675aa2d941aa, type: 3}
|
||||
|
||||
@@ -48,6 +48,6 @@ Material:
|
||||
- _Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
- _GridBackgroundColor: {r: 0.8666667, g: 0.8862745, b: 0.93333334, a: 1}
|
||||
- _GridColor: {r: 0.8666667, g: 0.8862745, b: 0.93333334, a: 1}
|
||||
- _Transform: {r: 9.787723, g: 8.369877, b: -4.381715, a: -3.846801}
|
||||
- _Transform: {r: 14.84472, g: 12.69432, b: 32.392414, a: -6.6925216}
|
||||
- _Vector0: {r: 0.95, g: 1, b: 0, a: 0}
|
||||
m_BuildTextureStacks: []
|
||||
|
||||
@@ -7,4 +7,4 @@ ScriptedImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {instanceID: 0}
|
||||
script: {fileID: 11500000, guid: 625f186215c104763be7675aa2d941aa, type: 3}
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
Shader "XericLibrary/UIGraph/XericUICurveProgress"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
[PerRendererData] _MainTex ("Progress Texture", 2D) = "white" {}
|
||||
_Color ("Tint", Color) = (1,1,1,1)
|
||||
|
||||
_Tile ("Tile Count Along Path", Float) = 1
|
||||
_Scroll ("Scroll Speed (along +progress)", Float) = 0
|
||||
_ScrollPhase ("Scroll Phase", Range(0, 1)) = 0
|
||||
|
||||
_Cutoff ("Progress Cutoff", Range(0, 1)) = 1
|
||||
_CutoffSoftness ("Cutoff Softness", Range(0, 0.5)) = 0.02
|
||||
|
||||
_EdgeExp ("Edge Exp", Range(0.001, 1)) = 0.8671591
|
||||
_EdgeStep ("Edge Step", Range(0, 1)) = 0
|
||||
_EdgeStepRange ("Edge Step Range", Range(0, 0.5)) = 0.1
|
||||
|
||||
_StencilComp ("Stencil Comparison", Float) = 8
|
||||
_Stencil ("Stencil ID", Float) = 0
|
||||
_StencilOp ("Stencil Operation", Float) = 0
|
||||
_StencilWriteMask ("Stencil Write Mask", Float) = 255
|
||||
_StencilReadMask ("Stencil Read Mask", Float) = 255
|
||||
_ColorMask ("Color Mask", Float) = 15
|
||||
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" }
|
||||
|
||||
Stencil
|
||||
{
|
||||
Ref [_Stencil]
|
||||
ReadMask [_StencilReadMask]
|
||||
WriteMask [_StencilWriteMask]
|
||||
Comp [_StencilComp]
|
||||
Pass [_StencilOp]
|
||||
}
|
||||
|
||||
Cull Off
|
||||
Lighting Off
|
||||
ZWrite Off
|
||||
ZTest [unity_GUIZTestMode]
|
||||
Blend One OneMinusSrcAlpha
|
||||
ColorMask [_ColorMask]
|
||||
|
||||
Pass
|
||||
{
|
||||
Name "Default"
|
||||
CGPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#pragma target 3.0
|
||||
|
||||
#include "UnityCG.cginc"
|
||||
#include "UnityUI.cginc"
|
||||
|
||||
#pragma multi_compile_local _ UNITY_UI_CLIP_RECT
|
||||
#pragma multi_compile_local _ UNITY_UI_ALPHACLIP
|
||||
|
||||
struct appdata_t
|
||||
{
|
||||
float4 vertex : POSITION;
|
||||
float4 color : COLOR;
|
||||
float2 uv0 : TEXCOORD0;
|
||||
float4 uv1 : TEXCOORD1;
|
||||
UNITY_VERTEX_INPUT_INSTANCE_ID
|
||||
};
|
||||
|
||||
struct v2f
|
||||
{
|
||||
float4 vertex : SV_POSITION;
|
||||
fixed4 color : COLOR;
|
||||
float2 uv0 : TEXCOORD0;
|
||||
float4 worldPosition : TEXCOORD1;
|
||||
float4 mask : TEXCOORD2;
|
||||
float featherOn : TEXCOORD3;
|
||||
UNITY_VERTEX_OUTPUT_STEREO
|
||||
};
|
||||
|
||||
sampler2D _MainTex;
|
||||
fixed4 _Color;
|
||||
float4 _MainTex_ST;
|
||||
float4 _ClipRect;
|
||||
float _UIMaskSoftnessX;
|
||||
float _UIMaskSoftnessY;
|
||||
|
||||
float _Tile;
|
||||
float _Scroll;
|
||||
float _ScrollPhase;
|
||||
float _Cutoff;
|
||||
float _CutoffSoftness;
|
||||
float _EdgeExp;
|
||||
float _EdgeStep;
|
||||
float _EdgeStepRange;
|
||||
|
||||
v2f vert(appdata_t v)
|
||||
{
|
||||
v2f OUT;
|
||||
UNITY_SETUP_INSTANCE_ID(v);
|
||||
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
|
||||
|
||||
float4 vPosition = UnityObjectToClipPos(v.vertex);
|
||||
OUT.worldPosition = v.vertex;
|
||||
OUT.vertex = vPosition;
|
||||
|
||||
float2 pixelSize = vPosition.w;
|
||||
pixelSize /= float2(1, 1) * abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy));
|
||||
|
||||
float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
|
||||
OUT.mask = float4(v.vertex.xy * 2 - clampedRect.xy - clampedRect.zw,
|
||||
0.25 / (0.25 * half2(_UIMaskSoftnessX, _UIMaskSoftnessY) + abs(pixelSize.xy)));
|
||||
|
||||
OUT.uv0 = v.uv0;
|
||||
OUT.featherOn = v.uv1.x;
|
||||
OUT.color = v.color * _Color;
|
||||
return OUT;
|
||||
}
|
||||
|
||||
fixed4 frag(v2f IN) : SV_Target
|
||||
{
|
||||
// 与 UI 默认着色器同样的处理:把插值出来的 alpha 对齐到 1/256,
|
||||
// 否则预乘混合下容易抖
|
||||
const half alphaPrecision = half(0xff);
|
||||
const half invAlphaPrecision = half(1.0 / alphaPrecision);
|
||||
IN.color.a = round(IN.color.a * alphaPrecision) * invAlphaPrecision;
|
||||
|
||||
// uv0.y 已经是"沿整条面片的进度",横向不重复,纵向按 _Tile 平铺并可随时间流动
|
||||
float2 sampleUV = float2(
|
||||
IN.uv0.x,
|
||||
IN.uv0.y * _Tile - _Time.y * _Scroll + _ScrollPhase);
|
||||
fixed4 tex = tex2D(_MainTex, sampleUV);
|
||||
|
||||
float edge = smoothstep(
|
||||
_EdgeStep - _EdgeStepRange * 2.0,
|
||||
_EdgeStep + _EdgeStepRange,
|
||||
sin(IN.uv0.x * UNITY_PI));
|
||||
float4 color = lerp(IN.color, IN.color * pow(saturate(edge), _EdgeExp), IN.featherOn);
|
||||
color *= tex;
|
||||
float soft = max(_CutoffSoftness, 1e-4);
|
||||
color.a *= smoothstep(_Cutoff + soft, _Cutoff - soft, IN.uv0.y);
|
||||
|
||||
#ifdef UNITY_UI_CLIP_RECT
|
||||
color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
|
||||
#endif
|
||||
|
||||
#ifdef UNITY_UI_ALPHACLIP
|
||||
clip(color.a - 0.001);
|
||||
#endif
|
||||
|
||||
color.rgb *= color.a;
|
||||
return color;
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
Fallback Off
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6e11e3a532c926db17c9a5d958de6591
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -5,6 +5,7 @@ using UnityEngine.InputSystem;
|
||||
#endif
|
||||
using XericLibrary.Runtime.Blueprint.Canvas;
|
||||
using XericLibrary.Runtime.Blueprint.Element;
|
||||
using XericLibrary.Runtime.SuperStyleSheet;
|
||||
|
||||
namespace XericLibrary.Runtime.Blueprint
|
||||
{
|
||||
@@ -33,7 +34,7 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
[Header("Input")]
|
||||
[Tooltip("新输入系统 InputActionAsset。留空则仅支持鼠标基础操作(中键拖动平移、滚轮缩放)。")]
|
||||
[SerializeField] private Object _inputActions;
|
||||
|
||||
|
||||
public Object InputActions
|
||||
{
|
||||
get { return _inputActions; }
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,504 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericLibrary.Runtime.UIGraph
|
||||
{
|
||||
/// <summary>
|
||||
/// 一条面片切出来的一个贝塞尔小块:几何、逐点宽度、端点颜色、uv 进度区间。
|
||||
/// 位置都在 Graphic 的局部平面内,与渲染器同一坐标约定。
|
||||
/// </summary>
|
||||
internal struct RibbonPiece
|
||||
{
|
||||
public Vector2 c0;
|
||||
public Vector2 c1;
|
||||
public Vector2 c2;
|
||||
public Vector2 c3;
|
||||
public float w0;
|
||||
public float w1;
|
||||
public float w2;
|
||||
public float w3;
|
||||
public Color32 startColor;
|
||||
public Color32 endColor;
|
||||
public float uvYStart;
|
||||
public float uvYEnd;
|
||||
public int ribbonIndex;
|
||||
/// <summary>本小块占它所在样条段的比例,用来折算细分数</summary>
|
||||
public float segmentFraction;
|
||||
}
|
||||
|
||||
/// <summary>面片上 uv0.y 代表什么进度</summary>
|
||||
public enum SplineUvProgressSpace
|
||||
{
|
||||
/// <summary>每条面片各自 0→1:尾部 0、头部 1,效果跟着已填充部分伸缩</summary>
|
||||
RibbonLocal,
|
||||
/// <summary>钉在整条样条上:贴图位置不随进度变化</summary>
|
||||
SplineGlobal,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 样条面片切分器:把若干条 (头部,尾部) 面片切成逐段贝塞尔小块。
|
||||
/// <code>
|
||||
/// 纯函数:不碰 MonoBehaviour,也不碰 Debug.Log —— 后者在 Unity 之外会抛
|
||||
/// "ECall could not be defined",那样就没法在离线门里直接跑出数来验证。
|
||||
/// 几何切分用贝塞尔在 [t0,t1] 上的精确限制(Restrict);
|
||||
/// t0=0,t1=1 时逐点还原原始四个控制点,所以整段不产生任何漂移。
|
||||
/// </code>
|
||||
/// </summary>
|
||||
internal static class SplineRibbonBuilder
|
||||
{
|
||||
internal struct Request
|
||||
{
|
||||
/// <summary>各控制点锚点(局部坐标),长度 = pointCount</summary>
|
||||
public Vector2[] anchors;
|
||||
/// <summary>各点的出手柄(局部坐标世界式,即已含锚点)</summary>
|
||||
public Vector2[] outHandles;
|
||||
/// <summary>各点的入手柄(局部坐标世界式,即已含锚点)</summary>
|
||||
public Vector2[] inHandles;
|
||||
/// <summary>各点颜色</summary>
|
||||
public Color[] pointColors;
|
||||
/// <summary>各点宽度</summary>
|
||||
public float[] pointWidths;
|
||||
|
||||
public int pointCount;
|
||||
public int segmentCount;
|
||||
public bool curveLoop;
|
||||
|
||||
/// <summary>偶数下标=头部、奇数下标=尾部,成对;取值 [0,1]</summary>
|
||||
public float[] headTailPairs;
|
||||
/// <summary>可空;与面片同序时逐条叠乘到样条颜色上</summary>
|
||||
public Color32[] ribbonTints;
|
||||
|
||||
/// <summary>进度 → 参数 t(距离校正就落在这个回调里)</summary>
|
||||
public Func<float, float> progressToParametric;
|
||||
|
||||
public SplineUvProgressSpace uvSpace;
|
||||
/// <summary>每条面片至少切成几块;决定 uv0.y 与视觉距离对齐的精度</summary>
|
||||
public int chunksPerRibbon;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切分所有面片。无效输入返回空表而不是抛异常。
|
||||
/// </summary>
|
||||
internal static List<RibbonPiece> Build(Request req)
|
||||
{
|
||||
var pieces = new List<RibbonPiece>();
|
||||
|
||||
if (req.anchors == null || req.outHandles == null || req.inHandles == null)
|
||||
return pieces;
|
||||
if (req.headTailPairs == null || req.progressToParametric == null)
|
||||
return pieces;
|
||||
if (req.pointCount < 2 || req.segmentCount < 1 || req.headTailPairs.Length < 2)
|
||||
return pieces;
|
||||
|
||||
int chunks = Mathf.Max(1, req.chunksPerRibbon);
|
||||
int ribbonCount = req.headTailPairs.Length / 2;
|
||||
|
||||
for (int r = 0; r < ribbonCount; r++)
|
||||
{
|
||||
float head = Mathf.Clamp01(req.headTailPairs[r * 2]);
|
||||
float tail = Mathf.Clamp01(req.headTailPairs[r * 2 + 1]);
|
||||
if (Mathf.Abs(head - tail) <= 1e-6f)
|
||||
continue;
|
||||
|
||||
BuildRibbon(req, r, tail, head, chunks, pieces);
|
||||
}
|
||||
|
||||
return pieces;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 面片沿路径的一段连续进度区间(起点进度、终点进度、起终点的参数 t)
|
||||
/// </summary>
|
||||
private struct Span
|
||||
{
|
||||
public float fromProgress;
|
||||
public float toProgress;
|
||||
public float fromParam;
|
||||
public float toParam;
|
||||
public float ArcLength;
|
||||
}
|
||||
|
||||
private static void BuildRibbon(Request req, int ribbonIndex, float tail, float head,
|
||||
int chunks, List<RibbonPiece> output)
|
||||
{
|
||||
var spans = ResolveSpans(req, tail, head);
|
||||
if (spans.Count == 0)
|
||||
return;
|
||||
|
||||
float segments = req.segmentCount;
|
||||
|
||||
Color32 tint = (req.ribbonTints != null && ribbonIndex < req.ribbonTints.Length)
|
||||
? req.ribbonTints[ribbonIndex]
|
||||
: new Color32(255, 255, 255, 255);
|
||||
bool global = req.uvSpace == SplineUvProgressSpace.SplineGlobal;
|
||||
|
||||
int firstOfRibbon = output.Count;
|
||||
// 与本次新增的 piece 一一对应的实测弧长:RibbonLocal 的 uv 分母就用这一份加总
|
||||
var arcs = new List<float>();
|
||||
|
||||
foreach (var span in spans)
|
||||
{
|
||||
if (span.ArcLength <= 0f)
|
||||
continue;
|
||||
|
||||
var cuts = CollectCuts(req, span);
|
||||
int intervalCount = cuts.Count - 1;
|
||||
float spanWalked = 0f;
|
||||
|
||||
for (int i = 0; i < intervalCount; i++)
|
||||
{
|
||||
float a = cuts[i];
|
||||
float b = cuts[i + 1];
|
||||
float arc = MeasureArc(req, a, b);
|
||||
|
||||
int seg = Mathf.Clamp(Mathf.FloorToInt(a * segments), 0, req.segmentCount - 1);
|
||||
float localA = Mathf.Clamp01(a * segments - seg);
|
||||
float localB = Mathf.Clamp01(b * segments - seg);
|
||||
if (localB - localA <= 1e-6f)
|
||||
continue;
|
||||
|
||||
Vector2 p0, p1, p2, p3;
|
||||
GetSegment(req, seg, out p0, out p1, out p2, out p3);
|
||||
float q0, q1, q2, q3;
|
||||
GetWidthSegment(req, seg, out q0, out q1, out q2, out q3);
|
||||
|
||||
var piece = new RibbonPiece();
|
||||
piece.ribbonIndex = ribbonIndex;
|
||||
piece.segmentFraction = localB - localA;
|
||||
|
||||
Restrict(p0, p1, p2, p3, localA, localB,
|
||||
out piece.c0, out piece.c1, out piece.c2, out piece.c3);
|
||||
Restrict(q0, q1, q2, q3, localA, localB,
|
||||
out piece.w0, out piece.w1, out piece.w2, out piece.w3);
|
||||
|
||||
piece.startColor = Tint(ColorAt(req, a), tint);
|
||||
piece.endColor = Tint(ColorAt(req, b), tint);
|
||||
|
||||
// SplineGlobal:落在路径上的绝对进度,环绕时在 1→0 处天然断开。
|
||||
// spanWalked 只在这里用 —— 它要的是"在本区间内走了多长"。
|
||||
// RibbonLocal 的 uv 留给下面那一趟,用和分子同一份来源的弧长来分配。
|
||||
if (global)
|
||||
{
|
||||
piece.uvYStart = ProgressAt(span, spanWalked / span.ArcLength);
|
||||
piece.uvYEnd = ProgressAt(span, (spanWalked + arc) / span.ArcLength);
|
||||
spanWalked += arc;
|
||||
output.Add(piece);
|
||||
continue;
|
||||
}
|
||||
|
||||
spanWalked += arc;
|
||||
output.Add(piece);
|
||||
arcs.Add(arc);
|
||||
}
|
||||
}
|
||||
|
||||
if (global)
|
||||
return;
|
||||
|
||||
// RibbonLocal:整条面片(含环绕的两段)合成一个连续的 0→1。
|
||||
// <code>
|
||||
// 分子分母必须同源。以前分母用 MeasureSpanArc(整个区间一次量完的折线长),
|
||||
// 分子用逐小块的 MeasureArc(切得更细、量得更长),两者能差 0.2%,
|
||||
// 于是中间的小块算出 uv > 1,末片又被钉成 1 ⇒ 出现"某片 uv 非增"。
|
||||
// 更糟的是原先靠"emit 计数 == 预计块数"来认最后一块:被跳过的退化小块
|
||||
// 也已经计过一次数 ⇒ 1 被压到中间某块上,下一块又从 0.6 起重来 ——
|
||||
// 真机门量到的"跨尾面片 uv 回跳 4 次"就是这么来的。
|
||||
// 现在出完片再按这份弧长统一分配,两端自然落在 0 与 1,中间跳过几块都不影响单调。
|
||||
// </code>
|
||||
float totalArc = 0f;
|
||||
for (int i = 0; i < arcs.Count; i++)
|
||||
totalArc += arcs[i];
|
||||
|
||||
if (arcs.Count == 0 || totalArc <= 0f)
|
||||
{
|
||||
output.RemoveRange(firstOfRibbon, output.Count - firstOfRibbon);
|
||||
return;
|
||||
}
|
||||
|
||||
float cursor = 0f;
|
||||
for (int i = 0; i < arcs.Count; i++)
|
||||
{
|
||||
int pieceIndex = firstOfRibbon + i;
|
||||
var piece = output[pieceIndex];
|
||||
float from = cursor;
|
||||
cursor += arcs[i];
|
||||
|
||||
piece.uvYStart = from / totalArc;
|
||||
piece.uvYEnd = i == arcs.Count - 1 ? 1f : cursor / totalArc;
|
||||
output[pieceIndex] = piece;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 把一对 (头部,尾部) 展开成沿路径的区间。
|
||||
/// head > tail 是一个区间;循环样条上 head < tail 表示"跨过终点绕回去",
|
||||
/// 拆成 [tail,1] 与 [0,head] 两个区间,面片仍然连续。非循环样条不做环绕,直接不出片。
|
||||
/// </summary>
|
||||
private static List<Span> ResolveSpans(Request req, float tail, float head)
|
||||
{
|
||||
var spans = new List<Span>(req.curveLoop ? 2 : 1);
|
||||
|
||||
if (head > tail)
|
||||
{
|
||||
AddSpan(spans, req, tail, head);
|
||||
return spans;
|
||||
}
|
||||
|
||||
if (!req.curveLoop)
|
||||
return spans;
|
||||
|
||||
AddSpan(spans, req, tail, 1f);
|
||||
AddSpan(spans, req, 0f, head);
|
||||
return spans;
|
||||
}
|
||||
|
||||
private static void AddSpan(List<Span> spans, Request req, float fromProgress, float toProgress)
|
||||
{
|
||||
if (toProgress - fromProgress <= 1e-6f)
|
||||
return;
|
||||
|
||||
// 退化:校正后端点仍然重合(长度为零的样条会出现),这个区间不出
|
||||
float fromParam = Mathf.Clamp01(req.progressToParametric(fromProgress));
|
||||
float toParam = Mathf.Clamp01(req.progressToParametric(toProgress));
|
||||
if (toParam - fromParam <= 1e-6f)
|
||||
return;
|
||||
|
||||
var span = new Span
|
||||
{
|
||||
fromProgress = fromProgress,
|
||||
toProgress = toProgress,
|
||||
fromParam = fromParam,
|
||||
toParam = toParam,
|
||||
};
|
||||
span.ArcLength = MeasureSpanArc(req, fromParam, toParam);
|
||||
spans.Add(span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 跨段的区间弧长:先按样条段边界切开,再逐段量。
|
||||
/// MeasureArc 只能量"同一段内"的两点,直接拿来量整段会静默少算。
|
||||
/// </summary>
|
||||
private static float MeasureSpanArc(Request req, float a, float b)
|
||||
{
|
||||
float segments = req.segmentCount;
|
||||
float sum = 0f;
|
||||
float cursor = a;
|
||||
|
||||
while (cursor < b - 1e-6f)
|
||||
{
|
||||
int seg = Mathf.Clamp(Mathf.FloorToInt(cursor * segments), 0, req.segmentCount - 1);
|
||||
float nextBoundary = (seg + 1f) / segments;
|
||||
float stop = Mathf.Min(b, nextBoundary);
|
||||
sum += MeasureArc(req, cursor, stop);
|
||||
cursor = stop;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
/// <summary>区间内按弧长占比折算出该处的进度值</summary>
|
||||
private static float ProgressAt(Span span, float fraction)
|
||||
{
|
||||
return Mathf.Clamp01(span.fromProgress + (span.toProgress - span.fromProgress) * fraction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切分点:等进度网格 + 所有落在区间内的样条段边界,去重后升序。
|
||||
/// 有了段边界,每个小块必然完整落在同一段贝塞尔里。
|
||||
/// </summary>
|
||||
private static List<float> CollectCuts(Request req, Span span)
|
||||
{
|
||||
int chunks = Mathf.Max(1, req.chunksPerRibbon);
|
||||
var cuts = new List<float>(chunks + req.segmentCount + 2) { span.fromParam, span.toParam };
|
||||
|
||||
for (int s = 1; s < req.segmentCount; s++)
|
||||
{
|
||||
float boundary = (float)s / req.segmentCount;
|
||||
if (boundary > span.fromParam && boundary < span.toParam)
|
||||
cuts.Add(boundary);
|
||||
}
|
||||
|
||||
for (int k = 1; k < chunks; k++)
|
||||
{
|
||||
float progress = span.fromProgress
|
||||
+ (span.toProgress - span.fromProgress) * k / chunks;
|
||||
cuts.Add(Mathf.Clamp01(req.progressToParametric(progress)));
|
||||
}
|
||||
|
||||
cuts.Sort();
|
||||
|
||||
var unique = new List<float>(cuts.Count);
|
||||
for (int i = 0; i < cuts.Count; i++)
|
||||
{
|
||||
if (unique.Count > 0 && cuts[i] - unique[unique.Count - 1] <= 1e-6f)
|
||||
continue;
|
||||
unique.Add(cuts[i]);
|
||||
}
|
||||
return unique;
|
||||
}
|
||||
|
||||
/// <summary>段 seg 的四个控制点:本点锚、本点出手柄、下一点入手柄、下一点锚</summary>
|
||||
private static void GetSegment(Request req, int seg,
|
||||
out Vector2 p0, out Vector2 p1, out Vector2 p2, out Vector2 p3)
|
||||
{
|
||||
int next = NextPoint(req, seg);
|
||||
p0 = req.anchors[seg];
|
||||
p1 = req.outHandles[seg];
|
||||
p2 = req.inHandles[next];
|
||||
p3 = req.anchors[next];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 宽度也走同一条贝塞尔:本段两端取本点宽、下段两端取下点宽。
|
||||
/// 与几何用同一个 [t0,t1] 限制,带宽过渡才不会和形状错位。
|
||||
/// </summary>
|
||||
private static void GetWidthSegment(Request req, int seg,
|
||||
out float w0, out float w1, out float w2, out float w3)
|
||||
{
|
||||
int next = NextPoint(req, seg);
|
||||
w0 = WidthAt(req, seg);
|
||||
w1 = WidthAt(req, seg);
|
||||
w2 = WidthAt(req, next);
|
||||
w3 = WidthAt(req, next);
|
||||
}
|
||||
|
||||
private static int NextPoint(Request req, int seg)
|
||||
{
|
||||
return req.curveLoop
|
||||
? PositiveMod(seg + 1, req.pointCount)
|
||||
: Mathf.Min(seg + 1, req.pointCount - 1);
|
||||
}
|
||||
|
||||
private static float WidthAt(Request req, int index)
|
||||
{
|
||||
if (req.pointWidths == null || req.pointWidths.Length == 0)
|
||||
return 0f;
|
||||
return req.pointWidths[Mathf.Clamp(index, 0, req.pointWidths.Length - 1)];
|
||||
}
|
||||
|
||||
/// <summary>按渐层取色;索引越界一律夹紧,绝不裸索引(渐层列表可能比点少)</summary>
|
||||
private static Color ColorAt(Request req, float parametric)
|
||||
{
|
||||
if (req.pointColors == null || req.pointColors.Length == 0)
|
||||
return Color.white;
|
||||
|
||||
int count = req.pointColors.Length;
|
||||
// 与 XericRectSpline.GetDeltaT 同一套映射(那个是 internal,Release 侧够不到),
|
||||
// 所以取色和几何形状保持同相
|
||||
float usable = req.curveLoop ? count : Mathf.Max(1, count - 1);
|
||||
float scaled = Mathf.Clamp01(parametric) * usable;
|
||||
int index = Mathf.FloorToInt(scaled);
|
||||
float dt = Mathf.Clamp01(scaled - index);
|
||||
|
||||
int a = req.curveLoop ? PositiveMod(index, count) : Mathf.Clamp(index, 0, count - 1);
|
||||
int b = req.curveLoop ? PositiveMod(index + 1, count) : Mathf.Clamp(index + 1, 0, count - 1);
|
||||
return Color.Lerp(req.pointColors[a], req.pointColors[b], dt);
|
||||
}
|
||||
|
||||
private static Color32 Tint(Color baseColor, Color32 tint)
|
||||
{
|
||||
var t = (Color)tint;
|
||||
var r = new Color(baseColor.r * t.r, baseColor.g * t.g,
|
||||
baseColor.b * t.b, baseColor.a * t.a);
|
||||
return r;
|
||||
}
|
||||
|
||||
private static int PositiveMod(int value, int modulus)
|
||||
{
|
||||
if (modulus <= 0)
|
||||
return 0;
|
||||
return ((value % modulus) + modulus) % modulus;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- 贝塞尔裁剪
|
||||
|
||||
/// <summary>
|
||||
/// 三次贝塞尔在 [t0,t1] 上的精确限制:
|
||||
/// Q0=B(t0),Q1=B(t0)+(t1-t0)/3·B'(t0),Q2=B(t1)-(t1-t0)/3·B'(t1),Q3=B(t1)。
|
||||
/// </summary>
|
||||
internal static void Restrict(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3,
|
||||
float t0, float t1, out Vector2 q0, out Vector2 q1, out Vector2 q2, out Vector2 q3)
|
||||
{
|
||||
float span = t1 - t0;
|
||||
q0 = Eval(p0, p1, p2, p3, t0);
|
||||
q1 = q0 + Derivative(p0, p1, p2, p3, t0) * (span / 3f);
|
||||
q3 = Eval(p0, p1, p2, p3, t1);
|
||||
q2 = q3 - Derivative(p0, p1, p2, p3, t1) * (span / 3f);
|
||||
}
|
||||
|
||||
internal static void Restrict(float p0, float p1, float p2, float p3,
|
||||
float t0, float t1, out float q0, out float q1, out float q2, out float q3)
|
||||
{
|
||||
float span = t1 - t0;
|
||||
q0 = Eval(p0, p1, p2, p3, t0);
|
||||
q1 = q0 + Derivative(p0, p1, p2, p3, t0) * (span / 3f);
|
||||
q3 = Eval(p0, p1, p2, p3, t1);
|
||||
q2 = q3 - Derivative(p0, p1, p2, p3, t1) * (span / 3f);
|
||||
}
|
||||
|
||||
internal static Vector2 Eval(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float t)
|
||||
{
|
||||
var a = Vector2.Lerp(p0, p1, t);
|
||||
var b = Vector2.Lerp(p1, p2, t);
|
||||
var c = Vector2.Lerp(p2, p3, t);
|
||||
var d = Vector2.Lerp(a, b, t);
|
||||
var e = Vector2.Lerp(b, c, t);
|
||||
return Vector2.Lerp(d, e, t);
|
||||
}
|
||||
|
||||
internal static float Eval(float p0, float p1, float p2, float p3, float t)
|
||||
{
|
||||
var a = Mathf.Lerp(p0, p1, t);
|
||||
var b = Mathf.Lerp(p1, p2, t);
|
||||
var c = Mathf.Lerp(p2, p3, t);
|
||||
var d = Mathf.Lerp(a, b, t);
|
||||
var e = Mathf.Lerp(b, c, t);
|
||||
return Mathf.Lerp(d, e, t);
|
||||
}
|
||||
|
||||
/// <summary>导数 = 3 × 一阶差分控制点的二次贝塞尔</summary>
|
||||
internal static Vector2 Derivative(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float t)
|
||||
{
|
||||
var d0 = p1 - p0;
|
||||
var d1 = p2 - p1;
|
||||
var d2 = p3 - p2;
|
||||
var a = Vector2.Lerp(d0, d1, t);
|
||||
var b = Vector2.Lerp(d1, d2, t);
|
||||
return Vector2.Lerp(a, b, t) * 3f;
|
||||
}
|
||||
|
||||
internal static float Derivative(float p0, float p1, float p2, float p3, float t)
|
||||
{
|
||||
var d0 = p1 - p0;
|
||||
var d1 = p2 - p1;
|
||||
var d2 = p3 - p2;
|
||||
var a = Mathf.Lerp(d0, d1, t);
|
||||
var b = Mathf.Lerp(d1, d2, t);
|
||||
return Mathf.Lerp(a, b, t) * 3f;
|
||||
}
|
||||
|
||||
/// <summary>小块弧长(折线近似,够 uv 分配用)</summary>
|
||||
private static float MeasureArc(Request req, float a, float b)
|
||||
{
|
||||
const int samples = 5;
|
||||
int seg = Mathf.Clamp(Mathf.FloorToInt(a * req.segmentCount), 0, req.segmentCount - 1);
|
||||
|
||||
Vector2 p0, p1, p2, p3;
|
||||
GetSegment(req, seg, out p0, out p1, out p2, out p3);
|
||||
|
||||
float la = Mathf.Clamp01(a * req.segmentCount - seg);
|
||||
float lb = Mathf.Clamp01(b * req.segmentCount - seg);
|
||||
|
||||
var last = Eval(p0, p1, p2, p3, la);
|
||||
float sum = 0f;
|
||||
for (int i = 1; i <= samples; i++)
|
||||
{
|
||||
var next = Eval(p0, p1, p2, p3, Mathf.Lerp(la, lb, (float)i / samples));
|
||||
sum += Vector2.Distance(last, next);
|
||||
last = next;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2849f073cdc1b8028779b7b6c5fedfe3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,472 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
using XericLibrary.Runtime.XericComponent;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace XericLibrary.Runtime.UIGraph
|
||||
{
|
||||
/// <summary>
|
||||
/// 由界面样条(XericRectSplinePath)驱动的曲线绘制组件。
|
||||
/// <code>
|
||||
/// 一条样条上可以同时铺多条"面片",每条由一对 (头部, 尾部) 进度值决定覆盖范围,
|
||||
/// 于是 {1,0} 是铺满整条的底轨,{0.6f,0} 就是填到 60% 的进度条本体。
|
||||
/// 进度默认经样条烘焙后的弧长重映射校正,画面上的推进长度才和进度值成正比;
|
||||
/// 面片上的 uv0.y 被重映射成整条面片的 0→1,材质按 UV 就能做流动/遮罩类效果。
|
||||
/// </code>
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI/UISplineCurveRenderer", 17)]
|
||||
public class UISplineCurveRenderer : CurveCacheRendererBase
|
||||
{
|
||||
#region 字段属性
|
||||
|
||||
/// <summary>驱动本曲线的界面样条</summary>
|
||||
public XericRectSplinePath splinePath;
|
||||
|
||||
/// <summary>
|
||||
/// 面片对:偶数下标=头部、奇数下标=尾部,取值 [0,1]。
|
||||
/// 例:{1,0} 一条铺满;{1,0, 0.6f,0} 两条=底轨 + 60% 填充。
|
||||
/// </summary>
|
||||
public float[] headTailPairs = { 1f, 0f };
|
||||
|
||||
/// <summary>可空;与面片同序时逐条叠乘到样条颜色上(底轨压暗、填充提亮)</summary>
|
||||
public Color32[] ribbonTints;
|
||||
|
||||
/// <summary>进度值的含义:直接按参数,还是先经烘焙做弧长校正</summary>
|
||||
public SplineProgressMapping progressMapping = SplineProgressMapping.BakedDistance;
|
||||
|
||||
/// <summary>烘焙采样数,越大校正越细;同时压住库侧 1/(N+1) 的系统偏差</summary>
|
||||
[Range(8, 4096)]
|
||||
public int bakeSubdivide = 256;
|
||||
|
||||
/// <summary>
|
||||
/// 什么时候做弧长烘焙。默认 OnEditEnd:几何停止变化后补烘一次。
|
||||
/// 烘焙是 O(subdivide) 的采样,放在每帧会把编辑器拖到卡。
|
||||
/// </summary>
|
||||
public SplineBakeTrigger bakeTrigger = SplineBakeTrigger.OnEditEnd;
|
||||
|
||||
/// <summary>已经烘过几次(排查用:编辑器里拖动一整轮应该只加 1)</summary>
|
||||
public int BakeRunCount => m_BakeRunCount;
|
||||
|
||||
/// <summary>面片上 uv0.y 代表条内进度还是全样条进度</summary>
|
||||
public SplineUvProgressSpace uvProgressSpace = SplineUvProgressSpace.RibbonLocal;
|
||||
|
||||
/// <summary>每条面片至少切成几块,决定 uv 与视觉距离对齐的精度</summary>
|
||||
[Range(1, 64)]
|
||||
public int chunksPerRibbon = 8;
|
||||
|
||||
/// <summary>基准带宽(样条所在矩形的局部像素)</summary>
|
||||
[Min(0.01f)]
|
||||
public float curveWidth = 10f;
|
||||
|
||||
/// <summary>带宽来源:常量,还是乘上样条的缩放渐层 Y</summary>
|
||||
public SplineWidthSource widthSource = SplineWidthSource.ScaleGradientY;
|
||||
|
||||
/// <summary>单段贝塞尔的细分段数</summary>
|
||||
[Range(2, 128)]
|
||||
public int tessellationSegments = 32;
|
||||
|
||||
/// <summary>样条颜色渐层全透明时的兜底颜色</summary>
|
||||
public Color fallbackColor = Color.white;
|
||||
|
||||
/// <summary>
|
||||
/// 推给材质 _MainTex 的贴图。
|
||||
/// Canvas 是用 MaterialPropertyBlock 把 Graphic.mainTexture 顶到 _MainTex 上的,
|
||||
/// 所以必须走这个口子交出去;只改 material 的贴图会被默认白图覆盖掉。
|
||||
/// </summary>
|
||||
public Texture progressTexture;
|
||||
|
||||
/// <summary>SetProgress 作用在哪条面片的头部;-1 表示不指定</summary>
|
||||
public int fillRibbonIndex = 1;
|
||||
|
||||
/// <summary>每帧跟随样条变化重建(关掉后只能靠 RebuildCurve 手动触发)</summary>
|
||||
public bool followSplineChanges = true;
|
||||
|
||||
/// <summary>与曲线缓存同序的 uv 区间,x=起点 y=终点</summary>
|
||||
private readonly List<Vector2> m_PieceUv = new List<Vector2>();
|
||||
|
||||
private bool m_NeedUvPatch;
|
||||
private int m_LastBakeSignature = int.MinValue;
|
||||
private bool m_BakePending;
|
||||
private int m_BakeRunCount;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 生命周期
|
||||
|
||||
public override Texture mainTexture
|
||||
=> progressTexture != null ? progressTexture : base.mainTexture;
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
RebuildCurve();
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
if (curveWidth < 0.01f) curveWidth = 0.01f;
|
||||
if (tessellationSegments < 2) tessellationSegments = 2;
|
||||
if (followSplineChanges && isActiveAndEnabled)
|
||||
{
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
if (this != null && isActiveAndEnabled)
|
||||
RebuildCurve();
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
protected override void LateUpdate()
|
||||
{
|
||||
if (followSplineChanges)
|
||||
RebuildCurve();
|
||||
|
||||
base.LateUpdate();
|
||||
|
||||
// 只能在"刚刚重建过缓存"之后改写一次:
|
||||
// 改写后 uv0.y 就不再是生成器写的段内 t,重复改写会把映射套两遍
|
||||
if (m_NeedUvPatch)
|
||||
{
|
||||
m_NeedUvPatch = false;
|
||||
ApplyRibbonUv();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region API
|
||||
|
||||
/// <summary>
|
||||
/// 把指定面片的头部推到 value(进度条前进就调这个)
|
||||
/// </summary>
|
||||
public void SetProgress(float value)
|
||||
{
|
||||
int index = fillRibbonIndex >= 0 ? fillRibbonIndex : 0;
|
||||
if (headTailPairs == null || headTailPairs.Length < (index + 1) * 2)
|
||||
return;
|
||||
|
||||
headTailPairs[index * 2] = Mathf.Clamp01(value);
|
||||
RebuildCurve();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取指定面片的头部进度
|
||||
/// </summary>
|
||||
public float GetProgress()
|
||||
{
|
||||
int index = fillRibbonIndex >= 0 ? fillRibbonIndex : 0;
|
||||
if (headTailPairs == null || headTailPairs.Length < (index + 1) * 2)
|
||||
return 0f;
|
||||
return headTailPairs[index * 2];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按当前样条与面片配置重建曲线
|
||||
/// </summary>
|
||||
public void RebuildCurve()
|
||||
{
|
||||
ClearAll();
|
||||
m_PieceUv.Clear();
|
||||
m_NeedUvPatch = false;
|
||||
|
||||
if (splinePath == null || rectTransform == null)
|
||||
return;
|
||||
|
||||
var spline = splinePath.xericSpline;
|
||||
if (spline == null || spline.Count < 2)
|
||||
return;
|
||||
|
||||
// 锚点→世界的换算依赖矩形角点
|
||||
splinePath.UpdateRectDepend();
|
||||
var rect = rectTransform.rect;
|
||||
if (Mathf.Abs(rect.width) < 1e-6f || Mathf.Abs(rect.height) < 1e-6f)
|
||||
return;
|
||||
|
||||
int pointCount = spline.Count;
|
||||
var anchors = new Vector2[pointCount];
|
||||
var outHandles = new Vector2[pointCount];
|
||||
var inHandles = new Vector2[pointCount];
|
||||
var colors = new Color[pointCount];
|
||||
var widths = new float[pointCount];
|
||||
|
||||
int signature = pointCount;
|
||||
for (int i = 0; i < pointCount; i++)
|
||||
{
|
||||
anchors[i] = (Vector2)rectTransform.InverseTransformPoint(splinePath.GetPointPosition(i));
|
||||
outHandles[i] = (Vector2)rectTransform.InverseTransformPoint(splinePath.GetEndHandlePosition(i));
|
||||
inHandles[i] = (Vector2)rectTransform.InverseTransformPoint(splinePath.GetStartHandlePosition(i));
|
||||
colors[i] = ColorAt(spline, i);
|
||||
widths[i] = WidthAt(spline, i);
|
||||
|
||||
unchecked
|
||||
{
|
||||
signature = signature * 31 + anchors[i].GetHashCode()
|
||||
+ outHandles[i].GetHashCode() * 7 + inHandles[i].GetHashCode() * 13;
|
||||
}
|
||||
}
|
||||
// 非均匀缩放矩形会改变世界空间弧长的比例,签名要把矩形尺寸也算进去
|
||||
unchecked
|
||||
{
|
||||
signature = signature * 31 + rect.width.GetHashCode() + rect.height.GetHashCode() * 3;
|
||||
}
|
||||
|
||||
var request = new SplineRibbonBuilder.Request
|
||||
{
|
||||
anchors = anchors,
|
||||
outHandles = outHandles,
|
||||
inHandles = inHandles,
|
||||
pointColors = colors,
|
||||
pointWidths = widths,
|
||||
pointCount = pointCount,
|
||||
segmentCount = spline.useablePointCount,
|
||||
curveLoop = spline.CurveLoop,
|
||||
headTailPairs = headTailPairs,
|
||||
ribbonTints = ribbonTints,
|
||||
uvSpace = uvProgressSpace,
|
||||
chunksPerRibbon = chunksPerRibbon,
|
||||
progressToParametric = BuildMapping(spline, signature),
|
||||
};
|
||||
|
||||
var pieces = SplineRibbonBuilder.Build(request);
|
||||
if (pieces.Count == 0)
|
||||
return;
|
||||
|
||||
var ctrl = new Vector2[4];
|
||||
var pieceWidths = new float[4];
|
||||
|
||||
foreach (var piece in pieces)
|
||||
{
|
||||
ctrl[0] = piece.c0; ctrl[1] = piece.c1; ctrl[2] = piece.c2; ctrl[3] = piece.c3;
|
||||
pieceWidths[0] = piece.w0; pieceWidths[1] = piece.w1;
|
||||
pieceWidths[2] = piece.w2; pieceWidths[3] = piece.w3;
|
||||
|
||||
var entry = new CurveCacheEntry
|
||||
{
|
||||
startColor = piece.startColor,
|
||||
endColor = piece.endColor,
|
||||
tessellationSegments = PieceTessellation(piece),
|
||||
};
|
||||
|
||||
AddCurve(entry, (Vector2[])ctrl.Clone(), (float[])pieceWidths.Clone());
|
||||
m_PieceUv.Add(new Vector2(piece.uvYStart, piece.uvYEnd));
|
||||
}
|
||||
|
||||
m_NeedUvPatch = m_PieceUv.Count > 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 取数
|
||||
|
||||
/// <summary>
|
||||
/// 造出"进度 → 参数 t"的映射。校正走样条烘焙的弧长重映射,
|
||||
/// 库侧要求先烘焙且长度不足时会抛,所以这里自己把关并回落。
|
||||
/// </summary>
|
||||
private Func<float, float> BuildMapping(XericRectSpline spline, int signature)
|
||||
{
|
||||
if (progressMapping != SplineProgressMapping.BakedDistance)
|
||||
return ParametricIdentity;
|
||||
|
||||
if (!EnsureBaked(spline, signature))
|
||||
return ParametricIdentity;
|
||||
|
||||
return ParametricFromBakedDistance;
|
||||
}
|
||||
|
||||
private float ParametricIdentity(float progress) => progress;
|
||||
|
||||
/// <summary>
|
||||
/// 进度→参数 t。两端是边界真值:进度 0 就是曲线起点、1 就是终点,
|
||||
/// 所以先把两端钉死再走重映射 —— 库里的弧长重映射原本按"采样点数"而不是"段数"
|
||||
/// 做分母,进度 1 会落在 (N-1)/N,{1,0} 的面片就永远短一小截贴不到末尾。
|
||||
/// 库侧已经修了分母,这里钉端点既是兜底也让旧 DLL 下也能立刻闭合。
|
||||
/// </summary>
|
||||
private float ParametricFromBakedDistance(float progress)
|
||||
{
|
||||
if (progress <= 0f) return 0f;
|
||||
if (progress >= 1f) return 1f;
|
||||
return Mathf.Clamp01(splinePath.RemappingTimeValue(progress));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按 <see cref="bakeTrigger"/> 决定这一趟烘不烘,返回 false 表示当前用不了弧长校正。
|
||||
/// <code>
|
||||
/// 烘焙是 O(subdivide) 的采样:以前几何签名一变就烘,编辑器里拖动控制点等于每帧烘一次。
|
||||
/// OnEditEnd 的做法是"几何正在线上变"时先挂起、继续用上一次那份烘焙数据,
|
||||
/// 等下一趟签名不再变化(编辑停了)才补烘那一次。
|
||||
/// </code>
|
||||
/// </summary>
|
||||
private bool EnsureBaked(XericRectSpline spline, int signature)
|
||||
{
|
||||
int target = Mathf.Max(bakeSubdivide, spline.Count);
|
||||
bool geometryChanged = signature != m_LastBakeSignature;
|
||||
m_LastBakeSignature = signature;
|
||||
|
||||
switch (bakeTrigger)
|
||||
{
|
||||
case SplineBakeTrigger.Manual:
|
||||
// 一次都不自动烘,等 BakeNow
|
||||
return HasUsableBake(spline, target);
|
||||
|
||||
case SplineBakeTrigger.EveryRebuild:
|
||||
if (!geometryChanged && HasUsableBake(spline, target))
|
||||
return true;
|
||||
return RunBake(spline, target);
|
||||
|
||||
default:
|
||||
if (geometryChanged)
|
||||
{
|
||||
// 几何正在线上变:这一趟不烘,也**不用**上一份烘焙数据。
|
||||
// 结构大改(清点重铺、切循环)后那份表对应的已经不是这条曲线了,
|
||||
// 拿它做校正会把面片排到错处 —— 宁可这一趟退回参数映射,
|
||||
// 等编辑停下来的下一趟烘完再恢复校正。
|
||||
m_BakePending = true;
|
||||
return false;
|
||||
}
|
||||
if (!m_BakePending)
|
||||
return HasUsableBake(spline, target);
|
||||
|
||||
m_BakePending = false;
|
||||
return RunBake(spline, target);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>烘焙数据存在且长度够用(旧数据只要长度够就继续用,别再烘一次)</summary>
|
||||
private static bool HasUsableBake(XericRectSpline spline, int target)
|
||||
=> spline.BakeCount >= target;
|
||||
|
||||
private bool RunBake(XericRectSpline spline, int target)
|
||||
{
|
||||
splinePath.BakePoint(target);
|
||||
m_BakeRunCount++;
|
||||
return spline.BakeCount >= target;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手动烘一次(bakeTrigger 为 Manual 时用这个,或改完曲线想立刻精确时按)。
|
||||
/// 烘完直接重建,按下就能看见贴合结果。
|
||||
/// </summary>
|
||||
public void BakeNow()
|
||||
{
|
||||
if (splinePath == null || splinePath.xericSpline == null)
|
||||
return;
|
||||
|
||||
var spline = splinePath.xericSpline;
|
||||
RunBake(spline, Mathf.Max(bakeSubdivide, spline.Count));
|
||||
m_BakePending = false;
|
||||
RebuildCurve();
|
||||
}
|
||||
|
||||
private Color ColorAt(XericRectSpline spline, int index)
|
||||
{
|
||||
var gradient = spline.ColorGradient;
|
||||
if (gradient == null || index < 0 || index >= gradient.Count)
|
||||
return fallbackColor;
|
||||
|
||||
var color = gradient[index];
|
||||
return color.a <= 0f ? fallbackColor : color;
|
||||
}
|
||||
|
||||
private float WidthAt(XericRectSpline spline, int index)
|
||||
{
|
||||
switch (widthSource)
|
||||
{
|
||||
case SplineWidthSource.ScaleGradientY:
|
||||
return curveWidth * Mathf.Abs(GradientAt(spline.ScaleGradient, index, Vector3.one).y);
|
||||
case SplineWidthSource.FloatGradient:
|
||||
// 额外参数本来就是"每点多大"的语义时用它最直观;
|
||||
// 注意它默认全 0,不填就什么都画不出来
|
||||
return curveWidth * Mathf.Max(0f, GradientAt(spline.FloatGradient, index, 1f));
|
||||
default:
|
||||
return curveWidth;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>渐层可能比点数短(在 Inspector 里直接改 ControlPoint 长度就会这样),一律夹紧</summary>
|
||||
private static T GradientAt<T>(SplineGradientBase<T> gradient, int index, T fallback)
|
||||
{
|
||||
if (gradient == null || index < 0 || index >= gradient.Count)
|
||||
return fallback;
|
||||
return gradient[index];
|
||||
}
|
||||
|
||||
private int PieceTessellation(RibbonPiece piece)
|
||||
{
|
||||
int tess = Mathf.RoundToInt(tessellationSegments * Mathf.Clamp01(piece.segmentFraction));
|
||||
return Mathf.Clamp(tess, 2, 512);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region uv 重写
|
||||
|
||||
/// <summary>
|
||||
/// 把生成器写在每个小块里的"段内 t"重映射成整条面片的 0→1。
|
||||
/// 默认材质只用到 uv0.x(边缘羽化),uv0.y 留给换上去的材质消费。
|
||||
/// </summary>
|
||||
private void ApplyRibbonUv()
|
||||
{
|
||||
if (m_PieceUv.Count == 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < m_Curves.Count && i < m_PieceUv.Count; i++)
|
||||
{
|
||||
var curve = m_Curves[i];
|
||||
var range = m_PieceUv[i];
|
||||
int end = curve.startVertexIndex + curve.vertexCount;
|
||||
if (end > m_MeshVertexCache.Count)
|
||||
end = m_MeshVertexCache.Count;
|
||||
|
||||
for (int v = curve.startVertexIndex; v < end; v++)
|
||||
{
|
||||
var vert = m_MeshVertexCache[v];
|
||||
vert.uv0.y = Mathf.LerpUnclamped(range.x, range.y, vert.uv0.y);
|
||||
m_MeshVertexCache[v] = vert;
|
||||
}
|
||||
}
|
||||
|
||||
SetVerticesDirty();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>进度值如何换算成曲线参数</summary>
|
||||
public enum SplineProgressMapping
|
||||
{
|
||||
/// <summary>进度直接当参数 t:段与段的视觉长度不等</summary>
|
||||
Parametric,
|
||||
/// <summary>进度按烘焙后的弧长重映射:画面上推进的长度和进度成正比</summary>
|
||||
BakedDistance,
|
||||
}
|
||||
|
||||
/// <summary>弧长烘焙的触发时机(校正的数据源就是这份烘焙数据)</summary>
|
||||
public enum SplineBakeTrigger
|
||||
{
|
||||
/// <summary>从不自动烘,只认 BakeNow / Inspector 上的烘焙按钮</summary>
|
||||
Manual,
|
||||
/// <summary>几何停止变化后补烘一次:编辑器里拖动控制点不会逐帧烘</summary>
|
||||
OnEditEnd,
|
||||
/// <summary>几何一变就烘(每帧一变烘一次,贵;只在需要立刻精确时用)</summary>
|
||||
EveryRebuild,
|
||||
}
|
||||
|
||||
/// <summary>带宽来源</summary>
|
||||
public enum SplineWidthSource
|
||||
{
|
||||
/// <summary>恒用 curveWidth</summary>
|
||||
Constant,
|
||||
/// <summary>curveWidth × 该点缩放渐层的 Y(渐层默认是 1,不填也等价于常量)</summary>
|
||||
ScaleGradientY,
|
||||
/// <summary>curveWidth × 该点的额外参数。默认全 0,不填什么都画不出来</summary>
|
||||
FloatGradient,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91cbc8bc694b85a8f15fefce8d3a72b1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,65 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericLibrary.Runtime.UIGraph
|
||||
{
|
||||
/// <summary>
|
||||
/// 曲线进度条的播放驱动器:把时间推成 UISplineCurveRenderer 的进度。
|
||||
/// 只负责"进度从哪来",几何与着色全在渲染器那一侧。
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI/UISplineProgressDriver", 18)]
|
||||
[RequireComponent(typeof(UISplineCurveRenderer))]
|
||||
public class UISplineProgressDriver : MonoBehaviour
|
||||
{
|
||||
/// <summary>自动推进(关掉就只认手动 SetProgress)</summary>
|
||||
public bool autoDrive = true;
|
||||
|
||||
/// <summary>从 0 走到 1 所需秒数</summary>
|
||||
[Min(0.01f)]
|
||||
public float duration = 2.5f;
|
||||
|
||||
/// <summary>用 scaledTime,随引擎暂停/缩放</summary>
|
||||
public bool useUnscaledTime = false;
|
||||
|
||||
/// <summary>走到尽头后往返还是从头再来</summary>
|
||||
public bool pingPong = false;
|
||||
|
||||
/// <summary>起始进度</summary>
|
||||
[Range(0f, 1f)]
|
||||
public float startProgress = 0f;
|
||||
|
||||
private UISplineCurveRenderer m_Renderer;
|
||||
private float m_Elapsed;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
m_Renderer = GetComponent<UISplineCurveRenderer>();
|
||||
m_Elapsed = startProgress * duration;
|
||||
if (m_Renderer != null)
|
||||
m_Renderer.SetProgress(startProgress);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!autoDrive || m_Renderer == null)
|
||||
return;
|
||||
|
||||
m_Elapsed += useUnscaledTime ? Time.unscaledDeltaTime : Time.deltaTime;
|
||||
float cycle = m_Elapsed / duration;
|
||||
|
||||
float progress = pingPong
|
||||
? Mathf.PingPong(cycle, 1f)
|
||||
: cycle - Mathf.Floor(cycle);
|
||||
|
||||
m_Renderer.SetProgress(progress);
|
||||
}
|
||||
|
||||
/// <summary>手动设进度,同时让自动推进从该处继续</summary>
|
||||
public void SetProgress(float value)
|
||||
{
|
||||
value = Mathf.Clamp01(value);
|
||||
m_Elapsed = value * duration;
|
||||
if (m_Renderer != null)
|
||||
m_Renderer.SetProgress(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f851c7a6653a78879ce665e643f5a28
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
@@ -0,0 +1,127 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 65b82257fc9da6b4ba046b02555dd0d6
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 12
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Server
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <https://unlicense.org/>
|
||||
@@ -1,6 +1,6 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9abe5f919a1840a4dace5de52d721a06
|
||||
TextScriptImporter:
|
||||
guid: affe7d56dc01a48439aedc888552bf47
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
@@ -0,0 +1,638 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!29 &1
|
||||
OcclusionCullingSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_OcclusionBakeSettings:
|
||||
smallestOccluder: 5
|
||||
smallestHole: 0.25
|
||||
backfaceThreshold: 100
|
||||
m_SceneGUID: 00000000000000000000000000000000
|
||||
m_OcclusionCullingData: {fileID: 0}
|
||||
--- !u!104 &2
|
||||
RenderSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 9
|
||||
m_Fog: 0
|
||||
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||
m_FogMode: 3
|
||||
m_FogDensity: 0.01
|
||||
m_LinearFogStart: 0
|
||||
m_LinearFogEnd: 300
|
||||
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
|
||||
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
|
||||
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
|
||||
m_AmbientIntensity: 1
|
||||
m_AmbientMode: 0
|
||||
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
|
||||
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_HaloStrength: 0.5
|
||||
m_FlareStrength: 1
|
||||
m_FlareFadeSpeed: 3
|
||||
m_HaloTexture: {fileID: 0}
|
||||
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
|
||||
m_DefaultReflectionMode: 0
|
||||
m_DefaultReflectionResolution: 128
|
||||
m_ReflectionBounces: 1
|
||||
m_ReflectionIntensity: 1
|
||||
m_CustomReflection: {fileID: 0}
|
||||
m_Sun: {fileID: 705507994}
|
||||
m_IndirectSpecularColor: {r: 0.44657874, g: 0.49641275, b: 0.5748172, a: 1}
|
||||
m_UseRadianceAmbientProbe: 0
|
||||
--- !u!157 &3
|
||||
LightmapSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 12
|
||||
m_GIWorkflowMode: 1
|
||||
m_GISettings:
|
||||
serializedVersion: 2
|
||||
m_BounceScale: 1
|
||||
m_IndirectOutputScale: 1
|
||||
m_AlbedoBoost: 1
|
||||
m_EnvironmentLightingMode: 0
|
||||
m_EnableBakedLightmaps: 1
|
||||
m_EnableRealtimeLightmaps: 0
|
||||
m_LightmapEditorSettings:
|
||||
serializedVersion: 12
|
||||
m_Resolution: 2
|
||||
m_BakeResolution: 40
|
||||
m_AtlasSize: 1024
|
||||
m_AO: 0
|
||||
m_AOMaxDistance: 1
|
||||
m_CompAOExponent: 1
|
||||
m_CompAOExponentDirect: 0
|
||||
m_ExtractAmbientOcclusion: 0
|
||||
m_Padding: 2
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_LightmapsBakeMode: 1
|
||||
m_TextureCompression: 1
|
||||
m_FinalGather: 0
|
||||
m_FinalGatherFiltering: 1
|
||||
m_FinalGatherRayCount: 256
|
||||
m_ReflectionCompression: 2
|
||||
m_MixedBakeMode: 2
|
||||
m_BakeBackend: 1
|
||||
m_PVRSampling: 1
|
||||
m_PVRDirectSampleCount: 32
|
||||
m_PVRSampleCount: 500
|
||||
m_PVRBounces: 2
|
||||
m_PVREnvironmentSampleCount: 500
|
||||
m_PVREnvironmentReferencePointCount: 2048
|
||||
m_PVRFilteringMode: 2
|
||||
m_PVRDenoiserTypeDirect: 0
|
||||
m_PVRDenoiserTypeIndirect: 0
|
||||
m_PVRDenoiserTypeAO: 0
|
||||
m_PVRFilterTypeDirect: 0
|
||||
m_PVRFilterTypeIndirect: 0
|
||||
m_PVRFilterTypeAO: 0
|
||||
m_PVREnvironmentMIS: 0
|
||||
m_PVRCulling: 1
|
||||
m_PVRFilteringGaussRadiusDirect: 1
|
||||
m_PVRFilteringGaussRadiusIndirect: 5
|
||||
m_PVRFilteringGaussRadiusAO: 2
|
||||
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
|
||||
m_PVRFilteringAtrousPositionSigmaIndirect: 2
|
||||
m_PVRFilteringAtrousPositionSigmaAO: 1
|
||||
m_ExportTrainingData: 0
|
||||
m_TrainingDataDestination: TrainingData
|
||||
m_LightProbeSampleCountMultiplier: 4
|
||||
m_LightingDataAsset: {fileID: 0}
|
||||
m_LightingSettings: {fileID: 0}
|
||||
--- !u!196 &4
|
||||
NavMeshSettings:
|
||||
serializedVersion: 2
|
||||
m_ObjectHideFlags: 0
|
||||
m_BuildSettings:
|
||||
serializedVersion: 3
|
||||
agentTypeID: 0
|
||||
agentRadius: 0.5
|
||||
agentHeight: 2
|
||||
agentSlope: 45
|
||||
agentClimb: 0.4
|
||||
ledgeDropHeight: 0
|
||||
maxJumpAcrossDistance: 0
|
||||
minRegionArea: 2
|
||||
manualCellSize: 0
|
||||
cellSize: 0.16666667
|
||||
manualTileSize: 0
|
||||
tileSize: 256
|
||||
buildHeightMesh: 0
|
||||
maxJobWorkers: 0
|
||||
preserveTilesOutsideBounds: 0
|
||||
debug:
|
||||
m_Flags: 0
|
||||
m_NavMeshData: {fileID: 0}
|
||||
--- !u!1 &246206081
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 246206082}
|
||||
- component: {fileID: 246206084}
|
||||
- component: {fileID: 246206083}
|
||||
m_Layer: 5
|
||||
m_Name: LineRenderer
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &246206082
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 246206081}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1.5, y: 1.5, z: 1.5}
|
||||
m_ConstrainProportionsScale: 1
|
||||
m_Children: []
|
||||
m_Father: {fileID: 387643621}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 1000, y: 300}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &246206083
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 246206081}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 5b33b2e663e78774c9f0c9af55018725, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
points:
|
||||
- {x: 0, y: 0}
|
||||
- {x: 58.7, y: 138.4}
|
||||
- {x: 125.1, y: 80.6}
|
||||
- {x: 238.9, y: 300}
|
||||
- {x: 354, y: 100}
|
||||
- {x: 512.2, y: 70}
|
||||
- {x: 716.1, y: 192.5}
|
||||
- {x: 938.8, y: 3.85}
|
||||
- {x: 1000, y: 133}
|
||||
thickness: 20
|
||||
center: 1
|
||||
--- !u!222 &246206084
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 246206081}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!1 &387643617
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 387643621}
|
||||
- component: {fileID: 387643620}
|
||||
- component: {fileID: 387643619}
|
||||
- component: {fileID: 387643618}
|
||||
m_Layer: 5
|
||||
m_Name: Canvas
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &387643618
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 387643617}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IgnoreReversedGraphics: 1
|
||||
m_BlockingObjects: 0
|
||||
m_BlockingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
--- !u!114 &387643619
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 387643617}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_UiScaleMode: 1
|
||||
m_ReferencePixelsPerUnit: 100
|
||||
m_ScaleFactor: 1
|
||||
m_ReferenceResolution: {x: 1920, y: 1080}
|
||||
m_ScreenMatchMode: 0
|
||||
m_MatchWidthOrHeight: 0
|
||||
m_PhysicalUnit: 3
|
||||
m_FallbackScreenDPI: 96
|
||||
m_DefaultSpriteDPI: 96
|
||||
m_DynamicPixelsPerUnit: 1
|
||||
m_PresetInfoIsWorld: 0
|
||||
--- !u!223 &387643620
|
||||
Canvas:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 387643617}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 3
|
||||
m_RenderMode: 0
|
||||
m_Camera: {fileID: 0}
|
||||
m_PlaneDistance: 100
|
||||
m_PixelPerfect: 0
|
||||
m_ReceivesEvents: 1
|
||||
m_OverrideSorting: 0
|
||||
m_OverridePixelPerfect: 0
|
||||
m_SortingBucketNormalizedSize: 0
|
||||
m_VertexColorAlwaysGammaSpace: 0
|
||||
m_AdditionalShaderChannelsFlag: 0
|
||||
m_UpdateRectTransformForStandalone: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingOrder: 0
|
||||
m_TargetDisplay: 0
|
||||
--- !u!224 &387643621
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 387643617}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 0, y: 0, z: 0}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 1231462426}
|
||||
- {fileID: 246206082}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0, y: 0}
|
||||
--- !u!1 &705507993
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 705507995}
|
||||
- component: {fileID: 705507994}
|
||||
m_Layer: 0
|
||||
m_Name: Directional Light
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!108 &705507994
|
||||
Light:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 705507993}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 10
|
||||
m_Type: 1
|
||||
m_Shape: 0
|
||||
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
|
||||
m_Intensity: 1
|
||||
m_Range: 10
|
||||
m_SpotAngle: 30
|
||||
m_InnerSpotAngle: 21.80208
|
||||
m_CookieSize: 10
|
||||
m_Shadows:
|
||||
m_Type: 2
|
||||
m_Resolution: -1
|
||||
m_CustomResolution: -1
|
||||
m_Strength: 1
|
||||
m_Bias: 0.05
|
||||
m_NormalBias: 0.4
|
||||
m_NearPlane: 0.2
|
||||
m_CullingMatrixOverride:
|
||||
e00: 1
|
||||
e01: 0
|
||||
e02: 0
|
||||
e03: 0
|
||||
e10: 0
|
||||
e11: 1
|
||||
e12: 0
|
||||
e13: 0
|
||||
e20: 0
|
||||
e21: 0
|
||||
e22: 1
|
||||
e23: 0
|
||||
e30: 0
|
||||
e31: 0
|
||||
e32: 0
|
||||
e33: 1
|
||||
m_UseCullingMatrixOverride: 0
|
||||
m_Cookie: {fileID: 0}
|
||||
m_DrawHalo: 0
|
||||
m_Flare: {fileID: 0}
|
||||
m_RenderMode: 0
|
||||
m_CullingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_RenderingLayerMask: 1
|
||||
m_Lightmapping: 1
|
||||
m_LightShadowCasterMode: 0
|
||||
m_AreaSize: {x: 1, y: 1}
|
||||
m_BounceIntensity: 1
|
||||
m_ColorTemperature: 6570
|
||||
m_UseColorTemperature: 0
|
||||
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_UseBoundingSphereOverride: 0
|
||||
m_UseViewFrustumForShadowCasterCull: 1
|
||||
m_ShadowRadius: 0
|
||||
m_ShadowAngle: 0
|
||||
--- !u!4 &705507995
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 705507993}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
|
||||
m_LocalPosition: {x: 0, y: 3, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
|
||||
--- !u!1 &963194225
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 963194228}
|
||||
- component: {fileID: 963194227}
|
||||
- component: {fileID: 963194226}
|
||||
m_Layer: 0
|
||||
m_Name: Main Camera
|
||||
m_TagString: MainCamera
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!81 &963194226
|
||||
AudioListener:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 963194225}
|
||||
m_Enabled: 1
|
||||
--- !u!20 &963194227
|
||||
Camera:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 963194225}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 2
|
||||
m_ClearFlags: 1
|
||||
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
|
||||
m_projectionMatrixMode: 1
|
||||
m_GateFitMode: 2
|
||||
m_FOVAxisMode: 0
|
||||
m_Iso: 200
|
||||
m_ShutterSpeed: 0.005
|
||||
m_Aperture: 16
|
||||
m_FocusDistance: 10
|
||||
m_FocalLength: 50
|
||||
m_BladeCount: 5
|
||||
m_Curvature: {x: 2, y: 11}
|
||||
m_BarrelClipping: 0.25
|
||||
m_Anamorphism: 0
|
||||
m_SensorSize: {x: 36, y: 24}
|
||||
m_LensShift: {x: 0, y: 0}
|
||||
m_NormalizedViewPortRect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 1
|
||||
height: 1
|
||||
near clip plane: 0.3
|
||||
far clip plane: 1000
|
||||
field of view: 60
|
||||
orthographic: 0
|
||||
orthographic size: 5
|
||||
m_Depth: -1
|
||||
m_CullingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_RenderingPath: -1
|
||||
m_TargetTexture: {fileID: 0}
|
||||
m_TargetDisplay: 0
|
||||
m_TargetEye: 3
|
||||
m_HDR: 1
|
||||
m_AllowMSAA: 1
|
||||
m_AllowDynamicResolution: 0
|
||||
m_ForceIntoRT: 0
|
||||
m_OcclusionCulling: 1
|
||||
m_StereoConvergence: 10
|
||||
m_StereoSeparation: 0.022
|
||||
--- !u!4 &963194228
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 963194225}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 1, z: -10}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1231462425
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1231462426}
|
||||
- component: {fileID: 1231462428}
|
||||
- component: {fileID: 1231462427}
|
||||
m_Layer: 5
|
||||
m_Name: Background
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &1231462426
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1231462425}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 1
|
||||
m_Children: []
|
||||
m_Father: {fileID: 387643621}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1231462427
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1231462425}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 21300000, guid: 980e274b7ed2de945bdefcc1d0423472, type: 3}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!222 &1231462428
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1231462425}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!1 &1721653935
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1721653938}
|
||||
- component: {fileID: 1721653937}
|
||||
- component: {fileID: 1721653936}
|
||||
m_Layer: 0
|
||||
m_Name: EventSystem
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &1721653936
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1721653935}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_SendPointerHoverToParent: 1
|
||||
m_HorizontalAxis: Horizontal
|
||||
m_VerticalAxis: Vertical
|
||||
m_SubmitButton: Submit
|
||||
m_CancelButton: Cancel
|
||||
m_InputActionsPerSecond: 10
|
||||
m_RepeatDelay: 0.5
|
||||
m_ForceModuleActive: 0
|
||||
--- !u!114 &1721653937
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1721653935}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_FirstSelected: {fileID: 0}
|
||||
m_sendNavigationEvents: 1
|
||||
m_DragThreshold: 10
|
||||
--- !u!4 &1721653938
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1721653935}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1660057539 &9223372036854775807
|
||||
SceneRoots:
|
||||
m_ObjectHideFlags: 0
|
||||
m_Roots:
|
||||
- {fileID: 963194228}
|
||||
- {fileID: 705507995}
|
||||
- {fileID: 387643621}
|
||||
- {fileID: 1721653938}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb3b7561320c3df409493f5b073278f0
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,91 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Radishmouse
|
||||
{
|
||||
[RequireComponent(typeof(CanvasRenderer))]
|
||||
public class XericSimpleUILineRenderer : MaskableGraphic
|
||||
{
|
||||
public Vector2[] points;
|
||||
|
||||
public float thickness = 10f;
|
||||
public bool center = true;
|
||||
|
||||
protected override void OnPopulateMesh(VertexHelper vh)
|
||||
{
|
||||
vh.Clear();
|
||||
|
||||
if (points.Length < 2)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < points.Length-1; i++)
|
||||
{
|
||||
// Create a line segment between the next two points
|
||||
CreateLineSegment(points[i], points[i+1], vh);
|
||||
|
||||
int index = i * 5;
|
||||
|
||||
// Add the line segment to the triangles array
|
||||
vh.AddTriangle(index, index+1, index+3);
|
||||
vh.AddTriangle(index+3, index+2, index);
|
||||
|
||||
// These two triangles create the beveled edges
|
||||
// between line segments using the end point of
|
||||
// the last line segment and the start points of this one
|
||||
if (i != 0)
|
||||
{
|
||||
vh.AddTriangle(index, index-1, index-3);
|
||||
vh.AddTriangle(index+1, index-1, index-2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a rect from two points that acts as a line segment
|
||||
/// </summary>
|
||||
/// <param name="point1">The starting point of the segment</param>
|
||||
/// <param name="point2">The endint point of the segment</param>
|
||||
/// <param name="vh">The vertex helper that the segment is added to</param>
|
||||
private void CreateLineSegment(Vector3 point1, Vector3 point2, VertexHelper vh)
|
||||
{
|
||||
Vector3 offset = center ? (rectTransform.sizeDelta / 2) : Vector2.zero;
|
||||
|
||||
// Create vertex template
|
||||
UIVertex vertex = UIVertex.simpleVert;
|
||||
vertex.color = color;
|
||||
|
||||
// Create the start of the segment
|
||||
Quaternion point1Rotation = Quaternion.Euler(0, 0, RotatePointTowards(point1, point2) + 90);
|
||||
vertex.position = point1Rotation * new Vector3(-thickness / 2, 0);
|
||||
vertex.position += point1 - offset;
|
||||
vh.AddVert(vertex);
|
||||
vertex.position = point1Rotation * new Vector3(thickness / 2, 0);
|
||||
vertex.position += point1 - offset;
|
||||
vh.AddVert(vertex);
|
||||
|
||||
// Create the end of the segment
|
||||
Quaternion point2Rotation = Quaternion.Euler(0, 0, RotatePointTowards(point2, point1) - 90);
|
||||
vertex.position = point2Rotation * new Vector3(-thickness / 2, 0);
|
||||
vertex.position += point2 - offset;
|
||||
vh.AddVert(vertex);
|
||||
vertex.position = point2Rotation * new Vector3(thickness / 2, 0);
|
||||
vertex.position += point2 - offset;
|
||||
vh.AddVert(vertex);
|
||||
|
||||
// Also add the end point
|
||||
vertex.position = point2 - offset;
|
||||
vh.AddVert(vertex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the angle that a vertex needs to rotate to face target vertex
|
||||
/// </summary>
|
||||
/// <param name="vertex">The vertex being rotated</param>
|
||||
/// <param name="target">The vertex to rotate towards</param>
|
||||
/// <returns>The angle required to rotate vertex towards target</returns>
|
||||
private float RotatePointTowards(Vector2 vertex, Vector2 target)
|
||||
{
|
||||
return (float)(Mathf.Atan2(target.y - vertex.y, target.x - vertex.x) * (180 / Mathf.PI));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e20d14972d02c04eba084112d510af7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
Binary file not shown.
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user