Silverlight.XNA(C#)跨平台3D游戲研發手記:(十)3D 場景與控制設計①


模型和骨骼動畫僅僅是開啟3D游戲的敲門磚,置入基於攝像機的場景設計方能呈現最完美的3D游戲。本節,我們依舊從簡單着手,一步步創建基於模型的3D游戲場景。

XNA4.0學習指南(中文)》是一本絕對值得一看的好書,對於3D游戲的基礎知識、概念以及簡單應用講解非常全面。比如書中提到關於XNA內置了創建攝像機的方案代碼,根據該提示我們便可輕松實現一個名為Camera3D的類:

3D攝像機
     ///   <summary>
    
///  3D攝像機
    
///   </summary>
     public  sealed  class Camera3D : Object3D {

         public Matrix View {  getset; }
         public Matrix Projection {  getset; }
         public Vector3 Target {  getset; }
         public Vector3 Up {  getset; }
         public  float Near {  getset; }
         public  float Far {  getset; }

         ///   <summary>
        
///  獲取或設置視角模式
        
///   </summary>
         public ViewModes ViewMode {  getset; }

         public Camera3D(ContentManager content, GraphicsDevice device, Vector3 position, Vector3 target, Vector3 up,  float near,  float far) :
             base(content, device) {
             this.Position = position;
             this.Target = target;
             this.Up = up;
             this.Near = near;
             this.Far = far;
        }

         ///   <summary>
        
///  更新以3D角色為中心的視口
        
///   </summary>
        
///   <param name="role3D"></param>
         public  void UpdateViewPort(Role3D role3D) {
             if (role3D !=  null) {
                Vector3 center = role3D.Position;
                 switch (ViewMode) {
                     case ViewModes.FirstPerson:
                        SetViewPort(center, - 5, - 10, role3D.Eye -  10, role3D.Eye -  10);  break;
                     case ViewModes.ThirdPersonHeadlook:
                        SetViewPort(center,  30, - 410, role3D.Eye, role3D.Eye -  130);  break;
                     case ViewModes.ThirdPersonOverlook:
                        SetViewPort(center, role3D.Eye +  800, role3D.Eye +  1000);  break;
                }
            }
        }

         ///   <summary>
        
///  從(0,y1,a)向(0,y2,b)方向看,其中a,b調節遠近,y1,y2調節高度,Scale伸縮攝像機
        
///   </summary>
        
///   <param name="center"> 參照物中心位置 </param>
        
///   <param name="a"> Position的Z位置 </param>
        
///   <param name="b"> Target的Z位置 </param>
        
///   <param name="y1"> Position的Y位置 </param>
        
///   <param name="y2"> Target的Y位置 </param>
         void SetViewPort(Vector3 center,  float a,  float b,  float y1,  float y2) {
            a *= Scale;
            b *= Scale;
             float x1 = ( float)(a * Math.Sin(RotationY));
             float z1 = ( float)(a * Math.Cos(RotationY));
             float x2 = ( float)(b * Math.Sin(RotationY));
             float z2 = ( float)(b * Math.Cos(RotationY));
            Position = center +  new Vector3(x1, y1, z1);
            Target = center +  new Vector3(x2, y2, z2);
        }

         ///   <summary>
        
///  更新攝像機的視圖矩陣和投影矩陣
        
///   </summary>
         public  override  void Update(GameTimerEventArgs e) {
            View = Matrix.CreateLookAt(Position, Target, Up) ;
            Projection = Matrix.CreatePerspectiveFieldOfView(
            MathHelper.PiOver4,
            device.Viewport.AspectRatio,
            Near, Far);
        }
    }

攝像機是我們觀查3D世界的窗口,很多游戲開發者會親切的稱之為“上帝之眼”。毫不誇張的說,有了它,市面上一切3D游戲視覺設定都能隨意實現。

比如“第一人稱視角”的RPG和FPS,經典代表作有《魔法門》,《反恐精英》等:


第一人稱視角的最大特點是玩家置入感強,屏幕就好比玩家的眼睛,玩家通過屏幕看去仿佛置身其中,身臨其境般感覺,非常真實。

當然,在此基礎上又進化出來了“第三人稱平視”視角,該視角很好的規避了“第一人稱視角”中存在的視覺死角以及容易產生眩暈等問題,動作感及玩家操控體驗更強,逐漸成為3D游戲,尤其是動作和射擊類3D游戲的主流視角。

經典代表作有ACT《鬼泣》和TPS《全球使命》等:


而“第三人稱俯視”視角則是我們最最常見的游戲視角,該視角非常有利於玩家時刻觀察大范圍周邊環境,視覺面廣,立體感強,操作尤為爽快。應該感謝網游,使之能夠成為目前絕大多數玩家最鍾愛的3D(2.5D)游戲視角模式。經典游戲耳熟能詳,舉不勝舉,比如ARPG《暗黑破壞神》和SLG《英雄無敵》:


總體來說,游戲視角的選取應該符合游戲本身的性質以及匹配游戲的核心玩法,像《刺客信條》等神作,為了在不同動作、環境時得到最好的玩家操控體驗,采用了極為復雜的動態視角切換技術。當然還有比如《上古卷軸5》和《輻射3》等大作,為了不失去任何玩家,系統可根據玩家自身適應性與操作習慣選擇相應的特定視角等等。

通過對以上案例的分析,目的只想向大家傳遞一個思想:開發3D游戲若能很好的把握住攝像機的運作原理,對於3D游戲場景設計來說,一切都是小Case。

接下來,為了讓大家更進一步理解3D攝像機,以上一節中的骨骼動畫角色作為主角,以3DMAX導出的FBX格式建築模型模擬游戲實際場景,再根據之前創建的攝像機代碼,通過修改其中的Position和Target兩個關鍵參數即可調節出任意3D視角:

當我們將攝像機置於主角的眼睛位置(很多射擊類游戲會將攝像機置於角色胸口處,這樣角色的雙手及手中的武器都能清晰可見),並同時看向主角的正前方時,此時便形成了“第一人稱視角”(通常我們會在屏幕正中心放置一個十字准心,用於方向與目標的定位):



當攝像機置於主角身邊某處(比如正后方)並穿過主角看向前方,同時僅作圍繞主角的垂直方向旋轉時,便形成了“第三人稱平視”視角:



將攝像機固定於主角的上空,並呈一定角度的傾斜對准主角腳底俯視時,便形成了“第三人稱俯視”視角:



其實很簡單對吧,不妨將攝像機看作是我們的游戲屏幕,那么各類視角的實現其實都不過如此。

最后,為了配合Windows Phone等移動設備獨特的操作方式,按照游戲玩家們的傳統習慣,即左手控制角色移動,右手控制行為指令;那么我們便可創建出一個名為Controller的“虛擬拇指搖桿控制器”類,並分別置於屏幕左右兩邊:

虛擬拇指搖桿
     ///   <summary>
    
///  基於虛擬拇指搖桿的控制器
    
///   </summary>
     public  sealed  class Controller : Object2D {

        Texture2D rtexture, ltexture, backStick;
         const  int maxThumbstickDistance =  50;
         const  int distanceThumbsticks =  50;
        Vector2 rightCornerPosition, leftCornerPosition;
        Vector2 rightBackStick, leftBackStick;
        Vector2 rightPosition, leftPosition;
        Vector2 rightThumbstickCenter, leftThumbstickCenter;

         public Controller(ContentManager content, Vector2 size)
            :  base(content) {
            backStick = content.Load<Texture2D>( " Image/BackgroundStick ");
            rtexture = content.Load<Texture2D>( " Image/RStick ");
            ltexture = content.Load<Texture2D>( " Image/LStick ");
            Vector2 middleTexture =  new Vector2(rtexture.Width /  2, rtexture.Height /  2);

            rightThumbstickCenter =  new Vector2(size.X - distanceThumbsticks - middleTexture.X, size.Y - distanceThumbsticks - middleTexture.Y);
            leftThumbstickCenter =  new Vector2(distanceThumbsticks + middleTexture.X, size.Y - distanceThumbsticks - middleTexture.Y);

            rightCornerPosition = rightThumbstickCenter - middleTexture;
            leftCornerPosition = leftThumbstickCenter - middleTexture;

            rightBackStick = rightCornerPosition -  new Vector2(distanceThumbsticks, distanceThumbsticks);
            leftBackStick = leftCornerPosition -  new Vector2(distanceThumbsticks, distanceThumbsticks);
        }

         ///   <summary>
        
///  獲取或設置操作模式(精確度)
        
///   </summary>
         public ControlModes ControlMode {  getset; }

         ///   <summary>
        
///  獲取或設置左側虛擬拇指搖桿位置
        
///   </summary>
         public Vector2 LeftThumbstick {
             get {
                 // 縮放向量計算觸摸位置的中心,縮放最大搖桿距離
                Vector2 l = (leftPosition - leftThumbstickCenter) / maxThumbstickDistance;
                 // 如果長度大於1,轉化為單位矢量
                 if (l.LengthSquared() > 1f) { l.Normalize(); }
                 return l;
            }
        }

         ///   <summary>
        
///  獲取或設置右側虛擬拇指搖桿位置
        
///   </summary>
         public Vector2 RightThumbstick {
             get {
                Vector2 l = (rightPosition - rightThumbstickCenter) / maxThumbstickDistance;
                 if (l.LengthSquared() > 1f) { l.Normalize(); }
                 return l;
            }
        }

         public  override  void Update(GameTimerEventArgs e) {
            TouchLocation? leftTouch =  null, rightTouch =  null;
            TouchCollection touches = TouchPanel.GetState();
             foreach (TouchLocation touch  in touches) {
                 switch (ControlMode) {
                     case ControlModes.Accurate:
                         if (Math.Pow((touch.Position.X - leftThumbstickCenter.X),  2) + Math.Pow((touch.Position.Y - leftThumbstickCenter.Y),  2) <= Math.Pow(backStick.Width /  22)) {
                            leftTouch = touch;
                            leftPosition = touch.Position;
                             continue;
                        }
                         if (Math.Pow((touch.Position.X - rightThumbstickCenter.X),  2) + Math.Pow((touch.Position.Y - rightThumbstickCenter.Y),  2) <= Math.Pow(backStick.Width /  22)) {
                            rightTouch = touch;
                            rightPosition = touch.Position;
                             continue;
                        }
                         break;
                     case ControlModes.Rough:
                         if (touch.Position.X <= TouchPanel.DisplayWidth /  2 && touch.Position.Y >= TouchPanel.DisplayHeight /  3) {
                            leftTouch = touch;
                            leftPosition = touch.Position;
                             continue;
                        }
                         if (touch.Position.X > TouchPanel.DisplayWidth /  2 && touch.Position.Y >= TouchPanel.DisplayHeight /  3) {
                            rightTouch = touch;
                            rightPosition = touch.Position;
                             continue;
                        }
                         break;
                }
                 if (leftTouch.HasValue && rightTouch.HasValue) {  break; }
            }
             if (!leftTouch.HasValue) { leftPosition = leftThumbstickCenter; }
             if (!rightTouch.HasValue) { rightPosition = rightThumbstickCenter; }
        }

         public  override  void Draw(GameTimerEventArgs e, SpriteBatch spriteBatch) {
             if (RightThumbstick.Length() >  0) {
                spriteBatch.Draw(backStick, rightBackStick, Color.White);
            }
            spriteBatch.Draw(rtexture, rightCornerPosition + RightThumbstick * distanceThumbsticks, Color.White);
             if (LeftThumbstick.Length() >  0) {
                spriteBatch.Draw(backStick, leftBackStick, Color.White);
            }
            spriteBatch.Draw(ltexture, leftCornerPosition + LeftThumbstick * distanceThumbsticks, Color.White);
        }

    }
}

在本節的Demo中,左手遙控桿用於移動角色。需要說明一點,基於XNA右手坐標系下,一個場景模型從3DMAX中默認坐標系中導入進游戲,角色若要在其表面上移動,改變的不是X、Y值,而是X、Z值,Y值代表實際高低深度,這與后面章節將要講到的HeightMap有很大區別:

右手遙控桿則用來旋轉攝像機和角色(模擬PC中的鼠標右鍵按住不放時的場景旋轉功能),實際游戲開發中大家可以在此基礎上作更進一步設計,比如單擊、雙擊、長時按壓以實現主角攻擊、射擊、特技、魔法等行為,使得遙控桿功能得以最大化,滿足游戲更多的操控需求:


本節源碼中集成了EngineNine源碼的核心部分,源碼下載地址:(WP)SLXnaGame2

手記小結:本節主要講解了基於不同視角的3D場景搭建以及傳統的基於遙控桿的游戲操控功能實現,也算是非常簡單的3D游戲開發入門級場景設計知識。后續章節我將在本節源碼的基礎上進行深度拓展,通過搭建出各種類型的經典3D游戲Demo案例向大家展示SL.XNA在跨平台3D游戲開發方面的強大與高效,敬請關注。

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


免責聲明!

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



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