建立自訂精靈類
前面我們已經能建立精靈和動畫,並且對精靈執行輸入控制(滑鼠和鍵盤),但是也許我們縱觀上一節那個程式,發現變數種類繁多不利於管理,各種控制語句都在update方法裡面,如果有幾十個精靈,那這個程式豈不是臃腫不堪,所以我們就需要自訂精靈類,讓各種精靈有序的工作,分門別類的進行管理,在XNA中還有一些遊戲組件,這些組件更加方便了我們的管理,遊戲組件將在下一節介紹,由於本節內容十分重要,所以為分為了兩節!
在我們的遊戲精靈中,雖然有很多精靈,但是說白了就只有兩個種類,一個 使用者控制的(如角色),一個是自動的(如敵人),但是這些精靈都擁有很多相同的屬性,例如都會移動,都有座標,都要繪製,都要更新,經過總結,他們的關係層次圖如下:
所以在這裡我們先在項目中添加一個新類,命名為Sprite,右鍵點擊項目選項-添加-類,並且在sprite.cs中加入這樣的包,以便使用XNA的功能
using Microsoft.Xna.Framework;using Microsoft.Xna.Framework.Graphics;
另外由於這個類只是基類,不是實際的類,所以我們修改他的類屬性為抽象 abstract屬性!
接下來就是要給類新增成員變數了,那麼該添加些什麼勒?我們在前面已經用到了很多了,這裡貼出代碼:
Texture2D textureImage; //各種精靈屬性的成員變數 protected Point frameSize; Point currentFrame; Point sheetSize; int collisionOffset; int timeSinceLastFrame = 0; int millisecondsPerFrame; const int defaultMillisecondsPerFrame = 16; protected Vector2 speed; protected Vector2 position;
可以看到類成員變數中有 位元影像,控制精靈位元影像輸出的三個point變數,控制精靈的幀率,速度,當前位置等等變數,應有盡有,這些都記載了精靈的屬性,其中一些有預設值,一些沒有,而且一些為protected屬性,這是為了子類更好的使用,接下來看看我們的建構函式:
public sprite(Texture2D textureImage, //建構函式1,有預設參數 Vector2 position, Point frameSize, int collisionOffset, Point currentFrame, Point sheetSize, Vector2 speed) : this(textureImage, position, frameSize, collisionOffset, currentFrame, sheetSize, speed, defaultMillisecondsPerFrame) { } public sprite(Texture2D textureImage, //建構函式2,沒有預設參數 Vector2 position, Point frameSize, int collisionOffset, Point currentFrame, Point sheetSize, Vector2 speed, int millisecondsPerFrame) { this.textureImage = textureImage; this.position = position; this.frameSize = frameSize; this.collisionOffset = collisionOffset; this.currentFrame = currentFrame; this.sheetSize = sheetSize; this.speed = speed; this.millisecondsPerFrame = millisecondsPerFrame; }
兩個建構函式,一個是有預設參數的,一個是沒有預設參數的,接下來看看該類需要什麼方法:
public virtual void Update(GameTime gameTime, Rectangle clientBounds) //精靈的繪製更新函數 { timeSinceLastFrame += gameTime.ElapsedGameTime.Milliseconds; if (timeSinceLastFrame > millisecondsPerFrame) { timeSinceLastFrame = 0; ++currentFrame.X; if (currentFrame.X >= sheetSize.X) { currentFrame.X = 0; ++currentFrame.Y; if (currentFrame.Y >= sheetSize.Y) currentFrame.Y = 0; } } }
這是update方法,就跟我們在Game類update方法中的一樣,進行精靈位元影像的位置更新,設計為虛函數是為了可以被子類重寫,另外參數列表中也多添加了一個矩形類的變數,這是為了在碰撞檢測的時候使用的!
public virtual void Draw(GameTime gameTime, SpriteBatch spriteBatch) //draw方法中需要spritebatch對象,所以需要傳遞 { spriteBatch.Draw(textureImage, position, new Rectangle(currentFrame.X * frameSize.X, currentFrame.Y * frameSize.Y, frameSize.X, frameSize.Y), Color.White, 0, Vector2.Zero, 1f, SpriteEffects.None, 0); }
這是我們的精靈類的draw方法,就是拷貝的原Game類的draw方法,繪製出精靈,由於在Game中已經有一個SpriteBatch變數,我們這裡沒有,所以需要傳遞一個spriteBatch變數,在繪製的時候使用!
public Rectangle collisionRect //返回精靈的封裝矩形,用於碰撞檢測 { get { return new Rectangle((int)position.X + collisionOffset, (int)position.Y + collisionOffset, frameSize.X - (collisionOffset * 2), frameSize.Y - (collisionOffset * 2)); } }
該方法返回精靈的封裝矩形,在碰撞檢測的時候需要
public abstract Vector2 direction //精靈移動方向,子類都不相同,所以應該設定成抽象 { get; }
該方法為direction的訪問器,擷取精靈的移動方向,具體如何使用稍後會講!
好了,我們的精靈基類就設計完成了,下面我們開始設計我們的角色類!
2:設計使用者控制的角色類
同樣的方法建立一個類,註明繼承自Sprite類,並且添加XNA的包,與上面不同的是這裡需要該類擷取到使用者的控制,所以還要多加一行
using Microsoft.Xna.Framework.Input;
這行就可以提供擷取使用者輸入的控制,然後添加建構函式,幾乎都跟基類一樣,一個一個賦值而已!
public UserControlledSprite( //帶預設參數的建構函式 調用了基類建構函式 Texture2D textureImage, Vector2 position, Point frameSize, int collisionOffset, Point currentFrame, Point sheetSize, Vector2 speed) : base(textureImage, position, frameSize, collisionOffset, currentFrame, sheetSize, speed) { } public UserControlledSprite( //一般的建構函式 調用了基類建構函式 Texture2D textureImage, Vector2 position, Point frameSize, int collisionOffset, Point currentFrame, Point sheetSize, Vector2 speed, int millisecondsPerFrame) : base( textureImage, position, frameSize, collisionOffset, currentFrame, sheetSize, speed, millisecondsPerFrame) { }
接下來先實現基類中的抽象方法drection:
public override Vector2 direction //遊戲方向的確定,由使用者輸入和自訂的速度共同決定 { get { Vector2 inputDirection = Vector2.Zero; if (Keyboard.GetState( ).IsKeyDown(Keys.Left)) inputDirection.X -= 1; if (Keyboard.GetState( ).IsKeyDown(Keys.Right)) inputDirection.X += 1; if (Keyboard.GetState( ).IsKeyDown(Keys.Up)) inputDirection.Y -= 1; if (Keyboard.GetState( ).IsKeyDown(Keys.Down)) inputDirection.Y += 1; return inputDirection * speed; } }
這個方向向量表示了精靈的移動距離,在以前我們按下左鍵精靈直接移動2,按多少次就移動多少個2,這樣就無法中途更改移動速度,所以這裡用方向乘以速度來決定位移量,如果沒有按鍵,返回的是0!
public override void Update(GameTime gameTime, Rectangle clientBounds) { position += direction; MouseState currMouseState = Mouse.GetState( ); //滑鼠控制 就不在使用方向了 if (currMouseState.X != prevMouseState.X || currMouseState.Y != prevMouseState.Y) { position = new Vector2(currMouseState.X, currMouseState.Y); } prevMouseState = currMouseState; if (position.X < 0) //保持精靈始終在視窗中間 position.X = 0; if (position.Y < 0) position.Y = 0; if (position.X > clientBounds.Width - frameSize.X) { position.X = clientBounds.Width - frameSize.X; } if (position.Y > clientBounds.Height - frameSize.Y) { position.Y = clientBounds.Height - frameSize.Y; } base.Update(gameTime, clientBounds); }
這裡我們也知道,滑鼠控制是要檢測上一幀滑鼠位置和當前幀有沒有發生改變,所以需要一個prevmousestate變數,這個需要在寫update方法的時候加入到類成員變數中,設為私人的!
由於draw方法不需要做改變,所以我們這裡就不需要重寫draw方法了,現在我們的角色類也就已經寫完了。
3:設計自動的遊戲精靈類
同上建立一個繼承自sprite的類,並且加入XNA的包,這裡不需要使用者輸入,所以可以不加第三個包,
這個類設計很簡單,我就不用多說了
class AutomatedSprite:sprite { public AutomatedSprite (Texture2D textureImage, Vector2 position, Point frameSize, int collisionOffset, Point currentFrame, Point sheetSize, Vector2 speed) : base( textureImage, position, frameSize, collisionOffset, currentFrame, sheetSize, speed) { } public AutomatedSprite( Texture2D textureImage, Vector2 position, Point frameSize, int collisionOffset, Point currentFrame, Point sheetSize, Vector2 speed, int millisecondsPerFrame) : base( textureImage, position, frameSize, collisionOffset, currentFrame, sheetSize, speed, millisecondsPerFrame) { } public override Vector2 direction { get { return speed; //他沒有方向的控制,傳回值直接就是速度 } } public override void Update(GameTime gameTime, Rectangle clientBounds) { position += direction; base.Update(gameTime, clientBounds); //別忘記調用基類的方法,以便不影響精靈位元影像的更新 } }
我們的三個類都設計完了,本節的工作也就完成了,為了需要我這裡把前面兩個類的完整代碼貼出來
Sprite類
using System;using System.Collections.Generic;using System.Linq;using System.Text;using Microsoft.Xna.Framework;using Microsoft.Xna.Framework.Graphics;namespace WindowsGame6{ abstract class sprite { Texture2D textureImage; //各種精靈屬性的成員變數 protected Point frameSize; Point currentFrame; Point sheetSize; int collisionOffset; int timeSinceLastFrame = 0; int millisecondsPerFrame; const int defaultMillisecondsPerFrame = 16; protected Vector2 speed; protected Vector2 position; public sprite(Texture2D textureImage, //建構函式1,有預設參數 Vector2 position, Point frameSize, int collisionOffset, Point currentFrame, Point sheetSize, Vector2 speed) : this(textureImage, position, frameSize, collisionOffset, currentFrame, sheetSize, speed, defaultMillisecondsPerFrame) { } public sprite(Texture2D textureImage, //建構函式2,沒有預設參數 Vector2 position, Point frameSize, int collisionOffset, Point currentFrame, Point sheetSize, Vector2 speed, int millisecondsPerFrame) { this.textureImage = textureImage; this.position = position; this.frameSize = frameSize; this.collisionOffset = collisionOffset; this.currentFrame = currentFrame; this.sheetSize = sheetSize; this.speed = speed; this.millisecondsPerFrame = millisecondsPerFrame; } public virtual void Update(GameTime gameTime, Rectangle clientBounds) //精靈的繪製更新函數 { timeSinceLastFrame += gameTime.ElapsedGameTime.Milliseconds; if (timeSinceLastFrame > millisecondsPerFrame) { timeSinceLastFrame = 0; ++currentFrame.X; if (currentFrame.X >= sheetSize.X) { currentFrame.X = 0; ++currentFrame.Y; if (currentFrame.Y >= sheetSize.Y) currentFrame.Y = 0; } } } public virtual void Draw(GameTime gameTime, SpriteBatch spriteBatch) //draw方法中需要spritebatch對象,所以需要傳遞 { spriteBatch.Draw(textureImage, position, new Rectangle(currentFrame.X * frameSize.X, currentFrame.Y * frameSize.Y, frameSize.X, frameSize.Y), Color.White, 0, Vector2.Zero, 1f, SpriteEffects.None, 0); } public abstract Vector2 direction //精靈移動方向,子類都不相同,所以應該設定成抽象 { get; } public Rectangle collisionRect //返回精靈的封裝矩形,用於碰撞檢測 { get { return new Rectangle((int)position.X + collisionOffset, (int)position.Y + collisionOffset, frameSize.X - (collisionOffset * 2), frameSize.Y - (collisionOffset * 2)); } } }}
UserControlledSprite類
using System;using System.Collections.Generic;using System.Linq;using System.Text;using Microsoft.Xna.Framework;using Microsoft.Xna.Framework.Graphics;using Microsoft.Xna.Framework.Input; //使用者控制的類,需要從裝置擷取資料namespace WindowsGame6{ class UserControlledSprite:sprite { private MouseState prevMouseState; public UserControlledSprite( //帶預設參數的建構函式 調用了基類建構函式 Texture2D textureImage, Vector2 position, Point frameSize, int collisionOffset, Point currentFrame, Point sheetSize, Vector2 speed) : base(textureImage, position, frameSize, collisionOffset, currentFrame, sheetSize, speed) { } public UserControlledSprite( //一般的建構函式 調用了基類建構函式 Texture2D textureImage, Vector2 position, Point frameSize, int collisionOffset, Point currentFrame, Point sheetSize, Vector2 speed, int millisecondsPerFrame) : base( textureImage, position, frameSize, collisionOffset, currentFrame, sheetSize, speed, millisecondsPerFrame) { } public override Vector2 direction //遊戲方向的確定,由使用者輸入和自訂的速度共同決定 { get { Vector2 inputDirection = Vector2.Zero; if (Keyboard.GetState( ).IsKeyDown(Keys.Left)) inputDirection.X -= 1; if (Keyboard.GetState( ).IsKeyDown(Keys.Right)) inputDirection.X += 1; if (Keyboard.GetState( ).IsKeyDown(Keys.Up)) inputDirection.Y -= 1; if (Keyboard.GetState( ).IsKeyDown(Keys.Down)) inputDirection.Y += 1; return inputDirection * speed; } } public override void Update(GameTime gameTime, Rectangle clientBounds) { position += direction; MouseState currMouseState = Mouse.GetState( ); if (currMouseState.X != prevMouseState.X || currMouseState.Y != prevMouseState.Y) { position = new Vector2(currMouseState.X, currMouseState.Y); } prevMouseState = currMouseState; if (position.X < 0) position.X = 0; if (position.Y < 0) position.Y = 0; if (position.X > clientBounds.Width - frameSize.X) { position.X = clientBounds.Width - frameSize.X; } if (position.Y > clientBounds.Height - frameSize.Y) { position.Y = clientBounds.Height - frameSize.Y; } base.Update(gameTime, clientBounds); } }}
下一節我們介紹如何利用遊戲組件,把這些類有機的組合在一起,他們的使用會讓遊戲程式變得那麼的物件導向化!