/*
* 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
}
}