XNA教程-3D游戏-09-添加敌方UFO
应当说之前的Chapter08是插进来的一个部分,让我们的工程还没有变得非常巨大之前,在我们第一个需要声音的对象创建之后,插进如何制作音频的教程。而按照正常的逻辑,我们现在应该添加敌人,让我们的导弹”有的放矢”(有地方使)。和2D类似,UFO是我们的敌人,他从屏幕的最远端向我们靠近,而最终飞过我们的头顶后消失,其实就游戏逻辑来说和2D是一回事,只是所在空间和视角不同了。
惯例,在第一段之后说一些废话。最近更新的非常缓慢,不当心看了看最近博客的点击量发现这数字也是小的可怕至极。不过我不太关注那些东西的,只是自己也期待着快点把这个教程结束,然后继续我原来的博客路线。本来以为暑假可以用XNA做点东西,所以觉得写这个也挺有趣,不过最近做别的东西中,所以这个教程快快结束吧,希望能帮助到有需要的人就对了。说起来2D教程只用了一个星期啊……
言归正传,开始这一节的教程吧。
第一步、初始化UFO
和terrain以及missilelauncher的base或head一样,载入一个GameObject的典型方法也同样适用于UFO,只是不止一个UFO,所以我们还是类似2D的,使用了GameObject的数组。
1、声明
在Game类,你声明了一大串一大串的地方添加如下的声明。
Random r = new Random();
const int numEnemyShips = 3;
GameObject[] enemyShips;
-
r是一个Random对象,可以让我们用来随机确定UFO的初始位置及速度。
-
numEnemyShips看来是一个会被我恶搞的变量,指同屏(最大)出现的UFO数量。
-
enemyShips当然就是GameObject的数组,数量由上一个变量确定。
2、实例化
找到LoadContent方法,在方法的最后加上实例化一个GameObject的代码。
enemyShips = new GameObject[numEnemyShips];
for (int i = 0; i < numEnemyShips; i++)
{
enemyShips[i] = new GameObject();
enemyShips[i].model = Content.Load<Model>(
@"Modelsenemy");
enemyShips[i].scale = 0.1f;
enemyShips[i].rotation = new Vector3(
0.0f, MathHelper.Pi, 0.0f);
}
实例化数组后,用一个 for 循环遍历这个数组。
每一次遍历,先实例化GameObject,然后确定模型以及缩放和旋转,还记得旋转吗?Pitch, Yaw, Roll,所以这里的初始化值是0,pi,0,yaw了180度,即朝向屏幕外的我们。
这里没有对其他的一些成员做初始化,它们被留到Update里面去处理。
第二步、更新UFO
留了一部分工作放在Update里完成,所以Update的工作也比较大,和Missiles一样,我们写另外一个方法来处理。
方法命名为:UpdateEnemyShips()。把它的调用放在Game类中Update方法处理音频update的后面。
然后写UpdateEnemyShips方法的内容如下:
void UpdateEnemyShips()
{
foreach (GameObject ship in enemyShips)
{
if (ship.alive)
{
ship.position += ship.velocity;
if (ship.position.Z > 500.0f)
{
ship.alive = false;
}
}
// next step...
}
}
因为已经实例化,所以可以用foreach来遍历了。如果UFO存活,那么位置加上速度即更新为当前位置。但是也必须处理如何杀掉它,记得第一段说的,”飞过头顶的时候”杀掉,那么就是检查Z轴就可以了。
注释部分是留给下一步,完成第一步欠下的,UFO的一些初始化工作。
第三步、继续初始化UFO
就像这一步的名字一样,继续初始化,这是第一步欠下的债。第一步没有个position和velocity初始化,因为所有的enemy的这两个属性不是一次性初始化就好了的,随着Update里的死亡,他们要重新被初始化,所以我们不把这个工作放在第一步的LoadContent方法里,而是放在Update方法里。
1、声明
同样的我们需要先声明几个变量,帮助我们使用随机数。在Game类内声明以下变量:
Vector3 shipMinPosition = new Vector3(-2000.0f, 300.0f, -6000.0f);
Vector3 shipMaxPosition = new Vector3(2000.0f, 800.0f, -4000.0f);
const float shipMinVelocity = 5.0f;
const float shipMaxVelocity = 10.0f;
如果你看过2D教程,那么这四个变量的用意就一定知道。
两个Vector3把起始位置限定在了一个立方体内。X轴-2000~2000(屏幕的左右位置),Y轴300~800(屏幕的上下位置,即高度),Z轴-6000~-4000(屏幕的深度,即距离屏幕6000到4000这个距离)。
而velocity,即速度,因为我们的UFO走平行于Y轴的直线,所以只用float限定他在Z轴上的速率。5到10之间。
2、继续更新UFO
第二步的时候,我们处理了或者的UFO如何的更新,和如何杀死。现在要处理杀死的UFO,如何复活,包括游戏刚刚运行时所有的都在死掉状态的UFO如何初始化。在第二步的方法注释部分添加如下代码:
else
{
ship.alive = true;
ship.position = new Vector3(
MathHelper.Lerp(
shipMinPosition.X,
shipMaxPosition.X,
(float)r.NextDouble()),
MathHelper.Lerp(
shipMinPosition.Y,
shipMaxPosition.Y,
(float)r.NextDouble()),
MathHelper.Lerp(
shipMinPosition.Z,
shipMaxPosition.Z,
(float)r.NextDouble()));
ship.velocity = new Vector3(
0.0f,
0.0f,
MathHelper.Lerp(
shipMinVelocity,
shipMaxVelocity,
(float)r.NextDouble()));
}
代码看起来很长,但其实逻辑很简单,我们只修改了三个属性:alive、position、velocity。
只是我们用到了MathHelper.Lerp方法和Random.NextDouble方法嵌在代码里。Lerp限定r.NextDouble生成的一个随机的双精度浮点数的范围,分到X,Y,Z三个分量上而已。而velocity就像之前说的,只需要Z轴有速度,所以只在Z轴用了相应的方法。
那么处理完这些,最后一步就是绘制UFO了。
第四步、绘制UFO
找到Draw方法,和missiles的画法一样,用一个foreach循环就搞定了。多亏了我们有写DrawGameObject这样的方法。
foreach (GameObject enemyShip in enemyShips)
{
if (enemyShip.alive)
{
DrawGameObject(enemyShip);
}
}
好了好了,这样就没有问题了。编译、运行。(效果如图,点击放大)我承认我改了UFO数量。
【官方源代码下载】