石头
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public interface ISocket
|
||||
{
|
||||
public void PlugIn();
|
||||
public void PlugOut();
|
||||
public Vector3 GetSocketPos();
|
||||
}
|
||||
|
||||
public class BaseSocket : MonoBehaviour, ISocket
|
||||
{
|
||||
public void PlugIn()
|
||||
{
|
||||
Debug.Log("Plug Back InitSocket");
|
||||
}
|
||||
|
||||
public void PlugOut()
|
||||
{
|
||||
Debug.Log("Plug Out InitSocket");
|
||||
}
|
||||
|
||||
public Vector3 GetSocketPos()
|
||||
{
|
||||
return transform.position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 14ffdfdc63be167489dc01c0b661c1ac
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using AibisDream.Framework;
|
||||
using Newtonsoft.Json;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public class BodyModule : MonoBehaviour, ISocket
|
||||
{
|
||||
#region 组件引用
|
||||
|
||||
private const string SocketObjName = "Socket";
|
||||
private const string ModulePicName = "Module Pic";
|
||||
private Transform _socketPos;
|
||||
private BodyModuleSystem _bodyModuleSystem;
|
||||
|
||||
#endregion
|
||||
|
||||
// 模块基本数据
|
||||
[SerializeField] public BodyModuleData data;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// 获取组件索引
|
||||
InitReference();
|
||||
}
|
||||
|
||||
private void InitReference()
|
||||
{
|
||||
_socketPos = transform.Find(SocketObjName);
|
||||
_bodyModuleSystem = transform.parent.parent.GetComponent<BodyModuleSystem>();
|
||||
}
|
||||
|
||||
#region 插槽功能
|
||||
|
||||
public void PlugIn()
|
||||
{
|
||||
// 插入后先播放wave
|
||||
ShowWave();
|
||||
// 然后触发Yarn节点
|
||||
DialogController.Instance.StartDialogNode(_bodyModuleSystem.inHotErr ? "系统过热" : data.PlugInNodeName);
|
||||
}
|
||||
|
||||
public void PlugOut()
|
||||
{
|
||||
// 先关闭wave
|
||||
CloseWave();
|
||||
// TODO 触发Yarn节点(似乎目前没有拔出对话?)
|
||||
}
|
||||
|
||||
public void DeepIn()
|
||||
{
|
||||
if (!data.waveHasError) return;
|
||||
// 如果有错误并且已经深入了
|
||||
DialogController.Instance.StartDialogNode(data.DeepInNodeName);
|
||||
}
|
||||
|
||||
public void CloseWave()
|
||||
{
|
||||
_bodyModuleSystem.oscilloscopeSystem.StopWave();
|
||||
}
|
||||
|
||||
public void ShowWave()
|
||||
{
|
||||
if (_bodyModuleSystem.inHotErr)
|
||||
{
|
||||
_bodyModuleSystem.oscilloscopeSystem.PlayWave(WaveformType.HotNoise, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_bodyModuleSystem.oscilloscopeSystem.PlayWave(data.waveformType, data.waveHasError);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Vector3 GetSocketPos()
|
||||
{
|
||||
return _socketPos.position;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 模块数据保存与卸载
|
||||
|
||||
public void Load(BodyModuleData newData)
|
||||
{
|
||||
var modulePic = transform.Find(ModulePicName).GetComponent<SpriteRenderer>();
|
||||
var socketPos = transform.Find(SocketObjName);
|
||||
|
||||
gameObject.name = newData.moduleName;
|
||||
// 初始化位置与外形
|
||||
transform.localPosition = newData.ModulePos;
|
||||
modulePic.sprite = newData.ModulePic;
|
||||
socketPos.localPosition = newData.SocketPos;
|
||||
// 数据同步
|
||||
data = newData;
|
||||
}
|
||||
|
||||
public BodyModuleData Save()
|
||||
{
|
||||
var modulePic = transform.Find(ModulePicName).GetComponent<SpriteRenderer>();
|
||||
var socketPos = transform.Find(SocketObjName);
|
||||
|
||||
return new BodyModuleData
|
||||
{
|
||||
moduleName = data.moduleName,
|
||||
ModulePic = modulePic.sprite,
|
||||
ModulePos = transform.localPosition,
|
||||
SocketPos = socketPos.localPosition,
|
||||
waveformType = data.waveformType,
|
||||
waveHasError = data.waveHasError
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct BodyModuleData
|
||||
{
|
||||
public const string PlugInNodeTemplate = "{0}PlugIn";
|
||||
public const string DeepInTemplate = "{0}DeepIn";
|
||||
|
||||
// ID
|
||||
public string moduleName;
|
||||
|
||||
// 外形信息
|
||||
public string modulePicPath;
|
||||
public SerializableVector3 modulePos;
|
||||
public SerializableVector3 socketPos;
|
||||
|
||||
// 功能信息
|
||||
public WaveformType waveformType;
|
||||
public bool waveHasError;
|
||||
|
||||
// 只读
|
||||
[JsonIgnore] public string PlugInNodeName => string.Format(PlugInNodeTemplate, moduleName);
|
||||
[JsonIgnore] public string DeepInNodeName => string.Format(DeepInTemplate, moduleName);
|
||||
|
||||
[JsonIgnore]
|
||||
public Sprite ModulePic
|
||||
{
|
||||
get => !string.IsNullOrEmpty(modulePicPath) ? ResourceKit.LoadSpriteFromPsd(modulePicPath) : null;
|
||||
set => modulePicPath = ResourceKit.SaveSprite(value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public Vector3 ModulePos
|
||||
{
|
||||
get => modulePos.ToVector3();
|
||||
set => modulePos = new SerializableVector3(value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public Vector3 SocketPos
|
||||
{
|
||||
get => socketPos.ToVector3();
|
||||
set => socketPos = new SerializableVector3(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 485fe5a69106f8146a3ef9ca070f306d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,229 @@
|
||||
using System.Linq;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public class BodyModuleSystem : SystemHasData
|
||||
{
|
||||
public static string DataPath = Application.streamingAssetsPath + "/LevelData/BodyModuleSystem/";
|
||||
|
||||
#region 内部索引
|
||||
|
||||
public BodyModule[] BodyModules { get; private set; }
|
||||
public CableSystem CableSystem { get; private set; }
|
||||
public GameObject bodyModulePrefab;
|
||||
public Transform bodyModuleGroup;
|
||||
public OscilloscopeSystem oscilloscopeSystem;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 数据保存相关
|
||||
|
||||
public override string CurDataKey { get; set; }
|
||||
private string[] _keyOptions;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 事件
|
||||
|
||||
[HideInInspector] public UnityEvent<ISocket> plugInEvent;
|
||||
[HideInInspector] public UnityEvent<ISocket> plugOutEvent;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 系统状态
|
||||
|
||||
public bool inHotErr;
|
||||
public BodyModule curBodyModule;
|
||||
|
||||
#endregion
|
||||
|
||||
[SerializeField] public float plugSnappingRange = 1f;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
InitComponentRefs();
|
||||
}
|
||||
|
||||
private void InitComponentRefs()
|
||||
{
|
||||
// 处理内部索引
|
||||
BodyModules = transform.GetComponentsInChildren<BodyModule>();
|
||||
CableSystem = transform.Find("Cable System").GetComponent<CableSystem>();
|
||||
bodyModuleGroup = transform.Find("Body Module Group");
|
||||
oscilloscopeSystem = transform.Find("Oscilloscope System").GetComponent<OscilloscopeSystem>();
|
||||
|
||||
FixSystemCenter.SystemDic.Register(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 寻找最接近的Module
|
||||
/// </summary>
|
||||
/// <param name="sourcePos">源位置</param>
|
||||
/// <param name="targetModule">最近的Module</param>
|
||||
/// <returns>是否能找到目标范围内的Module</returns>
|
||||
public bool TryFindClosestModule(Vector3 sourcePos, out BodyModule targetModule)
|
||||
{
|
||||
var closestDistance = Mathf.Infinity;
|
||||
BodyModule closestModule = null;
|
||||
|
||||
// 寻找最接近的BodyModule
|
||||
foreach (var bodyModule in BodyModules)
|
||||
{
|
||||
float tempDistance = Vector3.Distance(sourcePos, bodyModule.GetSocketPos());
|
||||
|
||||
if (tempDistance < closestDistance)
|
||||
{
|
||||
closestDistance = tempDistance;
|
||||
closestModule = bodyModule;
|
||||
}
|
||||
}
|
||||
|
||||
// 判断距离是否在目标范围内,在的话就返回,不在的话返回个空的
|
||||
if (closestDistance < plugSnappingRange && closestModule)
|
||||
{
|
||||
targetModule = closestModule;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetModule = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 深入检查
|
||||
/// </summary>
|
||||
public void HandleDeepIn()
|
||||
{
|
||||
if (CableSystem.curBodyModule)
|
||||
{
|
||||
CableSystem.curBodyModule.DeepIn();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 冷却后处理
|
||||
/// </summary>
|
||||
public void OnCooling()
|
||||
{
|
||||
if (!inHotErr) return;
|
||||
|
||||
// 系统冷却
|
||||
inHotErr = false;
|
||||
curBodyModule?.ShowWave();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插头重新插入初始插孔
|
||||
/// </summary>
|
||||
public void ResetPlug()
|
||||
{
|
||||
CableSystem.ResetPlug();
|
||||
}
|
||||
|
||||
#region 数据加载与保存
|
||||
|
||||
public override string[] GetDataKeyOptions()
|
||||
{
|
||||
return _keyOptions ??= ConfigUtil.Instance.GetFileNames(DataPath);
|
||||
}
|
||||
|
||||
public override void Load()
|
||||
{
|
||||
if (string.IsNullOrEmpty(CurDataKey))
|
||||
{
|
||||
Debug.Log("配置项名称为空");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ConfigUtil.Instance.TryLoadConfig<BodyModuleSystemData>(DataPath + CurDataKey,
|
||||
out var data))
|
||||
{
|
||||
Debug.Log("没有找到配置项");
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// 现将当前配置保存为temp避免丢失
|
||||
if (CurDataKey != "temp.json")
|
||||
{
|
||||
SaveByKey("temp.json");
|
||||
}
|
||||
#endif
|
||||
|
||||
// 加载数据
|
||||
LoadData(data);
|
||||
}
|
||||
|
||||
private void LoadData(BodyModuleSystemData systemData)
|
||||
{
|
||||
inHotErr = systemData.hasHotErr;
|
||||
|
||||
var oldModules = GetComponentsInChildren<BodyModule>();
|
||||
// 先销毁旧的Module
|
||||
foreach (var oldModule in oldModules)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
DestroyImmediate(oldModule.gameObject);
|
||||
#else
|
||||
Destory(oldModule);
|
||||
#endif
|
||||
}
|
||||
|
||||
// 再加载新的Module
|
||||
foreach (var newModuleData in systemData.moduleDataArray)
|
||||
{
|
||||
BodyModule newModule = Instantiate(bodyModulePrefab, bodyModuleGroup).GetComponent<BodyModule>();
|
||||
newModule.Load(newModuleData);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Save()
|
||||
{
|
||||
if (string.IsNullOrEmpty(CurDataKey)) return;
|
||||
|
||||
SaveByKey(CurDataKey);
|
||||
if (!_keyOptions.Contains(CurDataKey))
|
||||
{
|
||||
// 重新读取key
|
||||
_keyOptions = ConfigUtil.Instance.GetFileNames(DataPath);
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveByKey(string key)
|
||||
{
|
||||
var oldModules = GetComponentsInChildren<BodyModule>();
|
||||
var moduleDataArray = new BodyModuleData[oldModules.Length];
|
||||
for (int i = 0; i < oldModules.Length; i++)
|
||||
{
|
||||
moduleDataArray[i] = oldModules[i].Save();
|
||||
}
|
||||
|
||||
var systemData = new BodyModuleSystemData
|
||||
{
|
||||
moduleDataArray = moduleDataArray,
|
||||
hasHotErr = inHotErr
|
||||
};
|
||||
|
||||
ConfigUtil.Instance.SaveConfig(systemData, DataPath + key);
|
||||
}
|
||||
|
||||
public void AddBodyModule()
|
||||
{
|
||||
Instantiate(bodyModulePrefab, bodyModuleGroup);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public struct BodyModuleSystemData
|
||||
{
|
||||
public bool hasHotErr;
|
||||
public BodyModuleData[] moduleDataArray;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9290bcfb4cf7b6846b60dcd0851a5091
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
[RequireComponent(typeof(LineRenderer))]
|
||||
public class Cable : MonoBehaviour
|
||||
{
|
||||
private Transform _start;
|
||||
private Transform _end;
|
||||
|
||||
private LineRenderer _lineRenderer;
|
||||
private Vector3[] _points;
|
||||
|
||||
private CableSystem _cableSystem;
|
||||
|
||||
[SerializeField]
|
||||
private CableConfig config = CableConfig.GeneDefaultConfig();
|
||||
|
||||
private void Start()
|
||||
{
|
||||
InitComponentRefs();
|
||||
InitPoints();
|
||||
}
|
||||
|
||||
private void InitComponentRefs()
|
||||
{
|
||||
_cableSystem = transform.parent.GetComponent<CableSystem>();
|
||||
|
||||
_lineRenderer = GetComponent<LineRenderer>();
|
||||
_lineRenderer.positionCount = config.resolution;
|
||||
|
||||
// 连线起止点
|
||||
_start = _cableSystem.CableRootPos;
|
||||
_end = _cableSystem.PlugRef.transform;
|
||||
}
|
||||
|
||||
private void InitPoints()
|
||||
{
|
||||
_points = new Vector3[config.resolution];
|
||||
for (int i = 0; i < config.resolution; i++)
|
||||
{
|
||||
float t = i / (float)(config.resolution - 1);
|
||||
_points[i] = Vector3.Lerp(_start.position, _end.position, t);
|
||||
}
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
DrawLine();
|
||||
}
|
||||
|
||||
void DrawLine()
|
||||
{
|
||||
var points = UpdateRope();
|
||||
for (int i = 0; i < config.resolution; i++)
|
||||
{
|
||||
_lineRenderer.SetPosition(i, points[i] + Vector3.forward * -.3f);
|
||||
}
|
||||
}
|
||||
|
||||
Vector3[] UpdateRope()
|
||||
{
|
||||
|
||||
float t = Mathf.InverseLerp(config.dstMin, config.dstMax, (_start.position - _end.position).magnitude);
|
||||
float F = Mathf.Lerp(config.forceMin, config.forceMax, t);
|
||||
_points[0] = _start.position;
|
||||
_points[^1] = _end.position;
|
||||
|
||||
for (int ik = 0; ik < config.k; ik++)
|
||||
{
|
||||
for (int i = 1; i < _points.Length - 1; i++)
|
||||
{
|
||||
Vector3 offsetPrev = _points[i - 1] - _points[i];
|
||||
Vector3 offsetNext = _points[i + 1] - _points[i];
|
||||
Vector3 velocity = offsetPrev.normalized * (offsetPrev.magnitude * F) +
|
||||
offsetNext.normalized * (offsetNext.magnitude * F);
|
||||
_points[i] += velocity * Time.deltaTime / config.k;
|
||||
}
|
||||
|
||||
for (int i = 1; i < _points.Length - 1; i++)
|
||||
{
|
||||
_points[i] += Vector3.down * (9.8f * Time.deltaTime) / config.k;
|
||||
}
|
||||
}
|
||||
|
||||
return _points;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为线缆设置末端点
|
||||
/// </summary>
|
||||
/// <param name="endPos">末端点</param>
|
||||
public void SetEndPos(Transform endPos)
|
||||
{
|
||||
_end = endPos;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct CableConfig
|
||||
{
|
||||
public int resolution;
|
||||
public float dstMin;
|
||||
public float dstMax;
|
||||
public float forceMin;
|
||||
public float forceMax;
|
||||
public int k;
|
||||
|
||||
public static CableConfig GeneDefaultConfig()
|
||||
{
|
||||
return new CableConfig
|
||||
{
|
||||
resolution = 10,
|
||||
dstMin = 0.1f,
|
||||
dstMax = 1.0f,
|
||||
forceMin = 0.1f,
|
||||
forceMax = 150f,
|
||||
k = 10
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae3259f7b7344d67800eb8718a16dcc4
|
||||
timeCreated: 1730993890
|
||||
@@ -0,0 +1,122 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using Yarn.Unity;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public class CableSystem : MonoBehaviour
|
||||
{
|
||||
#region 组件索引
|
||||
|
||||
public Plug PlugRef { get; private set; }
|
||||
public Cable CableRef { get; private set; }
|
||||
public Transform CableRootPos { get; private set; }
|
||||
public ISocket InitSocket { get; private set; }
|
||||
|
||||
public BodyModuleSystem BodyModuleSystem { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 系统状态
|
||||
|
||||
public bool isInDialog = false;
|
||||
public bool isActive = true;
|
||||
|
||||
[HideInInspector] public ISocket curSocket;
|
||||
[HideInInspector] public BodyModule curBodyModule;
|
||||
|
||||
#endregion
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
InitComponentRefs();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
InitSystem();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
DialogController.OnDialogueStart += HandleDialogueStart;
|
||||
DialogController.OnDialogueComplete += HandleDialogueComplete;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
DialogController.OnDialogueStart -= HandleDialogueStart;
|
||||
DialogController.OnDialogueComplete -= HandleDialogueComplete;
|
||||
}
|
||||
|
||||
private void InitComponentRefs()
|
||||
{
|
||||
PlugRef = transform.Find("Plug").GetComponent<Plug>();
|
||||
CableRef = transform.Find("Cable").GetComponent<Cable>();
|
||||
CableRootPos = transform.Find("Cable Root Pos").transform;
|
||||
InitSocket = transform.Find("Init Socket").GetComponent<ISocket>();
|
||||
|
||||
BodyModuleSystem = transform.parent.GetComponent<BodyModuleSystem>();
|
||||
}
|
||||
|
||||
private void InitSystem()
|
||||
{
|
||||
PlugRef.InitPlug(InitSocket);
|
||||
}
|
||||
|
||||
public void ResetPlug()
|
||||
{
|
||||
// 已经在初始插孔里了,不用动
|
||||
if (curSocket == InitSocket) return;
|
||||
// 不在初始插孔里,就从当前插孔拔出来,插回初始插孔
|
||||
PlugRef.PullUpSocket();
|
||||
PlugRef.InsertSocket(InitSocket);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 寻找最接近的Module
|
||||
/// </summary>
|
||||
/// <param name="sourcePos">源位置</param>
|
||||
/// <param name="targetModule">最近的Module</param>
|
||||
/// <returns>是否能找到目标范围内的Module</returns>
|
||||
public bool TryFindClosestModule(Vector3 sourcePos, out BodyModule targetModule)
|
||||
{
|
||||
return BodyModuleSystem.TryFindClosestModule(sourcePos, out targetModule);
|
||||
}
|
||||
|
||||
#region 系统可用状态
|
||||
|
||||
private void HandleDialogueStart()
|
||||
{
|
||||
isInDialog = true;
|
||||
}
|
||||
|
||||
private void HandleDialogueComplete()
|
||||
{
|
||||
isInDialog = false;
|
||||
}
|
||||
|
||||
[YarnCommand("Disable_plugInput")]
|
||||
public void Disable_plugInput()
|
||||
{
|
||||
isActive = false;
|
||||
}
|
||||
|
||||
[YarnCommand("Enable_plugInput")]
|
||||
public void Enable_plugInput()
|
||||
{
|
||||
isActive = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询插线系统是否可用
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPlugAvailable()
|
||||
{
|
||||
return isActive && !isInDialog;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 587f3ee69d7c47d291e4e488f9ca3090
|
||||
timeCreated: 1731039298
|
||||
@@ -0,0 +1,154 @@
|
||||
using AibisDream.Utility;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public class OscilloscopeSystem : MonoBehaviour
|
||||
{
|
||||
#region 组件索引
|
||||
|
||||
private BodyModuleSystem _bodyModuleSystem;
|
||||
|
||||
private TMP_Text _screenText;
|
||||
private WaveformLine _waveformLine;
|
||||
private ScanLine _scanLine;
|
||||
private SpriteRenderer _screen;
|
||||
private WaveSlider _waveSlider;
|
||||
private SpriteButton _deepinButton;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 状态
|
||||
|
||||
public bool isScanning;
|
||||
public bool isPlaying;
|
||||
public bool canScan;
|
||||
|
||||
#endregion
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
FixSystemCenter.SystemDic.Register(this);
|
||||
InitComponentRefs();
|
||||
}
|
||||
|
||||
private void InitComponentRefs()
|
||||
{
|
||||
_screenText = transform.Find("Screen Text").GetComponent<TMP_Text>();
|
||||
_waveformLine = transform.Find("Waveform Visualizer").GetComponent<WaveformLine>();
|
||||
_scanLine = transform.Find("Scan Line").GetComponent<ScanLine>();
|
||||
_screen = transform.Find("Screen").GetComponent<SpriteRenderer>();
|
||||
_waveSlider = transform.Find("Wave Slider").GetComponent<WaveSlider>();
|
||||
_deepinButton = new SpriteButton(transform.Find("Deepin Button").gameObject, false);
|
||||
|
||||
_waveformLine.SetLineSize(_screen.bounds.size.x, _screen.bounds.size.y);
|
||||
_scanLine.SetScreenSize(_screen.bounds.size.x, _screen.bounds.size.y);
|
||||
_screenText.text = "";
|
||||
|
||||
_deepinButton.OnButtonDown += StartScan;
|
||||
_scanLine.ScanErrorEvent += OnScanSuccess;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
_deepinButton.CheckButtonClick();
|
||||
}
|
||||
|
||||
#region 对外开放
|
||||
|
||||
/// <summary>
|
||||
/// 屏幕上播放波形
|
||||
/// </summary>
|
||||
/// <param name="waveformType">波形类型</param>
|
||||
/// <param name="hasError">是否有错误</param>
|
||||
public void PlayWave(WaveformType waveformType, bool hasError)
|
||||
{
|
||||
_waveSlider.InitSlider();
|
||||
isPlaying = true;
|
||||
canScan = hasError;
|
||||
_deepinButton.SetActive(canScan);
|
||||
|
||||
var initialRandomPhase = Mathf.PI * Random.Range(7, 13) * 0.1f;
|
||||
_waveformLine.PlayWave(waveformType, initialRandomPhase, hasError);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止播放波形
|
||||
/// </summary>
|
||||
public void StopWave()
|
||||
{
|
||||
isPlaying = false;
|
||||
canScan = false;
|
||||
isScanning = false;
|
||||
_deepinButton.SetActive(false);
|
||||
_waveSlider.InitSlider();
|
||||
|
||||
_waveformLine.StopWave();
|
||||
_scanLine.CloseScan();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调整相位
|
||||
/// </summary>
|
||||
public void AdjustPhaseShift(float phaseShift)
|
||||
{
|
||||
if (isPlaying)
|
||||
{
|
||||
_waveformLine.AdjustPhaseShift(phaseShift);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始扫描
|
||||
/// </summary>
|
||||
public void StartScan()
|
||||
{
|
||||
if (!canScan) return;
|
||||
|
||||
float spikeX = _waveformLine.GetSpikeX();
|
||||
StartCoroutine(_scanLine.ScanForError(spikeX));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 扫描成功后
|
||||
/// </summary>
|
||||
public void OnScanSuccess()
|
||||
{
|
||||
_bodyModuleSystem.HandleDeepIn();
|
||||
_ = CommonUtil.Delay(2000, StopWave);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示消息
|
||||
/// </summary>
|
||||
/// <param name="message">消息</param>
|
||||
public void ShowScreenMessage(string message)
|
||||
{
|
||||
_screenText.text = message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除消息
|
||||
/// </summary>
|
||||
public void ClearScreenMessage()
|
||||
{
|
||||
_screenText.text = "";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public enum WaveformType
|
||||
{
|
||||
Default,
|
||||
Sine,
|
||||
Square,
|
||||
Triangle,
|
||||
Sawtooth,
|
||||
HotNoise,
|
||||
Composite,
|
||||
Pulse
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a2e20b111511460a8f3a151bd470da9d
|
||||
timeCreated: 1731328037
|
||||
@@ -0,0 +1,150 @@
|
||||
using AibisDream.Utility;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public class Plug : MonoBehaviour, IDragHandler, IPointerDownHandler, IPointerUpHandler
|
||||
{
|
||||
#region 插头当前信息
|
||||
|
||||
private Vector3 _startPos;
|
||||
private Vector3 _startRotate;
|
||||
private readonly Vector3 _pickupOffset = new(0, 0, 0);
|
||||
private readonly Quaternion _pickupRotationOffset = Quaternion.Euler(0, 0, 30);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 索引
|
||||
|
||||
private CableSystem _cableSystem;
|
||||
private SpriteRenderer _sprite;
|
||||
private Transform _plugRootPos;
|
||||
|
||||
#endregion
|
||||
|
||||
private bool _isDragging;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
InitComponentRef();
|
||||
}
|
||||
|
||||
private void InitComponentRef()
|
||||
{
|
||||
_cableSystem = transform.parent.GetComponent<CableSystem>();
|
||||
_sprite = GetComponent<SpriteRenderer>();
|
||||
_plugRootPos = transform.Find("Plug Root Pos");
|
||||
}
|
||||
|
||||
public void InitPlug(ISocket initSocket)
|
||||
{
|
||||
InsertSocket(initSocket);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插入某个socket
|
||||
/// </summary>
|
||||
/// <param name="targetSocket">目标socket</param>
|
||||
public void InsertSocket(ISocket targetSocket)
|
||||
{
|
||||
// 修改插头位置
|
||||
transform.position = targetSocket.GetSocketPos();
|
||||
_cableSystem.CableRef.SetEndPos(transform);
|
||||
|
||||
// 处理插槽插入
|
||||
targetSocket.PlugIn();
|
||||
AudioManager.RandomPlayInteraction("plug_in");
|
||||
_sprite.enabled = false;
|
||||
|
||||
// 触发事件
|
||||
_cableSystem.BodyModuleSystem.plugInEvent?.Invoke(targetSocket);
|
||||
|
||||
_cableSystem.curSocket = targetSocket;
|
||||
if (targetSocket is BodyModule bodyModule)
|
||||
{
|
||||
_cableSystem.curBodyModule = bodyModule;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从某个Socket拔出来
|
||||
/// </summary>
|
||||
public void PullUpSocket()
|
||||
{
|
||||
// 修改线头位置
|
||||
_cableSystem.CableRef.SetEndPos(_plugRootPos);
|
||||
|
||||
_cableSystem.curSocket?.PlugOut();
|
||||
AudioManager.RandomPlayInteraction("plug_out");
|
||||
_sprite.enabled = true;
|
||||
// 触发事件
|
||||
_cableSystem.BodyModuleSystem.plugOutEvent?.Invoke(_cableSystem.curSocket);
|
||||
|
||||
_cableSystem.curBodyModule = null;
|
||||
_cableSystem.curSocket = null;
|
||||
}
|
||||
|
||||
public void OnDrag(PointerEventData eventData)
|
||||
{
|
||||
// 系统被锁定就返回
|
||||
if (!_cableSystem.IsPlugAvailable()) return;
|
||||
|
||||
// 未被拖拽也返回
|
||||
if (!_isDragging) return;
|
||||
|
||||
transform.position = CommonUtil.GetMouseWorldPos(eventData.position, transform);
|
||||
}
|
||||
|
||||
public void OnPointerDown(PointerEventData eventData)
|
||||
{
|
||||
// 系统被锁定就返回
|
||||
if (!_cableSystem.IsPlugAvailable()) return;
|
||||
|
||||
// 开启拖拽
|
||||
_isDragging = true;
|
||||
// 进行插拔动作
|
||||
if (_cableSystem.curSocket != null) PullUpSocket();
|
||||
|
||||
// 调整插头形状
|
||||
AdjustPlugPos();
|
||||
}
|
||||
|
||||
public void OnPointerUp(PointerEventData eventData)
|
||||
{
|
||||
// 系统被锁定就返回
|
||||
if (!_cableSystem.IsPlugAvailable()) return;
|
||||
|
||||
// 停止拖拽
|
||||
_isDragging = false;
|
||||
|
||||
// 寻找可吸附的Module
|
||||
if (_cableSystem.TryFindClosestModule(transform.position, out var targetModule))
|
||||
{
|
||||
// 找到了就插入目标Module
|
||||
InsertSocket(targetModule);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 没找到就插回初始Socket,需要播动画
|
||||
transform.DOMove(_cableSystem.InitSocket.GetSocketPos(), 0.1f).OnComplete(() =>
|
||||
{
|
||||
InsertSocket(_cableSystem.InitSocket);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void AdjustPlugPos()
|
||||
{
|
||||
transform.position += _pickupOffset;
|
||||
transform.rotation = _pickupRotationOffset;
|
||||
}
|
||||
|
||||
void OnDrawGizmosSelected()
|
||||
{
|
||||
Gizmos.color = Color.yellow;
|
||||
Gizmos.DrawWireSphere(transform.position, 0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eceaf3bcec07439d8391175331323e3c
|
||||
timeCreated: 1731043337
|
||||
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public class ScanLine : MonoBehaviour
|
||||
{
|
||||
#region 组件索引
|
||||
|
||||
private LineRenderer _line;
|
||||
private OscilloscopeSystem _oscilloscopeSystem;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 部分参数(后面改成从screen获取)
|
||||
|
||||
private float _screenLeftX;
|
||||
private float _screenRightX;
|
||||
|
||||
private float _topY;
|
||||
private float _bottomY;
|
||||
|
||||
private const float ScanSpeed = 2f;
|
||||
|
||||
public event Action ScanErrorEvent;
|
||||
|
||||
#endregion
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
InitComponentRefs();
|
||||
}
|
||||
|
||||
private void InitComponentRefs()
|
||||
{
|
||||
_oscilloscopeSystem = GetComponentInParent<OscilloscopeSystem>();
|
||||
|
||||
_line = GetComponent<LineRenderer>();
|
||||
_line.enabled = false;
|
||||
_line.positionCount = 2;
|
||||
}
|
||||
|
||||
public IEnumerator ScanForError(float spikeX)
|
||||
{
|
||||
_line.enabled = true;
|
||||
_oscilloscopeSystem.isScanning = true;
|
||||
// 初始化扫描线
|
||||
InitStartLine();
|
||||
// 扫描线右移
|
||||
var currentX = _screenLeftX;
|
||||
var lastX = currentX;
|
||||
while (currentX <= _screenRightX)
|
||||
{
|
||||
// 横坐标右移
|
||||
currentX += Time.deltaTime * ScanSpeed;
|
||||
SetLinePos(currentX);
|
||||
|
||||
// 检查是否扫到尖峰
|
||||
if (CheckSpikeInRange(lastX, currentX, spikeX))
|
||||
{
|
||||
// 修改线条
|
||||
MarkLineAsError(spikeX);
|
||||
// 触发扫描事件
|
||||
yield return new WaitForSeconds(1f);
|
||||
ScanErrorEvent?.Invoke();
|
||||
break;
|
||||
}
|
||||
|
||||
lastX = currentX;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
_oscilloscopeSystem.isScanning = false;
|
||||
}
|
||||
|
||||
private void InitStartLine()
|
||||
{
|
||||
// 初始状态是一根靠左的竖线
|
||||
SetLinePos(_screenLeftX);
|
||||
|
||||
_line.enabled = true;
|
||||
_line.startColor = Color.white;
|
||||
_line.endColor = Color.white;
|
||||
}
|
||||
|
||||
private void SetLinePos(float posX)
|
||||
{
|
||||
var topPosition = new Vector3(posX, _topY, 0);
|
||||
var bottomPosition = new Vector3(posX, _bottomY, 0);
|
||||
_line.SetPosition(0, topPosition);
|
||||
_line.SetPosition(1, bottomPosition);
|
||||
}
|
||||
|
||||
private bool CheckSpikeInRange(float x1, float x2, float spikeX)
|
||||
{
|
||||
var left = Mathf.Min(x1, x2);
|
||||
var right = Mathf.Max(x1, x2);
|
||||
return spikeX >= left && spikeX <= right;
|
||||
}
|
||||
|
||||
private void MarkLineAsError(float spikeX)
|
||||
{
|
||||
SetLinePos(spikeX);
|
||||
_line.startColor = Color.red;
|
||||
_line.endColor = Color.red;
|
||||
}
|
||||
|
||||
public void CloseScan()
|
||||
{
|
||||
_line.enabled = false;
|
||||
}
|
||||
|
||||
public void SetScreenSize(float width, float height)
|
||||
{
|
||||
_screenLeftX = - width / 2;
|
||||
_screenRightX = width / 2;
|
||||
|
||||
_topY = height / 2;
|
||||
_bottomY = - height / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d539f4fdddef410c9df21320f69fe38f
|
||||
timeCreated: 1731586860
|
||||
@@ -0,0 +1,99 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public class WaveSlider : MonoBehaviour
|
||||
{
|
||||
private OscilloscopeSystem _oscilloscopeSystem;
|
||||
|
||||
private Transform _slider; // 滑块的 Transform
|
||||
|
||||
private Vector2 _startPos;
|
||||
private Vector2 _endPos;
|
||||
|
||||
private float _value;
|
||||
private bool _isDragging;
|
||||
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
InitComponentsRef();
|
||||
}
|
||||
|
||||
private void InitComponentsRef()
|
||||
{
|
||||
_oscilloscopeSystem = transform.GetComponentInParent<OscilloscopeSystem>();
|
||||
|
||||
_slider = transform.Find("Sliding Block");
|
||||
var startTrans = transform.Find("Slider Start Pos");
|
||||
var endTrans = transform.Find("Slider End Pos");
|
||||
|
||||
_startPos = new Vector2(startTrans.position.x, startTrans.position.y);
|
||||
_endPos = new Vector2(endTrans.position.x, endTrans.position.y);
|
||||
InitSlider();
|
||||
}
|
||||
|
||||
public void InitSlider()
|
||||
{
|
||||
_slider.position = new Vector3(_startPos.x, _startPos.y, _slider.position.z);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_oscilloscopeSystem.isScanning) return;
|
||||
|
||||
if (Input.GetMouseButtonDown(0))
|
||||
{
|
||||
OnMouseButtonDown();
|
||||
}
|
||||
|
||||
if (Input.GetMouseButtonUp(0))
|
||||
{
|
||||
OnMouseButtonUp();
|
||||
}
|
||||
|
||||
if (_isDragging)
|
||||
{
|
||||
OnDragging();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseButtonDown()
|
||||
{
|
||||
// 检测鼠标是否点击在滑块上
|
||||
Vector2 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
|
||||
Collider2D hitCollider = Physics2D.OverlapPoint(mousePos);
|
||||
|
||||
if (hitCollider && hitCollider.transform == _slider)
|
||||
{
|
||||
_isDragging = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseButtonUp()
|
||||
{
|
||||
_isDragging = false;
|
||||
}
|
||||
|
||||
private void OnDragging()
|
||||
{
|
||||
Vector2 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
|
||||
float clampedX = Mathf.Clamp(mousePos.x, _startPos.x, _endPos.x);
|
||||
_slider.position = new Vector3(clampedX, _slider.position.y, _slider.position.z);
|
||||
|
||||
// 计算滑块在轨道上的比例
|
||||
var curValue = (clampedX - _startPos.x) / (_endPos.x - _startPos.x);
|
||||
UpdateValue(curValue);
|
||||
}
|
||||
|
||||
private void UpdateValue(float curValue)
|
||||
{
|
||||
if (!Mathf.Approximately(curValue, _value))
|
||||
{
|
||||
// 如果相位有更新,就同步给波形
|
||||
_oscilloscopeSystem.AdjustPhaseShift(curValue);
|
||||
_value = curValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c6494bd9d5943cf8e875ae582481cf1
|
||||
timeCreated: 1732863766
|
||||
@@ -0,0 +1,378 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public class WaveformLine : MonoBehaviour
|
||||
{
|
||||
#region 组件索引
|
||||
|
||||
private LineRenderer _waveformLine;
|
||||
|
||||
#endregion
|
||||
|
||||
private float _screenWidth;
|
||||
private float _screenHeight;
|
||||
|
||||
#region 状态
|
||||
|
||||
private bool _isWavePlaying;
|
||||
|
||||
#endregion
|
||||
|
||||
private WaveParam _waveParam;
|
||||
|
||||
#region Line数据
|
||||
|
||||
private float[] _waveformData;
|
||||
private float[] _noiseData;
|
||||
private float[] _spikeData;
|
||||
|
||||
private float[] _displayData; // 最后展示数据
|
||||
|
||||
#endregion
|
||||
|
||||
[SerializeField] private WaveformConfig config;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
InitComponentRefs();
|
||||
InitValues();
|
||||
}
|
||||
|
||||
private void InitValues()
|
||||
{
|
||||
_waveformData = new float[config.samples];
|
||||
_noiseData = new float[config.samples];
|
||||
_spikeData = new float[config.samples];
|
||||
_displayData = new float[config.samples];
|
||||
|
||||
_waveformLine.positionCount = config.samples;
|
||||
_waveformLine.enabled = false;
|
||||
}
|
||||
|
||||
private void InitComponentRefs()
|
||||
{
|
||||
_waveformLine = GetComponent<LineRenderer>();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_isWavePlaying)
|
||||
{
|
||||
// 绘制线条
|
||||
DrawWaveform();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawWaveform()
|
||||
{
|
||||
// 计算当前线条的点数据
|
||||
CalcLinePointsData();
|
||||
// 计算结束,绘制线条
|
||||
DrawLine();
|
||||
}
|
||||
|
||||
#region 波形数据计算
|
||||
|
||||
private void CalcLinePointsData()
|
||||
{
|
||||
// 分组计算:
|
||||
var waveformData = _waveParam.waveformType == WaveformType.Default
|
||||
? GenerateNoiseData()
|
||||
: GenerateWaveformData(); // 波形数据
|
||||
// 计算错误数据
|
||||
var spikeData = GenerateSpikeData(); // 错误尖峰数据
|
||||
// 合成最终结果
|
||||
for (int i = 0; i < config.samples; i++)
|
||||
{
|
||||
// 正常线条加错误尖峰
|
||||
var lineValue = waveformData[i] + spikeData[i];
|
||||
_displayData[i] = Mathf.Clamp(lineValue, -_screenHeight / 1.8f, _screenHeight / 1.8f);
|
||||
}
|
||||
}
|
||||
|
||||
private float[] GenerateNoiseData()
|
||||
{
|
||||
for (var i = 0; i < config.samples; i++)
|
||||
{
|
||||
_noiseData[i] = Random.Range(-config.AdjustedNoiseAmplitude, config.AdjustedNoiseAmplitude);
|
||||
}
|
||||
|
||||
return _noiseData;
|
||||
}
|
||||
|
||||
private float[] GenerateWaveformData()
|
||||
{
|
||||
// 计算波形
|
||||
for (int i = 0; i < config.samples; i++)
|
||||
{
|
||||
float basicAngle = TransIndexToAngle(i);
|
||||
// 正波形
|
||||
float wavePoint = CalcLineValueByAngle(basicAngle);
|
||||
// 如果有相位偏移
|
||||
if (_waveParam.PhaseShift > 0)
|
||||
{
|
||||
wavePoint -= CalcLineValueByAngle(basicAngle + _waveParam.PhaseShift);
|
||||
}
|
||||
|
||||
_waveformData[i] = wavePoint;
|
||||
}
|
||||
|
||||
return _waveformData;
|
||||
}
|
||||
|
||||
private float TransIndexToAngle(int idx)
|
||||
{
|
||||
return (float)idx / config.samples * Mathf.PI * 2f * config.spatialFrequency;
|
||||
}
|
||||
|
||||
private float CalcLineValueByAngle(float angle)
|
||||
{
|
||||
// 计算波形值
|
||||
var value = _waveParam.waveformType switch
|
||||
{
|
||||
WaveformType.Sine => Mathf.Sin(angle),
|
||||
WaveformType.Square => Mathf.Sign(Mathf.Sin(angle)),
|
||||
WaveformType.Triangle => Mathf.PingPong(angle / Mathf.PI, 1f) * 2f - 1f,
|
||||
WaveformType.Sawtooth => angle / (Mathf.PI * 2f) % 1f * 2f - 1f, // 统一周期为 2π
|
||||
WaveformType.Pulse => Mathf.PingPong(angle / (Mathf.PI * 2f), 1f) > 0.5f ? 1f : -1f, // 统一周期为 2π
|
||||
WaveformType.HotNoise => Mathf.PerlinNoise(angle / (Mathf.PI * 2f), Time.time * config.temporalFrequency) *
|
||||
2f - 1f, // 统一周期为 2π
|
||||
WaveformType.Composite => Mathf.Sin(angle) + Mathf.Sin(angle * 2f) * 0.5f, // 组合波形也统一周期
|
||||
_ => 0f
|
||||
};
|
||||
|
||||
return value * config.amplitude; // 最终返回角度对应波形
|
||||
}
|
||||
|
||||
private float[] GenerateSpikeData()
|
||||
{
|
||||
if (CheckErrorShow())
|
||||
{
|
||||
// 错误点可以显示
|
||||
for (var i = 0; i < config.samples; i++)
|
||||
{
|
||||
_spikeData[i] = CalcSpikeValueByIdx(i);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 错误点尚不可显示,全部置为0
|
||||
Array.Clear(_spikeData, 0, _spikeData.Length);
|
||||
}
|
||||
|
||||
return _spikeData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 错误点是否已经可以定位
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool CheckErrorShow()
|
||||
{
|
||||
// 没有错误当然就不显示尖峰点
|
||||
if (_waveParam.spikeIdx == -1) return false;
|
||||
// 尖峰点小于1/3时显示
|
||||
return _displayData[_waveParam.spikeIdx] < config.amplitude / 3f;
|
||||
}
|
||||
|
||||
private float CalcSpikeValueByIdx(int idx)
|
||||
{
|
||||
var distanceToSpike = Mathf.Abs(_waveParam.spikeIdx - idx);
|
||||
// 尖峰范围外直接数据为0
|
||||
if (distanceToSpike > config.spikeRange) return 0f;
|
||||
|
||||
// 尖峰范围内计算
|
||||
// 计算随机范围
|
||||
var attenuation = (1f - distanceToSpike / 20f) * 0.9f;
|
||||
var baseAmplitude = _displayData[idx] * config.AmplitudeScale * (_screenHeight / 2f);
|
||||
var randomRange = (Mathf.Abs(baseAmplitude) - config.amplitude / 3f) * attenuation;
|
||||
// 得出结果
|
||||
if (Mathf.Abs(baseAmplitude) < config.amplitude / 3)
|
||||
{
|
||||
return Random.Range(-randomRange, randomRange); // 对 Y 值进行随机扰动
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 根据计算出的点数绘制线条
|
||||
/// </summary>
|
||||
private void DrawLine()
|
||||
{
|
||||
for (int i = 0; i < config.samples; i++)
|
||||
{
|
||||
// 计算线上每个点的位置
|
||||
float x = (float)i / (config.samples - 1) * _screenWidth - _screenWidth / 2f; // x轴数据
|
||||
float y = _displayData[i] * config.AmplitudeScale * (_screenHeight / 2f);
|
||||
Vector3 position = new Vector3(x, y, 0);
|
||||
// 设置
|
||||
_waveformLine.SetPosition(i, position);
|
||||
}
|
||||
|
||||
// 设置颜色和宽度
|
||||
_waveformLine.startColor = config.LineColor;
|
||||
_waveformLine.endColor = config.LineColor;
|
||||
_waveformLine.startWidth = config.LineWidth;
|
||||
_waveformLine.endWidth = config.LineWidth;
|
||||
}
|
||||
|
||||
private int GeneSpikeIdx()
|
||||
{
|
||||
float[] waveData = GenerateWaveformData();
|
||||
int resIdx = GetRandomIndexFromTop20Percent(waveData);
|
||||
Array.Clear(waveData, 0, waveData.Length);
|
||||
|
||||
return resIdx;
|
||||
}
|
||||
|
||||
int GetRandomIndexFromTop20Percent(float[] array)
|
||||
{
|
||||
int n = array.Length;
|
||||
int topCount = (int)Math.Ceiling(n * 0.2);
|
||||
|
||||
var indexedArray = array
|
||||
.Select((value, index) => new { Value = Math.Abs(value), Index = index })
|
||||
.OrderByDescending(item => item.Value)
|
||||
.Take(topCount)
|
||||
.Select(item => item.Index)
|
||||
.ToArray();
|
||||
|
||||
int randomIndex = indexedArray[Random.Range(0, indexedArray.Length)];
|
||||
|
||||
return randomIndex;
|
||||
}
|
||||
|
||||
#region 对外暴露
|
||||
|
||||
/// <summary>
|
||||
/// 设置屏幕尺寸作为参数
|
||||
/// </summary>
|
||||
/// <param name="width">宽</param>
|
||||
/// <param name="height">高</param>
|
||||
public void SetLineSize(float width, float height)
|
||||
{
|
||||
_screenHeight = height;
|
||||
_screenWidth = width;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示波形
|
||||
/// </summary>
|
||||
public void PlayWave(WaveformType waveformType, float initialPhase, bool hasError)
|
||||
{
|
||||
// 配置波形数据
|
||||
config.waveformType = waveformType;
|
||||
_waveParam = new WaveParam
|
||||
{
|
||||
waveformType = waveformType,
|
||||
spikeIdx = -1,
|
||||
initPhaseShift = initialPhase,
|
||||
varPhaseShift = 0
|
||||
};
|
||||
_waveParam.spikeIdx = hasError ? GeneSpikeIdx() : -1;
|
||||
|
||||
// 进行播放
|
||||
_waveformLine.enabled = true;
|
||||
_isWavePlaying = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止显示波形
|
||||
/// </summary>
|
||||
public void StopWave()
|
||||
{
|
||||
// 清除数据
|
||||
config.waveformType = WaveformType.Default;
|
||||
_waveParam = new WaveParam
|
||||
{
|
||||
waveformType = WaveformType.Default,
|
||||
spikeIdx = -1,
|
||||
initPhaseShift = 0,
|
||||
varPhaseShift = 0
|
||||
};
|
||||
|
||||
// 停止
|
||||
_isWavePlaying = false;
|
||||
_waveformLine.enabled = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取尖峰点坐标
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public float GetSpikeX()
|
||||
{
|
||||
if (CheckErrorShow())
|
||||
{
|
||||
return _waveParam.spikeIdx / (float)config.samples * _screenWidth - _screenWidth / 2;
|
||||
}
|
||||
|
||||
return float.NegativeInfinity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调整相位
|
||||
/// </summary>
|
||||
/// <param name="phaseShift">相位</param>
|
||||
public void AdjustPhaseShift(float phaseShift)
|
||||
{
|
||||
_waveParam.varPhaseShift = phaseShift * 2 * Mathf.PI;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class WaveformConfig
|
||||
{
|
||||
public int samples = 1024;
|
||||
public float amplitude = 0.6f;
|
||||
public int spikeRange = 20;
|
||||
|
||||
public float spatialFrequency = 1f;
|
||||
public float temporalFrequency = 0.1f;
|
||||
public float targetWaveformNoiseAmplitude = 0.1f;
|
||||
public float noiseAmplitude = 0.5f;
|
||||
|
||||
public Color noiseColor = Color.gray;
|
||||
public Color clearColor = Color.green;
|
||||
public Color hotColor = Color.red;
|
||||
|
||||
public WaveformType waveformType;
|
||||
|
||||
public Color LineColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (waveformType == WaveformType.Default)
|
||||
{
|
||||
return noiseColor;
|
||||
}
|
||||
|
||||
return waveformType == WaveformType.HotNoise ? hotColor : clearColor;
|
||||
}
|
||||
}
|
||||
public float LineWidth => waveformType != WaveformType.Default ? 0.05f : 0.02f;
|
||||
public float AmplitudeScale => waveformType != WaveformType.Default ? 1f : 0.2f;
|
||||
|
||||
public float AdjustedNoiseAmplitude =>
|
||||
waveformType != WaveformType.Default ? targetWaveformNoiseAmplitude : noiseAmplitude;
|
||||
}
|
||||
|
||||
public struct WaveParam
|
||||
{
|
||||
public WaveformType waveformType;
|
||||
public float initPhaseShift;
|
||||
public int spikeIdx;
|
||||
public float varPhaseShift;
|
||||
|
||||
public float PhaseShift => initPhaseShift + varPhaseShift;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd4bf97f1c7d4f559fe7ce23a64bff87
|
||||
timeCreated: 1731495781
|
||||
Reference in New Issue
Block a user