文章目录
-
- 一、地形制作
-
- [1.1 地形制作流程](#1.1 地形制作流程)
- [1.2 关卡白盒](#1.2 关卡白盒)
- [1.3 场景美化](#1.3 场景美化)
- [1.4 优化场景](#1.4 优化场景)
- 二、触发结构
- 三、玩家指引(函数封装)
-
- [3.1 项目拆解](#3.1 项目拆解)
- [3.2 功能实现](#3.2 功能实现)
-
- [3.2.1 绘制UI界面](#3.2.1 绘制UI界面)
- [3.2.2 UI的读取显示和刷新](#3.2.2 UI的读取显示和刷新)
- [3.2.3 交互功能](#3.2.3 交互功能)
- [3.2.4 最终实现](#3.2.4 最终实现)
- 四、NPC对话系统
-
- [4.1 项目拆解](#4.1 项目拆解)
- [4.2 UI内容读取和显示](#4.2 UI内容读取和显示)
- [4.3 NPC聊天的统一入口](#4.3 NPC聊天的统一入口)
- [4.4 使用锁确保线程安全](#4.4 使用锁确保线程安全)
- 五、物品使用
-
- [5.1 项目拆解](#5.1 项目拆解)
- [5.2 火焰效果实现](#5.2 火焰效果实现)
- [5.2 简易使用](#5.2 简易使用)
- [5.3 靠近后自动使用](#5.3 靠近后自动使用)
- 六、商店系统
-
- [6.1 项目拆解](#6.1 项目拆解)
- [6.2 物编准备](#6.2 物编准备)
- [6.3 功能实现](#6.3 功能实现)
-
- [6.3.1 锻造钥匙](#6.3.1 锻造钥匙)
- [6.3.2 打开宝箱](#6.3.2 打开宝箱)
- 七、装备限制(自定义键值传递信息)
-
- [7.1 项目分解](#7.1 项目分解)
- [7.2 物编准备](#7.2 物编准备)
- [7.3 功能实现](#7.3 功能实现)
-
- [7.3.1 装备拾取](#7.3.1 装备拾取)
- [7.3.2 装备丢弃](#7.3.2 装备丢弃)
- [7.3.3 显示特效](#7.3.3 显示特效)
- 八、野怪刷新(数组字典、自定义键值简化存储)
-
- [8.1 数据初始化](#8.1 数据初始化)
- [8.2 自定义刷怪函数](#8.2 自定义刷怪函数)
- [8.3 刷怪机制](#8.3 刷怪机制)
- 九、行为树(自定义事件传递信息)
-
- [9.1 游戏初始化](#9.1 游戏初始化)
- [9.2 行为树分敛](#9.2 行为树分敛)
- [9.3 AI实现](#9.3 AI实现)
-
- [9.3.1 近卫AI](#9.3.1 近卫AI)
- [9.3.2 治疗师AI](#9.3.2 治疗师AI)
- 十、简单状态机
-
- [10.1 游戏初始化(休眠状态)](#10.1 游戏初始化(休眠状态))
- [10.2 战斗状态](#10.2 战斗状态)
- 10.3变身状态
- [10.4 死亡状态](#10.4 死亡状态)
- 十一、护送任务
-
- [11.1 项目分解](#11.1 项目分解)
- [11.2 任务开启](#11.2 任务开启)
- [11.3 任务执行](#11.3 任务执行)
- [11.4 任务结束](#11.4 任务结束)
- [11.5 UI设计](#11.5 UI设计)
- 十二、收集任务
-
- [12.1 游戏初始化](#12.1 游戏初始化)
- [12.2 功能实现](#12.2 功能实现)
参考官方教程《中级触发课程》
一、地形制作
1.1 地形制作流程
在进行游戏场景的开发时,需要对接上下游岗位的各种需求,了解制作的全局安排,以保证整个项目的方向不产生偏差,最终的成品符合目标预期。整个游戏场景开发的流程结构包括以下几个步骤:
- 策划:确认场景需求、画面表现风格、地形变化、镜头参数等各种细节,以便在正式制作场景地图时有所准备。
- 原画师:根据需求提供概念设计,为3D美术师提供制作场景资源的基础。
- 3D美术师:会根据概念设计,制作3D建模与材质
- 地编:整合素材导入引擎,并完成地图内的摆放调试、灯光和渲染效果。
- 程序:优化游戏性能和玩法,实现目标所需的体验。
1.2 关卡白盒
关卡白盒(Blockout)是游戏开发中的一个重要概念,它指的是使用简单的3D几何形状(通常是白色或单色的块状物)构建的基本游戏内草图,用于快速测试关卡的基本结构和游戏玩法。
通常在制作场景美术前,需要和策划对接关卡设计文档,并共同配合在编辑器中搭建关卡白盒。通过关卡白盒可以快速验证对应的玩法和关卡体验,以及是否能达到预期的游戏目标,及时调整游戏的设计,减少在后期大幅修改导致进度被延缓的可能。
视频中正在制作的游戏叫做"大罗",玩家需要在黑暗中寻找道路,并净化地图各处被腐蚀的怪物。而高低视野差本身是玩法的一环,怪物或战利品会藏匿其中。这些影响玩家体验的地形布置,必须严格按照策划量化的标准来进行调整。
1.3 场景美化
在整个地图的白盒阶段完成后,接下来就正式开始场景美术的部分了。在这个阶段,我们可以跳过原画和3D制作的环节,直接根据文案对故事的背景设定,或者策划提供的场景分为参考,到编辑器中寻找所需的资产。但我们依然需要逐步拆解出画面所需的元素,比如场景是在寒带还是热带、故事发生的时间、树木植被的种类、岩石山体的状态等等。然后可以在资源管理中浏览和下载模型,以及在物编中创建自定义装饰物。
在挑选时要保证模型的风格尽量统一,避免造成画风上的割裂,对视觉上的体验造成负面影响。使用材质相近,但是造型不同的模型可以制作出不同的效果,比如不同山体的轮廓或植被的覆盖。同时利用组合装饰物的功能,将搭建好的小景别或建筑等打包成一个装饰物。除此之外,还可以使用投射物和材质变化等物编设置,给装饰物增加更多细节。
准备好素材之后,就是围绕着关卡开始拼积木了。从体积较大的装饰物开始,再一步步地优化画面的表达。
除此之外,场景设计还需要大量的美术知识,构图的技巧、空间关系、远景里的拉伸、大气透视等,需要不断地进行练习和积累才能做出更好的作品。
1.4 优化场景
- 提高游戏帧数
- 降低游戏运行所需的性能
- 场景BUG
- 模型碰撞
- 效果降级
比如,使用地形编辑中的植被笔刷大面积覆盖密度较高的草地,可能会导致游戏的帧数显著下降。所以使用装饰物中的植被来铺设场景。另外,经常容易出现玩家角色卡死在地图边缘,或者装饰物以及单位的附近等,需要较为细致地去处理这些碰撞体积。
大量不合理的放置灯光等也容易造成游戏渲染方面的压力,掉帧、卡死等。应该先处理好最主要的光源,再进一步去优化漫反射的影响等。有时候为了游戏的体验,不得不做出一些妥协,寻找效率与效果的平衡。一切以输出最终成品为目标不断地进行改进,养成良好的创作习惯和培养规范的开发意识,能极大的提高生产效率,避免不必要的麻烦。
二、触发结构
当我们玩游戏时,我们可以看到和体验到不同的游戏场景互动,甚至不同玩家的不同故事。使用Y3编辑器,我们可以在ECA中实现所有这些游戏效果,无论是简单的拿起武器还是多人支线任务。有时候ECA可能看起来相当长和复杂,但它们通常可以分解为三种简单的编程结构:顺序、选择和循环。
-
顺序结构:按顺序执行一系列操作,是编程中最基本和默认的模式。
比如当玩家第一次进入游戏时,会先播放动画视频,然后弹出初学者指南,几秒钟后将显示一个关闭按钮,最后玩家可以单击以关闭指南。在大逃杀游戏中,地图通常会随着时间的推移而缩小,迫使每个玩家彼此靠近,这也是顺序结构。
-
选择结构:根据条件执行不同的操作。例如,检查玩家是否持有正确的钥匙来打开门,或者在大逃杀游戏中检查玩家是否活着以及是否是最后剩下的玩家。选择结构使用if-then-else逻辑来实现。
-
循环结构:重复执行一组操作,直到满足特定条件。Y3编辑器提供了多种循环类型,包括指定次数循环、条件循环和遍历数组循环
这三个结构看起来非常简单,但是组合起来可以创建非常复杂的程序,几乎可以解决所有的问题,实现所有的游戏效果。例如我们可以使用选择结构和循环结构来计算AOE伤害:首先循环所有在范围内的单位,然后使用选择结构即if语句来检查单位的情况;如果单位是友方单位,我们会为其添加增益效果;如果该单位是敌方单位,我们会对其进行真正的伤害。
三、玩家指引(函数封装)
3.1 项目拆解
在许多游戏中,玩家指引都是必不可少的一部分,它能够快速的帮助玩家熟悉核心玩法,并在需要时随时查看。通常玩家指引会以弹窗的形式出现,但也可以是更传统的电子书形式,或更为详细的教程。在设计玩家指引时,需要考虑易用性、可访问性和自适应性,确保玩家可以轻松查看和使用指引。
下面将以一个交互式UI的形式,为玩家创建一个实用的玩家指引,将包括翻页、隐藏和取消的交互功能以及快捷键操作方式。为了提高信息的组织性和易读性,需要将内容分成不同页面显示,方便玩家快速找到需要的信息;并遵循常见的游戏UI设计规范,如从左到右阅读,按钮在左边,内容在右边。
根据以上需求,我们要制作的玩家指引UI应该是全局可见的,并且允许被修改;玩家在点击这四个标题时不应有任何限制以便可以自由的查看。这些功能可以分为三个部分:内容预设、UI的读取显示和刷新、交互。
3.2 功能实现
3.2.1 绘制UI界面
UI界面需要包括一个底板、四个标题、四个标题文本、一个页面和一个页面文本。由于我们希望玩家能够点击标题来查看内容,因此我们需要为这四个标题添加相应的事件。
我们需要将UI上显示的所有内容存储在变量中,以方便调用,这通常是游戏初始化的一部分。我们可以设置一个全局变量TitleText(字符串数组)来存储标题文本,再设置一个全局数组变量PageText来存储与标题对应的页面文本。
3.2.2 UI的读取显示和刷新
- 设置UI的初始状态 :默认打开UI时,会显示四个标题和起始页内容,所以需要将四个标题控件文本内容设为
titleText[1]
到titleText[4]
,将pageText
控件的文本内容设为pageText[1]
。 - 新建函数库函数实现刷新功能 :
- 在函数库中创建一个名为"刷新UI"的新函数;
- 创建全局变量page表示我们应该向玩家显示的页数,比如点击标题3时,page=3;
- 使用变量page作为索引来获取相应的内容,即
pageText[page]
,然后将其设为pageText
控件的文本内容
- 创建四个触发器来刷新UI。
3.2.3 交互功能
这部分我们将添加两个功能来使UI交互更加友好。第一个是点击标题时的显示效果,比如当玩家点击titleText1时,titleText1的控件将显示不同的颜色,让玩家清楚的知道自己点选了什么。
为了实现这种视觉效果,需要在UI上添加一个半透明的控件SelectEffect
,这个控件的大小与标题控件相同,用来覆盖在标题上,以改变其视觉效果。由于交互效果与点击标题和刷新UI的效果相结合,我们可以在函数刷新UI中进行添加:
添加UI隐藏功能:通过点击开关按钮或按下H键,玩家能够隐藏或显示整个UI。
3.2.4 最终实现
两个函数库函数:
按下隐藏图标swich或按下H键时,显示/隐藏界面:
对于游戏来说,UI是不可或缺的一部分。在这里,我们只是创建了一个简单的指引,但它同样可以作为一个冒险日志、任务日志、主菜单、RPG中角色信息概览以及竞技游戏中的排行榜等等。UI是我们想要高密度表达信息的绝佳载体。
四、NPC对话系统
4.1 项目拆解
NPC在游戏中有许多用途,包括推进故事情节、作为玩家的伙伴、物品商店、生命恢复点数等。与NPC互动是一种广泛使用的游戏机制。在本教程中,我们将实现与NPC交谈的效果。玩家将能够接近或单击以与NPC进行对话,并能随时关闭对话。
为了简化NPC对话系统的开发,我们使用统一的对话界面模块来适配所有NPC,以提高效率和保持界面一致性。我们希望玩家能够以多种方式与NPC互动,因此我们需要函数库功能,以此通知编辑器在玩家靠近NPC或单击NPC时显示对话界面。最后,我们需要确保对话界面不会重复触发,例如,当我接近NPC1时会弹出MPC1的聊天界面,此时单击NPC2,其聊天界面不应该弹出。整个项目功能可拆解为三个部分:
4.2 UI内容读取和显示
- 需要在物体编辑器中设置所有NPC的聊天内容,为此,添加一个自定义属性"Talk",来存储每个NPC的聊天文本。
- 设计对话界面,它至少应该包括背景、NPC图标、文本和退出按钮。
- 自定义一个带有参数NPC的函数,使对话界面能够读取和显示正确的信息
设置字符串类型的局部变量
TalkKey
为:键值->字符串自定义键值->NPC的字符串,值为talk。
4.3 NPC聊天的统一入口
创建一个统一的NPC聊天入口函数"NPC与英雄对话",当英雄与NPC对话时,将NPC信息写入聊天对话框并显示给玩家。我们还需要设置退出机制,使用循环计时器,每秒检查英雄和NPC之间的距离以及退出按钮的状态。
设置进入区域事件和点击事件的触发器,当玩家控制的英雄靠近NPC,或者玩家点击了一个非英雄单位(NPC)且距离小于400时,都触发对话函数,展示对话界面。
这里有一个bug,如果区域大于400,比如450。那么英雄一进入就开始计算距离,这时候肯定是大于400,马上就删了计时器,distance也删了,界面直接关闭。所以得设置事件区域小于350,最好计时器也不要马上执行。
4.4 使用锁确保线程安全
为了确保玩家与NPC交互对话时,无法再次重新开展新的对话,可以在展示对话界面之前,创建一个布尔变量"ChatState"来跟踪聊天。当玩家触发点击NPC或者靠近NPC时会触发对话机制,ChatState=True
说明正在聊天中,并打印信息,提示"你已经在对话中"。
此时,只有玩家离开NPC太远或者点击退出按钮这两种情况,会退出对话,此时应设置ChatState=False
。这时,玩家可以进行新的对话,重复展示对话UI和监测UI的逻辑。
当玩家退出时,结束对话并解锁对话状态:
不用锁的时候可以不删计时器,用了之后退出谈话必须删,还没搞懂为什么。
今天,我们介绍了如何制作NPC聊天系统。我们可以接近NPC,或者在一定范围内点击NPC与它交谈。我们可以单击退出或离开以结束聊天。我们还介绍了如何设置锁,以确保线程安全。当大量同类型的操作出现时,我们需要在这些机制中找到共点,并使用函数库进行封装。它是游戏开发中不可或缺的一部分。这里我们以NPC聊天系统为例,演示如何在游戏制作中使用封装。封装还可以在很多地方使用,比如在多个分支的文字冒险游戏中,或者在不同枪械的子弹以及伤害系统中。
五、物品使用
5.1 项目拆解
今天我们将介绍如何通过不同的方法实现使用物品的效果。我们可以点击物品栏中的物品来使用,可以单击UI来使用,也可以靠近物品自动使用(例如跑酷游戏中的金币),最终都会对它们进行整合,以传输给必要且唯一的执行结构。这种结构是所有游戏的组成部分,由此延伸出玩家操作层、系统命令层和执行层的基本概念。
物品使用过程分为两个阶段:玩家触发物品使用和使用物品后的效果,所以我们需要一个统一的入口来进行触发(封装火焰技能)。前两种方法很容易就能实现,因为编辑器中本身就内置了这些功能,只需要在物体编辑器和UI编辑器中进行设置;对于第三种方法,我们需要设计一个计时器检测周围环境,在有物品交互对象时立即使用物品。根据我们的需求,我们可以将项目分解为三个部分:
5.2 火焰效果实现
在函数库中封装火焰技能,并使用参数Point来确定火焰的位置。火焰效果有上升和爆炸两个阶段:
- 火焰上升:使用后一秒外观层显示火焰技能。为了让火焰技能飞向天空的效果更加逼真,我们希望这种上升效果的速度会逐渐降低(设置两个变量来存储火焰的速度和高度,每帧速度减去2)。
- 火焰爆炸:速度为0时,等待0.3秒,创建火焰炸裂效果。
设置了火焰爆炸高度之后,我为其添加得爆炸声没了,真是醉了。
5.2 简易使用
- 自定义火焰技能,在技能触发器设置:施法完成后,调用函数库函数"火焰技能"
- 自定义物品照明石,并为其添加主动技能:火焰技能。这样点击物品就会自动使用火焰技能。注意,应该设置指示器类型无、索敌目标无,只在点自动释放。
为玩家添加"火焰技能"按钮,并为其添加鼠标点击事件:
5.3 靠近后自动使用
创建帧计时器检测周围环境,使用变量itemGroup作为单位特定范围内的所有物品,当其中包含星星时,自动使用。为了保证每颗星星只使用一次,需要为物品添加锁,即used标签。
六、商店系统
6.1 项目拆解
在本节课中,我们将以多种形式的货币与商店进行互动,达成购买和兑换的目的。我们先使用货币购买物品,随后锻造获得钥匙,最后使用钥匙打开宝箱,总结起来就是:
- 用货币购买物品:使用货币购买三块铁锭、一份说明书和一个工具包。这里需要给商人添加物品来实现购买功能。
- 以物易物:当玩家拥有所有锻造材料时,点击铁匠得锻造工具,让铁匠锻造钥匙。
视觉上呈现为锻造成功,物品栏中获得一把钥匙。但在游戏背后,我们需要开启物品栏搜索功能,满足条件的情况下,删除相应数量的铁锭、设计图和工具包,创造一把钥匙。当材料不足时,给予提示。
- 使用新物品触发对应效果:玩家使用钥匙打开宝箱。
6.2 物编准备
在物体编辑器中,创建铁锭、设计图、工具箱、锻造工具、钥匙和宝箱这几个物品,并设置其商店属性,比如库存、购买所需资源等。接着创建商店老板、铁匠铺铁匠以及宝箱兑换铺三个单位,勾选"作为商店",表示可以出售物品。将铁锭、设计图、工具箱添加到商店中,铁匠铺添加锻造工具。玩家打开宝箱后会获得金币,所以我们需要在宝箱内写入获得金币的触发。
6.3 功能实现
6.3.1 锻造钥匙
下面实现锻造事件(玩家点击使用锻造工具,感觉过程可以简化):
物品堆叠数必须大于要减去的数量,设置堆叠数才有效,否则只能进行删除操作。
- 物品只有一个时,只能删除,设置堆叠数-1无效
- 物品有N个,设置堆叠数-N无效,只能直接删除
- 物品有N个,设置堆叠数-M且M<N,设置有效。
6.3.2 打开宝箱
必须是单位类型才能播放动画,所以创建的宝箱不应该是物品,而应该是建筑类型。开完宝箱可以保留在地图上,也可以删除它。如果要删除,需要播完动画再删除,也就是创建一个2s的计时器。
在游戏中,玩家与商店之间的互动不一定都是买卖行为。无论是武器锻造、物品合成,还是密室逃脱中通关钥匙的获取等操作,它们的实现逻辑都与商店交易的实现逻辑相同。
七、装备限制(自定义键值传递信息)
装备限制是RPG类型游戏中常见的机制,比如我们规定玩家只能携带一把武器、一件护甲,或者在FPS游戏中,玩家当前只能持有一把武器,切换另一把武器时,就需要放下当前持有的武器。
7.1 项目分解
在本节课中,我们为角色设置了两种武器和两种护甲,玩家角色最多可以持有一件武器以及一件护甲。武器的特效是随着玩家单位的移动,在玩家单位的手腕位置会产生一个拖尾效果;护甲一的特效则是周期性的,在玩家单位所在点出现寒冰刺;护甲二的特效是一个紫色的电球,围绕着玩家单位不断旋转。
当玩家装备物品时,角色身上将会出现对应的特效用于表示穿戴状态;当玩家手动丢弃物品时,则删除对应的特效。如果是玩家已经拥有某类型装备并再次拾取时,由于装备限制所以会自动触发系统丢弃。系统丢弃是静默的,不会通知玩家,不更新玩家状态,因此不会有特效或其他游戏效果被改变。所以整个项目可分为四部分:数据存储、装备拾取和丢弃、特效。
7.2 物编准备
首先在物编中定义好各个装备的模型、名字、图标等,然后使用自定义值功能为每个装备添加一个type
属性,用于标识装备的类型(weapon、armour);在物编器-魔法特效中,创建两种武器特效;在投射物中,创建两种护甲特效。最后并为武器装备添加projectType
属性,表示装备对应的特效。
闪电和充能两种武器特效(魔法特效):
寒冰刺和紫色电球两种护甲特效(投射物):
7.3 功能实现
7.3.1 装备拾取
- 当玩家单位拾取物品后,使用两个局部变量
unit
和item
分别缓存玩家单位和玩家获得的物品;使用局部变量type缓存该物品的类型 - 如果玩家没有此类型装备,则为其装备上,且为玩家单位添加type标签,值为type,表示装备了此类型物品。发送自定义事件,根据
item
的类型生成相应的特效。 - 如果玩家具有type标签,且值也为type,表示具有同类物品,将其丢弃,并将此物品打上
systemdiscard
标签,在后续处理中用于标识此物品是系统丢弃。
单位和物品上的所有属性都是全局变量,所以可以通过添加自定义键值的方式来为其添加自己需要处理的变量,通过发送和接收自定义键值可以完成全局信息的传递。
特效分敛:根据自定义事件传递的物品和type参数,分敛到不同类型的特效创建函数,执行对应的效果。
7.3.2 装备丢弃
- 当单位失去物品时,使用unit和item缓存失去物品的单位和被丢弃的物品。
- 根据item的自定义值systemdiscard判断丢弃类型,自动丢弃则清除标记,手动丢弃则删除玩家单位中的自定义值和对应的特效。
在拾取物品时通过发送自定义键值------unit的type信息给创建物品特效事件,后者根据接收的type信息创建对应的特效。物品手动丢弃时删除unit的type键值,物品特效也随之被删除。
7.3.3 显示特效
-
武器特效:给装备单位添加对应的魔法特效,并用秒计时器轮询装备是否被删除,若删除则移除特效。
-
护甲一特效:每秒检测一次,特效将在每秒计时器到时后立即生成,并在两秒后将其销毁。
-
护甲二特效:创建投射物特效,使用角度变量angle实现电球围绕单位旋转的效果,使用计时器每隔0.03秒更新电球位置
八、野怪刷新(数组字典、自定义键值简化存储)
野怪刷新机制有三个步骤:
- 设置野怪刷新区域,并存储刷怪数据,例如刷怪数量和类型
- 设计刷怪函数,根据刷怪数据执行刷怪
- 设定刷怪机制,即游戏初始化或者怪物被消灭10秒后刷怪(两个触发器)
8.1 数据初始化
放置四个区域作为刷怪点,并存入要刷敌人的数据,包括面向角度、数量和单位类型等。在本例中,我们应避免创建12个全局变量去储存刷题数据这种低效做法,而是给每个刷怪点创建自定义键值。
自定义键值是一种特殊的全局变量,由主索引和一个键组成(类似python里的字典)。在触发器中选择数据类->通用->键值,即可启用自定义键值,填入主索引和键就行。
新建触发器,创建游戏初始化事件,使用一个数组变量spawner
来存储四个刷怪区域,这样可以使用循环指令一次命令数组内所有刷怪点做动作。最后给各个区域设置自定义键值,包括敌人角度、数量和类型,完成数据初始化。
区域数组还能有字典数据,比我想象的高级啊。
8.2 自定义刷怪函数
上一步我们放置了区域,并利用自定义键值存储了刷怪数据。下面创建函数库函数,以刷怪区域作为参数,根据其刷怪数据执行刷怪命令。在动作中新建一个循环来创建单位,循环次数为刷怪数量。最后为此区域添加自定义键值unitGroup
(单位组),将刷出的怪物添加到此单位组中,便于后续判定。
8.3 刷怪机制
-
游戏初始化刷怪:遍历刷怪点数组,都执行一遍刷怪函数即可
-
怪物全灭10s后刷怪:将死亡的单位从该区域单位组中移除,并判断单位组数量是否为0,若是,则新建一个十秒的计时器,之后执行刷怪函数。
注意,这里不要使用变量来记录刷怪点的单位组,执行删除操作。因为这样删除的只是这个新的变量,而不是刷怪点的单位组属性。怪会越刷越多的。
在刷怪函数中,每个生成的怪都使用homeSpawner
键记录了其对应的刷怪点,刷怪点的unitGroup
中也记录了其生成的怪物。所以任意单位死亡时,我们获取该单位所属区域,并将该区域的单位组中的这个怪物删除。如果该区域没有怪物了,则10秒后再刷新。除了刷怪,你也可以创建补给刷新点、矿石刷新点等要素加入进你的游戏里,给地图更多乐趣与复玩性。
九、行为树(自定义事件传递信息)
游戏中的AI指的是通过模仿真实玩家的操作,使单位拥有智能化行为,比如人机对战模式、自由行走的NPC、系统托管的友军、对战中的敌兵以及玩家的召唤物等等。游戏AI的实现主要有两种方法:一种是行为树,一种是状态机,本节课将介绍行为树的方法。
行为树即是用树状结构分支使AI能根据条件执行不同行为(如走、跑、跳、攻击),让单位通过模拟真实玩家的行动而拥有"智能"行为,增强玩家的参与感。
9.1 游戏初始化
本节课需要实现的游戏AI的是两种小怪:近卫和治疗师。近卫只会在警戒区域Area AI内进行活动,敌人靠近治疗师时进行攻击,生命值过低时向治疗师靠拢。治疗师固定位于Area AI区域的中心位置,优先治疗自己,满血则治疗血量最低的近卫,无需治疗时则攻击敌人。
初始化前,需要创建一个全局变量(比如unitPlayer)来存储玩家角色,创建一个全局变量单位组来存储敌方单位。通过动作:"玩家-选择单位",让玩家选中自己的主控单位。为了更沉浸的游玩体验,使用"镜头 - 跟随单位"功能让玩家的视角能始终跟随在自己的英雄上。
9.2 行为树分敛
游戏开始0.1秒后,每隔1秒遍历一次敌方单位组,每次遍历所有单位,根据敌方单位类型(近卫和治疗者),对其应用对应的行为树。
判断完单位类型后,怎么通知他们去执行不同的AI行为呢?我们可以通过发送自定义事件和接收自定义事件来完成信息的传递。参数必须是变量,不支持数组。比如行为树是针对某单位执行,那么就需要将单位作为参数传递出去。
在Y3编辑器中,我们可以选择自定义事件->发送自定义事件->新建事件->增加参数,参数类型选择单位,然后选择获取事件中的单位,如下图所示:
9.3 AI实现
9.3.1 近卫AI
9.3.2 治疗师AI
新建一个单位类型局部变量unit
,存储通过"自定义事件:AI_Healer"发送过来的单位。使用函数MinHP unit
获取当前最需要治疗的友军。
- 如果
MinHP unit
返回空空(友军都是满血),判断自己身边是否有敌人,如果有,就使用飞弹攻击它;没有则在警戒区域的中心不动,以方便巡逻的守卫保护自己。 - 如果
MinHP unit
不为空,则停止当前的动作(这里并非使用'技能'来实现治疗,所以为了防止治疗者边攻击边治疗,需要先停止攻击动作)并对其进行治疗。为了让这种动作更具真实感,我们可以播放对应的动画动作,并为治疗效果添加一个特效。
这里治疗师攻击范围是多少,就设置unitPlayer距离多少才攻击,超了会拉脱。另外写逻辑又麻烦。
MinHP unit
函数:新建一个变量unit用于存储最低生命的单位,初始为空单位。设置两个局部变量,分别表示单位组中最低血量百分比,和当前遍历单位目前的血量百分比。
十、简单状态机
状态机指的是游戏角色拥有多种状态属性,比如行走、跳跃、战斗、休眠、死亡等,在多种状态下可以进行切换。举个例子,角色在行走时如果遇到敌人将会从行走状态切换至战斗状态。各类游戏中的Boss战,就可以使用状态机的方法来完成。
本节课我们制作的Boss有4种状态:休眠、战斗、变身、死亡。一开始Boss处于休眠,玩家进入警戒区域后,Boss进入战斗状态。当其血量低于50%时进入变身状态,其外形和技能都将发生变化。Boss在变身状态下获得攻击吸血,回血到一定数值后Boss重新切换回战斗状态。若玩家死亡,将在初始位置点进行复活继续进行战斗。
10.1 游戏初始化(休眠状态)
在Y3编辑器中点击事件管理,为游戏设置四个自定义事件:休眠、战斗、变身、死亡。当Boss需要切换某一状态时,通过发送和接收特定的自定义事件来通知Boss进行状态切换。
当游戏开始后,Boss首先处于休眠状态,我们向其发送自定义事件"休眠"。设置一个布尔值局部变量,作为切换Boss状态的开关(初始为False,True表示需要切换状态)。接下来我们利用计时器循环监测,根据玩家单位是否有进入Boss的警戒区域进行状态的判定。
为什么这里要加一个状态切换开关呢?直接根据在不在警戒区判断要不要战斗不行么
10.2 战斗状态
利用计时器循环监测玩家单位是否存活、是否在警戒区域中以及状态开关是否打开。如果玩家单位存活,那么Boss与之进行对战。设置一个实数类型的局部变量Proportion HP
表示Boss当前血量百分比,用于判断是否要切换到变身状态。
Proportion HP <0.5
: 设置bool = True
,并发送自定义事件"变身"Proportion HP >0.5
: 使用技能1(火焰技能,CD=50s)与玩家进行对战。技能CD先进行自我累计,达到50则重置为0,并释放技能。
如果玩家存活,那么继续判断玩家单位是否离开警戒区域。如果离开,那么Boss恢复至休眠状态,即打开状态开关使其等于True,并发送自定义事件"休眠"。最后判断状态开关,如果等于True,则删除计时器和Boss单位;否则不做任何动作。
火焰技能参数为技能释放点point,创建一个火焰预警的投射物用来提示玩家此处位置将会发生火焰爆炸。3秒过后,创建火焰爆炸的投射物,如果玩家在技能释放点附近,则对玩家造成真实伤害。
10.3变身状态
闪电技能:
10.4 死亡状态
Boss死亡:
玩家死亡:
十一、护送任务
本章介绍如何设计护送任务。护送任务中,玩家需要保护NPC角色或物体,免受伤害,直到终点。本案例中,当雷姆接近NPC时,任务信息UI就会弹出;如果我们拒绝任务,UI就会消失;如果我们再次进入该区域,任务信息UI将再次弹出。
接受任务后,在不同的任务阶段,UI会显示不同的信息。玩家可以点击按钮来隐藏或取消隐藏UI。当马车单位活着到达终点时,任务就完成了。如果马车单位死亡,玩家可以回到NPC并重新接受任务。
11.1 项目分解
根据我们的需求,我们可以将功能分为四个部分:
- 任务分配:需要与任务NPC进行互动,显示任务UI,并允许玩家选择是否接受任务。
- 任务执行:需要护送一辆车到终点,汽车需要能够自己向终点移动。当汽车被摧毁时,重置任务;当汽车到达特定区域时,刷出敌人
- 任务结束:当汽车到达终点时完成任务
- 任务提醒UI:在任务的每个阶段显示任务的详细信息,追踪任务进程
创建任务NPC和任务区域,并设计UI以展示任务信息。UI包含三个基本部分:任务描述、是和否两个选项。你也可以添加人物肖像、打勾和交叉的图片,使UI更漂亮。然后为这两个选项添加UI事件accept quest
和reject quest
。
11.2 任务开启
使用三个触发器来实现玩家接受或拒绝任务的逻辑。
NPC region event
:当玩家进入NPC区域时,弹出一个带有任务信息的UI。quest accepted
:如果玩家接受任务,我们将隐藏任务信息UI并发送任务开始自定义事件,告诉下一阶段触发器开始执行任务,并关闭NPC region event
触发器(搜索触发器->开启/禁用)。quest rejected
:如果玩家拒绝任务,隐藏任务信息UI,并保持触发NPC region event
。
11.3 任务执行
任务执行阶段可以分为三个阶段:起点至战斗点、战斗点战斗、战斗点至终点。首先在地图上绘制点和路径,当玩家接受任务时,即发送自定义事件Quest starts
,创建需要在起点护送的汽车,并将其存储在变量npc_Car
中。
任务开始后,向npc_Car
发布移动命令,使其沿着Path1移动。实时监控npc_Car
是否存活以及是否到达战斗点。
- 如果
npc_Car
死亡,任务失败,移除计时器并发送自定义事件QuestFail
。 - 如果到达战斗点,移除计时器并发送自定义事件
Quest Stage2 Starts
,并为此事件添加自定义参数PARAM1
,将其设为npcCar
。
npcCar
作为一个局部变量,只能在当前触发器中调用,不能在其他触发器中使用,但我们仍需要在接下来的触发器中用到它,因此我们需要用自定义事件传递参数。
我一开始设置车为建筑单位,结果无法移动。。。切换成生物单位也不行,只能重新创建一个生物单位的马车。
11.4 任务结束
本次护送任务设计成可以多次尝试的任务,所以任务失败时可以重复领取,即当收到自定义事件quest fail
时,再次打开触发器NPC region event
。
11.5 UI设计
任务提醒UI包括显示/隐藏切换按钮、背景图片、提醒标题和提醒信息,有四种状态:
-
默认状态:任务开始前(游戏初始化)和任务失败时,处于默认状态,可以显示诸如"没有任务正在进行"之类的信息。
-
stage1
:显示"护送汽车到指定目的地" -
stage2
:显示"打败所有的敌人,保护马车" -
stage3
:显示"这里不安全,快点到目的地去"
在任务的不同阶段,UI应该显示相应的信息,我们需要在相应的触发器中进行设置,例如在quest stage1的触发器中,UI应该处于stage1状态,小部件文本显示stage1的信息。
最后我们需要设置UI的切换按钮。创建一个布尔变量UI_State
来存储UI的状态,true表示UI是可见的,false表示隐藏,用于判定和控制UI的状态。
护送任务是一个基于目标的任务,他也可以是一个狩猎任务、一个逃跑任务,或者同时与NPC约会的任务,他们可能看起来不同,但它们都具有相同的底层结构,即从一种状态切换到另一种状态。就护送任务本身而言,汽车的行进路径可以延伸到MOBA游戏中,生成士兵的路径,通过添加友方和敌方AI,他可以从护送任务升级为进攻和防御战争。
十二、收集任务
游戏中的收集任务,重点在于如何将游戏内容抽象为数据,并实现计数逻辑。比如杀死一定数量的敌人、收集特定数量的物品等。本章案例中,雷姆在探险时遇到了石林遗迹,需要完成三个任务才会打开大门进入遗迹:
- 对小怪累计造成500点伤害(考验战斗力)。
- 收集五颗魔法石(击杀掉落,考验体能)。
- 完成前两个任务后任务3也就完成了,将获得额外奖励。
12.1 游戏初始化
我们设置三个接任务的区域,玩家进入到对应区域即会弹出相应的任务面板。每个任务都分为三个阶段,即未开始、执行中、任务完成,所以可以使用自定义键值state来表示任务状态,三个键0,1,2
分别对应三种状态。
游戏初始化后设置一个类型为单位的全局变量unit Player
存储玩家单位。为了防止玩家误选到其他单位,可以为玩家关闭鼠标点选、框选以及选中玩家单位。一开始隐藏三个任务面板,后续触发任务时再显示。
12.2 功能实现
- 任务一:伤害统计函数如下
- 设置触发事件"任意单位受到伤害事件",然后设置两个单位类型的局部变量,表示承受伤害的单位和施加伤害的单位。
- 设置一个整型局部变量
Int Damage
,存储本次伤害值;设置一个整型全局变量Damage
,存储累计伤害值。 - 判断施加伤害的单位是否是玩家单位,若为真,那么累计玩家单位对敌人造成的伤害值
任务一触发机制:
- 任务二:当玩家进入任务区域2且task2的state=0时,开启任务2,其state设为1,并显示任务2面板,提示文本设为0/5。设置一个整数变量number统计收集到的魔法石数量,然后利用计时器实时监测玩家物品栏里魔法石的数量:
- 任务3:前面触发过程一样,循环检测任务一和任务二的触发和完成情况。