Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 95 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
95
Dung lượng
1 MB
Nội dung
XNA Project: Petzold.Phone.Xna File: Button.cs (excerpt) public bool ProcessTouch(TouchLocation touch) { bool touchHandled = false; bool isInside = Destination.Contains((int)touch.Position.X, (int)touch.Position.Y); switch (touch.State) { case TouchLocationState.Pressed: if (isInside) { isPressed = true; touchId = touch.Id; touchHandled = true; } break; case TouchLocationState.Moved: if (touchId.HasValue && touchId.Value == touch.Id) { isPressed = isInside; touchHandled = true; } break; case TouchLocationState.Released: if (touchId.HasValue && touchId.Value == touch.Id) { if (isInside && Click != null) Click(this, EventArgs.Empty); touchId = null; isPressed = false; touchHandled = true; } break; } return touchHandled; } If the finger is released when it is inside the Destination rectangle, then Button fires a Click event. The Draw override draws the button, which is basically a border consisting of a white rectangle with a somewhat smaller black rectangle on top, with the text string: XNA Project: Petzold.Phone.Xna File: Button.cs (excerpt) public override void Draw(GameTime gameTime) { 903 spriteBatch.Begin(); if (isPressed) { // Draw reverse-video background spriteBatch.Draw(tinyTexture, Destination, Color.White); } else { // Draw button border and background Rectangle rect = Destination; spriteBatch.Draw(tinyTexture, rect, Color.White); rect.Inflate(-3, -3); spriteBatch.Draw(tinyTexture, rect, Color.Black); } // Draw button text if (SpriteFont != null && !String.IsNullOrEmpty(Text)) spriteBatch.DrawString(SpriteFont, Text, textPosition, isPressed ? Color.Black : Color.White); spriteBatch.End(); base.Draw(gameTime); } ColorBlock, on the other hand, is part of the PhingerPaint program, and it does not implement the IProcessTouch interface. Here it is in its entirety: XNA Project: PhingerPaint File: ColorBlock.cs (complete) using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input.Touch; namespace PhingerPaint { public class ColorBlock : DrawableGameComponent { SpriteBatch spriteBatch; Texture2D block; public ColorBlock(Game game) : base(game) { } public Color Color { set; get; } public Rectangle Destination { set; get; } public bool IsSelected { set; get; } 904 public override void Initialize() { base.Initialize(); } protected override void LoadContent() { spriteBatch = new SpriteBatch(this.GraphicsDevice); block = new Texture2D(this.GraphicsDevice, 1, 1); block.SetData<uint>(new uint[] { Color.White.PackedValue }); base.LoadContent(); } public override void Update(GameTime gameTime) { base.Update(gameTime); } public override void Draw(GameTime gameTime) { Rectangle rect = Destination; spriteBatch.Begin(); spriteBatch.Draw(block, rect, IsSelected ? Color.White : Color.DarkGray); rect.Inflate(-6, -6); spriteBatch.Draw(block, rect, Color); spriteBatch.End(); base.Draw(gameTime); } } } ColorBlock relies on three public properties—Color, Destination, and IsSelected—to govern its appearance. Notice during the LoadContent method that it too creates a Texture2D that is exactly one pixel in size. This block object is drawn twice in the Draw method. First it’s drawn to the entire dimensions of the Destination rectangle as either dark gray or white, depending on the value of IsSelected. Then it’s contracted in size by six pixels on all sides and drawn again based on the Color property. The PhingerPaint Canvas The components created by PhingerPaint are stored as fields along with some of the other expected information: 905 XNA Project: PhingerPaint File: Game1.cs (excerpt showing fields) public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; Texture2D canvas; Vector2 canvasSize; Vector2 canvasPosition; uint[] pixels; List<float> xCollection = new List<float>(); Button clearButton, saveButton; string filename; List<ColorBlock> colorBlocks = new List<ColorBlock>(); Color drawingColor = Color.Blue; int? touchIdToIgnore; … } The List stores the 12 ColorBlock components and drawingColor is the currently selected color. The main canvas is, of course, the Texture2D object called canvas and the pixels array stores the texture’s pixels. The xCollection object is repeatedly reused in calls to the RoundCappedLine class that I discussed in Chapter 21. The constructor sets the back buffer for portrait mode, but it sets the height to 768 rather than 800. This leaves enough space for the status bar so the back buffer is allowed to display in its full size: XNA Project: PhingerPaint File: Game1.cs (excerpt) public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; // Frame rate is 30 fps by default for Windows Phone. TargetElapsedTime = TimeSpan.FromTicks(333333); // Set to portrait mode but leave room for status bar graphics.PreferredBackBufferWidth = 480; graphics.PreferredBackBufferHeight = 768; } The Initialize override is responsible for creating the Button and ColorBlack components, partially initializing them, and adding them to the Components collection of the Game class. This ensures that they get their own calls to Initialize, LoadContent, Update, and Draw. 906 XNA Project: PhingerPaint File: Game1.cs (excerpt) protected override void Initialize() { // Create Button components clearButton = new Button(this, "clear"); clearButton.Click += OnClearButtonClick; this.Components.Add(clearButton); saveButton = new Button(this, "save"); saveButton.Click += OnSaveButtonClick; this.Components.Add(saveButton); // Create ColorBlock components Color[] colors = { Color.Red, Color.Green, Color.Blue, Color.Cyan, Color.Magenta, Color.Yellow, Color.Black, new Color(0.2f, 0.2f, 0.2f), new Color(0.4f, 0.4f, 0.4f), new Color(0.6f, 0.6f, 0.6f), new Color(0.8f, 0.8f, 0.8f), Color.White }; foreach (Color clr in colors) { ColorBlock colorBlock = new ColorBlock(this); colorBlock.Color = clr; colorBlocks.Add(colorBlock); this.Components.Add(colorBlock); } base.Initialize(); } The remainder of the initialization of the components occurs during the LoadContent override when the font can be loaded for the Button components. It seems a little odd to set a back buffer to an explicit size in the constructor, and yet calculate dimensions more abstractly in the LoadContent method, but it’s usually best to keep code as generalized and as flexible as possible. XNA Project: PhingerPaint File: Game1.cs (excerpt) protected override void LoadContent() { spriteBatch = new SpriteBatch(GraphicsDevice); Rectangle clientBounds = this.GraphicsDevice.Viewport.Bounds; SpriteFont segoe14 = this.Content.Load<SpriteFont>("Segoe14"); // Set up Button components clearButton.SpriteFont = segoe14; saveButton.SpriteFont = segoe14; Vector2 textSize = segoe14.MeasureString(clearButton.Text); 907 int buttonWidth = (int)(2 * textSize.X); int buttonHeight = (int)(1.5 * textSize.Y); clearButton.Destination = new Rectangle(clientBounds.Left + 20, clientBounds.Bottom - 2 - buttonHeight, buttonWidth, buttonHeight); saveButton.Destination = new Rectangle(clientBounds.Right - 20 - buttonWidth, clientBounds.Bottom - 2 - buttonHeight, buttonWidth, buttonHeight); int colorBlockSize = clientBounds.Width / (colorBlocks.Count / 2) - 2; int xColorBlock = 2; int yColorBlock = 2; foreach (ColorBlock colorBlock in colorBlocks) { colorBlock.Destination = new Rectangle(xColorBlock, yColorBlock, colorBlockSize, colorBlockSize); xColorBlock += colorBlockSize + 2; if (xColorBlock + colorBlockSize > clientBounds.Width) { xColorBlock = 2; yColorBlock += colorBlockSize + 2; } } canvasPosition = new Vector2(0, 2 * colorBlockSize + 6); canvasSize = new Vector2(clientBounds.Width, clientBounds.Height - canvasPosition.Y - buttonHeight - 4); } The LoadContent method concludes by calculating a location and size for the Texture2D used as a canvas. But LoadContent doesn’t take the final step in actually creating that Texture2D because the LoadContent method might soon be followed by a call to the OnActivated override which signals either that the program is starting up, or it’s returning from a tombstoned state. It is important for PhingerPaint to implement tombstoning because users tend to become enraged when their creative efforts disappear from the screen. For that reason the OnDeactivated override saves the image to the PhoneApplicationService in PNG format, and the OnActivated override gets it back out. I chose PNG for this process because it’s a lossless compression format, and I felt that the image should be restored exactly to its original state. To slightly ease the process of saving and loading Texture2D object, I used the methods in the Texture2DExtensions class in the Petzold.Phone.Xna library that I described in the previous 908 chapter. The OnActivated method calls LoadFromPhoneService to obtain a saved Texture2D, and if that’s not available, only then does it create a new one and clear it. The use of the PhoneApplicationService class requires references to the System.Windows and Microsoft.Phone assemblies, and a using directive for Microosft.Phone.Shell. XNA Project: PhingerPaint File: Game1.cs (excerpt) protected override void OnActivated(object sender, EventArgs args) { // Recover from tombstoning bool newlyCreated = false; canvas = Texture2DExtensions.LoadFromPhoneServiceState(this.GraphicsDevice, "canvas"); if (canvas == null) { // Otherwise create new Texture2D canvas = new Texture2D(this.GraphicsDevice, (int)canvasSize.X, (int)canvasSize.Y); newlyCreated = true; } // Create pixels array pixels = new uint[canvas.Width * canvas.Height]; canvas.GetData<uint>(pixels); if (newlyCreated) ClearPixelArray(); // Get drawing color from State, initialize selected ColorBlock if (PhoneApplicationService.Current.State.ContainsKey("color")) drawingColor = (Color)PhoneApplicationService.Current.State["color"]; foreach (ColorBlock colorBlock in colorBlocks) colorBlock.IsSelected = colorBlock.Color == drawingColor; base.OnActivated(sender, args); } The OnDeactivated override stores the Texture2D using the SaveToPhoneServiceState extension method: XNA Project: PhingerPaint File: Game1.cs (excerpt) protected override void OnDeactivated(object sender, EventArgs args) { PhoneApplicationService.Current.State["color"] = drawingColor; canvas.SaveToPhoneServiceState("canvas"); base.OnDeactivated(sender, args); } 909 If the program is starting up, OnActivated calls a method named ClearPixelArray: XNA Project: PhingerPaint File: Game1.cs (excerpt) void ClearPixelArray() { for (int y = 0; y < canvas.Height; y++) for (int x = 0; x < canvas.Width; x++) { pixels[x + canvas.Width * y] = Color.GhostWhite.PackedValue; } canvas.SetData<uint>(pixels); } void OnClearButtonClick(object sender, EventArgs e) { ClearPixelArray(); } You’ll also notice the Click event handler for the “clear” Button also calls this method. As you’ll recall, the Button class fires the Click event based on touch input, and Button gets touch input when the parent Game class calls the ProcessTouch method from its own Update override. This means that this OnClearButtonClick method is actually called during a call to the Update override of this class. When the user presses the Button labeled “save” the program must display some kind of dialog box to let the user type in a filename. An XNA program can get keyboard input in one of two ways: a low-level approach involving Keyboard and a high-level approach by calling the Guide.BeginShowKeyboardInput method in the Microsoft.Xna.Framework.GamerServices namespace. I chose the high-level option. Guide.BeginShowKeyboardInput wants some initialization information and a callback function, so the method fabricates a unique filename from the current date and time: XNA Project: PhingerPaint File: Game1.cs (excerpt) void OnSaveButtonClick(object sender, EventArgs e) { DateTime dt = DateTime.Now; filename = String.Format("PhingerPaint-{0:D2}-{1:D2}-{2:D2}-{3:D2}-{4:D2}-{5:D2}", dt.Year % 100, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second); Guide.BeginShowKeyboardInput(PlayerIndex.One, "phinger paint save file", "enter filename:", filename, KeyboardCallback, null); } 910 The Guide.BeginShowKeyboardInput call causes the program to receive a call to OnDeactivated, after which the following screen is displayed: The only parts of this screen you can customize are the text strings in the headings and the initial text in the text-entry box. The screen looks much better in portrait mode than in landscape mode. In landscape mode, all the text headings, the text-entry box, and the on- screen keyboard are re-oriented but the two buttons are not, and the combination looks very peculiar. One look at it and you might never call Guide.BeginShowKeyboardInput from a landscape-mode program! When either the “OK” or “Cancel” button is clicked, the program is re-activated and the callback function in PhingerPaint is called: XNA Project: PhingerPaint File: Game1.cs (excerpt) void KeyboardCallback(IAsyncResult result) { filename = Guide.EndShowKeyboardInput(result); } Your program should assume that this callback function is being called asynchronously (as the argument implies) so you shouldn’t do a whole lot here except call Guide.EndShowKeyboardInput and save the return value in a field. If the user pressed the “OK” 911 button, then the return value is the final text entered into the text-entry field. If the user pressed “Cancel” or the Back button, then Guide.EndShowKeyboardInput returns null. A good place to do something with that return value is during the next call to the program’s Update override: XNA Project: PhingerPaint File: Game1.cs (excerpt) protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); // If the Save File dialog box has returned, save the image if (!String.IsNullOrEmpty(filename)) { canvas.SaveToPhotoLibrary(filename); filename = null; } … } Notice that the logic checks if the filename field is non-null and non-empty but at the end it sets the filename field back to null to ensure that it’s saved only once. SaveToPhotoLibrary is not a real method of the Texture2D class! It’s another extension method in the Texture2DExtensions class in the Petzold.Phone.Xna library. XNA Project: Petzold.Phone.Xna File: Texture2DExtensions.cs (excerpt) public static void SaveToPhotoLibrary(this Texture2D texture, string filename) { MemoryStream memoryStream = new MemoryStream(); texture.SaveAsJpeg(memoryStream, texture.Width, texture.Height); memoryStream.Position = 0; MediaLibrary mediaLibrary = new MediaLibrary(); mediaLibrary.SavePicture(filename, memoryStream); memoryStream.Close(); } This is the standard code for saving a Texture2D to the Saved Pictures album of the phone’s photo library. Although PhingerPaint uses the PNG format when saving the image during tombstoning, pictures saved to the photo library must be JPEG. The SaveAsJpeg method saves the whole image to a MemoryStream, and then the MemoryStream position is reset and it’s passed to the SavePicture method of MediaLibrary with a filename. 912 [...]... actual phone, and you’re running the desktop Zune software so Visual Studio can communicate with the phone, this code will raise an exception When Zune is running it wants exclusive access to the phone s media library You’ll need to terminate the Zune program and instead run the WPDTPTConnect tool, either WPDTPTConnect32.exe or WPDTPTConnect64.exe depending on whether you run 32-bit or 64-bit Windows. .. just terrible I wrote the first XNA version for the Zune HD before I had an actual Windows Phone, and then I ported that version to the one I’ll show you here SpinPaint comes up with a white disk that rotates 12 times per minute You’ll also notice that the title of the program cycles through a series of colors every 10 seconds: 916 When you touch the disk, it paints with that title color as if your... the back buffer for portrait mode, but like PhingerPaint it sets the height to 76 8 rather than 800 to make room for the status bar: XNA Project: SpinPaint File: Game1.cs (excerpt) public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; // Frame rate is 30 fps by default for Windows Phone TargetElapsedTime = TimeSpan.FromTicks(333333); // Portrait, but allow room... gameTime) { … // Disk rotates every 5 seconds double seconds = gameTime.TotalGameTime.TotalSeconds; currentAngle = (float)(2 * Math.PI * seconds / 5); // Colors cycle every 10 seconds 923 float fraction = (float)(6 * (seconds % 10) / 10) ; if (fraction < 1) currentColor = new else if (fraction < 2) currentColor = new else if (fraction < 3) currentColor = new else if (fraction < 4) currentColor = new else... } A Little Tour Through SpinPaint SpinPaint has an unusual genesis I wrote the first version one morning while attending a twoday class on programming for Microsoft Surface—those coffee-table computers designed for public places That version was written for the Windows Presentation Foundation and could be used by several people sitting around the machine I originally wanted to have a Silverlight version... TextureExtensions class in the Petzold .Phone. Xna library If there’s nothing to retrieve, then the program is starting up fresh and a new Texture2D needs to be created XNA Project: SpinPaint File: Game1.cs (excerpt) protected override void OnActivated(object sender, EventArgs args) { // Recover from tombstoning bool newlyCreated = false; diskTexture = Texture2DExtensions.LoadFromPhoneServiceState(this.GraphicsDevice,... disk is moving below it, but the painted line is also flipped around the horizontal and vertical axes: As you continue to paint, you can get some fancy designs: 9 17 Obviously you’ll want to press the “save” button to save the result to the phone s photo library, and later email it to your friends As with the PhingerPaint program, you can use up to four fingers for simultaneous drawing, and that’s why... a destination spot, and there is no way to move multiple cards in one shot There is no undo and no hints My coding for PhreeCell began not with an XNA program but with a Windows Presentation Foundation program to generate a single 104 0 × 448 bitmap containing all 52 playing cards, each of which is 96 pixels wide and 112 pixels tall This program uses mostly TextBlock objects to adorn a Canvas with numbers,... PhreeCell File: Game1.cs (excerpt) public Game1() { graphics = new GraphicsDeviceManager(this); graphics.IsFullScreen = true; Content.RootDirectory = "Content"; // Frame rate is 30 fps by default for Windows Phone TargetElapsedTime = TimeSpan.FromTicks(333333); graphics.IsFullScreen = true; 931 graphics.PreferredBackBufferWidth = 800; graphics.PreferredBackBufferHeight = 480; // Enable gestures TouchPanel.EnabledGestures... integer index 0 through 52 This required a bit of rather boring code: XNA Project: PhreeCell File: Game1.cs (excerpt) protected override void OnDeactivated(object sender, EventArgs args) { PhoneApplicationService appService = PhoneApplicationService.Current; // Save piles integers List[] piles = new List[8]; for (int i = 0; i < piles.Length; i++) { piles[i] = new List(); foreach (CardInfo cardInfo . clear it. The use of the PhoneApplicationService class requires references to the System .Windows and Microsoft .Phone assemblies, and a using directive for Microosft .Phone. Shell. XNA Project:. Petzold .Phone. Xna library that I described in the previous 908 chapter. The OnActivated method calls LoadFromPhoneService. GraphicsDeviceManager(this); Content.RootDirectory = "Content"; // Frame rate is 30 fps by default for Windows Phone. TargetElapsedTime = TimeSpan.FromTicks(333333); // Set to portrait mode but leave room