Ver.0.3.0.33

This commit is contained in:
2025-07-08 08:32:46 +00:00
parent 99f4dd67c2
commit 0ad5e29917
6831 changed files with 695623 additions and 234455 deletions
@@ -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();
}
}
}
+66 -12
View File
@@ -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
+81 -40
View File
@@ -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);
}
}
}