安卓-3D-游戏开发入门指南-七-

116 阅读24分钟

安卓 3D 游戏开发入门指南(七)

原文:Beginning Android 3D Game Development

协议:CC BY-NC-SA 4.0

九、无人机网格案例研究:用户界面

Abstract

本章将介绍我们的无人机网格案例研究游戏的用户界面。首先是主菜单系统,它允许玩家在新游戏、继续旧游戏或查看高分表之间进行选择。接下来,将介绍高分表的创建,包括用于实现它的类。然后与用来实现该菜单系统的类讨论高分入口菜单。最后,给出了一个实际例子来演示这些用户界面。

本章将介绍我们的无人机网格案例研究游戏的用户界面。首先是主菜单系统,它允许玩家在新游戏、继续旧游戏或查看高分表之间进行选择。接下来,将介绍高分表的创建,包括用于实现它的类。然后与用来实现该菜单系统的类讨论高分入口菜单。最后,给出了一个实际例子来演示这些用户界面。

创建主菜单系统

我们游戏的菜单系统将由一个 main menu 类表示的主菜单组成。主菜单中的每一项都由一个 MenuItem 类表示。

MenuItem 类

MenuItem 类包含一个菜单项,该菜单项也包含实际的 3D 图形。

MenuItem 类派生自 BillBoard 类。

public class MenuItem extends BillBoard

MenuItem 构造函数调用其父 BillBoard 类的构造函数。(参见清单 9-1。)

清单 9-1。MenuItem 构造函数

MenuItem(Context iContext, Mesh iMesh, MeshEx iMeshEx, Texture[] iTextures, Material iMaterial, Shader iShader)

{

super(iContext, iMesh, iMeshEx, iTextures, iMaterial, iShader);

}

GetObject3dWindowCoords()函数获取菜单项对象上一个点的窗口坐标,该点可以偏移ObjOffset并显示在由ViewPortWidthViewPortHeight输入参数定义的视窗内。(参见清单 9-2。)

清单 9-2。获取 MenuItem 对象的窗口坐标

float[] GetObject3dWindowCoords(int ViewPortWidth,int ViewPortHeight,Vector3 ObjOffset)

{

float[] WindowCoords;

int[] View = new int[4];

View[0] = 0;

View[1] = 0;

View[2] = ViewPortWidth;

View[3] = ViewPortHeight;

WindowCoords = MapObjectCoordsToWindowCoords(View, 0, ObjOffset);

// Flip Y starting point so that 0 is at top of window

WindowCoords[1] = ViewPortHeight - WindowCoords[1];

return WindowCoords;

}

该函数执行以下操作:

Creates the View variable, which is a viewport window defined by the coordinates (0,0) and (ViewPortWidth, ViewPortHeight)   Gets the window coordinates of the menu item object at ObjOffset offset position by calling the MapObjectCoordsToWindowCoords() function   Converts the y component of the window coordinates to screen space from OpenGL space   Returns the window coordinates  

如果输入屏幕触摸坐标TouchXTouchY映射到该菜单项内,则Touched()功能返回 true。(参见清单 9-3。)

清单 9-3。测试用户的触摸输入

boolean Touched(float TouchX, float TouchY,int ViewPortWidth,int ViewPortHeight)

{

boolean result = false;

float Radius = GetRadius();

Vector3 ObjCoordsUpperLeft  = new Vector3(-Radius,  Radius, 0);

Vector3 ObjCoordsUpperRight = new Vector3( Radius,  Radius, 0);

Vector3 ObjCoordsLowerLeft  = new Vector3(-Radius, -Radius, 0);

float[] UpperLeft  = GetObject3dWindowCoords(ViewPortWidth, ViewPortHeight, ObjCoordsUpperLeft);

float[] UpperRight = GetObject3dWindowCoords(ViewPortWidth, ViewPortHeight, ObjCoordsUpperRight);

float[] LowerLeft  = GetObject3dWindowCoords(ViewPortWidth, ViewPortHeight, ObjCoordsLowerLeft);

if ((TouchX >= UpperLeft[0]) && (TouchX <= UpperRight[0]) &&

(TouchY >= UpperLeft[1]) && (TouchY <= LowerLeft[1]))

{

result = true;

}

return result;

}

以下是Touched()功能的属性:

For our menu items, we will use a Cube class object that has equal lengths on all sides. We get the radius of this cube by calling the GetRadius() function and store it in the Radius variable.   Using the Radius value, we create the object’s upper left, upper right, and lower left object coordinates. We store these values in the variables ObjCoordsUpperLeft, ObjCoordsUpperRight, and ObjCoordsLowerLeft.   The function retrieves the window coordinates of the upper left, upper right, and lower left corners of the menu item object by calling GetObject3dWindowCoords() using the object’s upper left, upper right, and lower left object coordinates created in step 2.   The function tests to see if the screen coordinates (TouchX, TouchY) are within the bounds of the screen coordinates of the menu item. If they are, then return true; otherwise, return false.  

MainMenu 类

MainMenu 类表示我们的无人机网格案例研究游戏的实际主菜单界面。

MenuStates枚举代表主菜单的可用菜单选项(见清单 9-4 ),例如

  • 无:这意味着没有选择菜单项
  • 新游戏:这意味着玩家想要开始玩一个全新的游戏
  • ContinueCurrentGame:这意味着玩家想继续上次保存的游戏
  • HighScoreTable:这意味着玩家想要查看高分表
  • 版权:用于调试模式,帮助测试高分录入系统

清单 9-4。菜单显示

enum MenuStates

{

None,

NewGame,

ContinueCurrentGame,

HighScoreTable,

Copyright

}

如果用户想开始一个新游戏,变量m_NewGameItem保存一个菜单项的引用。

MenuItem m_NewGameItem;

m_ContinueGameItem变量保存了一个菜单项的引用,如果用户想继续上次保存的游戏,他或她可以触摸这个菜单项。

MenuItem m_ContinueGameItem;

m_HighScoresItem变量保存了一个菜单项的引用,如果用户想要查看高分表,可以触摸该菜单项。

MenuItem m_HighScoresItem;

m_CopyRightItem变量保存一个菜单项的引用,该菜单项用于调试高分输入系统。

MenuItem m_CopyRightItem;

MainMenu 构造函数通过向主菜单中的每个菜单项分配对象来初始化 MainMenu 类。(参见清单 9-5。)

清单 9-5。MainMenu 构造函数

MainMenu(MenuItem NewGameItem,MenuItem ContinueGameItem,MenuItem HighScoresItem,MenuItem CopyRightItem)

{

m_NewGameItem      = NewGameItem;

m_ContinueGameItem = ContinueGameItem;

m_HighScoresItem   = HighScoresItem;

m_CopyRightItem    = CopyRightItem;

}

GetMainMenuStatus()功能测试主菜单项,查看是否有任何菜单项在屏幕位置被触摸(TouchXTouchY)。通过调用按钮上的Touched()功能,对每个菜单项按钮进行测试,以查看其是否被触摸。返回被触摸的按钮类型,或者如果没有触摸菜单按钮,则返回 None。(参见清单 9-6。)

清单 9-6。获取主菜单状态

MenuStates GetMainMenuStatus(float TouchX, float TouchY,int ViewPortWidth,int ViewPortHeight)

{

MenuStates Selection = MenuStates.None;

boolean Touched = false;

// New Game Menu Item

Touched = m_NewGameItem.Touched(TouchX, TouchY, ViewPortWidth, ViewPortHeight);

if (Touched)

{

Selection = MenuStates.NewGame;

}

// New ContinueGame Menu Item

Touched =  m_ContinueGameItem.Touched(TouchX, TouchY, ViewPortWidth, ViewPortHeight);

if (Touched)

{

Selection = MenuStates.ContinueCurrentGame;

}

// New HighScoreTable Menu Item

Touched =  m_HighScoresItem.Touched(TouchX, TouchY, ViewPortWidth, ViewPortHeight);

if (Touched)

{

Selection = MenuStates.HighScoreTable;

}

// CopyRight Menu Item

Touched =  m_CopyRightItem.Touched(TouchX, TouchY, ViewPortWidth, ViewPortHeight);

if (Touched)

{

Selection = MenuStates.Copyright;

}

return Selection;

}

RenderMenu()功能将主菜单项绘制到屏幕上。(参见清单 9-7。)

清单 9-7。呈现主菜单

void RenderMenu(Camera Cam, PointLight Light, boolean DebugOn)

{

m_NewGameItem.DrawObject(Cam, Light);

m_ContinueGameItem.DrawObject(Cam, Light);

m_HighScoresItem.DrawObject(Cam, Light);

m_CopyRightItem.DrawObject(Cam, Light);

}

UpdateMenu()功能更新每个主菜单项,并通过调用UpdateObject3d()功能将每个菜单项(广告牌)转向摄像机。所有主菜单项,包括新游戏按钮、继续游戏按钮、显示高分按钮和版权图形,都在这里处理。(参见清单 9-8。)

清单 9-8。更新主菜单

void UpdateMenu(Camera Cam)

{

m_NewGameItem.UpdateObject3d(Cam);

m_ContinueGameItem.UpdateObject3d(Cam);

m_HighScoresItem.UpdateObject3d(Cam);

m_CopyRightItem.UpdateObject3d(Cam);

}

创建高分表

保存玩家在游戏中最高分数的高分表由两个类组成:HighScoreEntry 类和 high score table 类。

HighScoreEntry 类

HighScoreEntry 类保存高分条目的数据。这个类实现了 Comparable 公共接口,并定义了一个函数,允许对高分条目进行比较和排序。

public class HighScoreEntry implements Comparable<HighScoreEntry>

如果该高分条目有效,则m_ItemValid变量设置为真,并应显示在高分表中。

private boolean m_ItemValid;

m_Initials变量保存玩家的姓名首字母。

private String  m_Initials;

m_Score变量保存玩家的分数。

private int     m_Score;

HighScoreEntry 构造函数通过设置首字母和分数来初始化条目。(参见清单 9-9。)

清单 9-9。HighScoreEntry 构造函数

HighScoreEntry(String Initials,int Score)

{

m_Initials = Initials;

m_Score    = Score;

}

compareTo()函数与Collections.sort()函数结合使用,以降序排列高分表格条目,高分排在第一位(见清单 9-10)。通常情况下,compareTo()函数按升序对条目进行排序。因为我们是按降序排序,所以需要做一些关键的更改。

  • 如果该条目实例中的分数大于输入参数变量Another的分数,则该函数返回一个负整数。
  • 如果该条目实例中的分数小于输入参数变量Another的分数,则该函数返回一个正整数。

清单 9-10。比较和排序条目

public int compareTo(HighScoreEntry Another)

{

/*

Normally ascending sorting - Returns

a negative integer if this instance is less than another; a positive integer if this

instance is greater than another; 0 if this instance has the same order as another.

*/

int result = 0;

if (m_Score > Another.m_Score)

{

result = -1;

}

else

if (m_Score < Another.m_Score)

{

result = 1;

}

return result;

}

HighScoreTable 类

HighScoreTable 类表示包含所有玩家高分条目的高分表。

HIGH_SCORES字符串变量保存加载和保存高分表的句柄。

private String HIGH_SCORES = "HighScores";

MAX_RANK变量保存要显示的高分的最大数量。

private int MAX_RANK = 10;

MAX_SCORES变量保存内部存储的用于处理和计算的最大分数。

private int MAX_SCORES = 11;

m_HighScoreTable数组保存实际的高分条目。

private HighScoreEntry[] m_HighScoreTable = new HighScoreEntry[MAX_SCORES];

m_Text变量保存将用于打印高分表格图形文本的字符集。

private BillBoardCharacterSet m_Text;

m_FontWidth变量保存用于打印高分表格文本的字符集中每个字符的宽度。

private int m_FontWidth;

m_FontHeight变量保存用于打印高分表文本的字符集中每个字符的高度。

private int m_FontHeight;

m_BackGroundTexture变量保存用于清除高分表纹理的纹理。

private Texture m_BackGroundTexture;

m_HighScoreTableImage变量保存一个对 BillBoard 对象的引用,该对象包含带有玩家高分的纹理。

private BillBoard m_HighScoreTableImage;

如果自从上次更新以来高分表已经被改变,则m_Dirty变量为真,否则为假。

private boolean m_Dirty = false;

HighScoreTable()构造函数(见清单 9-11)通过以下方式创建一个新的高分表

Creating a new background texture for the high score table   Initializing the high score table by creating blank high score entries in the m_HighScoreTable array   Initializing other class member variables   Loading in the player’s previously saved high scores, if any  

清单 9-11。HighScoreTable 构造函数

HighScoreTable(Context iContext,BillBoardCharacterSet CharacterSet,BillBoard HighScoreTableImage)

{

m_Context = iContext;

m_BackGroundTexture = new Texture(iContext, R.drawable.background);

String  Initials        = "AAA";

int     Score   = 0;

// Initialize High Score Entries

for (int i = 0; i < MAX_SCORES; i++)

{

m_HighScoreTable[i] = new HighScoreEntry(Initials,Score);

m_HighScoreTable[i].SetItemValidState(false);

}

m_Text = CharacterSet;

m_FontWidth = m_Text.GetFontWidth();

m_FontHeight = m_Text.GetFontHeight();

m_HighScoreTableImage = HighScoreTableImage;

// Load In Saved high Scores

LoadHighScoreTable(HIGH_SCORES);

m_Dirty = true;

}

SaveHighScoreTable()功能保存由玩家姓名首字母和玩家分数组成的玩家前MAX_RANK号高分条目。(参见清单 9-12。)

清单 9-12。保存高分表

void SaveHighScoreTable(String Handle)

{

// We need an Editor object to make preference changes.

// All objects are from android.context.Context

SharedPreferences settings = m_Context.getSharedPreferences(Handle, 0);

SharedPreferences.Editor editor = settings.edit();

for (int i = 0; i < MAX_RANK; i++)

{

editor.putString("Name" + i, m_HighScoreTable[i].GetInitials());

editor.putInt("Score" + i, m_HighScoreTable[i].GetScore());

}

// Commit the edits!

editor.commit();

}

LoadHighScoreTable()函数加载玩家的高分数据,包括玩家的名字或首字母以及玩家的分数。如果玩家的分数大于 0,则条目有效。(参见清单 9-13。)

清单 9-13。加载高分表

void LoadHighScoreTable(String Handle)

{

// Restore preferences

SharedPreferences settings = m_Context.getSharedPreferences(Handle, 0);

for (int i = 0; i < MAX_RANK; i++)

{

String Name = settings.getString("Name" + i, "...");

int Score = settings.getInt("Score" + i, 0);

m_HighScoreTable[i].SetName(Name);

m_HighScoreTable[i].SetScore(Score);

if (Score > 0)

{

m_HighScoreTable[i].SetItemValidState(true);

}

}

}

NumberValidHighScores()函数查找高分表中有效高分条目的数量。(参见清单 9-14。)

清单 9-14。寻找有效高分的数量

int NumberValidHighScores()

{

int NumberValidScores = 0;

for (int i = 0; i < MAX_RANK; i++)

{

if (m_HighScoreTable[i].IsValid())

{

NumberValidScores++;

}

}

return NumberValidScores;

}

GetLowestScore()功能从高分表m_HighScoreTable中检索最低有效玩家分数。(参见清单 9-15。)

清单 9-15。得到最低分

int GetLowestScore()

{

// Get Lowest valid score

int LowestScore = 0;

int ValidScores = 0;

for (int i = 0; i < MAX_RANK; i++)

{

if (m_HighScoreTable[i].IsValid())

{

ValidScores++;

}

}

if (ValidScores > 0)

{

LowestScore = m_HighScoreTable[ValidScores-1].GetScore();

}

return LowestScore;

}

FindEmptySlot()函数检索m_HighScoreTable中的空(意味着无效)高分入口槽的索引。(参见清单 9-16。)

清单 9-16。为高分条目寻找空位置

int FindEmptySlot()

{

int EmptySlot = -1;

for (int i = 0; i < MAX_SCORES; i++)

{

if (m_HighScoreTable[i].IsValid() == false)

{

return i;

}

}

return EmptySlot;

}

如果找到一个空位置,函数AddItem()将一个高分条目添加到高分表格中的一个空位置。更具体地说,如果有一个空槽,则将高分条目分配给m_HighScoreTable数组中的那个槽,将条目的有效状态设置为真,并将m_Dirty值设置为真,以指示需要对高分表进行排序和呈现。(参见清单 9-17。)

清单 9-17。向高分表添加项目

boolean AddItem(HighScoreEntry Item)

{

boolean result = false;

int EmptySlot =  FindEmptySlot();

if (EmptySlot >= 0)

{

m_HighScoreTable[EmptySlot] = Item;

m_HighScoreTable[EmptySlot].SetItemValidState(true);

result = true;

m_Dirty = true;

}

return result;

}

SortHighScoreTable()函数按降序对高分表进行排序。按照降序对前 10 个条目进行排序后,该函数获取第 11 个高分条目,并将状态设置为无效,这样,如果需要,可以将高分表中的新条目放在最后。(参见清单 9-18。)

清单 9-18。对高分表进行排序

void SortHighScoreTable()

{

Collections.sort(Arrays.asList(m_HighScoreTable));

// Only keep top 10 and make room for another to be added to end of array

m_HighScoreTable[MAX_SCORES-1].SetItemValidState(false);

}

ClearHighScoreTable()功能通过使用CopySubTextureToTexture()功能将空白纹理m_BackGroundTexture复制到高分表格纹理图像上来清除m_HighScoreTableImage中保存的高分表格纹理图像。(参见清单 9-19。)

清单 9-19。清除高分表

void ClearHighScoreTable()

{

Texture HighScoreTableTexture = m_HighScoreTableImage.GetTexture(0);

// Clear Composite Texture;

Bitmap BackGroundBitmap = m_BackGroundTexture.GetTextureBitMap();

HighScoreTableTexture.CopySubTextureToTexture(0, 0, 0, BackGroundBitmap);

}

RenderTitle()函数将文本“High Scores”呈现给m_HighScoreTableImage,后者是为高分表保存最终合成纹理的广告牌。(参见清单 9-20。)

清单 9-20。渲染高分表的标题

void RenderTitle()

{

m_Text.SetText("High".toCharArray());

m_Text.RenderToBillBoard(m_HighScoreTableImage, 0, 0);

m_Text.SetText("Scores".toCharArray());

m_Text.RenderToBillBoard(m_HighScoreTableImage, 5*m_FontWidth, 0);

}

CopyHighScoreEntryToHighScoreTable()功能将高分条目复制到用于显示最终玩家高分的最终合成m_HighScoreTableImage高分广告牌。(参见清单 9-21。)

清单 9-21。将高分条目复制到最终的合成广告牌纹理对象

void CopyHighScoreEntryToHighScoreTable(int Rank, Camera Cam, HighScoreEntry Item)

{

// Put HighScore Entry onto Final Composite Bitmap

// CharacterPosition

int HeightOffset = 10;

int CharPosX = 0;

int CharPosY = m_FontHeight + (Rank * (m_FontHeight + HeightOffset));

// Render Rank

String RankStr = Rank + ".";

m_Text.SetText(RankStr.toCharArray());

m_Text.RenderToBillBoard(m_HighScoreTableImage, CharPosX, CharPosY);

// Render Player Name/Initials and render to composite billboard

String Name = Item.GetInitials();

m_Text.SetText(Name.toCharArray());

CharPosX = CharPosX + m_FontWidth * 3;

m_Text.RenderToBillBoard(m_HighScoreTableImage, CharPosX, CharPosY);

// Render Numerical Value and render to composite billboard

String Score = String.valueOf(Item.GetScore());

m_Text.SetText(Score.toCharArray());

int BlankSpace = 4 * m_FontWidth;

CharPosX = CharPosX + Name.length() + BlankSpace;

m_Text.RenderToBillBoard(m_HighScoreTableImage, CharPosX, CharPosY);

}

该函数执行以下操作:

Calculates the beginning x,y position for the high score entry, based on the height of the character font to be used, the rank of the score (1st, 2nd, 3rd, etc.), and the HeightOffset variable   Renders the high score entry’s rank to the m_HighScoreTableImage billboard   Renders the high score entry’s player’s initials to the m_HighScoreTableImage billboard   Renders the high score entry’s player’s score to the m_HighScoreTableImage billboard  

UpdateHighScoreTable()函数更新高分表。(参见清单 9-22。)

清单 9-22。更新高分表

void UpdateHighScoreTable(Camera Cam)

{

if (m_Dirty)

{

// Sort High Score Table in descending order for score

SortHighScoreTable();

// Clear High Score Table and set background texture

ClearHighScoreTable();

// Render Title

RenderTitle();

// For the Top Ranked entries copy these to the HighScore Table BillBoard

for (int i = 0; i < MAX_RANK; i++)

{

if (m_HighScoreTable[i].IsValid())

{

CopyHighScoreEntryToHighScoreTable(i+1, Cam, m_HighScoreTable[i]);

}

}

// Save High Scores

SaveHighScoreTable(HIGH_SCORES);

m_Dirty = false;

}

// Update BillBoard orientation for Score

m_HighScoreTableImage.UpdateObject3d(Cam);

}

该函数执行以下操作:

If m_Dirty is true, it adds a new high score entry and processes and renders it to the high score table billboard.   If m_Dirty is true, it   Sorts the high score table in descending order, with the highest score listed first, by calling the SortHighScoreTable() function   Clears the high score table by calling the ClearHighScoreTable() function   Renders the high score table’s heading by calling the RenderTitle() function   Renders the top 10 high scores to the high score table by calling the CopyHighScoreEntryToHighScoreTable() function   Saves the high score table by calling the SaveHighScoreTable() function   Sets m_Dirty to false to indicate that the high score table has been updated   Updates the high score table billboard by calling the UpdateObject3d() function to turn the billboard toward the camera  

RenderHighScoreTable()函数将高分表广告牌和包含玩家高分的纹理绘制到屏幕上。(参见清单 9-23。)

清单 9-23。渲染高分表

void RenderHighScoreTable(Camera Cam, PointLight Light, boolean DebugOn)

{

// Render Final High Score Table Composite Image

m_HighScoreTableImage.DrawObject(Cam, Light);

}

创建高分进入系统

HighScoreEntryMenu 类控制输入玩家高分的菜单。

EntryMenuStates枚举保存了被点击的菜单按钮的类型(见清单 9-24),包括

  • 无:没有点击按钮。
  • NextCharacterPressed:按下下一个字符按钮,将选择中的字符更改为用户选择的字符集中的下一个字符。
  • PreviousCharacterPressed:按下 previous character 按钮,将选择中的字符更改为用户选择的字符集中的前一个字符。
  • 输入:输入当前显示的字符作为该位置的字符。

清单 9-24。高分条目菜单按钮

enum EntryMenuStates

{

None,

NextCharacterPressed,

PreviousCharacterPressed,

Enter

}

MAX_ENTRY_CHARACTERS变量包含输入玩家名字或首字母的最大字符数。

private int MAX_ENTRY_CHARACTERS = 3;

m_EntryIndex变量保存玩家名字/初始选择输入中的当前角色位置。例如,0 表示玩家正在选择要输入的首字母。

private int m_EntryIndex = 0;

m_Entry变量保存玩家的名字/首字母。

private char[] m_Entry = new char[MAX_ENTRY_CHARACTERS];

m_NextCharacterButton按钮在可用于输入玩家姓名/首字母的字符集中向前循环。

private MenuItem m_NextCharacterButton;

m_PreviousCharacterButton按钮在可用于输入玩家姓名/首字母的字符集中向后循环。

private MenuItem m_PreviousCharacterButton;

按下m_EnterButton按钮,选择当前玩家姓名/初始进入位置的角色。

private MenuItem m_EnterButton;

m_Text变量保存将用于玩家名字/首字母的字符集。

private BillBoardCharacterSet m_Text;

m_NumberCharactersInSet变量保存了m_Text字符集中的字符数。

private int m_NumberCharactersInSet = 0;

m_CharacterSetIndex变量用于跟踪用户选择输入其姓名/首字母的当前字符。

private int m_CharacterSetIndex = 0;

m_FontWidth是字符集m_Text中使用的字符字体的宽度。

private int m_FontWidth;

m_FontHeight是字符集m_Text中使用的字符字体的高度。

private int m_FontHeight;

m_HighScoreEntryMenuImage变量保存高分入口菜单的广告牌。

private BillBoard m_HighScoreEntryMenuImage;

如果m_HighScoreEntryMenuImage必须更新,则m_Dirty variable为真。

private boolean m_Dirty = true;

m_StartingEntryPositionX变量保存玩家姓名/首字母输入字段的起始 x 位置。

private int m_StartingEntryPositionX;

m_StartingEntryPositionY变量保存玩家姓名/首字母输入字段的起始 y 位置。

private int m_StartingEntryPositionY;

m_CurrentEntryPositionX变量保存玩家名字/首字母输入位置的当前 x 位置。

private int m_CurrentEntryPositionX;

m_CurrentEntryPositionY变量保存玩家名字/首字母输入位置的当前 y 位置。

private int m_CurrentEntryPositionY;

如果用户已经完成输入他或她的名字/首字母,变量m_EntryFinished为真。

private boolean m_EntryFinished = false;

HighScoreEntryMenu 构造函数初始化并重置高分入口菜单以供使用。(参见清单 9-25。)

清单 9-25。HighScoreEntryMenu 构造函数

HighScoreEntryMenu(MenuItem NextCharacterButton,MenuItem PreviousCharacterButton,MenuItem EnterButton,BillBoardCharacterSet Text,BillBoard HighScoreEntryMenuImage,int StartingEntryXPos,int StartingEntryYPos)

{

m_NextCharacterButton = NextCharacterButton;

m_PreviousCharacterButton = PreviousCharacterButton;

m_EnterButton = EnterButton;

m_Text = Text;

m_HighScoreEntryMenuImage = HighScoreEntryMenuImage;

m_FontWidth = m_Text.GetFontWidth();

m_FontHeight = m_Text.GetFontHeight();

m_NumberCharactersInSet = m_Text.GetNumberCharactersInSet();

m_CurrentEntryPositionX = StartingEntryXPos;

m_CurrentEntryPositionY = StartingEntryYPos;

m_StartingEntryPositionX = StartingEntryXPos;

m_StartingEntryPositionY = StartingEntryYPos;

ResetMenu();

}

ResetMenu()功能将菜单重置为初始状态,用户可以从首字母开始输入他或她的首字母。默认首字母被重置为"..."。(参见清单 9-26。)

清单 9-26。重置高分入口菜单

void ResetMenu()

{

m_CharacterSetIndex = 10;

m_EntryIndex = 0;

m_CurrentEntryPositionX = m_StartingEntryPositionX;

m_CurrentEntryPositionY = m_StartingEntryPositionY;

m_Text.SetText("...".toCharArray());

m_Text.RenderToBillBoard(m_HighScoreEntryMenuImage, m_CurrentEntryPositionX, m_CurrentEntryPositionY);

m_EntryFinished = false;

FindCurrentCharacter()函数查找匹配用户当前初始输入选择的字符并返回它。(参见清单 9-27。)

清单 9-27。查找当前选定的首字母

char FindCurrentCharacter()

{

BillBoardFont Font = m_Text.GetCharacter(m_CharacterSetIndex);

return Font.GetCharacter();

}

ProcessEnterMenuSelection()功能输入当前选择的首字母作为当前玩家姓名/初始位置的条目,并将入口点增加到下一个初始入口位置。m_Dirty变量也被设置为 true,表示需要更新菜单的广告牌。(参见清单 9-28。)

清单 9-28。处理菜单选择

void ProcessEnterMenuSelection()

{

char EnteredChar = FindCurrentCharacter();

m_Entry[m_EntryIndex] = EnteredChar;

m_EntryIndex++;

if (m_EntryIndex >= MAX_ENTRY_CHARACTERS)

{

m_EntryFinished = true;

}

m_CurrentEntryPositionX = m_CurrentEntryPositionX + m_FontWidth;

m_Dirty = true;

}

ProcessPreviousMenuSelection()功能通过减少用于从m_Text中保存的字符集中检索当前字符选择的索引来处理前一个字符菜单按钮。如果索引小于 0,它会绕回指向字符集中的最后一个字符。m_Dirty变量也被设置为真。(参见清单 9-29。)

清单 9-29。处理前一个字符菜单选择按钮

void ProcessPreviousMenuSelection()

{

// Go to next character

m_CharacterSetIndex--;

if (m_CharacterSetIndex < 0)

{

m_CharacterSetIndex = m_NumberCharactersInSet-1;

}

m_Dirty = true;

}

ProcessNextMenuSelection()功能通过增加索引来处理下一个字符菜单按钮,该索引用于从m_Text中保存的字符集中检索当前字符选择。如果索引大于数组中的最后一个元素,它会绕回指向字符集中的第一个字符。m_Dirty变量也被设置为真。(参见清单 9-30。)

清单 9-30。处理下一个菜单选择按钮

void ProcessNextMenuSelection()

{

// Go to next character

m_CharacterSetIndex++;

if (m_CharacterSetIndex >= m_NumberCharactersInSet)

{

m_CharacterSetIndex = 0;

}

m_Dirty = true;

}

RenderTextToMenu()功能将输入字符Character渲染到高分入口菜单的屏幕位置XPos, YPos,即广告牌m_HighScoreEntryMenuImage。(参见清单 9-31。)

清单 9-31。将文本呈现到输入菜单

void RenderTextToMenu(String Character, int XPos, int YPos)

{

m_Text.SetText(Character.toCharArray());

m_Text.RenderToBillBoard(m_HighScoreEntryMenuImage, XPos, YPos);

}

RenderEntryToMenu()功能将当前选择的玩家姓名首字母字符呈现到输入菜单中。(参见清单 9-32。)

清单 9-32。呈现当前条目选择

void RenderEntryToMenu()

{

char CurrentCharacter = FindCurrentCharacter();

String StringCharacter = CurrentCharacter + "";

RenderTextToMenu(StringCharacter, m_CurrentEntryPositionX, m_CurrentEntryPositionY);

}

GetEntryMenuStatus()功能测试输入屏幕坐标TouchXTouchY以确定用户是否按下了高分输入菜单按钮。如果他或她有,则返回按钮类型。(参见清单 9-33。)

清单 9-33。获取入口菜单状态

EntryMenuStates GetEntryMenuStatus(float TouchX, float TouchY,int ViewPortWidth,int ViewPortHeight)

{

EntryMenuStates Selection = EntryMenuStates.None;

boolean Touched = false;

// Next character Menu Item

Touched = m_NextCharacterButton.Touched(TouchX, TouchY, ViewPortWidth, ViewPortHeight);

if (Touched)

{

Selection = EntryMenuStates.NextCharacterPressed;

}

// Previous character Menu Item

Touched =  m_PreviousCharacterButton.Touched(TouchX, TouchY, ViewPortWidth, ViewPortHeight);

if (Touched)

{

Selection = EntryMenuStates.PreviousCharacterPressed;

}

// Enter Menu Item

Touched =  m_EnterButton.Touched(TouchX, TouchY, ViewPortWidth, ViewPortHeight);

if (Touched)

{

Selection = EntryMenuStates.Enter;

}

return Selection;

}

UpdateHighScoreEntryMenu()功能更新组成高分输入菜单的所有组件,包括用户选择的输入字符、下一个字符按钮、上一个字符按钮、输入按钮和高分输入菜单广告牌。(参见清单 9-34。)

清单 9-34。更新高分条目菜单

void UpdateHighScoreEntryMenu(Camera Cam)

{

//Update Menu Texture if changed

if (m_Dirty)

{

// If need to alter Menu texture then render new texture data

RenderEntryToMenu();

m_Dirty = false;

}

// Update Buttons

m_NextCharacterButton.UpdateObject3d(Cam);

m_PreviousCharacterButton.UpdateObject3d(Cam);

m_EnterButton.UpdateObject3d(Cam);

// Update Initial Entry Area

m_HighScoreEntryMenuImage.UpdateObject3d(Cam);

}

RenderHighScoreEntryMenu()函数(见清单 9-35)按照以下顺序将高分入口菜单的所有组件呈现在屏幕上:

Renders the cycle to the Next Character button   Renders the cycle to the Previous Character button   Renders the Enter player initial selection button   Renders the m_HighScoreEntryMenuImage billboard, which contains the player’s input and the rest of the high score entry menu graphics data  

清单 9-35。呈现高分入口菜单

void RenderHighScoreEntryMenu(Camera Cam, PointLight Light, boolean DebugOn)

{

// Render Buttons

m_NextCharacterButton.DrawObject(Cam, Light);

m_PreviousCharacterButton.DrawObject(Cam, Light);

m_EnterButton.DrawObject(Cam, Light);

// Render Billboard with Entry Menu info

m_HighScoreEntryMenuImage.DrawObject(Cam, Light);

}

动手示例:演示用户界面

在这个动手示例中,我们将把用户界面与第八章中的动手示例挂钩。我们将构建一个工作主菜单,用户可以从中选择一个新的游戏或继续以前保存的游戏。对于高分表,我们将使用一个测试按钮来手动输入一些高分,然后在第十章中让这个功能在最终游戏中完全发挥作用。

修改 MyGLRenderer 类

必须修改 MyGLRenderer 类,以便集成主菜单、高分表和高分条目菜单。

GameState枚举(见清单 9-36)保存了游戏的一般状态,可以是下列之一:

  • 主菜单:显示主菜单的状态
  • ActiveGamePlay:游戏处于活动状态
  • HighScoreTable:显示高分表格的状态
  • 高分输入:玩家在获得高分后输入自己姓名首字母的状态

清单 9-36。游戏状态

enum GameState

{

MainMenu,

ActiveGamePlay,

HighScoreTable,

HighScoreEntry

}

m_GameState变量保存游戏的状态。

private GameState m_GameState = GameState.MainMenu;

m_MainMenu变量保存对实现主菜单的 MainMenu 类对象的引用。

private MainMenu m_MainMenu;

m_HighScoreEntryMenu变量包含对 HighScoreEntryMenu 类对象的引用,该类对象实现了高分入口菜单系统。

private HighScoreEntryMenu m_HighScoreEntryMenu;

m_HighScoreTable变量保存对实现高分表的类对象的引用。

private HighScoreTable m_HighScoreTable;

如果有一个先前保存的游戏,用户可以从那里继续,那么m_CanContinue变量为真。

private boolean m_CanContinue = false;

CreateInitBillBoard()函数根据输入纹理资源、对象位置和对象比例值创建并返回一个新的 BillBoard 对象。(参见清单 9-37。)

清单 9-37。创建广告牌对象

BillBoard CreateInitBillBoard(Context iContext,int TextureResourceID,Vector3 Position,Vector3 Scale)

{

BillBoard NewBillBoard = null;

Texture BillBoardTexture = new Texture(iContext, TextureResourceID);

//Create Shader

Shader Shader = new Shader(iContext, R.raw.vsonelight, R.raw.fsonelight);      // ok

MeshEx Mesh = new MeshEx(8,0,3,5,Cube.CubeData, Cube.CubeDrawOrder);

// Create Material for this object

Material Material1 = new Material();

// Create Texture for BillBoard

Texture[] Tex = new Texture[1];

Tex[0] = BillBoardTexture;

// Create new BillBoard

NewBillBoard = new BillBoard(iContext, null, Mesh, Tex, Material1, Shader );

// Set Initial Position and Orientation

NewBillBoard.m_Orientation.SetPosition(Position);

NewBillBoard.m_Orientation.SetScale(Scale);

NewBillBoard.GetObjectPhysics().SetGravity(false);

return NewBillBoard;

}

CreateHighScoreTable()函数创建分配给m_HighScoreTable变量的高分表。(参见清单 9-38。)

清单 9-38。创建高分表

void CreateHighScoreTable(Context iContext)

{

int TextureResourceID = R.drawable.background;

Vector3 Position = new Vector3(0.5f, 1, 4);

Vector3 Scale = new Vector3(4.5f,5,1);

BillBoard HighScoreTableImage = CreateInitBillBoard(iContext,TextureResourceID,Position,Scale);

m_HighScoreTable = new HighScoreTable(iContext,m_CharacterSet,HighScoreTableImage);

}

CreateHighScoreEntryMenu()功能创建高分输入菜单。(参见清单 9-39。)

清单 9-39。创建高分条目菜单

void CreateHighScoreEntryMenu(Context iContext)

{

// Create High Score Entry Menu Billboard

int TextureResourceID = R.drawable.backgroundentrymenu;

Vector3 Position = new Vector3(0.0f, 1, 4);

Vector3 Scale = new Vector3(4.5f,5,1);

BillBoard HighScoreEntryMenuImage = CreateInitBillBoard(iContext,TextureResourceID,Position,Scale);

// Create Menu Buttons

Shader ObjectShader = new Shader(iContext, R.raw.vsonelight, R.raw.fsonelight);     // ok

MeshEx MenuItemMeshEx = new MeshEx(8,0,3,5,Cube.CubeData, Cube.CubeDrawOrder);

Mesh MenuItemMesh = null;

// Create Material for this object

Material Material1 = new Material();

Material1.SetEmissive(0.3f, 0.3f, 0.3f);

// Create Texture

int NumberTextures = 1;

Texture TexNextButton = new Texture(iContext,R.drawable.nextbutton);

Texture[] Tex = new Texture[NumberTextures];

Tex[0] = TexNextButton;

boolean AnimateTextures = false;

float TimeDelay = 0.0f;

Position = new Vector3(-1.0f, 1.3f, 4.25f);

Scale = new Vector3(1.4f,1.0f,1.0f);

// Next Character Button

MenuItem NextCharacterButton = CreateMenuItem(iContext, MenuItemMesh,MenuItemMeshEx, Material1,NumberTextures,Tex,AnimateTextures,TimeDelay,Position,Scale,ObjectShader );

// Previous Character Button

Position = new Vector3(0.5f, 1.3f, 4.25f);

Texture TexPreviousGameButton = new Texture(iContext,R.drawable.previousbutton);

Tex = new Texture[NumberTextures];

Tex[0] = TexPreviousGameButton;

MenuItem PreviousCharacterButton = CreateMenuItem(iContext, MenuItemMesh,MenuItemMeshEx, Material1, NumberTextures,Tex,AnimateTextures,TimeDelay,Position,Scale,ObjectShader);

// Enter Button

Position = new Vector3(0.0f, 0.0f, 4.25f);

Texture TexEnterButton = new Texture(iContext,R.drawable.enterbutton);

Tex = new Texture[NumberTextures];

Tex[0] = TexEnterButton;

Scale = new Vector3(3.0f,1.0f,1.0f);

MenuItem EnterButton = CreateMenuItem(iContext, MenuItemMesh, MenuItemMeshEx, Material1, NumberTextures, Tex, AnimateTextures, TimeDelay, Position, Scale, ObjectShader);

int StartingEntryXPos = 168;

int StartingEntryYPos = 100;

m_HighScoreEntryMenu = new HighScoreEntryMenu(NextCharacterButton, PreviousCharacterButton, EnterButton, m_CharacterSet, HighScoreEntryMenuImage, StartingEntryXPos, StartingEntryYPos);

}

CreateMenuItem()函数创建一个新的 MenuItem 对象。(参见清单 9-40。)

清单 9-40。创建菜单项

MenuItem CreateMenuItem(Context iContext, Mesh MenuItemMesh, eshEx MenuItemMeshEx, Material Material1, int NumberTextures, Texture[] Tex, boolean AnimateTextures, float TimeDelay, Vector3 Position, Vector3 Scale, Shader ObjectShader)

{

MenuItem NewMenuItem = null;

NewMenuItem = new MenuItem(iContext, MenuItemMesh, MenuItemMeshEx, Tex, Material1, ObjectShader);

NewMenuItem.SetAnimateTextures(AnimateTextures, TimeDelay, 0, NumberTextures-1);

NewMenuItem.m_Orientation.SetPosition(Position);

NewMenuItem.m_Orientation.SetScale(Scale);

NewMenuItem.GetObjectPhysics().SetGravity(false);

return NewMenuItem;

}

CreateMainMenu()功能为我们的游戏创建主菜单。该功能还在主菜单中创建单独的菜单项,例如新游戏菜单项、继续游戏菜单项、显示高分表菜单项和版权菜单项。(参见清单 9-41。)

清单 9-41。创建主菜单

void CreateMainMenu(Context iContext)

{

// Create New Game Button

Shader ObjectShader = new Shader(iContext, R.raw.vsonelight, R.raw.fsonelight);        // ok

MeshEx MenuItemMeshEx = new MeshEx(8,0,3,5,Cube.CubeData, Cube.CubeDrawOrder);

Mesh MenuItemMesh = null;

// Create Material for this object

Material Material1 = new Material();

// Create Texture

int NumberTextures = 1;

Texture TexNewGameButton = new Texture(iContext,R.drawable.newgamebutton);

Texture[] Tex = new Texture[NumberTextures];

Tex[0] = TexNewGameButton;

boolean AnimateTextures = false;

float TimeDelay = 0.0f;

Vector3 Position = new Vector3(0.0f, 2.5f, 4.25f);

Vector3 Scale = new Vector3(3.0f,1.0f,1.0f);

MenuItem NewGameMenuItem = CreateMenuItem(iContext, MenuItemMesh,MenuItemMeshEx, Material1, NumberTextures, Tex, AnimateTextures, TimeDelay, Position, Scale, ObjectShader);

// Continue Game

Position = new Vector3(0.0f, 1.3f, 4.25f);

Texture TexContinueGameButton = new Texture(iContext, R.drawable.continuegamebutton);

Tex = new Texture[NumberTextures];

Tex[0] = TexContinueGameButton;

MenuItem ContinueGameMenuItem = CreateMenuItem(iContext, MenuItemMesh, MenuItemMeshEx, Material1, NumberTextures, Tex, AnimateTextures, TimeDelay, Position, Scale, ObjectShader);

// View High Scores

Position = new Vector3(0.0f, 0.0f, 4.25f);

Texture TexHighScoresButton = new Texture(iContext, R.drawable.highscoresbutton);

Tex = new Texture[NumberTextures];

Tex[0] = TexHighScoresButton;

MenuItem HighScoreMenuItem = CreateMenuItem(iContext, MenuItemMesh, MenuItemMeshEx, Material1, NumberTextures, Tex, AnimateTextures, TimeDelay, Position, Scale, ObjectShader);

// CopyRight Notice

Position = new Vector3(0.0f, -1.3f, 4.25f);

Texture TexCopyrightButton = new Texture(iContext,R.drawable.copyright);

Tex = new Texture[NumberTextures];

Tex[0] = TexCopyrightButton;

Material Material2 = new Material();

Material2.SetEmissive(0.3f, 0.3f, 0.3f);

MenuItem CopyrightMenuItem = CreateMenuItem(iContext, MenuItemMesh, MenuItemMeshEx, Material2, NumberTextures, Tex, AnimateTextures, TimeDelay, Position, Scale, ObjectShader);

m_MainMenu = new MainMenu(NewGameMenuItem, ContinueGameMenuItem, HighScoreMenuItem, CopyrightMenuItem);

}

必须修改CheckTouch()函数(见清单 9-42)。CheckTouch()函数在用户触摸屏幕时被调用,用于处理用户触摸。

清单 9-42。修改CheckTouch()功能

if (m_GameState == GameState.MainMenu)

{

// Reset camera to face main menu

MenuStates result = m_MainMenu.GetMainMenuStatus(m_TouchX, m_TouchY, m_ViewPortWidth, m_ViewPortHeight);

if (result == MenuStates.NewGame)

{

ResetGame();

m_GameState = GameState.ActiveGamePlay;

}

else

if (result == MenuStates.ContinueCurrentGame)

{

LoadContinueStatus(MainActivity.SAVE_GAME_HANDLE);

if (m_CanContinue)

{

LoadGameState(MainActivity.SAVE_GAME_HANDLE);

}

else

{

ResetGame();

}

m_GameState = GameState.ActiveGamePlay;

}

else

if (result == MenuStates.HighScoreTable)

{

m_GameState = GameState.HighScoreTable;

}

else

if (result == MenuStates.Copyright)

{

m_GameState = GameState.HighScoreEntry;

}

return;

}

else

if (m_GameState == GameState.HighScoreTable)

{

m_GameState = GameState.MainMenu;

return;

}

else

if (m_GameState == GameState.HighScoreEntry)

{

// If User presses finished button from High Score Entry Menu

EntryMenuStates result = m_HighScoreEntryMenu.GetEntryMenuStatus(m_TouchX, m_TouchY, m_ViewPortWidth, m_ViewPortHeight);

if (result == EntryMenuStates.NextCharacterPressed)

{

m_HighScoreEntryMenu.ProcessNextMenuSelection();

}

else

if (result == EntryMenuStates.PreviousCharacterPressed)

{

m_HighScoreEntryMenu.ProcessPreviousMenuSelection();

}

else

if (result == EntryMenuStates.Enter)

{

m_HighScoreEntryMenu.ProcessEnterMenuSelection();

if (m_HighScoreEntryMenu.IsEntryFinished())

{

char[] Initials = m_HighScoreEntryMenu.GetEntry();

String StrInitials = new String(Initials);

CreateHighScoreEntry(StrInitials, m_Score);

m_GameState = GameState.HighScoreTable;

m_HighScoreEntryMenu.ResetMenu();

}

}

return;

}

对于CheckTouch()功能的修改

If the main menu is being displayed, get the main menu’s status in terms of finding out what menu item has been pressed.   If the new game menu item has been pressed, reset the game and set the game state to ActiveGamePlay.   If the continue current game menu item has been pressed, load the m_CanContinue status. If the status is true, load the previously saved game; otherwise, reset the game. Set the game state to ActiveGamePlay.   If the high score table menu item has been selected, set the game state to HighScoreTable.   If the copyright menu item has been selected, set the game state to HighScoreEntry to activate the high score entry menu. This is a debug button that will be commented out in the final game. In this chapter, we use it to test the high score menu entry system.   If the high score table is being displayed, set the game state to the main menu.   If the high score entry menu is being displayed, process the menu item being clicked.   If the next character button was clicked, call ProcessNextMenuSelection().   If the previous character button was clicked, call ProcessPreviousMenuSelection().   If the enter button was clicked, call ProcessEnterMenuSelection() to process it. If the entry was complete, add the new high score to the high score table and set the game state to display the high score table.  

必须修改onDrawFrame()函数,根据m_GameState变量更新和呈现主菜单、高分表和高分入口菜单。(参见清单 9-43。)

清单 9-43。修改onDrawFrame()功能

if (m_GameState == GameState.MainMenu)

{

m_MainMenu.UpdateMenu(m_Camera);

m_MainMenu.RenderMenu(m_Camera, m_PointLight, false);

return;

}

// High Score Table

if (m_GameState == GameState.HighScoreTable)

{

m_HighScoreTable.UpdateHighScoreTable(m_Camera);

m_HighScoreTable.RenderHighScoreTable(m_Camera, m_PointLight, false);

return;

}

// High Score Entry

if (m_GameState == GameState.HighScoreEntry)

{

m_HighScoreEntryMenu.UpdateHighScoreEntryMenu(m_Camera);

m_HighScoreEntryMenu.RenderHighScoreEntryMenu(m_Camera, m_PointLight, false);

return;

}

现在,运行程序。您应该会看到下面的主菜单出现。(见图 9-1 。)

A978-1-4302-6548-1_9_Fig1_HTML.jpg

图 9-1。

The main menu

如果点击版权按钮,应该会弹出高分入口菜单。对于这个动手操作示例,尝试手动输入一些高分,条目包含不同的分值。例如,您可以在实际源代码中将分数(m_Score)设置为 93,然后保存文件,重新编译,运行程序,并在高分表中创建新条目。表格中的新条目应该是您分配给源代码中的m_Score值的分数。(见图 9-2 。)

private int m_Score = 93;

A978-1-4302-6548-1_9_Fig2_HTML.jpg

图 9-2。

Entering a new high score

输入一些首字母,当你完成时,你应该被带到高分台。(见图 9-3 。)

A978-1-4302-6548-1_9_Fig3_HTML.jpg

图 9-3。

The high score table

摘要

在这一章中,我介绍了案例研究游戏的用户界面。我首先讨论了主菜单,包括实现主菜单所需的 Android Java 类和代码。接下来,我介绍了高分表以及如何用代码实现它。然后,我看了一遍高分入口菜单。最后,我们在一个工作演示中实现了主菜单、高分表和高分入口菜单。

十、最终的无人机网格游戏

Abstract

这一章涵盖了最终的无人机网格游戏。我首先介绍了在游戏中帮助管理敌人物品所需的职业。然后讨论 GamePlayController 类,它控制诸如什么类型的敌人被添加到游戏场之类的元素。接下来,代码修改和保存和加载游戏状态的新功能将被涵盖。接下来讨论在无人机网格游戏中实现图形游戏和州代码游戏。最后,还包括一个实践示例,演示了本章前面讨论的概念和类。

这一章涵盖了最终的无人机网格游戏。我首先介绍了在游戏中帮助管理敌人物品所需的职业。然后讨论 GamePlayController 类,它控制诸如什么类型的敌人被添加到游戏场之类的元素。接下来,代码修改和保存和加载游戏状态的新功能将被涵盖。接下来讨论在无人机网格游戏中实现图形游戏和州代码游戏。最后,还包括一个实践示例,演示了本章前面讨论的概念和类。

组织和控制敌人团体

在我们的最终游戏中,我们将需要一些支持职业来让我们更容易的操作我们的敌人。我们需要的两个类是 ArenaObjectSet 类,它控制我们的竞技场敌人,和 TankFleet 类,它控制我们的坦克敌人。

ArenaObjectSet 类

ArenaObjectSet 类包含一组 arena 对象,并具有管理这些对象的函数。

变量MAX_ARENA_OBJECTS保存了该集合将保存的竞技场对象的最大数量。

private int MAX_ARENA_OBJECTS = 25;

变量m_NumberArenaObjects保存集合中竞技场对象的实际数量。

private int m_NumberArenaObjects = 0;

变量数组保存了将在这个类中使用的一组 arena 对象。

private ArenaObject3d[] m_ArenaObjectSet = new ArenaObject3d[MAX_ARENA_OBJECTS];

如果m_ArenaObjectSet中相应的 arena 对象是活动的,并且必须被渲染和更新,则m_Active布尔数组对元素成立。

private boolean[] m_Active = new boolean[MAX_ARENA_OBJECTS];

m_ExplosionMinVelocity变量保存与这组竞技场对象相关的爆炸中粒子的最小速度。

private float m_ExplosionMinVelocity = 0.02f;

m_ExplosionMaxVelocity变量保存与这组竞技场对象相关的爆炸中粒子的最大速度。

private float m_ExplosionMaxVelocity = 0.4f;

Init()函数将该类中的所有 arena 对象初始化为 null,并且m_Active状态被设置为 false,这意味着它是不活动的,并且不被渲染、更新或处理。(参见清单 10-1。)

清单 10-1。初始化竞技场对象

void Init()

{

for (int i = 0; i < MAX_ARENA_OBJECTS; i++)

{

m_ArenaObjectSet[i] = null;

m_Active[i] = false;

}

}

ArenaObjectSet 构造函数通过调用Init()函数初始化 ArenaObject 集合。(参见清单 10-2。)

清单 10-2。ArenaObjectSet 构造函数

ArenaObjectSet(Context iContext)

{

m_Context = iContext;

Init();

}

SaveSet()函数保存 ArenaObject 集合的数据。(参见清单 10-3。)

SaveSet()功能执行以下操作:

Retrieves a SharedPreferences object for the input parameter Handle by calling the getSharedPreferences() function   Retrieves an Editor object by calling edit() on the SharedPreferences object from Step 1   Saves the values of the m_Active array to the shared preferences by creating a unique handle consisting of the input parameter Handle, the “Active” keyword, and the index i   Saves the arena objects by calling the SaveObjectState() function using a handle composed of the input parameter Handle, the “ArenaObject” keyword, and the index i for all the elements in the m_ArenaObjectSet array, if there is a valid element for that array index slot   Saves and commits the changes to the shared preferences  

清单 10-3。保存 ArenaObject 集合

void SaveSet(String Handle)

{

SharedPreferences settings = m_Context.getSharedPreferences(Handle, 0);

SharedPreferences.Editor editor = settings.edit();

for (int i = 0; i < MAX_ARENA_OBJECTS; i++)

{

// Active Status

String ActiveHandle = Handle + "Active" + i;

editor.putBoolean(ActiveHandle, m_Active[i]);

if (m_ArenaObjectSet[i] != null)

{

String ArenaObjectHandle = Handle + "ArenaObject" + i;

m_ArenaObjectSet[i].SaveObjectState(ArenaObjectHandle);

}

}

// Commit the edits!

editor.commit();

}

LoadSet()函数加载 ArenaObject 集合数据。(参见清单 10-4。)

LoadSet()功能执行以下操作:

Retrieves a SharedPreferences object based on the input parameter Handle   Reads in all the elements of the m_Active boolean array from the shared preferences using a handle based on the input parameter Handle, “Active” keyword, and the slot index   Reads in the saved data by calling the LoadObjectState() function for each element in the m_ArenaObjectSet array that has a valid object  

清单 10-4。加载 ArenaObject 集合

void LoadSet(String Handle)

{

// Restore preferences

SharedPreferences settings = m_Context.getSharedPreferences(Handle, 0);

for (int i = 0; i < MAX_ARENA_OBJECTS; i++)

{

// Active Status

String ActiveHandle = Handle + "Active" + i;

m_Active[i] = settings.getBoolean(ActiveHandle, false);

if (m_ArenaObjectSet[i] != null)

{

String ArenaObjectHandle = Handle + "ArenaObject" + i;

m_ArenaObjectSet[i].LoadObjectState(ArenaObjectHandle);

}

}

}

ResetSet()函数通过将每个对象的活动状态更改为 false 并将对象的可见性设置为 false 来重置整个竞技场对象集。(参见清单 10-5。)

清单 10-5。重置设置

void ResetSet()

{

// Sets all objects to inactive and invisible

for (int i = 0; i < MAX_ARENA_OBJECTS; i++)

{

if (m_ArenaObjectSet[i] != null)

{

m_Active[i] = false;

m_ArenaObjectSet[i].SetVisibility(false);

}

}

}

函数返回集合中活动竞技场对象的数量。(参见清单 10-6。)

清单 10-6。获取活动竞技场对象的数量

int NumberActiveArenaObjects()

{

int NumberActiveVehicles = 0;

for (int i = 0; i < MAX_ARENA_OBJECTS; i++)

{

if (m_Active[i] == true)

{

NumberActiveVehicles++;

}

}

return NumberActiveVehicles;

}

GetAvailableArenaObject()函数(见清单 10-7)通过以下方式返回一个可用的竞技场对象

Searching through the entire set of arena objects in the m_ArenaObjectSet array and trying to find an ArenaObject3d object that is not null and not active   Processing a non-null object by setting the object’s visibility to true, setting the object to active by setting the corresponding slot in the m_Active array, and returning the object   Returning a null value if no available object is found  

清单 10-7。获取可用的竞技场对象

ArenaObject3d GetAvailableArenaObject()

{

ArenaObject3d temp = null;

for (int i = 0; i < MAX_ARENA_OBJECTS; i++)

{

if (m_ArenaObjectSet[i] != null)

{

if (m_Active[i] == false)

{

m_ArenaObjectSet[i].SetVisibility(true);

m_Active[i] = true;

return m_ArenaObjectSet[i];

}

}

}

return temp;

}

GetRandomAvailableArenaObject()函数(见清单 10-8)通过以下方式从一组竞技场对象中获取一个随机的竞技场对象

Creating a random number generator called RandomNumber   Creating an array that will hold the indices of available arena objects to choose from for our random selection   Building a list of available arena objects and putting the indices into the AvailableObjects array   Finding a random arena object based on the output of the random number generator and the list of available arena objects   Returning an available object after setting the visibility and active status to true  

清单 10-8。获取随机竞技场对象

ArenaObject3d GetRandomAvailableArenaObject()

{

ArenaObject3d Obj = null;

Random RandomNumber = new Random();

int RandomIndex = 0;

int AvailableObjectsIndex = 0;

int[] AvailableObjects = new int[MAX_ARENA_OBJECTS];

// Build list of available objects

for (int i = 0; i < MAX_ARENA_OBJECTS; i++)

{

if (m_ArenaObjectSet[i] != null)

{

if (m_Active[i] == false)

{

AvailableObjects[AvailableObjectsIndex] = i;

AvailableObjectsIndex++;

}

}

}

// If there are Available Objects then choose one at random from the list of available objects

if (AvailableObjectsIndex > 0)

{

// Find Random Object from array of available objects

RandomIndex = RandomNumber.nextInt(AvailableObjectsIndex);

int ObjIndex = AvailableObjects[RandomIndex];

Obj =  GetArenaObject(ObjIndex);

if (Obj != null)

{

Obj.SetVisibility(true);

m_Active[ObjIndex] = true;

}

else

{

Log.e("ARENAOBJECTSSET", "Random Arena OBJECT = NULL ERROR!!!! ");

}

}

return Obj;

}

AddNewArenaObject()函数(见清单 10-9)通过以下方式向竞技场对象集合中添加一个新的竞技场对象:

Finding an empty slot in the m_ArenaObjectSet array   If an empty slot is found, setting the new input arena object to invisible; setting the empty slot to point to the new arena object; increasing the number of arena objects in the set by incrementing the m_NumberArenaObjects variable; and returning true   Returning false if an empty slot is not found  

清单 10-9。向集合中添加新的竞技场对象

boolean AddNewArenaObject(ArenaObject3d ArenaObj)

{

boolean result = false;

for (int i = 0; i < MAX_ARENA_OBJECTS; i++)

{

if (m_ArenaObjectSet[i] == null)

{

ArenaObj.SetVisibility(false);

m_ArenaObjectSet[i] = ArenaObj;

m_NumberArenaObjects++;

return true;

}

}

return result;

}

SetSoundOnOff()功能根据输入参数Value将一组竞技场对象的音效设置为开或关。对于每个有效的竞技场对象,调用SetSFXOnOff()函数。(参见清单 10-10。)

清单 10-10。设置 ArenaObject 集合的声音效果

void SetSoundOnOff(boolean Value)

{

for (int i = 0; i < MAX_ARENA_OBJECTS; i++)

{

if (m_ArenaObjectSet[i] != null)

{

m_ArenaObjectSet[i].SetSFXOnOff(Value);

}

}

}

ProcessCollisionsWeapon()函数(见清单 10-11)处理武器弹药和竞技场物体之间的碰撞

Determining the active status for each element in the ArenaObject set   If the element is active, checking for collisions between that arena object and the ammunition from the input iWeapon weapon   If there is a collision, processing the collision by calling ApplyLinearImpulse() to apply action reaction forces to the arena object and the weapon’s ammunition   Starting the explosion graphic and sound associated with the arena object involved in the collision   Processing the damage to the arena object by calling the TakeDamage() function   Checking the health of the arena object and, if it is 0 or less, updating the total kill value, which is the total value of all the objects destroyed by the weapon’s ammunition, and destroying the arena object by setting its active status to false and its visibility to false   Returning the total kill value  

清单 10-11。武器弹药和非目标集合之间的碰撞检测

int ProcessCollisionsWeapon(Weapon iWeapon)

{

int TotalKillValue = 0;

for (int i = 0; i < MAX_ARENA_OBJECTS; i++)

{

if ((m_ArenaObjectSet[i] != null) && (m_Active[i] == true))

{

Object3d CollisionObj = iWeapon.CheckAmmoCollision(m_ArenaObjectSet[i]);

if (CollisionObj != null)

{

CollisionObj.ApplyLinearImpulse(m_ArenaObjectSet[i]);

SphericalPolygonExplosion Exp = m_ArenaObjectSet[i].GetExplosion(0);

Exp.StartExplosion(m_ArenaObjectSet[i].m_Orientation.GetPosition(), m_ExplosionMaxVelocity, m_ExplosionMinVelocity);

m_ArenaObjectSet[i].PlayExplosionSFX();

// Process Damage

m_ArenaObjectSet[i].TakeDamage(CollisionObj);

int Health = m_ArenaObjectSet[i].GetObjectStats().GetHealth();

if (Health <= 0)

{

int KillValue = m_ArenaObjectSet[i].GetObjectStats().GetKillValue();

TotalKillValue = TotalKillValue + KillValue;

m_Active[i] = false;

m_ArenaObjectSet[i].SetVisibility(false);

}

}

}

}

return TotalKillValue;

}

AddArenaObjectsToGravityGrid()函数将集合中所有活动的竞技场对象添加到作为输入参数的重力网格iGrid中。(参见清单 10-12。)

清单 10-12。将集合中的竞技场对象添加到重力网格

void AddArenaObjectsToGravityGrid(GravityGridEx iGrid)

{

for (int i = 0; i < MAX_ARENA_OBJECTS; i++)

{

if ((m_ArenaObjectSet[i] != null) && (m_Active[i] == true))

{

// Add Mass of AirVehicle to grid

iGrid.AddMass(m_ArenaObjectSet[i]);

}

}

}

GetArenaObject()函数从包含当前活动的输入 id 对象 ID 的m_ArenaObjectSet中的 arena 对象集合中返回 arena 对象。(参见清单 10-13。)

清单 10-13。根据 ID 获取 Arena 对象

ArenaObject3d GetArenaObject(String ID)

{

ArenaObject3d temp = null;

for (int i = 0; i < MAX_ARENA_OBJECTS; i++)

{

if (m_ArenaObjectSet[i] != null)

{

if ((m_Active[i]== true) && (m_ArenaObjectSet[i].GetArenaObjectID() == ID))

{

return m_ArenaObjectSet[i];

}

}

}

return temp;

}

ProcessCollisionWithObject()函数(见清单 10-14)通过以下方式处理输入对象Obj和 ArenaObject 集合之间的冲突:

For each arena object in the set that is active, it checks for a collision with the input Obj object.   If there is a collision, then it processes the collision by calling ApplyLinearImpulse() to apply the action and reaction forces on both colliding objects. It starts the explosion graphics and plays the explosion sound effect for the arena object that is colliding. It plays the explosion graphic for input object Obj that is involved in the collision. It processes the damage to the arena object and the object Obj from the collision. If the health of the arena object is less than or equal to 0, it adds the kill value of the arena object to the total kill value. The arena object is destroyed by setting the active status to false and the visible status to false.   The total kill value that equals the total of all the kill values of the destroyed arena objects that have just been destroyed by a collision with the input object Obj is returned.  

清单 10-14。处理对象和 ArenaObject 集之间的冲突

int ProcessCollisionWithObject(Object3d Obj)

{

int TotalKillValue = 0;

for (int i = 0; i < MAX_ARENA_OBJECTS; i++)

{

if ((m_ArenaObjectSet[i] != null) && (m_Active[i] == true))

{

Physics.CollisionStatus result = Obj.CheckCollision(m_ArenaObjectSet[i]);

if ((result == Physics.CollisionStatus.COLLISION) ||

(result == Physics.CollisionStatus.PENETRATING_COLLISION))

{

// Process Collision

Obj.ApplyLinearImpulse(m_ArenaObjectSet[i]);

// Arena Object Explosion

SphericalPolygonExplosion Exp = m_ArenaObjectSet[i].GetExplosion(0);

if (Exp != null)

{

Exp.StartExplosion(m_ArenaObjectSet[i].m_Orientation.GetPosition(), m_ExplosionMaxVelocity,  m_ExplosionMinVelocity);

m_ArenaObjectSet[i].PlayExplosionSFX();

}

// Pyramid Explosion

Exp = Obj.GetExplosion(0);

if (Exp != null)

{

Exp.StartExplosion(Obj.m_Orientation.GetPosition(),m_ExplosionMaxVelocity,   m_ExplosionMinVelocity);

}

// Process Damage

Obj.TakeDamage(m_ArenaObjectSet[i]);

m_ArenaObjectSet[i].TakeDamage(Obj);

int Health = m_ArenaObjectSet[i].GetObjectStats().GetHealth();

if (Health <= 0)

{

int KillValue = m_ArenaObjectSet[i].GetObjectStats().GetKillValue();

TotalKillValue = TotalKillValue + KillValue;

m_Active[i] = false;

m_ArenaObjectSet[i].SetVisibility(false);

}

}

}

}

return TotalKillValue;

}

RenderArenaObjects()函数呈现集合m_ArenaObjectSet数组中所有有效的非空对象。(参见清单 10-15。)

清单 10-15。渲染 ArenaObject 集

void RenderArenaObjects(Camera Cam, PointLight Light, boolean DebugOn)

{

for (int i = 0; i < MAX_ARENA_OBJECTS; i++)

{

if (m_ArenaObjectSet[i] != null)

{

m_ArenaObjectSet[i].RenderArenaObject(Cam, Light);

}

}

}

函数更新集合中所有有效的竞技场对象。(参见清单 10-16。)

清单 10-16。更新 ArenaObject 集

void UpdateArenaObjects()

{

for (int i = 0; i < MAX_ARENA_OBJECTS; i++)

{

if (m_ArenaObjectSet[i] != null)

{

m_ArenaObjectSet[i].UpdateArenaObject();

}

}

}

坦克舰队级

TankFleet 类拥有一组坦克对象,并包含帮助管理和操纵坦克车队的函数。TankFleet 类和 ArenaObjectSet 类之间有许多相似之处,所以我在这里只讨论 TankFleet 类中本质上不同或重要的函数。

MAX_TANKS变量保存该组中可容纳的最大罐数。

private int MAX_TANKS = 5;

m_TankFleet数组保存该集合的坦克对象。

private Tank[] m_TankFleet = new Tank[MAX_TANKS];

如果m_TankFleet数组中的相应坦克是活动的并且需要更新和渲染,则m_InService变量数组为元素保存真值。

private boolean[] m_InService = new boolean[MAX_TANKS];

TankFleet()构造函数初始化坦克车队,类似于 ArenaObjectSet 类的初始化方式。(参见清单 10-17。)

清单 10-17。坦克舰队建造者

TankFleet(Context iContext)

{

m_Context = iContext;

Init();

}

ResetSet()功能(见清单 10-18)通过以下方式重置坦克车队

Setting existing tanks to inactive, m_InService = false   Setting the main body and the turret to invisible   Resetting the tank’s finite state machine and weapons  

清单 10-18。重置水箱组件

void ResetSet()

{

// Sets all objects to inactive and invisible

for (int i = 0; i < MAX_TANKS; i++)

{

if (m_TankFleet[i] != null)

{

m_InService[i] = false;

m_TankFleet[i].GetMainBody().SetVisibility(false);

m_TankFleet[i].GetTurret().SetVisibility(false);

m_TankFleet[i].Reset();

}

}

}

SetSoundOnOff()功能打开/关闭坦克车队的音效。(参见清单 10-19。)打开或关闭坦克部件的声音效果,这些部件是

The tank’s main body   The tank’s turret   The tank’s weapons  

清单 10-19。为坦克设置音效

void SetSoundOnOff(boolean Value)

{

for (int i = 0; i < MAX_TANKS; i++)

{

if (m_TankFleet[i] != null)

{

m_TankFleet[i].GetMainBody().SetSFXOnOff(Value);

m_TankFleet[i].GetTurret().SetSFXOnOff(Value);

int NumberWeapons = m_TankFleet[i].GetNumberWeapons();

for (int j = 0; j < NumberWeapons; j++)

{

m_TankFleet[i].GetWeapon(j).TurnOnOffSFX(Value);

}

}

}

}

AddTankFleetToGravityGrid()功能将车队中所有现役坦克添加到输入参数重力网格iGrid中。更具体地说,坦克的主体和坦克武器的主动弹药都被添加到重力网格中。(参见清单 10-20。)

清单 10-20。将坦克舰队加入重力网格

// Add in all the Air vehicles in the fleet to the gravity grid

void AddTankFleetToGravityGrid(GravityGridEx iGrid)

{

Object3d[] Masses = new Object3d[50];

int NumberMasses = 0;

for (int i = 0; i < MAX_TANKS; i++)

{

if ((m_TankFleet[i] != null) && (m_InService[i] == true))

{

// Add Mass of AirVehicle to grid

iGrid.AddMass(m_TankFleet[i].GetMainBody());

// Add Weapons Fire from AirVehicle to grid

int NumberWeapons = m_TankFleet[i].GetNumberWeapons();

for (int j = 0; j < NumberWeapons; j++)

{

NumberMasses =  m_TankFleet[i].GetWeapon(j).GetActiveAmmo(0, Masses);

iGrid.AddMasses(NumberMasses, Masses);

}

}

}

}

ProcessWeaponAmmoCollisionObject()功能处理坦克车队中坦克的实弹与输入物体Obj的碰撞。(参见清单 10-21。)

对于坦克车队中的每辆现役坦克

Check for a collision between the ammunition from the tank’s weapons and the object Obj.   If there is a collision, apply a linear force to the ammunition and the object by calling the ApplyLinearImpulse() function   Process the damage to the object by calling TakeDamage()   Start the explosion graphic for the object by calling StartExplosion()  

清单 10-21。物体与坦克实弹的碰撞处理

boolean ProcessWeaponAmmoCollisionObject(Object3d Obj)

{

Object3d CollisionObj = null;

boolean hitresult = false;

for (int i = 0; i < MAX_TANKS; i++)

{

if ((m_TankFleet[i] != null) && (m_InService[i] == true))

{

int NumberWeapons = m_TankFleet[i].GetNumberWeapons();

for (int j=0; j < NumberWeapons; j++)

{

CollisionObj = m_TankFleet[i].GetWeapon(j).CheckAmmoCollision(Obj);

if (CollisionObj != null)

{

hitresult = true;

CollisionObj.ApplyLinearImpulse(Obj);

//Process Damage

Obj.TakeDamage(CollisionObj);

// Obj Explosion

SphericalPolygonExplosion Exp = Obj.GetExplosion(0);

if (Exp != null)

{

Exp.StartExplosion(Obj.m_Orientation.GetPosition(), m_VehicleExplosionMaxVelocity, m_VehicleExplosionMinVelocity);

}

}

}

}

}

return hitresult;

}

GamePlayController 类

GamePlayController 类用于控制游戏的进行,包括每种类型的敌人在重力网格上的数量,他们第一次出现的位置,以及他们出现的速度。

m_RandNumber变量保存随机数发生器。

private Random m_RandNumber = new Random();

m_ArenaObjectsSet变量保存将在运动场上使用的一组竞技场对象。

private ArenaObjectSet m_ArenaObjectsSet;

变量m_TankFleet保存了将要在游戏场上使用的坦克组。

private TankFleet m_TankFleet;

m_Grid变量保存标记游戏区域边界的重力网格。

private GravityGridEx  m_Grid;

DROP_HEIGHT变量表示竞技场物体将被放入运动场的高度。

private float DROP_HEIGHT = 13;

m_TimeDeltaAddArenaObject变量保存添加新竞技场对象之间的毫秒数。

private long m_TimeDeltaAddArenaObject = 1000 * 15;

m_TimeLastArenaObjectAdded变量保存最后一个竞技场对象被添加到运动场的时间。

private long m_TimeLastArenaObjectAdded = 0;

m_MinArenaObjectsOnPlayField变量保存了必须在游戏场上的最小数量的竞技场对象。

private int m_MinArenaObjectsOnPlayField = 1;

m_MaxSpeedArenaObjects变量保存竞技场物体的最大速度。

private float m_MaxSpeedArenaObjects = 0.1f;

变量m_TimeDeltaAddTank保存将新坦克添加到游戏场之间的毫秒数。

private long m_TimeDeltaAddTank = 1000 * 25;

变量m_TimeLastTankOnGrid保存了坦克敌人最后一次加入游戏场的时间。

private long m_TimeLastTankOnGrid = 0;

m_MaxTanksOnPlayField变量保存游戏场上一次允许的最大坦克数量。

private int m_MaxTanksOnPlayField = 2;

m_NumberTankRoutes变量保存可供选择的坦克路线总数。

private int m_NumberTankRoutes = 0;

m_TankRouteIndex变量保存当前可用坦克路线的索引,也就是m_TankRoutes数组变量。

private int m_TankRouteIndex = 0;

m_TankRoutes数组变量保存由一组路点组成的坦克路线。

private Route[] m_TankRoutes = null;

GamePlayController()构造函数通过设置关键的类成员变量来初始化 GamePlayController 类,例如竞技场对象集、坦克车队、重力网格和可用的坦克路线。(参见清单 10-22。)

清单 10-22。GamePlayController 构造函数

GamePlayController(Context iContext,ArenaObjectSet  ArenaObjectsSet,TankFleet TankFleet, GravityGridEx   Grid,int NumberTankRoutes,Route[] TankRoutes)

{

m_Context = iContext;

m_ArenaObjectsSet = ArenaObjectsSet;

m_TankFleet = TankFleet;

m_Grid = Grid;

m_NumberTankRoutes = NumberTankRoutes;

m_TankRoutes = TankRoutes;

}

GenerateRandomGridLocation()生成并返回重力网格上的随机位置。(参见清单 10-23。)

该函数执行以下操作:

Gets the minimum x boundary for the gravity grid   Gets the maximum x boundary for the gravity grid   Finds the difference between the maximum x boundary and the minimum x boundary   Finds a random offset position based on the difference found in step 3 multiplied by a random number that ranges from 0 to 1   Calculates the final x position based on the minimum x value added with the randomly generated x offset value   Repeats steps 1 through 5 for the z axis, in order to find a random z coordinate   Returns a random location vector consisting of the random x and z values calculated in the previous steps and the DROP_HEIGHT value as the y value  

清单 10-23。生成随机网格位置

Vector3 GenerateRandomGridLocation()

{

Vector3 Location = new Vector3(0,0,0);

// Get Random X

float MinX = m_Grid.GetXMinBoundary();

float MaxX = m_Grid.GetXMaxBoundary();

float DiffX = MaxX - MinX;

float RandomXOffset = DiffX * m_RandNumber.nextFloat(); // DiffX * (Number from 0-1);

float PosX = MinX + RandomXOffset;

// Get Random Z

float MinZ = m_Grid.GetZMinBoundary();

float MaxZ = m_Grid.GetZMaxBoundary();

float DiffZ = MaxZ - MinZ;

float RandomZOffset = DiffZ * m_RandNumber.nextFloat(); // DiffX * (Number from 0-1);

float PosZ = MinZ + RandomZOffset;

// Y is 0 for Ground Level for Playfield

float PosY = DROP_HEIGHT;

// Set Random Location

Location.Set(PosX, PosY, PosZ);

return Location;

}

GenerateGridLocationRestricted()功能生成一个随机网格位置,用于在最小和最大边界内放下一个敌人。(参见清单 10-24。)

该函数执行以下操作:

The GenerateRandomGridLocation() function is called to create a random location within the grid.   Then, the location is limited or clamped to the maximum location value by taking the lesser value between Max and the randomly generated location from step 1.   Then the location is clamped to the minimum location value in Min by taking the greater value between Min and the clamped location from step 2.   The final clamped location from step 3 is returned.  

清单 10-24。在边界内生成随机网格位置

Vector3 GenerateGridLocationRestricted(Vector3 Max, Vector3 Min)

{

Vector3 ClampedLocation = new Vector3(0,0,0);

Vector3 OriginalLocation = null;

OriginalLocation = GenerateRandomGridLocation();

ClampedLocation.x = Math.min(OriginalLocation.x, Max.x);

ClampedLocation.y = Math.min(OriginalLocation.y, Max.y);

ClampedLocation.z = Math.min(OriginalLocation.z, Max.z);

ClampedLocation.x = Math.max(ClampedLocation.x, Min.x);

ClampedLocation.y = Math.max(ClampedLocation.y, Min.y);

ClampedLocation.z = Math.max(ClampedLocation.z, Min.z);

return ClampedLocation;

}

GenerateRandomVelocityArenaObjects()函数为 xz 平面上的竞技场对象生成随机速度。(参见清单 10-25。)

该函数执行以下操作:

Generates a random speed along the x axis by multiplying the maximum speed for an arena object by a randomly generated number within the range of 0 to 1   Generates a random speed along the z axis by multiplying the maximum speed for an arena object by a randomly generated number within the range of 0 to 1   Creates the final velocity by using the x and z values from steps 1 and 2 with a y value of 0   Returns the final random velocity  

清单 10-25。产生随机速度

Vector3 GenerateRandomVelocityArenaObjects()

{

Vector3 Velocity = new Vector3(0,0,0);

float VelX = m_MaxSpeedArenaObjects * m_RandNumber.nextFloat();

float VelZ = m_MaxSpeedArenaObjects * m_RandNumber.nextFloat();

Velocity.Set(VelX, 0, VelZ);

return Velocity;

}

AddNewArenaObject()函数将一个竞技场对象添加到运动场。(参见清单 10-26。)

该函数执行以下操作:

Tries to retrieve a new available arena object by calling the GetRandomAvailableArenaObject() function   If an arena object is available, it Sets its visibility to true and its health to 100   Creates a location vector called Max that holds the maximum location of the arena object along the x and z axes   Creates a location vector called Min that holds the minimum location of the arena object along the x and z axes   Calls the GenerateGridLocationRestricted() function with the Max and Min locations to retrieve a random location within the bounds of Max and Min   Sets the position of the new arena object to the location generated from the previous step   Returns true if a new arena object has been added to the game and false otherwise    

清单 10-26。添加新的竞技场对象

boolean AddNewArenaObject()

{

boolean result = false;

ArenaObject3d AO = m_ArenaObjectsSet.GetRandomAvailableArenaObject();

if (AO != null)

{

// Respawn

AO.SetVisibility(true);

AO.GetObjectStats().SetHealth(100);

Vector3 Max = new Vector3(m_Grid.GetXMaxBoundary(), DROP_HEIGHT, -5.0f);

Vector3 Min = new Vector3(m_Grid.GetXMinBoundary(), DROP_HEIGHT, m_Grid.GetZMinBoundary());

Vector3 Position = GenerateGridLocationRestricted(Max, Min);

AO.m_Orientation.GetPosition().Set(Position.x, Position.y, Position.z);

result = true;

}

return result;

}

函数CreatePatrolAttackTankCommand()创建一个新的巡逻/攻击坦克命令并返回它。(参见清单 10-27。)

该命令的关键数据字段是

The Command variable, which is set to AIVehicleCommand.Patrol   The DeltaAmount variable, which is set to the number of rounds for the tank to fire in one burst   The DeltaIncrement variable, which is set to the time delay in milliseconds between the tank’s bursts of fire  

清单 10-27。创建巡逻/攻击坦克指挥部

VehicleCommand CreatePatrolAttackTankCommand(AIVehicleObjectsAffected ObjectsAffected, int NumberWayPoints, Vector3[] WayPoints, Vector3 Target, Object3d TargetObj, int NumberRoundToFire,int FiringDelay)

{

VehicleCommand TankCommand = null;

AIVehicleCommand Command = AIVehicleCommand.Patrol;

int     NumberObjectsAffected = 0;

int     DeltaAmount = NumberRoundToFire;

int     DeltaIncrement = FiringDelay;

int     MaxValue = 0;

int     MinValue = 0;

TankCommand = new VehicleCommand(m_Context,Command, ObjectsAffected, NumberObjectsAffected,  DeltaAmount, DeltaIncrement,MaxValue, MinValue, NumberWayPoints,WayPoints, Target, TargetObj);

return TankCommand;

}

SetTankOrder()功能创建一个新的坦克巡逻/攻击命令,并将该命令下达给坦克。(参见清单 10-28。)

SetTankOrder()功能执行以下操作:

Sets the tank route index to cycle through all the available routes   Retrieves the selected tank route   Retrieves the tank’s waypoints from the route   Retrieves the number of waypoints from the route   Sets up the tank’s patrol/attack command to fire three-round bursts of ammunition at the target located at the origin every five seconds   Creates the tank’s patrol/attack command and sets this command for the input TankVehicle  

清单 10-28。设定巡逻/攻击坦克的顺序

void SetTankOrder(Tank TankVehicle)

{

// Set Tank Route Index to cycle through all available routes

m_TankRouteIndex++;

if (m_TankRouteIndex >= m_NumberTankRoutes)

{

m_TankRouteIndex = 0;

}

// Set Patrol Order

Route SelectedRoute = m_TankRoutes[m_TankRouteIndex];

Vector3[] WayPoints = SelectedRoute.GetWayPoints();

int NumberWayPoints = SelectedRoute.GetNumberWayPoints();

AIVehicleObjectsAffected ObjectsAffected = AIVehicleObjectsAffected.PrimaryWeapon;

Vector3 Target = new Vector3(0,0,0);

Object3d TargetObj = null;

int NumberRoundToFire = 3;

int FiringDelay = 5000;

VehicleCommand Command = CreatePatrolAttackTankCommand(ObjectsAffected, NumberWayPoints, WayPoints, Target, TargetObj,NumberRoundToFire,FiringDelay);

TankVehicle.GetDriver().SetOrder(Command);

}

AddNewTank()函数将坦克对象添加到游戏区。(参见清单 10-29。)

该函数执行以下操作:

Tries to retrieve a new available tank object by calling the GetAvailableTank() function.   If a tank object was available, it Resets the tank and its health to 100   Creates a location vector called Max that holds the maximum location of the tank object along the x and z axes   Creates a location vector called Min that holds the minimum location of the tank object along the x and z axes   Calls GenerateGridLocationRestricted() with the Max and Min locations to retrieve a random location within the bounds of Max and Min   Sets the position of the new tank object to the location generated from the previous step   Creates and sets the tank object’s order by calling SetTankOrder()   Returns true if a new tank object has been added to the game and false otherwise    

清单 10-29。在操场上增加一辆新坦克

boolean AddNewTank()

{

boolean result = false;

Tank TankVehicle = m_TankFleet.GetAvailableTank();

if (TankVehicle != null)

{

TankVehicle.Reset();

TankVehicle.GetMainBody().GetObjectStats().SetHealth(100);

// Set Position

Vector3 Max = new Vector3(m_Grid.GetXMaxBoundary(), DROP_HEIGHT, -5.0f);

Vector3 Min = new Vector3(m_Grid.GetXMinBoundary(), DROP_HEIGHT, m_Grid.GetZMinBoundary());

Vector3 Position = GenerateGridLocationRestricted(Max, Min);

TankVehicle.GetMainBody().m_Orientation.GetPosition().Set( Position.x,  Position.y, Position.z);

// Set Command

SetTankOrder(TankVehicle);

result = true;

}

return result;

}

如果需要的话,UpdateArenaObjects()函数会向游戏场添加更多的竞技场对象。(参见清单 10-30。)

UpdateArenaObjects()功能执行以下操作:

If there are fewer arena objects on the playfield than the minimum number, it creates a new arena object by calling AddNewArenaObject().   If there are enough arena objects on the playfield, it checks to see if the elapsed time since adding the last arena object is greater or equal to m_TimeDeltaAddArenaObject. If it is, it adds another object by calling AddNewArenaObject().  

清单 10-30。更新竞技场对象

void UpdateArenaObjects(long CurrentTime)

{

// Check to see if need to add in more Arena Objects

int NumberObjects = m_ArenaObjectsSet.NumberActiveArenaObjects();

if (NumberObjects < m_MinArenaObjectsOnPlayField)

{

// Add another object to meet minimum

boolean result = AddNewArenaObject();

if (result == true)

{

m_TimeLastArenaObjectAdded = System.currentTimeMillis();

}

}

else

{

// Check to see if enough time has elapsed to add in another object.

long ElapsedTime = CurrentTime - m_TimeLastArenaObjectAdded;

if (ElapsedTime >= m_TimeDeltaAddArenaObject)

{

// Add New Arena Object

boolean result = AddNewArenaObject();

if (result == true)

{

m_TimeLastArenaObjectAdded = System.currentTimeMillis();

}

}

}

}

如果当前坦克数量少于m_MaxTanksOnPlayField并且自从上一辆坦克被添加后经过的时间大于m_TimeDeltaAddTank,则UpdateTanks()功能通过调用AddNewTank()向游戏场添加一辆新坦克。(参见清单 10-31。)

清单 10-31。更新坦克

void UpdateTanks(long CurrentTime)

{

int NumberTanks = m_TankFleet.NumberActiveVehicles();

long ElapsedTime = CurrentTime - m_TimeLastTankOnGrid;

if ((NumberTanks < m_MaxTanksOnPlayField)&&

(ElapsedTime > m_TimeDeltaAddTank))

{

// Add New Tank

boolean result = AddNewTank();

if (result == true)

{

m_TimeLastTankOnGrid = System.currentTimeMillis();

}

}

}

如果需要的话,UpdateController()函数通过调用UpdateArenaObjects()更新游戏场上竞技场物体的数量,如果需要的话,通过调用UpdateTanks()更新游戏场上坦克物体的数量。(参见清单 10-32。)

清单 10-32。更新游戏控制器

void UpdateController(long CurrentTime)

{

UpdateArenaObjects(CurrentTime);

UpdateTanks(CurrentTime);

}

保存和加载游戏状态

为了保存游戏的状态并恢复这个状态,必须向 MainActivity 类和 MyGLRenderer 类添加新代码。

修改 MainActivity 类

对于 MainActivity 类,新代码被添加到onPause()函数中,该函数在 Android 游戏暂停时调用 MyGLRenderer 类中的SaveGameState()函数。(参见清单 10-33。)

清单 10-33。修改onPause()功能

@Override

protected void onPause()

{

super.onPause();

m_GLView.onPause();

// Save State

m_GLView.CustomGLRenderer.SaveGameState(SAVE_GAME_HANDLE);

}

修改 MyGLRenderer 类

MyGLRenderer 类必须通过添加在游戏状态中保存和加载的函数来修改。

如果游戏当前正在进行(GameState.ActiveGamePlay),则SaveGameState()功能通过保存关键游戏变量来保存状态。(参见清单 10-34。)

保存的游戏的关键元素是

Player’s score   Player’s health   The m_CanContinue variable, which is true if there is a previously saved game to load and then to continue from   Camera’s state   Arena objects   Tank objects  

清单 10-34。保存游戏状态

void SaveGameState(String Handle)

{

// Only save game state when game is active and being played not at

// menu or high score table etc.

if (m_GameState != GameState.ActiveGamePlay)

{

return;

}

// Save Player's Score

SharedPreferences settings = m_Context.getSharedPreferences(Handle, 0);

SharedPreferences.Editor editor = settings.edit();

// Player's Score

editor.putInt("Score", m_Score);

// Player's Health

editor.putInt("Health", m_Pyramid.GetObjectStats().GetHealth());

// Can Continue Game

editor.putBoolean("CanContinue", m_CanContinue);

// Commit the edits!

editor.commit();

// Camera

m_Camera.SaveCameraState("Camera");

// Arena Objects Set

m_ArenaObjectsSet.SaveSet(ARENA_OBJECTS_HANDLE);

// Tank Fleet

m_TankFleet.SaveSet(TANK_FLEET_HANDLE);

}

LoadGameState()函数加载从SaveGameState()函数保存的数据。(参见清单 10-35。)

清单 10-35。加载游戏状态

void LoadGameState(String Handle)

{

// Load game state of last game that was interrupted during play

// Restore preferences

SharedPreferences settings = m_Context.getSharedPreferences(Handle, 0);

// Load In Player Score

m_Score = settings.getInt("Score", 0);

// Load in Player's Health

int Health = settings.getInt("Health", 100);

m_Pyramid.GetObjectStats().SetHealth(Health);

// Can Continue

m_CanContinue = settings.getBoolean("CanContinue", false);

// Camera

m_Camera.LoadCameraState("Camera");

// Arena Objects Set

m_ArenaObjectsSet.LoadSet(ARENA_OBJECTS_HANDLE);

// Tank Fleet

m_TankFleet.LoadSet(TANK_FLEET_HANDLE);

}

在游戏中添加游戏状态

我们必须添加到最终游戏中的最后一个特征是游戏结束信息。我们将不得不修改 MyGLRenderer 类,以添加新的代码来处理游戏的图形和游戏逻辑。

m_GameOverBillBoard变量保存告诉玩家游戏结束的图形。

private BillBoard m_GameOverBillBoard;

m_GameOverPauseTime变量保存在用户输入被处理以继续游戏之前,在图形上显示游戏的最小时间。

private long m_GameOverPauseTime = 1000;

m_GameOverStartTime变量保存游戏结束的时间。

private long m_GameOverStartTime;

CreateGameOverBillBoard()功能创建包含游戏结束图形的游戏结束广告牌,该图形在玩家的游戏结束时显示。广告牌实际上是通过调用函数CreateInitBillBoard()创建的。(参见清单 10-36。)

清单 10-36。创建广告牌上的游戏

void CreateGameOverBillBoard(Context iContext)

{

// Put Game over Billboard in front of camera

int TextureResourceID = R.drawable.gameover;

Vector3 Position= new Vector3(0,0,0);

Vector3 Scale   = new Vector3(1, 0.5f, 0.5f);

m_GameOverBillBoard = CreateInitBillBoard(iContext,TextureResourceID, Position, Scale);

}

UpdateGameOverBillBoard()功能计算并定位摄像机前面距离摄像机DistanceToBillBoard处的游戏广告牌。(参见清单 10-37。)

清单 10-37。更新广告牌上的游戏

void UpdateGameOverBillBoard()

{

Vector3 TempVec = new Vector3(0,0,0);

float DistanceToBillBoard = 5;

TempVec.Set(m_Camera.GetOrientation().GetForwardWorldCoords().x, m_Camera.GetOrientation().GetForwardWorldCoords().y, m_Camera.GetOrientation().GetForwardWorldCoords().z);

TempVec.Multiply(DistanceToBillBoard);

Vector3 Position = Vector3.Add(m_Camera.GetOrientation().GetPosition(), TempVec);

m_GameOverBillBoard.m_Orientation.SetPosition(Position);

}

IsNewHighScore()函数返回 true,这意味着如果玩家的分数大于高分表中的最低分数或者如果玩家的分数大于零并且高分表中的前十个分数中至少有一个空白位置,那么高分表中将有一个新条目。后一种情况处理玩家的分数等于或小于表中当前的最低分数,但是在高分数表中的前十个分数中留有空白条目的情况。(参见清单 10-38。)

清单 10-38。测试玩家是否有新的高分

boolean IsNewHighScore()

{

boolean result = false;

int LowestScore = m_HighScoreTable.GetLowestScore();

int MaxScores = m_HighScoreTable.MaxNumberHighScores();

int NumberValidScores = m_HighScoreTable.NumberValidHighScores();

boolean SlotAvailable = false;

if (NumberValidScores < MaxScores)

{

SlotAvailable = true;

}

if ((m_Score > LowestScore) ||

((m_Score > 0) && SlotAvailable))

{

result = true;

}

return result;

}

SaveContinueStatus()函数保存m_CanContinue变量,如果有先前保存的游戏可以加载然后继续,则该变量为真。(参见清单 10-39。)

清单 10-39。保存继续状态

void SaveContinueStatus(String Handle)

{

SharedPreferences settings = m_Context.getSharedPreferences(Handle, 0);

SharedPreferences.Editor editor = settings.edit();

editor.putBoolean("CanContinue", m_CanContinue);

// Commit the edits!

editor.commit();

}

必须修改CheckTouch()功能以整合游戏结束功能。(参见清单 10-40。)

如果用户触摸屏幕,并且状态是游戏结束屏幕,并且从游戏结束起已经过去的时间还不大于或等于m_GameOverPauseTime,则程序执行从功能返回,而不处理用户的触摸。这确保游戏结束消息将显示至少m_GameOverPauseTime毫秒。

如果所需的时间已经过去,则调用IsNewHighScore()函数来检查玩家是否获得了必须输入到高分表中的高分。如果有新的高分,游戏状态被设置为GameState.HighScoreEntry,以指示必须显示高分进入菜单。如果没有新的高分,必须显示主菜单。

然后,相机被重置到其初始位置和旋转。将m_CanContinue变量设置为 false,表示此游戏现在结束,以后不能继续,调用SaveContinueStatus()保存变量。

清单 10-40。修改CheckTouch()功能

if (m_GameState == GameState.GameOverScreen)

{

long CurTime = System.currentTimeMillis();

long Delay = CurTime - m_GameOverStartTime;

if (Delay < m_GameOverPauseTime)

{

return;

}

// Test for High Score

if (IsNewHighScore())

{

// Go to High Score Entry Screen

m_GameState = GameState.HighScoreEntry;

}

else

{

m_GameState = GameState.MainMenu;

}

ResetCamera();

// Cannot continue further since game is now over

m_CanContinue = false;

SaveContinueStatus(MainActivity.SAVE_GAME_HANDLE);

return;

}

必须修改UpdateScene()函数来处理游戏结束状态。(参见 10-41 清单。)

如果游戏状态是游戏结束屏幕状态,UpdateScene()功能会更新游戏结束广告牌的位置,以确保其位于摄像头前方并面向摄像头。如果玩家力量金字塔的健康度小于或等于 0,UpdateScene()功能会将游戏状态设置为游戏结束屏幕状态,并将“可以继续”状态设置为 false,以指示此游戏已经结束,以后不能继续。

列出 10-41。修改UpdateScene()功能

if (m_GameState == GameState.GameOverScreen)

{

// Update Game Over Screen Here

UpdateGameOverBillBoard();

m_GameOverBillBoard.UpdateObject3d(m_Camera);

return;

}

if (m_Pyramid.GetObjectStats().GetHealth() <= 0)

{

m_GameState = GameState.GameOverScreen;

m_GameOverStartTime = System.currentTimeMillis();

// Game is over cannnot continue current game.

m_CanContinue = false;

}

必须修改RenderScene()功能,以便当游戏状态处于游戏结束屏幕状态时,游戏结束广告牌被渲染到屏幕上。(参见清单 10-42。)

清单 10-42。修改RenderScene()功能

if (m_GameState == GameState.GameOverScreen)

{

// Update Game Over Screen Here

m_GameOverBillBoard.DrawObject(m_Camera, m_PointLight);

}

动手示例:无人机网格游戏

这个动手操作的例子将演示最终的无人机网格游戏,它有一个完全可用的菜单系统,并使用本章前面讨论的类来创建和管理一组竞技场对象和坦克。此外,我还编写了控制游戏帧率的代码,使游戏以平稳恒定的帧率运行。所有这些添加和更改都在 MyGLRenderer 类中进行。

修改 MyGLRenderer 类

m_GamePlayController变量保存了对这个游戏的游戏控制器的引用。

private GamePlayController m_GamePlayController;

字符串保存了一组竞技场对象的句柄名称。

private String ARENA_OBJECTS_HANDLE = "ArenaObjectsSet";

字符串保存了坦克舰队的名字。

private String TANK_FLEET_HANDLE = "TankFleet";

变量m_ArenaObjectsSet保存了一个将在这个游戏中使用的竞技场对象集合的引用。

private ArenaObjectSet m_ArenaObjectsSet;

变量m_TankFleet保存了游戏中将要使用的坦克车队的引用。

private TankFleet m_TankFleet;

k_SecondsPerTick变量保存游戏每次滴答或更新的毫秒时间。这个变量用来帮助游戏以恒定的速率更新。

private float   k_SecondsPerTick    =       0.05f   * 1000.0f/1.0f; // milliseconds 20 frames /sec

m_ElapsedTime变量保存从游戏最后一次更新以来经过的时间。

private long   m_ElapsedTime          =     0;

m_CurrentTime变量以毫秒为单位保存当前时间。

private long  m_CurrentTime          =      0;

m_UpdateTimeCount变量用于跟踪游戏中需要更新的次数,基于自上次更新以来经过的时间。

private long  m_UpdateTimeCount      =      0;

如果帧更新时序控制相关变量已经初始化,则m_TimeInit变量为真,否则为假。

private boolean m_TimeInit           =  false;

CreateArenaObjectsSet()函数为游戏创建 ArenaObject 集合,并用两个 arena 对象填充它。通过调用AddNewArenaObject()函数添加新的竞技场对象。(参见清单 10-43。)

清单 10-43。为游戏创建 ArenaObject 集合

void CreateArenaObjectsSet(Context iContext)

{

m_ArenaObjectsSet = new ArenaObjectSet(iContext);

// Cube 1

float MaxVelocity = 0.1f;

ArenaObject3d Obj = CreateArenaObjectCube1(iContext);

Obj.SetArenaObjectID("cube1");

Obj.GetObjectStats().SetDamageValue(10);

Obj.GetObjectPhysics().GetMaxVelocity().Set(MaxVelocity, 1, MaxVelocity);

boolean result = m_ArenaObjectsSet.AddNewArenaObject(Obj);

// Cube 2

Obj = CreateArenaObjectCube2(iContext);

Obj.SetArenaObjectID("cube2");

Obj.GetObjectStats().SetDamageValue(10);

Obj.GetObjectPhysics().GetMaxVelocity().Set(MaxVelocity, 1, MaxVelocity);

result = m_ArenaObjectsSet.AddNewArenaObject(Obj);

}

CreateTankFleet()函数通过生成两种不同类型的坦克并通过调用AddNewTankVehicle()函数将它们添加到m_TankFleet数组来创建坦克舰队。(参见清单 10-44。)

清单 10-44。创建坦克车队

void CreateTankFleet(Context iContext)

{

m_TankFleet = new TankFleet(iContext);

// Tank1

Tank TankVehicle = CreateTankType1(iContext);

// Set Material

TankVehicle.GetMainBody().GetMaterial().SetEmissive(0.0f, 0.5f, 0f);

TankVehicle.GetTurret().GetMaterial().SetEmissive(0.5f, 0, 0.0f);

// Tank ID

TankVehicle.SetVehicleID("tank1");

// Set Patrol Order

int MAX_WAYPOINTS = 10;

Vector3[] WayPoints = new Vector3[MAX_WAYPOINTS];

int NumberWayPoints = GenerateTankWayPoints(WayPoints);

AIVehicleObjectsAffected ObjectsAffected = AIVehicleObjectsAffected.PrimaryWeapon;

Vector3 Target = new Vector3(0,0,0);

Object3d TargetObj = null;

int NumberRoundToFire = 2;

int FiringDelay = 5000;

VehicleCommand Command = CreatePatrolAttackTankCommand(ObjectsAffected, NumberWayPoints, WayPoints, Target,TargetObj, NumberRoundToFire,FiringDelay);

TankVehicle.GetDriver().SetOrder(Command);

boolean result = m_TankFleet.AddNewTankVehicle(TankVehicle);

// Tank 2

TankVehicle = CreateTankType2(iContext);

// Set Material

TankVehicle.GetMainBody().GetMaterial().SetEmissive(0, 0.5f, 0.5f);

TankVehicle.GetTurret().GetMaterial().SetEmissive(0.5f, 0, 0.5f);

// Tank ID

TankVehicle.SetVehicleID("tank2");

// Set Patrol Order

WayPoints = new Vector3[MAX_WAYPOINTS];

NumberWayPoints = GenerateTankWayPoints2(WayPoints);

Target = new Vector3(0,0,0);

TargetObj = null;

NumberRoundToFire = 3;

FiringDelay = 3000;

Command = CreatePatrolAttackTankCommand(ObjectsAffected, NumberWayPoints, WayPoints, Target, TargetObj, NumberRoundToFire, FiringDelay);

TankVehicle.GetDriver().SetOrder(Command);

result = m_TankFleet.AddNewTankVehicle(TankVehicle);

}

CreateTankRoute1()函数创建一个路径对象,该对象由坦克要驶向的路点组成,然后返回该对象。Route 类非常简单,为了节省空间,我决定不包含它。有关路由类的更多信息,请参考位于apress.com的源代码/下载区域中的完整源代码。(参见清单 10-45。)

清单 10-45。创建坦克路线

Route CreateTankRoute1()

{

// Around Pyramid

Route TankRoute = null;

int NumberWayPoints = 4;

Vector3[] WayPoints = new Vector3[NumberWayPoints];

WayPoints[0] = new Vector3(  7, 0, -10);

WayPoints[1] = new Vector3( -7, 0, -10);

WayPoints[2] = new Vector3( -7, 0, 5);

WayPoints[3] = new Vector3(  7, 0, 5);

TankRoute = new Route(NumberWayPoints, WayPoints);

return TankRoute;

}

CreateTankRoutes()函数创建一个路由数组,并在TankRoutes数组中返回这些路由,以及数组中路由的数量。(参见清单 10-46。)

清单 10-46。创建坦克路线列表

int CreateTankRoutes(Route[] TankRoutes)

{

int NumberRoutes = 6;

TankRoutes[0] = CreateTankRoute1();

TankRoutes[1] = CreateTankRoute2();

TankRoutes[2] = CreateTankRoute3();

TankRoutes[3] = CreateTankRoute4();

TankRoutes[4] = CreateTankRoute5();

TankRoutes[5] = CreateTankRoute6();

return NumberRoutes;

}

CreateGamePlayController()函数创建一个坦克路线数组,游戏控制器用它来分配到敌人坦克的路径,然后创建实际的游戏控制器。(参见清单 10-47。)

清单 10-47。创建游戏控制器

void CreateGamePlayController(Context iContext)

{

int MaxNumberRoutes = 10;

// Tanks

int NumberTankRoutes = 0;

Route[] TankRoutes = new Route[MaxNumberRoutes];

NumberTankRoutes = CreateTankRoutes(TankRoutes);

m_GamePlayController = new GamePlayController(iContext, m_ArenaObjectsSet, m_TankFleet, m_Grid, NumberTankRoutes, TankRoutes);

}

接下来,onDrawFrame()函数中更新和渲染游戏元素的代码被分成了UpdateScene()RenderScene()中的代码。这是实现额外的新代码所需要的,这些代码有助于以固定的帧速率和游戏速度运行游戏,我将在本节稍后讨论。

UpdateScene()函数更新游戏元素的位置、方向、状态等。一些元素,如主菜单、高分表、高分入口菜单和游戏结束图形,仅在游戏处于特定状态时更新,更新后返回,不更新其余的元素。(参见清单 10-48。)

清单 10-48。更新游戏

void UpdateScene()

{

m_Camera.UpdateCamera();

// Main Menu

if (m_GameState == GameState.MainMenu)

{

m_MainMenu.UpdateMenu(m_Camera);

return;

}

// High Score Table

if (m_GameState == GameState.HighScoreTable)

{

m_HighScoreTable.UpdateHighScoreTable(m_Camera);

return;

}

// High Score Entry

if (m_GameState == GameState.HighScoreEntry)

{

// Update HighScore Entry Table

m_HighScoreEntryMenu.UpdateHighScoreEntryMenu(m_Camera);

return;

}

// Game Over Screen

if (m_GameState == GameState.GameOverScreen)

{

// Update Game Over Screen Here

UpdateGameOverBillBoard();

m_GameOverBillBoard.UpdateObject3d(m_Camera);

return;

}

// Check if Game has ended and go to

if (m_Pyramid.GetObjectStats().GetHealth() <= 0)

{

m_GameState = GameState.GameOverScreen;

m_GameOverStartTime = System.currentTimeMillis();

// Game is over cannot continue current game.

m_CanContinue = false;

}

// Process the Collisions in the Game

ProcessCollisions();

////////////////////////// Update Objects

// Arena Objects

m_ArenaObjectsSet.UpdateArenaObjects();

// Tank Objects

m_TankFleet.UpdateTankFleet();

////////////////////////// Update and Draw Grid

UpdateGravityGrid();

// Player's Pyramid

m_Pyramid.UpdateObject3d();

// Player's Weapon

m_Weapon.UpdateWeapon();

///////////////////////// HUD

// Update HUD

UpdateHUD();

m_HUD.UpdateHUD(m_Camera);

// Update Game Play Controller

m_GamePlayController.UpdateController(System.currentTimeMillis());

}

RenderScene()函数将游戏元素渲染到屏幕上。不更新元素,只将对象绘制到屏幕上。(参见清单 10-49。)

清单 10-49。渲染游戏

void RenderScene()

{

// Main Menu

if (m_GameState == GameState.MainMenu)

{

m_MainMenu.RenderMenu(m_Camera, m_PointLight, false);

return;

}

// High Score Table

if (m_GameState == GameState.HighScoreTable)

{

m_HighScoreTable.RenderHighScoreTable(m_Camera, m_PointLight, false);

return;

}

// High Score Entry

if (m_GameState == GameState.HighScoreEntry)

{

m_HighScoreEntryMenu.RenderHighScoreEntryMenu(m_Camera, m_PointLight, false);

return;

}

// Game Over Screen

if (m_GameState == GameState.GameOverScreen)

{

// Update Game Over Screen Here

m_GameOverBillBoard.DrawObject(m_Camera, m_PointLight);

}

//////////////////////////// Draw Objects

m_ArenaObjectsSet.RenderArenaObjects(m_Camera, m_PointLight,false);

m_TankFleet.RenderTankFleet(m_Camera, m_PointLight,false);

////////////////////////// Update and Draw Grid

m_Grid.DrawGrid(m_Camera);

// Player's Pyramid

m_Pyramid.DrawObject(m_Camera, m_PointLight);

// Player's Weapon

m_Weapon.RenderWeapon(m_Camera, m_PointLight, false);

///////////////////////// HUD

// Render HUD

m_HUD.RenderHUD(m_Camera, m_PointLight);

}

CalculateFrameUpdateElapsedTime()函数计算自上一次帧更新以来经过的时间,并将该值存储在m_ElapsedTime中。(参见清单 10-50。)

清单 10-50。计算帧更新所用的时间

void CalculateFrameUpdateElapsedTime()

{

long Oldtime;

// Elapsed Time Since Last in this function

if (!m_TimeInit)

{

m_ElapsedTime = 0;

m_CurrentTime = System.currentTimeMillis();

m_TimeInit = true;

}

else

{

Oldtime = m_CurrentTime;

m_CurrentTime = System.currentTimeMillis();

m_ElapsedTime = m_CurrentTime - Oldtime;

}

}

FrameMove()函数通过调用UpdateScene()来更新游戏,如果需要的话,还会调用ProcessCameraMove()函数。该功能的目的是以恒定平滑的帧速率更新游戏,尽可能接近每k_SecondsPerTick更新一次游戏的速率。(参见清单 10-51。)

FrameMove()功能执行以下操作:

The UpdateScene() and ProcessCameraMove() functions are called only if the elapsed time since the last update is greater than k_SecondsPerTick, which is the time for one update to occur.   After UpdateScene() is called, m_UpdateTimeCount is changed to reflect that an update has happened, by subtracting k_SecondsPerTick, which is the time for a single frame update or “tick.”   If more frame updates have to occur in order to meet the goal of one game update every k_SecondsPerTick, which means that m_UpdateTimeCount ➤ k_SecondsPerTick, UpdateScene() is called repeatedly, until the elapsed time since the last game update is equal to or less than k_SecondsPerTick.  

清单 10-51。更新游戏

void FrameMove()

{

m_UpdateTimeCount += m_ElapsedTime;

if (m_UpdateTimeCount > k_SecondsPerTick)

{

while(m_UpdateTimeCount > k_SecondsPerTick)

{

// Update Camera Position

if (m_CameraMoved)

{

ProcessCameraMove();

}

// update the scene

UpdateScene();

m_UpdateTimeCount -= k_SecondsPerTick;

}

}

}

onDrawFrame()功能修改(见清单 10-52)包括以下内容:

New code for sound control is added. If m_SFXOn is true, the sound effects are turned on for the arena objects, tanks, and pyramid by calling TurnSFXOnOff(). Otherwise, the sound effects are turned off for these game elements.   The time that has elapsed since the last game update is calculated by calling the CalculateFrameUpdateElapsedTime() function.   The game is updated by calling FrameMove().   The game objects are rendered to the screen by calling RenderScene().  

清单 10-52。修改onDrawFrame()功能

public void onDrawFrame(GL10 unused)

{

GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);

// UPDATE SFX

if (m_SFXOn)

{

TurnSFXOnOff(true);

}

else

{

TurnSFXOnOff(false);

}

// Did user touch screen

if (m_ScreenTouched)

{

// Process Screen Touch

CheckTouch();

m_ScreenTouched = false;

}

CalculateFrameUpdateElapsedTime();

FrameMove();

RenderScene();

}

现在,运行并玩游戏,您应该会看到如下图所示的内容 10-1 到 10-4 。

A978-1-4302-6548-1_10_Fig4_HTML.jpg

图 10-4。

Game over screen

A978-1-4302-6548-1_10_Fig3_HTML.jpg

图 10-3。

Android arena object

A978-1-4302-6548-1_10_Fig2_HTML.jpg

图 10-2。

Two different tank types

A978-1-4302-6548-1_10_Fig1_HTML.jpg

图 10-1。

Heart arena object

摘要

在这一章中,我已经讨论了最终的无人机网格游戏。我首先讨论了管理玩家敌人所需的职业。然后,我讲述了 GamePlayController 类,它用于控制我们的敌人如何呈现的重要元素。接下来,我检查了保存和加载最终游戏状态所需的代码和函数。然后讨论了图形游戏和相关的游戏逻辑。最后,给出了一个实际操作的例子,说明了本章中给出的类、代码和概念。