Posts

深度探索DxFramework 10-1

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com 第10章 进入三维的世界 1 10.1.3D预备数学知识和相关代码的分析 10.2 向量,矩阵,四元数 10.3 三维几何工具集 10.3.1 与三维空间的平面相关算法 对于给定的3D点P0和法向量N,过点P0并且和N垂直的平面就可以定义为满足方程N.(P-P0) = 0的集合。(不明白这个定义怎么来的读者,可以回忆一下两向量点积的几何意义)。如下图,平面中任意点与P0的连线都与法线N是垂直的。 如果令法向量N为(A,B,C),点P0为(P0x,P0y,P0z)。则平面的方程为: $ Ax + By + Cz + D = 0 $ ( 此时 $ Ax + By +Cz + D = \overset{\rightharpoonup}{N}\cdot P $ ,$ D = -\overset{\rightharpoonup}{N}\cdot P $ ) 根据平面法线的指向,平面把它所处的空间划分成两个半空间,法线所指向的半空间称为正半空间,也可以称为正侧空间。另一半则称为负半空间,也称为负侧空间。判断某点和某平面的位置关系可以依照上面三个判断式,如下: 判断式 位置关系 Ax+By+Cz+D=0 点(x,y,z)在平面上 Ax+By+Cz+D>0 点(x,y,z)在正半空间 Ax+By+Cz+D<0 点(x,y,z)在负半空间 在此可以举一个例子来说明。如下图: 在图中,平面A由法线N(0,1,0)和点P0(0,5,0)定义。那么,定义式Ax+By+Cz+D=0的D为: D=-N.P0 = -(00+15+0*0) = -5 P3的坐标为(6,5,2),代入方程式Ax+By+Cz+D中,为: 60+51+0*2-5=0 所以P3在平面上。P1的坐标为(0,10,0),代入方程式Ax+By+Cz+D中,为: 00+101+0*0-5=5>0 所以P1在平面的正半空间。P2的坐标为(0,2,0),代入方程式Ax+By+Cz+D中,为: 00+21+0*0-5=-3<0 所以P1在平面的负半空间。tool3d.cpp文件中定义了点和平面的位置关系判断的两个函数,如下:

深度探索DxFramework 8-2

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com 第8章 World Map的剖析 2 8.2 Demo的游戏角色 8.2.1 游戏角色(role)的概念 world map demo用C_MapPC类描述了游戏主角player。而C_MapPC又继承自C_MapObject。追本溯源,就必须先要分析和理解C_MapPC类的实现。前面提过,C_MapObject继承自C_GameObject2D。是作为任何一种在游戏场景地图上的游戏对象(game object)的基类。比如主角,敌人,各种宝物等等。在这个demo中。这些“地图上的游戏对象”有什么共同的属性?答案就在C_MapObejct类的成员变量中: class C_MapObject : public C_GameObject2D { protected: DIR_TYPE face; //角色的朝向,东,南,西,北 int moveX; //每次角色移动距离的 int moveY; //大小,以像素为单位 int stepSize; //moveX/moveY变量的修正值 int xPos, yPos; //角色在世界中的坐标 int xModifier, //用在包围盒计算的修正值,将会在 int yModifier; //CheckWalkOn成员函数中使用 int animFrame; //当前播放的动画帧 C_Sprite* p_MapSprite // sprite on map }; DIR_TYPE类型的face成员变量很好理解,在大部分2D游戏中,主角,或者是NPC。都有着四个或者更多的朝向。如果是静止的物品,比如说是一个不会动的道具,那么它的朝向就只有一个。对应地,每一个朝向也对应着一系列的动画帧,如图: int类型的成员变量moveX,moveY也容易理解,当游戏对象在世界中移动时,每一帧都产生了位移。world map demo是基于二维空间的,所以必须使用两个变量来分别描述游戏对象沿着X轴和Y轴产生的位移。游戏对象既然生存在“游戏世界”中,所以就需要两个成员变量xPos,yPos来描述它在世界中的位置,以便进行绘制和其他相关的操作。作为一个2D游戏对象,要能渲染出来,就肯定少不了需要一个C_Sprite实例来作为它的成员。如果这个2D游戏对象还有动画效果的话,就应还需要一个变量来维护当前正绘制的动画帧。C_Sprite类型的对象指针p_MapSprite和int类型成员变animFrame正是满足所需。另外的几个修正值变量:stepSize,xModifier,yModifer将会在后面代码分析中详细描述它们的含义和作用。 在world map demo中,游戏对象有几个重要的行为。它们是: 游戏对象和场景间的碰撞检测。即是说某个游戏对象,比如主角,能否通过某一个格子而向前移动。 游戏对象沿着某个方向移动。 游戏对象在游戏场景地图中绘制。 游戏对象在小地图中绘制。 //负责移动map object在地图上移动的函数 void C_MapObject::Move(DIR_TYPE direction) { if (direction == DIR_NORTH) { moveY = -stepSize; } else if (direction == DIR_SOUTH) { moveY = stepSize; } else if (direction == DIR_EAST) { moveX = stepSize; } else { //(direction == DIR_WEST) moveX = -stepSize; } face = direction; ++animFrame; } 上面的函数很好理解,stepSize成员变量的作用在此也很明白了。就是根据不同的移动方向,设定沿X轴,Y轴移动的位移量。

深度探索DxFramework 8-1

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com 第8章 World Map的剖析 1 World map demo展示了经典的2D RPG的一些基本的实现技术。在world map demo中,玩家通过键盘操纵主角,即一个小精灵。在几个场景中来回行走。。游戏运行时如图: 这个demo的程序代码分别在以下几个文件中,它们分别是: worldmap.h worldmap.cpp mapobject.cpp mapobject.h map_pc.h map_pc.cpp 这个Demo主要实现了的几个技术点功能是: 游戏场景的瓷砖拼图实现。 大于屏幕的地图的卷轴(scrolling)显示技术 游戏场景地图之间的切换。 主角和场景之间的碰撞检测 小地图的显示。 玩一玩这个demo。并且用面向对象的思维方式思考一下,这个demo可以划分成怎样的类?对象的粒度,类的层次应该如何搭建比较合适?我们可以追随这个demo的设计者的思维,看看为了实现这个demo,他们是如何进行架构分析的。 前面我们提过,每一个利用DxFramework开发的游戏实例,都是一个game state。world map demo也不能违反这个规定,所以实现了一个类C_WorldMap,此类继承自C_GameState。此类作为一个游戏“总管”,负责了游戏场景地图的初始化和渲染;外设输入的响应;游戏状态的更新;以及对所有在地图上的游戏对象(game object),比如主角等,进行管理。 类C_MapObject继承自C_GameObject2D。它可以用来作为任何一种在游戏场景地图上的物体(map object)的基类。比如主角,敌人,各种道具等等。world map demo只实现了“主角”这一个在地图上的物体。如果要实现各种宝物的话,可以继承自C_MapObject,派生一个具有宝物行为和属性的类即可。 类C_MapPC则表示了由玩家控制的主角player。C_MapPC继承自C_MapObject。表明了主角player也是“生存”在场景中,拥有map object的一般行为。world map demo主要就是利用了这三个类实现了游戏,架构很简单明了。下面是这个demo的静态类图: 上面的图反映了world map demo的类层次架构,而这个demo的运行时序则如下图: DxFramework调用类C_WorldMap的构造函数,构造出游戏场景;构造出各种游戏对象(game object)实例对象,在这demo中就是C_MapPC类的一个实例对象。然后在每一帧中,DxFramework调用void C_WorldMap::Update()函数,在这个函数中响应了外设的输入;更新world map demo的游戏场景和游戏对象的各个属性状态。其后DxFramework调用void C_WorldMap::Render2D(),把游戏场景,各种游戏对象,统计窗口,小地图依次渲染出来。 8.1. demo的场景地图分析 游戏场景是一个游戏中最重要的部件之一。作为角色,道具,游戏剧本的载体。游戏中发生的各种各样的事件,游戏剧情的发展,都离不开游戏场景这一个“容器”。因此,场景作为游戏世界的一个具体表现,它应该负责整个游戏框架内场景状态的各种操作,控制。同时它也应该能对“在它上面”的游戏组成元素进行有效的管理,比如对各种敌人的添加,删除,状态更新等等。游戏场景各种角色道具剧本都应该以特定的次序添加到场景中。它还应该负责对玩家的输入操作进行管理和派发,等等。正因为如此,demo用整个游戏实例类C_Worldmap来实现场景管理是合理的。下面就来一步一步分析world map demo是如何组织和管理场景的。 8.1.1. 场景坐标系和屏幕坐标系 在讨论场景之前,有必要弄明白两个非常重要的概念:场景坐标系 ,屏幕坐标系。所作者在写作过程中曾经以“世界坐标系”表达这同一个概念,但是在3D渲染流水线中也有一个“世界坐标系”。考虑到这样会导致读者产生混淆,所以在这里改用“场景坐标系”表示这一概念谓的场景坐标系就是指,以实际游戏场景地图的某个点为原点,指定。。。。屏幕坐标系是指。。。 为什么要建立两个坐标系?用一个屏幕坐标系也完全可以实现的啊?道理很简单,因为一个屏幕不能全部显现整副地图,地图需要卷动才能显现各部分于屏幕上。引入场景坐标系,将大大方便了后文提到的“地图卷轴”技术的实现。 指定一个坐标系有两个要素,第一个就是原点的定位,第二个就是坐标轴的朝向。千言万语不如一张图来得直观。世界坐标系和屏幕坐标系的关系如下图: 明白了场景坐标系和屏幕坐标系后,可以开始着手分析了游戏场景的组成了World map采用了常用的正方形瓷砖拼图(tile)的方式来组织地图元素。什么是瓷砖拼图?观察上面的游戏运行时的图,可以发现,大多数场景都是呈有规律状地重复排列的。因此,美术人员在着手绘制游戏场景的时候,并不需要整幅整副图去绘制。而是绘制一系列大小相同而样子不同的图片,这些图片便称之为瓷砖(tile)。然后针对不同的场景设定要求,利用编辑器,把整个场景划分成一系列和瓷砖大小一样的格子。接着好像铺地板一样,把瓷砖贴到相对应的格子中。外观表现相同的格子就贴同一种瓷砖就可以了。这样子,利用有限的瓷砖图片,就可以组合成各种各样不同的场景,既可以大量地节省美术人员的时间,也可以提高游戏的性能。一般地,美术人员绘制好所有的瓷砖图片之后,都会把这些瓷砖整合成一张大的图片。这样子的好处是,用一个大存储空间来保存这些图片数据,其运行效率和工作性能都比用几个小的存储空间保存多个图片要好。world map demo使用的场景瓷砖图片的文件如下: 可以看到,world map使用了四种瓷砖,通过对这四种瓷砖进行各样的排列,就能拼成各种不同的游戏场景地图。这四种瓷砖分别是草地,岩石,树林,河水。 8.1.2. 场景数据的组织 有了不同的瓷砖,在计算机中,就必须要用不同的数值去表示它。在world map demo中。分别使用整数0,1,2,3表示草地,岩石,树林,河水。在游戏中,游戏场景地图数据都是用文件的方式保存在磁盘中,在初始化的时候再从磁盘中载入到内存中。world map demo也不例外。这个demo的所有游戏场景地图的信息都是保存在map.

深度探索DxFramework 4-1

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com 第4章 初始锋芒 剖析DemoA 4.1 Demo实现的功能点和整体架构 4.1.1 实现的功能点 4.1.2 Demo的整体架构 4.2 Demo实例类的定义和初始化 class C_DemoA : public C_GameState { public: C_DemoA(); virtual ~C_DemoA(); virtual void Input(); virtual void Update(); virtual void Render2D(); private: C_Sprite* p_Background; //背景图片 C_Sprite* p_Box; //用来演示的小方盒 // 各种dxfrramework支持的图片格式 C_Sprite* p_Bmp8; //8位bmp格式 C_Sprite* p_Bmp24; //24位bmp格式 C_Sprite* p_Png8; //8位png格式 C_Sprite* p_Png24; //24位png格式 C_Sprite* p_Png32; //32位png格式 DVECTOR2 translation; // box的屏幕坐标 int alpha; // box的透明度 static const int VELOCITY; // box的移动速度,为常量 static const int ANIMATION_SPEED; // box的动画变化速度,为常量 }; C_DemoA::C_DemoA() { alpha = 255; p_Background = new C_Sprite(_T("demoa_background.

深度探索DxFramework 3-2

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com 第3章 步入二维游戏的世界 2 3.5 DxFramework对精灵的封装实现 3.5.1 什么是精灵 3.5.2 精灵的实现——C_Sprite类 3.6 游戏中的Game Object 3.6.1 game object的定义 3.6.2 二维的game object GameObject2D类

深度探索DxFramework 3-1

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com 第3章 步入二维游戏的世界 1 3.1 二维动画的原理 在介绍计算机动画的原理之前,先谈谈大家都非常熟悉的电影的播放原理。播放电影的时候需要一大盘的电影胶片。胶片上面就是一帧一帧连续的静止图像。当播放的时候,把胶片按播放顺序,依次通过播放机的镜头,这样胶片上的图像将会逐帧投影到银幕上。因为人眼存在着视觉暂留的生理现象。当眼睛正在观察的物体突然消失的时候,被观察的物体还能在人眼中存留约1/24秒左右。因此,只要一系列本来就是连续的静止的图像,当以合适的速度逐个通过人眼的时候。便会产生连续的“会动”的视觉效果。 根据人的视觉存留时间可以知道,当电影胶片以每秒24帧的速度播放时,人们看到的就是和现实生活中速度相同的画面。当电影胶片以每秒大于24帧的速度播放时,画面中的各种动作将会变快,这就是俗称的“快镜头”。而如果电影胶片以每秒低于24帧的速度播放时,画面中的各种动作将会变慢,这就是俗称的“慢镜头”。 计算机动画的原理和播放电影的原理是类似的。举一个简单的二维动画例子:假如要做一个人物走动的二维动画,首先要研究人走路时身体各部位,如躯干、四肢移动摆动的位置。然后按相应时间相应的位置,把这些连续动作绘制成一副副的图。在计算机中就可以存储在一些图片文件中。这些“一副副的图”其实就是类似于电影胶片。把图从磁盘文件中载入到内存,然后按顺序将其逐帧在屏幕中绘制出来,就类似于播放电影的过程。当一组动作播放完成后,再从头从动作的第一帧播放,周而复始,就形成了连贯的动画。 三维动画和二维动画播放原理是一致的,也是一帧一帧地在屏幕中绘制。与二维动画类似,三维动画也可以分实时和非实时。我们玩的三维计算机游戏的游戏画面其实就可以认为是一种实时三维动画,这些画面都是在每一帧显示前由计算机实时地计算生成。非实时三维动画则用相关软件预先逐帧计算好并且存储在文件中,播放的时候从文件中逐帧读取数据显示即可。在本章中先讨论二维动画。 3.2 基础数学工具 接着介绍是vector2.h文件中的模板类和函数。VECTOR2定义了一个二维矢量的模板类。 #pragma once #include "globals.h" #include "error.h" //最小误差,意思是两浮点数之差为小于0.0001的时候, //就认为这两浮点数是相等的。 const XFLOAT VECTOR2_ZERO_TOLERANCE = 0.0001; /* MSVC6 cannot use the overloaded operators due to a compiler bug/feature */ #if _MSC_VER < 1300 #ifndef NO_TEMPLATE_SELECTION #define NO_TEMPLATE_SELECTION #endif #endif template< class T > class VECTOR2 { public: VECTOR2() {} VECTOR2(const T& x, const T& y) : x(x), y(y) {} //复制Direct3D的D3DXVECTOR2类型数据的内容 VECTOR2(const D3DXVECTOR2& v) : x((T)v.