关闭

3D 游戏控制

标签: 手机动画windows phone 7游戏设计windows phone
3262人阅读 评论(3) 收藏 举报
分类:

摘要

了解如何制作 XNA 游戏使用的 3D 模型,以及于 XNA 游戏中显示 3D 模型的基本做法之后,这一次我们将要为大家介绍 3D 模型的控制技巧,包括旋转、放大/缩小、平移、以及相机控制等游戏常见的操作。有关如何制作 XNA 游戏使用的 3D 模型,以及于 XNA 游戏中显示 3D 模型的基本做法可以参考 [3D 游戏设计] 一文的详细说明。

 3D 游戏设计基础

在执行控制与显示 3D 模型之前,首先我们必须了解以 XNA 为基础的 3D 游戏采用的坐标系统,表示坐标与控制坐标转换常用的数据型态,并认识 Object Space、World Space、View Space、和 Screen Space 四种空间代表的意义与用途。

以 XNA 为基础的 3D 游戏采用的是右手坐标系统 (Right-Handed System),其X轴往右递增,Y 轴往上递增,而 Z 轴则是往使用者的方向递增,如图1 所示:

图1:以 XNA 为基础的 3D 游戏采用的右手坐标系统

控制或显示 3D 模型,我们需要用到描述坐标的 Vector2 和 Vector3 结构,以及用来协助坐标计算的 Vector4 结构与 Matrix 结构。有关 Vector2、Vector3、和 Vector4 等和坐标有关的结构型态的详细功能可以参考 [XNA Framework 常用的类别] 一文的说明。

除了描述坐标和进行坐标计算需要用到的结构型态以外,要显示和控制 3D 模型,我们还需要了解 3D 模型的 Object Space (亦称 Model Space)、World Space、View Space、和 Screen Space (亦称 Projection Space) 四种空间表示法的意义。

简单来说,3D 模型在 3D 的世界中有几种不同的空间表示法,3D 模型中的坐标点未经任何的转置处理前称为物体空间 (object space),当 3D 模型经过旋转,放大/缩小等转置处理之后就会得到世界空间 (world space),经过转置处理过的世界空间在显示给用户检视之前,还需要加入相机的位置和角度的处理,形成检视空间 (view space),最后的检视空间在显示之前必须投射到屏幕的2维空间,称为屏幕空间 (screen space),而屏幕空间显示的内容就是用户最终看到的结果。

要将 3D 模型的物体空间转换成世界空间、检视空间、和屏幕空间,我们需要借助于 Matrix 结构及其提供的属性和方法。Matrix 结构负责定义一个 4x4 的矩阵,其常用的属性可以参考表1 的说明:

表1:Matrix 结构常用的属性
属性名称 说明
Backward 描述后退的向量。
Down 描述下移的向量。
Forward 描述前进的向量。
Identity 传回单位矩阵。单位矩阵的定义就是任何矩阵和单位矩阵相乘之后的结果会得到原来的矩阵。
Left 描述左移的向量。
Right 描述右移的向量。
Translation 描述平移的向量。
Up 描述上移的向量。

[说明]

单位矩阵的定义是矩阵的元素中,除了列编号和栏编号内容值相同的元素的内容值为 1 以外,其他的元素的内容值皆为 0。例如 2x2 的单位矩阵的内容如下:

3x3 的单位矩阵如下:

而 n*n 的单位矩阵如下:

Matrix 结构常用的方法请参考表2 的说明:

表2:Matrix 结构常用的方法
方法名称 说明
Add 支持矩阵相加的方法。
CreateBillboard 依据指定的物体的位置建立球形的广告牌。
CreateConstrainedBillboard 依据指定的轴建立圆柱体广告牌。
CreateFromAxisAngle 依据指定的旋转轴心和角度建立 Matrix 结构。
CreateFromQuaternion 依据 Quaternion 结构指定的向量和角度建立用来旋转物体的 Matrix 结构。
CreateFromYawPitchRoll 依据X轴旋转角度、Y 轴旋转角度、和 Z 轴旋转角度建立用来旋转物体的 Matrix 结构。
CreateLookAt 建立以相机为原点的 View Space,并表示成 Matrix 结构。
CreateOrthographic 建立直角的 Projection Space,并表示成 Matrix 结构。
CreateOrthographicOffCenter 建立可客制化的直角 Projection Space,并表示成 Matrix 结构。
CreatePerspective 建立透视的 Projection Space,并表示成 Matrix 结构。
CreatePerspectiveFieldOfView 依据视角 (Field of View-简称 FOV) 建立透视的 Projection Space,并表示成 Matrix 结构。
CreatePerspectiveOffCenter 建立可客制化的透视 Projection Space,并表示成 Matrix 结构。
CreateReflection 利用指定的平面的坐标系统的反射建立 Matrix 结构。
CreateRotationX 建立能够以 X 轴为轴心旋转指定角度的 Matrix 结构。
CreateRotationY 建立能够以 Y 轴为轴心旋转指定角度的 Matrix 结构。
CreateRotationZ 建立能够以 Z 轴为轴心旋转指定角度的 Matrix 结构。
CreateScale 建立可以用来执行放大/缩小 3D 模型的 Matrix 结构。
CreateShadow 依据指定的光源建立可以用来表示阴影的 Matrix 结构。
CreateTranslation 建立可以用来平移 3D 模型的 Matrix 结构。
CreateWorld 建立可以用来表示 World Space 的 Matrix 结构。
Decompose 从 Matrix 结构取出放大/缩小、平移、和旋转的成份值。
Determinant 计算 Matrix 结构的 determinant 值。以 2x2 的 Matrix 结构为例,其 determinant 值为 ad-bc。
Divide 计算 Matrix 结构除以一个数值或是另外一个 Matrix 结构的结果,并表示成 Matrix 结构。
Invert 取得反矩阵,并表示成 Matrix 结构。
Lerp 对两个 Matrix 结构对应的元素值执行线性内插。
Multiply 计算 Matrix 结构除以一个数值或是另外一个 Matrix 结构的结果,并表示成 Matrix 结构。
Negate 将 Matrix 结构每一个元素值改成负数,并将执行的结果表示成 Matrix 结构。
op_Addition 对两个 Matrix 结构执行加法,并将执行的结果表示成 Matrix 结构。
op_Division 将 Matrix 结构除以一个数值,或是另外一个 Matrix 结构的元素,并将执行的结果表示成 Matrix 结构。
op_Equality 判断两个 Matrix 结构是否相等。
op_Inequality 测试 Matrix 结构和另外一个 Matrix 结构是否不相等。
op_Multiply 将 Matrix 结构乘以一个数值,或是另外一个 Matrix 的元素,并将执行的结果表示成 Matrix 结构。
op_Subtraction 对两个 Matrix 结构执行减法,并将执行的结果表示成 Matrix 结构。
op_UnaryNegation 将 Matrix 结构的每一个元素都设定成负值。
Subtract 支持 Matrix 结构相减的方法。
Transform 依据 Quaternion 结构的内容对 Matrix 结构执行旋转。
Transpose 对 Matrix 结构执行转置 (Transpose) 运算,也就是列变成行,行变成列的转换。

[说明]

建立 3D 场景的时候,将 2D 的物体显示成 3D 的效果以得到较好的效能的做法称之为 Billboarding (广告牌),而 Matrix 结构提供的 CreateBillboard 方法与 CreateConstrainedBillboard 方法就是支持建立广告牌的方法。将 2D 的物体显示成 3D 的效果主要的概念就是由物体图案组成的纹理套用在矩形的元素上,由程序视需要旋转矩形的元素,使其面向使用者的目光。请注意物体的图案的形状可以不是矩形的形状,而且广告牌还可以允许某一部分形成透明的效果,以隐藏对象图案的内容。

许多游戏程序都是利用广告牌技巧显示 3D 动画,例如游戏的主角在 3D 的迷宫内走动,所看到,而且可以取用的武器和宝物都是由 2D 的图案套用在矩形元素形成的效果。3D 游戏显示树林、灌木丛、或是天空的云的时候,常常都是靠广告牌技术呈现的效果。当游戏程序将图案套用到广告牌的时候,矩形元素会先旋转至面对用户的视线,然后再执行套用纹理的动作。

广告牌的效果适合套用在对称的物体,特别是垂直对称的物体,但是物体的视角不可以太高,否则当物体由上往下审视广告牌的时候,其效果会变成 2D 的效果,而不再具有 3D 的视觉效果。

 3D 模型转置计算

Matrix 结构是游戏程序显示与控制 3D 模型极常用的型态,当游戏程序需要平移、旋转、或是放大/缩小 3D 模型时,就可以利用 Matrix 结构提供的功能建立可以将 3D 模型从 Object Space 转换到 World Space 的 World Matrix。例如游戏程序可以呼叫 Matrix 结构的 CreateTranslation 方法建立可以平移 3D 模型的 World Matrix,当您想将 3D 模型平移至 X 坐标为 10,Y 坐标为 0,Z 坐标为 50 的位置,就可以利用以下的程序代码建立 World Matrix:

  1. Matrix WorldTranslation = Matrix.CreateTranslation(new Vector3(10050));

做好之后,游戏程序只要将所建立的 World Matrix 套用至 Mesh 的 Effect,就可以达到平移 3D 模型的效果。

如果游戏程序需要旋转 3D 模型,可以呼叫 Matrix 结构的 CreateRotationX 方法并传入旋转角度表示要依据 X 轴为轴心旋转指定的角度,呼叫 Matrix 结构的 CreateRotationY 方法并传入旋转角度表示要依据 Y 轴为轴心旋转指定的角度,或是呼叫 Matrix 结构的 CreateRotationZ 方法并传入旋转角度表示要依据 Z 轴为轴心旋转指定的角度。请注意 CreateRotationX、CreateRotationY、和 CreateRotationZ 方法需要的角度的单位是弪度量,游戏程序可以视需要先利用 MathHelper 类别的 ToRadians 方法将单位为度度量的角度转换成弪度量,以便执行旋转的动作。

以下的程序代码会建立可以将 3D 模型以 Y 轴为核心旋转 180 度的 Matrix:

  1. Matrix worldRotation=Matrix.CreateRotationY(MathHelper.Pi);

请注意上述的程序代码会利用 MathHelper 类别的 Pi 属性指定旋转的角度为 180 度。游戏程序除了可以利用 CreateRotationX、CreateRotationY、和 CreateRotationZ 方法来旋转 3D 模型以外,也可以利用 Matrix 结构提供的 CreateFromYawPitchRoll 方法指定以 X 轴、Y 轴、和 Z 轴为轴心的旋转角度,其中的 Yaw 代表以 Y 轴为轴心的旋转角度,Pitch 代表以 X 轴为轴心的旋转角度,而 Roll 代表以 Z 轴为轴心的旋转角度,如图2 所示:

图2:Pitch 代表以 X 轴为轴心的旋转角度,而 Roll 代表以 Z 轴为轴心的旋转角度

例如以下的程序代码中呼叫 Matrix 结构的 CreateRotationX、CreateRotationY、和 CreateRotationZ 方法的语法:

  1. Matrix modelWorldMatrix = Matrix.CreateScale(2.0f) * 
  2. Matrix.CreateRotationX(MathHelper.Pi) * Matrix.CreateRotationY(MathHelper.Pi) *
  3.                            Matrix.CreateRotationZ(MathHelper.Pi) *
  4.                        Matrix.CreateTranslation(10050);

就可以改呼叫 Matrix 结构的 CreateFromYawPitchRoll 方法来代替,如下:

  1. Matrix modelWorldMatrix = Matrix.CreateScale(2.0f) * 
  2. Matrix.CreateFromYawPitchRoll(MathHelper.Pi, MathHelper.Pi, MathHelper.Pi) *
  3.      Matrix.CreateTranslation(10050);

[提示]

游戏程序可以利用 XNA Framework 提供的 MathHelper 类别的方法完成常用的数*算,包括计算角度,例如游戏程序需要用到 180 度角,就可以利用 MathHelper.Pi 来取得,需要 360 度角,可以直接利用 MathHelper.TwoPi 来取得,不需要执行 MathHelper.Pi * 2,需要 90 度角的时候则使用 MathHelper.PiOver2 来取得,不需执行 MathHelper.Pi / 2,需要 45 度角时可以利用 MathHelper.PiOver4 来取得,不需要执行 MathHelper.Pi / 4,以获得最佳的执行效能。

除了可以协助游戏程序取得角度以外,MathHelper 类别也可以协助游戏程序执行度度量和弪度量之间的转换,例如 MathHelper 类别的 ToDegree 方法可以将弪度量转换成度度量,而 MathHelper 类别的 ToRadians 方法可以将度度量转换成弪度量,不需要游戏程序自行执行转换的动作。

如果游戏程序需要放大/缩小 3D 模型,可以善用 Matrix 结构提供的 CreateScale 方法,例如以下的程序代码便会建立可以将 3D 模型放大两倍的 Matrix 结构:

  1. Matrix worldSclae = Matrix.CreateScale(2.0f);

透过上述放大/缩小、旋转、和平移等操作,游戏程序能够将 3D 模型定位在指定的位置和角度。请注意 Matrix 结构进行乘法运算的时候必须按照 SRT (Scale * Rotation * Translation) 顺序来执行,也就是必须先执行放大/缩小的动作,再执行旋转的动作,最后再执行平移的动作,因为 Matrix 结构的乘法不会有累积的效果。

学会将 3D 模型的物体空间 (Object Space) 转换成世界空间 (World Space) 之后,接下来我们就要引入相机的位置,计算出 3D 模型的检视空间 (View Space),也就是从使用者的角度检视 3D 模型的位置。

游戏程序可以呼叫 Matrix 结构提供的 CreateLookAt 方法,依据包括相机位置在内的相关信息,建立可以将世界空间转换成检视空间的 Matrix 结构。例如以下的程序代码就会利用 Matrix 结构的 CreateLookAt 方法建立可以将 3D 模型世界空间转换成检视空间的 Matrix 结构:

  1. Matrix viewMatrix = Matrix.CreateLookAt(new Vector3(035), new Vector3(000), 
  2. new Vector3(010));

请注意 CreateLookAt 方法的第一个参数代表相机的位置,第二个参数代表欲检视的物体的位置,而第三个参数则是代表 Up 向量,也就是相机旋转依据的轴心,传入 (0,1,0) 表示相机可以以 Y 轴为轴心旋转 (即图3 中的 UP 轴),传入 (0,0,-1) 表示相机可以以 Z 轴为轴心旋转 (即图3 中的 LOOK 轴),而传入 (1,0,0) 表示相机可以以 X 轴为轴心旋转 (即图3 中的 RIGHT 轴),如图3 所示:

[提示]

X、Y、Z 三个元素值皆为 0 的 Vector3 结构可以表示成 Vector.Zero,而 X、Y、Z 三个元素值分别为 0,1,0 的 Vector3 结构可以表示成 Vector.Up,所以上述建立可以将 3D 模型的世界空间转换成检视空间的 Matrix 结构的程序代码可以简写成以下的样子:

  1. Matrix viewMatrix = Matrix.CreateLookAt(new Vector3(035), Vector3.Zero, Vector3.Up);

计算好 3D 模型的检视空间之后,游戏程序还需要将检视空间转换成屏幕空间,也就是将 3D 模型的检视空间投影到 2D 的平面空间的位置,完成呈现 3D 模型的动作,因此屏幕空间 (Screen Space) 又称为投影空间 (Projection Space)。要完成从检视空间至投影空间的转换,游戏程序可以利用 Matrix 结构提供的 CreatePerspectiveFieldOfView 方法,建立能够执行转换工作的 Matrix 结构 (即 Projection Matrix)。

例如以下的程序代码就会呼叫 Matrix 结构的 CreatePerspectiveFieldOfView 方法,建立可以将 3D 模型的检视空间投射到屏幕空称的 Matrix 结构:

  1. float aspect = (float)Window.ClientBounds.Width / (float)Window.ClientBounds.Height;
  2. Matrix projectionMatrix = Matrix.CreatePerspectiveFieldOfView(
  3. MathHelper.PiOver4, aspect, 1100);

Matrix 结构的 CreatePerspectiveFieldOfView 方法需要的第一个参数是单位为弪度量的视角,其角度必须介于 0~p (即 0~180 度) 之间,第二个参数是高宽比例,您可以将游戏窗口的寛度除以游戏窗口的高度当做高宽比例,或是直接使用 GraphicsDeviceManager 的 GraphicsDevice 属性的 Viewport 属性的 AspectRatio 属性的内容值当做高宽比例,第三个参数是与近平面 (Near Plane) 的距离,请注意距离比与近平面还近的内容都不会呈现在游戏窗口中,第四个参数是与远平面 (Far Plane) 的距离,请注意距离比与远平面还远的内容都不会呈现在游戏窗口中。

有关近平面与远平面在 3 度空间的意义可以参考图4:

图4:近平面与远平面在3度空间的意义

[注意]

以 XNA 为基础的游戏程序必须为每一个欲显示的 3D 模型建立 World Matrix,每一次改变相机的位置或方向都必须重新计算 View Matrix,而 Projection Matrix 则只要在游戏程序开始的时候计算一次即可。

 3D 模型显示控制

了解 3D 模型的显示与控制原理,以及如何建立将 3D 模型的物体空间转换成世界空间的 Matrix 结构,如何建立将世界空间转换成检视空间的 Matrix 结构,以及如何建立将检视空间投射在屏幕空间的 Matrix 结构之后,接下来我们就要制作可以控制 3D 模型显示的游戏程序。

首先请启动 Visual Studio 2010 Express for Windows Phone,建立 [Windows Phone Game(4.0)] 型态的项目,并将制作好的 3D 模型加入到 Content Pipeline 项目中。有关如何制作 3D 模型可以参考 [3D 游戏设计] 一文的说明。

准备好游戏程序欲显示的 3D 模型之后,请编辑 Game1 类别的建构函式,加入以下的变量宣告,负责管理 3D 模型、3D 模型的显示位置、旋转角度、3D 模型大小、相机位置、以及记载能够将 3D 模型的检视空间投射到屏幕空间的 Matrix 结构:

  1. Model SnowMan;                                            //管理 3D 模型的變數
  2. Vector3 modelPosition = Vector3.Zero;                //記錄 3D 模型的顯示位置的變數
  3. float modelRotation = 0.0f;                        //記錄 3D 模型的旋轉角度的變數
  4. float modelScale=2f;                                    //記錄 3D 模型大小的變數
  5. Vector3 cameraPosition = new Vector3(0.0f, 50.0f, 5000.0f);         //記錄相機位置的變數
  6. Matrix ProjectionMatrix;            //能夠將 3D 模型的檢視空間投射到螢幕空間的 Matrix 結構

宣告妥必要的变量之后,请将 Game1 类别的建构函式编辑成以下的样子,负责定义 XNA 游戏的窗口大小:

  1. public Game1()
  2. {
  3.     graphics = new GraphicsDeviceManager(this);
  4.     Content.RootDirectory = "Content";
  5.     graphics.PreferredBackBufferHeight = 800;                //設定遊戲視窗的高度為 800
  6.     graphics.PreferredBackBufferWidth = 480;                //設定遊戲視窗的寬度為 480
  7.     TargetElapsedTime = TimeSpan.FromTicks(333333);
  8. }

设定好游戏窗口的高度和宽度之后,请编辑 Game1 类别的 Initialize 方法,加入初始化触控面板,以及计算能够将 3D 模型的检视空间投射到屏幕空间的 Matrix 结构的程序代码:

  1. protected override void Initialize()
  2. {
  3.     TouchPanel.EnabledGestures =
  4.                 GestureType.FreeDrag |
  5.                 GestureType.Pinch;            //初始化觸控面板,啟用 FreeDrag 和 Pinch 功能
  6.     ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, 
  7. graphics.GraphicsDevice.Viewport.AspectRatio, 
  8. 1.0f, 100000.0f);    //計算能夠將 3D 模型的檢視空間投射到螢幕空間的 Matrix 結構,
  9. //指定視角為 45 度,使用遊戲視窗的高寬比例,與近平面的距離為 //1,與遠平面的距離為 100000
  10.     base.Initialize();
  11. }

完成初始化触控面板,以及计算能够将 3D 模型的检视空间投射到屏幕空间的 Matrix 结构之后,请编辑 Game1 类别的 LoadContent 方法,加入加载 3D 模型资源的程序代码:

  1. protected override void LoadContent()
  2. {
  3.     spriteBatch = new SpriteBatch(GraphicsDevice);
  4.     SnowMan = Content.Load<Model>("SnowMan");            //載入名稱為 SnowMan 的 3D 模型資源
  5. }

[注意]

读者必须视需要修改上述程序代码所指定的 3D 模型资源名称,以加载正确的 3D 模型资源。

加载妥游戏程序欲显示的 3D 模型之后,请将 Game1 类别的 Update 方法编辑成以下的样子,负责依据使用者的触控操作改变旋转 3D 模型的角度,以及放大/缩小 3D 模型的比例:

  1. protected override void Update(GameTime gameTime)
  2. {
  3.     if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
  4.         this.Exit();
  5.     while (TouchPanel.IsGestureAvailable)            //判斷使用者是否執行了手勢操作
  6.     {
  7.         GestureSample gesture = TouchPanel.ReadGesture();    //讀取使用者的手勢操作
  8.    switch (gesture.GestureType)                    //判斷使用者的手勢操作型態
  9.         {
  10.             case GestureType.FreeDrag:                            //FreeDrag 操作
  11.                 modelRotation += gesture.Delta.X;        //依據使用者拖曳的軌跡旋轉 3D 模型
  12.                 break;
  13.             case GestureType.Pinch:                                    //Pinch 操作
  14.                 Vector2 a = gesture.Position;                       //取得第一個觸碰點
  15.                 Vector2 aOld = gesture.Position – 
  16. gesture.Delta;                   //取得第一個觸碰點的起始位置
  17.                 Vector2 b = gesture.Position2;                   //取得第二個觸碰點
  18.                 Vector2 bOld = gesture.Position2 – 
  19. gesture.Delta2;                   //取得第二個觸碰點的起始位置
  20. float d = Vector2.Distance(a, b);            //計算兩個觸碰點之間的距離
  21.                 float dOld = Vector2.Distance(aOld, bOld);  //計算兩個原始座標之間的距離
  22. float scaleChange = (d - dOld) * .01f;               //計算距離的變化量
  23.                  modelScale += scaleChange;     //將距離變化量的 1/10 當做縮放 3D 模型的比例
  24.                  break;
  25.         }
  26.     }
  27.     base.Update(gameTime);
  28. }

制作好利用触控操作改变 3D 模型大小以及旋转角度的程序代码之后,接下来我们就可以依据 3D 模型的旋转角度和大小显示 3D 模型。请将 Game1 类别的 Draw 方法编辑成以下的样子:

  1. protected override void Draw(GameTime gameTime)
  2. {
  3.     GraphicsDevice.Clear(Color.CornflowerBlue);
  4.     Matrix[] transforms = 
  5. new Matrix[SnowMan.Bones.Count];     //依據 3D 模型內含的骨骼數量建立 Matrix 陣列
  6.     SnowMan.CopyAbsoluteBoneTransformsTo(transforms);          //取出骨骼的轉置矩陣資料
  7.     foreach (ModelMesh mesh in SnowMan.Meshes)        //逐一取出 3D 模型中所有的網格資料
  8.     {
  9.         foreach (BasicEffect effect in mesh.Effects)    //逐一設定網格資料的所有 Effect
  10.         {
  11.             effect.EnableDefaultLighting();                        //啟用預設光源
  12.             effect.World = transforms[mesh.ParentBone.Index] * 
  13. Matrix.CreateScale(modelScale) * Matrix.CreateRotationY(modelRotation) * Matrix.CreateTranslation(modelPosition);        //設定Effect的World Space
  14. effect.View = Matrix.CreateLookAt(cameraPosition, Vector3.Zero, 
  15. Vector3.Up);            //設定 Effect 的 View Space
  16.                effect.Projection = ProjectionMatrix;//計算 3D 模型投影到 2D 空間的投影位置
  17.         }
  18.         mesh.Draw();                                            //繪製網格的內容
  19.     }
  20.     base.Draw(gameTime);
  21. }

做好之后请将游戏程序部署到 Windows Phone 7 智能型手机上执行,读者可以利用单点触控的方式在游戏窗口拖曳,控制 3D 模型的旋转角度,也可以利用多点触控的方式放大/缩小 3D 模型的大小。允许用户控制 3D 模型旋转角度和大小的游戏程序的执行画面如图5 所示:

图5:允许用户控制 3D 模型旋转角度和大小的游戏程序的执行画面

[范例档案下载]

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:702030次
    • 积分:6600
    • 等级:
    • 排名:第3759名
    • 原创:19篇
    • 转载:252篇
    • 译文:1篇
    • 评论:47条
    文章分类
    最新评论