#region File Description //----------------------------------------------------------------------------- // TiledSprites.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #endregion #region Using Statements using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.GamerServices; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Media; #endregion namespace TiledSprites { /// /// This sample showcases a variety of common sprite operations /// using SpriteBatch /// public class TiledSpritesSample : Game { #region Private Types /// /// Some tiles based on included media /// public enum TileName : int { Empty = 0, Base = 1, Detail1 = 2, Detail2 = 3, Detail3 = 4, Detail4 = 5, SoftDetail1 = 6, SoftDetail2 = 7, SoftDetail3 = 8, SoftDetail4 = 9, Rocks1 = 10, Rocks2 = 11, Rocks3 = 12, Rocks4 = 13, Clouds = 14 } #endregion #region Constants private const float MovementRate = 500f; private const float ZoomRate = 0.5f; private const float RotationRate = 1.5f; private const int numTiles = 200; private const float animationTime = 0.1f; private static readonly Vector2 animatedSpriteScale = new Vector2 (.3f, .3f); #endregion #region Fields //utility types GraphicsDeviceManager graphics; //input state storage KeyboardState lastKeyboardState = new KeyboardState (); #if IPHONE GamePadState lastGamePadState = new GamePadState (); GamePadState currentGamePadState = new GamePadState (); #endif KeyboardState currentKeyboardState = new KeyboardState (); private Random rand; //2D camera abstraction private Camera2D camera; private Vector2 screenCenter; //tile information private SpriteSheet groundSheet; private SpriteSheet cloudSheet; private SpriteBatch spriteBatch; private TileGrid rockLayer; private TileGrid groundLayer; private TileGrid cloudLayer; private TileGrid detailLayer; //animated sprite private SpriteSheet animatedSpriteSheet; private AnimatedSprite animatedSprite; private Vector2 animatedSpritePosition; private float accumulator; #endregion #region Initialization public TiledSpritesSample () { graphics = new GraphicsDeviceManager (this); Content.RootDirectory = "Content"; graphics.PreferredBackBufferWidth = 320; graphics.PreferredBackBufferHeight = 480; rand = new Random (); } /// /// Load the graphics content. /// protected override void LoadContent () { //When the backbuffer resolution changes, this part of the //LoadContent calback is used to reset the screen center screenCenter = new Vector2 ( (float)graphics.GraphicsDevice.Viewport.Width / 2f, (float)graphics.GraphicsDevice.Viewport.Height / 2f); #region Set up Graphics resources Texture2D groundTexture = Content.Load ("ground"); Texture2D cloudTexture = Content.Load ("clouds"); spriteBatch = new SpriteBatch (graphics.GraphicsDevice); #endregion #region Set Up Tile Sources //set up the tile sheets with source rectangles //for each of the different sprites cloudSheet = new SpriteSheet (cloudTexture); cloudSheet.AddSourceSprite ((int)TileName.Clouds, new Rectangle (0, 0, 1024, 1024)); groundSheet = new SpriteSheet (groundTexture); groundSheet.AddSourceSprite ((int)TileName.Base, new Rectangle (0, 0, 510, 510)); groundSheet.AddSourceSprite ((int)TileName.Detail1, new Rectangle (514, 0, 255, 255)); groundSheet.AddSourceSprite ((int)TileName.Detail2, new Rectangle (769, 0, 255, 255)); groundSheet.AddSourceSprite ((int)TileName.Detail3, new Rectangle (514, 256, 255, 255)); groundSheet.AddSourceSprite ((int)TileName.Detail4, new Rectangle (769, 256, 255, 255)); groundSheet.AddSourceSprite ((int)TileName.SoftDetail1, new Rectangle (514, 514, 255, 255)); groundSheet.AddSourceSprite ((int)TileName.SoftDetail2, new Rectangle (769, 514, 255, 255)); groundSheet.AddSourceSprite ((int)TileName.SoftDetail3, new Rectangle (514, 769, 255, 255)); groundSheet.AddSourceSprite ((int)TileName.SoftDetail4, new Rectangle (769, 769, 255, 255)); groundSheet.AddSourceSprite ((int)TileName.Rocks1, new Rectangle (0, 514, 255, 255)); groundSheet.AddSourceSprite ((int)TileName.Rocks2, new Rectangle (256, 514, 255, 255)); groundSheet.AddSourceSprite ((int)TileName.Rocks3, new Rectangle (0, 769, 255, 255)); groundSheet.AddSourceSprite ((int)TileName.Rocks4, new Rectangle (256, 769, 255, 255)); #endregion #region Setup Tile Grids //Create the ground layer tile groundLayer = new TileGrid (510, 510, numTiles, numTiles, Vector2.Zero, groundSheet, graphics); //calculate the number of detial tiles, which are //half the size of the base tiles, so there are //twice as many (minus one since they are being offset) int numDetailTiles = (numTiles * 2 - 1); //add an offset to break up the pattern detailLayer = new TileGrid (255, 255, numDetailTiles, numDetailTiles, new Vector2 (127, 127), groundSheet, graphics); rockLayer = new TileGrid (255, 255, numDetailTiles, numDetailTiles, new Vector2 (0, 0), groundSheet, graphics); int numCloudTiles = numTiles / 6 + 1; cloudLayer = new TileGrid (1024, 1024, numCloudTiles, numCloudTiles, Vector2.Zero, cloudSheet, graphics); //These loops fill the datas with some appropriate data. //The clouds and ground clutter have been randomized. for (int i = 0; i < numTiles; i++) { for (int j = 0; j < numTiles; j++) { groundLayer.SetTile (i, j, 1); } } for (int i = 0; i < numDetailTiles; i++) { for (int j = 0; j < numDetailTiles; j++) { switch (rand.Next (20)) { case 0: detailLayer.SetTile (i, j, (int)TileName.Detail1); break; case 1: detailLayer.SetTile (i, j, (int)TileName.Detail2); break; case 2: detailLayer.SetTile (i, j, (int)TileName.Detail3); break; case 3: detailLayer.SetTile (i, j, (int)TileName.Detail4); break; case 4: case 5: detailLayer.SetTile (i, j, (int)TileName.SoftDetail1); break; case 6: case 7: detailLayer.SetTile (i, j, (int)TileName.SoftDetail2); break; case 8: case 9: detailLayer.SetTile (i, j, (int)TileName.SoftDetail3); break; case 10: case 11: detailLayer.SetTile (i, j, (int)TileName.SoftDetail4); break; } } } for (int i = 0; i < numDetailTiles; i++) { for (int j = 0; j < numDetailTiles; j++) { switch (rand.Next (25)) { case 0: rockLayer.SetTile (i, j, (int)TileName.Rocks1); break; case 1: rockLayer.SetTile (i, j, (int)TileName.Rocks2); break; case 2: rockLayer.SetTile (i, j, (int)TileName.Rocks3); break; case 3: case 4: rockLayer.SetTile (i, j, (int)TileName.Rocks4); break; } } } for (int i = 0; i < numCloudTiles; i++) { for (int j = 0; j < numCloudTiles; j++) { cloudLayer.SetTile (i, j, (int)TileName.Clouds); } } #endregion // Set up AnimatedSprite animatedSpriteSheet = new SpriteSheet (Content.Load ("ball")); animatedSprite = new AnimatedSprite (animatedSpriteSheet, 254, 254, 1, 4, 4, new Point (1, 0), 15); // Set Up a 2D Camera camera = new Camera2D (); ResetToInitialPositions (); //assuming a resolution change, need to update the sprite's "position" animatedSprite.Origin = camera.Position - animatedSpritePosition; animatedSprite.Position = screenCenter; } /// /// Reset the camera to the center of the tile grid /// and reset the position of the animted sprite /// private void ResetToInitialPositions () { //set up the 2D camera //set the initial position to the center of the //tile field camera.Position = new Vector2 (numTiles * 255); camera.Rotation = 0f; camera.Zoom = 1f; camera.MoveUsingScreenAxis = true; //the animated sprite has no concept of a camera, so //making the sprite camera relative is the job //of the game program animatedSpritePosition = camera.Position; animatedSprite.ScaleValue = animatedSpriteScale; animatedSprite.Position = screenCenter; CameraChanged (); } #endregion #region Update and Render /// /// Update the game world. /// /// Provides a snapshot of timing values. protected override void Update (GameTime gameTime) { HandleInput (); //Set the camera's state to Unchanged for this frame //this will save us from having to update visibility if the camera //does not move camera.ResetChanged (); //Call sample-specific input handling function HandleKeyboardInput ((float)gameTime.ElapsedGameTime.TotalSeconds); #if IPHONE HandleGamePadInput ((float)gameTime.ElapsedGameTime.TotalSeconds); #endif if (camera.IsChanged) { CameraChanged (); } //thottle the animation update speed to the frame animation //time accumulator += (float)gameTime.ElapsedGameTime.TotalSeconds; if (accumulator > animationTime) { animatedSprite.IncrementAnimationFrame (); accumulator -= animationTime; } base.Update (gameTime); } #region Input Handling Functions #if IPHONE /// /// Handle Game Pad input during Update /// public void HandleGamePadInput (float elapsed) { if (currentGamePadState.IsConnected) { //the left thumbstick moves the animated sprite around the world if ((Math.Abs (currentGamePadState.ThumbSticks.Left.X) > .1f) || (Math.Abs (currentGamePadState.ThumbSticks.Left.Y) > .1f)) { //Sprite movement is being updated relative to the camera //rotation animatedSpritePosition.X += (float)Math.Cos (-camera.Rotation) * currentGamePadState.ThumbSticks.Left.X * elapsed * MovementRate; animatedSpritePosition.Y += (float)Math.Sin (-camera.Rotation) * currentGamePadState.ThumbSticks.Left.X * elapsed * MovementRate; animatedSpritePosition.Y -= (float)Math.Cos (camera.Rotation) * currentGamePadState.ThumbSticks.Left.Y * elapsed * MovementRate; animatedSpritePosition.X -= (float)Math.Sin (camera.Rotation) * currentGamePadState.ThumbSticks.Left.Y * elapsed * MovementRate; //since the sprite position has changed, the Origin must be updated //on the animated sprite object animatedSprite.Origin = (camera.Position - animatedSpritePosition) / animatedSpriteScale.X; } //right thumbstick controls the camera position if ((Math.Abs (currentGamePadState.ThumbSticks.Right.X) > .1f) || (Math.Abs (currentGamePadState.ThumbSticks.Right.Y) > .1f)) { float dX = currentGamePadState.ThumbSticks.Right.X * elapsed * MovementRate; float dY = currentGamePadState.ThumbSticks.Right.Y * elapsed * MovementRate; camera.MoveRight (ref dX); camera.MoveUp (ref dY); } //the triggers control rotation if ((Math.Abs (currentGamePadState.Triggers.Left) > .1f) || (Math.Abs (currentGamePadState.Triggers.Right) > .1f)) { float dX = currentGamePadState.Triggers.Left * elapsed * RotationRate; dX += -currentGamePadState.Triggers.Right * elapsed * RotationRate; camera.Rotation += dX; } //the A and B buttons control zoom if ((currentGamePadState.Buttons.A == ButtonState.Pressed) || (currentGamePadState.Buttons.B == ButtonState.Pressed)) { float delta = elapsed * ZoomRate; if (currentGamePadState.Buttons.B == ButtonState.Pressed) delta = -delta; camera.Zoom += delta; if (camera.Zoom < .5f) camera.Zoom = .5f; if (camera.Zoom > 2f) camera.Zoom = 2f; } if ((currentGamePadState.Buttons.RightStick == ButtonState.Pressed) && (lastGamePadState.Buttons.RightStick == ButtonState.Released)) { ResetToInitialPositions (); } } } #endif /// /// Handle Keyboard input during Update /// public void HandleKeyboardInput (float elapsed) { //check for camera movement float dX = ReadKeyboardAxis (currentKeyboardState, Keys.Left, Keys.Right) * elapsed * MovementRate; float dY = ReadKeyboardAxis (currentKeyboardState, Keys.Down, Keys.Up) * elapsed * MovementRate; camera.MoveRight (ref dX); camera.MoveUp (ref dY); //check for animted sprite movement animatedSpritePosition.X += (float)Math.Cos (-camera.Rotation) * ReadKeyboardAxis (currentKeyboardState, Keys.A, Keys.D) * elapsed * MovementRate; animatedSpritePosition.Y += (float)Math.Sin (-camera.Rotation) * ReadKeyboardAxis (currentKeyboardState, Keys.A, Keys.D) * elapsed * MovementRate; animatedSpritePosition.X -= (float)Math.Sin (camera.Rotation) * ReadKeyboardAxis (currentKeyboardState, Keys.S, Keys.W) * elapsed * MovementRate; animatedSpritePosition.Y -= (float)Math.Cos (camera.Rotation) * ReadKeyboardAxis (currentKeyboardState, Keys.S, Keys.W) * elapsed * MovementRate; //since the sprite position has changed, the Origin must be updated //on the animated sprite object animatedSprite.Origin = (camera.Position - animatedSpritePosition) / animatedSpriteScale.X; //check for camera rotation dX = ReadKeyboardAxis (currentKeyboardState, Keys.E, Keys.Q) * elapsed * RotationRate; camera.Rotation += dX; //check for camera zoom dX = ReadKeyboardAxis (currentKeyboardState, Keys.X, Keys.Z) * elapsed * ZoomRate; //limit the zoom camera.Zoom += dX; if (camera.Zoom < .5f) camera.Zoom = .5f; if (camera.Zoom > 2f) camera.Zoom = 2f; //check for camera reset if (currentKeyboardState.IsKeyDown (Keys.R) && lastKeyboardState.IsKeyDown (Keys.R)) { ResetToInitialPositions (); } } /// /// This function is called when the camera's values have changed /// and is used to update the properties of the tiles and animated sprite /// public void CameraChanged () { //set rotation groundLayer.CameraRotation = detailLayer.CameraRotation = cloudLayer.CameraRotation = rockLayer.CameraRotation = animatedSprite.Rotation = camera.Rotation; //set zoom groundLayer.CameraZoom = detailLayer.CameraZoom = rockLayer.CameraZoom = camera.Zoom; animatedSprite.ScaleValue = animatedSpriteScale * camera.Zoom; cloudLayer.CameraZoom = camera.Zoom + 1.0f; //For an extra special effect, the camera zoom is figured into the cloud //alpha. The clouds will appear to fade out as camera zooms in. cloudLayer.Color = new Color (new Vector4 ( 1.0f, 1.0f, 1.0f, 2 / (2f * camera.Zoom + 1.0f))); //set position groundLayer.CameraPosition = camera.Position; detailLayer.CameraPosition = camera.Position; rockLayer.CameraPosition = camera.Position; //to acheive a paralax effect, scale down cloud movement cloudLayer.CameraPosition = camera.Position / 3.0f; //The animcated sprite's origin is set so that rotation //will occur around the camera center (accounting for scale) animatedSprite.Origin = (camera.Position - animatedSpritePosition) / animatedSpriteScale.X; //changes have been accounted for, reset the changed value so that this //function is not called unnecessarily camera.ResetChanged (); } #endregion /// /// This is called when the game should draw itself. /// /// Provides a snapshot of timing values. protected override void Draw (GameTime gameTime) { //since we're drawing in order from back to front, //depth buffer is disabled // TODO graphics.GraphicsDevice.RenderState.DepthBufferEnable = false; graphics.GraphicsDevice.Clear (Color.CornflowerBlue); //draw the background layers groundLayer.Color = Color.LightGray; groundLayer.Draw (spriteBatch); detailLayer.Draw (spriteBatch); rockLayer.Draw (spriteBatch); animatedSprite.Draw (spriteBatch, Color.AntiqueWhite, BlendState.AlphaBlend); //draw the clouds cloudLayer.Draw (spriteBatch); base.Draw (gameTime); } #endregion #region Handle Input /// /// Handles input for quitting the game. /// private void HandleInput () { lastKeyboardState = currentKeyboardState; #if IPHONE lastGamePadState = currentGamePadState; #endif currentKeyboardState = Keyboard.GetState (); #if IPHONE currentGamePadState = GamePad.GetState (PlayerIndex.One); // Check for exit. if (currentKeyboardState.IsKeyDown (Keys.Escape) || currentGamePadState.Buttons.Back == ButtonState.Pressed) { Exit (); } #endif } /// /// Uses a pair of keys to simulate a positive or negative axis input. /// private static float ReadKeyboardAxis (KeyboardState keyState, Keys downKey, Keys upKey) { float value = 0; if (keyState.IsKeyDown (downKey)) value -= 1.0f; if (keyState.IsKeyDown (upKey)) value += 1.0f; return value; } #endregion } }