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的游戏开发博客。

3D绘制允许你在游戏和编辑器中绘制场景中的所有对象。 记住这不是一个贴花系统,所以你的游戏不会因为你画了多少而延迟。相反,您的FPS将保持不变,即使您将对象绘制一百万次! 看看:WebGL演示|论坛线程 ▶一致的性能 即使你画了很多,在3D中画的速度还是很快的原因是因为颜料被烘焙成物体的纹理。你的对象已经有纹理了,为什么不使用它们呢?代码也不会产生垃圾(0 gc alloc),所以您不必担心随机延迟峰值。 ▶闪电快速绘画 烤漆成纹理听起来很慢,但是画图代码是在GPU上100%完成的,这使得它的速度非常快。绘制代码也经过了大量优化,以通过将绘制操作组合在一起来最小化状态更改。 ▶完整的c#源代码 就像我的所有资产一样,我提供了完整的c#源代码——没有什么隐藏在.dll中。代码的组织和注释也很好,所以如果需要,可以很容易地进行更改。 ▶长期支持 就像我所有的资产,我提供长期的支持,不会在你购买后就消失。我也提供定期的免费更新基于伟大的功能从客户的要求。 ▶蒙皮绘画 在3D绘制允许您绘制动画对象与伟大的性能。看看WebGL的演示,看看僵尸油漆看起来有多棒。 ▶无缝紫外线绘画 如果你有一个复杂的网格,它是常见的接缝时绘画,甚至当使用专业的绘画软件。3D绘画解决 了这个问题,包括“缝线固定”工具。3D绘画也使多个对象之间的绘画无缝连接,即使它们有不同的比例。 ▶易于使用 就像我的所有资产一样,我尽量保持界面简单。在几分钟之内,你就可以在游戏中添加绘画功能,并根据你的需要调整简单而强大的设置。 ▶团队基础绘画 3D绘画具有易于使用的基于团队的绘画功能。你可以指定一个特定的颜色给一个特定的团队,并计算每个团队在你的场景中画了多少像素! ▶Multi-Texture绘画 如果你有一个复杂的材质和着色器,有多个纹理一起工作,那么没问题。3D绘画允许你创建画笔,在同一时间绘制多个纹理,并给予每个画笔独特的设置。 ▶完整的物质支持 3D绘制不仅支持绘制所有的材质和着色器与统一,但所有你的定制的!使用直观的检查器,您可以轻松地选择您想要绘制的材质和纹理。 ▶在游戏&编辑器 3D绘画从一开始就被设计来支持游戏和编辑器中的绘画。这允许您在编辑器中快速调整纹理,然后在游戏中使用完全相同的特性绘制它们。 ▶混合模式 你不局限于画普通的反照率纹理。3D绘画有一系列的混合模式和设置,允许你画任何类型的纹理你喜欢。例如,添加混合照明纹理,RGB隔离阿尔法混合切割纹理,和更多。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值