/* * Entity.cs * Authors: August Zinsser, Brian Chesbrough * 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 { /// /// Serves as the base class for almost every object or character in a game. /// It has many attributes that game objects are likely to have, as well as some standard behaviors /// like fading in and out. /// For 2D entities, just ignore the z position of any applicable variables or functions. /// Entities can also be sorted by texture for effecient sprite batching. /// public class Entity : Collidable, IComparable { #region Fields /// /// Checks if the entity is alive or not /// protected bool mAlive = true; /// /// The path name of the texture file /// protected string mTexturePath; /// /// The sprite to use for this entity /// protected Animation mSprite; /// /// Allows for mirroring during rendering /// protected SpriteEffects mSpriteEffects; /// /// Velocity (can be ignored if the game updates position manually) /// protected Vector3 mVel; /// /// Display the entity or not /// protected bool mVisible = true; /// /// Rotation in radians /// protected float mRot; /// /// Angular velocity in radians /// protected float mAngVel; /// /// Blow up or shrink the entity /// protected float mScale = 1; /// /// Draw the entity with a color /// protected Color mTint = Color.White; /// /// Target for linear interpolation (overrides velocity) /// protected Vector3 mTargetPos; /// /// The size of the entity /// protected Vector3 mTargetSize; /// /// The degree of rotation in radians /// protected float mTargetRot; /// /// > 0 when the entity is lerping to mTargetPos /// protected float mLerpCounter; /// /// How long the fade in effect lasts /// protected float mFadeInLifeSpan; /// /// The counter for the fade in effect /// protected float mFadeInCounter; /// /// How long the fade out effect lasts /// protected float mFadeOutLifeSpan; /// /// The counter for the fade out effect /// protected float mFadeOutCounter; /// /// Whether or not this is currently fading out /// protected bool mFadeIn; /// /// Whether or not this is currently fading out /// protected bool mFadeOut; #endregion #region Properties /// /// Gets the texture representing the entity /// CAUTION: This is fast for repeated calls to static image entities, but slow /// for animated entites and slow the first call to static iamge entities! /// public Texture2D Texture { get { return mSprite != null ? mSprite.CurrentFrameCopy : null; } } /// /// Gets/Sets the sprite effect to use when rendering /// public SpriteEffects SpriteEffects { get { return mSpriteEffects; } set { mSpriteEffects = value; } } /// /// Gets/Sets the x velocity value /// public float dX { get { return mVel.X; } set { mVel.X = value; } } /// /// Gets/Sets the y velocity value /// public float dY { get { return mVel.Y; } set { mVel.Y = value; } } /// /// Gets/Sets the z velocity value /// public float dZ { get { return mVel.Z; } set { mVel.Z = value; } } /// /// Gets/Sets whether or not this entity is visible /// public bool Visible { get { return mVisible; } set { mVisible = value; } } /// /// Gets whether the entity is linearly interpolating to another transform. Velocity is ignored while lerping. /// public bool IsLerping { get { return mLerpCounter > 0; } } /// /// Gets, if Lerping, where the entity is going to. Otherwise, where the entity is now. /// public Vector3 TargetPosition { get { return mLerpCounter > 0 ? mTargetPos : mPos; } } /// /// Gets/Sets the current velocity /// public Vector3 Velocity { get { return mVel; } set { mVel = value; } } /// /// Gets/Sets the current rotation /// public float Rotation { get { return mRot; } set { mRot = value; } } /// /// Gets/Sets the current angular rotation /// public float AngularVelocity { get { return mAngVel; } set { mAngVel = value; } } /// /// Gets/Sets the current size scale /// public float Scale { get { return mScale; } set { mScale = value; } } /// /// Gets/Sets the current color /// public Color Tint { get { return mTint; } set { // Preserve old functionality - remove if unneed mTint = new Color(value.R, value.G, value.B, mTint.A); } } /// /// Gets/Sets the current opacity /// public float Opacity { get { return mTint.A / 255.0f; } set { mTint = new Color(mTint.R, mTint.G, mTint.B, (byte)(value * 255)); } } #endregion #region Predicates /// /// Notifies when this entity is no longer considered alive /// public static readonly Predicate NotAlive = new Predicate(delegate(Entity entity) { return !entity.mAlive; }); #endregion #region Creation /* Constructors: * Provide a constructor for each dimension (2D / 3D) as well as animated and non-animated. * To overload a constructor, call up to the 3D version (sticking to animated/vs non). * For animated constructors, call down to the non-animated 3D constructor */ /// /// Create a placeholder entity /// public Entity() { mVisible = false; } /// /// Create a non-animated entity with a cubic bounding box /// /// Sprite image public Entity(string texturePath) : this(texturePath, Vector3.Zero, Vector3.Zero) { } /// /// Create a non-animated entity with a rectangular bounding box (with a depth of zero) /// /// Single image /// Width of the bounding box as well as the display rectangle /// Height of the bounding box as well as the display rectangle public Entity(string texturePath, float boundingWidth, float boundingHeight) : this(texturePath, Vector3.Zero, new Vector3(boundingWidth, boundingHeight, 0f)) { } /// /// Create a non-animated entity with a cubic bounding box. /// This is the "real" constructor for non-animated entities. /// /// Sprite image. /// Center coordinates. /// Create a bounding cube with this length. public Entity(string texturePath, Vector3 position, Vector3 size) { mPos = position; mSize = size; mTexturePath = texturePath; } #endregion #region Management /// /// Loads in all necessary information for all content dependent objects /// /// Reference to the content manager for loading public virtual void LoadContent(ContentManager content) { if (mTexturePath != null) { Texture2D texture = content.Load(mTexturePath); if (mSize.Equals(Vector3.Zero)) { mSize = new Vector3(texture.Width, texture.Height, 0f); } // Create an animation with just one clip of one frame Animation newAnim = new Animation(new Vector2(texture.Width, texture.Height)); newAnim.AddClip("Default", texture, 1, 1f); mSprite = newAnim; mSprite.Play("Default"); } } /// /// Unloads any needed information for content dependent /// TODO! /// public virtual void UnloadContent() { } #endregion #region Update /// /// Updates necessary logic /// /// Time passed in game public virtual void Update(GameTime gameTime) { float dt = (float)gameTime.ElapsedGameTime.TotalSeconds; // Update any animations if (mSprite != null) { mSprite.Update(gameTime); } // Update physics if not lerping if (mLerpCounter <= 0f) { mPos += mVel * dt; mRot += mAngVel * dt; } // Update lerp else { // Position float distanceRemaining = (float)Math.Sqrt(Math.Pow((mPos.X - mTargetPos.X), 2) + Math.Pow((mPos.Y - mTargetPos.Y), 2) + Math.Pow((mPos.Z - mTargetPos.Z), 2)); float speed = distanceRemaining / mLerpCounter; Vector3 direction = mTargetPos - mPos; direction.Normalize(); mPos += Vector3.Multiply(direction, speed * dt); // Size Vector3 scaleStep = (mTargetSize - mSize) / mLerpCounter; mSize += Vector3.Multiply(scaleStep, dt); // Rotation float rotationRemaining = mTargetRot - mRot; float angularVelocity = (float)rotationRemaining / mLerpCounter; mRot += angularVelocity * dt; mLerpCounter -= dt; // Ensure that the entity makes it exactly to its target if (mLerpCounter < 0f) { mPos = mTargetPos; mSize = mTargetSize; mRot = mTargetRot; } } // Update fading if (mFadeIn) { if (mFadeInCounter > 0f) { mFadeInCounter -= dt; Opacity = 1.0f - (mFadeInCounter / mFadeInLifeSpan); } else { Opacity = 1.0f; mFadeIn = false; } } if (mFadeOut) { if (mFadeOutCounter > 0f) { mFadeOutCounter -= dt; Opacity = mFadeOutCounter / mFadeOutLifeSpan; } else { Opacity = 0f; mVisible = false; mFadeOut = false; } } } #endregion #region Render /// /// Draw the entity based on its Position and Size properties /// /// Sprite Batch to draw to public virtual void Draw(SpriteBatch spriteBatch) { Draw(spriteBatch, new Rectangle((int)(mPos.X), (int)(mPos.Y), (int)mSize.X, (int)mSize.Y), MathHelper.Clamp(mPos.Z, 0f, 1f)); } /// /// Draw the entity based on its Position and Size properties with an offset /// /// Sprite Batch to draw to /// Position offset public virtual void Draw(SpriteBatch spriteBatch, Vector3 offset) { Draw(spriteBatch, new Rectangle((int)(mPos.X + offset.X), (int)(mPos.Y + offset.Y), (int)mSize.X, (int)mSize.Y), MathHelper.Clamp(mPos.Z + offset.Z, 0f, 1f)); } /// /// Draw the entity at the specified location (ignores internal position and size) /// /// the sprite batch to draw to /// The position and dimension of the entity /// The layer of which to render on for z ordering public virtual void Draw(SpriteBatch spriteBatch, Rectangle destination, float layerDepth) { if (mVisible) { mSprite.Draw(spriteBatch, destination, mTint, (float)mRot, mSpriteEffects, layerDepth); } } #endregion #region Util /// /// Used to sort Entities by their texture (the current sprite sheet in use by the Entity's Animation) /// /// /// 0 for if they are the same, otherwise 1 or -1 for whichever is "greater" public int CompareTo(object obj) { Entity that = obj as Entity; if (mSprite == null || that == null || that.mSprite == null) { return 0; } Texture2D thisTexture = mSprite.CurrentSpriteSheet; Texture2D thatTexture = that.mSprite.CurrentSpriteSheet; if (thisTexture == null || thatTexture == null) { return 0; } int thisHash = thisTexture.GetHashCode(); int thatHash = thatTexture.GetHashCode(); return thisHash.CompareTo(thatHash); } /// /// Lineraly interpolates from current position, size, and rotation to the targets over the period specified /// /// Final Position /// Final Size /// Final Rotation /// Seconds before arriving at destination public void Lerp(Vector3 targetPosition, Vector3 targetSize, float targetRotation, float duration) { mLerpCounter = duration; mTargetPos = targetPosition; mTargetSize = targetSize; mTargetRot = targetRotation; // See if this call was used to stop previous Lerping if (mLerpCounter == 0) { mPos = mTargetPos; mSize = mTargetSize; mRot = mTargetRot; } } /// /// Relays the play command to this entity's sprite /// /// Name of the clip to play public void Play(string clipName) { mSprite.Play(clipName); } #endregion #region Fade /// /// Causes the entity to go from completely transparent to completely opaque over the specified period /// /// Time to go from 0 to 1 opacity public void FadeIn(float seconds) { mVisible = true; Opacity = 0f; mFadeInCounter = seconds; mFadeInLifeSpan = seconds; mFadeOutCounter = 0; mFadeIn = true; } /// /// Causes the entity to go from completely opaque completely transparent over the specified period /// /// Time to go from 1 to 0 opacity public void FadeOut(float seconds) { Opacity = 1f; mFadeOutCounter = seconds; mFadeOutLifeSpan = seconds; mFadeInCounter = 0; mFadeOut = true; } #endregion } }