設計支援手勢操作的 XNA 遊戲

 

摘要

上一回我們為大家介紹了更多的 XNA Framework 支援遊戲開發的類別,包括支援輸入控制,音效播放,以及背景音樂播放控制的類別等等,讓讀者能夠為所製作的遊戲程式加入更豐富的遊戲效果。這一回我們將要為大家介紹進階的輸入控制技巧,讓使用者可以利用 Windows Phone 7 智慧型手機支援多點觸控的觸控螢幕控制遊戲程式的執行。

認識手勢操作

在上一回的介紹中,我們學會如何利用 TouchPanel 類別的 GetState 方法查詢智慧型手機的觸控式螢幕的狀態,並依據使用者觸碰在觸控螢幕的位置移動遊戲程式顯示的圖形的位置。

呼叫 TouchPanel 類別的 GetState 方法查詢觸控螢幕的狀態並判斷使用者觸碰觸控螢幕的位置只是最簡單的觸控螢幕控制技巧,除了支援取得觸控螢幕的狀態以外,觸控螢幕還支援使用者進行多種不同的控制,包括觸碰、觸碰不放、水平拖曳、垂直拖曳、自由拖曳、以及輕拂等操作。表1 所示即為觸控螢幕的各種狀態的說明:

表1:觸控螢幕的各種狀態的說明

操作動作 說明
Tap 觸碰。觸碰觸控螢幕後放開,沒有移動的動作。
DoubleTap 連續觸碰。連續觸碰同一個位置兩次。
Hold 點住不放。觸碰後不放達一段時間。
VerticalDrag 垂直拖曳。觸碰螢幕後上下移動。
HorizontalDrag 水平拖曳。觸碰螢幕後左右移動。
FreeDrag 自由拖曳。觸碰螢幕後往任意方向移動
DragComplete 拖曳結束。
Flick 輕拂。觸碰螢幕後往任意方向拂動後離開螢幕。
Pinch 同時便用兩個手指頭觸碰觸控螢幕後移動。
PinchComplete Pinch 操作結束。

 

以 XNA 為基礎的遊戲程式可以利用表1所列的各種觸控螢幕狀態判斷使用者執行的觸控操作種類,以反應使用者的觸控操作。

[注意]

以 XNA 為基礎的遊戲程式必須啟用觸控功能才能夠讓遊戲的使用者進行觸控操作,如果已啟用 Pinch 操作功能,則當使用者利用兩個手指頭同時觸碰觸控螢幕並進行移動時,就會產生 Pinch 操作,而不是兩個不同的拖曳操作,如果未啟用 Pinch 操作功能,則所產生的就不是 Pinch 操作,而是依據兩個觸碰位置的平均為準的單一拖曳操作。

啟用手勢操作支援

以 XNA 為基礎的遊戲程式必須設定 TouchPanel 類別的 EnabledGestures 屬性,才能夠啟用手勢操作功能,以支援使用者以手勢操作遊戲程式。

程式設計師可以在 Game1 類別的 Initialize 方法執行設定 TouchPanel 類別的 EnabledGestures 屬性的動作,以啟用手勢操作支援,做法如下:

TouchPanel.EnabledGestures = GestureType.Hold | GestureType.Tap | GestureType.DoubleTap | GestureType.FreeDrag | GestureType.Flick | GestureType.Pinch;

[說明]

請注意在上述的程式中,GestureType.FreeDrag 設定表示要支援使用者以自由拖曳的方式操作遊戲程式,設定了 GestureType.FreeDrag 就已經涵蓋 GestureType.VerticalDrag 設定和 GestureType.HorizontalDrag 設定。而 DragComplete 狀態和 PinchComplete 狀態代表觸控動作結束的狀態,不需要啟用。

[注意]

以 XNA 為基礎的遊戲程式可以視需要啟用需要使用的觸控功能,例如只支援使用者利用觸控的方式選取功能表的遊戲程式,就可以僅啟用 Tap 和 VerticalDrag 兩種觸控功能,讓使用者以垂直拖曳的方式捲動遊戲程式提供的功能表,再觸碰欲選擇的功能表,其他不需要用到的觸控控制功能就不需要啟用,避免啟用多種觸控操作功能,造成判斷觸控操作動作的邏輯複雜,進而影響到觸控的精確度和遊戲程式執行的效能。

處理使用者的手勢操作

啟用了手勢操作功能之後,以 XNA 為基礎的應用程式可以在 Game1 類別的 Update 方法中呼叫 TouchPanel 類別的 ReadGesture 方法取得使用者的手勢操作資訊。請注意讀取使用者的手勢操作的做法和呼叫 TouchPanel 類別的 GetState 方法讀取觸控面板的狀態的做法不同,因為使用者對遊戲程式的觸控操作會產生多個手勢資訊,來不及被遊戲處理的手勢資訊會被存放到佇列中等待處理,讓遊戲程式利用迴圈取出並加以處理。

以下的 Update 方法便會利用 while 迴圈,搭配 TouchPanel 類別的 IsGestureAvailable 屬性判斷是否還有使用者觸控操作產生的手勢資訊尚未被處理,如果尚有使用者觸控操作產生的手勢資訊尚未被處理,則呼叫 TouchPanel 類別的 ReadGesture 方法讀取手勢資訊,並加以處理:

 

protected override void Update(GameTime gameTime) { … while (TouchPanel.IsGestureAvailable) //判斷是否尚有手勢資訊尚未被處理 { GestureSample gesture = TouchPanel.ReadGesture(); //讀取尚未處理的手勢資訊 switch (gesture.GestureType) //判斷手勢操作的種類 { case GestureType.Tap: //如果手勢操作的種類是Tap //處理 Tap 操作 break; … } } }

因為 GestureType.FreeDrag 自由拖曳操作已經包括 GestureType.VerticalDrag 垂直拖曳操作和 GestureType.HorizontalDrag 水平拖曳操作,所以遊戲程式在判斷使用者的觸控操作的動作時,不需要既判斷動作是否為 GestureType.FreeDrag,又判斷動作是否為 GestureType.VerticalDrag 或 GestureType.HorizontalDrag,兩者擇一處理即可。

[特別注意]

使用 TouchPanel 類別進行觸控控制的遊戲程式可以呼叫 TouchPanel 類別的 GetState 方法取得使用者對觸控面板的觸控狀態,或是呼叫 TouchPanel 類別的 ReadGesture 方法取得使用者的手勢操作狀態,不要兩者混用,否則將會無法得到正確的結果。例如先利用 TouchPanel 類別的 ReadGesture 方法取得使用者的手勢操作狀態,再利用 TouchPanel 類別的 GetState 方法取得使用者觸碰螢幕的位置,當做手勢操作觸碰螢幕的位置來使用就是錯誤的做法。

請注意呼叫 TouchPanel 類別的 ReadGesture 方法讀取手勢資訊時,讀取到的手勢資訊會以 GestureSample 結構的型式傳回給呼叫者。表2 所示為 GestureSample 結構常用的屬性:

表2:GestureSample 結構常用的屬性

屬性名稱 說明
Delta 存放與第一個碰觸點的偏差量。
Delta2 存放與第二個碰觸點的偏差量。
GestureType 存放觸控操作的種類。
Position 存放第一個碰觸點的位置。
Position2 存放第二個碰觸點的位置。
Timestamp 存放觸控操作發生的時間。

 

因為呼叫 TouchPanel 類別的 ReadGesture 方法傳回的 GestureSample 結構只能存放第一個和第二觸碰點的位置:Position 和 Postition2,以及存放兩個觸碰點的偏差量,所以只能支援到最多兩個手指頭的操作,事實上 Windows Phone 7 配備的觸控螢幕最多可以支援到多達四個觸碰點的觸控操作,程式可以經由呼叫 TouchPanel 類別的 GetCapabilities 方法查詢觸控螢幕的基本功能,取得 TouchPanelCapabilities 結構型態的傳回值之後,就可以利用 TouchPanelCapabilities 結構的 MaximumTouchCount 成員得知觸控螢幕最大支援的觸碰點數,做法如下:

TouchPanelCapabilities tcs = TouchPanel.GetCapabilities(); //查詢觸控螢幕的基本功能

有關 TouchPanelCapabilities 結構常用的屬性請參考表3 的說明:

表3:TouchPanelCapabilities 結構常用的屬性

屬性名稱 說明
IsConnected 查詢觸控螢幕的可用狀態。
MaximumTouchCount 取得支援使用者同時觸碰觸控螢幕的觸碰點數量。

 

如果需要取得使用者同時觸碰螢幕的所有座標點,則可以呼叫 TouchPanel 類別的 GetState 方法,取得所有觸碰點的集合,再利用迴圈取出集合中的所有觸碰點並加以處理,做法如下:

TouchCollection tc = TouchPanel.GetState(); //取得觸控螢幕的狀態 foreach (TouchLocation tl in tc) //取得所有的觸碰點 { //利用 tl.Position 取得觸碰點座標 }

[說明]

請注意 GestureSample 結構的屬性的型態為 TimeSpan 結構而不是 GameTime 類別,用來代表手勢操作與手勢操作之間的時間間隔。

各種手勢操作需要用到的 GestureSample 結構的屬性可以參考表4 的詳細說明:

表4:各種手勢操作需要用到的 GestureSample 結構的屬性

觸控操作 說明
Tap Position 屬性
DoubleTap Position 屬性
Hold Position 屬性
VerticalDrag Position 屬性和 Delta 屬性
HorizontalDrag Position 屬性和 Delta 屬性
FreeDrag Position 屬性和 Delta 屬性
DragComplete
Flick Delta 屬性
Pinch Position、Position2、Delta、和 Delta 2 四個屬性
PinchComplete

 

處理手勢操作的要訣

在處理使用者的觸控操作方面,Flick 觸控操作可以利用 GestureSample 結構的 Delta 屬性的內容值當做使用者輕拂的快慢速度,以控制捲動遊戲內容的速度。VerticalDrag 和 HorizontalDrag 觸控操作可以利用 GestureSample 結構的 Delta 屬性的內容值判斷垂直和水平移動的距離,而且 VerticalDrag 觸控操作的 Delta 屬性的 X 成員的內容值必為 0,而 HorizontalDrag 觸控操作的 Delta 屬性的 Y 成員的內容值必為 0,不需要另外透過程式碼進行設定。Pinch 觸控操作是兩個手指頭併用的觸控操作,常常用來執行旋轉物件、放大縮小物件、或是旋轉相機鏡頭的動作。遊戲程式可以利用 GestureSample 結構的 Position 屬性和 Delta 屬性,取得第一個手指頭的觸碰點和偏差量,利用 Position2 屬性和 Delta2 屬性取得第二個手指頭的觸碰點和偏差量,再據以執行變更遊戲程式顯示的內容的動作。

設計支援手勢操作的 XNA 遊戲

了解 XNA Framework 支援觸控操作的基本功能之後,接下來我們就要設計一個能夠允許使用者利用觸控螢幕操作的簡單遊戲。

首先請啟動 Microsoft Visual Studio 2010 Express,建立型態為 [XNA Game Studio(4.0)] 型態的專案,然後於 Content Pipeline 專案中加入讓使用者進行觸控操作的圖形的圓形檔案,以及當圓形碰撞到遊戲程式的視窗時欲發出的聲響的音效檔案。例如本範例準備了兩個圓形的圖案,名稱為 Ball.png 的圖形檔案是一個圓形,名稱為 HoldBall.png 的圖形檔案是一個中心有一個紅點的圓形,用來表示被點選的圖形,而 Hitwall.wav 則是當圓形的圖案碰撞到遊戲程式的視窗時欲發出的聲音的音效檔案。

將遊戲程式需要使用的資源加入到 Content Pipeline 專案之後,請開啟遊戲專案中的 Game1.cs 檔案,於類別中加入以下的變數和屬性宣告:

Texture2D Ball; //管理圓形圖案的變數 Texture2D HoldBall; //管理中心有紅點的圓形圖案的變數 Vector2 BallPosition=Vector2.Zero; //記錄圓形圖案位置的變數 bool isHold = false; //記錄使用者是否執行Hold式的觸控操作的變數 SoundEffect HitWall; //管理圖案碰撞遊戲程式視窗的音效的變數 SoundEffectInstance HitWallEffect; //管理欲播放的音效的變數 Vector2 Velocity = Vector2.Zero; //記錄Flick操作速度的變數 public const float Friction = 0.9f; //記錄摩擦力的變數 public const float BounceMagnitude = .5f; //記錄反彈速度的變數 public const float MinScale = .5f; //記載最小縮小比例的變數 public const float MaxScale = 2f; //記載最大放大比例的變數 private float scale = 1f; //記載目前比例的變數 public float Scale //記載目前比例的屬性 { get { return scale; } set { scale=MathHelper.Clamp(value, MinScale, MaxScale);//控制縮放的比例的最大/最小值 } }

準備好遊戲程式需要的變數之後,請修改 Game1 類別的建構函式,在建立 GraphicsDeviceManager 類別的物件之後設定 GraphicsDeviceManager 類別的物件的 PreferredBackBufferHeight 屬性和 graphics.PreferredBackBufferWidth 屬性,將遊戲程式的視窗設定成寬 480 x 高 800 的大小。編輯妥的 Game1 類別建構函式如下:

public Game1() { graphics = new GraphicsDeviceManager(this);//建立GraphicsDeviceManager類別的物件 Content.RootDirectory = "Content"; //設定載入遊戲資源的根路徑 graphics.PreferredBackBufferHeight = 800; //設定遊戲程式視窗的高度 graphics.PreferredBackBufferWidth = 480; //設定遊戲程式視窗的寬度 // Frame rate is 30 fps by default for Windows Phone. TargetElapsedTime = TimeSpan.FromTicks(333333); //設定 Update 方法被呼叫的頻率 }

設定妥遊戲程式視窗的大小之後請編輯 Game1 類別的 Initialize 方法,負責啟用手勢操作的功能,編輯妥的 Initialize 方法如下:

protected override void Initialize() { // TODO: Add your initialization logic here TouchPanel.EnabledGestures = GestureType.Hold | GestureType.Tap | GestureType.DoubleTap | GestureType.FreeDrag | GestureType.HorizontalDrag | GestureType.Flick | GestureType.Pinch; //啟用手勢操作功能 base.Initialize(); }

啟用手勢操作功能之後請於 Game1 類別的 LoadContent 方法加入載入遊戲資源的程式碼,編輯好的 LoadContent 方法如下:

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 Ball = Content.Load<Texture2D>("Ball"); //載入圓形圖案 HoldBall = Content.Load<Texture2D>("HoldBall"); //載入中心有紅點的圓形圖案 BallPosition = new Vector2((Window.ClientBounds.Width - Ball.Width) / 2, (Window.ClientBounds.Height-Ball.Height)/2);//設定圓形圖案要顯示在視窗的正中央 // TODO: use this.Content to load your game content here HitWall = Content.Load<SoundEffect>("HitWall");//載入圓形圖案撞擊視窗邊界要發出的音效檔案 HitWallEffect = HitWall.CreateInstance(); //利用 SoundEffect 類別的物 //件建立 SoundEffectInstance 類別的物件 HitWallEffect.Apply3D(new AudioListener(), new AudioEmitter()); //呼叫SoundEffectInstance類別的 Apply3D 方法套用 3D 音效 }

您可以視需要修改上述的程式碼載入的資源名稱,以載入遊戲程式執行時需要的資源。

載入妥遊戲程式需要使用的資源之後,請修改名稱為 Update 的方法,以反應使用者的手勢操作動作。加入手勢控制功能的 Update 方法如下:

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 while (TouchPanel.IsGestureAvailable) //判斷使用者是否執行了手勢操作 { GestureSample gesture = TouchPanel.ReadGesture(); //讀取使用者的手勢操作 switch (gesture.GestureType) //判斷使用者的手勢操作型態 { case GestureType.Tap: //Tap 操作 case GestureType.DoubleTap: //DoubleTap 操作 BallPosition.X=gesture.Position.X-Ball.Width/2; //設定圖案顯示在使用 BallPosition.Y=gesture.Position.Y-Ball.Height/2; //者觸碰的座標點 break; case GestureType.Hold: //Hold 操作 Rectangle BallRect = new Rectangle(0, 0, (int)(Ball.Width * Scale), (int)(Ball.Height * Scale)); //計算圖形的矩形大小 if (BallRect.Contains((int)(gesture.Position.X - BallPosition.X), (int)(gesture.Position.Y - BallPosition.Y)))//判斷觸碰點是否在矩形中 { isHold = true; //將 IsHold 變數的內容值設定為 true } break; case GestureType.FreeDrag: //FreeDrag 操作 BallPosition += gesture.Delta; //循著使用者拖曳的軌跡移動圖形 break; case GestureType.Flick: //Flick 操作 Velocity = gesture.Delta; //設定移動圖形的速度 break; case GestureType.Pinch: //Pinch 操作 Vector2 a = gesture.Position; //取得第一個觸碰點 Vector2 aOld = gesture.Position - gesture.Delta;//取得第一個觸碰點的起始位置 Vector2 b = gesture.Position2; //取得第二個觸碰點 Vector2 bOld = gesture.Position2 - gesture.Delta2;//取得第二個觸碰點的起始位置 float d = Vector2.Distance(a, b); //計算兩個觸碰點之間的距離 float dOld = Vector2.Distance(aOld, bOld);//計算兩個原始座標之間的距離 float scaleChange = (d - dOld) * .01f; //計算距離的變化量 Scale += scaleChange; //將距離變化量的 1/10 當做縮放的比例 break; } } BallPosition += Velocity * (float)gameTime.ElapsedGameTime.TotalSeconds; //依據Flick操作的速度移動圖形 Velocity *= 1f - (Friction * (float)gameTime.ElapsedGameTime.TotalSeconds);//利用磨擦係數減緩圖形的移動速度 float HalfWidth = (Ball.Width * Scale) / 2f; //取得圖形寬度的 1/2 float HalfHeight = (Ball.Height * Scale) / 2f; //取得圖形高度的 1/2 if (BallPosition.X < Window.ClientBounds.Left) //判斷圖形是否觸及視窗的左邊界 { BallPosition.X=Window.ClientBounds.Left;//設定圖形左上角點的X座標等於視窗左邊界 Velocity.X *= -BounceMagnitude; //設定反彈的速度為目前速度的一半 HitWallEffect.Play(); //播放碰撞視窗邊界的音效 } if (BallPosition.X > Window.ClientBounds.Right – Ball.Width * Scale) //判斷圖形是否觸及視窗的右邊界 { BallPosition.X = Window.ClientBounds.Right – Ball.Width * Scale; //設定圖形右上角點的X座標等於視窗右邊界 Velocity.X *= -BounceMagnitude; //設定反彈的速度為目前速度的一半 HitWallEffect.Play(); //播放碰撞視窗邊界的音效 } if (BallPosition.Y < Window.ClientBounds.Top) //判斷圖形是否觸及視窗的上邊界 { BallPosition.Y = Window.ClientBounds.Top;//設定圖形左上角點的Y座標等於視窗上邊界 Velocity.Y *= -BounceMagnitude; //設定反彈的速度為目前速度的一半 HitWallEffect.Play(); //播放碰撞視窗邊界的音效 } if (BallPosition.Y > Window.ClientBounds.Bottom – Ball.Height * Scale) //判斷圖形是否觸及視窗的下邊界 { BallPosition.Y = Window.ClientBounds.Bottom – Ball.Height * Scale; //設定圖形右下角點的Y座標等於視窗下邊界 Velocity.Y *= -BounceMagnitude; //設定反彈的速度為目前速度的一半 HitWallEffect.Play(); //播放碰撞視窗邊界的音效 } base.Update(gameTime); }

最後我們還要修改名稱為 Draw 的方法,將遊戲程式顯示的圖形顯示在反應使用者手勢操作的位置上,做法如下:

protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); // TODO: Add your drawing code here spriteBatch.Begin(); //開始繪製遊戲內容 if (isHold) //如果圖形被使用點中 { spriteBatch.Draw(HoldBall, BallPosition, null, Color.White, 0, Vector2.Zero, Scale, SpriteEffects.None, 0); //在指定的位置顯示中心有紅點的圓形 } else { spriteBatch.Draw(Ball, BallPosition, null, Color.White, 0, Vector2.Zero, Scale, SpriteEffects.None, 0); //在指定的位置顯示圓形 } spriteBatch.End(); //結束繪製遊戲內容 base.Draw(gameTime); }

做好之後請執行 [建置] 專案的動作。

將設計妥的遊戲程式部署到 Windows Phone 7 智慧型手機

因為 Windows Phone 7 模擬器不支援使用者觸控操作,所以允許使用者利用手勢進行遊戲操作的遊戲程式必須部署到真實的 Windows Phone 7 智慧型手機才能測試和手勢操作有關的功能是否正確,無法透過 Windows Phone 7 模擬器進行測試,因為在一般電腦上執行的 Windows Phone 7 模擬器無法接受使用者透過手勢動作進行操作。

[提示]

如果使用者所使用的電腦配備的是觸控螢幕,則在配備觸控螢幕的電腦執行的 Windows Phone 7 模擬器仍然可以允許使用者透過觸控螢幕進行操作。

欲將開發妥的遊戲程式部署到實際的 Windows Phone 7 智慧型手機,而不是 Windows Phone 7 模擬器,程式設計師使用的電腦必須先安裝 Zune 軟體。安裝完成後請重新啟動電腦,並將 Windows Phone 7 智慧型手機連上開發電腦的 USB 連接埠,所安裝的 Zune 軟體將會在 Windows Phone 7 智慧型手機連上電腦時自動啟動。圖1 所示即為 Zune 軟體啟動後的執行畫面:

圖1:Zune 執行的畫面

Zune 自動啟動之後,請執行 [所有程式 | Windows Phone Developer Tools] 群組中的 [Application Deployment] 程式,並在 [Application Deployment] 程式啟動之後於 [Target] 下拉式選項中選擇:Windows Phone 7 Device,再按下 [Browse] 鍵瀏覽到建置專案成功得到的 XAP 檔案,如圖2 所示:

圖2:使用 [Application Deployment] 程式部署 Windows Phone 7 遊戲程式的畫面

做好之後按下 [Deploy] 鍵將所選擇的 Windows Phone 7 遊戲程式部署到 Windows Phone 7 智慧型手機。請注意 Windows Phone 7 智慧型手機不可處於螢幕鎖定的狀態,否則將無法部署成功。

[提示]

除了可以利用 [Application Deployment] 程式部署 Windows Phone 7 遊戲程式到 Windows Phone 7 智慧型手機以外,程式設計師也可以利用 Visual Studio 2010 Express for Windows Phone 直接將所開發的程式部署到 Windows Phone 7 智慧型手機,不過程式設計師所使用的電腦仍然必須事先安裝好 Zune 軟體。程式設計師可以利用 [XNA Game Studio Device Management] 工具列提供的下拉選項 [ ] 選擇到 Windows Phone 7 Device,再按下 CTRL+F5 組合鍵將開發好的程式部署到 Windows Phone 7 智慧型手機。同樣地,Windows Phone 7 智慧型手機不可以處於螢幕鎖定的狀態,否則將無法部署成功。

請執行部署到 Windows Phone 7 智慧型手機的遊戲程式,並利用 XNA Framework 支援的各種手勢操作技巧點選、自由移動、輕拂、或放大縮小遊戲程式顯示的圓形圖案,體驗利用手勢操作程式的高度方便性。圖3所示即為允許使用者利用手勢操作的程式執行的情形:

圖3:允許使用者利用手勢操作的程式執行的情形

請注意當圓形圖案被輕拂至碰撞到程式的視窗時會發出聲響並產生反彈。

程式下載:GestureControl.zip