我正在制作一个复古风格的游戏C# .NET-Framework,对于对话,我使用了一个 for 语句,它会一个字母一个字母地打印我的文本(就像打字机效果):
我正在处理不同的场景,我有一个跳过按钮(右下角)可以跳过当前对话并传递到下一个场景。当显示所有文本时,我的打字机效果会自动停止,但是当我单击跳过按钮时,它会自动跳到下一个场景。
我希望在打字机仍处于活动状态时,如果我单击跳过按钮,它首先显示所有文本,而不是跳到下一个场景。
这样它只会在显示所有文本时(自动或手动)跳到下一个场景。
这是我用于打字机方法(+ 变量)的(工作代码):
public string FullTextBottom;
public string CurrentTextBottom = "";
public bool IsActive;
public async void TypeWriterEffectBottom()
{
if(this.BackgroundImage != null) // only runs on backgrounds that arent black
{
for(i=0; i < FullTextBottom.Length + 1; i++)
{
CurrentTextBottom = FullTextBottom.Substring(0, i); // updating current string with one extra letter
LblTextBottom.Text = CurrentTextBottom; // "temporarily place string in text box"
await Task.Delay(30); // wait for next update
#region checks for IsActive // for debugging only!
if(i < FullTextBottom.Length + 1)
{
IsActive = true;
Debug1.Text = "IsActive = " + IsActive.ToString();
}
if(CurrentTextBottom.Length == FullTextBottom.Length)
{
IsActive = false;
Debug1.Text = "IsActive = " + IsActive.ToString();
}
#endregion
}
}
}
Run Code Online (Sandbox Code Playgroud)
这是我想为我的跳过按钮(名为Pb_FastForward)获取的代码:
private void PbFastForward_Click(object sender, EventArgs e)
{
if( //typewriter is active)
{
//print all text into the textbox
}
else if( //all text is printed)
{
// skip to the next scene
}
}
Run Code Online (Sandbox Code Playgroud)
但我不知道如何制定代码的第二部分。我尝试了许多不同的方法,例如使用在单击按钮时增加的计数器(并使用它来检查 if 语句),以及许多不同类型的 if 语句来查看打字机是否仍然处于活动状态,但我还没有任何工作。
这是不同组件需要加载的顺序(点击按钮时),这与不同变量的更新方式有关:
Gamestate_Cycle() --> 调用加载新场景。FullTextBottom = LblTextBottom.Text --> 调用以刷新打字机的变量。TypeWriterEffectBottom() --> 调用执行打字机效果。aep*_*pot 46
避免async void。否则你会得到一个Exception会破坏你的游戏并且你将无法catch做到的。
然后在async方法中尽可能少地使用全局变量。
我建议CancellationTokenSource作为线程安全的方式来停止 Type Writer。
public async Task TypeWriterEffectBottom(string text, CancellationToken token)
{
if (this.BackgroundImage != null)
{
Debug1.Text = "TypeWriter is active";
StringBuilder sb = new StringBuilder(text.Length);
foreach (char c in text)
{
if (token.IsCancellationRequested)
{
LblTextBottom.Text = text;
break;
}
sb.Append(c);
LblTextBottom.Text = sb.ToString();
await Task.Delay(30);
}
Debug1.Text = "TypeWriter is finished";
}
}
Run Code Online (Sandbox Code Playgroud)
定义 CTS。它是线程安全的,因此可以在全局范围内使用它。
private CancellationTokenSource cts = null;
Run Code Online (Sandbox Code Playgroud)
从async方法调用 TypeWriter就可以await了。
// set button layout as "Skip text" here
using (cts = new CancellationTokenSource())
{
await TypeWriterEffectBottom(yourString, cts.Token);
}
cts = null;
// set button layout as "Go to the next scene" here
Run Code Online (Sandbox Code Playgroud)
最后
private void PbFastForward_Click(object sender, EventArgs e)
{
if (cts != null)
{
cts?.Cancel();
}
else
{
// go to the next scene
}
}
Run Code Online (Sandbox Code Playgroud)
Geb*_*ebb 19
我对你的任务思考了更多,我突然想到 Rx.Net库是一项很好的工作。
这种方法的一个优点是你需要关心的可变状态较少,你几乎不需要考虑线程、同步等;您可以操作更高级别的构建块:可观察对象、订阅。
我稍微扩展了任务以更好地说明 Rx 功能:
这是表单代码(C# 8,System.Reactive.Linq v4.4.1):
private enum DialogState
{
NpcSpeaking,
PlayerSpeaking,
EverythingShown
}
private enum EventKind
{
AnimationFinished,
Skip,
SkipToEnd
}
DialogState _state;
private readonly Subject<DialogState> _stateChanges = new Subject<DialogState>();
Dictionary<DialogState, (string, Label)> _lines;
IDisposable _eventsSubscription;
IDisposable _animationSubscription;
public Form1()
{
InitializeComponent();
_lines = new Dictionary<DialogState, (string, Label)>
{
{ DialogState.NpcSpeaking, ("NPC speaking...", lblNpc) },
{ DialogState.PlayerSpeaking, ("Player speaking...", lblCharacter) },
};
// tick = 1,2...
IObservable<long> tick = Observable
.Interval(TimeSpan.FromSeconds(0.15))
.ObserveOn(this)
.StartWith(-1)
.Select(x => x + 2);
IObservable<EventPattern<object>> fastForwardClicks = Observable.FromEventPattern(
h => btnFastForward.Click += h,
h => btnFastForward.Click -= h);
IObservable<EventPattern<object>> skipToEndClicks = Observable.FromEventPattern(
h => btnSkipToEnd.Click += h,
h => btnSkipToEnd.Click -= h);
// On each state change animationFarames starts from scratch: 1,2...
IObservable<long> animationFarames = _stateChanges
.Select(
s => Observable.If(() => _lines.ContainsKey(s), tick.TakeUntil(_stateChanges)))
.Switch();
var animationFinished = new Subject<int>();
_animationSubscription = animationFarames.Subscribe(frame =>
{
(string line, Label lbl) = _lines[_state];
if (frame > line.Length)
{
animationFinished.OnNext(default);
return;
}
lbl.Text = line.Substring(0, (int)frame);
});
IObservable<EventKind> events = Observable.Merge(
skipToEndClicks.Select(_ => EventKind.SkipToEnd),
fastForwardClicks.Select(_ => EventKind.Skip),
animationFinished.Select(_ => EventKind.AnimationFinished));
_eventsSubscription = events.Subscribe(e =>
{
DialogState prev = _state;
_state = prev switch
{
DialogState.NpcSpeaking => WhenSpeaking(e, DialogState.PlayerSpeaking),
DialogState.PlayerSpeaking => WhenSpeaking(e, DialogState.EverythingShown),
DialogState.EverythingShown => WhenEverythingShown(e)
};
_stateChanges.OnNext(_state);
});
Reset();
}
private DialogState WhenEverythingShown(EventKind _)
{
Close();
return _state;
}
private DialogState WhenSpeaking(EventKind e, DialogState next)
{
switch (e)
{
case EventKind.AnimationFinished:
case EventKind.Skip:
{
(string l, Label lbl) = _lines[_state];
lbl.Text = l;
return next;
}
case EventKind.SkipToEnd:
{
ShowFinalState();
return DialogState.EverythingShown;
}
default:
throw new NotSupportedException($"Unknown event '{e}'.");
}
}
private void ShowFinalState()
{
foreach ((string l, Label lbl) in _lines.Values)
{
lbl.Text = l;
}
}
private void Reset()
{
foreach ((_, Label lbl) in _lines.Values)
{
lbl.Text = "";
}
_state = DialogState.NpcSpeaking;
_stateChanges.OnNext(_state);
}
protected override void OnClosed(EventArgs e)
{
_eventsSubscription?.Dispose();
_animationSubscription?.Dispose();
base.OnClosed(e);
}
private void btnReset_Click(object sender, EventArgs e)
{
Reset();
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
10952 次 |
| 最近记录: |