From b2b59aa78a0ef4def723f4f94229a5b49fa58c10 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Fri, 6 Sep 2019 15:49:59 +0200 Subject: [PATCH] added text animation --- Demos/UiDemo.cs | 6 +++ MLEM.Ui/Elements/Paragraph.cs | 4 +- MLEM/Formatting/FormattingCode.cs | 9 +++- MLEM/Formatting/TextAnimation.cs | 30 +++++++++++++ MLEM/Formatting/TextFormatting.cs | 73 ++++++++++++++++++++----------- 5 files changed, 95 insertions(+), 27 deletions(-) create mode 100644 MLEM/Formatting/TextAnimation.cs diff --git a/Demos/UiDemo.cs b/Demos/UiDemo.cs index 8d5be22..11d8c96 100644 --- a/Demos/UiDemo.cs +++ b/Demos/UiDemo.cs @@ -102,6 +102,12 @@ namespace Demos { TextFormatting.FormattingCodes["tree"] = new FormattingCode(tree); root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "Additionally, you can create custom formatting codes that contain [Grass] images! Note that these images have to be square, or [Tree] bad things happen.")); + var animatedPar = root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "Defining text animations as formatting codes is also possible, including [Wobbly]wobbly text[Unanimated] as well as a [Typing]dialogue-esc typing effect by default. Of course, more animations can be added though.")); + root.AddChild(new Button(Anchor.AutoCenter, new Vector2(1, 10), "Reset Typing Animation") { + // to reset any animation, simply change the paragraph's TimeIntoAnimation + OnPressed = e => animatedPar.TimeIntoAnimation = TimeSpan.Zero + }); + root.AddChild(new VerticalSpace(3)); root.AddChild(new Paragraph(Anchor.AutoCenter, 1, "Text input:", true)); root.AddChild(new TextField(Anchor.AutoLeft, new Vector2(1, 10)) { diff --git a/MLEM.Ui/Elements/Paragraph.cs b/MLEM.Ui/Elements/Paragraph.cs index 6a17d7a..0fcda97 100644 --- a/MLEM.Ui/Elements/Paragraph.cs +++ b/MLEM.Ui/Elements/Paragraph.cs @@ -35,6 +35,7 @@ namespace MLEM.Ui.Elements { } public bool AutoAdjustWidth; public TextCallback GetTextCallback; + public TimeSpan TimeIntoAnimation; public Paragraph(Anchor anchor, float width, TextCallback textCallback, bool centerText = false) : this(anchor, width, "", centerText) { @@ -64,6 +65,7 @@ namespace MLEM.Ui.Elements { base.Update(time); if (this.GetTextCallback != null) this.Text = this.GetTextCallback(this); + this.TimeIntoAnimation += time.ElapsedGameTime; } public override void Draw(GameTime time, SpriteBatch batch, float alpha) { @@ -78,7 +80,7 @@ namespace MLEM.Ui.Elements { this.regularFont.DrawString(batch, this.splitText, pos, this.TextColor * alpha, 0, Vector2.Zero, sc, SpriteEffects.None, 0); } else { // if we have formatting codes, we should do it - this.regularFont.DrawFormattedString(batch, pos, this.splitText, this.codeLocations, this.TextColor * alpha, sc, this.boldFont, this.italicFont); + this.regularFont.DrawFormattedString(batch, pos, this.splitText, this.codeLocations, this.TextColor * alpha, sc, this.boldFont, this.italicFont, 0, this.TimeIntoAnimation); } base.Draw(time, batch, alpha); } diff --git a/MLEM/Formatting/FormattingCode.cs b/MLEM/Formatting/FormattingCode.cs index 75f1637..c5a7838 100644 --- a/MLEM/Formatting/FormattingCode.cs +++ b/MLEM/Formatting/FormattingCode.cs @@ -8,6 +8,7 @@ namespace MLEM.Formatting { public readonly Color Color; public readonly TextStyle Style; public readonly TextureRegion Icon; + public readonly TextAnimation.DrawCharacter Animation; public FormattingCode(Color color) { this.Color = color; @@ -24,6 +25,11 @@ namespace MLEM.Formatting { this.CodeType = Type.Icon; } + public FormattingCode(TextAnimation.DrawCharacter animation) { + this.Animation = animation; + this.CodeType = Type.Animation; + } + public string GetReplacementString() { return this.CodeType == Type.Icon ? TextFormatting.OneEmString : string.Empty; } @@ -32,7 +38,8 @@ namespace MLEM.Formatting { Color, Style, - Icon + Icon, + Animation } diff --git a/MLEM/Formatting/TextAnimation.cs b/MLEM/Formatting/TextAnimation.cs new file mode 100644 index 0000000..a7684a7 --- /dev/null +++ b/MLEM/Formatting/TextAnimation.cs @@ -0,0 +1,30 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MLEM.Font; + +namespace MLEM.Formatting { + public static class TextAnimation { + + public static float WobbleModifier = 5; + public static float WobbleHeightModifier = 1 / 8F; + public static float TypingSpeed = 20; + + public static readonly DrawCharacter Default = (font, batch, totalText, index, effectStartIndex, charSt, position, color, scale, layerDepth, timeIntoAnimation) => { + font.DrawString(batch, charSt, position, color, 0, Vector2.Zero, scale, SpriteEffects.None, layerDepth); + }; + + public static readonly DrawCharacter Wobbly = (font, batch, totalText, index, effectStartIndex, charSt, position, color, scale, layerDepth, timeIntoAnimation) => { + var offset = new Vector2(0, (float) Math.Sin(index + timeIntoAnimation.TotalSeconds * WobbleModifier) * font.LineHeight * WobbleHeightModifier * scale); + font.DrawString(batch, charSt, position + offset, color, 0, Vector2.Zero, scale, SpriteEffects.None, layerDepth); + }; + + public static readonly DrawCharacter Typing = (font, batch, totalText, index, effectStartIndex, charSt, position, color, scale, layerDepth, timeIntoAnimation) => { + if (timeIntoAnimation.TotalSeconds * TypingSpeed > index - effectStartIndex + 1) + font.DrawString(batch, charSt, position, color, 0, Vector2.Zero, scale, SpriteEffects.None, layerDepth); + }; + + public delegate void DrawCharacter(IGenericFont font, SpriteBatch batch, string totalText, int index, int effectStartIndex, string charSt, Vector2 position, Color color, float scale, float layerDepth, TimeSpan timeIntoAnimation); + + } +} \ No newline at end of file diff --git a/MLEM/Formatting/TextFormatting.cs b/MLEM/Formatting/TextFormatting.cs index 3deb7a5..03bb56e 100644 --- a/MLEM/Formatting/TextFormatting.cs +++ b/MLEM/Formatting/TextFormatting.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Text.RegularExpressions; using Microsoft.Xna.Framework; @@ -20,15 +21,22 @@ namespace MLEM.Formatting { static TextFormatting() { SetFormatIndicators('[', ']'); + // style codes FormattingCodes["regular"] = new FormattingCode(TextStyle.Regular); FormattingCodes["italic"] = new FormattingCode(TextStyle.Italic); FormattingCodes["bold"] = new FormattingCode(TextStyle.Bold); + // color codes var colors = typeof(Color).GetProperties(); foreach (var color in colors) { if (color.GetGetMethod().IsStatic) FormattingCodes[color.Name.ToLowerInvariant()] = new FormattingCode((Color) color.GetValue(null)); } + + // animations + FormattingCodes["unanimated"] = new FormattingCode(TextAnimation.Default); + FormattingCodes["wobbly"] = new FormattingCode(TextAnimation.Wobbly); + FormattingCodes["typing"] = new FormattingCode(TextAnimation.Typing); } public static void SetFormatIndicators(char opener, char closer) { @@ -40,7 +48,10 @@ namespace MLEM.Formatting { } public static string RemoveFormatting(this string s) { - return formatRegex.Replace(s, match => FromMatch(match).GetReplacementString()); + return formatRegex.Replace(s, match => { + var code = FromMatch(match); + return code != null ? code.GetReplacementString() : string.Empty; + }); } public static Dictionary GetFormattingCodes(this string s) { @@ -48,50 +59,61 @@ namespace MLEM.Formatting { var codeLengths = 0; foreach (Match match in formatRegex.Matches(s)) { var code = FromMatch(match); + if (code == null) + continue; codes[match.Index - codeLengths] = code; codeLengths += match.Length - code.GetReplacementString().Length; } return codes; } - public static void DrawFormattedString(this IGenericFont regularFont, SpriteBatch batch, Vector2 pos, string text, Dictionary codeLocations, Color color, float scale, IGenericFont boldFont = null, IGenericFont italicFont = null, float depth = 0) { - var characterCounter = 0; + public static void DrawFormattedString(this IGenericFont regularFont, SpriteBatch batch, Vector2 pos, string text, Dictionary codeLocations, Color color, float scale, IGenericFont boldFont = null, IGenericFont italicFont = null, float depth = 0, TimeSpan timeIntoAnimation = default) { var currColor = color; var currFont = regularFont; + var currAnim = TextAnimation.Default; + var animStart = 0; var innerOffset = new Vector2(); - foreach (var c in text) { + for (var i = 0; i < text.Length; i++) { // check if the current character's index has a formatting code - codeLocations.TryGetValue(characterCounter, out var code); + codeLocations.TryGetValue(i, out var code); if (code != null) { // if so, apply it - if (code.CodeType == FormattingCode.Type.Color) { - currColor = code.Color.CopyAlpha(color); - } else if (code.CodeType == FormattingCode.Type.Style) { - switch (code.Style) { - case TextStyle.Regular: - currFont = regularFont; - break; - case TextStyle.Bold: - currFont = boldFont ?? regularFont; - break; - case TextStyle.Italic: - currFont = italicFont ?? regularFont; - break; - } - } else if (code.CodeType == FormattingCode.Type.Icon) { - var iconSc = new Vector2(1F / code.Icon.Width, 1F / code.Icon.Height) * regularFont.LineHeight * scale; - batch.Draw(code.Icon, pos + innerOffset, color, 0, Vector2.Zero, iconSc, SpriteEffects.None, depth); + switch (code.CodeType) { + case FormattingCode.Type.Color: + currColor = code.Color.CopyAlpha(color); + break; + case FormattingCode.Type.Style: + switch (code.Style) { + case TextStyle.Regular: + currFont = regularFont; + break; + case TextStyle.Bold: + currFont = boldFont ?? regularFont; + break; + case TextStyle.Italic: + currFont = italicFont ?? regularFont; + break; + } + break; + case FormattingCode.Type.Icon: + var iconSc = new Vector2(1F / code.Icon.Width, 1F / code.Icon.Height) * regularFont.LineHeight * scale; + batch.Draw(code.Icon, pos + innerOffset, color, 0, Vector2.Zero, iconSc, SpriteEffects.None, depth); + break; + case FormattingCode.Type.Animation: + currAnim = code.Animation; + animStart = i; + break; } } - characterCounter++; + var c = text[i]; var cSt = c.ToString(); if (c == '\n') { innerOffset.X = 0; innerOffset.Y += regularFont.LineHeight * scale; } else { - currFont.DrawString(batch, cSt, pos + innerOffset, currColor, 0, Vector2.Zero, scale, SpriteEffects.None, depth); + currAnim(currFont, batch, text, i, animStart, cSt, pos + innerOffset, currColor, scale, depth, timeIntoAnimation); // we measure the string with the regular font here so that previously split // strings don't get too long with a bolder font. This shouldn't effect visuals too much innerOffset.X += regularFont.MeasureString(cSt).X * scale; @@ -101,7 +123,8 @@ namespace MLEM.Formatting { private static FormattingCode FromMatch(Capture match) { var rawCode = match.Value.Substring(1, match.Value.Length - 2).ToLowerInvariant(); - return FormattingCodes[rawCode]; + FormattingCodes.TryGetValue(rawCode, out var val); + return val; } }