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