/*
* Entity.cs
* Authors: August Zinsser, Brian Chesbrough
*
* Copyright Matthew Belmonte 2007
*/
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
namespace tAC_Engine
{
///
/// 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 : IComparable
{
protected Animation mSprite; // The sprite to use for this entity
protected SpriteEffects mSpriteEffects; // Allows for mirroring during rendering
protected Vector3 mSize; // Volume of the Entity
protected Vector3 mPos; // Center of the Entity
protected Vector3 mVel; // Velocity (can be ignored if the game updates position manually)
protected bool mVisible; // Display the entity or not
protected double mRot; // Rotation in radians
protected double mAngVel; // Angular velocity in radians
protected float mScale; // Blow up or shrink the entity
protected Color mTint; // Draw the entity with a color
protected Vector3 mTargetPos; // Target for linear interpolation (overrides velocity)
protected Vector3 mTargetSize; // ''
protected double mTargetRot; // ''
protected float mLerpCounter = 0; // > 0 when the entity is lerping to mTargetPos
protected float mFadeInLifeSpan = 0; // Used for fading in
protected float mFadeInCounter = 0; // Used for fading in
protected float mFadeOutLifeSpan = 0; // Used for fading out
protected float mFadeOutCounter = 0; // Used for fading out
///
/// 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; } }
public SpriteEffects SpriteEffects { set { mSpriteEffects = value; } get { return mSpriteEffects; } }
public virtual float Width { set { mSize.X = value; } get { return mSize.X; } }
public virtual float Height { set { mSize.Y = value; } get { return mSize.Y; } }
///
/// Z-Dimension of size, not layer depth
///
public virtual float Depth { set { mSize.Z = value; } get { return mSize.Z; } }
public float X { set { mPos.X = value; } get { return mPos.X; } }
public float Y { set { mPos.Y = value; } get { return mPos.Y; } }
///
/// Translates to layer depth
///
public float Z { set { mPos.Z = value; } get { return mPos.Z; } }
public float dX { set { mVel.X = value; } get { return mVel.X; } }
public float dY { set { mVel.Y = value; } get { return mVel.Y; } }
public float dZ { set { mVel.Z = value; } get { return mVel.Z; } }
public bool Visible { set { mVisible = value; } get { return mVisible; } }
///
/// True when the entity is linearly interpolating to another transform. Velocity is ignored while lerping.
///
public bool IsLerping { get { return mLerpCounter > 0; } }
///
/// If Lerping, where the entity is going to. Otherwise, where the entity is now.
///
public Vector3 TargetPosition { get { return mLerpCounter > 0 ? mTargetPos : mPos; } }
public virtual Vector3 Size { set { mSize = value; } get { return mSize; } }
public Vector3 Position { set { mPos = value; } get { return mPos; } }
public Vector3 Velocity { set { mVel = value; } get { return mVel; } }
public double Rotation { set { mRot = value; } get { return mRot; } }
public double AngularVelocity { set { mAngVel = value; } get { return mAngVel; } }
public float Scale { set { mScale = value; } get { return mScale; } }
public Color Tint
{
set
{
// Preserve old functionality - remove if unneed
mTint = new Color(value.R, value.G, value.B, mTint.A);
}
get { return mTint; }
}
public float Opacity { set { mTint = new Color(mTint.R, mTint.G, mTint.B, (byte)( value * 255)); } get { return mTint.A / 255.0f; } }
/* 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()
: this((Texture2D)null, 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(Texture2D texture, float boundingWidth, float boundingHeight)
: this(texture, Vector3.Zero, new Vector3(boundingWidth, boundingHeight, 0f))
{
}
///
/// Create a non-animated entity with a cubic bounding box
///
/// Sprite image
/// Center coordinates
/// Create a bounding cube with this length
public Entity(Texture2D texture, Vector3 position, Vector3 size)
{
// This is the "real" constructor for non-animated entities
// Create an animation with just one clip of one frame
float animWidth = texture == null ? size.X : texture.Width;
float animHeight = texture == null ? size.Y : texture.Height;
Animation newAnim = new Animation(new Vector2(animWidth, animHeight));
newAnim.AddClip("Default", texture, 1, 1f);
mSprite = newAnim;
mSprite.Play("Default");
mPos = position;
mSize = size;
mVisible = true;
mRot = 0;
mAngVel = 0;
mScale = 1;
mTint = Color.White;
}
///
/// Create a non-animated entity who's bounding box are the dimensions of the sprite image
///
/// Sprite image
public Entity(Texture2D texture)
: this(texture, texture != null? texture.Width : 0, texture != null? texture.Height : 0)
{
// This actually goes to the 2D non-animated version, not the 3D version
}
///
/// Create an animated entity with a cubic bounding box
///
/// Animated sprite images
/// Center coordinates
/// Create a bounding cube with this length
public Entity(Animation sprite, Vector3 position, Vector3 size)
: this((Texture2D)null, position, size)
{
// Overwrite the 1-frame animation that the non-animated constructor creates
mSprite = sprite;
}
///
/// Relays the play command to this entity's sprite
///
/// Name of the clip to play
public void Play(string clipName)
{
mSprite.Play(clipName);
}
///
/// Tests collision between 2 entities' bounding boxes in x and y dimensions
///
///
///
/// True if collision occurs, false otherwise
public static bool Collides2D(Entity entity1, Entity entity2)
{
return (entity1.X + entity1.Width / 2 >= entity2.X - entity2.Width / 2 &&
entity1.X - entity1.Width / 2 <= entity2.X + entity2.Width / 2 &&
entity1.Y + entity1.Height / 2 >= entity2.Y - entity2.Height / 2 &&
entity1.Y - entity1.Height / 2 <= entity2.Y + entity2.Height / 2);
}
///
/// Tests collision between 2 entities' boundning boxes in 3 dimensions
///
///
///
/// True if collision occurs, false otherwise
public static bool Collides3D(Entity entity1, Entity entity2)
{
return (entity1.X + entity1.Width / 2 >= entity2.X - entity2.Width / 2 &&
entity1.X - entity1.Width / 2 <= entity2.X + entity2.Width / 2 &&
entity1.Y + entity1.Height / 2 >= entity2.Y - entity2.Height / 2 &&
entity1.Y - entity1.Height / 2 <= entity2.Y + entity2.Height / 2 &&
entity1.Z + entity1.Depth / 2 >= entity2.Z - entity2.Depth / 2 &&
entity1.Z - entity1.Depth / 2 <= entity2.Z + entity2.Depth / 2);
}
///
/// Updates necessary logic
///
public virtual void Update()
{
float dt = GenericBaseApplication.GameManager.ElapsedSeconds;
// Update any animations
if (mSprite != null)
mSprite.Update();
// Update physics if not lerping
if (mLerpCounter <= 0)
{
mPos += Vector3.Multiply(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
double 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 < 0)
{
mPos = mTargetPos;
mSize = mTargetSize;
mRot = mTargetRot;
}
}
// Update fading
if (mFadeInCounter > 0)
{
mFadeInCounter -= dt;
Opacity = MathHelper.Clamp(1.0F - (mFadeInCounter / mFadeInLifeSpan), 0f, 1f);
}
else if (mFadeInCounter < 0)
{
mFadeInCounter = 0;
Opacity = 1.0F;
}
if (mFadeOutCounter > 0)
{
mFadeOutCounter -= dt;
Opacity = MathHelper.Clamp((mFadeOutCounter / mFadeOutLifeSpan), 0f, 1f);
}
else if (mFadeOutCounter < 0)
{
Opacity = 0;
mFadeOutCounter = 0;
mVisible = false;
}
}
///
/// 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
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)
///
public virtual void Draw(SpriteBatch spriteBatch, Rectangle destination, float layerDepth)
{
if (mVisible)
{
mSprite.Draw(
spriteBatch,
destination,
mTint,
(float)mRot,
mSpriteEffects,
layerDepth);
}
}
///
/// Used to sort Entities by their texture (the current sprite sheet in use by the Entity's Animation)
///
///
///
public int CompareTo(object obj)
{
Entity that = obj as Entity;
Texture2D thisTexture = this.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, double 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;
}
}
///
/// 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;
}
///
/// 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;
}
///
/// If the entity's position and size are in screen coordinates, this function checks
/// to see if the mouse is hovering over it.
///
/// True if the mouse is over this entity
public bool IsMouseOver()
{
MouseState mouse = Mouse.GetState();
return (mouse.X > mPos.X - mSize.X / 2 &&
mouse.X < mPos.X + mSize.X / 2 &&
mouse.Y > mPos.Y - mSize.Y / 2 &&
mouse.Y < mPos.Y + mSize.Y / 2);
}
///
/// Mouse Over function calculated in screen space rather than world space
/// Function also makes requires the mouse to be closer to the center
/// since the Hex tiles in HH aren't squares
///
/// x off set of screen
/// Y off set of screen
/// True if the mouse is over the center of this entity
public bool IsMouseOver(float xOff, float yOff)
{
MouseState mouse = Mouse.GetState();
return (mouse.X > (mPos.X - xOff) - mSize.X / 3 &&
mouse.X < (mPos.X - xOff) + mSize.X / 3 &&
mouse.Y > (mPos.Y - yOff) - mSize.Y / 3 &&
mouse.Y < (mPos.Y - yOff) + mSize.Y / 3);
}
}
}