2D / 3D 遊戲程式設計入門 使用 XNA 3.0 與 C# 2D圖形與字型的呈現
本章目的 介紹XNA支援的 2D圖形格式 介紹2D圖形的應用範圍 以多個範例來示範在XNA架構下2D圖形和字型的呈現方式
2D 圖形的應用範圍 紋理圖(Texture) 精靈圖(Sprite) 小張的背景拼圖 大張的背景圖
紋理圖 展開的UV貼圖
2D角色精靈圖
無接縫2D圖形
大張捲動的背景圖
XNA可輸入的 2D圖形格式 .BMP取自Bitmap的縮寫,是無壓縮的點陣圖形檔。 .DDS 是可以包含有 alpha 值的圖形檔案。也可以是有六張貼圖的立體紋理圖 (cube map) .DIB與BMP相似,所以BMP也被稱為DIB。 .HDR每一個像素除了有RGB資訊外,還有改點的亮度資訊。 .JPG全彩及灰階圖形資料標準壓縮檔。採用失真壓縮演算法。 .PFM一種字型格式。 .PNGPNG 是包含有 alpha 值的圖形檔案。 .PPM可移植的像素映射位圖檔。 .TGA全稱Truevision Targa,包含有 alpha 值的圖形檔案。
MipMap 圖
一個典型的2D圖形上載與繪出方式 宣告 上載 (更新) 繪出 public class Game1 :Microsof.Xna.Framework.Game { // 宣告 一個 Texture2D 參照 Texture2D mySpriteTexture; … protected override void LoadContent() // 上載一張2D圖形 mySpriteTexture = Content.Load<Texture2D>("png-0001"); Protected override void Draw(GameTime gameTime) // 繪出 Texture2D spriteBatch.Begin(); Vector2 pos = new Vector2(100, 100); spriteBatch.Draw(mySpriteTexture, pos, Color.White); spriteBatch.End(); 宣告 上載 (更新) 繪出
呈現出一個2D圖形 protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); // TODO: Add your drawing code here spriteBatch.Begin(); Vector2 pos = new Vector2(0, 0); spriteBatch.Draw( mySpriteTexture, pos, // 位置 Color.White); // 過濾顏色 也就是完全不過濾 spriteBatch.End(); base.Draw(gameTime); }
Draw XNA提供: 七種繪出紋理圖的方法 和三種畫出字串的方法 範例一:上載並呈現一張2D圖形(4-9~4-11) 載入一張2D圖在content中 宣告2D圖之變數於Game1 Load 2D圖於LoadContent() 繪圖於Draw()
縮放或呈現部分 2D 圖形 (範例一及範例二) // 方式 1 :目的地位置 Vector2 pos = new Vector2(0, 0); spriteBatch.Draw(mySpriteTexture, pos, // 位置 Color.White); // 過濾顏色 也就是完全不過濾 // 方式 2 :改變 過濾顏色 pos.X = 150; spriteBatch.Draw(mySpriteTexture, pos, Color.Red); // 只要紅色
縮放或呈現部分 2D 圖形 // 方式 3 :標示目的地的位置和寬高 Rectangle rec = new Rectangle(300, 10, 43, 64); spriteBatch.Draw(mySpriteTexture, rec, Color.White); // 方式 4 :標示目的地位置寬高和來源矩形的位置寬高 Rectangle rec2 = new Rectangle(400, 10, 340, 210); Rectangle rec_SRC = new Rectangle(40, 20, 380, 230); spriteBatch.Draw(mySpriteTexture, rec2, rec_SRC, Color.White);
範例三:2D圖形的旋轉 宣告一Global variable “Angle”於Game1 在Update()中改變Angle的值 於Draw()中繪出旋轉後的圖形
旋轉的 2D 圖形(範例三) Rectangle recDest = new Rectangle(graphics.GraphicsDevice.Viewport.Width / 2, graphics.GraphicsDevice.Viewport.Height / 2, mySpriteTexture.Width, mySpriteTexture.Height); spriteBatch.Draw(mySpriteTexture, // 2D Texture recDest, // 目的區 的 矩形區塊 null, // 來源區 的 矩形區塊 Color.White, // 顏色 濾鏡 MathHelper.ToRadians(Angle), // 旋轉徑度 new Vector2(mySpriteTexture.Width / 2, mySpriteTexture.Height / 2), // 2D Texture旋轉中心點 SpriteEffects.None, // 旋轉效果 0.6f); // 圖層深度 0.0 ~ 1.0 (後)
SpriteSortMode.BackToFront
SpriteSortMode.FrontToBack
範例四:彈跳得2D圖形 如何實作一個精靈圖類別: 專案 加入類別 輸入檔案名稱,選擇『加入』 加入程式碼
範例四:彈跳得2D圖形 namespace WindowsGame1 { class ClassSprite { public Texture2D texture; // 2D 紋理圖 public Vector2 position; // 2D 紋理圖 的位置 private Vector2 screenSize; // 視窗寬高 public Vector2 velocity = Vector2.Zero; // 2D 紋理圖 的位移速度 public ClassSprite(Texture2D texture, Vector2 position, Vector2 screenSize) { this.texture = texture; this.position = position; this.screenSize = screenSize; }
範例四:彈跳得2D圖形 // 移動 public void Move() { // 右緣 碰到 視窗右邊了 if (position.X + texture.Width + velocity.X > screenSize.X) velocity.X = -velocity.X; // 下緣 碰到 視窗底邊了 if (position.Y + texture.Height + velocity.Y > screenSize.Y) velocity.Y = -velocity.Y; // 左緣 碰到 視窗左邊了 if (position.X + velocity.X < 0) // 上緣 碰到 視窗上邊了 if (position.Y + velocity.Y < 0) position += velocity; } } }
範例四:彈跳得2D圖形 protected override void LoadContent() { // Create a new SpriteBatch, which can be used to draw textures. spriteBatch = new SpriteBatch(GraphicsDevice); // TODO: use this.Content to load your game content here Texture2D texture = Content.Load<Texture2D>("CD");// 上載圖形 // 產生精靈圖物件 mySprite1 = new ClassSprite(texture, new Vector2(0f, 0f), new Vector2(graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height)); mySprite1.velocity = new Vector2(5, 5); // 設位移速度 }
範例四:彈跳得2D圖形 protected override void Update(GameTime gameTime) { // Allows the game to exit if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); // TODO: Add your update logic here mySprite1.Move(); // 移動精靈圖物件 base.Update(gameTime); }
範例四:彈跳得2D圖形 protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); // TODO: Add your drawing code here spriteBatch.Begin(SpriteBlendMode.AlphaBlend); spriteBatch.Draw(mySprite1.texture, mySprite1.position, Color.White); spriteBatch.End(); base.Draw(gameTime); }
範例五:互相碰撞的2D圖形 定義『碰撞』 宣告兩個Sprite物件 設定位置及速度 處理碰撞的情況 繪圖
兩個矩形是否在水平方向 (X軸) 重疊 如果 X2 > X3 而且 X4 > X1
兩個矩形是否在垂直方向 (Y軸) 重疊
範例五:互相碰撞的2D圖形(新增mthod) public bool Collides(ClassSprite other) { // 檢查是否 碰撞 return (this.position.X + texture.Width > other.position.X && this.position.X < other.position.X + other.texture.Width && this.position.Y + texture.Height > other.position.Y && this.position.Y < other.position.Y + other.texture.Height); }
範例五:互相碰撞的2D圖形 namespace WindowsGame1 { /// <summary> /// This is the main type for your game /// </summary> public class Game1 : Microsoft.Xna.Framework.Game GraphicsDeviceManager graphics; SpriteBatch spriteBatch; ClassSprite mySprite1; // 精靈圖物件參照 ClassSprite mySprite2; // 精靈圖物件參照 ………
範例五:互相碰撞的2D圖形 protected override void LoadContent() { ……… mySprite1 = new ClassSprite(texture, new Vector2(0f, 0f), new Vector2(graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height)); mySprite1.velocity = new Vector2(5, 5); // 設位移速度 mySprite2 = new ClassSprite(texture, new Vector2(300f, 200f), mySprite2.velocity = new Vector2(-8, -5); }
範例五:互相碰撞的2D圖形 protected override void Update(GameTime gameTime) { // Allows the game to exit if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); // TODO: Add your update logic here mySprite1.Move(); // 移動精靈圖物件 mySprite2.Move(); // 移動精靈圖物件 if (mySprite1.Collides(mySprite2)) { Vector2 tempVelocity = mySprite1.velocity; mySprite1.velocity = mySprite2.velocity; mySprite2.velocity = tempVelocity; } base.Update(gameTime);
範例五:互相碰撞的2D圖形 protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); // TODO: Add your drawing code here spriteBatch.Begin(SpriteBlendMode.AlphaBlend); spriteBatch.Draw(mySprite1.texture, mySprite1.position, Color.White); spriteBatch.Draw(mySprite2.texture, mySprite2.position, Color.White); spriteBatch.End(); base.Draw(gameTime); }
使用無接縫圖佈滿視窗客戶區
範例六:無接縫貼圖 在Game1.cs宣告一個global variable叫BX_Texture 在LoadContent內上載2D圖形 利用視窗與圖形的長度與寬度計算Do Loop 的巡迴次數,並於Draw中繪圖
範例六:無接縫貼圖 利用視窗與圖形的長度與寬度計算Do Loop 的巡迴次數,並於Draw中繪圖 for (int i = 0; i <= W / BG_Texture.Width; i++) // i 是 X 方向 要貼幾次 { for (int j = 0; j <= H / BG_Texture.Height; j++) // j 是 Y 方向 要貼幾次 Vector2 position = new Vector2(i * BG_Texture.Width, j * BG_Texture.Height); // 算出要 貼上的位置 spriteBatch.Draw(BG_Texture, position, Color.White); }
使用捲動的無接縫圖佈滿視窗客戶區
範例七:捲動的無接縫貼圖 在Game1.cs宣告一個global variable叫BX_Texture 在LoadContent內上載2D圖形 加入一個global variable為橫向與縱向的偏移植Offset_X和Offset_Y,並於Update()中修改其值 利用視窗與圖形的長度與寬度加上偏移值後計算Do Loop 的巡迴次數,並於Draw()中繪圖
範例七:捲動的無接縫貼圖 protected override void Update(GameTime gameTime) { // Allows the game to exit if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); // TODO: Add your update logic here Offset_X += -1; // 向左 Offset_Y += 0; // 不增加 base.Update(gameTime); }
範例七:捲動的無接縫貼圖 protected override void Draw(GameTime gameTime) { ……. int W = graphics.GraphicsDevice.Viewport.Width; int H = graphics.GraphicsDevice.Viewport.Height; spriteBatch.Begin(); for (int i = -1; i <= W / BG_Texture.Width; i++) // i 是 X 方向 要貼幾次 { for (int j = -1; j <= H / BG_Texture.Height; j++) // j 是 Y 方向 要貼幾次 { Vector2 position = new Vector2( i * BG_Texture.Width + (Offset_X % BG_Texture.Width), j * BG_Texture.Height + (Offset_Y % BG_Texture.Height)); // 算出要 貼上的位置 spriteBatch.Draw(BG_Texture, position, Color.White); } } spriteBatch.End(); base.Draw(gameTime); }
範例八:2D精靈與捲動背景 public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; Texture2D BG_Texture; int Offset_X = 0; // X 軸 偏移値 int Offset_Y = 0; // Y 軸 偏移値 Texture2D mySpriteTexture; Rectangle[,] srcRect = new Rectangle[4, 4]; int pcW = 85; // 主角的寬 int pcH = 153; // 主角的高 int Dir = 2; // 走路的方向 int Seq = 0; // 走路的第幾個動作 bool pcStop = true; // 主角 停止走路 KeyboardState oldState; double StepDuration = 0; …........... }
範例八:2D精靈與捲動背景 public class Game1 : Microsoft.Xna.Framework.Game { …………. public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; this.IsMouseVisible = true; this.Window.AllowUserResizing = true; this.Window.Title = "行走中的小王子 (↑↓←→空白鍵)"; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) srcRect[i, j] = new Rectangle(j * pcW, i * pcH, pcW, pcH); }
範例八:2D精靈與捲動背景 protected override void Update(GameTime gameTime) {………… // TODO: Add your update logic here KeyboardState newState; // 宣告一個 KeyboardState 結構的變數 newState = Keyboard.GetState(); //得到目前鍵盤全部按鍵的狀況 if (newState.IsKeyDown(Keys.Escape)) this.Exit(); //判斷Esc鍵是否已經被按下 else if (newState.IsKeyDown(Keys.Up) && Dir != 1) { Dir = 1; Seq = 0; pcStop = false; } else if (newState.IsKeyDown(Keys.Down) && Dir != 0) { Dir = 0; Seq = 0; pcStop = false; } else if (newState.IsKeyDown(Keys.Right) && Dir != 2) { Dir = 2; Seq = 0; pcStop = false; } else if (newState.IsKeyDown(Keys.Left) && Dir != 3) { Dir = 3; Seq = 0; pcStop = false; } else if (newState.IsKeyDown(Keys.Space) && oldState.IsKeyUp(Keys.Space)) { pcStop = !pcStop; } else if (!pcStop) { StepDuration += gameTime.ElapsedGameTime.TotalMilliseconds; if (StepDuration > 300) { StepDuration = 0; Seq++; // 下一張 Seq = Seq % 4; // 每一方向只有四張 } } oldState = newState; if (pcStop) { } else if (Dir == 1) // 向下 Offset_Y += 1; else if (Dir == 0) // 向上 Offset_Y -= 1; else if (Dir == 2) // 向左 Offset_X -= 1; else if (Dir == 3) // 向右 Offset_X += 1; base.Update(gameTime);
範例八:2D精靈與捲動背景 protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); // TODO: Add your drawing code here int W = graphics.GraphicsDevice.Viewport.Width; int H = graphics.GraphicsDevice.Viewport.Height; spriteBatch.Begin(); for (int i = -1; i <= W / BG_Texture.Width+1; i++) // i 是 X 方向 要貼幾次 { for (int j = -1; j <= H / BG_Texture.Height+1; j++) // j 是 Y 方向 要貼幾次 { Vector2 position = new Vector2( i * BG_Texture.Width + (Offset_X % BG_Texture.Width), j * BG_Texture.Height + (Offset_Y % BG_Texture.Height)); // 算出要 貼上的位置 spriteBatch.Draw(BG_Texture, position, Color.White); } } Rectangle DesRect = new Rectangle( this.Window.ClientBounds.Width / 2 - pcW / 2, this.Window.ClientBounds.Height / 2 - pcH / 2, pcW, pcH); spriteBatch.Draw(mySpriteTexture, DesRect, srcRect[Dir, Seq], Color.White); spriteBatch.End(); base.Draw(gameTime); } } }
有16個小圖的角色圖
範例九: 2D 字型 新增XNA遊戲方案 Contnet(^-R); 加入; 新增項目; 選用Sprite Font範本,並將名稱改為Courier New.spritefont; 加入 系統產生新的文字檔Courier New.spritefont在Content下
加入字型項
Courier New.spritefont 字型項目
範例九: 2D 字型 說明page 4-35 XML格式檔案 宣告global variable(Font1)在Game1 Load Font 於LoadContent() 設定字串的內容及要寫的位置 使用spriteBatch.DrawString在draw()來顯示字串。 說明三種spriteBatch.DrawString的格式(4-37~4-39)
SpriteBatch .DrawString() 第一種格式:(簡易型) SpriteBatch.DrawString (SpriteFont, String, Vector2, Color); 使用範例: spriteBatch.DrawString(Font1, // 字型 message, // 字串 FontPos, // 位置 Color.Black // 字的顏色 );
SpriteBatch .DrawString() SpriteBatch.DrawString (SpriteFont, String, Vector2, Color, Single, Vector2, Single, SpriteEffects, Single); 使用範例: spriteBatch.DrawString(Font1, // 字型 message, // 字串 FontPos, // 位置 Color.Black, // 字的顏色 0, // 旋轉角度 FontOrigin,// 字串中心點 3.0f, // 縮放倍數 SpriteEffects.None,// 旋轉效果 0); // 圖層深度 0.0 ~ 1.0 (後)
SpriteBatch .DrawString() SpriteBatch.DrawString (SpriteFont, String, Vector2, Color, Single, Vector2, Vector2, SpriteEffects, Single); 使用範例: spriteBatch.DrawString(Font1, // 字型 message, // 字串 FontPos, // 位置 Color.Black, // 字的顏色 0, // 旋轉角度 FontOrigin,// 字串中心點 new Vector2(2,3), // 縮放倍數 SpriteEffects.None,// 旋轉校果 0); // 圖層深度 0.0 ~ 1.0 (後)
範例十: 旋轉的2D 字型 Easy 設定一旋轉角度Angle為global variable 在update()中改變角度 在draw()中將字寫出
範例十一: 2D圖形與字型之應用 先做出一個透空的PNG圖檔(如page 4-41),在Content中讀入此圖檔。 在Game1中宣告一global variable為儲血量。 在LoadContent將圖形載入 在Update控制血量的改變 在Draw繪出血條圖,包括外框,血條框和血條損耗框 繪出文字
範例十一: 2D圖形與字型之應用 在Game1中宣告一global variable為儲血量。 public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; Texture2D myHPBar; // 2D圖形 int myCurrentHP = 100; // HP總數值 SpriteFont Font1; // 文字(myCurrentHP數值顯示)
範例十一: 2D圖形與字型之應用 在LoadContent將圖形載入 protected override void LoadContent() { // Create a new SpriteBatch, which can be used to draw textures. spriteBatch = new SpriteBatch(GraphicsDevice); myHPBar = Content.Load<Texture2D>(“HPBar”);// HPBar整張圖案載入 Font1 = Content.Load<SpriteFont>(“Courier New”);//文字載入 // TODO: use this.Content to load your game content here }
範例十一: 2D圖形與字型之應用 在Update控制血量的改變 protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); HandleInput(); // 按鍵控制 // TODO: Add your update logic here base.Update(gameTime); } private void HandleInput() { KeyboardState myKeys = Keyboard.GetState(); // 按下方向鍵"上"HP數值+1 if (myKeys.IsKeyDown(Keys.Up) == true) { myCurrentHP += 1; } // 按下方向鍵"下"HP數值-1 if (myKeys.IsKeyDown(Keys.Down) == true) { myCurrentHP -= 1; } // 將myCurrentHP值定為在最小0,最大100 myCurrentHP = (int)MathHelper.Clamp(myCurrentHP, 0, 100); }
spriteBatch.Draw() (p. 4-12) 1. 繪出 spriteBatch.Draw(mySpriteTexture, pos, Color.White); 2. 過濾顏色 spriteBatch.Draw(mySpriteTexture, pos, Color.Red); 3. 設定目的地之位置(前兩參數)和寬高(後兩參數) spriteBatch.Draw(mySpriteTexture, rec, Color.White); 4. 設定目的地的位置與寬高以及來源圖型的位置與寬高 spriteBatch.Draw(mySpriteTexture, rec2, rec_src, Color.White);
範例十一: 2D圖形與字型之應用 在Draw繪出血條圖,包括外框,血條框和血條損耗框 protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); // TODO: Add your drawing code here // 程式視窗的寬/2 int WDWidth = this.Window.ClientBounds.Width / 2; int WDHeight = this.Window.ClientBounds.Height / 2; // 文字部分定義 string output = Convert.ToString(myCurrentHP) + "%"; // 顯示出來的文字 Vector2 FontOrigin = Font1.MeasureString(output) / 2; // 字串中心點 Vector2 FontPos = new Vector2(graphics.GraphicsDevice.Viewport.Width / 2, graphics.GraphicsDevice.Viewport.Height / 2 + 20);// 字串的位置
範例十一: 2D圖形與字型之應用 在Draw繪出血條圖,包括外框,血條框和血條損耗框 // 開始繪出圖案 spriteBatch.Begin(); //1. 灰色全劃 2. HP值量條,綠色依照比例畫出 3. 畫整個外框 spriteBatch.Draw(myHPBar, new Rectangle(WDWidth - myHPBar.Width / 2, WDHeight - myHPBar.Height / 2, myHPBar.Width, 22), // 寬與高 new Rectangle(0, 45, myHPBar.Width, 44), Color.Gray); // 目的來源的位置, 顏色 // 2. new Rectangle(WDWidth - myHPBar.Width / 2, WDHeight - myHPBar.Height / 2, (int)(myHPBar.Width * ((double)myCurrentHP / 100)), 22),// 寬與高 new Rectangle(0, 45, myHPBar.Width, 44), Color.Green);// 目的來源的位置, 顏色 // 3. 整個外框 new Rectangle(WDWidth - myHPBar.Width / 2, WDHeight - myHPBar.Height / 2, myHPBar.Width, 22),// 寬與高 new Rectangle(0, 0, myHPBar.Width, 44), Color.White);// 目的來源的位置, 顏色
範例十一: 2D圖形與字型之應用 繪出文字 // 開始繪出圖案與文字 // Draw the string spriteBatch.DrawString(Font1, // 字型 output, // 字串 FontPos, // 位置 Color.Black, // 字的顏色 0, // 旋轉角度 FontOrigin,// 字串中心點 1.0f, // 縮放倍數 SpriteEffects.None, 0.5f); // 圖層深度 0.0 ~ 1.0 (後) spriteBatch.End(); base.Draw(gameTime); }
The End