123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using Microsoft.Xna.Framework;
- using Microsoft.Xna.Framework.Graphics;
- using Microsoft.Xna.Framework.Input;
- namespace XNAPacMan {
- public enum GhostState { Home, Scatter, Attack, Blue, Dead }
- public enum Ghosts { Blinky, Pinky, Inky, Clyde }
- /// <summary>
- /// One of the ghosts that try to kill Pac Man.
- /// </summary>
- public class Ghost {
- /// <summary>
- /// Instantiates a ghost.
- /// </summary>
- /// <param name="game">A reference to the Game object, needed for access to services.</param>
- /// <param name="player">A reference to the Pac Man, needed for AI.</param>
- /// <param name="identity">Which ghost, needed for appearance and behavior.</param>
- public Ghost(Game game, Player player, Ghosts identity) {
- spriteBatch_ = (SpriteBatch)game.Services.GetService(typeof(SpriteBatch));
- ghostBase1_ = game.Content.Load<Texture2D>("sprites/GhostBase");
- ghostBase2_ = game.Content.Load<Texture2D>("sprites/GhostBase2");
- ghostChased_ = game.Content.Load<Texture2D>("sprites/GhostChased");
- eyesBase_ = game.Content.Load<Texture2D>("sprites/GhostEyes");
- eyesCenter_ = game.Content.Load<Texture2D>("sprites/GhostEyesCenter");
- colorBase_ = Constants.colors(identity);
- identity_ = identity;
- previousNumCrumps_ = 0;
- Reset(true, player);
- wiggle_ = true;
- direction_ = new Direction();
- lastJunction_ = new Point();
- scatterTiles_ = Constants.scatterTiles(identity);
- }
- /// <summary>
- /// Put the ghosts back in their home, ready to begin a new attack
- /// </summary>
- /// <param name="newLevel">True at level start, false otherwise</param>
- /// <param name="player">The pac man. Pac Man is often respawned with the ghosts, so they need to know about the new Pac Man.</param>
- public void Reset(bool newLevel, Player player) {
- State = GhostState.Home; // Ghosts start at home
- previousState_ = GhostState.Home; // Sounds are played when currentState != previousState.
- // So to get them playing at level start, we do this simple hack.
- updateCount_ = 0;
- initialJumps_ = Constants.InitialJumps(identity_, newLevel);
- position_ = Constants.startPosition(identity_);
- scheduleStateEval_ = true;
- lastJunction_ = new Point();
- player_ = player;
- scatterModesLeft_ = 4;
- UpdateSpeed();
- }
- /// <summary>
- /// When we need to lock the game, make sure the timers here are updated to reflect
- /// the "waste of time".
- /// </summary>
- public void LockTimer(GameTime gameTime) {
- timeInCurrentState += gameTime.ElapsedGameTime;
- }
- /// <summary>
- /// In case this ghost is Inky, he will need a reference to blinky in order
- /// to find his way around the maze. Otherwise, setting this has no effect.
- /// </summary>
- /// <param name="blinky">A reference to Blinky.</param>
- public void SetBlinky(Ghost blinky) {
- blinky_ = blinky;
- }
- /// <summary>
- /// Call this every game update to get those ghosts moving around.
- /// </summary>
- /// <param name="gameTime">Provides a snapshot of timing values.</param>
- public void Update(GameTime gameTime) {
- if (scheduleStateEval_) {
- StateEval();
- }
- // After the AI has had the occasion to run, update lastJuntion.
- if (position_.DeltaPixel == Point.Zero && IsAJunction(position_.Tile)) {
- lastJunction_ = position_.Tile;
- }
- Move();
- }
- void PlaySound() {
- if (State == GhostState.Attack || State == GhostState.Scatter || State == GhostState.Home) {
- if (Grid.NumCrumps < 50) {
- GhostSoundsManager.playLoopAttackVeryFast();
- } else if (Grid.NumCrumps < 120) {
- GhostSoundsManager.playLoopAttackFast();
- } else {
- GhostSoundsManager.playLoopAttack();
- }
- } else if (State == GhostState.Blue) {
- GhostSoundsManager.playLoopBlue();
- } else if (State == GhostState.Dead) {
- GhostSoundsManager.playLoopDead();
- }
- }
- void StateEval() {
- GhostState initialState = State;
- scheduleStateEval_ = false;
- switch (State) {
- case GhostState.Home:
- // Ghost exit the home state for the scatter state when they get to the row
- // above the home
- if (position_.Tile.Y == 11 && position_.DeltaPixel.Y == 0) {
- // Select inital direction based on scatter tiles
- if (Constants.scatterTiles(identity_)[0].X < 13) {
- direction_ = Direction.Left;
- } else {
- direction_ = Direction.Right;
- }
- if (scatterModesLeft_ > 0) {
- State = GhostState.Scatter;
- } else {
- State = GhostState.Attack;
- }
- return;
- }
- // Ghosts move up when they are aligned with the entrance
- else if (position_.Tile.X == 13 && position_.DeltaPixel.X == 8) {
- direction_ = Direction.Up;
- }
- // When on one side, move towards middle when on the bottom and time's up
- // If time's not up, keep bouncing up and down
- else if ((position_.DeltaPixel.Y == 8) &&
- ((position_.Tile.X == 11 && position_.DeltaPixel.X == 8) ||
- (position_.Tile.X == 15 && position_.DeltaPixel.X == 8))) {
- if (position_.Tile.Y == 14) {
- initialJumps_--;
- if (initialJumps_ == 0) {
- if (position_.Tile.X == 11) {
- direction_ = Direction.Right;
- } else {
- direction_ = Direction.Left;
- }
- } else {
- direction_ = Direction.Up;
- }
- } else if (position_.Tile.Y == 13) {
- direction_ = Direction.Down;
- }
- }
- break;
- case GhostState.Scatter:
- // Attempt to reverse direction upon entering this state
- if (previousState_ == GhostState.Attack) {
- scatterModesLeft_--;
- if (NextTile(OppositeDirection(direction_)).IsOpen) {
- direction_ = OppositeDirection(direction_);
- }
- }
- AIScatter();
- int timeInScatterMode = scatterModesLeft_ <= 2 ? 5 : 7;
- if ((DateTime.Now - timeInCurrentState) > TimeSpan.FromSeconds(timeInScatterMode)) {
- State = GhostState.Attack;
- }
- break;
- case GhostState.Dead:
- // Attempt to reverse direction upon entering this state
- if (previousState_ != GhostState.Dead && previousState_ != GhostState.Blue) {
- if (NextTile(OppositeDirection(direction_)).IsOpen) {
- direction_ = OppositeDirection(direction_);
- }
- } else {
- AIDead();
- }
- if (position_.DeltaPixel.X == 8 && position_.DeltaPixel.Y == 8) {
- if (position_.Tile.Y == 14) {
- State = GhostState.Home;
- }
- }
- break;
- case GhostState.Attack:
- // Attempt to reverse direction upon entering this state
- if (previousState_ != GhostState.Attack && previousState_ != GhostState.Blue) {
- if (NextTile(OppositeDirection(direction_)).IsOpen) {
- direction_ = OppositeDirection(direction_);
- }
- } else {
- AIAttack();
- }
- if ((DateTime.Now - timeInCurrentState) > TimeSpan.FromSeconds(20)) {
- State = GhostState.Scatter;
- }
- break;
- case GhostState.Blue:
- // Attempt to reverse direction upon entering this state
- if (previousState_ != GhostState.Blue) {
- if (NextTile(OppositeDirection(direction_)).IsOpen) {
- direction_ = OppositeDirection(direction_);
- }
- } else {
- // TODO : make special blue AI
- AIAttack();
- }
- // When blue time is over, revert to attack mode.
- if ((DateTime.Now - timeInCurrentState) > TimeSpan.FromSeconds(Constants.BlueTime())) {
- State = GhostState.Attack;
- }
- break;
- }
- // TODO : move all these magic numbers to the Constants class.
- // We select a new sound only upon some state change, or when
- // the number of crumps goes below certain thresholds.
- if ((initialState != previousState_) ||
- (Grid.NumCrumps == 199 && previousNumCrumps_ == 200) ||
- (Grid.NumCrumps == 19 && previousNumCrumps_ == 20)) {
- PlaySound();
- }
- previousState_ = initialState;
- }
- void Move() {
- updateCount_++;
- updateCount_ %= updatesPerPixel_;
- if (updateCount_ == 0) {
- // There is no point running the full AI each update, especially considering we
- // want this code to run at 1000 updates per second. We run it each time it moves by a pixel.
- scheduleStateEval_ = true;
- // Now is a nice time to evaluate whether our current speed is correct. This variable
- // may change under a wide array of circumstances, so by putting it here we probably generate
- // too many checks but we ensure correct behavior.
- UpdateSpeed();
- switch (direction_) {
- case Direction.Up:
- position_.DeltaPixel.Y--;
- if (position_.DeltaPixel.Y < 0) {
- position_.DeltaPixel.Y = 15;
- position_.Tile.Y--;
- }
- break;
- case Direction.Down:
- position_.DeltaPixel.Y++;
- if (position_.DeltaPixel.Y > 15) {
- position_.DeltaPixel.Y = 0;
- position_.Tile.Y++;
- }
- break;
- case Direction.Left:
- position_.DeltaPixel.X--;
- if (position_.DeltaPixel.X < 0) {
- position_.DeltaPixel.X = 15;
- position_.Tile.X--;
- // Special case : the tunnel
- if (position_.Tile.X < 0) {
- position_.Tile.X = Grid.Width - 1;
- }
- }
- break;
- case Direction.Right:
- position_.DeltaPixel.X++;
- if (position_.DeltaPixel.X > 15) {
- position_.DeltaPixel.X = 0;
- position_.Tile.X++;
- // Special case : the tunnel
- if (position_.Tile.X > Grid.Width - 1) {
- position_.Tile.X = 0;
- }
- }
- break;
- }
- }
- }
- void UpdateSpeed() {
- int baseSpeed = Constants.GhostSpeed();
- if (State == GhostState.Home) {
- updatesPerPixel_ = baseSpeed * 2;
- } else if (State == GhostState.Blue) {
- updatesPerPixel_ = (int)(baseSpeed * 1.5);
- } else if (identity_ == Ghosts.Blinky && Grid.NumCrumps <= Constants.CruiseElroyTimer()) {
- updatesPerPixel_ = baseSpeed - 1;
- }
- // If in the tunnel, reduce speed
- else if (position_.Tile.Y == 14 &&
- ((0 <= position_.Tile.X && position_.Tile.X <= 5) ||
- (22 <= position_.Tile.X && position_.Tile.X <= 27))) {
- updatesPerPixel_ = baseSpeed + 5;
- } else {
- updatesPerPixel_ = baseSpeed;
- }
- }
- /// <summary>
- /// Guides the ghost towards his "favored" area of the board, as defined by scatterTiles_.
- /// </summary>
- void AIScatter() {
- // As with AIAttack(), all the method does is change direction if necessary,
- // which only happens when exactly at a junction
- if (position_.DeltaPixel != Point.Zero || !IsAJunction(position_.Tile)) {
- return;
- }
- // Scatter AI may be overriden by certain tiles
- if (AIOverride()) {
- return;
- }
- // If we are on one of our favored tiles, go on to the next
- int favoredTile = scatterTiles_.FindIndex(i => i == position_.Tile);
- int nextFavoredTile = (favoredTile + 1) % (scatterTiles_.Count);
- if (favoredTile != -1) {
- direction_ = FindDirection(scatterTiles_[nextFavoredTile]);
- }
- // If our last junction was a favored tile and this one isn't, ignore it
- else if (!scatterTiles_.Contains(lastJunction_)) {
- // Otherwise, find scatter tile closest to our position and head for it.
- List<Point> orderedTiles = scatterTiles_.ToList();
- orderedTiles.Sort((a, b) => Vector2.Distance(new Vector2(position_.Tile.X, position_.Tile.Y),
- new Vector2(a.X, a.Y)).
- CompareTo(
- Vector2.Distance(new Vector2(position_.Tile.X, position_.Tile.Y),
- new Vector2(b.X, b.Y))));
- direction_ = FindDirection(orderedTiles[0]);
- }
- }
- /// <summary>
- /// Guides the ghost to try to reach the player
- /// </summary>
- void AIAttack() {
- // All this method does is change direction if necessary.
- // There is only one case in which we may potentially change direction :
- // when the ghost is exactly at a junction.
- if (position_.DeltaPixel != Point.Zero || !IsAJunction(position_.Tile)) {
- return;
- }
- // Attack AI may be overriden by certain tiles
- if (AIOverride()) {
- return;
- }
- switch (identity_) {
- case Ghosts.Blinky:
- AttackAIBlinky();
- break;
- case Ghosts.Pinky:
- AttackAIPinky();
- break;
- case Ghosts.Inky:
- AttackAIInky();
- break;
- case Ghosts.Clyde:
- AttackAIClyde();
- break;
- default:
- throw new ArgumentException();
- }
- }
- void AIDead() {
- // When aligned with the entrance, go down. When the ghost has fully entered the house,
- // stateEval will change state to Home.
- if (position_.Tile.Y == 11 && position_.Tile.X == 13 && position_.DeltaPixel.X == 8) {
- direction_ = Direction.Down;
- }
- // Otherwise, only change direction if the ghost is exactly on a tile
- else if (position_.DeltaPixel == Point.Zero) {
- // We direct the ghost towards one of the two squares above the home. When he reaches one,
- // he still has to move slightly right or left in order to align with the entrance.
- if (position_.Tile.X == 13 && position_.Tile.Y == 11) {
- direction_ = Direction.Right;
- } else if (position_.Tile.X == 14 && position_.Tile.Y == 11) {
- direction_ = Direction.Left;
- }
- // Otherwise, when at a junction, head for the square above home closest to us.
- else if (IsAJunction(position_.Tile)) {
- if (position_.Tile.X > 13) {
- direction_ = FindDirection(new Point(14, 11));
- } else {
- direction_ = FindDirection(new Point(13, 11));
- }
- }
- }
- }
- /// <summary>
- /// Blinky is extremely straightforward : head directly for the player
- /// </summary>
- void AttackAIBlinky() {
- direction_ = FindDirection(player_.Position.Tile);
- }
- /// <summary>
- /// Pinky tries to head for two tiles ahead of the player.
- /// </summary>
- void AttackAIPinky() {
- Tile nextTile = NextTile(player_.Direction, player_.Position);
- Tile nextNextTile = NextTile(player_.Direction, new Position(nextTile.ToPoint, Point.Zero));
- direction_ = FindDirection(nextNextTile.ToPoint);
- }
- /// <summary>
- /// Inky is a bit more random. He will try to head for a square situated across
- /// the pac man from blinky's location.
- /// </summary>
- void AttackAIInky() {
- Tile nextTile = NextTile(player_.Direction, player_.Position);
- Tile nextNextTile = NextTile(player_.Direction, new Position(nextTile.ToPoint, Point.Zero));
- Vector2 line = new Vector2(blinky_.Position.Tile.X - nextNextTile.ToPoint.X, blinky_.Position.Tile.Y - nextNextTile.ToPoint.Y);
- line *= 2;
- Point destination = new Point(position_.Tile.X + (int)line.X, position_.Tile.Y + (int)line.Y);
- // Prevent out-of-bounds exception
- destination.X = (int)MathHelper.Clamp(destination.X, 0, Grid.Width - 1);
- destination.Y = (int)MathHelper.Clamp(destination.Y, 0, Grid.Height - 1);
- direction_ = FindDirection(destination);
- }
- /// <summary>
- /// Clyde is the bizarre one. When within 8 tiles of the player,
- /// he will head for his favored corner (AIScatter). When farther away,
- /// he will near the player using Blinky's AI.
- /// </summary>
- void AttackAIClyde() {
- float distanceToPlayer = Vector2.Distance(
- new Vector2(player_.Position.Tile.X, player_.Position.Tile.Y),
- new Vector2(position_.Tile.X, position_.Tile.Y));
- if (distanceToPlayer >= 8) {
- AttackAIBlinky();
- } else {
- AIScatter();
- }
- }
- // On certain tiles, we force all the ghosts in a particular direction.
- // This is to prevent them from bunching up too much, and give attentive
- // players some sure-fire ways to escape.
- bool AIOverride() {
- if (position_.Tile.Y == 11 && (position_.Tile.X == 12 || position_.Tile.X == 15)) {
- return (direction_ == Direction.Right || direction_ == Direction.Left);
- } else if (position_.Tile.Y == 20 && (position_.Tile.X == 9 || position_.Tile.X == 18)) {
- return (direction_ == Direction.Right || direction_ == Direction.Left);
- } else {
- return false;
- }
- }
- /// <summary>
- /// Returns the opposite of the specified direction
- /// </summary>
- /// <param name="d">direction</param>
- /// <returns>opposite direction</returns>
- Direction OppositeDirection(Direction d) {
- switch (d) {
- case Direction.Up:
- return Direction.Down;
- case Direction.Down:
- return Direction.Up;
- case Direction.Left:
- return Direction.Right;
- case Direction.Right:
- return Direction.Left;
- default:
- throw new ArgumentException();
- }
- }
- /// <summary>
- /// Returns in what direction we should go next in order to reach the destination.
- /// </summary>
- /// <param name="destination">Where we want to go</param>
- /// <returns>Where to go next</returns>
- Direction FindDirection(Point destination) {
- // We use a stupid but very efficient algorithm : go in the direction that
- // closes most distance between position and destination. This works well given
- // there are no dead ends and multiple paths leading to the destination.
- // However, we can never turn 180, and will rather choose a less-than-optimal path.
- int xDistance = destination.X - position_.Tile.X;
- int yDistance = destination.Y - position_.Tile.Y;
- var directions = new List<Direction>(4);
- // Order directions by shortest path. Note: there is probably a better way to do this, probably if I hadn't used an
- // enum for directions in the first place.
- if (Math.Abs(xDistance) > Math.Abs(yDistance)) {
- if (xDistance > 0) {
- if (yDistance < 0) { // Direction is Up-Right, favor Right
- directions = new List<Direction> { Direction.Right, Direction.Up, Direction.Down, Direction.Left };
- } else { // Direction is Down-Right, favor Right
- directions = new List<Direction> { Direction.Right, Direction.Down, Direction.Up, Direction.Left };
- }
- } else {
- if (yDistance < 0) { // Direction is Up-Left, favor Left
- directions = new List<Direction> { Direction.Left, Direction.Up, Direction.Down, Direction.Right };
- } else { // Direction is Down-Left, favor Left
- directions = new List<Direction> { Direction.Left, Direction.Down, Direction.Up, Direction.Right };
- }
- }
- } else {
- if (xDistance > 0) {
- if (yDistance < 0) { // Direction is Up-Right, favor Up
- directions = new List<Direction> { Direction.Up, Direction.Right, Direction.Left, Direction.Down };
- } else { // Direction is Down-Right, favor Down
- directions = new List<Direction> { Direction.Down, Direction.Right, Direction.Left, Direction.Up };
- }
- } else {
- if (yDistance < 0) { // Direction is Up-Left, favor Up
- directions = new List<Direction> { Direction.Up, Direction.Left, Direction.Right, Direction.Down };
- } else { // Direction is Down-Left, favor Down
- directions = new List<Direction> { Direction.Down, Direction.Left, Direction.Right, Direction.Up };
- }
- }
- }
- // Select first item in the list to meet two essential conditions : the path ahead is open,
- // and it's not the opposite direction. If we can't meet both conditions, just return the first
- // direction that leads to an open square.
- int index = directions.FindIndex(i => i != OppositeDirection(direction_) && NextTile(i).IsOpen);
- if (index != -1) {
- return directions[index];
- } else {
- // Put a breakpoint here, this should never happen.
- return directions.Find(i => NextTile(i).IsOpen);
- }
- }
- /// <summary>
- /// Retrieves the next tile in the specified direction from the ghost's position.
- /// </summary>
- /// <param name="d">Direction in which to look</param>
- /// <returns>The tile</returns>
- Tile NextTile(Direction d) {
- return NextTile(d, position_);
- }
- /// <summary>
- /// Retrieves the next tile in the specified direction from the specified position.
- /// </summary>
- /// <param name="d">Direction in which to look</param>
- /// <param name="p">Position from which to look</param>
- /// <returns>The tile</returns>
- public static Tile NextTile(Direction d, Position p) {
- switch (d) {
- case Direction.Up:
- if (p.Tile.Y - 1 < 0) {
- return Grid.TileGrid[p.Tile.X, p.Tile.Y];
- } else {
- return Grid.TileGrid[p.Tile.X, p.Tile.Y - 1];
- }
- case Direction.Down:
- if (p.Tile.Y + 1 >= Grid.Height) {
- return Grid.TileGrid[p.Tile.X, p.Tile.Y];
- } else {
- return Grid.TileGrid[p.Tile.X, p.Tile.Y + 1];
- }
- case Direction.Left:
- // Special case : the tunnel
- if (p.Tile.X == 0) {
- return Grid.TileGrid[Grid.Width - 1, p.Tile.Y];
- } else {
- return Grid.TileGrid[p.Tile.X - 1, p.Tile.Y];
- }
- case Direction.Right:
- // Special case : the tunnel
- if (p.Tile.X + 1 >= Grid.Width) {
- return Grid.TileGrid[0, p.Tile.Y];
- } else {
- return Grid.TileGrid[p.Tile.X + 1, p.Tile.Y];
- }
- default:
- throw new ArgumentException();
- }
- }
- /// <summary>
- /// Returns whether the specified tile is a junction.
- /// </summary>
- /// <param name="tile">Tile to check</param>
- /// <returns>whether the specified tile is a junction</returns>
- bool IsAJunction(Point tile) {
- if (NextTile(direction_).Type == TileTypes.Open) {
- // If the path ahead is open, we're at a junction if it's also open
- // to one of our sides
- if (direction_ == Direction.Up || direction_ == Direction.Down) {
- return ((NextTile(Direction.Left).IsOpen) ||
- (NextTile(Direction.Right).IsOpen));
- } else {
- return ((NextTile(Direction.Up).IsOpen) ||
- (NextTile(Direction.Down).IsOpen));
- }
- }
- // If the path ahead is blocked, then we're definitely at a junction, because there are no dead-ends
- else {
- return true;
- }
- }
- /// <summary>
- /// Assumes spritebatch.begin() was called
- /// </summary>
- /// <param name="gameTime">Provides a snapshot of timing values.</param>
- public void Draw(GameTime gameTime, Vector2 boardPosition) {
- // Ghosts have a two-frame animation; we use a bool to toggle between
- // the two. We divide DateTime.Now.Milliseconds by the period.
- if (((DateTime.Now.Millisecond / 125) % 2) == 0 ^ wiggle_) {
- wiggle_ = !wiggle_;
- }
- Vector2 position;
- position.X = boardPosition.X + (position_.Tile.X * 16) + (position_.DeltaPixel.X) - 6;
- position.Y = boardPosition.Y + (position_.Tile.Y * 16) + (position_.DeltaPixel.Y) - 6;
- Vector2 eyesBasePosition;
- eyesBasePosition.X = position.X + 4;
- eyesBasePosition.Y = position.Y + 6;
- Vector2 eyesCenterPosition = new Vector2();
- switch (direction_) {
- case Direction.Up:
- eyesBasePosition.Y -= 2;
- eyesCenterPosition.X = eyesBasePosition.X + 2;
- eyesCenterPosition.Y = eyesBasePosition.Y;
- break;
- case Direction.Down:
- eyesBasePosition.Y += 2;
- eyesCenterPosition.X = eyesBasePosition.X + 2;
- eyesCenterPosition.Y = eyesBasePosition.Y + 6;
- break;
- case Direction.Left:
- eyesBasePosition.X -= 2;
- eyesCenterPosition.X = eyesBasePosition.X;
- eyesCenterPosition.Y = eyesBasePosition.Y + 3;
- break;
- case Direction.Right:
- eyesBasePosition.X += 2;
- eyesCenterPosition.X = eyesBasePosition.X + 4;
- eyesCenterPosition.Y = eyesBasePosition.Y + 3;
- break;
- }
- if (State == GhostState.Blue) {
- if (((DateTime.Now - timeInCurrentState).Seconds < 0.5 * Constants.BlueTime())) {
- RenderSprite(wiggle_ ? ghostBase1_ : ghostBase2_, null, boardPosition, position, Color.Blue);
- RenderSprite(ghostChased_, null, boardPosition, position, Color.White);
- } else {
- bool flash = (DateTime.Now.Second + DateTime.Now.Millisecond / 200) % 2 == 0;
- RenderSprite(wiggle_ ? ghostBase1_ : ghostBase2_, null, boardPosition, position, flash ? Color.Blue : Color.White);
- RenderSprite(ghostChased_, null, boardPosition, position, flash ? Color.White : Color.Blue);
- }
- } else if (State == GhostState.Dead) {
- RenderSprite(eyesBase_, null, boardPosition, eyesBasePosition, Color.White);
- RenderSprite(eyesCenter_, null, boardPosition, eyesCenterPosition, Color.White);
- } else {
- RenderSprite(wiggle_ ? ghostBase1_ : ghostBase2_, null, boardPosition, position, colorBase_);
- RenderSprite(eyesBase_, null, boardPosition, eyesBasePosition, Color.White);
- RenderSprite(eyesCenter_, null, boardPosition, eyesCenterPosition, Color.White);
- }
- }
- /// <summary>
- /// Allows rendering across the tunnel, which is tricky.
- /// </summary>
- /// <param name="spriteSheet">Source texture</param>
- /// <param name="rectangle">Portion of the source to render. Pass null for rendering the whole texture.</param>
- /// <param name="boardPosition">Top-left pixel of the board in the screen</param>
- /// <param name="position">Where to render the texture.</param>
- void RenderSprite(Texture2D spriteSheet, Rectangle? rectangle, Vector2 boardPosition, Vector2 position, Color color) {
- Rectangle rect = rectangle == null ? new Rectangle(0, 0, spriteSheet.Width, spriteSheet.Height) :
- rectangle.Value;
- int textureWidth = rectangle == null ? spriteSheet.Width : rectangle.Value.Width;
- int textureHeight = rectangle == null ? spriteSheet.Height : rectangle.Value.Height;
- // What happens when we are crossing to the other end by the tunnel?
- // We detect if part of the sprite is rendered outside of the board.
- // First, to the left.
- if (position.X < boardPosition.X) {
- int deltaPixel = (int)(boardPosition.X - position.X); // Number of pixels off the board
- var leftPortion = new Rectangle(rect.X + deltaPixel, rect.Y, textureWidth - deltaPixel, textureHeight);
- var leftPortionPosition = new Vector2(boardPosition.X, position.Y);
- var rightPortion = new Rectangle(rect.X, rect.Y, deltaPixel, textureHeight);
- var rightPortionPosition = new Vector2(boardPosition.X + (16 * 28) - deltaPixel, position.Y);
- spriteBatch_.Draw(spriteSheet, leftPortionPosition, leftPortion, color);
- spriteBatch_.Draw(spriteSheet, rightPortionPosition, rightPortion, color);
- }
- // Next, to the right
- else if (position.X > (boardPosition.X + (16 * 28) - textureWidth)) {
- int deltaPixel = (int)((position.X + textureWidth) - (boardPosition.X + (16 * 28))); // Number of pixels off the board
- var leftPortion = new Rectangle(rect.X + textureWidth - deltaPixel, rect.Y, deltaPixel, textureHeight);
- var leftPortionPosition = new Vector2(boardPosition.X, position.Y);
- var rightPortion = new Rectangle(rect.X, rect.Y, textureWidth - deltaPixel, textureHeight);
- var rightPortionPosition = new Vector2(position.X, position.Y);
- spriteBatch_.Draw(spriteSheet, leftPortionPosition, leftPortion, color);
- spriteBatch_.Draw(spriteSheet, rightPortionPosition, rightPortion, color);
- }
- // Draw normally - in one piece
- else {
- spriteBatch_.Draw(spriteSheet, position, rect, color);
- }
- }
- // DRAWING
- SpriteBatch spriteBatch_;
- Texture2D ghostBase1_;
- Texture2D ghostBase2_;
- Texture2D ghostChased_;
- Texture2D eyesBase_;
- Texture2D eyesCenter_;
- Color colorBase_;
- bool wiggle_;
- // LOGIC
- Ghost blinky_; // Only Inky needs this for his AI
- Ghosts identity_;
- Direction direction_;
- Position position_;
- GhostState state_;
- GhostState previousState_;
- List<Point> scatterTiles_;
- Point lastJunction_;
- DateTime timeInCurrentState;
- Player player_;
- int updatesPerPixel_;
- bool scheduleStateEval_;
- int scatterModesLeft_;
- int initialJumps_;
- int previousNumCrumps_;
- int updateCount_;
- public GhostState State { get { return state_; } set { state_ = value; timeInCurrentState = DateTime.Now; } }
- public Position Position { get { return position_; } }
- public Ghosts Identity { get { return identity_; } }
- }
- }
|