作者:John Boardman
欢迎继续阅读本文第二部分内容。(
点击阅读第一部分)
*使用PlayerPrefs执行高分排行榜
*如何用文本域来执行搜集用户数据的对话框
*如何在Unity中执行作弊码
这些代码现在就在GitHub。 本文代码可能会比第1部分更多,现在载入Unity,点击一个脚本,开始吧!
使用PlayerPrefs执行高分排行榜PlayerPrefs API乍一看可能相当有限。它有10个方法,其中包括DeleteAll(), DeleteKey(), GetFloat(), GetInt(), GetString(), HasKey(), Save(),SetFloat(), SetInt()和SetString()。
有一个显眼的遗漏是取回所有key的方法。对我来说,这就好比去银行存钱,之后回来取款时就要报出票据的序列号。
所以如果找不到PlayerPrefs中的东西,我们如何存储高分?我们事先并不知道用户名称……光有分数根本没用啊。有一个线索就是HasKey()方法。我们可以询问对象它是否含有一个key。所以,如果我们遇到了自己已知的key,我们就可以用来保存和载入高分。让我们看看如何执行。
我决定使用PlayerData这个前缀作为key的已知部分,然后为其附加一个索引以便创造一个独特的key。在scriptSceneManager,我定义了在PlayerPrefs中访问key时的静态字符串。这可以避免程序员因输入错误的key名称而抓狂,并将所有的key置于同一地点,以便追踪被存储的内容。
当30秒过去之后,或者玩家已经没有命值时,就会调用 saveHighScores()。让我们浏览一下该代码中的重要部分。无论是C#还是Unity类、方法还是属性,我都将把它链接到各自的文档中。如果没有链接,这意味着它是我自己的方法之一。
首先发生的一个情况就是调用MakePlayerKey(),它会连接到用户进入CSV格式的数据,并将其以字符串形式返回。你可能会问,这难道不是PlayerData外加索引的key?是这样的,这款游戏的一个条件就是任何独特的玩家数据只会在用户玩游戏时存储一次。这是因为游戏是用来让玩家参与竞赛。所以,PlayerIndex()方法会使用“real”key)“PlayerData”外加一个索引)进行检索,试图找到用户进入的信息。如果找到了,用户仍可继续玩游戏,系统会忽略分数。因为索引0并不用于存储玩家数据,如果找到玩家,0就会返回到KeyIndex。如果没找到玩家,经过上一名玩家的索引就会返回,并创建托管高分的列表。此时就会创建一个KeyValuePair列表,以便将分数从其余数据中隔离出来,更便于排序。
// this data is how we form the key to search for the player
string playerData = MakePlayerKey();
int keyIndex = PlayerIndex(playerData);
if (keyIndex > 0) {
int maxPlayerIndex = MaxPlayerIndex();
List<KeyValuePair> highScores = new List<KeyValuePair>();
现在我们不只要载入前10名,尽管列表能显示的就是这10个名次。另一个条件就是存储所有玩过游戏的用户数据。要知道,这可是一种营销机会!别担心,keyhole并不打算出卖用户数据。所以,我们不但要载入前10名,还要载入1-maxPLayerIndex. 并将其置于列表之上。因为分数是居于每段数据末尾的值,因此我们可以在KeyValuePair.中抓取和单独使用。你可能会说,这里看不到排序。那是因为列表是以排序方式存储,所以不会出现完整的排序。
// read in scores & names
for (int i = 1; i <= maxPlayerIndex; i++) {
string currentData = PlayerPrefs.GetString(PREF_PLAYER_DATA + i);
if (currentData.Length > 0) { int currentScore =
int.Parse(currentData.Substring(currentData.LastIndexOf(“,”) + 1));
KeyValuePair highScore = new KeyValuePair(currentScore, currentData);
highScores.Add(highScore);
}
}
分数已经显示在列表,但还是要添加新的分数。这只需在列表上进行简单的线性检索,将当前分数与列表上的分数进行对比即可。首个获得某个分数的玩家排名应该高于列表上其他获得相同分数的玩家,排名只选取更高分数。这样我们就能按顺序存储了相同的分数,如果列表上的分数太低,就将其添加到列表末尾。
// add current score in sorted position
playerData += “,” + score; KeyValuePair newScore = new
KeyValuePair(score, playerData);
bool playerInserted = false;
for (int i = 0; i < highScores.Count; i++) {
if (score > highScores[i].Key) {
highScores.Insert(i, newScore);
playerInserted = true;
break;
}
}
if (!playerInserted) {
highScores.Add(newScore);
}
现在已经创建了新列表,列表可用新数据(包括新分数)进行重写。这会转变为在列表进行标准的0-Count重复循环。如果列表变化了,首选项仍会保持。因为Unity支持许多平台,它会关注每个平台所谓的“持续” 标准。我们所需知道的是它是否可行。如果初始检索找到了玩家,就要设置另一个临时首选项指出这一事实。这可以用于显示高分之时。最后,要载入关卡以显示高分。
// write out new scores including new player
for (int i = 0; i < highScores.Count; i++) {
PlayerPrefs.SetString(PREF_PLAYER_DATA + (i + 1), highScores[i].Value);
}
PlayerPrefs.Save();
} else {
PlayerPrefs.SetString(PREF_DOES_PLAYER_EXIST, “TRUE”);
}
Application.LoadLevel(“sceneScreenWin”);
}
我们可以在sceneScreenWin脚本中看到显示的高分。这里我要简单陈述下这个画面的设置。
首先,我们要检索标志以便查看是否存在该用户。无论结果如何,我们都要一直使用PREF_SCORE临时首选项来呈现分数。我们可以从scriptScreenGetPlayerInfo脚本中得知这些首选项是临时性的,每次游戏开始时都会删除key。我们会在之后的部分讲到这个画面。如果玩家已经存在,显示他们刚获得分数的画面并不会影响到高分。
bool doesPlayerExist = layerPrefs.HasKey(scriptSceneManager.PREF_DOES_PLAYER_EXIST);
float y = 0;
GUI.Label(new Rect(60.0f, y, 290.0f, 50.0f), “Score: ” + PlayerPrefs.GetInt(scriptSceneManager.PREF_SCORE));
if (doesPlayerExist) {
y += 30.0f;
GUI.Label(new Rect(60.0f, y, 290.0f, 50.0f), “Player found! Score not recorded!”);
}
现在让我们跳到分数环节。前10名的每个数据都会被复原。因为10名玩家可能还没有玩过游戏,因此查看数据是否存在是避免执行时间脚本错误的一个关键。如果数据存在,被存储的CSV数据就会被解析成各个不同部分,然后以一个格式化的列表呈现。我没有时间去想如何使用等宽字体或其他很棒的效果来呈现数据,有这些基本的可行元素就可以了。
for (int i = 1; i <= 10; i++) {
y += 20.0f;
string currentData = PlayerPrefs.GetString(scriptSceneManager.PREF_PLAYER_DATA + i);
int score = 0;
string firstName = “”;
string lastName = “”;
if (currentData.Length > 0) {
score = int.Parse(currentData.Substring(currentData.LastIndexOf(“,”) + 1));
int index = currentData.IndexOf(“,”);
firstName = currentData.Substring(0, index);
int index2 =currentData.IndexOf(“,”, index + 1);
lastName = currentData.Substring(index + 1, index2 – index – 1);
}
GUI.Label(new Rect(60.0f, y, 290.0f, y + 20.0f), string.Format(“{0,2}. {1,10} : {2}”, i, score, firstName + ” ” + lastName)); }
KeyShotHighScores
另一个显示玩家数据的画面是scriptScreenShowData,这可以通过使用我们之后将描述的代码来实现。
如何用文本域来执行搜集用户数据的对话框我们已经说过了高分列表,那么这些数据是如何进入的呢?答案就是Unity有一个丰富的用户界面控制集合。我只使用了一些,没有进行验证,因为这只是一款用于一个会议的简单游戏。如果我是为发行一款游戏而编 码,一定会进行完整的检验。
MonoBehavior是一个Unity类,是每个Unity脚本的基本类。它提供了丰富的功能,并且易于扩展。在JavaScript,C#类会从MonoBehavior自动生成扩展——但在C#编码时,程序员必须明确从中扩展。
OnGUI()这个Unity方法会在每一帧调用。只要在这个方法中添加一个print语句然后运行画面即可证明这一点。这也正是firstName, lastName, phone和email fields在类级中而非在方法中定义的原因,因为我们希望它们进行更新,而不是在每一帧中重新初始化。
public class scriptScreenGetPlayerInfo : MonoBehaviour {
public float buttonWidth = 90.0f;
public float buttonHeight = 40.0f;
string firstName = “”;
string lastName = “”;
string phone = “”;
string email = “”;
正如之前所言,调用Start() 时会移除临时首选项。每次加载一个画面时Unity都会调用Start()。
现在,要使用GUI.TextField() API来搜集用户数据。要定义一个Rect给屏幕一个域尺寸,将临近呈现的数据,以及屏幕能够接受的最大字符数。由用户输入的数据将被返回。以下是一个域定义,以及在其左边呈 现的标签:
float y = 30.0f;
GUI.Label(new Rect(10.0f, y, 80.0f, 40.0f), “First Name”); firstName =
GUI.TextField(new Rect(90.0f, y, 200.0f, 30.0f), firstName, 40);
呈现数据域之后,现在要做的就是进行收集。用户点击“Save Info”按钮时即可执行。当用户点击按钮时,GUI.Button() 返回为真。此时域会在首选项中保存,并加载下一个画面。
if (GUI.Button(new Rect(90.0f, y, buttonWidth, buttonHeight), “Save Info”)) {
// here is where you would validate data if it is required PlayerPrefs.SetString(scriptSceneManager.PREF_PLAYER_FIRST_NAME, firstName); PlayerPrefs.SetString
(scriptSceneManager.PREF_PLAYER_LAST_NAME, lastName); PlayerPrefs.SetString(scriptSceneManager.PREF_PLAYER_PHONE, phone); PlayerPrefs.SetString(scriptSceneManager.PREF_PLAYER_EMAIL,
email); Application.LoadLevel(“sceneScreenLoad”);
}
KeyShot_GatherInfo
游戏结束时,字段会在临时首选项域中等待。
如何在Unity中执行作弊码这对我来说很棘手。我希望用一种方法找开管理函数,以免用户在玩游戏时用很短的时间就想出方法。《KeyShot》有两个管理条件:
1.要在玩家进入游戏之前重置高分,以便在“真正”的游戏开始之前进行检测。
2.在会议结束之后,要能够呈现所有由用户输入的数据。另一个方法就是执行一个远程调用向一个服务器发送数据,但我没有时间来设置这个操作。因为数据是独立于游戏进行存储,我可以在游戏结束后一直添加数据。
执行作弊码的关键在于Unity UI classInput中的inputString域。这个域会托管施加于当前帧的key,即使屏幕上没有任何输入域。这个API的文档尚不明确这一点,并且看似会让用户输入的任何文本处于该域,但事实并非如此。该域会在每一帧重置。所以,如果一个作弊码超过1个字符,那就需要执行更多操作以令其可用。
每次调用Update() ,inputString中的值都会进行检查。如果这个值存在,它就会串连到隐藏域(游戏邦注:这是一个类变量),这样它就会存在于到达Update()的调用之间。现在可以检查隐藏域,看看是否已输入字符串“Key”。 要使用Contains()方法,这样就可以在作弊码之前输入key令其仍然可行。如果超过10个字符在没有key的情况下输入,隐藏域就会重置以免它变得过长。注意,如果k或者e是11个字符,这可以在一开始就避免作 弊码生效。这种情况发生的概率很低,但还是有可能发生。通过检查其他字符串,任何数量的作弊码都可以用这种方法来检查。
如果存在key,numberOfButtons域增加到6,就表明管理画面目前处于活跃状态。
// Update is called once per frame
void Update ()
{ string input = Input.inputString;
if (input.Length > 0) {
hidden += input; }
if (hidden.Contains(“key”))
{ numberOfButtons = 6;
} else if (hidden.Length > 10) {
hidden = “”;
}
}
如果numberOfButtons是6,那么“Show Data”和“Clear Data”按钮也会显示并且处于可用状态。
if (numberOfButtons == 6) {
y += buttonHeight + 10.0f;
if (GUI.Button(Rect(10.0f, y, buttonWidth, buttonHeight), “Show Data”)) { Application.LoadLevel(“sceneScreenShowData”);
}
y += buttonHeight + 10.0f;
if (GUI.Button(Rect(10.0f, y, buttonWidth, buttonHeight), “Clear Data”)) { PlayerPrefs.DeleteAll();
}
}
KeyShotAllData
以下是带有一个分数的“Show Data”画面。虽然只有基础元素,但已经满足条件了。