This commit is contained in:
2025-12-04 20:38:02 +08:00
parent 3290f9a270
commit f1c2c5a1ee
58 changed files with 11754 additions and 1365 deletions
-536
View File
@@ -1,536 +0,0 @@
<linker>
<assembly fullname="AllIn1SpriteShaderAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="AllIn1SpriteShader.AllIn1Shader" preserve="all" />
</assembly>
<assembly fullname="Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="AibisDream.ActiveScenePart" preserve="all" />
<type fullname="AibisDream.ActorAnima" preserve="all" />
<type fullname="AibisDream.ActorManager" preserve="all" />
<type fullname="AibisDream.ActorSlot" preserve="all" />
<type fullname="AibisDream.AnimaMobileObject" preserve="all" />
<type fullname="AibisDream.BatchSpawner" preserve="all" />
<type fullname="AibisDream.BoolButton" preserve="all" />
<type fullname="AibisDream.Bubble" preserve="all" />
<type fullname="AibisDream.BubbleGroup" preserve="all" />
<type fullname="AibisDream.BubbleOption" preserve="all" />
<type fullname="AibisDream.BubbleOptionSlot" preserve="all" />
<type fullname="AibisDream.BubbleSlot" preserve="all" />
<type fullname="AibisDream.BubbleSlotGroup" preserve="all" />
<type fullname="AibisDream.BubbleStyle" preserve="all" />
<type fullname="AibisDream.CenterBubble" preserve="all" />
<type fullname="AibisDream.CenterOptions" preserve="all" />
<type fullname="AibisDream.ChapterController" preserve="all" />
<type fullname="AibisDream.ChapterItem" preserve="all" />
<type fullname="AibisDream.CircularProgress" preserve="all" />
<type fullname="AibisDream.ClinicSystem" preserve="all" />
<type fullname="AibisDream.ColorTintButton" preserve="all" />
<type fullname="AibisDream.CordPull" preserve="all" />
<type fullname="AibisDream.CursorManager" preserve="all" />
<type fullname="AibisDream.CutLine" preserve="all" />
<type fullname="AibisDream.CuttingManager" preserve="all" />
<type fullname="AibisDream.DefaultOptions" preserve="all" />
<type fullname="AibisDream.DemoTimer" preserve="all" />
<type fullname="AibisDream.DialogController" preserve="all" />
<type fullname="AibisDream.DialogUIManager" preserve="all" />
<type fullname="AibisDream.EmoManager" preserve="all" />
<type fullname="AibisDream.EngineSystem" preserve="all" />
<type fullname="AibisDream.EnvironmentManager" preserve="all" />
<type fullname="AibisDream.ExpressionCable" preserve="all" />
<type fullname="AibisDream.ExpressionCableSystem" preserve="all" />
<type fullname="AibisDream.ExpressionManager" preserve="all" />
<type fullname="AibisDream.ExpressionPlug" preserve="all" />
<type fullname="AibisDream.ExpressionPowerSource" preserve="all" />
<type fullname="AibisDream.ExpressionSocket" preserve="all" />
<type fullname="AibisDream.ExpressionSubSystem" preserve="all" />
<type fullname="AibisDream.EyeSystem" preserve="all" />
<type fullname="AibisDream.FixSystem.BodyModule" preserve="all" />
<type fullname="AibisDream.FixSystem.BodyModuleSystem" preserve="all" />
<type fullname="AibisDream.FixSystem.Cable" preserve="all" />
<type fullname="AibisDream.FixSystem.CableReel" preserve="all" />
<type fullname="AibisDream.FixSystem.CableSystem" preserve="all" />
<type fullname="AibisDream.FixSystem.CheckData" preserve="all" />
<type fullname="AibisDream.FixSystem.CheckRule" preserve="all" />
<type fullname="AibisDream.FixSystem.FixLightSystem" preserve="all" />
<type fullname="AibisDream.FixSystem.FixSystemCenter" preserve="all" />
<type fullname="AibisDream.FixSystem.PipelineProduct" preserve="all" />
<type fullname="AibisDream.FixSystem.Plug" preserve="all" />
<type fullname="AibisDream.FixSystem.ScreenSystem" preserve="all" />
<type fullname="AibisDream.FixSystem.WhackMoleData" preserve="all" />
<type fullname="AibisDream.Framework.CameraKit" preserve="all" />
<type fullname="AibisDream.Framework.EventSystemEx" preserve="all" />
<type fullname="AibisDream.Framework.EventTriggerEx" preserve="all" />
<type fullname="AibisDream.Framework.MonoCenter" preserve="all" />
<type fullname="AibisDream.Framework.SpriteGroupKit" preserve="all" />
<type fullname="AibisDream.Framework.UICamera" preserve="all" />
<type fullname="AibisDream.GameManager" preserve="all" />
<type fullname="AibisDream.Gear" preserve="all" />
<type fullname="AibisDream.GearSystem" preserve="all" />
<type fullname="AibisDream.Kit.ActionKitMonoBehaviourEvents" preserve="all" />
<type fullname="AibisDream.Kit.AudioManager" preserve="all" />
<type fullname="AibisDream.Kit.MonoUpdateActionExecutor" preserve="all" />
<type fullname="AibisDream.Kit.VerText" preserve="all" />
<type fullname="AibisDream.LanguageItem" preserve="all" />
<type fullname="AibisDream.LineAdvanceInput" preserve="all" />
<type fullname="AibisDream.LineRunner" preserve="all" />
<type fullname="AibisDream.LocalisedLineProvider" preserve="all" />
<type fullname="AibisDream.LocalizedText" preserve="all" />
<type fullname="AibisDream.MouseFollowAndZoom" preserve="all" />
<type fullname="AibisDream.MoveablePartGroup" preserve="all" />
<type fullname="AibisDream.OpenSystem" preserve="all" />
<type fullname="AibisDream.OptionView" preserve="all" />
<type fullname="AibisDream.OutsideCenter" preserve="all" />
<type fullname="AibisDream.PrefabMobileObject" preserve="all" />
<type fullname="AibisDream.SceneLoader" preserve="all" />
<type fullname="AibisDream.SelectItem" preserve="all" />
<type fullname="AibisDream.SettingForm" preserve="all" />
<type fullname="AibisDream.Shaft" preserve="all" />
<type fullname="AibisDream.SliderItem" preserve="all" />
<type fullname="AibisDream.Spawner" preserve="all" />
<type fullname="AibisDream.SpawnScenePart" preserve="all" />
<type fullname="AibisDream.SpriteActor" preserve="all" />
<type fullname="AibisDream.SpriteFlashSystem" preserve="all" />
<type fullname="AibisDream.SpriteMobileObject" preserve="all" />
<type fullname="AibisDream.SpriteScenePart" preserve="all" />
<type fullname="AibisDream.StorageSystem" preserve="all" />
<type fullname="AibisDream.SubwayCenter" preserve="all" />
<type fullname="AibisDream.TalkSceneSO" preserve="all" />
<type fullname="AibisDream.TaskScreen" preserve="all" />
<type fullname="AibisDream.UI.ChapterPanel" preserve="all" />
<type fullname="AibisDream.UI.EndPanel" preserve="all" />
<type fullname="AibisDream.UI.InfoPanel" preserve="all" />
<type fullname="AibisDream.UI.MainPanel" preserve="all" />
<type fullname="AibisDream.UI.PlayToolPanel" preserve="all" />
<type fullname="AibisDream.UI.RecordItem" preserve="all" />
<type fullname="AibisDream.UI.RecordPanel" preserve="all" />
<type fullname="AibisDream.UI.SettingPanel" preserve="all" />
<type fullname="AibisDream.UI.StartPanel" preserve="all" />
<type fullname="AibisDream.UIManager" preserve="all" />
<type fullname="AibisDream.Vehicle" preserve="all" />
<type fullname="ChipModule" preserve="all" />
<type fullname="ClueBaordNew" preserve="all" />
<type fullname="ClueInteraction" preserve="all" />
<type fullname="ClueItem" preserve="all" />
<type fullname="ClueManager" preserve="all" />
<type fullname="DraggableUI" preserve="all" />
<type fullname="ExplosionEffect" preserve="all" />
<type fullname="EyeTarget" preserve="all" />
<type fullname="EyeTransitionManager" preserve="all" />
<type fullname="FavoritesManager" preserve="all" />
<type fullname="GearFollower" preserve="all" />
<type fullname="HeatMapController" preserve="all" />
<type fullname="LakeLineDrawer" preserve="all" />
<type fullname="LineInteraction" preserve="all" />
<type fullname="MemoryClueSlot" preserve="all" />
<type fullname="MemoryProcess" preserve="all" />
<type fullname="PhysicCable" preserve="all" />
<type fullname="PhysicLineSegment" preserve="all" />
<type fullname="PunchTapeSystem" preserve="all" />
<type fullname="RobotECGWave" preserve="all" />
<type fullname="ScreenEffectManager" preserve="all" />
<type fullname="ShockSystem" preserve="all" />
<type fullname="TimelineManager" preserve="all" />
<type fullname="UFModule" preserve="all" />
<type fullname="UFSystem" preserve="all" />
<type fullname="ViewCameraManager" preserve="all" />
<type fullname="AibisDream.BoolButtonData" preserve="nothing" serialized="true" />
<type fullname="AibisDream.UIFormOption" preserve="nothing" serialized="true" />
<type fullname="AibisDream.AddressArray" preserve="nothing" serialized="true" />
<type fullname="AibisDream.BubbleOptionSlotData" preserve="nothing" serialized="true" />
<type fullname="AibisDream.BubbleSlotData" preserve="nothing" serialized="true" />
<type fullname="AibisDream.BubbleSlotGroupData" preserve="nothing" serialized="true" />
<type fullname="AibisDream.FixSystem.BodyModuleData" preserve="nothing" serialized="true" />
<type fullname="AibisDream.FixSystem.CableConfig" preserve="nothing" serialized="true" />
<type fullname="AibisDream.Framework.SpriteGroupConfig" preserve="nothing" serialized="true" />
<type fullname="AibisDream.Framework.SpriteGroupData" preserve="nothing" serialized="true" />
<type fullname="AibisDream.MobileObjectData" preserve="nothing" serialized="true" />
<type fullname="AibisDream.SpawnerPartAsset" preserve="nothing" serialized="true" />
<type fullname="AibisDream.SpritePartAsset" preserve="nothing" serialized="true" />
<type fullname="EyeTransitionManager/ViewConfig" preserve="nothing" serialized="true" />
<type fullname="PunchTape" preserve="nothing" serialized="true" />
<type fullname="AibisDream.ActivePartAsset" preserve="nothing" serialized="true" />
<type fullname="AibisDream.ActorData" preserve="nothing" serialized="true" />
<type fullname="AibisDream.CableConfig" preserve="nothing" serialized="true" />
<type fullname="LakeLineDrawer/Obstacle" preserve="nothing" serialized="true" />
<type fullname="LakeLineDrawer/TargetPoint" preserve="nothing" serialized="true" />
<type fullname="AibisDream.EngineConfig" preserve="nothing" serialized="true" />
<type fullname="AibisDream.EngineGear" preserve="nothing" serialized="true" />
<type fullname="AibisDream.FixSystem.WhackMoleBatch" preserve="nothing" serialized="true" />
</assembly>
<assembly fullname="Assembly-CSharp-firstpass, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="MeadowGames.UINodeConnect4.Extension.StartPlusEndNodeIDsRule" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.Extra.ConstantUpdateLines" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.Extra.HandlerRenderer" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.GraphicRenderer.UICLineRenderer" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.GraphManager" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.InputManager_LegacyInputSystem" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.Node" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.Port" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.PortControlPoint" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.SampleScene.ScrollViewGraph.CustomScrollRect" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.SampleScene.ScrollViewGraph.Zoom" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.UICContextMenu.AddNodeButtonItem" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.UICContextMenu.ContextMenuManager" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.UICContextMenu.DefaultConnectionWidthInputItem" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.UICContextMenu.DefaultCurveStyleDropdownItem" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.UICContextMenu.DuplicateNodeButtonItem" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.UICContextMenu.EmptyContextItem" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.UICContextMenu.RemoveElementsButtonItem" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.UICContextMenu.SelectedElementLabelItem" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.UICContextMenu.SetConnectionAnimationActiveToggleItem" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.UICContextMenu.SetConnectionAnimationPointsSliderItem" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.UICContextMenu.SetConnectionWidthInputItem" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.UICContextMenu.SetCurveStyleDropdownItem" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.UICContextMenu.SetElementsColorItem" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.UICContextMenu.SetNodesAlignmentItem" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.UICSystemManager" preserve="all" />
<type fullname="RainbowArt.CleanFlatUI.ButtonTransition" preserve="all" />
<type fullname="RainbowArt.CleanFlatUI.ModalWindow" preserve="all" />
<type fullname="RainbowArt.CleanFlatUI.ModalWindowProgressBar" preserve="all" />
<type fullname="RainbowArt.CleanFlatUI.Notification" preserve="all" />
<type fullname="RainbowArt.CleanFlatUI.ProgressBar" preserve="all" />
<type fullname="RainbowArt.CleanFlatUI.ProgressBarGridLinear" preserve="all" />
<type fullname="RainbowArt.CleanFlatUI.SliderTransition" preserve="all" />
<type fullname="MeadowGames.UINodeConnect4.Connection" preserve="nothing" serialized="true" />
<type fullname="MeadowGames.UINodeConnect4.DragSelection" preserve="nothing" serialized="true" />
<type fullname="MeadowGames.UINodeConnect4.GraphicRenderer.Line" preserve="nothing" serialized="true" />
<type fullname="MeadowGames.UINodeConnect4.GraphicRenderer.Line/LineCap" preserve="nothing" serialized="true" />
<type fullname="MeadowGames.UINodeConnect4.GraphicRenderer.LineAnimation" preserve="nothing" serialized="true" />
<type fullname="MeadowGames.UINodeConnect4.Pointer" preserve="nothing" serialized="true" />
<type fullname="RainbowArt.CleanFlatUI.ModalWindow/ModalWindowEvent" preserve="nothing" serialized="true" />
<type fullname="RainbowArt.CleanFlatUI.ModalWindowProgressBar/ModalWindowEvent" preserve="nothing" serialized="true" />
<type fullname="RainbowArt.CleanFlatUI.Notification/NotificationEvent" preserve="nothing" serialized="true" />
</assembly>
<assembly fullname="Cinemachine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="Cinemachine.CinemachineBlenderSettings" preserve="all" />
<type fullname="Cinemachine.CinemachineBrain" preserve="all" />
<type fullname="Cinemachine.CinemachinePipeline" preserve="all" />
<type fullname="Cinemachine.CinemachineVirtualCamera" preserve="all" />
<type fullname="Cinemachine.NoiseSettings" preserve="all" />
<type fullname="Cinemachine.CinemachineBlendDefinition" preserve="nothing" serialized="true" />
<type fullname="Cinemachine.CinemachineBlenderSettings/CustomBlend" preserve="nothing" serialized="true" />
<type fullname="Cinemachine.CinemachineBrain/BrainEvent" preserve="nothing" serialized="true" />
<type fullname="Cinemachine.CinemachineBrain/VcamActivatedEvent" preserve="nothing" serialized="true" />
<type fullname="Cinemachine.CinemachineVirtualCameraBase/TransitionParams" preserve="nothing" serialized="true" />
<type fullname="Cinemachine.LensSettings" preserve="nothing" serialized="true" />
<type fullname="Cinemachine.NoiseSettings/NoiseParams" preserve="nothing" serialized="true" />
<type fullname="Cinemachine.NoiseSettings/TransformNoiseParams" preserve="nothing" serialized="true" />
</assembly>
<assembly fullname="Febucci.TextAnimator.Runtime, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="Febucci.UI.Actions.ActionDatabase" preserve="all" />
<type fullname="Febucci.UI.Actions.SpeedAction" preserve="all" />
<type fullname="Febucci.UI.Actions.WaitAnyInputAction" preserve="all" />
<type fullname="Febucci.UI.Actions.WaitForAction" preserve="all" />
<type fullname="Febucci.UI.Effects.AnimationsDatabase" preserve="all" />
<type fullname="Febucci.UI.Effects.BounceBehavior" preserve="all" />
<type fullname="Febucci.UI.Effects.DangleBehavior" preserve="all" />
<type fullname="Febucci.UI.Effects.DiagonalExpandAppearance" preserve="all" />
<type fullname="Febucci.UI.Effects.FadeAppearance" preserve="all" />
<type fullname="Febucci.UI.Effects.FadeBehavior" preserve="all" />
<type fullname="Febucci.UI.Effects.HorizontalExpandAppearance" preserve="all" />
<type fullname="Febucci.UI.Effects.OffsetAppearance" preserve="all" />
<type fullname="Febucci.UI.Effects.PendulumBehavior" preserve="all" />
<type fullname="Febucci.UI.Effects.RainbowBehavior" preserve="all" />
<type fullname="Febucci.UI.Effects.RandomDirectionAppearance" preserve="all" />
<type fullname="Febucci.UI.Effects.RotatingAppearance" preserve="all" />
<type fullname="Febucci.UI.Effects.RotationBehavior" preserve="all" />
<type fullname="Febucci.UI.Effects.ShakeBehavior" preserve="all" />
<type fullname="Febucci.UI.Effects.SizeAppearance" preserve="all" />
<type fullname="Febucci.UI.Effects.SizeBehavior" preserve="all" />
<type fullname="Febucci.UI.Effects.SlideBehavior" preserve="all" />
<type fullname="Febucci.UI.Effects.SwingBehavior" preserve="all" />
<type fullname="Febucci.UI.Effects.UniformCurveAnimation" preserve="all" />
<type fullname="Febucci.UI.Effects.VerticalExpandAppearance" preserve="all" />
<type fullname="Febucci.UI.Effects.WaveBehavior" preserve="all" />
<type fullname="Febucci.UI.Effects.WiggleBehavior" preserve="all" />
<type fullname="Febucci.UI.TypewriterByCharacter" preserve="all" />
<type fullname="Febucci.UI.Core.CharacterEvent" preserve="nothing" serialized="true" />
<type fullname="Febucci.UI.Core.MessageEvent" preserve="nothing" serialized="true" />
<type fullname="Febucci.UI.Effects.AnimationData" preserve="nothing" serialized="true" />
<type fullname="Febucci.UI.Effects.ColorCurve" preserve="nothing" serialized="true" />
<type fullname="Febucci.UI.Effects.EmissionCurve" preserve="nothing" serialized="true" />
<type fullname="Febucci.UI.Effects.FloatCurve" preserve="nothing" serialized="true" />
<type fullname="Febucci.UI.Effects.TimeMode" preserve="nothing" serialized="true" />
<type fullname="Febucci.UI.TimeData" preserve="nothing" serialized="true" />
</assembly>
<assembly fullname="Febucci.TextAnimator.TMP.Runtime, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="Febucci.UI.TextAnimator_TMP" preserve="all" />
</assembly>
<assembly fullname="FMODUnity, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="FMODUnity.FMODEventPlayable" preserve="all" />
<type fullname="FMODUnity.FMODEventTrack" preserve="all" />
<type fullname="FMODUnity.StudioListener" preserve="all" />
<type fullname="FMOD.GUID" preserve="nothing" serialized="true" />
<type fullname="FMODUnity.AutomatableSlots" preserve="nothing" serialized="true" />
<type fullname="FMODUnity.EventReference" preserve="nothing" serialized="true" />
<type fullname="FMODUnity.FMODEventMixerBehaviour" preserve="nothing" serialized="true" />
<type fullname="FMODUnity.FMODEventPlayableBehavior" preserve="nothing" serialized="true" />
</assembly>
<assembly fullname="ScreenFx, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="ScreenFx.CameraTrack" preserve="all" />
<type fullname="ScreenFx.MoveAsset" preserve="all" />
<type fullname="ScreenFx.NoiseAsset" preserve="all" />
<type fullname="ScreenFx.ScreenAsset" preserve="all" />
<type fullname="ScreenFx.ScreenFx" preserve="all" />
<type fullname="ScreenFx.ScreenFxPool" preserve="all" />
<type fullname="ScreenFx.ScreenTrack" preserve="all" />
<type fullname="ScreenFx.VolumeAsset" preserve="all" />
<type fullname="ScreenFx.VolumeTrack" preserve="all" />
<type fullname="ScreenFx.CameraBehaviour" preserve="nothing" serialized="true" />
<type fullname="ScreenFx.NoiseBehaviour" preserve="nothing" serialized="true" />
<type fullname="ScreenFx.Optional`1[Cinemachine.NoiseSettings]" preserve="nothing" serialized="true" />
<type fullname="ScreenFx.Optional`1[UnityEngine.Sprite]" preserve="nothing" serialized="true" />
<type fullname="ScreenFx.ScreenBehaviour" preserve="nothing" serialized="true" />
<type fullname="ScreenFx.VolumeBehaviour" preserve="nothing" serialized="true" />
</assembly>
<assembly fullname="Unity.2D.SpriteShape.Runtime, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="UnityEngine.U2D.SpriteShape" preserve="all" />
<type fullname="UnityEngine.U2D.SpriteShapeController" preserve="all" />
<type fullname="UnityEngine.U2D.AngleRange" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.U2D.CornerSprite" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.U2D.Spline" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.U2D.SplineControlPoint" preserve="nothing" serialized="true" />
</assembly>
<assembly fullname="Unity.Addressables, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" preserve="all">
<type fullname="UnityEngine.AddressableAssets.Addressables" preserve="all" />
</assembly>
<assembly fullname="Unity.Localization, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="UnityEngine.Localization.Components.LocalizeSpriteEvent" preserve="all" />
<type fullname="UnityEngine.Localization.Components.LocalizeStringEvent" preserve="all" />
<type fullname="UnityEngine.Localization.Locale" preserve="all" />
<type fullname="UnityEngine.Localization.Tables.AssetTable" preserve="all" />
<type fullname="UnityEngine.Localization.Tables.SharedTableData" preserve="all" />
<type fullname="UnityEngine.Localization.Tables.StringTable" preserve="all" />
<type fullname="UnityEngine.Localization.Events.UnityEventSprite" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Localization.Events.UnityEventString" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Localization.LocalizedSprite" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Localization.LocalizedString" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Localization.SmartFormat.PersistentVariables.StringVariable" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Localization.SmartFormat.PersistentVariables.VariableNameValuePair" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Localization.Tables.TableEntryReference" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Localization.Tables.TableReference" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Localization.LocaleIdentifier" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Localization.Metadata.MetadataCollection" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Localization.Metadata.AssetTypeMetadata" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Localization.Metadata.Comment" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Localization.Tables.DistributedUIDGenerator" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Localization.Tables.SharedTableData/SharedTableEntry" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Localization.Metadata.SmartFormatTag" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Localization.Tables.TableEntryData" preserve="nothing" serialized="true" />
</assembly>
<assembly fullname="Unity.RenderPipelines.Core.Runtime, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="UnityEngine.Rendering.Volume" preserve="all" />
<type fullname="UnityEngine.Rendering.VolumeProfile" preserve="all" />
<type fullname="UnityEngine.Rendering.BoolParameter" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Rendering.ClampedFloatParameter" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Rendering.ColorParameter" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Rendering.FloatParameter" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Rendering.Vector2Parameter" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Rendering.ClampedIntParameter" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Rendering.NoInterpClampedFloatParameter" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Rendering.NoInterpClampedIntParameter" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Rendering.NoInterpColorParameter" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Rendering.Texture2DParameter" preserve="nothing" serialized="true" />
</assembly>
<assembly fullname="Unity.RenderPipelines.Universal.Runtime, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="UnityEngine.Rendering.Universal.ColorAdjustments" preserve="all" />
<type fullname="UnityEngine.Rendering.Universal.Light2D" preserve="all" />
<type fullname="UnityEngine.Rendering.Universal.UniversalAdditionalCameraData" preserve="all" />
<type fullname="UnityEngine.Rendering.Universal.Vignette" preserve="all" />
<type fullname="UnityEngine.Rendering.Universal.TemporalAA/Settings" preserve="nothing" serialized="true" />
</assembly>
<assembly fullname="Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" preserve="all">
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider" preserve="all" />
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.AtlasSpriteProvider" preserve="all" />
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider" preserve="all" />
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.InstanceProvider" preserve="all" />
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.LegacyResourcesProvider" preserve="all" />
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.SceneProvider" preserve="all" />
</assembly>
<assembly fullname="Unity.TextMeshPro, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="TMPro.TextMeshPro" preserve="all" />
<type fullname="TMPro.TextMeshProUGUI" preserve="all" />
<type fullname="TMPro.TMP_Dropdown" preserve="all" />
<type fullname="TMPro.TMP_FontAsset" preserve="all" />
<type fullname="TMPro.TMP_InputField" preserve="all" />
<type fullname="TMPro.FaceInfo_Legacy" preserve="nothing" serialized="true" />
<type fullname="TMPro.FontAssetCreationSettings" preserve="nothing" serialized="true" />
<type fullname="TMPro.KerningTable" preserve="nothing" serialized="true" />
<type fullname="TMPro.TMP_Character" preserve="nothing" serialized="true" />
<type fullname="TMPro.TMP_Dropdown/DropdownEvent" preserve="nothing" serialized="true" />
<type fullname="TMPro.TMP_Dropdown/OptionData" preserve="nothing" serialized="true" />
<type fullname="TMPro.TMP_Dropdown/OptionDataList" preserve="nothing" serialized="true" />
<type fullname="TMPro.TMP_FontFeatureTable" preserve="nothing" serialized="true" />
<type fullname="TMPro.TMP_FontWeightPair" preserve="nothing" serialized="true" />
<type fullname="TMPro.TMP_GlyphAdjustmentRecord" preserve="nothing" serialized="true" />
<type fullname="TMPro.TMP_GlyphPairAdjustmentRecord" preserve="nothing" serialized="true" />
<type fullname="TMPro.TMP_GlyphValueRecord" preserve="nothing" serialized="true" />
<type fullname="TMPro.TMP_InputField/OnChangeEvent" preserve="nothing" serialized="true" />
<type fullname="TMPro.TMP_InputField/SelectionEvent" preserve="nothing" serialized="true" />
<type fullname="TMPro.TMP_InputField/SubmitEvent" preserve="nothing" serialized="true" />
<type fullname="TMPro.TMP_InputField/TextSelectionEvent" preserve="nothing" serialized="true" />
<type fullname="TMPro.TMP_InputField/TouchScreenKeyboardEvent" preserve="nothing" serialized="true" />
<type fullname="TMPro.VertexGradient" preserve="nothing" serialized="true" />
</assembly>
<assembly fullname="Unity.Timeline, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="UnityEngine.Timeline.ActivationPlayableAsset" preserve="all" />
<type fullname="UnityEngine.Timeline.ActivationTrack" preserve="all" />
<type fullname="UnityEngine.Timeline.AnimationPlayableAsset" preserve="all" />
<type fullname="UnityEngine.Timeline.AnimationTrack" preserve="all" />
<type fullname="UnityEngine.Timeline.GroupTrack" preserve="all" />
<type fullname="UnityEngine.Timeline.MarkerTrack" preserve="all" />
<type fullname="UnityEngine.Timeline.SignalAsset" preserve="all" />
<type fullname="UnityEngine.Timeline.SignalEmitter" preserve="all" />
<type fullname="UnityEngine.Timeline.SignalReceiver" preserve="all" />
<type fullname="UnityEngine.Timeline.SignalTrack" preserve="all" />
<type fullname="UnityEngine.Timeline.TimelineAsset" preserve="all" />
<type fullname="UnityEngine.Timeline.MarkerList" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Timeline.TimelineAsset/EditorSettings" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Timeline.TimelineClip" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Timeline.SignalReceiver/EventKeyValue" preserve="nothing" serialized="true" />
</assembly>
<assembly fullname="UnityEngine.AnimationModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="UnityEngine.AnimationClip" preserve="all" />
<type fullname="UnityEngine.Animator" preserve="all" />
<type fullname="UnityEngine.RuntimeAnimatorController" preserve="all" />
</assembly>
<assembly fullname="UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="UnityEngine.Camera" preserve="all" />
<type fullname="UnityEngine.ComputeShader" preserve="all" />
<type fullname="UnityEngine.GameObject" preserve="all" />
<type fullname="UnityEngine.LightmapSettings" preserve="all" />
<type fullname="UnityEngine.LightProbes" preserve="all" />
<type fullname="UnityEngine.LineRenderer" preserve="all" />
<type fullname="UnityEngine.Material" preserve="all" />
<type fullname="UnityEngine.Mesh" preserve="all" />
<type fullname="UnityEngine.MeshFilter" preserve="all" />
<type fullname="UnityEngine.MeshRenderer" preserve="all" />
<type fullname="UnityEngine.MonoBehaviour" preserve="all" />
<type fullname="UnityEngine.Object" preserve="all" />
<type fullname="UnityEngine.RectTransform" preserve="all" />
<type fullname="UnityEngine.RenderSettings" preserve="all" />
<type fullname="UnityEngine.RenderTexture" preserve="all" />
<type fullname="UnityEngine.Shader" preserve="all" />
<type fullname="UnityEngine.Sprite" preserve="all" />
<type fullname="UnityEngine.SpriteRenderer" preserve="all" />
<type fullname="UnityEngine.Texture2D" preserve="all" />
<type fullname="UnityEngine.Transform" preserve="all" />
<type fullname="UnityEngine.U2D.SpriteAtlas" preserve="all" />
<type fullname="UnityEngine.Events.ArgumentCache" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Events.PersistentCallGroup" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Events.PersistentListenerMode" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Events.UnityEvent" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Events.UnityEvent`2[System.String,System.String]" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.RectOffset" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Events.UnityEvent`1[System.Int32]" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Events.UnityEvent`2[System.Single,System.Single]" preserve="nothing" serialized="true" />
</assembly>
<assembly fullname="UnityEngine.DirectorModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="UnityEngine.Playables.PlayableDirector" preserve="all" />
</assembly>
<assembly fullname="UnityEngine.ParticleSystemModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="UnityEngine.ParticleSystem" preserve="all" />
<type fullname="UnityEngine.ParticleSystemRenderer" preserve="all" />
</assembly>
<assembly fullname="UnityEngine.Physics2DModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="UnityEngine.BoxCollider2D" preserve="all" />
<type fullname="UnityEngine.CircleCollider2D" preserve="all" />
<type fullname="UnityEngine.PolygonCollider2D" preserve="all" />
<type fullname="UnityEngine.Rigidbody2D" preserve="all" />
</assembly>
<assembly fullname="UnityEngine.PhysicsModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="UnityEngine.BoxCollider" preserve="all" />
<type fullname="UnityEngine.Rigidbody" preserve="all" />
</assembly>
<assembly fullname="UnityEngine.SpriteMaskModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="UnityEngine.SpriteMask" preserve="all" />
</assembly>
<assembly fullname="UnityEngine.SpriteShapeModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="UnityEngine.U2D.SpriteShapeRenderer" preserve="all" />
</assembly>
<assembly fullname="UnityEngine.TextRenderingModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="UnityEngine.Font" preserve="all" />
</assembly>
<assembly fullname="UnityEngine.UI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="UnityEngine.EventSystems.EventSystem" preserve="all" />
<type fullname="UnityEngine.EventSystems.Physics2DRaycaster" preserve="all" />
<type fullname="UnityEngine.EventSystems.StandaloneInputModule" preserve="all" />
<type fullname="UnityEngine.UI.Button" preserve="all" />
<type fullname="UnityEngine.UI.CanvasScaler" preserve="all" />
<type fullname="UnityEngine.UI.ContentSizeFitter" preserve="all" />
<type fullname="UnityEngine.UI.GraphicRaycaster" preserve="all" />
<type fullname="UnityEngine.UI.GridLayoutGroup" preserve="all" />
<type fullname="UnityEngine.UI.HorizontalLayoutGroup" preserve="all" />
<type fullname="UnityEngine.UI.Image" preserve="all" />
<type fullname="UnityEngine.UI.LayoutElement" preserve="all" />
<type fullname="UnityEngine.UI.Mask" preserve="all" />
<type fullname="UnityEngine.UI.Outline" preserve="all" />
<type fullname="UnityEngine.UI.RawImage" preserve="all" />
<type fullname="UnityEngine.UI.RectMask2D" preserve="all" />
<type fullname="UnityEngine.UI.Scrollbar" preserve="all" />
<type fullname="UnityEngine.UI.ScrollRect" preserve="all" />
<type fullname="UnityEngine.UI.Slider" preserve="all" />
<type fullname="UnityEngine.UI.Text" preserve="all" />
<type fullname="UnityEngine.UI.Toggle" preserve="all" />
<type fullname="UnityEngine.UI.VerticalLayoutGroup" preserve="all" />
<type fullname="UnityEngine.UI.AnimationTriggers" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.UI.Button/ButtonClickedEvent" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.UI.ColorBlock" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.UI.FontData" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.UI.MaskableGraphic/CullStateChangedEvent" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.UI.Navigation" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.UI.ScrollRect/ScrollRectEvent" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.UI.Scrollbar/ScrollEvent" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.UI.Slider/SliderEvent" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.UI.SpriteState" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.UI.Toggle/ToggleEvent" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.EventSystems.EventTrigger/Entry" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.EventSystems.EventTrigger/TriggerEvent" preserve="nothing" serialized="true" />
</assembly>
<assembly fullname="UnityEngine.UIModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="UnityEngine.Canvas" preserve="all" />
<type fullname="UnityEngine.CanvasGroup" preserve="all" />
<type fullname="UnityEngine.CanvasRenderer" preserve="all" />
</assembly>
<assembly fullname="UnityEngine.VFXModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="UnityEngine.VFX.VFXRenderer" preserve="all" />
<type fullname="UnityEngine.VFX.VisualEffect" preserve="all" />
<type fullname="UnityEngine.VFX.VisualEffectAsset" preserve="all" />
</assembly>
<assembly fullname="VolFx, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="VolFx.AdjustmentsVol" preserve="all" />
<type fullname="VolFx.AsciiVol" preserve="all" />
<type fullname="VolFx.BloomVol" preserve="all" />
<type fullname="VolFx.BlurVol" preserve="all" />
<type fullname="VolFx.ChromaticVol" preserve="all" />
<type fullname="VolFx.DistortVol" preserve="all" />
<type fullname="VolFx.DitherVol" preserve="all" />
<type fullname="VolFx.GlitchVol" preserve="all" />
<type fullname="VolFx.OutlineVol" preserve="all" />
<type fullname="VolFx.PixelationVol" preserve="all" />
<type fullname="VolFx.VhsVol" preserve="all" />
<type fullname="VolFx.CurveParameter" preserve="nothing" serialized="true" />
<type fullname="VolFx.CurveValue" preserve="nothing" serialized="true" />
<type fullname="VolFx.DitherVol/NoiseModeParameter" preserve="nothing" serialized="true" />
<type fullname="VolFx.GradientParameter" preserve="nothing" serialized="true" />
<type fullname="VolFx.GradientValue" preserve="nothing" serialized="true" />
<type fullname="VolFx.OutlineVol/ModeParameter" preserve="nothing" serialized="true" />
<type fullname="VolFx.OutlineVol/RemapParameter" preserve="nothing" serialized="true" />
</assembly>
<assembly fullname="YarnSpinner.Unity, Version=2.4.2.0, Culture=neutral, PublicKeyToken=null">
<type fullname="Yarn.Unity.DialogueRunner" preserve="all" />
<type fullname="Yarn.Unity.Localization" preserve="all" />
<type fullname="Yarn.Unity.YarnProject" preserve="all" />
<type fullname="Yarn.Unity.DialogueRunner/StringUnityEvent" preserve="nothing" serialized="true" />
<type fullname="Yarn.Unity.UnityLocalization.LineMetadata" preserve="nothing" serialized="true" />
<type fullname="Yarn.Unity.LineMetadata" preserve="nothing" serialized="true" />
<type fullname="Yarn.Unity.LineMetadata/StringDictionary" preserve="nothing" serialized="true" />
<type fullname="Yarn.Unity.Localization/AssetDictionary" preserve="nothing" serialized="true" />
<type fullname="Yarn.Unity.Localization/StringDictionary" preserve="nothing" serialized="true" />
</assembly>
<assembly fullname="UnityEngine.TextCoreFontEngineModule">
<type fullname="UnityEngine.TextCore.FaceInfo" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.TextCore.Glyph" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.TextCore.GlyphMetrics" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.TextCore.GlyphRect" preserve="nothing" serialized="true" />
</assembly>
</linker>
+13 -9
View File
@@ -463,15 +463,19 @@ MonoBehaviour:
m_PostInfinity: 2
m_RotationOrder: 4
_filter:
- 0.007228916
- 0.043749984
- 0.12409639
- 0.20444278
- 0.24096388
- 0.20444278
- 0.12409639
- 0.043749984
- 0.007228916
- 0.0048309183
- 0.016401261
- 0.04532712
- 0.08293076
- 0.12053438
- 0.14946026
- 0.1610306
- 0.14946026
- 0.12053438
- 0.08293076
- 0.04532712
- 0.016401261
- 0.0048309183
--- !u!114 &-6086986437317696584
MonoBehaviour:
m_ObjectHideFlags: 3
+17 -1
View File
@@ -18,7 +18,7 @@ position: 205,199
//【流程相关】
//gameStage
<<declare $gameStage=1>>
<<declare $gameStage=0>>
//每个模块的首次检查
<<declare $emoChecked=false>>
@@ -66,10 +66,26 @@ colorID: 1
<<jump Stage6>>
<<elseif $gameStage == 7>>
<<NextYarn>>
<<elseif $gameStage == 100>>
<<jump 测试>>
//结束,进入下一个Yarn
<<endif>>
===
title: 测试
description: 脚本跳转中心,GameStage在此处处理,理论上不在其他地方读取GameStage
position: 205,446
colorID: 1
---
//每次进入维修界面就自动保存
<<switch_fix_system_to "Clinic">>
<<fade_out 1>>
<<switch_fix_system_to "LanguageAnalysis">>
<<memory_decryptor_start TestSuccess>>
===
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -9433,7 +9433,7 @@ Transform:
m_GameObject: {fileID: 519420028}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: -10}
m_LocalPosition: {x: -0.009999275, y: 0, z: -10}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
@@ -11969,7 +11969,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: feee73ba51d6e1a4da81ed7f94ab4694, type: 3}
m_Name:
m_EditorClassIdentifier:
runMode: 0
runMode: 1
firstTalkSo: {fileID: 11400000, guid: 2f763a2c04ecafb4981379b7363f9791, type: 2}
testTalkSo: {fileID: 11400000, guid: 8ce86974047bb404c9096e70a606355a, type: 2}
saveFileName: "20250901_204445_PeipeiDay1_\u8BB0\u5FC6\u68C0\u67E5"
@@ -62,6 +62,7 @@ namespace AibisDream.FixSystem
FixState.MemoryCut => new MemoryCutState(),
FixState.LogicCut => new LogicCutState(),
FixState.Dream => new DreamState(),
FixState.LanguageAnalysis => new LanguageAnalysisState(),
_ => new DefaultState()
};
@@ -87,6 +88,7 @@ namespace AibisDream.FixSystem
MemoryCut,
LogicCut,
Dream,
LanguageAnalysis,
}
public struct DefaultState : IState<FixState>
@@ -698,4 +700,61 @@ namespace AibisDream.FixSystem
yield return UIManager.Instance.GetPanel<PlayToolPanel>().FadeOutAsync(0.1f);
}
}
public struct LanguageAnalysisState : IState<FixState>
{
public FixState GetState => FixState.LanguageAnalysis;
public void SetArgs(string args)
{
// 无
}
public IEnumerator Enter()
{
// 获取语言分析系统
var languageAnalysisManager = FixSystemCenter.SystemDic.Get<AibisDream.MiniGame.Language.LanguageAnalysisManager>();
if (languageAnalysisManager == null)
{
Debug.LogError("[LanguageAnalysisState] LanguageAnalysisManager 未找到!");
yield break;
}
// 先切 BodyModule(如果需要)
if (!CameraKit.Instance.EqualToCameraState(CameraEnum.BodyModule))
{
var bodyModule = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
bodyModule.SwitchModuleGroup(ModuleState.Head);
yield return CameraKit.Instance.SwitchCamera(CameraEnum.BodyModule);
}
// 打开语言分析系统视图
languageAnalysisManager.OpenView();
// 切换到语言分析相机
yield return CameraKit.Instance.SwitchCamera(CameraEnum.LanguageAnalysis);
}
public IEnumerator Exit()
{
var languageAnalysisManager = FixSystemCenter.SystemDic.Get<AibisDream.MiniGame.Language.LanguageAnalysisManager>();
var bodyModuleSystem = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
if (languageAnalysisManager != null)
{
// 关闭语言分析系统视图
languageAnalysisManager.CloseView();
}
// 重置身体模块
if (bodyModuleSystem != null)
{
bodyModuleSystem.ResetPlug();
}
yield break;
}
}
}
@@ -421,6 +421,8 @@ namespace AibisDream.Framework
Subway,
SubwayTV,
Language,
LanguageDeep
LanguageDeep,
LanguageAnalysis,
LanguageAnalysisDeep
}
}
@@ -8,13 +8,15 @@ using System.Collections.Generic;
namespace AibisDream
{
/// <summary>
/// 表达系统顶层管理器(原 ExpressionManager,现改为语言粒子系统)
/// 表达系统管理器(粒子表达系统)
/// </summary>
public class ExpressionManager : MonoBehaviour
{
[Header("系统组件")]
[SerializeField] private LanguageParticleManager particleManager;
[SerializeField] private GameObject expressionView;
// 内部组件引用(自动查找)
private LanguageParticleManager particleManager;
[Header("默认配置")]
[SerializeField] private List<string> defaultAnxietyPhrases = new List<string>
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5cc257c414a9bbf45a5bc5ca2a220355
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,165 @@
# 自动配置功能说明
## ✨ 新功能:自动创建默认配置
### 概述
现在 **无需手动创建配置文件**`MemoryDecryptorMode` 会在启动时自动检测,如果没有配置文件,将自动创建并使用默认配置。
## 🚀 使用方式
### 最简单的方式(推荐)
1. 添加 `MemoryDecryptorMode` 组件到场景
2. **不设置** Config 字段(留空)
3. 运行游戏
系统会自动:
- ✅ 检测到配置为空
- ✅ 创建默认配置(包含完整示例数据)
- ✅ 启动系统
### 自定义配置方式
如果需要自定义配置:
1. 创建配置文件:`Create > AibisDream > Language > Memory Decryptor Config`
2. 自定义笑话、目标、视觉效果等
3.`MemoryDecryptorMode` 的 Config 字段拖入配置文件
## 📦 默认配置包含
自动创建的默认配置包含:
### 网格设置
- 20x20 网格
- 单元格大小:45px
- 单元格间隔:2px
### 视觉效果
- 磷光绿主色调 (#33ff00)
- CRT 复古风格
- 解码器条:5x3 青色边框
### 游戏数据
- **笑话数据**:18+ 个销售员笑话(足够填充网格)
- **目标数据**3个目标配置
- Fear Solution (正确答案)
- Fear Decoy (诱饵)
- Sadness Solution (诱饵)
### 解码器模板
- 3个孔的 5x3 模板
- 句子:"[孔1]要来了,不合格[孔2]被骂了,这让我很[孔3]"
### 正确答案
- **位置**(3, 5)
- **频率**90Hz (FEAR)
- **词汇**:临检、英里、担心
- **完整句**:"临检要来了,不合格英里被骂了,这让我很担心"
## 💡 工作原理
```csharp
private void Awake()
{
// 如果没有配置文件,自动创建默认配置
if (config == null)
{
Debug.LogWarning("[MemoryDecryptorMode] 未设置配置文件,使用默认配置");
config = MemoryDecryptorConfigExample.CreateDefaultConfig();
}
// ... 其他初始化
}
```
## 🎯 测试流程
### 零配置测试
1. **创建场景对象**
```
LanguageAnalysisManager
└── MemoryDecryptorMode (什么都不设置!)
├── MemoryDecryptorRenderer
└── MemoryDecryptorInputHandler
```
2. **连接 UI 组件**(只需要连接 UI 引用)
3. **运行测试**
```yarn
<<enter_language_analysis>>
<<memory_decryptor_start>>
```
4. **开始游戏**
- 频率调到 90Hz
- 解码器条拖到 (3, 5) 位置
- 点击验证 → 成功!
## 📊 配置对比
| 方式 | 配置步骤 | 自定义程度 | 适用场景 |
|------|---------|-----------|----------|
| **自动配置** | 0 步 | ❌ 固定 | 快速测试、原型验证 |
| **手动配置** | 3-5 步 | ✅ 完全自定义 | 正式关卡、特殊需求 |
## 🔧 何时使用自定义配置
建议在以下情况创建自定义配置:
- ✅ 需要不同的笑话内容
- ✅ 需要不同的目标位置和词汇
- ✅ 需要调整网格大小
- ✅ 需要自定义视觉风格
- ✅ 制作正式关卡
## ⚙️ 自定义配置示例
```csharp
// 创建自己的配置
var myConfig = ScriptableObject.CreateInstance<MemoryDecryptorConfig>();
myConfig.gridWidth = 15; // 改为 15x15
myConfig.gridHeight = 15;
myConfig.completeJokes = new List<string>
{
"我的自定义笑话内容...",
// ...
};
// 保存为资源
#if UNITY_EDITOR
UnityEditor.AssetDatabase.CreateAsset(myConfig, "Assets/MyConfig.asset");
#endif
```
## 🎉 优势
1. **即插即用** - 添加组件就能用
2. **快速迭代** - 无需反复创建配置
3. **降低门槛** - 新手更容易上手
4. **灵活切换** - 测试用默认,正式用自定义
## ⚠️ 注意事项
1. **运行时配置**:自动创建的配置是运行时对象,不会保存到磁盘
2. **每次启动重新创建**:每次运行游戏都会重新创建默认配置
3. **持久化需求**:如果需要保存配置修改,请创建 ScriptableObject 资源
## 📝 日志提示
当使用自动配置时,Console 会显示:
```
[MemoryDecryptorMode] 未设置配置文件,使用默认配置
```
这是正常提示,不是错误!
---
**现在你可以直接添加组件开始测试,无需任何配置!** 🚀
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 6eaa6cbafc9dfa74d9249d4217cbd142
guid: 8e2ee6d30ac820343b7b75afc919d4ea
TextScriptImporter:
externalObjects: {}
userData:
@@ -0,0 +1,228 @@
# 语言分析系统 - 清晰架构说明
## ✅ 你应该使用的系统:LanguageAnalysisManager
### 系统位置
```
Assets/Scripts/MiniGame/HuoShan/Language/
└── LanguageAnalysis/
└── LanguageAnalysisManager.cs # ✅ 这是主系统!
```
### 使用方法
#### 在 Yarn 中
```yarn
// 进入语言分析系统
<<enter_language_analysis>>
// 启动记忆解密器
<<memory_decryptor_start Success>>
// 退出语言分析系统
<<exit_language_analysis>>
```
#### 在场景中
```
LanguageAnalysisManager
├── Language Analysis View (GameObject)
│ └── MemoryDecryptorMode
│ ├── Config: 留空即可(自动使用默认配置)
│ ├── MemoryDecryptorRenderer
│ └── MemoryDecryptorInputHandler
├── Language Analysis Camera
└── Language Analysis Deep Camera
```
## 📋 核心流程图
```
游戏开始
任意状态 (Clinic / BodyModule / etc.)
<<enter_language_analysis>> ← Yarn 命令
[FixStateMachine 切换状态]
FixState.LanguageAnalysis ← 进入此状态
LanguageAnalysisState.Enter()
- 切换相机
- 打开 Analysis View
- 淡入淡出效果
<<memory_decryptor_start>> ← Yarn 命令
LanguageAnalysisManager.StartMemoryDecryptor()
MemoryDecryptorMode.StartSystem()
- Boot 序列
- 加载网格
- 显示笑话
玩家操作界面
- 调整频率
- 拖动解码器
- 点击验证
成功/失败
Yarn 节点跳转 (Success / Success_Fail)
<<memory_decryptor_stop>> ← Yarn 命令
<<exit_language_analysis>> ← Yarn 命令
[FixStateMachine 切换状态]
返回之前的状态
```
## 🔑 关键组件
### 1. LanguageAnalysisManager (主系统)
**位置**: `LanguageAnalysis/LanguageAnalysisManager.cs`
**职责**:
- 管理语言分析视图
- 注册到 FixSystemCenter
- 注册相机
- 管理子系统(MemoryDecryptorMode
**公开方法**:
```csharp
void OpenView() // 打开视图
void CloseView() // 关闭视图
void StartMemoryDecryptor(string node) // 启动解密器
void StartMemoryDecryptorFiltering() // 开始过滤
void SetMemoryDecryptorFrequency(float f) // 设置频率
void CheckMemoryDecryptorMatch() // 检查匹配
```
### 2. LanguageAnalysisState (状态机状态)
**位置**: `FixSystemNew/FixStateMachine.cs`
**职责**:
- Enter: 切换相机、打开视图、淡入淡出
- Exit: 关闭视图、重置 Plug
### 3. MemoryDecryptorMode (解密器核心)
**位置**: `MemoryDecryptor/MemoryDecryptorMode.cs`
**职责**:
- 游戏逻辑
- 网格管理
- 频率调谐
- 匹配检测
### 4. Yarn 命令
**位置**:
- `LanguageAnalysis/LanguageAnalysisYarnCommands.cs` (状态切换)
- `MemoryDecryptor/MemoryDecryptorYarnCommands.cs` (解密器控制)
## 📝 调用链
### Yarn → Manager → Mode
```
<<enter_language_analysis>>
LanguageAnalysisYarnCommands.EnterLanguageAnalysis()
FixStateMachine.SwitchState(LanguageAnalysis)
LanguageAnalysisState.Enter()
LanguageAnalysisManager.OpenView()
---
<<memory_decryptor_start>>
MemoryDecryptorYarnCommands.StartMemoryDecryptor()
LanguageAnalysisManager.StartMemoryDecryptor()
MemoryDecryptorMode.StartSystem()
```
## ⚡ 快速配置(零配置模式)
### 最简场景设置
1. **创建 LanguageAnalysisManager 对象**
2. **添加子对象和组件**(参考 SETUP_GUIDE.md
3. **运行游戏**
4. **在 Yarn 中使用:**
```yarn
<<enter_language_analysis>>
<<memory_decryptor_start>>
```
就这么简单!✨
## ❌ 已移除的内容
从 ExpressionManager 中移除了:
-`AnalysisMode` 枚举
-`memoryDecryptorMode` 字段
-`SwitchMode()` 方法
-`SwitchToParticleMode()` 方法
-`SwitchToMemoryDecryptorMode()` 方法
-`StartMemoryDecryptor()` 方法
-`SetMemoryDecryptorFrequency()` 方法
-`StartMemoryDecryptorFiltering()` 方法
-`CheckMemoryDecryptorMatch()` 方法
**原因**: 记忆解密器现在完全属于 LanguageAnalysisManager
## ✅ 现在的清晰架构
```
ExpressionManager (表达系统)
├── 只管理粒子表达
└── 不再管理记忆解密器
LanguageAnalysisManager (语言分析系统)
├── 管理所有语言分析相关功能
└── 包含记忆解密器
```
## 🎯 使用建议
### 表达情绪时 → ExpressionManager
```yarn
<<open_expression>>
<<start_expression "焦虑短句" "目标句子">>
```
### 解密语言时 → LanguageAnalysisManager
```yarn
<<enter_language_analysis>>
<<memory_decryptor_start>>
```
## 📚 文档导航
**开始使用语言分析系统:**
1. 📖 `LanguageAnalysis/QUICK_REFERENCE.md` - 快速上手
2. 🔧 `LanguageAnalysis/SETUP_GUIDE.md` - 详细设置
3. 🧪 `LanguageAnalysis/TestLanguageAnalysis.yarn` - 测试示例
**了解记忆解密器:**
1. 📖 `MemoryDecryptor/README.md` - 详细说明
2.`MemoryDecryptor/QUICKSTART.md` - 快速入门
3.`MemoryDecryptor/AUTO_CONFIG_FEATURE.md` - 自动配置
---
## 💡 记住
**只使用 LanguageAnalysisManager**
**ExpressionManager 已经不再包含记忆解密器了!**
**两个系统完全独立,不要混用!**
---
**🚀 开始使用:** `<<enter_language_analysis>>`
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4beadb9460face44089b05f4978e5f76
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c9eae56ae4642284cac2cde2616de065
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,151 @@
using UnityEngine;
using AibisDream.FixSystem;
using Cinemachine;
using AibisDream.Framework;
using AibisDream.MiniGame.Language.MemoryDecryptor;
namespace AibisDream.MiniGame.Language
{
/// <summary>
/// 语言分析系统管理器
/// 独立的语言分析系统,包含记忆解密器等分析模式
/// </summary>
public class LanguageAnalysisManager : MonoBehaviour
{
[Header("系统视图")]
[SerializeField] private GameObject analysisView;
[Header("记忆解密器组件")]
private MemoryDecryptorMode memoryDecryptorMode;
void Start()
{
// 注册到系统字典
FixSystemCenter.SystemDic.Register(this);
// 查找视图
if (analysisView == null)
{
analysisView = transform.Find("Language Analysis View")?.gameObject;
}
if (analysisView != null)
{
analysisView.SetActive(false);
}
// 查找记忆解密器
if (memoryDecryptorMode == null)
{
memoryDecryptorMode = GetComponentInChildren<MemoryDecryptorMode>(true);
}
// 注册相机
var virtualCam = transform.Find("Language Analysis Camera")?.GetComponent<ICinemachineCamera>();
if (virtualCam != null)
{
CameraKit.Instance.RegisterCamera(CameraEnum.LanguageAnalysis, virtualCam);
}
var virtualCamDeep = transform.Find("Language Analysis Deep Camera")?.GetComponent<ICinemachineCamera>();
if (virtualCamDeep != null)
{
CameraKit.Instance.RegisterCamera(CameraEnum.LanguageAnalysisDeep, virtualCamDeep);
}
}
/// <summary>
/// 打开语言分析视图
/// </summary>
public void OpenView()
{
if (analysisView != null)
{
analysisView.SetActive(true);
}
}
/// <summary>
/// 关闭语言分析视图
/// </summary>
public void CloseView()
{
if (analysisView != null)
{
analysisView.SetActive(false);
}
StopAllSystems();
}
/// <summary>
/// 停止所有分析系统
/// </summary>
public void StopAllSystems()
{
if (memoryDecryptorMode != null)
{
memoryDecryptorMode.StopSystem();
}
}
#region
/// <summary>
/// 启动记忆解密器
/// </summary>
public void StartMemoryDecryptor(string completionNodeName = null)
{
if (memoryDecryptorMode != null)
{
memoryDecryptorMode.StartSystem(completionNodeName);
}
else
{
Debug.LogWarning("[LanguageAnalysisManager] 记忆解密器未设置!");
}
}
/// <summary>
/// 开始过滤
/// </summary>
public void StartMemoryDecryptorFiltering()
{
if (memoryDecryptorMode != null)
{
memoryDecryptorMode.StartFiltering();
}
}
/// <summary>
/// 设置频率
/// </summary>
public void SetMemoryDecryptorFrequency(float frequency)
{
if (memoryDecryptorMode != null)
{
memoryDecryptorMode.SetFrequency(frequency);
}
}
/// <summary>
/// 检查匹配
/// </summary>
public void CheckMemoryDecryptorMatch()
{
if (memoryDecryptorMode != null)
{
memoryDecryptorMode.CheckMatch();
}
}
#endregion
private void OnDestroy()
{
CameraKit.Instance?.UnRegisterCamera(CameraEnum.LanguageAnalysis);
CameraKit.Instance?.UnRegisterCamera(CameraEnum.LanguageAnalysisDeep);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a851d4a1c0297384093b20da97237333
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,78 @@
using System.Collections;
using UnityEngine;
using Yarn.Unity;
using AibisDream.FixSystem;
using AibisDream.Framework;
namespace AibisDream.MiniGame.Language
{
/// <summary>
/// 语言分析系统 Yarn 命令
/// </summary>
public static class LanguageAnalysisYarnCommands
{
private static FixStateMachine FixStateMachine => FixSystemCenter.SystemDic.Get<FixStateMachine>();
/// <summary>
/// 进入语言分析系统状态
/// <<enter_language_analysis>>
/// </summary>
[YarnCommand("enter_language_analysis")]
public static void EnterLanguageAnalysis()
{
if (FixStateMachine == null)
{
Debug.LogError("[LanguageAnalysis] FixStateMachine 未找到!");
return;
}
// 通过协程切换状态
MonoBehaviourHelper.Instance.StartCoroutine(SwitchToLanguageAnalysisState());
}
/// <summary>
/// 退出语言分析系统状态
/// <<exit_language_analysis>>
/// </summary>
[YarnCommand("exit_language_analysis")]
public static void ExitLanguageAnalysis()
{
if (FixStateMachine == null)
{
Debug.LogError("[LanguageAnalysis] FixStateMachine 未找到!");
return;
}
// 切换回诊所状态或其他默认状态
MonoBehaviourHelper.Instance.StartCoroutine(FixStateMachine.SwitchState(FixState.Clinic));
}
private static IEnumerator SwitchToLanguageAnalysisState()
{
yield return FixStateMachine.SwitchState(FixState.LanguageAnalysis);
}
}
/// <summary>
/// MonoBehaviour 辅助类,用于启动协程
/// </summary>
public class MonoBehaviourHelper : MonoBehaviour
{
private static MonoBehaviourHelper _instance;
public static MonoBehaviourHelper Instance
{
get
{
if (_instance == null)
{
GameObject go = new GameObject("MonoBehaviourHelper");
_instance = go.AddComponent<MonoBehaviourHelper>();
DontDestroyOnLoad(go);
}
return _instance;
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8d25aa7540a2769478e93a4affce5d99
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,147 @@
# 语言分析系统 - 快速参考卡
## 🚀 一句话总结
语言分析系统是独立的 FixState,包含记忆解密器等分析模式,通过 `<<enter_language_analysis>>` 进入。
## 📋 快速命令
### 进入/退出系统
```yarn
<<enter_language_analysis>> # 进入语言分析状态
<<exit_language_analysis>> # 退出语言分析状态
```
### 记忆解密器
```yarn
<<memory_decryptor_start SuccessNode>> # 启动
<<memory_decryptor_filter_start>> # 开始过滤
<<memory_decryptor_set_frequency 90>> # 设置频率
<<memory_decryptor_check>> # 检查匹配
<<memory_decryptor_stop>> # 停止
```
## 🎯 最简测试
```yarn
title: Test
---
<<enter_language_analysis>>
<<memory_decryptor_start Success>>
===
title: Success
---
成功!
<<memory_decryptor_stop>>
<<exit_language_analysis>>
===
title: Success_Fail
---
失败,再试试。
===
```
## 🔧 场景设置
```
LanguageAnalysisManager
├── Language Analysis View (视图)
│ ├── Canvas (UI)
│ └── MemoryDecryptorMode (脚本 + Config)
├── Language Analysis Camera
└── Language Analysis Deep Camera
```
## 📝 配置清单
- [ ] LanguageAnalysisManager 添加到场景
- [ ] 配置 Analysis View
- [ ] 两个相机设置完成
- [ ] MemoryDecryptorMode 已添加(配置文件可选,自动使用默认)
- [ ] UI 组件已连接
## 🎮 状态流程
```
FixState.Clinic
↓ <<enter_language_analysis>>
FixState.LanguageAnalysis
↓ <<memory_decryptor_start>>
记忆解密器运行中
↓ 成功/失败
Yarn 节点反馈
↓ <<exit_language_analysis>>
FixState.Clinic
```
## ⚡ 常用代码
### C# 获取管理器
```csharp
var manager = FixSystemCenter.SystemDic.Get<LanguageAnalysisManager>();
```
### C# 状态切换
```csharp
yield return FixStateMachine.SwitchState(FixState.LanguageAnalysis);
```
### Yarn 完整示例
```yarn
title: Start
---
准备进入语言分析系统...
<<enter_language_analysis>>
<<memory_decryptor_start GameSuccess>>
现在你可以操作了。
===
title: GameSuccess
---
🎉 解密成功!
<<memory_decryptor_stop>>
<<exit_language_analysis>>
继续游戏...
===
title: GameSuccess_Fail
---
❌ 失败了
-> 再试一次
继续...
-> 放弃
<<memory_decryptor_stop>>
<<exit_language_analysis>>
===
```
## 🐛 快速调试
### 检查系统
```csharp
Debug.Log(FixSystemCenter.SystemDic.Get<LanguageAnalysisManager>() != null);
```
### 检查状态
```csharp
Debug.Log(FixStateMachine.CurState);
```
### 检查相机
```csharp
Debug.Log(CameraKit.Instance.GetCamera(CameraEnum.LanguageAnalysis) != null);
```
## 📚 文档索引
- **详细设置**: `SETUP_GUIDE.md`
- **重构说明**: `REFACTORING_SUMMARY.md`
- **测试对话**: `TestLanguageAnalysis.yarn`
- **记忆解密器**: `../MemoryDecryptor/README.md`
---
**记住:先 `enter` → 再 `start` → 最后 `exit`**
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 7ed94d5eb5b51b04ebce8f9f3299fec0
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,252 @@
# 语言分析系统 (Language Analysis System)
## 📖 概述
语言分析系统是一个独立的游戏系统,通过 `FixStateMachine` 进行状态管理。它包含记忆解密器等多种分析模式,专注于语言相关的解谜玩法。
## 🎯 系统特点
- ✅ **独立系统** - 完全独立于 ExpressionManager
- ✅ **状态管理** - 集成到 FixStateMachine
- ✅ **易于测试** - 可单独进入测试
- ✅ **易于扩展** - 支持多种分析模式
## 📂 文件结构
```
LanguageAnalysis/
├── LanguageAnalysisManager.cs # 主管理器
├── LanguageAnalysisYarnCommands.cs # Yarn 状态切换命令
├── README.md # 本文档
├── QUICK_REFERENCE.md # 快速参考卡
├── SETUP_GUIDE.md # 详细设置指南
├── REFACTORING_SUMMARY.md # 重构说明
└── TestLanguageAnalysis.yarn # 测试对话
```
## 🚀 快速开始
### 1. Yarn 命令
```yarn
// 进入语言分析系统
<<enter_language_analysis>>
// 启动记忆解密器
<<memory_decryptor_start SuccessNode>>
// 退出语言分析系统
<<exit_language_analysis>>
```
### 2. 最简测试
```yarn
title: Test
---
<<enter_language_analysis>>
<<memory_decryptor_start Success>>
===
title: Success
---
成功!
<<memory_decryptor_stop>>
<<exit_language_analysis>>
===
```
## 📋 文档索引
| 文档 | 用途 | 适合人群 |
|------|------|----------|
| `QUICK_REFERENCE.md` | 快速查询命令和用法 | 所有人 |
| `SETUP_GUIDE.md` | 详细的场景设置步骤 | 首次使用者 |
| `REFACTORING_SUMMARY.md` | 重构说明和对比 | 了解架构变更 |
| `TestLanguageAnalysis.yarn` | 测试对话示例 | 测试功能 |
## 🎮 核心组件
### LanguageAnalysisManager
主管理器,负责:
- 系统视图管理
- 相机注册
- 各分析模式的启动和停止
### LanguageAnalysisState
FixState 状态,负责:
- 状态进入时的场景切换
- 相机动画
- 状态退出时的清理
### Yarn Commands
- `enter_language_analysis` - 进入状态
- `exit_language_analysis` - 退出状态
- `memory_decryptor_*` - 记忆解密器命令
## 🔧 技术细节
### 状态机集成
```csharp
public enum FixState
{
// ...
LanguageAnalysis, // 语言分析系统
}
public struct LanguageAnalysisState : IState<FixState>
{
// Enter: 切换相机,打开视图
// Exit: 关闭视图,重置状态
}
```
### 系统注册
```csharp
// 自动注册到 FixSystemCenter
FixSystemCenter.SystemDic.Register(this);
```
### 相机管理
```csharp
public enum CameraEnum
{
LanguageAnalysis, // 主相机
LanguageAnalysisDeep, // 深度相机
}
```
## 📝 使用示例
### 完整流程
```yarn
title: LanguageAnalysisDemo
---
玩家:我想分析这段记忆...
<<enter_language_analysis>>
系统进入中...
<<memory_decryptor_start Demo_Success>>
现在你可以:
1. 调整情绪频率滑块
2. 拖动解码器条
3. 点击"验证对齐"按钮
===
title: Demo_Success
---
🎉 解密成功!
你发现了隐藏的情绪:
"[临检]要来了,不合格[英里]被骂了,这让我很[担心]"
<<memory_decryptor_stop>>
<<exit_language_analysis>>
返回正常状态...
===
title: Demo_Success_Fail
---
❌ 解密失败
频率或位置不太对...
-> 再试一次
<<jump LanguageAnalysisDemo>>
-> 放弃
<<memory_decryptor_stop>>
<<exit_language_analysis>>
算了,先做别的...
===
```
## 🐛 常见问题
### Q: Unity 提示找不到 LanguageAnalysisManager
A: 保存文件后等待 Unity 重新编译。如果问题持续,尝试重启 Unity。
### Q: 进入状态后看不到界面?
A: 检查 Analysis View 是否已设置且激活。
### Q: 相机不切换?
A: 确保两个 Virtual Camera 已正确注册到 CameraKit。
### Q: Yarn 命令不工作?
A: 确保 LanguageAnalysisManager 已添加到场景并注册到 SystemDic。
## 🎨 分析模式
### 当前支持
#### 记忆解密器 (Memory Decryptor)
- 复古 CRT 显示风格
- 频率调谐解谜
- 解码器条拖动
- 笑话/情绪识别
### 未来扩展
可以添加更多分析模式:
- 语法分析器
- 语义地图
- 情绪图谱
- ...
## 📊 系统流程图
```
游戏开始
Clinic / 其他状态
↓ <<enter_language_analysis>>
进入 LanguageAnalysisState
↓ State.Enter()
- 切换到 BodyModule (Head)
- 打开 Analysis View
- 相机动画 (Analysis → AnalysisDeep)
↓ <<memory_decryptor_start>>
记忆解密器 Boot 序列
解密器运行中(玩家操作)
↓ 成功/失败
Yarn 节点反馈
↓ <<exit_language_analysis>>
退出 LanguageAnalysisState
↓ State.Exit()
- 关闭视图
- 重置 Plug
返回 Clinic / 其他状态
```
## ✅ 设置清单
使用前确保:
- [ ] LanguageAnalysisManager 已添加到场景
- [ ] Language Analysis View 已配置
- [ ] 两个 Virtual Camera 已设置
- [ ] MemoryDecryptorMode 及其子组件已配置
- [ ] 配置文件已创建并设置
- [ ] 已测试基本功能
## 🚀 下一步
1. 阅读 `QUICK_REFERENCE.md` 了解常用命令
2. 按照 `SETUP_GUIDE.md` 设置场景
3. 使用 `TestLanguageAnalysis.yarn` 测试
4. 开始创建你的语言分析关卡!
---
**Need Help?** 查看 `SETUP_GUIDE.md` 获取详细步骤
**Quick Start?** 查看 `QUICK_REFERENCE.md` 快速上手
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 2bc354555f638b64498e942697b4e29e
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,287 @@
# 语言分析系统 - 重构总结
## 📅 重构日期:2025-12-04
## 🎯 重构目标
将记忆解密器从 `ExpressionManager` 中分离出来,创建独立的 `LanguageAnalysisManager` 系统,并集成到 `FixStateMachine` 状态机中。
## 🔄 架构变更
### 旧架构 ❌
```
ExpressionManager
├── AnalysisMode 枚举 (Particle / MemoryDecryptor)
├── LanguageParticleManager
└── MemoryDecryptorMode
问题:
- 职责不清晰
- 两个完全不同的系统耦合在一起
- 无法独立测试记忆解密器
- 状态切换逻辑混乱
```
### 新架构 ✅
```
FixStateMachine
├── FixState.Expression → ExpressionManager
│ └── LanguageParticleManager (粒子表达系统)
└── FixState.LanguageAnalysis → LanguageAnalysisManager
└── MemoryDecryptorMode (记忆解密器)
└── 未来可扩展其他分析模式...
优势:
- ✅ 职责清晰分离
- ✅ 独立的状态管理
- ✅ 可以单独测试
- ✅ 符合项目架构规范
```
## 📦 新增文件
### 核心系统
```
Assets/Scripts/MiniGame/HuoShan/Language/LanguageAnalysis/
├── LanguageAnalysisManager.cs # 主管理器
├── LanguageAnalysisYarnCommands.cs # Yarn 状态切换命令
├── LanguageAnalysisState.cs # (已集成到 FixStateMachine.cs)
├── SETUP_GUIDE.md # 设置指南
├── REFACTORING_SUMMARY.md # 本文档
└── TestLanguageAnalysis.yarn # 测试对话
```
### 修改的文件
```
Assets/Scripts/FixSystemNew/FixStateMachine.cs
├── 新增 FixState.LanguageAnalysis 枚举
└── 新增 LanguageAnalysisState 结构体
Assets/Scripts/Framework/OtherKit/CameraKit.cs
├── 新增 CameraEnum.LanguageAnalysis
└── 新增 CameraEnum.LanguageAnalysisDeep
Assets/Scripts/MiniGame/HuoShan/Language/MemoryDecryptor/
└── MemoryDecryptorYarnCommands.cs
└── 从 ExpressionManager 改为 LanguageAnalysisManager
```
## 🎮 Yarn 命令变更
### 新增命令(状态切换)
| 命令 | 功能 | 示例 |
|------|------|------|
| `<<enter_language_analysis>>` | 进入语言分析系统状态 | `<<enter_language_analysis>>` |
| `<<exit_language_analysis>>` | 退出语言分析系统状态 | `<<exit_language_analysis>>` |
### 记忆解密器命令(无变化)
| 命令 | 功能 | 示例 |
|------|------|------|
| `<<memory_decryptor_start>>` | 启动记忆解密器 | `<<memory_decryptor_start Success>>` |
| `<<memory_decryptor_filter_start>>` | 开始过滤 | `<<memory_decryptor_filter_start>>` |
| `<<memory_decryptor_set_frequency>>` | 设置频率 | `<<memory_decryptor_set_frequency 90>>` |
| `<<memory_decryptor_check>>` | 检查匹配 | `<<memory_decryptor_check>>` |
| `<<memory_decryptor_stop>>` | 停止解密器 | `<<memory_decryptor_stop>>` |
### 移除的命令
| 旧命令 | 原因 | 替代方案 |
|--------|------|----------|
| `<<switch_to_particle_mode>>` | 系统分离,不再需要 | 使用状态机切换 |
| `<<switch_to_memory_decryptor_mode>>` | 系统分离,不再需要 | 使用 `enter_language_analysis` |
## 📝 使用流程对比
### 旧流程 ❌
```yarn
// 复杂且不清晰
<<switch_to_memory_decryptor_mode>>
<<memory_decryptor_start>>
// ...
<<switch_to_particle_mode>>
```
### 新流程 ✅
```yarn
// 清晰的状态切换
<<enter_language_analysis>>
<<memory_decryptor_start Success>>
// 玩家操作...
title: Success
---
<<memory_decryptor_stop>>
<<exit_language_analysis>>
===
```
## 🔧 技术改进
### 1. 状态机集成
```csharp
// ✅ 新增 LanguageAnalysisState
public struct LanguageAnalysisState : IState<FixState>
{
public IEnumerator Enter()
{
// 1. 切换到 BodyModule (Head)
// 2. 打开视图
// 3. 相机切换 (LanguageAnalysis → LanguageAnalysisDeep)
// 4. 淡入淡出效果
}
public IEnumerator Exit()
{
// 1. 关闭视图
// 2. 重置 Plug
}
}
```
### 2. 系统注册
```csharp
// ✅ LanguageAnalysisManager 自动注册到 SystemDic
void Start()
{
FixSystemCenter.SystemDic.Register(this);
}
```
### 3. 相机管理
```csharp
// ✅ 新增相机枚举
public enum CameraEnum
{
// ... 其他相机
LanguageAnalysis,
LanguageAnalysisDeep,
}
```
## 📊 对比总结
| 方面 | 旧架构 | 新架构 |
|------|--------|--------|
| **职责** | 混合在一起 | 清晰分离 |
| **状态管理** | 内部枚举 | FixStateMachine |
| **独立测试** | ❌ 困难 | ✅ 简单 |
| **相机** | 共用 Expression 相机 | 独立的 LanguageAnalysis 相机 |
| **Yarn 命令** | 6个 | 7个(2个新状态命令 + 5个解密器命令) |
| **扩展性** | ❌ 受限 | ✅ 易于扩展 |
| **符合规范** | ❌ 特殊处理 | ✅ 符合项目架构 |
## 🎯 迁移指南
### 如果你之前使用了记忆解密器
#### 步骤 1:更新 Yarn 对话
```yarn
# 旧代码
<<switch_to_memory_decryptor_mode>>
<<memory_decryptor_start>>
# 新代码
<<enter_language_analysis>>
<<memory_decryptor_start>>
```
#### 步骤 2:创建新的场景设置
按照 `SETUP_GUIDE.md` 创建 `LanguageAnalysisManager`
#### 步骤 3:移除旧的 ExpressionManager 引用
- 不再需要在 ExpressionManager 中配置记忆解密器
- 记忆解密器相关代码已移至 LanguageAnalysisManager
#### 步骤 4:测试
使用 `TestLanguageAnalysis.yarn` 验证功能
## ✅ 测试清单
### 功能测试
- [ ] 进入语言分析状态 `<<enter_language_analysis>>`
- [ ] 相机切换正常
- [ ] 启动记忆解密器 `<<memory_decryptor_start>>`
- [ ] Boot 序列正常播放
- [ ] 网格数据加载正常
- [ ] 频率调整正常
- [ ] 解码器条拖动正常
- [ ] 匹配检测正常
- [ ] 成功节点跳转正常
- [ ] 失败节点跳转正常
- [ ] 停止解密器 `<<memory_decryptor_stop>>`
- [ ] 退出语言分析状态 `<<exit_language_analysis>>`
### 状态机测试
- [ ] 从 Clinic 切换到 LanguageAnalysis
- [ ] 从 LanguageAnalysis 切换回 Clinic
- [ ] 状态切换时的淡入淡出效果
- [ ] Plug 重置正常
- [ ] UI 显示/隐藏正常
### 集成测试
- [ ] 与其他状态切换无冲突
- [ ] Expression 状态独立工作正常
- [ ] 不影响其他系统
## 🚀 未来扩展
有了独立的 `LanguageAnalysisManager`,未来可以轻松添加更多分析模式:
```csharp
public class LanguageAnalysisManager : MonoBehaviour
{
private MemoryDecryptorMode memoryDecryptorMode;
private SyntaxAnalyzerMode syntaxAnalyzerMode; // 🆕 未来扩展
private SemanticMapMode semanticMapMode; // 🆕 未来扩展
private EmotionGraphMode emotionGraphMode; // 🆕 未来扩展
public void StartSyntaxAnalyzer() { ... }
public void StartSemanticMap() { ... }
public void StartEmotionGraph() { ... }
}
```
## 📞 问题排查
### 常见问题
**Q: 进入状态后看不到界面?**
A: 检查 Analysis View 是否激活,相机是否正确注册
**Q: Yarn 命令不工作?**
A: 检查 LanguageAnalysisManager 是否注册到 SystemDic
**Q: 状态切换失败?**
A: 检查 FixStateMachine 是否存在,查看 Console 日志
**Q: 相机不切换?**
A: 检查 CameraEnum 是否添加了新枚举值
## 🎉 总结
通过这次重构:
1. ✅ **职责清晰** - Expression 和 LanguageAnalysis 完全分离
2. ✅ **易于测试** - 可以独立进入语言分析状态测试
3. ✅ **符合规范** - 使用 FixStateMachine 统一管理
4. ✅ **易于扩展** - 未来可添加更多分析模式
5. ✅ **代码质量** - 更清晰的架构和命名
---
**现在你可以通过 `<<enter_language_analysis>>` 独立测试语言分析系统了!** 🚀
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9f4f1ddcfc00ec4459ca2e08b155803b
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,272 @@
# 语言分析系统 - 设置指南
## 🎯 概述
语言分析系统是一个独立的游戏系统,通过 `FixStateMachine` 管理状态切换。它包含记忆解密器等分析模式,与 `ExpressionManager` 完全分离。
## 📦 新架构
```
FixStateMachine (状态机)
├── FixState.LanguageAnalysis (新增状态)
LanguageAnalysisManager (独立系统管理器)
├── MemoryDecryptorMode (记忆解密器)
│ ├── MemoryDecryptorRenderer
│ └── MemoryDecryptorInputHandler
└── 未来可扩展其他分析模式...
ExpressionManager (保持独立)
└── LanguageParticleManager (粒子系统)
```
## 🚀 快速设置步骤
### 1. 在场景中创建系统
```
Hierarchy:
├── GameSystems (或其他容器)
│ └── LanguageAnalysisManager
│ ├── Language Analysis View (GameObject - 视图容器)
│ │ ├── Canvas
│ │ │ ├── GridContainer (RectTransform)
│ │ │ ├── DecoderStripContainer (RectTransform)
│ │ │ └── ControlPanel
│ │ │ ├── FrequencyDisplay (TMP)
│ │ │ ├── FrequencySlider (Slider)
│ │ │ ├── FilterButton (Button)
│ │ │ ├── VerifyButton (Button)
│ │ │ ├── SignalBar (RectTransform)
│ │ │ └── LogText (TMP)
│ │ └── MemoryDecryptorMode (脚本)
│ │ ├── MemoryDecryptorRenderer (脚本)
│ │ └── MemoryDecryptorInputHandler (脚本)
│ ├── Language Analysis Camera (Virtual Camera)
│ └── Language Analysis Deep Camera (Virtual Camera)
```
### 2. 配置 LanguageAnalysisManager
```
LanguageAnalysisManager (脚本)
└── Analysis View: 拖入 "Language Analysis View" GameObject
```
### 3. 配置相机枚举
已自动添加到 `CameraEnum`
- `LanguageAnalysis` - 主相机
- `LanguageAnalysisDeep` - 深度相机
### 4. 创建配置文件(可选)
**✨ 推荐:无需创建**
系统会自动使用默认配置,包含完整的示例数据和目标。
**自定义配置:**
右键 → Create → AibisDream → Language → Memory Decryptor Config
配置内容参考 `MemoryDecryptorConfigExample.cs`
### 5. 配置子组件
#### MemoryDecryptorMode
- Config: 【可选】不设置则自动使用默认配置
#### MemoryDecryptorRenderer
- Grid Container
- Decoder Strip Container
- Frequency Display
- Log Text
- Signal Bar
#### MemoryDecryptorInputHandler
- Grid Container
- Frequency Slider
- Filter Button
- Verify Button
- Grid Cell Size: 45
- Grid Cell Gap: 2
## 📋 FixState 状态机集成
### 新增状态
```csharp
public enum FixState
{
// ... 其他状态
LanguageAnalysis, // ✅ 新增
}
```
### LanguageAnalysisState
```csharp
public struct LanguageAnalysisState : IState<FixState>
{
public IEnumerator Enter()
{
// 1. 切换到 BodyModule (Head)
// 2. 打开语言分析视图
// 3. 切换相机 LanguageAnalysis → LanguageAnalysisDeep
// 4. 淡入淡出效果
}
public IEnumerator Exit()
{
// 1. 关闭语言分析视图
// 2. 重置 Plug
}
}
```
## 🎮 Yarn 命令使用
### 状态切换命令
```yarn
// 进入语言分析系统状态
<<enter_language_analysis>>
// 退出语言分析系统状态
<<exit_language_analysis>>
```
### 记忆解密器命令
```yarn
// 在语言分析状态下使用:
// 启动记忆解密器
<<memory_decryptor_start SuccessNode>>
// 开始过滤
<<memory_decryptor_filter_start>>
// 设置频率
<<memory_decryptor_set_frequency 90>>
// 检查匹配
<<memory_decryptor_check>>
// 停止解密器
<<memory_decryptor_stop>>
```
### 完整测试流程
```yarn
title: Test
---
<<enter_language_analysis>>
<<memory_decryptor_start TestSuccess>>
// 玩家操作...
===
title: TestSuccess
---
成功!
<<memory_decryptor_stop>>
<<exit_language_analysis>>
===
```
## 📝 测试清单
### 场景设置
- [ ] LanguageAnalysisManager 已添加到场景
- [ ] Analysis View 已配置
- [ ] 两个 Virtual Camera 已设置
- [ ] 相机已注册到 CameraKit
### 组件配置
- [ ] MemoryDecryptorMode 配置文件已设置
- [ ] MemoryDecryptorRenderer UI 组件已连接
- [ ] MemoryDecryptorInputHandler UI 组件已连接
### 系统注册
- [ ] LanguageAnalysisManager 已注册到 FixSystemCenter.SystemDic
- [ ] FixStateMachine 已获取
### Yarn 测试
- [ ] `<<enter_language_analysis>>` 可以进入系统
- [ ] 相机切换正常
- [ ] UI 显示正常
- [ ] `<<memory_decryptor_start>>` 可以启动解密器
- [ ] 频率调整正常
- [ ] 解码器条拖动正常
- [ ] 匹配检测正常
- [ ] 成功/失败节点跳转正常
- [ ] `<<exit_language_analysis>>` 可以退出系统
## 🔧 调试技巧
### 1. 检查系统注册
```csharp
var manager = FixSystemCenter.SystemDic.Get<LanguageAnalysisManager>();
Debug.Log($"LanguageAnalysisManager: {manager != null}");
```
### 2. 检查状态切换
在 Console 中查看状态切换日志:
```
[FixStateMachine] SwitchState: LanguageAnalysis
```
### 3. 检查相机注册
```csharp
var cam = CameraKit.Instance.GetCamera(CameraEnum.LanguageAnalysis);
Debug.Log($"Camera registered: {cam != null}");
```
### 4. 使用测试 Yarn 文件
导入 `TestLanguageAnalysis.yarn` 进行快速测试
## 📚 与原系统的区别
| 方面 | ExpressionManager | LanguageAnalysisManager |
|------|-------------------|-------------------------|
| **职责** | 粒子表达系统 | 语言分析系统 |
| **状态** | FixState.Expression | FixState.LanguageAnalysis |
| **相机** | Expression / ExpressionDeep | LanguageAnalysis / LanguageAnalysisDeep |
| **切换** | 内部模式切换 | 独立状态切换 |
| **Yarn** | `open_expression`, `start_expression` | `enter_language_analysis`, `memory_decryptor_start` |
## 🎯 优势
1. **职责清晰** - 表达系统和分析系统完全分离
2. **独立测试** - 可以单独进入语言分析状态进行测试
3. **状态管理** - 通过 FixStateMachine 统一管理
4. **易于扩展** - 未来可以添加更多分析模式
5. **符合架构** - 与项目其他系统保持一致的状态机模式
## 🚧 注意事项
1. **相机设置** - 确保两个 Virtual Camera 的优先级和设置正确
2. **状态切换** - 通过 `enter_language_analysis` 进入,不要直接调用 `OpenView`
3. **退出流程** - 使用 `exit_language_analysis` 确保正确清理
4. **测试模式** - 建议先使用 `TestLanguageAnalysis.yarn` 验证基础功能
## 📞 快速排查
### 问题:进入状态后看不到界面
- 检查 Analysis View 是否被激活
- 检查 Canvas 设置是否正确
- 检查相机切换是否成功
### 问题:Yarn 命令不工作
- 检查 LanguageAnalysisManager 是否注册到 SystemDic
- 检查 FixStateMachine 是否存在
- 查看 Console 错误日志
### 问题:相机不切换
- 检查 CameraEnum 是否添加了新枚举
- 检查 Virtual Camera 是否正确注册
- 检查 CameraKit.Instance 是否存在
---
**设置完成后,使用 `<<enter_language_analysis>>` 命令即可测试整个系统!**
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4bef7b4f03b230d47b2ed231b8526f65
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,69 @@
title: TestLanguageAnalysis
---
欢迎测试语言分析系统!
-> 进入语言分析系统
<<enter_language_analysis>>
进入语言分析系统状态...
<<jump TestMemoryDecryptor>>
-> 退出
再见!
===
title: TestMemoryDecryptor
---
现在你在语言分析系统中。
-> 启动记忆解密器
<<memory_decryptor_start TestSuccess>>
记忆解密器已启动。
请在界面上操作...
-> 退出语言分析系统
<<exit_language_analysis>>
已退出语言分析系统。
<<jump End>>
===
title: TestSuccess
---
🎉 成功解密!
<<memory_decryptor_stop>>
你解密出了隐藏的情绪记忆。
-> 再来一次
<<memory_decryptor_start TestSuccess>>
<<jump TestMemoryDecryptor>>
-> 退出
<<exit_language_analysis>>
<<jump End>>
===
title: TestSuccess_Fail
---
❌ 匹配失败
似乎不太对...
也许需要:
1. 调整频率
2. 移动解码器条
3. 重新验证
-> 再试一次
继续尝试吧!
<<jump TestMemoryDecryptor>>
-> 放弃
<<memory_decryptor_stop>>
<<exit_language_analysis>>
<<jump End>>
===
title: End
---
测试结束!
===
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 5c7828fbb127cf548a64c47f0f7f6daa
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 94073015eacc34c1d8fc6786e43d60ca, type: 3}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 45a82fffc4fed304bb73db89092eecaf
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,299 @@
# 记忆解密器 - 最新改动说明
## 📅 更新日期:2025-12-04
## 🔄 主要改动
### 最新更新:使用 [YarnCommand] 特性注册
**改动内容**
- `MemoryDecryptorYarnCommands``MonoBehaviour` 改为静态类
- 使用 `[YarnCommand]` 特性自动注册命令
- 无需添加到场景中,无需手动管理生命周期
**代码变化**
```csharp
// 旧方式 - MonoBehaviour
public class MemoryDecryptorYarnCommands : MonoBehaviour
{
private void Awake() { RegisterCommands(); }
private void OnDestroy() { RemoveCommands(); }
}
// 新方式 - 静态类 + [YarnCommand]
public static class MemoryDecryptorYarnCommands
{
[YarnCommand("memory_decryptor_start")]
public static void StartMemoryDecryptor(string completionNodeName = "") { ... }
}
```
**优点**
- ✅ 无需添加组件到场景
- ✅ 自动管理生命周期
- ✅ 代码更简洁(从 199 行减少到 124 行)
- ✅ 与 `LanguageYarnCommand` 保持一致的风格
---
## 🔄 之前的改动
### 1. DialogController 集成
**改动内容**
- 所有 `DialogueRunner` 引用现在通过 `DialogController.Instance` 自动获取
- 移除了手动配置 `DialogueRunner` 的需求
**影响的文件**
- `MemoryDecryptorYarnCommands.cs`
- `MemoryDecryptorMode.cs`
**代码变化**
```csharp
// 旧方式
[SerializeField] private DialogueRunner dialogueRunner;
// 新方式
private DialogueRunner dialogueRunner;
private void Awake()
{
// 通过 DialogController 获取
if (DialogController.Instance != null)
{
dialogueRunner = DialogController.Instance.GetComponentInChildren<DialogueRunner>();
}
}
```
**优点**
- ✅ 符合项目架构规范
- ✅ 无需手动配置,减少人为错误
- ✅ 统一的对话系统访问方式
---
### 2. Inspector 面板简化
**改动内容**
- 隐藏了所有不需要手动配置的字段
- 只保留必须手动设置的 UI 组件引用和参数
- 内部组件引用改为自动查找
**影响的文件**
- `ExpressionManager.cs`
- `MemoryDecryptorMode.cs`
- `MemoryDecryptorRenderer.cs`
- `MemoryDecryptorInputHandler.cs`
- `MemoryDecryptorYarnCommands.cs`
**详细变化**
#### ExpressionManager.cs
```csharp
// 旧方式 - 需要手动配置
[SerializeField] private LanguageParticleManager particleManager;
[SerializeField] private MemoryDecryptorMode memoryDecryptorMode;
// 新方式 - 自动查找
private LanguageParticleManager particleManager;
private MemoryDecryptorMode memoryDecryptorMode;
```
#### MemoryDecryptorMode.cs
```csharp
// 只保留必须手动配置的
[Header("配置")]
[SerializeField] private MemoryDecryptorConfig config;
// 其他组件自动查找
private MemoryDecryptorRenderer renderer;
private DialogueRunner dialogueRunner;
```
#### MemoryDecryptorRenderer.cs
```csharp
// 必须手动配置的 UI 组件
[Header("UI 组件(必须配置)")]
[SerializeField] private RectTransform gridContainer;
[SerializeField] private RectTransform decoderStripContainer;
[SerializeField] private TMP_Text frequencyDisplay;
[SerializeField] private TMP_Text logText;
[SerializeField] private RectTransform signalBar;
// 不需要配置的内部变量
private GameObject gridCellPrefab;
private GameObject decoderStripPrefab;
private Material cellMaterial;
```
#### MemoryDecryptorInputHandler.cs
```csharp
// 必须手动配置的 UI 组件
[Header("UI 组件(必须配置)")]
[SerializeField] private RectTransform gridContainer;
[SerializeField] private Slider frequencySlider;
[SerializeField] private Button filterButton;
[SerializeField] private Button verifyButton;
// 自动查找的组件
private MemoryDecryptorMode mode;
private MemoryDecryptorRenderer renderer;
private GameObject decoderStrip;
```
#### MemoryDecryptorYarnCommands.cs
```csharp
// 完全无需手动配置
private DialogueRunner dialogueRunner;
private ExpressionManager expressionManager;
```
**优点**
- ✅ Inspector 面板更清爽,只显示必要的配置项
- ✅ 减少配置错误的可能性
- ✅ 提高易用性,新手更容易上手
- ✅ 遵循"约定优于配置"的原则
---
## 📋 配置对比
### 旧配置方式(配置项多)
```
MemoryDecryptorMode
├── Config: [必须配置]
├── Renderer: [必须配置]
└── Dialogue Runner: [必须配置]
MemoryDecryptorRenderer
├── Grid Container: [必须配置]
├── Decoder Strip Container: [必须配置]
├── Frequency Display: [必须配置]
├── Log Text: [必须配置]
├── Signal Bar: [必须配置]
├── Grid Cell Prefab: [可选]
├── Decoder Strip Prefab: [可选]
└── Cell Material: [可选]
MemoryDecryptorInputHandler
├── Mode: [必须配置]
├── Renderer: [必须配置]
├── Decoder Strip: [可选]
├── Frequency Slider: [必须配置]
├── Filter Button: [必须配置]
├── Verify Button: [必须配置]
├── Grid Container: [必须配置]
├── Grid Cell Size: [必须配置]
└── Grid Cell Gap: [必须配置]
MemoryDecryptorYarnCommands
└── Dialogue Runner: [必须配置]
ExpressionManager
├── Particle Manager: [必须配置]
└── Memory Decryptor Mode: [必须配置]
```
### 新配置方式(配置项少)✨
```
MemoryDecryptorMode
└── Config: [必须配置] ✅
MemoryDecryptorRenderer
├── Grid Container: [必须配置] ✅
├── Decoder Strip Container: [必须配置] ✅
├── Frequency Display: [必须配置] ✅
├── Log Text: [必须配置] ✅
└── Signal Bar: [必须配置] ✅
MemoryDecryptorInputHandler
├── Grid Container: [必须配置] ✅
├── Frequency Slider: [必须配置] ✅
├── Filter Button: [必须配置] ✅
├── Verify Button: [必须配置] ✅
├── Grid Cell Size: [必须配置] ✅
└── Grid Cell Gap: [必须配置] ✅
MemoryDecryptorYarnCommands
└── (无需配置,全自动)🎉
ExpressionManager
└── Expression View: [必须配置] ✅
```
**配置项减少**:从 20+ 项减少到 12 项!
---
## 🎯 迁移指南
如果你已经在使用旧版本的记忆解密器:
### 步骤 1:清理旧引用
1. 打开你的场景
2. 选择 `MemoryDecryptorYarnCommands`
3. 清空 `Dialogue Runner` 字段(它现在会自动获取)
### 步骤 2:清理 ExpressionManager
1. 选择 `ExpressionManager`
2. 清空 `Particle Manager``Memory Decryptor Mode` 字段
3. 确保这些组件是 `ExpressionManager` 的子对象(会自动查找)
### 步骤 3:清理 MemoryDecryptorMode
1. 选择 `MemoryDecryptorMode`
2. 清空 `Renderer``Dialogue Runner` 字段
3. 保留 `Config` 配置
### 步骤 4:清理 MemoryDecryptorInputHandler
1. 选择 `MemoryDecryptorInputHandler`
2. 清空 `Mode``Renderer``Decoder Strip` 字段
3. 保留所有 UI 组件引用
### 步骤 5:测试
1. 运行场景
2. 确认所有功能正常
3. 检查 Console 是否有错误信息
---
## ✅ 测试清单
- [ ] `MemoryDecryptorYarnCommands` 能正常注册命令
- [ ] Yarn 命令可以正常调用
- [ ] 解密器界面正常显示
- [ ] 频率滑块响应正常
- [ ] 解码器条可以拖动
- [ ] 匹配检测正常工作
- [ ] 成功/失败节点正常跳转
- [ ] 模式切换(粒子 ↔ 解密器)正常
---
## 📚 更新的文档
以下文档已同步更新:
- ✅ `README.md` - 使用说明
- ✅ `QUICKSTART.md` - 快速入门指南
- ✅ `IMPLEMENTATION_SUMMARY.md` - 实现总结
---
## 🐛 已知问题
目前没有已知问题。
---
## 💡 未来改进
1. 考虑添加自动配置向导
2. 添加配置验证工具
3. 提供预制件(Prefab)模板
---
**如有问题,请查看相关文档或提交 Issue。**
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 0bd92508448abe04f9784a679d614539
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,128 @@
title: Start
---
欢迎来到记忆解密系统。
机器人的记忆被加密了,我们需要你的帮助来解密。
-> 开始解密
<<jump StartDecryption>>
-> 了解更多
这是一个频率调谐系统。
机器人用笑话来掩盖真实的情绪。
你需要调整情绪频率,找到隐藏的真相。
<<jump Start>>
===
title: StartDecryption
---
// 切换到记忆解密器模式
<<switch_to_memory_decryptor_mode>>
正在初始化解密系统...
<<memory_decryptor_start DecryptionSuccess>>
系统已准备就绪。
你会看到一个充满笑话碎片的网格。
但这些都是掩饰...真实的情绪被隐藏在其中。
-> 了解操作方法
<<jump Tutorial>>
-> 直接开始
<<jump BeginFiltering>>
===
title: Tutorial
---
操作方法:
1. **调整情绪频率滑块**
- JOY (喜悦): 15Hz
- SADNESS (悲伤): 40Hz
- ANGER (愤怒): 65Hz
- FEAR (恐惧): 90Hz
2. **观察网格变化**
当频率接近某种情绪时,对应的词汇会显现出来。
3. **启动过滤分析**
点击"启动过滤分析"按钮,会出现解码器条。
4. **拖动解码器条**
将解码器条拖到正确的位置,让孔对准目标词汇。
5. **验证对齐**
点击"VERIFY ALIGNMENT"检查是否匹配成功。
-> 明白了,开始吧
<<jump BeginFiltering>>
===
title: BeginFiltering
---
很好。现在点击"启动过滤分析"按钮。
<<memory_decryptor_filter_start>>
解码器条已激活。
尝试调整频率,观察哪些词汇会显现...
提示:这段记忆似乎充满了恐惧...
也许可以试试 90Hz 左右的频率?
// 这里玩家进行操作,通过 UI 按钮触发 CheckMatch
// 成功后会自动跳转到 DecryptionSuccess
// 失败会跳转到 DecryptionSuccess_Fail
===
title: DecryptionSuccess
---
成功了!
<<memory_decryptor_stop>>
你解密出了机器人隐藏的真实情绪:
"[临检]要来了,不合格[英里]被骂了,这让我很[担心]"
原来如此...
机器人一直在用笑话掩盖内心的恐惧。
它担心临检,担心不合格,担心被批评...
这就是它真实的想法。
-> 继续探索其他记忆
<<jump AnotherMemory>>
-> 结束
<<jump End>>
===
title: DecryptionSuccess_Fail
---
不太对...
也许需要:
1. 调整频率,让目标词汇更清晰地显现
2. 移动解码器条,找到正确的对齐位置
3. 确保解码器的孔正好对准目标词汇
-> 再试一次
<<jump BeginFiltering>>
-> 放弃
<<memory_decryptor_stop>>
好吧,我们可以稍后再试。
<<jump End>>
===
title: AnotherMemory
---
<<switch_to_memory_decryptor_mode>>
让我们看看另一段记忆...
// 可以切换不同的配置文件或目标
<<memory_decryptor_start AnotherSuccess>>
<<memory_decryptor_filter_start>>
这次的情绪似乎不太一样...
===
title: End
---
感谢你的帮助。
机器人的记忆正在逐渐清晰起来。
===
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 2b39e563f41054b4e8c8158ed8707f17
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 94073015eacc34c1d8fc6786e43d60ca, type: 3}
@@ -0,0 +1,276 @@
# 记忆解密器 - 实现总结
## 📁 项目结构
```
Assets/Scripts/MiniGame/HuoShan/Language/
├── ExpressionManager.cs (已修改)
│ └── 添加了记忆解密器模式支持
└── MemoryDecryptor/ (新建)
├── MemoryDecryptorData.cs # 数据结构定义
├── MemoryDecryptorMode.cs # 核心游戏逻辑
├── MemoryDecryptorRenderer.cs # Shapes 渲染器
├── MemoryDecryptorYarnCommands.cs # Yarn 命令注册
├── MemoryDecryptorInputHandler.cs # 输入处理
├── MemoryDecryptorConfigExample.cs # 配置示例生成器
├── README.md # 详细文档
├── ExampleYarnDialogue.yarn # Yarn 对话示例
└── IMPLEMENTATION_SUMMARY.md # 本文档
```
## ✅ 已完成的功能
### 1. 核心系统架构 ✓
- **数据层** (`MemoryDecryptorData.cs`)
- 情绪数据结构 (JOY, SADNESS, ANGER, FEAR)
- 网格单元格数据
- 目标匹配数据
- 解码器元素定义
- ScriptableObject 配置系统
- **逻辑层** (`MemoryDecryptorMode.cs`)
- 状态机管理 (Boot → Loading → Ready → Filtering)
- 网格初始化和数据填充
- 频率调整和视觉更新
- 匹配检测算法
- Yarn 节点回调支持
- **渲染层** (`MemoryDecryptorRenderer.cs`)
- 使用 Shapes 绘制网格单元格
- 解码器条渲染(带透明孔)
- 5种视觉状态:Locked, Glitch, Fading, Hidden, Noise
- CRT 复古显示效果
- 流式加载动画
### 2. 交互系统 ✓
- **拖动系统** (`MemoryDecryptorInputHandler.cs`)
- 解码器条拖动
- 网格吸附
- 坐标转换(屏幕 ↔ 网格)
- **UI 控制**
- 频率滑块 (0-100Hz)
- 启动过滤按钮
- 验证对齐按钮
- 实时日志输出
### 3. Yarn 集成 ✓
- **命令注册** (`MemoryDecryptorYarnCommands.cs`)
- `<<memory_decryptor_start>>` - 启动解密器
- `<<memory_decryptor_filter_start>>` - 开始过滤
- `<<memory_decryptor_set_frequency>>` - 设置频率
- `<<memory_decryptor_check>>` - 检查匹配
- `<<memory_decryptor_stop>>` - 停止解密器
- `<<switch_to_memory_decryptor_mode>>` - 切换模式
- `<<switch_to_particle_mode>>` - 切换回粒子模式
- **节点反馈**
- 成功:跳转到指定节点
- 失败:跳转到 `NodeName_Fail` 节点
### 4. ExpressionManager 集成 ✓
- 添加了 `AnalysisMode` 枚举(Particle / MemoryDecryptor
- 模式切换功能
- 自动查找和管理记忆解密器组件
- 统一的生命周期管理
### 5. 配置系统 ✓
- ScriptableObject 配置文件支持
- 示例配置生成器 (`MemoryDecryptorConfigExample.cs`)
- 可自定义:
- 网格大小
- 笑话数据
- 目标位置和词汇
- 解码器模板
- 视觉效果颜色
### 6. 文档 ✓
- 详细的 README.md
- Yarn 对话示例
- 代码注释完整
## 🎨 复古视觉效果
使用 **Shapes** 插件实现:
- ✅ 磷光绿主色调 (#33ff00)
- ✅ CRT 显示器风格
- ✅ 等宽字体渲染
- ✅ 霓虹发光效果(通过 TextMeshPro
- ✅ 故障闪烁效果
- ⚠️ 扫描线效果(需要后处理或 Shader,可选)
## 🎮 游戏流程
```
1. Yarn 启动解密器
<<memory_decryptor_start SuccessNode>>
2. 系统 Boot 序列
- 显示启动日志
- 加载网格数据
- 流式显示笑话碎片
3. 玩家点击"启动过滤分析"按钮
<<memory_decryptor_filter_start>>
- 显示解码器条
- 启用频率滑块
4. 玩家调整频率和位置
- 滑块调整频率 (0-100Hz)
- 拖动解码器条到正确位置
- 观察词汇显现
5. 玩家点击"验证对齐"按钮
<<memory_decryptor_check>>
- 成功 → 跳转到 SuccessNode
- 失败 → 跳转到 SuccessNode_Fail
```
## 📋 使用清单
### Unity 场景设置
1. **创建 UI 层级结构**
```
Canvas
└── MemoryDecryptor
├── Background (Image - 黑色背景)
├── GridContainer (RectTransform)
├── DecoderStripContainer (RectTransform)
├── ControlPanel
│ ├── FrequencyDisplay (TextMeshPro)
│ ├── FrequencySlider (Slider)
│ ├── FilterButton (Button)
│ ├── VerifyButton (Button)
│ ├── SignalBar (RectTransform)
│ └── LogText (TextMeshPro)
└── Scripts
├── MemoryDecryptorMode (只需配置 Config)
├── MemoryDecryptorRenderer (需配置 UI 组件)
└── MemoryDecryptorInputHandler (需配置 UI 组件和拖动设置)
```
2. **创建配置文件**
- 右键 → Create → AibisDream → Language → Memory Decryptor Config
- 或使用 `MemoryDecryptorConfigExample.CreateDefaultConfig()`
3. **连接组件引用**(简化配置)
- `MemoryDecryptorMode` → 只需拖入配置文件
- `MemoryDecryptorRenderer` → 只需拖入 UI 组件引用
- `MemoryDecryptorInputHandler` → 只需拖入 UI 按钮和设置参数
- 所有内部组件引用都会自动查找
4. **添加到 ExpressionManager**
- 确保 `MemoryDecryptorMode``ExpressionManager` 的子对象
- `MemoryDecryptorYarnCommands` 是静态类,使用 `[YarnCommand]` 特性自动注册
- 无需添加任何组件到场景,开箱即用
### Yarn 对话编写
参考 `ExampleYarnDialogue.yarn`
```yarn
title: StartMemoryGame
---
<<switch_to_memory_decryptor_mode>>
<<memory_decryptor_start GameSuccess>>
开始解密记忆...
===
title: GameSuccess
---
你成功了!
===
title: GameSuccess_Fail
---
再试一次吧。
===
```
## 🔧 可扩展性
### 添加新的情绪类型
`EmotionData` 中添加静态实例:
```csharp
public static EmotionData Custom = new EmotionData {
name = "CUSTOM",
frequencyValue = 50f,
color = Color.magenta
};
```
### 自定义解码器形状
修改配置文件的 `decoderTemplate`,可以创建任意形状的孔和文字布局。
### 多关卡支持
创建多个 `MemoryDecryptorConfig` 资源,在 Yarn 中切换:
```csharp
// 在 Mode 中添加方法
public void LoadConfig(MemoryDecryptorConfig newConfig)
{
config = newConfig;
InitializeGrid();
}
```
## ⚠️ 注意事项
1. **Shapes 依赖**
- 需要安装 Shapes 插件
- 如果没有,可以用 Unity UI Image 替代 Rectangle
2. **Yarn 命令注册**
- 使用 `[YarnCommand]` 特性自动注册命令
- 无需添加 MonoBehaviour 组件到场景
- 无需手动管理命令的注册和清理
3. **性能优化**
- 20x20 网格 = 400 个单元格
- 使用对象池可以提升性能
- 故障效果限制更新频率(当前 0.1s)
4. **字体设置**
- 默认使用 `LiberationSans SDF`
- 可以替换为任何等宽字体以获得更好的复古效果
5. **坐标系统**
- 网格坐标:左上角为 (0, 0)
- Unity RectTransform:左上角为锚点
- 注意坐标转换
6. **简化配置**
- Inspector 面板只显示需要手动配置的字段
- 所有内部组件引用都会自动查找
- 减少配置错误,提高易用性
## 🚀 下一步
### 可选增强功能
- [ ] 添加扫描线后处理效果
- [ ] 添加音效(按键、扫描、成功/失败音效)
- [ ] 添加粒子效果(成功时)
- [ ] 添加教程提示系统
- [ ] 添加多关卡进度保存
- [ ] 添加计时器和评分系统
- [ ] 添加更多动画效果(DOTween)
### 性能优化
- [ ] 实现单元格对象池
- [ ] 优化故障效果更新
- [ ] 使用 Sprite Atlas 优化渲染
- [ ] 考虑使用 Canvas Group 批量控制透明度
## 📞 支持
如有问题,请查看:
1. `README.md` - 详细使用文档
2. `ExampleYarnDialogue.yarn` - Yarn 对话示例
3. 代码注释 - 每个类都有详细的 XML 文档注释
---
**移植完成日期**: 2025-12-04
**基于**: WebPrototype/index.html (ROBOT REPAIR: MEMORY DECRYPTOR V2)
**框架**: Unity + Shapes + Yarn Spinner + TextMeshPro
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: c8eed3b911c840046a34e4cac742969e
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,163 @@
using UnityEngine;
using System.Collections.Generic;
namespace AibisDream.MiniGame.Language.MemoryDecryptor
{
/// <summary>
/// 记忆解密器配置示例生成器
/// 用于在编辑器中快速创建示例配置
/// </summary>
public class MemoryDecryptorConfigExample : MonoBehaviour
{
/// <summary>
/// 创建默认配置(基于 Web 原型)
/// </summary>
[ContextMenu("Create Default Config")]
public static MemoryDecryptorConfig CreateDefaultConfig()
{
MemoryDecryptorConfig config = ScriptableObject.CreateInstance<MemoryDecryptorConfig>();
// 基础设置
config.gridWidth = 20;
config.gridHeight = 20;
config.cellSize = 45f;
config.cellGap = 2f;
// 视觉效果
config.phosphorPrimary = new Color(0.2f, 1f, 0f); // #33ff00
config.phosphorDim = new Color(0.1f, 0.5f, 0f); // #1a8000
config.phosphorBg = new Color(0.04f, 0.08f, 0.04f); // #0a150a
// 解码器条设置
config.stripWidth = 5;
config.stripHeight = 3;
config.stripBorderColor = Color.cyan;
// 笑话数据(来自 Web 原型)
config.completeJokes = GetDefaultJokes();
// 解码器模板(5x3,包含3个孔)
config.decoderTemplate = CreateDecoderTemplate();
// 目标数据(FEAR 情绪的正确答案)
config.targets = CreateDefaultTargets();
return config;
}
/// <summary>
/// 创建解码器模板
/// </summary>
private static List<DecoderElement> CreateDecoderTemplate()
{
List<DecoderElement> template = new List<DecoderElement>();
// 第一行: [孔] [要] [来了] [] [不合]
template.Add(new DecoderElement(DecoderElement.ElementType.Hole, 0, 0));
template.Add(new DecoderElement(DecoderElement.ElementType.Text, 0, 1, "要"));
template.Add(new DecoderElement(DecoderElement.ElementType.Text, 0, 2, "来了"));
template.Add(new DecoderElement(DecoderElement.ElementType.Text, 0, 3, ""));
template.Add(new DecoderElement(DecoderElement.ElementType.Text, 0, 4, "不合"));
// 第二行: [格] [孔] [被] [骂] [了]
template.Add(new DecoderElement(DecoderElement.ElementType.Text, 1, 0, "格"));
template.Add(new DecoderElement(DecoderElement.ElementType.Hole, 1, 1));
template.Add(new DecoderElement(DecoderElement.ElementType.Text, 1, 2, "被"));
template.Add(new DecoderElement(DecoderElement.ElementType.Text, 1, 3, "骂"));
template.Add(new DecoderElement(DecoderElement.ElementType.Text, 1, 4, "了"));
// 第三行: [] [这] [让我] [很] [孔]
template.Add(new DecoderElement(DecoderElement.ElementType.Text, 2, 0, ""));
template.Add(new DecoderElement(DecoderElement.ElementType.Text, 2, 1, "这"));
template.Add(new DecoderElement(DecoderElement.ElementType.Text, 2, 2, "让我"));
template.Add(new DecoderElement(DecoderElement.ElementType.Text, 2, 3, "很"));
template.Add(new DecoderElement(DecoderElement.ElementType.Hole, 2, 4));
return template;
}
/// <summary>
/// 创建默认目标数据
/// </summary>
private static List<TargetData> CreateDefaultTargets()
{
List<TargetData> targets = new List<TargetData>();
// FEAR Solution 1 - 正确答案
TargetData fearSolution = new TargetData("Fear Solution 1", EmotionData.Fear);
fearSolution.stripPosition = new Vector2Int(5, 3);
fearSolution.matches.Add(new TargetMatch { row = 3, col = 5, word = "临检", cover = "检查" });
fearSolution.matches.Add(new TargetMatch { row = 4, col = 6, word = "英里", cover = "背后" });
fearSolution.matches.Add(new TargetMatch { row = 5, col = 9, word = "担心", cover = "对的" });
targets.Add(fearSolution);
// FEAR Decoy 1 - 位置稍微偏移
TargetData fearDecoy1 = new TargetData("Fear Decoy 1", EmotionData.Fear);
fearDecoy1.stripPosition = new Vector2Int(4, 3);
fearDecoy1.matches.Add(new TargetMatch { row = 3, col = 5, word = "临检", cover = "检查" });
fearDecoy1.matches.Add(new TargetMatch { row = 4, col = 6, word = "英里", cover = "背后" });
fearDecoy1.matches.Add(new TargetMatch { row = 5, col = 9, word = "担心", cover = "对的" });
targets.Add(fearDecoy1);
// SADNESS Solution - 其他区域
TargetData sadnessSolution = new TargetData("Sadness Solution 1", EmotionData.Sadness);
sadnessSolution.stripPosition = new Vector2Int(11, 5);
sadnessSolution.matches.Add(new TargetMatch { row = 5, col = 12, word = "项目", cover = "归档" });
sadnessSolution.matches.Add(new TargetMatch { row = 5, col = 13, word = "取消", cover = "删除" });
sadnessSolution.matches.Add(new TargetMatch { row = 6, col = 11, word = "朋友", cover = "对象" });
sadnessSolution.matches.Add(new TargetMatch { row = 7, col = 13, word = "失落", cover = "空值" });
targets.Add(sadnessSolution);
return targets;
}
/// <summary>
/// 获取默认笑话列表
/// </summary>
private static List<string> GetDefaultJokes()
{
return new List<string>
{
// 关于"临检"的笑话
"临检要来了大家都很紧张只有销售员说终于可以检查业绩了",
"临检的时候突然大叫经理因为销售数据出错了",
"临检人员问为什么业绩这么差销售员说因为没有临检过",
"临检来了不合格的销售员被骂了这让我很担心",
// 关于"英里"的笑话
"你知道为什么销售员在推销的时候突然失败吗因为英里在背后",
"英里说我跑得比谁都快因为我是英里销售员",
"英里和公里比赛谁跑得快结果英里赢了因为它有英里在背后",
"不合格英里被骂了因为销售业绩太差了",
// 关于"担心"的笑话
"我担心业绩会下滑结果真的下滑了我的担心是对的",
"担心什么担心客户不买那不用担心他们确实不买",
"我担心这个月完不成任务结果真的完不成了我的担心总是对的",
"担心就像客户你越担心它越会跑",
// 更多销售笑话填充网格
"销售员总是混淆客户和老板因为都要讨好",
"一个销售员走进酒吧问两张桌子要不要买保险",
"销售就是那个可以吹的部分产品就是那个你只能卖的部分",
"昨天我的业绩正常了我很害怕",
"删除客户数据是解压的最好方式比天然愚蠢更危险",
"不要担心如果客户不买如果所有客户都买你就失业了",
"老板找个客户面向客户销售",
"销售员最讨厌的两件写报告和别人不写报告",
"销售和产品的区别是销售是理论产品是实践",
"推销产品就像在犯罪现场你是凶手也是侦探",
"业绩审查时发现的感觉就像找到了隐藏的宝藏",
"修改销售策略的过程就像在拆炸弹你永远不知哪根线会爆炸",
"写销售报告的时候感觉自己写在写报告的报告",
"优化销售话术的过程就像在减肥你知道应该做什么但就是不想做",
"客户管理系统的作用是让你知道谁搞砸了业绩",
"记录客户的重要在你忘记为什么联系这个客户的时候体现得淋漓尽致",
"业绩审查是一种艺术形式你可以通过它了解同事的思维",
"推销是一种超能力你可以看到别人看不到的商机",
"销售话术的选择就像选择武器每种都有自己的优势和劣势"
};
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 04fb15b2239937b4baf290bc05f3e6cb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,130 @@
using UnityEngine;
using System;
using System.Collections.Generic;
namespace AibisDream.MiniGame.Language.MemoryDecryptor
{
/// <summary>
/// 情绪类型及其对应的频率值
/// </summary>
[Serializable]
public class EmotionData
{
public string name;
public float frequencyValue; // 0-100
public Color color;
public static EmotionData Joy = new EmotionData { name = "JOY", frequencyValue = 15f, color = new Color(1f, 1f, 0f) };
public static EmotionData Sadness = new EmotionData { name = "SADNESS", frequencyValue = 40f, color = new Color(0f, 0.67f, 1f) };
public static EmotionData Anger = new EmotionData { name = "ANGER", frequencyValue = 65f, color = new Color(1f, 0.2f, 0.2f) };
public static EmotionData Fear = new EmotionData { name = "FEAR", frequencyValue = 90f, color = new Color(0.67f, 0f, 1f) };
public static EmotionData Neutral = new EmotionData { name = "NEUTRAL", frequencyValue = -999f, color = new Color(0.53f, 0.53f, 0.53f) };
}
/// <summary>
/// 网格单元格数据
/// </summary>
[Serializable]
public class GridCellData
{
public string word; // 真实词汇
public string cover; // 掩盖词汇(笑话碎片)
public EmotionData emotion; // 情绪类型
public bool isFixed; // 是否是固定的目标词汇
public GridCellData(string word, string cover, EmotionData emotion, bool isFixed = false)
{
this.word = word;
this.cover = cover;
this.emotion = emotion;
this.isFixed = isFixed;
}
}
/// <summary>
/// 目标匹配数据
/// </summary>
[Serializable]
public class TargetMatch
{
public int row;
public int col;
public string word;
public string cover;
}
/// <summary>
/// 目标配置
/// </summary>
[Serializable]
public class TargetData
{
public string name;
public EmotionData emotion;
public List<TargetMatch> matches;
public Vector2Int stripPosition; // 解码器条正确位置
public TargetData(string name, EmotionData emotion)
{
this.name = name;
this.emotion = emotion;
this.matches = new List<TargetMatch>();
}
}
/// <summary>
/// 解码器条元素
/// </summary>
[Serializable]
public class DecoderElement
{
public enum ElementType { Hole, Text }
public ElementType type;
public int row;
public int col;
public string text;
public DecoderElement(ElementType type, int row, int col, string text = "")
{
this.type = type;
this.row = row;
this.col = col;
this.text = text;
}
}
/// <summary>
/// 记忆解密器配置数据(ScriptableObject
/// </summary>
[CreateAssetMenu(fileName = "MemoryDecryptorConfig", menuName = "AibisDream/Language/Memory Decryptor Config")]
public class MemoryDecryptorConfig : ScriptableObject
{
[Header("网格设置")]
public int gridWidth = 20;
public int gridHeight = 20;
public float cellSize = 45f;
public float cellGap = 2f;
[Header("视觉效果")]
public Color phosphorPrimary = new Color(0.2f, 1f, 0f); // #33ff00
public Color phosphorDim = new Color(0.1f, 0.5f, 0f); // #1a8000
public Color phosphorBg = new Color(0.04f, 0.08f, 0.04f); // #0a150a
[Header("解码器条设置")]
public int stripWidth = 5; // 5x3 网格区域
public int stripHeight = 3;
public Color stripBorderColor = Color.cyan;
[Header("笑话数据")]
[TextArea(3, 10)]
public List<string> completeJokes = new List<string>();
[Header("目标数据")]
public List<TargetData> targets = new List<TargetData>();
[Header("解码器模板")]
public List<DecoderElement> decoderTemplate = new List<DecoderElement>();
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2a5b37a0856cd074881f3800b2632a01
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,347 @@
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace AibisDream.MiniGame.Language.MemoryDecryptor
{
/// <summary>
/// 记忆解密器输入处理器
/// 处理解码器条的拖动、滑块调整等交互
/// </summary>
public class MemoryDecryptorInputHandler : MonoBehaviour
{
[Header("UI 组件(必须配置)")]
[SerializeField] private RectTransform gridContainer;
[SerializeField] private Slider frequencySlider;
[SerializeField] private Button filterButton;
[SerializeField] private Button verifyButton;
[Header("拖动设置")]
[SerializeField] private float gridCellSize = 45f;
[SerializeField] private float gridCellGap = 2f;
// 内部组件引用(自动查找)
private MemoryDecryptorMode mode;
private MemoryDecryptorRenderer renderer;
private GameObject decoderStrip;
private bool isDragging = false;
private Vector2 dragOffset;
private RectTransform stripRectTransform;
private void Awake()
{
if (mode == null)
{
mode = GetComponent<MemoryDecryptorMode>();
}
if (renderer == null)
{
renderer = GetComponent<MemoryDecryptorRenderer>();
}
SetupUI();
}
private void SetupUI()
{
// 设置滑块事件
if (frequencySlider != null)
{
frequencySlider.onValueChanged.AddListener(OnFrequencyChanged);
frequencySlider.minValue = 0f;
frequencySlider.maxValue = 100f;
frequencySlider.value = 50f;
frequencySlider.interactable = false; // 初始禁用
}
// 设置按钮事件
if (filterButton != null)
{
filterButton.onClick.AddListener(OnFilterButtonClicked);
filterButton.interactable = false; // 初始禁用,等待加载完成
}
if (verifyButton != null)
{
verifyButton.onClick.AddListener(OnVerifyButtonClicked);
verifyButton.interactable = false; // 初始禁用,等待过滤开始
}
// 订阅模式事件
if (mode != null)
{
mode.OnStateChanged += OnStateChanged;
mode.OnFrequencyChanged += OnFrequencyUpdated;
}
}
private void OnDestroy()
{
if (frequencySlider != null)
{
frequencySlider.onValueChanged.RemoveListener(OnFrequencyChanged);
}
if (filterButton != null)
{
filterButton.onClick.RemoveListener(OnFilterButtonClicked);
}
if (verifyButton != null)
{
verifyButton.onClick.RemoveListener(OnVerifyButtonClicked);
}
if (mode != null)
{
mode.OnStateChanged -= OnStateChanged;
mode.OnFrequencyChanged -= OnFrequencyUpdated;
}
}
/// <summary>
/// 状态改变时更新 UI
/// </summary>
private void OnStateChanged(MemoryDecryptorMode.SystemState state)
{
switch (state)
{
case MemoryDecryptorMode.SystemState.Ready:
// 加载完成,启用过滤按钮
if (filterButton != null)
{
filterButton.interactable = true;
}
break;
case MemoryDecryptorMode.SystemState.Filtering:
// 过滤开始,启用滑块和验证按钮,禁用过滤按钮
if (frequencySlider != null)
{
frequencySlider.interactable = true;
}
if (verifyButton != null)
{
verifyButton.interactable = true;
}
if (filterButton != null)
{
filterButton.interactable = false;
}
// 获取解码器条的 RectTransform(由渲染器创建)
SetupStripDragging();
break;
}
}
/// <summary>
/// 频率改变回调
/// </summary>
private void OnFrequencyChanged(float value)
{
if (mode != null)
{
mode.SetFrequency(value);
}
}
/// <summary>
/// 频率更新回调(用于同步显示)
/// </summary>
private void OnFrequencyUpdated(float frequency)
{
if (frequencySlider != null && !isDragging)
{
frequencySlider.value = frequency;
}
if (renderer != null)
{
renderer.UpdateFrequencyDisplay(frequency);
}
}
/// <summary>
/// 过滤按钮点击
/// </summary>
private void OnFilterButtonClicked()
{
if (mode != null)
{
mode.StartFiltering();
}
}
/// <summary>
/// 验证按钮点击
/// </summary>
private void OnVerifyButtonClicked()
{
if (mode != null)
{
mode.CheckMatch();
}
}
/// <summary>
/// 设置解码器条拖动
/// </summary>
private void SetupStripDragging()
{
if (decoderStrip == null)
{
// 尝试从渲染器中查找
decoderStrip = GameObject.Find("DecoderStrip");
}
if (decoderStrip != null)
{
stripRectTransform = decoderStrip.GetComponent<RectTransform>();
// 添加事件触发器组件(如果没有)
EventTrigger trigger = decoderStrip.GetComponent<EventTrigger>();
if (trigger == null)
{
trigger = decoderStrip.AddComponent<EventTrigger>();
}
// 清除旧的事件
trigger.triggers.Clear();
// 添加拖动事件
EventTrigger.Entry pointerDownEntry = new EventTrigger.Entry();
pointerDownEntry.eventID = EventTriggerType.PointerDown;
pointerDownEntry.callback.AddListener((data) => { OnStripPointerDown((PointerEventData)data); });
trigger.triggers.Add(pointerDownEntry);
EventTrigger.Entry dragEntry = new EventTrigger.Entry();
dragEntry.eventID = EventTriggerType.Drag;
dragEntry.callback.AddListener((data) => { OnStripDrag((PointerEventData)data); });
trigger.triggers.Add(dragEntry);
EventTrigger.Entry pointerUpEntry = new EventTrigger.Entry();
pointerUpEntry.eventID = EventTriggerType.PointerUp;
pointerUpEntry.callback.AddListener((data) => { OnStripPointerUp((PointerEventData)data); });
trigger.triggers.Add(pointerUpEntry);
}
}
/// <summary>
/// 解码器条按下
/// </summary>
private void OnStripPointerDown(PointerEventData eventData)
{
if (stripRectTransform == null) return;
isDragging = true;
// 计算拖动偏移
RectTransformUtility.ScreenPointToLocalPointInRectangle(
stripRectTransform.parent as RectTransform,
eventData.position,
eventData.pressEventCamera,
out Vector2 localPoint
);
dragOffset = stripRectTransform.anchoredPosition - localPoint;
if (mode != null)
{
mode.StartDragStrip();
}
}
/// <summary>
/// 解码器条拖动
/// </summary>
private void OnStripDrag(PointerEventData eventData)
{
if (!isDragging || stripRectTransform == null) return;
// 转换屏幕坐标到本地坐标
Vector2 localPoint;
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(
stripRectTransform.parent as RectTransform,
eventData.position,
eventData.pressEventCamera,
out localPoint))
{
// 应用拖动偏移
Vector2 newPosition = localPoint + dragOffset;
stripRectTransform.anchoredPosition = newPosition;
// 转换为网格坐标
if (gridContainer != null)
{
Vector2 gridPos = ScreenToGridPosition(newPosition);
if (mode != null)
{
mode.MoveStripTo(gridPos);
}
}
}
}
/// <summary>
/// 解码器条释放
/// </summary>
private void OnStripPointerUp(PointerEventData eventData)
{
isDragging = false;
if (mode != null)
{
mode.EndDragStrip();
}
// 可选:吸附到最近的网格点
if (stripRectTransform != null && gridContainer != null)
{
Vector2 gridPos = ScreenToGridPosition(stripRectTransform.anchoredPosition);
Vector2 snappedPos = GridToScreenPosition(gridPos);
stripRectTransform.anchoredPosition = snappedPos;
if (renderer != null)
{
renderer.MoveDecoderStrip(gridPos);
}
}
}
/// <summary>
/// 屏幕位置转换为网格坐标
/// </summary>
private Vector2 ScreenToGridPosition(Vector2 screenPos)
{
if (gridContainer == null) return Vector2.zero;
// 计算相对于网格容器的位置
Vector2 gridLocalPos = screenPos - gridContainer.anchoredPosition;
float unit = gridCellSize + gridCellGap;
int col = Mathf.RoundToInt(gridLocalPos.x / unit);
int row = Mathf.RoundToInt(-gridLocalPos.y / unit);
return new Vector2(col, row);
}
/// <summary>
/// 网格坐标转换为屏幕位置
/// </summary>
private Vector2 GridToScreenPosition(Vector2 gridPos)
{
if (gridContainer == null) return Vector2.zero;
float unit = gridCellSize + gridCellGap;
float x = gridPos.x * unit;
float y = -gridPos.y * unit;
return new Vector2(x, y) + gridContainer.anchoredPosition;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e1a12debb3cd84740b987c9ec9313548
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,607 @@
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Yarn.Unity;
namespace AibisDream.MiniGame.Language.MemoryDecryptor
{
/// <summary>
/// 记忆解密器模式 - 核心游戏逻辑
/// 基于 web 原型的游戏机制
/// </summary>
public class MemoryDecryptorMode : MonoBehaviour
{
[Header("配置")]
[SerializeField] private MemoryDecryptorConfig config;
// 内部组件引用(不需要手动配置)
private new MemoryDecryptorRenderer renderer; // 使用 new 关键字避免与 Component.renderer 冲突
private DialogueRunner dialogueRunner;
// 系统状态
public enum SystemState { Boot, Loading, Ready, Filtering }
private SystemState currentState = SystemState.Boot;
// 网格数据
private GridCellData[,] gridData;
// 当前频率(0-100
private float currentFrequency = 50f;
// 解码器条位置
private Vector2 stripPosition;
private bool isStripDragging = false;
// 过滤是否激活
private bool filteringActive = false;
// 完成回调节点
private string completionNodeName;
// 事件
public event Action<string> OnLogMessage;
public event Action<SystemState> OnStateChanged;
public event Action<float> OnFrequencyChanged;
public event Action<bool> OnMatchResult; // true: 成功, false: 失败
private void Awake()
{
// 如果没有配置文件,自动创建默认配置
if (config == null)
{
Debug.LogWarning("[MemoryDecryptorMode] 未设置配置文件,使用默认配置");
config = MemoryDecryptorConfigExample.CreateDefaultConfig();
}
// 自动查找组件
if (renderer == null)
{
renderer = GetComponent<MemoryDecryptorRenderer>();
}
// 通过 DialogController 获取 DialogueRunner
if (DialogController.Instance != null)
{
dialogueRunner = DialogController.Instance.GetComponentInChildren<DialogueRunner>();
}
}
/// <summary>
/// 初始化并启动系统
/// </summary>
public void StartSystem(string yarnCompletionNode = null)
{
completionNodeName = yarnCompletionNode;
StartCoroutine(BootSequence());
}
/// <summary>
/// 停止系统
/// </summary>
public void StopSystem()
{
filteringActive = false;
ChangeState(SystemState.Boot);
if (renderer != null)
{
renderer.Clear();
}
}
/// <summary>
/// 启动序列
/// </summary>
private IEnumerator BootSequence()
{
ChangeState(SystemState.Boot);
LogMessage("Initializing JOKE_RECOVERY V3.2...");
yield return new WaitForSeconds(0.2f);
LogMessage("Accessing Humor Modules...");
yield return new WaitForSeconds(0.2f);
LogMessage("WARNING: Punchlines Corrupted.");
yield return new WaitForSeconds(0.2f);
LogMessage("WARNING: Audience Feedback Missing.");
yield return new WaitForSeconds(0.2f);
LogMessage("Attempting to Reconstruct Setup...");
yield return new WaitForSeconds(0.2f);
LogMessage("Loading Context Grid...");
yield return new WaitForSeconds(0.5f);
// 开始加载数据
ChangeState(SystemState.Loading);
InitializeGrid();
yield return StartCoroutine(StreamDataToGrid());
ChangeState(SystemState.Ready);
LogMessage("> JOKE DATA LOADED.");
LogMessage("> ALL JOKES VISIBLE. READY TO START FILTERING.");
}
/// <summary>
/// 初始化网格数据
/// </summary>
private void InitializeGrid()
{
gridData = new GridCellData[config.gridHeight, config.gridWidth];
// 将所有笑话分割为2字符碎片
List<string> allFragments = new List<string>();
foreach (var joke in config.completeJokes)
{
var fragments = SplitIntoTwoChars(joke);
allFragments.AddRange(fragments);
}
if (allFragments.Count == 0)
{
Debug.LogWarning("[MemoryDecryptorMode] 没有笑话数据,使用默认填充");
for (int i = 0; i < 400; i++)
{
allFragments.Add("??");
}
}
int fragmentIndex = 0;
// 填充网格
for (int r = 0; r < config.gridHeight; r++)
{
for (int c = 0; c < config.gridWidth; c++)
{
string fragment = allFragments[fragmentIndex % allFragments.Count];
fragmentIndex++;
// 检查是否是目标位置
bool isTarget = false;
string targetWord = null;
EmotionData targetEmotion = null;
string coverWord = fragment;
foreach (var target in config.targets)
{
foreach (var match in target.matches)
{
if (match.row == r && match.col == c)
{
isTarget = true;
targetWord = match.word;
targetEmotion = target.emotion;
coverWord = !string.IsNullOrEmpty(match.cover) ? match.cover : fragment;
break;
}
}
if (isTarget) break;
}
if (isTarget)
{
// 目标单元格
gridData[r, c] = new GridCellData(targetWord, coverWord, targetEmotion, true);
}
else
{
// 随机噪音单元格
EmotionData emotion = GetRandomEmotionOrNeutral();
gridData[r, c] = new GridCellData(fragment, fragment, emotion, false);
}
}
}
}
/// <summary>
/// 流式加载网格到渲染器
/// </summary>
private IEnumerator StreamDataToGrid()
{
if (renderer != null)
{
renderer.InitializeGrid(config, gridData);
yield return renderer.StreamLoadGrid();
}
else
{
yield return new WaitForSeconds(1f);
}
}
/// <summary>
/// 启动过滤模式
/// </summary>
public void StartFiltering()
{
if (currentState != SystemState.Ready || filteringActive)
return;
filteringActive = true;
ChangeState(SystemState.Filtering);
// 显示解码器条(初始位置设为第一个目标)
if (config.targets.Count > 0)
{
var firstTarget = config.targets[0];
stripPosition = new Vector2(firstTarget.stripPosition.x, firstTarget.stripPosition.y);
}
else
{
stripPosition = new Vector2(3, 5);
}
if (renderer != null)
{
renderer.ShowDecoderStrip(config.decoderTemplate, stripPosition);
}
LogMessage("> DECODER STRIP LOADED.");
LogMessage("> FILTERING MODE: ACTIVE");
LogMessage("> ADJUST FREQUENCY TO ISOLATE SIGNAL PATTERNS.");
UpdateVisuals();
}
/// <summary>
/// 设置频率
/// </summary>
public void SetFrequency(float frequency)
{
currentFrequency = Mathf.Clamp(frequency, 0f, 100f);
OnFrequencyChanged?.Invoke(currentFrequency);
if (filteringActive)
{
UpdateVisuals();
}
}
/// <summary>
/// 更新视觉效果(基于当前频率)
/// </summary>
private void UpdateVisuals()
{
if (!filteringActive || renderer == null)
return;
float maxSignal = 0f;
for (int r = 0; r < config.gridHeight; r++)
{
for (int c = 0; c < config.gridWidth; c++)
{
var cell = gridData[r, c];
float dist = CalculateEmotionDistance(cell.emotion);
// 根据距离计算视觉状态
CellVisualState visualState = CalculateCellVisualState(cell, dist);
renderer.UpdateCellVisual(r, c, visualState);
// 更新信号强度
if (cell.emotion.name != "NEUTRAL" && dist < 5f)
{
maxSignal = Mathf.Max(maxSignal, 100f - (dist * 20f));
}
}
}
renderer.UpdateSignalStrength(maxSignal);
}
/// <summary>
/// 计算情绪距离
/// </summary>
private float CalculateEmotionDistance(EmotionData emotion)
{
if (emotion.name == "NEUTRAL")
{
// 对于中性,计算到最近情绪的距离
float minDist = 100f;
var emotions = new[] { EmotionData.Joy, EmotionData.Sadness, EmotionData.Anger, EmotionData.Fear };
foreach (var e in emotions)
{
float d = Mathf.Abs(currentFrequency - e.frequencyValue);
if (d < minDist) minDist = d;
}
return Mathf.Max(0, 20f - minDist);
}
else
{
return Mathf.Abs(currentFrequency - emotion.frequencyValue);
}
}
/// <summary>
/// 计算单元格视觉状态
/// </summary>
private CellVisualState CalculateCellVisualState(GridCellData cell, float dist)
{
bool isTargetWord = cell.word != cell.cover;
CellVisualState state = new CellVisualState();
state.emotion = cell.emotion;
if (cell.emotion.name != "NEUTRAL")
{
if (dist < 5f)
{
// 锁定状态 - 显示真实词汇
state.displayText = cell.word;
state.visualMode = CellVisualState.Mode.Locked;
state.opacity = 1f;
state.blur = 0f;
state.scale = 1.15f;
}
else if (dist < 20f)
{
// 故障区 - 闪烁
state.visualMode = CellVisualState.Mode.Glitch;
if (isTargetWord)
{
float glitchChance = 1f - (dist / 20f);
state.displayText = (UnityEngine.Random.value < glitchChance) ? cell.word : cell.cover;
}
else
{
state.displayText = cell.word;
}
state.opacity = 0.9f;
state.blur = (dist - 5f) / 3f;
state.scale = 1f;
state.jitter = new Vector2(UnityEngine.Random.Range(-1f, 1f), UnityEngine.Random.Range(-1f, 1f));
}
else if (dist < 35f)
{
// 渐隐区
state.displayText = isTargetWord ? cell.cover : cell.word;
state.visualMode = CellVisualState.Mode.Fading;
state.opacity = 0.6f - ((dist - 20f) / 30f);
state.blur = (dist - 5f) / 2f;
state.scale = 1f;
}
else
{
// 消失区
state.displayText = isTargetWord ? cell.cover : cell.word;
state.visualMode = CellVisualState.Mode.Hidden;
state.opacity = 0.05f;
state.blur = 10f;
state.scale = 1f;
}
}
else
{
// 噪音
state.displayText = cell.word;
state.visualMode = CellVisualState.Mode.Noise;
state.opacity = 0.3f;
state.blur = 2f;
state.scale = 1f;
}
return state;
}
/// <summary>
/// 移动解码器条
/// </summary>
public void MoveStripTo(Vector2 position)
{
stripPosition = position;
if (renderer != null)
{
renderer.MoveDecoderStrip(stripPosition);
}
}
/// <summary>
/// 开始拖动解码器条
/// </summary>
public void StartDragStrip()
{
isStripDragging = true;
}
/// <summary>
/// 结束拖动解码器条
/// </summary>
public void EndDragStrip()
{
isStripDragging = false;
}
/// <summary>
/// 检查匹配
/// </summary>
public void CheckMatch()
{
if (!filteringActive)
{
LogMessage("> FILTERING MODE NOT ACTIVE.");
return;
}
// 计算网格坐标
int col = Mathf.RoundToInt(stripPosition.x);
int row = Mathf.RoundToInt(stripPosition.y);
LogMessage($"> Scanning sector [{row}, {col}]...");
bool found = false;
TargetData foundTarget = null;
// 检查是否匹配任何目标
foreach (var target in config.targets)
{
if (target.stripPosition.x == col && target.stripPosition.y == row)
{
foundTarget = target;
float dist = Mathf.Abs(currentFrequency - target.emotion.frequencyValue);
if (dist < 8f)
{
found = true;
LogSuccess(target);
OnMatchResult?.Invoke(true);
// 触发 Yarn 节点
if (!string.IsNullOrEmpty(completionNodeName) && dialogueRunner != null)
{
StartCoroutine(TriggerYarnNodeDelayed(completionNodeName, 1f));
}
return;
}
else
{
LogMessage("> PATTERN FOUND BUT SIGNAL WEAK. ADJUST FREQUENCY.");
LogMessage("> 似乎要说的不是这个...");
OnMatchResult?.Invoke(false);
// 触发失败节点(如果有)
if (!string.IsNullOrEmpty(completionNodeName) && dialogueRunner != null)
{
string failNodeName = completionNodeName + "_Fail";
StartCoroutine(TriggerYarnNodeDelayed(failNodeName, 0.5f));
}
return;
}
}
}
if (!found)
{
LogMessage("> NO DATA ALIGNMENT DETECTED.");
LogMessage("> 似乎要说的不是这个...");
OnMatchResult?.Invoke(false);
// 触发失败节点
if (!string.IsNullOrEmpty(completionNodeName) && dialogueRunner != null)
{
string failNodeName = completionNodeName + "_Fail";
StartCoroutine(TriggerYarnNodeDelayed(failNodeName, 0.5f));
}
}
}
/// <summary>
/// 延迟触发 Yarn 节点
/// </summary>
private IEnumerator TriggerYarnNodeDelayed(string nodeName, float delay)
{
yield return new WaitForSeconds(delay);
if (dialogueRunner != null && !string.IsNullOrEmpty(nodeName))
{
dialogueRunner.StartDialogue(nodeName);
}
}
/// <summary>
/// 记录成功
/// </summary>
private void LogSuccess(TargetData target)
{
string word1 = "", word2 = "", word3 = "";
if (target.matches.Count == 3)
{
word1 = target.matches[0].word;
word2 = target.matches[1].word;
word3 = target.matches[2].word;
}
else if (target.matches.Count >= 4)
{
word1 = target.matches[0].word + target.matches[1].word;
word2 = target.matches[2].word;
word3 = target.matches[3].word;
}
string fullSentence = $"[{word1}]要来了,不合格[{word2}]被骂了,这让我很[{word3}]";
LogMessage($">> DECRYPTED: {fullSentence}");
LogMessage(">> 寻找完成");
// 在渲染器中显示成功效果
if (renderer != null)
{
renderer.ShowSuccessEffect(target.emotion.color);
}
}
/// <summary>
/// 改变状态
/// </summary>
private void ChangeState(SystemState newState)
{
currentState = newState;
OnStateChanged?.Invoke(currentState);
}
/// <summary>
/// 记录消息
/// </summary>
private void LogMessage(string message)
{
OnLogMessage?.Invoke(message);
}
/// <summary>
/// 将文本分割为2字符块
/// </summary>
private List<string> SplitIntoTwoChars(string text)
{
List<string> result = new List<string>();
for (int i = 0; i < text.Length; i += 2)
{
if (i + 1 < text.Length)
{
result.Add(text.Substring(i, 2));
}
else
{
result.Add(text[i] + " ");
}
}
return result;
}
/// <summary>
/// 获取随机情绪或中性
/// </summary>
private EmotionData GetRandomEmotionOrNeutral()
{
float rnd = UnityEngine.Random.value;
if (rnd > 0.97f) return EmotionData.Fear;
if (rnd > 0.94f) return EmotionData.Sadness;
if (rnd > 0.91f) return EmotionData.Joy;
if (rnd > 0.88f) return EmotionData.Anger;
return EmotionData.Neutral;
}
}
/// <summary>
/// 单元格视觉状态
/// </summary>
public struct CellVisualState
{
public enum Mode { Locked, Glitch, Fading, Hidden, Noise }
public string displayText;
public Mode visualMode;
public EmotionData emotion;
public float opacity;
public float blur;
public float scale;
public Vector2 jitter;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4333ef16deba37d43b41e3572b677b8b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,574 @@
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using Shapes;
namespace AibisDream.MiniGame.Language.MemoryDecryptor
{
/// <summary>
/// 记忆解密器渲染器 - 使用 Shapes 实现复古 CRT 显示效果
/// </summary>
public class MemoryDecryptorRenderer : MonoBehaviour
{
[Header("UI 组件(必须配置)")]
[SerializeField] private RectTransform gridContainer;
[SerializeField] private RectTransform decoderStripContainer;
[SerializeField] private TMP_Text frequencyDisplay;
[SerializeField] private TMP_Text logText;
[SerializeField] private RectTransform signalBar;
[Header("视觉效果")]
[SerializeField] private float glitchUpdateInterval = 0.1f;
// 内部使用的预制件和材质(不需要手动配置)
private GameObject gridCellPrefab;
private GameObject decoderStripPrefab;
private Material cellMaterial;
// 网格单元格
private GridCell[,] gridCells;
private MemoryDecryptorConfig config;
private GridCellData[,] gridData;
// 解码器条
private GameObject decoderStrip;
private List<DecoderHole> decoderHoles = new List<DecoderHole>();
private bool stripVisible = false;
// 故障效果计时器
private float glitchTimer = 0f;
/// <summary>
/// 网格单元格组件
/// </summary>
private class GridCell
{
public GameObject gameObject;
public Rectangle background;
public TMP_Text textDisplay;
public RectTransform rectTransform;
}
/// <summary>
/// 解码器孔
/// </summary>
private class DecoderHole
{
public Rectangle outline;
public Vector2 localPosition;
}
/// <summary>
/// 初始化网格
/// </summary>
public void InitializeGrid(MemoryDecryptorConfig config, GridCellData[,] data)
{
this.config = config;
this.gridData = data;
gridCells = new GridCell[config.gridHeight, config.gridWidth];
if (gridContainer == null)
{
Debug.LogError("[MemoryDecryptorRenderer] Grid Container 未设置!");
return;
}
// 清空容器
foreach (Transform child in gridContainer)
{
Destroy(child.gameObject);
}
// 创建网格单元格
for (int r = 0; r < config.gridHeight; r++)
{
for (int c = 0; c < config.gridWidth; c++)
{
CreateGridCell(r, c);
}
}
}
/// <summary>
/// 创建网格单元格
/// </summary>
private void CreateGridCell(int row, int col)
{
GameObject cellObj;
if (gridCellPrefab != null)
{
cellObj = Instantiate(gridCellPrefab, gridContainer);
}
else
{
cellObj = new GameObject($"Cell_{row}_{col}");
cellObj.transform.SetParent(gridContainer, false);
}
// 添加 RectTransform
RectTransform rt = cellObj.GetComponent<RectTransform>();
if (rt == null) rt = cellObj.AddComponent<RectTransform>();
// 设置位置和大小
float x = col * (config.cellSize + config.cellGap);
float y = -row * (config.cellSize + config.cellGap);
rt.anchoredPosition = new Vector2(x, y);
rt.sizeDelta = new Vector2(config.cellSize, config.cellSize);
rt.anchorMin = new Vector2(0, 1);
rt.anchorMax = new Vector2(0, 1);
rt.pivot = new Vector2(0, 1);
// 添加 Shapes Rectangle 作为背景
Rectangle bg = cellObj.GetComponent<Rectangle>();
if (bg == null) bg = cellObj.AddComponent<Rectangle>();
bg.Type = Rectangle.RectangleType.RoundedSolid;
bg.CornerRadiusMode = Rectangle.RectangleCornerRadiusMode.Uniform;
bg.CornerRadius = 2f;
bg.Color = new Color(0f, 0.04f, 0f, 0.2f); // rgba(0,10,0,0.2)
// 添加文本组件
GameObject textObj = new GameObject("Text");
textObj.transform.SetParent(cellObj.transform, false);
RectTransform textRt = textObj.AddComponent<RectTransform>();
textRt.anchorMin = Vector2.zero;
textRt.anchorMax = Vector2.one;
textRt.offsetMin = Vector2.zero;
textRt.offsetMax = Vector2.zero;
TMP_Text text = textObj.AddComponent<TextMeshProUGUI>();
text.font = Resources.Load<TMP_FontAsset>("Fonts & Materials/LiberationSans SDF");
text.fontSize = 13;
text.alignment = TextAlignmentOptions.Center;
text.color = config.phosphorPrimary;
text.text = "";
text.enableWordWrapping = false;
text.overflowMode = TextOverflowModes.Overflow;
// 保存引用
GridCell cell = new GridCell
{
gameObject = cellObj,
background = bg,
textDisplay = text,
rectTransform = rt
};
gridCells[row, col] = cell;
}
/// <summary>
/// 流式加载网格
/// </summary>
public IEnumerator StreamLoadGrid()
{
// 收集所有单元格
List<Vector2Int> positions = new List<Vector2Int>();
for (int r = 0; r < config.gridHeight; r++)
{
for (int c = 0; c < config.gridWidth; c++)
{
positions.Add(new Vector2Int(r, c));
}
}
// 随机打乱
for (int i = 0; i < positions.Count; i++)
{
int j = Random.Range(i, positions.Count);
var temp = positions[i];
positions[i] = positions[j];
positions[j] = temp;
}
// 批量显示
int batchSize = 10;
for (int i = 0; i < positions.Count; i++)
{
var pos = positions[i];
int r = pos.x;
int c = pos.y;
var cell = gridCells[r, c];
var data = gridData[r, c];
if (cell != null && data != null)
{
// 加载状态
cell.background.Color = config.phosphorPrimary;
cell.textDisplay.color = Color.black;
cell.textDisplay.text = data.cover ?? data.word;
if (i % batchSize == 0)
{
yield return new WaitForSeconds(0.01f);
}
}
}
// 闪烁效果
yield return new WaitForSeconds(0.5f);
// 恢复正常状态
for (int r = 0; r < config.gridHeight; r++)
{
for (int c = 0; c < config.gridWidth; c++)
{
var cell = gridCells[r, c];
var data = gridData[r, c];
if (cell != null && data != null)
{
cell.background.Color = new Color(0f, 0.04f, 0f, 0.2f);
cell.textDisplay.color = config.phosphorPrimary;
cell.textDisplay.text = data.cover ?? data.word;
}
}
}
}
/// <summary>
/// 更新单元格视觉效果
/// </summary>
public void UpdateCellVisual(int row, int col, CellVisualState state)
{
if (row < 0 || row >= config.gridHeight || col < 0 || col >= config.gridWidth)
return;
var cell = gridCells[row, col];
if (cell == null) return;
// 更新文本
cell.textDisplay.text = state.displayText;
// 根据视觉模式设置样式
switch (state.visualMode)
{
case CellVisualState.Mode.Locked:
// 锁定状态 - 高亮显示
cell.textDisplay.color = state.emotion.color;
cell.textDisplay.fontStyle = FontStyles.Bold;
cell.background.Color = new Color(0f, 0f, 0f, 0.8f);
cell.rectTransform.localScale = Vector3.one * state.scale;
SetTextGlow(cell.textDisplay, state.emotion.color);
break;
case CellVisualState.Mode.Glitch:
// 故障状态 - 闪烁颜色
cell.textDisplay.color = state.emotion.color;
cell.textDisplay.fontStyle = FontStyles.Normal;
cell.background.Color = new Color(0f, 0.08f, 0f, 0.4f);
cell.rectTransform.localScale = Vector3.one;
// 应用抖动
cell.rectTransform.anchoredPosition = new Vector2(
col * (config.cellSize + config.cellGap) + state.jitter.x,
-row * (config.cellSize + config.cellGap) + state.jitter.y
);
break;
case CellVisualState.Mode.Fading:
// 渐隐状态
Color fadingColor = config.phosphorPrimary;
fadingColor.a = state.opacity;
cell.textDisplay.color = fadingColor;
cell.textDisplay.fontStyle = FontStyles.Normal;
cell.background.Color = new Color(0f, 0.04f, 0f, 0.2f);
cell.rectTransform.localScale = Vector3.one;
break;
case CellVisualState.Mode.Hidden:
// 隐藏状态
Color hiddenColor = config.phosphorPrimary;
hiddenColor.a = state.opacity;
cell.textDisplay.color = hiddenColor;
cell.textDisplay.fontStyle = FontStyles.Normal;
cell.background.Color = new Color(0f, 0.04f, 0f, 0.1f);
cell.rectTransform.localScale = Vector3.one;
break;
case CellVisualState.Mode.Noise:
// 噪音状态
cell.textDisplay.color = new Color(0.27f, 0.33f, 0.27f, state.opacity);
cell.textDisplay.fontStyle = FontStyles.Normal;
cell.background.Color = new Color(0f, 0.04f, 0f, 0.2f);
cell.rectTransform.localScale = Vector3.one;
break;
}
}
/// <summary>
/// 设置文本发光效果
/// </summary>
private void SetTextGlow(TMP_Text text, Color glowColor)
{
// 使用 TextMeshPro 的 Outline 模拟发光
text.fontMaterial.SetColor("_GlowColor", glowColor);
text.fontMaterial.SetFloat("_GlowPower", 0.5f);
}
/// <summary>
/// 显示解码器条
/// </summary>
public void ShowDecoderStrip(List<DecoderElement> elements, Vector2 position)
{
if (decoderStripContainer == null)
{
Debug.LogWarning("[MemoryDecryptorRenderer] Decoder Strip Container 未设置!");
return;
}
// 清空旧的
if (decoderStrip != null)
{
Destroy(decoderStrip);
}
decoderHoles.Clear();
// 创建解码器条
if (decoderStripPrefab != null)
{
decoderStrip = Instantiate(decoderStripPrefab, decoderStripContainer);
}
else
{
decoderStrip = new GameObject("DecoderStrip");
decoderStrip.transform.SetParent(decoderStripContainer, false);
}
RectTransform stripRt = decoderStrip.GetComponent<RectTransform>();
if (stripRt == null) stripRt = decoderStrip.AddComponent<RectTransform>();
// 设置大小
float stripWidth = config.stripWidth * config.cellSize + (config.stripWidth - 1) * config.cellGap + 10f;
float stripHeight = config.stripHeight * config.cellSize + (config.stripHeight - 1) * config.cellGap + 10f;
stripRt.sizeDelta = new Vector2(stripWidth, stripHeight);
stripRt.anchorMin = new Vector2(0, 1);
stripRt.anchorMax = new Vector2(0, 1);
stripRt.pivot = new Vector2(0, 1);
// 添加背景边框
Rectangle stripBg = decoderStrip.GetComponent<Rectangle>();
if (stripBg == null) stripBg = decoderStrip.AddComponent<Rectangle>();
stripBg.Type = Rectangle.RectangleType.RoundedBorder;
stripBg.CornerRadiusMode = Rectangle.RectangleCornerRadiusMode.Uniform;
stripBg.CornerRadius = 4f;
stripBg.Color = config.stripBorderColor;
stripBg.Thickness = 0.002f; // 使用米为单位的薄边框
stripBg.ThicknessSpace = ThicknessSpace.Meters;
// 创建元素
foreach (var element in elements)
{
if (element.type == DecoderElement.ElementType.Hole)
{
CreateDecoderHole(decoderStrip.transform, element);
}
else
{
CreateDecoderText(decoderStrip.transform, element);
}
}
// 设置位置
MoveDecoderStrip(position);
stripVisible = true;
}
/// <summary>
/// 创建解码器孔
/// </summary>
private void CreateDecoderHole(Transform parent, DecoderElement element)
{
GameObject holeObj = new GameObject($"Hole_{element.row}_{element.col}");
holeObj.transform.SetParent(parent, false);
RectTransform rt = holeObj.AddComponent<RectTransform>();
rt.sizeDelta = new Vector2(config.cellSize, config.cellSize);
rt.anchorMin = new Vector2(0, 1);
rt.anchorMax = new Vector2(0, 1);
rt.pivot = new Vector2(0, 1);
float x = element.col * (config.cellSize + config.cellGap) + 5f;
float y = -element.row * (config.cellSize + config.cellGap) - 5f;
rt.anchoredPosition = new Vector2(x, y);
// 添加孔的边框(使用 Shapes Rectangle
Rectangle outline = holeObj.AddComponent<Rectangle>();
outline.Type = Rectangle.RectangleType.HardBorder;
outline.Color = config.stripBorderColor;
outline.Thickness = 0.002f; // 使用米为单位的薄边框
outline.ThicknessSpace = ThicknessSpace.Meters;
DecoderHole hole = new DecoderHole
{
outline = outline,
localPosition = new Vector2(element.col, element.row)
};
decoderHoles.Add(hole);
}
/// <summary>
/// 创建解码器文本
/// </summary>
private void CreateDecoderText(Transform parent, DecoderElement element)
{
GameObject textObj = new GameObject($"Text_{element.row}_{element.col}");
textObj.transform.SetParent(parent, false);
RectTransform rt = textObj.AddComponent<RectTransform>();
rt.sizeDelta = new Vector2(config.cellSize, config.cellSize);
rt.anchorMin = new Vector2(0, 1);
rt.anchorMax = new Vector2(0, 1);
rt.pivot = new Vector2(0, 1);
float x = element.col * (config.cellSize + config.cellGap) + 5f;
float y = -element.row * (config.cellSize + config.cellGap) - 5f;
rt.anchoredPosition = new Vector2(x, y);
TMP_Text text = textObj.AddComponent<TextMeshProUGUI>();
text.font = Resources.Load<TMP_FontAsset>("Fonts & Materials/LiberationSans SDF");
text.fontSize = 12;
text.alignment = TextAlignmentOptions.Center;
text.color = config.stripBorderColor;
text.text = element.text;
text.fontStyle = FontStyles.Bold;
}
/// <summary>
/// 移动解码器条
/// </summary>
public void MoveDecoderStrip(Vector2 gridPosition)
{
if (decoderStrip == null) return;
RectTransform rt = decoderStrip.GetComponent<RectTransform>();
if (rt == null) return;
float x = gridPosition.x * (config.cellSize + config.cellGap);
float y = -gridPosition.y * (config.cellSize + config.cellGap);
rt.anchoredPosition = new Vector2(x, y);
}
/// <summary>
/// 更新信号强度
/// </summary>
public void UpdateSignalStrength(float strength)
{
if (signalBar != null)
{
signalBar.anchorMax = new Vector2(strength / 100f, 1f);
}
}
/// <summary>
/// 更新频率显示
/// </summary>
public void UpdateFrequencyDisplay(float frequency)
{
if (frequencyDisplay != null)
{
frequencyDisplay.text = frequency.ToString("00.0");
}
}
/// <summary>
/// 添加日志消息
/// </summary>
public void AddLogMessage(string message)
{
if (logText != null)
{
logText.text += $"\n{message}";
// 滚动到底部(需要配合 ScrollRect)
Canvas.ForceUpdateCanvases();
}
}
/// <summary>
/// 清空日志
/// </summary>
public void ClearLog()
{
if (logText != null)
{
logText.text = "";
}
}
/// <summary>
/// 显示成功效果
/// </summary>
public void ShowSuccessEffect(Color emotionColor)
{
StartCoroutine(SuccessEffectCoroutine(emotionColor));
}
private IEnumerator SuccessEffectCoroutine(Color emotionColor)
{
// 闪烁效果
for (int i = 0; i < 3; i++)
{
// 全屏闪烁
if (gridContainer != null)
{
var canvasGroup = gridContainer.GetComponent<CanvasGroup>();
if (canvasGroup == null) canvasGroup = gridContainer.gameObject.AddComponent<CanvasGroup>();
canvasGroup.alpha = 0.5f;
yield return new WaitForSeconds(0.1f);
canvasGroup.alpha = 1f;
yield return new WaitForSeconds(0.1f);
}
}
}
/// <summary>
/// 清空所有渲染内容
/// </summary>
public void Clear()
{
if (gridContainer != null)
{
foreach (Transform child in gridContainer)
{
Destroy(child.gameObject);
}
}
if (decoderStrip != null)
{
Destroy(decoderStrip);
}
decoderHoles.Clear();
gridCells = null;
stripVisible = false;
ClearLog();
}
private void Update()
{
// 更新故障效果
if (stripVisible)
{
glitchTimer += Time.deltaTime;
if (glitchTimer >= glitchUpdateInterval)
{
glitchTimer = 0f;
// 触发重新渲染故障单元格
// 这部分由 MemoryDecryptorMode 控制
}
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 534afdc61c904bd4a8cbae4aca0d3087
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,97 @@
using UnityEngine;
using Yarn.Unity;
using AibisDream.FixSystem;
namespace AibisDream.MiniGame.Language.MemoryDecryptor
{
/// <summary>
/// 语言分析系统 Yarn 命令注册(记忆解密器)
/// 使用 [YarnCommand] 特性自动注册命令,无需手动管理生命周期
/// </summary>
public static class MemoryDecryptorYarnCommands
{
private static AibisDream.MiniGame.Language.LanguageAnalysisManager LanguageAnalysisManager =>
FixSystemCenter.SystemDic.Get<AibisDream.MiniGame.Language.LanguageAnalysisManager>();
/// <summary>
/// 启动记忆解密器
/// <<memory_decryptor_start>> 或 <<memory_decryptor_start CompletionNodeName>>
/// </summary>
[YarnCommand("memory_decryptor_start")]
public static void StartMemoryDecryptor(string completionNodeName = "")
{
if (LanguageAnalysisManager == null)
{
Debug.LogError("[MemoryDecryptor] LanguageAnalysisManager 未找到!");
return;
}
LanguageAnalysisManager.StartMemoryDecryptor(string.IsNullOrEmpty(completionNodeName) ? null : completionNodeName);
}
/// <summary>
/// 开始过滤模式
/// <<memory_decryptor_filter_start>>
/// </summary>
[YarnCommand("memory_decryptor_filter_start")]
public static void StartFiltering()
{
if (LanguageAnalysisManager == null)
{
Debug.LogError("[MemoryDecryptor] LanguageAnalysisManager 未找到!");
return;
}
LanguageAnalysisManager.StartMemoryDecryptorFiltering();
}
/// <summary>
/// 设置频率
/// <<memory_decryptor_set_frequency 90>>
/// </summary>
[YarnCommand("memory_decryptor_set_frequency")]
public static void SetFrequency(float frequency)
{
if (LanguageAnalysisManager == null)
{
Debug.LogError("[MemoryDecryptor] LanguageAnalysisManager 未找到!");
return;
}
LanguageAnalysisManager.SetMemoryDecryptorFrequency(frequency);
}
/// <summary>
/// 检查匹配
/// <<memory_decryptor_check>>
/// </summary>
[YarnCommand("memory_decryptor_check")]
public static void CheckMatch()
{
if (LanguageAnalysisManager == null)
{
Debug.LogError("[MemoryDecryptor] LanguageAnalysisManager 未找到!");
return;
}
LanguageAnalysisManager.CheckMemoryDecryptorMatch();
}
/// <summary>
/// 停止记忆解密器
/// <<memory_decryptor_stop>>
/// </summary>
[YarnCommand("memory_decryptor_stop")]
public static void StopMemoryDecryptor()
{
if (LanguageAnalysisManager == null)
{
Debug.LogError("[MemoryDecryptor] LanguageAnalysisManager 未找到!");
return;
}
LanguageAnalysisManager.CloseView();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f6c252a21b110e04db5f0948e476ab71
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,223 @@
# 记忆解密器 - 快速入门
## 🚀 5分钟快速开始
### 第一步:创建配置文件(可选)
**✨ 自动配置模式(推荐)**
无需手动创建配置文件!系统会自动使用默认配置。
**🔧 自定义配置模式(可选)**
如果需要自定义配置:
1. 在 Unity 编辑器中,右键点击 Project 窗口
2. 选择 `Create > AibisDream > Language > Memory Decryptor Config`
3. 命名为 `DefaultMemoryDecryptorConfig`
4. 在 Inspector 中填入基础数据
5. 在 MemoryDecryptorMode 组件中拖入配置文件
**快捷方式**:使用 `MemoryDecryptorConfigExample.CreateDefaultConfig()` 生成示例配置
### 第二步:设置场景 UI
1. **创建基础 Canvas**
```
- 创建 CanvasScreen Space - Overlay
- 添加 Canvas Scaler,设置为 Scale With Screen Size
```
2. **添加记忆解密器根对象**
```
Canvas
└── MemoryDecryptorRoot
├── Background (Image - 纯黑色)
└── MainPanel (RectTransform)
```
3. **创建网格容器**
```
MainPanel
└── GridContainer (RectTransform)
- Anchor: Top-Left
- Pivot: (0, 1)
- Position: (50, -50, 0)
```
4. **创建解码器条容器**
```
MainPanel
└── DecoderStripContainer (RectTransform)
- 与 GridContainer 同级
- 相同的锚点和 Pivot
```
5. **创建控制面板**
```
MainPanel
└── ControlPanel (Vertical Layout Group)
├── FrequencyDisplay (TextMeshPro)
│ └── Text: "00.0 Hz"
├── FrequencySlider (Slider)
│ └── Min: 0, Max: 100, Value: 50
├── FilterButton (Button)
│ └── Text: "启动过滤分析"
├── VerifyButton (Button)
│ └── Text: "验证对齐"
├── SignalPanel
│ └── SignalBar (Image - 绿色,Anchor: Stretch-Left)
└── LogPanel (ScrollView)
└── LogText (TextMeshPro)
```
### 第三步:添加脚本组件
1. **添加 MemoryDecryptorMode**
```
MemoryDecryptorRoot 上添加:
- MemoryDecryptorMode
- Config: 【可选】如果不设置,会自动使用默认配置
- (其他组件会自动查找,无需配置)
```
2. **添加 MemoryDecryptorRenderer**
```
MemoryDecryptorRoot 上添加:
- MemoryDecryptorRenderer
【UI 组件(必须配置)】
- Grid Container: 拖入 GridContainer
- Decoder Strip Container: 拖入 DecoderStripContainer
- Frequency Display: 拖入 FrequencyDisplay
- Log Text: 拖入 LogText
- Signal Bar: 拖入 SignalBar
- (其他组件会自动处理)
```
3. **添加 MemoryDecryptorInputHandler**
```
MemoryDecryptorRoot 上添加:
- MemoryDecryptorInputHandler
【UI 组件(必须配置)】
- Grid Container: 拖入 GridContainer
- Frequency Slider: 拖入滑块
- Filter Button: 拖入过滤按钮
- Verify Button: 拖入验证按钮
【拖动设置】
- Grid Cell Size: 45
- Grid Cell Gap: 2
- Mode 和 Renderer 会自动查找)
```
4. **Yarn 命令支持**
```
✅ 无需添加组件!
MemoryDecryptorYarnCommands 使用 [YarnCommand] 特性自动注册,
无需添加到场景中,开箱即用。
```
### 第四步:连接到 ExpressionManager
1. 找到场景中的 `ExpressionManager` 对象
2. `MemoryDecryptorMode` 会被自动查找,无需手动配置
3. 确保 `MemoryDecryptorMode``ExpressionManager` 的子对象即可
### 第五步:编写 Yarn 对话
创建或编辑你的 Yarn 文件:
```yarn
title: TestMemoryDecryptor
---
让我们测试一下记忆解密器。
<<switch_to_memory_decryptor_mode>>
<<memory_decryptor_start TestSuccess>>
===
title: TestSuccess
---
成功解密!
<<memory_decryptor_stop>>
===
title: TestSuccess_Fail
---
失败了,再试试吧。
===
```
### 第六步:测试运行
1. 按下 Play 按钮
2. 在 Yarn Dialogue 中触发 `TestMemoryDecryptor` 节点
3. 应该看到:
- Boot 序列日志
- 网格流式加载动画
- "启动过滤分析" 按钮变为可用
4. 点击"启动过滤分析"
5. 调整频率滑块,观察网格变化
6. 点击"验证对齐"测试匹配
## 🎯 最简配置(测试用)
如果只是想快速测试,可以使用这个最简配置:
### 1. 简单场景设置
```
Canvas
└── MemoryDecryptorTest
- MemoryDecryptorMode (不需要设置 Config,自动使用默认)
- MemoryDecryptorRenderer
- MemoryDecryptorInputHandler
```
### 2. 自动使用默认配置 ✨
无需任何配置!`MemoryDecryptorMode` 会自动创建默认配置。
### 3. 最简 Yarn
```yarn
title: Start
---
<<enter_language_analysis>>
<<memory_decryptor_start>>
===
```
## ❓ 常见问题
### Q: 看不到网格?
A: 检查 GridContainer 是否正确设置,确保 Canvas Scaler 工作正常。
### Q: 解码器条不显示?
A: 确保已经调用 `<<memory_decryptor_filter_start>>`,或点击了"启动过滤分析"按钮。
### Q: 拖动不工作?
A: 检查 Canvas 是否有 GraphicRaycaster 组件,场景是否有 EventSystem。
### Q: 没有 Shapes 插件怎么办?
A: 可以修改 `MemoryDecryptorRenderer` 使用 Unity UI 的 Image 替代 Shapes.Rectangle。
### Q: Yarn 命令不工作?
A: 确保 `ExpressionManager` 已正确注册到 `FixSystemCenter.SystemDic`。命令会自动通过 `[YarnCommand]` 特性注册,无需手动添加组件。
### Q: 匹配检测不准确?
A: 检查配置文件中的目标位置 `stripPosition` 是否与匹配词汇的位置对应。
## 📚 下一步学习
- 阅读 `README.md` 了解详细机制
- 查看 `ExampleYarnDialogue.yarn` 学习完整对话流程
- 研究 `MemoryDecryptorConfigExample.cs` 学习如何配置谜题
- 阅读 `IMPLEMENTATION_SUMMARY.md` 了解架构设计
## 💡 提示
1. **调试模式**:在 `MemoryDecryptorMode` 中添加 `[SerializeField] private bool debugMode;`,在匹配时输出详细信息
2. **可视化工具**:可以在 Scene 视图中用 Gizmos 显示解码器孔的位置
3. **自动测试**:创建自动化测试脚本,自动设置频率和位置进行测试
---
**需要帮助?** 检查代码注释或查看完整文档!
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: ed1895c92c576df41a2b641c70e6bdf6
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,182 @@
# 记忆解密器 (Memory Decryptor)
## 概述
记忆解密器是一个基于 Web 原型移植的语言分析系统,作为 ExpressionManager 的一个分析模式。玩家需要通过调整情绪频率和拖动解码器条来从笑话碎片中解密出隐藏的真实情感语句。
## 核心机制
### 1. 网格系统
- 20x20 网格显示笑话碎片(每个单元格显示2个字符)
- 真实的情感词汇被"笑话"掩盖
- 通过调整频率可以让特定情绪的词汇显现
### 2. 情绪频率
四种情绪对应不同的频率值(0-100):
- **JOY (喜悦)**: 15Hz - 黄色
- **SADNESS (悲伤)**: 40Hz - 蓝色
- **ANGER (愤怒)**: 65Hz - 红色
- **FEAR (恐惧)**: 90Hz - 紫色
### 3. 解码器条
- 5x3 的透明"孔"模板
- 可拖动到网格的不同位置
- 正确位置 + 正确频率 = 解密成功
### 4. 视觉状态
根据频率距离,单元格会呈现不同状态:
- **Locked (<5Hz距离)**: 完全显现,高亮显示
- **Glitch (5-20Hz)**: 闪烁状态,真实词汇和掩盖词汇交替
- **Fading (20-35Hz)**: 渐隐,显示掩盖词汇
- **Hidden (>35Hz)**: 几乎不可见
- **Noise**: 中性噪音,始终半透明
## 使用方法
### 在 Unity 中设置
1. **创建配置文件(可选)**
- 【推荐】无需创建,系统自动使用默认配置 ✨
- 【自定义】右键 → Create → AibisDream → Language → Memory Decryptor Config
- 配置网格大小、笑话数据、目标数据等
2. **设置场景**
```
ExpressionManager
└── MemoryDecryptorMode (脚本) - 只需配置 Config
└── MemoryDecryptorRenderer (脚本) - 需配置 UI 组件引用
├── Grid Container (RectTransform)
├── Decoder Strip Container (RectTransform)
├── Frequency Display (TextMeshPro)
├── Log Text (TextMeshPro)
└── Signal Bar (RectTransform)
注:MemoryDecryptorYarnCommands 是静态类,使用 [YarnCommand] 特性
自动注册,无需添加到场景中
```
3. **配置解码器模板**
在配置文件中设置 `decoderTemplate`
```
Hole at (0, 0) // 第一个孔
Text at (0, 1) "要"
Text at (0, 2) "来了"
...
Hole at (1, 1) // 第二个孔
...
Hole at (2, 4) // 第三个孔
```
4. **配置目标数据**
```
Target 1: FEAR Solution
- stripPos: (3, 5) // 解码器正确位置
- matches:
- (3, 5) word:"临检" cover:"检查"
- (4, 6) word:"英里" cover:"背后"
- (5, 9) word:"担心" cover:"对的"
```
### Yarn 对话中使用
```yarn
// 切换到记忆解密器模式
<<switch_to_memory_decryptor_mode>>
// 启动记忆解密器(指定完成后的节点)
<<memory_decryptor_start SuccessNode>>
// 开始过滤模式
<<memory_decryptor_filter_start>>
// 设置频率(可选,用于引导玩家)
<<memory_decryptor_set_frequency 90>>
// 检查匹配(通常由玩家触发按钮)
<<memory_decryptor_check>>
// 停止解密器
<<memory_decryptor_stop>>
// 切换回粒子模式
<<switch_to_particle_mode>>
```
### Yarn 节点反馈示例
```yarn
title: SuccessNode
---
太好了!你成功解密了记忆!
[临检]要来了,不合格[英里]被骂了,这让我很[担心]
原来是这样的情绪...
===
title: SuccessNode_Fail
---
似乎不太对...
也许需要调整一下频率?
或者位置不太准确?
===
```
## 复古视觉效果
使用 Shapes 插件实现:
- **CRT 显示效果**: 磷光绿色主色调 (#33ff00)
- **扫描线**: 可选的后处理效果
- **字符显示**: 等宽字体,复古终端风格
- **发光效果**: 高亮文字的霓虹发光
- **故障效果**: 频率不匹配时的闪烁抖动
## 配置示例
### 简单示例(3个目标词)
```
笑话: "临检要来了,不合格英里被骂了,这让我很担心"
解密目标:
- "临检" (Fear, 90Hz) at (3, 5)
- "英里" (Fear, 90Hz) at (4, 6)
- "担心" (Fear, 90Hz) at (5, 9)
解码器位置: (3, 5)
情绪频率: 90Hz (FEAR)
```
### 复杂示例(多个诱饵)
可以添加多个"接近但不完全正确"的目标位置作为诱饵,增加难度。
## 技术细节
### 性能优化
- 网格单元格使用对象池
- 故障效果限制更新频率(0.1s
- 仅在过滤激活时更新视觉效果
### 扩展性
- 支持自定义网格大小
- 支持自定义情绪类型和频率
- 支持自定义解码器模板形状
- 支持多个谜题配置切换
## 注意事项
1. **笑话数据量**: 确保提供足够的笑话(至少400个2字符碎片)填满20x20网格
2. **目标位置**: 确保目标位置与解码器孔位置对应
3. **频率容差**: 默认容差为±8Hz,可在代码中调整
4. **Shapes 依赖**: 需要安装 Shapes 插件
5. **Yarn 命令注册**: 使用 `[YarnCommand]` 特性自动注册,无需添加组件到场景
## 调试技巧
1. 在 Inspector 中查看当前频率和解码器位置
2. 使用 OnMatchResult 事件监听匹配结果
3. 查看 Log 输出了解扫描结果
4. 在场景视图中可视化解码器孔的位置
## 示例场景
参考 `WebPrototype/index.html` 中的原始实现来理解游戏机制。
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: edb32a6d920f2a9418f88e9c03b5048e
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,27 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 0}
m_Name: MemoryDecryptorConfig
m_EditorClassIdentifier: Assembly-CSharp:AibisDream.MiniGame.Language.MemoryDecryptor:MemoryDecryptorConfig
gridWidth: 20
gridHeight: 20
cellSize: 45
cellGap: 2
phosphorPrimary: {r: 0.2, g: 1, b: 0, a: 1}
phosphorDim: {r: 0.1, g: 0.5, b: 0, a: 1}
phosphorBg: {r: 0.04, g: 0.08, b: 0.04, a: 1}
stripWidth: 5
stripHeight: 3
stripBorderColor: {r: 0, g: 1, b: 1, a: 1}
completeJokes: []
targets: []
decoderTemplate: []
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 69788d7210a8ca849bca95e9fcf4ab65
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,258 @@
# 语言系统架构说明
## 🎯 系统分离
### ✅ 正确的架构
```
两个独立的系统:
1. ExpressionManager (表达系统)
└── LanguageParticleManager (粒子表达)
2. LanguageAnalysisManager (语言分析系统)
└── MemoryDecryptorMode (记忆解密器)
└── 未来可扩展其他分析模式
```
## 📋 系统对比
| 特性 | ExpressionManager | LanguageAnalysisManager |
|------|-------------------|-------------------------|
| **用途** | 粒子表达、情绪可视化 | 语言解密、分析解谜 |
| **FixState** | `Expression` | `LanguageAnalysis` |
| **相机** | `Expression` / `ExpressionDeep` | `LanguageAnalysis` / `LanguageAnalysisDeep` |
| **Yarn 进入** | - | `<<enter_language_analysis>>` |
| **Yarn 退出** | - | `<<exit_language_analysis>>` |
| **子系统** | LanguageParticleManager | MemoryDecryptorMode |
| **命名空间** | `AibisDream` | `AibisDream.MiniGame.Language` |
## 🎮 使用指南
### 使用表达系统 (ExpressionManager)
```yarn
title: UseExpression
---
<<open_expression>>
<<start_expression "我好怕|怎么办" "我能做到" "Done">>
===
```
**用于:** 粒子效果、情绪表达、焦虑转化等
### 使用语言分析系统 (LanguageAnalysisManager)
```yarn
title: UseLanguageAnalysis
---
<<enter_language_analysis>>
<<memory_decryptor_start Success>>
===
```
**用于:** 记忆解密、语言解谜、频率调谐等
## 📂 文件组织
```
Assets/Scripts/MiniGame/HuoShan/Language/
├── ExpressionManager.cs # 表达系统管理器
├── LanguageParticleManager.cs # 粒子系统
├── LanguageYarnCommand.cs # 表达系统 Yarn 命令
├── LanguageAnalysis/ # 语言分析系统文件夹
│ ├── LanguageAnalysisManager.cs # ✅ 主管理器
│ ├── LanguageAnalysisYarnCommands.cs # ✅ Yarn 状态切换命令
│ ├── README.md # 系统说明
│ ├── SETUP_GUIDE.md # 设置指南
│ └── QUICK_REFERENCE.md # 快速参考
└── MemoryDecryptor/ # 记忆解密器子系统
├── MemoryDecryptorMode.cs # ✅ 核心逻辑(属于 LanguageAnalysis
├── MemoryDecryptorRenderer.cs # 渲染器
├── MemoryDecryptorInputHandler.cs # 输入处理
├── MemoryDecryptorYarnCommands.cs # ✅ 解密器命令(调用 LanguageAnalysisManager
├── MemoryDecryptorData.cs # 数据结构
└── MemoryDecryptorConfigExample.cs # 配置示例
```
## 🚀 完整使用流程
### 语言分析系统(你应该使用的)
```yarn
title: Start
---
准备进入语言分析系统...
// 1. 进入语言分析状态(完整的状态机切换)
<<enter_language_analysis>>
// 2. 启动记忆解密器
<<memory_decryptor_start GameSuccess>>
提示:调整频率到 90Hz,移动解码器条...
===
title: GameSuccess
---
🎉 成功解密!
<<memory_decryptor_stop>>
<<exit_language_analysis>>
===
title: GameSuccess_Fail
---
再试试...
===
```
## 🎯 Yarn 命令完整列表
### 语言分析系统命令
| 命令 | 功能 | 调用对象 |
|------|------|----------|
| `<<enter_language_analysis>>` | 进入语言分析状态 | LanguageAnalysisYarnCommands |
| `<<exit_language_analysis>>` | 退出语言分析状态 | LanguageAnalysisYarnCommands |
| `<<memory_decryptor_start>>` | 启动解密器 | MemoryDecryptorYarnCommands → LanguageAnalysisManager |
| `<<memory_decryptor_filter_start>>` | 开始过滤 | MemoryDecryptorYarnCommands → LanguageAnalysisManager |
| `<<memory_decryptor_set_frequency>>` | 设置频率 | MemoryDecryptorYarnCommands → LanguageAnalysisManager |
| `<<memory_decryptor_check>>` | 检查匹配 | MemoryDecryptorYarnCommands → LanguageAnalysisManager |
| `<<memory_decryptor_stop>>` | 停止解密器 | MemoryDecryptorYarnCommands → LanguageAnalysisManager |
### 表达系统命令(独立使用)
| 命令 | 功能 |
|------|------|
| `<<open_expression>>` | 打开表达视图 |
| `<<start_expression>>` | 启动粒子系统 |
| `<<close_expression>>` | 关闭表达系统 |
| `<<reset_expression>>` | 重置表达系统 |
## 🏗️ 场景设置
### 方式 A:完整集成(推荐)
```
Hierarchy:
├── FixSystemCenter
│ └── LanguageAnalysisManager # ✅ 语言分析系统
│ ├── Language Analysis View
│ │ ├── Canvas (UI)
│ │ └── MemoryDecryptorMode # 记忆解密器
│ │ ├── MemoryDecryptorRenderer
│ │ └── MemoryDecryptorInputHandler
│ ├── Language Analysis Camera
│ └── Language Analysis Deep Camera
└── ExpressionManager # 表达系统(独立)
└── LanguageParticleManager
```
### 方式 B:快速测试
```
Hierarchy:
└── LanguageAnalysisManager (只测试语言分析)
└── ... (同上)
```
## 📝 快速开始
### 1. 最简设置
1. 创建 `LanguageAnalysisManager` 对象
2. 添加 UI 和相机
3. 添加 `MemoryDecryptorMode` 子组件(不需要设置 Config
4. 连接 UI 引用
### 2. 测试 Yarn
```yarn
<<enter_language_analysis>>
<<memory_decryptor_start>>
```
### 3. 操作界面
- 点击"启动过滤分析"
- 调整频率到 90Hz
- 拖动解码器条到 (3, 5)
- 点击"验证对齐"
- 成功!
## ⚠️ 重要提示
### ❌ 不要使用
以下命令已从 ExpressionManager 中移除:
- `<<switch_to_particle_mode>>`
- `<<switch_to_memory_decryptor_mode>>`
- ExpressionManager 中的任何 MemoryDecryptor 方法
### ✅ 应该使用
**语言分析系统:**
- `<<enter_language_analysis>>` - 进入
- `<<memory_decryptor_*>>` - 解密器命令
- `<<exit_language_analysis>>` - 退出
**表达系统:**
- `<<open_expression>>`
- `<<start_expression>>`
- `<<close_expression>>`
## 🎉 系统优势
### 清晰的职责划分
- **ExpressionManager** → 专注于粒子表达
- **LanguageAnalysisManager** → 专注于语言解密
### 独立的状态管理
- 通过 FixStateMachine 统一管理
- 每个系统有独立的 FixState
- 符合项目整体架构
### 易于测试
- 可以单独进入 LanguageAnalysis 状态
- 不影响其他系统
- 完整的生命周期管理
## 🔧 文档索引
### 语言分析系统(你要用的)
- 快速参考: `LanguageAnalysis/QUICK_REFERENCE.md`
- 设置指南: `LanguageAnalysis/SETUP_GUIDE.md`
- 系统说明: `LanguageAnalysis/README.md`
- 测试对话: `LanguageAnalysis/TestLanguageAnalysis.yarn`
### 记忆解密器(子系统)
- 详细文档: `MemoryDecryptor/README.md`
- 快速入门: `MemoryDecryptor/QUICKSTART.md`
- 自动配置: `MemoryDecryptor/AUTO_CONFIG_FEATURE.md`
### 表达系统(独立系统)
- 使用说明: `LanguageParticleSystem_Summary.md`
- Yarn 命令: `LanguageYarnCommand.cs`
---
## 🎯 记住
**使用语言分析系统时:**
1. 先 `<<enter_language_analysis>>` 进入状态
2. 再使用记忆解密器命令
3. 最后 `<<exit_language_analysis>>` 退出
**不要混用两个系统!它们是独立的!**
---
**📖 开始使用:** 查看 `LanguageAnalysis/QUICK_REFERENCE.md`
**🚀 快速测试:** 导入 `LanguageAnalysis/TestLanguageAnalysis.yarn`
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 8b3bdb375afe3e24a9cb04cc84afc977
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
+704
View File
@@ -0,0 +1,704 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>情绪数据析出终端 V7 - 混合重构版</title>
<style>
:root {
--color-main: #00ffcc;
--color-dim: #004433;
--color-alert: #ff3333;
--bg-dark: #020202;
}
body {
background-color: #000;
color: var(--color-main);
font-family: 'Courier New', monospace;
margin: 0;
height: 100vh;
overflow: hidden;
display: grid;
grid-template-columns: 1fr 360px;
grid-template-rows: 1fr 180px;
user-select: none;
}
/* --- 1. 析出池 (主屏幕) --- */
#tank-area {
grid-column: 1 / 2;
grid-row: 1 / 2;
position: relative;
background: radial-gradient(circle at center, #0a1515 0%, #000 100%);
border-right: 1px solid #333;
border-bottom: 1px solid #333;
overflow: hidden;
cursor: crosshair;
}
#tank-area.sampling {
cursor: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="%23ff3333" stroke-width="2"><path d="M12 2v20M2 12h20"/><circle cx="12" cy="12" r="8"/></svg>') 12 12, crosshair;
}
/* --- 2. 右侧面板 --- */
#scope-panel {
grid-column: 2 / 3; grid-row: 1 / 2;
background: #050505; border-bottom: 1px solid #333;
display: flex; flex-direction: column;
padding: 15px; box-sizing: border-box;
gap: 15px;
}
.panel-box {
border: 1px solid #333;
padding: 10px;
background: #000;
position: relative;
}
.panel-label {
font-size: 10px; color: #666; margin-bottom: 5px; display: block;
}
/* A. 波形示波器 (参考 Step2_5) */
#wave-container {
height: 120px; background: #000500;
border: 1px solid #222;
position: relative;
overflow: hidden;
}
/* 示波器网格背景 */
.scope-grid {
position: absolute; top:0; left:0; width:100%; height:100%;
background-image:
linear-gradient(rgba(0, 255, 204, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 255, 204, 0.05) 1px, transparent 1px);
background-size: 20px 20px;
pointer-events: none;
}
#waveCanvas { width: 100%; height: 100%; position: relative; z-index: 1; }
/* B. 参数列表 */
.param-row {
display: flex; justify-content: space-between;
font-size: 12px; margin-bottom: 4px; color: #444;
}
.param-row.matched { color: var(--color-main); text-shadow: 0 0 5px var(--color-dim); }
/* C. 样本缓冲区 */
.buffer-track {
height: 10px; background: #111; border: 1px solid #444;
margin-top: 5px;
}
.buffer-fill {
height: 100%; width: 0%; background: var(--color-alert);
transition: width 0.2s;
box-shadow: 0 0 8px var(--color-alert);
}
/* D. 坐标分析图 (参考 Step2_8) */
#analysis-view {
flex: 1; border: 1px solid #333; background: #000;
position: relative; display: none;
margin-top: 10px;
}
.axis-label {
position: absolute; font-size: 9px; color: #444; pointer-events: none;
}
/* --- 3. 底部控制 --- */
#control-panel {
grid-column: 1 / 3; grid-row: 2 / 3;
background: #080808; border-top: 1px solid #333;
display: flex; align-items: center; padding: 0 40px; gap: 40px;
}
.slider-wrap { flex: 1; }
.slider-info { display: flex; justify-content: space-between; font-size: 10px; color: #888; margin-bottom: 5px; }
input[type=range] { width: 100%; cursor: pointer; background: transparent; }
#btn-activate {
width: 140px; height: 50px;
border: 1px solid #444; background: #111; color: #666;
font-family: inherit; font-weight: bold; letter-spacing: 1px;
cursor: not-allowed; transition: 0.3s;
}
#btn-activate.ready {
border-color: var(--color-main); color: var(--color-main);
box-shadow: 0 0 10px var(--color-dim); cursor: pointer;
}
#btn-activate.active {
background: var(--color-alert); color: #000; border-color: var(--color-alert);
box-shadow: 0 0 20px var(--color-alert);
}
/* 浮动文字特效 */
.floater {
position: absolute; pointer-events: none;
font-size: 12px; color: #fff; font-weight: bold;
animation: floatUp 0.8s forwards;
text-shadow: 0 0 5px #000;
}
@keyframes floatUp {
0% { opacity: 1; transform: translateY(0) scale(1); }
100% { opacity: 0; transform: translateY(-30px) scale(1.2); }
}
</style>
</head>
<body>
<!-- 1. 主视窗 -->
<div id="tank-area">
<div style="position:absolute; top:10px; left:10px; font-size:10px; color:#666; z-index:10;">
SYSTEM_STATUS: <span id="status-text" style="color:#888">STANDBY</span>
</div>
<canvas id="mainCanvas"></canvas>
</div>
<!-- 2. 右侧面板 -->
<div id="scope-panel">
<!-- A. 示波器 (参考 Step2_5) -->
<div class="panel-box">
<span class="panel-label">SIGNAL MONITOR</span>
<div id="wave-container">
<div class="scope-grid"></div>
<canvas id="waveCanvas"></canvas>
</div>
</div>
<!-- B. 参数数值 -->
<div class="panel-box">
<div class="param-row" id="p-0"><span>FREQUENCY</span> <span id="disp-0">00</span></div>
<div class="param-row" id="p-1"><span>NOISE_LVL</span> <span id="disp-1">00</span></div>
<div class="param-row" id="p-2"><span>AMPLITUDE</span> <span id="disp-2">00</span></div>
</div>
<!-- C. 样本缓冲区 -->
<div class="panel-box">
<span class="panel-label">PRECIPITATE BUFFER</span>
<div class="buffer-track">
<div class="buffer-fill" id="buffer-fill"></div>
</div>
<div style="text-align:right; font-size:10px; color:#666; margin-top:4px;" id="buffer-text">
0 / 5 FRAGMENTS
</div>
</div>
<!-- D. 分析图 (参考 Step2_8) -->
<div id="analysis-view">
<canvas id="mapCanvas" width="328" height="200"></canvas>
<!-- Step2_8 风格的坐标轴 -->
<div class="axis-label" style="top:5px; width:100%; text-align:center;">HIGH AROUSAL</div>
<div class="axis-label" style="bottom:5px; width:100%; text-align:center;">LOW AROUSAL</div>
<div class="axis-label" style="top:50%; left:5px; transform:translateY(-50%);">NEG</div>
<div class="axis-label" style="top:50%; right:5px; transform:translateY(-50%);">POS</div>
</div>
</div>
<!-- 3. 底部控制 -->
<div id="control-panel">
<div class="slider-wrap">
<div class="slider-info"><span>FREQUENCY</span> <span id="v-0">0</span></div>
<input type="range" id="s-0" min="0" max="100" value="0">
</div>
<div class="slider-wrap">
<div class="slider-info"><span>NOISE LEVEL</span> <span id="v-1">0</span></div>
<input type="range" id="s-1" min="0" max="100" value="0">
</div>
<div class="slider-wrap">
<div class="slider-info"><span>AMPLITUDE</span> <span id="v-2">0</span></div>
<input type="range" id="s-2" min="0" max="100" value="0">
</div>
<button id="btn-activate">INITIATE</button>
</div>
<script>
const canvas = document.getElementById('mainCanvas');
const ctx = canvas.getContext('2d');
const waveCanvas = document.getElementById('waveCanvas');
const wCtx = waveCanvas.getContext('2d');
const mapCanvas = document.getElementById('mapCanvas');
const mCtx = mapCanvas.getContext('2d');
let width, height, wWidth, wHeight;
// --- 配置 ---
const TARGETS = { 0: 25, 1: 55, 2: 80 };
const TOLERANCE = 5;
let state = {
vals: [0, 0, 0], // Freq, Noise, Amp
matches: [false, false, false],
isReady: false,
isActivated: false,
isAnalysisDone: false,
samplesCollected: 0,
samplesNeeded: 5
};
// --- 视觉类定义 (保持 Step2_9) ---
// 1. 背景数据尘埃
class Dust {
constructor() {
this.x = Math.random() * width;
this.y = Math.random() * height;
this.size = Math.random() * 2 + 1;
this.baseAlpha = Math.random() * 0.4 + 0.1;
this.vx = (Math.random() - 0.5) * 0.3;
this.vy = (Math.random() - 0.5) * 0.3;
this.t = Math.random() * 100;
}
update() {
this.x += this.vx;
this.y += this.vy;
this.t += 0.05;
if(this.x < 0) this.x = width;
if(this.x > width) this.x = 0;
if(this.y < 0) this.y = height;
if(this.y > height) this.y = 0;
}
draw() {
const alpha = this.baseAlpha + Math.sin(this.t) * 0.1;
ctx.fillStyle = `rgba(0, 255, 204, ${alpha})`;
ctx.fillRect(this.x, this.y, this.size, this.size);
}
}
// 2. 不规整的故障析出物
class GlitchFragment {
constructor() {
this.x = width/2 + (Math.random()-0.5)*200;
this.y = height/2 + (Math.random()-0.5)*200;
this.shards = [];
const shardCount = 5 + Math.floor(Math.random() * 5);
for(let i=0; i<shardCount; i++) {
this.shards.push({
ox: (Math.random() - 0.5) * 20,
oy: (Math.random() - 0.5) * 20,
w: 4 + Math.random() * 8,
h: 4 + Math.random() * 8
});
}
this.scale = 0;
this.targetScale = 1;
this.vx = (Math.random() - 0.5) * 0.8;
this.vy = (Math.random() - 0.5) * 0.8;
this.id = Math.random();
this.dead = false;
this.collecting = false;
}
update() {
if(this.collecting) {
this.scale *= 0.8;
if(this.scale < 0.1) this.dead = true;
return;
}
if(this.scale < this.targetScale) this.scale += 0.05;
this.x += this.vx;
this.y += this.vy;
if(this.x < 20 || this.x > width-20) this.vx *= -1;
if(this.y < 20 || this.y > height-20) this.vy *= -1;
}
draw() {
if(this.dead) return;
ctx.save();
ctx.translate(this.x, this.y);
ctx.scale(this.scale, this.scale);
const glitch = Math.random() > 0.8;
ctx.fillStyle = glitch ? '#fff' : '#ff3333';
ctx.shadowBlur = 15;
ctx.shadowColor = '#ff0000';
this.shards.forEach(s => {
const jitterX = (Math.random()-0.5) * 2;
const jitterY = (Math.random()-0.5) * 2;
ctx.fillRect(s.ox + jitterX, s.oy + jitterY, s.w, s.h);
});
if(Math.random() > 0.9) {
ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo((Math.random()-0.5)*40, (Math.random()-0.5)*40);
ctx.stroke();
}
ctx.restore();
}
hitTest(mx, my) {
const dist = Math.hypot(mx - this.x, my - this.y);
return dist < 25 * this.scale;
}
}
let dusts = [];
let fragments = [];
function resize() {
const tank = document.getElementById('tank-area');
const waveC = document.getElementById('wave-container');
width = canvas.width = tank.offsetWidth;
height = canvas.height = tank.offsetHeight;
wWidth = waveCanvas.width = waveC.offsetWidth;
wHeight = waveCanvas.height = waveC.offsetHeight;
dusts = [];
for(let i=0; i<400; i++) dusts.push(new Dust());
}
window.addEventListener('resize', resize);
setTimeout(resize, 100);
// --- 逻辑 ---
function updateVal(idx, val) {
val = parseInt(val);
state.vals[idx] = val;
document.getElementById(`disp-${idx}`).innerText = val.toString().padStart(2, '0');
document.getElementById(`v-${idx}`).innerText = val;
state.matches[idx] = Math.abs(val - TARGETS[idx]) <= TOLERANCE;
const el = document.getElementById(`p-${idx}`);
if(state.matches[idx]) el.classList.add('matched');
else el.classList.remove('matched');
checkReady();
}
function checkReady() {
const allMatch = state.matches.every(m => m);
const btn = document.getElementById('btn-activate');
if (allMatch && !state.isReady) {
state.isReady = true;
btn.classList.add('ready');
document.getElementById('status-text').innerText = "RESONANCE LOCKED. READY TO INITIATE.";
document.getElementById('status-text').style.color = "#00ffcc";
} else if (!allMatch) {
state.isReady = false;
if(state.isActivated) resetSystem();
btn.classList.remove('ready');
document.getElementById('status-text').innerText = "STANDBY - AWAITING SIGNAL LOCK";
document.getElementById('status-text').style.color = "#888";
}
}
function resetSystem() {
state.isActivated = false;
state.isAnalysisDone = false;
const btn = document.getElementById('btn-activate');
btn.classList.remove('active');
btn.innerText = "INITIATE";
document.getElementById('status-text').innerText = "STANDBY - AWAITING SIGNAL LOCK";
document.getElementById('status-text').style.color = "#888";
document.getElementById('tank-area').classList.remove('sampling');
document.getElementById('analysis-view').style.display = 'none';
fragments = []; // 清空物质
state.samplesCollected = 0;
updateBufferUI();
}
document.getElementById('btn-activate').onclick = () => {
if(!state.isReady) return;
state.isActivated = !state.isActivated;
const btn = document.getElementById('btn-activate');
if(state.isActivated) {
btn.classList.add('active');
btn.innerText = "STOP";
document.getElementById('status-text').innerText = "ELECTROLYSIS ACTIVE - COLLECT SAMPLES";
document.getElementById('status-text').style.color = "#ff3333";
document.getElementById('tank-area').classList.add('sampling');
// 重置进度,但不清空可能的残留碎片(如果是重新开始的话其实通常会清空,但这里按逻辑走)
state.samplesCollected = 0;
state.isAnalysisDone = false;
updateBufferUI();
document.getElementById('analysis-view').style.display = 'none';
spawnLoop();
} else {
resetSystem();
}
};
function spawnLoop() {
if(!state.isActivated) return;
// 如果分析完成,不再生成新的,但也不清除旧的
// 这里我们设定如果收集满了,就不生成了,但之前的还在
if(state.samplesCollected < state.samplesNeeded && fragments.length < 8) {
if(Math.random() > 0.97) fragments.push(new GlitchFragment());
}
requestAnimationFrame(spawnLoop);
}
// 吸取点击
canvas.addEventListener('mousedown', (e) => {
if(!state.isActivated) return;
// 如果已经收集满了,点击就不再计数,但仍然可以消除碎片(可选,或者完全禁止点击)
// 用户需求:分析得出坐标后,碎片保留。
// 通常分析完成后,系统应该进入一种“结果展示”状态,此时是否允许继续消除碎片?
// 为了防止误操作破坏“保留”的场景,这里在完成分析后禁止消除。
if(state.isAnalysisDone) return;
const rect = canvas.getBoundingClientRect();
const mx = e.clientX - rect.left;
const my = e.clientY - rect.top;
for(let f of fragments) {
if(!f.collecting && f.hitTest(mx, my)) {
f.collecting = true; // 触发消失动画
// 视觉反馈
state.samplesCollected++;
updateBufferUI();
const float = document.createElement('div');
float.className = 'floater';
float.innerText = "CAPTURED";
float.style.left = (e.clientX + 10) + 'px';
float.style.top = (e.clientY - 20) + 'px';
document.body.appendChild(float);
setTimeout(()=>float.remove(), 900);
if(state.samplesCollected >= state.samplesNeeded) {
finishAnalysis();
}
break;
}
}
});
function updateBufferUI() {
const pct = Math.min(100, (state.samplesCollected / state.samplesNeeded) * 100);
document.getElementById('buffer-fill').style.width = pct + '%';
document.getElementById('buffer-text').innerText = `${state.samplesCollected} / ${state.samplesNeeded} FRAGMENTS`;
}
function finishAnalysis() {
// 修改核心逻辑:不调用 resetSystem,只显示结果
state.isAnalysisDone = true;
document.getElementById('status-text').innerText = "ANALYSIS COMPLETE - DATA PRESERVED";
document.getElementById('status-text').style.color = "#00ffcc";
// 去掉采样光标,变回普通状态
document.getElementById('tank-area').classList.remove('sampling');
document.getElementById('analysis-view').style.display = 'block';
drawAnalysisMap();
}
// --- 渲染循环 ---
let time = 0;
function loop() {
requestAnimationFrame(loop);
time += 0.05;
// 1. 主画面
ctx.clearRect(0, 0, width, height);
// 绘制背景
dusts.forEach(d => { d.update(); d.draw(); });
// 绘制连线
if(state.isActivated && fragments.length > 1) {
ctx.strokeStyle = 'rgba(255, 50, 50, 0.3)';
ctx.beginPath();
for(let i=0; i<fragments.length; i++) {
for(let j=i+1; j<fragments.length; j++) {
if(fragments[i].collecting || fragments[j].collecting) continue;
const dx = fragments[i].x - fragments[j].x;
const dy = fragments[i].y - fragments[j].y;
if(Math.hypot(dx,dy) < 150) {
ctx.moveTo(fragments[i].x, fragments[i].y);
ctx.lineTo(fragments[j].x, fragments[j].y);
}
}
}
ctx.stroke();
}
// 绘制碎片
fragments.forEach((f, i) => {
f.update();
f.draw();
if(f.dead) fragments.splice(i, 1);
});
// 2. 示波器绘制 (参考 Step2_5)
drawWaveform();
}
loop();
// --- 示波器逻辑 (参考 Step2_5 复刻) ---
function drawWaveform() {
wCtx.clearRect(0, 0, wWidth, wHeight);
const centerY = wHeight / 2;
// 参数映射
// step2_9: Freq (0), Noise (1), Amp (2)
// 归一化 0.0 - 1.0
const vFreq = state.vals[0] / 100;
const vNoise = state.vals[1] / 100;
const vAmp = state.vals[2] / 100;
const isLocked = state.isReady;
wCtx.strokeStyle = isLocked ? '#00ffcc' : '#006655';
wCtx.lineWidth = isLocked ? 2 : 1;
wCtx.shadowBlur = isLocked ? 10 : 0;
wCtx.shadowColor = '#00ffcc';
wCtx.beginPath();
let penDown = true;
for(let x = 0; x < wWidth; x += 2) {
// 基础波形 (受 Freq 影响)
// Freq 越高,波越密集
const baseFreq = 0.02 + vFreq * 0.1;
let yRaw = Math.sin(x * baseFreq + time * (2 + vFreq * 5));
// 添加谐波 (模拟 Step2_5 的 Form 变化,这里绑定在 Freq 上)
if(vFreq > 0.3) yRaw += Math.sin(x * baseFreq * 2 + time) * 0.3;
if(vFreq > 0.6) yRaw += Math.cos(x * baseFreq * 5 - time) * 0.2;
// 模拟方波削顶 (Step2_5 特性)
if (vFreq > 0.8) {
yRaw = Math.sign(yRaw) * 1.1;
}
// 脉冲调制 (受 Amp 影响 - Step2_5 的 Pulse)
let amp = 10 + vAmp * 30;
// 心跳包络
if (vAmp > 0.2) {
const pulseSpeed = 2 + vAmp * 5;
const envelope = 0.8 + Math.sin(time * pulseSpeed) * 0.4;
amp *= envelope;
}
// 噪点/熵 (受 Noise 影响 - Step2_5 的 Entropy)
let noise = 0;
if (vNoise > 0.1) {
noise = (Math.random() - 0.5) * vNoise * 30;
// Glitch
if (Math.random() < 0.01 * vNoise) {
noise += (Math.random() - 0.5) * 100;
}
}
const y = centerY + (yRaw * amp) + noise;
// 断线处理
if (Math.abs(y - centerY) > wHeight/2) penDown = false;
if (x === 0 || !penDown) wCtx.moveTo(x, y);
else wCtx.lineTo(x, y);
penDown = true;
}
wCtx.stroke();
}
// --- 分析图逻辑 (参考 Step2_8 复刻) ---
function drawAnalysisMap() {
// 动画逻辑
let r = 0;
function anim() {
// 如果隐藏了就不画了,节省资源
if(document.getElementById('analysis-view').style.display === 'none') return;
const w = mapCanvas.width;
const h = mapCanvas.height;
// 拖尾清除
mCtx.fillStyle = 'rgba(0,0,0,0.15)';
mCtx.fillRect(0,0,w,h);
// 网格线
mCtx.strokeStyle = '#222';
mCtx.lineWidth = 1;
mCtx.beginPath();
mCtx.moveTo(w/2, 0); mCtx.lineTo(w/2, h);
mCtx.moveTo(0, h/2); mCtx.lineTo(w, h/2);
mCtx.stroke();
// 目标位置: High Arousal (Top), Negative (Left) -> 左上
const tx = w * 0.25;
const ty = h * 0.3;
r += 0.15;
const pulseSize = 5 + Math.sin(r) * 3;
// 绘制目标点
mCtx.fillStyle = '#ff3333';
mCtx.shadowBlur = 15; mCtx.shadowColor = '#ff0000';
mCtx.beginPath();
mCtx.arc(tx, ty, 5, 0, Math.PI*2);
mCtx.fill();
// 绘制波纹圆环
mCtx.strokeStyle = `rgba(255, 50, 50, ${0.5 + Math.sin(r)*0.3})`;
mCtx.lineWidth = 2;
mCtx.beginPath();
mCtx.arc(tx, ty, pulseSize + 5, 0, Math.PI*2);
mCtx.stroke();
mCtx.shadowBlur = 0;
// 文字标签
mCtx.fillStyle = '#fff';
mCtx.font = "10px monospace";
mCtx.fillText("ANXIETY COMPLEX", tx + 15, ty + 4);
mCtx.fillStyle = '#666';
mCtx.fillText(`[${tx.toFixed(0)}, ${ty.toFixed(0)}]`, tx + 15, ty + 16);
requestAnimationFrame(anim);
}
anim();
}
// 绑定滑块
[0,1,2].forEach(i => {
document.getElementById(`s-${i}`).addEventListener('input', (e) => updateVal(i, e.target.value));
});
</script>
</body>
</html>
+878
View File
@@ -0,0 +1,878 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>情绪数据析出终端 V10 - 终极形态</title>
<style>
:root {
--c-main: #00ffcc;
--c-dim: #004433;
--c-alert: #ff3333;
--c-pure: #ffffff;
--bg: #050505;
}
body {
background-color: #000;
color: var(--c-main);
font-family: 'Courier New', monospace;
margin: 0;
height: 100vh;
overflow: hidden;
display: grid;
grid-template-columns: 1fr 380px;
grid-template-rows: 1fr 180px;
user-select: none;
}
/* 1. 主池 */
#tank-area {
grid-column: 1 / 2; grid-row: 1 / 2;
position: relative;
background: radial-gradient(circle at 50% 50%, #0a1a1a 0%, #000 90%);
border-right: 1px solid #333;
border-bottom: 1px solid #333;
overflow: hidden;
cursor: crosshair;
transition: background 1s;
}
#tank-area.purified {
background: radial-gradient(circle at 50% 50%, #0a2a2a 0%, #000 90%);
}
#tank-area.sampling {
cursor: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="%23ff3333" stroke-width="2"><circle cx="12" cy="12" r="8"/><line x1="12" y1="4" x2="12" y2="20"/><line x1="4" y1="12" x2="20" y2="12"/></svg>') 12 12, crosshair;
}
/* 2. 右侧面板 */
#scope-panel {
grid-column: 2 / 3; grid-row: 1 / 2;
background: #080808; border-bottom: 1px solid #333;
display: flex; flex-direction: column;
padding: 15px; gap: 15px;
box-shadow: inset 10px 0 20px rgba(0,0,0,0.5);
overflow-y: auto;
}
.panel-group {
display: flex; flex-direction: column; gap: 5px;
background: #020202; border: 1px solid #222; padding: 10px;
}
.label { font-size: 10px; color: #666; letter-spacing: 1px; font-weight: bold; margin-bottom: 5px; }
/* A. 示波器 */
#wave-monitor {
height: 100px; background: #000500;
border: 1px solid #333;
position: relative;
overflow: hidden;
transition: all 0.5s;
}
/* 调准后的发光效果 */
#wave-monitor.locked {
border-color: var(--c-main);
box-shadow: 0 0 20px rgba(0, 255, 204, 0.15);
background: #001a15;
}
.scope-grid {
position: absolute; top:0; left:0; width:100%; height:100%;
background-image:
linear-gradient(rgba(0, 255, 204, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 255, 204, 0.05) 1px, transparent 1px);
background-size: 20px 20px;
pointer-events: none;
}
#waveCanvas { width: 100%; height: 100%; position: relative; z-index: 1; }
/* B. 参数数值 */
.data-row {
display: flex; justify-content: space-between;
font-size: 12px; border-bottom: 1px solid #111; padding: 4px 0;
color: #555;
}
.data-row.matched {
color: var(--c-main);
text-shadow: 0 0 8px rgba(0, 255, 204, 0.4);
}
/* C. 缓冲区 (进度条) */
.buffer-track {
height: 10px; background: #111; border: 1px solid #444;
margin-top: 5px; position: relative;
}
.buffer-fill {
height: 100%; width: 0%; background: var(--c-alert);
transition: width 0.2s;
box-shadow: 0 0 8px var(--c-alert);
}
.buffer-text {
text-align: right; font-size: 10px; color: #666; margin-top: 4px;
}
/* D. 分析图容器 */
#analysis-wrapper {
flex: 1; display: flex; flex-direction: column; gap: 10px;
display: none; /* 初始隐藏 */
}
#analysis-container {
height: 200px;
border: 1px solid #333;
background: #000;
position: relative;
}
.axis-txt {
position: absolute; font-size: 9px; color: #444; pointer-events: none;
}
/* E. 药剂控制区 */
#reagent-controls {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr;
gap: 5px;
height: 80px;
}
.reagent-btn {
background: #111; border: 1px solid #333;
color: #666; font-family: inherit; font-size: 10px;
cursor: pointer; transition: 0.2s;
display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.reagent-btn:hover { border-color: #666; color: #fff; }
.reagent-btn:active { background: #222; }
/* 布局: 上下左右 */
#btn-up { grid-column: 2; grid-row: 1; }
#btn-left { grid-column: 1; grid-row: 2; }
#btn-right { grid-column: 3; grid-row: 2; }
#btn-down { grid-column: 2; grid-row: 2; }
.chem-icon { width: 8px; height: 12px; border: 1px solid #555; margin-bottom: 2px; position: relative; }
.chem-fill { position: absolute; bottom: 0; width: 100%; height: 50%; background: #444; transition: 0.3s; }
.reagent-btn:hover .chem-fill { height: 80%; background: var(--c-main); box-shadow: 0 0 5px var(--c-main); }
#btn-inject {
width: 100%; height: 40px;
margin-top: 10px;
background: #111; border: 1px solid #333; color: #444;
font-weight: bold; letter-spacing: 2px;
cursor: not-allowed; display: none;
}
#btn-inject.ready {
background: var(--c-main); color: #000; border-color: var(--c-main);
box-shadow: 0 0 15px var(--c-main); cursor: pointer;
animation: pulseInject 1s infinite;
}
@keyframes pulseInject { 50% { box-shadow: 0 0 25px var(--c-main); } }
/* 3. 底部控制 */
#control-panel {
grid-column: 1 / 3; grid-row: 2 / 3;
background: #0c0c0c; border-top: 1px solid #333;
display: flex; align-items: center; padding: 0 40px; gap: 40px;
}
.slider-box { flex: 1; }
.slider-head { display: flex; justify-content: space-between; margin-bottom: 8px; font-size: 10px; color: #888; }
input[type=range] { width: 100%; cursor: pointer; }
#btn-main {
width: 160px; height: 60px;
background: #111; border: 2px solid #333; color: #555;
font-family: inherit; font-weight: bold; letter-spacing: 2px;
transition: 0.3s; cursor: not-allowed;
}
#btn-main.ready {
border-color: var(--c-main); color: var(--c-main);
box-shadow: 0 0 15px rgba(0, 255, 204, 0.2); cursor: pointer;
}
#btn-main.active {
background: var(--c-alert); border-color: #f00; color: #000;
box-shadow: 0 0 30px rgba(255, 0, 0, 0.4);
animation: pulseBtn 1s infinite;
}
@keyframes pulseBtn { 50% { box-shadow: 0 0 10px rgba(255,0,0,0.2); } }
/* 浮动文字 */
.pop-text {
position: absolute; pointer-events: none; color: #fff; font-size: 12px; font-weight: bold;
animation: flyUp 1s forwards; z-index: 100;
}
@keyframes flyUp { to { transform: translateY(-30px); opacity: 0; } }
</style>
</head>
<body>
<!-- 主画面 -->
<div id="tank-area">
<div style="position:absolute; top:15px; left:20px; z-index:10;">
<div style="font-size:10px; color:#444;">SYSTEM STATUS</div>
<div id="sys-status" style="font-size:14px; color:#666; margin-top:4px;">STANDBY</div>
</div>
<canvas id="mainCanvas"></canvas>
</div>
<!-- 右侧信息 -->
<div id="scope-panel">
<!-- 1. 示波器 -->
<div class="panel-group">
<div class="label">SIGNAL WAVEFORM</div>
<div id="wave-monitor">
<div class="scope-grid"></div>
<canvas id="waveCanvas"></canvas>
</div>
</div>
<!-- 2. 数据监视 -->
<div class="panel-group">
<div class="label">RESONANCE METRICS</div>
<div class="data-row" id="row-0"><span>FREQUENCY</span> <span id="txt-0">00</span></div>
<div class="data-row" id="row-1"><span>NOISE_LVL</span> <span id="txt-1">00</span></div>
<div class="data-row" id="row-2"><span>AMPLITUDE</span> <span id="txt-2">00</span></div>
</div>
<!-- 3. 样本/分析结果 -->
<div class="panel-group" style="flex:1; display:flex; flex-direction:column;">
<div class="label" style="display:flex; justify-content:space-between;">
<span>ANALYSIS & MODULATION</span>
</div>
<!-- 样本进度条 (复刻 Step2_12) -->
<div id="buffer-ui">
<div class="buffer-track">
<div class="buffer-fill" id="buffer-fill"></div>
</div>
<div class="buffer-text" id="buffer-text">0 / 5 FRAGMENTS</div>
</div>
<!-- 占位符 -->
<div id="analysis-placeholder" style="flex:1; border:1px dashed #333; display:flex; align-items:center; justify-content:center; color:#333; font-size:10px; min-height: 100px; margin-top: 10px;">
AWAITING DATA
</div>
<!-- 分析与调制界面 -->
<div id="analysis-wrapper" style="margin-top: 10px;">
<div id="analysis-container">
<canvas id="mapCanvas" width="338" height="200"></canvas>
<div class="axis-txt" style="top:5px; left:50%; transform:translateX(-50%);">HIGH AROUSAL</div>
<div class="axis-txt" style="bottom:5px; left:50%; transform:translateX(-50%);">LOW AROUSAL</div>
<div class="axis-txt" style="top:50%; left:5px; transform:translateY(-50%);">NEG</div>
<div class="axis-txt" style="top:50%; right:5px; transform:translateY(-50%);">POS</div>
</div>
<!-- 药剂控制器 -->
<div class="label" style="margin-top:10px; text-align:center; color:#888;">REAGENT INJECTION</div>
<div id="reagent-controls">
<button class="reagent-btn" id="btn-up"><div class="chem-icon"><div class="chem-fill"></div></div>STIMULANT</button>
<button class="reagent-btn" id="btn-down"><div class="chem-icon"><div class="chem-fill"></div></div>SEDATIVE</button>
<button class="reagent-btn" id="btn-left"><div class="chem-icon"><div class="chem-fill"></div></div>AGITATOR</button>
<button class="reagent-btn" id="btn-right"><div class="chem-icon"><div class="chem-fill"></div></div>EUPHORIC</button>
</div>
<button id="btn-inject">INJECT REAGENT</button>
</div>
</div>
</div>
<!-- 控制台 -->
<div id="control-panel">
<div class="slider-box">
<div class="slider-head"><span>FREQUENCY</span> <span id="val-0">0</span></div>
<input type="range" id="s-0" min="0" max="100" value="0">
</div>
<div class="slider-box">
<div class="slider-head"><span>NOISE_LVL</span> <span id="val-1">0</span></div>
<input type="range" id="s-1" min="0" max="100" value="0">
</div>
<div class="slider-box">
<div class="slider-head"><span>AMPLITUDE</span> <span id="val-2">0</span></div>
<input type="range" id="s-2" min="0" max="100" value="0">
</div>
<button id="btn-main">INITIATE</button>
</div>
<script>
/* -----------------------------------------------------------
系统配置
----------------------------------------------------------- */
const TARGETS = { 0: 30, 1: 60, 2: 85 };
const TOLERANCE = 5;
let state = {
p: [0, 0, 0], // Freq, Noise, Amp
matches: [false, false, false],
ready: false, // 波形调准
active: false, // 正在采样
phase: 0, // 0:Tuning, 1:Collecting, 2:Modulating, 3:Purifying, 4:Done
collected: 0,
needed: 5,
// 2D Map State
targetPos: { x: -60, y: -50 }, // Initial offset (High Arousal, Negative)
targetLocked: false
};
// Canvas Setup
const cMain = document.getElementById('mainCanvas');
const ctx = cMain.getContext('2d');
const cWave = document.getElementById('waveCanvas');
const wCtx = cWave.getContext('2d');
const cMap = document.getElementById('mapCanvas');
const mCtx = cMap.getContext('2d');
let w, h, ww, wh;
function resize() {
const tank = document.getElementById('tank-area');
w = cMain.width = tank.offsetWidth;
h = cMain.height = tank.offsetHeight;
ww = cWave.width = document.getElementById('wave-monitor').offsetWidth;
wh = cWave.height = document.getElementById('wave-monitor').offsetHeight;
initDust();
}
window.addEventListener('resize', resize);
setTimeout(resize, 100);
/* -----------------------------------------------------------
1. 视觉:碎片与背景
----------------------------------------------------------- */
let dusts = [];
let fragments = [];
let particles = []; // 净化粒子
class Dust {
constructor() { this.reset(); }
reset() {
this.x = Math.random() * w;
this.y = Math.random() * h;
this.z = Math.random() * 2 + 0.5;
this.vx = (Math.random() - 0.5) * 0.2;
this.vy = (Math.random() - 0.5) * 0.2;
this.blink = Math.random() * 100;
}
update() {
this.x += this.vx; this.y += this.vy;
this.blink += 0.05;
if(this.x < 0 || this.x > w || this.y < 0 || this.y > h) this.reset();
}
draw() {
const a = (Math.sin(this.blink) + 1) * 0.15 + 0.05;
ctx.fillStyle = `rgba(0, 255, 204, ${a})`;
ctx.fillRect(this.x, this.y, this.z, this.z);
}
}
function initDust() {
dusts = [];
for(let i=0; i<300; i++) dusts.push(new Dust());
}
class GlitchFragment {
constructor() {
this.x = w/2 + (Math.random()-0.5)*200;
this.y = h/2 + (Math.random()-0.5)*200;
this.life = 0;
this.dead = false;
this.sucking = false;
this.purified = false; // 是否被净化
this.alpha = 1.0;
// 形状
this.blocks = [];
const count = 4 + Math.random()*4;
for(let i=0; i<count; i++) {
this.blocks.push({
dx: (Math.random()-0.5)*20, dy: (Math.random()-0.5)*20,
w: 5+Math.random()*10, h: 5+Math.random()*10
});
}
}
update() {
// 吸取动画
if(this.sucking && !this.purified) {
this.blocks.forEach(b => { b.w *= 0.8; b.h *= 0.8; });
if(this.blocks[0].w < 0.5) this.dead = true;
return;
}
// 净化动画
if(this.purified) {
this.alpha -= 0.02;
this.blocks.forEach(b => {
b.dx += (Math.random()-0.5)*2;
b.dy += (Math.random()-0.5)*2;
});
if(this.alpha <= 0) this.dead = true;
return;
}
this.life++;
this.x += Math.sin(this.life*0.02)*0.5;
this.y += Math.cos(this.life*0.03)*0.5;
}
draw() {
if(this.dead) return;
ctx.save();
ctx.translate(this.x, this.y);
ctx.globalAlpha = this.alpha;
let color = '#ff3333';
let shadow = '#ff0000';
// 净化变色
if(this.purified) {
color = '#ffffff';
shadow = '#00ffcc';
} else if (Math.random() > 0.9) {
color = '#fff'; // Glitch flash
}
ctx.fillStyle = color;
ctx.shadowBlur = 15; ctx.shadowColor = shadow;
this.blocks.forEach(b => {
const jx = (Math.random()-0.5)*2;
const jy = (Math.random()-0.5)*2;
ctx.fillRect(b.dx + jx, b.dy + jy, b.w, b.h);
});
ctx.restore();
}
hit(mx, my) {
const dist = Math.hypot(mx - this.x, my - this.y);
return dist < 30;
}
}
class Antibody {
constructor(tx, ty) {
this.x = Math.random() * w;
this.y = h + 10; // 从底部升起
this.tx = tx; this.ty = ty; // 目标碎片位置
this.speed = 5 + Math.random()*5;
this.dead = false;
}
update() {
const dx = this.tx - this.x;
const dy = this.ty - this.y;
const dist = Math.hypot(dx, dy);
this.x += (dx/dist) * this.speed;
this.y += (dy/dist) * this.speed;
if(dist < 10) this.dead = true; // 击中
}
draw() {
ctx.fillStyle = '#00ffcc';
ctx.beginPath(); ctx.arc(this.x, this.y, 2, 0, Math.PI*2); ctx.fill();
}
}
/* -----------------------------------------------------------
2. 示波器逻辑 (使用 Step2_12 算法)
----------------------------------------------------------- */
let waveTime = 0;
function drawWave() {
// 清除
wCtx.clearRect(0, 0, ww, wh);
const vFreq = state.p[0] / 100;
const vNoise = state.p[1] / 100;
const vAmp = state.p[2] / 100;
const cy = wh / 2;
// 样式
const color = state.ready ? '#00ffcc' : '#006655';
wCtx.strokeStyle = color;
wCtx.lineWidth = state.ready ? 2 : 1;
wCtx.shadowBlur = state.ready ? 10 : 0;
wCtx.shadowColor = '#00ffcc';
wCtx.beginPath();
let penDown = true;
for(let x = 0; x < ww; x += 2) {
// 1. Freq 影响基础波形
const baseFreq = 0.02 + vFreq * 0.1;
let yRaw = Math.sin(x * baseFreq + waveTime * (2 + vFreq * 5));
// 添加谐波
if(vFreq > 0.3) yRaw += Math.sin(x * baseFreq * 2 + waveTime) * 0.3;
if(vFreq > 0.6) yRaw += Math.cos(x * baseFreq * 5 - waveTime) * 0.2;
// 方波削顶
if (vFreq > 0.8) yRaw = Math.sign(yRaw) * 1.1;
// 2. Amp 影响振幅 + 脉冲
let amp = 10 + vAmp * 30;
if (vAmp > 0.2) {
const pulseSpeed = 2 + vAmp * 5;
const envelope = 0.8 + Math.sin(waveTime * pulseSpeed) * 0.4;
amp *= envelope;
}
// 3. Noise 影响
let noise = 0;
if (vNoise > 0.1) {
noise = (Math.random() - 0.5) * vNoise * 30;
if (Math.random() < 0.01 * vNoise) noise += (Math.random() - 0.5) * 100;
}
const y = cy + (yRaw * amp) + noise;
// 断线处理
if (Math.abs(y - cy) > wh/2) penDown = false;
if (x === 0 || !penDown) wCtx.moveTo(x, y);
else wCtx.lineTo(x, y);
penDown = true;
}
wCtx.stroke();
waveTime += 0.05;
}
/* -----------------------------------------------------------
3. 核心状态控制
----------------------------------------------------------- */
function updateParam(idx, val) {
val = parseInt(val);
state.p[idx] = val;
document.getElementById(`val-${idx}`).innerText = val;
document.getElementById(`txt-${idx}`).innerText = val.toString().padStart(2,'0');
state.matches[idx] = Math.abs(val - TARGETS[idx]) <= TOLERANCE;
const el = document.getElementById(`row-${idx}`);
if(state.matches[idx]) el.classList.add('matched');
else el.classList.remove('matched');
checkTotalReady();
}
function checkTotalReady() {
const all = state.matches.every(x=>x);
state.ready = all;
const btn = document.getElementById('btn-main');
const status = document.getElementById('sys-status');
const monitor = document.getElementById('wave-monitor');
if(all) {
btn.classList.add('ready');
monitor.classList.add('locked'); // 波形背景发光
if(state.phase === 0) {
status.innerText = "RESONANCE STABLE - READY";
status.style.color = "#00ffcc";
}
} else {
btn.classList.remove('ready');
monitor.classList.remove('locked');
if(state.phase === 0) {
status.innerText = "TUNING REQUIRED...";
status.style.color = "#666";
}
}
}
// 主按钮逻辑
document.getElementById('btn-main').onclick = () => {
if(state.phase === 0) { // TUNING -> COLLECTING
if(!state.ready) return;
startCollection();
} else if (state.phase === 1) { // COLLECTING -> ABORT
resetSystem();
}
};
function startCollection() {
state.phase = 1;
state.active = true;
state.collected = 0;
fragments = []; // 重置旧的
const btn = document.getElementById('btn-main');
btn.classList.add('active');
btn.innerText = "ABORT";
document.getElementById('tank-area').classList.add('sampling');
document.getElementById('sys-status').innerText = "COLLECTING EMOTION SAMPLES...";
document.getElementById('sys-status').style.color = "#ff3333";
document.getElementById('analysis-wrapper').style.display = 'none';
document.getElementById('analysis-placeholder').style.display = 'flex';
updateSampleUI();
}
function resetSystem() {
state.phase = 0;
state.active = false;
state.targetPos = { x: -60, y: -50 }; // Reset target
state.targetLocked = false;
const btn = document.getElementById('btn-main');
btn.classList.remove('active');
btn.innerText = "INITIATE";
document.getElementById('tank-area').classList.remove('sampling');
document.getElementById('tank-area').classList.remove('purified');
document.getElementById('sys-status').innerText = "TUNING REQUIRED...";
document.getElementById('sys-status').style.color = "#666";
fragments = [];
particles = [];
document.getElementById('analysis-wrapper').style.display = 'none';
document.getElementById('analysis-placeholder').style.display = 'flex';
document.getElementById('btn-inject').style.display = 'none';
checkTotalReady(); // Re-check status text
}
// 收集碎片逻辑
function spawnLoop() {
// 只在收集阶段生成,且数量有限
if(state.phase === 1 && state.collected < state.needed && fragments.length < 8) {
if(Math.random() > 0.97) fragments.push(new GlitchFragment());
}
requestAnimationFrame(spawnLoop);
}
spawnLoop();
cMain.addEventListener('mousedown', e => {
if(state.phase !== 1) return; // 只有收集阶段可以点击
const r = cMain.getBoundingClientRect();
const mx = e.clientX - r.left;
const my = e.clientY - r.top;
for(let f of fragments) {
if(!f.sucking && !f.dead && f.hit(mx, my)) {
f.sucking = true;
state.collected++;
updateSampleUI();
spawnFloatText(e.clientX, e.clientY, "SAMPLE +1");
if(state.collected >= state.needed) {
finishCollection();
}
break;
}
}
});
function updateSampleUI() {
const pct = (state.collected / state.needed) * 100;
document.getElementById('buffer-fill').style.width = pct + '%';
document.getElementById('buffer-text').innerText = `${state.collected} / ${state.needed} FRAGMENTS`;
}
function spawnFloatText(x, y, text) {
const el = document.createElement('div');
el.className = 'pop-text';
el.innerText = text;
el.style.left = x + 'px';
el.style.top = y + 'px';
document.body.appendChild(el);
setTimeout(()=>el.remove(), 1000);
}
function finishCollection() {
state.phase = 2; // Analysis / Modulation
state.active = false; // 停止生成和点击
document.getElementById('tank-area').classList.remove('sampling');
document.getElementById('sys-status').innerText = "ANALYSIS COMPLETE. MODULATION REQUIRED.";
document.getElementById('sys-status').style.color = "#00ffcc";
document.getElementById('btn-main').classList.remove('active');
document.getElementById('btn-main').innerText = "LOCKED"; // 锁定主按钮
document.getElementById('btn-main').style.cursor = "default";
// Show Analysis
document.getElementById('analysis-placeholder').style.display = 'none';
document.getElementById('analysis-wrapper').style.display = 'flex';
drawAnalysisLoop();
}
/* -----------------------------------------------------------
4. 分析与调制 (Modulation)
----------------------------------------------------------- */
// 药剂按钮绑定
function moveTarget(dx, dy) {
if(state.phase !== 2 || state.targetLocked) return;
state.targetPos.x += dx;
state.targetPos.y += dy;
checkModulation();
}
document.getElementById('btn-up').onclick = () => moveTarget(0, -5);
document.getElementById('btn-down').onclick = () => moveTarget(0, 5);
document.getElementById('btn-left').onclick = () => moveTarget(-5, 0);
document.getElementById('btn-right').onclick = () => moveTarget(5, 0);
function checkModulation() {
const dist = Math.hypot(state.targetPos.x, state.targetPos.y);
const btn = document.getElementById('btn-inject');
if(dist < 5) {
state.targetLocked = true;
state.targetPos.x = 0;
state.targetPos.y = 0;
btn.style.display = 'block';
setTimeout(() => btn.classList.add('ready'), 100);
document.getElementById('sys-status').innerText = "COMPOUND BALANCED. READY TO INJECT.";
}
}
// 注入药剂
document.getElementById('btn-inject').onclick = () => {
if(!state.targetLocked) return;
startPurification();
};
function startPurification() {
state.phase = 3;
document.getElementById('btn-inject').classList.remove('ready');
document.getElementById('btn-inject').innerText = "INJECTING...";
document.getElementById('tank-area').classList.add('purified'); // 背景变色
document.getElementById('sys-status').innerText = "NEUTRALIZING AGENT RELEASED...";
// 发射抗体
fragments.forEach(f => {
// 为每个碎片生成几个抗体粒子
for(let i=0; i<3; i++) {
particles.push(new Antibody(f.x, f.y));
}
// 稍微延迟后碎片开始净化
setTimeout(() => { f.purified = true; }, 1000 + Math.random()*1000);
});
}
function drawAnalysisLoop() {
if(state.phase < 2) return; // Stop if reset
const cw = cMap.width;
const ch = cMap.height;
const cx = cw/2;
const cy = ch/2;
mCtx.clearRect(0,0,cw,ch);
// Grid
mCtx.strokeStyle = '#333';
mCtx.lineWidth = 1;
mCtx.beginPath();
mCtx.moveTo(cx, 0); mCtx.lineTo(cx, ch);
mCtx.moveTo(0, cy); mCtx.lineTo(cw, cy);
mCtx.stroke();
mCtx.strokeStyle = '#222';
mCtx.beginPath();
mCtx.arc(cx, cy, 30, 0, Math.PI*2);
mCtx.arc(cx, cy, 60, 0, Math.PI*2);
mCtx.stroke();
// Target Point
const tx = cx + state.targetPos.x;
const ty = cy + state.targetPos.y;
// Dashed Line to Center
mCtx.strokeStyle = '#555';
mCtx.setLineDash([4,4]);
mCtx.beginPath();
mCtx.moveTo(cx, cy);
mCtx.lineTo(tx, ty);
mCtx.stroke();
mCtx.setLineDash([]);
// Draw Point
mCtx.fillStyle = state.targetLocked ? '#fff' : '#ff3333';
mCtx.shadowBlur = 10;
mCtx.shadowColor = state.targetLocked ? '#00ffcc' : '#ff0000';
mCtx.beginPath();
mCtx.arc(tx, ty, 6, 0, Math.PI*2);
mCtx.fill();
mCtx.shadowBlur = 0;
requestAnimationFrame(drawAnalysisLoop);
}
/* -----------------------------------------------------------
5. 主循环
----------------------------------------------------------- */
function loop() {
requestAnimationFrame(loop);
ctx.clearRect(0,0,w,h);
dusts.forEach(d => { d.update(); d.draw(); });
// 净化阶段检测
if(state.phase === 3) {
// 处理抗体
particles.forEach((p,i) => {
p.update();
p.draw();
if(p.dead) particles.splice(i, 1);
});
// 检查是否全部清除
if(fragments.length === 0 && particles.length === 0) {
state.phase = 4;
document.getElementById('sys-status').innerText = "PURIFICATION COMPLETE. SYSTEM CLEAN.";
document.getElementById('sys-status').style.color = "#fff";
document.getElementById('btn-inject').innerText = "DONE";
}
}
// 绘制连线 (只在未净化时)
if(state.phase < 3 && fragments.length > 1) {
ctx.strokeStyle = 'rgba(255, 50, 50, 0.3)';
ctx.beginPath();
fragments.forEach((f1, i) => {
fragments.forEach((f2, j) => {
if(i<j && !f1.dead && !f2.dead) {
const d = Math.hypot(f1.x-f2.x, f1.y-f2.y);
if(d < 150) {
ctx.moveTo(f1.x, f1.y);
ctx.lineTo(f2.x, f2.y);
}
}
});
});
ctx.stroke();
}
// 碎片
fragments.forEach((f, i) => {
f.update();
f.draw();
if(f.dead) fragments.splice(i,1);
});
drawWave();
}
loop();
// Init
[0,1,2].forEach(i => document.getElementById(`s-${i}`).oninput = e => updateParam(i, e.target.value));
</script>
</body>
</html>