/* * Widget.cs * Authors: August Zinsser, 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 Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; namespace MaritimeDefender { /// /// The basic widget for use in the HUD layer /// public abstract class Widget { #region Fields /// /// Only call Draw() on visible widgets /// protected internal bool mVisible = true; /// /// In screen coordinates /// protected Vector3 mPosition = Vector3.Zero; /// /// In screen coordinates /// protected Vector3 mSize = Vector3.Zero; /// /// If false, then height is based on the width to maintain ratio of source size. /// Can be ignored if the widget implements its own Draw function. /// protected bool mHeightBasedOnScreen = true; /// /// If false, then width is based on the height to maintain source ratio. At least 1 /// of width/heightbased must be true. Can be ignored if the widget implements its own Draw function. /// protected bool mWidthBasedOnScreen = true; /// /// Ignored if HeightBasedOnScreen is false. Can be ignored if the widget implements its own Draw function. /// protected float mHeightPercentage; /// /// Ignored if WidthBasedOnScreen is false. Can be ignored if the widget implements its own Draw function. /// protected float mWidthPercentage; #endregion #region Properties /// /// Gets/Sets the x position of the widget /// public float X { get { return mPosition.X; } set { mPosition = new Vector3(value, mPosition.Y, mPosition.Z); } } /// /// Gets/Sets the y position of the widget /// public float Y { get { return mPosition.Y; } set { mPosition = new Vector3(mPosition.X, value, mPosition.Z); } } /// /// Gets/Sets the width of the widget /// public float Width { get { return mSize.X; } set { mSize = new Vector3(value, mSize.Y, mSize.Z); } } /// /// Gets/Sets the height of the widget /// public float Height { get { return mSize.Y; } set { mSize = new Vector3(mSize.X, value, mSize.Z); } } /// /// Gets/Sets whether or not this widget is visible /// public bool Visible { get { return mVisible; } set { mVisible = value; } } #endregion #region Event Functions // Event functions. Derived classes may choose ignore them. /// /// Updates the Widget. Must be overwritten to actually do anything /// /// Time passed in game so far public virtual void Update(GameTime gameTime) { } /// /// Changes the resolution of the screen. Must be overwritten to actually do anything /// public virtual void ChangeResolution() { } #endregion } /// /// A generic 2D widget /// public abstract class Widget2D : Widget { #region Properties /// /// Gets/Sets the z depth for draw order /// public float ZDepth { get { return mPosition.Z; } set { mPosition.Z = value; } } #endregion #region Render /// /// Renders the 2D widget. Must be overwritten /// /// Reference to the current sprite batch for rendering public abstract void Draw(SpriteBatch spriteBatch); #endregion } /// /// A standard rectangular health bar /// public class HealthBar2D : Widget2D { #region Enums /// /// Defines the direction that the health bar increases/decreases in /// public enum Direction { /// /// Defines decreases in the health bar to the right and increases in the health bar to the left /// Left, /// /// Defines decreases in the health bar to the left and increases in the health bar to the right /// Right }; #endregion #region Fields /// /// The direction for the health bar to increase and decrease in. /// protected Direction mDirection = Direction.Left; /// /// The top line of the health bar /// protected StaticSprite mTopLine; /// /// The bottom line of the health bar /// protected StaticSprite mBottomLine; /// /// The left line of the health bar /// protected StaticSprite mLeftLine; /// /// The right line of the health bar /// protected StaticSprite mRightLine; /// /// The sprite used to measure the amount of life left in the health bar /// protected StaticSprite mFilling; /// /// The sprite representing the background of the health bar /// protected StaticSprite mBackground; /// /// The amount of time the health bar is displayed for after becoming visible. /// protected float mLife = 1f; /// /// The color of the lines on the border of the health bar /// protected Color mBorderColor = new Color(0, 0, 0, 0); /// /// The color of the background of the health bar /// protected Color mBackgroundColor = new Color(0, 0, 0, 255); /// /// The color of the health bar filling for when it's almost full /// protected Color mHealthyColor = new Color(0, 255, 0); /// /// The color of the health bar filling for when it's almost empty /// protected Color mDeathlyColor = new Color(255, 0, 0); /// /// The opacity of the health bar. 1 represents fully opaque. /// protected float mOpacity = 1f; #endregion #region Properties /// /// Gets/Sets the width/height in screen coordinates (ignores the z value) /// public Color BorderColor { get { return mBorderColor; } set { mBorderColor = value; } } /// /// Gets/Sets the color of the health bar for when the bar is mostly full /// public Color HealthyColor { get { return mHealthyColor; } set { mHealthyColor = value; } } /// /// Gets/Sets the color of the health bar for when the bar is mostly full /// public Color DeathlyColor { get { return mDeathlyColor; } set { mDeathlyColor = value; } } /// /// Gets/Sets the background color of the health bar /// public Color BackgroundColor { get { return mBackgroundColor; } set { mBackgroundColor = value; } } /// /// Gets/Sets the percentage of life to display (0 to 1) /// public float Life { get { return mLife; } set { mLife = value; } } /// /// Gets/Sets the opacity of the health bar /// public float Opacity { get { return mOpacity; } set { mOpacity = value; } } /// /// Gets/Sets which way the bar moves as it loses Life /// public Direction FluxDirection { get { return mDirection; } set { mDirection = value; } } #endregion #region Creation /// /// Create a new health bar /// /// The position and dimensions of the health bar public HealthBar2D(Rectangle location) : this(location.X, location.Y, location.Width, location.Height) { } /// /// Create a new health bar /// /// The position of the health bar /// The dimensions of the health bar public HealthBar2D(Vector2 position, Vector2 size) : this((int)position.X, (int)position.Y, (int)size.X, (int)size.Y) { } /// /// Create a new health bar /// /// The x position of the health bar /// The y position of the health bar /// The width of the health bar /// the height of the health bar public HealthBar2D(int x, int y, int width, int height) { mPosition.X = x; mPosition.Y = y; mPosition.Z = .000001f; mSize.X = width; mSize.Y = height; // TODO - you don't need 6 sprites mTopLine = new StaticSprite(Rectangle.Empty, 0, @"General\White"); mBottomLine = new StaticSprite(Rectangle.Empty, 0, @"General\White"); mLeftLine = new StaticSprite(Rectangle.Empty, 0, @"General\White"); mRightLine = new StaticSprite(Rectangle.Empty, 0, @"General\White"); mFilling = new StaticSprite(Rectangle.Empty, 0, @"General\White"); mBackground = new StaticSprite(Rectangle.Empty, 0, @"General\White"); } #endregion #region Management /// /// Loads in any the necessary information for all content dependent objects /// /// Reference to the current content manager for loading content public void LoadContent(ContentManager Content) { mTopLine.LoadContent(Content); mBottomLine.LoadContent(Content); mLeftLine.LoadContent(Content); mRightLine.LoadContent(Content); mFilling.LoadContent(Content); mBackground.LoadContent(Content); } #endregion #region Update /// /// Recolor, resize and rescale elements based on the internal variables /// private void UpdatePositionAndColor() { Rectangle location = new Rectangle((int)mPosition.X, (int)mPosition.Y, (int)mSize.X, (int)mSize.Y); mTopLine.Location = new Rectangle(location.X, location.Y - (location.Height >> 1) + 1, location.Width, 1); mTopLine.ZDepth = mPosition.Z; mTopLine.Tint = new Color(mBorderColor.R, mBorderColor.G, mBorderColor.B, (byte)(mBorderColor.A * mOpacity)); mBottomLine.Location = new Rectangle(location.X, mTopLine.Location.Y + location.Height - 1, location.Width, 1); mBottomLine.ZDepth = mPosition.Z; mBottomLine.Tint = mTopLine.Tint; mLeftLine.Location = new Rectangle(location.X - (location.Width >> 1) + 1, location.Y, 1, location.Height); mLeftLine.ZDepth = mPosition.Z; mLeftLine.Tint = mTopLine.Tint; mRightLine.Location = new Rectangle(mLeftLine.Location.X + location.Width - 1, location.Y, 1, location.Height); mRightLine.ZDepth = mPosition.Z; mRightLine.Tint = mTopLine.Tint; mFilling.Location = location; mFilling.ZDepth = mPosition.Z + 0.0000001f; mFilling.Tint = new Color((Vector3.Multiply(mHealthyColor.ToVector3(), mLife)) + (Vector3.Multiply(mDeathlyColor.ToVector3(), (1 - mLife)))); mFilling.Tint = new Color(mFilling.Tint.R, mFilling.Tint.G, mFilling.Tint.B, (byte)(mFilling.Tint.A * mOpacity)); mBackground.Location = location; mBackground.ZDepth = mPosition.Z + 0.0000002f; mBackground.Tint = new Color(mBackgroundColor.R, mBackgroundColor.G, mBackgroundColor.B, (byte)(mBackgroundColor.A * mOpacity)); if (mDirection == Direction.Left) { mFilling.Location.Width = (int)(Math.Max(mLife * location.Width, 0)); mFilling.Location.X = location.X - (int)((1 - mLife) * location.Width) / 2; } else if (mDirection == Direction.Right) { mFilling.Location.Width = (int)(Math.Max(mLife * location.Width, 0)); mFilling.Location.X = location.X + (int)((1 - mLife) * location.Width) / 2; } } #endregion #region Render /// /// Draw to the given spritebatch /// /// Reference to the current sprite batch for rendering public override void Draw(SpriteBatch spriteBatch) { // Render the elements to the right place/size/color/etc. UpdatePositionAndColor(); // Now display them // TODO (awz): As of this comment, the RenderState is set to DepthBufferTest = true, but DepthBufferWrite = false forcing me to // render the items back to front. In the future, step through the HUD render code and try to render as much front to back as possible // to speed things up. Or at least set both DepthBufferTest/Write to false to elminate the checks. mBackground.Draw(spriteBatch); mFilling.Draw(spriteBatch); mTopLine.Draw(spriteBatch); mBottomLine.Draw(spriteBatch); mLeftLine.Draw(spriteBatch); mRightLine.Draw(spriteBatch); } #endregion } /// /// A 3-tiered Health bar. Useful for boss fights. /// public class BossHealthBar2D : Widget2D { #region Fields // The path name of the sprite font file private readonly string mFontName; /// /// The text lebel displayed in the health bar /// protected string mLabel; /// /// The sprite font used to render the text /// protected SpriteFont mFont; /// /// The position of the text label /// protected Vector2 mLabelPosition; /// /// The first health bar of the boss /// protected HealthBar2D mHealthBar1; /// /// The second health bar of the boss /// protected HealthBar2D mHealthBar2; /// /// The third health bar of the boss /// protected HealthBar2D mHealthBar3; /// /// The background texture of the health bar /// protected StaticSprite mBackgroundImage; #endregion #region Properties /// /// Sets the value of the first health bar's life /// public float Life1 { set { mHealthBar1.Life = value; } } /// /// Sets the value of the second health bar's life /// public float Life2 { set { mHealthBar2.Life = value; } } /// /// Sets the value of the third health bar's life /// public float Life3 { set { mHealthBar3.Life = value; } } #endregion #region Creation /// /// Create a new Boss Health Bar widget /// /// Text for the health bar's label /// The path name of the sprite font to use /// The position and dimensions of the health bar /// The path name of the health bar's background texture file /// Color for the first health bar part /// Color for the second health bar part /// Color for the third health bar part public BossHealthBar2D(string label, string fontName, Rectangle location, string backgroundImage, Color color1, Color color2, Color color3) { mLabel = label; mFontName = fontName; mBackgroundImage = new StaticSprite(location, .000001f, backgroundImage); int paddingDoubled = (int)(location.Height * .33f); int x = location.X; int y = location.Y; int w = location.Width - paddingDoubled; int h = location.Height - paddingDoubled; mHealthBar1 = new HealthBar2D(new Rectangle(x, y, w, h)); mHealthBar2 = new HealthBar2D(new Rectangle(x, y, w, h)); mHealthBar3 = new HealthBar2D(new Rectangle(x, y, w, h)); mHealthBar1.ZDepth = mBackgroundImage.ZDepth - .0000005f; mHealthBar2.ZDepth = mBackgroundImage.ZDepth - .0000004f; mHealthBar3.ZDepth = mBackgroundImage.ZDepth - .0000003f; mHealthBar1.BorderColor = Color.Black; mHealthBar2.BorderColor = Color.Black; mHealthBar3.BorderColor = Color.Black; mHealthBar1.BackgroundColor = new Color(0, 0, 0, 0); mHealthBar2.BackgroundColor = new Color(0, 0, 0, 0); mHealthBar3.BackgroundColor = new Color(0, 0, 0, 255); mHealthBar1.HealthyColor = color1; mHealthBar2.HealthyColor = color2; mHealthBar3.HealthyColor = color3; mHealthBar1.DeathlyColor = color1; mHealthBar2.DeathlyColor = color2; mHealthBar3.DeathlyColor = color3; mHealthBar1.FluxDirection = HealthBar2D.Direction.Right; mHealthBar2.FluxDirection = HealthBar2D.Direction.Right; mHealthBar3.FluxDirection = HealthBar2D.Direction.Right; } #endregion #region Management /// /// Loads in any necessary information for all content dependent objects /// /// Reference to the current content manager for loading content public void LoadContent(ContentManager pContent) { mBackgroundImage.LoadContent(pContent); mHealthBar1.LoadContent(pContent); mHealthBar2.LoadContent(pContent); mHealthBar3.LoadContent(pContent); mFont = pContent.Load(mFontName); // Make room for the label if necessary if (!string.IsNullOrEmpty(mLabel)) { Rectangle location = mBackgroundImage.Location; int paddingDoubled = (int)(location.Height * .33f); int x = location.X; int y = location.Y; int w = location.Width - paddingDoubled; Vector2 labelSize = mFont.MeasureString(mLabel); mLabelPosition = new Vector2(x - (location.Width * 0.5f) + (paddingDoubled * 0.5f), y - (labelSize.Y * 0.5f)); x += (int)((labelSize.X * 0.5f) + (paddingDoubled * 0.25f)); w -= (int)labelSize.X + paddingDoubled; mHealthBar1.X = x; mHealthBar2.X = x; mHealthBar3.X = x; mHealthBar1.Width = w; mHealthBar2.Width = w; mHealthBar3.Width = w; } } #endregion #region Render /// /// Draw the spritebatch /// /// Reference to the current sprite batch object public override void Draw(SpriteBatch spriteBatch) { mBackgroundImage.Draw(spriteBatch); mHealthBar1.Draw(spriteBatch); mHealthBar2.Draw(spriteBatch); mHealthBar3.Draw(spriteBatch); spriteBatch.DrawString(mFont, mLabel, mLabelPosition, Color.White); } #endregion } }