Ver.0.3.0.33
This commit is contained in:
@@ -2,6 +2,9 @@ using System;
|
||||
using AibisDream.Framework;
|
||||
using Newtonsoft.Json;
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using DG.Tweening;
|
||||
using AibisDream.Kit;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
@@ -10,11 +13,12 @@ namespace AibisDream.FixSystem
|
||||
#region 组件引用
|
||||
|
||||
private const string SocketObjName = "Socket";
|
||||
private const string ModulePicName = "Module Pic";
|
||||
|
||||
public Transform socketPos;
|
||||
private Transform _socketPos;
|
||||
private Transform _socketLeft;
|
||||
private Transform _socketRight;
|
||||
private Tween _socketTween;
|
||||
|
||||
// public Transform _indicatorPos;
|
||||
private BodyModuleSystem _bodyModuleSystem;
|
||||
private Material _highlightMaterial;
|
||||
private Material _alarmMaterial;
|
||||
@@ -22,11 +26,24 @@ namespace AibisDream.FixSystem
|
||||
|
||||
private SpriteRenderer _targetSpriteRenderer; // 目标的 SpriteRenderer
|
||||
|
||||
private SpriteRenderer _cutSpriteRenderer;
|
||||
private SpriteRenderer _cutTextRenderer; // 添加cutText的SpriteRenderer引用
|
||||
|
||||
private CutLine _cutLine;
|
||||
|
||||
[Header("CutText闪烁效果")] private float cutTextFlickerThreshold = 0.4f; // 开始闪烁的阈值
|
||||
private float cutTextFlickerInterval = 0.1f; // 闪烁间隔
|
||||
private float cutTextFlickerTimer = 0f; // 闪烁计时器
|
||||
private bool isCutTextFlickering = false; // 是否正在闪烁
|
||||
private Coroutine flickerCoroutine; // 闪烁协程引用
|
||||
|
||||
#endregion
|
||||
|
||||
// 模块基本数据
|
||||
[SerializeField] public BodyModuleData data;
|
||||
private bool _available = true;
|
||||
public bool available = true;
|
||||
private bool _cutting;
|
||||
private bool _isOpen;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
@@ -36,11 +53,23 @@ namespace AibisDream.FixSystem
|
||||
|
||||
private void InitReference()
|
||||
{
|
||||
socketPos = transform.Find(SocketObjName);
|
||||
_socketPos = transform.Find(SocketObjName);
|
||||
_socketLeft = _socketPos.Find("Left");
|
||||
_socketRight = _socketPos.Find("Right");
|
||||
|
||||
_bodyModuleSystem = transform.parent.parent.GetComponent<BodyModuleSystem>();
|
||||
|
||||
_targetSpriteRenderer = transform.Find("Module Pic").GetComponent<SpriteRenderer>();
|
||||
_originalMaterial = _targetSpriteRenderer.GetComponent<SpriteRenderer>().material;
|
||||
|
||||
_cutSpriteRenderer = transform.Find("Cut Pic")?.GetComponent<SpriteRenderer>();
|
||||
if (_cutSpriteRenderer != null)
|
||||
{
|
||||
_cutTextRenderer =
|
||||
_cutSpriteRenderer.transform.Find("Cut Text")
|
||||
?.GetComponent<SpriteRenderer>(); // 初始化cutText的SpriteRenderer
|
||||
}
|
||||
|
||||
_highlightMaterial = Resources.Load<Material>("Materials/HighlightMaterial");
|
||||
_alarmMaterial = Resources.Load<Material>("Materials/AlarmMaterial");
|
||||
}
|
||||
@@ -49,23 +78,16 @@ namespace AibisDream.FixSystem
|
||||
|
||||
public bool IsAvailable()
|
||||
{
|
||||
return _available;
|
||||
return available;
|
||||
}
|
||||
|
||||
public void SetSocketAvailable(bool isAvailable)
|
||||
{
|
||||
_available = isAvailable;
|
||||
available = isAvailable;
|
||||
}
|
||||
|
||||
public void PlugIn()
|
||||
{
|
||||
// // 插入就在示波器显示图片
|
||||
// if (!_bodyModuleSystem.inHotErr)
|
||||
// {
|
||||
// StartCoroutine(FixSystemCenter.SystemDic.Get<IOscilloscopeSystem>()
|
||||
// ?.ShowScanImage(data.ScreenPic, data.checkPoints));
|
||||
// }
|
||||
|
||||
if (DialogController.Instance)
|
||||
// 然后触发Yarn节点
|
||||
DialogController.Instance.StartDialogNode(_bodyModuleSystem.inHotErr ? "系统发热" : data.PlugInNodeName);
|
||||
@@ -73,58 +95,39 @@ namespace AibisDream.FixSystem
|
||||
|
||||
public void PlugOut()
|
||||
{
|
||||
FixSystemCenter.SystemDic.Get<IOscilloscopeSystem>()?.HideScanImage();
|
||||
|
||||
_bodyModuleSystem.oscilloscopeSystem.MessageBoxSetNull();
|
||||
StartCoroutine(_bodyModuleSystem.oscilloscopeSystem.DisableDeepInButton());
|
||||
|
||||
// TODO 触发Yarn节点(似乎目前没有拔出对话?)
|
||||
}
|
||||
|
||||
public void DeepIn()
|
||||
{
|
||||
if(DialogController.Instance)
|
||||
DialogController.Instance.StartDialogNode(data.DeepInNodeName);
|
||||
}
|
||||
|
||||
public Vector3 GetSocketPos()
|
||||
{
|
||||
return socketPos.position;
|
||||
return _socketPos.position;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 模块数据保存与卸载
|
||||
|
||||
public void Load(BodyModuleData newData)
|
||||
public void OpenPlugSlot()
|
||||
{
|
||||
var modulePic = transform.Find(ModulePicName).GetComponent<SpriteRenderer>();
|
||||
var newSocketPos = transform.Find(SocketObjName);
|
||||
|
||||
gameObject.name = newData.moduleName;
|
||||
// 初始化位置与外形
|
||||
transform.localPosition = newData.ModulePos;
|
||||
modulePic.sprite = newData.ModulePic;
|
||||
newSocketPos.localPosition = newData.SocketPos;
|
||||
// 数据同步
|
||||
data = newData;
|
||||
|
||||
InitReference();
|
||||
if (!_socketLeft) return;
|
||||
|
||||
if (_isOpen) return;
|
||||
_isOpen = true;
|
||||
// 打开插槽
|
||||
if (_socketTween.IsActive() && _socketTween.IsPlaying()) _socketTween.Kill(true);
|
||||
var sequence = DOTween.Sequence();
|
||||
sequence.Join(_socketLeft.DOScale(new Vector3(0, 1, 1), 0.2f).SetEase(Ease.InSine));
|
||||
sequence.Join(_socketRight.DOScale(new Vector3(0, 1, 1), 0.2f).SetEase(Ease.InSine));
|
||||
_socketTween = sequence;
|
||||
}
|
||||
|
||||
public BodyModuleData Save()
|
||||
public void ClosePlugSlot()
|
||||
{
|
||||
var modulePic = transform.Find(ModulePicName).GetComponent<SpriteRenderer>();
|
||||
var newSocketPos = transform.Find(SocketObjName);
|
||||
|
||||
return new BodyModuleData
|
||||
{
|
||||
moduleName = data.moduleName,
|
||||
ModulePic = modulePic.sprite,
|
||||
ModulePos = transform.localPosition,
|
||||
SocketPos = newSocketPos.localPosition,
|
||||
cantStopRunning = data.cantStopRunning,
|
||||
};
|
||||
if (!_socketLeft) return;
|
||||
if (!_isOpen) return;
|
||||
|
||||
_isOpen = false;
|
||||
// 关闭插槽
|
||||
if (_socketTween.IsActive() && _socketTween.IsPlaying()) _socketTween.Kill(true);
|
||||
var sequence = DOTween.Sequence();
|
||||
sequence.Join(_socketLeft.DOScale(new Vector3(1, 1, 1), 0.2f).SetEase(Ease.OutSine));
|
||||
sequence.Join(_socketRight.DOScale(new Vector3(1, 1, 1), 0.2f).SetEase(Ease.OutSine));
|
||||
_socketTween = sequence;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -149,69 +152,170 @@ namespace AibisDream.FixSystem
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 切割相关
|
||||
|
||||
public void OnCuttingDown()
|
||||
{
|
||||
_bodyModuleSystem.RemoveModule(this);
|
||||
|
||||
DialogController.Instance?.StartDialogNode($"{data.moduleName}CutDown");
|
||||
EnumEventSystem.Global.Send(EventEnum.CutEnd);
|
||||
Destroy(gameObject);
|
||||
DialogController.Instance?.StartDialogNode($"{data.moduleName}CutDown");
|
||||
EnumEventSystem.Global.Send(EventEnum.CutEnd);
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
public void SetCutTextVisible(bool visible)
|
||||
{
|
||||
if (_cutTextRenderer != null)
|
||||
{
|
||||
_cutTextRenderer.enabled = visible;
|
||||
}
|
||||
}
|
||||
|
||||
public SpriteRenderer GetCutSpriteRenderer()
|
||||
{
|
||||
return _cutSpriteRenderer;
|
||||
}
|
||||
public SpriteRenderer GetCutTextSpriteRenderer()
|
||||
{
|
||||
return _cutTextRenderer;
|
||||
}
|
||||
|
||||
public void StartCutTextFlicker()
|
||||
{
|
||||
if (!isCutTextFlickering)
|
||||
{
|
||||
isCutTextFlickering = true;
|
||||
if (flickerCoroutine != null)
|
||||
{
|
||||
StopCoroutine(flickerCoroutine);
|
||||
}
|
||||
|
||||
flickerCoroutine = StartCoroutine(CutTextFlickerCoroutine());
|
||||
}
|
||||
}
|
||||
|
||||
public void StopCutTextFlicker()
|
||||
{
|
||||
isCutTextFlickering = false;
|
||||
if (flickerCoroutine != null)
|
||||
{
|
||||
StopCoroutine(flickerCoroutine);
|
||||
flickerCoroutine = null;
|
||||
}
|
||||
|
||||
// 确保最终状态是可见的
|
||||
SetCutTextVisible(true);
|
||||
}
|
||||
|
||||
private IEnumerator CutTextFlickerCoroutine()
|
||||
{
|
||||
while (isCutTextFlickering)
|
||||
{
|
||||
// 随机决定是否显示
|
||||
bool shouldShow = UnityEngine.Random.value > 0.5f;
|
||||
SetCutTextVisible(shouldShow);
|
||||
|
||||
// 播放相应的音效
|
||||
if (shouldShow)
|
||||
{
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/light_flicker_on");
|
||||
}
|
||||
else
|
||||
{
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/light_flicker_off");
|
||||
}
|
||||
|
||||
// 随机等待时间,模拟接触不良效果
|
||||
float waitTime = UnityEngine.Random.Range(cutTextFlickerInterval * 0.5f, cutTextFlickerInterval * 1.5f);
|
||||
yield return new WaitForSeconds(waitTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateCutProgress(float progress)
|
||||
{
|
||||
// 检查是否达到闪烁阈值
|
||||
if (!isCutTextFlickering && progress >= cutTextFlickerThreshold)
|
||||
{
|
||||
StartCutTextFlicker();
|
||||
}
|
||||
}
|
||||
|
||||
// public void EnableCut()
|
||||
// {
|
||||
// // 打开或获取CutLine
|
||||
// ShowCutLine();
|
||||
// _cutLine.OnCuttingDown += OnCuttingDown;
|
||||
// _cutting = true;
|
||||
|
||||
// EnumEventSystem.Global.Send(EventEnum.CutStart);
|
||||
// }
|
||||
|
||||
// private void OnCut()
|
||||
// {
|
||||
// Vector2 mousePos = CommonUtil.GetMouseWorldPos(Input.mousePosition);
|
||||
// _cutLine.DetectLineClick(mousePos, clickRadius);
|
||||
// }
|
||||
|
||||
// private void OnCuttingDown()
|
||||
// {
|
||||
// _cutLine.OnCuttingDown -= OnCuttingDown;
|
||||
// _cutting = false;
|
||||
// // 模块脱落
|
||||
// _cutLine.HideLine();
|
||||
// _bodyModuleSystem.RemoveModule(this);
|
||||
// ActionKit.Sequence()
|
||||
// .Delay(0.8f)
|
||||
// .DOTween(() => transform.DOMoveY(ConstRef.TweenEndY,
|
||||
// (transform.position.y - ConstRef.TweenEndY) / (ConstRef.FallSpeed * 0.6f))
|
||||
// .SetEase(Ease.InQuad))
|
||||
// .Callback(() => DialogController.Instance?.StartDialogNode($"{data.moduleName}CutDown"))
|
||||
// .Callback(() => EnumEventSystem.Global.Send(EventEnum.CutEnd))
|
||||
// .Start(this, () => Destroy(gameObject));
|
||||
// }
|
||||
|
||||
// private void ShowCutLine()
|
||||
// {
|
||||
// // 切割线不存在,就新建一个
|
||||
// if (!_cutLine)
|
||||
// {
|
||||
// var prefab = ResourceKit.LoadAssetSync<GameObject>(ConstRef.CutLinePrefabName);
|
||||
// var cutLineObj = Instantiate(prefab, transform);
|
||||
// _cutLine = cutLineObj.GetComponent<CutLine>();
|
||||
// }
|
||||
|
||||
// // 对切割线进行初始化
|
||||
// var shapePoints = new List<Vector2>();
|
||||
// _targetSpriteRenderer.sprite.GetPhysicsShape(0, shapePoints);
|
||||
// if (shapePoints.Count <= 0)
|
||||
// {
|
||||
// Debug.LogWarning($"{name} has no shape");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // 本地坐标转世界坐标
|
||||
// var shapePointsV3 = shapePoints
|
||||
// .Select(item => _targetSpriteRenderer.transform.TransformPoint(item))
|
||||
// .ToList();
|
||||
|
||||
// _cutLine.Init(shapePointsV3, clickRadius);
|
||||
// }
|
||||
|
||||
#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;
|
||||
|
||||
// 功能信息
|
||||
[Header("功能参数")] public bool cantStopRunning;
|
||||
|
||||
public string showPicPath;
|
||||
public CheckPoint[] checkPoints;
|
||||
|
||||
// 只读
|
||||
[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 Sprite ScreenPic =>
|
||||
!string.IsNullOrEmpty(showPicPath) ? ResourceKit.LoadSpriteFromPsd(showPicPath) : null;
|
||||
|
||||
[JsonIgnore]
|
||||
public Vector3 ModulePos
|
||||
{
|
||||
get => modulePos.ToVector3();
|
||||
set => modulePos = new SerializableVector3(value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public Vector3 SocketPos
|
||||
{
|
||||
get => socketPos.ToVector3();
|
||||
set => socketPos = new SerializableVector3(value);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct CheckPoint
|
||||
{
|
||||
public string id;
|
||||
private SerializableVector3 _serializablePos3;
|
||||
|
||||
[JsonIgnore]
|
||||
public Vector3 PointPos
|
||||
{
|
||||
get => _serializablePos3.ToVector3();
|
||||
set => _serializablePos3 = new SerializableVector3(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,31 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using Cinemachine;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public class BodyModuleSystem : SystemHasData
|
||||
public class BodyModuleSystem : MonoBehaviour
|
||||
{
|
||||
public static string DataPath = Application.streamingAssetsPath + "/LevelData/BodyModuleSystem/";
|
||||
|
||||
#region 内部索引
|
||||
|
||||
public List<BodyModule> BodyModules { get; private set; }
|
||||
|
||||
private GameObject _bodyModuleGroup;
|
||||
private GameObject _headModuleGroup;
|
||||
|
||||
public CableSystem CableSystem { get; private set; }
|
||||
public GameObject bodyModulePrefab;
|
||||
[HideInInspector] public Transform bodyModuleGroup;
|
||||
[HideInInspector] public OscilloscopeSystem oscilloscopeSystem;
|
||||
[HideInInspector] public HeatMapController heatMapController;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 数据保存相关
|
||||
|
||||
public override string CurDataKey { get; set; }
|
||||
private string[] _keyOptions;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 系统状态
|
||||
|
||||
private readonly ModuleSystemData _data = new();
|
||||
public bool inHotErr;
|
||||
private int _heatValue;
|
||||
public bool isAvailable;
|
||||
@@ -49,6 +44,11 @@ namespace AibisDream.FixSystem
|
||||
|
||||
[SerializeField] public float plugSnappingRange = 1f;
|
||||
|
||||
private Vector3 _headModuleOriginalPos;
|
||||
private Coroutine _breathingCoroutine;
|
||||
private Coroutine _painShakeCoroutine;
|
||||
private Coroutine _continuousPainCoroutine;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
InitComponentRefs();
|
||||
@@ -56,24 +56,30 @@ namespace AibisDream.FixSystem
|
||||
|
||||
private void InitComponentRefs()
|
||||
{
|
||||
// 注册数据
|
||||
StorageSystem.Instance.RegisterData(_data);
|
||||
|
||||
// 处理内部索引
|
||||
BodyModules = transform.GetComponentsInChildren<BodyModule>().ToList();
|
||||
_bodyModuleGroup = transform.Find("Body Module Group")?.gameObject;
|
||||
_headModuleGroup = transform.Find("Head Module Group")?.gameObject;
|
||||
CableSystem = transform.Find("Cable System").GetComponent<CableSystem>();
|
||||
bodyModuleGroup = transform.Find("Body Module Group");
|
||||
oscilloscopeSystem = transform.Find("Oscilloscope System").GetComponent<OscilloscopeSystem>();
|
||||
if (transform.Find("HeatMapController"))
|
||||
{
|
||||
heatMapController = transform.Find("HeatMapController").GetComponent<HeatMapController>();
|
||||
}
|
||||
|
||||
FixSystemCenter.SystemDic.Register(this);
|
||||
|
||||
|
||||
// 注册Camera
|
||||
var virtualCam = transform.GetComponentInChildren<ICinemachineCamera>();
|
||||
var virtualCam = transform.Find("Body Module Camera").GetComponent<ICinemachineCamera>();
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.BodyModule, virtualCam);
|
||||
var virtualCam2 = transform.Find("Body Module Camera Center").GetComponent<ICinemachineCamera>();
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.BodyModuleCenter, virtualCam2);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
StorageSystem.Instance.UnregisterData<ModuleSystemData>();
|
||||
CameraKit.Instance.UnRegisterCamera(CameraEnum.BodyModule);
|
||||
}
|
||||
|
||||
@@ -88,6 +94,12 @@ namespace AibisDream.FixSystem
|
||||
var closestDistance = Mathf.Infinity;
|
||||
BodyModule closestModule = null;
|
||||
|
||||
if (BodyModules is not { Count: > 0 })
|
||||
{
|
||||
targetModule = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 寻找最接近的BodyModule
|
||||
foreach (var bodyModule in BodyModules)
|
||||
{
|
||||
@@ -95,7 +107,7 @@ namespace AibisDream.FixSystem
|
||||
{
|
||||
continue; // 跳过不可用的模块
|
||||
}
|
||||
|
||||
|
||||
float tempDistance = Vector3.Distance(sourcePos, bodyModule.GetSocketPos());
|
||||
|
||||
if (tempDistance < closestDistance)
|
||||
@@ -105,19 +117,40 @@ namespace AibisDream.FixSystem
|
||||
}
|
||||
}
|
||||
|
||||
bool res;
|
||||
// 判断距离是否在目标范围内,在的话就返回,不在的话返回个空的
|
||||
if (closestDistance < plugSnappingRange && closestModule)
|
||||
{
|
||||
targetModule = closestModule;
|
||||
return true;
|
||||
res = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetModule = null;
|
||||
return false;
|
||||
res = false;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public void OpenModuleSlots()
|
||||
{
|
||||
foreach (var module in BodyModules)
|
||||
{
|
||||
module.OpenPlugSlot();
|
||||
}
|
||||
}
|
||||
|
||||
public void CloseModuleSlots([CanBeNull] BodyModule curModule)
|
||||
{
|
||||
foreach (var module in BodyModules)
|
||||
{
|
||||
if (module != curModule)
|
||||
{
|
||||
module.ClosePlugSlot();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 寻找指定名称的BodyModule
|
||||
/// </summary>
|
||||
@@ -128,23 +161,6 @@ namespace AibisDream.FixSystem
|
||||
return BodyModules?.FirstOrDefault(module => module.data.moduleName == modulename);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置死循环状态
|
||||
/// </summary>
|
||||
public void SetModuleState_CantStopRunning(string moduleName, bool state)
|
||||
{
|
||||
BodyModule result = FindBodyModuleByName(moduleName);
|
||||
if (result != null)
|
||||
{
|
||||
Debug.Log("Found module: " + moduleName);
|
||||
result.data.cantStopRunning = state;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("Module not found.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置发热
|
||||
/// </summary>
|
||||
@@ -186,112 +202,6 @@ namespace AibisDream.FixSystem
|
||||
CableSystem.ResetPlug();
|
||||
}
|
||||
|
||||
public void HandleDeepIn()
|
||||
{
|
||||
if (CableSystem.curBodyModule)
|
||||
{
|
||||
CableSystem.curBodyModule.DeepIn();
|
||||
}
|
||||
}
|
||||
|
||||
#region 数据加载与保存
|
||||
|
||||
public override string GetConfigPath()
|
||||
{
|
||||
return DataPath;
|
||||
}
|
||||
|
||||
public override void LoadLevel()
|
||||
{
|
||||
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
|
||||
Destroy(oldModule);
|
||||
#endif
|
||||
}
|
||||
|
||||
// 再加载新的Module
|
||||
var newModules = new BodyModule[systemData.moduleDataArray.Length];
|
||||
for (int i = 0; i < systemData.moduleDataArray.Length; i++)
|
||||
{
|
||||
BodyModule newModule = Instantiate(bodyModulePrefab, bodyModuleGroup).GetComponent<BodyModule>();
|
||||
newModule.Load(systemData.moduleDataArray[i]);
|
||||
newModules[i] = newModule;
|
||||
}
|
||||
|
||||
BodyModules = newModules.ToList();
|
||||
}
|
||||
|
||||
public override void SaveLevel()
|
||||
{
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// 激活交互系统
|
||||
/// </summary>
|
||||
@@ -307,11 +217,183 @@ namespace AibisDream.FixSystem
|
||||
{
|
||||
isAvailable = false;
|
||||
}
|
||||
|
||||
public void SwitchModuleGroup(ModuleState moduleState)
|
||||
{
|
||||
// 先切换Group
|
||||
switch (moduleState)
|
||||
{
|
||||
case ModuleState.Body:
|
||||
_bodyModuleGroup?.SetActive(true);
|
||||
_headModuleGroup?.SetActive(false);
|
||||
break;
|
||||
case ModuleState.Head:
|
||||
_bodyModuleGroup?.SetActive(false);
|
||||
_headModuleGroup?.SetActive(true);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(moduleState), moduleState, null);
|
||||
}
|
||||
|
||||
// 重新获取ModuleGroup
|
||||
var root = moduleState == ModuleState.Body ? _bodyModuleGroup : _headModuleGroup;
|
||||
BodyModules = root?.GetComponentsInChildren<BodyModule>().ToList();
|
||||
// 更改data
|
||||
_data.moduleState = moduleState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从模块中清除
|
||||
/// </summary>
|
||||
/// <param name="module">将要被移除的模块</param>
|
||||
public void RemoveModule(BodyModule module)
|
||||
{
|
||||
BodyModules.Remove(module);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (_headModuleGroup != null)
|
||||
{
|
||||
_headModuleOriginalPos = _headModuleGroup.transform.localPosition;
|
||||
StartBreathing();
|
||||
}
|
||||
}
|
||||
|
||||
public void StartBreathing()
|
||||
{
|
||||
if (_breathingCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_breathingCoroutine);
|
||||
}
|
||||
_breathingCoroutine = StartCoroutine(BreathingAnimation());
|
||||
}
|
||||
|
||||
public void StopBreathing()
|
||||
{
|
||||
if (_breathingCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_breathingCoroutine);
|
||||
_breathingCoroutine = null;
|
||||
}
|
||||
if (_headModuleGroup != null)
|
||||
{
|
||||
_headModuleGroup.transform.localPosition = _headModuleOriginalPos;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator BreathingAnimation()
|
||||
{
|
||||
float time = 0;
|
||||
while (true)
|
||||
{
|
||||
time += Time.deltaTime;
|
||||
float yOffset = Mathf.Sin(time * 2f) * 0.02f; // 调整呼吸幅度和速度
|
||||
_headModuleGroup.transform.localPosition = _headModuleOriginalPos + new Vector3(0, yOffset, 0);
|
||||
yield return null;
|
||||
}
|
||||
// ReSharper disable once IteratorNeverReturns
|
||||
}
|
||||
|
||||
public void TriggerPainShake()
|
||||
{
|
||||
if (_painShakeCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_painShakeCoroutine);
|
||||
}
|
||||
_painShakeCoroutine = StartCoroutine(PainShakeAnimation());
|
||||
}
|
||||
|
||||
private IEnumerator PainShakeAnimation()
|
||||
{
|
||||
StopBreathing();
|
||||
float duration = 0.5f;
|
||||
float elapsed = 0f;
|
||||
Vector3 originalPos = _headModuleGroup.transform.localPosition;
|
||||
|
||||
while (elapsed < duration)
|
||||
{
|
||||
elapsed += Time.deltaTime;
|
||||
float intensity = 1f - (elapsed / duration);
|
||||
_headModuleGroup.transform.localPosition = originalPos + new Vector3(
|
||||
UnityEngine.Random.Range(-0.1f, 0.1f) * intensity,
|
||||
UnityEngine.Random.Range(-0.1f, 0.1f) * intensity,
|
||||
UnityEngine.Random.Range(-0.1f, 0.1f) * intensity
|
||||
);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
_headModuleGroup.transform.localPosition = originalPos;
|
||||
StartBreathing();
|
||||
}
|
||||
|
||||
public void StartContinuousPain()
|
||||
{
|
||||
if (_continuousPainCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_continuousPainCoroutine);
|
||||
}
|
||||
_continuousPainCoroutine = StartCoroutine(ContinuousPainAnimation());
|
||||
}
|
||||
|
||||
public void StopContinuousPain()
|
||||
{
|
||||
if (_continuousPainCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_continuousPainCoroutine);
|
||||
_continuousPainCoroutine = null;
|
||||
}
|
||||
if (_headModuleGroup != null)
|
||||
{
|
||||
_headModuleGroup.transform.localPosition = _headModuleOriginalPos;
|
||||
}
|
||||
StartBreathing();
|
||||
}
|
||||
|
||||
private IEnumerator ContinuousPainAnimation()
|
||||
{
|
||||
StopBreathing();
|
||||
while (true)
|
||||
{
|
||||
// 抖动阶段
|
||||
float shakeDuration = 1f;
|
||||
float shakeElapsed = 0f;
|
||||
Vector3 originalPos = _headModuleGroup.transform.localPosition;
|
||||
|
||||
while (shakeElapsed < shakeDuration)
|
||||
{
|
||||
shakeElapsed += Time.deltaTime;
|
||||
_headModuleGroup.transform.localPosition = originalPos + new Vector3(
|
||||
UnityEngine.Random.Range(-0.05f, 0.05f),
|
||||
UnityEngine.Random.Range(-0.05f, 0.05f),
|
||||
UnityEngine.Random.Range(-0.05f, 0.05f)
|
||||
);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// 暂停阶段
|
||||
_headModuleGroup.transform.localPosition = originalPos;
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
}
|
||||
// ReSharper disable once IteratorNeverReturns
|
||||
}
|
||||
}
|
||||
|
||||
public struct BodyModuleSystemData
|
||||
[LoadIndex(2)]
|
||||
public class ModuleSystemData : IData
|
||||
{
|
||||
public bool hasHotErr;
|
||||
public BodyModuleData[] moduleDataArray;
|
||||
public ModuleState moduleState;
|
||||
|
||||
public IEnumerator Load()
|
||||
{
|
||||
FixSystemCenter.SystemDic.Get<BodyModuleSystem>().SwitchModuleGroup(moduleState);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
public enum ModuleState
|
||||
{
|
||||
Body,
|
||||
Head
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,28 @@
|
||||
using System.Collections;
|
||||
using AibisDream.FixSystem;
|
||||
using AibisDream.Framework;
|
||||
using Yarn.Unity;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public static class BodyModuleYarnCommand
|
||||
{
|
||||
private static BodyModuleSystem BodyModuleSystem => FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
||||
private static OscilloscopeSystem OscilloscopeSystem => FixSystemCenter.SystemDic.Get<OscilloscopeSystem>();
|
||||
|
||||
|
||||
private static PunchTapeSystem PunchTapeSystem => FixSystemCenter.SystemDic.Get<PunchTapeSystem>();
|
||||
|
||||
[YarnCommand("SetModuleState_CantStopRunning")]
|
||||
public static void SetModuleState_CantStopRunning(string moduleName, bool state)
|
||||
[YarnCommand("enable_cut")]
|
||||
public static void EnableCut(string moduleName)
|
||||
{
|
||||
BodyModuleSystem.SetModuleState_CantStopRunning(moduleName, state);
|
||||
Reset_Plug();
|
||||
var cutModule = BodyModuleSystem.FindBodyModuleByName(moduleName);
|
||||
//cutModule.EnableCut();
|
||||
}
|
||||
|
||||
[YarnCommand("Reset_Plug")]
|
||||
public static void Reset_Plug()
|
||||
{
|
||||
BodyModuleSystem.CableSystem.ResetPlug();
|
||||
}
|
||||
|
||||
[YarnCommand("ShowHeatMap")]
|
||||
@@ -42,12 +49,12 @@ namespace AibisDream
|
||||
var _UFModule = BodyModuleSystem.FindBodyModuleByName("UF").transform.GetComponent<UFModule>();
|
||||
_UFModule.RemoveUFModule();
|
||||
BodyModuleSystem.FindBodyModuleByName("UF").SetSocketAvailable(false);
|
||||
|
||||
}
|
||||
|
||||
[YarnCommand("Temp_RemoveUFCover")]
|
||||
public static void Temp_RemoveUFCover()
|
||||
{ BodyModuleSystem.CableSystem.ResetPlug();
|
||||
{
|
||||
BodyModuleSystem.CableSystem.ResetPlug();
|
||||
var _UFModule = BodyModuleSystem.FindBodyModuleByName("UF").transform.GetComponent<UFModule>();
|
||||
_UFModule.RemoveUFCover();
|
||||
}
|
||||
@@ -70,20 +77,24 @@ namespace AibisDream
|
||||
[YarnCommand("Temp_InstallChip")]
|
||||
public static void Temp_InstallChip()
|
||||
{
|
||||
var _chipModule = BodyModuleSystem.FindBodyModuleByName("Color").transform.GetComponent<ChipModule>();
|
||||
var _chipModule = BodyModuleSystem.FindBodyModuleByName("Color").transform.GetComponent<ChipModule>();
|
||||
_chipModule.InstallChipModule();
|
||||
}
|
||||
|
||||
[YarnCommand("Temp_RemoveChip")]
|
||||
public static void Temp_RemoveChip()
|
||||
{
|
||||
var _chipModule = BodyModuleSystem.FindBodyModuleByName("Color").transform.GetComponent<ChipModule>();
|
||||
_chipModule.RemoveChipModule();
|
||||
var _chipModule = BodyModuleSystem.FindBodyModuleByName("Color")?.transform.GetComponent<ChipModule>();
|
||||
if (_chipModule != null)
|
||||
{
|
||||
_chipModule.RemoveChipModule();
|
||||
}
|
||||
}
|
||||
|
||||
[YarnCommand("Temp_InstallUFColor")]
|
||||
public static void Temp_InstallUFChip()
|
||||
{
|
||||
var _chipModule = BodyModuleSystem.FindBodyModuleByName("UF").transform.GetComponent<UFModule>();
|
||||
var _chipModule = BodyModuleSystem.FindBodyModuleByName("UF").transform.GetComponent<UFModule>();
|
||||
_chipModule.InstallUFColor();
|
||||
}
|
||||
|
||||
@@ -105,7 +116,14 @@ namespace AibisDream
|
||||
public static void ModuleHightLight(string moduleName, bool enable)
|
||||
{
|
||||
var targetModule = BodyModuleSystem.FindBodyModuleByName(moduleName);
|
||||
targetModule.Highlight(enable);
|
||||
if (targetModule != null)
|
||||
{
|
||||
targetModule.Highlight(enable);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"Module {moduleName} not found!");
|
||||
}
|
||||
}
|
||||
|
||||
[YarnCommand("ModuleAlarm")]
|
||||
@@ -115,70 +133,52 @@ namespace AibisDream
|
||||
targetModule.Alarm(enable);
|
||||
}
|
||||
|
||||
[YarnCommand("MessageBoxSetError")]
|
||||
public static void MessageBoxSetError()
|
||||
{
|
||||
OscilloscopeSystem.MessageBoxSetError();
|
||||
}
|
||||
|
||||
[YarnCommand("MessageBoxSetWarning")]
|
||||
public static void MessageBoxSetWarning()
|
||||
{
|
||||
OscilloscopeSystem.MessageBoxSetWarning();
|
||||
}
|
||||
|
||||
[YarnCommand("MessageBoxSetNormal")]
|
||||
public static void MessageBoxSetNormal()
|
||||
{
|
||||
OscilloscopeSystem.MessageBoxSetNormal();
|
||||
}
|
||||
|
||||
[YarnCommand("MessageBoxSetChecking")]
|
||||
public static void MessageBoxSetChecking()
|
||||
{
|
||||
OscilloscopeSystem.MessageBoxSetChecking();
|
||||
}
|
||||
|
||||
[YarnCommand("MessageBoxSetNull")]
|
||||
public static void MessageBoxSetNull()
|
||||
{
|
||||
OscilloscopeSystem.MessageBoxSetNull();
|
||||
}
|
||||
|
||||
[YarnCommand("MessageBoxSetText")]
|
||||
public static void MessageBoxSetText(string text)
|
||||
{
|
||||
OscilloscopeSystem.MessageBoxSetText(text);
|
||||
}
|
||||
|
||||
[YarnCommand("EnableDeepInButton")]
|
||||
public static void EnableDeepInButton()
|
||||
{
|
||||
OscilloscopeSystem.StartCoroutine(OscilloscopeSystem.EnableDeepInButton());
|
||||
}
|
||||
|
||||
[YarnCommand("DisableDeepInButton")]
|
||||
public static void DisableDeepInButton()
|
||||
{
|
||||
OscilloscopeSystem.StartCoroutine(OscilloscopeSystem.DisableDeepInButton());
|
||||
}
|
||||
|
||||
[YarnCommand("PrintPunchTapes")]
|
||||
public static IEnumerator PrintPunchTapes(int count)
|
||||
{
|
||||
yield return PunchTapeSystem.StartCoroutine(PunchTapeSystem.PrintPunchTapes(count));
|
||||
}
|
||||
|
||||
[YarnCommand("HidePunchTapeGroup")]
|
||||
public static IEnumerator HidePunchTapeGroup()
|
||||
[YarnCommand("DropCable")]
|
||||
public static void DropCable()
|
||||
{
|
||||
yield return PunchTapeSystem.StartCoroutine(PunchTapeSystem.Temp_HidePunchTapeGroup());
|
||||
BodyModuleSystem.CableSystem.DropDownCable();
|
||||
}
|
||||
|
||||
[YarnCommand("ShowPunchTapeGroup")]
|
||||
public static void ShowPunchTapeGroup()
|
||||
[YarnCommand("RetractCable")]
|
||||
public static void RetractCable()
|
||||
{
|
||||
PunchTapeSystem.Temp_showPunchTapeGroup();
|
||||
BodyModuleSystem.CableSystem.PullUpCable();
|
||||
}
|
||||
|
||||
[YarnCommand("StartHeadBreathing")]
|
||||
public static void StartHeadBreathing()
|
||||
{
|
||||
BodyModuleSystem.StartBreathing();
|
||||
}
|
||||
|
||||
[YarnCommand("StopHeadBreathing")]
|
||||
public static void StopHeadBreathing()
|
||||
{
|
||||
BodyModuleSystem.StopBreathing();
|
||||
}
|
||||
|
||||
[YarnCommand("TriggerHeadPainShake")]
|
||||
public static void TriggerHeadPainShake()
|
||||
{
|
||||
BodyModuleSystem.TriggerPainShake();
|
||||
}
|
||||
|
||||
[YarnCommand("StartHeadContinuousPain")]
|
||||
public static void StartHeadContinuousPain()
|
||||
{
|
||||
BodyModuleSystem.StartContinuousPain();
|
||||
}
|
||||
|
||||
[YarnCommand("StopHeadContinuousPain")]
|
||||
public static void StopHeadContinuousPain()
|
||||
{
|
||||
BodyModuleSystem.StopContinuousPain();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ namespace AibisDream.FixSystem
|
||||
private Transform _end;
|
||||
|
||||
private LineRenderer _lineRenderer;
|
||||
private LineRenderer _stiffLineRenderer; // 用于渲染刚性部分
|
||||
private Vector3[] _points;
|
||||
|
||||
private CableSystem _cableSystem;
|
||||
@@ -19,6 +20,17 @@ namespace AibisDream.FixSystem
|
||||
[SerializeField]
|
||||
private CableConfig config = CableConfig.GeneDefaultConfig();
|
||||
|
||||
public void ShowCable()
|
||||
{
|
||||
_lineRenderer.enabled = true;
|
||||
_stiffLineRenderer.enabled = true;
|
||||
}
|
||||
public void HideCable()
|
||||
{
|
||||
_lineRenderer.enabled = false;
|
||||
_stiffLineRenderer.enabled = false;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
InitComponentRefs();
|
||||
@@ -30,7 +42,22 @@ namespace AibisDream.FixSystem
|
||||
_cableSystem = transform.parent.GetComponent<CableSystem>();
|
||||
|
||||
_lineRenderer = GetComponent<LineRenderer>();
|
||||
_lineRenderer.positionCount = config.resolution;
|
||||
_lineRenderer.positionCount = config.resolution - config.stiffCount;
|
||||
|
||||
// 创建第二个LineRenderer用于刚性部分
|
||||
GameObject stiffObj = new GameObject("StiffLineRenderer");
|
||||
stiffObj.transform.SetParent(transform);
|
||||
_stiffLineRenderer = stiffObj.AddComponent<LineRenderer>();
|
||||
_stiffLineRenderer.positionCount = config.stiffCount;
|
||||
|
||||
// 复制第一个LineRenderer的属性到第二个
|
||||
_stiffLineRenderer.startWidth = _lineRenderer.startWidth;
|
||||
_stiffLineRenderer.endWidth = _lineRenderer.startWidth;
|
||||
_stiffLineRenderer.material = _lineRenderer.material;
|
||||
_stiffLineRenderer.startColor = _lineRenderer.startColor;
|
||||
_stiffLineRenderer.endColor = _lineRenderer.startColor;
|
||||
_stiffLineRenderer.sortingLayerName = _cableSystem.CableReelRef.GetComponent<SpriteRenderer>().sortingLayerName;
|
||||
_stiffLineRenderer.sortingOrder = _cableSystem.CableReelRef.GetComponent<SpriteRenderer>().sortingOrder - 1;
|
||||
|
||||
// 连线起止点
|
||||
_start = _cableSystem.CableRootPos;
|
||||
@@ -55,31 +82,54 @@ namespace AibisDream.FixSystem
|
||||
void DrawLine()
|
||||
{
|
||||
var points = UpdateRope();
|
||||
for (int i = 0; i < config.resolution; i++)
|
||||
|
||||
// 渲染刚性部分
|
||||
for (int i = 0; i < config.stiffCount; i++)
|
||||
{
|
||||
_lineRenderer.SetPosition(i, points[i] + Vector3.forward * -.3f);
|
||||
_stiffLineRenderer.SetPosition(i, points[i] + Vector3.forward * -.3f);
|
||||
}
|
||||
|
||||
// 渲染非刚性部分
|
||||
for (int i = 0; i < config.resolution - config.stiffCount; i++)
|
||||
{
|
||||
_lineRenderer.SetPosition(i, points[i + config.stiffCount] + 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++)
|
||||
|
||||
// 计算出线方向(从转盘出线口到起始点)
|
||||
Vector3 dir = (_start.position - _cableSystem.CableReelRef.transform.position).normalized;
|
||||
|
||||
// 设置刚性部分
|
||||
for (int i = 0; i < config.stiffCount; i++)
|
||||
{
|
||||
for (int i = 1; i < _points.Length - 1; i++)
|
||||
float t_stiff = (float)i / (config.stiffCount - 1);
|
||||
_points[i] = _start.position + dir * (config.stiffLength * t_stiff);
|
||||
}
|
||||
|
||||
// 设置末端点
|
||||
_points[^1] = _end.position;
|
||||
|
||||
// 只对非刚性部分进行物理模拟
|
||||
for (int ik = 0; ik < config.k; ik++)
|
||||
{
|
||||
// 确保非刚性部分的第一个点与刚性部分的最后一个点重合
|
||||
_points[config.stiffCount] = _points[config.stiffCount - 1];
|
||||
|
||||
for (int i = config.stiffCount + 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++)
|
||||
for (int i = config.stiffCount + 1; i < _points.Length - 1; i++)
|
||||
{
|
||||
_points[i] += Vector3.down * (9.8f * Time.deltaTime) / config.k;
|
||||
}
|
||||
@@ -98,7 +148,7 @@ namespace AibisDream.FixSystem
|
||||
}
|
||||
public Vector3 GetDirection()
|
||||
{
|
||||
return _points[^1]-_points[_points.Length-1];
|
||||
return _points[^1] - _points[^2];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +161,8 @@ namespace AibisDream.FixSystem
|
||||
public float forceMin;
|
||||
public float forceMax;
|
||||
public int k;
|
||||
public int stiffCount; // 刚性部分点数
|
||||
public float stiffLength; // 刚性部分长度
|
||||
|
||||
public static CableConfig GeneDefaultConfig()
|
||||
{
|
||||
@@ -121,7 +173,9 @@ namespace AibisDream.FixSystem
|
||||
dstMax = 1.0f,
|
||||
forceMin = 0.1f,
|
||||
forceMax = 150f,
|
||||
k = 10
|
||||
k = 10,
|
||||
stiffCount = 3,
|
||||
stiffLength = 0.1f
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public class CableReel : MonoBehaviour
|
||||
{
|
||||
[Header("转盘设置")]
|
||||
[SerializeField] private float torqueStrength = 200f; // 扭矩强度
|
||||
[SerializeField] private float angularDamping = 5f; // 角速度阻尼
|
||||
|
||||
private CableSystem cableSystem;
|
||||
private float angularVelocity; // 角速度
|
||||
private float currentAngle; // 当前角度
|
||||
public Vector3 outletPos { get; private set; } // 出线口位置
|
||||
private SpriteRenderer spriteRenderer;
|
||||
private float reelRadius; // 转盘半径
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// 获取CableSystem引用
|
||||
cableSystem = transform.parent.GetComponent<CableSystem>();
|
||||
currentAngle = transform.eulerAngles.z;
|
||||
|
||||
// 获取SpriteRenderer并计算半径
|
||||
spriteRenderer = GetComponent<SpriteRenderer>();
|
||||
if (spriteRenderer != null)
|
||||
{
|
||||
// 使用sprite的bounds来计算半径
|
||||
reelRadius = spriteRenderer.bounds.extents.y; // 使用y方向的半高作为半径
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("CableReel没有SpriteRenderer组件!");
|
||||
reelRadius = 0.5f; // 默认值
|
||||
}
|
||||
|
||||
// 初始化出线口位置
|
||||
UpdateOutletPosition();
|
||||
}
|
||||
|
||||
private void UpdateOutletPosition()
|
||||
{
|
||||
// 更新出线口位置(圆形本地坐标 (0, -radius) 转成世界坐标)
|
||||
outletPos = transform.TransformPoint(new Vector3(0, -reelRadius, 0));
|
||||
}
|
||||
|
||||
public void ResetRotation()
|
||||
{
|
||||
// 重置角度和角速度
|
||||
currentAngle = 0;
|
||||
angularVelocity = 0;
|
||||
transform.rotation = Quaternion.identity;
|
||||
UpdateOutletPosition();
|
||||
}
|
||||
|
||||
public void UpdateRotation()
|
||||
{
|
||||
// 如果PhysicCable激活,不进行旋转
|
||||
if (cableSystem == null || cableSystem.PlugRef == null ||
|
||||
cableSystem.PhysicCableRef.GetComponent<LineRenderer>().enabled) return;
|
||||
|
||||
Vector2 center = transform.position;
|
||||
|
||||
// 使用当前出线口位置
|
||||
Vector2 outlet = outletPos;
|
||||
|
||||
// 力向量(线缆拉力方向)
|
||||
Vector2 force = (Vector2)cableSystem.PlugRef.transform.position - outlet;
|
||||
|
||||
// 杆臂向量(力作用点相对于圆心的位置)
|
||||
Vector2 r = outlet - center;
|
||||
|
||||
// 计算 2D 扭矩:r × F
|
||||
float torque = (r.x * force.y - r.y * force.x);
|
||||
|
||||
// 应用扭矩 + 阻尼
|
||||
angularVelocity += torque * torqueStrength * Time.deltaTime;
|
||||
angularVelocity *= Mathf.Exp(-angularDamping * Time.deltaTime); // 简易阻尼
|
||||
|
||||
// 应用旋转
|
||||
currentAngle += angularVelocity * Time.deltaTime;
|
||||
transform.rotation = Quaternion.Euler(0, 0, currentAngle);
|
||||
|
||||
// 更新出线口位置
|
||||
UpdateOutletPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 81da72a4f38d7f94784a80abb68a0ddd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,12 +1,13 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.PlayerLoop;
|
||||
using Yarn.Unity;
|
||||
using DG.Tweening;
|
||||
using AibisDream.Kit;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public class CableSystem : MonoBehaviour
|
||||
{
|
||||
private const string CABLE_RETRACTED_KEY = "$global.cableRetracted";
|
||||
|
||||
#region 组件索引
|
||||
|
||||
public Plug PlugRef { get; private set; }
|
||||
@@ -14,24 +15,28 @@ namespace AibisDream.FixSystem
|
||||
|
||||
public PhysicCable PhysicCableRef { get; private set; }
|
||||
public Transform CableRootPos { get; private set; }
|
||||
|
||||
public Transform ControlPoint { get; private set; }
|
||||
public ISocket InitSocket { get; private set; }
|
||||
public CableReel CableReelRef { get; private set; }
|
||||
|
||||
public BodyModuleSystem BodyModuleSystem { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 系统状态
|
||||
|
||||
|
||||
public bool isInDialog;
|
||||
public bool isActive = true;
|
||||
|
||||
public bool isCableRetracted = true; // 线缆是否收起
|
||||
|
||||
[SerializeField] private Transform retractedPos; // 收起位置
|
||||
[SerializeField] private float dropDownDuration = 0.5f; // 放下动画时长
|
||||
[SerializeField] private float swingDuration = 0.3f; // 摆动动画时长
|
||||
[SerializeField] private float swingAngle = 15f; // 摆动角度
|
||||
|
||||
[HideInInspector] public ISocket curSocket;
|
||||
[HideInInspector] public BodyModule curBodyModule;
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
InitComponentRefs();
|
||||
@@ -40,6 +45,7 @@ namespace AibisDream.FixSystem
|
||||
private void Start()
|
||||
{
|
||||
InitSystem();
|
||||
LoadCableState();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
@@ -54,12 +60,13 @@ namespace AibisDream.FixSystem
|
||||
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>();
|
||||
CableReelRef = transform.Find("CableReel").GetComponent<CableReel>();
|
||||
CableRootPos = CableReelRef.transform.Find("Cable Root Pos").transform;
|
||||
PhysicCableRef = transform.Find("PhysicCable").GetComponent<PhysicCable>();
|
||||
|
||||
BodyModuleSystem = transform.parent.GetComponent<BodyModuleSystem>();
|
||||
@@ -67,22 +74,60 @@ namespace AibisDream.FixSystem
|
||||
|
||||
private void InitSystem()
|
||||
{
|
||||
|
||||
//PhysicCableRef.Init();
|
||||
PlugRef.InitPlug(InitSocket);
|
||||
if (isCableRetracted)
|
||||
{
|
||||
SetRetractCable();
|
||||
}
|
||||
else
|
||||
{
|
||||
PlugRef.ReturnToStartPosition();
|
||||
}
|
||||
}
|
||||
public void Update()
|
||||
{
|
||||
|
||||
private void LoadCableState()
|
||||
{
|
||||
if (StorageSystem.Instance.TryGetValue(CABLE_RETRACTED_KEY, out bool savedRetractedState))
|
||||
{
|
||||
if (isCableRetracted)
|
||||
{
|
||||
if (!savedRetractedState)
|
||||
{
|
||||
DropDownCable();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (savedRetractedState)
|
||||
{
|
||||
PullUpCable();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveCableState()
|
||||
{
|
||||
Debug.Log("Save cableRetractedstate" + isCableRetracted);
|
||||
StorageSystem.Instance.SetValue(CABLE_RETRACTED_KEY, isCableRetracted);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// 获取线缆方向并更新转盘旋转
|
||||
if (CableRef != null && CableReelRef != null)
|
||||
{
|
||||
CableReelRef.UpdateRotation();
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetPlug()
|
||||
{
|
||||
// 已经在初始插孔里了,不用动
|
||||
if (curSocket == InitSocket) return;
|
||||
// 不在初始插孔里,就从当前插孔拔出来,插回初始插孔
|
||||
PlugRef.PullUpSocket();
|
||||
BodyModuleSystem.CloseModuleSlots(null);
|
||||
PlugRef.ReturnToStartPosition();
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/mods_close");
|
||||
AudioManager.Instance.PlaySfx("event:/FollowInput/plugdrop");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -107,7 +152,7 @@ namespace AibisDream.FixSystem
|
||||
{
|
||||
isInDialog = false;
|
||||
}
|
||||
|
||||
|
||||
public void Disable_plugInput()
|
||||
{
|
||||
isActive = false;
|
||||
@@ -128,5 +173,58 @@ namespace AibisDream.FixSystem
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void PullUpCable()
|
||||
{
|
||||
if (isCableRetracted) return;
|
||||
//禁用plug交互
|
||||
Disable_plugInput();
|
||||
ResetPlug();
|
||||
PlugRef.SetPhysicCableActive(false);
|
||||
PlugRef.GetComponent<SpriteRenderer>().sortingLayerName =
|
||||
CableReelRef.GetComponent<SpriteRenderer>().sortingLayerName;
|
||||
PlugRef.GetComponent<SpriteRenderer>().sortingOrder = 41;
|
||||
PlugRef.transform.DOMove(retractedPos.position, 0.1f).OnComplete(() =>
|
||||
{
|
||||
PhysicCableRef.GetComponent<LineRenderer>().enabled = false;
|
||||
isCableRetracted = true;
|
||||
SaveCableState();
|
||||
});
|
||||
}
|
||||
|
||||
public void DropDownCable()
|
||||
{
|
||||
if (!isCableRetracted) return;
|
||||
Debug.Log("DorpDown");
|
||||
PlugRef.PullUpSocket();
|
||||
CableReelRef.ResetRotation();
|
||||
PlugRef.transform.DOMove(PlugRef.GetStartPos(), 0.1f).OnComplete(() =>
|
||||
{
|
||||
PlugRef.SwitchToPhysicCableState();
|
||||
isCableRetracted = false;
|
||||
SaveCableState();
|
||||
// 添加一个短暂的延迟来确保状态正确设置
|
||||
DOVirtual.DelayedCall(0.1f, () => {
|
||||
Enable_plugInput();
|
||||
});
|
||||
});
|
||||
// 只有在非收起状态时才切换到物理线缆状态
|
||||
}
|
||||
|
||||
public void SetRetractCable()
|
||||
{
|
||||
// 禁用物理线缆和普通线缆
|
||||
PhysicCableRef.GetComponent<LineRenderer>().enabled = false;
|
||||
CableRef.HideCable();
|
||||
|
||||
// 设置Plug位置和层级
|
||||
PlugRef.transform.position = retractedPos.position;
|
||||
PlugRef.transform.rotation = retractedPos.rotation;
|
||||
PlugRef.GetComponent<SpriteRenderer>().sortingLayerName =
|
||||
CableReelRef.GetComponent<SpriteRenderer>().sortingLayerName;
|
||||
PlugRef.GetComponent<SpriteRenderer>().sortingOrder = 41;
|
||||
isCableRetracted = true;
|
||||
//SaveCableState();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
using UnityEngine;
|
||||
using DG.Tweening;
|
||||
using System.Collections;
|
||||
|
||||
public class ClawMachineController : MonoBehaviour
|
||||
{
|
||||
[Header("组件引用")]
|
||||
[SerializeField] private Transform topEdge; // 上边沿
|
||||
[SerializeField] private Transform leftClaw; // 左爪
|
||||
[SerializeField] private Transform rightClaw; // 右爪
|
||||
|
||||
[Header("移动参数")]
|
||||
[SerializeField] private float moveSpeed = 5f;
|
||||
[SerializeField] private float scaleSpeed = 2f;
|
||||
[SerializeField] private float minScale = 0.8f; // 下降时的缩放比例
|
||||
|
||||
[Header("抓取参数")]
|
||||
[SerializeField] private float grabOffset = 0.1f; // 抓取时的位置偏移
|
||||
[SerializeField] private float clawExtendSpeed = 2f; // 爪子伸缩速度
|
||||
|
||||
[Header("测试")]
|
||||
public Transform target;
|
||||
|
||||
private Vector3 initialPosition;
|
||||
private Vector3 initialScale;
|
||||
private Transform currentTarget;
|
||||
private bool isGrabbing;
|
||||
private bool isMoving;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
initialPosition = transform.position;
|
||||
initialScale = transform.localScale;
|
||||
|
||||
}
|
||||
|
||||
private void Update() {
|
||||
if (Input.GetKeyDown(KeyCode.Space))
|
||||
{
|
||||
GrabTarget(target);
|
||||
}
|
||||
}
|
||||
|
||||
public void GrabTarget(Transform target)
|
||||
{
|
||||
if (isGrabbing || isMoving) return;
|
||||
|
||||
currentTarget = target;
|
||||
StartCoroutine(GrabSequence());
|
||||
}
|
||||
|
||||
private IEnumerator GrabSequence()
|
||||
{
|
||||
isGrabbing = true;
|
||||
isMoving = true;
|
||||
|
||||
// 1. 移动到目标上方
|
||||
Vector3 targetPos = currentTarget.position;
|
||||
targetPos.x = transform.position.x;
|
||||
yield return MoveToPosition(targetPos);
|
||||
|
||||
// 2. 调整位置和抓钩
|
||||
yield return AdjustPositionAndClaws();
|
||||
|
||||
// 3. 下降
|
||||
yield return Descend();
|
||||
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
// 4. 再次调整抓钩
|
||||
yield return AdjustClawsForGrab();
|
||||
|
||||
// 5. 抓取目标
|
||||
currentTarget.SetParent(transform);
|
||||
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
// 6. 上升
|
||||
yield return Ascend();
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
// 7. 返回初始位置
|
||||
yield return ReturnToInitialPosition();
|
||||
|
||||
isGrabbing = false;
|
||||
isMoving = false;
|
||||
}
|
||||
|
||||
private IEnumerator MoveToPosition(Vector3 targetPos)
|
||||
{
|
||||
if (currentTarget == null) yield break;
|
||||
|
||||
SpriteRenderer targetSprite = currentTarget.GetComponent<SpriteRenderer>();
|
||||
if (targetSprite == null) yield break;
|
||||
float targetLeft = targetSprite.bounds.min.x;
|
||||
float targetRight = targetSprite.bounds.max.x;
|
||||
|
||||
targetPos.x=(targetLeft+targetRight)/2;
|
||||
float distance = Vector3.Distance(transform.position, targetPos);
|
||||
float duration = distance / moveSpeed;
|
||||
|
||||
transform.DOMove(targetPos, duration).SetEase(Ease.Linear);
|
||||
yield return new WaitForSeconds(duration);
|
||||
}
|
||||
|
||||
private IEnumerator AdjustPositionAndClaws()
|
||||
{
|
||||
if (currentTarget == null) yield break;
|
||||
|
||||
SpriteRenderer targetSprite = currentTarget.GetComponent<SpriteRenderer>();
|
||||
if (targetSprite == null) yield break;
|
||||
|
||||
// 计算目标边界
|
||||
float targetTop = targetSprite.bounds.max.y;
|
||||
float targetLeft = targetSprite.bounds.min.x;
|
||||
|
||||
|
||||
float topOffset = targetTop - topEdge.position.y + 0.5f; // 确保上边沿高于目标
|
||||
float leftOffset = leftClaw.position.x -(targetLeft-0.5f); // 确保左爪超出目标左边界
|
||||
|
||||
// 调整位置
|
||||
Vector3 newPos = transform.position;
|
||||
newPos.y += topOffset;
|
||||
yield return MoveToPosition(newPos);
|
||||
|
||||
// 调整爪子位置
|
||||
float extendAmount=leftOffset;
|
||||
|
||||
yield return ExtendClaws(extendAmount);
|
||||
}
|
||||
|
||||
private IEnumerator Descend()
|
||||
{
|
||||
Vector3 targetScale = initialScale * minScale;
|
||||
float duration = (initialScale.x - targetScale.x) / scaleSpeed;
|
||||
|
||||
transform.DOScale(targetScale, duration).SetEase(Ease.Linear);
|
||||
yield return new WaitForSeconds(duration);
|
||||
}
|
||||
|
||||
private IEnumerator AdjustClawsForGrab()
|
||||
{
|
||||
if (currentTarget == null) yield break;
|
||||
|
||||
SpriteRenderer targetSprite = currentTarget.GetComponent<SpriteRenderer>();
|
||||
if (targetSprite == null) yield break;
|
||||
|
||||
// 计算目标边界
|
||||
float targetTop = targetSprite.bounds.max.y;
|
||||
float targetLeft = targetSprite.bounds.min.x;
|
||||
|
||||
|
||||
// float topOffset = targetTop - topEdge.position.y + grabOffset; // 确保上边沿高于目标
|
||||
float leftOffset = leftClaw.position.x -(targetLeft-grabOffset); // 确保左爪超出目标左边界
|
||||
|
||||
// 调整位置
|
||||
// Vector3 newPos = transform.position;
|
||||
// newPos.y += topOffset;
|
||||
// yield return MoveToPosition(newPos);
|
||||
|
||||
// 调整爪子位置
|
||||
float extendAmount=leftOffset;
|
||||
|
||||
yield return ExtendClaws(extendAmount);
|
||||
}
|
||||
|
||||
private IEnumerator ExtendClaws(float amount)
|
||||
{
|
||||
Vector3 leftTarget = leftClaw.localPosition;
|
||||
Vector3 rightTarget = rightClaw.localPosition;
|
||||
|
||||
leftTarget.x -= amount;
|
||||
rightTarget.x += amount;
|
||||
|
||||
float duration = Mathf.Abs(amount) / clawExtendSpeed;
|
||||
|
||||
leftClaw.DOLocalMove(leftTarget, duration).SetEase(Ease.Linear);
|
||||
rightClaw.DOLocalMove(rightTarget, duration).SetEase(Ease.Linear);
|
||||
|
||||
yield return new WaitForSeconds(duration);
|
||||
}
|
||||
|
||||
private IEnumerator Ascend()
|
||||
{
|
||||
float duration = (initialScale.x - transform.localScale.x) / scaleSpeed;
|
||||
|
||||
transform.DOScale(initialScale, duration).SetEase(Ease.Linear);
|
||||
yield return new WaitForSeconds(duration);
|
||||
}
|
||||
|
||||
private IEnumerator ReturnToInitialPosition()
|
||||
{
|
||||
// 先回到初始Y位置
|
||||
Vector3 targetPos = transform.position;
|
||||
targetPos.y = initialPosition.y;
|
||||
yield return MoveToPosition(targetPos);
|
||||
|
||||
// 再回到初始X位置
|
||||
targetPos.x = initialPosition.x;
|
||||
yield return MoveToPosition(targetPos);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3bf3dc1f81a3a614bb15b4464058b2db
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,311 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class CutLine : MonoBehaviour
|
||||
{
|
||||
#region Shader参数
|
||||
|
||||
private static readonly int RangeCount = Shader.PropertyToID("_RangeCount");
|
||||
|
||||
#endregion
|
||||
|
||||
private LineRenderer _lineRenderer;
|
||||
private Material _lineMaterial;
|
||||
|
||||
private Vector2[] _linePoints;
|
||||
private float[] _normalizedLinePoints;
|
||||
private float _totalLength;
|
||||
private Bounds _bounds;
|
||||
private List<Vector2> _recordedRanges = new();
|
||||
private static readonly int Ranges = Shader.PropertyToID("_Ranges");
|
||||
private static readonly int TotalLength = Shader.PropertyToID("_TotalLength");
|
||||
|
||||
public event Action OnCuttingDown;
|
||||
|
||||
#region 初始化
|
||||
|
||||
public void Init(List<Vector3> shapePoints, float clickRadius)
|
||||
{
|
||||
if (!_lineRenderer)
|
||||
{
|
||||
_lineRenderer = GetComponent<LineRenderer>();
|
||||
_lineMaterial = _lineRenderer.materials[0];
|
||||
}
|
||||
|
||||
_lineRenderer.gameObject.SetActive(true);
|
||||
|
||||
// 清除旧数据
|
||||
ClearOldData();
|
||||
// 生成归一化点
|
||||
NormalizePoints(shapePoints);
|
||||
// 按照新点位重绘线条
|
||||
RedrawLine(shapePoints);
|
||||
// 生成基本的包围盒
|
||||
GeneBoundingBox(clickRadius);
|
||||
|
||||
ClearRecordedRanges();
|
||||
}
|
||||
|
||||
private void ClearOldData()
|
||||
{
|
||||
_recordedRanges.Clear();
|
||||
_totalLength = 0;
|
||||
}
|
||||
|
||||
private void RedrawLine(List<Vector3> shapePoints)
|
||||
{
|
||||
// 画线
|
||||
var linePoints = shapePoints.ToArray();
|
||||
|
||||
_lineRenderer.positionCount = linePoints.Length;
|
||||
_lineRenderer.SetPositions(linePoints);
|
||||
// 设置Shader
|
||||
_lineMaterial.SetFloat(TotalLength, _totalLength);
|
||||
_lineMaterial.SetInt(RangeCount, 1);
|
||||
_lineMaterial.SetVectorArray(Ranges, new List<Vector4> { Vector4.zero });
|
||||
}
|
||||
|
||||
private void UpdateMaterial()
|
||||
{
|
||||
if (_recordedRanges.Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 将Range组装成Shader可以读取的格式
|
||||
var ranges = new List<Vector4>();
|
||||
|
||||
for (int i = 0; i < _recordedRanges.Count; i += 2)
|
||||
{
|
||||
var v1 = _recordedRanges[i];
|
||||
var v2 = i + 1 < _recordedRanges.Count ? _recordedRanges[i + 1] : Vector2.zero;
|
||||
var combined = new Vector4(v1.x, v1.y, v2.x, v2.y);
|
||||
ranges.Add(combined);
|
||||
}
|
||||
|
||||
// 更新线条
|
||||
_lineMaterial.SetInt(RangeCount, ranges.Count);
|
||||
_lineMaterial.SetVectorArray(Ranges, ranges);
|
||||
}
|
||||
|
||||
private void NormalizePoints(List<Vector3> shapePoints)
|
||||
{
|
||||
// 先生成首尾相连的点,默认Loop
|
||||
shapePoints.Add(shapePoints[0]);
|
||||
_linePoints = shapePoints
|
||||
.Select(item => new Vector2(item.x, item.y))
|
||||
.ToArray();
|
||||
|
||||
var segmentLengths = new List<float>();
|
||||
|
||||
// 计算总长度和各段长度
|
||||
for (var i = 1; i < _linePoints.Length; i++)
|
||||
{
|
||||
var segmentLength = Vector2.Distance(_linePoints[i - 1], _linePoints[i]);
|
||||
segmentLengths.Add(segmentLength);
|
||||
_totalLength += segmentLength;
|
||||
}
|
||||
|
||||
// 计算每个点的归一化位置
|
||||
var normalizedLinePoints = new float[segmentLengths.Count + 1];
|
||||
normalizedLinePoints[0] = 0f;
|
||||
var accumulatedLength = 0f;
|
||||
|
||||
for (var i = 0; i < segmentLengths.Count; i++)
|
||||
{
|
||||
accumulatedLength += segmentLengths[i];
|
||||
normalizedLinePoints[i + 1] = accumulatedLength / _totalLength;
|
||||
}
|
||||
|
||||
_normalizedLinePoints = normalizedLinePoints;
|
||||
}
|
||||
|
||||
private void GeneBoundingBox(float clickRadius)
|
||||
{
|
||||
_bounds = new Bounds();
|
||||
foreach (var point in _linePoints)
|
||||
{
|
||||
_bounds.Encapsulate(point);
|
||||
}
|
||||
|
||||
_bounds.Expand(clickRadius * 2);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 切割检测
|
||||
|
||||
public void DetectLineClick(Vector2 clickPosition, float clickRadius)
|
||||
{
|
||||
// 先进行包围盒检测,不在包围盒直接返回
|
||||
if (!_bounds.Contains(clickPosition))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<Vector2> newRanges = new List<Vector2>();
|
||||
|
||||
// 检查每条线段
|
||||
for (int i = 1; i < _linePoints.Length; i++)
|
||||
{
|
||||
Vector2 start = _linePoints[i - 1];
|
||||
Vector2 end = _linePoints[i];
|
||||
|
||||
// 计算点覆盖线段的范围
|
||||
if (!TryGetCoverRange(start, end, clickPosition, clickRadius, out var rawRange))
|
||||
{
|
||||
// 如果未覆盖直接计算下一段
|
||||
continue;
|
||||
}
|
||||
|
||||
// 将线段范围转成整体范围
|
||||
var normalizedStart = _normalizedLinePoints[i - 1];
|
||||
var normalizedEnd = _normalizedLinePoints[i];
|
||||
var normalizedDistance = normalizedEnd - normalizedStart;
|
||||
|
||||
var range = rawRange * normalizedDistance + new Vector2(normalizedStart, normalizedStart);
|
||||
newRanges.Add(range);
|
||||
}
|
||||
|
||||
// 合并新检测到的区间
|
||||
foreach (Vector2 range in newRanges)
|
||||
{
|
||||
_recordedRanges.Add(range);
|
||||
}
|
||||
|
||||
// 合并所有重叠区间
|
||||
MergeRanges();
|
||||
|
||||
// 判断当前切割有没有割到95%
|
||||
if (CalcTotalRange() >= 0.95)
|
||||
{
|
||||
Debug.Log("cutting All");
|
||||
OnCuttingDown?.Invoke();
|
||||
}
|
||||
|
||||
// 打印当前所有区间(调试用)
|
||||
Debug.Log($"rangesCount:{_recordedRanges.Count}");
|
||||
|
||||
string log = "Ranges: ";
|
||||
foreach (Vector2 range in _recordedRanges)
|
||||
{
|
||||
log += $"{range.x:F2}-{range.y:F2}; ";
|
||||
}
|
||||
|
||||
Debug.Log(log);
|
||||
|
||||
UpdateMaterial();
|
||||
}
|
||||
|
||||
private bool TryGetCoverRange(Vector2 lineStart, Vector2 lineEnd, Vector2 point, float radius,
|
||||
out Vector2 rawRange)
|
||||
{
|
||||
// 计算线段方向向量和长度
|
||||
Vector2 lineDir = lineEnd - lineStart;
|
||||
float lineLength = lineDir.magnitude;
|
||||
|
||||
// 如果线段长度为零(两个端点重合)
|
||||
if (Mathf.Approximately(lineLength, 0f))
|
||||
{
|
||||
// 检查点是否覆盖了这个点
|
||||
var distance = Vector2.Distance(lineStart, point);
|
||||
rawRange = distance <= radius ? new Vector2(0f, 1f) : default;
|
||||
return distance <= radius;
|
||||
}
|
||||
|
||||
// 计算点到线段所在直线距离
|
||||
Vector2 lineDirNormalized = lineDir / lineLength; // 归一化方向向量
|
||||
Vector2 pointToStart = point - lineStart; // 计算点到线段起点的向量
|
||||
float projection = Vector2.Dot(pointToStart, lineDirNormalized); // 计算投影长度(点在线段上的投影位置)
|
||||
float closestT = Mathf.Clamp01(projection / lineLength); // 计算实际最近点在线段上的比例位置
|
||||
Vector2 closestPoint = lineStart + closestT * lineDir; // 计算最近点坐标
|
||||
float distanceToLine = Vector2.Distance(point, closestPoint); // 计算点到最近点的距离
|
||||
|
||||
// 如果距离大于半径,完全没有覆盖
|
||||
if (distanceToLine > radius)
|
||||
{
|
||||
rawRange = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 计算覆盖范围
|
||||
float halfCoveredLength =
|
||||
Mathf.Sqrt(radius * radius - distanceToLine * distanceToLine); // 计算实际覆盖的线段长度(勾股定理)
|
||||
|
||||
float startCoverage = (projection - halfCoveredLength) / lineLength;
|
||||
float endCoverage = (projection + halfCoveredLength) / lineLength; // 计算覆盖的起点和终点比例
|
||||
|
||||
startCoverage = Mathf.Clamp01(startCoverage);
|
||||
endCoverage = Mathf.Clamp01(endCoverage); // 限制在0-1范围内
|
||||
|
||||
// 如果起点和终点相同,表示只覆盖了一个点
|
||||
if (Mathf.Approximately(startCoverage, endCoverage))
|
||||
{
|
||||
rawRange = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
rawRange = new Vector2(startCoverage, endCoverage);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void MergeRanges()
|
||||
{
|
||||
if (_recordedRanges.Count <= 1) return;
|
||||
|
||||
// 先按起始位置排序
|
||||
_recordedRanges.Sort((a, b) => a.x.CompareTo(b.x));
|
||||
|
||||
List<Vector2> mergedRanges = new List<Vector2>();
|
||||
Vector2 currentRange = _recordedRanges[0];
|
||||
|
||||
for (int i = 1; i < _recordedRanges.Count; i++)
|
||||
{
|
||||
Vector2 nextRange = _recordedRanges[i];
|
||||
|
||||
// 检查是否有重叠或相邻
|
||||
if (nextRange.x - currentRange.y <= 0.05f)
|
||||
{
|
||||
// 合并区间
|
||||
currentRange.y = Mathf.Max(currentRange.y, nextRange.y);
|
||||
}
|
||||
else
|
||||
{
|
||||
mergedRanges.Add(currentRange);
|
||||
currentRange = nextRange;
|
||||
}
|
||||
}
|
||||
|
||||
mergedRanges.Add(currentRange);
|
||||
_recordedRanges = mergedRanges;
|
||||
}
|
||||
|
||||
private float CalcTotalRange()
|
||||
{
|
||||
return _recordedRanges.Select(item => item.y - item.x).Sum();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void HideLine()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
// 获取当前所有记录的范围(0-1之间的值)
|
||||
public List<Vector2> GetRecordedRanges()
|
||||
{
|
||||
return new List<Vector2>(_recordedRanges);
|
||||
}
|
||||
|
||||
// 清除所有记录的范围
|
||||
public void ClearRecordedRanges()
|
||||
{
|
||||
_recordedRanges.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5842a5c9314449c8a972472b796da927
|
||||
timeCreated: 1745912339
|
||||
@@ -0,0 +1,118 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class CutLineManager : MonoBehaviour
|
||||
{
|
||||
[Header("切割线参数")]
|
||||
public float cutLineWidth = 0.1f;
|
||||
public float noiseAmount = 0.05f; // 毛刺偏移幅度
|
||||
public float segmentDuration = 4f; // 线段持续时间
|
||||
public float cutLineDelay = 3f; // 延迟几秒才显示轨迹点
|
||||
|
||||
public LineRenderer mainCutLineRenderer;
|
||||
private List<TimedPoint> timedCutPoints = new List<TimedPoint>();
|
||||
private bool isCutting = false;
|
||||
public bool IsCutting => isCutting; // Public property to access isCutting state
|
||||
|
||||
private class TimedPoint
|
||||
{
|
||||
public Vector3 position;
|
||||
public float timeAdded;
|
||||
public TimedPoint(Vector3 pos, float time)
|
||||
{
|
||||
position = pos;
|
||||
timeAdded = time;
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
InitializeMainCutLine();
|
||||
}
|
||||
|
||||
private void InitializeMainCutLine()
|
||||
{
|
||||
GameObject cutLineObj = new GameObject("MainCutLine");
|
||||
cutLineObj.transform.parent = transform;
|
||||
mainCutLineRenderer = cutLineObj.AddComponent<LineRenderer>();
|
||||
mainCutLineRenderer.positionCount = 0;
|
||||
mainCutLineRenderer.widthCurve = AnimationCurve.Constant(0, 1, cutLineWidth);
|
||||
mainCutLineRenderer.material = new Material(Shader.Find("Sprites/Default"));
|
||||
mainCutLineRenderer.numCapVertices = 0;
|
||||
mainCutLineRenderer.sortingOrder = 1;
|
||||
}
|
||||
|
||||
public void StartCutting()
|
||||
{
|
||||
isCutting = true;
|
||||
timedCutPoints.Clear();
|
||||
if (mainCutLineRenderer != null)
|
||||
{
|
||||
mainCutLineRenderer.positionCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddCutPoint(Vector3 point)
|
||||
{
|
||||
if (!isCutting) return;
|
||||
|
||||
if (timedCutPoints.Count == 0 || Vector3.Distance(timedCutPoints[timedCutPoints.Count - 1].position, point) > 0.05f)
|
||||
{
|
||||
timedCutPoints.Add(new TimedPoint(point, Time.time));
|
||||
|
||||
if (timedCutPoints.Count >= 2)
|
||||
{
|
||||
Vector3 prev = timedCutPoints[timedCutPoints.Count - 2].position;
|
||||
Vector3 curr = timedCutPoints[timedCutPoints.Count - 1].position;
|
||||
CreateSegment(prev, curr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void EndCutting()
|
||||
{
|
||||
isCutting = false;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
UpdateCutLineRenderer();
|
||||
}
|
||||
|
||||
private void UpdateCutLineRenderer()
|
||||
{
|
||||
if (mainCutLineRenderer == null) return;
|
||||
|
||||
float currentTime = Time.time;
|
||||
List<Vector3> visiblePoints = new List<Vector3>();
|
||||
|
||||
foreach (var timedPoint in timedCutPoints)
|
||||
{
|
||||
if (currentTime - timedPoint.timeAdded >= cutLineDelay)
|
||||
{
|
||||
visiblePoints.Add(timedPoint.position);
|
||||
}
|
||||
}
|
||||
|
||||
mainCutLineRenderer.positionCount = visiblePoints.Count;
|
||||
if (visiblePoints.Count > 0)
|
||||
mainCutLineRenderer.SetPositions(visiblePoints.ToArray());
|
||||
}
|
||||
|
||||
private void CreateSegment(Vector3 from, Vector3 to)
|
||||
{
|
||||
GameObject segObj = new GameObject("HeatLineSegment");
|
||||
segObj.transform.parent = transform;
|
||||
|
||||
LineRenderer segLine = segObj.AddComponent<LineRenderer>();
|
||||
segLine.positionCount = 2;
|
||||
segLine.SetPosition(0, from);
|
||||
segLine.SetPosition(1, to);
|
||||
segLine.sortingOrder = 4;
|
||||
segLine.material = new Material(Shader.Find("Sprites/Default"));
|
||||
segLine.widthCurve = AnimationCurve.Constant(0, 1, cutLineWidth);
|
||||
segLine.numCapVertices = 0;
|
||||
var segmentController = segObj.AddComponent<HeatLineSegmentController>();
|
||||
segmentController.duration = segmentDuration;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6fe8e1ba5a1fbed40b777c74953b13bd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,350 @@
|
||||
using UnityEngine;
|
||||
using AibisDream.FixSystem;
|
||||
using Cinemachine;
|
||||
using AibisDream.Framework;
|
||||
using DG.Tweening;
|
||||
using System.Collections;
|
||||
using AibisDream.Kit;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class CuttingManager : MonoBehaviour
|
||||
{
|
||||
public BubbleSlotGroup bubbleSlotGroup;
|
||||
[Header("切割组件")]
|
||||
public GameObject gearFollowerPrefab;
|
||||
public GameObject entrancePanelPrefab; // 入场面板预制体
|
||||
public float entranceDuration = 1.5f; // 入场动画时长
|
||||
public float exitDuration = 1.5f; // 退场动画时长
|
||||
public float panelOffsetY = 2f; // 面板在相机视野下方的偏移量
|
||||
|
||||
private GearFollower gearFollower;
|
||||
private bool isCuttingMode = false;
|
||||
private bool isEntranceComplete = false; // 入场是否完成
|
||||
private bool isGearActive = false; // 标记gear是否已激活
|
||||
private BodyModule _targetModule;
|
||||
private SpriteRenderer _targetSpriteRenderer;
|
||||
private GameObject entrancePanel; // 入场面板实例
|
||||
private Camera _mainCamera;
|
||||
private CinemachineVirtualCamera _currentVirtualCamera;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// 注册
|
||||
FixSystemCenter.SystemDic.Register(this);
|
||||
var virtualCam1 = transform.Find("Cut Emo Camera").GetComponent<ICinemachineCamera>();
|
||||
var virtualCam2 = transform.Find("Cut Memory Camera").GetComponent<ICinemachineCamera>();
|
||||
var virtualCam3 = transform.Find("Cut Logic Camera").GetComponent<ICinemachineCamera>();
|
||||
var virtualCam4 = transform.Find("Cut Emo Deep Camera").GetComponent<ICinemachineCamera>();
|
||||
var virtualCam5 = transform.Find("Cut Memory Deep Camera").GetComponent<ICinemachineCamera>();
|
||||
var virtualCam6 = transform.Find("Cut Logic Deep Camera").GetComponent<ICinemachineCamera>();
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.CutEmo, virtualCam1);
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.CutMemory, virtualCam2);
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.CutLogic, virtualCam3);
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.CutEmoDeep, virtualCam4);
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.CutMemoryDeep, virtualCam5);
|
||||
CameraKit.Instance.RegisterCamera(CameraEnum.CutLogicDeep, virtualCam6);
|
||||
|
||||
_mainCamera = Camera.main;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (isCuttingMode && !isGearActive && gearFollower != null)
|
||||
{
|
||||
// 检测点击
|
||||
if (Input.GetMouseButtonDown(0))
|
||||
{
|
||||
Ray ray = _mainCamera.ScreenPointToRay(Input.mousePosition);
|
||||
RaycastHit2D hit = Physics2D.Raycast(ray.origin, ray.direction);
|
||||
|
||||
if (hit.collider != null && hit.collider.gameObject.GetComponent<GearFollower>() != null)
|
||||
{
|
||||
gearFollower=hit.collider.gameObject.GetComponent<GearFollower>();
|
||||
ActivateGear();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (isCuttingMode && isGearActive && gearFollower != null)
|
||||
{
|
||||
gearFollower.GearUpdate();
|
||||
}
|
||||
}
|
||||
public void ClearCutLine()
|
||||
{
|
||||
if (gearFollower != null)
|
||||
{
|
||||
// 停止所有DOTween动画
|
||||
if (gearFollower.gameObject != null)
|
||||
{
|
||||
DOTween.Kill(gearFollower.gameObject);
|
||||
}
|
||||
|
||||
gearFollower.ClearCutLine();
|
||||
DestroyImmediate(gearFollower.gameObject);
|
||||
gearFollower = null;
|
||||
}
|
||||
|
||||
// 重置状态
|
||||
isCuttingMode = false;
|
||||
isEntranceComplete = false;
|
||||
isGearActive = false;
|
||||
}
|
||||
|
||||
private void ActivateGear()
|
||||
{
|
||||
AudioManager.Instance.PlaySfx("event:/FollowInput/drill");
|
||||
isGearActive = true;
|
||||
if (gearFollower != null)
|
||||
{
|
||||
// 解除父子关系
|
||||
gearFollower.transform.SetParent(null);
|
||||
gearFollower.Init(_targetSpriteRenderer);
|
||||
}
|
||||
|
||||
// 面板退场
|
||||
if (entrancePanel != null)
|
||||
{
|
||||
Vector3 startPosition = GetPanelSpawnPosition();
|
||||
entrancePanel.transform.DOMove(startPosition, exitDuration)
|
||||
.SetEase(Ease.InBack)
|
||||
.OnComplete(() => {
|
||||
Destroy(entrancePanel);
|
||||
entrancePanel = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator StartCutting(BodyModule targetModule)
|
||||
{
|
||||
// 检查参数
|
||||
if (targetModule == null)
|
||||
{
|
||||
Debug.LogError("targetModule 为空");
|
||||
yield break;
|
||||
}
|
||||
|
||||
// 保存目标模块
|
||||
_targetModule = targetModule;
|
||||
|
||||
// 获取目标精灵渲染器
|
||||
_targetSpriteRenderer = targetModule.GetCutSpriteRenderer();
|
||||
if (_targetSpriteRenderer == null)
|
||||
{
|
||||
Debug.LogError("targetSprite 为空");
|
||||
yield break;
|
||||
}
|
||||
|
||||
// 停止之前的切割
|
||||
StopCutting();
|
||||
|
||||
// 设置切割模式
|
||||
isCuttingMode = true;
|
||||
isEntranceComplete = false;
|
||||
|
||||
// 创建入场面板
|
||||
CreateEntrancePanel();
|
||||
|
||||
// 等待入场动画完成
|
||||
yield return new WaitUntil(() => isEntranceComplete);
|
||||
}
|
||||
|
||||
private Vector3 GetPanelSpawnPosition()
|
||||
{
|
||||
if (_mainCamera == null)
|
||||
{
|
||||
_mainCamera = Camera.main;
|
||||
}
|
||||
|
||||
|
||||
// 获取相机的位置和旋转
|
||||
Vector3 cameraPosition = _mainCamera.transform.position;
|
||||
Quaternion cameraRotation = _mainCamera.transform.rotation;
|
||||
|
||||
// 计算相机视野下方的位置
|
||||
Vector3 forward = cameraRotation * Vector3.forward;
|
||||
Vector3 right = cameraRotation * Vector3.right;
|
||||
Vector3 up = cameraRotation * Vector3.up;
|
||||
|
||||
// 计算相机视野的底部中心点
|
||||
float height = 2f * Mathf.Tan(_mainCamera.fieldOfView * 0.5f * Mathf.Deg2Rad) * _mainCamera.nearClipPlane;
|
||||
float width = height * _mainCamera.aspect;
|
||||
|
||||
// 计算面板的起始位置(在相机视野下方)
|
||||
Vector3 bottomCenter = cameraPosition + forward * _mainCamera.nearClipPlane - up * (height * 0.5f + panelOffsetY);
|
||||
|
||||
return bottomCenter;
|
||||
}
|
||||
|
||||
private void CreateEntrancePanel()
|
||||
{
|
||||
// 获取基于当前相机位置的生成位置
|
||||
Vector3 startPosition = GetPanelSpawnPosition();
|
||||
if (startPosition == Vector3.zero)
|
||||
{
|
||||
Debug.LogError("无法获取面板生成位置");
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建入场面板,保持原始旋转
|
||||
entrancePanel = Instantiate(entrancePanelPrefab, startPosition, Quaternion.Euler(0,0,-90));
|
||||
|
||||
// 获取GearFollower组件(作为子物体)
|
||||
gearFollower = entrancePanel.GetComponentInChildren<GearFollower>();
|
||||
if (gearFollower == null)
|
||||
{
|
||||
Debug.LogError("入场面板预制体中没有找到GearFollower组件");
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算相机视野高度
|
||||
float height = 2f * Mathf.Tan(_mainCamera.fieldOfView * 0.5f * Mathf.Deg2Rad) * _mainCamera.nearClipPlane;
|
||||
|
||||
// 计算目标位置(相机视野中心偏下)
|
||||
Vector3 targetPosition = startPosition + _mainCamera.transform.up * 3.5f;
|
||||
|
||||
// 执行入场动画
|
||||
entrancePanel.transform.DOMove(targetPosition, entranceDuration)
|
||||
.SetEase(Ease.OutBack)
|
||||
.OnComplete(() => {
|
||||
isEntranceComplete = true;
|
||||
});
|
||||
}
|
||||
|
||||
public void OnCutComplete()
|
||||
{
|
||||
isCuttingMode = false;
|
||||
isEntranceComplete = false;
|
||||
isGearActive = false;
|
||||
|
||||
if (gearFollower != null)
|
||||
{
|
||||
gearFollower.FinishCutting();
|
||||
}
|
||||
|
||||
// 如果面板还存在,删除它
|
||||
if (entrancePanel != null)
|
||||
{
|
||||
Destroy(entrancePanel);
|
||||
entrancePanel = null;
|
||||
}
|
||||
}
|
||||
public void UpdateModule()
|
||||
{
|
||||
if (_targetModule != null)
|
||||
{
|
||||
_targetModule.OnCuttingDown();
|
||||
}
|
||||
}
|
||||
public void SetCutTextVisible(bool visible)
|
||||
{
|
||||
if (_targetModule != null)
|
||||
{
|
||||
_targetModule.SetCutTextVisible(visible);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateCutProgress(float progress)
|
||||
{
|
||||
if (_targetModule != null)
|
||||
{
|
||||
_targetModule.UpdateCutProgress(progress);
|
||||
}
|
||||
}
|
||||
public void StartCutTextFlicker()
|
||||
{
|
||||
if (_targetModule != null)
|
||||
{
|
||||
_targetModule.StartCutTextFlicker();
|
||||
}
|
||||
}
|
||||
public void StopCutTextFlicker()
|
||||
{
|
||||
if (_targetModule != null)
|
||||
{
|
||||
_targetModule.StopCutTextFlicker();
|
||||
}
|
||||
}
|
||||
|
||||
public void StopCutting()
|
||||
{
|
||||
isCuttingMode = false;
|
||||
isEntranceComplete = false;
|
||||
isGearActive = false;
|
||||
|
||||
if (_targetModule != null)
|
||||
{
|
||||
_targetModule.StopCutTextFlicker();
|
||||
}
|
||||
|
||||
// 清理GearFollower
|
||||
if (gearFollower != null)
|
||||
{
|
||||
// 停止所有DOTween动画
|
||||
if (gearFollower.gameObject != null)
|
||||
{
|
||||
DOTween.Kill(gearFollower.gameObject);
|
||||
}
|
||||
|
||||
// 强制销毁GearFollower
|
||||
DestroyImmediate(gearFollower.gameObject);
|
||||
gearFollower = null;
|
||||
}
|
||||
|
||||
if (entrancePanel != null)
|
||||
{
|
||||
// 停止面板的DOTween动画
|
||||
DOTween.Kill(entrancePanel);
|
||||
|
||||
// 获取当前相机位置
|
||||
Vector3 startPosition = GetPanelSpawnPosition();
|
||||
|
||||
// 执行退场动画
|
||||
entrancePanel.transform.DOMove(startPosition, exitDuration)
|
||||
.SetEase(Ease.InBack)
|
||||
.OnComplete(() => {
|
||||
Destroy(entrancePanel);
|
||||
entrancePanel = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// 从系统字典中注销
|
||||
FixSystemCenter.SystemDic.Unregister<CuttingManager>();
|
||||
|
||||
// 清理相机注册
|
||||
CameraKit.Instance.UnRegisterCamera(CameraEnum.CutEmo);
|
||||
CameraKit.Instance.UnRegisterCamera(CameraEnum.CutMemory);
|
||||
CameraKit.Instance.UnRegisterCamera(CameraEnum.CutLogic);
|
||||
CameraKit.Instance.UnRegisterCamera(CameraEnum.CutEmoDeep);
|
||||
CameraKit.Instance.UnRegisterCamera(CameraEnum.CutMemoryDeep);
|
||||
CameraKit.Instance.UnRegisterCamera(CameraEnum.CutLogicDeep);
|
||||
|
||||
// 强制清理所有相关对象
|
||||
if (gearFollower != null)
|
||||
{
|
||||
// 如果gearFollower还在运行,强制停止
|
||||
if (gearFollower.gameObject != null)
|
||||
{
|
||||
DestroyImmediate(gearFollower.gameObject);
|
||||
}
|
||||
gearFollower = null;
|
||||
}
|
||||
|
||||
if (entrancePanel != null)
|
||||
{
|
||||
DestroyImmediate(entrancePanel);
|
||||
entrancePanel = null;
|
||||
}
|
||||
|
||||
// 重置状态
|
||||
isCuttingMode = false;
|
||||
isEntranceComplete = false;
|
||||
isGearActive = false;
|
||||
_targetModule = null;
|
||||
_targetSpriteRenderer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe673cb153d7fdf428d58d3261c48863
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,198 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class CuttingSystem : MonoBehaviour
|
||||
{
|
||||
[Header("切割系统参数")]
|
||||
public float snapDistance = 0.3f;
|
||||
public float detachDistance = 1.0f;
|
||||
public float directionSwitchCooldown = 0.2f;
|
||||
private CutLineManager cutLineManager;
|
||||
public float tractionSpeed = 2f;
|
||||
|
||||
[Header("测试")]
|
||||
public SpriteRenderer TargetspriteRenderer;
|
||||
private GearController gearController;
|
||||
|
||||
private Camera mainCamera;
|
||||
private int currentEdgeIndex = 0;
|
||||
private int tractionDirection = 1;
|
||||
private float directionSwitchTimer = 0f;
|
||||
|
||||
private enum State { Follow, Traction }
|
||||
private State currentState = State.Follow;
|
||||
|
||||
private List<Vector2> edgePoints;
|
||||
private List<Vector2> originalEdgePoints;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
mainCamera = Camera.main;
|
||||
gearController = GetComponentInChildren<GearController>();
|
||||
cutLineManager = GetComponentInChildren<CutLineManager>();
|
||||
|
||||
edgePoints = new List<Vector2>();
|
||||
originalEdgePoints = new List<Vector2>();
|
||||
|
||||
if (TargetspriteRenderer != null)
|
||||
{
|
||||
InitializeCutting(TargetspriteRenderer);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("TargetspriteRenderer is null!");
|
||||
}
|
||||
}
|
||||
private void Update()
|
||||
{
|
||||
UpdateCutting();
|
||||
}
|
||||
|
||||
public void InitializeCutting(SpriteRenderer targetSprite, float edgePointsScale = 1.2f)
|
||||
{
|
||||
if (targetSprite == null) return;
|
||||
LoadEdgePointsFromSprite(targetSprite, edgePointsScale);
|
||||
Debug.Log($"InitializeCutting: edgePoints count = {edgePoints.Count}");
|
||||
}
|
||||
|
||||
public void UpdateCutting()
|
||||
{
|
||||
Vector2 mouseWorldPos = mainCamera.ScreenToWorldPoint(Input.mousePosition);
|
||||
|
||||
if (Input.GetMouseButton(0))
|
||||
{
|
||||
gearController.SetRotation(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
gearController.SetRotation(false);
|
||||
}
|
||||
|
||||
switch (currentState)
|
||||
{
|
||||
case State.Follow:
|
||||
UpdateFollowState(mouseWorldPos);
|
||||
break;
|
||||
case State.Traction:
|
||||
UpdateTractionState(mouseWorldPos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadEdgePointsFromSprite(SpriteRenderer targetSprite, float edgePointsScale)
|
||||
{
|
||||
var shapePoints = CuttingUtils.GetSpritePhysicsShapePoints(targetSprite);
|
||||
if (shapePoints == null) return;
|
||||
|
||||
var (scaledPoints, originalPoints) = CuttingUtils.ProcessEdgePoints(shapePoints, targetSprite, edgePointsScale);
|
||||
if (scaledPoints == null) return;
|
||||
|
||||
edgePoints = scaledPoints;
|
||||
originalEdgePoints = originalPoints;
|
||||
Debug.Log($"LoadEdgePointsFromSprite: edgePoints count = {edgePoints.Count}");
|
||||
}
|
||||
|
||||
private void UpdateFollowState(Vector2 mousePos)
|
||||
{
|
||||
gearController.transform.position = mousePos;
|
||||
|
||||
int nearestIndex = CuttingUtils.FindNearestEdgePointIndex(mousePos, edgePoints, out float dist);
|
||||
Debug.Log($"Distance to nearest point: {dist}, Snap distance: {snapDistance}");
|
||||
|
||||
if (dist < snapDistance)
|
||||
{
|
||||
currentEdgeIndex = nearestIndex;
|
||||
|
||||
Vector2 prev = edgePoints[CuttingUtils.WrapIndex(currentEdgeIndex - 1, edgePoints.Count)];
|
||||
Vector2 next = edgePoints[CuttingUtils.WrapIndex(currentEdgeIndex + 1, edgePoints.Count)];
|
||||
|
||||
float distToPrev = Vector2.Distance(mousePos, prev);
|
||||
float distToNext = Vector2.Distance(mousePos, next);
|
||||
|
||||
tractionDirection = (distToNext < distToPrev) ? 1 : -1;
|
||||
|
||||
gearController.transform.position = edgePoints[currentEdgeIndex];
|
||||
currentState = State.Traction;
|
||||
Debug.Log("Switched to Traction state");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTractionState(Vector2 mousePos)
|
||||
{
|
||||
Vector2 gearPos = transform.position;
|
||||
|
||||
if (Vector2.Distance(mousePos, gearPos) > detachDistance)
|
||||
{
|
||||
currentState = State.Follow;
|
||||
cutLineManager.EndCutting();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Input.GetMouseButton(0))
|
||||
{
|
||||
cutLineManager.EndCutting();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cutLineManager.IsCutting)
|
||||
{
|
||||
cutLineManager.StartCutting();
|
||||
}
|
||||
|
||||
int nextIndex = CuttingUtils.WrapIndex(currentEdgeIndex + tractionDirection, edgePoints.Count);
|
||||
Vector2 from = edgePoints[currentEdgeIndex];
|
||||
Vector2 to = edgePoints[nextIndex];
|
||||
|
||||
Vector2 forward = (to - from).normalized;
|
||||
Vector2 toMouse = (mousePos - from).normalized;
|
||||
float angle = Vector2.SignedAngle(forward, toMouse);
|
||||
|
||||
if (Mathf.Abs(angle) > 120f)
|
||||
{
|
||||
directionSwitchTimer += Time.deltaTime;
|
||||
if (directionSwitchTimer > directionSwitchCooldown)
|
||||
{
|
||||
tractionDirection *= -1;
|
||||
directionSwitchTimer = 0f;
|
||||
|
||||
nextIndex = CuttingUtils.WrapIndex(currentEdgeIndex + tractionDirection, edgePoints.Count);
|
||||
from = edgePoints[currentEdgeIndex];
|
||||
to = edgePoints[nextIndex];
|
||||
forward = (to - from).normalized;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
directionSwitchTimer = 0f;
|
||||
}
|
||||
|
||||
Vector2 targetPoint = CuttingUtils.GetClosestPointOnSegment(from, to, mousePos);
|
||||
Vector2 moveDir = (targetPoint - gearPos);
|
||||
float distance = moveDir.magnitude;
|
||||
|
||||
float maxStep = tractionSpeed * Time.deltaTime;
|
||||
|
||||
if (distance <= maxStep)
|
||||
{
|
||||
gearController.transform.position = targetPoint;
|
||||
|
||||
if (Vector2.Distance(targetPoint, to) < 0.01f)
|
||||
{
|
||||
currentEdgeIndex = nextIndex;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
gearController.transform.position += (Vector3)(moveDir.normalized * maxStep);
|
||||
}
|
||||
|
||||
Vector2 originalFrom = originalEdgePoints[currentEdgeIndex];
|
||||
Vector2 originalTo = originalEdgePoints[nextIndex];
|
||||
Vector2 originalTargetPoint = CuttingUtils.GetClosestPointOnSegment(originalFrom, originalTo, transform.position);
|
||||
|
||||
Vector3 noisyPoint = originalTargetPoint;
|
||||
noisyPoint.x += Random.Range(-cutLineManager.noiseAmount, cutLineManager.noiseAmount);
|
||||
noisyPoint.y += Random.Range(-cutLineManager.noiseAmount, cutLineManager.noiseAmount);
|
||||
cutLineManager.AddCutPoint(noisyPoint);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee21f841c30549c43837ad222c4892b3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,102 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public static class CuttingUtils
|
||||
{
|
||||
public static int FindNearestEdgePointIndex(Vector2 pos, List<Vector2> edgePoints, out float minDist)
|
||||
{
|
||||
int nearestIndex = 0;
|
||||
minDist = float.MaxValue;
|
||||
|
||||
for (int i = 0; i < edgePoints.Count; i++)
|
||||
{
|
||||
float dist = Vector2.Distance(pos, edgePoints[i]);
|
||||
if (dist < minDist)
|
||||
{
|
||||
minDist = dist;
|
||||
nearestIndex = i;
|
||||
}
|
||||
}
|
||||
return nearestIndex;
|
||||
}
|
||||
|
||||
public static Vector2 GetClosestPointOnSegment(Vector2 a, Vector2 b, Vector2 p)
|
||||
{
|
||||
Vector2 ab = b - a;
|
||||
float t = Vector2.Dot(p - a, ab) / ab.sqrMagnitude;
|
||||
t = Mathf.Clamp01(t);
|
||||
return a + ab * t;
|
||||
}
|
||||
|
||||
public static int WrapIndex(int index, int count)
|
||||
{
|
||||
if (index < 0) return count - 2;
|
||||
if (index >= count - 1) return 0;
|
||||
return index;
|
||||
}
|
||||
|
||||
public static List<Vector2> GetSpritePhysicsShapePoints(SpriteRenderer spriteRenderer)
|
||||
{
|
||||
List<Vector2> shapePoints = new List<Vector2>();
|
||||
spriteRenderer.sprite.GetPhysicsShape(0, shapePoints);
|
||||
|
||||
if (shapePoints.Count == 0)
|
||||
{
|
||||
Debug.LogError("目标Sprite没有物理形状点");
|
||||
return null;
|
||||
}
|
||||
|
||||
return shapePoints;
|
||||
}
|
||||
|
||||
public static (List<Vector2> scaledPoints, List<Vector2> originalPoints) ProcessEdgePoints(
|
||||
List<Vector2> shapePoints,
|
||||
SpriteRenderer spriteRenderer,
|
||||
float edgePointsScale = 1.2f,
|
||||
float desiredSegmentLength = 0.1f)
|
||||
{
|
||||
if (shapePoints == null || shapePoints.Count == 0) return (null, null);
|
||||
|
||||
List<Vector2> scaledPoints = new List<Vector2>();
|
||||
List<Vector2> originalPoints = new List<Vector2>();
|
||||
|
||||
// 计算中心点
|
||||
Vector2 center = Vector2.zero;
|
||||
foreach (Vector2 point in shapePoints)
|
||||
{
|
||||
center += point;
|
||||
}
|
||||
center /= shapePoints.Count;
|
||||
|
||||
// 处理每个线段
|
||||
for (int i = 0; i < shapePoints.Count; i++)
|
||||
{
|
||||
Vector2 p1 = spriteRenderer.transform.TransformPoint(shapePoints[i]);
|
||||
Vector2 p2 = spriteRenderer.transform.TransformPoint(shapePoints[(i + 1) % shapePoints.Count]);
|
||||
|
||||
Vector2 scaledP1 = center + (p1 - center) * edgePointsScale;
|
||||
Vector2 scaledP2 = center + (p2 - center) * edgePointsScale;
|
||||
|
||||
float segmentLength = Vector2.Distance(scaledP1, scaledP2);
|
||||
int steps = Mathf.Max(1, Mathf.CeilToInt(segmentLength / desiredSegmentLength));
|
||||
|
||||
for (int s = 0; s < steps; s++)
|
||||
{
|
||||
float t = (float)s / steps;
|
||||
Vector2 interpolated = Vector2.Lerp(scaledP1, scaledP2, t);
|
||||
Vector2 originalInterpolated = Vector2.Lerp(p1, p2, t);
|
||||
scaledPoints.Add(interpolated);
|
||||
originalPoints.Add(originalInterpolated);
|
||||
}
|
||||
}
|
||||
|
||||
// 确保首尾相连
|
||||
if ((scaledPoints[0] - scaledPoints[scaledPoints.Count - 1]).sqrMagnitude > 0.0001f)
|
||||
{
|
||||
scaledPoints.Add(scaledPoints[0]);
|
||||
originalPoints.Add(originalPoints[0]);
|
||||
}
|
||||
|
||||
return (scaledPoints, originalPoints);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6fd7ba5ee9e9b7a4ca602330a830ac95
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,26 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class GearController : MonoBehaviour
|
||||
{
|
||||
[Header("齿轮参数")]
|
||||
public float rotationSpeed = 360f;
|
||||
public float snapDistance = 0.3f;
|
||||
public float detachDistance = 10.0f;
|
||||
|
||||
private bool isRotating = false;
|
||||
|
||||
public void SetRotation(bool shouldRotate)
|
||||
{
|
||||
isRotating = shouldRotate;
|
||||
}
|
||||
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (isRotating)
|
||||
{
|
||||
transform.Rotate(Vector3.forward, -rotationSpeed * Time.deltaTime);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b570555589f44fb4889f5dd5712ced34
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,936 @@
|
||||
using System.Collections.Generic;
|
||||
using Shapes;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
using UnityEngine.VFX;
|
||||
using DG.Tweening;
|
||||
using AibisDream.FixSystem;
|
||||
using AibisDream; // 添加OpenSystem的命名空间
|
||||
using System.Collections;
|
||||
using AibisDream.Kit;
|
||||
|
||||
public class GearFollower : MonoBehaviour
|
||||
{
|
||||
[Header("参数")]
|
||||
public SpriteRenderer targetSpriteRenderer;
|
||||
public float snapDistance = 1.0f;
|
||||
public float detachDistance = 1.0f;
|
||||
public float tractionSpeed = 2f;
|
||||
public float edgePointsScale = 1.2f; // 边缘点缩放倍率
|
||||
|
||||
[Header("切割轨迹")]
|
||||
public LineRenderer cutLineRenderer;
|
||||
public float cutLineWidth = 0.1f;
|
||||
public float noiseAmount = 0.05f; // 毛刺偏移幅度
|
||||
public float segmentDuration = 4f; // 线段持续时间
|
||||
[Header("震动与光照反馈")]
|
||||
public Light2D cutLight; // 需要使用 Unity 的 2D 点光源(URP)
|
||||
public float lightPulseSpeed = 5f;
|
||||
public float lightMaxIntensity = 1.5f;
|
||||
public float shakeAmount = 0.05f;
|
||||
|
||||
[Header("VFX火花")]
|
||||
public VisualEffect sparkVFX; // 关联Spark VFX Graph组件
|
||||
public float maxEmissionRate = 50f; // 最大发射速率
|
||||
public float emissionChangeSpeed = 100f; // 发射速率变化速度
|
||||
|
||||
[Header("切割完成效果")]
|
||||
public float cutCompletionThreshold = 0.95f; // 切割完成阈值(切割路径占边缘的比例)
|
||||
public float minCutLength = 0.5f; // 最小切割长度(相对于总长度的比例)
|
||||
public float targetShakeAmount = 0.02f; // 目标抖动幅度
|
||||
public float cutCompleteTiltAngle = 15f; // 切割完成后的倾斜角度
|
||||
public float cutCompleteOffset = 0.2f; // 切割完成后的位移
|
||||
public float cutCompleteScale = 1.2f; // 切割完成后的放大倍数
|
||||
public float fadeOutDuration = 1f; // 淡出持续时间
|
||||
public float cutCompleteDelay = 1f; // 切割完成后的延迟时间
|
||||
public float scaleUpDuration = 1f; // 放大动画持续时间
|
||||
public float fadeOutDelay = 1f; // 淡出前的延迟时间
|
||||
public AudioClip cutCompleteSound; // 切割完成音效
|
||||
|
||||
[Header("CutText闪烁效果")]
|
||||
private float cutTextFlickerThreshold = 0.7f; // 开始闪烁的阈值
|
||||
private float cutTextFlickerInterval = 0.1f; // 闪烁间隔
|
||||
private float cutTextFlickerTimer = 0f; // 闪烁计时器
|
||||
private bool isCutTextFlickering = false; // 是否正在闪烁
|
||||
|
||||
private float totalEdgeLength = 0f; // 总边缘长度
|
||||
private float cutLength = 0f; // 已切割长度
|
||||
private bool isCutComplete = false;
|
||||
private bool isFadingOut = false;
|
||||
private float fadeOutTimer = 0f;
|
||||
private Vector3 originalTargetPosition;
|
||||
private Quaternion originalTargetRotation;
|
||||
private Vector3 originalTargetScale;
|
||||
private AudioSource audioSource;
|
||||
private DG.Tweening.Sequence cutCompleteSequence; // DOTween序列
|
||||
private HashSet<int> cutEdgeIndices = new HashSet<int>(); // 记录已切割的边缘点索引
|
||||
|
||||
private float currentEmissionRate = 0f;
|
||||
private Vector3 originalLocalPosition;
|
||||
private List<Vector2> edgePoints;
|
||||
private List<Vector2> originalEdgePoints; // 存储原始边缘点
|
||||
private int currentEdgeIndex = 0;
|
||||
private int tractionDirection = 1;
|
||||
|
||||
private enum State { Follow, Traction }
|
||||
private State currentState = State.Follow;
|
||||
private Camera mainCamera;
|
||||
private bool isCutting = false;
|
||||
private float directionSwitchCooldown = 0.2f;
|
||||
private float directionSwitchTimer = 0f;
|
||||
private class TimedPoint
|
||||
{
|
||||
public Vector3 position;
|
||||
public float timeAdded;
|
||||
public TimedPoint(Vector3 pos, float time)
|
||||
{
|
||||
position = pos;
|
||||
timeAdded = time;
|
||||
}
|
||||
}
|
||||
private List<TimedPoint> timedCutPoints = new List<TimedPoint>();
|
||||
public float cutLineDelay = 3f;
|
||||
private Vector3 shakeOffset = Vector3.zero;
|
||||
private Vector3 targetPosition = Vector3.zero;
|
||||
private bool isInTractionMode = false;
|
||||
private bool isDrillSfxPlaying = false; // 标记音效是否已播放
|
||||
|
||||
void Start()
|
||||
{
|
||||
}
|
||||
public void Init(SpriteRenderer targetSpriteRenderer)
|
||||
{
|
||||
SpriteRenderer gearRenderer = GetComponent<SpriteRenderer>();
|
||||
if (gearRenderer != null)
|
||||
{
|
||||
gearRenderer.color = new Color(1, 1, 1, 1);
|
||||
}
|
||||
this.targetSpriteRenderer = null;
|
||||
this.targetSpriteRenderer = targetSpriteRenderer;
|
||||
mainCamera = Camera.main;
|
||||
LoadEdgePointsFromSprite();
|
||||
CalculateTotalEdgeLength();
|
||||
if (cutLineRenderer != null)
|
||||
{
|
||||
cutLineRenderer.positionCount = 0;
|
||||
cutLineRenderer.widthCurve = AnimationCurve.Constant(0, 1, cutLineWidth);
|
||||
cutLineRenderer.material = new Material(Shader.Find("Sprites/Default"));
|
||||
cutLineRenderer.numCapVertices = 0;
|
||||
cutLineRenderer.sortingOrder = 1;
|
||||
}
|
||||
if (cutLight != null)
|
||||
{
|
||||
cutLight.intensity = 0f;
|
||||
cutLight.enabled = false;
|
||||
}
|
||||
|
||||
// 初始化目标相关变量
|
||||
if (targetSpriteRenderer != null)
|
||||
{
|
||||
originalTargetPosition = targetSpriteRenderer.transform.position;
|
||||
originalTargetRotation = targetSpriteRenderer.transform.rotation;
|
||||
originalTargetScale = targetSpriteRenderer.transform.localScale;
|
||||
}
|
||||
|
||||
// 添加音频源组件
|
||||
audioSource = gameObject.AddComponent<AudioSource>();
|
||||
audioSource.playOnAwake = false;
|
||||
audioSource.spatialBlend = 0f; // 2D音效
|
||||
|
||||
// 确保初始状态
|
||||
isCutting = false;
|
||||
isInTractionMode = false;
|
||||
if (sparkVFX != null)
|
||||
{
|
||||
sparkVFX.Stop();
|
||||
}
|
||||
}
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
public void GearUpdate()
|
||||
{
|
||||
// 获取鼠标在屏幕上的位置
|
||||
Vector3 mouseScreenPos = Input.mousePosition;
|
||||
// 将屏幕坐标转换为世界坐标
|
||||
Vector3 mouseWorldPos = mainCamera.ScreenToWorldPoint(new Vector3(mouseScreenPos.x, mouseScreenPos.y, -mainCamera.transform.position.z));
|
||||
mouseWorldPos.z = 0; // 确保z坐标为0
|
||||
|
||||
Debug.Log($"鼠标屏幕坐标: {mouseScreenPos}");
|
||||
Debug.Log($"鼠标世界坐标: {mouseWorldPos}");
|
||||
Debug.Log($"齿轮当前位置: {transform.position}");
|
||||
if (targetSpriteRenderer != null)
|
||||
{
|
||||
Debug.Log($"目标精灵位置: {targetSpriteRenderer.transform.position}");
|
||||
Debug.Log($"目标精灵边界: {targetSpriteRenderer.bounds}");
|
||||
}
|
||||
|
||||
if (Input.GetMouseButton(0))
|
||||
{
|
||||
transform.Rotate(Vector3.forward, -360f * Time.deltaTime);
|
||||
}
|
||||
switch (currentState)
|
||||
{
|
||||
case State.Follow:
|
||||
UpdateFollowState(mouseWorldPos);
|
||||
break;
|
||||
case State.Traction:
|
||||
UpdateTractionState(mouseWorldPos);
|
||||
break;
|
||||
}
|
||||
UpdateCutLineRenderer();
|
||||
UpdateVisualFeedback();
|
||||
}
|
||||
private void UpdateVisualFeedback()
|
||||
{
|
||||
if (isInTractionMode && isCutting)
|
||||
{
|
||||
// 原始小幅震动
|
||||
Vector2 baseShake = Random.insideUnitCircle * shakeAmount;
|
||||
shakeOffset = (Vector3)baseShake;
|
||||
transform.position = targetPosition + shakeOffset;
|
||||
|
||||
// 目标抖动
|
||||
if (targetSpriteRenderer != null && !isCutComplete)
|
||||
{
|
||||
Vector2 targetShake = Random.insideUnitCircle * targetShakeAmount;
|
||||
targetSpriteRenderer.transform.position = originalTargetPosition + (Vector3)targetShake;
|
||||
}
|
||||
|
||||
// 根据震动幅度控制灯光强度
|
||||
if (cutLight != null)
|
||||
{
|
||||
cutLight.enabled = true;
|
||||
float intensity = shakeOffset.magnitude / shakeAmount; // 得到 [0,1] 比例
|
||||
cutLight.intensity = intensity * lightMaxIntensity;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
shakeOffset = Vector3.zero;
|
||||
|
||||
if (isInTractionMode)
|
||||
{
|
||||
transform.position = targetPosition;
|
||||
}
|
||||
|
||||
if (cutLight != null)
|
||||
{
|
||||
cutLight.intensity = 0f;
|
||||
cutLight.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理淡出效果
|
||||
if (isFadingOut)
|
||||
{
|
||||
fadeOutTimer += Time.deltaTime;
|
||||
float alpha = 1f - (fadeOutTimer / fadeOutDuration);
|
||||
|
||||
if (targetSpriteRenderer != null)
|
||||
{
|
||||
Color color = targetSpriteRenderer.color;
|
||||
color.a = alpha;
|
||||
targetSpriteRenderer.color = color;
|
||||
}
|
||||
|
||||
// 齿轮也淡出
|
||||
SpriteRenderer gearRenderer = GetComponent<SpriteRenderer>();
|
||||
if (gearRenderer != null)
|
||||
{
|
||||
Color gearColor = gearRenderer.color;
|
||||
gearColor.a = alpha;
|
||||
gearRenderer.color = gearColor;
|
||||
}
|
||||
|
||||
if (fadeOutTimer >= fadeOutDuration)
|
||||
{
|
||||
// 淡出完成后销毁物体
|
||||
Destroy(gameObject);
|
||||
if (targetSpriteRenderer != null)
|
||||
{
|
||||
Destroy(targetSpriteRenderer.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void LoadEdgePointsFromSprite()
|
||||
{
|
||||
edgePoints = new List<Vector2>();
|
||||
originalEdgePoints = new List<Vector2>();
|
||||
if (targetSpriteRenderer == null)
|
||||
{
|
||||
Debug.LogError("targetSpriteRenderer 为空");
|
||||
return;
|
||||
}
|
||||
|
||||
List<Vector2> shapePoints = new List<Vector2>();
|
||||
targetSpriteRenderer.sprite.GetPhysicsShape(0, shapePoints);
|
||||
|
||||
if (shapePoints.Count == 0)
|
||||
{
|
||||
Debug.LogError("目标Sprite没有物理形状点");
|
||||
return;
|
||||
}
|
||||
Debug.Log($"原始形状点数量: {shapePoints.Count}");
|
||||
float desiredSegmentLength = 0.1f;
|
||||
|
||||
edgePoints.Clear();
|
||||
originalEdgePoints.Clear();
|
||||
|
||||
// 获取精灵的世界坐标
|
||||
Vector3 spriteWorldPos = targetSpriteRenderer.transform.position;
|
||||
Debug.Log($"精灵世界坐标: {spriteWorldPos}");
|
||||
|
||||
// 计算精灵的局部边界
|
||||
Bounds localBounds = targetSpriteRenderer.bounds;
|
||||
Vector3 localCenter = localBounds.center;
|
||||
Vector3 localSize = localBounds.size;
|
||||
|
||||
// 计算缩放后的边界
|
||||
float maxScale = Mathf.Max(localSize.x, localSize.y) * edgePointsScale;
|
||||
float minScale = Mathf.Min(localSize.x, localSize.y) * edgePointsScale;
|
||||
|
||||
Debug.Log($"精灵局部边界: 中心={localCenter}, 大小={localSize}");
|
||||
Debug.Log($"缩放后边界: 最大={maxScale}, 最小={minScale}");
|
||||
|
||||
for (int i = 0; i < shapePoints.Count; i++)
|
||||
{
|
||||
// 将局部坐标转换为世界坐标
|
||||
Vector3 localPoint1 = shapePoints[i];
|
||||
Vector3 localPoint2 = shapePoints[(i + 1) % shapePoints.Count];
|
||||
|
||||
// 转换为世界坐标
|
||||
Vector3 worldPoint1 = targetSpriteRenderer.transform.TransformPoint(localPoint1);
|
||||
Vector3 worldPoint2 = targetSpriteRenderer.transform.TransformPoint(localPoint2);
|
||||
|
||||
// 确保坐标在合理范围内
|
||||
worldPoint1 = ClampPosition(worldPoint1, spriteWorldPos, maxScale);
|
||||
worldPoint2 = ClampPosition(worldPoint2, spriteWorldPos, maxScale);
|
||||
|
||||
Debug.Log($"点 {i} 局部坐标: {localPoint1} -> 世界坐标: {worldPoint1}");
|
||||
|
||||
// 计算相对于精灵中心点的缩放
|
||||
Vector3 scaledPoint1 = spriteWorldPos + (worldPoint1 - spriteWorldPos) * edgePointsScale;
|
||||
Vector3 scaledPoint2 = spriteWorldPos + (worldPoint2 - spriteWorldPos) * edgePointsScale;
|
||||
|
||||
// 确保缩放后的点也在合理范围内
|
||||
scaledPoint1 = ClampPosition(scaledPoint1, spriteWorldPos, maxScale);
|
||||
scaledPoint2 = ClampPosition(scaledPoint2, spriteWorldPos, maxScale);
|
||||
|
||||
Debug.Log($"点 {i} 缩放后世界坐标: {scaledPoint1}");
|
||||
|
||||
float segmentLength = Vector3.Distance(scaledPoint1, scaledPoint2);
|
||||
int steps = Mathf.Max(1, Mathf.CeilToInt(segmentLength / desiredSegmentLength));
|
||||
|
||||
for (int s = 0; s < steps; s++)
|
||||
{
|
||||
float t = (float)s / steps;
|
||||
Vector3 interpolated = Vector3.Lerp(scaledPoint1, scaledPoint2, t);
|
||||
Vector3 originalInterpolated = Vector3.Lerp(worldPoint1, worldPoint2, t);
|
||||
|
||||
// 确保插值点也在合理范围内
|
||||
interpolated = ClampPosition(interpolated, spriteWorldPos, maxScale);
|
||||
originalInterpolated = ClampPosition(originalInterpolated, spriteWorldPos, maxScale);
|
||||
|
||||
// 存储为Vector2,但保持世界坐标
|
||||
edgePoints.Add(new Vector2(interpolated.x, interpolated.y));
|
||||
originalEdgePoints.Add(new Vector2(originalInterpolated.x, originalInterpolated.y));
|
||||
}
|
||||
}
|
||||
|
||||
if ((edgePoints[0] - edgePoints[edgePoints.Count - 1]).sqrMagnitude > 0.0001f)
|
||||
{
|
||||
edgePoints.Add(edgePoints[0]);
|
||||
originalEdgePoints.Add(originalEdgePoints[0]);
|
||||
}
|
||||
|
||||
Debug.Log($"生成的边缘点数量: {edgePoints.Count}");
|
||||
Debug.Log($"第一个边缘点世界坐标: {edgePoints[0]}");
|
||||
Debug.Log($"最后一个边缘点世界坐标: {edgePoints[edgePoints.Count - 1]}");
|
||||
}
|
||||
|
||||
// 添加一个辅助方法来限制坐标范围
|
||||
private Vector3 ClampPosition(Vector3 position, Vector3 center, float maxDistance)
|
||||
{
|
||||
Vector3 offset = position - center;
|
||||
float distance = offset.magnitude;
|
||||
|
||||
if (distance > maxDistance)
|
||||
{
|
||||
offset = offset.normalized * maxDistance;
|
||||
position = center + offset;
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
private void UpdateFollowState(Vector2 mousePos)
|
||||
{
|
||||
transform.position = mousePos;
|
||||
isInTractionMode = false;
|
||||
|
||||
int nearestIndex = FindNearestEdgePointIndex(mousePos, out float dist);
|
||||
Debug.Log($"最近边缘点索引: {nearestIndex}, 距离: {dist}");
|
||||
Debug.Log($"最近边缘点位置: {edgePoints[nearestIndex]}");
|
||||
Debug.Log($"当前snapDistance: {snapDistance}");
|
||||
|
||||
if (dist < snapDistance)
|
||||
{
|
||||
currentEdgeIndex = nearestIndex;
|
||||
|
||||
Vector2 prev = edgePoints[WrapIndex(currentEdgeIndex - 1)];
|
||||
Vector2 next = edgePoints[WrapIndex(currentEdgeIndex + 1)];
|
||||
Debug.Log($"前一个点: {prev}, 后一个点: {next}");
|
||||
|
||||
float distToPrev = Vector2.Distance(mousePos, prev);
|
||||
float distToNext = Vector2.Distance(mousePos, next);
|
||||
Debug.Log($"到前一个点距离: {distToPrev}, 到后一个点距离: {distToNext}");
|
||||
|
||||
tractionDirection = (distToNext < distToPrev) ? 1 : -1;
|
||||
|
||||
targetPosition = edgePoints[currentEdgeIndex];
|
||||
transform.position = targetPosition;
|
||||
currentState = State.Traction;
|
||||
isInTractionMode = true;
|
||||
|
||||
// 设置渲染顺序为5
|
||||
SpriteRenderer gearRenderer = GetComponent<SpriteRenderer>();
|
||||
if (gearRenderer != null)
|
||||
{
|
||||
gearRenderer.sortingOrder = 5;
|
||||
} // 只在未播放时播放音效
|
||||
}
|
||||
}
|
||||
private void UpdateTractionState(Vector2 mousePos)
|
||||
{
|
||||
Vector2 gearPos = transform.position - shakeOffset;
|
||||
|
||||
if (Vector2.Distance(mousePos, gearPos) > detachDistance)
|
||||
{
|
||||
EndCutting();
|
||||
return;
|
||||
}
|
||||
if (!Input.GetMouseButton(0))
|
||||
{
|
||||
EndCutting();
|
||||
return;
|
||||
}
|
||||
if (!isCutting && Input.GetMouseButton(0))
|
||||
{
|
||||
StartCutting();
|
||||
}
|
||||
|
||||
int nextIndex = WrapIndex(currentEdgeIndex + tractionDirection);
|
||||
Vector2 from = edgePoints[currentEdgeIndex];
|
||||
Vector2 to = edgePoints[nextIndex];
|
||||
|
||||
Vector2 forward = (to - from).normalized;
|
||||
Vector2 toMouse = (mousePos - from).normalized;
|
||||
float angle = Vector2.SignedAngle(forward, toMouse);
|
||||
|
||||
if (Mathf.Abs(angle) > 120f)
|
||||
{
|
||||
directionSwitchTimer += Time.deltaTime;
|
||||
if (directionSwitchTimer > directionSwitchCooldown)
|
||||
{
|
||||
tractionDirection *= -1;
|
||||
directionSwitchTimer = 0f;
|
||||
|
||||
nextIndex = WrapIndex(currentEdgeIndex + tractionDirection);
|
||||
from = edgePoints[currentEdgeIndex];
|
||||
to = edgePoints[nextIndex];
|
||||
forward = (to - from).normalized;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
directionSwitchTimer = 0f;
|
||||
}
|
||||
|
||||
Vector2 targetPoint = GetClosestPointOnSegment(from, to, mousePos);
|
||||
Vector2 moveDir = (targetPoint - gearPos);
|
||||
float distance = moveDir.magnitude;
|
||||
|
||||
float maxStep = tractionSpeed * Time.deltaTime;
|
||||
|
||||
if (distance <= maxStep)
|
||||
{
|
||||
targetPosition = targetPoint;
|
||||
UpdateCutProgress(from, targetPoint); // 更新切割进度
|
||||
|
||||
if (Vector2.Distance(targetPoint, to) < 0.01f)
|
||||
{
|
||||
currentEdgeIndex = nextIndex;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
targetPosition = (Vector3)gearPos + (Vector3)(moveDir.normalized * maxStep);
|
||||
}
|
||||
transform.position = targetPosition + shakeOffset;
|
||||
|
||||
// 使用原始边缘点来记录切割线
|
||||
Vector2 originalFrom = originalEdgePoints[currentEdgeIndex];
|
||||
Vector2 originalTo = originalEdgePoints[nextIndex];
|
||||
Vector2 originalTargetPoint = GetClosestPointOnSegment(originalFrom, originalTo, transform.position - shakeOffset);
|
||||
|
||||
// 记录切割点(带噪声模拟毛糙)
|
||||
Vector3 noisyPoint = originalTargetPoint;
|
||||
noisyPoint.x += Random.Range(-noiseAmount, noiseAmount);
|
||||
noisyPoint.y += Random.Range(-noiseAmount, noiseAmount);
|
||||
AddCutPoint(noisyPoint);
|
||||
|
||||
if (sparkVFX != null)
|
||||
{
|
||||
// 计算从切割点到目标精灵中心的向量
|
||||
Vector2 center = targetSpriteRenderer.transform.position;
|
||||
Vector2 toCenter = center - (Vector2)originalTargetPoint;
|
||||
// 计算法线方向(垂直于切割方向)
|
||||
Vector2 normal = Vector2.Perpendicular(moveDir.normalized);
|
||||
// 根据切割方向决定法线方向
|
||||
if (tractionDirection > 0)
|
||||
{
|
||||
normal = -normal;
|
||||
}
|
||||
Vector3 velocity = (Vector3)normal * 10f;
|
||||
sparkVFX.SetVector3("Initial Velocity1", velocity);
|
||||
}
|
||||
}
|
||||
|
||||
private void StartCutting()
|
||||
{
|
||||
isCutting = true;
|
||||
//cutLinePoints.Clear();
|
||||
if (cutLineRenderer != null)
|
||||
{
|
||||
cutLineRenderer.positionCount = 0;
|
||||
}
|
||||
if (sparkVFX != null)
|
||||
{
|
||||
sparkVFX.Play();
|
||||
}
|
||||
|
||||
|
||||
AudioManager.Instance.SetSfxParam("event:/FollowInput/drill","is_drill_cutting",1f);
|
||||
|
||||
}
|
||||
private void AddCutPoint(Vector3 point)
|
||||
{
|
||||
if (timedCutPoints.Count == 0 || Vector3.Distance(timedCutPoints[timedCutPoints.Count - 1].position, point) > 0.05f)
|
||||
{
|
||||
timedCutPoints.Add(new TimedPoint(point, Time.time));
|
||||
|
||||
// 更新光照位置到最新切点
|
||||
if (cutLight != null)
|
||||
{
|
||||
cutLight.transform.position = point;
|
||||
}
|
||||
// 把火花特效位置设置到当前切割点
|
||||
if (sparkVFX != null)
|
||||
{
|
||||
sparkVFX.transform.position = point;
|
||||
}
|
||||
if (timedCutPoints.Count >= 2)
|
||||
{
|
||||
Vector3 prev = timedCutPoints[timedCutPoints.Count - 2].position;
|
||||
Vector3 curr = timedCutPoints[timedCutPoints.Count - 1].position;
|
||||
CreateSegment(prev, curr);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void UpdateCutLineRenderer()
|
||||
{
|
||||
if (cutLineRenderer == null) return;
|
||||
|
||||
float currentTime = Time.time;
|
||||
List<Vector3> visiblePoints = new List<Vector3>();
|
||||
|
||||
foreach (var timedPoint in timedCutPoints)
|
||||
{
|
||||
if (currentTime - timedPoint.timeAdded >= cutLineDelay)
|
||||
{
|
||||
visiblePoints.Add(timedPoint.position);
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
cutLineRenderer.positionCount = visiblePoints.Count;
|
||||
if (visiblePoints.Count > 0)
|
||||
cutLineRenderer.SetPositions(visiblePoints.ToArray());
|
||||
}
|
||||
|
||||
private void EndCutting()
|
||||
{
|
||||
isCutting = false;
|
||||
if (sparkVFX != null)
|
||||
{
|
||||
sparkVFX.Stop();
|
||||
}
|
||||
|
||||
// AudioManager.Instance.SetGlobalParam("is_drill_cutting", 0f);
|
||||
AudioManager.Instance.SetSfxParam("event:/FollowInput/drill","is_drill_cutting",0f);
|
||||
|
||||
// 关闭切割光照
|
||||
if (cutLight != null)
|
||||
{
|
||||
cutLight.enabled = false;
|
||||
cutLight.intensity = 0f;
|
||||
}
|
||||
// 停止音效
|
||||
}
|
||||
|
||||
private int FindNearestEdgePointIndex(Vector2 pos, out float minDist)
|
||||
{
|
||||
int nearestIndex = 0;
|
||||
minDist = float.MaxValue;
|
||||
|
||||
// 使用世界坐标进行距离计算
|
||||
for (int i = 0; i < edgePoints.Count; i++)
|
||||
{
|
||||
float dist = Vector2.Distance(pos, edgePoints[i]);
|
||||
if (dist < minDist)
|
||||
{
|
||||
minDist = dist;
|
||||
nearestIndex = i;
|
||||
}
|
||||
}
|
||||
return nearestIndex;
|
||||
}
|
||||
private Vector2 GetClosestPointOnSegment(Vector2 a, Vector2 b, Vector2 p)
|
||||
{
|
||||
Vector2 ab = b - a;
|
||||
float t = Vector2.Dot(p - a, ab) / ab.sqrMagnitude;
|
||||
t = Mathf.Clamp01(t);
|
||||
return a + ab * t;
|
||||
}
|
||||
|
||||
private int WrapIndex(int index)
|
||||
{
|
||||
if (index < 0) return edgePoints.Count - 2;
|
||||
if (index >= edgePoints.Count - 1) return 0;
|
||||
return index;
|
||||
}
|
||||
private void CreateSegment(Vector3 from, Vector3 to)
|
||||
{
|
||||
GameObject segObj = new GameObject("HeatLineSegment");
|
||||
segObj.transform.parent = this.transform;
|
||||
|
||||
LineRenderer segLine = segObj.AddComponent<LineRenderer>();
|
||||
segLine.positionCount = 2;
|
||||
segLine.SetPosition(0, from);
|
||||
segLine.SetPosition(1, to);
|
||||
segLine.sortingOrder = 4;
|
||||
segLine.material = new Material(Shader.Find("Sprites/Default"));
|
||||
segLine.widthCurve = AnimationCurve.Constant(0, 1, cutLineWidth);
|
||||
segLine.numCapVertices = 0;
|
||||
var segmentController = segObj.AddComponent<HeatLineSegmentController>();
|
||||
segmentController.duration = segmentDuration;
|
||||
}
|
||||
|
||||
private void CalculateTotalEdgeLength()
|
||||
{
|
||||
totalEdgeLength = 0f;
|
||||
for (int i = 0; i < edgePoints.Count - 1; i++)
|
||||
{
|
||||
totalEdgeLength += Vector2.Distance(edgePoints[i], edgePoints[i + 1]);
|
||||
}
|
||||
Debug.Log($"计算总边缘长度: {totalEdgeLength}");
|
||||
}
|
||||
|
||||
private void UpdateCutProgress(Vector2 from, Vector2 to)
|
||||
{
|
||||
// 计算当前段的切割长度
|
||||
float segmentLength = Vector2.Distance(from, to);
|
||||
cutLength += segmentLength;
|
||||
|
||||
// 记录已切割的边缘点
|
||||
cutEdgeIndices.Add(currentEdgeIndex);
|
||||
|
||||
// 计算切割进度
|
||||
float progress = cutLength / totalEdgeLength;
|
||||
Debug.Log($"当前切割长度: {cutLength}, 总长度: {totalEdgeLength}, 进度: {progress * 100}%");
|
||||
|
||||
// 更新切割进度
|
||||
var cuttingManager = FixSystemCenter.SystemDic.Get<CuttingManager>();
|
||||
if (cuttingManager != null)
|
||||
{
|
||||
cuttingManager.UpdateCutProgress(progress);
|
||||
}
|
||||
|
||||
// 检查是否满足切割完成条件
|
||||
if (!isCutComplete && progress >= cutCompletionThreshold)
|
||||
{
|
||||
// 检查是否形成了连续的切割路径
|
||||
bool hasContinuousCut = CheckContinuousCut();
|
||||
Debug.Log($"连续切割检查: {hasContinuousCut}, 已切割点数: {cutEdgeIndices.Count}, 总点数: {edgePoints.Count}");
|
||||
|
||||
if (hasContinuousCut)
|
||||
{
|
||||
isCutComplete = true;
|
||||
OnCutComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckContinuousCut()
|
||||
{
|
||||
if (cutEdgeIndices.Count < 2) return false;
|
||||
|
||||
// 将索引转换为有序列表
|
||||
List<int> sortedIndices = new List<int>(cutEdgeIndices);
|
||||
sortedIndices.Sort();
|
||||
|
||||
// 检查是否有连续的切割点
|
||||
int continuousCount = 1;
|
||||
int maxContinuousCount = 1;
|
||||
int totalPoints = edgePoints.Count;
|
||||
|
||||
// 检查所有可能的连续段
|
||||
for (int startIndex = 0; startIndex < sortedIndices.Count; startIndex++)
|
||||
{
|
||||
continuousCount = 1;
|
||||
int currentIndex = sortedIndices[startIndex];
|
||||
|
||||
// 向前检查
|
||||
for (int i = 1; i < sortedIndices.Count; i++)
|
||||
{
|
||||
int nextIndex = (currentIndex + i) % totalPoints;
|
||||
if (sortedIndices.Contains(nextIndex))
|
||||
{
|
||||
continuousCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 向后检查
|
||||
for (int i = 1; i < sortedIndices.Count; i++)
|
||||
{
|
||||
int prevIndex = (currentIndex - i + totalPoints) % totalPoints;
|
||||
if (sortedIndices.Contains(prevIndex))
|
||||
{
|
||||
continuousCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
maxContinuousCount = Mathf.Max(maxContinuousCount, continuousCount);
|
||||
}
|
||||
|
||||
// 计算连续切割点的比例
|
||||
float continuousRatio = (float)maxContinuousCount / totalPoints;
|
||||
Debug.Log($"最大连续切割点数: {maxContinuousCount}, 总点数: {totalPoints}, 比例: {continuousRatio * 100}%");
|
||||
|
||||
// 如果连续切割点的数量超过总点数的70%,认为形成了有效的切割路径
|
||||
return continuousRatio >= 0.7f;
|
||||
}
|
||||
|
||||
private void OnCutComplete()
|
||||
{
|
||||
var cuttingManager = FixSystemCenter.SystemDic.Get<CuttingManager>();
|
||||
if (cuttingManager != null)
|
||||
{
|
||||
cuttingManager.OnCutComplete();
|
||||
}
|
||||
}
|
||||
public void ClearCutLine()
|
||||
{
|
||||
if (cutLineRenderer != null)
|
||||
{
|
||||
cutLineRenderer.positionCount = 0;
|
||||
Destroy(this.gameObject);
|
||||
}
|
||||
}
|
||||
public void FinishCutting()
|
||||
{
|
||||
// 清理特效
|
||||
if (cutLight != null)
|
||||
{
|
||||
cutLight.enabled = false;
|
||||
}
|
||||
if (sparkVFX != null)
|
||||
{
|
||||
sparkVFX.Stop();
|
||||
}
|
||||
|
||||
// 停止切割音效
|
||||
var audioManager = AibisDream.Kit.AudioManager.Instance;
|
||||
if (audioManager != null)
|
||||
{
|
||||
// 快速将is_drill_cutting参数从1拉到0
|
||||
AudioManager.Instance.SetSfxParam("event:/FollowInput/drill","is_drill_cutting",0f);
|
||||
}
|
||||
|
||||
// 计算随机方向
|
||||
float randomAngle = Random.Range(-cutCompleteTiltAngle, cutCompleteTiltAngle);
|
||||
Vector2 randomOffset = Random.insideUnitCircle * cutCompleteOffset;
|
||||
var cuttingManager = FixSystemCenter.SystemDic.Get<CuttingManager>();
|
||||
if (cuttingManager != null)
|
||||
{
|
||||
cuttingManager.StopCutTextFlicker();
|
||||
cuttingManager.SetCutTextVisible(false);
|
||||
}
|
||||
|
||||
// 立即应用倾斜和位移
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/mod_cutout");
|
||||
targetSpriteRenderer.transform.rotation = Quaternion.Euler(0, 0, randomAngle);
|
||||
targetSpriteRenderer.transform.position = originalTargetPosition + (Vector3)randomOffset;
|
||||
|
||||
|
||||
// 创建动画序列
|
||||
cutCompleteSequence = DOTween.Sequence();
|
||||
|
||||
// 等待一段时间后开始放大
|
||||
cutCompleteSequence.AppendInterval(cutCompleteDelay);
|
||||
cutCompleteSequence.Append(targetSpriteRenderer.transform.DOScale(originalTargetScale * cutCompleteScale, scaleUpDuration)
|
||||
.SetEase(Ease.OutQuad));
|
||||
|
||||
// 等待一段时间后开始淡出
|
||||
cutCompleteSequence.AppendInterval(fadeOutDelay);
|
||||
cutCompleteSequence.Append(targetSpriteRenderer.DOFade(0f, fadeOutDuration));
|
||||
|
||||
// 齿轮也同时淡出
|
||||
SpriteRenderer gearRenderer = GetComponent<SpriteRenderer>();
|
||||
if (gearRenderer != null)
|
||||
{
|
||||
cutCompleteSequence.Join(gearRenderer.DOFade(0f, fadeOutDuration));
|
||||
}
|
||||
cutCompleteSequence.OnComplete(() =>
|
||||
{
|
||||
|
||||
var cuttingManager = FixSystemCenter.SystemDic.Get<CuttingManager>();
|
||||
if (cuttingManager != null)
|
||||
{
|
||||
cuttingManager.UpdateModule();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void StartCutTextFlicker()
|
||||
{
|
||||
var cuttingManager = FixSystemCenter.SystemDic.Get<CuttingManager>();
|
||||
if (cuttingManager != null)
|
||||
{
|
||||
// 开始闪烁效果
|
||||
StartCoroutine(CutTextFlickerCoroutine(cuttingManager));
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator CutTextFlickerCoroutine(CuttingManager cuttingManager)
|
||||
{
|
||||
while (isCutTextFlickering && !isCutComplete)
|
||||
{
|
||||
// 随机决定是否显示
|
||||
bool shouldShow = Random.value > 0.5f;
|
||||
cuttingManager.SetCutTextVisible(shouldShow);
|
||||
|
||||
// 随机等待时间,模拟接触不良效果
|
||||
float waitTime = Random.Range(cutTextFlickerInterval * 0.5f, cutTextFlickerInterval * 1.5f);
|
||||
yield return new WaitForSeconds(waitTime);
|
||||
}
|
||||
|
||||
// 确保最终状态是可见的
|
||||
if (!isCutComplete)
|
||||
{
|
||||
cuttingManager.SetCutTextVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// 清理DOTween序列
|
||||
if (cutCompleteSequence != null)
|
||||
{
|
||||
cutCompleteSequence.Kill();
|
||||
cutCompleteSequence = null;
|
||||
}
|
||||
|
||||
// 停止所有协程
|
||||
StopAllCoroutines();
|
||||
|
||||
// 清理特效
|
||||
if (cutLight != null)
|
||||
{
|
||||
cutLight.enabled = false;
|
||||
}
|
||||
|
||||
if (sparkVFX != null)
|
||||
{
|
||||
sparkVFX.Stop();
|
||||
}
|
||||
|
||||
// 清理切割线
|
||||
if (cutLineRenderer != null)
|
||||
{
|
||||
cutLineRenderer.positionCount = 0;
|
||||
}
|
||||
|
||||
// 停止音效
|
||||
StopDrillSfxIfPlaying();
|
||||
|
||||
// 重置目标精灵状态
|
||||
if (targetSpriteRenderer != null)
|
||||
{
|
||||
// 恢复原始状态
|
||||
targetSpriteRenderer.transform.position = originalTargetPosition;
|
||||
targetSpriteRenderer.transform.rotation = originalTargetRotation;
|
||||
targetSpriteRenderer.transform.localScale = originalTargetScale;
|
||||
|
||||
// 恢复透明度
|
||||
Color color = targetSpriteRenderer.color;
|
||||
color.a = 1f;
|
||||
targetSpriteRenderer.color = color;
|
||||
}
|
||||
|
||||
// 重置所有状态变量
|
||||
isCutting = false;
|
||||
isInTractionMode = false;
|
||||
isCutComplete = false;
|
||||
isFadingOut = false;
|
||||
isCutTextFlickering = false;
|
||||
currentState = State.Follow;
|
||||
|
||||
// 清理列表
|
||||
if (timedCutPoints != null)
|
||||
{
|
||||
timedCutPoints.Clear();
|
||||
}
|
||||
|
||||
if (cutEdgeIndices != null)
|
||||
{
|
||||
cutEdgeIndices.Clear();
|
||||
}
|
||||
|
||||
if (edgePoints != null)
|
||||
{
|
||||
edgePoints.Clear();
|
||||
}
|
||||
|
||||
if (originalEdgePoints != null)
|
||||
{
|
||||
originalEdgePoints.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
// 停止音效的辅助方法
|
||||
private void StopDrillSfxIfPlaying()
|
||||
{
|
||||
var audioManager = AibisDream.Kit.AudioManager.Instance;
|
||||
if (audioManager != null)
|
||||
{
|
||||
audioManager.StopSfx("event:/FollowInput/drill");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 07505bc4dd78e1e4c87141a1d061dd10
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,65 @@
|
||||
using UnityEngine;
|
||||
using DG.Tweening;
|
||||
|
||||
public class HeatLineSegmentController : MonoBehaviour
|
||||
{
|
||||
private LineRenderer lineRenderer;
|
||||
private Sequence sequence;
|
||||
|
||||
public float duration = 4f; // 整个冷却淡出时间
|
||||
public Color whiteColor = Color.white; // 最热 - 白色
|
||||
public Color orangeColor = new Color(1f, 0.5f, 0f, 1f); // 次热 - 橙色
|
||||
public Color redColor = Color.red; // 较热 - 红色
|
||||
public Color coldColor = Color.black; // 冷却 - 黑色
|
||||
|
||||
void Awake()
|
||||
{
|
||||
lineRenderer = GetComponent<LineRenderer>();
|
||||
if (lineRenderer == null)
|
||||
{
|
||||
Debug.LogError("HeatLineSegmentController需要LineRenderer组件");
|
||||
}
|
||||
lineRenderer.startColor = lineRenderer.endColor = whiteColor;
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
// 创建颜色渐变序列
|
||||
sequence = DOTween.Sequence();
|
||||
|
||||
// 白色到橙色 (快速过渡)
|
||||
sequence.Append(DOTween.To(() => lineRenderer.startColor, x => {
|
||||
lineRenderer.startColor = x;
|
||||
lineRenderer.endColor = x;
|
||||
}, orangeColor, duration * 0.1f).SetEase(Ease.InOutQuad));
|
||||
|
||||
// 橙色到红色
|
||||
sequence.Append(DOTween.To(() => lineRenderer.startColor, x => {
|
||||
lineRenderer.startColor = x;
|
||||
lineRenderer.endColor = x;
|
||||
}, redColor, duration * 0.3f).SetEase(Ease.InOutQuad));
|
||||
|
||||
// 红色到黑色
|
||||
sequence.Append(DOTween.To(() => lineRenderer.startColor, x => {
|
||||
lineRenderer.startColor = x;
|
||||
lineRenderer.endColor = x;
|
||||
}, coldColor, duration * 0.3f).SetEase(Ease.InOutQuad));
|
||||
|
||||
// 淡出效果
|
||||
sequence.Append(DOTween.To(() => lineRenderer.startColor.a, x => {
|
||||
Color c = lineRenderer.startColor;
|
||||
c.a = x;
|
||||
lineRenderer.startColor = c;
|
||||
lineRenderer.endColor = c;
|
||||
}, 0f, duration * 0.3f).SetEase(Ease.InQuad));
|
||||
|
||||
// 完成后销毁对象
|
||||
sequence.OnComplete(() => Destroy(gameObject));
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
// 清理DOTween序列
|
||||
sequence?.Kill();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a522756b1d3f1640981d73f044ab755
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -15,7 +15,7 @@ public class ChipModule : MonoBehaviour
|
||||
private void Awake()
|
||||
{
|
||||
// 将数据注册到数据容器内
|
||||
GameLoopManager.FixDataContainer.Register(_chipData);
|
||||
StorageSystem.Instance.RegisterData(_chipData);
|
||||
// 添加加载逻辑
|
||||
_chipData.LoadEvent += LoadData;
|
||||
_chipModule = transform.Find("Chip").gameObject;
|
||||
@@ -66,8 +66,9 @@ public class ChipData : IData
|
||||
|
||||
public event Action<ChipData> LoadEvent;
|
||||
|
||||
public void Load()
|
||||
public IEnumerator Load()
|
||||
{
|
||||
LoadEvent?.Invoke(this);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using UnityEngine;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using AibisDream.FixSystem;
|
||||
|
||||
public class UFModule : MonoBehaviour
|
||||
@@ -17,7 +18,7 @@ public class UFModule : MonoBehaviour
|
||||
private void Awake()
|
||||
{
|
||||
// 将数据注册到数据容器内
|
||||
GameLoopManager.FixDataContainer.Register(_ufData);
|
||||
StorageSystem.Instance.RegisterData(_ufData);
|
||||
// 添加加载逻辑
|
||||
_ufData.LoadEvent += LoadData;
|
||||
_ufModule = transform.Find("Module Pic").gameObject;
|
||||
@@ -118,8 +119,9 @@ public class UFData : IData
|
||||
|
||||
public event Action<UFData> LoadEvent;
|
||||
|
||||
public void Load()
|
||||
public IEnumerator Load()
|
||||
{
|
||||
LoadEvent?.Invoke(this);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using DG.Tweening;
|
||||
using System.Collections;
|
||||
using Yarn.Unity;
|
||||
|
||||
namespace AibisDream.FixSystem
|
||||
{
|
||||
public class OscilloscopeSystem : MonoBehaviour
|
||||
{
|
||||
#region 旧的组件索引
|
||||
|
||||
private TMP_Text _state; // 显示状态的TextMeshPro组件
|
||||
private TMP_Text _screenText; // 显示消息的TextMeshPro组件
|
||||
private SpriteButton _deepinButton;
|
||||
|
||||
#endregion
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
FixSystemCenter.SystemDic.Register(this);
|
||||
InitComponentRefs();
|
||||
}
|
||||
|
||||
private void InitComponentRefs()
|
||||
{
|
||||
_screenText = transform.Find("Screen Text").GetComponent<TMP_Text>();
|
||||
_state = transform.Find("Screen State Text").GetComponent<TMP_Text>();
|
||||
_deepinButton = new SpriteButton(transform.Find("Deepin Button").gameObject, false);
|
||||
MessageBoxSetNull();
|
||||
|
||||
_deepinButton.OnButtonDown += HandleDeepInButtonClick;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
_deepinButton.CheckButtonClick();
|
||||
}
|
||||
|
||||
#region 旧显示屏接口
|
||||
|
||||
public void MessageBoxSetError()
|
||||
{
|
||||
_state.text = "错误";
|
||||
_state.color = Color.red; // 红色
|
||||
}
|
||||
|
||||
private void HandleDeepInButtonClick()
|
||||
{
|
||||
FixSystemCenter.SystemDic.Get<BodyModuleSystem>().HandleDeepIn();
|
||||
}
|
||||
|
||||
public void MessageBoxSetWarning()
|
||||
{
|
||||
_state.text = "警告";
|
||||
_state.color = new Color(1.0f, 0.55f, 0.0f); // 琥珀色
|
||||
}
|
||||
|
||||
public void MessageBoxSetNormal()
|
||||
{
|
||||
_state.text = "正常";
|
||||
_state.color = Color.blue; // 琥珀色
|
||||
}
|
||||
|
||||
public void MessageBoxSetChecking()
|
||||
{
|
||||
_state.text = "检查中";
|
||||
_state.color = Color.white; // 琥珀色
|
||||
}
|
||||
|
||||
public void MessageBoxSetText(string text)
|
||||
{
|
||||
_screenText.color = Color.white; // 默认文本颜色
|
||||
_screenText.text += text + "<br>";
|
||||
}
|
||||
|
||||
public void MessageBoxSetNull()
|
||||
{
|
||||
_state.text = "未检测到信号";
|
||||
_state.color = Color.white; // 蓝色
|
||||
_screenText.text = "";
|
||||
_screenText.color = Color.white; // 默认文本颜色
|
||||
}
|
||||
|
||||
public IEnumerator EnableDeepInButton()
|
||||
{
|
||||
yield return _deepinButton.GetTransform().DOLocalMoveX(0.35f, 0.5f).WaitForCompletion();
|
||||
_deepinButton.SetActive(true);
|
||||
}
|
||||
|
||||
public IEnumerator DisableDeepInButton()
|
||||
{
|
||||
yield return _deepinButton.GetTransform().DOLocalMoveX(-0.2f, 0.5f).WaitForCompletion();
|
||||
_deepinButton.SetActive(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 示波器接口。暂时能想到的就是这几个对外接口
|
||||
/// 用示波器显示对话的部分额外实现IDialogView接口。这部分方案定下来之后老丁来实现。
|
||||
/// </summary>
|
||||
public interface IOscilloscopeSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// 展示示波器上的图片和检测点位
|
||||
/// </summary>
|
||||
/// /// <param name="moduleImage">显示在示波器里的图片</param>
|
||||
/// <param name="checkPoints">检查点信息</param>
|
||||
public IEnumerator ShowScanImage(Sprite moduleImage, CheckPoint[] checkPoints);
|
||||
|
||||
/// <summary>
|
||||
/// 聚焦到某个点上
|
||||
/// </summary>
|
||||
/// <param name="checkPointId">检查点</param>
|
||||
public IEnumerator FocusOnPoint(string checkPointId);
|
||||
|
||||
/// <summary>
|
||||
/// 停止聚焦
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerator StopFocus();
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏显示屏上的图像
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public void HideScanImage();
|
||||
}
|
||||
|
||||
public static class OscilloscopeYarnCommand
|
||||
{
|
||||
private static IOscilloscopeSystem OscilloscopeSystem => FixSystemCenter.SystemDic.Get<IOscilloscopeSystem>();
|
||||
|
||||
[YarnCommand("focus_on_point")]
|
||||
public static IEnumerator FocusOnPoint(string checkPointId)
|
||||
{
|
||||
return OscilloscopeSystem?.FocusOnPoint(checkPointId);
|
||||
}
|
||||
|
||||
[YarnCommand("stop_focus")]
|
||||
public static IEnumerator StopFocus()
|
||||
{
|
||||
return OscilloscopeSystem?.StopFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a2e20b111511460a8f3a151bd470da9d
|
||||
timeCreated: 1731328037
|
||||
@@ -2,7 +2,6 @@
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.Utility;
|
||||
using DG.Tweening;
|
||||
using MeadowGames.UINodeConnect4.GraphicRenderer;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
@@ -11,14 +10,15 @@ namespace AibisDream.FixSystem
|
||||
[RequireComponent(typeof(EventTriggerEx))]
|
||||
public class Plug : MonoBehaviour, IInteraction
|
||||
{
|
||||
private static readonly int ShineFade = Shader.PropertyToID("_ShineFade");
|
||||
|
||||
#region 插头当前信息
|
||||
|
||||
private Vector3 _startPos;
|
||||
|
||||
private readonly Vector3 _pickupOffset = new(0, 0, 0);
|
||||
private readonly Quaternion _pickupRotationOffset = Quaternion.Euler(0, 0, 30);
|
||||
|
||||
private readonly Quaternion initRotation = Quaternion.Euler(0, 0, 180);
|
||||
|
||||
private readonly Quaternion _initRotation = Quaternion.Euler(0, 0, 180);
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -32,13 +32,14 @@ namespace AibisDream.FixSystem
|
||||
#endregion
|
||||
|
||||
private bool _isDragging;
|
||||
private bool _isPhysicCableActive=false;
|
||||
private bool _isPhysicCableActive;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
InitComponentRef();
|
||||
EventRegister();
|
||||
_startPos = transform.position; // 初始化时记录初始位置
|
||||
_startPos = new Vector3(transform.position.x + 0.3f, transform.position.y - 2f,
|
||||
transform.position.z); // 初始化时记录初始位置
|
||||
}
|
||||
|
||||
private void InitComponentRef()
|
||||
@@ -47,8 +48,11 @@ namespace AibisDream.FixSystem
|
||||
_sprite = GetComponent<SpriteRenderer>();
|
||||
_plugRootPos = transform.Find("Plug Root Pos");
|
||||
_trigger = GetComponent<EventTriggerEx>();
|
||||
|
||||
// 处理插头高亮
|
||||
_sprite.material.SetFloat(ShineFade, GlobalVariableKit.IsPlugHighLight ? 1 : 0);
|
||||
}
|
||||
|
||||
|
||||
private void EventRegister()
|
||||
{
|
||||
_trigger.Register(EventTriggerType.Drag, OnDrag);
|
||||
@@ -56,17 +60,12 @@ namespace AibisDream.FixSystem
|
||||
_trigger.Register(EventTriggerType.PointerUp, OnPointerUp);
|
||||
}
|
||||
|
||||
public void InitPlug(ISocket initSocket)
|
||||
{
|
||||
ReturnToStartPosition();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 插入某个socket
|
||||
/// </summary>
|
||||
/// <param name="targetSocket">目标socket</param>
|
||||
public void InsertSocket(ISocket targetSocket)
|
||||
private void InsertSocket(ISocket targetSocket)
|
||||
{
|
||||
// 修改插头位置
|
||||
transform.position = targetSocket.GetSocketPos();
|
||||
@@ -93,39 +92,62 @@ namespace AibisDream.FixSystem
|
||||
{
|
||||
// 修改线头位置
|
||||
_cableSystem.CableRef.SetEndPos(_plugRootPos);
|
||||
|
||||
|
||||
_cableSystem.curSocket?.PlugOut();
|
||||
AudioManager.Instance.PlaySfx("event:/FollowInput/plugout");
|
||||
_sprite.enabled = true;
|
||||
|
||||
|
||||
if (_cableSystem.curSocket is BodyModule)
|
||||
{
|
||||
// 从模块拔出时触发事件
|
||||
EnumEventSystem.Global.Send(EventEnum.PlugOut);
|
||||
_cableSystem.curBodyModule = null;
|
||||
}
|
||||
|
||||
|
||||
_cableSystem.curSocket = null;
|
||||
}
|
||||
|
||||
private void OnDrag(BaseEventData eventData)
|
||||
{
|
||||
// 系统被锁定就返回
|
||||
if (!_cableSystem.IsPlugAvailable()) return;
|
||||
|
||||
if (!_cableSystem.IsPlugAvailable())
|
||||
{
|
||||
Debug.Log("OnDrag - System not available");
|
||||
return;
|
||||
}
|
||||
|
||||
// 未被拖拽也返回
|
||||
if (!_isDragging) return;
|
||||
|
||||
if (!_isDragging)
|
||||
{
|
||||
Debug.Log("OnDrag - Not dragging");
|
||||
return;
|
||||
}
|
||||
|
||||
if (eventData is PointerEventData pointerData)
|
||||
{
|
||||
transform.position = CommonUtil.GetMouseWorldPos(pointerData.position, transform);
|
||||
transform.position = CameraKit.GetMouseWorldPos(pointerData.position);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPointerDown(BaseEventData eventData)
|
||||
{
|
||||
if (eventData is PointerEventData { button: PointerEventData.InputButton.Right })
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GetComponent<SpriteRenderer>().sortingLayerName = "Tools";
|
||||
GetComponent<SpriteRenderer>().sortingOrder = 48;
|
||||
// 系统被锁定就返回
|
||||
if (!_cableSystem.IsPlugAvailable()) return;
|
||||
|
||||
// 首次拖拽后关闭高亮
|
||||
if (GlobalVariableKit.IsPlugHighLight)
|
||||
{
|
||||
GlobalVariableKit.IsPlugHighLight = false;
|
||||
_sprite.material.SetFloat(ShineFade, 0);
|
||||
DialogController.Instance.StartDialogNode("拿起插头");
|
||||
}
|
||||
|
||||
// 开启拖拽
|
||||
_isDragging = true;
|
||||
@@ -139,11 +161,21 @@ namespace AibisDream.FixSystem
|
||||
// 关闭 PhysicCable 的更新,并显示 Cable 的 LineRenderer
|
||||
_cableSystem.PhysicCableRef.GetComponent<LineRenderer>().enabled = false;
|
||||
_isPhysicCableActive = false;
|
||||
_cableSystem.CableRef.GetComponent<LineRenderer>().enabled = true;
|
||||
_cableSystem.CableRef.ShowCable();
|
||||
|
||||
// 打开所有模块
|
||||
FixSystemCenter.SystemDic.Get<BodyModuleSystem>().OpenModuleSlots();
|
||||
AudioManager.Instance.PlaySfx("event:/ActionFB/mods_open");
|
||||
AudioManager.Instance.PlaySfx("event:/FollowInput/plugpick");
|
||||
}
|
||||
|
||||
private void OnPointerUp(BaseEventData eventData)
|
||||
{
|
||||
if (eventData is PointerEventData { button: PointerEventData.InputButton.Right })
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 停止拖拽
|
||||
_isDragging = false;
|
||||
|
||||
@@ -152,20 +184,24 @@ namespace AibisDream.FixSystem
|
||||
{
|
||||
// 找到了就插入目标Module
|
||||
InsertSocket(targetModule);
|
||||
FixSystemCenter.SystemDic.Get<BodyModuleSystem>().CloseModuleSlots(targetModule);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 没找到就插回初始位置
|
||||
// 没找到就回到初始位置
|
||||
ReturnToStartPosition();
|
||||
FixSystemCenter.SystemDic.Get<BodyModuleSystem>().CloseModuleSlots(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReturnToStartPosition()
|
||||
{
|
||||
transform.DOMove(_startPos, 0.1f).OnComplete(() =>
|
||||
{
|
||||
PlugPosBack();
|
||||
transform.DOMove(_startPos, 0.1f).OnComplete(() =>
|
||||
{
|
||||
_cableSystem.CableReelRef.ResetRotation();
|
||||
SwitchToPhysicCableState();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void AdjustPlugPos()
|
||||
@@ -173,31 +209,31 @@ namespace AibisDream.FixSystem
|
||||
transform.position += _pickupOffset;
|
||||
transform.rotation = _pickupRotationOffset;
|
||||
}
|
||||
|
||||
private void PlugPosBack()
|
||||
{
|
||||
transform.position -= _pickupOffset;
|
||||
transform.rotation = initRotation;
|
||||
transform.rotation = _initRotation;
|
||||
}
|
||||
private void SwitchToPhysicCableState()
|
||||
|
||||
public void SwitchToPhysicCableState()
|
||||
{
|
||||
|
||||
// 隐藏 Cable 的 LineRenderer
|
||||
PlugPosBack();
|
||||
_cableSystem.PhysicCableRef.Init(transform.position);
|
||||
_cableSystem.CableRef.GetComponent<LineRenderer>().enabled = false;
|
||||
// 激活 PhysicCable
|
||||
_cableSystem.PhysicCableRef.GetComponent<LineRenderer>().enabled = true;
|
||||
_cableSystem.PhysicCableRef.Init(transform.position);
|
||||
_cableSystem.CableRef.HideCable();
|
||||
_cableSystem.PhysicCableRef.GetComponent<LineRenderer>().enabled = true;
|
||||
_isPhysicCableActive = true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void SetPhysicCableActive(bool active)
|
||||
{
|
||||
_isPhysicCableActive = active;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_isPhysicCableActive)
|
||||
if (_isPhysicCableActive && !_cableSystem.isCableRetracted)
|
||||
{
|
||||
_cableSystem.PhysicCableRef.physicLine.UpdatePlug(this.transform);
|
||||
_cableSystem.PhysicCableRef.physicLine.UpdatePlug(transform);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,11 +245,16 @@ namespace AibisDream.FixSystem
|
||||
|
||||
public bool IsActive => true;
|
||||
|
||||
public bool IsAvailable => _cableSystem.BodyModuleSystem.isAvailable;
|
||||
public bool IsAvailable => _cableSystem.BodyModuleSystem.isAvailable && !_cableSystem.isCableRetracted;
|
||||
|
||||
public GameObject GetGameObject()
|
||||
{
|
||||
return gameObject;
|
||||
}
|
||||
|
||||
public Vector3 GetStartPos()
|
||||
{
|
||||
return _startPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
using AibisDream.FixSystem;
|
||||
using UnityEngine.UI;
|
||||
using Unity.VisualScripting;
|
||||
using AibisDream;
|
||||
using AibisDream.Kit;
|
||||
|
||||
public class PunchTapeSystem : MonoBehaviour
|
||||
@@ -14,84 +11,54 @@ public class PunchTapeSystem : MonoBehaviour
|
||||
public RectTransform punchTape;
|
||||
|
||||
public GameObject blackPanel;
|
||||
public float stepInterval = 0.1f; // Interval between each printing step
|
||||
public float stepDistance = 0.5f; // Distance moved per step
|
||||
public float[] stepPatterns = { 0.3f, 0.5f, 0.2f, 0.4f, 0.6f }; // Pattern of intervals to simulate rhythmic printing
|
||||
|
||||
public GameObject tempPunchTapeGroup;
|
||||
private void Awake()
|
||||
{
|
||||
FixSystemCenter.SystemDic.Register(this);
|
||||
}
|
||||
|
||||
// public void Update()
|
||||
// {
|
||||
// if (Input.GetKeyDown(KeyCode.P))
|
||||
// {
|
||||
// StartPrinting(2);
|
||||
// }
|
||||
// }
|
||||
|
||||
public void StartPrinting(int tapeCount)
|
||||
private void OnDestroy()
|
||||
{
|
||||
StartCoroutine(PrintPunchTapes(tapeCount));
|
||||
FixSystemCenter.SystemDic.Unregister<PunchTapeSystem>();
|
||||
}
|
||||
|
||||
public IEnumerator PrintPunchTapes(int tapeCount)
|
||||
{
|
||||
blackPanel.SetActive(true);
|
||||
Vector3 machineStartPos = punchTapeMachine.anchoredPosition;
|
||||
Vector3 tapeStartPos = punchTape.anchoredPosition;
|
||||
Color tapeStartColor = punchTape.GetComponent<Image>().color;
|
||||
|
||||
while (tapeCount > 0)
|
||||
{
|
||||
// Move the printer to x = 2 over 1 second
|
||||
yield return punchTapeMachine.DOAnchorPos(new Vector2(-1252f, punchTapeMachine.anchoredPosition.y), 1f).WaitForCompletion();
|
||||
// 0-0.7秒:打印机移动到指定位置
|
||||
AudioManager.Instance.PlaySfx("event:/Scriptal/typing");
|
||||
// Gradually move the punch tape to x = -3.8 with rhythmic pattern
|
||||
yield return punchTapeMachine.DOAnchorPos(new Vector2(-1252f, punchTapeMachine.anchoredPosition.y), 0.7f)
|
||||
.WaitForCompletion();
|
||||
|
||||
|
||||
// 0.7-1秒:停顿
|
||||
yield return new WaitForSeconds(0.3f);
|
||||
|
||||
// 1-1.6秒:纸带匀速弹出
|
||||
Vector2 targetPosition = new Vector2(796f, punchTape.anchoredPosition.y);
|
||||
int patternIndex = 0;
|
||||
yield return punchTape.DOAnchorPos(targetPosition, 0.6f).WaitForCompletion();
|
||||
|
||||
while (Vector2.Distance(punchTape.anchoredPosition, targetPosition) > stepDistance)
|
||||
{
|
||||
punchTape.anchoredPosition = Vector3.MoveTowards(punchTape.anchoredPosition, targetPosition, stepDistance);
|
||||
yield return new WaitForSeconds(stepPatterns[patternIndex % stepPatterns.Length]);
|
||||
patternIndex++;
|
||||
}
|
||||
// 1.6-2.3秒:停顿
|
||||
yield return new WaitForSeconds(0.7f);
|
||||
|
||||
// Ensure exact final position
|
||||
punchTape.anchoredPosition = targetPosition;
|
||||
// 2.3-2.7秒:纸带淡出
|
||||
yield return punchTape.GetComponent<Image>().DOFade(0f, 0.4f).WaitForCompletion();
|
||||
|
||||
// Fade out the punch tape over 1 second
|
||||
yield return punchTape.GetComponent<Image>().DOFade(0f, 1f).WaitForCompletion();
|
||||
|
||||
// Reset the punch tape position and opacity
|
||||
// 重置纸带位置和透明度
|
||||
punchTape.anchoredPosition = tapeStartPos;
|
||||
punchTape.GetComponent<Image>().color = tapeStartColor;
|
||||
|
||||
// Decrement the tape count
|
||||
tapeCount--;
|
||||
}
|
||||
|
||||
// Move the printer back to x = 0 over 1 second
|
||||
yield return punchTapeMachine.DOAnchorPos(new Vector2(-1428.186f, punchTapeMachine.anchoredPosition.y), 1f).WaitForCompletion();
|
||||
// 打印机返回原位
|
||||
yield return punchTapeMachine.DOAnchorPos(new Vector2(-1428.186f, punchTapeMachine.anchoredPosition.y), 1f)
|
||||
.WaitForCompletion();
|
||||
blackPanel.SetActive(false);
|
||||
}
|
||||
public void Temp_showPunchTapeGroup()
|
||||
{
|
||||
tempPunchTapeGroup.SetActive(true);
|
||||
}
|
||||
public IEnumerator Temp_HidePunchTapeGroup()
|
||||
{
|
||||
foreach(SpriteRenderer s in tempPunchTapeGroup.GetComponentsInChildren<SpriteRenderer>())
|
||||
{
|
||||
s.DOFade(0,0.5f);
|
||||
}
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
tempPunchTapeGroup.SetActive(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user