七、添加基础敌方人工智能

敌人的人工智能(AI)将定义敌人如何攻击玩家,以及玩家赢得游戏的难易程度。通过拦截玩家对touchListener的监听调用,可以很容易地创建一个预测玩家一举一动的人工智能。然而,这不会给玩家带来有趣的体验,你的游戏也不会很有成就感。你在上一章创造的敌人需要某种攻击计划来吸引玩家并创造出令人满意的游戏体验。

在这一章中,你将为三种不同的敌人类型添加三种不同的人工智能,这三种类型在第二章第一节中讨论过,在第六章第三节中创建:截击机、侦察兵和战舰。从表面上看,鉴于你在上一章学到的东西,这个任务似乎很容易,但事实是创造敌人比创造可玩角色更难。为什么?可玩角色不用思考;这就是玩家所做的。另一方面,敌人至少需要一个基本的人工智能来引导他们完成游戏。

让敌人为人工智能做好准备

在你能处理 AI 之前,你需要首先初始化敌人和他们的纹理。所以,要开始,打开并编辑游戏循环,SFGameRenderer()。你需要添加一个数组来容纳游戏中的所有敌人。要确定敌人的数量,将TOTAL_INTECEPTORSTOTAL_SCOUTSTOTAL_WARSHIPS的值相加(减 1 表示零基数组)。

`package com.proandroidgames;

import java.util.Random;

import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import android.opengl.GLSurfaceView.Renderer;

public class SFGameRenderer implements Renderer{ private SFBackground background = new SFBackground(); private SFBackground background2 = new SFBackground(); private SFGoodGuy player1 = new SFGoodGuy();

private SFEnemy[] enemies = new SFEnemy[SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1];

private int goodGuyBankFrames = 0; private long loopStart = 0; private long loopEnd = 0; private long loopRunTime = 0 ;

private float bgScroll1; private float bgScroll2;

}`

接下来,创建一个新的SFTextures类实例和一个新的 int 数组来保存公共的 sprite 表。现在,spriteSheets[]数组将包含一个元素。在下一章,你将改变这个数组,使它能容纳更多的内容。

提示:你可以更进一步,修改spriteSheets[]数组和SFBackground()来保存背景和精灵的纹理。这样做很容易,并且会让你获得更多的优化。

`public class SFGameRenderer implements Renderer{

private SFEnemy[] enemies = new SFEnemy[SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1]; private SFTextures textureLoader; private int[] spriteSheets = new int[1];

private int goodGuyBankFrames = 0; private long loopStart = 0; private long loopEnd = 0; private long loopRunTime = 0 ;

private float bgScroll1; private float bgScroll2;

}`

现在,你有一个数组来容纳你的敌人,但是没有敌人可以放进去。你需要三个私有方法来填充你的数组:截击机、侦察兵和战舰各一个。

`;

public class SFGameRenderer implements Renderer{

private SFEnemy[] enemies = new SFEnemy[SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1]; private SFTextures textureLoader; private int[] spriteSheets = new int[2];

**private void initializeInterceptors(){

}

private void initializeScouts(){

}

private void initializeWarships(){

}**

}`

创造每个敌人的逻辑

使用一个简单的for循环实例化一个相应类型的新敌人,并将其添加到数组中。例如,在initializeInterceptors()方法中,创建一个for循环,该循环计数到TOTAL_INTERCEPTORS的值。这个循环将实例化一个新的TYPE_INTERCEPTOR类型的敌人,并将其添加到数组中。

`public class SFGameRenderer implements Renderer{

… private SFEnemy[] enemies = new SFEnemy[SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1]; private SFTextures textureLoader; private int[] spriteSheets = new int[2];

;

**private void initializeInterceptors(){

for (int x = 0; x<SFEngine.TOTAL_INTERCEPTORS -1 ; x++){ SFEnemy interceptor = new SFEnemy(SFEngine.TYPE_INTERCEPTOR, SFEngine.ATTACK_RANDOM); enemies[x] = interceptor; }

}

private void initializeScouts(){

}

private void initializeWarships(){

}**

}`

在战舰上使用同样的循环逻辑。

`public class SFGameRenderer implements Renderer{

private SFEnemy[] enemies = new SFEnemy[SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1]; private SFTextures textureLoader; private int[] spriteSheets = new int[2];

**private void initializeInterceptors(){

for (int x = 0; x<SFEngine.TOTAL_INTERCEPTORS -1 ; x++){ SFEnemy interceptor = new SFEnemy(SFEngine.TYPE_INTERCEPTOR, SFEngine.ATTACK_RANDOM); enemies[x] = interceptor; }

}

private void initializeScouts(){

}

private void initializeWarships(){

for (int x = SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS -1; x<SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS -1; x++){ SFEnemy interceptor = new SFEnemy(SFEngine.TYPE_WARSHIP, SFEngine.ATTACK_RANDOM); enemies[x] = interceptor; }

}**

}`

截击机和战舰都从任意方向攻击。然而,侦察兵会从右边或者左边进攻。因此,在实例化 scouts 的循环中,将负载分成两半,从右边实例化一半,从左边实例化一半。

`package com.proandroidgames;

public class SFGameRenderer implements Renderer{

… private SFEnemy[] enemies = new SFEnemy[SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1]; private SFTextures textureLoader; private int[] spriteSheets = new int[2];

**private void initializeInterceptors(){

for (int x = 0; x<SFEngine.TOTAL_INTERCEPTORS -1 ; x++){ SFEnemy interceptor = new SFEnemy(SFEngine.TYPE_INTERCEPTOR, SFEngine.ATTACK_RANDOM); enemies[x] = interceptor; }

}

private void initializeScouts(){

for (int x = SFEngine.TOTAL_INTERCEPTORS -1; x=(SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS) / 2 ){ interceptor = new SFEnemy(SFEngine.TYPE_SCOUT, SFEngine.ATTACK_RIGHT); }else{ interceptor = new SFEnemy(SFEngine.TYPE_SCOUT, SFEngine.ATTACK_LEFT); } enemies[x] = interceptor; }

} private void initializeWarships(){

for (int x = SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS -1; x<SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS -1; x++){ SFEnemy interceptor = new SFEnemy(SFEngine.TYPE_WARSHIP, SFEngine.ATTACK_RANDOM); enemies[x] = interceptor; }

}**

}`

初始化敌人

你有你的方法来初始化你的敌人。所有其他游戏循环初始化都发生在SFGameRendereronSurfaceCreated()方法中。因此,按理说,您刚刚创建的新初始化方法也将从这里调用。

`public class SFGameRenderer implements Renderer{

private SFEnemy[] enemies = new SFEnemy[SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1]; private SFTextures textureLoader; private int[] spriteSheets = new int[2];

**private void initializeInterceptors(){

for (int x = 0; x<SFEngine.TOTAL_INTERCEPTORS -1 ; x++){ SFEnemy interceptor = new SFEnemy(SFEngine.TYPE_INTERCEPTOR, SFEngine.ATTACK_RANDOM); enemies[x] = interceptor; }

}

private void initializeScouts(){

for (int x = SFEngine.TOTAL_INTERCEPTORS -1; x=(SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS) / 2 ){ interceptor = new SFEnemy(SFEngine.TYPE_SCOUT, SFEngine.ATTACK_RIGHT); }else{ interceptor = new SFEnemy(SFEngine.TYPE_SCOUT, SFEngine.ATTACK_LEFT); } enemies[x] = interceptor; }

}

private void initializeWarships(){

for (int x = SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS -1; x<SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS -1; x++){ SFEnemy interceptor = new SFEnemy(SFEngine.TYPE_WARSHIP, SFEngine.ATTACK_RANDOM); enemies[x] = interceptor; }

}**

**@Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { initializeInterceptors(); initializeScouts(); initializeWarships();

gl.glEnable(GL10.GL_TEXTURE_2D); gl.glClearDepthf(1.0f); gl.glEnable(GL10.GL_DEPTH_TEST); gl.glDepthFunc(GL10.GL_LEQUAL);

background.loadTexture(gl,SFEngine.BACKGROUND_LAYER_ONE, SFEngine.context); background2.loadTexture(gl,SFEngine.BACKGROUND_LAYER_TWO, SFEngine.context); }**

}`

加载精灵表

随着enemies[]数组的初始化,您可以关注 sprite 表。回想一下,您创建了一个通用纹理方法,该方法将返回在 int 数组中指定的所有纹理的 OpenGL 指定名称。这个 OpenGL 名称的 int 数组将保存在spriteSheets[]数组中。

实例化你的textureLoader()方法。在textureLoader()被实例化后,调用loadTexture()方法,将它传递给CHARACTER_SHEET,并将返回值赋给spriteSheets[]数组。

`public class SFGameRenderer implements Renderer{

@Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { initializeInterceptors(); initializeScouts(); initializeWarships(); textureLoader = new SFTextures(gl); spriteSheets = textureLoader.loadTexture(gl, SFEngine.CHARACTER_SHEET, SFEngine.context, 1);

gl.glEnable(GL10.GL_TEXTURE_2D); gl.glClearDepthf(1.0f); gl.glEnable(GL10.GL_DEPTH_TEST); gl.glDepthFunc(GL10.GL_LEQUAL);

background.loadTexture(gl,SFEngine.BACKGROUND_LAYER_ONE, SFEngine.context); background2.loadTexture(gl,SFEngine.BACKGROUND_LAYER_TWO, SFEngine.context); }

}`

敌人及其纹理的初始化已经完成。3+39.′′′

+是时候转向人工智能逻辑了。让我们从拦截器开始。

回顾人工智能

截击机 AI 的描述听起来很复杂,但实际上,它是三个敌人中最简单的。拦截器将开始沿着 y 轴直线运动。在 y 轴的某个点上,它会锁定玩家的飞船,并直接飞向这些坐标,试图撞击玩家的飞船。

实现这一点的方法是从 y 轴位置减去一个预定义的量INTERCEPTOR_SPEED,慢慢地将拦截器向屏幕下方移动。因为截击机可能在屏幕可见边缘上方的任意点,所以你必须等到它可见后才能锁定敌人。一旦拦截器到达这一点,你就可以把玩家飞船的 x 和 y 坐标传给它。最后,您将使用一个简单的斜率公式将拦截器移向这些坐标。

创建 moveEnemy()方法

添加一些敌人 AI 的第一步是创建一个moveEnemy()方法,它将为你的敌人保存所有的 AI 逻辑。就像movePlayer1()方法一样,moveEnemy()方法将被游戏循环调用来更新敌舰的位置。

`package com.proandroidgames;

import java.util.Random;

import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLSurfaceView.Renderer;

public class SFGameRenderer implements Renderer{

**private void moveEnemy(GL10 gl){

}**

}`

方法会在一次调用中更新你所有的敌人。在一次召唤中解决所有的敌人是更新大量不可玩角色的最好方法。这样做可以节省宝贵的处理器周期。

创建敌人[]数组循环

您想要在moveEnemy()方法中创建一个for循环,它将能够遍历enemies[]数组中的每个活着的敌人。通过将你进程的核心限制在那些没有被消灭的敌人身上,你在游戏中注意了两件事。首先,你要确保你没有画出不应该出现在屏幕上的敌人。第二,你要确保你不会在没有任何移动要处理的敌人身上浪费周期。

`public class SFGameRenderer implements Renderer{

**private void moveEnemy(GL10 gl){

for (int x = 0; x < SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1; x++){ if (!enemies[x].isDestroyed){

} } }**

}`

注意:不要太担心到底是什么树立了敌人的isDestroyed旗帜。我们将在下一章关于碰撞检测的章节中解决这个问题。你也会把这个逻辑应用到玩家的角色上。

现在,在你的更新方法中有一个循环,它为游戏中的每个敌人运行一次,并跳过那些已经被消灭的敌人。

使用人工智能逻辑移动每个敌人

你必须为三种不同的敌人运行这个循环,每一种都有自己的 AI。enemy类有一个enemyType属性,是在实例化敌人时设置的。因此,你需要在enemyType上设置一个开关,这样你就会知道哪个 AI 会为正在更新的敌人运行。

`public class SFGameRenderer implements Renderer{

**private void moveEnemy(GL10 gl){

for (int x = 0; x < SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1; x++){ if (!enemies[x].isDestroyed){

switch (enemies[x].enemyType){ case SFEngine.TYPE_INTERCEPTOR:

break; case SFEngine.TYPE_SCOUT:

break; case SFEngine.TYPE_WARSHIP

break;

}

} }

}**

}`

在下一部分中,你将创建拦截者 AI,这个逻辑将拦截者的敌人推向玩家。

创造拦截者人工智能

让我们现在创建拦截者人工智能。在这个 AI 中,你要测试的第一件事是拦截器是否已经离开了屏幕。回想一下,所有的敌人都将从屏幕的顶部移动到底部。除非它们被玩家摧毁,否则它们最终会到达屏幕底部。

当你设计这样的游戏时,你有一个选择。当一个敌人到达屏幕的底部时,你可以杀死它,让它退出循环,或者你可以重置它再次运行。对于星际战士,你要把敌人重置到屏幕上方的随机位置,让它继续攻击玩家,直到被消灭。

测试敌人的 y 轴位置是否小于 0——低于屏幕底部边缘——如果是,将其 x 和 y 位置重置为随机位置。此外,你会想要清除任何锁定位置和锁定标志,以防敌人先前锁定玩家。

`public class SFGameRenderer implements Renderer{

**private void moveEnemy(GL10 gl){

for (int x = 0; x < SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1; x++){ if (!enemies[x].isDestroyed){

Random randomPos = new Random();

switch (enemies[x].enemyType){ case SFEngine.TYPE_INTERCEPTOR:

if (enemies[x].posY <= 0){ enemies[x].posY = (randomPos.nextFloat() * 4) + 4; enemies[x].posX = randomPos.nextFloat() * 3; enemies[x].isLockedOn = false; enemies[x].lockOnPosX = 0; }

break; case SFEngine.TYPE_SCOUT:

break; case SFEngine.TYPE_WARSHIP

break; }

} }

}**

}`

在下一节中,您将向逻辑中添加 OpenGL 代码。

调整顶点

人工智能的下一步是一些标准的 OpenGL 工作。您需要加载模型矩阵模式并调整顶点。这段代码对你来说应该很熟悉,因为它已经在上一章中介绍过了。简而言之,你调整了顶点的大小,使敌人的飞船和玩家的大小差不多,而不是整个屏幕的大小。

`package com.proandroidgames;

import java.util.Random;

import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLSurfaceView.Renderer;

public class SFGameRenderer implements Renderer{

**private void moveEnemy(GL10 gl){

for (int x = 0; x < SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1; x++){ if (!enemies[x].isDestroyed){

Random randomPos = new Random();

switch (enemies[x].enemyType){ case SFEngine.TYPE_INTERCEPTOR:

if (enemies[x].posY <= 0){ enemies[x].posY = (randomPos.nextFloat() * 4) + 4; enemies[x].posX = randomPos.nextFloat() * 3; enemies[x].isLockedOn = false; enemies[x].lockOnPosX = 0; } gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); gl.glPushMatrix(); gl.glScalef(.25f, .25f, 1f);

break; case SFEngine.TYPE_SCOUT:

break; case SFEngine.TYPE_WARSHIP

break;

}

} }

}**

}`

此时,拦截器的 AI 将被分成两个不同的子句。第一个描述了拦截器锁定玩家位置之前发生的事情,第二个描述了拦截器锁定玩家位置之后发生的事情。

锁定玩家的位置

在拦截器锁定玩家的位置之前,它只会沿着屏幕直线向下移动。选取 y 轴上的任意位置;这将是拦截器锁定玩家位置的点。对于星际战斗机来说,拦截器锁定到玩家位置时的 y 轴位置是 3,意味着拦截器会从 y 轴上的任意位置开始,沿直线向下移动,直到到达 3。

`;

public class SFGameRenderer implements Renderer{

**private void moveEnemy(GL10 gl){

for (int x = 0; x < SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1; x++){ if (!enemies[x].isDestroyed){

Random randomPos = new Random();

switch (enemies[x].enemyType){ case SFEngine.TYPE_INTERCEPTOR: if (enemies[x].posY <= 0){ enemies[x].posY = (randomPos.nextFloat() * 4) + 4; enemies[x].posX = randomPos.nextFloat() * 3; enemies[x].isLockedOn = false; enemies[x].lockOnPosX = 0; } gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); gl.glPushMatrix(); gl.glScalef(.25f, .25f, 1f); if (enemies[x].posY >= 3){

}else{

}

break; case SFEngine.TYPE_SCOUT:

break; case SFEngine.TYPE_WARSHIP

break;

}

} }

}**

}`

在拦截器到达锁定位置之前,它将沿着屏幕直线移动。这是通过从拦截器的当前 y 轴位置减去INTERCEPTOR_SPEED值来实现的。

`public class SFGameRenderer implements Renderer{

**private void moveEnemy(GL10 gl){

for (int x = 0; x < SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1; x++){ if (!enemies[x].isDestroyed){

Random randomPos = new Random();

switch (enemies[x].enemyType){ case SFEngine.TYPE_INTERCEPTOR:

if (enemies[x].posY <= 0){ enemies[x].posY = (randomPos.nextFloat() * 4) + 4; enemies[x].posX = randomPos.nextFloat() * 3; enemies[x].isLockedOn = false; enemies[x].lockOnPosX = 0; } gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); gl.glPushMatrix(); gl.glScalef(.25f, .25f, 1f); if (enemies[x].posY >= 3){ enemies[x].posY -= SFEngine.INTERCEPTOR_SPEED; }else{

}

break; case SFEngine.TYPE_SCOUT:

break; case SFEngine.TYPE_WARSHIP

break;

}

} }

}**

}`

现在,你可以编程拦截器的人工智能逻辑的第二部分。

实现斜率公式

首先,将拦截器设置为锁定,并获取玩家的当前位置。

`public class SFGameRenderer implements Renderer{

**private void moveEnemy(GL10 gl){

for (int x = 0; x < SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1; x++){ if (!enemies[x].isDestroyed){

Random randomPos = new Random();

switch (enemies[x].enemyType){ case SFEngine.TYPE_INTERCEPTOR:

if (enemies[x].posY <= 0){ enemies[x].posY = (randomPos.nextFloat() * 4) + 4; enemies[x].posX = randomPos.nextFloat() * 3; enemies[x].isLockedOn = false; enemies[x].lockOnPosX = 0; } gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); gl.glPushMatrix(); gl.glScalef(.25f, .25f, 1f); if (enemies[x].posY >= 3){ enemies[x].posY -= SFEngine.INTERCEPTOR_SPEED; }else{ if (!enemies[x].isLockedOn){ enemies[x].lockOnPosX = SFEngine.playerBankPosX; enemies[x].isLockedOn = true;

}

}

break; case SFEngine.TYPE_SCOUT:

break; case SFEngine.TYPE_WARSHIP

break;

}

} }

}**

}`

接下来,您将使用一个简单的斜率公式来确定拦截器到达玩家所需移动的增量。斜率可通过以下公式确定:

(x<sub>1</sub> - x<sub>2</sub>) / (y<sub>1</sub> - y<sub>2</sub>)

让事情变得更有趣一点,您希望拦截器在锁定目标后加速。因此,将斜率中的 y 2 替换为INTERCEPTOR_SPEED。在使用这个公式之前,你需要对它做一个修改。

这个公式可以让你一次就把全部位置直接给玩家。然而,你想以稳定的增量向玩家移动。所以你需要用 y 1 除以 y 2 而不是减去 y 2 。这将为您提供一个增量值,您可以不断地将它添加到自身中,以推动拦截器前进。

`public class SFGameRenderer implements Renderer{

**private void moveEnemy(GL10 gl){

for (int x = 0; x < SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1; x++){ if (!enemies[x].isDestroyed){

Random randomPos = new Random();

switch (enemies[x].enemyType){ case SFEngine.TYPE_INTERCEPTOR:

if (enemies[x].posY <= 0){ enemies[x].posY = (randomPos.nextFloat() * 4) + 4; enemies[x].posX = randomPos.nextFloat() * 3; enemies[x].isLockedOn = false; enemies[x].lockOnPosX = 0; } gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); gl.glPushMatrix(); gl.glScalef(.25f, .25f, 1f); if (enemies[x].posY >= 3){ enemies[x].posY -= SFEngine.INTERCEPTOR_SPEED; }else{ if (!enemies[x].isLockedOn){ enemies[x].lockOnPosX = SFEngine.playerBankPosX; enemies[x].isLockedOn = true; enemies[x].incrementXToTarget = (float) ((enemies[x].lockOnPosX - enemies[x].posX )/ (enemies[x].posY / (SFEngine.INTERCEPTOR_SPEED * 4))); }

}

break; case SFEngine.TYPE_SCOUT:

break; case SFEngine.TYPE_WARSHIP

break;

}

} }

}**

}`

通过设置拦截器的 x 和 y 位置来完成逻辑。

`public class SFGameRenderer implements Renderer{

**private void moveEnemy(GL10 gl){

for (int x = 0; x < SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1; x++){ if (!enemies[x].isDestroyed){

Random randomPos = new Random();

switch (enemies[x].enemyType){ case SFEngine.TYPE_INTERCEPTOR:

if (enemies[x].posY <= 0){ enemies[x].posY = (randomPos.nextFloat() * 4) + 4; enemies[x].posX = randomPos.nextFloat() * 3; enemies[x].isLockedOn = false; enemies[x].lockOnPosX = 0; } gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); gl.glPushMatrix(); gl.glScalef(.25f, .25f, 1f); if (enemies[x].posY >= 3){ enemies[x].posY -= SFEngine.INTERCEPTOR_SPEED; }else{ if (!enemies[x].isLockedOn){ enemies[x].lockOnPosX = SFEngine.playerBankPosX; enemies[x].isLockedOn = true; enemies[x].incrementXToTarget = (float) ((enemies[x].lockOnPosX - enemies[x].posX )/ (enemies[x].posY / (SFEngine.INTERCEPTOR_SPEED * 4))); } enemies[x].posY -= (SFEngine.INTERCEPTOR_SPEED * 4); enemies[x].posX += enemies[x].incrementXToTarget;

}

break; case SFEngine.TYPE_SCOUT:

break; case SFEngine.TYPE_WARSHIP

break;

}

} }

}**

}`

最后,您可以用您对拦截器所做的位置更改来更新 OpenGL。您需要根据拦截器新的 x 和 y 轴位置来移动顶点。然后,您需要将纹理矩阵推出堆栈,并将纹理设置为公共 sprite 表上的拦截器 sprite。

`public class SFGameRenderer implements Renderer{

**private void moveEnemy(GL10 gl){

for (int x = 0; x < SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1; x++){ if (!enemies[x].isDestroyed){

Random randomPos = new Random();

switch (enemies[x].enemyType){ case SFEngine.TYPE_INTERCEPTOR:

if (enemies[x].posY <= 0){ enemies[x].posY = (randomPos.nextFloat() * 4) + 4; enemies[x].posX = randomPos.nextFloat() * 3; enemies[x].isLockedOn = false; enemies[x].lockOnPosX = 0; } gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); gl.glPushMatrix(); gl.glScalef(.25f, .25f, 1f); if (enemies[x].posY >= 3){ enemies[x].posY -= SFEngine.INTERCEPTOR_SPEED; }else{ if (!enemies[x].isLockedOn){ enemies[x].lockOnPosX = SFEngine.playerBankPosX; enemies[x].isLockedOn = true; enemies[x].incrementXToTarget = (float) ((enemies[x].lockOnPosX - enemies[x].posX )/ (enemies[x].posY / (SFEngine.INTERCEPTOR_SPEED * 4))); } enemies[x].posY -= (SFEngine.INTERCEPTOR_SPEED * 4); enemies[x].posX += enemies[x].incrementXToTarget; gl.glTranslatef(enemies[x].posX, enemies[x].posY, 0f); gl.glMatrixMode(GL10.GL_TEXTURE); gl.glLoadIdentity(); gl.glTranslatef(0.25f, .25f , 0.0f); }

break; case SFEngine.TYPE_SCOUT:

break; case SFEngine.TYPE_WARSHIP

break;

}

} }

}**

}`

画出拦截者,你就准备好对付侦察兵 AI 了。

`public class SFGameRenderer implements Renderer{

**private void moveEnemy(GL10 gl){

for (int x = 0; x < SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1; x++){ if (!enemies[x].isDestroyed){

Random randomPos = new Random();

switch (enemies[x].enemyType){ case SFEngine.TYPE_INTERCEPTOR:

if (enemies[x].posY <= 0){ enemies[x].posY = (randomPos.nextFloat() * 4) + 4; enemies[x].posX = randomPos.nextFloat() * 3; enemies[x].isLockedOn = false; enemies[x].lockOnPosX = 0; } gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); gl.glPushMatrix(); gl.glScalef(.25f, .25f, 1f); if (enemies[x].posY >= 3){ enemies[x].posY -= SFEngine.INTERCEPTOR_SPEED; }else{ if (!enemies[x].isLockedOn){ enemies[x].lockOnPosX = SFEngine.playerBankPosX; enemies[x].isLockedOn = true; enemies[x].incrementXToTarget = (float) ((enemies[x].lockOnPosX - enemies[x].posX )/ (enemies[x].posY / (SFEngine.INTERCEPTOR_SPEED * 4))); } enemies[x].posY -= (SFEngine.INTERCEPTOR_SPEED * 4); enemies[x].posX += enemies[x].incrementXToTarget; gl.glTranslatef(enemies[x].posX, enemies[x].posY, 0f); gl.glMatrixMode(GL10.GL_TEXTURE); gl.glLoadIdentity(); gl.glTranslatef(0.25f, .25f , 0.0f); } enemies[x].draw(gl, spriteSheets); gl.glPopMatrix(); gl.glLoadIdentity();

break; case SFEngine.TYPE_SCOUT:**

**break; case SFEngine.TYPE_WARSHIP

break;

}

} }

}**

}`

在下一节中,您将创建侦察敌人类型的 AI 逻辑。

创造侦察兵 AI

现在你已经用一点人工智能工作弄脏了你的手,剩下的两个敌人应该是相当容易的。侦察兵和截击机之间唯一的主要区别是侦察兵会按照预先定义的模式在屏幕上移动。

首先,测试以确定侦察兵是否离开屏幕底部;如果是,重置它。在拦截器的相同逻辑中,您将 x 和 y 轴位置都设置为随机值。但是,侦察兵只会从画面的最左边或者最右边攻击。因此,根据其攻击方向将 x 轴位置设置为 0 或 3。

`public class SFGameRenderer implements Renderer{

...

**private void moveEnemy(GL10 gl){

for (int x = 0; x < sfengine.TOTAL_INTERCEPTORS + sfengine.TOTAL_SCOUTS + sfengine.TOTAL_WARSHIPS - 1; x++){ if (!enemies[x].isDestroyed){

Random randomPos = new Random();

switch (enemies[x].enemyType){ case SFEngine.TYPE_INTERCEPTOR:

...

break; case SFEngine.TYPE_SCOUT: if (enemies[x].posY <= 0){ enemies[x].posY = (randomPos.nextFloat() * 4) + 4; enemies[x].isLockedOn = false; enemies[x].posT = SFEngine.SCOUT_SPEED; enemies[x].lockOnPosX = enemies[x].getNextScoutX(); enemies[x].lockOnPosY = enemies[x].getNextScoutY(); if(enemies[x].attackDirection == SFEngine.ATTACK_LEFT){ enemies[x].posX = 0; }else{ enemies[x].posX = 3f; } } gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); gl.glPushMatrix(); gl.glScalef(.25f, .25f, 1f);

break; case SFEngine.TYPE_WARSHIP

break;

}

} }

}**

...

}`

就像你对截击机所做的一样,你要慢慢地移动侦察机直到它到达锁定点。

设置一个随机点来移动侦察员

因为如果所有的敌人都在屏幕上的同一点改变方向,这个动作对玩家来说会显得过于机械,所以你应该把侦察兵的锁定点设置得比截击机的锁定点低一点;否则,代码是相同的。

`public class SFGameRenderer implements Renderer{

...

**private void moveEnemy(GL10 gl){

for (int x = 0; x < sfengine.TOTAL_INTERCEPTORS + sfengine.TOTAL_SCOUTS + sfengine.TOTAL_WARSHIPS - 1; x++){ if (!enemies[x].isDestroyed){

Random randomPos = new Random();**

**switch (enemies[x].enemyType){ case SFEngine.TYPE_INTERCEPTOR:

...

break; case SFEngine.TYPE_SCOUT: if (enemies[x].posY <= 0){ enemies[x].posY = (randomPos.nextFloat() * 4) + 4; enemies[x].isLockedOn = false; enemies[x].posT = SFEngine.SCOUT_SPEED; enemies[x].lockOnPosX = enemies[x].getNextScoutX(); enemies[x].lockOnPosY = enemies[x].getNextScoutY(); if(enemies[x].attackDirection == SFEngine.ATTACK_LEFT){ enemies[x].posX = 0; }else{ enemies[x].posX = 3f; } } gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); gl.glPushMatrix(); gl.glScalef(.25f, .25f, 1f); if (enemies[x].posY >= 2.75f){ enemies[x].posY -= SFEngine.SCOUT_SPEED; }else{

}

break; case SFEngine.TYPE_WARSHIP

break;

}

} }

}**

...

} In the next section, you will learn how to move the scout along a Bezier curve.`

沿着贝塞尔曲线移动

幸运的是,您已经创建了在贝塞尔曲线中自动为您提供下一个 x 和 y 坐标的方法。现在您所要做的就是调用getNextScoutX()getNextScoutY()方法,开始沿着曲线路径移动侦察器。在调用这些方法后,将posT增加SCOUT_SPEED的值;否则,下次调用它们时,您将获得相同的值。

`public class SFGameRenderer implements Renderer{

...

**private void moveEnemy(GL10 gl){ for (int x = 0; x < sfengine.TOTAL_INTERCEPTORS + sfengine.TOTAL_SCOUTS + sfengine.TOTAL_WARSHIPS - 1; x++){ if (!enemies[x].isDestroyed){ Random randomPos = new Random();

switch (enemies[x].enemyType){ case SFEngine.TYPE_INTERCEPTOR:

...

break; case SFEngine.TYPE_SCOUT: if (enemies[x].posY <= 0){ enemies[x].posY = (randomPos.nextFloat() * 4) + 4; enemies[x].isLockedOn = false; enemies[x].posT = SFEngine.SCOUT_SPEED; enemies[x].lockOnPosX = enemies[x].getNextScoutX(); enemies[x].lockOnPosY = enemies[x].getNextScoutY(); if(enemies[x].attackDirection == SFEngine.ATTACK_LEFT){ enemies[x].posX = 0; }else{ enemies[x].posX = 3f; } } gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); gl.glPushMatrix(); gl.glScalef(.25f, .25f, 1f); if (enemies[x].posY >= 2.75f){ enemies[x].posY -= SFEngine.SCOUT_SPEED; }else{ enemies[x].posX = enemies[x].getNextScoutX(); enemies[x].posY = enemies[x].getNextScoutY(); enemies[x].posT += SFEngine.SCOUT_SPEED; }

break; case SFEngine.TYPE_WARSHIP

break;

}

} }

}**

...

}`

信不信由你,这就是侦察兵 AI 的全部。完成这个敌人的 AI,执行你的 OpenGL 程序来转换顶点,并为侦察兵设置正确的精灵。

`public class SFGameRenderer implements Renderer{

...

**private void moveEnemy(GL10 gl){

for (int x = 0; x < SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1; x++){ if (!enemies[x].isDestroyed){

Random randomPos = new Random();

switch (enemies[x].enemyType){ case SFEngine.TYPE_INTERCEPTOR:

...

break; case SFEngine.TYPE_SCOUT: if (enemies[x].posY <= 0){ enemies[x].posY = (randomPos.nextFloat() * 4) + 4; enemies[x].isLockedOn = false; enemies[x].posT = SFEngine.SCOUT_SPEED; enemies[x].lockOnPosX = enemies[x].getNextScoutX(); enemies[x].lockOnPosY = enemies[x].getNextScoutY(); if(enemies[x].attackDirection == SFEngine.ATTACK_LEFT){ enemies[x].posX = 0; }else{ enemies[x].posX = 3f; } } gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); gl.glPushMatrix(); gl.glScalef(.25f, .25f, 1f); if (enemies[x].posY >= 2.75f){ enemies[x].posY -= SFEngine.SCOUT_SPEED; }else{ enemies[x].posX = enemies[x].getNextScoutX(); enemies[x].posY = enemies[x].getNextScoutY(); enemies[x].posT += SFEngine.SCOUT_SPEED; } gl.glTranslatef(enemies[x].posX, enemies[x].posY, 0f); gl.glMatrixMode(GL10.GL_TEXTURE); gl.glLoadIdentity(); gl.glTranslatef(0.75f, .25f , 0.0f); enemies[x].draw(gl, spriteSheets); gl.glPopMatrix(); gl.glLoadIdentity();

break; case SFEngine.TYPE_WARSHIP

break;

}

} }

}**

...

}`

你需要添加到你的moveEnemy()方法中的最后一点 AI 是战舰。

创造人工智能战舰

星球大战 er 的故事中,战舰朝着玩家随机的方向移动。你将通过在 x 轴上选择一个随机位置,并使用与拦截器相同的逻辑,将战舰移向随机点,而不是直接移动到玩家的位置,来实现这一点。

将战舰移动到一个随机的位置会使游戏变得不可预测,并使敌人更难对抗。然而,战舰的 AI 将与拦截者的 AI 几乎相同,除了你需要用 0 到 3 之间的随机数替换玩家锁定的 x 位置。

`public class SFGameRenderer implements Renderer{

...

**private void moveEnemy(GL10 gl){

for (int x = 0; x < SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1; x++){ if (!enemies[x].isDestroyed){

Random randomPos = new Random();

switch (enemies[x].enemyType){ case SFEngine.TYPE_INTERCEPTOR:

...

break; ...

break; case SFEngine.TYPE_WARSHIP if (enemies[x].posY < 0){ enemies[x].posY = (randomPos.nextFloat() * 4) + 4; enemies[x].posX = randomPos.nextFloat() * 3; enemies[x].isLockedOn = false; enemies[x].lockOnPosX = 0; } gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); gl.glPushMatrix(); gl.glScalef(.25f, .25f, 1f);

if (enemies[x].posY >= 3){ enemies[x].posY -= SFEngine.WARSHIP_SPEED; }else{ if (!enemies[x].isLockedOn){ enemies[x].lockOnPosX = randomPos.nextFloat() * 3; enemies[x].isLockedOn = true; enemies[x].incrementXToTarget = (float) ((enemies[x].lockOnPosX - enemies[x].posX )/ (enemies[x].posY / (SFEngine.WARSHIP_SPEED * 4))); } enemies[x].posY -= (SFEngine.WARSHIP_SPEED * 2); enemies[x].posX += enemies[x].incrementXToTarget; } gl.glTranslatef(enemies[x].posX, enemies[x].posY, 0f); gl.glMatrixMode(GL10.GL_TEXTURE); gl.glLoadIdentity(); gl.glTranslatef(0.50f, .25f , 0.0f);

enemies[x].draw(gl,spriteSheets); gl.glPopMatrix(); gl.glLoadIdentity(); break;

}

} }

}**

...

}`

在调用movePlayer1()之后,通过从onDrawFrame()方法调用新的moveEnemy()方法来完成游戏循环。

总结

在这一章中,你学到了很多关于为你的敌人创造三种不同的,基本的人工智能结构。你也是

  • 创建了加载多个纹理的调用
  • 创造方法来移动你的敌人
  • 在移动你的敌人之前测试它的状况
  • 创造逻辑让你的敌人沿着路径移动

在下一章,你将通过开发一些武器和实现碰撞检测来完成你的游戏。