Silverlight.XNA(C#)跨平台3D游戲研發手記:(七)向Windows Phone移植之雙向交互


繼完成游戲主體框架搭建后,接下來我將通過SL.XNA模式中Silverlight控件與XNA對象之間雙向交互操作的例子,向大家進一步講解框架的拓展使用及簡單的承載演示。在此之前大家需要理解Windows Phone移動設備與傳統桌面設備在操作方面的差異。

直觀上看,桌面應用大多使用鼠標加鍵盤的操作方式:鼠標左鍵、中鍵、右鍵,包括單擊(按下、放開)、雙擊以及滑動、拖動、滾動等;而Windows Phone的操作方式則以觸控虛擬鍵盤為主:按下、放開、按住滑動、各種(多指)手勢等;當然,也可以實現像NDS那樣通過麥克風實現語音與游戲的交互。而Windows Phone游戲開發中則觸摸以為主要輸入方式,下面我將分開講解各種觸控操作在Windows Phone中的代碼實現。

一、XNA中的觸控操作

在此之前,很有必要向新手朋友們簡要闡述一下XNA的運行機制:XNA是通過傳統的輪詢(循環)方式呈現游戲的,即每秒鍾對游戲邏輯進行N數據更新(Update)並將對象重新繪制到屏幕上(Draw),這個頻率N在代碼中的設定為timer.UpdateInterval = TimeSpan.FromTicks(333333); 30fps(30幀每秒)

舉個例子:假如我們想讓一名戰士從(0,0)坐標向(300,300)坐標移動,若該戰士的移動速度系數Speed=5,那么我們只需在Update()方法中讓他的位置屬性(X,Y)分別加上Speed(X+Speed,Y+Speed),於是兩秒鍾后戰士將到達目的地(X+2*5*30Y+2*5*30)=(300,300)。根據前面所述游戲循環機制,每執行Update數據一次,屏幕就會擦除掉之前的畫面然后重新Draw新的畫面,於是在這兩秒鍾內,戰士的位置會發生2*30=60次變化,依次為(0,0),(5,5),(10,10),(15,15)……(295,295),(300,300)。短短2秒鍾的時間里戰士位置連續變化了60次,游戲屏幕好比黑板一樣,擦了60次,又畫了60次,由於頻率很高,好比電視欺騙人眼球同樣原理,呈現的是一種連續的動態效果,最終便形成了游戲意義上的“戰士移動”動畫。

由此我們可以明確:XNA中無論編寫什么都必須基於循環,因此XNA模式中無論是低級觸控(按下、放開、按住滑動)還是高級(路由)手勢,都得放在循環(Update進行時時監測(輪詢式)

XNA 觸控
         private  void Update( object sender, GameTimerEventArgs e) {
            inputHandler.MonitorTouch();
            inputHandler.MonitorGestures();
        }

         ///   <summary>
        
///  監測觸摸(XNA模式,置於Update輪詢中)
        
///   </summary>
         public  void MonitorTouch() {
            TouchCollection touchCollection = TouchPanel.GetState();
             foreach (TouchLocation location  in touchCollection) {
                TouchEventArgs e =  new TouchEventArgs() { Location = location };
                 switch (location.State) {
                     case TouchLocationState.Pressed:  if (Press !=  null) { Press( this, e); }  break;
                     case TouchLocationState.Moved:  if (Move !=  null) { Move( this, e); }  break;
                     case TouchLocationState.Released:  if (Release !=  null) { Release( this, e); }  break;
                }
            }
        }

         ///   <summary>
        
///  監測手勢(XNA模式,置於Update輪詢中)
        
///   </summary>
         public  void MonitorGestures() {
             while (TouchPanel.IsGestureAvailable) {      // 判斷是否還有手勢尚未被處理
                GestureSample gesture = TouchPanel.ReadGesture();  // 讀取尚未處理的手勢
                GesturesEventArgs e =  new GesturesEventArgs() { Gesture = gesture };
                 switch (gesture.GestureType) {
                     case GestureType.Tap:  if (Tap !=  null) { Tap( this, e); }  break;
                     case GestureType.DoubleTap:  if (DoubleTap !=  null) { DoubleTap( this, e); }  break;
                     case GestureType.Hold:  if (Hold !=  null) { Hold( this, e); }  break;
                     case GestureType.VerticalDrag:  if (VerticalDrag !=  null) { VerticalDrag( this, e); }  break;
                     case GestureType.HorizontalDrag:  if (HorizontalDrag !=  null) { HorizontalDrag( this, e); }  break;
                     case GestureType.FreeDrag:  if (FreeDrag !=  null) { FreeDrag( this, e); }  break;
                     case GestureType.DragComplete:  if (DragComplete !=  null) { DragComplete( this, e); }  break;
                     case GestureType.Flick:  if (Flick !=  null) { Flick( this, e); }  break;
                     case GestureType.Pinch:  if (Pinch !=  null) { Pinch( this, e); }  break;
                     case GestureType.PinchComplete:  if (PinchComplete !=  null) { PinchComplete( this, e); }  break;
                }
            }
        }

 

    如果還不能理解XNA的游戲循環原理,大家不妨將游戲循環頻率設定為每3/次,再進一步測試XNA中觸控操作,是否發現了什么?

二、Silverlight控件的觸控操作

如果你是一位Silverlight(Web)開發者,你會發現Windows Phone中的Silverlight控件相當給力(Image控件為例)

圖中所有框住的事件均為路由觸控事件,且慢,居然還有Mouse……事件,剎那間淚崩了,這不是BUG,我反正信了。就如智能感知提示的那樣:Mouse這一系列事件是“觸筆的筆尖接觸屏幕”時發生的,類比桌面鼠標事件。而其余紫色框住的部分則為Windows Phone專有觸控事件,其中TapDoubleTapHoldXNA中的一致,剩下的手勢則可通過3個Manipulation連鎖事件自由實現,靈活度相當高。此時或有有朋友會問:同樣是路由的MouseLeftButtonDownTap到底有何區別?不妨拿起你手中的Windows Phone測試一下不就知道啦 ^ ^

當然,Silverlight同樣擁有類似XNA中的非路由低級觸。於是我將以上關於XNASilverlight的觸控操作進行最終整合,以代碼的形式封裝成一個名為InputHandler(輸入處理器)的類,內置了SilverlightXNA的所有觸控操作(注意了,如果使用該類中的XNA模式觸控必須將MonitorTouch()MonitorGestures()兩個方法放置於主循環的Update()中方能起效)

InputHandler
     ///   <summary>
    
///  輸入處理器
    
///   </summary>
     public  sealed  class InputHandler {

         ///   <summary> 觸摸按下時觸發 </summary>
         public  event EventHandler<TouchEventArgs> Press;
         ///   <summary> 觸摸持續按住並移動時觸發 </summary>
         public  event EventHandler<TouchEventArgs> Move;
         ///   <summary> 觸摸放開時觸發 </summary>
         public  event EventHandler<TouchEventArgs> Release;

         ///   <summary>
        
///  創建輸入處理器
        
///   </summary>
        
///   <param name="mode"> 模式 </param>
         public InputHandler(TouchModes mode) { Mode = mode; }

        TouchModes _Mode;
         ///   <summary>
        
///  獲取或設置觸控模式
        
///   </summary>
         public TouchModes Mode {
             get {  return _Mode; }
             set {
                _Mode = value;
                Touch.FrameReported -= Touch_FrameReported;
                TouchPanel.EnabledGestures = GestureType.None;
                 if (value == TouchModes.Silverlight) {
                    Touch.FrameReported += Touch_FrameReported;
                }  else  if (value == TouchModes.XNA) {
                     // 啟動所有手勢(也可根據實際情況選擇部分啟動)
                    TouchPanel.EnabledGestures = GestureType.Tap | GestureType.DoubleTap | GestureType.Hold | GestureType.FreeDrag | GestureType.Flick | GestureType.Pinch;
                }
            }
        }

         #region Silverlight模式

         ///   <summary>
        
///  獲取或設置參照物(Silverlight模式)
        
///   </summary>
         public UIElement Reference {  getset; }

         void Touch_FrameReported( object sender, TouchFrameEventArgs args) {
            TouchPoint point = args.GetPrimaryTouchPoint(Reference);  // 參數若為null則相對於屏幕垂直呈現時的左上角(按鍵右置水平呈現時的左下角)
             if (point !=  null) {
                TouchEventArgs e =  new TouchEventArgs() { Point = point };
                 switch (point.Action) {
                     case TouchAction.Down:  if (Press !=  null) { Press( this, e); }  break;
                     case TouchAction.Move:  if (Move !=  null) { Move( this, e); }  break;
                     case TouchAction.Up:  if (Release !=  null) { Release( this, e); }  break;
                }
            }
        }

         #endregion

         #region XNA模式

         ///   <summary> 短暫地觸控了屏幕上的一個點時觸發 </summary>
         public  event EventHandler<GesturesEventArgs> Tap;
         ///   <summary> 快速連續點按了屏幕兩次時觸發 </summary>
         public  event EventHandler<GesturesEventArgs> DoubleTap;
         ///   <summary> 觸控屏幕上的某一點約一秒鍾時間時觸發 </summary>
         public  event EventHandler<GesturesEventArgs> Hold;
         ///   <summary> 觸控屏幕,然后執行水平(從左到右,或從右到左)手勢時觸發 </summary>
         public  event EventHandler<GesturesEventArgs> VerticalDrag;
         ///   <summary> 觸控屏幕,然后執行垂直(從頂部到底部,或從底部到頂部)手勢時觸發 </summary>
         public  event EventHandler<GesturesEventArgs> HorizontalDrag;
         ///   <summary> 觸控屏幕,然后執行自由格式的拖動手勢時觸發 </summary>
         public  event EventHandler<GesturesEventArgs> FreeDrag;
         ///   <summary> 觸控屏幕上的兩點,然后聚合或分開兩點時觸發 </summary>
         public  event EventHandler<GesturesEventArgs> DragComplete;
         ///   <summary> 同時執行觸控與快速擦過屏幕的操作時觸發 </summary>
         public  event EventHandler<GesturesEventArgs> Flick;
         ///   <summary> 拖動手勢(VerticalDrag、HorizontalDrag 或 FreeDrag)已完成時觸發 </summary>
         public  event EventHandler<GesturesEventArgs> Pinch;
         ///   <summary> 收縮操作已完成時觸發 </summary>
         public  event EventHandler<GesturesEventArgs> PinchComplete;

         ///   <summary>
        
///  監測觸摸(XNA模式,置於Update輪詢中)
        
///   </summary>
         public  void MonitorTouch() {
            TouchCollection touchCollection = TouchPanel.GetState();
             foreach (TouchLocation location  in touchCollection) {
                TouchEventArgs e =  new TouchEventArgs() { Location = location };
                 switch (location.State) {
                     case TouchLocationState.Pressed:  if (Press !=  null) { Press( this, e); }  break;
                     case TouchLocationState.Moved:  if (Move !=  null) { Move( this, e); }  break;
                     case TouchLocationState.Released:  if (Release !=  null) { Release( this, e); }  break;
                }
            }
        }

         ///   <summary>
        
///  監測手勢(XNA模式,置於Update輪詢中)
        
///   </summary>
         public  void MonitorGestures() {
             while (TouchPanel.IsGestureAvailable) {      // 判斷是否還有手勢尚未被處理
                GestureSample gesture = TouchPanel.ReadGesture();  // 讀取尚未處理的手勢
                GesturesEventArgs e =  new GesturesEventArgs() { Gesture = gesture };
                 switch (gesture.GestureType) {
                     case GestureType.Tap:  if (Tap !=  null) { Tap( this, e); }  break;
                     case GestureType.DoubleTap:  if (DoubleTap !=  null) { DoubleTap( this, e); }  break;
                     case GestureType.Hold:  if (Hold !=  null) { Hold( this, e); }  break;
                     case GestureType.VerticalDrag:  if (VerticalDrag !=  null) { VerticalDrag( this, e); }  break;
                     case GestureType.HorizontalDrag:  if (HorizontalDrag !=  null) { HorizontalDrag( this, e); }  break;
                     case GestureType.FreeDrag:  if (FreeDrag !=  null) { FreeDrag( this, e); }  break;
                     case GestureType.DragComplete:  if (DragComplete !=  null) { DragComplete( this, e); }  break;
                     case GestureType.Flick:  if (Flick !=  null) { Flick( this, e); }  break;
                     case GestureType.Pinch:  if (Pinch !=  null) { Pinch( this, e); }  break;
                     case GestureType.PinchComplete:  if (PinchComplete !=  null) { PinchComplete( this, e); }  break;
                }
            }
        }

         #endregion

    }

 

最后經過反復的對比測試,得出以下結論:Silverlight模式在觸控方面對比XNA最大優勢在於:Silverlight的觸是基於事件驅動的(觸發式)同步的顯而易見,針對低級觸控操作,Silverlight模式性能更高;相對於具體控件,Silverlight觸控事件效率更高,使用更方便;總體來說,不論是精確度、靈活度還是開發效率和維護效率,Silverlight觸控模式都明顯優於XNA模式。

因此,這也是為什么微軟會在WP7.1開始重點推出SL.XNA模式的主要原因了:SL負責游戲的UI部分,而XNA則負責繪制游戲對象,分工明確,效率與性能兼具。

為了精確論證以上觀點的完美可執行性,我特意編寫了一個SL控件與XNA精靈交互的Demo

如上圖,不僅有2D精靈,也有3D骨骼動畫模型;有Silverlight按鈕(UI),也有XNA按鈕(UI);所有對象均同屏顯示,且可相互操作(封裝了一個名為UIHandlerUI管理類,詳見本文結尾處源碼)。以最左邊“SL控件+XNA 2D精靈”的“轉向”交互為例,首先我們賦予XNA精靈一個Direction屬性:

         ///   <summary>
        
///  獲取或設置朝向
        
///   </summary>
         public  virtual Directions Direction {  getset; }

 

當點擊“轉向”Button時,觸發Click事件修改該精靈的Direction屬性:

        button.Click +=  delegate {
             int direction = ( int)role1.Direction +  1;
            role1.Direction = direction >  3 ?  0 : (Directions)direction;
        };

 

同時,該精靈的Direction屬性新值重新反饋給SilverlightTextBlock控件並顯示出來:

 textBlock.Text = ((Directions)role1.Direction).ToString();

 

由此便完成了SL -> XNA –> SL這樣一個雙向交互的演示。從該案例中大家是否體會到了Silvelight UIXNA Sprite之間的親密無間?當然了,SLXNA 3D對象的交互也同樣方便快捷(詳見源碼),不過需要注意一點,在繪制模型代碼段中必須加入以下三句話方能正常顯示(否則會出現因與SpriteBatch混合作用而導致的貼圖呈現部分透明狀態)

             // 呈現模型蒙皮網格
             foreach (ModelMesh mesh  in currentModel.Meshes) {
                 foreach (SkinnedEffect effect  in mesh.Effects) {
                ......            
                    effect.GraphicsDevice.BlendState = BlendState.AlphaBlend;
                    effect.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
                    effect.GraphicsDevice.SamplerStates[ 0] = SamplerState.PointClamp;
                ......
                }
            }

 

至於純XNA界面與XNA精靈的交互例子我就不再多說了,開發過XNA的朋友都清楚,需要將UI繪制出來后時時判斷觸控點與各UI間的位置關系最終做出判斷,代碼量大,編碼繁瑣;總之XNAUI吃力不討好,巨蛋痛,真的。

結尾,我認為很有必要提醒大家一下關於游戲開發中使用非常廣泛的Point結構體和Math數學庫。WPF/Silverlight/XNA中的Point設計各不相同,移植時需特別注意;System.MathMicrosoft.Xna.Framework.MathHelper針對的領域各不相同,前者在三角函數方面更全面(double),如正弦、余弦、正切、余切等;而后者則主要針對float類型的計算,比如弧度和角度之間的換算等常用的靜態方法。

    OK,至此我們摸透了SL.XNA中各元素的操控原理與實現,下一節我將對第5節的Demo進行移植,進而向大家展示該框架強而有力的實用性,敬請關注。

本節源碼下載地址:SLXnaGame2.zip

手記小結:SL.XNA模式乃集大成者,無論開發2D或是3D游戲,Silverlight負責制作UI高效且便捷,XNA則專業繪制高性能精靈與模型,分工明確,結構合理,互利互補。掌握好SL.XNA開發模式,深刻透析游戲結構布局,分工合作處理好游戲各環節邏輯關系,游戲整體“效率”與“性能”必將最大化。

推薦參考:NowpaperWilliams關於Windows Phone的游戲開發博客。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM