搜寻Wumpus或体验编写经典的Android游戏

图片

您听说过吸血鬼吗? 不管答案如何-欢迎来到他的任期!

在本文中,我想告诉您我为Android创建游戏的故事。 根据读者的能力,我传达的经验,思想和决定或多或少会有用。 但是,我希望至少我的故事会有趣。

目录内容


1. 简介
2. 资金选择
3. 项目构想
4. 我给一只乌贼喝
5. 基础知识-项目结构
6. 吸血鬼迷宫的产生并与之合作
7. 存储游戏消息及其输出给玩家
8. 第一个结果
9. 即使是小游戏也值得历史。
10. 吸血鬼转化和最终结果
11. 结论

1.简介


目录内容

首先,关于您自己的几句话。 不幸的是,编程不是我的主要活动,但我很乐意将自己的空闲时间投入其中。

由于某种原因,我选择了创建手机游戏的方式。 我以前的移动设备项目是在Qt环境中与QML和C ++语言一起创建的。 我从这三人中获得了极大的乐趣,但是,在分析自己的想法时,我意识到,将来以我所知道的方法解决某些问题将花费太多的时间和精力。 因此,考虑下一个项目,我决定寻找新的,更合适的开发工具并从中获得经验。

2.资金选择


目录内容

以前,我只关注Android,在新项目中,我决定专注于此OS,了解其原生的Android Studio,为自己尝试新的Java(并且在将来,如果您喜欢它,也希望Kotlin)。

我以前从未使用过AS或Java,并且面对许多新任务需要面对巨大的工作,因此我随时准备着手进行战斗,只是想出一个项目而已。

3.项目构想


目录内容

众所周知,最重要的是,培训是针对实际任务进行的,尤其是那些有趣的任务。 对我来说,这样的培训项目应该成为一款游戏,为此我提出了许多要求:

  1. 重播价值。
  2. 游戏机制的简单性。
  3. 最少使用图形。
  4. 游戏玩法应迫使玩家思考。
  5. 游戏聚会应该不长。
  6. 快速实施项目(2个月)。
  7. UI的简单性和易用性。

整理了许多选择并客观地评估了我的长处之后,我想起了无论开始时花了多少时间和资源,实际上都要花很多时间,我得出的结论是,最好将经过测试的经典作为基础而不是发明自己的东西。 我绝对不想制造条件蛇,因此我开始研究旧游戏。 于是我发现了好奇的亨特·沃普斯。

4.我给一只乌贼喝


目录内容
图片

Vampus Hunting是由Gregory Yob于1972年发明的经典文字游戏。同年,他在杂志文章中对这款游戏进行了描述,并提供了源代码。

游戏的本质是玩家研究十二面体迷宫(这是邪恶的吸血鬼的故乡),并尝试根据游戏日志中显示的指示信息猜测位于顶层房间的迷宫。 除了吸血鬼本人(发出难闻的气味)外,还有蝙蝠(听到噪音)将玩家运送到一个随机的房间,并进站(穿透),击中导致比赛结束。 游戏的目标是杀死吸血鬼,玩家有5个箭头,可以一次从1个房间飞到5个房间(玩家自己决定要进行射击的“强度”)。 因此,玩家有两个动作:从弓箭射击,进入房间。 结果将取决于运气和意识程度。

总的来说,我喜欢这种机制:它很简单,但同时又带有风险元素。 Google Play上的Vampus上没有有趣的游戏(当时Lovecraft世界中有一款新鲜的游戏,但后来我发现它是基于相同的Vampus机制制作的。但是本文是关于在Habré上创建游戏的),因此被接受以吸血鬼为基础的决定。 我设定了保留经典游戏的目标,但略作更新并添加了新功能。

5.基础知识-项目结构


目录内容

首先,我学习了经典游戏的规则并熟悉了各种Vampus实现。 然后我用游戏的逻辑(可点击)制作了一个图表:


最初,该方案很有用,因为 允许分析游戏的机制,消除缺陷并自行制造东西。 而且,当我后来与艺术家一起解释游戏的本质时,这种方案派上了用场。

我将项目分为四个部分,每个部分都解决了不同的任务。 我只会给他们一些。

1.游戏机制

  • 有关地牢的信息将以什么形式存储?
  • 您需要什么类型和多少个变量?
  • 编写算法:组成地牢,吊杆飞行,检查射击结果,使用错误选择的房间顺序进行吊杆飞行,移动玩家,移动时检查房间,用蝙蝠移动玩家等。
  • 如何以什么顺序在游戏日志中显示信息?
  • 以什么顺序检查房间?

2.用户界面

  • 申请中应开展哪些活动? 它们应该看起来如何,应该包含哪些元素?
  • 可以在设置中更改哪些参数?
  • 我需要游戏中的图像吗?
  • 通常,应用程序的样式是什么(颜色,心情,消息样式)?
  • 使用什么字体?

3.其他

  • 连接到Google Play服务
  • 处理XML文件
  • 使用什么字体?

4.编写游戏文字

  • 游戏讯息
  • 规则
  • Google Play游戏说明

描述所有内容是没有必要的,而不是没有可能的,因此,我将仅关注某些点,然后说明获得的第一个结果。

6.吸血鬼迷宫的产生并与之合作


目录内容

吸血鬼迷宫是十二面体,可以表示为尺寸为20x20的矩阵G。 我们将顶点从0编号为19。如果矩阵元素为1,则在顶点(房间)之间有一个通道,否则为否。

我们还引入了一个20×3 N的矩阵,该矩阵存储每个房间的邻居索引。 此矩阵将加快使用G的速度


矩阵GN被缝在游戏代码中,并且不会改变(当然,存储G是不必要的,因为您只能使用N ,但现在就这样吧)。 这些是一劳永逸的十二面体的“真实”顶点指数。 对于玩家而言,将“游戏”索引形成维度20的向量V ,这是“真实”索引的一种掩饰,如下所示:

//  ""    for (byte i = 0; i < 20; i++) { V[i] = i; } //    ""  for (int i = 0; i < 20; i++) { int tmpRand = random.nextInt(20); byte tmpVar = V[i]; V[i] = V[tmpRand]; V[tmpRand] = tmpVar; } 

因此,获得以下图片:


每个新游戏都会形成向量V,从而为玩家提供了一个“新”地牢。

为了建立“真实”和“游戏”房间索引之间的对应关系,使用了indByNmb转换方法:

 public byte indByNmb(int room) { byte ind = -1; for (byte i = 0; i < V.length; i++) { if (V[i] == room) { ind = i; break; } } return ind; } 

在输入处, indByNmb方法获取房间房间的“游戏”索引,在输出处,其给出“ true” ind

生成了地牢的结构后,我们放置:2组蝙蝠,2个孔,一个吸血鬼和一个玩家:

 byte[] randomRooms = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; for (int i = 0; i < 20; i++) { int tmpRand = random.nextInt(20); byte tmpVar = randomRooms[i]; randomRooms[i] = randomRooms[tmpRand]; randomRooms[tmpRand] = tmpVar; } P = randomRooms[0]; W = randomRooms[1]; Pits[0] = randomRooms[2]; Pits[1] = randomRooms[3]; Bats[0] = randomRooms[4]; Bats[1] = randomRooms[5]; 

这样的放置确保了一个房间中不会有两个居民,而且玩家从一开始就不会被扔到房间里去吸血鬼。

完整的地牢生成如下:

代号
 byte[] V = new byte[20]; // ""  int P; //  , byte W; //   byte[] Bats = new byte[2]; //    , byte[] Pits = new byte[2]; //    public void generateDungeons() { resetVars(); //      for (int i = 0; i < 20; i++) { int tmpRand = random.nextInt(20); byte tmpVar = V[i]; V[i] = V[tmpRand]; V[tmpRand] = tmpVar; } byte[] randomRooms = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; for (int i = 0; i < 20; i++) { int tmpRand = random.nextInt(20); byte tmpVar = randomRooms[i]; randomRooms[i] = randomRooms[tmpRand]; randomRooms[tmpRand] = tmpVar; } P = randomRooms[0]; W = randomRooms[1]; Pits[0] = randomRooms[2]; Pits[1] = randomRooms[3]; Bats[0] = randomRooms[4]; Bats[1] = randomRooms[5]; } 


现在,您可以实现游戏机制的所有算法。 因此,例如,当玩家使用currentRoom索引进入房间时,就会发生相邻房间的输出:

 public void printNearRooms(byte currentRoom) { byte ind = indByNmb(currentRoom); appendText(V[N[ind][0]], V[N[ind][1]], V[N[ind][2]]); } 

在输入处, printNearRooms方法获取currentRoom房间的当前“游戏”索引。

考虑示例的机制。 让玩家移到新房间,然后出现一条消息:“现在我在房间8中”。 数字8是游戏指数。 房间的“真实”索引为6(请参见上面的屏幕截图)。 该代码专门用于“ true”索引,即 6.对于6,确定“真实”邻居的指数:2、5、7。“游戏”的指数分别是:10、0、7。我们在日志中显示玩家:“我可以去10、0、7房间”。

因此,通过将向量V形成每个新游戏并使用迷宫图的“真”和“游戏”索引,可以创建每个游戏都是唯一的事实的外观。

多亏了appendText函数消息以指定的时间间隔显示。 我们待会儿见。

这是检查房间是否靠近老鼠的示例:

 public boolean isBatsNear() { boolean answer = false; byte indP = indByNmb(P); for (int i = 0; i < 3; i++) { if ((V[N[indP][i]] == Bats[0]) || (V[N[indP][i]] == Bats[1])) { answer = true; break; } } return answer; } 


7.存储游戏消息及其输出给玩家


目录内容

经典游戏是玩家与游戏机之间互动的过程,即 游戏纯粹是文字游戏。 我想保存此功能,以便将所有游戏消息分解成块,例如:

  • 新游戏的第一条消息。
  • 玩家移动时的消息。
  • 信息在坑附近。
  • 移动吸血鬼时的消息。
  • 掉进洞里的消息。

文本存储在XML文件中。 为了各种游戏玩法,每个块都有几个消息选项。

如果相邻房间之一中有孔,将显示一个消息块的示例:

 <string name="g_pitsNear_1">—  \n</string> <string name="g_pitsNear_2">—    \n</string> <string name="g_pitsNear_3">—     \n</string> <string name="g_pitsNear_4">—  , \n</string> <string name="g_pitsNear_5">—   \n</string> <string-array name="g_pitsNear"> <item>@string/g_pitsNear_1</item> <item>@string/g_pitsNear_2</item> <item>@string/g_pitsNear_3</item> <item>@string/g_pitsNear_4</item> <item>@string/g_pitsNear_5</item> </string-array> 

通过这种结构,您可以轻松地编辑现有消息或添加新消息,而不会影响Java代码。

例如,如果在检查房间时,前面显示的isBatsNear方法返回true ,那么我们从XML获取所需的消息块,然后随机将其中一个作为appendText的参数:

 if (isBatsNear()) { String[] g_batsNear = getResources().getStringArray(R.array.g_batsNear); appendText(g_batsNear[random.nextInt(g_batsNear.length)]); } 

游戏消息被输出到控制台,该控制台是一个TextView对象。 让我们看一下appendText方法。

 public void appendText(final String str) { msgBuffer.add(str); if (!isTimerGameMsgWork) { mTimerGameMsg.run(); isTimerGameMsgWork = true; } } 

当需要显示游戏消息时,将调用appendText方法,该方法将其作为参数。 在方法本身中,首先将一行添加到msgBuffer缓冲区。 接下来是检查布尔变量isTimerGameMsgWork 。 当mTimerGameMsg计时器启动时,它接受true 。 当此计时器工作时,然后根据FIFO原则(先进先出)从msgBuffer缓冲区中接收消息,并以指定的间隔mIntervalGameMsg接收消息,并将消息添加到游戏日志-txtViewGameLog中

完整的消息输出代码:

代号
 ArrayList<String> msgBuffer = new ArrayList<>(); Handler mHandlerGameMsg; private int mIntervalGameMsg = 1000; boolean isTimerGameMsgWork = false; public void appendText(final String str) { msgBuffer.add(str); if (!isTimerGameMsgWork) { mTimerGameMsg.run(); isTimerGameMsgWork = true; } } final Runnable mTimerGameMsg = new Runnable() { @Override public void run() { if (msgBuffer.size() == 0) { mHandlerGameMsg.removeCallbacks(mTimerGameMsg); isTimerGameMsgWork = false; } else { txtViewGameLog.append(msgBuffer.get(0)); msgBuffer.remove(0); mHandlerGameMsg.postDelayed(mTimerGameMsg, mIntervalGameMsg); } } }; 



8.第一个结果


目录内容

经过一个月的开发,该游戏的第一个可玩版本具有完全实现的功能。 我附上截图:

游戏第一版的屏幕截图





在显示的屏幕截图上,您可以看到:主菜单,规则窗口,设置窗口,游戏窗口。

当然,我知道结果完全没有意思。 最主要的是我获得了AS和Java的实践经验,这是第一个任务。

与经典游戏一样,玩家必须通过控制台进行交互:输入要移动的房间号或箭头飞行的路线。 在设置中,我可以更改字体大小和背景的透明度。 我假设每个游戏窗口都有自己的图片背景(为了使游戏玩法稍微多样化,哈哈!)。

接下来,我计划用艺术家绘制的图像替换现有的图像(我从互联网上不道德地拍摄)。 然后,我将游戏发布到Play市场,并放心地将其忘却,并将获得的经验应用到新项目中。 然后我无法想象吸血鬼会改变多少...

9.即使是小游戏也值得历史。


目录内容

当一个人与灵魂一起工作时,该项目只会从中受益。 我很幸运,艺术家Anastasia Frolikova竟然是这样的人。 即 她不仅画了我需要的东西,还对游戏世界产生了兴趣,并想了解它的工作原理。 突然之间,事实证明,总体上没有和平! 这个吸血鬼是谁? 玩家为什么要杀死他? 吸血鬼房间是什么样的? 依此类推,依此类推,我没有考虑过,也没有打算告诉玩家。 结果,我们同意,即使是一个看似很小的游戏也应该有自己的故事。 她出现了。

根据我们的传说,吸血鬼虽然古老,却不是一个喜欢取笑人的邪恶神话生物。 是的,他住在迷宫中,但这个迷宫不是经典的阴沉地牢,而是一堆难以想象的房子,其中包括一堆房间,其中的内容就是吸血鬼的特征。 因此,例如,在其中一个房间中有一个电影院,墙上有他最喜欢的电影的海报,在另一个房间中有他的壁橱,他在准备他的“恶作剧”。


一位来自无名猎人的玩家变成了密码学论坛的常客,他想证明吸血鬼的存在。 我们用照相机和胶卷代替了经典的弓箭,游戏的目的不是杀死Vampus,而是得到他的照片。 顺便说一句,主菜单已重做一个论坛,供人们讨论吸血鬼,玩家可以从讨论中了解它。

主菜单的第一个版本,以论坛的形式进行

至于其他方面,它们几乎保持不变:鼠标的行为方式相同,进入漏洞开始导致崩溃,相机故障和游戏结束(而不是玩家死亡)。


关于相机的另一刻。 在经典游戏中,玩家可以在1到5个房间的范围内射箭,看起来很合逻辑。 我们有一个摄像头,而不是弓形相机(一次拍摄1到3个房间的照片,但是就像打到吸血鬼的经典箭头一样工作)。 那看起来很奇怪,不是吗? 有一种想法是将摄像机范围缩小到一个房间,以便只能拍摄下一个房间,但是,这首先使游戏变得复杂,其次,当无法赢得游戏时会出现这种情况,这是错误的。 通常,这是我个人仍然困扰的时刻,但我尚未找到解决方案。

关于游戏的风格和气氛。 几乎所有有关吸血鬼的游戏都是用无聊的灰色调制作的,动作发生在地下城的黑暗地方。 因此,我们决定我们的游戏应该与此不同,并以幽默和鲜艳的色彩来完成。




10.吸血鬼转化和最终结果


目录内容

然后又有2个月的游戏工作在等我们。 由于Vampus有20个房间,每个房间都有自己的内部。 此外,绘制了成就的图标,游戏中的图标,并就总体设计和UI做出了决策。 还添加了整个游戏文本,对代码进行了补充和优化,添加了新功能(例如,出现了一个记事本,用于在游戏过程中记录信息)。 总的来说,吸血鬼发生了重大变化。

例如,房间的创建如下:

图片
有20个房间,所有房间都是唯一的,玩家在每次游戏中都会收到一个“新”迷宫。 如何将每个新的游戏图片附加到新房间? 最简单的方法是使用相同的“ true”和“ game”索引方法:

 public void changeImgOfRoom() { ImageView img = findViewById(R.id.imgRoom); int ind = indByNmb(P); String imgName = "room_" + ind; int id = getResources().getIdentifier(imgName, "drawable", this.getPackageName()); Glide.with(this) .load(id) .transition(DrawableTransitionOptions.withCrossFade()) .into(img); } 

正方形格式的图片(以减少在不同屏幕上查看时的失真)存储在名称为[room_0; 房间_1; ...,room_19]。 实际上,它们与“真实的”十二面体索引相关联,但是对于玩家来说,同一个房间的每个新游戏将具有不同的图片。 为什么需要这个? 为了让特定的游戏聚会有机会将文字信息与特定房间的图像相关联(“是的,我记得在X室,这是客厅,有一个草稿”),但并没有得出“为什么它总是在我的房间里”的信息X是同一张图片吗?” 多样化和对播放器有帮助的一切(但是,经验显示,视觉记忆并没有帮助,使用文本更有效)。

最终,我们获得了游戏的新版本。 你知道吗? 吸血鬼变得非常吸引人,最重要的是,它仍然是经典的吸血鬼,只是在一个舒适的新房子里!

游戏第二版的屏幕截图





在屏幕截图上:主菜单,规则窗口,设置窗口,游戏窗口。

在机械方面,它只是稍作修改和重命名(实际上,有什么区别:如果需要执行相同的操作,是摄像机还是弓?)。 机械方面最明显的变化是简化了玩家与游戏之间的交互过程-删除了使用键盘输入房间号的经典方法。 现在,要在房间之间切换,您需要在弹出窗口中选择3个数字中的1个,并形成拍照的“路线”,只需滚动感光鼓上的轮子即可:


在下面的视频中,您可以看到最终结果:



11.结论


目录内容

我在一个月的开发中就收到了游戏的第一版,下班后要花1-2个小时。 同时,我以前都不熟悉AS和Java。 游戏的第二版又花了两个月的时间。 因此,只有3个月的休闲时间。

当然,这种形式的游戏范围不广,因为 对于现代玩家来说,这看起来似乎并不复杂,但并不令人兴奋,但更重要的是,也许可以保持经典游戏的精神,是吗?

我对结果满意吗? 绝对可以。 我在编程和团队合作方面都积累了很多经验。 我既喜欢游戏的结果机制,也喜欢它的视觉组件。 我有什么想改变的吗? 当然,完美无极限,您可以随时添加/改进某些东西,但不能永远做到这一点!

这个游戏有趣吗? 好吧,这不是我决定的。 但是,让吸血鬼在我们为他建造的房子中以极大的爱和关怀感到舒适。

祝你成功!

谢谢您,提防吸血鬼!

PS不断出现的任务和解决它们的乐趣正是我喜欢编程的目的。 希望您能获得更多的快乐。

Source: https://habr.com/ru/post/zh-CN462943/


All Articles