/* * Util.DialogScreen.cs * Authors: Adam Nabinger * Copyright (c) 2007-2008 Cornell University This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ using System; using System.Collections.Generic; using System.Text; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace Util { /// /// Which corner the image for the dialog will appear, with the text adjacent to it horizontally. /// public enum Corner { /// /// The Top Left Corner. /// TopLeft, /// /// The Top Right Corner. /// TopRight, /// /// The Bottom Left Corner. /// BottomLeft, /// /// The Bottom Right Corner. /// BottomRight }; /// /// An individual dialog to appear on the screen. /// public sealed class Dialog : IDisposable { #region Constants // Rate at which text appears private const float mScrollRate = 80f; // Default path name for dialog font private const string defaultfontname = @"Fonts\dialogfont"; // Defualt path name for label dialog font private const string defaultlabelfontname = @"Fonts\dialoglabelfont"; // String giving the appropriate message for advancing between dialogs private const string nextString = "(Press the space bar to continue)"; #endregion #region Fields // Coloring of shadow private readonly Color ShadowColor = new Color(0, 0, 0, 140); // Coloring of tint private readonly Color Tint = Color.White; // Character label for dialog text private readonly string mLabel; // Current text to display private string mText; // Path name of the sprite font to use for dialog text private readonly string mFontName = defaultfontname; // Path name of the sprite font to use for dialog label text private readonly string mLabelFontName = defaultlabelfontname; // Path name to the texture file for the dialog private readonly string mIconTextureName; // Path name to the sound file to play - UNIMPLEMENTED //private readonly string mSound; // Position of the dialog screen private readonly Corner mPosition; // Location of the dialog label private Vector2 LabelLocation; // Location of the label's shadow private Vector2 LabelShadowLocation; // Location of the text private Vector2 TextLocation; // Location of the text's shadow private Vector2 TextShadowLocation; // The texture for the background image of the dialog screen private Texture2D backgroundTexture; // The location and dimnsions of the dialog screen's background private Rectangle backgroundLocation; // Location of the next string private Vector2 nextStringLocation; // Location of the next string's shadow private Vector2 nextStringShadowLocation; // Location of the icon iamge private Rectangle IconLocation; // Flip effect to apply to the icon texture private SpriteEffects Flip; // Texture for the icon in the dialog screen private Texture2D IconTexture; // Sprite Font used to render the dialog text private SpriteFont Font; // Label font used to render the dialog label private SpriteFont LabelFont; // Length of the current text private float CurrentLength; // Current text being displayed in the dialog screen private string CurrentText; /// /// Is the user allow to skip this dialog? /// internal readonly bool mSkipAllowed; /// /// Is this dialog timed? /// internal readonly bool Timed; /// /// The time remaining for displaying the rest of the text /// internal float TimeRemaining; /// /// Whether or not the dialog is ready to skip /// internal bool readyToSkip; #endregion #region Creation /// /// Constructor /// /// The character label for the dialog text /// The dialog text to be displayed /// File path name to the texture image to use /// Position of the dialog screen /// Time duration in which text appears /// Whether or not this dialog is skippable public Dialog(string label, string text, string iconTextureName, Corner position, float time, bool skip) { mLabel = label; mText = text; mIconTextureName = iconTextureName; mSkipAllowed = skip; mPosition = position; if (time <= 0) { return; } Timed = true; TimeRemaining = time; } /// /// Constructor /// /// The character label for the dialog text /// The dialog text to be displayed /// File path name to the texture image to use /// Position of the dialog screen /// Time duration in which text appears /// Whether or not this dialog is skippable /// File path name of the spritefont to use for the text /// File path name of the sprite font to use for the label public Dialog(string label, string text, string iconTextureName, Corner position, float time, bool skip, string fontName, string labelFontName) { mLabel = label; mText = text; mFontName = fontName; mLabelFontName = labelFontName; mIconTextureName = iconTextureName; mSkipAllowed = skip; mPosition = position; if (time <= 0) { return; } Timed = true; TimeRemaining = time; } #endregion #region Initialization /// /// Initializes any needed objects upon creation /// /// The viewport in which this is displayed in public void Initialize(Viewport viewport) { int screenWidth = viewport.Width; int screenHeight = viewport.Height; switch (mPosition) { case Corner.BottomLeft: backgroundLocation = new Rectangle(0, (int)(screenHeight * 0.750f), screenWidth, (int)(screenHeight * 0.250f)); //nextArrowLocation = new Rectangle((int)(screenWidth * 0.950f), (int)(screenHeight * 0.950f), (int)(screenWidth * 0.030f), (int)(screenHeight * 0.030f)); Flip = SpriteEffects.FlipHorizontally; IconLocation = new Rectangle(0, (int)(screenHeight * 0.750f), (int)(screenWidth * 0.250f), (int)(screenHeight * 0.330f)); LabelLocation = new Vector2(screenWidth * 0.250f, screenHeight * 0.800f); nextStringLocation = new Vector2(screenWidth * 0.255f, screenHeight * 0.985f); break; case Corner.BottomRight: backgroundLocation = new Rectangle(0, (int)(screenHeight * 0.750f), screenWidth, (int)(screenHeight * 0.250f)); //nextArrowLocation = new Rectangle((int)(screenWidth * 0.720f), (int)(screenHeight * 0.950f), (int)(screenWidth * 0.030f), (int)(screenHeight * 0.030f)); Flip = SpriteEffects.None; IconLocation = new Rectangle((int)(screenWidth * 0.800f), (int)(screenHeight * 0.750f), (int)(screenWidth * 0.250f), (int)(screenHeight * 0.330f)); LabelLocation = new Vector2(screenWidth * 0.025f, screenHeight * 0.800f); nextStringLocation = new Vector2(screenWidth * 0.030f, screenHeight * 0.985f); break; case Corner.TopLeft: backgroundLocation = new Rectangle(0, 0, screenWidth, (int)(screenHeight * 0.250f)); //nextArrowLocation = new Rectangle((int)(screenWidth * 0.950f), (int)(screenHeight * 0.200f), (int)(screenWidth * 0.030f), (int)(screenHeight * 0.030f)); Flip = SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically; IconLocation = new Rectangle(0, 0, (int)(screenWidth * 0.250f), (int)(screenHeight * 0.330f)); LabelLocation = new Vector2(screenWidth * 0.250f, screenHeight * 0.050f); nextStringLocation = new Vector2(screenWidth * 0.255f, screenHeight * 0.235f); break; case Corner.TopRight: backgroundLocation = new Rectangle(0, 0, screenWidth, (int)(screenHeight * 0.250f)); //nextArrowLocation = new Rectangle((int)(screenWidth * 0.720f), (int)(screenHeight * 0.200f), (int)(screenWidth * 0.030f), (int)(screenHeight * 0.030f)); Flip = SpriteEffects.FlipVertically; IconLocation = new Rectangle((int)(screenWidth * 0.800f), 0, (int)(screenWidth * 0.330f), (int)(screenHeight * 0.250f)); LabelLocation = new Vector2(screenWidth * 0.025f, screenHeight * 0.050f); nextStringLocation = new Vector2(screenWidth * 0.030f, screenHeight * 0.235f); break; } float maxTextLength = screenWidth * 0.7f; string[] data = mText.Split(new char[] { '\n' }, StringSplitOptions.None); List result = new List(); foreach (string s in data) { result.AddRange(TextUtility.BreakString(s, maxTextLength, Font)); } StringBuilder sb = new StringBuilder(); foreach (string s in result) { sb.AppendLine(s); } mText = sb.ToString(); TextLocation = LabelLocation; TextLocation.Y += screenHeight * 0.040f; nextStringLocation.Y -= Font.MeasureString(nextString).Y; LabelShadowLocation = LabelLocation + new Vector2(screenWidth * 0.003f, screenHeight * 0.003f); TextShadowLocation = TextLocation + new Vector2(screenWidth * 0.003f, screenHeight * 0.003f); nextStringShadowLocation = nextStringLocation + new Vector2(screenWidth * 0.003f, screenHeight * 0.003f); CurrentText = string.Empty; } #endregion #region Management /// /// Loads in all the information needed for content dependent objects /// /// The current content manager used for content loading public void LoadContent(ContentManager content) { backgroundTexture = content.Load(@"General\DialogBackground"); //nextArrowTexture = content.Load(@"General\NextArrow"); Font = content.Load(mFontName); LabelFont = content.Load(mLabelFontName); if (!string.IsNullOrEmpty(mIconTextureName)) { IconTexture = content.Load(mIconTextureName); } } /// /// Disposes of any unmanaged objects /// TODO: actually implement /// public void Dispose() { } #endregion #region Update /// /// Updates the dialog screen /// /// Time that has passed in seconds public void Update(float seconds) { if (Timed) { TimeRemaining -= seconds; } if (mSkipAllowed && CurrentLength >= mText.Length) { readyToSkip = true; } if (readyToSkip) { CurrentText = mText; } else { CurrentLength += seconds * mScrollRate; CurrentText = mText.Substring(0, Math.Min((int)CurrentLength, mText.Length)); } } #endregion #region Render /// /// Renders the dialog screen /// public void Draw(SpriteBatch sb) { sb.Begin(SpriteBlendMode.AlphaBlend); sb.Draw(backgroundTexture, backgroundLocation, Tint); if (IconTexture != null) { sb.Draw(IconTexture, IconLocation, null, Tint, 0f, Vector2.Zero, Flip, 0f); } sb.DrawString(LabelFont, mLabel + ":", LabelShadowLocation, ShadowColor); sb.DrawString(Font, CurrentText, TextShadowLocation, ShadowColor); sb.DrawString(LabelFont, mLabel + ":", LabelLocation, Tint); sb.DrawString(Font, CurrentText, TextLocation, Tint); if (readyToSkip) { //sb.Draw(nextArrowTexture, nextArrowLocation, Tint); sb.DrawString(Font, nextString, nextStringShadowLocation, ShadowColor); sb.DrawString(Font, nextString, nextStringLocation, Tint); } sb.End(); } #endregion } /// /// A screen for displaying a series of dialogs. /// public sealed class DialogScreen : GameScreen { #region Fields // Whether or not this dialog screen is skippable private readonly bool mSkippable; // The array of dialogs private readonly Dialog[] dialogs; // The current index in dialogs private int index; // The sprite batch to use for rendering private SpriteBatch sb; // The input handler for handling all the input for the dialog screen private InputHandler inputHandler; #endregion #region Creation /// /// Create a new dialog screen, giving an ordered list of dialogs to show. /// /// List of dialogs to display public DialogScreen(List dialogsToShow) { mSkippable = true; dialogs = dialogsToShow.ToArray(); } /// /// Create a new dialog screen, giving an ordered array of dialogs to show. /// /// Array of dialogs to display public DialogScreen(Dialog[] dialogsToShow) { mSkippable = true; dialogs = dialogsToShow; } /// /// Create a new dialog screen, giving an ordered list of dialogs to show, and specifying whether skipping is allowed. /// /// List of dialogs to display /// Whether or not this screen is skippable public DialogScreen(List dialogsToShow, bool skip) { mSkippable = skip; dialogs = dialogsToShow.ToArray(); } /// /// Create a new dialog screen, giving an ordered arrow of dialogs to show, and specifying whether skipping is allowed. /// /// Array of dialogs to display /// Whether or not this screen is skippable public DialogScreen(Dialog[] dialogsToShow, bool skip) { mSkippable = skip; dialogs = dialogsToShow; } #endregion #region Initialization /// /// Initialize. /// public override void Initialize() { base.Initialize(); foreach (Dialog d in dialogs) { d.Initialize(GraphicsDevice.Viewport); } inputHandler = GetInputHandler(); inputHandler.Initialize(); inputHandler.BindButton(Keys.Space, Trigger.Activated, 500); inputHandler.BindButton(Keys.Enter, Trigger.Activated, 500); if (mSkippable) { inputHandler.BindButton(Keys.Escape, Trigger.Activated, 600); } } /// /// The dialog screen shouldn't need to be reinitialized, it should always be on top. /// public override void Reinitialize() { } #endregion #region Management /// /// Loads in all the necessary information for content dependent objects /// protected override void LoadContent() { base.LoadContent(); sb = new SpriteBatch(GraphicsDevice); foreach (Dialog d in dialogs) { d.LoadContent(Content); } } /// /// Dispose this dialog screen, and all dialogs. /// protected override void Dispose(bool disposing) { if (disposing) { foreach (Dialog d in dialogs) { d.Dispose(); } sb.Dispose(); } base.Dispose(disposing); } /// /// Remove this dialog screen. /// public void Clear() { Done(); } #endregion #region Update /// /// Updates the dialog screen /// public override void Update(GameTime gameTime) { if (index == dialogs.Length) { Done(); return; } dialogs[index].Update((float)gameTime.ElapsedGameTime.TotalSeconds); if (dialogs[index].Timed && dialogs[index].TimeRemaining < 0) { ++index; if (index == dialogs.Length) { Done(); return; } } while (inputHandler.HasEvent()) { InputEvent e = inputHandler.GetNextEvent(); switch (e.EventCode) { case 500: if (dialogs[index].mSkipAllowed) { if (dialogs[index].readyToSkip) { ++index; if (index == dialogs.Length) { Done(); return; } } else { dialogs[index].readyToSkip = true; } } break; case 600: Done(); break; } } base.Update(gameTime); } #endregion #region Render /// /// Renders the dialog screen /// public override void Draw(GameTime gameTime) { dialogs[index].Draw(sb); base.Draw(gameTime); } #endregion } /// /// A dialog component is similar to a dialog screen, except that it is only a component within another screen, /// and not an overlay. /// public sealed class DialogComponent : DrawableGameScreenComponent { #region Fields // Array of dialogs private readonly Dialog[] dialogs; // Current index in the dialogs private int index; // The current sprite batch to use for rendering private SpriteBatch sb; // The current content manager for loading content private readonly ContentManager mContent; #endregion #region Creation /// /// Create a new dialog component giving an ordered list of dialogs to show. /// /// List of dialogs to be displayed /// Current content manager used for loading content public DialogComponent(List dialogsToShow, ContentManager content) { mContent = content; dialogs = dialogsToShow.ToArray(); } /// /// Create a new dialog component giving an ordered array of dialogs to show. /// /// Array of dialogs to be displayed /// Current content manager used for loading content public DialogComponent(Dialog[] dialogsToShow, ContentManager content) { mContent = content; dialogs = dialogsToShow; } #endregion #region Initialization /// /// Initializes objects needed at creation of class /// public override void Initialize() { base.Initialize(); foreach (Dialog d in dialogs) { d.Initialize(GraphicsDevice.Viewport); } } #endregion #region Management /// /// Loads all the needed information for content dependent objects /// protected override void LoadContent() { base.LoadContent(); sb = new SpriteBatch(GraphicsDevice); foreach (Dialog d in dialogs) { d.LoadContent(mContent); } } /// /// Disposes of unmanaged objects /// protected override void Dispose(bool disposing) { if (disposing) { foreach (Dialog d in dialogs) { d.Dispose(); } sb.Dispose(); } base.Dispose(disposing); } #endregion #region Update /// /// Updates the dialog screen /// public override void Update(GameTime gameTime) { if (index >= dialogs.Length) { Enabled = false; Visible = false; return; } dialogs[index].Update((float)gameTime.ElapsedGameTime.TotalSeconds); if (dialogs[index].Timed && dialogs[index].TimeRemaining < 0) { ++index; if (index >= dialogs.Length) { Enabled = false; Visible = false; return; } } base.Update(gameTime); } #endregion #region Render /// /// Renders the dialog screen /// public override void Draw(GameTime gameTime) { if (index >= dialogs.Length) { Enabled = false; Visible = false; return; } dialogs[index].Draw(sb); base.Draw(gameTime); } #endregion #region State Modifier /// /// Push to the next dialog immediately /// public void Next() { if (index >= dialogs.Length) { Enabled = false; Visible = false; return; } if (dialogs[index].readyToSkip) { ++index; if (index >= dialogs.Length) { Enabled = false; Visible = false; return; } } else { dialogs[index].readyToSkip = true; } } #endregion } }