123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461 |
- #region File Description
- //-----------------------------------------------------------------------------
- // Player.cs
- //
- // Microsoft XNA Community Game Platform
- // Copyright (C) Microsoft Corporation. All rights reserved.
- //-----------------------------------------------------------------------------
- #endregion
- using System;
- using Microsoft.Xna.Framework;
- using Microsoft.Xna.Framework.Audio;
- using Microsoft.Xna.Framework.Graphics;
- using Microsoft.Xna.Framework.Input;
- using Microsoft.Xna.Framework.Input.Touch;
- namespace Platformer
- {
- /// <summary>
- /// Our fearless adventurer!
- /// </summary>
- class Player
- {
- // Animations
- private Animation idleAnimation;
- private Animation runAnimation;
- private Animation jumpAnimation;
- private Animation celebrateAnimation;
- private Animation dieAnimation;
- private SpriteEffects flip = SpriteEffects.None;
- private AnimationPlayer sprite;
- // Sounds
- private SoundEffect killedSound;
- private SoundEffect jumpSound;
- private SoundEffect fallSound;
- public Level Level
- {
- get { return level; }
- }
- Level level;
- public bool IsAlive
- {
- get { return isAlive; }
- }
- bool isAlive;
- // Physics state
- public Vector2 Position
- {
- get { return position; }
- set { position = value; }
- }
- Vector2 position;
- private float previousBottom;
- public Vector2 Velocity
- {
- get { return velocity; }
- set { velocity = value; }
- }
- Vector2 velocity;
- // Constants for controling horizontal movement
- private const float MoveAcceleration = 13000.0f;
- private const float MaxMoveSpeed = 1750.0f;
- private const float GroundDragFactor = 0.48f;
- private const float AirDragFactor = 0.58f;
- // Constants for controlling vertical movement
- private const float MaxJumpTime = 0.35f;
- private const float JumpLaunchVelocity = -3500.0f;
- private const float GravityAcceleration = 3400.0f;
- private const float MaxFallSpeed = 550.0f;
- private const float JumpControlPower = 0.14f;
- // Input configuration
- private const float MoveStickScale = 1.0f;
- private const float AccelerometerScale = 1.5f;
- private const Buttons JumpButton = Buttons.A;
- /// <summary>
- /// Gets whether or not the player's feet are on the ground.
- /// </summary>
- public bool IsOnGround
- {
- get { return isOnGround; }
- }
- bool isOnGround;
- /// <summary>
- /// Current user movement input.
- /// </summary>
- private float movement;
- // Jumping state
- private bool isJumping;
- private bool wasJumping;
- private float jumpTime;
- private Rectangle localBounds;
- /// <summary>
- /// Gets a rectangle which bounds this player in world space.
- /// </summary>
- public Rectangle BoundingRectangle
- {
- get
- {
- int left = (int)Math.Round(Position.X - sprite.Origin.X) + localBounds.X;
- int top = (int)Math.Round(Position.Y - sprite.Origin.Y) + localBounds.Y;
- return new Rectangle(left, top, localBounds.Width, localBounds.Height);
- }
- }
- /// <summary>
- /// Constructors a new player.
- /// </summary>
- public Player(Level level, Vector2 position)
- {
- this.level = level;
- LoadContent();
- Reset(position);
- }
- /// <summary>
- /// Loads the player sprite sheet and sounds.
- /// </summary>
- public void LoadContent()
- {
- // Load animated textures.
- idleAnimation = new Animation(Level.Content.Load<Texture2D>("Sprites/Player/Idle"), 0.1f, true);
- runAnimation = new Animation(Level.Content.Load<Texture2D>("Sprites/Player/Run"), 0.1f, true);
- jumpAnimation = new Animation(Level.Content.Load<Texture2D>("Sprites/Player/Jump"), 0.1f, false);
- celebrateAnimation = new Animation(Level.Content.Load<Texture2D>("Sprites/Player/Celebrate"), 0.1f, false);
- dieAnimation = new Animation(Level.Content.Load<Texture2D>("Sprites/Player/Die"), 0.1f, false);
- // Calculate bounds within texture size.
- int width = (int)(idleAnimation.FrameWidth * 0.4);
- int left = (idleAnimation.FrameWidth - width) / 2;
- int height = (int)(idleAnimation.FrameWidth * 0.8);
- int top = idleAnimation.FrameHeight - height;
- localBounds = new Rectangle(left, top, width, height);
- // Load sounds.
- killedSound = Level.Content.Load<SoundEffect>("Sounds/PlayerKilled");
- jumpSound = Level.Content.Load<SoundEffect>("Sounds/PlayerJump");
- fallSound = Level.Content.Load<SoundEffect>("Sounds/PlayerFall");
- }
- /// <summary>
- /// Resets the player to life.
- /// </summary>
- /// <param name="position">The position to come to life at.</param>
- public void Reset(Vector2 position)
- {
- Position = position;
- Velocity = Vector2.Zero;
- isAlive = true;
- sprite.PlayAnimation(idleAnimation);
- }
- /// <summary>
- /// Handles input, performs physics, and animates the player sprite.
- /// </summary>
- /// <remarks>
- /// We pass in all of the input states so that our game is only polling the hardware
- /// once per frame. We also pass the game's orientation because when using the accelerometer,
- /// we need to reverse our motion when the orientation is in the LandscapeRight orientation.
- /// </remarks>
- public void Update(
- GameTime gameTime,
- KeyboardState keyboardState,
- GamePadState gamePadState,
- TouchCollection touchState,
- AccelerometerState accelState,
- DisplayOrientation orientation)
- {
- GetInput(keyboardState, gamePadState, touchState, accelState, orientation);
- ApplyPhysics(gameTime);
- if (IsAlive && IsOnGround)
- {
- if (Math.Abs(Velocity.X) - 0.02f > 0)
- {
- sprite.PlayAnimation(runAnimation);
- }
- else
- {
- sprite.PlayAnimation(idleAnimation);
- }
- }
- // Clear input.
- movement = 0.0f;
- isJumping = false;
- }
- /// <summary>
- /// Gets player horizontal movement and jump commands from input.
- /// </summary>
- private void GetInput(
- KeyboardState keyboardState,
- GamePadState gamePadState,
- TouchCollection touchState,
- AccelerometerState accelState,
- DisplayOrientation orientation)
- {
- // Get analog horizontal movement.
- movement = gamePadState.ThumbSticks.Left.X * MoveStickScale;
- // Ignore small movements to prevent running in place.
- if (Math.Abs(movement) < 0.5f)
- movement = 0.0f;
- // Move the player with accelerometer
- if (Math.Abs(accelState.Acceleration.Y) > 0.10f)
- {
- // set our movement speed
- movement = MathHelper.Clamp(-accelState.Acceleration.Y * AccelerometerScale, -1f, 1f);
- // if we're in the LandscapeLeft orientation, we must reverse our movement
- if (orientation == DisplayOrientation.LandscapeRight)
- movement = -movement;
- }
- // If any digital horizontal movement input is found, override the analog movement.
- if (gamePadState.IsButtonDown(Buttons.DPadLeft) ||
- keyboardState.IsKeyDown(Keys.Left) ||
- keyboardState.IsKeyDown(Keys.A))
- {
- movement = -1.0f;
- }
- else if (gamePadState.IsButtonDown(Buttons.DPadRight) ||
- keyboardState.IsKeyDown(Keys.Right) ||
- keyboardState.IsKeyDown(Keys.D))
- {
- movement = 1.0f;
- }
- // Check if the player wants to jump.
- isJumping =
- gamePadState.IsButtonDown(JumpButton) ||
- keyboardState.IsKeyDown(Keys.Space) ||
- keyboardState.IsKeyDown(Keys.Up) ||
- keyboardState.IsKeyDown(Keys.W) ||
- touchState.AnyTouch();
- }
- /// <summary>
- /// Updates the player's velocity and position based on input, gravity, etc.
- /// </summary>
- public void ApplyPhysics(GameTime gameTime)
- {
- float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
- Vector2 previousPosition = Position;
- // Base velocity is a combination of horizontal movement control and
- // acceleration downward due to gravity.
- velocity.X += movement * MoveAcceleration * elapsed;
- velocity.Y = MathHelper.Clamp(velocity.Y + GravityAcceleration * elapsed, -MaxFallSpeed, MaxFallSpeed);
- velocity.Y = DoJump(velocity.Y, gameTime);
- // Apply pseudo-drag horizontally.
- if (IsOnGround)
- velocity.X *= GroundDragFactor;
- else
- velocity.X *= AirDragFactor;
- // Prevent the player from running faster than his top speed.
- velocity.X = MathHelper.Clamp(velocity.X, -MaxMoveSpeed, MaxMoveSpeed);
- // Apply velocity.
- Position += velocity * elapsed;
- Position = new Vector2((float)Math.Round(Position.X), (float)Math.Round(Position.Y));
- // If the player is now colliding with the level, separate them.
- HandleCollisions();
- // If the collision stopped us from moving, reset the velocity to zero.
- if (Position.X == previousPosition.X)
- velocity.X = 0;
- if (Position.Y == previousPosition.Y)
- velocity.Y = 0;
- }
- /// <summary>
- /// Calculates the Y velocity accounting for jumping and
- /// animates accordingly.
- /// </summary>
- /// <remarks>
- /// During the accent of a jump, the Y velocity is completely
- /// overridden by a power curve. During the decent, gravity takes
- /// over. The jump velocity is controlled by the jumpTime field
- /// which measures time into the accent of the current jump.
- /// </remarks>
- /// <param name="velocityY">
- /// The player's current velocity along the Y axis.
- /// </param>
- /// <returns>
- /// A new Y velocity if beginning or continuing a jump.
- /// Otherwise, the existing Y velocity.
- /// </returns>
- private float DoJump(float velocityY, GameTime gameTime)
- {
- // If the player wants to jump
- if (isJumping)
- {
- // Begin or continue a jump
- if ((!wasJumping && IsOnGround) || jumpTime > 0.0f)
- {
- if (jumpTime == 0.0f)
- jumpSound.Play();
- jumpTime += (float)gameTime.ElapsedGameTime.TotalSeconds;
- sprite.PlayAnimation(jumpAnimation);
- }
- // If we are in the ascent of the jump
- if (0.0f < jumpTime && jumpTime <= MaxJumpTime)
- {
- // Fully override the vertical velocity with a power curve that gives players more control over the top of the jump
- velocityY = JumpLaunchVelocity * (1.0f - (float)Math.Pow(jumpTime / MaxJumpTime, JumpControlPower));
- }
- else
- {
- // Reached the apex of the jump
- jumpTime = 0.0f;
- }
- }
- else
- {
- // Continues not jumping or cancels a jump in progress
- jumpTime = 0.0f;
- }
- wasJumping = isJumping;
- return velocityY;
- }
- /// <summary>
- /// Detects and resolves all collisions between the player and his neighboring
- /// tiles. When a collision is detected, the player is pushed away along one
- /// axis to prevent overlapping. There is some special logic for the Y axis to
- /// handle platforms which behave differently depending on direction of movement.
- /// </summary>
- private void HandleCollisions()
- {
- // Get the player's bounding rectangle and find neighboring tiles.
- Rectangle bounds = BoundingRectangle;
- int leftTile = (int)Math.Floor((float)bounds.Left / Tile.Width);
- int rightTile = (int)Math.Ceiling(((float)bounds.Right / Tile.Width)) - 1;
- int topTile = (int)Math.Floor((float)bounds.Top / Tile.Height);
- int bottomTile = (int)Math.Ceiling(((float)bounds.Bottom / Tile.Height)) - 1;
- // Reset flag to search for ground collision.
- isOnGround = false;
- // For each potentially colliding tile,
- for (int y = topTile; y <= bottomTile; ++y)
- {
- for (int x = leftTile; x <= rightTile; ++x)
- {
- // If this tile is collidable,
- TileCollision collision = Level.GetCollision(x, y);
- if (collision != TileCollision.Passable)
- {
- // Determine collision depth (with direction) and magnitude.
- Rectangle tileBounds = Level.GetBounds(x, y);
- Vector2 depth = RectangleExtensions.GetIntersectionDepth(bounds, tileBounds);
- if (depth != Vector2.Zero)
- {
- float absDepthX = Math.Abs(depth.X);
- float absDepthY = Math.Abs(depth.Y);
- // Resolve the collision along the shallow axis.
- if (absDepthY < absDepthX || collision == TileCollision.Platform)
- {
- // If we crossed the top of a tile, we are on the ground.
- if (previousBottom <= tileBounds.Top)
- isOnGround = true;
- // Ignore platforms, unless we are on the ground.
- if (collision == TileCollision.Impassable || IsOnGround)
- {
- // Resolve the collision along the Y axis.
- Position = new Vector2(Position.X, Position.Y + depth.Y);
- // Perform further collisions with the new bounds.
- bounds = BoundingRectangle;
- }
- }
- else if (collision == TileCollision.Impassable) // Ignore platforms.
- {
- // Resolve the collision along the X axis.
- Position = new Vector2(Position.X + depth.X, Position.Y);
- // Perform further collisions with the new bounds.
- bounds = BoundingRectangle;
- }
- }
- }
- }
- }
- // Save the new bounds bottom.
- previousBottom = bounds.Bottom;
- }
- /// <summary>
- /// Called when the player has been killed.
- /// </summary>
- /// <param name="killedBy">
- /// The enemy who killed the player. This parameter is null if the player was
- /// not killed by an enemy (fell into a hole).
- /// </param>
- public void OnKilled(Enemy killedBy)
- {
- isAlive = false;
- if (killedBy != null)
- killedSound.Play();
- else
- fallSound.Play();
- sprite.PlayAnimation(dieAnimation);
- }
- /// <summary>
- /// Called when this player reaches the level's exit.
- /// </summary>
- public void OnReachedExit()
- {
- sprite.PlayAnimation(celebrateAnimation);
- }
- /// <summary>
- /// Draws the animated player.
- /// </summary>
- public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
- {
- // Flip the sprite to face the way we are moving.
- if (Velocity.X > 0)
- flip = SpriteEffects.FlipHorizontally;
- else if (Velocity.X < 0)
- flip = SpriteEffects.None;
- // Draw that sprite.
- sprite.Draw(gameTime, spriteBatch, Position, flip);
- }
- }
- }
|