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