init push 0.2.4
@@ -0,0 +1,119 @@
|
||||
# Changelog
|
||||
|
||||
## [0.3.1] - 2025-01-10
|
||||
|
||||
一个大功能更新:数据库宏
|
||||
添加了一个关于数据库读写的部分,将数据库的sql语言用更易管理的方式封装。
|
||||
数据库宏需要对应的数据库dll支持,为了便于分发,暂时没有纳入自动封装的范围
|
||||
数据库宏分为两个部分:
|
||||
一个以order为基础,封装了基本操作指令的操作库。
|
||||
一个以关键字函数式为基础,允许使用链式语法构建指令的指令集,依靠编译器实现语法。
|
||||
|
||||
自上次以来,添加其他的一些小功能:
|
||||
可以对一个字符串使用.InXml_xxx来快速使用xml格式包裹文本。
|
||||
调整了mono单例的一些单例模式,添加了惰性单例,编辑时单例,匿名单例,旁路优先单例,终端单例,现在一共可以用9种单例。
|
||||
添加了一个关于user32.dll的控制宏。
|
||||
|
||||
## [0.2.6] - 2024-10-10
|
||||
|
||||
添加了程序宏:
|
||||
单例类中添加了对自源与他源的区分,替代了之前的抛出异常,而是可以通过不同的属性分别获取自己的单例和源自自己的单例.
|
||||
添加了ui变换宏;
|
||||
添加了摄像机宏:可以在编辑器下获取屏幕尺寸(用于解决当前高版本下获取低版本untiy的screen参数时总是不对的问题);
|
||||
|
||||
## [0.2.5] - 2024-8-12
|
||||
|
||||
添加了列表宏:列表二分法迭代器,合并迭代器之前,合并迭代器之后,获取子迭代器片段,。
|
||||
添加了对象宏:广度优先节点迭代器,深度优先节点迭代器,获取家族序列,从家族序列查找对象。
|
||||
添加了树型结构:目录树。
|
||||
添加了超驰宏。
|
||||
添加了硬件信息查询,不过相关命令在unity里无法执行,需要系统权限。
|
||||
添加了一维到三维布尔数据结构。
|
||||
添加了按键宏,可以识别点击,长按,双击,连击,长按释放等操作,支持动作触发,时间触发,坐标触发。
|
||||
添加了一个测试用的角色控制器,默认使用旧输入系统,不过有提供便捷的重写结构。
|
||||
添加了编辑距离检查算法。
|
||||
添加了3d贝塞尔曲线(样条曲线),适用于3d世界场景。
|
||||
添加了2d界面的贝塞尔曲线,适用于ui界面,可以跟随界面定位缩放。
|
||||
添加了注册器。
|
||||
|
||||
之前更改的weaklyMono类在判断空方面有点繁琐,所以添加了 <code>IsNull</code> 和 <code>sNotNull</code> 来判断 weaklyMono 是否为空。
|
||||
|
||||
修复了颜色梯度图表的一个编辑时错误,现在不会在每次展开inspector时添加默认值在0点了。
|
||||
修复了一些快捷枚举位操作宏会出现计算错误的情况。
|
||||
修复了数学库中的clamp计算错误。
|
||||
|
||||
## [0.2.4] - 2024-06-19
|
||||
|
||||
添加了更多的关于快速文件保存,数据算法,html解析,http连接的扩展实现。
|
||||
对于线路绘制库没有进行过多更改,但修复了一些可能存在的问题。
|
||||
添加了基于机器码的简易认证。
|
||||
|
||||
## [0.2.3-alpha] - 2024-06-14
|
||||
|
||||
主要添加了一些关于弱元素的扩展
|
||||
这是一个测试版本,基于上一版本进行更新,并调整了部分实验性API。
|
||||
|
||||
* 注意
|
||||
现在想要判断一个线路绘制库的元素是否为空,必须明确指出其空类型,或者使用 <code>is null</code> 以及 <code>is not null</code> 来更规范地检查是否为空。
|
||||
另外我在测试环境中的代码在此次更新中没有发生任何报错或警告,所以我认为这依然是一个小更新。
|
||||
但在未来可能会将此类语法扩展到其他类成员中,请悉知。
|
||||
|
||||
* 新增
|
||||
增加一些许可证行为。
|
||||
增加一些基础网络相关库。
|
||||
|
||||
* API调整
|
||||
重新设计了线路绘制库中序列化联盟的结构扩展,链式语法更紧凑了。
|
||||
同时反序列化的内容可以直接用于复制,同化等操作,并且逐组件同化的操作也可以使用快速链式调用了。
|
||||
已经被移除的旧方法大部分是非公开的,所以这算小更新,如果你通过软引用调用了旧api,请参考组件中新的反序列化进行调整。
|
||||
相连结构的检查接口包含新增与修改。
|
||||
|
||||
* 修复
|
||||
修复了线路绘制库中对线路的反序列化功能。
|
||||
修复了序列化联盟中存在的一些索引问题。
|
||||
|
||||
* 已知问题
|
||||
在电脑休眠后,untiy有几率会提示插件中的部分类missing the class attribute ‘ExtensionOfNativeClass’。
|
||||
|
||||
|
||||
## [0.2.2-alpha] - 2024-05-22
|
||||
|
||||
主要对线路绘制库的更新,以及对少量数学库的更新。
|
||||
这是一个测试版本,基于上一版本进行更新,并调整了部分实验性API。
|
||||
|
||||
* 新增
|
||||
添加了uid的全局存在查找功能。
|
||||
添加了序列化保存的功能。
|
||||
添加了线路吸附,并实装在工具中。
|
||||
添加了工具类的后台运行生命周期。
|
||||
|
||||
* API调整
|
||||
将原先添加巨大成员对象的方法中外置的空间转换方法隐去,而在IPossessorTrajectory2中添加了对默认可扩展的两种世界空间的默认转换方法。由于只有具备可执行脚本能力的类才可以继承此接口,因此在此处添加世界空间的转换方法个人认为是较为恰当的。
|
||||
移除了材质库,材质库现在独立为了新的插件。
|
||||
|
||||
* 修复
|
||||
修复了圆弧计算时,步长会随着半径提高而边长的问题,经过测试是正比关系,依此完成的矫正。
|
||||
|
||||
|
||||
## [0.2.1-alpha] - 2024-05-07
|
||||
|
||||
这是一个测试版本,但其中的api基本确定,可以进行基本的使用。
|
||||
|
||||
|
||||
## [0.2.0] - 2024-04-18
|
||||
|
||||
开始加入线路库*解构*功能
|
||||
|
||||
添加点,线的计算与绘制功能,以及全局标识。
|
||||
|
||||
## [0.1.0] - 2024-01-30
|
||||
|
||||
插件源自一个简单的数学整合库。
|
||||
现整合基本内容,并在此基础上编写线路库,期望通过dll封装形成大型但简洁的库。
|
||||
|
||||
简洁是可选的,移除不需要的dll功能即可。
|
||||
|
||||
|
||||
### This is the first release of *\<Sesotho Line\>*.
|
||||
|
||||
*Short description of this release*
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Lrss3.ConfigUIElement",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:6055be8ebefd69e48b49212b09b47b2f",
|
||||
"GUID:b1727b829ae4f0641acc8ad28ed624c2"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace LRC
|
||||
{
|
||||
public class ConfigButton : ConfigSelectableItem
|
||||
{
|
||||
#region 属性字段
|
||||
|
||||
public Button targetButton;
|
||||
|
||||
public override bool AllowInput
|
||||
{
|
||||
get => base.AllowInput;
|
||||
set
|
||||
{
|
||||
if (targetButton != null)
|
||||
targetButton.interactable = !value;
|
||||
base.AllowInput = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 实现
|
||||
|
||||
protected override void SetValue(object newValue)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public override void RefreshValueWithoutEvent(object newValue, bool forceSet = false)
|
||||
{
|
||||
base.RefreshValueWithoutEvent(newValue, forceSet);
|
||||
}
|
||||
|
||||
private bool _doOnce = false;
|
||||
protected override void Initialization_ChildConstruction(UIBehaviour component)
|
||||
{
|
||||
base.Initialization_ChildConstruction(component);
|
||||
|
||||
var name = component.transform.name;
|
||||
if (name is not "button") return;
|
||||
if (_doOnce)
|
||||
{
|
||||
Debug.LogError("存在重复命名的属性模块:" + name);
|
||||
return;
|
||||
}
|
||||
if (component is Button button)
|
||||
{
|
||||
_doOnce = true;
|
||||
if (targetButton != null)
|
||||
targetButton = button;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace LRC
|
||||
{
|
||||
/// <summary>
|
||||
/// 一种数值增减输入框,将使用浮点值作为默认
|
||||
/// </summary>
|
||||
public class ConfigDigitalInputField : ConfigSelectableItem
|
||||
{
|
||||
#region 属性字段
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public float editorInput;
|
||||
#endif
|
||||
|
||||
public TMP_InputField targetInput;
|
||||
|
||||
public Button digitalAddButton;
|
||||
public Button digitalSubButton;
|
||||
|
||||
public double maxValue = 114514;
|
||||
public double minValue = -114515;
|
||||
public int digits = 6;
|
||||
|
||||
/// <summary>
|
||||
/// 增量
|
||||
/// </summary>
|
||||
public double delta = 1;
|
||||
|
||||
public override bool AllowInput
|
||||
{
|
||||
get => base.AllowInput;
|
||||
set
|
||||
{
|
||||
if (targetInput != null)
|
||||
targetInput.readOnly = !value;
|
||||
if (digitalAddButton != null)
|
||||
digitalAddButton.interactable = value;
|
||||
if (digitalSubButton != null)
|
||||
digitalSubButton.interactable = value;
|
||||
base.AllowInput = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 生命周期
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
#if UNITY_EDITOR
|
||||
if (targetInput != null)
|
||||
targetInput.text = editorInput.ToString();
|
||||
#endif
|
||||
}
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
if (ReferenceEquals(targetInput, null))
|
||||
{
|
||||
Debug.LogError($"{name}配置项目条目的基本元素不存在");
|
||||
return;
|
||||
}
|
||||
targetInput.onSelect.AddListener(a =>
|
||||
{
|
||||
WhenStartEdit();
|
||||
});
|
||||
targetInput.onValueChanged.AddListener(o =>
|
||||
{
|
||||
double value = 0;
|
||||
if (double.TryParse(o, out value))
|
||||
{
|
||||
SetValue(value);
|
||||
}
|
||||
});
|
||||
targetInput.onEndEdit.AddListener(delegate
|
||||
{
|
||||
WhenEndEdit();
|
||||
});
|
||||
if (!ReferenceEquals(digitalAddButton, null))
|
||||
digitalAddButton.onClick.AddListener(delegate
|
||||
{
|
||||
SetValue(CastDigitalNumber(Value) + delta);
|
||||
RefreshFormatValue(Value);
|
||||
});
|
||||
if (!ReferenceEquals(digitalSubButton, null))
|
||||
digitalSubButton.onClick.AddListener(delegate
|
||||
{
|
||||
SetValue(CastDigitalNumber(Value) - delta);
|
||||
RefreshFormatValue(Value);
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 实现
|
||||
|
||||
protected override void SetValue(object newValue)
|
||||
{
|
||||
base.SetValue(CastDigitalNumber(newValue, minValue, maxValue, digits));
|
||||
}
|
||||
|
||||
public override void RefreshValueWithoutEvent(object newValue, bool forceSet = false)
|
||||
{
|
||||
base.RefreshValueWithoutEvent(CastDigitalNumber(newValue, minValue, maxValue, digits), forceSet);
|
||||
}
|
||||
|
||||
protected override void Initialization_ChildConstruction(UIBehaviour component)
|
||||
{
|
||||
base.Initialization_ChildConstruction(component);
|
||||
|
||||
|
||||
if (component is TMP_InputField inputField)
|
||||
{
|
||||
if (targetInput != null)
|
||||
targetInput = inputField;
|
||||
}
|
||||
else if(component is Button button)
|
||||
{
|
||||
var name = component.transform.name;
|
||||
switch (name)
|
||||
{
|
||||
case "subbutton":
|
||||
case "SubButton":
|
||||
if (digitalSubButton != null)
|
||||
digitalSubButton = button;
|
||||
break;
|
||||
case "addbutton":
|
||||
case "AddButton":
|
||||
if (digitalAddButton != null)
|
||||
digitalAddButton = button;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RefreshFormatValue(object newValue)
|
||||
{
|
||||
base.RefreshFormatValue(newValue);
|
||||
targetInput.text = CastDigitalNumber(newValue).ToString(numberFormat);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.Serialization;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace LRC
|
||||
{
|
||||
public class ConfigDropdown : ConfigSelectableItem
|
||||
{
|
||||
#region ÊôÐÔ×Ö¶Î
|
||||
|
||||
public TMP_Dropdown targetDropdown;
|
||||
|
||||
#endregion
|
||||
|
||||
#region ÉúÃüÖÜÆÚ
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
targetDropdown.onValueChanged.AddListener(index =>
|
||||
{
|
||||
SetValue(index);
|
||||
});
|
||||
}
|
||||
|
||||
public void InitializationDropdown(IEnumerable<string> options)
|
||||
{
|
||||
targetDropdown.options = options
|
||||
.Select(option => new TMP_Dropdown.OptionData(option)).ToList();
|
||||
targetDropdown.RefreshShownValue();
|
||||
if (IsValueInitNull)
|
||||
RefreshValueWithoutEvent(0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ʵÏÖ
|
||||
|
||||
protected override void SetValue(object newValue)
|
||||
{
|
||||
base.SetValue(CastIntNumber(newValue));
|
||||
}
|
||||
|
||||
public override void RefreshValueWithoutEvent(object newValue, bool forceSet = false)
|
||||
{
|
||||
base.RefreshValueWithoutEvent(CastIntNumber(newValue), forceSet);
|
||||
}
|
||||
|
||||
protected override void Initialization_ChildConstruction(UIBehaviour component)
|
||||
{
|
||||
base.Initialization_ChildConstruction(component);
|
||||
|
||||
var name = component.transform.name;
|
||||
if (name is not "dropdown") return;
|
||||
if (component is TMP_Dropdown dropdown)
|
||||
{
|
||||
if (targetDropdown != null)
|
||||
targetDropdown = dropdown;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RefreshFormatValue(object newValue)
|
||||
{
|
||||
base.RefreshFormatValue(newValue);
|
||||
if (newValue == null)
|
||||
return;
|
||||
targetDropdown.value = CastIntNumber(newValue);
|
||||
targetDropdown.RefreshShownValue();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,712 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.Serialization;
|
||||
using UnityEngine.UI;
|
||||
using XericLibrary.Runtime.CustomEditor;
|
||||
using XericLibrary.Runtime.MacroLibrary;
|
||||
|
||||
namespace LRC
|
||||
{
|
||||
/// <summary>
|
||||
/// 一种用于基础面板设置条目的类
|
||||
/// <code>
|
||||
/// 在此类中提供了双向的属性委托修改,并且拥有脏标记。
|
||||
/// 该类在初始化时以输入为优先。
|
||||
/// 脏标记仅当界面首次发生改变时进行记录,并不会影响值。
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public abstract class ConfigSelectableItem : MonoBehaviour
|
||||
{
|
||||
#region 静态事件
|
||||
|
||||
private static event Action<bool, bool> ForceUpdateAllConfigEvent;
|
||||
/// <summary>
|
||||
/// 强制所有激活的成员产生请求更新的事件(比如初始化时,外部更新时)
|
||||
/// </summary>
|
||||
/// <param name="resetDirty"></param>
|
||||
public static void ForceUpdateAll(bool resetDirty = false, bool forceSet = false)
|
||||
{
|
||||
ForceUpdateAllConfigEvent?.Invoke(resetDirty, forceSet);
|
||||
}
|
||||
|
||||
private static event Action ForceSetAllSourceValueEvent;
|
||||
/// <summary>
|
||||
/// 强制所有激活的成员产生请求设置的事件(比如按下应用按钮时)
|
||||
/// </summary>
|
||||
public static void ForceSetAllSourceValue()
|
||||
{
|
||||
ForceSetAllSourceValueEvent?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数值遭到修改时
|
||||
/// </summary>
|
||||
public static event Action<ConfigSelectableItem> OnAnyValueDirty;
|
||||
|
||||
/// <summary>
|
||||
/// 当数值收到修改时,产生事件,其中值的含义是修改前的值
|
||||
/// </summary>
|
||||
public static event Action<ConfigSelectableItem, object> OnAnyValueChange;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 静态成员
|
||||
|
||||
/// <summary>
|
||||
/// 所有有效的配置项目,当项目被隐藏(不处于激活状态时)就会从列表中删除。
|
||||
/// </summary>
|
||||
private static readonly List<ConfigSelectableItem> ConfigItemList = new List<ConfigSelectableItem>();
|
||||
|
||||
/// <summary>
|
||||
/// 项目列表有效成员的数量
|
||||
/// </summary>
|
||||
public static int ConfigItemListCount => ConfigItemList.Count;
|
||||
|
||||
/// <summary>
|
||||
/// 获取
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <returns></returns>
|
||||
public static ConfigSelectableItem GetConfigItemByIndex(int index) => ConfigItemList[index];
|
||||
public static IEnumerable<ConfigSelectableItem> ConfigItemLists => ConfigItemList;
|
||||
|
||||
private static bool autoUpdateValueAll;
|
||||
/// <summary>
|
||||
/// 自动更新所有数值
|
||||
/// </summary>
|
||||
public static bool AutoUpdateValueAll
|
||||
{
|
||||
get => autoUpdateValueAll;
|
||||
set
|
||||
{
|
||||
foreach (var item in ConfigItemList)
|
||||
item.autoUpdateValue = value;
|
||||
autoUpdateValueAll = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 状态
|
||||
|
||||
/// <summary>
|
||||
/// 数值遭到修改时,标记脏,这样在后续的应用中有可以保存的变化
|
||||
/// <code>
|
||||
/// 建议:这是可选的
|
||||
/// 这会立刻发生在值修改时,且比OnValueChange更早,但在复位之前只会调用一次。
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public event Action<ConfigSelectableItem> OnValueDirty;
|
||||
|
||||
/// <summary>
|
||||
/// 当数值收到修改时,产生事件,其中值的含义是修改前的值
|
||||
/// <code>
|
||||
/// 建议:这是可选的
|
||||
/// 这会立刻发生在值修改时,所以如果需要进行页面刷新的话,建议进行等待
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public event Action<ConfigSelectableItem, object> OnValueChange;
|
||||
|
||||
/// <summary>
|
||||
/// 当数值结束修改时
|
||||
/// </summary>
|
||||
public event Action<ConfigSelectableItem, object> OnEndEdit;
|
||||
/// <summary>
|
||||
/// 当前属性请求获取属性值
|
||||
/// <code>
|
||||
/// 建议:这是必要的
|
||||
/// 当发生刷新事件时,将产生调用。
|
||||
/// 含义是将外部的值设定到此。
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public event Func<ConfigSelectableItem, object> GetSourceValue;
|
||||
|
||||
/// <summary>
|
||||
/// 当前属性请求设置属性值
|
||||
/// <code>
|
||||
/// 建议:这是必要的
|
||||
/// 当被应用时,比如调用了ForceSetAllSourceValue,将产生调用。
|
||||
/// 含义是将此处的界面值返回到属性中。
|
||||
/// 警告:不要在这里面调用ForceSetAllSourceValue,SetSourceValueRequest等方法,
|
||||
/// 这会造成死循环以及内存溢出等问题。
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public event Action<ConfigSelectableItem, object> SetSourceValue;
|
||||
|
||||
/// <summary>
|
||||
/// 当前属性的自动更新请求获取
|
||||
/// <code>
|
||||
/// 建议:这是条件可选的
|
||||
/// 当被管理的目标中存在Toggle时(对应autoUpdateValueToggle项目)
|
||||
/// 此处将用于从外部的值是否勾选值设定到此
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public event Func<ConfigSelectableItem, bool> GetSourceValueAutoUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// 当前属性的自动更新请求设置
|
||||
/// <code>
|
||||
/// 建议:这是条件可选的
|
||||
/// 当被管理的目标中存在Toggle时(对应autoUpdateValueToggle项目)
|
||||
/// 此处将用于设置到外部的是否勾选值设定到此
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public event Action<bool> SetSourceValueAutoUpdate;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 请求此属性设置值(将值应用到寄存中)
|
||||
/// </summary>
|
||||
public void SetSourceValueRequest()
|
||||
{
|
||||
SetSourceValue?.Invoke(this, Value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 属性字段
|
||||
|
||||
/// <summary>
|
||||
/// 项目对应的标记tag
|
||||
/// </summary>
|
||||
[Rename("映射标记")]
|
||||
public string configTag;
|
||||
|
||||
/// <summary>
|
||||
/// 设定是否有效。
|
||||
/// 如果无效,则不允许输入
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
[Rename("允许输入")]
|
||||
private bool allowInput = true;
|
||||
|
||||
/// <summary>
|
||||
/// 自动更新数值
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
[Rename("主动更新")]
|
||||
private bool autoUpdateValue = true;
|
||||
|
||||
/// <summary>
|
||||
/// 允许阻塞自动更新,表示此项目可以脱离自动更新状态选框的状态运行
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
[Rename("允许阻塞自动更新")]
|
||||
private bool allowBlockUpdate = true;
|
||||
|
||||
/// <summary>
|
||||
/// 阻塞自动更新,一般由程序自动根据焦点更新
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
[Rename("阻塞自动更新")]
|
||||
private bool forceBlockUpdate = false;
|
||||
|
||||
/// <summary>
|
||||
/// 格式化文本
|
||||
/// </summary>
|
||||
public string numberFormat = "0.##";
|
||||
|
||||
/// <summary>
|
||||
/// 项目标题组件
|
||||
/// </summary>
|
||||
public TextMeshProUGUI titleLabel;
|
||||
#if UNITY_EDITOR
|
||||
[SerializeField]
|
||||
#endif
|
||||
private string titleTextContext;
|
||||
|
||||
/// <summary>
|
||||
/// 帮助文本组件
|
||||
/// </summary>
|
||||
public TextMeshProUGUI helpLabel;
|
||||
#if UNITY_EDITOR
|
||||
[SerializeField]
|
||||
#endif
|
||||
private string helpTextContext;
|
||||
|
||||
/// <summary>
|
||||
/// 单位文本组件
|
||||
/// </summary>
|
||||
public TextMeshProUGUI unitLabel;
|
||||
#if UNITY_EDITOR
|
||||
[SerializeField]
|
||||
#endif
|
||||
private string unitTextContext;
|
||||
|
||||
/// <summary>
|
||||
/// 条目前的选框,与autoUpdateValue对应
|
||||
/// </summary>
|
||||
[Rename("自动更新状态选框")]
|
||||
public Toggle autoUpdateValueToggle;
|
||||
|
||||
/// <summary>
|
||||
/// 反转选框状态
|
||||
/// </summary>
|
||||
[Rename("反转自动更新按钮状态")]
|
||||
public bool autoUpdateValueToggleReversalState;
|
||||
|
||||
[Rename("反转自动更新状态")]
|
||||
public bool autoUpdateValueReversalState;
|
||||
|
||||
/// <summary>
|
||||
/// 脏属性自动复位。
|
||||
/// 当属性被重新开关时,将取消脏状态
|
||||
/// </summary>
|
||||
[Rename("脏标记自动复位")]
|
||||
public bool dirtyAutoReset = false;
|
||||
|
||||
// ==== 属性 ==== //
|
||||
|
||||
/// <summary>
|
||||
/// 设定是否输入有效
|
||||
/// </summary>
|
||||
public virtual bool AllowInput
|
||||
{
|
||||
get => allowInput;
|
||||
set => allowInput = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 此属性项目已经初始化
|
||||
/// </summary>
|
||||
[HideInInspector]
|
||||
public bool IsValueInitNull { get; private set; } = true;
|
||||
|
||||
// [Obsolete("请使用访问器")]
|
||||
private bool _isDirty;
|
||||
/// <summary>
|
||||
/// 脏属性标记,首次脏会进行广播
|
||||
/// </summary>
|
||||
private bool IsDirty
|
||||
{
|
||||
get => _isDirty;
|
||||
set
|
||||
{
|
||||
if (_isDirty)
|
||||
{
|
||||
if (!value)
|
||||
_isDirty = false;
|
||||
return;
|
||||
}
|
||||
if (!value) return;
|
||||
|
||||
_isDirty = true;
|
||||
OnAnyValueDirty?.Invoke(this);
|
||||
OnValueDirty?.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设定自动更新状态,并同步选框
|
||||
/// <code>
|
||||
/// 为了书写方便,保存的状态是和外部暂存值一致,
|
||||
/// 此访问器将作为本地状态的标准访问。
|
||||
///
|
||||
/// 访问将自动进行转换,而设置则不进行转换,
|
||||
/// 所以应当使用此访问器进行设置或内部访问,而与外部通讯应当使用字段。
|
||||
///
|
||||
/// 如果需要将此内部判断状态转为界面状态:
|
||||
/// 应当先通过autoUpdateValueReversalState转为暂存值,
|
||||
/// 然后再通过autoUpdateValueToggleReversalState转为界面值。
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public bool AutoUpdateValue
|
||||
{
|
||||
get => autoUpdateValueReversalState ^ autoUpdateValue;
|
||||
set
|
||||
{
|
||||
autoUpdateValue = value;
|
||||
if (autoUpdateValueToggle != null)
|
||||
autoUpdateValueToggle.isOn = autoUpdateValueToggleReversalState ^ value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设定阻塞自动更新
|
||||
/// </summary>
|
||||
public bool ForceBlockUpdate
|
||||
{
|
||||
get => forceBlockUpdate;
|
||||
set => forceBlockUpdate = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 项目标题
|
||||
/// </summary>
|
||||
public string TitleName
|
||||
{
|
||||
get => titleLabel.text;
|
||||
set => titleLabel.text = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 帮助文本,再默认情况下应该是不显示的通过赋予空文本来关闭显示
|
||||
/// </summary>
|
||||
public string HelpName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (helpLabel != null) return helpLabel.text;
|
||||
return null;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (helpLabel != null)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
helpLabel.text = value;
|
||||
helpLabel.gameObject.SetActive(true);
|
||||
}
|
||||
else
|
||||
helpLabel.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string UnitName
|
||||
{
|
||||
get => unitLabel.text;
|
||||
set => unitLabel.text = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
// [Obsolete("请使用访问器")]
|
||||
private object _value;
|
||||
|
||||
/// <summary>
|
||||
/// 项目值
|
||||
/// </summary>
|
||||
public object Value
|
||||
{
|
||||
get => _value;
|
||||
protected set
|
||||
{
|
||||
if (IsValueInitNull && value != null)
|
||||
IsValueInitNull = false;
|
||||
_value = value;
|
||||
}
|
||||
}
|
||||
|
||||
// ==== 暂存 ==== //
|
||||
|
||||
public FieldInfo FieldInfo;
|
||||
|
||||
public Type FieldType;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 生命周期
|
||||
|
||||
protected virtual void OnValidate()
|
||||
{
|
||||
if (titleLabel != null)
|
||||
TitleName = titleTextContext;
|
||||
if (helpLabel != null)
|
||||
HelpName = helpTextContext;
|
||||
if (unitLabel != null)
|
||||
UnitName = unitTextContext;
|
||||
}
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
foreach (var child in transform.GetChildren())
|
||||
{
|
||||
var component = child.GetComponent<UIBehaviour>();
|
||||
if (component != null)
|
||||
Initialization_ChildConstruction(component);
|
||||
}
|
||||
|
||||
Initialization_EventBinding();
|
||||
}
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
ConfigItemList.Add(this);
|
||||
ForceUpdateAllConfigEvent += ForceUpdate;
|
||||
ForceSetAllSourceValueEvent += SetSourceValueRequest;
|
||||
|
||||
if (dirtyAutoReset)
|
||||
IsDirty = false;
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
ConfigItemList.Remove(this);
|
||||
ForceUpdateAllConfigEvent -= ForceUpdate;
|
||||
ForceSetAllSourceValueEvent -= SetSourceValueRequest;
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
ForceUpdate(true);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (IsValueInitNull || (AutoUpdateValue && !(allowBlockUpdate && forceBlockUpdate)))
|
||||
{
|
||||
ForceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
ForceUpdateAllConfigEvent -= ForceUpdate;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 换算与转换
|
||||
|
||||
protected static double CastDigitalNumber(object newValue)
|
||||
{
|
||||
if (double.TryParse(newValue.ToString(), out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换到数值类型(数值钳制与四舍五入)
|
||||
/// </summary>
|
||||
/// <param name="newValue"></param>
|
||||
/// <param name="minValue"></param>
|
||||
/// <param name="maxValue"></param>
|
||||
/// <param name="digits"></param>
|
||||
/// <returns></returns>
|
||||
protected static double CastDigitalNumber(object newValue, double minValue, double maxValue, int digits)
|
||||
{
|
||||
// var resultValue = newValue switch
|
||||
// {
|
||||
// bool boolValue => boolValue ? 1 : 0,
|
||||
// short intValue => intValue,
|
||||
// int intValue => intValue,
|
||||
// long intValue => intValue,
|
||||
// float floatValue => Math.Round(floatValue, digits),
|
||||
// double doubleValue => Math.Round(doubleValue, digits),
|
||||
// _ => 0
|
||||
// };
|
||||
#if UNITY_EDITOR
|
||||
if (newValue == null)
|
||||
{
|
||||
Debug.LogError($"参数转换错误:目标是一个空值,请在发生空值时提前退出");
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
if (double.TryParse(newValue.ToString(), out var value))
|
||||
{
|
||||
var resultValue = Math.Round(value, digits);
|
||||
return Math.Clamp(resultValue, minValue, maxValue);
|
||||
}
|
||||
Debug.LogError($"参数转换错误:{newValue}可能不是一个有效的数值");
|
||||
return -1;
|
||||
}
|
||||
|
||||
protected static int CastIntNumber(object newValue)
|
||||
{
|
||||
var resultValue = Convert.ToInt32(newValue);
|
||||
return resultValue;
|
||||
}
|
||||
|
||||
protected static Vector2 CastVector2(object newValue)
|
||||
{
|
||||
return newValue switch
|
||||
{
|
||||
Vector2 vec2Value => vec2Value,
|
||||
System.Numerics.Vector2 sysVec2Value => new Vector2(sysVec2Value.X, sysVec2Value.Y),
|
||||
_ => Vector2.zero
|
||||
};
|
||||
}
|
||||
|
||||
protected static Vector3 CastVector3(object newValue)
|
||||
{
|
||||
return newValue switch
|
||||
{
|
||||
Vector3 vec3Value => vec3Value,
|
||||
System.Numerics.Vector3 sysVec3Value => new Vector3(sysVec3Value.X, sysVec3Value.Y, sysVec3Value.Z),
|
||||
_ => Vector3.zero
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 初始化方法
|
||||
|
||||
/// <summary>
|
||||
/// 可选的基本初始化
|
||||
/// </summary>
|
||||
/// <param name="autoUpdateValue">设定自动更新</param>
|
||||
protected void Initialization(bool autoUpdateValue = true)
|
||||
{
|
||||
AutoUpdateValue = autoUpdateValue;
|
||||
}
|
||||
|
||||
private bool _doOnce = false;
|
||||
/// <summary>
|
||||
/// 构建属性,在初始化过程中,通过识别项目来构建域
|
||||
/// </summary>
|
||||
protected virtual void Initialization_ChildConstruction(UIBehaviour component)
|
||||
{
|
||||
// 跟踪节点的名称
|
||||
var name = component.gameObject.name;
|
||||
// 初始化自动更新对勾
|
||||
if (name == "toggle" && component is Toggle toggle)
|
||||
{
|
||||
if (autoUpdateValueToggle == null)
|
||||
autoUpdateValueToggle = toggle;
|
||||
return;
|
||||
}
|
||||
if (name is not ("title" or "name")) return;
|
||||
if (_doOnce)
|
||||
{
|
||||
Debug.LogError("存在重复命名的属性模块:" + name);
|
||||
return;
|
||||
}
|
||||
if (component is TextMeshProUGUI textMeshProUGUI)
|
||||
{
|
||||
_doOnce = true;
|
||||
if (titleLabel != null)
|
||||
titleLabel = textMeshProUGUI;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建事件,在初始化结束前,需要对所有动态绑定的按键进行事件绑定
|
||||
/// </summary>
|
||||
protected virtual void Initialization_EventBinding()
|
||||
{
|
||||
// 无论如何,事件需要被注册
|
||||
if (autoUpdateValueToggle != null)
|
||||
{
|
||||
autoUpdateValueToggle.onValueChanged.AddListener(a =>
|
||||
{
|
||||
autoUpdateValue = autoUpdateValueToggleReversalState ^ a;
|
||||
// 访问器将会自动转换,所以使用字段
|
||||
SetSourceValueAutoUpdate?.Invoke(autoUpdateValue);
|
||||
// 可能不会脏
|
||||
// OnValueDirty?.Invoke(this);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 更新方法
|
||||
|
||||
/// <summary>
|
||||
/// 更新函数
|
||||
/// <code>
|
||||
/// 在其他任意时刻调用时,将强制刷新其中的功能,如果已经是脏属性则不会有任何行为。
|
||||
/// 更新是指从本地数据中更新,这会立刻产生值获取回调。
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public virtual void ForceUpdate(bool resetDirty = false, bool forceSet = false)
|
||||
{
|
||||
if (resetDirty)
|
||||
IsDirty = false;
|
||||
var value = GetSourceValue?.Invoke(this);
|
||||
if (value == null)
|
||||
return ;
|
||||
RefreshValueWithoutEvent(value, forceSet);
|
||||
RefreshAutoUpdateValue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置值,并触发更改事件,通常由界面发起所以不会主动更新界面
|
||||
/// </summary>
|
||||
/// <param name="newValue"></param>
|
||||
protected virtual void SetValue(object newValue)
|
||||
{
|
||||
if (Value == newValue)
|
||||
return;
|
||||
var lastValue = Value;
|
||||
// 如果不允许输入,将复位为现在的值
|
||||
if (!allowInput)
|
||||
RefreshValueWithoutEvent(Value);
|
||||
else
|
||||
{
|
||||
Value = newValue;
|
||||
IsDirty = true;
|
||||
|
||||
OnAnyValueChange?.Invoke(this, lastValue);
|
||||
OnValueChange?.Invoke(this, lastValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置值,不触发更改时的事件,但应该进行界面数值更新。
|
||||
/// </summary>
|
||||
/// <param name="newValue"></param>
|
||||
/// <param name="forceSet"></param>
|
||||
public virtual void RefreshValueWithoutEvent(object newValue, bool forceSet = false)
|
||||
{
|
||||
if (!AutoUpdateValue && !forceSet)
|
||||
return;
|
||||
if (newValue == null)
|
||||
return;
|
||||
if (Value != null && (Value.ToString() == newValue.ToString()))
|
||||
return;
|
||||
Value = newValue;
|
||||
RefreshFormatValue(newValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新自动更新状态
|
||||
/// </summary>
|
||||
protected void RefreshAutoUpdateValue()
|
||||
{
|
||||
var isSourceValueAutoUpdate = GetSourceValueAutoUpdate?.Invoke(this);
|
||||
if (isSourceValueAutoUpdate != null)
|
||||
AutoUpdateValue = isSourceValueAutoUpdate.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新格式化文本,在触发刷新时将产生更新
|
||||
/// </summary>
|
||||
protected virtual void RefreshFormatValue(object newValue)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected void WhenStartEdit()
|
||||
{
|
||||
// Debug.Log("开始编辑");
|
||||
ForceBlockUpdate = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数值输入完毕,或焦点移除时调用。
|
||||
/// </summary>
|
||||
protected void WhenEndEdit()
|
||||
{
|
||||
// Debug.Log("结束编辑");
|
||||
ForceBlockUpdate = false;
|
||||
OnEndEdit?.Invoke(this, Value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 外观设定
|
||||
|
||||
/// <summary>
|
||||
/// 销毁自动更新复选框,并设定当前是否允许自动更新
|
||||
/// </summary>
|
||||
/// <param name="autoUpdateValue"></param>
|
||||
public void DestroyAutoUpdateValue(bool autoUpdateValue)
|
||||
{
|
||||
Destroy(autoUpdateValueToggle.gameObject);
|
||||
this.autoUpdateValue = autoUpdateValueReversalState ^ autoUpdateValue;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace LRC
|
||||
{
|
||||
/// <summary>
|
||||
/// 一种普通文本输入框
|
||||
/// </summary>
|
||||
public class ConfigTextInputField : ConfigSelectableItem
|
||||
{
|
||||
#region 属性字段
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public string editorInput;
|
||||
#endif
|
||||
|
||||
public TMP_InputField targetInput;
|
||||
|
||||
|
||||
public override bool AllowInput
|
||||
{
|
||||
get => base.AllowInput;
|
||||
set
|
||||
{
|
||||
if (targetInput != null)
|
||||
targetInput.readOnly = !value;
|
||||
base.AllowInput = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 生命周期
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
#if UNITY_EDITOR
|
||||
if (targetInput != null)
|
||||
targetInput.text = editorInput;
|
||||
#endif
|
||||
}
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
targetInput.onSelect.AddListener(a =>
|
||||
{
|
||||
WhenStartEdit();
|
||||
});
|
||||
targetInput.onValueChanged.AddListener(o =>
|
||||
{
|
||||
if(double.TryParse(o, out var value))
|
||||
SetValue(value);
|
||||
});
|
||||
targetInput.onEndEdit.AddListener(delegate
|
||||
{
|
||||
WhenEndEdit();
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 实现
|
||||
|
||||
protected override void SetValue(object newValue)
|
||||
{
|
||||
base.SetValue(newValue);
|
||||
}
|
||||
|
||||
public override void RefreshValueWithoutEvent(object newValue, bool forceSet = false)
|
||||
{
|
||||
base.RefreshValueWithoutEvent(newValue, forceSet);
|
||||
}
|
||||
|
||||
protected override void Initialization_ChildConstruction(UIBehaviour component)
|
||||
{
|
||||
base.Initialization_ChildConstruction(component);
|
||||
|
||||
if (component is TMP_InputField inputField)
|
||||
{
|
||||
if (targetInput != null)
|
||||
targetInput = inputField;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RefreshFormatValue(object newValue)
|
||||
{
|
||||
base.RefreshFormatValue(newValue);
|
||||
targetInput.text = newValue.ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.Serialization;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace LRC
|
||||
{
|
||||
/// <summary>
|
||||
/// 单选项控制组,将返回一个代表当前有效单选项索引作为值。
|
||||
/// </summary>
|
||||
public class ConfigToggleGroup : ConfigSelectableItem
|
||||
{
|
||||
#region 属性字段
|
||||
|
||||
public ToggleGroup targetToggle;
|
||||
|
||||
public List<Toggle> toggles;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 生命周期
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
for (int i = 0; i < toggles.Count; i++)
|
||||
{
|
||||
void BuildInGroupInit()
|
||||
{
|
||||
var index = i;
|
||||
var toggle = toggles[i];
|
||||
toggle.onValueChanged.AddListener(a =>
|
||||
{
|
||||
if (a)
|
||||
SetValue(index);
|
||||
});
|
||||
}
|
||||
BuildInGroupInit();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 实现
|
||||
|
||||
protected override void SetValue(object newValue)
|
||||
{
|
||||
base.SetValue(CastIntNumber(newValue));
|
||||
}
|
||||
|
||||
public override void RefreshValueWithoutEvent(object newValue, bool forceSet = false)
|
||||
{
|
||||
base.RefreshValueWithoutEvent(CastIntNumber(newValue), forceSet);
|
||||
}
|
||||
|
||||
protected override void Initialization_ChildConstruction(UIBehaviour component)
|
||||
{
|
||||
base.Initialization_ChildConstruction(component);
|
||||
|
||||
if (component is Toggle toggle &&
|
||||
!toggles.Contains(toggle))
|
||||
toggles.Add(toggle);
|
||||
}
|
||||
|
||||
protected override void RefreshFormatValue(object newValue)
|
||||
{
|
||||
base.RefreshFormatValue(newValue);
|
||||
var newIndex = CastIntNumber(newValue);
|
||||
// var index = 0;
|
||||
// foreach (var toggle in targetToggle.ActiveToggles())
|
||||
// {
|
||||
// if (newIndex != index++)
|
||||
// continue;
|
||||
// toggle.isOn = true;
|
||||
// Debug.Log(TitleName +":设置布尔项目");
|
||||
// }
|
||||
toggles[newIndex].SetIsOnWithoutNotify(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace LRC
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class ConfigTypeOverrideAttribute : PropertyAttribute
|
||||
{
|
||||
public readonly string TypeName;
|
||||
|
||||
public ConfigTypeOverrideAttribute(string name)
|
||||
{
|
||||
TypeName = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取其中的rename定义名称
|
||||
/// </summary>
|
||||
/// <param name="field"></param>
|
||||
/// <param name="name">通过特性获取的名称,如果没有则为原生的名称</param>
|
||||
/// <returns>是否成功通过特性获取名称,否则是普通类型名称</returns>
|
||||
public static bool TryGetMemberName(FieldInfo field, out string name)
|
||||
{
|
||||
if (field.GetCustomAttribute(typeof(ConfigTypeOverrideAttribute)) is ConfigTypeOverrideAttribute rename)
|
||||
{
|
||||
name = rename.TypeName;
|
||||
return true;
|
||||
}
|
||||
|
||||
name = field.FieldType.Name;
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using TMPro;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace LRC
|
||||
{
|
||||
public class ConfigVec2InputField : ConfigSelectableItem
|
||||
{
|
||||
#region ÊôÐÔ×Ö¶Î
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public Vector2 editorInput;
|
||||
#endif
|
||||
|
||||
public TMP_InputField targetInputX;
|
||||
public TMP_InputField targetInputY;
|
||||
|
||||
public override bool AllowInput
|
||||
{
|
||||
get => base.AllowInput;
|
||||
set
|
||||
{
|
||||
if (targetInputX != null)
|
||||
targetInputX.readOnly = !value;
|
||||
if (targetInputY != null)
|
||||
targetInputY.readOnly = value;
|
||||
base.AllowInput = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ÉúÃüÖÜÆÚ
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
#if UNITY_EDITOR
|
||||
if (targetInputX != null)
|
||||
targetInputX.text = editorInput.x.ToString();
|
||||
if (targetInputY != null)
|
||||
targetInputY.text = editorInput.y.ToString();
|
||||
#endif
|
||||
}
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
targetInputX.onSelect.AddListener(a =>
|
||||
{
|
||||
WhenStartEdit();
|
||||
});
|
||||
targetInputY.onSelect.AddListener(a =>
|
||||
{
|
||||
WhenStartEdit();
|
||||
});
|
||||
targetInputX.onValueChanged.AddListener(a =>
|
||||
{
|
||||
var value = CastVector3(Value);
|
||||
value.x = float.Parse(a);
|
||||
SetValue(value);
|
||||
});
|
||||
targetInputY.onValueChanged.AddListener(a =>
|
||||
{
|
||||
var value = CastVector3(Value);
|
||||
value.y = float.Parse(a);
|
||||
SetValue(value);
|
||||
});
|
||||
targetInputX.onEndEdit.AddListener(delegate
|
||||
{
|
||||
WhenEndEdit();
|
||||
});
|
||||
targetInputY.onEndEdit.AddListener(delegate
|
||||
{
|
||||
WhenEndEdit();
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ʵÏÖ
|
||||
|
||||
protected override void SetValue(object newValue)
|
||||
{
|
||||
base.SetValue(CastVector2(newValue));
|
||||
}
|
||||
|
||||
public override void RefreshValueWithoutEvent(object newValue, bool forceSet = false)
|
||||
{
|
||||
base.RefreshValueWithoutEvent(CastVector2(newValue), forceSet);
|
||||
}
|
||||
|
||||
protected override void Initialization_ChildConstruction(UIBehaviour component)
|
||||
{
|
||||
base.Initialization_ChildConstruction(component);
|
||||
|
||||
var name = component.transform.name;
|
||||
if (component is not TMP_InputField inputField)
|
||||
return;
|
||||
switch (name)
|
||||
{
|
||||
case "inputx":
|
||||
case "inputX":
|
||||
if (targetInputX != null)
|
||||
targetInputX = inputField;
|
||||
break;
|
||||
case "inputy":
|
||||
case "inputY":
|
||||
if (targetInputY != null)
|
||||
targetInputY = inputField;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RefreshFormatValue(object newValue)
|
||||
{
|
||||
base.RefreshFormatValue(newValue);
|
||||
var value = CastVector2(newValue);
|
||||
targetInputX.text = value.x.ToString(numberFormat);
|
||||
targetInputY.text = value.y.ToString(numberFormat);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using TMPro;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace LRC
|
||||
{
|
||||
|
||||
public class ConfigVec3InputField : ConfigSelectableItem
|
||||
{
|
||||
#region ÊôÐÔ×Ö¶Î
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public Vector3 editorInput;
|
||||
#endif
|
||||
|
||||
public TMP_InputField targetInputX;
|
||||
public TMP_InputField targetInputY;
|
||||
public TMP_InputField targetInputZ;
|
||||
|
||||
public override bool AllowInput
|
||||
{
|
||||
get => base.AllowInput;
|
||||
set
|
||||
{
|
||||
if (targetInputX != null)
|
||||
targetInputX.readOnly = !value;
|
||||
if (targetInputY != null)
|
||||
targetInputY.readOnly = value;
|
||||
if (targetInputZ != null)
|
||||
targetInputZ.readOnly = value;
|
||||
base.AllowInput = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ÉúÃüÖÜÆÚ
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
#if UNITY_EDITOR
|
||||
if (targetInputX != null)
|
||||
targetInputX.text = editorInput.x.ToString();
|
||||
if (targetInputY != null)
|
||||
targetInputY.text = editorInput.y.ToString();
|
||||
if (targetInputZ != null)
|
||||
targetInputZ.text = editorInput.z.ToString();
|
||||
#endif
|
||||
}
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
targetInputX.onSelect.AddListener(a =>
|
||||
{
|
||||
WhenStartEdit();
|
||||
});
|
||||
targetInputY.onSelect.AddListener(a =>
|
||||
{
|
||||
WhenStartEdit();
|
||||
});
|
||||
targetInputZ.onSelect.AddListener(a =>
|
||||
{
|
||||
WhenStartEdit();
|
||||
});
|
||||
targetInputX.onValueChanged.AddListener(a =>
|
||||
{
|
||||
var value = CastVector3(Value);
|
||||
value.x = float.Parse(a);
|
||||
SetValue(value);
|
||||
});
|
||||
targetInputY.onValueChanged.AddListener(a =>
|
||||
{
|
||||
var value = CastVector3(Value);
|
||||
value.y = float.Parse(a);
|
||||
SetValue(value);
|
||||
});
|
||||
targetInputZ.onValueChanged.AddListener(a =>
|
||||
{
|
||||
var value = CastVector3(Value);
|
||||
value.z = float.Parse(a);
|
||||
SetValue(value);
|
||||
});
|
||||
targetInputX.onEndEdit.AddListener(delegate
|
||||
{
|
||||
WhenEndEdit();
|
||||
});
|
||||
targetInputY.onEndEdit.AddListener(delegate
|
||||
{
|
||||
WhenEndEdit();
|
||||
});
|
||||
targetInputZ.onEndEdit.AddListener(delegate
|
||||
{
|
||||
WhenEndEdit();
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ʵÏÖ
|
||||
|
||||
protected override void SetValue(object newValue)
|
||||
{
|
||||
base.SetValue(CastVector3(newValue));
|
||||
}
|
||||
|
||||
public override void RefreshValueWithoutEvent(object newValue, bool forceSet = false)
|
||||
{
|
||||
base.RefreshValueWithoutEvent(CastVector3(newValue), forceSet);
|
||||
}
|
||||
|
||||
protected override void Initialization_ChildConstruction(UIBehaviour component)
|
||||
{
|
||||
base.Initialization_ChildConstruction(component);
|
||||
|
||||
var name = component.transform.name;
|
||||
if (component is not TMP_InputField inputField)
|
||||
return;
|
||||
switch (name)
|
||||
{
|
||||
case "inputx":
|
||||
case "inputX":
|
||||
if (targetInputX != null)
|
||||
targetInputX = inputField;
|
||||
break;
|
||||
case "inputy":
|
||||
case "inputY":
|
||||
if (targetInputY != null)
|
||||
targetInputY = inputField;
|
||||
break;
|
||||
case "inputz":
|
||||
case "inputZ":
|
||||
if (targetInputZ != null)
|
||||
targetInputZ = inputField;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RefreshFormatValue(object newValue)
|
||||
{
|
||||
base.RefreshFormatValue(newValue);
|
||||
var value = CastVector3(newValue);
|
||||
targetInputX.text = value.x.ToString(numberFormat);
|
||||
targetInputY.text = value.y.ToString(numberFormat);
|
||||
targetInputZ.text = value.z.ToString(numberFormat);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
>>>
|
||||
**_Package Documentation Template_**
|
||||
|
||||
Use this template to create preliminary, high-level documentation meant to introduce users to the feature and the sample files included in this package. When writing your documentation, do the following:
|
||||
|
||||
1. Follow instructions in blockquotes.
|
||||
|
||||
2. Replace angle brackets with the appropriate text. For example, replace "<package name>" with the official name of the package.
|
||||
|
||||
3. Delete sections that do not apply to your package. For example, a package containing only sample files does not have a "Using <package_name>" section, so this section can be removed.
|
||||
|
||||
4. After documentation is completed, make sure you delete all instructions and examples in blockquotes including this preamble and its title:
|
||||
|
||||
```
|
||||
>>>
|
||||
Delete all of the text between pairs of blockquote markdown.
|
||||
>>>
|
||||
```
|
||||
>>>
|
||||
|
||||
# About <package name>
|
||||
|
||||
>>>
|
||||
Name the heading of the first topic after the **displayName** of the package as it appears in the package manifest.
|
||||
|
||||
This first topic includes a brief, high-level explanation of the package and, if applicable, provides links to Unity Manual topics.
|
||||
|
||||
There are two types of packages:
|
||||
|
||||
- Packages that include features that augment the Unity Editor or Runtime.
|
||||
- Packages that include sample files.
|
||||
|
||||
Choose one of the following introductory paragraphs that best fits the package:
|
||||
>>>
|
||||
|
||||
Use the <package name> package to <list of the main uses for the package>. For example, use <package name> to create/generate/extend/capture <mention major use case, or a good example of what the package can be used for>. The <package name> package also includes <other relevant features or uses>.
|
||||
|
||||
> *or*
|
||||
|
||||
The <package name> package includes examples of <name of asset type, model, prefabs, and/or other GameObjects in the package>. For more information, see <xref to topic in the Unity Manual>.
|
||||
|
||||
>>>
|
||||
**_Examples:_**
|
||||
|
||||
Here are some examples for reference only. Do not include these in the final documentation file:
|
||||
|
||||
*Use the Unity Recorder package to capture and save in-game data. For example, use Unity Recorder to record an mp4 file during a game session. The Unity Recorder package also includes an interface for setting-up and triggering recording sessions.*
|
||||
|
||||
*The Timeline Examples package includes examples of Timeline assets, Timeline Instances, animation, GameObjects, and scripts that illustrate how to use Unity's Timeline. For more information, see [ Unity's Timeline](https://docs.unity3d.com/Manual/TimelineSection.html) in the [Unity Manual](https://docs.unity3d.com). For licensing and usage, see Package Licensing.*
|
||||
>>>
|
||||
|
||||
# Installing <package name>
|
||||
>>>
|
||||
Begin this section with a cross-reference to the official Unity Manual topic on how to install packages. If the package requires special installation instructions, include these steps in this section.
|
||||
>>>
|
||||
|
||||
To install this package, follow the instructions in the [Package Manager documentation](https://docs.unity3d.com/Packages/com.unity.package-manager-ui@latest/index.html).
|
||||
|
||||
>>>
|
||||
For some packages, there may be additional steps to complete the setup. You can add those here.
|
||||
>>>
|
||||
|
||||
In addition, you need to install the following resources:
|
||||
|
||||
- <name of resource>: To install, open *Window > <name of menu item>*. The resource appears <at this location>.
|
||||
- <name of sample>: To install, open *Window > <name of menu item>*. The new sample folder appears <at this location>.
|
||||
|
||||
|
||||
<a name="UsingPackageName"></a>
|
||||
# Using <package name>
|
||||
>>>
|
||||
The contents of this section depends on the type of package.
|
||||
|
||||
For packages that augment the Unity Editor with additional features, this section should include workflow and/or reference documentation:
|
||||
|
||||
* At a minimum, this section should include reference documentation that describes the windows, editors, and properties that the package adds to Unity. This reference documentation should include screen grabs (see how to add screens below), a list of settings, an explanation of what each setting does, and the default values of each setting.
|
||||
* Ideally, this section should also include a workflow: a list of steps that the user can easily follow that demonstrates how to use the feature. This list of steps should include screen grabs (see how to add screens below) to better describe how to use the feature.
|
||||
|
||||
For packages that include sample files, this section may include detailed information on how the user can use these sample files in their projects and scenes. However, workflow diagrams or illustrations could be included if deemed appropriate.
|
||||
|
||||
## How to add images
|
||||
|
||||
*(This section is for reference. Do not include in the final documentation file)*
|
||||
|
||||
If the [Using <package name>](#UsingPackageName) section includes screen grabs or diagrams, a link to the image must be added to this MD file, before or after the paragraph with the instruction or description that references the image. In addition, a caption should be added to the image link that includes the name of the screen or diagram. All images must be PNG files with underscores for spaces. No animated GIFs.
|
||||
|
||||
An example is included below:
|
||||
|
||||

|
||||
|
||||
Notice that the example screen shot is included in the images folder. All screen grabs and/or diagrams must be added and referenced from the images folder.
|
||||
|
||||
For more on the Unity documentation standards for creating and adding screen grabs, see this confluence page: https://confluence.hq.unity3d.com/pages/viewpage.action?pageId=13500715
|
||||
>>>
|
||||
|
||||
|
||||
|
||||
# Technical details
|
||||
## Requirements
|
||||
>>>
|
||||
This subtopic includes a bullet list with the compatible versions of Unity. This subtopic may also include additional requirements or recommendations for 3rd party software or hardware. An example includes a dependency on other packages. If you need to include references to non-Unity products, make sure you refer to these products correctly and that all references include the proper trademarks (tm or r)
|
||||
>>>
|
||||
|
||||
This version of <package name> is compatible with the following versions of the Unity Editor:
|
||||
|
||||
* 2018.1 and later (recommended)
|
||||
|
||||
To use this package, you must have the following 3rd party products:
|
||||
|
||||
* <product name and version with trademark or registered trademark.>
|
||||
* <product name and version with trademark or registered trademark.>
|
||||
* <product name and version with trademark or registered trademark.>
|
||||
|
||||
## Known limitations
|
||||
>>>
|
||||
This section lists the known limitations with this version of the package. If there are no known limitations, or if the limitations are trivial, exclude this section. An example is provided.
|
||||
>>>
|
||||
|
||||
<package name> version <package version> includes the following known limitations:
|
||||
|
||||
* <brief one-line description of first limitation.>
|
||||
* <brief one-line description of second limitation.>
|
||||
* <and so on>
|
||||
|
||||
>>>
|
||||
*Example (For reference. Do not include in the final documentation file):*
|
||||
|
||||
The Unity Recorder version 1.0 has the following limitations:*
|
||||
|
||||
* The Unity Recorder does not support sound.
|
||||
* The Recorder window and Recorder properties are not available in standalone players.
|
||||
* MP4 encoding is only available on Windows.
|
||||
>>>
|
||||
|
||||
## Package contents
|
||||
>>>
|
||||
This section includes the location of important files you want the user to know about. For example, if this is a sample package containing textures, models, and materials separated by sample group, you may want to provide the folder location of each group.
|
||||
>>>
|
||||
|
||||
The following table indicates the <describe the breakdown you used here>:
|
||||
|
||||
|Location|Description|
|
||||
|---|---|
|
||||
|`<folder>`|Contains <describe what the folder contains>.|
|
||||
|`<file>`|Contains <describe what the file represents or implements>.|
|
||||
|
||||
>>>
|
||||
*Example (For reference. Do not include in the final documentation file):*
|
||||
|
||||
The following table indicates the root folder of each type of sample in this package. Each sample's root folder contains its own Materials, Models, or Textures folders:
|
||||
|
||||
|Folder Location|Description|
|
||||
|---|---|
|
||||
|`WoodenCrate_Orange`|Root folder containing the assets for the orange crates.|
|
||||
|`WoodenCrate_Mahogany`|Root folder containing the assets for the mahogany crates.|
|
||||
|`WoodenCrate_Shared`|Root folder containing any material assets shared by all crates.|
|
||||
>>>
|
||||
|
||||
## Document revision history
|
||||
>>>
|
||||
This section includes the revision history of the document. The revision history tracks when a document is created, edited, and updated. If you create or update a document, you must add a new row describing the revision. The Documentation Team also uses this table to track when a document is edited and its editing level. An example is provided:
|
||||
|
||||
|Date|Reason|
|
||||
|---|---|
|
||||
|Sept 12, 2017|Unedited. Published to package.|
|
||||
|Sept 10, 2017|Document updated for package version 1.1.<br>New features: <li>audio support for capturing MP4s.<li>Instructions on saving Recorder prefabs|
|
||||
|Sept 5, 2017|Limited edit by Documentation Team. Published to package.|
|
||||
|Aug 25, 2017|Document created. Matches package version 1.0.|
|
||||
>>>
|
||||
|
After Width: | Height: | Size: 210 KiB |
@@ -0,0 +1,154 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace SesothoLine
|
||||
{
|
||||
/// <summary>
|
||||
/// 线路绘制编辑器
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(SesothoPeManager))]
|
||||
public class SesothoPeManagerEditor : Editor
|
||||
{
|
||||
#region 委托事件
|
||||
|
||||
/// <summary>
|
||||
/// 当前主要的鼠标动作
|
||||
/// </summary>
|
||||
private event Action CurrenMouseAction;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 字段属性
|
||||
|
||||
/// <summary>
|
||||
/// 是编辑绘制模式
|
||||
/// </summary>
|
||||
public bool IsDebugDrawMode;
|
||||
|
||||
private Vector3 _currentPosition;
|
||||
|
||||
private bool OpKey_ForceStr;
|
||||
private bool OpKey_90Arc;
|
||||
private bool OpKey_180Arc;
|
||||
private bool OpKey_RevCisoid;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 生命周期
|
||||
|
||||
// private void OnEnable()
|
||||
// {
|
||||
// throw new NotImplementedException();
|
||||
// }
|
||||
//
|
||||
// private void OnDisable()
|
||||
// {
|
||||
// throw new NotImplementedException();
|
||||
// }
|
||||
|
||||
private void OnSceneGUI()
|
||||
{
|
||||
if (!IsDebugDrawMode) return;
|
||||
|
||||
int controlID = GUIUtility.GetControlID(FocusType.Keyboard);
|
||||
if(GUIUtility.hotControl == 0)
|
||||
HandleUtility.AddDefaultControl(controlID);
|
||||
|
||||
OpKey_ForceStr = false;
|
||||
OpKey_90Arc = false;
|
||||
OpKey_180Arc = false;
|
||||
OpKey_RevCisoid = false;
|
||||
|
||||
if (Event.current.GetTypeForControl(controlID) == EventType.KeyDown)
|
||||
{
|
||||
switch (Event.current.keyCode)
|
||||
{
|
||||
case KeyCode.Escape:
|
||||
IsDebugDrawMode = false;
|
||||
break;
|
||||
case KeyCode.LeftControl:
|
||||
OpKey_ForceStr = true;
|
||||
break;
|
||||
case KeyCode.LeftAlt:
|
||||
OpKey_90Arc = true;
|
||||
break;
|
||||
case KeyCode.LeftShift:
|
||||
OpKey_180Arc = true;
|
||||
break;
|
||||
case KeyCode.R:
|
||||
OpKey_RevCisoid = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var mousePos = Event.current.mousePosition;
|
||||
if (Physics.Raycast(HandleUtility.GUIPointToWorldRay(mousePos), out var hit, float.MaxValue, 0xffff))
|
||||
{
|
||||
_currentPosition = hit.point;
|
||||
}
|
||||
|
||||
if (Event.current.type == EventType.MouseUp && GUIUtility.hotControl == 0)
|
||||
{
|
||||
if (Event.current.button == 0)
|
||||
{
|
||||
CurrenMouseAction?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
if (GUILayout.Button(IsDebugDrawMode ? "退出绘制模式" : "进入绘制模式"))
|
||||
IsDebugDrawMode = !IsDebugDrawMode;
|
||||
|
||||
base.OnInspectorGUI();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 画线
|
||||
|
||||
private TerminalLine2T3 _line;
|
||||
private TerminalPoint _startPoint;
|
||||
private TerminalPoint _endPoint;
|
||||
|
||||
private void DarwLine()
|
||||
{
|
||||
_endPoint.transform.position = _currentPosition;
|
||||
|
||||
// _line.SetLineSegment(
|
||||
// _lastPointPosition, _currentPosition,
|
||||
// _lastPointNormal, _helperNormal
|
||||
// );
|
||||
|
||||
|
||||
if (OpKey_ForceStr)
|
||||
_line.DrawStraightLine();
|
||||
else
|
||||
{
|
||||
var angle = -1;
|
||||
if (OpKey_90Arc || OpKey_180Arc)
|
||||
{
|
||||
angle = 0;
|
||||
if (OpKey_90Arc)
|
||||
angle += 90;
|
||||
if (OpKey_180Arc)
|
||||
angle += 180;
|
||||
}
|
||||
_line.AutomaticDrawCircularLine(angle, OpKey_RevCisoid);
|
||||
}
|
||||
|
||||
// 计算完毕后执行渲染,这里调用有可能会带来空引用问题
|
||||
// 但在此之前的阶段已经对此进行过了处理所以没有关系,如果要照抄的话需要进行一些修改。
|
||||
_line.UpdateRender();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "Lrss3.SesothoLine.Editor",
|
||||
"rootNamespace": "SesothoLineEditor",
|
||||
"references": [
|
||||
"Lrss3.SesothoLine",
|
||||
"Lrss3.Deconstruction"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Deconstruction.Runtime.UI;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UI;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Deconstruction.Editor.UI
|
||||
{
|
||||
|
||||
[CustomEditor(typeof(CircleSlider) , true)]
|
||||
public class CircleSliderEditor : SelectableEditor
|
||||
{
|
||||
private SerializedProperty m_Value;
|
||||
private SerializedProperty FillOrigin;
|
||||
private SerializedProperty m_MaxValue;
|
||||
private SerializedProperty m_MinValue;
|
||||
private SerializedProperty m_FillRect;
|
||||
private SerializedProperty m_HandleRect;
|
||||
private SerializedProperty m_FillOrigin;
|
||||
private SerializedProperty m_Radius;
|
||||
private SerializedProperty m_ClockWise;
|
||||
// private SerializedProperty m_MaxAngle;
|
||||
// private SerializedProperty m_MinAngle;
|
||||
private SerializedProperty m_WholeNumbers;
|
||||
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
m_FillRect = serializedObject.FindProperty("m_FillRect");
|
||||
m_HandleRect = serializedObject.FindProperty("m_HandleRect");
|
||||
m_MinValue = serializedObject.FindProperty("m_MinValue");
|
||||
m_MaxValue = serializedObject.FindProperty("m_MaxValue");
|
||||
m_Value = serializedObject.FindProperty("m_Value");
|
||||
m_FillOrigin = serializedObject.FindProperty("m_FillOrigin");
|
||||
m_Radius = serializedObject.FindProperty("m_Radius");
|
||||
m_ClockWise = serializedObject.FindProperty("m_ClockWise");
|
||||
m_WholeNumbers = serializedObject.FindProperty("m_WholeNumbers");
|
||||
// m_MaxAngle = serializedObject.FindProperty("m_MaxAngle");
|
||||
// m_MinAngle = serializedObject.FindProperty("m_MinAngle");
|
||||
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
base.OnInspectorGUI();
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUILayout.PropertyField(m_FillRect);
|
||||
EditorGUILayout.PropertyField(m_HandleRect);
|
||||
|
||||
if (m_FillRect.objectReferenceValue != null )
|
||||
{
|
||||
EditorGUILayout.PropertyField(m_MinValue);
|
||||
EditorGUILayout.PropertyField(m_MaxValue);
|
||||
EditorGUILayout.PropertyField(m_WholeNumbers);
|
||||
EditorGUILayout.Slider(m_Value, m_MinValue.floatValue, m_MaxValue.floatValue);
|
||||
EditorGUILayout.PropertyField(m_FillOrigin);
|
||||
EditorGUILayout.PropertyField(m_ClockWise);
|
||||
}
|
||||
|
||||
if (m_HandleRect.objectReferenceValue != null)
|
||||
{
|
||||
EditorGUILayout.PropertyField(m_Radius);
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 纸飞机
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
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 OR COPYRIGHT HOLDERS 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.
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ffdeedb95e1b528419119bc9a549a667
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,36 @@
|
||||
# SesothoLine
|
||||
|
||||
#### Description
|
||||
一种在unity中绘制线的插件,可以绘制直线与弧线,底层通过数学公式对线进行描述,无视线的分辨率,即可完成吸附,相交等操作。
|
||||
|
||||
#### Software Architecture
|
||||
Software architecture description
|
||||
|
||||
#### Installation
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### Instructions
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### Contribution
|
||||
|
||||
1. Fork the repository
|
||||
2. Create Feat_xxx branch
|
||||
3. Commit your code
|
||||
4. Create Pull Request
|
||||
|
||||
|
||||
#### Gitee Feature
|
||||
|
||||
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
|
||||
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
|
||||
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
|
||||
4. The most valuable open source project [GVP](https://gitee.com/gvp)
|
||||
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
|
||||
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eaa1bd9b1e9fc3749a09f1ae06a15627
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,314 @@
|
||||
# SesothoLine
|
||||
|
||||
## 简介
|
||||
|
||||
unity通用库插件,现已转为unity 6000版本。
|
||||
|
||||
## 软件架构
|
||||
|
||||
* CentralizeLog: 小日志,unity本身的日志在调用时开销巨大,所以提供一个小日志的类型保存临时的日志内容。
|
||||
* Deconstruction: 通过数学公式对线进行描述,进行标准的CAD线路参数计算,并支持在线路上进行导航,合理性计算等
|
||||
* RescissionRework: 撤销重做 🕑
|
||||
* SerializerHelper: 序列化库
|
||||
* XericLibrary: 实用性unity宏库,包含一些实用扩展用法。
|
||||
|
||||
## Deconstruction
|
||||
|
||||
Deconstruction 部分由两个dll组成,一个是 Deconstruction,另一个是 Deconstruction Style 。
|
||||
|
||||
### 线路绘制
|
||||
----
|
||||
|
||||
|
||||
线路绘制组件支持二维,或三维的线条计算,线条继承自“可放置物”类,为了能够显示线条,需要向其提供渲染器和,同理,为了能够算出构成线的点集合,需要提供线计算器。
|
||||
为此插件内默认提供了一个使用lineRenderer组件的线渲染器,以及二维的直线和圆弧的线计算器。
|
||||
在计算平面上的CAD线路时,通过实现二维的线计算器,并实现它的坐标转换方法,即可在任意表面上实现线的绘制与保存。
|
||||
|
||||
另外如果希望快速构建具有特制绘制规则的对象,则需要另外的可放置物管理器 PlaceElementManager ,
|
||||
它继承于 SingleMonoBase ,所以可以通过单例的方式访问它。
|
||||
|
||||
可放置物管理器管理的是所有的可放置物对象,和决策工具类 DecisionMakerToolBase ,以及它们的生命周期。
|
||||
|
||||
工具类则提供了细致的针对如何放置对象的操作规则,并提供了操作栈表结构,
|
||||
可以通过操作栈来实现具有链表关系的行为树,栈中的每个节点是行为树上的一个节点,
|
||||
每个节点可以访问到上一个对象和下一个对象成员的节点。
|
||||
|
||||
每个节点可以是正在绘制的线,正在计算寻路的ai,或其他任何行为,
|
||||
在节点内可以直接通过 ReplaceThis(new Exit_OpSlot()); 方法类来切换行为树的节点到一个新的节点。
|
||||
|
||||
* 基础规则
|
||||
建议在工具类中进行创建等操作,管理类中只进行管理操作,比如在插件的sesotho部分的非dll文件中,提供了SesothoPeManager和SesothoArrangementWiresTool,他们就是管理器与工具类的派生部分。
|
||||
|
||||
* 按键规则
|
||||
在没有任何工具启动的情况下,管理器可以接收按键输入,直到被工具输入抢占。
|
||||
不过,管理器到工具中并没有任何抢占逻辑,所以请根据工具激活逻辑适时关闭按键处理。
|
||||
另外,在没有任何工具激活的情况下,工具本身的按键获取机制也不会激活,记得使用原生的按键获取逻辑。
|
||||
|
||||
* 线路规则
|
||||
|
||||
|
||||
* 链路规则
|
||||
链路是指实现了IConterminousness接口的对象,这个接口提供了一个获取ICoterminous接口的访问器;
|
||||
ICoterminous接口通常是用于实现链接关系的抽象层,简单的通过输入和输出来定义链接的线路,在接口中可以通过GetOpposite在当前对象的链接结构上下文中访问另一个与当前访问者相对应的链接对象,通常这是与访问者同类的对象,但是会经过接口进行抽象;而GetIdentical则可以获取与访问者同类链接的对象成员。
|
||||
|
||||
链路方向使用LinkType进行标记,也可以直接使用LinkType.SwapType()来快速反转这个链接标记;
|
||||
LinkType可以直接和LinkedPortType相互进行转换,也可以使用LinkType.ConvertType()来快速转换类型。
|
||||
链路方向通常只用在内部作为方向标识,外部无法获取,一般只在设置时进行一次标识设置即可。
|
||||
|
||||
|
||||
|
||||
### 线路结构
|
||||
----
|
||||
|
||||
线路绘制组件提供了线路连接引用关系,可以将一个线连接到另一个线上,也可以用点将它们连接起来,
|
||||
默认线
|
||||
|
||||
|
||||
### 生命周期
|
||||
----
|
||||
|
||||
|
||||
由于所有的成员都是通过批处理器完成生命周期的定义,批处理器的调用时机对于后续的流程来说至关重要。
|
||||
为了便于使用,所有的批处理对象的生命周期都遵循以下规则:
|
||||
|
||||
```
|
||||
开始和结束时间在LateUpdate阶段触发;
|
||||
主线程更新事件在Update阶段触发;
|
||||
异步更新事件在LateUpdate阶段触发;
|
||||
|
||||
主线程事件在所有事件之后才执行;
|
||||
```
|
||||
|
||||
* 安全检查
|
||||
在批处理期间是允许销毁的,目前的安全限制仅限在更新前会检查一遍批处理的key值是否为空,在其他时刻,可以通过其他的接口传递的安全检查来辅助更新检查。
|
||||
比如在PromptLine2中,如果太早或太迟调用UpdateRender会导致在更新期间捕获到一个空引用的报错,来源是linerenderer组件在此时还不存在,具体原理不清楚,但是只会提示一次,所以此时的渲染操作可以直接跳过。
|
||||
|
||||
### 获取工具
|
||||
----
|
||||
|
||||
|
||||
如果希望获取放置物管理器中的工具:
|
||||
|
||||
```
|
||||
// 获取当前激活的工具目标
|
||||
DecisionMakerToolBase.ActiveDecisionMaker
|
||||
// 以及这个工具目标是否有效
|
||||
DecisionMakerToolBase.HasAciveDecisionMaker
|
||||
```
|
||||
|
||||
### 示例文件
|
||||
----
|
||||
|
||||
|
||||
在Runtime/HorizonLineOrbit中已经提供了基本的用例,将SesothoPeManager挂到任意一个节点上就可以了。
|
||||
运行后按下键盘L键,此时点击世界中layer为defualt的物体,就会点击处y轴为0的位置上生成一个线,移动光标,线也会跟着移动。
|
||||
|
||||
这些脚本提供了一个基本的示例,并且几乎将所有可以进行配置修改的要素都呈现了出来,可以在后续的扩展中通过参考这些文件来进行实现。
|
||||
|
||||
在示例脚本中主要分为对元素的实现,以及对过程的实现;
|
||||
* terminalPoint, TerminalLine2T3, TerminalIland是后续创建的实例所挂载的具有确定空间,绘制方式的实现。
|
||||
* 其他的都是管理器,工具,操作栈的实现。
|
||||
|
||||
其中管理器,工具,操作栈类都是层层嵌套的引用结构,尽量只让相邻的两个过程进行相互的引用,比如工具应该引用管理器的实现,开放给操作栈。
|
||||
|
||||
|
||||
### 使用须知
|
||||
----
|
||||
|
||||
|
||||
现在已知的线路绘制问题:
|
||||
* 如果同时创建大量的操作栈(>500),并在之后的某个时刻中将它们全部隐式地释放掉,有可能会在之后的某个时刻带来庞大的GC占用(>90ms)
|
||||
* 逆时针绘制的曲线线路没法计算最近点。
|
||||
|
||||
## XericLibrary
|
||||
|
||||
XericLibrary 部分由两个dll组成,一个是 Xeric Library,另一个是 Xeric Library Editor 。
|
||||
|
||||
### 宏库
|
||||
|
||||
#### 特性库
|
||||
----
|
||||
|
||||
* CanFind$
|
||||
其中包含多个特性类,用于查找类,字段,属性,方法等项目。
|
||||
|
||||
* ReName
|
||||
重命名特性,在没有安装odin插件的旧版本引擎上可以直接替换细节面板中的参数名称,不过由于无法和odin兼容,所以odin的功能会覆盖掉这个特性。
|
||||
主要作用是提供属性标签查找功能。
|
||||
|
||||
#### 协程宏
|
||||
----
|
||||
|
||||
#### 调试宏
|
||||
----
|
||||
|
||||
|
||||
|
||||
#### 模型网格宏
|
||||
----
|
||||
|
||||
|
||||
#### 机器标识宏
|
||||
----
|
||||
|
||||
|
||||
#### 宏库
|
||||
----
|
||||
* 摄像机宏
|
||||
* 颜色宏
|
||||
* 常数宏
|
||||
* 曲线宏
|
||||
* 枚举宏
|
||||
* 方程宏
|
||||
* 事件宏
|
||||
* 文件宏
|
||||
* 步进宏
|
||||
* 按键宏
|
||||
* 集合宏
|
||||
* 数学宏
|
||||
* 对象宏
|
||||
* PID宏
|
||||
* 对象池宏
|
||||
* 矩形变换宏
|
||||
* 超驰宏
|
||||
* 仓库宏
|
||||
* 材质宏
|
||||
* 平滑宏
|
||||
* 排序宏
|
||||
* 文本宏
|
||||
* 时间宏
|
||||
* 轴变换宏
|
||||
* 类型扩展
|
||||
* 矢量扩展
|
||||
|
||||
#### 导航宏
|
||||
----
|
||||
|
||||
#### 安全宏
|
||||
----
|
||||
|
||||
|
||||
#### 转换宏
|
||||
----
|
||||
|
||||
|
||||
#### 独特类型
|
||||
----
|
||||
|
||||
|
||||
|
||||
### 简易批处理器
|
||||
|
||||
在代码中引用 XericLibrary.Runtime.Type.BatchProcess ,
|
||||
并将需要进行批处理的对象实现 ICanBatchProcess<T> 接口,
|
||||
其中提供了所有额外支持的生命周期事件,可以直接实现或显式实现。
|
||||
|
||||
缺点是只有异步方法的生命周期约束,没有提供线程安全的数据类型。
|
||||
|
||||
和官方的相比没有什么优势,只是为了此处使用的方便。
|
||||
|
||||
|
||||
## DataBaseControl
|
||||
|
||||
数据库控制器,当前主要工作区域是SqlClient部分
|
||||
|
||||
----
|
||||
|
||||
### 基础使用
|
||||
|
||||
数据库操作的指令被抽象到Order类了(我在注释里会称为解释器),比如sqlClient就是sqlcOrder,其中sqlc代表sqlclient。
|
||||
order类本质上是一个文本拼接器,为了方便管理,你可以使用对应类的order.GetOrder来获取池中的一条指令,比如:
|
||||
```
|
||||
var orderValues = SQLCHelper.GetOrder();
|
||||
```
|
||||
这个orderValues必须手动释放才会回到池中,比如:
|
||||
```
|
||||
orderValues.Dispost();
|
||||
```
|
||||
GetOrder指令可以在编译时静态调用,用作静态参数,不去释放,这样的话指令的生命周期会跟随程序释放而释放。
|
||||
使用指令前建议先清空,避免指令污染:
|
||||
```
|
||||
orderValues.CleanOrder();
|
||||
```
|
||||
然后,可以在指令上调用对应的函数,里面预制了数据库连接增删改查,表增删改查,项增删改查,以及插入判断,筛选条件命令。
|
||||
对于连接功能,可以参考下面的示例:
|
||||
```
|
||||
public void InitDataBase(string hostName, string databaseName, string tableName,
|
||||
IEnumerable<MyData> valueData)
|
||||
{
|
||||
SQLCHelper.connectOrder.OrderSetServer(hostName, null);
|
||||
// 数据库不存在,就创建一个
|
||||
if ((int)orderValues.OrderIsDataBaseExist(databaseName)
|
||||
.ExecuteScalar() <= 0)
|
||||
orderValues.OrderCreatDataBase(databaseName)
|
||||
.ExecuteNonQuery();
|
||||
|
||||
SQLCHelper.connectOrder.OrderSetServer(hostName, databaseName);
|
||||
// 数据表不存在,就创建一个
|
||||
if ((int)orderValues.OrderIsTableExist(tableName)
|
||||
.ExecuteScalar() <= 0)
|
||||
orderValues.OrderCreatTable(tableName)
|
||||
.ExecuteNonQuery();
|
||||
|
||||
// 构建数据项目
|
||||
orderValues.CleanValue();
|
||||
orderValues.Values.AddRange(valueData.Select(a => new SqlCOrder.SqlCValue(a.name, a.value)));
|
||||
|
||||
// 数据项目不存在,就创建一个
|
||||
if (valueData != null)
|
||||
{
|
||||
orderValues.OrderInsetTableItem(tableName);
|
||||
}
|
||||
}
|
||||
```
|
||||
可以看到前面会使用同一个order指令来执行命令构建,然后通过Execute方法来执行指令。
|
||||
指令在构建时会将文本压入stringBuilder中,直到遇到Execute指令后,指令才会被拼接成具体的文本,存入历史指令中。
|
||||
当这个指令中stringBuilder里没有待输出的指令时,会优先使用之前拼接过的文本,也就是说一条已经构建的指令可以被重复执行。
|
||||
|
||||
刚刚的示例代码也可以通过调用预制的扩展方法完成,比如:
|
||||
```
|
||||
orderValues.InitializationDataBase("localhost", "DEFAULT_DATABASE", "DEFAULT_TABLE");
|
||||
```
|
||||
上面这段指令会自动连接到地址localhost下,名为DEFAULT_DATABASE的DEFAULT_TABLE表中。
|
||||
如果数据库中没有库或表,将会自动创建。这对于仅仅为了保存数据的情况来说会比较方便。
|
||||
这种扩展指令根据其使用情景可能会相对于普通的方法有所区别,就比方说这个扩展方法会立刻执行,不需要手动调用Execute方法。
|
||||
一般指令都仅仅只是将命令填入拼接池中,然后在Execute中执行order里的SubmitOrder()来持久化指令内容。
|
||||
|
||||
如果希望在内存中保留一个与数据库对应的数值,则可以使用order的Values属性;
|
||||
一个order里的Values对应一个数据库表里的一列下的一个数据。
|
||||
Value的name对应列名,value对应实际值。
|
||||
|
||||
还是上面那段完整的数据库创建函数实例中,最后的OrderInsetTableItem就是将values插入到表的指令,包括InitializationDataBase扩展中也会自动将当前执行的order里的values插入到表,所以这个指令会建议手动管理生命周期。
|
||||
|
||||
这里说明一下,指令里的方法名称是有特定规律的:
|
||||
* 比如用于单段执行的指令方法,会使用Order开头,使用<code>.Order...</code>可以搜索到很多相关的指令。他们都会返回自身,用以支持链式的调用。
|
||||
* 如果某个指令的后面可以继续添加指令,比如条件指令之类的,可以使用<code>.OrderJoin...</code>搜索到一些相关的预制指令。
|
||||
* 而其他的指令则可以在扩展库中找到,扩展库中会专门针对特定的应用情景开放专门的方法。
|
||||
|
||||
#### 数据读写
|
||||
|
||||
在上面已经说过如何新建表,建表的时候会默认写入一次数据,而如果希望手动定义表格里的内容的话可以这么写:
|
||||
```
|
||||
order.OrderJoinValuesDefinitinOnly(orderValues.Values);
|
||||
```
|
||||
如果希望填入当前order里的Values的话,可以这么写:
|
||||
```
|
||||
```
|
||||
|
||||
读取可以使用:
|
||||
```
|
||||
orderValues.OrderSelect("DEFAULT_TABLE", null, null, true, true);
|
||||
```
|
||||
这段指令将orderValues里的value作为项目进行查询,由于两个null没有指定条件和排序,所以这会返回所有value的值。
|
||||
记得使用ExecuteReader执行,并返回阅读器进行处理,在阅读器中,需要手动根据值名称一一对应地将值填回orderValues里。
|
||||
|
||||
不过在扩展类中也提供了自动地读取逻辑:
|
||||
```
|
||||
orderValues.ReadLatestValue("DEFAULT_TABLE");
|
||||
```
|
||||
这会读取数据库中的,orderValues中的值,并直接写在orderValues的每一项中。
|
||||
然后直接对orderValues中的values进行处理姐可以了。
|
||||
|
||||
需要注意的是,这些指令虽然没有明确要求数据库连接,但还是至少需要连接过一次数据库,比如至少调用过一次InitializationDataBase方法,这样,后续的所有指令都可以不需要主动指定connectOrder项目。
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 019144fe8aedc3f499e254bf3e42bf14
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,117 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: SceneGround
|
||||
m_Shader: {fileID: -6465566751694194690, guid: c508188bc2ab04746ba729aded8434fa, type: 3}
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _BumpMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailAlbedoMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailMask:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailNormalMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _EmissionMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _MetallicGlossMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _OcclusionMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _ParallaxMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _Texture2D:
|
||||
m_Texture: {fileID: 10309, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _Texture2D_Normal:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- unity_Lightmaps:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- unity_LightmapsInd:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- unity_ShadowMasks:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- _BUILTIN_QueueControl: 0
|
||||
- _BUILTIN_QueueOffset: 0
|
||||
- _BumpScale: 1
|
||||
- _Cutoff: 0.5
|
||||
- _DetailNormalMapScale: 1
|
||||
- _DstBlend: 0
|
||||
- _GlossMapScale: 1
|
||||
- _Glossiness: 0.5
|
||||
- _GlossyReflections: 1
|
||||
- _Metallic: 0
|
||||
- _Mode: 0
|
||||
- _OcclusionStrength: 1
|
||||
- _Parallax: 0.02
|
||||
- _QueueControl: -1
|
||||
- _QueueOffset: 0
|
||||
- _SmoothnessTextureChannel: 0
|
||||
- _SpecularHighlights: 1
|
||||
- _SrcBlend: 1
|
||||
- _UVSec: 0
|
||||
- _ZWrite: 1
|
||||
m_Colors:
|
||||
- _Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
--- !u!114 &1580504613398626283
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 11
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 639247ca83abc874e893eb93af2b5e44, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
version: 0
|
||||
@@ -0,0 +1,157 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: UISelectionBoxDraw
|
||||
m_Shader: {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords:
|
||||
- _BUILTIN_SURFACE_TYPE_TRANSPARENT
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _AlphaTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _BumpMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailAlbedoMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailMask:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailNormalMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _EmissionMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _MetallicGlossMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _OcclusionMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _ParallaxMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- unity_Lightmaps:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- unity_LightmapsInd:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- unity_ShadowMasks:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- PixelSnap: 0
|
||||
- _Alpha: 0.2
|
||||
- _BUILTIN_AlphaClip: 0
|
||||
- _BUILTIN_Blend: 0
|
||||
- _BUILTIN_CullMode: 2
|
||||
- _BUILTIN_DstBlend: 10
|
||||
- _BUILTIN_QueueControl: 0
|
||||
- _BUILTIN_QueueOffset: 0
|
||||
- _BUILTIN_SrcBlend: 5
|
||||
- _BUILTIN_Surface: 1
|
||||
- _BUILTIN_ZTest: 4
|
||||
- _BUILTIN_ZWrite: 0
|
||||
- _BUILTIN_ZWriteControl: 0
|
||||
- _BumpScale: 1
|
||||
- _ColorMask: 15
|
||||
- _CullMode: 0
|
||||
- _Cutoff: 0.5
|
||||
- _DetailNormalMapScale: 1
|
||||
- _DstBlend: 0
|
||||
- _Edge: 5
|
||||
- _EdgeAlpha: 0.8
|
||||
- _EdgeThera: 0.0001
|
||||
- _EnableExternalAlpha: 0
|
||||
- _GlossMapScale: 1
|
||||
- _Glossiness: 0.5
|
||||
- _GlossyReflections: 1
|
||||
- _Metallic: 0
|
||||
- _Mode: 0
|
||||
- _OcclusionStrength: 1
|
||||
- _Parallax: 0.02
|
||||
- _Precision: 100
|
||||
- _QueueControl: -1
|
||||
- _QueueOffset: 0
|
||||
- _SmoothnessTextureChannel: 0
|
||||
- _SpecularHighlights: 1
|
||||
- _SrcBlend: 1
|
||||
- _Stencil: 0
|
||||
- _StencilComp: 8
|
||||
- _StencilOp: 0
|
||||
- _StencilReadMask: 255
|
||||
- _StencilWriteMask: 255
|
||||
- _UVSec: 0
|
||||
- _UseUIAlphaClip: 0
|
||||
- _ZWrite: 1
|
||||
m_Colors:
|
||||
- _ClipRect: {r: -32767, g: -32767, b: 32767, a: 32767}
|
||||
- _Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
- _EdgeColor: {r: 1, g: 1, b: 1, a: 0.8352941}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
- _Flip: {r: 1, g: 1, b: 1, a: 1}
|
||||
- _Point1: {r: 0, g: 0, b: 0, a: 0}
|
||||
- _Point2: {r: 0, g: 0, b: 0, a: 0}
|
||||
- _RendererColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
--- !u!114 &5744030932169462819
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 11
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 639247ca83abc874e893eb93af2b5e44, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
version: 0
|
||||
--- !u!114 &7813913351238974909
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 11
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
version: 5
|
||||
|
After Width: | Height: | Size: 102 B |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 281 B |
|
After Width: | Height: | Size: 314 B |
|
After Width: | Height: | Size: 302 B |
|
After Width: | Height: | Size: 251 B |
@@ -0,0 +1,21 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SesothoLine
|
||||
{
|
||||
public class AGVSettingsManager : MonoBehaviour
|
||||
{
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
using XericLibrary.Runtime.CustomEditor;
|
||||
using XericLibrary.Runtime.MacroLibrary;
|
||||
using XericLibrary.Runtime.Type;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
public class ShowMyNameInScene : MonoBehaviour
|
||||
{
|
||||
[Rename("启用")] public bool Enable = false;
|
||||
[Rename("仅选中时显示")] public bool OnlyShowOnSelect = false;
|
||||
[Rename("目标是子项")] public bool ShowAllChindName = true;
|
||||
[Rename("显示名称")] public bool ShowGameObjectName = false;
|
||||
[Rename("显示模型名称")] public bool ShowMeshFilter_MeshName = false;
|
||||
|
||||
public bool RandomEachObjectColor = false;
|
||||
public Color TextColor = Color.white;
|
||||
public RectOffset TextBorder = new RectOffset(0,0,0,0);
|
||||
public RectOffset TextMargin = new RectOffset(0,0,0,0);
|
||||
public RectOffset TextPadding = new RectOffset(0,0,0,0);
|
||||
public RectOffset TextOverflow = new RectOffset(0,0,0,0);
|
||||
public Font font;
|
||||
public int fontSize = 0;
|
||||
public FontStyle fontStyle = FontStyle.Normal;
|
||||
public TextClipping TextClipping = TextClipping.Overflow;
|
||||
public ImagePosition ImagePosition = ImagePosition.ImageLeft;
|
||||
public Vector2 ContentOffset = Vector2.zero;
|
||||
public Vector2 FixedSize = Vector2.zero;
|
||||
public Bool2 Stretch = new Bool2();
|
||||
|
||||
private GUIStyle TextStyle;
|
||||
private StringBuilder _sb = new StringBuilder();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
if (!Enable)
|
||||
return;
|
||||
if (OnlyShowOnSelect && !ReferenceEquals(Selection.activeGameObject, this))
|
||||
return;
|
||||
|
||||
TextStyle = new GUIStyle()
|
||||
{
|
||||
normal = new GUIStyleState()
|
||||
{
|
||||
textColor = TextColor,
|
||||
// background =
|
||||
},
|
||||
border = TextBorder,
|
||||
margin = TextMargin,
|
||||
padding = TextPadding,
|
||||
overflow = TextOverflow,
|
||||
font = font,
|
||||
fontSize = fontSize,
|
||||
fontStyle = fontStyle,
|
||||
richText = true,
|
||||
clipping = TextClipping,
|
||||
imagePosition = ImagePosition,
|
||||
contentOffset = ContentOffset,
|
||||
fixedHeight = FixedSize.y,
|
||||
fixedWidth = FixedSize.x,
|
||||
stretchHeight = Stretch.y,
|
||||
stretchWidth = Stretch.x,
|
||||
};
|
||||
|
||||
if (ShowAllChindName)
|
||||
{
|
||||
foreach (var item in transform.GetChildren())
|
||||
{
|
||||
if (!item.gameObject.activeInHierarchy)
|
||||
continue;
|
||||
SwitchFilter(item);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawGameObjectNameInScene(transform, transform.name);
|
||||
}
|
||||
}
|
||||
|
||||
private void SwitchFilter(Transform target)
|
||||
{
|
||||
if (_sb == null)
|
||||
_sb = new StringBuilder();
|
||||
_sb.Clear();
|
||||
|
||||
if (RandomEachObjectColor)
|
||||
TextStyle.normal.textColor = MacroColor.RandomHueColor(target.gameObject.GetInstanceID());
|
||||
|
||||
var position = target.position;
|
||||
var mf = target.GetComponent<MeshFilter>();
|
||||
if (mf != null)
|
||||
{
|
||||
var mesh = mf.mesh;
|
||||
if (mesh != null)
|
||||
position += target.rotation * mesh.bounds.center;
|
||||
}
|
||||
|
||||
if (ShowGameObjectName)
|
||||
_sb.AppendLine($"Name: {target.name}");
|
||||
|
||||
if (ShowMeshFilter_MeshName)
|
||||
_sb.AppendLine($"Mesh: {GetComponentName<MeshFilter>(target, a => a.mesh?.name ?? "unknown")}");
|
||||
|
||||
DrawNameInScene(position, _sb.ToString());
|
||||
}
|
||||
|
||||
private void DrawGameObjectNameInScene(Transform obj, string name)
|
||||
{
|
||||
Handles.Label(obj.position, name,TextStyle);
|
||||
}
|
||||
|
||||
private void DrawNameInScene(Vector3 position, string name)
|
||||
{
|
||||
Handles.Label(position, name, TextStyle);
|
||||
}
|
||||
|
||||
private string GetComponentName<T>(Transform target, Func<T, string> getName)
|
||||
where T : Component
|
||||
{
|
||||
return getName(target.GetComponent<T>());
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Deconstruction.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// Xeric 库的运行时辅助类库
|
||||
/// </summary>
|
||||
public static class XericHelper
|
||||
{
|
||||
#region 程序运行
|
||||
|
||||
/// <summary>
|
||||
/// 离开出游戏模式(在编辑模式时仅退出)
|
||||
/// </summary>
|
||||
public static void ExitGameMode()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
UnityEditor.EditorApplication.ExitPlaymode();
|
||||
}
|
||||
#else
|
||||
Application.Quit();
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||
</project>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RiderProjectSettingsUpdater">
|
||||
<option name="vcsConfiguration" value="2" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="ebb90e97-f354-453f-8b74-0bb83b47e00e" name="更改" comment="">
|
||||
<change beforePath="$PROJECT_DIR$/../../Developer/DLLDev/XericLibrary/MacroLibrary/MacroObject.cs" beforeDir="false" afterPath="$PROJECT_DIR$/../../Developer/DLLDev/XericLibrary/MacroLibrary/MacroObject.cs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/../../Developer/DLLDev/XericLibraryEditor/XericLibraryEditor.csproj" beforeDir="false" afterPath="$PROJECT_DIR$/../../Developer/DLLDev/XericLibraryEditor/XericLibraryEditor.csproj" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/../.." />
|
||||
</component>
|
||||
<component name="ProjectColorInfo">{
|
||||
"associatedIndex": 7
|
||||
}</component>
|
||||
<component name="ProjectId" id="2tmgRy20ttbG5bPRqK1ymOlCF4G" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"WebServerToolWindowFactoryState": "false",
|
||||
"git-widget-placeholder": "master",
|
||||
"ignore.virus.scanning.warn.message": "true",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
},
|
||||
"keyToStringList": {
|
||||
"rider.external.source.directories": [
|
||||
"C:\\Users\\LRC\\AppData\\Roaming\\JetBrains\\Rider2023.2\\resharper-host\\DecompilerCache",
|
||||
"C:\\Users\\LRC\\AppData\\Roaming\\JetBrains\\Rider2023.2\\resharper-host\\SourcesCache",
|
||||
"C:\\Users\\LRC\\AppData\\Local\\Symbols\\src"
|
||||
]
|
||||
}
|
||||
}]]></component>
|
||||
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="应用程序级" UseSingleDictionary="true" transferred="true" />
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="默认任务">
|
||||
<changelist id="ebb90e97-f354-453f-8b74-0bb83b47e00e" name="更改" comment="" />
|
||||
<created>1740962660807</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1740962660807</updated>
|
||||
<workItem from="1740962665165" duration="4000" />
|
||||
<workItem from="1740962676618" duration="24000" />
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
<component name="VcsManagerConfiguration">
|
||||
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,365 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Deconstruction.Element;
|
||||
using Deconstruction.Interface;
|
||||
using Deconstruction.Manager;
|
||||
using Deconstruction.Tool;
|
||||
using Deconstruction.Trajectory;
|
||||
using Deconstruction.Type.DMToolSlot;
|
||||
using Deconstruction.Type.Linkable;
|
||||
using UnityEngine;
|
||||
using XericLibrary.Runtime.MacroLibrary;
|
||||
|
||||
namespace SesothoLine
|
||||
{
|
||||
using LineSegment2 = Deconstruction.Element.PromptLine2.LineSegment2;
|
||||
|
||||
/// <summary>
|
||||
/// 绘制轨迹线的操作
|
||||
/// <code>
|
||||
/// 当从其他WaitForUserInput_OpSlot到达此处时,将创建TerminalLine2T3用于线路的绘制,
|
||||
/// 同时在线路的两端有两个TerminalPoint用于线路的相连。
|
||||
///
|
||||
/// 支持常规的线路连续绘制,起点吸附,终点吸附。
|
||||
/// 不支持首尾相连。
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public class DrawTerminalLine2T3_OpSlot : WaitForUserInput_OpSlot
|
||||
{
|
||||
#region 字段属性
|
||||
|
||||
/// <summary>
|
||||
/// 上一步的坐标
|
||||
/// </summary>
|
||||
private Vector3 _lastPointPosition;
|
||||
|
||||
/// <summary>
|
||||
/// 上一步的对齐轨迹计算器
|
||||
/// </summary>
|
||||
private Vector3 _lastPointNormal;
|
||||
|
||||
/// <summary>
|
||||
/// 下一步的坐标
|
||||
/// </summary>
|
||||
private Vector3 _helperPosition = Vector3.zero;
|
||||
|
||||
/// <summary>
|
||||
/// 下一步的法向
|
||||
/// </summary>
|
||||
private Vector3 _helperNormal = Vector3.zero;
|
||||
|
||||
/// <summary>
|
||||
/// 操作的轨迹线
|
||||
/// </summary>
|
||||
private TerminalLine2T3 _line;
|
||||
|
||||
/// <summary>
|
||||
/// 起点
|
||||
/// </summary>
|
||||
private TerminalPoint _startPoint;
|
||||
|
||||
/// <summary>
|
||||
/// 操作的点,也是终点
|
||||
/// </summary>
|
||||
private TerminalPoint _endPoint;
|
||||
|
||||
/// <summary>
|
||||
/// 在场上找到的其他的点
|
||||
/// </summary>
|
||||
private TerminalPoint _otherPoint;
|
||||
|
||||
/// <summary>
|
||||
/// 起点是用上一步的终点替代的
|
||||
/// </summary>
|
||||
private bool _isStartPointSubstitute = false;
|
||||
|
||||
/// <summary>
|
||||
/// 绘制时,终点是自由点
|
||||
/// </summary>
|
||||
private bool _isFreeEndPoint;
|
||||
|
||||
// public SesothoArrangementWiresTool myWiresTool;
|
||||
// myWiresTool = targetTool as SesothoArrangementWiresTool;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 引用封装
|
||||
|
||||
/// <summary>
|
||||
/// 创建自己的工具
|
||||
/// </summary>
|
||||
private SesothoArrangementWiresTool selfWiresTool => SesothoPeManager.WiresTool;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 生命周期
|
||||
|
||||
protected override void OnStart()
|
||||
{
|
||||
base.OnStart();
|
||||
|
||||
// 检查抵达此处的流程是否正确
|
||||
if (!HasLastNode)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"绘制节点行为树顺序错误,必须从其他任意WaitForUserInput_OpSlot中抵达此处");
|
||||
RemoveThis();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Last is not WaitForUserInput_OpSlot)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"绘制节点行为树顺序错误,应该是 WaitForUserInput_OpSlot -> {nameof(DrawTerminalLine2T3_OpSlot)},而不是{Last.GetType().Name}");
|
||||
RemoveThis();
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置先决条件
|
||||
InitPrecondition();
|
||||
}
|
||||
|
||||
protected override void OnEnd()
|
||||
{
|
||||
base.OnEnd();
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
// 阻止基类的默认操作
|
||||
// base.Update();
|
||||
|
||||
// 线的绘制计算,包括空间点吸附,链接等
|
||||
DrawingProcess();
|
||||
// 计算完毕后执行渲染,这里调用有可能会带来空引用问题
|
||||
// 但在此之前的阶段已经对此进行过了处理所以没有关系,如果要在其他地方照抄的话需要注意。
|
||||
_line.UpdateRender();
|
||||
|
||||
// 下一步
|
||||
if (IsReleaseContinueKey())
|
||||
{
|
||||
_helperNormal = _line.TrajectoryCalculator.GetEndPointNormal.UpwardPlaneToVector3();
|
||||
|
||||
// 起点链接
|
||||
if (_isStartPointSubstitute)
|
||||
{
|
||||
// 在开始时已经做过转换了
|
||||
// var dtlt_Slot = Last as DrawTerminalLine2T3_OpSlot;
|
||||
_line.LinkedDataAddPolarity(_startPoint, LinkType.Import);
|
||||
}
|
||||
else
|
||||
{
|
||||
_line.LinkedDataAddPolarity(_startPoint, LinkType.Import);
|
||||
selfWiresTool.PersistentElement(_startPoint);
|
||||
selfWiresTool.AddPeToNeighborGrid(_startPoint);
|
||||
}
|
||||
|
||||
// 终点链接
|
||||
if (_otherPoint is null) // || _endPoint.gameObject.activeSelf
|
||||
{
|
||||
_line.LinkedDataAddPolarity(_endPoint, LinkType.Export);
|
||||
selfWiresTool.PersistentElement(_endPoint);
|
||||
selfWiresTool.AddPeToNeighborGrid(_endPoint);
|
||||
}
|
||||
// 如果存在吸附目标
|
||||
else
|
||||
{
|
||||
selfWiresTool.DeleteElement(_endPoint);
|
||||
_line.LinkedDataAddPolarity(_otherPoint, LinkType.Export);
|
||||
}
|
||||
|
||||
// 保存线
|
||||
_line.ReapplyOrigin();
|
||||
selfWiresTool.PersistentElement(_line);
|
||||
selfWiresTool.AddBulkPeToNeighborGrid(_line);
|
||||
|
||||
// 下一步
|
||||
ReplaceThis(new DrawTerminalLine2T3_OpSlot());
|
||||
}
|
||||
// 取消
|
||||
else if (IsReleaseCanelKey())
|
||||
{
|
||||
RemoveThis();
|
||||
EndPrecondition();
|
||||
}
|
||||
// todo: 回到上一步
|
||||
/*
|
||||
* 操作栈的结构使得操作与对象的更新可以分离,
|
||||
* 就是撤回的过程可能有点复杂,
|
||||
* 需要分为工具运行期间撤回和常规撤回。
|
||||
*/
|
||||
}
|
||||
|
||||
protected override void AsyncBeforeUpdate()
|
||||
{
|
||||
base.AsyncBeforeUpdate();
|
||||
}
|
||||
|
||||
protected override void AsyncUpdate()
|
||||
{
|
||||
base.AsyncUpdate();
|
||||
}
|
||||
|
||||
protected override void AsyncAfterUpdate()
|
||||
{
|
||||
base.AsyncAfterUpdate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 方法
|
||||
|
||||
/// <summary>
|
||||
/// 设置先决属性
|
||||
/// </summary>
|
||||
private void InitPrecondition()
|
||||
{
|
||||
switch (Last)
|
||||
{
|
||||
// 使用来自前者的元素
|
||||
case DrawTerminalLine2T3_OpSlot dtlt_Slot:
|
||||
_isStartPointSubstitute = true;
|
||||
_startPoint = dtlt_Slot._endPoint;
|
||||
_lastPointPosition = dtlt_Slot._helperPosition;
|
||||
_lastPointNormal = dtlt_Slot._helperNormal;
|
||||
break;
|
||||
// 新建,或从空间中继承
|
||||
case WaitForUserInput_OpSlot wfui_Slot:
|
||||
if (SesothoArrangementWiresTool.GetNearestObjectAsPoint(out var terminalPoint))
|
||||
{
|
||||
_isStartPointSubstitute = true;
|
||||
_startPoint = terminalPoint;
|
||||
_lastPointPosition = terminalPoint.GetPosition3();
|
||||
if (SesothoArrangementWiresTool.GetNearestObjectAsLine(terminalPoint, out var terminalLine,
|
||||
out var normal))
|
||||
_lastPointNormal = normal;
|
||||
else
|
||||
{
|
||||
Debug.LogError("错误:当前绘制的线路准备吸附的目标并非一个完整的链路,或类型错误,正在退出。");
|
||||
RemoveThis();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_isStartPointSubstitute = false;
|
||||
selfWiresTool.GetElement(out _startPoint);
|
||||
_lastPointPosition = SesothoArrangementWiresTool.OpKey_IgnoreNeighborAdsorption.Getkey() ?
|
||||
wfui_Slot.MouseHelperPosition :
|
||||
CameraMouseInputHelper.Inst.GetGridAdsorb(wfui_Slot.MouseHelperPosition);
|
||||
_lastPointNormal = default;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
selfWiresTool.GetElement(out _line);
|
||||
selfWiresTool.GetElement(out _endPoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 结束清理属性
|
||||
/// </summary>
|
||||
private void EndPrecondition()
|
||||
{
|
||||
selfWiresTool.DeleteElement(_line);
|
||||
// 不能把别人的东西给回收了
|
||||
if (!_isStartPointSubstitute)
|
||||
selfWiresTool.DeleteElement(_startPoint);
|
||||
selfWiresTool.DeleteElement(_endPoint);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 线路绘制过程
|
||||
/// </summary>
|
||||
private void DrawingProcess()
|
||||
{
|
||||
// 获取当前的坐标
|
||||
_helperPosition = PlaceElementManager.Inst.InputHelper.CurrentGridPosition;
|
||||
|
||||
_startPoint.transform.position = _lastPointPosition;
|
||||
|
||||
_isFreeEndPoint = true;
|
||||
// 如果吸附到任意物体,或者用按键忽略这一过程
|
||||
if (SesothoPeManager.NearestObject is not null &&
|
||||
SesothoArrangementWiresTool.GetNearestObjectAsPoint(out _otherPoint) &&
|
||||
!SesothoArrangementWiresTool.OpKey_IgnoreNeighborAdsorption.Getkey())
|
||||
{
|
||||
_isFreeEndPoint = false;
|
||||
if (_endPoint.gameObject.activeSelf)
|
||||
_endPoint.gameObject.SetActive(false);
|
||||
|
||||
_helperPosition = SesothoPeManager.NearestPoint;
|
||||
_endPoint.transform.position = SesothoPeManager.NearestPoint;
|
||||
if (SesothoArrangementWiresTool.GetNearestObjectAsLine(_otherPoint, out var terminalLine,
|
||||
out var normal))
|
||||
_helperNormal = normal;
|
||||
}
|
||||
// 如果吸附到任意切线
|
||||
// else if (NearestLineTangentObject != null)
|
||||
// {
|
||||
// NearestLineTangentPoint
|
||||
// }
|
||||
|
||||
// 否则自由点
|
||||
if (_isFreeEndPoint)
|
||||
{
|
||||
if (!_endPoint.gameObject.activeSelf)
|
||||
_endPoint.gameObject.SetActive(true);
|
||||
_otherPoint = null;
|
||||
_helperNormal = default;
|
||||
_endPoint.transform.position = _helperPosition;
|
||||
}
|
||||
|
||||
_line.SetLineSegment(
|
||||
_lastPointPosition, _helperPosition,
|
||||
_lastPointNormal, _helperNormal
|
||||
);
|
||||
|
||||
// 如果当前线的线计算器是圆弧计算器,那么获取它的角度
|
||||
// if (_line.NowCalculator is RoundTrajectory2 arc)
|
||||
// {
|
||||
// Debug.Log($"角度{arc.Angle} {arc.AngleExact}");
|
||||
// }
|
||||
|
||||
var forceStr = SesothoArrangementWiresTool.OpKey_ForceStr.Getkey();
|
||||
if (forceStr)
|
||||
_line.DrawStraightLine();
|
||||
else
|
||||
{
|
||||
var force90Arc = SesothoArrangementWiresTool.OpKey_90Arc.Getkey();
|
||||
var force180Arc = SesothoArrangementWiresTool.OpKey_180Arc.Getkey();
|
||||
var forceRevCisoid = SesothoArrangementWiresTool.OpKey_RevCisoid.Getkey();
|
||||
|
||||
|
||||
var angle = -1;
|
||||
if (force90Arc || force180Arc)
|
||||
{
|
||||
angle = 0;
|
||||
if (force90Arc)
|
||||
angle += 90;
|
||||
if (force180Arc)
|
||||
angle += 180;
|
||||
}
|
||||
|
||||
_line.AutomaticDrawCircularLine(angle, forceRevCisoid);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取光标下的起点
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool GetCurrentObject()
|
||||
{
|
||||
// SesothoPeManager.
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SesothoLine
|
||||
{
|
||||
/// <summary>
|
||||
/// 可链接的对象,表示此成员内包含一个链表的节点,可用于链表跟踪
|
||||
/// </summary>
|
||||
public interface ILinkconfident<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取链接点
|
||||
/// </summary>
|
||||
/// <param name="node"></param>
|
||||
/// <returns></returns>
|
||||
public bool GetLinkedNode(out LinkedListNode<T> node);
|
||||
/// <summary>
|
||||
/// 设置连接点
|
||||
/// </summary>
|
||||
/// <param name="node"></param>
|
||||
public void SetLinkedNode(LinkedListNode<T> node);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Deconstruction.Element;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SesothoLine
|
||||
{
|
||||
/// <summary>
|
||||
/// 可链接的对象,表示此成员内包含一个链表的节点,可用于链表跟踪
|
||||
/// </summary>
|
||||
public interface ILinkconfidentPe : ILinkconfident<PlacementBase>
|
||||
{ }
|
||||
}
|
||||
@@ -0,0 +1,558 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Deconstruction.Element;
|
||||
using Deconstruction.Interface;
|
||||
using Deconstruction.Manager;
|
||||
using Deconstruction.Tool;
|
||||
using Deconstruction.Type;
|
||||
using Deconstruction.Type.DMToolSlot;
|
||||
using Deconstruction.Type.Linkable;
|
||||
using SerializerHelper.Type;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Pool;
|
||||
using XericLibrary.Runtime.MacroLibrary;
|
||||
using XericLibrary.Runtime.Nav;
|
||||
|
||||
namespace SesothoLine
|
||||
{
|
||||
/// <summary>
|
||||
/// 线路绘制工具类
|
||||
/// </summary>
|
||||
public class SesothoArrangementWiresTool : DecisionMakerToolBase
|
||||
{
|
||||
#region 事件委托
|
||||
|
||||
/// <summary>
|
||||
/// 工具中成员变更委托
|
||||
/// </summary>
|
||||
public delegate void ToolMemberChange(PlacementBase obj);
|
||||
|
||||
/// <summary>
|
||||
/// 工具中成员添加事件
|
||||
/// </summary>
|
||||
public event ToolMemberChange OnAdditional;
|
||||
|
||||
/// <summary>
|
||||
/// 工具中成员移除事件
|
||||
/// </summary>
|
||||
public event ToolMemberChange OnDecreasing;
|
||||
|
||||
/// <summary>
|
||||
/// 当任意元素苏醒
|
||||
/// </summary>
|
||||
public event ToolMemberChange OnAnyVivification;
|
||||
|
||||
/// <summary>
|
||||
/// 当任意元素休眠
|
||||
/// </summary>
|
||||
public event ToolMemberChange OnAnyDormancy;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 快捷键
|
||||
|
||||
/*
|
||||
* 1 控制强制直线
|
||||
* 2,3 控制圆弧的角度为90和180度
|
||||
* R 反转圆弧
|
||||
* shift+b 忽略对象吸附
|
||||
* shift+n 忽略网格吸附
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// 强制直线
|
||||
/// </summary>
|
||||
public static KeyPack OpKey_ForceStr = new KeyPack(KeyCode.Alpha1);
|
||||
/// <summary>
|
||||
/// 强制90度圆弧
|
||||
/// </summary>
|
||||
public static KeyPack OpKey_90Arc = new KeyPack(KeyCode.Alpha2);
|
||||
/// <summary>
|
||||
/// 强制180度圆弧
|
||||
/// </summary>
|
||||
public static KeyPack OpKey_180Arc = new KeyPack(KeyCode.Alpha3);
|
||||
/// <summary>
|
||||
/// 反转顺向,比如单圆弧的方向翻转。
|
||||
/// 话说shift+alt+r是英伟达的一个快捷键呢,有点冲突
|
||||
/// </summary>
|
||||
public static KeyPack OpKey_RevCisoid = new KeyPack(KeyCode.R);
|
||||
/// <summary>
|
||||
/// 忽略吸附功能
|
||||
/// </summary>
|
||||
public static KeyPack OpKey_IgnoreNeighborAdsorption = new KeyPack(KeyCode.LeftShift, KeyCode.B);
|
||||
/// <summary>
|
||||
/// 忽略网格吸附
|
||||
/// </summary>
|
||||
public static KeyPack OpKey_IgnoreGridAdsorption = new KeyPack(KeyCode.LeftShift, KeyCode.N);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 字段属性
|
||||
|
||||
private ObjectPool<TerminalLine2T3> _linePool;
|
||||
private ObjectPool<TerminalPoint> _pointPool;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 获取转换
|
||||
|
||||
/// <summary>
|
||||
/// 获取光标下最近的对象为点
|
||||
/// </summary>
|
||||
/// <param name="point"></param>
|
||||
/// <returns></returns>
|
||||
public static bool GetNearestObjectAsPoint(out TerminalPoint point)
|
||||
{
|
||||
if (SesothoPeManager.NearestObject is not null &&
|
||||
SesothoPeManager.NearestObject is TerminalPoint terminalPoint)
|
||||
{
|
||||
point = terminalPoint;
|
||||
return true;
|
||||
}
|
||||
point = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取光标下最近的对象为线
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <returns></returns>
|
||||
public static bool GetNearestObjectAsLine(out TerminalLine2T3 line)
|
||||
{
|
||||
if (SesothoPeManager.NearestObject is not null &&
|
||||
SesothoPeManager.NearestObject is TerminalPoint terminalPoint &&
|
||||
terminalPoint.LinkedData.GetOpposite(LinkType.Export) is TerminalLine2T3 terminalLine)
|
||||
{
|
||||
line = terminalLine;
|
||||
return true;
|
||||
}
|
||||
line = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取以给定点上与最近的相切线相对
|
||||
/// </summary>
|
||||
/// <param name="point"></param>
|
||||
/// <param name="line"></param>
|
||||
/// <param name="normal"></param>
|
||||
/// <returns></returns>
|
||||
public static bool GetNearestObjectAsLine(TerminalPoint point, out TerminalLine2T3 line, out Vector3 normal)
|
||||
{
|
||||
if (point is null)
|
||||
goto End;
|
||||
// 检查最近线切线是否有效,是否相连
|
||||
if (SesothoPeManager.NearestLineTangentObject is TerminalLine2T3 otherLine &&
|
||||
point.TryGetLinkLineNormal(otherLine, out normal))
|
||||
{
|
||||
line = otherLine;
|
||||
return true;
|
||||
}
|
||||
|
||||
End:
|
||||
line = null;
|
||||
normal = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 生命周期
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
_linePool = new ObjectPool<TerminalLine2T3>(
|
||||
createFunc: CreatElement<TerminalLine2T3>,
|
||||
actionOnGet: ElementActive,
|
||||
actionOnRelease: ElementInactive,
|
||||
actionOnDestroy: a =>
|
||||
{ },
|
||||
collectionCheck: true,
|
||||
defaultCapacity: 10,
|
||||
maxSize: 10000);
|
||||
|
||||
_pointPool = new ObjectPool<TerminalPoint>(
|
||||
createFunc: CreatElement<TerminalPoint>,
|
||||
actionOnGet: ElementActive,
|
||||
actionOnRelease: ElementInactive,
|
||||
actionOnDestroy: a =>
|
||||
{ },
|
||||
collectionCheck: true,
|
||||
defaultCapacity: 10,
|
||||
maxSize: 10000);
|
||||
|
||||
}
|
||||
|
||||
protected override void OnEnableTool()
|
||||
{
|
||||
// 启用时创建一个输入等待行为,随后进入绘制行为
|
||||
var target = GenerateOperateSlot<WaitForUserInput_OpSlot>();
|
||||
target.InitializeNextTodo(() => new DrawTerminalLine2T3_OpSlot());
|
||||
|
||||
Debug.Log("激活线路绘制工具");
|
||||
}
|
||||
|
||||
protected override void OnDisableTool()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void OnFinalEnd()
|
||||
{
|
||||
base.OnFinalEnd();
|
||||
EnableTool = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 序列化
|
||||
|
||||
public override SerializeUnion ToolSerializeDispost()
|
||||
{
|
||||
base.ToolSerializeDispost();
|
||||
foreach (var element in PlaceElementManager.Inst.StructuralLinkedList)
|
||||
{
|
||||
var union = element.SerializedOccurs();
|
||||
// 索引是后面用来标识类的
|
||||
union.Index = TypeConsignMap.AddMapIndex(element.GetType());
|
||||
union.RefreshSerializedContext();
|
||||
SerializeTemp.Add(union);
|
||||
}
|
||||
SerializeTemp.RefreshSerializedContext();
|
||||
return SerializeTemp;
|
||||
}
|
||||
|
||||
public override bool ToolDiserializDispost(SerializeUnion context)
|
||||
{
|
||||
if (!base.ToolDiserializDispost(context))
|
||||
return false;
|
||||
|
||||
var deserializeList = new List<(PlacementBase, SerializeUnion)>();
|
||||
var safeCount = ushort.MaxValue;
|
||||
Debug.Log("反序列化过程开始创建对象");
|
||||
while (0 <-- safeCount && SerializeTemp.IndexMoveToNext(out var block))
|
||||
{
|
||||
var union = SerializeTemp.GetDeserializeObject<SerializeUnion>();
|
||||
var type = TypeConsignMap.GetMapType(union.Index);
|
||||
if (GetElement(type, out var obj))
|
||||
{
|
||||
deserializeList.Add((obj, union));
|
||||
union.RefreshDeserializeContext();
|
||||
union.IndexMoveToStart();
|
||||
obj.DeserializeOccurs(union);
|
||||
// switch (obj)
|
||||
// {
|
||||
// case TerminalLine2T3 line:
|
||||
// line.DeserializeOccurs(union);
|
||||
// break;
|
||||
// case TerminalPoint point:
|
||||
// point.DeserializeOccurs(union);
|
||||
// break;
|
||||
// }
|
||||
}
|
||||
else
|
||||
Debug.LogError($"无法创建序列化元素,原因是类型不支持{type}");
|
||||
|
||||
if (block) break;
|
||||
}
|
||||
Debug.Log("反序列化过程开始恢复链接关系");
|
||||
foreach (var item in deserializeList)
|
||||
item.Item1.DeserializeHysteresisOccurs(item.Item2);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 方法
|
||||
|
||||
/// <summary>
|
||||
/// 创建元素
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private T CreatElement<T>()
|
||||
where T : PlacementBase, ILinkconfidentPe
|
||||
{
|
||||
var obj = CreatPlacementObject<T>();
|
||||
obj.OnPlancementDestory += a =>
|
||||
{
|
||||
if (a is null)
|
||||
{
|
||||
Debug.LogError("元素已经被销毁,无法回收链表元素");
|
||||
return;
|
||||
}
|
||||
if (a is T b &&
|
||||
b.GetLinkedNode(out var node))
|
||||
SesothoPeManager.Inst.RemoveLinkedTarget(node);
|
||||
else
|
||||
Debug.LogError("元素并非工具元素类型,或者其中的链表节点已丢失,导致无法回收链表元素");
|
||||
};
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 池对象激活
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
private void ElementActive<T>(T obj)
|
||||
where T : PlacementBase, ILinkconfidentPe
|
||||
{
|
||||
obj.gameObject.SetActive(true);
|
||||
OnAnyVivification?.Invoke(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 池对象取消激活
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
private void ElementInactive<T>(T obj)
|
||||
where T : PlacementBase, ILinkconfidentPe
|
||||
{
|
||||
obj.gameObject.SetActive(false);
|
||||
OnAnyDormancy?.Invoke(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取元素
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool GetElement<T>(out T obj)
|
||||
where T : PlacementBase, ILinkconfidentPe
|
||||
{
|
||||
var name = typeof(T).Name;
|
||||
switch (name)
|
||||
{
|
||||
case nameof(TerminalLine2T3):
|
||||
obj = _linePool.Get() as T;
|
||||
return true;
|
||||
case nameof(TerminalPoint):
|
||||
obj = _pointPool.Get() as T;
|
||||
return true;
|
||||
default:
|
||||
Debug.LogError($"给定的元素类型({name})并非预期,这将跳过对象池过程");
|
||||
break;
|
||||
}
|
||||
obj = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取元素
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public bool GetElement(Type type, out PlacementBase obj)
|
||||
{
|
||||
var name = type.Name;
|
||||
switch (name)
|
||||
{
|
||||
case nameof(TerminalLine2T3):
|
||||
obj = _linePool.Get();
|
||||
return true;
|
||||
case nameof(TerminalPoint):
|
||||
obj = _pointPool.Get();
|
||||
return true;
|
||||
default:
|
||||
Debug.LogError($"给定的元素类型({name})并非预期,这将跳过对象池过程");
|
||||
break;
|
||||
}
|
||||
obj = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 删除元素
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public void DeleteElement<T>(T obj)
|
||||
where T : PlacementBase,
|
||||
ILinkconfidentPe
|
||||
{
|
||||
var name = typeof(T).Name;
|
||||
switch (name)
|
||||
{
|
||||
case nameof(TerminalLine2T3):
|
||||
_linePool.Release(obj as TerminalLine2T3);
|
||||
break;
|
||||
case nameof(TerminalPoint):
|
||||
_pointPool.Release(obj as TerminalPoint);
|
||||
break;
|
||||
default:
|
||||
Debug.LogError($"给定的元素类型({name})并非预期,这将跳过对象池过程");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 保存元素链,在创建并常态化元素后都需要执行的操作 (注意不是在创建后立刻保存)
|
||||
/// <code>
|
||||
/// 如果这是一个线,那么还应该调用 AddBulkLineToNeighborGrid;
|
||||
/// 如果这是一个点,那么应该调用 AddPeToNeighborGrid。
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public void PersistentElement<T>(T obj)
|
||||
where T : PlacementBase,
|
||||
ILinkconfidentPe
|
||||
{
|
||||
// 保存链表结构,反过来也持有这个节点,便于后续查找
|
||||
var lineNode = SesothoPeManager.Inst.AddLinkedTarget(obj);
|
||||
obj.SetLinkedNode(lineNode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除元素链
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public void ExcisionElement<T>(T obj)
|
||||
where T : PlacementBase,
|
||||
ILinkconfidentPe
|
||||
{
|
||||
if (obj.GetLinkedNode(out var node))
|
||||
SesothoPeManager.Inst.RemoveLinkedTarget(node);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 添加小型元素到网格中
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public void AddPeToNeighborGrid<T>(T obj)
|
||||
where T : PlacementBase,
|
||||
ILinkconfidentPe
|
||||
{
|
||||
if (obj.NeighborGridIndex != null)
|
||||
{
|
||||
Debug.LogError("无法重复插入元素:当前向管理器中插入的的元素,可能已经存在于其他管理器内,请先退出其他管理器后重试。");
|
||||
return;
|
||||
}
|
||||
SesothoPeManager.Inst.InsertNeighbor(obj, out var index);
|
||||
obj.NeighborGridIndex = index;
|
||||
|
||||
// 回调事件
|
||||
OnAdditional?.Invoke(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加大型元素到网格中
|
||||
/// </summary>
|
||||
public void AddBulkPeToNeighborGrid<T>(T obj)
|
||||
where T : PlacementBase,
|
||||
ILinkconfidentPe,
|
||||
IPossessorTrajectory2
|
||||
{
|
||||
if (obj.NeighborGridIndex != null)
|
||||
{
|
||||
Debug.LogError("无法重复插入元素:当前向管理器中插入的的元素,可能已经存在于其他管理器内,请先退出其他管理器后重试。");
|
||||
return;
|
||||
}
|
||||
SesothoPeManager.Inst.InsertGiantNeighbor<T>(obj, out var index);
|
||||
obj.NeighborGridIndex = index;
|
||||
|
||||
// 回调事件
|
||||
OnAdditional?.Invoke(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除这个小元素
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public void RemovePeToNeighborGrid<T>(T obj)
|
||||
where T : PlacementBase,
|
||||
ILinkconfidentPe
|
||||
{
|
||||
if (obj.NeighborGridIndex != null &&
|
||||
obj.NeighborGridIndex.GetAsIndex(out var index))
|
||||
SesothoPeManager.Inst.RemoveNeighbor(index);
|
||||
else
|
||||
SesothoPeManager.Inst.RemoveNeighbor(obj);
|
||||
ExcisionElement(obj);
|
||||
// 回调事件
|
||||
OnDecreasing?.Invoke(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除这个大型元素
|
||||
/// </summary>
|
||||
public void RemoveBulkPeFormNeighborGrid<T>(T obj)
|
||||
where T : PlacementBase,
|
||||
ILinkconfidentPe,
|
||||
IPossessorTrajectory2
|
||||
{
|
||||
if (obj.NeighborGridIndex != null &&
|
||||
obj.NeighborGridIndex.GetAsMappingIndex(out var index))
|
||||
SesothoPeManager.Inst.RemoveGiantNeighbor(index);
|
||||
else
|
||||
SesothoPeManager.Inst.RemoveGiantNeighbor<T>(obj);
|
||||
ExcisionElement(obj);
|
||||
// 回调事件
|
||||
OnDecreasing?.Invoke(obj);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 绘制寻路
|
||||
|
||||
/*
|
||||
* 没写完,如果不嫌麻烦可以看看Astart类,或者自己实现。
|
||||
*
|
||||
* 主要提供绘制线路时的多段线拟合,障碍物避让
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个可以通过的路径
|
||||
/// <code>
|
||||
/// 这是一个只在向上的二维平面上有效的路径。
|
||||
/// 寻路的规则是仅避开建筑物,贪婪算法。
|
||||
/// </code>
|
||||
/// </summary>
|
||||
/// <param name="startPoint">起点</param>
|
||||
/// <param name="endPoint">终点</param>
|
||||
/// <param name="avoidRadius">避让半径,在遭遇碰撞时将沿法向退回这个距离</param>
|
||||
public DrawWayPoints GetPassThroughRouteShortcut(Vector3 startPoint, Vector3 endPoint, float avoidRadius)
|
||||
{
|
||||
var result = new DrawWayPoints();
|
||||
Debug.LogError("此方法未完成");
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个可以通过的路径的迭代器;
|
||||
/// </summary>
|
||||
/// <param name="startPoint">起点</param>
|
||||
/// <param name="endPoint">终点</param>
|
||||
/// <param name="avoidRadius">避让半径,在遭遇障碍时将沿法向退回这个距离</param>
|
||||
/// <param name="layer">检查碰撞对象的层</param>
|
||||
/// <returns></returns>
|
||||
protected IEnumerable<Vector3> GetPassThroughRoute(Vector3 startPoint, Vector3 endPoint, float avoidRadius, LayerMask layer)
|
||||
{
|
||||
yield return Vector3.zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用给定的路径创建一段工具拟合的路径
|
||||
/// </summary>
|
||||
/// <param name="wayPoints">一段路径</param>
|
||||
public void BuildPaths(DrawWayPoints wayPoints)
|
||||
{
|
||||
new AStart2();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
#define _DEBUG_
|
||||
// #undef _DEBUG_
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Deconstruction.Element;
|
||||
using UnityEngine;
|
||||
|
||||
using Deconstruction.Manager;
|
||||
using Deconstruction.Tool;
|
||||
using Deconstruction.Type.Area;
|
||||
using Deconstruction.Type.Serialize;
|
||||
using Newtonsoft.Json;
|
||||
using SerializerHelper.Type;
|
||||
using UnityEngine.UI;
|
||||
using XericLibrary.Runtime.Debuger;
|
||||
using XericLibrary.Runtime.MacroLibrary;
|
||||
|
||||
namespace SesothoLine
|
||||
{
|
||||
/// <summary>
|
||||
/// 主要管理器
|
||||
/// <code>
|
||||
/// 不建议继续封装管理器类
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public sealed class SesothoPeManager : PlaceElementManager
|
||||
{
|
||||
#region 静态成员
|
||||
|
||||
/// <summary>
|
||||
/// 此处提供的单例与父类单例是独立的
|
||||
/// </summary>
|
||||
public new static SesothoPeManager Inst => _inst;
|
||||
private static SesothoPeManager _inst;
|
||||
|
||||
/// <summary>
|
||||
/// 线路绘制工具
|
||||
/// </summary>
|
||||
public static SesothoArrangementWiresTool WiresTool => _wiresTool;
|
||||
private static SesothoArrangementWiresTool _wiresTool;
|
||||
|
||||
/// <summary>
|
||||
/// 光标下最近的线上交点
|
||||
/// </summary>
|
||||
public static Vector3 NearestLineTangentPoint => _lineTangentTraceTool.NearsetPoint;
|
||||
/// <summary>
|
||||
/// 光标下最近的线
|
||||
/// </summary>
|
||||
public static PlacementBase NearestLineTangentObject => _lineTangentTraceTool.NearsetObject;
|
||||
/// <summary>
|
||||
/// 光标下最近的对象原点
|
||||
/// </summary>
|
||||
public static Vector3 NearestPoint => TrackingTool.TrackingCurrentPosition;
|
||||
/// <summary>
|
||||
/// 光标下最近的对象
|
||||
/// </summary>
|
||||
public static PlacementBase NearestObject => TrackingTool.TrackingCurrentTarget;
|
||||
|
||||
private static TrajectoryTrackingTool.TrackPersistentCommunicate _lineTangentTraceTool;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 调试功能
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
public bool EnableGridDebugDraw = false;
|
||||
|
||||
|
||||
public void DebugFunc()
|
||||
{
|
||||
if (EnableGridDebugDraw)
|
||||
{
|
||||
PlacementNeighbor.DebugDrawGrid();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region 生命周期
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
_inst = this;
|
||||
|
||||
if (LineMaterial == null)
|
||||
Debug.LogError("需要给管理器提供一个线段材质");
|
||||
if (PointMaterial == null)
|
||||
Debug.LogError("需要给管理器提供一个点材质");
|
||||
|
||||
// 初始化画线工具
|
||||
_wiresTool = new SesothoArrangementWiresTool();
|
||||
_wiresTool.InitializeParent(transform);
|
||||
|
||||
_lineTangentTraceTool = TrackingTool.InstantiationTrackCaculate(PlaceholdersAreaType.LinearContinuity);
|
||||
|
||||
// 手动开启代理更新
|
||||
EnableUpdate = true;
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected override void Start()
|
||||
{
|
||||
base.Start();
|
||||
}
|
||||
|
||||
private NeighborGrid<PlacementBase>.NeighborGridIndex index;
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
#if UNITY_EDITOR
|
||||
DebugFunc();
|
||||
#endif
|
||||
if (Input.GetMouseButtonDown(2))
|
||||
{
|
||||
index = PlacementNeighbor.GetNeighborIndex(null);
|
||||
index.SetDrivenAsWorld(InputHelper.CurrentPosition);
|
||||
Debug.Log($"{index.GetDrivenAsLinear()} => {index.GetNeighbor().FirstOrDefault()}");
|
||||
}
|
||||
|
||||
// new SerializeUnion();
|
||||
}
|
||||
|
||||
private string serializeContext = null;
|
||||
protected override void LateUpdate()
|
||||
{
|
||||
base.LateUpdate();
|
||||
// if (DecisionMakerToolBase.ActiveDecisionMaker == null ||
|
||||
// !DecisionMakerToolBase.ActiveDecisionMaker.EnableTool)
|
||||
#if !UNITY_EDITOR || !_DEBUG_
|
||||
return;
|
||||
#endif
|
||||
// 工具功能测试
|
||||
if (Input.GetKeyDown(KeyCode.L))
|
||||
{
|
||||
_wiresTool.EnableTool = !_wiresTool.EnableTool;
|
||||
Debug.Log($"{(_wiresTool.EnableTool ? "绘制工具已激活" : "工具已取消激活")}");
|
||||
}
|
||||
|
||||
// 存档功能测试
|
||||
if (Input.GetKeyDown(KeyCode.S))
|
||||
{
|
||||
var obj = _wiresTool.ToolSerializeDispost();
|
||||
serializeContext = JsonConvert.SerializeObject(obj);
|
||||
Debug.Log(obj.ToString());
|
||||
}
|
||||
if (Input.GetKeyDown(KeyCode.D))
|
||||
{
|
||||
if (serializeContext == null)
|
||||
{
|
||||
Debug.Log("反序列化内容为空");
|
||||
return;
|
||||
}
|
||||
|
||||
var obj = JsonConvert.DeserializeObject<SerializeUnion>(serializeContext);
|
||||
_wiresTool.ToolDiserializDispost(obj);
|
||||
}
|
||||
|
||||
if (Input.GetKeyDown(KeyCode.A))
|
||||
{
|
||||
_wiresTool.EnableCompress = !_wiresTool.EnableCompress;
|
||||
if (_wiresTool.EnableCompress )
|
||||
Debug.Log("开启压缩");
|
||||
else
|
||||
Debug.Log("关闭压缩");
|
||||
}
|
||||
|
||||
|
||||
// 绘制一下吸附的目标位置
|
||||
if (TrackingTool.EnableTool || TrackingTool._enableAuxiliaryTool)
|
||||
{
|
||||
MacroDebugDraw.DrawDownArrow(NearestPoint, Color.magenta);
|
||||
MacroDebugDraw.DrawDownArrow(NearestLineTangentPoint, Color.green);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using System.Text;
|
||||
using Deconstruction.Element;
|
||||
using Deconstruction.Interface;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
using XericLibrary.Runtime.Debuger;
|
||||
using XericLibrary.Runtime.MacroLibrary;
|
||||
using XericLibrary.Runtime.Type;
|
||||
|
||||
namespace SesothoLine
|
||||
{
|
||||
|
||||
using LineSegment2 = Deconstruction.Element.PromptLine2.LineSegment2;
|
||||
|
||||
/// <summary>
|
||||
/// 线终端执行脚本,使用二维线计算器,驱动三维线渲染器。
|
||||
/// </summary>
|
||||
public class TerminalLine2T3 : PromptLine2,
|
||||
ILinkconfidentPe // 链表跟踪
|
||||
{
|
||||
#region 字段属性
|
||||
|
||||
private LinkedListNode<PlacementBase> _linkNode;
|
||||
|
||||
/// <summary>
|
||||
/// 本地缓存
|
||||
/// </summary>
|
||||
private LineSegment2 _lineSegment = new LineSegment2();
|
||||
|
||||
public bool DebugDrawGrid = false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 生命周期
|
||||
|
||||
protected override void Start()
|
||||
{
|
||||
base.Start();
|
||||
InitPromptLine<LineRendererUnbodied>();
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
SesothoPeManager.Inst.AddEmbedded(this);
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
SesothoPeManager.Inst.RemoveEmbedded(this);
|
||||
}
|
||||
|
||||
protected virtual void OnDestroy()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void AgencyOnEnable()
|
||||
{
|
||||
base.AgencyOnEnable();
|
||||
}
|
||||
|
||||
protected override void AgencyOnDisable()
|
||||
{
|
||||
base.AgencyOnDisable();
|
||||
}
|
||||
|
||||
protected override void AgencyMainUpdate()
|
||||
{
|
||||
base.AgencyMainUpdate();
|
||||
|
||||
// 绘制线路本身在网格中的索引位置
|
||||
if (DebugDrawGrid &&
|
||||
NeighborGridIndex.SafeGetAsMappingIndex(out var indexs))
|
||||
{
|
||||
foreach (var index in indexs.Indexs)
|
||||
{
|
||||
MacroDebugDraw.DrawDownArrow(
|
||||
index.GetCellWorldIndexPosition() + MacroMath.RandomVector3(0.1f, Identifier),
|
||||
Color.red);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected override void AgencyAsyncBeforeUpdate()
|
||||
{
|
||||
base.AgencyAsyncBeforeUpdate();
|
||||
}
|
||||
|
||||
protected override void AgencyAsyncUpdate()
|
||||
{
|
||||
base.AgencyAsyncUpdate();
|
||||
}
|
||||
|
||||
protected override void AgencyAsyncAfterUpdate()
|
||||
{
|
||||
base.AgencyAsyncAfterUpdate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 线路构建
|
||||
|
||||
/// <summary>
|
||||
/// 创建合理坐标空间
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <param name="ah"></param>
|
||||
/// <param name="bh"></param>
|
||||
/// <returns></returns>
|
||||
public LineSegment2 SetLineSegment(Vector3 a, Vector3 b, Vector3 ah = default, Vector3 bh = default)
|
||||
{
|
||||
_lineSegment.SetValue(
|
||||
a.UpwardPlaneToVector2(),
|
||||
b.UpwardPlaneToVector2(),
|
||||
ah.UpwardPlaneToVector2(),
|
||||
bh.UpwardPlaneToVector2());
|
||||
// ah?.GetEndPointNormal ?? default,
|
||||
// bh?.GetStartPointNormal ?? default);
|
||||
return _lineSegment;
|
||||
}
|
||||
|
||||
public void AutomaticDrawCircularLine(float constraintAngle = -1, bool fullArc = false)
|
||||
{
|
||||
AutomaticDrawCircularLine(_lineSegment, constraintAngle, fullArc);
|
||||
}
|
||||
|
||||
private LineSegment2 straightLine = new LineSegment2();
|
||||
public void DrawStraightLine()
|
||||
{
|
||||
straightLine.SetValue(_lineSegment.pointA, _lineSegment.pointB);
|
||||
AutomaticDrawCircularLine(straightLine);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 重新计算原点
|
||||
/// <code>
|
||||
/// 如果此轨迹的原点在零点,那么通过此方法可以将原点设置到起点和终点之间。
|
||||
/// 注意:使用前需要将轨迹设为本地坐标
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public void ReapplyOrigin()
|
||||
{
|
||||
if (TrajectoryRenderer == null || TrajectoryCalculator == null)
|
||||
{
|
||||
Debug.LogError("当前轨迹或线渲染器无效,无法计算原点");
|
||||
return;
|
||||
}
|
||||
|
||||
// 假定其中的轨迹是本地坐标的
|
||||
|
||||
// 期望的本地零点
|
||||
var center = ((TrajectoryCalculator.GetStartPointPosition + TrajectoryCalculator.GetEndPointPosition) / 2).UpwardPlaneToVector3();
|
||||
// 本地原点
|
||||
var local = TrajectoryCalculator.Origin.UpwardPlaneToVector3();
|
||||
// 期望偏移
|
||||
var offset = local - center;
|
||||
|
||||
TrajectoryCalculator.Origin = offset.UpwardPlaneToVector2();
|
||||
transform.position += center;
|
||||
|
||||
// 更新
|
||||
UpdateRender();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 实现 - ILinkconfidentPe
|
||||
|
||||
public bool GetLinkedNode(out LinkedListNode<PlacementBase> node)
|
||||
{
|
||||
if (_linkNode == null)
|
||||
{
|
||||
node = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
node = _linkNode;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetLinkedNode(LinkedListNode<PlacementBase> node)
|
||||
{
|
||||
_linkNode = node;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 重写 - ISerializablePost
|
||||
|
||||
public override SerializerHelper.Type.SerializeUnion SerializedOccurs()
|
||||
{
|
||||
return base.SerializedOccurs();
|
||||
}
|
||||
public override bool CheckDeserializeUnion(SerializerHelper.Type.SerializeUnion context)
|
||||
{
|
||||
return base.CheckDeserializeUnion(context);
|
||||
}
|
||||
public override void DeserializeOccurs(SerializerHelper.Type.SerializeUnion context)
|
||||
{
|
||||
base.DeserializeOccurs(context);
|
||||
}
|
||||
public override void DeserializeHysteresisOccurs(SerializerHelper.Type.SerializeUnion context)
|
||||
{
|
||||
base.DeserializeHysteresisOccurs(context);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Deconstruction.Element;
|
||||
using UnityEngine;
|
||||
using XericLibrary.Runtime.Debuger;
|
||||
using XericLibrary.Runtime.MacroLibrary;
|
||||
using Random = System.Random;
|
||||
|
||||
namespace SesothoLine
|
||||
{
|
||||
/// <summary>
|
||||
/// 点,承担连接的责任
|
||||
/// </summary>
|
||||
public class TerminalPoint : Deconstruction.Element.ContactPoint,
|
||||
ILinkconfidentPe // 链表跟踪
|
||||
{
|
||||
private LinkedListNode<PlacementBase> _linkNode;
|
||||
|
||||
#region 生命周期
|
||||
|
||||
// 调试绘制一下位置
|
||||
// private void Update()
|
||||
// {
|
||||
// MacroDebugDraw.DrawDownArrow(transform.position + MacroMath.RandomVector3(0.1f, Identifier), Color.green);
|
||||
// }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 实现 - ILinkconfidentPe
|
||||
|
||||
public bool GetLinkedNode(out LinkedListNode<PlacementBase> node)
|
||||
{
|
||||
if (_linkNode == null)
|
||||
{
|
||||
node = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
node = _linkNode;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetLinkedNode(LinkedListNode<PlacementBase> node)
|
||||
{
|
||||
_linkNode = node;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 重写 - ISerializablePost
|
||||
|
||||
public override SerializerHelper.Type.SerializeUnion SerializedOccurs()
|
||||
{
|
||||
return base.SerializedOccurs();
|
||||
}
|
||||
public override bool CheckDeserializeUnion(SerializerHelper.Type.SerializeUnion context)
|
||||
{
|
||||
return base.CheckDeserializeUnion(context);
|
||||
}
|
||||
public override void DeserializeOccurs(SerializerHelper.Type.SerializeUnion context)
|
||||
{
|
||||
base.DeserializeOccurs(context);
|
||||
}
|
||||
public override void DeserializeHysteresisOccurs(SerializerHelper.Type.SerializeUnion context)
|
||||
{
|
||||
base.DeserializeHysteresisOccurs(context);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "Lrss3.Deconstruction",
|
||||
"rootNamespace": "Deconstruction",
|
||||
"references": [
|
||||
"GUID:6055be8ebefd69e48b49212b09b47b2f"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
#if CREST_UNITY_INPUT && ENABLE_INPUT_SYSTEM
|
||||
#define INPUT_SYSTEM_ENABLED
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
#if INPUT_SYSTEM_ENABLED
|
||||
using UnityEngine.InputSystem;
|
||||
#endif
|
||||
#if ENABLE_VR && ENABLE_VR_MODULE
|
||||
using UnityEngine.XR;
|
||||
#endif
|
||||
|
||||
namespace Deconstruction.Runtime.Player
|
||||
{
|
||||
/// <summary>
|
||||
/// 一个简单且功能有限的摄像机脚本,可通过 WASD 键和鼠标进行控制。
|
||||
/// </summary>
|
||||
public class CameraController : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// 此资产的版本。可用于不同版本之间的迁移。只有在编辑器升级版本时才应更改此值。
|
||||
/// </summary>
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _version = 0;
|
||||
#pragma warning restore 414
|
||||
|
||||
public float linSpeed = 10f;
|
||||
public float rotSpeed = 70f;
|
||||
|
||||
public bool simForwardInput = false;
|
||||
public bool _requireLMBToMove = false;
|
||||
|
||||
public bool enableWalkMode = false;
|
||||
|
||||
Vector2 _lastMousePos = -Vector2.one;
|
||||
bool _dragging = false;
|
||||
|
||||
public float _fixedDt = 1 / 60f;
|
||||
|
||||
Transform _targetTransform;
|
||||
|
||||
public CharacterController _moveComponent;
|
||||
|
||||
[Space(10)]
|
||||
|
||||
[SerializeField]
|
||||
DebugFields _debug = new DebugFields();
|
||||
|
||||
#pragma warning disable CS0108
|
||||
// 在编辑器中,我们需要使用“new”来屏蔽警告,但在构建时会再次给出警告,所以应该使用“pragma”来替代。
|
||||
private Camera camera;
|
||||
#pragma warning restore CS0108
|
||||
|
||||
[System.Serializable]
|
||||
class DebugFields
|
||||
{
|
||||
[Tooltip("Allows the camera to roll (rotating on the z axis).")]
|
||||
public bool _enableCameraRoll = false;
|
||||
|
||||
[Tooltip("Disables the XR occlusion mesh for debugging purposes. Only works with legacy XR.")]
|
||||
public bool _disableOcclusionMesh = false;
|
||||
|
||||
[Tooltip("Sets the XR occlusion mesh scale. Useful for debugging refractions. Only works with legacy XR."), UnityEngine.Range(1f, 2f)]
|
||||
public float _occlusionMeshScale = 1f;
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
if (_moveComponent == null)
|
||||
{
|
||||
_moveComponent = GetComponent<CharacterController>();
|
||||
}
|
||||
else if (Application.isEditor)
|
||||
{
|
||||
_moveComponent.enabled = enableWalkMode;
|
||||
}
|
||||
}
|
||||
|
||||
void Awake()
|
||||
{
|
||||
_targetTransform = transform;
|
||||
|
||||
if (!TryGetComponent(out camera))
|
||||
{
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_moveComponent != null)
|
||||
{
|
||||
_moveComponent.enabled = enableWalkMode;
|
||||
}
|
||||
#if ENABLE_VR && ENABLE_VR_MODULE
|
||||
if (XRSettings.enabled)
|
||||
{
|
||||
// Seems like the best place to put this for now. Most XR debugging happens using this component.
|
||||
// @FixMe: useOcclusionMesh doesn't work anymore. Might be a Unity bug.
|
||||
XRSettings.useOcclusionMesh = !_debug._disableOcclusionMesh;
|
||||
XRSettings.occlusionMaskScale = _debug._occlusionMeshScale;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
float dt = Time.deltaTime;
|
||||
if (_fixedDt > 0f)
|
||||
dt = _fixedDt;
|
||||
|
||||
UpdateMovement(dt);
|
||||
|
||||
#if ENABLE_VR && ENABLE_VR_MODULE
|
||||
// These aren't useful and can break for XR hardware.
|
||||
if (!XRSettings.enabled || XRSettings.loadedDeviceName.Contains("MockHMD"))
|
||||
#endif
|
||||
{
|
||||
UpdateDragging(dt);
|
||||
UpdateKillRoll();
|
||||
}
|
||||
|
||||
#if ENABLE_VR && ENABLE_VR_MODULE
|
||||
if (XRSettings.enabled)
|
||||
{
|
||||
// Check if property has changed.
|
||||
if (XRSettings.useOcclusionMesh == _debug._disableOcclusionMesh)
|
||||
{
|
||||
// @FixMe: useOcclusionMesh doesn't work anymore. Might be a Unity bug.
|
||||
XRSettings.useOcclusionMesh = !_debug._disableOcclusionMesh;
|
||||
}
|
||||
|
||||
XRSettings.occlusionMaskScale = _debug._occlusionMeshScale;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void UpdateMovement(float dt)
|
||||
{
|
||||
// New input system works even when game view is not focused.
|
||||
if (!Application.isFocused)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if INPUT_SYSTEM_ENABLED
|
||||
if (!Mouse.current.leftButton.isPressed && _requireLMBToMove) return;
|
||||
float forward = (Keyboard.current.wKey.isPressed ? 1 : 0) - (Keyboard.current.sKey.isPressed ? 1 : 0);
|
||||
#else
|
||||
if (!Input.GetMouseButton(1) && _requireLMBToMove) return;
|
||||
float forward = (Input.GetKey(KeyCode.W) ? 1 : 0) - (Input.GetKey(KeyCode.S) ? 1 : 0);
|
||||
#endif
|
||||
if (simForwardInput)
|
||||
{
|
||||
forward = 1f;
|
||||
}
|
||||
|
||||
_targetTransform.position += linSpeed * _targetTransform.forward * forward * dt;
|
||||
var speed = linSpeed;
|
||||
|
||||
#if INPUT_SYSTEM_ENABLED
|
||||
if (Keyboard.current.leftShiftKey.isPressed)
|
||||
#else
|
||||
if (Input.GetKey(KeyCode.LeftShift))
|
||||
#endif
|
||||
{
|
||||
speed *= 3f;
|
||||
}
|
||||
|
||||
var offset = _targetTransform.forward * forward;
|
||||
// _targetTransform.position += speed * _targetTransform.forward * forward * dt;
|
||||
//_transform.position += linSpeed * _transform.right * Input.GetAxis( "Horizontal" ) * dt;
|
||||
#if INPUT_SYSTEM_ENABLED
|
||||
offset += _targetTransform.up * (Keyboard.current.eKey.isPressed ? 1 : 0);
|
||||
offset -= _targetTransform.up * (Keyboard.current.qKey.isPressed ? 1 : 0);
|
||||
offset -= _targetTransform.right * (Keyboard.current.aKey.isPressed ? 1 : 0);
|
||||
offset += _targetTransform.right * (Keyboard.current.dKey.isPressed ? 1 : 0);
|
||||
#else
|
||||
offset += _targetTransform.up * (Input.GetKey(KeyCode.E) ? 1 : 0);
|
||||
offset -= _targetTransform.up * (Input.GetKey(KeyCode.Q) ? 1 : 0);
|
||||
offset -= _targetTransform.right * (Input.GetKey(KeyCode.A) ? 1 : 0);
|
||||
offset += _targetTransform.right * (Input.GetKey(KeyCode.D) ? 1 : 0);
|
||||
#endif
|
||||
offset *= speed * dt;
|
||||
|
||||
// 使用控制器移动
|
||||
if (enableWalkMode)
|
||||
{
|
||||
UpdateWalk(offset);
|
||||
}
|
||||
// 直接设置坐标移动
|
||||
else
|
||||
{
|
||||
_targetTransform.position += offset;
|
||||
}
|
||||
{
|
||||
float rotate = 0f;
|
||||
#if INPUT_SYSTEM_ENABLED
|
||||
rotate += (Keyboard.current.rightArrowKey.isPressed ? 1 : 0);
|
||||
rotate -= (Keyboard.current.leftArrowKey.isPressed ? 1 : 0);
|
||||
#else
|
||||
rotate += (Input.GetKey(KeyCode.RightArrow) ? 1 : 0);
|
||||
rotate -= (Input.GetKey(KeyCode.LeftArrow) ? 1 : 0);
|
||||
#endif
|
||||
|
||||
rotate *= 5f;
|
||||
Vector3 ea = _targetTransform.eulerAngles;
|
||||
ea.y += 0.1f * rotSpeed * rotate * dt;
|
||||
_targetTransform.eulerAngles = ea;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateDragging(float dt)
|
||||
{
|
||||
// New input system works even when game view is not focused.
|
||||
if (!Application.isFocused)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 mousePos =
|
||||
#if INPUT_SYSTEM_ENABLED
|
||||
Mouse.current.position.ReadValue();
|
||||
#else
|
||||
Input.mousePosition;
|
||||
#endif
|
||||
|
||||
var wasLeftMouseButtonPressed =
|
||||
#if INPUT_SYSTEM_ENABLED
|
||||
Mouse.current.leftButton.wasPressedThisFrame;
|
||||
#else
|
||||
Input.GetMouseButtonDown(1);
|
||||
#endif
|
||||
|
||||
if (!_dragging && wasLeftMouseButtonPressed && camera.rect.Contains(camera.ScreenToViewportPoint(mousePos)) )
|
||||
//!Crest.OceanDebugGUI.OverGUI(mousePos))
|
||||
{
|
||||
_dragging = true;
|
||||
_lastMousePos = mousePos;
|
||||
}
|
||||
#if INPUT_SYSTEM_ENABLED
|
||||
if (_dragging && Mouse.current.leftButton.wasReleasedThisFrame)
|
||||
#else
|
||||
if (_dragging && Input.GetMouseButtonUp(1))
|
||||
#endif
|
||||
{
|
||||
_dragging = false;
|
||||
_lastMousePos = -Vector2.one;
|
||||
}
|
||||
|
||||
if (_dragging)
|
||||
{
|
||||
Vector2 delta = mousePos - _lastMousePos;
|
||||
|
||||
Vector3 ea = _targetTransform.eulerAngles;
|
||||
ea.x += -0.1f * rotSpeed * delta.y * dt;
|
||||
ea.y += 0.1f * rotSpeed * delta.x * dt;
|
||||
_targetTransform.eulerAngles = ea;
|
||||
|
||||
_lastMousePos = mousePos;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateKillRoll()
|
||||
{
|
||||
if (_debug._enableCameraRoll) return;
|
||||
Vector3 ea = _targetTransform.eulerAngles;
|
||||
ea.z = 0f;
|
||||
transform.eulerAngles = ea;
|
||||
}
|
||||
|
||||
private float gravity = 0;
|
||||
void UpdateWalk(Vector3 offset)
|
||||
{
|
||||
if (_moveComponent != null)
|
||||
{
|
||||
_moveComponent.enabled = enableWalkMode;
|
||||
if (enableWalkMode)
|
||||
{
|
||||
if (offset.y > 0)
|
||||
gravity = 0;
|
||||
else
|
||||
{
|
||||
gravity -= 0.01f;
|
||||
gravity = Mathf.Max(gravity, -0.98f);
|
||||
}
|
||||
offset.y = offset.y <= 0 ? gravity : offset.y;
|
||||
_moveComponent.Move(offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
using XericLibrary.Runtime.MacroLibrary;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace XericLibrary.Runtime.Type.Base
|
||||
{
|
||||
/// <summary>
|
||||
/// 游戏对象与软引用 (gameobject with soft refrence)。
|
||||
/// 将游戏对象引用保存为软引用,这对于经常替换模型的场景来说比较有用。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class GowSoftRef
|
||||
{
|
||||
public readonly string interval = "/";
|
||||
|
||||
/// <summary>
|
||||
/// 对象
|
||||
/// </summary>
|
||||
[HorizontalGroup("SoftRef")]
|
||||
[HideLabel]
|
||||
public GameObject targetObejct;
|
||||
|
||||
/// <summary>
|
||||
/// 路径
|
||||
/// </summary>
|
||||
[HorizontalGroup("SoftRef")]
|
||||
[HideLabel]
|
||||
public string targetPath;
|
||||
|
||||
/// <summary>
|
||||
/// 获取对象
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public GameObject GetObject()
|
||||
{
|
||||
if (targetObejct == null)
|
||||
UpdateGameobjectByPath();
|
||||
return targetObejct;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将对象制作成路径
|
||||
/// </summary>
|
||||
public void UpdatePathByGameobject()
|
||||
{
|
||||
if (targetObejct == null)
|
||||
{
|
||||
Debug.LogError("对象引用为空,无法获取软引用");
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsPrefab(targetObejct))
|
||||
{
|
||||
targetPath = "I Dont Know"; // todo 获取预制体路径
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetObejct.transform == null)
|
||||
{
|
||||
Debug.LogError("对象引用为空,无法获取软引用");
|
||||
return;
|
||||
}
|
||||
|
||||
targetPath = targetObejct.transform.GetFamilyName().Join(a => a, interval: interval);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将路径制作成对象
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool UpdateGameobjectByPath()
|
||||
{
|
||||
if (MacroObject.FindFamilyMemberExact(null, targetPath.Split(interval), out var target))
|
||||
{
|
||||
targetObejct = target.gameObject;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前的对象是预制体吗
|
||||
/// </summary>
|
||||
public bool IsPrefab(Object target)
|
||||
#if UNITY_EDITOR
|
||||
{
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
var type = PrefabUtility.GetPrefabAssetType(target);
|
||||
return type is PrefabAssetType.Regular or PrefabAssetType.Variant;
|
||||
#else
|
||||
return PrefabUtility.GetPrefabType(target) == PrefabType.PrefabInstance;
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
=> false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using XericLibrary.Runtime.CustomEditor;
|
||||
|
||||
namespace Deconstruction.Runtime.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取屏幕缩放
|
||||
/// </summary>
|
||||
public class AutoCanvasScale : MonoBehaviour
|
||||
{
|
||||
#region 字段属性
|
||||
|
||||
public CanvasScaler canvasScaler;
|
||||
|
||||
[Rename("设计尺寸")]
|
||||
public Vector2 StandardSize = new Vector2(1920,1080);
|
||||
|
||||
public Vector2 ScaleMinMax = new Vector2(0.5f, 2);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 生命周期
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
FindCanvasScaler();
|
||||
GetScreenScale();
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
FindCanvasScaler();
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
canvasScaler.scaleFactor = GetScreenScale();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 方法
|
||||
|
||||
private void FindCanvasScaler()
|
||||
{
|
||||
if (canvasScaler == null)
|
||||
canvasScaler = transform.GetComponent<CanvasScaler>();
|
||||
}
|
||||
|
||||
private float GetScreenScale()
|
||||
{
|
||||
Vector2 screenSize = new Vector2(Screen.width, Screen.height);
|
||||
// Debug.Log(canvasScaler.referenceResolution);
|
||||
var sf = screenSize / StandardSize;
|
||||
return Mathf.Clamp(Mathf.Min(sf.x, sf.y), ScaleMinMax.x, ScaleMinMax.y);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,429 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Deconstruction.Runtime.UI
|
||||
{
|
||||
public class CircleSlider : Selectable, IDragHandler, IEndDragHandler, IBeginDragHandler
|
||||
{
|
||||
[SerializeField] private RectTransform m_HandleRect;
|
||||
|
||||
public RectTransform handleRect
|
||||
{
|
||||
get { return m_HandleRect; }
|
||||
set
|
||||
{
|
||||
if (SetPropertyUtility.SetClass(ref m_HandleRect, value))
|
||||
{
|
||||
UpdateCachedReferences();
|
||||
UpdateVisuals();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] private RectTransform m_FillRect;
|
||||
|
||||
public RectTransform fillrect
|
||||
{
|
||||
get { return m_FillRect; }
|
||||
set
|
||||
{
|
||||
if (SetPropertyUtility.SetClass(ref m_FillRect, value))
|
||||
{
|
||||
UpdateCachedReferences();
|
||||
UpdateVisuals();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] private float m_MinValue = 0;
|
||||
|
||||
public float minValue
|
||||
{
|
||||
get => m_MinValue;
|
||||
set
|
||||
{
|
||||
if (SetPropertyUtility.SetStruct(ref m_MinValue, value))
|
||||
{
|
||||
Set(m_Value);
|
||||
UpdateVisuals();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] private float m_MaxValue = 1;
|
||||
|
||||
public float maxValue
|
||||
{
|
||||
get { return m_MaxValue; }
|
||||
set
|
||||
{
|
||||
if (SetPropertyUtility.SetStruct(ref m_MaxValue, value))
|
||||
{
|
||||
Set(m_Value);
|
||||
UpdateVisuals();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] private float m_Value;
|
||||
|
||||
public float value
|
||||
{
|
||||
get
|
||||
{
|
||||
if (wholeNumbers)
|
||||
return Mathf.Round(m_Value);
|
||||
return m_Value;
|
||||
}
|
||||
set { Set(value); }
|
||||
}
|
||||
|
||||
[SerializeField] private Image.Origin360 m_FillOrigin;
|
||||
|
||||
public Image.Origin360 fillOrigin
|
||||
{
|
||||
get { return m_FillOrigin; }
|
||||
set
|
||||
{
|
||||
if (SetPropertyUtility.SetStruct(ref m_FillOrigin, value))
|
||||
{
|
||||
Set(m_Value);
|
||||
UpdateVisuals();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] private float m_Radius;
|
||||
|
||||
public float radius
|
||||
{
|
||||
get { return m_Radius; }
|
||||
set
|
||||
{
|
||||
if (SetPropertyUtility.SetStruct(ref m_Radius, value))
|
||||
{
|
||||
UpdateVisuals();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] private bool m_ClockWise;
|
||||
|
||||
public bool clockWise
|
||||
{
|
||||
get { return m_ClockWise; }
|
||||
set
|
||||
{
|
||||
if (SetPropertyUtility.SetStruct(ref m_ClockWise, value))
|
||||
{
|
||||
UpdateCachedReferences();
|
||||
UpdateVisuals();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] private bool m_WholeNumbers = false;
|
||||
|
||||
public bool wholeNumbers
|
||||
{
|
||||
get { return m_WholeNumbers; }
|
||||
set
|
||||
{
|
||||
if (SetPropertyUtility.SetStruct(ref m_WholeNumbers, value))
|
||||
{
|
||||
Set(m_Value);
|
||||
UpdateVisuals();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float normalizedValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Mathf.Approximately(m_MinValue, m_MaxValue))
|
||||
return 0;
|
||||
return Mathf.InverseLerp(m_MinValue, m_MaxValue, value);
|
||||
}
|
||||
|
||||
set { this.value = Mathf.Lerp(m_MinValue, m_MaxValue, value); }
|
||||
}
|
||||
|
||||
private Action<float> m_OnValueChanged;
|
||||
private Action m_OnDragEnd;
|
||||
private Action m_OnTweenComplete;
|
||||
private Image m_FillImage;
|
||||
private bool m_DelayedUpdateVisuals = false;
|
||||
private bool m_dragHandler;
|
||||
private Camera m_eventCamera;
|
||||
private Coroutine m_TweenCor;
|
||||
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
UpdateCachedReferences();
|
||||
Set(m_Value);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
if (IsActive())
|
||||
{
|
||||
UpdateCachedReferences();
|
||||
Set(m_Value, false);
|
||||
m_DelayedUpdateVisuals = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (m_DelayedUpdateVisuals)
|
||||
{
|
||||
m_DelayedUpdateVisuals = false;
|
||||
UpdateVisuals();
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateCachedReferences()
|
||||
{
|
||||
if (m_FillRect != null)
|
||||
{
|
||||
m_FillImage = m_FillRect.GetComponent<Image>();
|
||||
m_FillImage.fillMethod = Image.FillMethod.Radial360;
|
||||
m_FillImage.fillOrigin = (int)m_FillOrigin;
|
||||
m_FillImage.fillClockwise = m_ClockWise;
|
||||
}
|
||||
}
|
||||
|
||||
private bool MayDrag(PointerEventData eventData)
|
||||
{
|
||||
return IsActive() && IsInteractable() && eventData.button == PointerEventData.InputButton.Left;
|
||||
}
|
||||
|
||||
public void OnDrag(PointerEventData eventData)
|
||||
{
|
||||
if (!MayDrag(eventData))
|
||||
return;
|
||||
if (m_dragHandler)
|
||||
{
|
||||
Vector2 localCursor;
|
||||
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(m_FillRect, eventData.position,
|
||||
eventData.pressEventCamera, out localCursor))
|
||||
return;
|
||||
float angle = CalAngle(localCursor);
|
||||
normalizedValue = (angle / 360);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
m_dragHandler = false;
|
||||
if (m_OnDragEnd != null)
|
||||
{
|
||||
m_OnDragEnd();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
if (m_HandleRect != null)
|
||||
{
|
||||
if (RectTransformUtility.RectangleContainsScreenPoint(m_HandleRect, eventData.position,
|
||||
eventData.enterEventCamera))
|
||||
{
|
||||
m_dragHandler = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_dragHandler = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private float CalAngle(Vector2 pos)
|
||||
{
|
||||
float angle = 0;
|
||||
float rad = Mathf.Atan2(pos.y, pos.x) * Mathf.Rad2Deg;
|
||||
switch (m_FillOrigin)
|
||||
{
|
||||
case Image.Origin360.Top:
|
||||
angle = m_ClockWise ? 90 - rad : rad + 270;
|
||||
break;
|
||||
case Image.Origin360.Left:
|
||||
angle = m_ClockWise ? 180 - rad : rad + 180;
|
||||
break;
|
||||
case Image.Origin360.Right:
|
||||
angle = m_ClockWise ? -rad : rad;
|
||||
break;
|
||||
case Image.Origin360.Bottom:
|
||||
angle = m_ClockWise ? 270 - rad : rad + 90;
|
||||
break;
|
||||
}
|
||||
|
||||
if (angle > 360)
|
||||
{
|
||||
angle -= 360;
|
||||
}
|
||||
|
||||
if (angle < 0)
|
||||
{
|
||||
angle += 360;
|
||||
}
|
||||
|
||||
return angle;
|
||||
}
|
||||
|
||||
private void UpdateVisuals()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
UpdateCachedReferences();
|
||||
#endif
|
||||
if (m_FillImage != null)
|
||||
{
|
||||
m_FillImage.fillAmount = normalizedValue;
|
||||
}
|
||||
|
||||
if (m_HandleRect != null)
|
||||
{
|
||||
m_HandleRect.transform.localPosition = CalPos(normalizedValue * 360);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 CalPos(float angle)
|
||||
{
|
||||
Vector2 pos = Vector2.zero;
|
||||
switch (m_FillOrigin)
|
||||
{
|
||||
case Image.Origin360.Left:
|
||||
angle = m_ClockWise ? 180 - angle : angle + 180;
|
||||
break;
|
||||
case Image.Origin360.Top:
|
||||
angle = m_ClockWise ? 90 - angle : angle + 90;
|
||||
break;
|
||||
case Image.Origin360.Right:
|
||||
angle = m_ClockWise ? -angle : angle;
|
||||
break;
|
||||
case Image.Origin360.Bottom:
|
||||
angle = m_ClockWise ? 270 - angle : angle - 90;
|
||||
break;
|
||||
}
|
||||
|
||||
angle = Mathf.Deg2Rad * angle;
|
||||
pos.y = Mathf.Sin(angle) * m_Radius;
|
||||
pos.x = Mathf.Cos(angle) * m_Radius;
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
private void Set(float input, bool sendCallback = true)
|
||||
{
|
||||
float newValue = ClampValue(input);
|
||||
if (m_Value == newValue)
|
||||
return;
|
||||
|
||||
m_Value = newValue;
|
||||
UpdateVisuals();
|
||||
if (sendCallback)
|
||||
{
|
||||
if (m_OnValueChanged != null)
|
||||
{
|
||||
m_OnValueChanged.Invoke(newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float ClampValue(float input)
|
||||
{
|
||||
float newValue = Mathf.Clamp(input, m_MinValue, m_MaxValue);
|
||||
if (wholeNumbers)
|
||||
newValue = Mathf.Round(newValue);
|
||||
return newValue;
|
||||
}
|
||||
|
||||
public void OnValueChange(Action<float> call)
|
||||
{
|
||||
m_OnValueChanged = call;
|
||||
}
|
||||
|
||||
public void OnDragEnd(Action call)
|
||||
{
|
||||
m_OnDragEnd = call;
|
||||
}
|
||||
|
||||
public void TweenValue(float val, float time, Action call = null)
|
||||
{
|
||||
m_TweenCor = StartCoroutine(_tweenValue(val, time));
|
||||
m_OnTweenComplete = call;
|
||||
}
|
||||
|
||||
public void StopTween()
|
||||
{
|
||||
if (m_TweenCor != null)
|
||||
{
|
||||
StopCoroutine(m_TweenCor);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator _tweenValue(float val, float time)
|
||||
{
|
||||
float t = 0;
|
||||
float oldValue = value;
|
||||
float interval = val - oldValue;
|
||||
|
||||
while (t < time)
|
||||
{
|
||||
t += Time.deltaTime;
|
||||
value = oldValue + (t / time) * interval;
|
||||
yield return 0;
|
||||
}
|
||||
|
||||
if (m_OnTweenComplete != null)
|
||||
{
|
||||
m_OnTweenComplete();
|
||||
}
|
||||
|
||||
value = val;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class SetPropertyUtility
|
||||
{
|
||||
public static bool SetColor(ref Color currentValue, Color newValue)
|
||||
{
|
||||
if (currentValue.r == newValue.r && currentValue.g == newValue.g && currentValue.b == newValue.b &&
|
||||
currentValue.a == newValue.a)
|
||||
return false;
|
||||
|
||||
currentValue = newValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool SetStruct<T>(ref T currentValue, T newValue) where T : struct
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(currentValue, newValue))
|
||||
return false;
|
||||
|
||||
currentValue = newValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool SetClass<T>(ref T currentValue, T newValue) where T : class
|
||||
{
|
||||
if ((currentValue == null && newValue == null) || (currentValue != null && currentValue.Equals(newValue)))
|
||||
return false;
|
||||
|
||||
currentValue = newValue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
using XericLibrary.Runtime.CustomEditor;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
using Sirenix.OdinInspector;
|
||||
#if UNITY_EDITOR
|
||||
using Sirenix.Utilities.Editor;
|
||||
#endif
|
||||
|
||||
namespace XericLibrary.Runtime.MacroLibrary
|
||||
{
|
||||
/// <summary>
|
||||
/// UI相关的扩展
|
||||
/// </summary>
|
||||
public static class MacroUI
|
||||
{
|
||||
#region toggle 扩展
|
||||
|
||||
private static FieldInfo togglesFieldInfo = typeof(ToggleGroup).GetField("m_Toggles", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
|
||||
public static List<Toggle> GetToggles(this ToggleGroup toggleGroup)
|
||||
{
|
||||
if (toggleGroup == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(toggleGroup));
|
||||
}
|
||||
|
||||
if (togglesFieldInfo == null)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to access the 'm_Toggles' field.");
|
||||
}
|
||||
|
||||
var toggles = togglesFieldInfo.GetValue(toggleGroup) as List<Toggle>;
|
||||
if (toggles == null)
|
||||
{
|
||||
throw new InvalidCastException("The 'm_Toggles' field is not of type List<Toggle>.");
|
||||
}
|
||||
|
||||
return toggles;
|
||||
// return (List<Toggle>)togglesFieldInfo.GetValue(toggleGroup);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前单选项组中激活的索引
|
||||
/// </summary>
|
||||
/// <param name="toggleGroup"></param>
|
||||
/// <returns></returns>
|
||||
[Obsolete("索引依赖单选项自身在大纲中的顺序,在运行时顺序可能与编辑时不一致,请使用ToggleMapping")]
|
||||
public static int GetActiveToggleIndex(this ToggleGroup toggleGroup)
|
||||
{
|
||||
var index = -1;
|
||||
foreach (var toggle in toggleGroup.GetToggles())
|
||||
{
|
||||
index++;
|
||||
if (toggle.isOn)
|
||||
return index;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前单选项组的数量
|
||||
/// </summary>
|
||||
/// <param name="toggleGroup"></param>
|
||||
/// <returns></returns>
|
||||
public static int GetToggleCount(this ToggleGroup toggleGroup)
|
||||
{
|
||||
return toggleGroup.GetToggles().Count;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 在单选项组上注册一个事件,当组中的任意成员变成激活状态时调用(其他的不会发生调用)。
|
||||
/// </summary>
|
||||
/// <param name="toggleGroup"></param>
|
||||
/// <param name="onToggleChange"></param>
|
||||
public static void OnToggleGroupChangeEvent(this ToggleGroup toggleGroup, Action<Toggle> onToggleChange)
|
||||
{
|
||||
foreach (var toggle in toggleGroup.GetToggles())
|
||||
{
|
||||
toggle.onValueChanged.AddListener(a =>
|
||||
{
|
||||
if (!a) return;
|
||||
onToggleChange?.Invoke(toggle);
|
||||
});
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 清空单选项组中的所有事件(与注册所有事件对应,但那个事件没法单独注销)
|
||||
/// </summary>
|
||||
/// <param name="toggleGroup"></param>
|
||||
public static void RemoveToggleGroupChangeEvent(this ToggleGroup toggleGroup)
|
||||
{
|
||||
foreach (var toggle in toggleGroup.GetToggles())
|
||||
{
|
||||
toggle.onValueChanged.RemoveAllListeners();
|
||||
}
|
||||
}
|
||||
|
||||
#region toggle 索引
|
||||
|
||||
/// <summary>
|
||||
/// toggle映射集
|
||||
/// <code>
|
||||
/// toggle映射集必须使用unity序列化管理,否则和直接使用toggleGroup没区别,目的是解决toggleGroup在打包后大纲视图的索引可能发生错位的问题。
|
||||
/// </code>
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class ToggleMapping : IEnumerable<Toggle>
|
||||
{
|
||||
#region 事件委托
|
||||
|
||||
/// <summary>
|
||||
/// 在单选项目切换时产生回调,返回选中的单选项目在组中的引用
|
||||
/// </summary>
|
||||
public Action<Toggle> OnAnyToggleSwitchOn;
|
||||
|
||||
/// <summary>
|
||||
/// 在单选项目切换时产生回调,返回选中的单选项目在组中的索引
|
||||
/// </summary>
|
||||
public Action<int> OnAnyToggleIndexSwitchOn;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 字段属性
|
||||
|
||||
[LabelText("单选组")] public ToggleGroup ToggleGroup;
|
||||
|
||||
[SerializeField, LabelText("编辑单选项目顺序")]
|
||||
[ListDrawerSettings(OnTitleBarGUI = "GetAndSortToggle")]
|
||||
private List<Toggle> toggleList = new List<Toggle>();
|
||||
|
||||
// 当前选中的项目
|
||||
private int nowSelectToggleIndex = 0;
|
||||
private Toggle nowSelectToggle = null;
|
||||
|
||||
|
||||
public Transform TogglesContext => ToggleGroup.transform;
|
||||
|
||||
/// <summary>
|
||||
/// 当前选中的toggle索引
|
||||
/// </summary>
|
||||
public int NowSelectToggleIndex => nowSelectToggleIndex;
|
||||
|
||||
/// <summary>
|
||||
/// 当前选中的toggle
|
||||
/// </summary>
|
||||
public Toggle NowSelectToggle => nowSelectToggle;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取并给列表排序(顺序不一定与拼音有关)
|
||||
/// </summary>
|
||||
public void GetAndSortToggle()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
// 自动获取并排序
|
||||
if (SirenixEditorGUI.ToolbarButton(EditorIcons.Refresh))
|
||||
{
|
||||
var newToggleList = MacroSort.FullCharacterOrderSort(ToggleGroup.GetToggles(), a => a.name).ToList();
|
||||
if (newToggleList.Count <= 0 || newToggleList == null)
|
||||
Debug.LogError("如果无法更新获取自动排序toggle,可能是因为toggleGroup被隐藏了,手动将其激活后再获取即可。");
|
||||
else
|
||||
toggleList = newToggleList;
|
||||
}
|
||||
// 反转顺序
|
||||
if (SirenixEditorGUI.ToolbarButton(EditorIcons.TriangleDown))
|
||||
{
|
||||
toggleList.Reverse();
|
||||
}
|
||||
#else
|
||||
var newToggleList = MacroSort.FullCharacterOrderSort(ToggleGroup.GetToggles(), a => a.name).ToList();
|
||||
if (newToggleList.Count <= 0 || newToggleList == null)
|
||||
Debug.LogError("如果无法更新获取自动排序toggle,可能是因为toggleGroup被隐藏了,手动将其激活后再获取即可。");
|
||||
else
|
||||
toggleList = newToggleList;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 接口
|
||||
|
||||
/// <summary>
|
||||
/// 获取索引下的单选项组件
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
public Toggle this[int index] => toggleList[index];
|
||||
|
||||
public int Count => toggleList.Count;
|
||||
|
||||
public IEnumerator<Toggle> GetEnumerator()
|
||||
{
|
||||
return toggleList.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 方法
|
||||
|
||||
/// <summary>
|
||||
/// 初始化
|
||||
/// <code>
|
||||
/// 自动将所有的按键绑定回调事件;如果作为刷新函数调用,请确保已经完全组索引。
|
||||
/// 或者可以直接清空旧项目(CleanToggle),然后逐个添加(AddToggle)
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
// 未指定组时,说明压根没用这部分功能,用不着初始化。
|
||||
if (ToggleGroup == null)
|
||||
return;
|
||||
|
||||
// 防呆警告
|
||||
if (toggleList.Count <= 0)
|
||||
{
|
||||
var toggles = ToggleGroup.GetToggles();
|
||||
if (toggles.Count != toggleList.Count)
|
||||
{
|
||||
toggleList = toggles;
|
||||
Debug.LogWarning($"在初始化单选项组时,{ToggleGroup.name}并未预先指定索引顺序,将默认使用大纲顺序。");
|
||||
}
|
||||
}
|
||||
// 事件初始化
|
||||
for (int i = 0; i < toggleList.Count; i++)
|
||||
{
|
||||
var toggle = toggleList[i];
|
||||
|
||||
ToggleAddEvent(toggle);
|
||||
|
||||
if (toggle.isOn)
|
||||
{
|
||||
nowSelectToggleIndex = i;
|
||||
nowSelectToggle = toggle;
|
||||
}
|
||||
}
|
||||
|
||||
if (nowSelectToggle == null)
|
||||
SetToggleOn(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加一个toggle
|
||||
/// </summary>
|
||||
/// <param name="t"></param>
|
||||
/// <returns>返回toggle的索引</returns>
|
||||
public int AddToggle(Toggle t)
|
||||
{
|
||||
ToggleAddEvent(t);
|
||||
|
||||
var resultIndex = toggleList.Count;
|
||||
toggleList.Add(t);
|
||||
return resultIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除一个toggle,这不会影响其他toggle的索引,但此处移除的位置会为空。
|
||||
/// <code>
|
||||
/// 注 :这不会销毁toggle。
|
||||
/// </code>
|
||||
/// </summary>
|
||||
/// <param name="t"></param>
|
||||
/// <returns>是否成功移除toggle</returns>
|
||||
public bool RemoveToggle(Toggle t)
|
||||
{
|
||||
var index = toggleList.IndexOf(t);
|
||||
if (index < 0)
|
||||
return false;
|
||||
|
||||
t.onValueChanged.RemoveAllListeners();
|
||||
toggleList[index] = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除toggle
|
||||
/// </summary>
|
||||
/// <param name="allowDestroy">是否同时销毁所有toggle</param>
|
||||
public void CleanToggle(bool allowDestroy)
|
||||
{
|
||||
foreach (var t in toggleList)
|
||||
{
|
||||
t.onValueChanged.RemoveAllListeners();
|
||||
if (allowDestroy)
|
||||
Object.Destroy(t);
|
||||
}
|
||||
toggleList.Clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// toggle注册的事件,只有当按下时才需要调用此事件。
|
||||
/// </summary>
|
||||
/// <param name="t"></param>
|
||||
private void ToggleAddEvent(Toggle t)
|
||||
{
|
||||
t.onValueChanged.AddListener(a =>
|
||||
{
|
||||
if (a) ToggleRegister(t);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// toggle注册的事件,只有当按下时才需要调用此事件。
|
||||
/// </summary>
|
||||
/// <param name="t"></param>
|
||||
private void ToggleRegister(Toggle t)
|
||||
{
|
||||
nowSelectToggle = t;
|
||||
nowSelectToggleIndex = GetIndex(t);
|
||||
OnAnyToggleSwitchOn?.Invoke(t);
|
||||
OnAnyToggleIndexSwitchOn?.Invoke(nowSelectToggleIndex);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取toggle代表的索引
|
||||
/// </summary>
|
||||
/// <param name="target"></param>
|
||||
/// <returns>如果这个toggle不存在于当前的单选项组中,返回-1</returns>
|
||||
public int GetIndex(Toggle target)
|
||||
{
|
||||
return toggleList.IndexOf(target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取toggle代表的索引, 如果toggle不存在于这个映射集中,将返回否
|
||||
/// </summary>
|
||||
/// <param name="target"></param>
|
||||
/// <param name="index"></param>
|
||||
/// <returns></returns>
|
||||
public bool TryGetIndex(Toggle target, out int index)
|
||||
{
|
||||
index = toggleList.IndexOf(target);
|
||||
return index >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置单选项激活
|
||||
/// </summary>
|
||||
/// <param name="target"></param>
|
||||
public void SetToggleOn(Toggle target)
|
||||
{
|
||||
target.isOn = true;
|
||||
}
|
||||
/// <summary>
|
||||
/// 设置单选项激活
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
public void SetToggleOn(int index)
|
||||
{
|
||||
if (0 < index && index < toggleList.Count)
|
||||
{
|
||||
SetToggleOn(toggleList[index]);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 设置单选项激活
|
||||
/// </summary>
|
||||
/// <param name="target"></param>
|
||||
public void SetToggleOnWithoutNotify(Toggle target)
|
||||
{
|
||||
target.SetIsOnWithoutNotify(true);
|
||||
}
|
||||
/// <summary>
|
||||
/// 设置单选项激活
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
public void SetToggleOnWithoutNotify(int index)
|
||||
{
|
||||
if (0 < index && index < toggleList.Count)
|
||||
{
|
||||
SetToggleOnWithoutNotify(toggleList[index]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 清除映射结构(不会清除toggle实例)
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
ToggleGroup.RemoveToggleGroupChangeEvent();
|
||||
toggleList.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除映射结构,并销毁所有toggle组件
|
||||
/// </summary>
|
||||
public void RemoveAllToggle()
|
||||
{
|
||||
for (int i = toggleList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
Object.Destroy(toggleList[i]);
|
||||
}
|
||||
Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region 按钮扩展
|
||||
|
||||
/// <summary>
|
||||
/// 所有按钮注册一个事件
|
||||
/// </summary>
|
||||
/// <param name="buttons"></param>
|
||||
/// <param name="targetEvent"></param>
|
||||
public static void RegisterOnClickEvent(this IEnumerable<Button> buttons, UnityAction targetEvent)
|
||||
{
|
||||
foreach (var button in buttons)
|
||||
{
|
||||
button.onClick.AddListener(targetEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 所有按钮注销一个事件
|
||||
/// </summary>
|
||||
/// <param name="buttons"></param>
|
||||
/// <param name="targetEvent"></param>
|
||||
public static void LogoutOnClickEvent(this IEnumerable<Button> buttons, UnityAction targetEvent)
|
||||
{
|
||||
foreach (var button in buttons)
|
||||
{
|
||||
button.onClick.RemoveListener(targetEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置所有按钮的可交互性
|
||||
/// </summary>
|
||||
/// <param name="buttons"></param>
|
||||
/// <param name="interactable"></param>
|
||||
public static void SetButtonInteractable(this IEnumerable<Button> buttons, bool interactable)
|
||||
{
|
||||
foreach (var button in buttons)
|
||||
{
|
||||
button.interactable = interactable;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 文本组件扩展
|
||||
|
||||
public static void SetText(this IEnumerable<TextMeshProUGUI> texts, string text)
|
||||
{
|
||||
foreach (var t in texts)
|
||||
{
|
||||
t.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UI变换获取扩展
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个组件的矩形变换组件
|
||||
/// </summary>
|
||||
/// <param name="target"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static RectTransform RectTransform<T>(this T target)
|
||||
where T : Component
|
||||
=> target.transform as RectTransform;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using XericLibrary.Runtime.MacroLibrary;
|
||||
|
||||
namespace Deconstruction.Runtime.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供ui界面上的脚本
|
||||
/// </summary>
|
||||
public class WeaklyUIBehaviour : MonoBehaviour
|
||||
{
|
||||
#region 字段属性
|
||||
|
||||
/// <summary>
|
||||
/// 此处的矩形变换组件
|
||||
/// </summary>
|
||||
public RectTransform rectTransform { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 此组件所在的Canvas
|
||||
/// </summary>
|
||||
public Canvas CanvasRoot { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 此组件所在的Canvas下的CanvasScaler
|
||||
/// </summary>
|
||||
public CanvasScaler CanvasRootScaler { get; private set; }
|
||||
|
||||
private bool isFirstOnEnable = false;
|
||||
private bool isFirstOnEnableStart = false;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 在加载脚本实例时调用
|
||||
/// </summary>
|
||||
protected virtual void Awake()
|
||||
{
|
||||
rectTransform = transform as RectTransform;
|
||||
CanvasRoot = transform.GetParents()
|
||||
.Select(t => t.GetComponent<Canvas>())
|
||||
.FirstOrDefault(a => a != null);
|
||||
if (CanvasRoot != null)
|
||||
CanvasRootScaler = CanvasRoot.GetComponent<CanvasScaler>();
|
||||
else
|
||||
{
|
||||
Debug.LogError($"成员{name}不是一个有效的ui成员,或没有放置在canvas上,致使其无法找到ui根组件");
|
||||
}
|
||||
|
||||
isFirstOnEnable = true;
|
||||
isFirstOnEnableStart = true;
|
||||
}
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
if (isFirstOnEnable)
|
||||
{
|
||||
isFirstOnEnable = false;
|
||||
OnFirstEnable();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 首次行为激活时调用(用于界面类型元素在运行期间首次初始化)
|
||||
/// </summary>
|
||||
protected virtual void OnFirstEnable()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
if (isFirstOnEnableStart)
|
||||
{
|
||||
isFirstOnEnableStart = false;
|
||||
OnFirstEnableStart();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 首次行为激活开始时调用(用于界面类型元素在运行期间首次初始化)
|
||||
/// </summary>
|
||||
protected virtual void OnFirstEnableStart()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"displayName":"Example Sample",
|
||||
"description": "Replace this string with your own description of the sample. Delete the Samples folder if not needed.",
|
||||
"createSeparatePackage": false
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// Use this sample example C# file to develop samples to guide usage of APIs
|
||||
// in your package.
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
namespace Lrss3.SesothoLine
|
||||
{
|
||||
/// <summary>
|
||||
/// Provide a general description of the public class.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Packages require XmlDoc documentation for ALL Package APIs.
|
||||
/// https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/xmldoc/xml-documentation-comments
|
||||
/// </remarks>
|
||||
public class MyPublicSampleExampleClass
|
||||
{
|
||||
/// <summary>
|
||||
/// Provide a description of what this public method does.
|
||||
/// </summary>
|
||||
public void CountThingsAndDoStuffAndOutputIt()
|
||||
{
|
||||
var result = new MyPublicRuntimeExampleClass().CountThingsAndDoStuff(1, 2, false);
|
||||
Debug.Log("Call CountThingsAndDoStuffAndOutputIt returns " + result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEngine.TestTools;
|
||||
using NUnit.Framework;
|
||||
using System.Collections;
|
||||
|
||||
namespace Lrss3.SesothoLine.Editor.Tests
|
||||
{
|
||||
|
||||
class EditorExampleTest
|
||||
{
|
||||
|
||||
[Test]
|
||||
public void EditorSampleTestSimplePasses()
|
||||
{
|
||||
// Use the Assert class to test conditions.
|
||||
}
|
||||
|
||||
// A UnityTest behaves like a coroutine in PlayMode
|
||||
// and allows you to yield null to skip a frame in EditMode
|
||||
[UnityTest]
|
||||
public IEnumerator EditorSampleTestWithEnumeratorPasses()
|
||||
{
|
||||
// Use the Assert class to test conditions.
|
||||
// yield to skip a frame
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "Lrss3.SesothoLine.Editor.Tests",
|
||||
"references": [
|
||||
"Lrss3.SesothoLine.Editor",
|
||||
"Lrss3.SesothoLine"
|
||||
],
|
||||
"optionalUnityReferences": [
|
||||
"TestAssemblies"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": []
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "Lrss3.SesothoLine.Tests",
|
||||
"references": [
|
||||
"Lrss3.SesothoLine"
|
||||
],
|
||||
"optionalUnityReferences": [
|
||||
"TestAssemblies"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": []
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
using NUnit.Framework;
|
||||
using System.Collections;
|
||||
|
||||
namespace Lrss3.SesothoLine.Tests
|
||||
{
|
||||
|
||||
class RuntimeExampleTest
|
||||
{
|
||||
|
||||
[Test]
|
||||
public void PlayModeSampleTestSimplePasses()
|
||||
{
|
||||
// Use the Assert class to test conditions.
|
||||
}
|
||||
|
||||
// A UnityTest behaves like a coroutine in PlayMode
|
||||
// and allows you to yield null to skip a frame in EditMode
|
||||
[UnityTest]
|
||||
public IEnumerator PlayModeSampleTestWithEnumeratorPasses()
|
||||
{
|
||||
// Use the Assert class to test conditions.
|
||||
// yield to skip a frame
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
This package contains third-party software components governed by the license(s) indicated below:
|
||||
---------
|
||||
|
||||
Component Name: [provide component name]
|
||||
|
||||
License Type: [Provide license type, i.e. "MIT", "Apache 2.0"]
|
||||
|
||||
[Provide License Details]
|
||||
|
||||
---------
|
||||
Component Name: [provide component name]
|
||||
|
||||
License Type: [Provide license type, i.e. "MIT", "Apache 2.0"]
|
||||
|
||||
[Provide License Details]
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "com.lrss3.deconstruction",
|
||||
"displayName": "Deconstruction Tool Library",
|
||||
"version": "0.2.4",
|
||||
"unity": "2021.3",
|
||||
"description": "提供尽可能齐全用法的宏库。\n比如数学,对象,文本的快捷扩展用法,或是对调试,显示,计算的用法。",
|
||||
"type": "library",
|
||||
"samples": [
|
||||
{
|
||||
"displayName": "Example Sample",
|
||||
"description": "Replace this string with your own description of the sample. Delete the Samples folder if not needed.",
|
||||
"path": "Samples~/Example"
|
||||
}
|
||||
]
|
||||
}
|
||||