/* * HeadsUpDisplay.cs * Authors: August Zinsser, Sriharsha Matta * * Copyright Matthew Belmonte 2007 */ using System; using System.Collections.Generic; using System.Text; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Pina3D; using Astropolis; namespace tAC_Engine { /// /// A very simple class for drawing a static image, designed to be used by HUD elements. Game objects should use Entity instead of this struct. /// public class StaticSprite { public Rectangle Location; public float ZDepth; public Texture2D Texture; public Color Tint; /// /// Initializer /// /// /// /// public StaticSprite(Rectangle location, float zDepth, Texture2D texture) { Location = location; ZDepth = zDepth; Texture = texture; Tint = Color.White; } /// /// Draw to the given spritebatch /// /// public void Draw(SpriteBatch spriteBatch) { spriteBatch.Draw( Texture, Location, null, Tint, 0f, new Vector2((float)Texture.Width / 2f, (float)Texture.Height / 2f), SpriteEffects.None, ZDepth); } } /// /// Holds any combination of Texture2D's, ColladaModels, and Widgets and renders them to a spritebatch. /// Both the 2D and 3D layers' origins are in the upper left corner of the screen, with +x to the right /// and +y down as per standard screen coordinates. /// The z-depth of the Texture2D's and the Z axis of the 3D scene interprets +z as away from the viewer /// with z=0 lying on the plane of the physical display. /// There is also an optional transparency layer, as well as support for special effect maps on the 3D objects /// like normal, glow, reflection, etc. /// public static class HUD { static SpriteBatch mSpriteBatchAlpha; // Render to a self-contained spritebatch static SpriteBatch mSpriteBatchOpaque; // '' static Texture2D mMousePointer; // The image for the mouse pointer static bool mShowMousePointer = true; // If the mousepointer is visible or not, mostly set by games static bool mForceHideMousePointer = false; // Trumps the showMousePointer variable static List mSprites; // A list of sprites to draw static List mModels; // '' models static List mWidget2Ds; // '' widgets static List mWidget3Ds; // '' widgets static List mDialogs; // Dialogs to draw static List mDialogShadows; // Holds dialogs for an extra frame to prevent flicker when progressing to the next dialog static Texture2D mPauseMenuPlaceHolder; // The image for the pause menu placeholder window static bool mShowPauseMenu = false; // If the Pause Menu is visible or not static Button mResumeButton; //For the Pause Menu static Button mRestartButton; //For the Pause Menu static Button mMainMenuButton; //For the Pause Menu static Button mExitButton; //For the Pause Menu /// /// Whether or not to display the cursor. Is trumped by the ForceHideMousePointer property to allow cutscenes to hide the mouse pointer and then return it to its previous visibility. /// public static bool ShowMouseCursor { set { mShowMousePointer = value; } get { return mShowMousePointer; } } /// /// Trumps the ShowMousePointer property. Should only be used to hide the cursor and then return it to its previous visibility. /// public static bool ForceHideMousePointer { set { mForceHideMousePointer = value; } get { return mForceHideMousePointer; } } /// /// Whether or not to display the Pause Menu. /// public static bool ShowPauseMenu { set { mShowPauseMenu = value; } get { return mShowPauseMenu; } } /// /// Load content /// static HUD() { mSpriteBatchAlpha = new SpriteBatch(GenericBaseApplication.GameManager.GraphicsDevice); mSpriteBatchOpaque = new SpriteBatch(GenericBaseApplication.GameManager.GraphicsDevice); mMousePointer = TextureManager.Load(@"Content\General\StandardCursor"); mSprites = new List(); mModels = new List(); mWidget2Ds = new List(); mWidget3Ds = new List(); int screenWidth = GenericBaseApplication.GameManager.ScreenWidth; int screenHeight = GenericBaseApplication.GameManager.ScreenHeight; mDialogs = new List(); mDialogShadows = new List(); mPauseMenuPlaceHolder = TextureManager.Load(@"Content\General\DialogBackground"); mResumeButton = new Button( TextureManager.Load(@"Content\General\PauseResume"), TextureManager.Load(@"Content\General\PauseResume")); mRestartButton = new Button( TextureManager.Load(@"Content\General\PauseRestart"), TextureManager.Load(@"Content\General\PauseRestart")); mMainMenuButton = new Button( TextureManager.Load(@"Content\General\PauseMainMenu"), TextureManager.Load(@"Content\General\PauseMainMenu")); mExitButton = new Button( TextureManager.Load(@"Content\General\PauseQuit"), TextureManager.Load(@"Content\General\PauseQuit")); mResumeButton.Size = new Vector3(screenWidth * .22f, screenHeight * .09f, 0f); mRestartButton.Size = new Vector3(screenWidth * .22f, screenHeight * .09f, 0f); mMainMenuButton.Size = new Vector3(screenWidth * .22f, screenHeight * .09f, 0f); mExitButton.Size = new Vector3(screenWidth * .22f, screenHeight * .09f, 0f); mResumeButton.Position = new Vector3(screenWidth * .5f, screenHeight * (1 / 4f), 0f); mRestartButton.Position = mResumeButton.Position + new Vector3(0f, screenHeight * (1 / 8f), 0f); mMainMenuButton.Position = mRestartButton.Position + new Vector3(0f, screenHeight * (1 / 8f), 0f); mExitButton.Position = mMainMenuButton.Position + new Vector3(0f, screenHeight * (1 / 8f), 0f); } /// /// Adds a sprite to the HUD /// /// public static void Add(StaticSprite sprite) { mSprites.Add(sprite); } /// /// Adds a Model to the HUD /// /// public static void Add(ColladaFile model) { mModels.Add(model); } /// /// Adds a Widget to the HUD /// /// public static void Add(Widget2D widget) { mWidget2Ds.Add(widget); } /// /// Adds a widget to the HUD /// /// public static void Add(Widget3D widget) { mWidget3Ds.Add(widget); } /// /// Removes a sprite from the HUD /// /// public static void Remove(StaticSprite sprite) { mSprites.Remove(sprite); } /// /// Removes a Model from the HUD /// /// public static void Remove(ColladaFile model) { mModels.Remove(model); } /// /// Removes a Widget from the HUD /// /// public static void Remove(Widget2D widget) { mWidget2Ds.Remove(widget); } /// /// Removes a widget from the HUD /// /// public static void Remove(Widget3D widget) { mWidget3Ds.Remove(widget); } /// /// Clears all components from the HUD (except for the mouse) /// public static void Clear() { mSprites.Clear(); mModels.Clear(); mWidget2Ds.Clear(); mWidget3Ds.Clear(); mDialogs.Clear(); mDialogShadows.Clear(); } /// /// Create a new dialog box /// /// Optional lable to display (usually the speaker's name) /// Optional text to display /// Optional animation to display /// Clip to play (from the optional animation) /// Optional sound to play /// Position of the dialog box /// Lifespan in seconds (enter 0 for untimed) /// Whether or not the dialog box can be skipped with Enter/Space/Esc public static void ShowDialog(string label, string text, Animation animation, string clip, string sound, Dialog.Position position, float time, bool skippable) { mDialogs.Add(new Dialog(label, text, animation, clip, sound, position, time, skippable)); } /// /// Create a new dialog box /// /// /// Optional lable to display (usually the speaker's name) /// Optional text to display /// Optional iamge to display /// Optional sound to play /// Position of the dialog box /// Lifespan in seconds (enter 0 for untimed) /// /// Whether or not the dialog box can be skipped with Enter/Space/Esc public static void ShowDialog(string label, string text, Texture2D icon, string sound, Dialog.Position position, float time, bool skippable) { Animation anim = null; if (icon != null) { anim = new Animation(new Vector2(icon.Width, icon.Height)); anim.AddClip("Default", icon, 1, 0f); } ShowDialog(label, text, anim, "Default", sound, position, time, skippable); } /// /// Clears all dialog boxes but leaves the rest of the HUD intact /// public static void ClearDialogs() { // Hold the dialogs for an extra frame foreach (Dialog dialog in mDialogs) { mDialogShadows.Add(dialog); } mDialogs.Clear(); } /// /// Updates all the widgets /// public static void Update() { for (int i = 0; i < mWidget2Ds.Count; i++) mWidget2Ds[i].Update(); for (int i = 0; i < mWidget3Ds.Count; i++) mWidget3Ds[i].Update(); for (int i = mDialogs.Count - 1; i >= 0; i--) { mDialogs[i].Update(); if (!mDialogs[i].Alive) { mDialogShadows.Add(mDialogs[i]); mDialogs.RemoveAt(i); } } //Update the Pause Menu buttons mResumeButton.Update(); mRestartButton.Update(); mMainMenuButton.Update(); mExitButton.Update(); if (mResumeButton.IsLeftClicked) { // Unpause and Resume the Game AstroBaseApplication.GameManager.PauseGames = false; Pina3D.Particles.Sparx.Paused = false; HUD.ShowMouseCursor = false; HUD.ShowPauseMenu = false; Logger.LogCode(Logger.ExperimentalCode.APP_UnPause); } if (mRestartButton.IsLeftClicked) { //Undo the changes made by the Pause function AstroBaseApplication.GameManager.PauseGames = false; Pina3D.Particles.Sparx.Paused = false; HUD.ShowPauseMenu = false; // Restart the Game AstroBaseApplication.SwitchMode(AstroBaseApplication.Modes.MeteorMadness); } if (mMainMenuButton.IsLeftClicked) { //Undo the changes made by the Pause function AstroBaseApplication.GameManager.PauseGames = false; Pina3D.Particles.Sparx.Paused = false; HUD.ShowPauseMenu = false; // Go Back to the Main Menu AstroBaseApplication.SwitchMode(AstroBaseApplication.Modes.ModeSelector); } if (mExitButton.IsLeftClicked) { //Undo the changes made by the Pause function AstroBaseApplication.GameManager.PauseGames = false; Pina3D.Particles.Sparx.Paused = false; HUD.ShowPauseMenu = false; // Completely Exit the Game AstroBaseApplication.SwitchMode(AstroBaseApplication.Modes.ShutDown); } } /// /// Draws all HUD compnents to the HUD's internal spritebatch /// /// public static void Draw() { mSpriteBatchAlpha.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.BackToFront, SaveStateMode.None); // TODO: render opaque sprites in a separate Draw that is called first. That call should set the stencil buffer // Render the 3D scene // TODO // Draw sprites to the spritebatch foreach (StaticSprite sprite in mSprites) mSpriteBatchAlpha.Draw( sprite.Texture, sprite.Location, null, Color.White, 1f, new Vector2((float)sprite.Location.Width / 2f, (float)sprite.Location.Height / 2f), SpriteEffects.None, sprite.ZDepth); // Draw 2D widgets foreach (Widget2D widget in mWidget2Ds) { if (widget.Visible) widget.Draw(mSpriteBatchAlpha); } mSpriteBatchAlpha.End(); // Draw the dialog boxes for (int i = 0; i < mDialogs.Count; i++) { mDialogs[i].Draw(); } // Draw the shadowed dialog boxes for (int i = 0; i < mDialogShadows.Count; i++) { mDialogShadows[i].Draw(); } mDialogShadows.Clear(); //Draw the Pause Menu if (mShowPauseMenu) { mSpriteBatchAlpha.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None); GenericBaseApplication.GameManager.GraphicsDevice.RenderState.DepthBufferEnable = false; GenericBaseApplication.GameManager.GraphicsDevice.RenderState.DepthBufferWriteEnable = false; mSpriteBatchAlpha.Draw( mPauseMenuPlaceHolder, new Rectangle((GenericBaseApplication.GameManager.ScreenWidth) * 1 / 4, (GenericBaseApplication.GameManager.ScreenHeight) * 1 / 8, GenericBaseApplication.GameManager.ScreenWidth / 2, GenericBaseApplication.GameManager.ScreenHeight * 3 / 4), Color.White); mSpriteBatchAlpha.End(); //Draw the Pause Menu Buttons SpriteBatch sb = new SpriteBatch(GenericBaseApplication.GameManager.GraphicsDevice); sb.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None); mResumeButton.Draw(sb); mRestartButton.Draw(sb); mMainMenuButton.Draw(sb); mExitButton.Draw(sb); sb.End(); } // Draw the cursor if (mShowMousePointer && !mForceHideMousePointer) { mSpriteBatchAlpha.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None); GenericBaseApplication.GameManager.GraphicsDevice.RenderState.DepthBufferEnable = false; GenericBaseApplication.GameManager.GraphicsDevice.RenderState.DepthBufferWriteEnable = false; mSpriteBatchAlpha.Draw( mMousePointer, new Rectangle(Mouse.GetState().X, Mouse.GetState().Y, mMousePointer.Width, mMousePointer.Height), Color.White); mSpriteBatchAlpha.End(); } } } /// /// Displays text, character icon, and/or plays sound. Useful for cutscenes. Assumes portraits are upright and facing to the left. /// public class Dialog { public enum Position { TopLeft, TopRight, BottomLeft, BottomRight }; public const string NEXT = "next"; protected static float mScrollRate = 100f; protected SpriteBatch mSpriteBatch; protected Entity mDialogBackground; protected Entity mNextArrow; protected Vector3 mNextArrowLocation; protected Position mPosition; protected string mLabel; protected string mText; protected Rectangle mLabelLocation; protected Rectangle mTextLocation; protected float mCharsToUse; protected int mShadowDistance; protected Animation mIcon; protected Rectangle mIconLocation; protected SpriteEffects mSpriteEffects; protected bool mSkippable; protected bool mReadyToSkip; protected float mLifeLeft; protected bool mUntimed; public bool Alive { get { return mLifeLeft > 0f; } } /// /// Create a new dialog box /// /// Optional label to display (usually the speaker's name) /// Optional text to display /// Optional animation to display /// Clip to play (from the optional animation) /// Optional sound to play /// Position of the dialog box /// Time to display the dialog box (0 for infinite time) public Dialog(string label, string text, Animation animation, string clip, string sound, Position position, float time, bool skippable) { int screenWidth = GenericBaseApplication.GameManager.ScreenWidth; int screenHeight = GenericBaseApplication.GameManager.ScreenHeight; mLabel = label; mText = text; mIcon = animation; mPosition = position; mCharsToUse = 0; mSkippable = skippable; mReadyToSkip = false; if (time == 0f) { mLifeLeft = 1f; mUntimed = true; } else { mLifeLeft = time; mUntimed = false; } if (mIcon != null) mIcon.Play(clip); // Text renderer does unknown things to the spritebatches, so render the icon and background to their own spritebatch mSpriteBatch = new SpriteBatch(GenericBaseApplication.GameManager.GraphicsDevice); mDialogBackground = new Entity( TextureManager.Load(@"Content\General\DialogBackground"), new Vector3(screenWidth / 2, screenHeight * .125f, .000002f), new Vector3(screenWidth, screenHeight * .25f, 0f)); mNextArrow = new Entity( TextureManager.Load(@"Content\General\NextArrow"), Vector3.Zero, new Vector3(screenWidth * .03f, screenWidth * .03f, 0f)); mShadowDistance = (int)(screenHeight * .003f); mTextLocation = GenericBaseApplication.GameManager.DialogTextFont.MeasureString(text); mLabelLocation = GenericBaseApplication.GameManager.DialogTextFont.MeasureString(label); float iconSize = screenWidth * .33f; if (position == Position.BottomLeft) { if (mIcon == null) mLabelLocation.X = (int)(screenWidth * .025f); else mLabelLocation.X = (int)(screenWidth * .25f); mLabelLocation.Y = (int)(screenHeight * .80f); mIconLocation = new Rectangle((int)(screenWidth * .10f), (int)(screenHeight * (.75f + .125f)), (int)iconSize, (int)iconSize); mNextArrowLocation = new Vector3(screenWidth * .97f, screenHeight * .97f, .000001f); mSpriteEffects = SpriteEffects.FlipHorizontally; } else if (position == Position.BottomRight) { mLabelLocation.X = (int)(screenWidth * .025f); mLabelLocation.Y = (int)(screenHeight * .80f); mIconLocation = new Rectangle((int)(screenWidth * .90f), (int)(screenHeight * (.75f + .125f)), (int)iconSize, (int)iconSize); mNextArrowLocation = new Vector3(screenWidth * .74f, screenHeight * .97f, .000001f); mSpriteEffects = SpriteEffects.None; } else if (position == Position.TopLeft) { if (mIcon == null) mLabelLocation.X = (int)(screenWidth * .025f); else mLabelLocation.X = (int)(screenWidth * .25f); mLabelLocation.Y = (int)(screenHeight * .05f); mIconLocation = new Rectangle((int)(screenWidth * .10f), (int)(screenHeight * .125f), (int)iconSize, (int)iconSize); mSpriteEffects = SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically; mNextArrowLocation = new Vector3(screenWidth * .97f, screenHeight * .21f, .000001f); } else if (position == Position.TopRight) { mLabelLocation.X = (int)(screenWidth * .025f); mLabelLocation.Y = (int)(screenHeight * .05f); mIconLocation = new Rectangle((int)(screenWidth * .90f), (int)(screenHeight * .125f), (int)iconSize, (int)iconSize); mSpriteEffects = SpriteEffects.FlipVertically; mNextArrowLocation = new Vector3(screenWidth * .74f, screenHeight * .21f, .000001f); } mTextLocation.X = mLabelLocation.X; mTextLocation.Y = mLabelLocation.Y + (int)(screenHeight * .04f); // Move the background to the appropriate position if (position == Position.BottomLeft || position == Position.BottomRight) { mDialogBackground.Y = (int)(screenHeight * (.75f + .125f)); } mDialogBackground.SpriteEffects = mSpriteEffects; } /// /// Update the dialog box /// public void Update() { if (!mUntimed) mLifeLeft -= GenericBaseApplication.GameManager.ElapsedSeconds; if (mSkippable && mCharsToUse >= mText.Length) mReadyToSkip = true; if (mSkippable && ( InputState.IsKeyDown(Keys.Enter) && InputState.WasKeyUp(Keys.Enter) || InputState.IsKeyDown(Keys.Escape) && InputState.WasKeyUp(Keys.Escape) || InputState.IsKeyDown(Keys.Space) && InputState.WasKeyUp(Keys.Space))) { if (!mReadyToSkip) mReadyToSkip = true; else { mLifeLeft = 0f; ScriptManager.SendEventCode(NEXT); } } if (mReadyToSkip) mCharsToUse = mText.Length; else mCharsToUse += GenericBaseApplication.GameManager.ElapsedSeconds * mScrollRate; if (mIcon != null) mIcon.Update(); } /// /// Draws the dialog box to the specified spritebatch /// /// public void Draw() { // TODO (awz): Render the dialogs blended over the rest of the game mSpriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None); GenericBaseApplication.GameManager.GraphicsDevice.RenderState.DepthBufferEnable = true; GenericBaseApplication.GameManager.GraphicsDevice.RenderState.DepthBufferWriteEnable = true; if (mIcon != null) mIcon.Draw(mSpriteBatch, mIconLocation, Color.White, 0f, mSpriteEffects, 0.0000015f); mDialogBackground.Draw(mSpriteBatch); if (mReadyToSkip) { mNextArrow.Position = mNextArrowLocation; mNextArrow.Draw(mSpriteBatch); } mSpriteBatch.End(); // Now render the text GenericBaseApplication.GameManager.DialogLabelFont.DrawString( new Vector2(mLabelLocation.X + mShadowDistance, mLabelLocation.Y + mShadowDistance), mLabel + ":", new Color(0, 0, 0, 140)); GenericBaseApplication.GameManager.DialogTextFont.DrawString( new Vector2(mTextLocation.X + mShadowDistance, mTextLocation.Y + mShadowDistance), mText.Substring(0, Math.Min((int)mCharsToUse, mText.Length)), new Color(0, 0, 0, 145)); GenericBaseApplication.GameManager.DialogLabelFont.DrawString( new Vector2(mLabelLocation.X, mLabelLocation.Y), mLabel + ":", Color.White); GenericBaseApplication.GameManager.DialogTextFont.DrawString( new Vector2(mTextLocation.X, mTextLocation.Y), mText.Substring(0, Math.Min((int)mCharsToUse, mText.Length)), Color.White); } } /// /// The basic widget for use in the HUD layer /// public abstract class Widget { protected bool mVisible = true; // Only call Draw() on visible widgets protected Vector3 mPosition = Vector3.Zero; // In screen coordinates protected Vector3 mSize = Vector3.Zero; // In screen coordinates protected bool mHeightBasedOnScreen = true; // 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 mWidthBasedOnScreen = 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 float mHeightPercentage; // Ignored if HeightBasedOnScreen is false. Can be ignored if the widget implements its own Draw function. protected float mWidthPercentage; // '' Width public virtual float X { set { mPosition = new Vector3(value, mPosition.Y, mPosition.Z); } get { return mPosition.X; } } public virtual float Y { set { mPosition = new Vector3(mPosition.X, value, mPosition.Z); } get { return mPosition.Y; } } public virtual float Width { set { mSize = new Vector3(value, mSize.Y, mSize.Z); } get { return mSize.X; } } public virtual float Height { set { mSize = new Vector3(mSize.X, value, mSize.Z); } get { return mSize.Y; } } public virtual bool Visible { set { mVisible = value; } get { return mVisible; } } // Event functions. Derived classes may choose ignore them. public abstract void Update(); public abstract void ChangeResolution(); public abstract void OnMouseOver(); public abstract void OnLeftClick(); public abstract void OnRightClick(); } /// /// A generic 2D widget /// public class Widget2D : Widget { protected Texture2D mTexture = null; // Assign this to a texture to have the HUD automatically draw this widget public virtual Vector2 Position { set { mPosition.X = value.X; mPosition.Y = value.Y; } get { return new Vector2(mPosition.X, mPosition.Y); } } public virtual Vector2 Size { set { mSize.X = value.X; mSize.Y = value.Y; } get { return new Vector2(mSize.X, mSize.Y); } } public virtual float ZDepth { set { mPosition.Z = value; } get { return mPosition.Z; } } public override void Update() { // Check for mouseover Vector2 location = InputState.MouseLocation; if (location.X > this.Position.X - this.Size.X && location.X < this.Position.X + this.Size.X && location.Y > this.Position.Y - this.Size.Y && location.Y < this.Position.Y + this.Size.Y) { OnMouseOver(); // Check for clicks if (InputState.IsMouseLeftUp && InputState.WasMouseLeftDown) { OnLeftClick(); } if (InputState.IsMouseRightUp && InputState.WasMouseRightDown) { OnRightClick(); } } } public override void ChangeResolution() { } // TODO: implement this (it's currently a stub) public override void OnMouseOver() { } public override void OnLeftClick() { } public override void OnRightClick() { } public virtual void Draw(SpriteBatch spriteBatch) { } } /// /// A generic 3D widget /// public class Widget3D : Widget { protected ColladaFile mModel = null; // Assign this to a model to have the HUD automatically draw this widget public virtual Vector3 Position { set { mPosition = value; } get { return mPosition; } } public virtual Vector3 Size { set { mSize = value; } get { return mSize; } } public virtual float Z { set { mPosition = new Vector3(mPosition.X, mPosition.Y, value); } get { return mPosition.Z; } } public virtual float Depth { set { mSize = new Vector3(mSize.X, mSize.Y, value); } get { return mSize.Z; } } // Stub event functions TODO: implement them public override void Update() { } public override void ChangeResolution() { } public override void OnMouseOver() { } public override void OnLeftClick() { } public override void OnRightClick() { } } /// /// A standard rectangular health bar /// public class HealthBar2D : Widget2D { public enum Direction { Left, Right }; protected Direction mDirection = Direction.Left; protected StaticSprite mTopLine; protected StaticSprite mBottomLine; protected StaticSprite mLeftLine; protected StaticSprite mRightLine; protected StaticSprite mFilling; protected StaticSprite mBackground; protected float mLife = 1f; protected Color mBorderColor = new Color(0, 0, 0, 0); protected Color mBackgroundColor = new Color(0, 0, 0, 255); protected Color mHealthyColor = new Color(0, 255, 0); protected Color mDeathlyColor = new Color(255, 0, 0); protected float mOpacity = 1f; /// /// Width/Height in screen coordinates (ignores the z value) /// public Color BorderColor { set { mBorderColor = value; } get { return mBorderColor; } } public Color HealthyColor { set { mHealthyColor = value; } get { return mHealthyColor; } } public Color DeathlyColor { set { mDeathlyColor = value; } get { return mDeathlyColor; } } public Color BackgroundColor { set { mBackgroundColor = value; } get { return mBackgroundColor; } } /// /// Percentage of life to display (0 to 1) /// public float Life { set { mLife = value; } get { return mLife; } } public float Opacity { set { mOpacity = value; } get { return mOpacity; } } /// /// Which way the bar moves as it loses Life /// public Direction FluxDirection { set { mDirection = value; } get { return mDirection; } } /// /// Create a new health bar /// public HealthBar2D(Rectangle location) { mPosition.X = location.X; mPosition.Y = location.Y; mPosition.Z = .000001f; mSize.X = location.Width; mSize.Y = location.Height; mTopLine = new StaticSprite( Rectangle.Empty, 0, TextureManager.Load(@"Content\General\White")); mBottomLine = new StaticSprite( Rectangle.Empty, 0, TextureManager.Load(@"Content\General\White")); mLeftLine = new StaticSprite( Rectangle.Empty, 0, TextureManager.Load(@"Content\General\White")); mRightLine = new StaticSprite( Rectangle.Empty, 0, TextureManager.Load(@"Content\General\White")); mFilling = new StaticSprite( Rectangle.Empty, 0, TextureManager.Load(@"Content\General\White")); mBackground = new StaticSprite( Rectangle.Empty, 0, TextureManager.Load(@"Content\General\White")); } /// /// Create a new health bar /// public HealthBar2D(Vector2 position, Vector2 size) : this(new Rectangle((int)position.X, (int)position.Y, (int)size.X, (int)size.Y)) { } /// /// Create a new health bar /// public HealthBar2D(int x, int y, int width, int height) : this(new Rectangle(x, y, width, height)) { } /// /// 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 / 2 + 1 - (location.Height % 2), 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 / 2 + 1 - (location.Width % 2), 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; } } /// /// Draw to the given spritebatch /// /// public override void Draw(SpriteBatch spriteBatch) { base.Draw(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); } } /// /// A 3-tiered Health bar. Useful for boss fights. /// public class BossHealthBar2D : Widget2D { protected string mLabel; protected Vector2 mLabelPosition; protected HealthBar2D mHealthBar1; protected HealthBar2D mHealthBar2; protected HealthBar2D mHealthBar3; protected StaticSprite mBackgroundImage; public float Life1 { set { mHealthBar1.Life = value; } } public float Life2 { set { mHealthBar2.Life = value; } } public float Life3 { set { mHealthBar3.Life = value; } } /// /// Create a new Boss Health Bar widget /// public BossHealthBar2D(string label, Rectangle location, Texture2D backgroundImage, Color color1, Color color2, Color color3) { mLabel = label; 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; // Make room for the label if necessary if (mLabel != "") { Rectangle labelSize = GenericBaseApplication.GameManager.StandardFontBig.MeasureString(mLabel); mLabelPosition = new Vector2(location.X - location.Width / 2 + paddingDoubled / 2, y + labelSize.Height / 2); x += labelSize.Width / 2 + paddingDoubled * 1 / 4; w -= labelSize.Width + 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; } /// /// Draw the spritebatch /// /// public override void Draw(SpriteBatch spriteBatch) { mBackgroundImage.Draw(spriteBatch); mHealthBar1.Draw(spriteBatch); mHealthBar2.Draw(spriteBatch); mHealthBar3.Draw(spriteBatch); // Draw the text in its own batch if (mLabel != "") { spriteBatch.End(); spriteBatch.Begin(); GenericBaseApplication.GameManager.StandardFontBig.DrawString(mLabelPosition, mLabel); spriteBatch.End(); spriteBatch.Begin(); } base.Draw(spriteBatch); } } }