基于linux的贪吃蛇游戏设计_基于Unreal4的战棋类游戏《棋行傀儡》的设计与实现...

找个地方放一下简历附件

渣毕业设计,与游戏设计关系不大,更多地是展现自己有实际游戏开发能力,了解游戏开发各方面工作,能够使用游戏引擎。

本科生毕业论文(设计)

题目 基于Unreal4的战棋类游戏《棋行傀儡》的设计与实现

院 (系) 计算机科学与技术

专 业 软件工程

班 级 f160118

学 号 f16011822

学生姓名 李正彬

定稿日期: 2020 年 04 月 15 日

目 录

第一章 简述 1

1.1 游戏市场 1

1.2 目的 1

1.3 开发目标 1

第二章 可行性分析 3

2.1 技术可行性 3

2.2 资源可行性 4

第三章 游戏策划设计 6

3.1 游戏概念设计 6

3.2 游戏策划架构 6

3.3 游戏策划案 8

3.4 游戏策划架构 8

第四章 程序实现设计与难点 14

4.1 程序模块化设计 14

4.2 功能点列表 14

4.3程序关键与难点算法 14

第五章 总结与反思 35

5.1 模块、函数独立性反思 35

5.2 三层结构反思 35

5.3 棋盘算法反思 35

5.4 方法函数与函数库反思 37

5.5 控制台反思 37

5.6总结 37

参考文献 38

致谢 39

摘 要

随着计算机科学与技术的发展,智能设备大幅度地改变了人们生活的方式。而电子游戏作为信息技术发展的衍生品,也快速代替了传统娱乐成为了广大民众的日常娱乐方式之一。电子游戏打破了传统娱乐方式的限制,融入了其他艺术形式,成为了当下的“第九艺术”。而制作一款电子游戏可以视为一项非常复杂的软件工程项目。如果想制作一款3D游戏,不仅仅要考虑游戏玩法的程序逻辑实现,还要涉及UI、模型、动画、特效等等,其中每一项难题都能扩展出一个完整的课题。因此想要以个人之力完成该项目,需要强大的制作工具的帮助。游戏引擎是游戏开发必要工具,也是游戏开发的基础,游戏引擎是多个子系统构成的一个复杂的系统工具,方便游戏开发者能更好地处理游戏制作相关的所有内容。此次毕业设计选用当下先进、开源的Unreal4游戏引擎作为游戏开发的主要环境,完成游戏策划案中所设计的所有玩法。

关键词:游戏设计,游戏制作,软件工程,Unreal4

ABSTRACT

With the development of computer science and technology, smart devices have dramatically changed the way people live. As a derivative of the development of information technology, electronic games have quickly replaced traditional entertainment and become one of the daily entertainment methods of the general public. Video games have broken through the limitations of traditional entertainment methods and incorporated other art forms, becoming the current "ninth art." And making a video game can be regarded as a very complicated software engineering project. If you want to make a 3D game, you should not only consider the program logic of gameplay, but also involve UI, models, animations, special effects, etc., each of which can expand a complete subject. Therefore, if you want to complete the project with your own strength, you need the help of powerful production tools. The game engine is a necessary tool for game development and the basis for game development. The game engine is a complex system tool composed of multiple subsystems, which facilitates game developers to better handle all content related to game production. This graduation project selects the current advanced and open source Unreal4 game engine as the main environment for game development, and completes all the gameplays designed in the game planning case.

Keywords:Game Design, Game Production, Software Engineering, Unreal4

第一章 简述

1.1 游戏市场

随着物质生活的逐渐富裕,人们对精神娱乐方面的需求也在日渐提高。游戏作为娱乐与艺术的一种形式也有着越来越高的需求与市场。但由于以下各种原因导致目前游戏市场十分畸形,游戏品质也十分低下:

  • 由于国内经济起步较晚,也造成网络娱乐发展远落后于其他国家
  • 由于国内网络行业发展飞速,网络娱乐行业缺乏成熟的法律规章管制
  • 由于网络管制的限制与国内早期恶劣的游戏市场环境,国内玩家游戏审美十分低下,较少接触到优秀的游戏产品,逆向影响了游戏制作品质
  • 由于目前游戏设计并未算入正式专业,国内游戏设计与制作水平总体十分低下
  • 游戏宣发行业严重阻碍游戏创新,加上许多资源被大型厂商垄断因此难以产出优质游戏
  • 由于畸形的游戏产业环境,一些非移动平台的主流优秀工具

因此,国内游戏市场相对国际市场而言是处于极其不健康状态:劣质的游戏、低下的玩家平均审美、极度商业化的游戏、糟糕的消费习惯、紧缺的人才以及不完善的规章制度和生产模式。因此中国的游戏市场急需优秀的人才与优秀的游戏去打破现有的恶性循环。

1.2 目的

这次的毕业设计旨在设计并且完整地制作一款小而精的游戏DEMO,有着以下几点目的:

  • 制作一款游戏需要多方面知识,而实际动手制作出来可以锻炼各方面的能力,而需求最基层的能力便是编程能力
  • 检验综合能力水平,同时找到能力的弱项与强项
  • 游戏原型的制作是个人能力最好的证明,该作品也有利于日后入行游戏行业
  • 熟悉国际公认的优秀游戏3D引擎Unreal4

1.3 开发目标

使用Ureal4引擎制作出一款3D战棋类游戏DEMO,该游戏DEMO具有以下内容:

  • 完成游戏所设计出的基本玩法
  • 在基本玩法完成后尽可能多地完成次要功能与玩法的实现
  • 实现开发者模式,作为能方便地产出更多游戏内容的工具

第二章 可行性分析

一款完整的游戏制作工程是十分庞大的,即使是2D游戏如果没有游戏引擎工具的帮助一个小型团队也很难在短期内做出一个完整的游戏。因此想要一个人在毕设限制的时间内完成一款完整的3D游戏必须需要一款强大的3D游戏引擎的帮助。

2.1 技术可行性

2.1.1 Unreal4的选择

Unreal4是由著名的游戏公司Epic所制作的一款3D游戏引擎,它是目前世面上少有的功能强大、开源免费的优秀的游戏引擎之一。Ureal4是一套完整的工具集成,提供制作游戏时所需要的绝大多数基础工具如UMG、3D笔刷、蓝图编程、动画蓝图、游戏框架、材质编辑器、粒子系统、AI控制等等。正是因为有这些强大的工具,因此才使得个人开发游戏成为可能。并且由于Epic平台致力于创建Unreal4国际化学习平台,可以在官网、Bilibili等网站找到大量值得参考的学习资源。

2.1.2 蓝图的选择

蓝图编程是Unreal4所提供的一个可视化编脚本系统,同时Unreal4也重新封装了C++以供开发者使用。此系统的基础概念是使用基于节点的界面在虚幻编辑器中创建游戏性元素。与诸多常用脚本语言相同,其用于定于引擎中的对象驱动(OO)类或对象。相较于C++它有着以下好处:

  • 学习成本低

每一个蓝图节点可以视为一个封装好的函数,各种不同的函数按照流程管理连接在一起便成为了蓝图脚本。并且蓝图中增加了相较于函数更加灵活的“事件”的概念,因此学习者往往只需要知道每个蓝图节点有何作用、如何使用便可以灵活地使用在各个地方。同时,由于可视化的原因,节点如何使用以及有何种作用往往能非常容易的测试出来。而由于目前官方重新封装过C++并且在官网上提供的API查询功能有着诸多缺陷,因此Unreal的C++的学习成本远高于蓝图的学习成本。并且由于时间上的限制,想要在相同时间内尽可能实现更多功能,也不得不使用蓝图脚本。

  • 高关联性

Unreal4的蓝图系统与大多数的Unreal4内置工具有着良好的关联性,可以轻松使用蓝图系统将各种工具产出与游戏所关联在一起。尽管开发者仍然能通过C++完成任何蓝图所能够达到的功能,但是面对这些更加专业的工具,也意味着开发者需要更多时间与精力去学习各方面的接口框架等。而蓝图系统将所有工具与引擎基层可视化地关联于一体无疑大幅度提高了开发效率。

  • 高效排错

蓝图调试是一项非常强大的功能,它允许开发者在在编辑器中运行或在编辑器中模拟模式中暂停游戏执行,并通过使用断点单步调试蓝图或关卡蓝图的任何图表。同时蓝图拥有专门的断点功能,这提供了观察变量值并检查或单步调试蓝图内的执行流程的机会。给定蓝图的所有断点都显示在调试选项卡,可以在蓝图的图表中查看已选中的断点。能够大幅度提高开发中的排错效率。

  • 快速查询未知节点

蓝图中官方封装了大量的节点,这些节点等同于C++中的一个个方法。然而,官方在蓝图系统中制作了一个非常方便的分类与搜索功能。例如开发者如果需要一些针对玩家摄像头面向、旋转等控制的方法,或是一些常用的空间计算方法,可以在相应分类中快速查找。而同时由于官方APP制作不完善的问题,这使得蓝图的易用性与易学性又远远高出了C++编程。

2.2 资源可行性

一款完整的游戏固然离不开精美的各种美术资源,流畅的动画、精美的贴图、扣人心弦的音乐、舒适的UI会最终成就一款游戏的成功。但是这些资源往往都是由团队中的美术部门所制作出来的,一个人通常很难同时兼顾设计、程序与美术。然而很多时候游戏团队为了避免花费大量人力物力制作出的游戏可玩性低于预期,往往会先制作一个游戏DEMO用于测试游戏性,游戏DEMO往往会用快速廉价的美术素材,而游戏的功能也仅制作核心玩法。这样的游戏DEMO通常成本低廉,又可以一定程度上测试游戏的可玩性等,目前被大量游戏公司纳入游戏制作流程之中。而此次毕业设计为了保证能在规定时间内做出实际可玩的项目,并且规避美术资源的大量成本,因此将会制作游戏DEMO而非一款完整的游戏项目。

  • 模型资源

模型资源是游戏的必要资源之一,3D游戏的模型大部分往往非常复杂需要专门的人员制作。由于目标是制作游戏DEMO灰盒,因此并不要求太高质量的模型,相关模型需求可以通过以下几种方法解决:

  1. 对于较为复杂的模型,通过网上寻找免费或购买素材的方法获取
  2. 对于较为简单的模型,自己使用3DSMAX制作
  3. 在Unreal4提供的官方资源包中寻找合适的素材使用
  4. 对于类似的模型需求,使用同一模型代替以节省资源成本
  • UI资源

UI资源不同于其他资源,有着自己的独特性难以通过其他渠道获得。幸运的是Unreal4有着自己的强大的UI制作工具:UMG系统。UMG不仅可以轻松地绘制UI,还能够与蓝图脚本密切关联,极其方便地编写UI逻辑。同时由于拥有一定美术基础知识,因此决定UI自行绘制。

  • 贴图资源

贴图资源与模型资源同样具有复杂性和专业技能需求,贴图一般用作于模型材质、部分UI等,因此与模型资源同样是必要资源,因此通过以下方法解决:

  1. 在Unreal提供的官方资源包中寻找合适的素材使用
  2. 在网络上寻找免费或购买资源使用
  3. 对所找的资源进行略微改动已满足相似需求
  4. 在网上寻找合适的素材通过使用Photoshop制作所需贴图
  • 动画资源

动画资源相比模型资源要更加复杂,更加难以获取。大部分3D游戏中的动画往往都是与模型绑定的骨骼想配套,因此想在网络上寻找合适的素材几乎是无法实现的。所幸官方提供的标准人物模型骨骼配有一套常用动画,同时在Unreal官方商城中也售有此套骨骼动画资源。因此希望能够在商城和官方资源包中能够找到所需资源,如没有相应需求的资源则可能会将部分动画省略处理。

  • 声音资源

一款完整优秀的游戏往往离不开优秀的音乐音效资源,然而当制作一个游戏的DEMO版本时,声音资源甚至可以省略。声音资源与其他三种美术资源相比而言更加独特,需要更加专业的技能才能够制作。因此只有时间与条件允许时才会考虑加入声音资源。

第三章 游戏策划设计

3.1 游戏概念设计

本游戏设计是基于玩法设计的一个简单的回合制战棋类游戏。游戏中玩家需要在关卡之中通过招募棋子、攻占资源点、升级基地最终达成占领地方基地以获得胜利。游戏每个回合可以恢复所有棋子的行动力与攻击,并且根据基地等级提供基础资源。游戏地图是由一个个六边形单元地形块构成,每个单元地形块有着各自的地形与高度影响着棋子的攻击范围与行动,并且需要谨慎的控制自己的资源如何使用以达到更高的收益。玩家会根据自己所构建的卡组随机招募到棋子,并且可以通过升级基地来提高每回合收益与招募到更加稀有棋子的概率。当玩家胜利时,会随机解锁新的棋子供玩家驱使。

3.2 游戏策划架构

游戏策划架构如图3-1所示

基于linux的贪吃蛇游戏设计_基于Unreal4的战棋类游戏《棋行傀儡》的设计与实现..._第1张图片

图3-1 游戏策划架构思维导图

3.3 游戏策划案

  1. 战斗系统

战斗系统是游戏单局战斗开始为起始点到单局战斗结束所涉及到的所有系统,同时也定义了游戏的核心玩法。

  1. 非战斗系统

非战斗系统包含除开发系统与单局游戏外的系统,主要是由关卡选择系统、玩家棋牌构筑系统构成

  1. 开发系统

开发系统包含制作关卡所需要的所有系统,主要包含通过柏林噪声函数随机生成地形的系统,通过UI界面自定义游戏地图预设配置的系统,地图存取系统等

3.4 游戏策划架构

  1. 战斗系统

战斗系统是游戏单局战斗开始为起始点到单局战斗结束所涉及到的所有系统,同时也定义了游戏的核心玩法

1. 机制规则

(1) 回合制

回合制是游戏中所常见的一种游戏底层机制,传统的棋类游戏都可以理解为回合制的。回合制往往不需要玩家进行实时的操作,可以将更多的精力使用在如何行动上,兼容高策略性玩法。同时,回合制也决定了最微观的游戏体验循环,每个回合即使游戏体验循环的元单元。

  • 游戏是回合制进行的,各势力轮流行动
  • 每回合会重置一些基础资源如重置棋子行动力与攻击能力,并提供一些经济资源
  1. 基础资源

基础资源是所有游戏必不可少的,资源不仅仅是游戏里的金钱等面向玩家理解的资源,每个回合、玩家的每个棋子、棋子的行动力等等都可以视为游戏资源。然而基于策划层面考虑,这里所指的基本资源即玩家所拥有,游玩时所需要高度注意的资源,也是影响玩家行动的核心。目前将资金、粮食与人口定义为核心资源。

  • 玩家每回合根据战略点等级获得基础资金、粮食收入
  • 玩家可操控的棋子数量由战略点等级和棋子人口占用决定
  • 战略点提供棋子招募池
  • 玩家每回合能够自动刷新招募池
  • 玩家可花费少量资源将棋子放置在有上限的预招募池内
  • 棋子独立行动,行动耗费行动值,每回合行动值自动恢复
  1. 资金
  • 每局开始时拥有初始资金
  • 每回合结束时根据战略点等级提供基础资金收入
  • 野外各种资源点可提供每回合资金
  • 资金用于招募棋子,棋子能力技能越强资金耗费越多
  • 资金用于升级战略点,等级越高消耗越多
  1. 粮食
  • 每局开始时拥有初始粮食
  • 每回合结束时根据战略点等级提供基础粮食收入
  • 野外各种资源点可提供每回合粮食
  • 粮食用于招募棋子,棋子基础属性越高粮食耗费越多
  1. 人口
  • 基础人口上限由战略点等级决定
  • 招募棋子会占用人口
  1. 棋子

棋子是游戏核心资源,此游戏中棋子拥有复杂的属性,为了体现出各种不同棋子之间的差异。同时这些不同的属性也会作用不同的效果,以保证各具特色。

  1. ID

每种类型的棋子都有各自的ID,决定了该棋子是哪种棋子。ID类似于主键,是棋子类型的唯一标识

  1. 名称

每个ID的棋子拥有一个自己的名称

  1. 类型

目前棋子共分为三种类型,每种类型都有自己的特性,如表3-1所示

表3-1 棋子类型表

类型特性个体基础属性值较低,费用花销较低,且人口占用较低复数基础属性值偏高,费用花销较高,且人口占用较高建筑无法移动,血量为1,其他生命值均为护盾
  1. 血量

每个棋子拥有自己的血量,当血量为0时判定棋子死亡

  1. 护盾

护盾是位于血量之上的存在,当棋子受到伤害时,会优先扣除护盾值。当护盾存在时,会根据当前护盾值/护盾最大值百分比与棋子的防御属性共同决定最终的伤害减免。当护盾值为0时,棋子不再享受伤害减免效果。

  1. 攻击

攻击主要由物理攻击与魔法攻击值组成。物理攻击会根据物理防御计算伤害减免,魔法攻击会根据魔法防御计算伤害减免,最终计算伤害的总和对棋子造成伤害。

棋子也拥有攻击范围属性,决定了棋子能够攻击的范围。即使是相同的攻击范围,不同的攻击范围类型也会使得最终攻击范围有所不同。同时,棋子所在位置与目标位置之间的障碍也会影响是否能够实际攻击到目标单元格。

  1. 防御

防御主要由魔法防御、物理防御与闪避值决定。物理防御与魔法防御往往根据受击目标的护盾百分比来共同决定最终防御值,再由伤害计算公式来决定最终受到的伤害值

闪避值则为一个百分比数值,棋子每次收到攻击时会先进行一个闪避判定,如果判定成功则会避免掉这次攻击事件

  1. 行动力

不同的棋子拥有不同的行动力,行动力在棋子行动时消耗。如果行动力不足则无法移动,棋子的行动力会在每个回合开始时恢复至最大行动值

  1. 体型

棋子拥有自己的体型,建筑棋子往往会占用更多的的单元格,同时也会匹配模型的大小与位置

  1. 资源耗费

招募棋子时会消耗基础资源,如资金与粮食,同时会占用人口,如果任意一项资源无法满足招募条件则无法招募

  1. 稀有度

每个棋子拥有自己的稀有度,稀有度决定了招募概率,并且每套卡组对同一稀有度的棋子有最小数量要求限制

表3-2 棋子稀有度表

CommonUncommonRareEpicLegendary
  1. 战略点
  • 每局游戏开始时玩家拥有战略点
  • 通常战略点失守时游戏失败
  • 通常占领敌方战略点可获得胜利
  • 战略点提供棋子招募功能
    • 每次招募会招募到随机稀有度的棋子
    • 招募稀有度概率由战略点等级决定
    • 招募到的棋子会随机布置在战略点周围
    • 若战略点周围没有可放置棋子的单元格则无法招募
  1. 等级
  • 战略点初始等级由关卡定义,默认为1级
  • 战略点可通过花费资金、粮食来进行升级
  1. 资金产出
  • 战略点每回合根据战略点等级提供资金
  1. 粮食产出
  • 战略点每回合根据战略点等级提供粮食
  1. 人口上限
  • 战略点等级决定人口基础上限
  1. 胜负条件
  • 胜利条件由关卡模式定义
  • 胜利条件通常为占领敌方战略点
  • 失败条件通常为超过回合限制
  1. 棋盘

游戏的棋盘有一个个单元格构成,需要注意的是,游戏的棋盘并非传统的四边形而是六边形棋盘,这会使得玩家获得截然不同的游戏体验与更多可能性的游戏性,但同时六边形棋盘会面临着更高难度的实现难度。

(1) 天然地形

游戏的地形能够展现丰富的地貌,同时会实际地影响棋子的行动规划。目前,不同地形往往会消耗不同的移动值。

表3-3 地形移动损耗表

地形消耗移动值Null1Grass5Soil3Stone6Sand4Woods8Water-
  1. 建筑

建筑分为战略点建筑与资源点建筑,战略点是决定玩家胜负的核心,也是玩家的基础资源、经济来源、招募点等。而资源点建筑则是需要玩家设法攻占的资源地,资源建筑能每回合稳定地为玩家提供资源。

表3-4 建筑功能表

建筑名功能战略点决定胜负,提供招募功能,每回合提供资金、粮食并决定人口上限,可升级小型农场每回合提供额外少量粮食中型农场每回合提供额外中量粮食大型农场每回合提供额外大量粮食小型矿场每回合提供额外少量资金中型矿场每回合提供额外中量资金大型矿场每回合提供额外大量资金
  1. 高度

每个单元格都拥有自己的高度值,不同高度值结合在一起产生了丰富的地貌。与地形相同,移动时的地形高度差也会对棋子的移动力产生不同的影响,同时不同的高度差也会决定是否能够攻击到目标单元格

表3-5 高度差移动损耗表

高度差移动值耗费00-1+31-2-2+82+4其他∞
  1. 规模

不同的关卡拥有不同的长度与宽度,具体由关卡创建时的输入决定。

  • 具体规模由关卡模式决定
  1. 势力

游戏目前分为两个对立的势力,玩家所操作的势力将与另一方完全敌对

(1) 玩家

        • 玩家所操控的势力
  1. 敌方
  • 与玩家与盟友对立,通常是玩家需要战胜的目标势力
  1. 非战斗系统

非战斗系统包含除战斗系统与开发系统外的系统,包含游戏主菜单等内容。提供基本的开始游戏、选择关卡、构筑卡组、进入开发者模式与退出游戏等功能

  1. 关卡系统

关卡系统可以使玩家选择关卡进行游戏。每个关卡有着各自独立的地形、高度、敌人预设、建筑预设、回合限制等。

关卡系统与开发系统中的关卡生成密切相关。

  1. 构筑系统

游戏构筑系统可以帮助玩家构筑一套自定义的卡组,所有的玩家可使用卡牌被分为已解锁与未解锁两种,玩家会在游戏胜利后逐渐解锁所有卡牌。

  • 玩家可以选择性地将已经解锁的卡牌加入牌组
  • 玩家招募时只会招募到牌组中的卡牌
  • 构筑时每种稀有度卡牌有最小数量限制
  • 玩家在胜利时会随机解锁两张未解锁的卡牌
  1. 开发系统

开发系统包含随机地形生成工具,关卡存读工具,地图编辑工具以及玩家棋子配置等功能。这些开发工具能够保证游戏能够方便地持续生产游戏内容,同时为游戏的修改、配置做基础。由于这是一个3D游戏,游戏有着接近真实的地图以及大量的预设配置,地形单元格往往多达数百个之多,并且常常需要手动配置地形与部分高地。因此,没有特别开发工具的帮助完成开发是近乎不可能的事情,也不利于游戏内容的填充。

  1. 随机地形生成

随机地形的生成是游戏的基础,也是游戏的特色之一。在开发系统中,游戏将通过柏林噪声函数来生成随机的,近似真实的,连续的地形数据矩阵。然后再通过一系列算法生成最终的游戏地图。开发者可以随时修改输入参数以生成各个独特的地形,并且通过编辑并保存所需要的地形数据。

  1. 关卡存取

每个关卡是各不相同的,同时也要对应不同的存读数据。因此只要游戏设计关卡系统往往都需要预先开发一套专门的关卡存读系统。这个系统工具同样也会在日后的开发过程中节省大量的时间,并且可以将其中一部分作为游戏内的读取关卡系统直接使用。

  1. 地图编辑

如果想纯粹地靠随机算法生成所有的关卡内容是非常不切实际的事情。这需要非常多的各种专业算法融会贯通,经过大量的测试调整与开发才能够为自己的游戏量身定制一套完整的随机生成系统。而对于游戏DEMO而言,能够直观并且便捷地手动调整出开发者想要的效果便是关键。因此地图编辑系统也是几个不可或缺的核心开发系统之一。

  1. 玩家棋子配置

玩家棋子配置系统本质上与地图编辑并无需别,而在游戏里由于棋子与地图的编辑算法与数据结构上的区别,因此可以是作为一个单独的模块进行开发,并且棋子的配置是建立于地图存在基础之上的。该模块同样是不可缺少的核心开发系统模块之一。

第四章 程序实现设计与难点

  1. 程序模块化设计

程序模块化架构如图4-1所示

基于linux的贪吃蛇游戏设计_基于Unreal4的战棋类游戏《棋行傀儡》的设计与实现..._第2张图片

图4-1 程序模块化架构思维导图

  1. 功能点列表

功能点列表是游戏开发中一种敏捷的开发方法。由于游戏制作工程量大,需求修改频繁,因此游戏开发时往往以优先开发重要部分为目标,然后依次按照评估的优先级进行开发,同时评测核心部分的玩法功能等。这种敏捷开发方法尤其适合人力不足时使用,因此本次毕业设计中也采取该种方法进行测试。以下为功能点列表(详细优先级列表参照 《行棋傀儡》功能点列表.xlsx)

表4-1功能点列表简表

编号内容1WS进行水平面竖直轴移动2AD进行水平面横向轴移动3当面向下方时,前后滚轮进行世界z轴移动4右键时鼠标任意方向进行镜头转向5BeginPlay时对玩家控制视角坐标置位默认位置6设定速率变量以随时调节镜头移动速度7按U键进入UI模式8根据x逻辑坐标计算x实际坐标9根据y逻辑坐标计算y实际坐标10根据逻辑高度h计算实际高度值以及棋子着点11根据地形枚举值设定顶端块材质12清除场景中所有地形块Actor13通过一系列参数的设置与柏林噪声函数为不同逻辑坐标地形计算随机高度值14为生成参数绘制UI面板实时调节15重置内置随机起始坐标16BeginPlay时生成UI控制界面17控制面板UI界面搭建18生成UI界面,初始化设置,读取枚举选项,输入数据限制19通过UI输入数据改变地图生成控制模块中的各个参数与事件20通过快捷键切换鼠标/自由视角模式21通过JSON结构存储生成的地图信息22将JSON存储至指定目录下23通过UI界面设置文件名与保存按钮24将指定目录文件名的JSON文件进行读取25解构读取的JSON数据生成地图26将指定读取文件名,读取按钮添加至UI界面中27鼠标点击选中单个actor28鼠标框选时绘制提示框29鼠标框选多个actor30被选中的actor拥有描边特效31通过键盘+、-改变所选地形高度32通过面板输入选择改变地所选形块地形33通过面板选择放置预设棋子于选择地形块上34通过面板清除选择地形块的棋子35根据选择关卡加载关卡地图信息并生成地形36根据选择关卡加载地图关卡信息并生成预设棋子37将棋子与地形信息存至MapController中38玩家镜头设置39鼠标位于窗口四边时镜头固定高度平面移动40鼠标所指向的棋子或地形框会高亮显示41鼠标左键选取所指向的地形或棋子42绘制地形面板信息UI43绘制棋子面板信息UI44若选中地形则右上角跳出地形显示面板45实现棋子体型算法规则46若选中棋子则计算棋子的体型范围47显示棋子的体型范围48实现棋子移动算法规则49若选中棋子则根据当前行动值与移动算法规则计算可移动范围50显示棋子的可移动范围51实现棋子攻击范围算法规则52若选中棋子则根据攻击算法规则计算可攻击范围53显示棋子的可攻击范围54玩家按住右键可转动视角55玩家按Home键可使玩家视角回归基地56玩家左键其他位置取消选择57当玩家点击移动范围时显示移动路径并等待确认移动58玩家再次确认移动根据路径进行移动指令59棋子每格移动给予一定延迟60棋子攻击范围内存在地方单位则处于可攻击状态并特殊标识61左键处于可攻击状态棋子或地形则执行棋子攻击指令62棋子根据双方属性造成伤害63攻击方棋子处于不可攻击状态并且清除移动力64当棋子处于可攻击时,棋子模型播放默认动画65当棋子处于不可攻击时,棋子模型不播放动画66当棋子死亡时,播放棋子死亡动画67当棋子死亡时,播放棋子死亡动画68当玩家建筑被摧毁时,摧毁建筑69当敌方建筑被摧毁时,占领建筑70我方建筑呈现蓝色,敌方建筑呈现红色71造成伤害时,受伤棋子出现浮动伤害数值72棋子上方设置血条显示棋子血量与护盾值73棋子设置归属方提示框74棋子设置稀有度提示框75播放如移动、伤害、敌方行动、棋子死亡动画时,锁定玩家禁止操作76当玩家基地被摧毁时游戏失败77当敌方基地被占领时游戏胜利78玩家设有资金、粮食、人口、基地等基础资源79绘制玩家资源信息UI80绘制当前回合提示UI81绘制结束回合按钮UI82当玩家按下回合结束按钮时进入下一回合83回合结束时根据基地等级与数据表提供资源84回合结束时根据资源点数量计算提供资源85恢复所有棋子攻击与行动力86敌方所有棋子攻击一次可以攻击到的第一个棋子87敌人每个棋子行动之间给予一定延迟间隔88将玩家摄像机聚焦于行动中的敌人89根据基地等级读取招募概率表90根据概率表随机生成5个固定稀有度招募位91根据招募位稀有度随机招募等稀有度棋子92根据玩家构筑生成随机五个招募棋子93玩家拥有与招募到的棋子每种不超过3个94玩家游戏开始时设有默认初始资源95绘制招募信息UI96玩家可锁定/解锁招募,回合结束时无法刷新招募97玩家通过UI界面花费资源招募棋子98招募棋子随机生成与基地周边格上,如果没有生成位置则无法招募99玩家可以通过招募UI升级基地等级并刷新UI100当资源信息改变时刷新UI101刷新招募消耗金币随基地等级提高102从文件中读取玩家构筑棋子信息103当回合数超过地图限制回合数时,游戏失败104绘制玩家失败UI,按钮跳回至主菜单105玩家胜利时随机解锁两张未解锁卡牌106绘制玩家胜利UI,按钮跳回至主菜单107当棋子属性改变时刷新显示108新一回合时绘制特殊UI与动画109点开玩家棋子设置时读取所有棋子重设默认值110玩家可以设置棋子是否为玩家棋子111绘制玩家棋子设置界面112绘制游戏主菜单113当玩家选择关卡时激活开始游戏114绘制玩家关卡选择界面115绘制关卡信息界面116玩家点击选择关卡时读取可选择关卡列表117读取玩家构筑棋子、已解锁棋子、玩家棋子信息118绘制玩家构筑界面119玩家可将已解锁卡牌加入至构筑卡组中,或从构筑卡组中移除120为每张卡牌绘制信息面板121根据选择关卡进入关卡122退出游戏123退出游戏确认UI

4.3程序关键与难点算法

4.3.1 柏林噪声地形生成算法

(1)柏林噪声算法的选择

该游戏的需求是真实的地形概括,因此常用的等量随机数是无法满足要求的,因为它缺乏一个关键的性质:连续性。尽管常规的可以通过插值函数使常规随机出的数据之间进行平滑,但最终效果会仍然显得十分不自然,因此需要寻找一种更加优秀的随机方法。

柏林噪声算法是游戏制作中非常常见的一种伪随机方法,其常常用于制作波形纹理、生成地形、模拟火焰,云,水等特效,它可以模拟生成从一维到甚至四维的噪声数据。在著名的游戏《我的世界》中便是使用它来随机生成地形的基础。

  1. 柏林噪声算法的实现

三维的柏林噪声函数会接受三个坐标向量作为输入,最终输出一个double值,同时函数会使用一个随机数组,由于是伪随机算法,如果输入的坐标与随机数组都完全一致,则最终输出的值也会一致。算法中会先取三个坐标的小数部分,这样便可以表示其在单元立体空间的某一个点上。然后,取三维空间的八个定点,使用(1,1,0),(-1,1,0),(1,-1,0),(-1,-1,0), (1,0,1),(-1,0,1),(1,0,-1),(-1,0,-1), (0,1,1),(0,-1,1),(0,1,-1),(0,-1,-1)这十二个向量中的八个(伪随机决定)作为梯度向量,再取该点到八个单元格定点的距离向量,对其分别做点积运算,就得到了每个顶点的影响值。对8个影响值做插值,进行加权平均值,就得到了最终输出结果。这里的插值如果使用常规的lerp线性插值则会使得最终噪声效果不自然,因此为了得到更平滑的噪声效果要采用非线性插值函数fade函数,即:6t^5-15t^4+10t^3。Fade函数在游戏、动画等制作之中常常用到,其特性是变化先快后慢再快,会产生一个非常平滑的过度效果,在运动、变化、颜色变化等方面可以起到非常良好的缓和效果,而在柏林噪声之中使用作为插值函数有着同样良好的作用,可以生成平滑的过度地形。

最终生成的地图效果如图4-2所示:

基于linux的贪吃蛇游戏设计_基于Unreal4的战棋类游戏《棋行傀儡》的设计与实现..._第3张图片

图4-2 柏林噪声地图生成效果图

4.3.2 游戏棋盘算法

游戏棋盘与大部分棋盘游戏有着一个重要的不同点,也是技术难点:游戏棋盘为六边形地图,因此不能简单地使用二维数组矩阵来当作游戏的地图数据。

经过资料查询与研究,最终发现:二维矩阵数组仍然可以适用于六边形棋盘,但是需要一种特别定义单元格“相邻”的条件。每个单元格除了绝对坐标(1,0),(-1,0),(0,1),(0,-1)外,(-1,1)与(-1,-1)同样定义为“相邻”条件。图4-3与图4-4分别指出了游戏棋盘数据样式与二维坐标相邻定义样式:

*以下图示会有致命的错误,会在总结中指出

基于linux的贪吃蛇游戏设计_基于Unreal4的战棋类游戏《棋行傀儡》的设计与实现..._第4张图片

图4-3 棋盘坐标示意图

基于linux的贪吃蛇游戏设计_基于Unreal4的战棋类游戏《棋行傀儡》的设计与实现..._第5张图片

图4-4 棋盘坐标数据结构示意图

另外,为了保证游戏场景的美观度,所有的单元格之间需要有一定的间隙,这又引申出了另一种算法需求。这里设六边形单元格半径为300,间隙长度为x,实现单元格间隙的算法如下图4-5所示

基于linux的贪吃蛇游戏设计_基于Unreal4的战棋类游戏《棋行傀儡》的设计与实现..._第6张图片

图4-5 地形块间距示意图

由此可得

  • 单元格横向相距150√3+1/2x
  • 单元格纵向相距450+2√3/3*x

进一步可推出单元格的x,y轴实际坐标值:

  • x实际坐标 =(根号3*模型半径+间隔)*x逻辑坐标+(0.5*根号3*模型半径+0.5*半径) (当y为偶数时)

=(根号3*模型半径+间隔)*x逻辑坐标 (当y为奇数时)

  • y实际坐标 =(1.5*模型半径+2*根号3/3*间隔)*y逻辑坐标

4.3.3 游戏视角算法

游戏的制造中往往会涉及到玩家摄像头的控制与移动,3D游戏中尤为重要,这将决定了玩家最基本的操作手感与游戏体验。游戏中根据游戏模式的不同往往可能会有多种不同的视角操作方法,这也对应这多套不同的算法。在Unreal4中,虽然引擎中的范例提供了最常见的第三人称视角与第一人称视角模式,但是这并不满足该项目的需求,因为这两种算法都是基于玩家操作人物的物理控制与镜头追随。而在该项目中,玩家有着特殊的上帝视角移动控制方式与条件,游戏、开发模式两套视角控制算法,因此需要独自开发。同时,两个不同的模式的算法有部分重叠,甚至在开发者模式中需要可以开发者可以自由转换两种视角模式,因此将所有视角控制的算法进行拆解设计,再最终根据需求合并。

这里先将所用到的蓝图节点名称与作用一一列出,方便之后的解释:

  • GetControlRotation 获得控制者旋转量 该节点会先获得玩家目前操控的Actor,然后读取其旋转属性
  • GetForwardVector 获得面向前方的向量 该节点会将一个输入的旋转数据结构转换为旋转所面向的向量数据结构
  • GetRightVector 获得面向右方的向量 该节点会将一个输入的旋转数据结构转换为旋转所面向的向量数据结构
  • GetActorLocation 获得Actor的坐标属性
  • SetActorLocation 设置Actor的坐标属性
  • AddControllerYawInput 使控制Actor进行Yaw轴旋转
  • AddControllerPitchInput 使控制Actor进行Pitch轴旋转
  • GetViewportSize 获得窗口大小
  • GetMousePosition 获得鼠标位置
  1. 通过鼠标移动控制镜头面向

该功能是游戏中最常用的镜头控制方法之一,因此Unreal4也直接提供该方法,要注意的是鼠标仅有2个维度的控制,这里分别控制了Actor的Yaw轴与Pitch轴,可以通过AddControllerYawInput与AddControllerPitchInput两个方法直接实现。

  1. 通过WASD按键基于场景水平面上移动

由于此需求限制在场景的水平面上移动,因此常规的基于玩家面向直接通过GetForwardVector和GetRightVector无法直接达成要求,因此需要进一步改进算法。如图4-6所示,可以看到在3D坐标世界中,水平面的旋转量为Z轴旋转

基于linux的贪吃蛇游戏设计_基于Unreal4的战棋类游戏《棋行傀儡》的设计与实现..._第7张图片

图4-6 三维坐标旋转轴示意图

因此,可以先通过GetControlRotation获得玩家旋转向量,然后只取其Z轴旋转获得空间水平面的旋转方向,然后再通过GetRightVecotr与GetForwardVector方法即可获得基于玩家位置的在水平面中的位移方向,最后使用SetActorLocation使玩家在水平面上进行移动。

  1. 通过鼠标滚轮使镜头在场景竖直轴移动及限制条件

该算法十分简单,即在鼠标滚轮输入时,通过GetActorLocation获得玩家空间坐标,然后仅对Z轴进行自加减便可达成目的。

  1. 当玩家面向下方时,通过鼠标滚轮使玩家面向镜头方向前后移动

该算法主要分为两个部分:一是对于玩家面向下方的判定,其次是向镜头方向的先后移动。

首先是如何判断玩家面向是否为下方,先通过GetConrolRotation获得控制Actor的旋转属性,然后使用GetForwardVector转换为玩家所面向的向量属性,向量数据结构是(x,y,z)格式对应了三个坐标轴的向量值,这里只取得其中的Z值,如果Z值小于√2/2则可以判断出玩家面向下方45度或更高,原理如图4-7所示:

基于linux的贪吃蛇游戏设计_基于Unreal4的战棋类游戏《棋行傀儡》的设计与实现..._第8张图片

图4-7 游戏摄像机旋转坐标示意图

当玩家满足条件时,通过GetRightVector获得玩家所面向方向的向量,将其与玩家当前坐标相加即可完成镜头的移动。

  1. 当玩家鼠标位于窗口边缘时,镜头基于场景水平面上移动

该功能中基于场景水平面上移动与第2条的实现方法相同,这里主要说明对于判定鼠标窗口边缘的实现。

这里可以通过GetViewportSize 获得窗口大小并GetMousePosition方法用于获得鼠标位置,这样便得到了窗口的x轴与y轴最大值以及鼠标对应的x,y坐标位置。然后定义一个百分比阈值,该阈值用于判定是否位于屏幕边缘。在EvenTick事件中,判定鼠标坐标x是否位于区间[0,阈值百分比*窗口x值]OR[阈值百分比*窗口x,窗口x]内,以及鼠标坐标y是否位于区间[0,阈值百分比*窗口y值]OR[阈值百分比*窗口y,窗口y]内,即可判断鼠标是否处于屏幕边缘,并且根据判断结果使用第2条的方法实现基于场景水平面上的移动。

4.3.5 游戏框选算法

框选功能是游戏中非常常见的一个功能,尤其是在各类RTS游戏中。然而在Unreal4中目前并未找到合适实现此功能的方法,因此选择了一个并不十分完美的算法来代替实现:

这里用到了蓝图节点中的LineTraceByChannel节点,该节点的原理是:由给定的起始坐标与结束坐标连接而成一条线,判断该线上是否有位于指定碰撞频道上的物体阻挡,如果阻挡则返回一个复杂数据结构记录所有的信息。而实现框选算法的方法是:当玩家框选窗口时,计算窗口起止坐标,并计算其在3D空间中的射线坐标,并用100*100条射线平均地插入在起止坐标之中,返回所有射线碰撞到的位于框选频道的Actor,达成伪框选的目的。

因此这里需要另一个关键的节点方法:ConvertScreenLocationToWorldSpace,该节点接受鼠标的屏幕坐标(x,y),然后输出其在3D空间的起始坐标与方向向量,随后可以通过计算起止坐标+方向向量*射线长度即可得到射线终点坐标。示意图如图4-8:

基于linux的贪吃蛇游戏设计_基于Unreal4的战棋类游戏《棋行傀儡》的设计与实现..._第9张图片

图4-8 游戏框选功能原理示意图

4.3.6 游戏数据存读

Unreal4实际本身提供了一种游戏数据存读方法,但是由于目前个人未知其原理与存放哪些数据,因此自己使用JSON结构独立实现了游戏存读系统。JSON结构是存放的是一个序列化的对象或数字,易于阅读与编写,同时可进行良好的解析与生成。其存放的数据十分灵活,并且易于手动修改数据,因此选用它作为游戏数据存读的基础。

以下为游戏中使用到的几种数据存取结构及数据实例:

  • ChessBiuld文件 用于存放玩家的卡组构筑信息

{"Build":["105","106","107","108","109","110","111","112","113","114","115","116","117","118","119","120","121","122","123","124","125","126","127","128","129","130","131","132","aa133","134","135","136","104","102","103"]}

Json格式{"整形数组键Build":[“整形数据卡牌ID”]}

  • LevelList文件 用于存放玩家可选择的关卡名信息

{"levelNames":["Level1","Level2","Level3"]}

Json格式{"字符串数组键levelNames":["字符串数据关卡名"]}

  • PlayerChessList文件 用于存放所有棋子是否为玩家可用棋子

{"chesses":[{"ID":"1","IsPlayer":false},{"ID":"2","IsPlayer":false},{"ID":"3","IsPlayer":false},{"ID":"100","IsPlayer":false},{"ID":"101","IsPlayer":false},{"ID":"102","IsPlayer":true},{"ID":"103","IsPlayer":true},{"ID":"104","IsPlayer":true},{"ID":"105","IsPlayer":true},{"ID":"106","IsPlayer":true},{"ID":"107","IsPlayer":true},{"ID":"108","IsPlayer":true},{"ID":"109","IsPlayer":true},{"ID":"110","IsPlayer":true},{"ID":"111","IsPlayer":true},{"ID":"112","IsPlayer":true},{"ID":"113","IsPlayer":true},{"ID":"114","IsPlayer":true},{"ID":"115","IsPlayer":true},{"ID":"116","IsPlayer":true},{"ID":"117","IsPlayer":true},{"ID":"118","IsPlayer":true},{"ID":"119","IsPlayer":true},{"ID":"120","IsPlayer":true},{"ID":"121","IsPlayer":true},{"ID":"122","IsPlayer":true},{"ID":"123","IsPlayer":true},{"ID":"124","IsPlayer":true},{"ID":"125","IsPlayer":true},{"ID":"126","IsPlayer":true},{"ID":"127","IsPlayer":true},{"ID":"128","IsPlayer":true},{"ID":"129","IsPlayer":true},{"ID":"130","IsPlayer":true},{"ID":"131","IsPlayer":true},{"ID":"132","IsPlayer":true},{"ID":"133","IsPlayer":true},{"ID":"134","IsPlayer":true},{"ID":"135","IsPlayer":true},{"ID":"136","IsPlayer":true},{"ID":"94","IsPlayer":false},{"ID":"95","IsPlayer":false},{"ID":"96","IsPlayer":false},{"ID":"97","IsPlayer":false},{"ID":"98","IsPlayer":false},{"ID":"99","IsPlayer":false},{"ID":"137","IsPlayer":true},{"ID":"138","IsPlayer":true},{"ID":"139","IsPlayer":true},{"ID":"140","IsPlayer":true},{"ID":"141","IsPlayer":true}]}

Json格式{“Json结构体数组键chesses”:[{“整形键ID”:”整形ID值”,”布尔键IsPlayer”:布尔值是否为玩家棋子}]}

  • PlayerChessLock文件 用于存放玩家已经解锁的棋子ID

{"Unlock":["102","103","104","105","106","107","108","109","110","111","112","113","114","115","116","117","118","119","120","121","122","123","124","125","126","127","128","129","130","131","132","133","134","135","136"]}

Json格式{"整形数组键Unlock":["整形已解锁棋子ID值"]}

  • SelectLevel文件 用于存放玩家当前选择的关卡名信息

{"LevelName":"Level1"}

Json格式{"字符串键LevelName":"字符串已选择关卡名"}

  • 地图数据文件 文件名即为地图名称,用于存放该关卡所有地形信息与棋子预设信息

{"mapLength":10,"mapWidth":10,"unitBlocks":[{"landFormType":"water","xLogicCoordinate":1,"yLogicCoordinate":1,"heightLogic":-4},{"landFormType":"water","xLogicCoordinate":2,"yLogicCoordinate":1,"heightLogic":-4},{"landFormType":"water","xLogicCoordinate":3,"yLogicCoordinate":1,"heightLogic":-4},............,{"landFormType":"water","xLogicCoordinate":10,"yLogicCoordinate":10,"heightLogic":-4}],"chesses":[{"chessID":101,"xLogic":2,"yLogic":7,"chessFace":"leftUp"},{"chessID":100,"xLogic":9,"yLogic":6,"chessFace":"leftUp"},{"chessID":106,"xLogic":10,"yLogic":4,"chessFace":"leftUp"},{"chessID":107,"xLogic":7,"yLogic":3,"chessFace":"leftUp"},{"chessID":107,"xLogic":6,"yLogic":8,"chessFace":"leftUp"},{"chessID":115,"xLogic":6,"yLogic":4,"chessFace":"leftUp"},{"chessID":109,"xLogic":5,"yLogic":6,"chessFace":"leftUp"},{"chessID":113,"xLogic":10,"yLogic":8,"chessFace":"leftUp"}],"roundLimit":25}

Json格式{“整形键mapLength”:整形地图长度,”整形键mapWidth”:整形地图宽度,”Json结构体数组键unitBlocks”:[{“枚举键landFormType”:”枚举值地形类型”,”整形键xLogicCoordinate”:整形x逻辑坐标值,”整形键yLogicCoordinate”:整形y逻辑坐标值,”整形键heightLogic”:整形高度逻辑值}],”Json结构体数组键chesses”:[{“整形键chessID”:整型棋子ID值,”整形键xLogic”:整形棋子x逻辑坐标值,”整形键yLogic”:整形棋子y逻辑坐标值,”枚举键chessFace”:”枚举值棋子面向方向”}],”整形键roundLimit”:整形回合限制数}

4.3.7 棋子体型算法

每个棋子的数据结构中,存放了一个体型结构体,该结构体有着两个属性:体型大小值与体型类型。当游戏需要读取棋子的体型时,会先读取体型大小值,然后再读取该体型值对应的体型类型预设。最终输出为一个二维坐标数组,该数组记录了该棋子体型的所有单元格相对于棋子坐标点的相对坐标值。图4-9是一个体型值为4,类型值为1的体型示意图:

基于linux的贪吃蛇游戏设计_基于Unreal4的战棋类游戏《棋行傀儡》的设计与实现..._第10张图片

图4-9 游戏体型相对坐标示意图

系统会根据预设的二维坐标数组读出(0,0),(1,0),(-1,1),(0,1)这四个相对坐标坐标,然后再读取棋子当前坐标(x,y),依次将地形(x,y),(x+1,0),(x-1,y+1),(x,y+1)的chessOn属性设为该棋子,便达成了棋子体型的实现目标。

但是这里有个非常严重的问题,上述的四个相对坐标是只有棋子的y逻辑坐标为偶数时才能准确的。回到之前所述的图4-3-2中,若当前棋子的坐标为(0,1),那么对应的体型坐标应为(0,1),(1,1),(-1,2),(0,2),这显然与图示中所标注出的体型不符。所以这里应当增加一个修正判断逻辑:当目标的y逻辑坐标为奇数时,将读取出的所有y值为奇数坐标的x值+1,再计算对应的体型实际坐标。

上述问题将在后面的攻击范围算法、移动范围算法中同样遇到,因此后面会省略解释。

4.3.8 棋子攻击范围算法

在《行棋傀儡》中,所有攻击范围都按照一个算法处理。在游戏中有着高低起伏的地形,这些地形不仅仅影响着棋子的移动,也决定了棋子是否能够攻击到目标位置。任何一个棋子都不可能越过陡峭而又高耸的地形攻击到另一面的敌人。因此,该算法模拟了一个简易的抛物线,通过判断抛物线是否被地形阻挡来决定最终是否能够攻击到该目标。以下是算法的介绍:

首先,取攻击起始坐标与攻击目标坐标,读取两边逻辑坐标的地形在三维世界中的实际坐标,在每个地形的数据结构中存放了一个chessLocate的二维坐标,用于快捷地将棋子放置在该地形之上,因此可以直接取双方坐标地形的该数据值。然后分别将其z轴坐标加上一定高度h1,该高度h1一般为人物模型的三分之二。这样便得到了抛物线的起止点与终止点。玩家的攻击范围数据结构中会读取出一个整形值h2,该整型值h2用于决定玩家的攻击能够跨越高度的能力。取起始点与终止点两点的中点坐标,将该中点坐标的z值加上h2*固定系数,这样变得到了该抛物线的顶点坐标。最后,取中点坐标到两个端点的中点,再将得到的两个点的z值加上0.5乘以到中点坐标高度差的二分之一的值,这样便得到了最后的两个过渡点。最后将之前所得的5个点按顺序相连接,则得到了一条伪抛物线。

这条伪抛物线共有4条线段所组成,这里用之前所提到的LineTraceByChannel方法,将四条线段的顶点分别输入四次便可检测出该伪抛物线是否被地形所阻挡,如果阻挡则无法攻击到目标点,否则将该目标坐标加入可攻击范围的二维坐标数组中。图4-10从二维的角度说明了该算法原理,图4-11则为实际游戏中的实现效果:

基于linux的贪吃蛇游戏设计_基于Unreal4的战棋类游戏《棋行傀儡》的设计与实现..._第11张图片

图4-10 伪抛物线实现原理示意图

基于linux的贪吃蛇游戏设计_基于Unreal4的战棋类游戏《棋行傀儡》的设计与实现..._第12张图片

图4-11 游戏攻击范围判定效果图

4.3.9 棋子移动范围算法

棋子移动范围算法的核心为最优路径算法,在此基础上进行了重新设计并且与游戏契合,算法具体实现逻辑如下:该算法始终维护一个结构体数组a,该结构体记录了:一个目的二维坐标(x,y),一个方向枚举向量(该枚举向量表示与该坐标相邻的6个坐标的相对向量)数组,一个表示剩余体力值的整形数据。枚举向量数组即表示从原始目标到达该目标的路径,并且整形数据记录了使用此路径后剩余的体力值。

然后便开始一种递归算法:从出发点开始对周围6个单元格进行计算,得到一个新的路径以及新的剩余移动力,然后遍历结构体数组a,如果数组a中存在到达该目的最优路径则进行比较,取剩余移动力较大的路径原路径保存至数组a中;否则写入新的路径与剩余移动力,然后再以此单元格与记录中的最优路径剩余移动力为基础对周围6个单元格进行一轮新的递归计算,当剩余移动力无法移动至目标单元格或目标单元格为地图边界、已被其他棋子占有时则终止该次递归计算。

最终递归循环结束后,得到了结构体数组a,记录了所有可达到的目标点,剩余移动力和路径信息,棋子移动范围算法完成。图4-12显示了通过该算法计算出的所有可达到目标单元格,图4-13显示了移动到其中一个单元格的最优路径:

基于linux的贪吃蛇游戏设计_基于Unreal4的战棋类游戏《棋行傀儡》的设计与实现..._第13张图片

图4-12 游戏移动范围显示效果图

基于linux的贪吃蛇游戏设计_基于Unreal4的战棋类游戏《棋行傀儡》的设计与实现..._第14张图片

图4-13 游戏移动最优路径效果图

4.3.10 视角聚焦算法

该算法的目的是使得玩家摄像头在一定高度自动对准某个棋子,并且面向地图中心方向。该算法首先会从MapController中读取地图属性,计算出棋盘中心的地形块位置,取其3D实际位置的x坐标值与y坐标值,再取目标棋子的高度值z,将目标棋子的3D实际坐标与该向量相减,即可得到从棋子到地图中心方向的向量。将该向量除以自身长度得到方向单位向量,将方向单位向量乘以一个水平面的距离值l1,再将该值的z轴加上l1,与目标棋子坐标相加,这样便得到了面向棋子与地图中心,并且可以45度俯视棋子的坐标值。

最后,将之前所得的单位方向向量转换为旋转结构体向量,取其Roll轴与Yaw轴旋转,将Pitch轴设为-45,便可得到最终结果,算法完成。图4-14是以玩家战略点为目标的聚焦算法,图4-15为原理示意图。

基于linux的贪吃蛇游戏设计_基于Unreal4的战棋类游戏《棋行傀儡》的设计与实现..._第15张图片

图4-14 游戏视角聚焦基地效果图

基于linux的贪吃蛇游戏设计_基于Unreal4的战棋类游戏《棋行傀儡》的设计与实现..._第16张图片

图4-15 游戏聚焦算法原理示意图

4.3.11 伤害算法

该游戏有着一套稍微复杂的攻击伤害计算算法,当棋子受到攻击时,会先进行闪避判定,即若random(0,100)<棋子闪避概率值,则判定为闪避该次攻击,不进行伤害计算。若没有成功闪避,则进行伤害计算,伤害的计算方法为:先检测棋子是否存在护甲,若没有护盾则无法享受伤害减免效果,直接受到全额伤害;若存在护甲,先计算护甲剩余百分比,读取棋子最小护甲百分比值,计算[护甲剩余百分比值*(100(即护甲最大百分比值)-护甲最小百分比值)+护甲最小百分比值]=最终防御修正值。然后用最终防御修正值分别乘以棋子护甲与魔抗,即可得到护甲与魔抗的生效值。最后将护甲与魔抗的生效值分别代入伤害减免公式:护甲or魔抗/(护甲or魔抗+100),将伤害减免*受到攻击值得到最终受到伤害值。

最复杂的情况下,以护甲为例(魔抗同理计算),棋子在有护盾的情况并且没有闪避时受到的最终伤害值公式为:

最终伤害=(((当前护盾/最大护盾)*(100-护甲最小百分比)+护甲最小百分比)*护甲/(护甲+100))*受到伤害

4.3.12 招募算法

游戏的招募算法是游戏十分重要的一个算法之一,并且有着一定的复杂性,在此之前需要先明确招募玩法的规则:

首先玩家拥有5个招募槽位,每次招募时系统将随机5个招募结果填充至5个招募槽位并代替原本招募棋子。要想获得招募结果,首先需要读取玩家战略点等级数据,然后从表DataTable中读取对应的概率数值,游戏中所使用的的概率表如下所示:

表4-2 招募概率表

commonProbabilityuncommonProbabilityrareProbabilityepicProbabilitylegendaryProbability10.80.200020.5333330.40.0666670030.3750.50.1250040.2222220.5555560.2222220050.1212120.4848480.3636360.030304060.0606060.303030.4848480.1515160700.1379310.5517240.2758620.034483800.0714280.4285710.3571420.1428599000.3076920.3846150.307693

游戏会根据概率表随机决定五个招募槽位棋子的稀有度,然后再随机筛选出该稀有度下玩家牌库中的棋子,决定招募到的棋子。如果该稀有度下无可用棋子则再次重新招募。另外,玩家牌库中的棋子每种最多招募三个,因此玩家场上的棋子与招募到的棋子总和不会超过三个。

当玩家选择招募该棋子时,会判断玩家的基础资源(金钱、食物、人口)是否足够,以及基地周围是否有可用的单元格,当全部满足条件时玩家才能够成功招募。

基于此算法设计如下:系统将招募5个槽位分为招募1个棋子的五次循环,分别将结果放置于5个槽位中。招募之前会将原招募槽清空,并使原招募槽里的棋子招募数据记录-1。每次招募单个棋子前先读取基地等级数据,然后根据招募概率表中将招募概率数据随机决定此次招募棋子的稀有度。招募稀有度决定后先进行检测,该稀有度下是否有可供招募的棋子,如果没有则重新招募;如果存在则在可供招募的棋子列表中随机招募其中一个棋子。招募系统会始终维护一个结构体,该结构体记录了玩家所有牌库中的棋子与相应的招募次数,当招募到该棋子时,会使其招募次数+1,该结构体也是判断某稀有度下是否有可招募棋子的依据。

当玩家点击招募某棋子时,会先判断招募是否可用,当满足条件(招募消耗金钱<=当前金钱)and(招募消耗粮食<=当前粮食)and((最大人口-当前人口)>=棋子占用人口)时,第一条判定通过。然后再判断基地周围是否有可用单元格,这里系统会遍历以2为半径的周围单元格,该遍历算法是复用之前攻击范围算法中的一个方法,如果单元格不存在chessOn单位、单元格地形类型不为water,则该单元格可用。系统会随机在所有可用单元格中随机挑选一个单元格将招募到的棋子放置。

至此,招募算法完成,其中一些微小的算法不详细介绍。图4-16为招募效果示意图

基于linux的贪吃蛇游戏设计_基于Unreal4的战棋类游戏《棋行傀儡》的设计与实现..._第17张图片

图4-16 游戏招募效果示意图

4.3.13 玩家牌库算法

玩家牌库算法共分为两个部分:玩家构筑部分与棋子解锁部分。该部分主要为数据结构与数据处理:

首先是棋子解锁部分,游戏中所记录的所有棋子分为玩家棋子与非玩家棋子,玩家棋子为可被玩家解锁并加入构筑的棋子。棋子是否为玩家棋子可以在开发模式中设置,并且存放在专门的文件中,在之前提到的存储算法中提及。而玩家棋子又分为已解锁棋子与未解锁棋子,已解锁棋子可以加入玩家的构筑,未解锁棋子会在游戏胜利后随机解锁其中的2个棋子。

然后是玩家构筑部分,玩家只能将已经锁棋子加入构筑,也可以随时将构筑中的棋子取消构筑。玩家的构筑信息会以JSON结构形式保存在文件中,其决定了玩家在游戏中能够招募到哪些棋子。并且游戏对玩家的构筑有着一定的限制,即每种稀有度的卡牌至少需要有5个位于构筑内。图4-17显示了玩家构筑界面,图4-18显示了玩家未解锁的棋子。

基于linux的贪吃蛇游戏设计_基于Unreal4的战棋类游戏《棋行傀儡》的设计与实现..._第18张图片

图4-17 游戏玩家构筑界面

基于linux的贪吃蛇游戏设计_基于Unreal4的战棋类游戏《棋行傀儡》的设计与实现..._第19张图片

图4-18 游戏玩家未解锁棋子界面

当玩家进入主菜单界面时,游戏会先读取PlayerChessList文件,解析其中的JSON信息,并将所有玩家棋子记录在一个数组结构中。然后读取PlayerChessLock文件,解析JSON,将玩家已解锁、未解锁的棋子信息分别放在两个数组中。最后再读取ChessBuild文件,获得玩家构筑信息,将加入构筑的卡牌放入单独的数组中。该算法难点在于数据处理与UI逻辑,如图4-17所示,系统读取所有信息后会构建UI,在左侧会显示玩家当前加入构筑的棋子,在上放玩家可以点击分页来查看已经锁和未解锁的棋子。在未解锁的棋子中,可以点击Add Build将棋子加入构筑,同时可以点击左侧位于构筑内的棋子取消构筑,已经加入构筑的棋子会用Is In Build的提示信息覆盖原按钮。当玩家点击Apply时,如果达成构筑条件,则会重新生成构筑JSON信息并将数据覆盖原ChessBuild文件,如果不满足则在下方跳出提示信息。

第五章 总结与反思

虽然成功地完成了游戏DEMO的制作,但是仍然发现了诸多没能解决或由于进度的关系无法解决或实现的地方,这些可以说是制作的缺陷与改进的方法。不过第一次就能做出完美的软件成品也是不现实的事情,制作、发现问题、分析问题、解决问题从而提高自己的能力也是此次毕业设计的目的之一。最后总结出以下几点问题:

5.1 模块、函数独立性反思

首先便是编程中最基本的问题:类的独立性。这个问题源于个人的大意,导致了不少类之间没有明确的界限,并且不同类之间甚至会实现类似的算法等等。

起初先编写了一个简易的开发者系统,由于发现编写的过于混乱,方法的重合,一个类中积攒了过于多的功能实现,并且方法之间有大量的公共变量依赖,因此将其放弃又重新开始编写。第二次编写重新规划了类的功能与职责,减少了公共变量的使用,并且将一些未来可能需要复用的方法写进了函数库中。尽管有心为之,但最终发现类的功能分界仍然不够明确,并且开发者系统完成后仍然略显混乱,但相较于第一次编写时已经有所改善。

之后进行游戏战斗系统与非战斗系统的编写时,代码质量明显更进一步,将许多相对独立的功能等分别放入了不同的controller中,并且使各个controller存储的数据进行划分,也能够根据各自需要正常调用其他controller中的方法。但是之前所提到的问题并非不存在,并且由于之后所讲的MVC结构问题的忽略导致许多逻辑写入了UI类中。

总之,这方面的问题是编程的基础问题,也并非一朝一夕能完全掌握的,需要大量的编程经验以及预先设计,在日后再碰到类似的情况时定然会着重注意。

5.2 三层结构反思

众所周知,软件工程里有着一个经典的模型:三层结构。该结构定义了数据层、逻辑层、表示层的三层分离结构模式,三层结构有着高内聚、低耦合的特性。三层结构相对而言是密封的三层代码,并提供访问接口。这种结构将用户所见的,底层数据存取以及中间逻辑分离,用于复杂的软件工程项目,可以大幅度提高程序代码质量、可拓展性等。

游戏作为一个复杂的软件项目,有着大量的逻辑处理、数据处理、UI表现与用户交互。因此大部分游戏往往需要遵循三层结构来编写代码,而在此之前并没有意识到这一点。当把大量的处理逻辑无意地与UI逻辑写入一起时,才发现了这个问题,也明白了为何要强调软件工程的三层结构。UI逻辑、业务逻辑与数据存取逻辑的混杂在程序体量较大时会造成极大地开发障碍,因此在编写程序时需要预先分析好每个逻辑的结构层归属,方便后续开发。

5.3 棋盘算法反思

之前有所提及对于棋盘算法的描述,以及其缺陷。该缺陷为后来开发的许多算法带来了巨大的阻碍,其根本原因就是当y值的奇偶性质不同时,相对坐标的不一致。因此正确的数据坐标应当如下图5-1所示,实际显示情况如图5-2:

基于linux的贪吃蛇游戏设计_基于Unreal4的战棋类游戏《棋行傀儡》的设计与实现..._第20张图片

图5-1 游戏棋盘坐标数据结构修正示意图

基于linux的贪吃蛇游戏设计_基于Unreal4的战棋类游戏《棋行傀儡》的设计与实现..._第21张图片

图5-2 游戏棋盘坐标修正示意图

可惜的是,当发现这个问题时已经完成了整个开发系统,以及部分战斗系统,想要再修改需要额外花费大量的时间与精力,并且原先的问题并非不能解决,因此此次并没有将其修正。通过这个问题认识到当面对一些非常底层、基础的逻辑时,应当谨慎设计,并且要考虑到日后相关算法的设计影响。

5.4 方法函数与函数库反思

在函数独立性反思中已经提到,尽管有意尽地、可能地保证每个模块的独立性,并且开始将一些常用方法放入函数库以便复用,但是更微观地来看,此次做的并不漂亮。虽然确保了许多方法功能独立,但是细小的功能并没有很好地规划,因此常常出现某个方法涵盖了大量内容的情况。并且由于蓝图中有着事件的概念,事件有着类似方法的性质又有些不同,无法封装,因此使得这种情况更加混乱。

另外,函数库的使用也因此导致利用率较低,期间碰到一些可服用的小功能因为没有单独写成方法而无法复用。

这个问题个人认为仍然是经验不足导致得,希望下次能够吸取教训做到尽善尽美。

5.5 控制台反思

大部分游戏都有着控制台功能,也包括一些比较复杂的应用程序,控制台开发成本小,集齐了游戏中所有的逻辑指令,通过输入对应的指令便可完成相应的逻辑,是个非常便捷实用的调试工具。起初认为游戏规模较小,没有开发控制台工具的必要。但后来发现并非如此,许多情况如果有控制台能够大幅度增加开发效率,并且能够不需要编写临时的调试程序。

幸运的是,蓝图系统提供了非常多强大的调试工具,使得即使没有控制台也完成了游戏DEMO的开发,但是日后再遇到类似的开发需求,控制台也应当是第一开发优先级的系统。

5.6总结

此次毕业设计使我收获颇丰,虽然之前使用过2D的游戏引擎GameMakerStudio2,但是与3D游戏引擎仍然有着非常大的差距,并且之前从未完整地开发过一个游戏DEMO,仅仅是实现了部分功能。3D游戏的逻辑往往非常复杂,即使是看起来很小的游戏体量,也有着不俗的工作量。

并且这次毕业设计暴露了我非常多的问题。首先是程序设计上的不足,经验匮乏,导致开发工作饶了非常多的弯路。没有使用三层结构使得UI、数据存取以及实现逻辑混淆,难以修改与拓展;模块的划分、一个方法内所实现功能过多导致代码功能略显混乱;底层算法、数据结构的问题导致增加了工程量;以及由于第一次使用3D游戏引擎因此不熟练导致的诸多琐碎的问题等等。

不过发现问题并解决问题也是这次毕业设计的目标,这次成功地完成了该游戏的完整DEMO,使用了游戏开发中常用的Checklist的开发方式,设计了游戏系统架构,功能点列表,程序模块架构,熟悉了业界顶尖的3D游戏引擎Unreal4的使用等等。并且在不擅长的领域如UI、人物动画、贴图、模型等方面都完成了目标,大幅度提高了自己的综合能力。

希望我在以后类似的项目工作能够精益求精,止于至善

你可能感兴趣的:(基于linux的贪吃蛇游戏设计)