_oX
zLaC*$b=8Vc;cc|J31x(w*eZxrOQMcx#rD41DsLzETi7albtbpH50m%BL`h8gXp@QD
zflFH0d$hDzYr|=24O>G>YcXtTtp~bVqn5GI7?YsL%&P8UF2vEAfPbJg#{>jxts#`w
z>YAlB3T30sO(>4m3gT$3<
z=I$bw5-c>vgchO3z9b-~u}hW}0YP0jCLmZP4WU$0*DRG#9c;9@3B^%KL4KwZm8hi0
z)TlJ-YozeJL#ZT&v42WB#xR5dGzH1GUC1c-eAJw(41Y_J3wYl-r
zkw8JNk^p5P=`qC;7*lJZhzCVV138XGFpEufs}^LU8=HC5LLe{^mVv;G(Ol~mLkZDb
zw3I-LcGD6VXB{aT%vD+-2a`TqEQYbQ#Sl-gMB5V2;@uSy5r2Ja*@8@bW89Sp49-DJ
zVQ^n+F;rt}E?QzB#WyX1aTYtWZ7@F*gGxryXKTcW`kGS+0gz&d@y^PS^P{R?N@D>s
z@r_XuR|E#q2RO@C>SiO*dbE(Yfymb+71RJ=P|w-{0kHHjq#x@&D(0^=-pL~WC2{#9b2
zWQe!XqI^kPEQYaFVyIWl>fK)Y!izY|IjfrOyL!{z{POUBU!{9scz=+z4+V!8>~WFj
z#pPSg_FvNTGrSrWZ^YRf|Gnn0hYXAt^J$qrvp>@6Ib1u}Ud|5iSUDWPPO?&OwB=j@C=A{o4hN8)Ls39j>I+OFc)v3KQhH@tvg&L0FSr4B-lEFj2NU@_
z$;>Z{idSjI#k0C1izSP2vi)Ia{vVTI4KcH%3pO1A3X|I?KmlZv94Rvah?7t$lLfg5
z&0bQI=qXeID3d)ZKmnYSekw2lu#>MUHUZa@<|;=4o0BdpK>>)9a4R|iOOvZBHUe@F
zlaLP;llUtt0UVPfEIa|dlVB_%9OY$I+ENAp038|t02lxO000000096X0002#1e1>o
l6_c_oF9LB3laC7;ll?3z0$vT1j|&x(J}ngnt|$Ni0002Q=~Dmz
diff --git a/数据表/UIForm.txt b/数据表/UIForm.txt
index f8de70d..53ec801 100644
--- a/数据表/UIForm.txt
+++ b/数据表/UIForm.txt
@@ -7,3 +7,6 @@
101 设置 SettingForm Default False True
102 关于 AboutForm Default False True
103 组装玩法UI CombineForm Default False False
+ 104 Mask对话UI MaskDialogForm Default False False
+ 105 Bottom对话UI BottomDialogForm Default False False
+ 106 Bubble对话UI BubbleDialogForm Default True False
diff --git a/数据表/UIForm.xlsx b/数据表/UIForm.xlsx
index 9cfa1b13be1bd8dcdca0e720bc5e849634bab4cd..33a879a8eba683dd1f9fbab55ce967962e43a86c 100644
GIT binary patch
delta 2123
zcmV-R2(e@($(2Wo;yL{%KZNGycR@fD=L{Sb}4MIfh$cs0Sk2Aq5|h
z`q@++KyVBdNKnq=e>jTqkJo{b_Roe+IdIC-ZMENN7x$1YcrOm_jV%Xb(=<^t>SFl(
zF}|N)FS=F3Qnk0@fbvvu$_+4An|uMBypIKf+RGaP(mKsBf&@gZN9!BcjNyhCLuX5
z$e4~tG(IHm0eP4E_)p~DP)h>@k|i3mAOo-g3fAM0WgG+m0PPQx+5{kf+m53+5Qgtp
z+IK*{9}eks(rG0bO#{iSG%Jnvcul}bPy=SL6T07i%67nDyV+=k6rlXMT)$oA;PvOX
zvT&}rp*g8GC~$q`;3_3qUY$1R@4t4Ro{&RXQe{a&D!f7On4-_yUw>J*qJv=w}_#nq)
zp5ln4ml9V@I%#l`Fo>U?^O|>jR{RML)zA{f6LdlW2DJeY}
znyGT-twuV_)No{(tTJg{FZCyIT!XknUgYdu$n65cO!;Zs)@^Hl>Za8#iopa0OAoW~
zLK!Kege7RZ7Ax?*@wV$MhgsxH?=<+hL0^LBeK14NAdr7@+|t3b!;-_FxWFmH8RQCb
zcn%y0dF9}5VDhT*pOfCfr
zN%3!GC#O=mmNWl=m_^#b_@b{{-~};UN^UnPAaDVCSdFc;untK-Z>H_
zcSlzcDZ0YF>$>`1F+i-s5!{udftLBbViq??aKAa4L2!3}&BOB674BWvg&RDnizm>;
zgnDviMN;uR->w_dIo+
z`0fYbftdqv>+}F7G3y5qK}II=FeYM?n2(8_Nj#2;y-6&3Vk8t&)FPByr-hotvL8SS
zHHoJ&5&tKDu``L)*vEc~7zyP$Ypy7_P75`OK+CGof~d5TlVL%$#E313mKi$>qNT>(
zf~edWO$e`lV?tiIl}`w-9!*5pwK;u#1uCngP79*tMQlN|#MljygDsT(6fsf>yOu>I
zxArQ1eLcI@fle*$A`_WuUSbmobe%&)>eQUO74Ry6Pd_1MyvbS!kBW0UrJK0HWWWS$*Qx>h?FbOJ@ON^
zPEPpuq&ejkbqaihS@hi{{9iTl7$aX8skwe4=Rg>o;l$$^&e0e?V|^FkBO$DNffJ5w
z+X*8-X7va4?N5*%v$qI91`1z4aR})F001=vlK~GJe|?kRZqqOn#_y4Mhsa#B6b5LT
zl1ydOP%es)pby|QUTcxqC3g68In->Epj(C13Y86MV;cWNi&Oz!H}*7jlD!4)a_6L6
z`uooB$mct@vGUUQwC5;bm=Y7*S||Yxks5U|shgnsWc}t6&=`lrg&rly1g{VSE7tXE
z24h^4e=K1pXmH+K)^%2EARn>?+C)S;8#M4Cm&c&4vu1#x%NhuA-_viGO80ahVgj@p
z4GA~Fox4B_34Ru$)l67i1eU?DWpL|;p>xa7XNOEkx^FKJjxM8i@^_FPeo<}PRh2Sh
zh(ClrQjMzpAfREh$SQjteNhQLj%_lVzm|g^f5DK+>&onqTea8av}$`yxGFIo$YfR3
zq102gY|7-kC-(m2ydOQW^G+OZ$D`c%>91Bi>4;7{`TR4-T_u;CwTkWd{ZMpHM6{U>
z#^Sg?9e>L%AqF3(7jM*NS#{ihEjIVlqwga6ot|tZA9_i@m0>C%&QHbmBtG0yjWsME
ze^p^8WlvsqCV;1LOvF?`Z3~MZleeg(P%o^Re`cYLta!a
zBTYX=$?n&9)UDbown}{m6I29NAbTo?r|Iw@kIlUK(cGt;Q-2Y%$
zH3R?v6aWAK000000RSKX003nVlNTmH0+JJxStdCG6$z7+CN%;)36skvKLUUelME+2
z0hg0VCm|fx1ONc-4*&od000000000103ZMW0K){6-UJ$xpC=FoH6#E4004SR
B(qsSt
delta 1951
zcmV;Q2VnTpP_<96lK};q#*}YPlb8W43#1CHB$WVlq7qVzlg|Mce^P
z=~h<33m9A0Do1fdPymY8qEzb~-7aV0849dpO0cR{kfR+~G%Zd~GOj7F4cwSogK;I;
zz>}1vT<2)xoTeCCz5&T>Y3tJ+BB{L`$lX8eh*0Vjk^umr~ha}2k@P!B}3M+!b5
z^|PrufZ!M^kf5B!e{mG!AFl%=?Vk;uGIGk&?X=%%A9u_aycY-e)|P{@X_}}>yBI!y
zjPK{yi*D7hRPC)epdu5Tas$lOrdWV>ARobGcvNjKSTXD7{@AM$#Ko@odVl@=c+2o_
z#pAm4JrhLW^Uyy1LAgsWua+}ZBqW)HBn}g@Bngctl#qvZBJfdvo3Ag({rG>0Nl4CH
zVw%!qOgsRJF8A@DD82zuO9KRxoh2HxAp@`h3J^uz{GkH?04)uZ+XNtg*^b*F6o&6B
z^&Kqk$4e%=l8mO=TB(&rU9X8v5^HSZ8WPXEeGd?Cz@%vvDYg$EKj(*oLuL;zY2uto
z!D5=tQQ-Q>Az4VHI6KVIkMEniF>)BkS%edskvV!L3_UE~znhh`cxFdJxC06qo1-J=
z`PB1RcqA!iF3m{>Eqhvjq?p5Faqw7P5FE))Y2x*Kf9Rz+&X7`=7Pn~7{XP!KiiW3@
zWL$|9B*7f=XU8~amD)>mlU`Kd61-OVv{GEDmRc|98~LSiSWrgy+zn~!snkrD6K~S-
znTCdtWwKISJfHGA5YEBeE>2?pD(zMY5#H#OW$BhVbHnVCMQeh8f{{n~^29hzSD0h8
zn8_7*D!j!kieVPT(mMs&&(UKr-A)k1CGj&RC2QR~9Phr7goK<#P%6j~C9tFPS%9A)
z%R$6F4B^#dwC}6&HnMD<3MV3ad`kF|Ccol{ALnT7
zj(V-nJ*tqewERea;=_?cZhhp?6Hnrdd?sg-K<_ykicv$Fz!325q_L=X{@
zQV6b<(D1)9g3P55Tq~i8+j7>w)!OhP1lNmD50bmd%;?g86|Pm+;lIp^r`B1Pm?-+i
zte~X>F9cX{mSgd{3Z^3{*H8o>MO3yy?J$U*7yisULtTa!;)6*9HQ|AFJ%Cs)a{v*&
zK7dK|>H%buE0ehI5^Iy_cZrQj47$YDB!)H7F$x81F)E_hjhe)$9zYp2iE)=$|CQL7
z#H6dSy+(9@jEb5yS5!o=8#ReQtE#01Y064Xh6T|IV{JjS%Gg*Ctu(e4q^XV0goyob
zOvncj6%!(Mk7i<7wK?_v3Yw}?J}rn=7i$Zm6~?BG>};uQuMr)W@Yb@p6w%tHudip-
z+VZKD-O5CIn#$Tl0^MgDQ9d=1tw}VC((wteSc^}85p8@nUFBjetJcP6PeWD~M6X*D
zF?_ZWyHAs7GU@mf=UB69MYQp$Ww@+bTRyd&wXz`E&RUy@;j@j{eVW7@J||Y6BHH-W
zOf0L`md|GItNm#~v@N_g5yNL2vHQG6sP$9_NA;_o;{*AEi$k0-Cn0-yz4-13ws@h=
zf$D)K(_C10c9g>zSl%Ate+YqJe%}RnPbse+;E1T&HZmyAsr4V7#UGF!voi@m1`2Rv
zA4$jo004gjlYI{we{GY?YTG~*hWCZOgP5)xIVq&2ND3w)MT?X|>jxNHC-#6e<7!5|
zEK{tc&^R##o8Vj^7m{9x6EJOEC(YA%B;_qSsa73pIsbpY!<_%jSPSn0-*^KdCyd%?
zcBX_3pf%c;nl}SXC^Bj+qQXXx=8+Lm@;ZXWT$p=|9E%gj
z63zq56pm%y9daSJjVg(M#X&=vZz^Pm3^PRU5R`_
zTzWTuZ3=#Ye~uZd?WO%zj_H8B{w+7g$>F+gED`m%OX@`lF2O2RJJ5m2=qlUZlkKi<
ztN@LQ`ecI}^;q=kmmq}NMiC(6;bgp}1LZ1*DgyNqllCS#0+|StF()+wzzCCQCqDw?5R<7VI{}uH
Date: Mon, 9 Feb 2026 13:31:24 +0800
Subject: [PATCH 5/6] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AF=B9=E8=AF=9D?=
=?UTF-8?q?=E6=92=AD=E6=94=BE=E9=80=9F=E5=BA=A6=E8=AE=BE=E7=BD=AE=EF=BC=8C?=
=?UTF-8?q?=E8=B0=83=E6=95=B4=E5=AF=B9=E8=AF=9D=E4=BB=A5=E6=89=93=E5=AD=97?=
=?UTF-8?q?=E6=9C=BA=E7=9A=84=E5=BD=A2=E5=BC=8F=E6=98=BE=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Assets/GameMain/DataTables/DialogLine.txt | 2 +-
.../CustomComponent/DialogComponent.cs | 8 +-
.../UI/Dialog/Context/DialogFormContext.cs | 2 +
.../UI/Dialog/View/BottomDialogForm.cs | 196 +++++++++++++++---
.../Scripts/UI/Dialog/View/DialogFormBase.cs | 95 ++++++++-
.../Scripts/UI/Dialog/View/MaskDialogForm.cs | 11 +-
.../UI/UIForms/BottomDialogForm.prefab | 9 +-
Assets/Launcher.unity | 1 +
8 files changed, 272 insertions(+), 52 deletions(-)
diff --git a/Assets/GameMain/DataTables/DialogLine.txt b/Assets/GameMain/DataTables/DialogLine.txt
index 567ba54..901aa3f 100644
--- a/Assets/GameMain/DataTables/DialogLine.txt
+++ b/Assets/GameMain/DataTables/DialogLine.txt
@@ -2,7 +2,7 @@
# Id SpeakerId Expression SpeakerName Direction Text Emphasis ChapterId DialogId
# int string ExpressionType string int string EmphasisType int int
# 对话行编号 策划备注 说话人Id 表情 显示人名 说话朝向 说话内容 演出效果 章节Id 对话Id
- 100100001 Id规则为 Null None Null 0 相传。 None 1.00100001 1001.00001
+ 100100001 Id规则为 Null None Null 0 相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。 None 1.00100001 1001.00001
100100002 第1位数为章节Id Null None Null 0 Mask。 None 1.00100002 1001.00002
100100003 第2-4位数为对话Id Null None Null 0 很好。 None 1.00100003 1001.00003
100200001 第5-9位数为对话行Id Xu Normal 徐晟壹 0 你好,王。 None 1.00200001 1002.00001
diff --git a/Assets/GameMain/Scripts/CustomComponent/DialogComponent.cs b/Assets/GameMain/Scripts/CustomComponent/DialogComponent.cs
index 6cf6f39..0581337 100644
--- a/Assets/GameMain/Scripts/CustomComponent/DialogComponent.cs
+++ b/Assets/GameMain/Scripts/CustomComponent/DialogComponent.cs
@@ -15,6 +15,8 @@ namespace CustomComponent
{
#region Property
+ [SerializeField] private float _playingSpeed = 1.0f;
+
private const int DialogChapterDivisor = 1000;
private const int LineChapterDivisor = 100000000;
private const int LineDialogDivisor = 100000;
@@ -35,6 +37,8 @@ namespace CustomComponent
private bool _isInitialized;
private bool _isPlaying;
+ public float PlayingSpeed => _playingSpeed;
+
public bool IsInitialized => _isInitialized;
public bool IsPlaying => _isPlaying;
@@ -211,6 +215,7 @@ namespace CustomComponent
_formContext.DialogId = dialogRow.Id;
_formContext.DialogTitle = dialogRow.Title;
_formContext.DialogUIMode = dialogRow.UIMode;
+ _formContext.PlayingSpeed = Mathf.Max(0f, _playingSpeed);
_currentLineIndex = 0;
ApplyLineToContext(dialogLines[_currentLineIndex], _currentLineIndex, dialogLines.Count);
@@ -350,6 +355,7 @@ namespace CustomComponent
_formContext.Direction = lineRow.Direction;
_formContext.Text = lineRow.Text;
_formContext.Emphasis = lineRow.Emphasis;
+ _formContext.PlayingSpeed = Mathf.Max(0f, _playingSpeed);
_formContext.LineIndex = lineIndex;
_formContext.TotalLines = totalLines;
_formContext.IsLastLine = lineIndex >= totalLines - 1;
@@ -404,4 +410,4 @@ namespace CustomComponent
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/Assets/GameMain/Scripts/UI/Dialog/Context/DialogFormContext.cs b/Assets/GameMain/Scripts/UI/Dialog/Context/DialogFormContext.cs
index 9f1eb9c..46ec048 100644
--- a/Assets/GameMain/Scripts/UI/Dialog/Context/DialogFormContext.cs
+++ b/Assets/GameMain/Scripts/UI/Dialog/Context/DialogFormContext.cs
@@ -4,6 +4,8 @@ namespace UI
{
public class DialogFormContext : UIContext
{
+ public float PlayingSpeed = 1f;
+
public int ChapterId = 0;
public int DialogId = 0;
public string DialogTitle = string.Empty;
diff --git a/Assets/GameMain/Scripts/UI/Dialog/View/BottomDialogForm.cs b/Assets/GameMain/Scripts/UI/Dialog/View/BottomDialogForm.cs
index 1c53f1a..72128ae 100644
--- a/Assets/GameMain/Scripts/UI/Dialog/View/BottomDialogForm.cs
+++ b/Assets/GameMain/Scripts/UI/Dialog/View/BottomDialogForm.cs
@@ -1,4 +1,5 @@
-using Definition.Enum;
+using DG.Tweening;
+using Definition.Enum;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
@@ -10,6 +11,8 @@ namespace UI
{
public override DialogFormMode UIMode => DialogFormMode.BottomBox;
+ [SerializeField] private GameObject _speakerArea;
+
[SerializeField] private TMP_Text _speakerNameText;
[SerializeField] private TMP_Text _contentText;
@@ -22,10 +25,15 @@ namespace UI
[SerializeField] private int _rightSpritePosition = -450;
+ [SerializeField] private float _moveDuration = 0.25f;
+
+ [SerializeField] private Ease _moveEase = Ease.OutCubic;
+
private readonly int _singleSpeakerCenterPosition = Screen.width / 2;
private string _leftSpeakerToken = string.Empty;
private string _rightSpeakerToken = string.Empty;
+ private Sequence _layoutSequence;
public override void StartDialog(DialogFormContext context)
{
@@ -37,10 +45,11 @@ namespace UI
_context = context;
- string speakerName = NormalizeValue(context.SpeakerName);
- if (string.IsNullOrEmpty(speakerName))
+ string speakerName = context.SpeakerName;
+
+ if (_speakerArea != null)
{
- speakerName = NormalizeValue(context.SpeakerId);
+ _speakerArea.SetActive(!string.IsNullOrEmpty(speakerName));
}
if (_speakerNameText != null)
@@ -48,15 +57,12 @@ namespace UI
_speakerNameText.text = speakerName;
}
- if (_contentText != null)
- {
- _contentText.text = NormalizeValue(context.Text);
- }
+ PlayTypewriter(_contentText, context.Text, context.PlayingSpeed);
if (string.IsNullOrEmpty(speakerName))
{
ClearSpeakerState();
- ApplySpeakerLayout(false, false);
+ ApplySpeakerLayout(false, false, true);
return;
}
@@ -72,13 +78,14 @@ namespace UI
bool hasLeftSpeaker = !string.IsNullOrEmpty(_leftSpeakerToken);
bool hasRightSpeaker = !string.IsNullOrEmpty(_rightSpeakerToken);
- ApplySpeakerLayout(hasLeftSpeaker, hasRightSpeaker);
+ ApplySpeakerLayout(hasLeftSpeaker, hasRightSpeaker, false);
}
protected override void OnClose(bool isShutdown, object userData)
{
ClearSpeakerState();
- ApplySpeakerLayout(false, false);
+ KillLayoutTween();
+ ApplySpeakerLayout(false, false, true);
base.OnClose(isShutdown, userData);
}
@@ -88,35 +95,164 @@ namespace UI
_rightSpeakerToken = string.Empty;
}
- private void ApplySpeakerLayout(bool hasLeftSpeaker, bool hasRightSpeaker)
+ private void KillLayoutTween()
+ {
+ if (_layoutSequence != null)
+ {
+ _layoutSequence.Kill();
+ _layoutSequence = null;
+ }
+ }
+
+ private void ApplySpeakerLayout(bool hasLeftSpeaker, bool hasRightSpeaker, bool instant)
+ {
+ if (_leftSprite == null || _rightSprite == null)
+ {
+ return;
+ }
+
+ bool leftCurrentlyVisible = _leftSprite.gameObject.activeSelf;
+ bool rightCurrentlyVisible = _rightSprite.gameObject.activeSelf;
+
+ bool leftTargetVisible = hasLeftSpeaker;
+ bool rightTargetVisible = hasRightSpeaker;
+
+ float leftTargetX = GetTargetX(true, hasLeftSpeaker, hasRightSpeaker);
+ float rightTargetX = GetTargetX(false, hasLeftSpeaker, hasRightSpeaker);
+
+ KillLayoutTween();
+
+ PrepareStartState(
+ leftCurrentlyVisible,
+ rightCurrentlyVisible,
+ leftTargetVisible,
+ rightTargetVisible,
+ hasLeftSpeaker,
+ hasRightSpeaker);
+
+ if (instant || _moveDuration <= 0f)
+ {
+ SetSpritePosition(_leftSprite.rectTransform, leftTargetX);
+ SetSpritePosition(_rightSprite.rectTransform, rightTargetX);
+ SetSpriteVisible(_leftSprite, leftTargetVisible);
+ SetSpriteVisible(_rightSprite, rightTargetVisible);
+ return;
+ }
+
+ _layoutSequence = DOTween.Sequence();
+
+ Tween leftTween = CreateMoveTween(_leftSprite.rectTransform, leftTargetX);
+ if (leftTween != null)
+ {
+ _layoutSequence.Join(leftTween);
+ }
+
+ Tween rightTween = CreateMoveTween(_rightSprite.rectTransform, rightTargetX);
+ if (rightTween != null)
+ {
+ _layoutSequence.Join(rightTween);
+ }
+
+ if (_layoutSequence.active && _layoutSequence.Duration(false) > 0f)
+ {
+ _layoutSequence.OnComplete(() =>
+ {
+ SetSpriteVisible(_leftSprite, leftTargetVisible);
+ SetSpriteVisible(_rightSprite, rightTargetVisible);
+ _layoutSequence = null;
+ });
+ }
+ else
+ {
+ SetSpritePosition(_leftSprite.rectTransform, leftTargetX);
+ SetSpritePosition(_rightSprite.rectTransform, rightTargetX);
+ SetSpriteVisible(_leftSprite, leftTargetVisible);
+ SetSpriteVisible(_rightSprite, rightTargetVisible);
+ _layoutSequence.Kill();
+ _layoutSequence = null;
+ }
+ }
+
+ private void PrepareStartState(
+ bool leftCurrentlyVisible,
+ bool rightCurrentlyVisible,
+ bool leftTargetVisible,
+ bool rightTargetVisible,
+ bool hasLeftSpeaker,
+ bool hasRightSpeaker)
+ {
+ if (leftTargetVisible && !leftCurrentlyVisible)
+ {
+ float leftStartX = GetAppearStartX(true, rightCurrentlyVisible, hasLeftSpeaker, hasRightSpeaker);
+ SetSpritePosition(_leftSprite.rectTransform, leftStartX);
+ SetSpriteVisible(_leftSprite, true);
+ }
+
+ if (rightTargetVisible && !rightCurrentlyVisible)
+ {
+ float rightStartX = GetAppearStartX(false, leftCurrentlyVisible, hasLeftSpeaker, hasRightSpeaker);
+ SetSpritePosition(_rightSprite.rectTransform, rightStartX);
+ SetSpriteVisible(_rightSprite, true);
+ }
+
+ if (leftCurrentlyVisible && !leftTargetVisible)
+ {
+ SetSpriteVisible(_leftSprite, true);
+ }
+
+ if (rightCurrentlyVisible && !rightTargetVisible)
+ {
+ SetSpriteVisible(_rightSprite, true);
+ }
+ }
+
+ private float GetAppearStartX(bool isLeft, bool otherCurrentlyVisible, bool hasLeftSpeaker,
+ bool hasRightSpeaker)
{
if (hasLeftSpeaker && hasRightSpeaker)
{
- SetSpriteVisible(_leftSprite, true);
- SetSpriteVisible(_rightSprite, true);
- SetSpritePosition(_leftSprite.rectTransform, _leftSpritePosition);
- SetSpritePosition(_rightSprite.rectTransform, _rightSpritePosition);
- return;
+ // single -> multi: hidden side starts from center, then both move to side positions.
+ if (otherCurrentlyVisible)
+ {
+ return _singleSpeakerCenterPosition;
+ }
+
+ return isLeft ? _leftSpritePosition : _rightSpritePosition;
}
- if (hasLeftSpeaker)
+ // single appears: active side starts from its side and moves to center.
+ return isLeft ? _leftSpritePosition : _rightSpritePosition;
+ }
+
+ private float GetTargetX(bool isLeft, bool hasLeftSpeaker, bool hasRightSpeaker)
+ {
+ if (hasLeftSpeaker && hasRightSpeaker)
{
- SetSpriteVisible(_leftSprite, true);
- SetSpriteVisible(_rightSprite, false);
- SetSpritePosition(_leftSprite.rectTransform, _singleSpeakerCenterPosition);
- return;
+ return isLeft ? _leftSpritePosition : _rightSpritePosition;
}
- if (hasRightSpeaker)
+ if (hasLeftSpeaker || hasRightSpeaker)
{
- SetSpriteVisible(_leftSprite, false);
- SetSpriteVisible(_rightSprite, true);
- SetSpritePosition(_rightSprite.rectTransform, -_singleSpeakerCenterPosition);
- return;
+ // multi -> single: both move to center first, then inactive side hides.
+ return _singleSpeakerCenterPosition;
}
- SetSpriteVisible(_leftSprite, false);
- SetSpriteVisible(_rightSprite, false);
+ return isLeft ? _leftSpritePosition : _rightSpritePosition;
+ }
+
+ private Tween CreateMoveTween(RectTransform rectTransform, float targetX)
+ {
+ if (rectTransform == null)
+ {
+ return null;
+ }
+
+ if (Mathf.Abs(rectTransform.anchoredPosition.x - targetX) < 0.01f)
+ {
+ return null;
+ }
+
+ return rectTransform.DOAnchorPosX(targetX, _moveDuration).SetEase(_moveEase);
}
private static void SetSpriteVisible(Image spriteImage, bool visible)
@@ -141,4 +277,4 @@ namespace UI
rectTransform.anchoredPosition = anchoredPosition;
}
}
-}
\ No newline at end of file
+}
diff --git a/Assets/GameMain/Scripts/UI/Dialog/View/DialogFormBase.cs b/Assets/GameMain/Scripts/UI/Dialog/View/DialogFormBase.cs
index edfab3b..6d35785 100644
--- a/Assets/GameMain/Scripts/UI/Dialog/View/DialogFormBase.cs
+++ b/Assets/GameMain/Scripts/UI/Dialog/View/DialogFormBase.cs
@@ -1,14 +1,17 @@
+using System.Collections;
using Definition.Enum;
using Event;
+using TMPro;
using UnityEngine;
namespace UI
{
public abstract class DialogFormBase : UGuiForm
{
- [SerializeField] protected float _playSpeed;
-
protected DialogFormContext _context;
+ private Coroutine _typingCoroutine;
+ private TMP_Text _typingTargetText;
+ private bool _isTypewriting;
public abstract DialogFormMode UIMode { get; }
@@ -29,12 +32,18 @@ namespace UI
protected override void OnClose(bool isShutdown, object userData)
{
+ StopTypewriter();
_context = null;
base.OnClose(isShutdown, userData);
}
public void OnClickNextLine()
{
+ if (CompleteTypewriterIfRunning())
+ {
+ return;
+ }
+
GameEntry.Event.Fire(this, DialogNextLineRequestEventArgs.Create());
}
@@ -48,24 +57,90 @@ namespace UI
GameEntry.Event.Fire(this, DialogStopRequestEventArgs.Create());
}
- protected static string NormalizeValue(string value)
+ protected void PlayTypewriter(TMP_Text targetText, string text, float charsPerSecond)
{
- if (string.IsNullOrEmpty(value))
+ StopTypewriter();
+
+ if (targetText == null)
{
- return string.Empty;
+ return;
}
- if (string.Equals(value, "Null", System.StringComparison.OrdinalIgnoreCase))
+ string finalText = text ?? string.Empty;
+ _typingTargetText = targetText;
+
+ if (charsPerSecond <= 0f || string.IsNullOrEmpty(finalText))
{
- return string.Empty;
+ targetText.text = finalText;
+ targetText.maxVisibleCharacters = int.MaxValue;
+ _isTypewriting = false;
+ return;
}
- if (string.Equals(value, "None", System.StringComparison.OrdinalIgnoreCase))
+ _isTypewriting = true;
+ _typingCoroutine = StartCoroutine(TypewriterRoutine(targetText, finalText, charsPerSecond));
+ }
+
+ protected void StopTypewriter()
+ {
+ if (_typingCoroutine != null)
{
- return string.Empty;
+ StopCoroutine(_typingCoroutine);
+ _typingCoroutine = null;
}
- return value;
+ _typingTargetText = null;
+ _isTypewriting = false;
+ }
+
+ private bool CompleteTypewriterIfRunning()
+ {
+ if (!_isTypewriting || _typingTargetText == null)
+ {
+ return false;
+ }
+
+ _typingTargetText.maxVisibleCharacters = int.MaxValue;
+ StopTypewriter();
+ return true;
+ }
+
+ private IEnumerator TypewriterRoutine(TMP_Text targetText, string finalText, float charsPerSecond)
+ {
+ targetText.text = finalText;
+ targetText.ForceMeshUpdate();
+
+ int totalCharacters = targetText.textInfo.characterCount;
+ if (totalCharacters <= 0)
+ {
+ targetText.maxVisibleCharacters = int.MaxValue;
+ _typingCoroutine = null;
+ _typingTargetText = null;
+ _isTypewriting = false;
+ yield break;
+ }
+
+ targetText.maxVisibleCharacters = 0;
+ float elapsed = 0f;
+ int visibleCharacters = 0;
+
+ while (visibleCharacters < totalCharacters)
+ {
+ elapsed += Time.unscaledDeltaTime;
+ int nextVisible = Mathf.Min(totalCharacters, Mathf.FloorToInt(elapsed * charsPerSecond));
+ if (nextVisible != visibleCharacters)
+ {
+ visibleCharacters = nextVisible;
+ targetText.maxVisibleCharacters = visibleCharacters;
+ }
+
+ yield return null;
+ }
+
+ targetText.maxVisibleCharacters = int.MaxValue;
+ _typingCoroutine = null;
+ _typingTargetText = null;
+ _isTypewriting = false;
}
}
}
diff --git a/Assets/GameMain/Scripts/UI/Dialog/View/MaskDialogForm.cs b/Assets/GameMain/Scripts/UI/Dialog/View/MaskDialogForm.cs
index 68fd91a..0673233 100644
--- a/Assets/GameMain/Scripts/UI/Dialog/View/MaskDialogForm.cs
+++ b/Assets/GameMain/Scripts/UI/Dialog/View/MaskDialogForm.cs
@@ -9,11 +9,11 @@ namespace UI
public class MaskDialogForm : DialogFormBase
{
public override DialogFormMode UIMode => DialogFormMode.Mask;
-
+
[SerializeField] private Image _maskImage;
-
+
[SerializeField] private TMP_Text _text;
-
+
public override void StartDialog(DialogFormContext context)
{
if (context == null)
@@ -29,10 +29,7 @@ namespace UI
_maskImage.gameObject.SetActive(true);
}
- if (_text != null)
- {
- _text.text = NormalizeValue(context.Text);
- }
+ PlayTypewriter(_text, context.Text, context.PlayingSpeed);
}
}
}
diff --git a/Assets/GameMain/UI/UIForms/BottomDialogForm.prefab b/Assets/GameMain/UI/UIForms/BottomDialogForm.prefab
index ad6f356..021bc84 100644
--- a/Assets/GameMain/UI/UIForms/BottomDialogForm.prefab
+++ b/Assets/GameMain/UI/UIForms/BottomDialogForm.prefab
@@ -116,7 +116,7 @@ MonoBehaviour:
m_SelectedTrigger: Selected
m_DisabledTrigger: Disabled
m_Interactable: 1
- m_TargetGraphic: {fileID: 0}
+ m_TargetGraphic: {fileID: 3708131469420921886}
m_OnClick:
m_PersistentCalls:
m_Calls:
@@ -321,7 +321,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
- m_IsActive: 1
+ m_IsActive: 0
--- !u!224 &4594737505273760298
RectTransform:
m_ObjectHideFlags: 0
@@ -679,12 +679,15 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
_playSpeed: 0
+ _speakerArea: {fileID: 3736358320082617150}
_speakerNameText: {fileID: 2470970474825277305}
_contentText: {fileID: 6431296888118130931}
_leftSprite: {fileID: 7945103967507868302}
_rightSprite: {fileID: 5385698520020721016}
_leftSpritePosition: 450
_rightSpritePosition: -450
+ _moveDuration: 0.25
+ _moveEase: 9
--- !u!1 &7381337123922490182
GameObject:
m_ObjectHideFlags: 0
@@ -837,7 +840,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
- m_IsActive: 1
+ m_IsActive: 0
--- !u!224 &1023330169278438415
RectTransform:
m_ObjectHideFlags: 0
diff --git a/Assets/Launcher.unity b/Assets/Launcher.unity
index 720e3fc..0e353b2 100644
--- a/Assets/Launcher.unity
+++ b/Assets/Launcher.unity
@@ -841,6 +841,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: d6174838c30e460429e5628757bbf015, type: 3}
m_Name:
m_EditorClassIdentifier:
+ _playingSpeed: 10
--- !u!1 &513208572
GameObject:
m_ObjectHideFlags: 0
From 7ac832f74bc95bfc5da26364e138d3e1ea76ab55 Mon Sep 17 00:00:00 2001
From: SepComet <202308010230@stu.csust.edu.cn>
Date: Mon, 9 Feb 2026 13:53:04 +0800
Subject: [PATCH 6/6] =?UTF-8?q?-=20=E8=B0=83=E6=95=B4=E4=BA=86=E5=AF=B9?=
=?UTF-8?q?=E8=AF=9D=20UI=20=E7=B1=BB=E5=9E=8B=E7=9A=84=E5=91=BD=E5=90=8D?=
=?UTF-8?q?=EF=BC=8C=E4=BD=BF=E5=85=B6=E6=9B=B4=E5=85=B7=E8=BE=A8=E8=AF=86?=
=?UTF-8?q?=E5=BA=A6=20-=20=E6=98=BE=E5=BC=8F=E6=A0=87=E8=AE=B0=E8=BF=98?=
=?UTF-8?q?=E6=9C=AA=E5=AE=9E=E7=8E=B0=E7=9A=84=20BubbleDialogForm?=
=?UTF-8?q?=EF=BC=8C=E9=81=BF=E5=85=8D=E8=AF=AF=E7=94=A8=E7=9A=84=E6=83=85?=
=?UTF-8?q?=E5=86=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Assets/GameMain/DataTables/Dialog.txt | 2 +-
Assets/GameMain/DataTables/DialogLine.txt | 2 +-
Assets/GameMain/DataTables/UIForm.txt | 2 +-
.../Scripts/Definition/Enum/DialogFormMode.cs | 2 +-
.../Scripts/Definition/Enum/UIFormId.cs | 2 +-
.../Scripts/Procedure/ProcedureCombine.cs | 4 ++--
.../Dialog/Controller/DialogFormController.cs | 7 ++++---
...Form.prefab => BottomBoxDialogForm.prefab} | 2 +-
...b.meta => BottomBoxDialogForm.prefab.meta} | 0
数据表/Dialog.txt | 2 +-
数据表/Dialog.xlsx | Bin 10774 -> 10772 bytes
数据表/UIForm.txt | 2 +-
数据表/UIForm.xlsx | Bin 10322 -> 10331 bytes
13 files changed, 14 insertions(+), 13 deletions(-)
rename Assets/GameMain/UI/UIForms/{BottomDialogForm.prefab => BottomBoxDialogForm.prefab} (99%)
rename Assets/GameMain/UI/UIForms/{BottomDialogForm.prefab.meta => BottomBoxDialogForm.prefab.meta} (100%)
diff --git a/Assets/GameMain/DataTables/Dialog.txt b/Assets/GameMain/DataTables/Dialog.txt
index a3e4690..bb0b92c 100644
--- a/Assets/GameMain/DataTables/Dialog.txt
+++ b/Assets/GameMain/DataTables/Dialog.txt
@@ -4,4 +4,4 @@
# 对话编号 策划备注 对话标识 对话形式 章节编号
1001 第一章介绍 Chapter1_Intro Mask 1.001
1002 第一章主流程 Chapter1_Main BottomBox 1.002
- 1003 第一章玩法开始前闲聊 Chapter1_SmallTalk1 BubbleBox 1.003
+ 1003 第一章玩法开始前闲聊 Chapter1_SmallTalk1 Bubble 1.003
diff --git a/Assets/GameMain/DataTables/DialogLine.txt b/Assets/GameMain/DataTables/DialogLine.txt
index 901aa3f..567ba54 100644
--- a/Assets/GameMain/DataTables/DialogLine.txt
+++ b/Assets/GameMain/DataTables/DialogLine.txt
@@ -2,7 +2,7 @@
# Id SpeakerId Expression SpeakerName Direction Text Emphasis ChapterId DialogId
# int string ExpressionType string int string EmphasisType int int
# 对话行编号 策划备注 说话人Id 表情 显示人名 说话朝向 说话内容 演出效果 章节Id 对话Id
- 100100001 Id规则为 Null None Null 0 相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。相传。 None 1.00100001 1001.00001
+ 100100001 Id规则为 Null None Null 0 相传。 None 1.00100001 1001.00001
100100002 第1位数为章节Id Null None Null 0 Mask。 None 1.00100002 1001.00002
100100003 第2-4位数为对话Id Null None Null 0 很好。 None 1.00100003 1001.00003
100200001 第5-9位数为对话行Id Xu Normal 徐晟壹 0 你好,王。 None 1.00200001 1002.00001
diff --git a/Assets/GameMain/DataTables/UIForm.txt b/Assets/GameMain/DataTables/UIForm.txt
index 53ec801..eccc80b 100644
--- a/Assets/GameMain/DataTables/UIForm.txt
+++ b/Assets/GameMain/DataTables/UIForm.txt
@@ -8,5 +8,5 @@
102 关于 AboutForm Default False True
103 组装玩法UI CombineForm Default False False
104 Mask对话UI MaskDialogForm Default False False
- 105 Bottom对话UI BottomDialogForm Default False False
+ 105 Bottom对话UI BottomBoxDialogForm Default False False
106 Bubble对话UI BubbleDialogForm Default True False
diff --git a/Assets/GameMain/Scripts/Definition/Enum/DialogFormMode.cs b/Assets/GameMain/Scripts/Definition/Enum/DialogFormMode.cs
index 91c1a70..07c32ee 100644
--- a/Assets/GameMain/Scripts/Definition/Enum/DialogFormMode.cs
+++ b/Assets/GameMain/Scripts/Definition/Enum/DialogFormMode.cs
@@ -17,6 +17,6 @@ namespace Definition.Enum
///
/// 对话气泡
///
- BubbleBox = 3
+ Bubble = 3
}
}
\ No newline at end of file
diff --git a/Assets/GameMain/Scripts/Definition/Enum/UIFormId.cs b/Assets/GameMain/Scripts/Definition/Enum/UIFormId.cs
index c55f37f..f3ca7c5 100644
--- a/Assets/GameMain/Scripts/Definition/Enum/UIFormId.cs
+++ b/Assets/GameMain/Scripts/Definition/Enum/UIFormId.cs
@@ -47,7 +47,7 @@ namespace UI
///
/// 底部剧情对话界面。
///
- BottomDialogForm = 105,
+ BottomBoxDialogForm = 105,
///
/// 气泡剧情对话界面。
diff --git a/Assets/GameMain/Scripts/Procedure/ProcedureCombine.cs b/Assets/GameMain/Scripts/Procedure/ProcedureCombine.cs
index 76a2302..08ad6fd 100644
--- a/Assets/GameMain/Scripts/Procedure/ProcedureCombine.cs
+++ b/Assets/GameMain/Scripts/Procedure/ProcedureCombine.cs
@@ -90,8 +90,8 @@ namespace Procedure
//InitializeProcedureState();
GameEntry.Dialog.Init(1);
- GameEntry.Dialog.StartDialog(1001);
- //GameEntry.Dialog.StartDialog(1002);
+ //GameEntry.Dialog.StartDialog(1001);
+ GameEntry.Dialog.StartDialog(1002);
}
///
diff --git a/Assets/GameMain/Scripts/UI/Dialog/Controller/DialogFormController.cs b/Assets/GameMain/Scripts/UI/Dialog/Controller/DialogFormController.cs
index 592f803..5a3014b 100644
--- a/Assets/GameMain/Scripts/UI/Dialog/Controller/DialogFormController.cs
+++ b/Assets/GameMain/Scripts/UI/Dialog/Controller/DialogFormController.cs
@@ -1,3 +1,4 @@
+using System;
using Definition.Enum;
using GameFramework.Event;
using UnityGameFramework.Runtime;
@@ -109,9 +110,9 @@ namespace UI
case DialogFormMode.Mask:
return UIFormId.MaskDialogForm;
case DialogFormMode.BottomBox:
- return UIFormId.BottomDialogForm;
- case DialogFormMode.BubbleBox:
- return UIFormId.BottomDialogForm;
+ return UIFormId.BottomBoxDialogForm;
+ case DialogFormMode.Bubble:
+ throw new NotImplementedException("BubbleBox 对话框尚未实现");
default:
return UIFormId.Undefined;
}
diff --git a/Assets/GameMain/UI/UIForms/BottomDialogForm.prefab b/Assets/GameMain/UI/UIForms/BottomBoxDialogForm.prefab
similarity index 99%
rename from Assets/GameMain/UI/UIForms/BottomDialogForm.prefab
rename to Assets/GameMain/UI/UIForms/BottomBoxDialogForm.prefab
index 021bc84..19efbf2 100644
--- a/Assets/GameMain/UI/UIForms/BottomDialogForm.prefab
+++ b/Assets/GameMain/UI/UIForms/BottomBoxDialogForm.prefab
@@ -615,7 +615,7 @@ GameObject:
- component: {fileID: 4643264964412212504}
- component: {fileID: 2327325990879817607}
m_Layer: 5
- m_Name: BottomDialogForm
+ m_Name: BottomBoxDialogForm
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
diff --git a/Assets/GameMain/UI/UIForms/BottomDialogForm.prefab.meta b/Assets/GameMain/UI/UIForms/BottomBoxDialogForm.prefab.meta
similarity index 100%
rename from Assets/GameMain/UI/UIForms/BottomDialogForm.prefab.meta
rename to Assets/GameMain/UI/UIForms/BottomBoxDialogForm.prefab.meta
diff --git a/数据表/Dialog.txt b/数据表/Dialog.txt
index a3e4690..bb0b92c 100644
--- a/数据表/Dialog.txt
+++ b/数据表/Dialog.txt
@@ -4,4 +4,4 @@
# 对话编号 策划备注 对话标识 对话形式 章节编号
1001 第一章介绍 Chapter1_Intro Mask 1.001
1002 第一章主流程 Chapter1_Main BottomBox 1.002
- 1003 第一章玩法开始前闲聊 Chapter1_SmallTalk1 BubbleBox 1.003
+ 1003 第一章玩法开始前闲聊 Chapter1_SmallTalk1 Bubble 1.003
diff --git a/数据表/Dialog.xlsx b/数据表/Dialog.xlsx
index 6643c3718d1bc3b84d8167e840c0ee51786f541d..7ad1427ab86ac0c716b15b131fdd985f16495001 100644
GIT binary patch
delta 1619
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
b2^y2QDJlZv3X>5D6_fcX6$Wi300000zdiAD
diff --git a/数据表/UIForm.txt b/数据表/UIForm.txt
index 53ec801..eccc80b 100644
--- a/数据表/UIForm.txt
+++ b/数据表/UIForm.txt
@@ -8,5 +8,5 @@
102 关于 AboutForm Default False True
103 组装玩法UI CombineForm Default False False
104 Mask对话UI MaskDialogForm Default False False
- 105 Bottom对话UI BottomDialogForm Default False False
+ 105 Bottom对话UI BottomBoxDialogForm Default False False
106 Bubble对话UI BubbleDialogForm Default True False
diff --git a/数据表/UIForm.xlsx b/数据表/UIForm.xlsx
index 33a879a8eba683dd1f9fbab55ce967962e43a86c..ba5c5196d0c46c7d2dd661d56d738826ff61d1d6 100644
GIT binary patch
delta 2085
zcmV+=2-^43P}@+jlK}NhLs?sD#ua5Sy{DZA9!?wxR9L#49oI
zGAz6WPSTcCATj#>|GxY0zER(sukpD
z0~U>n@lbt0Rvmpbp0R^ku
zpq?O;)&wJejhAhYqbeANzn|p%2jlnkrPFDrleU{3Yr7}eO*ZHCGgaD}QE`RVnSZ|z
zprXKd8k4GU@$lSmLuLK>t;n4lu4zWfjT?G_>)!JAjO@m
z>EnfSBfla`YC_40c?l_enVP9`<*!CMi^K?I8LR@;uh;4m1g=2bG0QXdF64FzVW#3V
zP2)9x74?#`&0;V>;nK$}x>81p9b(AcuEh-esQv9a&0rMy)H^jkZQL*6^C6tMP$7_i
zGu+U@v%}EwFP!6q;S{{$I@|+CLS8xe2NVT7xBz%al(6iEBb?_OHx5_eqo1M|{tR9C
zeILW;uD`wT&5zni@d;gX7LokFEM=FC`{XTuXM^j(?T|l7^A%_33xnL|u0yUY&r19a
z-*67CH|~P>nvfj2fM2J`xZz!=KyOmaN8u@Z&%ya&+gvuBwGH9k$cAC)%^%hE$k$EV
z&`HGbw*tn2Ab*ho+`B+x$=%TuM2eDha#(o-E%STDEZRVS
zaPKp^Glyl{6z*Nqg%>`l3r2csKz%v0!c^Sn+jUJE2lfD%Vp<`-Lxs;vFlfRHzwlB~
z2X3KIdfNtT{{~J>TH?hd!k+LUcHaR{%#zbrq^D(hEiN`T<
zFo{J^jD&(kEkb$ev`~{+_8mx}Ch;_XCgT4k_9n3!$2d$8BcZUMEJAtdv`~`>HCIIz
zMA=FXh6T}#5nB+=8G8$&S>s?qls85L!q>lH@fTjo2ZXO4O+?hyIsN(ym8+yq3!?cV
zwji1@_Cw@oOXV;{j8wv|Wl_mXdzFDcpIz-hr)ImIiOjSpv5ADb&LJXoYC#TvCZQ%}
zq!YGSi%wpubgHIevZAh5>6~fE&VuN^H4#JS5V7kt36;r6C;uNcUCT?APA$Wzs~zan
zR@TmfXe%o=5kuz?vFkL64|MWRD^n*gRXQ~hQCBZ=D*wS8uPXJfPl7+V-a?1-_eokYfRtzRxLVfSGow%tIRwWzNi
z$klfw3-h4uCb5Mv<#ws?zCMsUg^cC$?-0|*a2^@@SaV?1S1Y*>YMDqQ+Zb)##}>wv
z+ohu9YF+Wulgw5jYG9>YdG3*)s1-Wn@2Ea!C3SLqf>8{-CH!C2@)#p1Ul^&lyPYFp
zaE23)7dS^__>2uafKP<5?gdUbvTiR5`7x_MsBeD(kRG!$2|xx3+|(av>;V7(IFo!2
zEPn{0BvZF(C>KRY&