From c2d2c87fbe3398a613299c39714d2e00e73f9124 Mon Sep 17 00:00:00 2001
From: bottlefish <781230111@qq.com>
Date: Thu, 13 Nov 2025 20:30:13 +0800
Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E6=96=B0=E7=9A=84=E6=95=88?=
=?UTF-8?q?=E6=9E=9C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Assets/Scenes/languageTest.unity | 572 +++++++++++-
.../HuoShan/Language/CandidateParticle.cs | 82 +-
.../HuoShan/Language/ConnectionRenderer.cs | 65 +-
.../HuoShan/Language/EffectPropagation.cs | 76 +-
.../HuoShan/Language/FloatingTextParticle.cs | 36 +-
.../Language/LanguageParticleManager.cs | 858 +++++++++++++-----
.../LanguageParticleSystem_Summary.md | 55 --
.../Language/NeuralPropagationConfig.asset | 32 +
.../NeuralPropagationConfig.asset.meta | 8 +
.../MiniGame/HuoShan/Language/TextParticle.cs | 26 +
.../HuoShan/Language/WaveformRenderer.cs | 4 +-
11 files changed, 1455 insertions(+), 359 deletions(-)
create mode 100644 Assets/Scripts/MiniGame/HuoShan/Language/NeuralPropagationConfig.asset
create mode 100644 Assets/Scripts/MiniGame/HuoShan/Language/NeuralPropagationConfig.asset.meta
diff --git a/Assets/Scenes/languageTest.unity b/Assets/Scenes/languageTest.unity
index b59dddfd8..6a0699da3 100644
--- a/Assets/Scenes/languageTest.unity
+++ b/Assets/Scenes/languageTest.unity
@@ -180,6 +180,299 @@ MonoBehaviour:
targetSentence: "\u522B\u8FC7\u6765\u6211\u611F\u89C9\u5BB3\u6015"
waveformAmplitude: 0.3
interactionRadius: 0.5
+ mouseInteractionRadius: 1.5
+ repulsionForce: 2
+ attractionForce: 1.5
+ enableAutoPropagation: 1
+ autoPropagationInterval: 2
+ autoPropagationCount: 1
+ propagationDuration: 1
+ propagationDelay: 0.08
+ maxPropagationNodes: 8
+ targetParticleColor: {r: 0.2, g: 0.78039217, b: 1, a: 1}
+ nonTargetParticleColor: {r: 1, g: 0.40870008, b: 0.38823533, a: 0.69803923}
+ propagationColor: {r: 0.6621573, g: 0.8664762, b: 0.9433962, a: 1}
+ floatingTextColor: {r: 1, g: 1, b: 1, a: 1}
+ targetCalmColor: {r: 0.78, g: 0.59, b: 0.59, a: 1}
+ targetParticleFontSize: 3.5
+ nonTargetParticleFontSize: 2.5
+ floatingTextFontSize: 2
+ targetParticleBold: 1
+ nonTargetParticleBold: 0
+ targetConnectionColor: {r: 0.3882353, g: 0.78039217, b: 1, a: 0.6666667}
+ nonTargetConnectionColor: {r: 1, g: 0.41911593, b: 0.38823533, a: 0.7}
+ propagationLineColor: {r: 0.6627451, g: 0.8666667, b: 0.94509804, a: 1}
+ targetConnectionThickness: 0.04
+ nonTargetConnectionThickness: 0.02
+ propagationLineThickness: 0.03
+ integrationProgressText: {fileID: 1294290436}
+ interferenceCountText: {fileID: 528708888}
+ focusSequenceButton: {fileID: 153766885}
+ focusZoomScale: 1.6
+ focusMoveDuration: 1.2
+ focusFadeDuration: 1
+ probabilityDuration: 3.5
+ predictionSequenceGap: 0.4
+ spreadDistance: 0.35
+ enableSeparation: 1
+ minParticleSpacing: 0.6
+ separationForce: 2.5
+ lineAlphaMultiplier: 0.65
+--- !u!1 &153766884
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 153766888}
+ - component: {fileID: 153766887}
+ - component: {fileID: 153766886}
+ - component: {fileID: 153766885}
+ m_Layer: 5
+ m_Name: Button
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!114 &153766885
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 153766884}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Navigation:
+ m_Mode: 3
+ m_WrapAround: 0
+ m_SelectOnUp: {fileID: 0}
+ m_SelectOnDown: {fileID: 0}
+ m_SelectOnLeft: {fileID: 0}
+ m_SelectOnRight: {fileID: 0}
+ m_Transition: 1
+ m_Colors:
+ m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
+ m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
+ m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
+ m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
+ m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
+ m_ColorMultiplier: 1
+ m_FadeDuration: 0.1
+ m_SpriteState:
+ m_HighlightedSprite: {fileID: 0}
+ m_PressedSprite: {fileID: 0}
+ m_SelectedSprite: {fileID: 0}
+ m_DisabledSprite: {fileID: 0}
+ m_AnimationTriggers:
+ m_NormalTrigger: Normal
+ m_HighlightedTrigger: Highlighted
+ m_PressedTrigger: Pressed
+ m_SelectedTrigger: Selected
+ m_DisabledTrigger: Disabled
+ m_Interactable: 1
+ m_TargetGraphic: {fileID: 153766886}
+ m_OnClick:
+ m_PersistentCalls:
+ m_Calls: []
+--- !u!114 &153766886
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 153766884}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 1, g: 1, b: 1, a: 1}
+ m_RaycastTarget: 1
+ m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+ m_Maskable: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
+ m_Type: 1
+ m_PreserveAspect: 0
+ m_FillCenter: 1
+ m_FillMethod: 4
+ m_FillAmount: 1
+ m_FillClockwise: 1
+ m_FillOrigin: 0
+ m_UseSpriteMesh: 0
+ m_PixelsPerUnitMultiplier: 1
+--- !u!222 &153766887
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 153766884}
+ m_CullTransparentMesh: 1
+--- !u!224 &153766888
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 153766884}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 0.012000492, y: 0.012000492, z: 0.012000492}
+ m_ConstrainProportionsScale: 0
+ m_Children:
+ - {fileID: 1137525304}
+ m_Father: {fileID: 1665911936}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0.5, y: 0.5}
+ m_AnchorMax: {x: 0.5, y: 0.5}
+ m_AnchoredPosition: {x: 6.26, y: 2.12}
+ m_SizeDelta: {x: 160, y: 80}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!1 &528708887
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 528708890}
+ - component: {fileID: 528708889}
+ - component: {fileID: 528708888}
+ m_Layer: 5
+ m_Name: Text (TMP) (2)
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!114 &528708888
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 528708887}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 1, g: 1, b: 1, a: 1}
+ m_RaycastTarget: 1
+ m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+ m_Maskable: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_text: New Text
+ m_isRightToLeft: 0
+ m_fontAsset: {fileID: 11400000, guid: f242fb3dde1933640859f1c54591c9c8, type: 2}
+ m_sharedMaterial: {fileID: -6659093157667844055, guid: f242fb3dde1933640859f1c54591c9c8, type: 2}
+ m_fontSharedMaterials: []
+ m_fontMaterial: {fileID: 0}
+ m_fontMaterials: []
+ m_fontColor32:
+ serializedVersion: 2
+ rgba: 4284643057
+ m_fontColor: {r: 0.9433962, g: 0.4632709, b: 0.38625845, a: 1}
+ m_enableVertexGradient: 0
+ m_colorMode: 3
+ m_fontColorGradient:
+ topLeft: {r: 1, g: 1, b: 1, a: 1}
+ topRight: {r: 1, g: 1, b: 1, a: 1}
+ bottomLeft: {r: 1, g: 1, b: 1, a: 1}
+ bottomRight: {r: 1, g: 1, b: 1, a: 1}
+ m_fontColorGradientPreset: {fileID: 0}
+ m_spriteAsset: {fileID: 0}
+ m_tintAllSprites: 0
+ m_StyleSheet: {fileID: 0}
+ m_TextStyleHashCode: -1183493901
+ m_overrideHtmlColors: 0
+ m_faceColor:
+ serializedVersion: 2
+ rgba: 4294967295
+ m_fontSize: 0.3
+ m_fontSizeBase: 0.3
+ m_fontWeight: 400
+ m_enableAutoSizing: 0
+ m_fontSizeMin: 18
+ m_fontSizeMax: 72
+ m_fontStyle: 0
+ m_HorizontalAlignment: 1
+ m_VerticalAlignment: 512
+ m_textAlignment: 65535
+ m_characterSpacing: 0
+ m_wordSpacing: 0
+ m_lineSpacing: 0
+ m_lineSpacingMax: 0
+ m_paragraphSpacing: 0
+ m_charWidthMaxAdj: 0
+ m_enableWordWrapping: 1
+ m_wordWrappingRatios: 0.4
+ m_overflowMode: 0
+ m_linkedTextComponent: {fileID: 0}
+ parentLinkedComponent: {fileID: 0}
+ m_enableKerning: 1
+ m_enableExtraPadding: 0
+ checkPaddingRequired: 0
+ m_isRichText: 1
+ m_parseCtrlCharacters: 1
+ m_isOrthographic: 1
+ m_isCullingEnabled: 0
+ m_horizontalMapping: 0
+ m_verticalMapping: 0
+ m_uvLineOffset: 0
+ m_geometrySortingOrder: 0
+ m_IsTextObjectScaleStatic: 0
+ m_VertexBufferAutoSizeReduction: 0
+ m_useMaxVisibleDescender: 1
+ m_pageToDisplay: 1
+ m_margin: {x: 0, y: 0.41540456, z: -2.6862788, w: 0}
+ m_isUsingLegacyAnimationComponent: 0
+ m_isVolumetricText: 0
+ m_hasFontAssetChanged: 0
+ m_baseMaterial: {fileID: 0}
+ m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
+--- !u!222 &528708889
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 528708887}
+ m_CullTransparentMesh: 1
+--- !u!224 &528708890
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 528708887}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 1665911936}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 1}
+ m_AnchorMax: {x: 0, y: 1}
+ m_AnchoredPosition: {x: 10.839999, y: -1.5600002}
+ m_SizeDelta: {x: 1, y: 1}
+ m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &786157861
GameObject:
m_ObjectHideFlags: 0
@@ -260,8 +553,8 @@ Camera:
m_GameObject: {fileID: 786157861}
m_Enabled: 1
serializedVersion: 2
- m_ClearFlags: 1
- m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
+ m_ClearFlags: 2
+ m_BackGroundColor: {r: 0, g: 0, b: 0, a: 0}
m_projectionMatrixMode: 1
m_GateFitMode: 2
m_FOVAxisMode: 0
@@ -317,6 +610,274 @@ Transform:
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &1137525303
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1137525304}
+ - component: {fileID: 1137525306}
+ - component: {fileID: 1137525305}
+ m_Layer: 5
+ m_Name: Text (TMP)
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &1137525304
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1137525303}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 153766888}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 0}
+ m_AnchorMax: {x: 1, y: 1}
+ m_AnchoredPosition: {x: 0, y: 0}
+ m_SizeDelta: {x: 0, y: 0}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &1137525305
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1137525303}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 1, g: 1, b: 1, a: 1}
+ m_RaycastTarget: 1
+ m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+ m_Maskable: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_text: "\u8F93\u51FA"
+ m_isRightToLeft: 0
+ m_fontAsset: {fileID: 11400000, guid: f242fb3dde1933640859f1c54591c9c8, type: 2}
+ m_sharedMaterial: {fileID: -6659093157667844055, guid: f242fb3dde1933640859f1c54591c9c8, type: 2}
+ m_fontSharedMaterials: []
+ m_fontMaterial: {fileID: 0}
+ m_fontMaterials: []
+ m_fontColor32:
+ serializedVersion: 2
+ rgba: 4281479730
+ m_fontColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
+ m_enableVertexGradient: 0
+ m_colorMode: 3
+ m_fontColorGradient:
+ topLeft: {r: 1, g: 1, b: 1, a: 1}
+ topRight: {r: 1, g: 1, b: 1, a: 1}
+ bottomLeft: {r: 1, g: 1, b: 1, a: 1}
+ bottomRight: {r: 1, g: 1, b: 1, a: 1}
+ m_fontColorGradientPreset: {fileID: 0}
+ m_spriteAsset: {fileID: 0}
+ m_tintAllSprites: 0
+ m_StyleSheet: {fileID: 0}
+ m_TextStyleHashCode: -1183493901
+ m_overrideHtmlColors: 0
+ m_faceColor:
+ serializedVersion: 2
+ rgba: 4294967295
+ m_fontSize: 24
+ m_fontSizeBase: 24
+ m_fontWeight: 400
+ m_enableAutoSizing: 0
+ m_fontSizeMin: 18
+ m_fontSizeMax: 72
+ m_fontStyle: 0
+ m_HorizontalAlignment: 2
+ m_VerticalAlignment: 512
+ m_textAlignment: 65535
+ m_characterSpacing: 0
+ m_wordSpacing: 0
+ m_lineSpacing: 0
+ m_lineSpacingMax: 0
+ m_paragraphSpacing: 0
+ m_charWidthMaxAdj: 0
+ m_enableWordWrapping: 1
+ m_wordWrappingRatios: 0.4
+ m_overflowMode: 0
+ m_linkedTextComponent: {fileID: 0}
+ parentLinkedComponent: {fileID: 0}
+ m_enableKerning: 1
+ m_enableExtraPadding: 0
+ checkPaddingRequired: 0
+ m_isRichText: 1
+ m_parseCtrlCharacters: 1
+ m_isOrthographic: 1
+ m_isCullingEnabled: 0
+ m_horizontalMapping: 0
+ m_verticalMapping: 0
+ m_uvLineOffset: 0
+ m_geometrySortingOrder: 0
+ m_IsTextObjectScaleStatic: 0
+ m_VertexBufferAutoSizeReduction: 0
+ m_useMaxVisibleDescender: 1
+ m_pageToDisplay: 1
+ m_margin: {x: 0, y: 0, z: 0, w: 0}
+ m_isUsingLegacyAnimationComponent: 0
+ m_isVolumetricText: 0
+ m_hasFontAssetChanged: 0
+ m_baseMaterial: {fileID: 0}
+ m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
+--- !u!222 &1137525306
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1137525303}
+ m_CullTransparentMesh: 1
+--- !u!1 &1294290433
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1294290434}
+ - component: {fileID: 1294290435}
+ - component: {fileID: 1294290436}
+ m_Layer: 5
+ m_Name: Text (TMP) (1)
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &1294290434
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1294290433}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 1665911936}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 1}
+ m_AnchorMax: {x: 0, y: 1}
+ m_AnchoredPosition: {x: 10.83, y: -0.71}
+ m_SizeDelta: {x: 1, y: 1}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!222 &1294290435
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1294290433}
+ m_CullTransparentMesh: 1
+--- !u!114 &1294290436
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1294290433}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 1, g: 1, b: 1, a: 1}
+ m_RaycastTarget: 1
+ m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+ m_Maskable: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_text: New Text
+ m_isRightToLeft: 0
+ m_fontAsset: {fileID: 11400000, guid: f242fb3dde1933640859f1c54591c9c8, type: 2}
+ m_sharedMaterial: {fileID: -6659093157667844055, guid: f242fb3dde1933640859f1c54591c9c8, type: 2}
+ m_fontSharedMaterials: []
+ m_fontMaterial: {fileID: 0}
+ m_fontMaterials: []
+ m_fontColor32:
+ serializedVersion: 2
+ rgba: 4294040693
+ m_fontColor: {r: 0.45745817, g: 0.86362934, b: 0.9433962, a: 1}
+ m_enableVertexGradient: 0
+ m_colorMode: 3
+ m_fontColorGradient:
+ topLeft: {r: 1, g: 1, b: 1, a: 1}
+ topRight: {r: 1, g: 1, b: 1, a: 1}
+ bottomLeft: {r: 1, g: 1, b: 1, a: 1}
+ bottomRight: {r: 1, g: 1, b: 1, a: 1}
+ m_fontColorGradientPreset: {fileID: 0}
+ m_spriteAsset: {fileID: 0}
+ m_tintAllSprites: 0
+ m_StyleSheet: {fileID: 0}
+ m_TextStyleHashCode: -1183493901
+ m_overrideHtmlColors: 0
+ m_faceColor:
+ serializedVersion: 2
+ rgba: 4294967295
+ m_fontSize: 0.3
+ m_fontSizeBase: 0.3
+ m_fontWeight: 400
+ m_enableAutoSizing: 0
+ m_fontSizeMin: 18
+ m_fontSizeMax: 72
+ m_fontStyle: 0
+ m_HorizontalAlignment: 1
+ m_VerticalAlignment: 512
+ m_textAlignment: 65535
+ m_characterSpacing: 0
+ m_wordSpacing: 0
+ m_lineSpacing: 0
+ m_lineSpacingMax: 0
+ m_paragraphSpacing: 0
+ m_charWidthMaxAdj: 0
+ m_enableWordWrapping: 1
+ m_wordWrappingRatios: 0.4
+ m_overflowMode: 0
+ m_linkedTextComponent: {fileID: 0}
+ parentLinkedComponent: {fileID: 0}
+ m_enableKerning: 1
+ m_enableExtraPadding: 0
+ checkPaddingRequired: 0
+ m_isRichText: 1
+ m_parseCtrlCharacters: 1
+ m_isOrthographic: 1
+ m_isCullingEnabled: 0
+ m_horizontalMapping: 0
+ m_verticalMapping: 0
+ m_uvLineOffset: 0
+ m_geometrySortingOrder: 0
+ m_IsTextObjectScaleStatic: 0
+ m_VertexBufferAutoSizeReduction: 0
+ m_useMaxVisibleDescender: 1
+ m_pageToDisplay: 1
+ m_margin: {x: 0, y: 0.41540456, z: -2.6862788, w: 0}
+ m_isUsingLegacyAnimationComponent: 0
+ m_isVolumetricText: 0
+ m_hasFontAssetChanged: 0
+ m_baseMaterial: {fileID: 0}
+ m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!1 &1665911932
GameObject:
m_ObjectHideFlags: 0
@@ -410,12 +971,15 @@ RectTransform:
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
- m_Children: []
+ m_Children:
+ - {fileID: 1294290434}
+ - {fileID: 528708890}
+ - {fileID: 153766888}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
- m_AnchoredPosition: {x: 0, y: 0}
+ m_AnchoredPosition: {x: -2.54, y: 0}
m_SizeDelta: {x: 10, y: 10}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &1764870496
diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/CandidateParticle.cs b/Assets/Scripts/MiniGame/HuoShan/Language/CandidateParticle.cs
index 323a8022a..749fd182d 100644
--- a/Assets/Scripts/MiniGame/HuoShan/Language/CandidateParticle.cs
+++ b/Assets/Scripts/MiniGame/HuoShan/Language/CandidateParticle.cs
@@ -40,6 +40,18 @@ namespace AibisDream.MiniGame.Language
public float dissolveProgress = 0f; // 消散进度(0-1)
public WaveformPoint waveformPoint; // 关联的波形点
+ // 颜色配置(由Manager设置)
+ private Color targetColor = new Color(1f, 0.39f, 0.39f, 1f); // 目标粒子颜色
+ private Color nonTargetColor = new Color(0.2f, 0.78f, 1f, 1f); // 非目标粒子颜色
+ private Color propagationColor = new Color(1f, 0.59f, 0.2f, 1f); // 传播颜色
+ private Color calmColor = new Color(0.78f, 0.59f, 0.59f, 1f); // 平静后颜色
+
+ // 字体样式配置(由Manager设置)
+ private float targetFontSize = 3.5f;
+ private float nonTargetFontSize = 2.5f;
+ private bool targetBold = true;
+ private bool nonTargetBold = false;
+
protected override void Awake()
{
base.Awake();
@@ -47,13 +59,14 @@ namespace AibisDream.MiniGame.Language
baseAlpha = 0.6f; // 0-1 范围的透明度
probability = Random.Range(0.1f, 1f);
alpha = baseAlpha + probability * 0.4f; // 最大 1.0
- size = 3f;
+ size = nonTargetFontSize; // 默认使用非目标粒子大小
velocity = new Vector2(Random.Range(-0.5f, 0.5f), Random.Range(-0.5f, 0.5f));
changeInterval = Random.Range(1f, 2.5f);
if (textMesh != null)
{
textMesh.fontSize = size;
+ UpdateFontStyle();
}
}
@@ -125,6 +138,9 @@ namespace AibisDream.MiniGame.Language
{
if (textMesh == null) return;
+ // 更新字体样式(大小和加粗)
+ UpdateFontStyle();
+
// 计算显示位置
Vector3 displayOffset = (Vector3)(vibrationOffset + anxietyShakeOffset) + Vector3.up * anxietyFloatY;
textMesh.transform.localPosition = displayOffset;
@@ -144,19 +160,21 @@ namespace AibisDream.MiniGame.Language
if (isRed)
{
- // 红色粒子:根据平静进度调整颜色
- float r = Mathf.Lerp(1f, 0.78f, calmProgress);
- float g = Mathf.Lerp(0.39f, 0.59f, calmProgress);
- float b = Mathf.Lerp(0.39f, 0.59f, calmProgress);
- color = new Color(r, g, b, displayAlpha); // displayAlpha 已经是 0-1 范围
+ // 目标粒子:根据平静进度从目标颜色过渡到平静颜色
+ float r = Mathf.Lerp(targetColor.r, calmColor.r, calmProgress);
+ float g = Mathf.Lerp(targetColor.g, calmColor.g, calmProgress);
+ float b = Mathf.Lerp(targetColor.b, calmColor.b, calmProgress);
+ color = new Color(r, g, b, displayAlpha);
}
else if (isOrange)
{
- color = new Color(1f, 0.59f, 0.2f, displayAlpha);
+ // 传播效果颜色
+ color = new Color(propagationColor.r, propagationColor.g, propagationColor.b, displayAlpha);
}
else
{
- color = new Color(0.2f, 0.78f, 1f, displayAlpha);
+ // 非目标粒子颜色
+ color = new Color(nonTargetColor.r, nonTargetColor.g, nonTargetColor.b, displayAlpha);
}
textMesh.color = color;
@@ -199,5 +217,53 @@ namespace AibisDream.MiniGame.Language
vibrationOffset = Vector2.zero;
effectTimer = 0;
}
+
+ ///
+ /// 设置粒子颜色配置
+ ///
+ public void SetColors(Color target, Color nonTarget, Color propagation, Color calm)
+ {
+ targetColor = target;
+ nonTargetColor = nonTarget;
+ propagationColor = propagation;
+ calmColor = calm;
+ }
+
+ ///
+ /// 设置字体样式配置
+ ///
+ public void SetFontStyles(float targetSize, float nonTargetSize, bool targetIsBold, bool nonTargetIsBold)
+ {
+ targetFontSize = targetSize;
+ nonTargetFontSize = nonTargetSize;
+ targetBold = targetIsBold;
+ nonTargetBold = nonTargetIsBold;
+
+ // 立即应用
+ if (textMesh != null)
+ {
+ UpdateFontStyle();
+ }
+ }
+
+ ///
+ /// 更新字体样式(根据粒子状态)
+ ///
+ private void UpdateFontStyle()
+ {
+ if (textMesh == null) return;
+
+ // 根据是否为目标粒子设置大小和加粗
+ if (isRed)
+ {
+ textMesh.fontSize = targetFontSize;
+ textMesh.fontStyle = targetBold ? TMPro.FontStyles.Bold : TMPro.FontStyles.Normal;
+ }
+ else
+ {
+ textMesh.fontSize = nonTargetFontSize;
+ textMesh.fontStyle = nonTargetBold ? TMPro.FontStyles.Bold : TMPro.FontStyles.Normal;
+ }
+ }
}
}
diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/ConnectionRenderer.cs b/Assets/Scripts/MiniGame/HuoShan/Language/ConnectionRenderer.cs
index 48b50169d..fc0d7461d 100644
--- a/Assets/Scripts/MiniGame/HuoShan/Language/ConnectionRenderer.cs
+++ b/Assets/Scripts/MiniGame/HuoShan/Language/ConnectionRenderer.cs
@@ -6,6 +6,7 @@ namespace AibisDream.MiniGame.Language
{
///
/// 连接线渲染器:使用Shapes绘制粒子间的连接线
+ /// 注意:sortingOrder 设置为 0,确保低于文字(文字 sortingOrder = 10)
///
[ExecuteAlways]
public class ConnectionRenderer : ImmediateModeShapeDrawer
@@ -15,19 +16,33 @@ namespace AibisDream.MiniGame.Language
[Header("连接线设置")]
public float connectionDistance = 2f;
- public Color blueConnectionColor = new Color(0.39f, 0.78f, 1f, 0.7f);
- public Color redConnectionColor = new Color(1f, 0.39f, 0.39f, 1f);
- public float blueLineThickness = 0.02f;
- public float redLineThickness = 0.04f;
+ private Color nonTargetConnectionColor = new Color(0.39f, 0.78f, 1f, 0.7f); // 非目标粒子连线颜色
+ private Color targetConnectionColor = new Color(1f, 0.39f, 0.39f, 1f); // 目标粒子连线颜色
+ private float nonTargetLineThickness = 0.02f;
+ private float targetLineThickness = 0.04f;
[Header("效果传播设置")]
- public Color propagationColor = new Color(1f, 0.59f, 0.2f, 1f);
- public float propagationLineThickness = 0.03f;
+ private Color propagationColor = new Color(1f, 0.59f, 0.2f, 1f);
+ private float propagationLineThickness = 0.03f;
private List candidateParticles;
private List effectPropagations;
private CompletionPhase completionPhase;
private float fadeOutAlpha;
+ private float nonTargetAlphaScale = 1f;
+ private float targetAlphaScale = 1f;
+
+ public float NonTargetAlphaScale
+ {
+ get => nonTargetAlphaScale;
+ set => nonTargetAlphaScale = Mathf.Clamp01(value);
+ }
+
+ public float TargetAlphaScale
+ {
+ get => targetAlphaScale;
+ set => targetAlphaScale = Mathf.Clamp(value, 0f, 1.5f);
+ }
public void SetData(
List candidates,
@@ -50,15 +65,18 @@ namespace AibisDream.MiniGame.Language
{
Draw.BlendMode = ShapesBlendMode.Transparent;
Draw.ZTest = UnityEngine.Rendering.CompareFunction.Always;
+ // 注意:连接线默认在底层,文字的 sortingOrder = 10 会显示在上层
// 绘制蓝色粒子间的连线
- if (completionPhase == CompletionPhase.None || completionPhase == CompletionPhase.Arranging)
+ if (completionPhase == CompletionPhase.None || completionPhase == CompletionPhase.Completed)
{
DrawBlueConnections();
}
// 绘制红色粒子间的连线
- if (completionPhase == CompletionPhase.None || completionPhase == CompletionPhase.Arranging)
+ if (completionPhase == CompletionPhase.None ||
+ completionPhase == CompletionPhase.Completed ||
+ completionPhase == CompletionPhase.Focusing)
{
DrawRedConnections();
}
@@ -74,7 +92,7 @@ namespace AibisDream.MiniGame.Language
private void DrawBlueConnections()
{
Draw.LineGeometry = LineGeometry.Flat2D;
- Draw.Thickness = blueLineThickness;
+ Draw.Thickness = nonTargetLineThickness;
for (int i = 0; i < candidateParticles.Count; i++)
{
@@ -90,8 +108,8 @@ namespace AibisDream.MiniGame.Language
if (distance < connectionDistance)
{
float alpha = Mathf.Lerp(0.7f, 0f, distance / connectionDistance) * fadeOutAlpha;
- Color lineColor = blueConnectionColor;
- lineColor.a = alpha;
+ Color lineColor = nonTargetConnectionColor;
+ lineColor.a = alpha * nonTargetAlphaScale;
Draw.Line(p1.transform.position, p2.transform.position, lineColor);
}
@@ -102,7 +120,7 @@ namespace AibisDream.MiniGame.Language
private void DrawRedConnections()
{
Draw.LineGeometry = LineGeometry.Flat2D;
- Draw.Thickness = redLineThickness;
+ Draw.Thickness = targetLineThickness;
var redParticles = new List();
foreach (var p in candidateParticles)
@@ -123,8 +141,8 @@ namespace AibisDream.MiniGame.Language
if (distance < connectionDistance)
{
float alpha = Mathf.Lerp(1f, 0.4f, distance / connectionDistance);
- Color lineColor = redConnectionColor;
- lineColor.a = alpha;
+ Color lineColor = targetConnectionColor;
+ lineColor.a = alpha * targetAlphaScale;
Draw.Line(redParticles[i].transform.position, redParticles[j].transform.position, lineColor);
}
@@ -205,6 +223,25 @@ namespace AibisDream.MiniGame.Language
Draw.Triangle(tip, left, right, color);
}
+
+ ///
+ /// 设置连接线样式
+ ///
+ public void SetConnectionStyles(
+ Color targetColor,
+ Color nonTargetColor,
+ Color propColor,
+ float targetThickness,
+ float nonTargetThickness,
+ float propThickness)
+ {
+ targetConnectionColor = targetColor;
+ nonTargetConnectionColor = nonTargetColor;
+ propagationColor = propColor;
+ targetLineThickness = targetThickness;
+ nonTargetLineThickness = nonTargetThickness;
+ propagationLineThickness = propThickness;
+ }
}
}
diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/EffectPropagation.cs b/Assets/Scripts/MiniGame/HuoShan/Language/EffectPropagation.cs
index 66ff537f9..52a0c2d3e 100644
--- a/Assets/Scripts/MiniGame/HuoShan/Language/EffectPropagation.cs
+++ b/Assets/Scripts/MiniGame/HuoShan/Language/EffectPropagation.cs
@@ -23,42 +23,46 @@ namespace AibisDream.MiniGame.Language
}
///
- /// 效果传播系统:管理从红色粒子到蓝色粒子的效果传递
+ /// 效果传播系统:管理从红色粒子到蓝色粒子的效果传递(使用BFS算法)
///
public class EffectPropagation
{
private CandidateParticle redParticle;
private List propagatedParticles = new List();
- private List propagationQueue = new List();
+ private Queue propagationQueue = new Queue();
private float lifeTimer;
- private float propagationDelay = 0.05f;
+ private float propagationDelay;
private float propagationCounter = 0f;
- private int maxPropagationSteps = 8;
- private int currentPropagationStep = 0;
+ private int maxPropagationNodes;
+ private int currentNodeCount = 0;
+ private int currentStep = 0;
public bool IsActive => lifeTimer > 0;
public List PropagatedParticles => propagatedParticles;
- public EffectPropagation(CandidateParticle redParticle, float duration = 1f)
+ public EffectPropagation(CandidateParticle redParticle, float duration, float delay, int maxNodes)
{
this.redParticle = redParticle;
this.lifeTimer = duration;
+ this.propagationDelay = delay;
+ this.maxPropagationNodes = maxNodes;
- // 从红色粒子开始传播
+ // 从红色粒子开始传播(第0步)
var connectedBlues = FindConnectedBlues(redParticle);
- if (connectedBlues.Count > 0 && currentPropagationStep < maxPropagationSteps)
+ if (connectedBlues.Count > 0 && currentNodeCount < maxPropagationNodes)
{
- // 随机选择一个连接的蓝色粒子
- var targetParticle = connectedBlues[Random.Range(0, connectedBlues.Count)];
- ApplyEffectToParticle(targetParticle);
+ // 第一步:处理所有直接连接的蓝色粒子(BFS第一层)
+ foreach (var targetParticle in connectedBlues)
+ {
+ if (currentNodeCount >= maxPropagationNodes) break;
- // 记录传播信息
- propagatedParticles.Add(new PropagationInfo(targetParticle, redParticle, 1));
-
- // 加入传播队列
- propagationQueue.Add(targetParticle);
- currentPropagationStep = 1;
+ ApplyEffectToParticle(targetParticle);
+ propagatedParticles.Add(new PropagationInfo(targetParticle, redParticle, 1));
+ propagationQueue.Enqueue(targetParticle);
+ currentNodeCount++;
+ }
+ currentStep = 1;
}
}
@@ -67,35 +71,33 @@ namespace AibisDream.MiniGame.Language
lifeTimer -= deltaTime;
propagationCounter += deltaTime;
- // 定期传播
- if (propagationCounter >= propagationDelay && currentPropagationStep < maxPropagationSteps)
+ // BFS逐层传播
+ if (propagationCounter >= propagationDelay && propagationQueue.Count > 0 && currentNodeCount < maxPropagationNodes)
{
propagationCounter = 0f;
+ currentStep++;
- List nextQueue = new List();
-
- foreach (var sourceParticle in propagationQueue)
+ // 处理当前层的所有节点
+ int currentLevelSize = propagationQueue.Count;
+
+ for (int i = 0; i < currentLevelSize; i++)
{
- if (currentPropagationStep >= maxPropagationSteps) break;
-
+ if (currentNodeCount >= maxPropagationNodes) break;
+
+ var sourceParticle = propagationQueue.Dequeue();
var connectedBlues = FindConnectedBlues(sourceParticle);
- if (connectedBlues.Count > 0)
+
+ // 将所有连接的蓝色粒子加入下一层
+ foreach (var targetParticle in connectedBlues)
{
- var targetParticle = connectedBlues[Random.Range(0, connectedBlues.Count)];
+ if (currentNodeCount >= maxPropagationNodes) break;
+
ApplyEffectToParticle(targetParticle);
-
- currentPropagationStep++;
-
- propagatedParticles.Add(new PropagationInfo(targetParticle, sourceParticle, currentPropagationStep));
-
- if (currentPropagationStep < maxPropagationSteps)
- {
- nextQueue.Add(targetParticle);
- }
+ propagatedParticles.Add(new PropagationInfo(targetParticle, sourceParticle, currentStep + 1));
+ propagationQueue.Enqueue(targetParticle);
+ currentNodeCount++;
}
}
-
- propagationQueue = nextQueue;
}
// 更新所有传播粒子的脉冲动画
diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/FloatingTextParticle.cs b/Assets/Scripts/MiniGame/HuoShan/Language/FloatingTextParticle.cs
index a43b3fd9b..603395358 100644
--- a/Assets/Scripts/MiniGame/HuoShan/Language/FloatingTextParticle.cs
+++ b/Assets/Scripts/MiniGame/HuoShan/Language/FloatingTextParticle.cs
@@ -7,6 +7,8 @@ namespace AibisDream.MiniGame.Language
///
public class FloatingTextParticle : TextParticle
{
+ private Color baseColor = new Color(0.7f, 0.78f, 1f, 1f); // 默认颜色
+
protected override void Awake()
{
base.Awake();
@@ -15,13 +17,37 @@ namespace AibisDream.MiniGame.Language
baseAlpha = Random.Range(0.04f, 0.1f); // 0-1 范围的透明度
alpha = baseAlpha;
size = Random.Range(1.5f, 2.5f);
- velocity = new Vector2(Random.Range(-0.3f, 0.3f), Random.Range(-0.3f, 0.3f));
- changeInterval = Random.Range(0.5f, 0.7f);
+ velocity = new Vector2(Random.Range(-1.2f, 1.2f), Random.Range(-1.2f, 1.2f));
+ changeInterval = Random.Range(0.2f, 0.3f);
if (textMesh != null)
{
textMesh.fontSize = size;
- textMesh.color = new Color(0.7f, 0.78f, 1f, alpha);
+ textMesh.color = new Color(baseColor.r, baseColor.g, baseColor.b, alpha);
+ }
+ }
+
+ ///
+ /// 设置粒子颜色
+ ///
+ public void SetColor(Color color)
+ {
+ baseColor = color;
+ if (textMesh != null)
+ {
+ textMesh.color = new Color(baseColor.r, baseColor.g, baseColor.b, alpha);
+ }
+ }
+
+ ///
+ /// 设置字体大小
+ ///
+ public void SetFontSize(float fontSize)
+ {
+ size = fontSize;
+ if (textMesh != null)
+ {
+ textMesh.fontSize = size;
}
}
@@ -29,13 +55,13 @@ namespace AibisDream.MiniGame.Language
{
if (textMesh != null)
{
- Color color = new Color(0.7f, 0.78f, 1f, alpha);
+ Color color = new Color(baseColor.r, baseColor.g, baseColor.b, alpha);
textMesh.color = color;
}
if (glowSprite != null)
{
- glowSprite.color = new Color(0.7f, 0.78f, 1f, alpha * 0.3f);
+ glowSprite.color = new Color(baseColor.r, baseColor.g, baseColor.b, alpha * 0.3f);
}
}
}
diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleManager.cs b/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleManager.cs
index b86560dd2..3d2b1f892 100644
--- a/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleManager.cs
+++ b/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleManager.cs
@@ -1,9 +1,11 @@
using UnityEngine;
+using System.Collections;
using System.Collections.Generic;
using System.Linq;
using DG.Tweening;
using Shapes;
using TMPro;
+using UnityEngine.UI;
namespace AibisDream.MiniGame.Language
{
@@ -13,9 +15,9 @@ namespace AibisDream.MiniGame.Language
public enum CompletionPhase
{
None, // 未完成
- Arranging, // 排列中
- Revealing, // 揭示中
- Completed // 已完成
+ Completed, // 已完成但尚未进入聚焦流程
+ Focusing, // 正在聚焦放大并收敛
+ Finished // 全流程完成
}
///
@@ -44,7 +46,7 @@ namespace AibisDream.MiniGame.Language
[SerializeField] private float textMargin = 1f;
[Header("目标句子")]
- [SerializeField] private string targetSentence = "别过来我感觉害怕";
+ [SerializeField] private string targetSentence = "这是一个测试示例";
[Header("波形参数")]
[SerializeField] private float waveformAmplitude = 0.3f;
@@ -55,10 +57,61 @@ namespace AibisDream.MiniGame.Language
[SerializeField] private float repulsionForce = 2f;
[SerializeField] private float attractionForce = 1.5f;
+ [Header("自动传播效果")]
+ [SerializeField] private bool enableAutoPropagation = true; // 是否启用自动传播
+ [SerializeField] private float autoPropagationInterval = 2f; // 自动传播间隔(秒)
+ [SerializeField] private int autoPropagationCount = 1; // 每次自动触发的传播数量
+
+ [Header("传播效果参数")]
+ [SerializeField] private float propagationDuration = 1f; // 传播效果持续时间
+ [SerializeField] private float propagationDelay = 0.08f; // 传播延迟(每层之间的间隔)
+ [SerializeField] private int maxPropagationNodes = 15; // 最大传播节点数量
+
+ [Header("颜色配置")]
+ [SerializeField] private Color targetParticleColor = new Color(1f, 0.39f, 0.39f, 1f); // 目标粒子颜色(原红色)
+ [SerializeField] private Color nonTargetParticleColor = new Color(0.2f, 0.78f, 1f, 1f); // 非目标粒子颜色(原蓝色)
+ [SerializeField] private Color propagationColor = new Color(1f, 0.59f, 0.2f, 1f); // 传播效果颜色(橙色)
+ [SerializeField] private Color floatingTextColor = new Color(0.7f, 0.78f, 1f, 1f); // 背景浮动文字颜色
+ [SerializeField] private Color targetCalmColor = new Color(0.78f, 0.59f, 0.59f, 1f); // 目标粒子平静后颜色
+
+ [Header("文字样式配置")]
+ [SerializeField] private float targetParticleFontSize = 3.5f; // 目标粒子字体大小
+ [SerializeField] private float nonTargetParticleFontSize = 2.5f; // 非目标粒子字体大小
+ [SerializeField] private float floatingTextFontSize = 2f; // 背景浮动文字大小
+ [SerializeField] private bool targetParticleBold = true; // 目标粒子是否加粗
+ [SerializeField] private bool nonTargetParticleBold = false; // 非目标粒子是否加粗
+
+ [Header("连接线配置")]
+ [SerializeField] private Color targetConnectionColor = new Color(1f, 0.39f, 0.39f, 1f); // 目标粒子连线颜色
+ [SerializeField] private Color nonTargetConnectionColor = new Color(0.39f, 0.78f, 1f, 0.7f);// 非目标粒子连线颜色
+ [SerializeField] private Color propagationLineColor = new Color(1f, 0.59f, 0.2f, 1f); // 传播线颜色
+ [SerializeField] private float targetConnectionThickness = 0.04f; // 目标粒子连线粗细
+ [SerializeField] private float nonTargetConnectionThickness = 0.02f; // 非目标粒子连线粗细
+ [SerializeField] private float propagationLineThickness = 0.03f; // 传播线粗细
+
+ [Header("目标 UI")]
+ [SerializeField] private TMP_Text integrationProgressText;
+ [SerializeField] private TMP_Text interferenceCountText;
+
+ [Header("完成阶段 UI")]
+ [SerializeField] private Button focusSequenceButton;
+ [SerializeField] private float focusZoomScale = 1.6f;
+ [SerializeField] private float focusMoveDuration = 1.2f;
+ [SerializeField] private float focusFadeDuration = 1f;
+ [SerializeField] private float probabilityDuration = 3.5f;
+ [SerializeField] private float predictionSequenceGap = 0.4f;
+ [SerializeField] private float spreadDistance = 0.35f;
+
+ [Header("布局优化")]
+ [SerializeField] private bool enableSeparation = true; // 是否启用粒子分离力
+ [SerializeField] private float minParticleSpacing = 0.6f; // 粒子最小间距
+ [SerializeField] private float separationForce = 2.5f; // 分离力度
+ [SerializeField] private float lineAlphaMultiplier = 0.65f; // 连接线透明度缩放
+
// 粒子列表
private List floatingParticles = new List();
private List candidateParticles = new List();
- private List redParticles = new List();
+ private List targetParticles = new List(); // 目标粒子列表(原 redParticles)
// 效果系统
private List effectPropagations = new List();
@@ -72,26 +125,52 @@ namespace AibisDream.MiniGame.Language
private float completionTimer = 0f;
private float fadeOutAlpha = 1f;
private List orderedRedParticles = new List();
- private List targetPositions = new List();
+ private bool allowInput = true;
+ private float focusTimer = 0f;
+ private Dictionary focusVisuals = new Dictionary();
+ private Vector3 focusCentroid;
+ private List finalTargetCharacters = new List();
+ private List finalTargetPositions = new List();
- // 波形系统
- private List waveformPoints = new List();
- private float calmProgress = 0f;
- private Vector2 lastMousePos;
- private float mouseDragSpeed;
+ private class TargetFocusVisual
+ {
+ public CandidateParticle particle;
+ public GameObject ringObject;
+ public Disc ringDisc;
+ public TMP_Text probabilityLabel;
+ public float probability = 90f;
+ public float targetProbability = 100f;
+ public float startShakeIntensity;
+ public string finalChar;
+ public bool stabilized;
+ public Vector3 focusPosition;
+ public Vector3 finalPosition;
+ public int orderIndex;
+ }
+
+ // 自动传播计时器
+ private float autoPropagationTimer = 0f;
+ private bool focusTweensCompleted = false;
// 边界
private Bounds movementBounds;
// 连接渲染(使用Shapes)
private ConnectionRenderer connectionRenderer;
- private WaveformRenderer waveformRenderer;
private void Start()
{
InitializeCanvas();
InitializeParticles();
InitializeRenderers();
+ UpdateStatusUI();
+ if (focusSequenceButton != null)
+ {
+ focusSequenceButton.interactable = false;
+ focusSequenceButton.gameObject.SetActive(false);
+ focusSequenceButton.onClick.RemoveListener(HandleFocusButtonClicked);
+ focusSequenceButton.onClick.AddListener(HandleFocusButtonClicked);
+ }
}
private void InitializeCanvas()
@@ -136,6 +215,8 @@ namespace AibisDream.MiniGame.Language
GameObject obj = CreateParticleObject(pos, $"FloatingParticle_{i}");
FloatingTextParticle particle = obj.AddComponent();
particle.SetFont(chineseFontAsset);
+ particle.SetColor(floatingTextColor);
+ particle.SetFontSize(floatingTextFontSize);
particle.SetMovementBounds(movementBounds);
floatingParticles.Add(particle);
}
@@ -147,6 +228,8 @@ namespace AibisDream.MiniGame.Language
GameObject obj = CreateParticleObject(pos, $"CandidateParticle_{i}");
CandidateParticle particle = obj.AddComponent();
particle.SetFont(chineseFontAsset);
+ particle.SetColors(targetParticleColor, nonTargetParticleColor, propagationColor, targetCalmColor);
+ particle.SetFontStyles(targetParticleFontSize, nonTargetParticleFontSize, targetParticleBold, nonTargetParticleBold);
particle.SetMovementBounds(movementBounds);
candidateParticles.Add(particle);
}
@@ -185,27 +268,30 @@ namespace AibisDream.MiniGame.Language
connectionRenderer = connObj.AddComponent();
connectionRenderer.manager = this;
connectionRenderer.connectionDistance = connectionDistance;
-
- // 波形渲染器
- GameObject waveObj = new GameObject("WaveformRenderer");
- waveObj.transform.SetParent(transform);
- waveObj.transform.localPosition = Vector3.zero;
- waveformRenderer = waveObj.AddComponent();
- waveformRenderer.interactionRadius = interactionRadius;
+ connectionRenderer.SetConnectionStyles(
+ targetConnectionColor,
+ nonTargetConnectionColor,
+ propagationLineColor,
+ targetConnectionThickness,
+ nonTargetConnectionThickness,
+ propagationLineThickness
+ );
+ connectionRenderer.NonTargetAlphaScale = lineAlphaMultiplier;
+ connectionRenderer.TargetAlphaScale = 1f;
}
private void SelectRedParticles()
{
- redParticles.Clear();
+ targetParticles.Clear();
List shuffled = candidateParticles.OrderBy(x => Random.value).ToList();
float minDistance = Mathf.Min(movementBounds.size.x, movementBounds.size.y) / (redParticleCount * 0.8f);
foreach (var particle in shuffled)
{
- if (redParticles.Count >= redParticleCount) break;
+ if (targetParticles.Count >= redParticleCount) break;
bool tooClose = false;
- foreach (var selected in redParticles)
+ foreach (var selected in targetParticles)
{
if (Vector3.Distance(particle.transform.position, selected.transform.position) < minDistance)
{
@@ -214,23 +300,23 @@ namespace AibisDream.MiniGame.Language
}
}
- if (!tooClose || redParticles.Count == 0)
+ if (!tooClose || targetParticles.Count == 0)
{
particle.isRed = true;
particle.originalIsRed = true;
- redParticles.Add(particle);
+ targetParticles.Add(particle);
}
}
// 如果不够,强制添加
- int remaining = redParticleCount - redParticles.Count;
+ int remaining = redParticleCount - targetParticles.Count;
for (int i = 0; i < remaining && i < shuffled.Count; i++)
{
if (!shuffled[i].isRed)
{
shuffled[i].isRed = true;
shuffled[i].originalIsRed = true;
- redParticles.Add(shuffled[i]);
+ targetParticles.Add(shuffled[i]);
}
}
}
@@ -252,44 +338,42 @@ namespace AibisDream.MiniGame.Language
}
}
- // 更新连接
- UpdateConnections();
-
- // 检测连接变化(触发效果传播)
- if (initializationComplete && completionPhase == CompletionPhase.None)
+ if (completionPhase == CompletionPhase.None)
{
- DetectConnectionChanges();
- }
+ UpdateConnections();
+ UpdateStatusUI();
- // 更新效果传播
- UpdateEffectPropagations();
+ if (initializationComplete)
+ {
+ DetectConnectionChanges();
+ UpdateAutoPropagation();
+ }
- // 检测完成状态
- if (!isCompleted && completionPhase == CompletionPhase.None)
- {
- if (CheckRedParticlesConnected())
+ UpdateEffectPropagations();
+
+ if (!isCompleted && CheckRedParticlesConnected())
{
isCompleted = true;
- StartCompletionSequence();
+ OnTargetsCompleted();
}
- }
- // 更新完成动画
- if (isCompleted)
+ if (allowInput)
+ {
+ UpdateInput();
+ }
+
+ ApplySeparationForces();
+ }
+ else
{
- UpdateCompletionAnimation();
+ UpdateConnections();
+ UpdateStatusUI();
}
- // 更新波形
- if (completionPhase == CompletionPhase.Revealing || completionPhase == CompletionPhase.Completed)
+ if (completionPhase == CompletionPhase.Focusing)
{
- UpdateWaveform();
+ UpdateFocusSequence();
}
-
- // 更新输入
- UpdateInput();
-
- // 更新渲染器数据
UpdateRenderers();
}
@@ -305,21 +389,6 @@ namespace AibisDream.MiniGame.Language
fadeOutAlpha
);
}
-
- // 更新波形渲染器
- if (waveformRenderer != null)
- {
- Vector2 mousePos = GetMouseWorldPosition();
- bool mouseDown = Input.GetMouseButton(0);
- waveformRenderer.SetData(
- waveformPoints,
- completionPhase,
- calmProgress,
- mousePos,
- mouseDown,
- mouseDragSpeed
- );
- }
}
private void UpdateConnections()
@@ -418,7 +487,12 @@ namespace AibisDream.MiniGame.Language
private void TriggerEffectPropagation(CandidateParticle redParticle, CandidateParticle blueParticle)
{
- effectPropagations.Add(new EffectPropagation(redParticle, 1f));
+ effectPropagations.Add(new EffectPropagation(
+ redParticle,
+ propagationDuration,
+ propagationDelay,
+ maxPropagationNodes
+ ));
}
private void UpdateEffectPropagations()
@@ -435,16 +509,39 @@ namespace AibisDream.MiniGame.Language
}
}
+ private void UpdateAutoPropagation()
+ {
+ if (!enableAutoPropagation) return;
+ if (targetParticles.Count == 0) return;
+
+ autoPropagationTimer += Time.deltaTime;
+
+ if (autoPropagationTimer >= autoPropagationInterval)
+ {
+ autoPropagationTimer = 0f;
+
+ // 随机选择目标粒子触发传播效果
+ int triggerCount = Mathf.Min(autoPropagationCount, targetParticles.Count);
+ var shuffledTargets = targetParticles.OrderBy(x => Random.value).Take(triggerCount).ToList();
+
+ foreach (var targetParticle in shuffledTargets)
+ {
+ // 触发传播效果(即使没有建立新连接)
+ TriggerEffectPropagation(targetParticle, null);
+ }
+ }
+ }
+
private bool CheckRedParticlesConnected()
{
- if (redParticles.Count == 0) return false;
- if (redParticles.Count == 1) return true;
+ if (targetParticles.Count == 0) return false;
+ if (targetParticles.Count == 1) return true;
- // 检查所有红色粒子是否连通
+ // 检查所有目标粒子是否连通
HashSet visited = new HashSet();
Queue queue = new Queue();
- queue.Enqueue(redParticles[0]);
- visited.Add(redParticles[0]);
+ queue.Enqueue(targetParticles[0]);
+ visited.Add(targetParticles[0]);
while (queue.Count > 0)
{
@@ -459,11 +556,11 @@ namespace AibisDream.MiniGame.Language
}
}
- if (visited.Count != redParticles.Count)
+ if (visited.Count != targetParticles.Count)
return false;
- // 检查红色粒子是否与蓝色粒子分离
- foreach (var red in redParticles)
+ // 检查目标粒子是否与非目标粒子分离
+ foreach (var red in targetParticles)
{
foreach (var other in candidateParticles)
{
@@ -478,62 +575,77 @@ namespace AibisDream.MiniGame.Language
return true;
}
- private void StartCompletionSequence()
+ private void OnTargetsCompleted()
{
- completionPhase = CompletionPhase.Arranging;
+ completionPhase = CompletionPhase.Completed;
completionTimer = 0f;
+ focusTimer = 0f;
fadeOutAlpha = 1f;
+ allowInput = false;
+ effectPropagations.Clear();
+ autoPropagationTimer = 0f;
+ focusVisuals.Clear();
+ finalTargetCharacters.Clear();
+ finalTargetPositions.Clear();
+ focusTweensCompleted = false;
- // 重置波形
- calmProgress = 0f;
- waveformPoints.Clear();
-
- // 获取排序的红色粒子
orderedRedParticles = GetOrderedRedParticles();
int displayLength = Mathf.Min(orderedRedParticles.Count, targetSentence.Length);
- // 计算目标位置
- float spacing = 0.8f;
- float totalWidth = displayLength * spacing;
- float startX = -totalWidth / 2f + spacing / 2f;
- float centerY = 0f;
-
- targetPositions.Clear();
- for (int i = 0; i < displayLength; i++)
- {
- targetPositions.Add(new Vector3(startX + i * spacing, centerY, 0f));
- }
-
- if (orderedRedParticles.Count > displayLength)
+ if (displayLength < orderedRedParticles.Count)
{
orderedRedParticles = orderedRedParticles.Take(displayLength).ToList();
}
- // 清理效果
- effectPropagations.Clear();
-
- // 停止非红色粒子
- foreach (var p in candidateParticles)
+ for (int i = 0; i < displayLength; i++)
{
- if (!p.isRed)
+ finalTargetCharacters.Add(targetSentence[i].ToString());
+ }
+
+ Vector3 displayCenter = worldCanvas != null ? worldCanvas.transform.position : Vector3.zero;
+ if (finalTargetCharacters.Count > 0)
+ {
+ float spacing = 0.9f;
+ float totalWidth = spacing * Mathf.Max(0, finalTargetCharacters.Count - 1);
+ float startX = -totalWidth / 2f;
+ finalTargetPositions.Clear();
+ for (int i = 0; i < finalTargetCharacters.Count; i++)
{
- p.velocity = Vector2.zero;
- if (p.isOrange)
- {
- p.RemoveEffect();
- }
+ finalTargetPositions.Add(displayCenter + new Vector3(startX + i * spacing, 0f, 0f));
}
}
+
+ if (focusSequenceButton != null)
+ {
+ focusSequenceButton.gameObject.SetActive(true);
+ focusSequenceButton.interactable = true;
+ }
+
+ foreach (var particle in candidateParticles)
+ {
+ if (!particle.isRed)
+ {
+ particle.isCalmed = true;
+ particle.hasEffect = false;
+ particle.isOrange = false;
+ particle.changeSpeedMultiplier = 1f;
+ }
+ }
+
+ foreach (var particle in floatingParticles)
+ {
+ particle.isCalmed = true;
+ }
}
private List GetOrderedRedParticles()
{
- if (redParticles.Count == 0) return new List();
- if (redParticles.Count == 1) return new List(redParticles);
+ if (targetParticles.Count == 0) return new List();
+ if (targetParticles.Count == 1) return new List(targetParticles);
// 从最左边的粒子开始
var ordered = new List();
- var startParticle = redParticles.OrderBy(p => p.transform.position.x).First();
+ var startParticle = targetParticles.OrderBy(p => p.transform.position.x).First();
HashSet visited = new HashSet();
Stack stack = new Stack();
@@ -558,154 +670,272 @@ namespace AibisDream.MiniGame.Language
return ordered;
}
- private void UpdateCompletionAnimation()
+ private void HandleFocusButtonClicked()
{
- completionTimer += Time.deltaTime;
+ if (completionPhase != CompletionPhase.Completed)
+ return;
- if (completionPhase == CompletionPhase.Arranging)
- {
- bool allArrived = true;
- for (int i = 0; i < orderedRedParticles.Count; i++)
- {
- var particle = orderedRedParticles[i];
- var target = targetPositions[i];
-
- particle.transform.position = Vector3.Lerp(
- particle.transform.position,
- target,
- Time.deltaTime * 5f
- );
-
- if (Vector3.Distance(particle.transform.position, target) > 0.01f)
- {
- allArrived = false;
- }
- }
-
- fadeOutAlpha = Mathf.Max(0f, fadeOutAlpha - Time.deltaTime * 2f);
-
- if (allArrived && completionTimer > 0.5f)
- {
- completionPhase = CompletionPhase.Revealing;
- completionTimer = 0f;
- InitializeWaveform();
-
- // 初始化抖动效果
- foreach (var p in orderedRedParticles)
- {
- p.InitializeAnxietyEffect();
- }
- }
- }
- else if (completionPhase == CompletionPhase.Revealing)
- {
- float revealDelay = 0.25f;
- int currentIndex = Mathf.FloorToInt(completionTimer / revealDelay);
-
- for (int i = 0; i < orderedRedParticles.Count; i++)
- {
- var p = orderedRedParticles[i];
-
- if (i < currentIndex)
- {
- // 已显示
- if (i < targetSentence.Length)
- {
- p.currentChar = targetSentence[i].ToString();
- p.isStatic = false;
- }
- }
- else if (i == currentIndex)
- {
- // 正在显示(闪烁)
- p.changeSpeedMultiplier = 20f;
- if (completionTimer % revealDelay >= revealDelay - 0.1f)
- {
- if (i < targetSentence.Length)
- {
- p.currentChar = targetSentence[i].ToString();
- }
- }
- }
- }
-
- if (currentIndex >= orderedRedParticles.Count)
- {
- completionPhase = CompletionPhase.Completed;
- for (int i = 0; i < orderedRedParticles.Count && i < targetSentence.Length; i++)
- {
- orderedRedParticles[i].currentChar = targetSentence[i].ToString();
- orderedRedParticles[i].isStatic = false;
- orderedRedParticles[i].changeSpeedMultiplier = 1f;
- }
- }
- }
- else if (completionPhase == CompletionPhase.Completed)
- {
- // 检查是否所有文字都已消散
- bool allDissolved = orderedRedParticles.All(p => p.dissolveProgress >= 1f);
- if (allDissolved && completionTimer > 1f)
- {
- ResetGame();
- }
- }
+ BeginFocusSequence();
}
- private void InitializeWaveform()
+ private void BeginFocusSequence()
{
- waveformPoints.Clear();
+ if (focusSequenceButton != null)
+ {
+ focusSequenceButton.interactable = false;
+ }
- if (orderedRedParticles.Count == 0) return;
+ completionPhase = CompletionPhase.Focusing;
+ focusTimer = 0f;
+ focusTweensCompleted = false;
- float baseY = 0f;
+ if (connectionRenderer != null)
+ {
+ connectionRenderer.NonTargetAlphaScale = 0f;
+ connectionRenderer.TargetAlphaScale = 1.2f;
+ }
+
+ foreach (var particle in floatingParticles)
+ {
+ particle.isCalmed = true;
+ particle.velocity = Vector2.zero;
+ FadeOutTextParticle(particle, focusFadeDuration);
+ }
+
+ foreach (var particle in candidateParticles)
+ {
+ if (!particle.isRed)
+ {
+ particle.isCalmed = true;
+ particle.velocity = Vector2.zero;
+ FadeOutTextParticle(particle, focusFadeDuration);
+ }
+ else
+ {
+ particle.isStatic = false;
+ particle.isCalmed = false;
+ particle.InitializeAnxietyEffect();
+ particle.changeSpeedMultiplier = 6f;
+ particle.anxietyShakeIntensity = Mathf.Max(particle.anxietyShakeIntensity, 4f);
+ particle.SetChangeInterval(0.05f);
+ particle.ResetChangeTimer(0.05f);
+ }
+ }
+
+ focusCentroid = ComputeCentroid(orderedRedParticles);
+ Vector3 focusTargetCenter = worldCanvas != null ? worldCanvas.transform.position : Vector3.zero;
+
+ focusVisuals.Clear();
+ int tweenTargetCount = orderedRedParticles.Count;
+ int completedTweens = 0;
for (int i = 0; i < orderedRedParticles.Count; i++)
{
var particle = orderedRedParticles[i];
- var point = new WaveformPoint(
- particle.transform.position.x,
- baseY,
- waveformAmplitude,
- i,
- particle
- );
- waveformPoints.Add(point);
- particle.waveformPoint = point;
+ Vector3 offset = particle.transform.position - focusCentroid;
+ float indexCenter = (orderedRedParticles.Count - 1) * 0.5f;
+ Vector3 spreadOffset = new Vector3((i - indexCenter) * spreadDistance, Mathf.Sin(i * 1.4f) * spreadDistance * 0.6f, 0f);
+ Vector3 focusPosition = focusTargetCenter + offset * focusZoomScale + spreadOffset;
+ Vector3 finalPosition = finalTargetPositions.Count > i ? finalTargetPositions[i] : focusTargetCenter;
+
+ particle.transform.DOKill();
+ particle.transform.DOMove(focusPosition, focusMoveDuration)
+ .SetEase(Ease.InOutQuad)
+ .OnComplete(() =>
+ {
+ completedTweens++;
+ if (completedTweens >= tweenTargetCount)
+ {
+ focusTweensCompleted = true;
+ focusTimer = 0f;
+ }
+ });
+ particle.transform.DOScale(Vector3.one * focusZoomScale, focusMoveDuration).SetEase(Ease.InOutQuad);
+
+ string finalChar = i < finalTargetCharacters.Count ? finalTargetCharacters[i] : particle.currentChar;
+ TargetFocusVisual visual = CreateFocusVisual(particle, finalChar);
+ visual.focusPosition = focusPosition;
+ visual.finalPosition = finalPosition;
+ visual.orderIndex = i;
+ focusVisuals[particle] = visual;
}
- lastMousePos = GetMouseWorldPosition();
+ if (tweenTargetCount == 0)
+ {
+ focusTweensCompleted = true;
+ focusTimer = 0f;
+ }
}
- private void UpdateWaveform()
+ private TargetFocusVisual CreateFocusVisual(CandidateParticle particle, string finalChar)
{
- if (waveformPoints.Count == 0)
+ TargetFocusVisual visual = new TargetFocusVisual
{
- InitializeWaveform();
+ particle = particle,
+ finalChar = finalChar,
+ startShakeIntensity = Mathf.Max(particle.anxietyShakeIntensity, 4f)
+ };
+
+ GameObject ringObj = new GameObject("FocusRing");
+ ringObj.transform.SetParent(particle.transform, false);
+ visual.ringObject = ringObj;
+
+ float estimatedRadius = 0.55f;
+ if (particle != null && particle.textMesh != null)
+ {
+ estimatedRadius = Mathf.Clamp(particle.textMesh.fontSize * 0.05f, 0.35f, 0.75f);
+ }
+
+ var disc = ringObj.AddComponent();
+ disc.Type = DiscType.Ring;
+ disc.Radius = estimatedRadius;
+ disc.Thickness = Mathf.Clamp(estimatedRadius * 0.25f, 0.06f, 0.15f);
+ disc.Color = new Color(1f, 0.78f, 0.39f, 0.8f);
+ visual.ringDisc = disc;
+
+ GameObject labelObj = new GameObject("ProbabilityLabel");
+ labelObj.transform.SetParent(particle.transform, false);
+ RectTransform rectTransform = labelObj.AddComponent();
+ rectTransform.localPosition = new Vector3(0f, estimatedRadius + 0.55f, 0f);
+ rectTransform.localScale = Vector3.one;
+ rectTransform.sizeDelta = new Vector2(2f, 0.6f);
+
+ var label = labelObj.AddComponent();
+ if (chineseFontAsset != null)
+ {
+ label.font = chineseFontAsset;
+ }
+ label.fontSize = 2f;
+ label.alignment = TextAlignmentOptions.Center;
+ label.text = $"{Mathf.RoundToInt(visual.probability)}%";
+ visual.probabilityLabel = label;
+
+ return visual;
+ }
+
+ private void UpdateFocusSequence()
+ {
+ if (!focusTweensCompleted)
return;
- }
- Vector2 mousePos = GetMouseWorldPosition();
- mouseDragSpeed = Vector2.Distance(mousePos, lastMousePos) / Time.deltaTime;
- lastMousePos = mousePos;
+ focusTimer += Time.deltaTime;
+ float baseInterval = Mathf.Max(0.2f, probabilityDuration);
+ float arrangementDuration = Mathf.Max(0.2f, probabilityDuration * 0.6f);
- bool mousePressed = Input.GetMouseButton(0);
-
- foreach (var point in waveformPoints)
+ foreach (var visual in focusVisuals.Values)
{
- point.Update(0f, waveformAmplitude, mousePos, mousePressed, mouseDragSpeed, interactionRadius);
+ if (visual == null || visual.particle == null)
+ continue;
+
+ float localTime = focusTimer - visual.orderIndex * predictionSequenceGap;
+ float probabilityProgress = Mathf.Clamp01(localTime / baseInterval);
+ float arrangementProgress = Mathf.Clamp01(localTime / arrangementDuration);
+
+ float probability = Mathf.Lerp(visual.probability, visual.targetProbability, probabilityProgress);
+ if (visual.probabilityLabel != null)
+ {
+ visual.probabilityLabel.text = $"{Mathf.RoundToInt(probability)}%";
+ }
+
+ float interval = Mathf.Lerp(0.05f, 0.8f, probabilityProgress);
+ visual.particle.SetChangeInterval(interval);
+ visual.particle.changeSpeedMultiplier = Mathf.Lerp(6f, 1f, probabilityProgress);
+ visual.particle.anxietyShakeIntensity = Mathf.Lerp(visual.startShakeIntensity, 0f, probabilityProgress);
+
+ Vector3 targetPosition = Vector3.Lerp(visual.focusPosition, visual.finalPosition, Mathf.SmoothStep(0f, 1f, arrangementProgress));
+ visual.particle.transform.position = targetPosition;
+
+ if (!visual.stabilized && probabilityProgress >= 1f)
+ {
+ visual.particle.ForceSetCharacter(visual.finalChar);
+ visual.particle.SetChangeInterval(1.5f);
+ visual.particle.ResetChangeTimer(visual.particle.changeSpeedMultiplier);
+ visual.particle.isStatic = true;
+ visual.stabilized = true;
+ if (visual.probabilityLabel != null)
+ {
+ visual.probabilityLabel.text = "100%";
+ }
+ }
}
- // 更新整体平静进度
- if (waveformPoints.Count > 0)
+ bool allStable = focusVisuals.Values.All(v => v.stabilized);
+ if (allStable)
{
- calmProgress = waveformPoints.Average(p => p.calmAmount);
+ CompleteFocusSequence();
}
}
+ private void CompleteFocusSequence()
+ {
+ foreach (var visual in focusVisuals.Values)
+ {
+ if (visual.ringObject != null)
+ {
+ Destroy(visual.ringObject);
+ }
+
+ if (visual.probabilityLabel != null)
+ {
+ Destroy(visual.probabilityLabel.gameObject);
+ }
+
+ if (visual.particle != null)
+ {
+ visual.particle.transform.DOScale(Vector3.one * focusZoomScale, 0.3f).SetEase(Ease.OutQuad);
+ visual.particle.isStatic = true;
+ visual.particle.changeSpeedMultiplier = 1f;
+ }
+ }
+
+ focusVisuals.Clear();
+
+ if (connectionRenderer != null)
+ {
+ connectionRenderer.TargetAlphaScale = 0f;
+ }
+
+ completionPhase = CompletionPhase.Finished;
+
+ if (focusSequenceButton != null)
+ {
+ focusSequenceButton.gameObject.SetActive(false);
+ }
+ }
+
+ private void FadeOutTextParticle(TextParticle particle, float duration)
+ {
+ if (particle == null) return;
+
+ DOTween.To(() => particle.alpha, value => particle.alpha = value, 0f, duration)
+ .SetEase(Ease.OutQuad);
+
+ if (particle.glowSprite != null)
+ {
+ particle.glowSprite.DOFade(0f, duration);
+ }
+ }
+
+ private Vector3 ComputeCentroid(List particles)
+ {
+ if (particles == null || particles.Count == 0)
+ {
+ return worldCanvas != null ? worldCanvas.transform.position : Vector3.zero;
+ }
+
+ Vector3 sum = Vector3.zero;
+ foreach (var particle in particles)
+ {
+ sum += particle.transform.position;
+ }
+
+ return sum / particles.Count;
+ }
+
private void UpdateInput()
{
- // 只在第一阶段(未完成)时处理鼠标输入
- if (completionPhase != CompletionPhase.None) return;
+ // 只在允许输入时处理鼠标逻辑
+ if (!allowInput || completionPhase != CompletionPhase.None) return;
Vector2 mouseWorldPos = GetMouseWorldPosition();
@@ -760,15 +990,33 @@ namespace AibisDream.MiniGame.Language
isCompleted = false;
completionPhase = CompletionPhase.None;
completionTimer = 0f;
+ focusTimer = 0f;
fadeOutAlpha = 1f;
+ allowInput = true;
orderedRedParticles.Clear();
- targetPositions.Clear();
+ focusVisuals.Clear();
+ finalTargetCharacters.Clear();
+ finalTargetPositions.Clear();
effectPropagations.Clear();
previousConnections.Clear();
initializationComplete = false;
initializationFrames = 0;
- calmProgress = 0f;
- waveformPoints.Clear();
+ autoPropagationTimer = 0f;
+ focusTweensCompleted = false;
+
+ if (focusSequenceButton != null)
+ {
+ focusSequenceButton.onClick.RemoveListener(HandleFocusButtonClicked);
+ focusSequenceButton.onClick.AddListener(HandleFocusButtonClicked);
+ focusSequenceButton.interactable = false;
+ focusSequenceButton.gameObject.SetActive(false);
+ }
+
+ if (connectionRenderer != null)
+ {
+ connectionRenderer.NonTargetAlphaScale = lineAlphaMultiplier;
+ connectionRenderer.TargetAlphaScale = 1f;
+ }
// 重置所有粒子
foreach (var p in candidateParticles)
@@ -782,13 +1030,153 @@ namespace AibisDream.MiniGame.Language
p.vibrationOffset = Vector2.zero;
p.velocity = new Vector2(Random.Range(-0.5f, 0.5f), Random.Range(-0.5f, 0.5f));
p.transform.position = GetRandomPositionInBounds();
- p.calmProgress = 0f;
- p.dissolveProgress = 0f;
p.isCalmed = false;
- p.waveformPoint = null;
+ p.alpha = p.baseAlpha;
+ p.transform.localScale = Vector3.one;
+ p.transform.DOKill();
+
+ if (p.glowSprite != null)
+ {
+ Color glowColor = p.glowSprite.color;
+ glowColor.a = 0.2f;
+ p.glowSprite.color = glowColor;
+ }
+ }
+
+ foreach (var floating in floatingParticles)
+ {
+ floating.isCalmed = false;
+ floating.alpha = floating.baseAlpha;
+ floating.transform.DOKill();
}
SelectRedParticles();
+ UpdateStatusUI();
+ }
+
+ private void ApplySeparationForces()
+ {
+ if (!enableSeparation || candidateParticles.Count <= 1)
+ return;
+
+ float minSpacingSqr = minParticleSpacing * minParticleSpacing;
+
+ for (int i = 0; i < candidateParticles.Count - 1; i++)
+ {
+ var particleA = candidateParticles[i];
+ if (particleA.isStatic)
+ continue;
+
+ Vector2 posA = particleA.transform.position;
+
+ for (int j = i + 1; j < candidateParticles.Count; j++)
+ {
+ var particleB = candidateParticles[j];
+ if (particleB.isStatic)
+ continue;
+
+ Vector2 posB = particleB.transform.position;
+ Vector2 diff = posA - posB;
+ float distSqr = diff.sqrMagnitude;
+
+ if (distSqr < minSpacingSqr && distSqr > 0.0001f)
+ {
+ float distance = Mathf.Sqrt(distSqr);
+ float pushStrength = 1f - (distance / minParticleSpacing);
+ Vector2 direction = diff / distance;
+ Vector2 push = direction * (pushStrength * separationForce);
+
+ particleA.ApplyForce(push * Time.deltaTime);
+ particleB.ApplyForce(-push * Time.deltaTime);
+ }
+ }
+ }
+ }
+
+ private void UpdateStatusUI()
+ {
+ if (integrationProgressText != null)
+ {
+ float ratio = CalculateIntegrationRatio();
+ int percent = Mathf.RoundToInt(ratio * 100f);
+ integrationProgressText.text = $"{percent}% 语言整合完成度";
+ }
+
+ if (interferenceCountText != null)
+ {
+ int interferenceCount = CountInterferenceNodes();
+ interferenceCountText.text = $"{interferenceCount} 处异常思绪仍在干扰表达";
+ }
+ }
+
+ private float CalculateIntegrationRatio()
+ {
+ if (targetParticles.Count == 0)
+ return 0f;
+
+ HashSet visited = new HashSet();
+ int largestCluster = 0;
+
+ foreach (var red in targetParticles)
+ {
+ if (visited.Contains(red))
+ continue;
+
+ int clusterSize = 0;
+ Queue queue = new Queue();
+ queue.Enqueue(red);
+ visited.Add(red);
+
+ while (queue.Count > 0)
+ {
+ var current = queue.Dequeue();
+ clusterSize++;
+
+ foreach (var connected in current.connections)
+ {
+ if (connected == null || !connected.isRed)
+ continue;
+
+ if (!visited.Contains(connected))
+ {
+ visited.Add(connected);
+ queue.Enqueue(connected);
+ }
+ }
+ }
+
+ if (clusterSize > largestCluster)
+ {
+ largestCluster = clusterSize;
+ }
+ }
+
+ largestCluster = Mathf.Clamp(largestCluster, 0, targetParticles.Count);
+ return largestCluster / Mathf.Max(1f, targetParticles.Count);
+ }
+
+ private int CountInterferenceNodes()
+ {
+ if (targetParticles.Count == 0)
+ return 0;
+
+ HashSet interferingParticles = new HashSet();
+
+ foreach (var red in targetParticles)
+ {
+ foreach (var connected in red.connections)
+ {
+ if (connected == null)
+ continue;
+
+ if (!connected.isRed)
+ {
+ interferingParticles.Add(connected);
+ }
+ }
+ }
+
+ return interferingParticles.Count;
}
}
}
diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleSystem_Summary.md b/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleSystem_Summary.md
index 498976d24..cbe30d54d 100644
--- a/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleSystem_Summary.md
+++ b/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleSystem_Summary.md
@@ -38,61 +38,6 @@
### 6. 游戏目标与完成序列
**目标**:让所有红色粒子连接成一个整体,且与蓝色粒子分离
-**完成序列**:
-1. **arranging阶段**:
- - 红色粒子排列成水平一行(屏幕中央)
- - 蓝色/橙色粒子停止移动
- - 非红色粒子淡出
-
-2. **revealing阶段**:
- - 红色粒子逐个显示目标文字:"别过来我感觉害怕"
- - 显示前快速闪烁随机字符
- - 文字开始剧烈抖动(焦虑效果)
-
-3. **completed阶段**:
- - 进入波形交互阶段
-
-### 7. 波形交互系统(核心创新)
-**初始状态**:
-- 每个文字下方有一个对应的波形点
-- 波形使用Perlin噪声生成(多层噪声叠加)
-- 波形呈现异常、混乱的抖动
-- 文字随波形剧烈抖动+上下漂浮(表现焦虑情绪)
-
-**交互机制**:
-- 玩家按住鼠标左键并拖动,抚平附近的波形
-- 波形有"抵抗力"机制:
- - 异常程度越高,抵抗力越强
- - 需要持续按住30-90帧才能完全抚平
- - 拖动速度越快,抚平效果越强
-- 抚平区域有交互半径(50像素)
-- 显示进度环反馈
-
-**抚平效果**:
-- 波形从异常噪波逐渐变为规律的正弦波
-- 波形颜色从红色渐变为蓝色
-- 对应文字的抖动和漂浮逐渐减弱
-- 波形完全抚平后:
- - 文字停止变化和抖动
- - 文字开始消散(透明度降低)
- - 波形点也随之消散
-
-**视觉反馈**:
-- 抚平时产生蓝色粒子效果
-- 鼠标周围显示交互范围圈
-- 波形点显示抚平进度环
-- 底部显示整体平静进度条
-
-**情感隐喻**:
-- 异常波形 = 焦虑、不安的情绪
-- 文字抖动 = 情绪的外在表现
-- 抚平波形 = 安抚、平复情绪的过程
-- 文字消散 = 负面情绪的消解
-
-### 8. 重置机制
-- 所有文字消散后,游戏自动重置
-- 粒子重新随机分布
-- 重新选择8个红色目标粒子
## Unity实现要点
diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/NeuralPropagationConfig.asset b/Assets/Scripts/MiniGame/HuoShan/Language/NeuralPropagationConfig.asset
new file mode 100644
index 000000000..fb9de5da5
--- /dev/null
+++ b/Assets/Scripts/MiniGame/HuoShan/Language/NeuralPropagationConfig.asset
@@ -0,0 +1,32 @@
+%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: 11500000, guid: aff5f43b9eb5c4242842ca3cc33afd99, type: 3}
+ m_Name: NeuralPropagationConfig
+ m_EditorClassIdentifier:
+ bluePropagations:
+ - startChar: "\u6628"
+ content: "\u6628\u5929\u6211\u78B0"
+ - startChar: "\u6211"
+ content: "\u544A\u8BC9\u6211"
+ anxietyWords:
+ - "\u5BB3\u6015"
+ - "\u600E\u4E48\u529E"
+ - "\u56DE\u907F"
+ - "\u7D27\u5F20"
+ - "\u7126\u8651"
+ - "\u62C5\u5FC3"
+ - "\u6050\u60E7"
+ - "\u4E0D\u5B89"
+ hiddenInfo: "\u82F1\u91CC"
+ bluePropagationSpeed: 1
+ redOverrideInterval: 2
+ propagationDistance: 2
diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/NeuralPropagationConfig.asset.meta b/Assets/Scripts/MiniGame/HuoShan/Language/NeuralPropagationConfig.asset.meta
new file mode 100644
index 000000000..2912f490c
--- /dev/null
+++ b/Assets/Scripts/MiniGame/HuoShan/Language/NeuralPropagationConfig.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 3896a6c1f7dac79448309febc22689de
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/TextParticle.cs b/Assets/Scripts/MiniGame/HuoShan/Language/TextParticle.cs
index 2466c0246..f0a8f515a 100644
--- a/Assets/Scripts/MiniGame/HuoShan/Language/TextParticle.cs
+++ b/Assets/Scripts/MiniGame/HuoShan/Language/TextParticle.cs
@@ -161,6 +161,32 @@ namespace AibisDream.MiniGame.Language
textMesh.text = currentChar;
}
+ public void ForceSetCharacter(string character)
+ {
+ currentChar = character;
+ UpdateText();
+ }
+
+ public void SetStaticState(bool value)
+ {
+ isStatic = value;
+ }
+
+ public void SetCalmedState(bool value)
+ {
+ isCalmed = value;
+ }
+
+ public void SetChangeInterval(float interval)
+ {
+ changeInterval = Mathf.Max(0.02f, interval);
+ }
+
+ public void ResetChangeTimer(float timer)
+ {
+ changeTimer = timer;
+ }
+
protected virtual void UpdateVisuals()
{
if (textMesh != null)
diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/WaveformRenderer.cs b/Assets/Scripts/MiniGame/HuoShan/Language/WaveformRenderer.cs
index 89e84035c..0fd239ad2 100644
--- a/Assets/Scripts/MiniGame/HuoShan/Language/WaveformRenderer.cs
+++ b/Assets/Scripts/MiniGame/HuoShan/Language/WaveformRenderer.cs
@@ -6,6 +6,7 @@ namespace AibisDream.MiniGame.Language
{
///
/// 波形渲染器:使用Shapes绘制波形和交互效果
+ /// 注意:渲染在底层,确保低于文字(文字 sortingOrder = 10)
///
[ExecuteAlways]
public class WaveformRenderer : ImmediateModeShapeDrawer
@@ -83,13 +84,14 @@ namespace AibisDream.MiniGame.Language
if (waveformPoints == null || waveformPoints.Count == 0)
return;
- if (completionPhase != CompletionPhase.Revealing && completionPhase != CompletionPhase.Completed)
+ if (completionPhase != CompletionPhase.Focusing)
return;
using (Draw.Command(cam))
{
Draw.BlendMode = ShapesBlendMode.Transparent;
Draw.ZTest = UnityEngine.Rendering.CompareFunction.Always;
+ // 注意:波形默认在底层,文字的 sortingOrder = 10 会显示在上层
// 绘制粒子效果
DrawWaveformParticles();